Git 是一个开源的分布式版本控制系统,我们可以很方便的通过该工具进行代码的管理,例如查看修改历史、创建项目快照,在多人协作开发时,可以记录每处代码的改动等。

目录如下:

在开始之前,这里给出一些学习 Git 的资源,例如,官方的 Git-Book、一个基于图形界面可以在线练习 Git 的网站 Learn Git Branching。此外,这里也有一篇「给开源项目提交 PR 的完成 Git 流程」的文章

这里仅给出常用的 Git 命令,方便及时查阅。

  • git help <command>: 获取 command 命令的帮助信息。
    • 符号<>表示可选的意思,你可以在git help后跟一个命令,例如git help init。或者不跟,例如git help
  • git init: 创建一个新的 git 仓库,其中的数据会保存在.git这一隐藏的目录下。
  • git status: 显示当前仓库的状态。
  • git add <filename>: 将文件添加到暂存区。
    • 如果你想将当前目录下的所有文件都进行提交,可以执行git add .
  • git commit -m <message info>: 创建一个新的提交,并说明提交信息。
  • git log: 显示历史日志。
  • git log --all --graph --decorate: 以有向无环图的形式可视化历史记录。
  • git diff <filename>: 显示当前文件中的内容与上一次提交之间的差异。
  • git diff <revision> <filename>: 显示某个文件两个版本之间的差异。
    • 如果执行git diff hello.txt,则实际上是以HEAD为准的,也就相当于执行了git diff HEAD hello.txt
  • git checkout <revision>: 更新(改变) HEAD 和目前的分支。

当我使用git log --all --graph --decorate之后,会出现以下内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
* commit 53a1f501bbbce4a5e45702d7ef8ddf00543c2be8 (HEAD -> master)
| Author: mycaroltest <carolsir95@gmail.com>
| Date:   Tue Dec 1 15:16:41 2020 +0800
| 
|     append new line
| 
* commit 06958eddd326032f5e199409ada6b575037c3479
  Author: mycaroltest <carolsir95@gmail.com>
  Date:   Tue Dec 1 15:00:27 2020 +0800
  
      Add hello.txt

最新提交的结果,将显示在最上方。除了提交的作者(Author)、提交日期(Date)以及提交信息(commit)以外,注意到还有一个HEAD -> master标志。其中,master表示默认的分支,这是在执行完git init后自动生成的。而HEAD相当于一个指针(或者说引用),它指向当前所在的分支。显然,当前所在的分支为master

当我执行git checkout 06958e的时候,那么我将会来到06958e分支,并且指针HEAD将会指向06958e分支,而不再指向master分支。如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
➜  demo git:(master) git checkout 06958e
Note: switching to '06958e'.
...
(some information)
...
➜  demo git:(06958ed) git log --all --graph --decorate
* commit 53a1f501bbbce4a5e45702d7ef8ddf00543c2be8 (master)
| Author: mycaroltest <carolsir95@gmail.com>
| Date:   Tue Dec 1 15:16:41 2020 +0800
| 
|     append new line
| 
* commit 06958eddd326032f5e199409ada6b575037c3479 (HEAD)
  Author: mycaroltest <carolsir95@gmail.com>
  Date:   Tue Dec 1 15:00:27 2020 +0800
  
      Add hello.txt
➜  demo git:(06958ed) 

来到06958e分支后,你可以查看一下文件hellp.txt的内容,它显示了当前版本(06958e)下的内容。如下所示:

1
2
3
➜  demo git:(06958ed) cat hello.txt 
hello
➜  demo git:(06958ed) 

然后,你可以通过git checkout master再将master作为当前的分支。此时再次查看hello.txt中的内容,则会显示当前master分支提交的结果。如下所示:

1
2
3
4
5
6
7
➜  demo git:(06958ed) git checkout master
Previous HEAD position was 06958ed Add hello.txt
Switched to branch 'master'
➜  demo git:(master) cat hello.txt 
hello
another line
➜  demo git:(master) 

下面是操作分支合并的一些命令。

  • git branch: 显示所有创建的分支。
    • git branch -vv: 在显示所有创建的分支的同时,还会显示该分支的其它信息。
  • git branch <name>: 创建分支。
  • git checkout -b <name>: 创建一个新的分支并切换到该新的分支。
    • 该命令相当于git branch <name>; git checkout <name>
  • git merge <revision>: 将<revision>分支合并到当前分支。
  • git mergetool: 使用工具来处理合并冲突。
  • git rebase: 将一系列的补丁变基(rebase)为新的基线。

当我在创建完一个animal.py文件之后,将其添加到暂存区中,然后再创建一个cat分支。显示的信息如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
➜  demo git:(master) git branch -vv
  cat    9e378e5 Add animal.py
