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

    打印出最终定位到的“坏提交”

Last Updated 2015-11-14 Sat 14:43.

Created by Howard Hou with Emacs 24.5.1 (Org mode 8.2.10)