Git基本命令
Table of Contents
1 操纵git的配置文件
有三个配置文件,依据优先级从高到低排序,
- 打开当前项目的配置文件,
$ git config -e
- 打开全局配置文件,
$ git config -e --global
- 打开系统配置文件,
$ git config -e --system
以全局配置文件为例,介绍配置中属性的查询和修改。
- 查询,
$ git config --global section.attribute
- 增加及修改,
$ git config --global section.attribute value
- 删除,
$ git config --unset --global section.attribute
如果全局配置文件包含以下内容,
[x “y”] z = something
相应分别命令是,
$ git config --global x.y.z $ git config --global x.y.z value
2 差异比较
- 工作区与暂存区,
$ git diff
- 工作区与HEAD(当前工作分支),
$ git diff HEAD
- 暂存区与HEAD(当前工作分支),
$ git diff --staged
- 不同版本,
$ git diff <commit1> <commit2> <path>
3 查看文件修改状态
$ git status -s MM welcome.txt
输出中,第一个M表示版本库和暂存区的文件相比较,有改动;第二个M表示工作区和暂存区的文件相比较,有改动。
4 查看提交日志
$ git log --pretty=oneline f4ad1bdeb9d813a25cb87149dcc03b2a6509e4b7 For browse git directory 20dde90b990a801ed9b30da237a98c98d25ca478 Append a nice line 6575a00938b9f37d047b090d295de3c69e9f8e8a initialize git-exercise $ git log --pretty=raw --graph f4ad \* commit f4ad1bdeb9d813a25cb87149dcc03b2a6509e4b7 | tree 0a58c2b5df45b779fba20395ecd9ee47215dae7e | parent 20dde90b990a801ed9b30da237a98c98d25ca478 | author tech-note <Email Addr> 1361255086 +0800 | committer tech-note <Email Addr> 1361255086 +0800 | | For browse git directory | \* commit 20dde90b990a801ed9b30da237a98c98d25ca478 | tree e5c730d67c1fa9b37927a50341e22314e7c240fb | parent 6575a00938b9f37d047b090d295de3c69e9f8e8a | author tech-note <Email Addr> 1361250374 +0800 | committer tech-note <Email Addr> 1361250374 +0800 | | Append a nice line | \* commit 6575a00938b9f37d047b090d295de3c69e9f8e8a tree d53e0ada312de31f102c8adea3a60caf7df72ff0 author tech-note <Email Addr> 1361249827 +0800 committer tech-note <Email Addr> 1361249827 +0800 initialize git-exercise
其中,命令行参数
- –pretty=raw: 使得commit ID(本次提交), tree ID(本次提交对应的目录树), parent ID(本次提交的上一次提交)都被打印出来
- –graph: 显示跟踪链(父子关系)
5 以对象ID为参数查看对象信息
- commit 对象
$ git cat-file -p f4ad tree 0a58c2b5df45b779fba20395ecd9ee47215dae7e parent 20dde90b990a801ed9b30da237a98c98d25ca478 author tech-note <Email Addr> 1361255086 +0800 committer tech-note <Email Addr> 1361255086 +0800
- tree 对象
$ git cat-file -p 0a58 040000 tree 6e2abf3eb71d7653b25989abb549b0ee18974e0e a 100644 blob 41ea8a4f322ac2a4daafd21b14eaced6fbac7ee7 welcome.txt
6 浏览目录树
6.1 ls-tree
- 浏览版本库的目录树
$ git ls-tree -l -rt HEAD 040000 tree 6e2abf3eb71d7653b25989abb549b0ee18974e0e - a 040000 tree c8e1fc145a80521374ef3909c05155568fca0ae1 - a/b 040000 tree 003e4f14fee402a23ab7586016dad1bbc45e67a9 - a/b/c 100644 blob 25735f595470e0e6894159694a4238a3ee8a3df0 7 a/b/c/hello.txt 100644 blob 41ea8a4f322ac2a4daafd21b14eaced6fbac7ee7 25 welcome.txt
- 浏览暂存区的目录树
$ git write-tree 0a58c2b5df45b779fba20395ecd9ee47215dae7e $ git ls-tree -l -rt 0a58 040000 tree 6e2abf3eb71d7653b25989abb549b0ee18974e0e - a 040000 tree c8e1fc145a80521374ef3909c05155568fca0ae1 - a/b 040000 tree 003e4f14fee402a23ab7586016dad1bbc45e67a9 - a/b/c 100644 blob 25735f595470e0e6894159694a4238a3ee8a3df0 7 a/b/c/hello.txt 100644 blob 41ea8a4f322ac2a4daafd21b14eaced6fbac7ee7 25 welcome.txt
6.2 ls-files
- 浏览暂存区的目录树
$ mkdir -p a/b/c $ echo "Where do I hide" > ./a/b/c/hide.txt $ git add . $ git ls-files a/b/c/hide.txt hack-1.txt welcome.txt who.txt
- 浏览工作区的目录树
$ git ls-tree HEAD 100644 blob dc71c5dca28bb97be915d47ae3549456ef5fdd75 welcome.txt
7 查看文件信息
$ git cat-file -p HEAD:welcome.txt hello. Nice to meet you.
8 add
工作区向暂存区增加修改的命令。
git add -u
, 将本地有变动(包括修改、删除)的文件都标记到暂存区。git add -i
, 进入交互界面,然后输入a,就可以有选择地添加工作区中未被跟踪的文件。
#+beginsrc git $ git add -i staged unstaged path
8.0.1 Commands *
1: [s]tatus 2: [u]pdate 3: [r]evert 4: [a]dd untracked 5: [p]atch 6: [d]iff 7: [q]uit 8: [h]elp What now> #+endsrc
9 文件忽略
对工作区某个目录或文件设置了忽略后,使用git status -s时,被忽略的文件即使存在也不会显示为未跟踪状态,使用git add -A和git add .都无法把被忽略的文件或目录添加到暂存区。
文件忽略功能是通过文件.gitignore实现的,把要忽略的文件及目录记录在该文件中,该文件的语法见下。
- 空行或以#开始的行会被忽略
- 支持通配符
- 以/开头的,表示要忽略的文件就在此目录下,而非子目录下的文件
- 以/结尾的,表示要忽略的是整个目录,同名文件不忽略,否则同名的文件和目录都忽略
- 以!开头的,表示不忽略
.gitignore的作用范围是其所处的目录及子目录。
可以把该文件添加到版本库,如果不希望将.gitignore添加到版本库中,可以在该文件中忽略自己。
注意,忽略只对未跟踪的文件有效,若一个文件已被添加到暂存区了,那么再在.gitignore中忽略也无效了。
10 reset
10.1 重置
git reset [-q] [<commit>] [--] <path>
不会改变引用,也不会改变工作区,对于暂存区,会用指定提交状态(<commit>)下的文件(<path>)替换掉暂存区中的相应文件。<commit>可省略,默认为HEAD。
- 撤销暂存区中未提交的修改
$ echo "cancel modification staged but have not committed" >> welcome.txt $ git add welcome.txt warning: LF will be replaced by CRLF in welcome.txt. The file will have its original line endings in your working directory. lhou@NAN-LHOU ~/git-exercise (master) $ git diff --staged diff --git a/welcome.txt b/welcome.txt index 41ea8a4..3dafc72 100644 --- a/welcome.txt +++ b/welcome.txt @@ -1,2 +1,3 @@ hello. Nice to meet you. +cancel modification staged but have not committed $ git reset HEAD welcome.txt Unstaged changes after reset: M welcome.txt $ git diff --staged
- 撤销暂存区中未提交的修改
git reset [--soft | --mixed | --hard] [-q] [<commit>]
肯定会重置引用
git reset --soft <commit>
只改变引用,不改变暂存区和工作区git reset --mixed <commit>
改变引用,改变暂存区,暂存区的内容将会和引用指向的目录树一致。–mixed 可省略git reset --hard <commit>
改变引用,改变暂存区,改变工作区,工作区中的内容将会和暂存区的一致,也和引用指向得分目录树一致
10.2 恢复
- 重置前
$ git log --graph --oneline * [#C] 3ef2a2d add a file for undo after git reset * f4ad1bd For browse git directory * 20dde90 Append a nice line * 6575a00 initialize git-exercise $ cat .git/refs/heads/master 3ef2a2dd8f7d416b9d77ffe8fea653d22a723a26
- 重置
$ git reset --hard 20dde90 HEAD is now at 20dde90 Append a nice line
- 恢复后
$ git log --graph --oneline * 20dde90 Append a nice line * 6575a00 initialize git-exercise $ cat .git/refs/heads/master 20dde90b990a801ed9b30da237a98c98d25ca478 $ ls welcome.txt
查看对master分支的所有变更(包括重置)
$ git reflog show master 20dde90 master@{0}: reset: moving to 20dde90 3ef2a2d master@{1}: commit: add a file for undo after git reset f4ad1bd master@{2}: commit: For browse git directory 20dde90 master@{3}: commit: Append a nice line 6575a00 master@{4}: commit (initial): initialize git-exercise
<refname>@{<n>} 表示分支<refname>第<n>次改变时情况。
第一行结果,表示重置之后,master分支指向的commit ID为20dde90,等同于,第四行结果(commit: Append a nice line之后的master分支指向)的commit ID。
- 恢复重置前
$ git reset --hard master@{1} HEAD is now at 3ef2a2d add a file for undo after git reset
- 恢复后
$ git log --graph --oneline * 3ef2a2d add a file for undo after git reset * f4ad1bd For browse git directory * 20dde90 Append a nice line * 6575a00 initialize git-exercise $ cat .git/refs/heads/master 3ef2a2dd8f7d416b9d77ffe8fea653d22a723a26 $ ls a newcommit.txt welcome.txt
可以发现以上输出与重置前完全一致。
$ git reflog show master 3ef2a2d master@{0}: reset: moving to master@{1} 20dde90 master@{1}: reset: moving to 20dde90 3ef2a2d master@{2}: commit: add a file for undo after git reset f4ad1bd master@{3}: commit: For browse git directory 20dde90 master@{4}: commit: Append a nice line 6575a00 master@{5}: commit (initial): initialize git-exercise
通过上述内容,仍然可以看到两次重置,并且第二次重置是对第一次重置的恢复。
11 checkout
11.1 检出
git checkout [-q] [<commit>] [--] <path>
- 参数 <commit> 被省略时,暂存区的文件覆盖工作区的文件
- 参数 <commit> 存在时, 用指定提交(<commit>)中的文件覆盖暂存区和工作区的文件
11.2 切换分支
git checkout branch
更新HEAD以指向branch分支,并用branch分支指向的树更新暂存区和工作区 分离头指针:HEAD头指针指向的是一个具体的commit ID,而不是一个引用(分支)
11.3 拉分支
git checkout [-m] [[--b | --orphan] <new_branch>] [<start_point>]
12 stash
12.1 命令简介
git stash [save [-k|--keep-index] [<message>]]
保存当前的工作进度,会分别对暂存区和工作区的状态进行保存,并重置工作区和暂存区。
参数–keep-index,保存进度后不会重置暂存区。
git stash list
显示保存的进度列表。
git stash pop [--index] [<stash>]
恢复工作进度<stash>,若未提供参数<stash>,则默认恢复最新的工作进度。恢复之后,将已恢复的工作进度从进度列表中删除。
参数–index,除了恢复工作区之外,还会恢复暂存区。
git stash apply [--index] [<stash>]
除了不删除恢复的进度外,等同于git stash pop。
git stash drop [<stash>]
删除一个存储的进度,默认为删除最新的进度。
git stash clear
删除存储的所有进度。
12.2 实例
12.2.1 只为工作区状态保存进度
- 工作区变更前
$ git init Initialized empty Git repository in /cygdrive/d/git-exercise/.git/ $ echo "Hello" > welcome.txt $ git add . $ git commit -m "welcome.txt: Hello" [master (root-commit) 1362aed] welcome.txt: Hello 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 welcome.txt $ git log commit 1362aeddba817069ad7116ec3242df84d08dacfb Author: tech-note <Email Addr> Date: Sat Feb 23 14:30:28 2013 +0800 welcome.txt: Hello
- 变更工作区
$ echo "Nice to meet you" >> welcome.txt
- 保存进度
$ git stash save "Nice to meet you" Saved working directory and index state On master: Nice to meet you HEAD is now at 1362aed welcome.txt: Hello
- 查看保存的进度
$ git stash list stash@{0}: On master: Nice to meet you $ git reflog show refs/stash 927dbfe refs/stash@{0}: On master: Nice to meet you
- 分析进度保存的实质
$ git log commit 1362aeddba817069ad7116ec3242df84d08dacfb Author: tech-note <Email Addr> Date: Sat Feb 23 14:30:28 2013 +0800 welcome.txt: Hello $ git diff HEAD $ git diff $ cat welcome.txt Hello
当前的引用没有发生改变,最新的commit ID认为1362ae。工作区被重置,也即,welcome.txt的内容与工作区变更之前相比,未发生改变。
在保存进度之前,在welcome.txt中,新增的内容Nice to meet you到那里了呢?
$ git log --graph --pretty=raw refs/stash \* commit 927dbfe8708087d5a11e3a874d8368ec0994064e |\ tree 26675b461c4e320dc98efb1ea8f2c33ddf4aa117 | | parent 1362aeddba817069ad7116ec3242df84d08dacfb | | parent ed767841071ff35b079320f5a77a20f8c51db48b | | author tech-note <Email Addr> 1361601464 +0800 | | committer tech-note <Email Addr> 1361601464 +0800 | | | | On master: Nice to meet you | | | * commit ed767841071ff35b079320f5a77a20f8c51db48b |/ tree 6222d0694ffcab4de64f6a43d8d480afdecb4d35 | parent 1362aeddba817069ad7116ec3242df84d08dacfb | author tech-note <Email Addr> 1361601464 +0800 | committer tech-note <Email Addr> 1361601464 +0800 | | index on master: 1362aed welcome.txt: Hello | \* commit 1362aeddba817069ad7116ec3242df84d08dacfb tree 6222d0694ffcab4de64f6a43d8d480afdecb4d35 author tech-note <Email Addr> 1361601028 +0800 committer tech-note <Email Addr> 1361601028 +0800 welcome.txt: Hello Administrator@2013-0106-1533 /cygdrive/d/git-exercise $ git cat-file -p 26675b 100644 blob dc71c5dca28bb97be915d47ae3549456ef5fdd75 welcome.txt Administrator@2013-0106-1533 /cygdrive/d/git-exercise $ git cat-file -p dc71c5 Hello Nice to meet you
可见,更新后的welcome.txt被保存在了tree ID为26675b下面的blob对象中,该blob对象的ID为dc71cd。注意到,commit ID为ed7678的提交同commit ID为1362ae的提交一样,都指向了tree ID为6222d0的树,继续查看该树,
$ git cat-file -p 6222d0 100644 blob e965047ad7c57865823c7d992b1d046ea66edf78 welcome.txt $ git cat-file -p e96504 Hello
- 修改工作区后,尝试使用git checkout恢复
$ echo "Bye-Bye" >> welcome.txt $ git stash apply error: Your local changes to the following files would be overwritten by merge: welcome.txt Please, commit your changes or stash them before you can merge. Aborting $ cat welcome.txt Hello Bye-Bye
恢复失败!既然使用git stash保存工作进度时,其实就是创建了一个新的提交(如上面的commit ID: 927dbf),那么可不可以使用该提交恢复呢?
$ git checkout 927dbf welcome.txt $ cat welcome.txt Hello Nice to meet you $ git diff $ git diff HEAD diff --git a/welcome.txt b/welcome.txt index e965047..dc71c5d 100644 --- a/welcome.txt +++ b/welcome.txt @@ -1 +1,2 @@ Hello +Nice to meet you
可见,工作区被恢复了,同时也将工作区的改变更新到暂存区了,但版本库没有被更新,这与git checkout [<commit>] – path命令的作用是吻合的。
- 修改工作区,并将新的修改提交后,尝试恢复。
- 恢复到最初的状态。
$ git reset --hard 1362ae HEAD is now at 1362aed welcome.txt: Hello $ cat welcome.txt Hello
- 再次开始恢复之旅
$ echo "Bye-Bye" >> welcome.txt $ git stash apply error: Your local changes to the following files would be overwritten by merge: welcome.txt Please, commit your changes or stash them before you can merge. Aborting $ git add welcome.txt $ git stash apply Auto-merging welcome.txt CONFLICT (content): Merge conflict in welcome.txt $ git commit -m "Bye-Bye" U welcome.txt error: 'commit' is not possible because you have unmerged files. hint: Fix them up in the work tree, hint: and then use 'git add/rm <file>' as hint: appropriate to mark resolution and make a commit, hint: or use 'git commit -a'. fatal: Exiting because of an unresolved conflict.
可见,哪怕是在把进度保存后的最新提交了之后,也还是不能使用git stash apply|pop恢复进度,因为这里引入了冲突。
- 恢复到最初的状态。
12.2.2 同时为工作区和暂存区状态保存进度
- 修改工作区和暂存区
$ echo "Bye, Bye" >> welcome.txt $ echo "hello, hacker" >> hack-1.txt $ git add hack-1.txt $ cat welcome.txt Hello Nice to meet you Bye, Bye $ cat hack-1.txt hello, hacker $ cat hack-1.txt hello, hacker
- 保存进度
$ git stash save "WIP:append a line in welcome.txt, index:add a new file hack-1.txt" Saved working directory and index state On master: WIP:append a line in welcome.txt, index:add a new file hack-1.txt HEAD is now at 0b9df67 Nice to meet you
- 工作区、暂存区的变化
$ ls welcome.txt $ cat welcome.txt Hello Nice to meet you
可见,工作区被重置。
$ git write-tree 26675b461c4e320dc98efb1ea8f2c33ddf4aa117 $ git ls-tree 26675b 100644 blob dc71c5dca28bb97be915d47ae3549456ef5fdd75 welcome.txt
可见,暂存区也被重置。
- 寻找刚才保存的进度
$ git log --graph --pretty=raw stash \* commit 1a2d7f398a33e3fed3f96c1bbea5d78913b8a1c2 |\ tree 6ef577094edce68c6a558587994b00eca8a018ee | | parent 0b9df67988a5cb0d0f20a5d510b0343559ebcf07 | | parent e616f66136960582ac783864a350758865ea7ae3 | | author tech-note <Email Addr> 1361706859 +0800 | | committer tech-note <Email Addr> 1361706859 +0800 | | | | On master: WIP:append a line in welcome.txt, index:add a new file hack-1.txt | | | * commit e616f66136960582ac783864a350758865ea7ae3 |/ tree bbf4b46eea10a3ddcd628f42a638d49878b11c5e | parent 0b9df67988a5cb0d0f20a5d510b0343559ebcf07 | author tech-note <Email Addr> 1361706858 +0800 | committer tech-note <Email Addr> 1361706858 +0800 | | index on master: 0b9df67 Nice to meet you | \* commit 0b9df67988a5cb0d0f20a5d510b0343559ebcf07 | tree 26675b461c4e320dc98efb1ea8f2c33ddf4aa117 | parent 1362aeddba817069ad7116ec3242df84d08dacfb | author tech-note <Email Addr> 1361679000 +0800 | committer tech-note <Email Addr> 1361679000 +0800 | | Nice to meet you | \* commit 1362aeddba817069ad7116ec3242df84d08dacfb tree 6222d0694ffcab4de64f6a43d8d480afdecb4d35 author tech-note <Email Addr> 1361601028 +0800 committer tech-note <Email Addr> 1361601028 +0800 welcome.txt: Hello
上面输出中的index on master:表示该提交代表的是暂存区, On master:表示该提交代表的是工作区。
$ git cat-file -p bbf4b4 100644 blob 1d6d547beac3b9fe58ca216ee3cbe3548937efe3 hack-1.txt 100644 blob dc71c5dca28bb97be915d47ae3549456ef5fdd75 welcome.txt $ git cat-file -p 1d6d54 hello, hacker $ git cat-file -p dc71c5 Hello Nice to meet you $ git cat-file -p 26675b 100644 blob dc71c5dca28bb97be915d47ae3549456ef5fdd75 welcome.txt
可见,tree ID为bbf4b4的树下有两个blob对象,其中,commit ID为1d6d54的blob对象中保存的正好是暂存区刚更新的内容,而另一个blob对象的commit ID为1d6d54,正好等同于tree ID为26675b的树下的welcome.txt的commit ID,这是因为暂存区的welcome.txt没有更新。
$ git cat-file -p 6ef577 100644 blob 1d6d547beac3b9fe58ca216ee3cbe3548937efe3 hack-1.txt 100644 blob b4c6a5378547e7ab0831a360cf27ad2dba3e91a4 welco me.txt $ git cat-file -p 1d6d54 hello, hacker $ git cat-file -p b4c6a5 Hello Nice to meet you Bye, Bye
可见,tree ID为6ef577的树下有两个blob对象,其中,hack-1.txt的commit ID和上面我们看到的该文件在暂存区中的commit ID一样,welcome.txt的commit ID不再和我们上面看到的一样了,因为这里的welcome.txt记录了我们在工作区中修改的welcome.txt的内容。
13 文件追溯
$ cat welcome.txt hello. $ echo "Nice " >> welcome.txt $ git add . $ git commit -m "Nice" $ echo "bye" >> welcome.txt $ git add . $ echo "end" >> welcome.txt $ git blame welcome.txt ^6575a00 (tech-note 2013-02-19 12:57:07 +0800 1) hello. 5ff0e5cb (tech-note 2013-02-26 08:12:47 +0800 2) Nice 00000000 (Not Committed Yet 2013-02-26 08:13:35 +0800 3) bye 00000000 (Not Committed Yet 2013-02-26 08:13:35 +0800 4) end $ git blame -L 2,+3 welcome.txt 5ff0e5cb (tech-note 2013-02-26 08:12:47 +0800 2) Nice 00000000 (Not Committed Yet 2013-02-26 08:20:04 +0800 3) bye 00000000 (Not Committed Yet 2013-02-26 08:20:04 +0800 4) end
git blame
, 逐行显示文件,指明每行最早是在什么版本中引入的,由谁引入,时间,行号。也可以只关注某些行。
14 二分查找
用途:利用二分法在没有问题的“好版本”和有问题的“坏版本”之间查找是哪一个版本引入了问题。
相关命令有:
14.1 手动进行
git bisect start
git bisect bad HEAD
将当前版本标记为“提交”
git bisect good <commit>
标记一个坏版本, 并按二分查找自动切换到需要验证的下一个提交
git bisect <good|bad>
将新的当前版本标记为“好提交”或“坏提交”,并按二分查找自动切换到需要验证的下一个提交
继续迭代执行这一步,直到打印出提示:”<commit ID> is the first bad commit”。
git checkout bisect/bad
切换到最终找到的“坏提交”,然后进行修改
git bisect reset
撤销二分查找产生的临时文件和引用,并将版本库切换回查找之前所在的分支
14.2 自动进行
- 测试脚本
good-or-bad.sh
#!/bin/sh [ -f doc/B.txt ] && exit 1 exit 0
文件B.txt存在返回错误码1,否则返回0
git bisect start master <bad commit>
从已知的“坏提交”master和“好提交”<bad commit>开始
git bisect run sh good-or-bad.sh
git bisect describe refs/bisect/bad
打印出最终定位到的“坏提交”