* master 9e378e5 Add animal.py
➜  demo git:(master) git log --all --graph --decorate
* commit 9e378e559be54fef0e923509e19e7e09c1d1c7c8 (HEAD -> master, cat)
| Author: mycaroltest <carolsir95@gmail.com>
| Date:   Tue Dec 1 16:13:19 2020 +0800
| 
|     Add animal.py
| 
* commit 53a1f501bbbce4a5e45702d7ef8ddf00543c2be8
| Author: mycaroltest <carolsir95@gmail.com>
| Date:   Tue Dec 1 15:16:41 2020 +0800
| 
|     append new line
| 
* commit 06958eddd326032f5e199409ada6b575037c3479
  Author: mycaroltest <carolsir95@gmail.com>
  Date:   Tue Dec 1 15:00:27 2020 +0800
  
      Add hello.txt
➜  demo git:(master) 

根据(HEAD -> master, cat)所示,你可以看到,这里多了一个新的分支cat,但此时的master分支是位于cat分支之前的。而当我切换到cat分支之后,就会将cat置于master之前,即(HEAD -> cat, master)

如果我在cat分支下,修改了animal.py文件中的内容,并进行了addcommit。那么,此时查看log将会是如下情况:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
➜  demo git:(master) git log --all --graph --decorate
* commit ee92c5932ef1f5720c3f4645f9eafb5598d75f4d (HEAD -> cat)
| Author: mycaroltest <carolsir95@gmail.com>
| Date:   Tue Dec 1 16:30:37 2020 +0800
| 
|     Add cat function in animal.py
| 
* commit 9e378e559be54fef0e923509e19e7e09c1d1c7c8 (master)
| Author: mycaroltest <carolsir95@gmail.com>
| Date:   Tue Dec 1 16:13:19 2020 +0800
| 
|     Add animal.py
| 
* commit 53a1f501bbbce4a5e45702d7ef8ddf00543c2be8
| Author: mycaroltest <carolsir95@gmail.com>
| Date:   Tue Dec 1 15:16:41 2020 +0800
| 
|     append new line
| 
* commit 06958eddd326032f5e199409ada6b575037c3479
  Author: mycaroltest <carolsir95@gmail.com>
  Date:   Tue Dec 1 15:00:27 2020 +0800
  
      Add hello.txt
➜  demo git:(master) 

可以使用git log --all --graph --decorate --oneline命令,简化日志的输出,如下所示:

1
2
3
4
5
6
➜  demo git:(cat) git log --all --graph --decorate --oneline
* ee92c59 (HEAD -> cat) Add cat function in animal.py
* 9e378e5 (master) Add animal.py
* 53a1f50 append new line
* 06958ed Add hello.txt
➜  demo git:(cat)

如果再将分支从cat却换到master的话,则在处于master的情况下,则不会看到catanimal.py的修改。

好,现在我们创建一个dog分支,并同时切换到dog分支,即git checkout -b dog。所有分支信息如下所示:

1
2
3
4
5
6
➜  demo git:(dog) git log --all --graph --decorate --oneline
* ee92c59 (cat) Add cat function in animal.py
* 9e378e5 (HEAD -> dog, master) Add animal.py
* 53a1f50 append new line
* 06958ed Add hello.txt
➜  demo git:(dog)

我在dog分支下,修改animal.py中的内容,其中dog()函数是新添加的内容。如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import sys

def default():
    print("Hello")

def dog():
    print("Woof!")

def main():
    if sys.argv[1] == 'dog':
        dog()
    else:
        default()

if __name__ == '__main__':
    main()

然后进行addcommit操作。现在,我们再看一看日志中的内容:

1
2
3
4
5
6
7
8
➜  demo git:(dog) git log --all --graph --decorate --oneline
* dd42b6e (HEAD -> dog) Add dog function in animal.py
| * ee92c59 (cat) Add cat function in animal.py
|/  
* 9e378e5 (master) Add animal.py
* 53a1f50 append new line
* 06958ed Add hello.txt
➜  demo git:(dog)

可以看到,与之前不同的是,这里出现了一个分叉。下面将演示merge命令的使用。

首先将分支切换到master,然后将mastercat分支进行合并,如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
378e5..ee92c59
Fast-forward
 animal.py | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)
➜  demo git:(master) git log --all --graph --decorate --oneline
2c59 (HEAD -> master, cat) Add cat function in animal.py
|/  
* 9e378e5 Add animal.py
* 53a1f50 append new line
* 06958ed Add hello.txt
➜  demo git:(master) 

如果此时再进行git merge dog的话,则会出现 Merge conflict ,即合并冲突。因此,这里使用git mergetool中的 vimdiff 进行冲突检查,并对animal.py进行一些修改,修改完以后的代码如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import sys

def cat():
    print("Meow!")

def default():
    print("Hello")

