Git 操作技巧

发表于: 2016-11-07分类于:Unix/Linux

从其他分支拉取指定文件

git checkout [branch] -- [file name]

git log

显示每次提交的内容差异

$ git log -p -2

-p 表示显示提交事内容差异,-2 表示最近两次

查看每次提交的简略统计信息

$ git log --stat

使用不同于默认格式的方式展示提交历史

$ git log --pretty=oneline

其中 oneline 可以是 short,full 和 fuller。还可以使用 format 来定制要显示的记录格式

$ git log --pretty=format:"%h - %an, %ar : %s"

其中 format 中可以提供下列参数:

选项 说明
%H 提交对象(commit)的完整哈希字串
%h 提交对象的简短哈希字串
%T 树对象(tree)的完整哈希字串
%t 树对象的简短哈希字串
%P 父对象(parent)的完整哈希字串
%p 父对象的简短哈希字串
%an 作者(author)的名字
%ae 作者的电子邮件地址
%ad 作者修订日期(可以用 –date= 选项定制格式)
%ar 作者修订日期,按多久以前的方式显示
%cn 提交者(committer)的名字
%ce 提交者的电子邮件地址
%cd 提交日期
%cr 提交日期,按多久以前的方式显示
%s 提交说明

git log 的常用选项

选项 说明
-p 按补丁格式显示每个更新之间的差异。
–stat 显示每次更新的文件修改统计信息。
–shortstat 只显示 –stat 中最后的行数修改添加移除统计。
–name-only 仅在提交信息后显示已修改的文件清单。
–name-status 显示新增、修改、删除的文件清单。
–abbrev-commit 仅显示 SHA-1 的前几个字符,而非所有的 40 个字符。
–relative-date 使用较短的相对时间显示(比如,”2 weeks ago”)。
–graph 显示 ASCII 图形表示的分支合并历史。
–pretty 使用其他格式显示历史提交信息。可用的选项包括 oneline,short,full,fuller 和 format(后跟指定格式)。

限制输出长度

按照时间作限制的选项

$ git log --since=2.weeks

显示最近两周的提交

根据搜索条件列出符合的提交

使用 -S 选项过滤出删除或者添加了 function_name 的那些提交

$ git log -Sfunction_name

还可以给出若干搜索条件,列出符合的提交。 用 –author 选项显示指定作者的提交,用 –grep 选项搜索提交说明中的关键字。 (请注意,如果要得到同时满足这两个选项搜索条件的提交,就必须用 –all-match 选项。否则,满足任意一个条件的提交都会被匹配出来)

限制 git log 输出的选项:

选项 说明
-(n) 仅显示最近的 n 条提交
–since, –after 仅显示指定时间之后的提交。
–until, –before 仅显示指定时间之前的提交。
–author 仅显示指定作者相关的提交。
–committer 仅显示指定提交者相关的提交。
–grep 仅显示含指定关键字的提交
-S 仅显示添加或移除了某个关键字的提交

回滚单个文件至指定版本

使用 git log filename 查看该文件的修改历史:

$ git log index.js

得到需要的版本的 hash 使用 git reset hash filename 来将文件回退至指定版本

$ git reset b4c4bafc2e32c6f165b710957f1bc8d75c6f83f2 index.js

这个时候会出现:

On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

           modified:   index.js

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

           modified:   index.js

这时该文件已经回到了特定的版本,只是其内容被修改了,被修改为了回滚之前的内容,因此只需要使用 git checkout filename 撤销修改就好了。

$ git checkout index.js

至此,就已经完成了回滚。

标签

列出所有标签

$ git tag

也可以匹配特定的标签:

$ git tag -l 'v1.1.*'

创建标签

有两种形式的标签:

  • 轻量标签
  • 附注标签

    附注标签

$ git tag -a v2.0.0 -m "version 2.0.0"

这里的 -m 同 commit 中一样,是为此标签添加说明信息,若没有添加 -m 选项的时候会弹出编辑器要求你输入说明信息。

完成之后就可以使用 git show v2.0.0 来查看与标签对应的提交信息了。

轻量标签

轻量标签不需要添加标签信息,可以使用下面的命令来打一个轻量标签:

$ git tag v2.1

后期打标签

可以对过去的某次提交打上标签,具体做法如下:

$ git tag -a v1.0.0 9fd67es

最后面跟的是某次提交的 hash 校验和。

共享标签

默认情况下,git push 命令不会将传送标签信息,如果要将标签信息推送至远程仓库,可以使用下面操作:

$ git push origin v1.0.0

如果有很多个标签需要推送,那么可以使用下面命令一次性全部推送至远程仓库:

$ git push origin --tags

回到特定标签的版本

如果希望工作目录回到某个特定标签的状态,可以使用下面操作:

$ git checkout -b version2 v2.0.0

这里的 -b 表示新建一个分支,而 version2 为新建的分支的名字,完成操作后分支 version2 中的内容就和 v2.0.0 标签所在位置一致了。这也就是用 v2.0.0 所在内容新建了一个分支。

