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
文件中的内容,并进行了add
与commit
。那么,此时查看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
的情况下,则不会看到cat
对animal.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()
|
然后进行add
和commit
操作。现在,我们再看一看日志中的内容:
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
,然后将master
和cat
分支进行合并,如下所示:
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
操作,最后我们可以merge
到dog
分支了。如下所示:
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
这一远程仓库clone
到demo2
目录下,则其日志如下所示:
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 官网进行查看。