def dog():
    print("Woof!")

def main():
    if sys.argv[1] == 'cat':
        cat()
    elif sys.argv[1] == 'dog':
        dog()
    else:
        default()

if __name__ == '__main__':
    main()

此时,冲突已经被解决了,然后将animal.py进行add操作,最后我们可以mergedog分支了。如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
➜  demo git:(master) ✗ git merge --continue
[master 0013a1f] Merge branch 'dog'
➜  demo git:(master) ✗ git log --all --graph --decorate --oneline
*   0013a1f (HEAD -> master) Merge branch 'dog'
|\  
| * dd42b6e (dog) Add dog function in animal.py
* | ee92c59 (cat) Add cat function in animal.py
|/  
* 9e378e5 Add animal.py
* 53a1f50 append new line
* 06958ed Add hello.txt
➜  demo git:(master)

至此,你就可以任意执行animal.py文件了,如下所示:

1
2
3
4
5
6
7
➜  demo git:(master) ✗ python3 animal.py cat
Meow!
➜  demo git:(master) ✗ python3 animal.py dog
Woof!
➜  demo git:(master) ✗ python3 animal.py dsdda  
Hello
➜  demo git:(master)

接下来是对远程仓库进行操作的命令。常用的命令如下:

  • git remote: 列出当前已经连接的远端。
  • git remote add <name> <url>: 添加一个远端。
  • git push <remote> <local branch>:<remote branch>: 将对象传送至远端,并更新远端的引用。
  • git branch --set-upstream-to=<remote>/<remote branch>: 创建本地和远端分支的关联关系。
  • git fetch: 从远端获取对象或索引。
  • git pull: 相当于git fetch; git merge
  • git clone <url> <folder name>: 从远端下载仓库。
    • 如果 clone 速度较慢的话,可以浏览这篇文章,通过使用代理的方式来提升 clone 速度。
  • git commit --amend: 编辑提交的内容或信息。
  • git reset HEAD <file>: 恢复暂存的文件。
  • git checkout -- <file>: 丢弃修改。

为了方便演示,这里使用本地仓库代替 Github 远程仓库。我在demo同级目录下创建了一个remote目录,将其当作远程仓库。然后将remote目录添加到demo的远程仓库中。如下所示:

1
2
3
4
➜  demo git:(master) ✗ git remote add origin ../remote
➜  demo git:(master) ✗ git remote
origin
➜  demo git:(master)

然后进行push操作,并查看log。如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
➜  demo git:(master) ✗ git push origin master:master
Enumerating objects: 19, done.
Counting objects: 100% (19/19), done.
Delta compression using up to 4 threads
Compressing objects: 100% (15/15), done.
Writing objects: 100% (19/19), 1.72 KiB | 1.72 MiB/s, done.
Total 19 (delta 4), reused 0 (delta 0)
To ../remote
 * [new branch]      master -> master
➜  demo git:(master) ✗ git log --all --graph --decorate --oneline
*   0013a1f (HEAD -> master, origin/master) Merge branch 'dog'
|\  
| * dd42b6e (dog) Add dog function in animal.py
* | ee92c59 (cat) Add cat function in animal.py
|/  
* 9e378e5 Add animal.py
* 53a1f50 append new line
* 06958ed Add hello.txt
➜  demo git:(master)

此时,我在master分支下,将animal.py文件进行一些修改。当commit之后,日志如下所示:

1
2
3
4
5
6
7
8
9
* 32a4ab7 (HEAD -> master) x
*   0013a1f (origin/master) Merge branch 'dog'
|\  
| * dd42b6e (dog) Add dog function in animal.py
* | ee92c59 (cat) Add cat function in animal.py
|/  
* 9e378e5 Add animal.py
* 53a1f50 append new line
* 06958ed Add hello.txt

如果我将remote这一远程仓库clonedemo2目录下,则其日志如下所示:

1
2
3
4
5
6
7
8
*   0013a1f (HEAD -> master, origin/master, origin/HEAD) Merge branch 'dog'
|\  
| * dd42b6e Add dog function in animal.py
* | ee92c59 Add cat function in animal.py
|/  
* 9e378e5 Add animal.py
* 53a1f50 append new line
* 06958ed Add hello.txt

此时你会发现,demo2中的animal.py文件是没有被修改过的。为了实现同步,我们需要在demo目录下进行--set-upstream-to=操作,最后直接进行push操作。

此时,在demo2中,就需要进行同步操作了,即git fetch。这一操作仅仅是更新了索引或对象,而文件还没有被更新。我们可以使用git pull拉取文件中的内容,从而实现同步。

至此,通过上面所介绍的,已经将 Git 的大部分操作都演示了一遍。对于一些高级的命令或操作,可以通过之前介绍的 Git 官网进行查看。