列出某个 tag 的具体信息

$ git show v1.1

删除 tag

删除本地 tag

$ git tag -d <tag-name>

删除远程分支

本地 tag 删除了以后,可以直接 git push origin --tag 来删除 tag

或者

$ git push origin --delete tag <tag-name>

git 分支操作

git 中每次 commit 都会生成一个提交对象,每个提交对象会包含一个指向前一次提交的指针,也就是父指针(当然了初次提交是不包含父指针的)。最终就形成了一个链,这个链有时候会出现分叉,而分支就是代表这样一个分叉。

查看分支

希望查看当前已经存在的分支的信息,可以使用下面指令:

$ git branch

如果希望得到更加详细的信息,可以使用:

$ git branch -v

--merged--no-merged 这两个选项可以过滤列表,只显示已经合并至当前分支的分支,或者尚未合并到当前分支的分支。

创建分支

创建分支也就是新建一个指针,指向当前某个提交对象,最简单的可以使用下面操作来创建一个指向当前提交对象的分支。

$ git branch test

这样新建了一个分支但是还没有切换到这个分支上,可以进行切换分支。

分支的切换

$ git checkout test

为了更加简便,可以使用下面命令,一步完成新建与切换操作:

$ git checkout -b test

使用 -b 选项表示新建一个分支并切换至该分支上。如果在切换分支的时候当前所在分支存在没有提交的内容,切换将不会顺利进行,这需要一些其他手段。一个好的建议是在切换之前先提交在当前所在分支上的任何改动。

当完了切换之后当前工作目录中的内容就会被替换为该分支最后一次提交的状态了。

远程分支

本地有一些分支类似于 origin/master ,origin/dev 这些分支都指向远程仓库中对应的分支,在运行 git fetch 的时候会得到更新,当别人向远程仓库中 master 分支推送了更新之后,本地的 origin/master 并不会更改,需要运行 git fetch 才能获取更新。

不能直接在 origin/dev 这样的分支上直接更改,如果你想要更改,可以使用 git checkout -b dev origin/dev 这样在本地新建一个分支。说到底 origin/dev 这样的分支只是存在在本地的一个远程仓库的书签。

如果希望本地分支和远程分支的名字一样,那么 git checkout -b dev origin/dev 也可以是使用 git checkout --track origin/dev 来代替。

在本地建立一个分支对应于远程分支,也就叫做跟踪分支(tracking branch)

远程分支就是对远程仓库的引用,包括分支、标签等等。可以使用下面指令获得关于远程分支的更多信息。

$ git ls-remote

// 或者

$ git remote show <remote>

远程分支以 (remote)/(branch) 的形式命名。当你使用 git branch -v 的时候它们不会显示在列表中,因为他们不是本地分支。

当使用 git fetch origin 命令后会将远程仓库中的全部信息拉回到本地,如果该远程仓库中存在两个分支,分别是 dev 和 master ,那么这个时候就可以通过下面命令来查看 origin/dev 分支:

$ git checkout -b dev origin/dev

这个命令的意义是新建一个名为 dev 的本地分支,其中以 origin/dev 为镜像。

从远程仓库拉取数据

通常使用 git fetch <remote> 来拉取某个远程仓库中本地仓库不存在的信息,比如执行 git fetch origin 这个操作的首先会查找 origin 的地址,然后从中拉取本地不存在的数据,最后更新本地的 origin/* 分支的最新位置。比如别人在 origin 的 master 分支上又提交了几次。这个时候你本地的 origin/master 分支就落后于远程仓库 origin 中的 master 分支了。把数据 fetch 下来之后紧接着做的就是更新本地的 origin/* 分支的位置,或者创建新的 origin/* 分支(如果在你上次 fetch 之后有人新建了分支)。

如果执行了下面命令,切 origin 只有一个 master 分支

$ git fetch origin

拉取到数据之后只会在本地有一个不可以修改的 origin/master 分支的指针,可以在该位置新建一个分支来进行开发:

$ git checkout -b new_branch origin/master

使用远程分支新建一个本地分支的操作很常见,所以有一个快捷的选项:

$ git checkout --track origin/gh-pages

以上这个命令会在从 origin/gh-pages 上在本地新建一个名为 gh-pages 的分支。

向远程仓库推送

在本地进行开发的时候并不会影响远程仓库,如果要分享自己的修改或添加,就需要推送向远程仓库。

如果希望将本地的 dev 分支推送到 origin 仓库中,那么可以向下面这样做:

$ git push origin dev

这个命令的意思是说使用本地的 dev 分支作为远程仓库 origin 的 dev 分支。如果希望远程仓库的分支名不叫 dev 那么也可以使用下面的命令:

$ git push origin dev:develop

这样在远程仓库中就会有一个 develop 的分支。下次其他人 fetch 该仓库 (假设命令为origin) 的时候,就会在他们本地出现一个远程分支 origin/develop 。

跟踪分支

当从一个远程分支上检出一个本地分支后,这个本地分支也远程分支之间就有了联系,如果在一个分支上使用 git pull ,那么就会自动找到检出该本地分支的远程分支,并从中拉取数据,并合并。

克隆一个分支的时候,会自动地创建一个跟踪 origin/master 的 master 分支。

还可以给本地创建的分支与一个远程分支进行关联。使用以下操作:

$ git branch -u origin/hotfix

设置了跟踪分支以后,还可以使用快捷方式 @{u} 或者 @{upstream} 来引用该远程分支。

如果希望查看本地分支对应的远程分支可以使用下面命令:

$ git branch -vv

删除远程分支

如果希望删除远程分支,那么可以执行下面命令:

$ git push origin --delete hotfix

也可以如下操作:

git push origin :serverfix

git push [远程名] [本地分支]:[远程分支]

上面的命令可以理解为,从本地抽取空白然后将其变为远程分支,这样就把远程分支删除了。

暂存(stash)

正在工作的时候,需要切换分支,但是手头的事情有不能立刻提交,这个时候可以首先保存下当前的状态,之后在某个合适的时候在恢复

执行存储

git stash

查看存储情况:

git stash list

恢复存储

git stash apply

这个命令会帮助你恢复到最近的一次存储

如果有多个存储,那么你需要指明:

git stash apply stash@2

删除存储

使用 apply 恢复后,存储还在,可以使用 drop 来删除

git stash drop stash stash@{0}

快速恢复

你可以使用 git stash pop 来重新应用存储,同时立刻将其从堆栈中删除

等同于:

git stash apply
git stash drop stash@0

从存储中创建分支

git stash branch newbranch

重写历史

改变最近一次提交

git commit --amend

这个命令可以允许你添加更多文件,并重写提交信息

修改多个提交

例如下面指令会修改最近三次的提交说明,随后 git 会打开一个编辑器,你需要根据要求来作出一些修改,在打开的编辑器中,注释部分注明了操作方法,将前面的 pick 修改为其他选项即可,在保存文件之后,会提示你如何进行下一步。

git rebase -i HEAD~3

修改提交说明,修改提交次序,合并提交

拆分提交

将希望拆分的那个提交,设置为 edit ,然后会回到那一次提交,然后使用 git reset HEAD^ 回到父提交,之后再进行增量提交,这就可以将其拆分多次提交了。

.gitignore 规则

.gitignore 的编写规则如下:

  • A blank line matches no files, so it can serve as a separator for readability.
  • A line starting with # serves as a comment. Put a backslash (\) in front of the first hash for patterns that begin with a hash.
  • Trailing spaces are ignored unless they are quoted with backslash (\).
  • An optional prefix ! which negates the pattern; any matching file excluded by a previous pattern will become included again. It is not possible to re-include a file if a parent directory of that file is excluded. Git doesn’t list excluded directories for performance reasons, so any patterns on contained files have no effect, no matter where they are defined. Put a backslash (\) in front of the first ! for patterns that begin with a literal !, for example, \!important!.txt.
  • If the pattern ends with a slash, it is removed for the purpose of the following description, but it would only find a match with a directory. In other words, foo/ will match a directory foo and paths underneath it, but will not match a regular file or a symbolic link foo (this is consistent with the way how pathspec works in general in Git).
  • If the pattern does not contain a slash /, Git treats it as a shell glob pattern and checks for a match against the pathname relative to the location of the .gitignore file (relative to the toplevel of the work tree if not from a .gitignore file).
  • Otherwise, Git treats the pattern as a shell glob suitable for consumption by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will not match a / in the pathname. For example, Documentation/*.html matches Documentation/git.html but not Documentation/ppc/ppc.html or tools/perf/Documentation/perf.html.
  • A leading slash matches the beginning of the pathname. For example, /*.c matches cat-file.c but not mozilla-sha1/sha1.c.
  • Two consecutive asterisks (**) in patterns matched against full pathname may have special meaning:
  • A leading ** followed by a slash means match in all directories. For example, **/foo matches file or directory foo anywhere, the same as pattern foo. **/foo/bar matches file or directory bar anywhere that is directly under directory foo.
  • A trailing /** matches everything inside. For example, abc/** matches all files inside directory abc, relative to the location of the .gitignore file, with infinite depth.
  • A slash followed by two consecutive asterisks then a slash matches zero or more directories. For example, a/**/b matches a/b, a/x/b, a/x/y/b and so on.
  • Other consecutive asterisks are considered invalid.

submodule

初始化 submodule

一个项目使用了 submodule 在 clone 下来后,需要执行 git submodule init 才能将 submodule 也 clone 下来。

安装 submodule

$ git submodule add <repository> <path>

克隆含有子模块的项目

执行了 git clone 之后并不会下载子模块,需要再次执行 git submodule init

# 克隆该项目
$ git clone https://github.com/user/repo

# 加载子模块
$ git submodule init

或者使用 --recursive 选项来进行 clone:

$ git clone --recursiv https://github.com/user/repo

更新 submodule

$ git submodule update --remote

push submodule

$ cd sub  # 进入子模块
$ git push  # 推送代码