git使用的基本技巧

导语:git是什么

git是用于Linux内核开发的版本控制工具。与CVS、Subversion一类的集中式版本控制工具不同,它采用了分布式版本库的作法,不需要服务器端软件,就可以运作版本控制,使得源代码的发布和交流极其方便。git的速度很快,这对于诸如Linux内核这样的大项目来说自然很重要。git最为出色的是它的合并追踪(merge tracing)能力。

安装git

下载git并直接安装

在Windows上可以直接在官网下载Git安装程序,下载完成后,直接按照步骤进行安装即可,记得选中Add to Path以将其bin目录添加进系统环境中,方便使用。安装完成后,右键选中git bash here,开启bash。

配置全局参数

在bash下输入以下代码,全局配置参数,例如:

1
2
$ git config --global user.name "Your Name"
$ git config --global user.email "email@example.com"

输入git config --global --list命令可以显示设置过的全局变量,如下图所示:
git config --global --list显示的结果

git的基础使用

创建git仓库

git仓库即repository,在其中的所有文件均可以被git进行管理,对于文件的新建,修改,删除,git均可以对其进行记录,在进行记录之后,任何事件都可以对历史版本进行追踪、还原等操作。

创建git仓库的方法非常简单,首先将跳转到合适的路径下,创建一个文件夹(也可以是已经包含文件的文件夹),在bash下可以使用如下代码创建一个git仓库:

1
2
3
4
5
$ cd /e/ProgramDemo
$ mkdir Learn_git
$ pwd
/e/ProgramDemo/Learn_git
$ git init

在输入完成后可以发现路径下出现了一个以.git命名的隐藏文件夹,说明仓库创建成功。

将文件添加到版本库

在使用git init对git仓库进行建立后,即可在该路径下进行相关项目的创建与书写。

git可以对这些文件进行跟踪,告知每次文件的修改情况(注意:只能跟踪文本文件,可以监测到添加删除的内容。但对二进制文件无法进行跟踪,只能检测到文件大小发生了变化,因此word文档无法进行跟踪,因为其是二进制文件)在进行文本编辑时,建议使用utf8编码,减少冲突的可能。

下面对如何将git中的文件添加到仓库进行演示:

首先在仓库路径下右键选择git bash here打开bash。使用touch命令新建一个测试文件test1.txt,向文件中随意写入一些内容,并进行保存。

1
2
3
4
5
$ touch test1.txt
$ vim test1.txt # 随意写入一些内容
$ cat test1.txt
Hello world!
This is HeavyTiger!

写入内容后,可以使用git对文件进行管理了。首先使用命令git status可以看到,git已经检测到我们在路径中创建了新文件。

1
2
3
Untracked files:
(use "git add <file>..." to include in what will be committed)
test1.txt

使用git add命令对文件进行添加,建文件添加到仓库。如果想添加所有的文件,只需要使用git add .命令,即可添加所有文件,如果有不想追踪的文件格式,例如.class文件,可以创建 .gitignore文件对匹配到的相关后缀文件进行忽视。

1
2
3
4
5
git add test1.txt
git status
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: test1.txt

查看修改的内容

对test1.txt的文件内容进行修改,修改后,可以使用git diff test1.txt命令查看对文件的修改情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ vim test1.txt     #对文本文件进行编辑,做出修改
$ cat test1.txt
This is HeavyTiger! how are you?
This is a new line which I insert on the second day!
$ git diff test1.txt
diff --git a/test1.txt b/test1.txt
index 02e6d3d..6eac541 100644
--- a/test1.txt
+++ b/test1.txt
@@ -1,2 +1,2 @@
-Hello world!
-This is HeavyTiger!
+This is HeavyTiger! how are you?
+This is a new line which I insert on the second day!

可以看到,文件已经被修改,git diff命令详细给出了行的改变情况。在查询到修改的内容没有问题后,即可放心地将文件进行add与commit操作。(建议在add与commit之前使用git status命令了解代码是否经过了修改,若发现存在修改,可以使用git diff命令查看修改的详细位置)。

暂存区修改提交至本地仓库

git commit命令可以将暂存区中的修改提交至本地的版本库。使用git commit命令会在本地的版本库中生成一个40位的哈希值(原因是多人对项目进行commit时,若使用常规的编码如0、1、2等,会导致产生冲突),称为commit-id,在进行版本回退(git reset)时,需要通过该值进行回退。

通常会使用命令git commit -m "message"进行版本提交,在添加了-m参数后,可以输入提交信息,在message中需要简洁地对此次提交进行描述,方便查看版本信息。若提交的message过长,想进行更加清楚的描述,可以使用以下方式:

1
2
3
4
git commit -m '
message1
message2
'

此外,若已经提交了一个版本至服务器中(即已进行git commitgit push操作,未被git merge),但是发现版本中出现了小bug,不想再重新提交一个新的版本,可以使用git commit --amend命令进行追加提交提交至前一次版本的commit-id。

撤销修改

如果在上一次使用git addgit commit之后,你对文件又进行了修改,文件的内容发生了改变,但是我们发现开发出现了不可逆的错误,想直接回退到上次git addgit commit时的状态,撤销工作区中的所有修改,可以使用git checkout -- file命令对其作出修改(如果不添加– file即切换分支,注意git checkout命令是一个危险的命令,可能会替换当前的工作区,请务必在再三确认之后,再键入该命令)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ cat test.txt
Hello world!
this is a new line which I add the second time!
And I deleted the raw second line.
$ vim test.txt
$ cat test.txt
Hello world!
this is a new line which I add the second time!
And I deleted the raw second line.
Add a line which I don't want to save.
$ git checkout -- test.txt
$ cat test.txt
Hello world!
this is a new line which I add the second time!
And I deleted the raw second line.

如果此时的修改不止存在于工作区下,并且非常憨憨地使用了git add .将所有地修改全部添加到了暂存区,此时显然使用git checkout -- file已经不可能起作用,但是仍然可以进行撤销操作。使用git reset HEAD <file>可以将暂存区中的修改回退到工作区中,使用HEAD表示回退到最新版本。

版本回退

我们已经知道了如何对工作区和暂存区中的文件进行版本回退,那么是否可以对已经使用git commit命令提交到本地的版本库中的内容进行回退呢?

答案是可以的,也需要使用git reset命令进行回退。在向前回退时,可以使用git log命令查看commit-id,通过该id进行版本的回退与更改。查看得到的结果如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ git log
commit a52f403b886f5168b00ad1c91897cf808d26a990 (HEAD -> master)
Author: HeavyTiger <462857080@qq.com>
Date: Mon Jun 14 16:23:43 2021 +0800

The second commitment

commit 4369702618f9fe107a9e2629446ce4e1d4b2a6a8
Author: HeavyTiger <462857080@qq.com>
Date: Mon Jun 14 16:19:26 2021 +0800

The first commitment

commit dcd812278c90f6c66a622ae2e6202054d1740f80 (origin/master, origin/HEAD)
Author: HeavyTiger <462857080@qq.com>
Date: Mon Jun 14 16:10:24 2021 +0800

Initial commit

通过commit-id可以进行版本的回退。使用git reset --hard ID,例如:git reset --hard dcd812回退到Initial commit版本,也可以通过HEAD参数进行版本回退,HEAD参数表示当前的版本,在右上角附带^表示上一个版本,故git reset --hard HEAD^表示回退到上一个版本,git reset --hard HEAD^^表示回退到前两个版本。如果回退到前100个版本,也可以使用如下的方法进行回退git reset --hard HEAD~100

但是在进行版本回退之后将无法看到新版本的commit-id,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ git reset --hard HEAD^
HEAD is now at 4369702 The first commitment
$ git log
commit 4369702618f9fe107a9e2629446ce4e1d4b2a6a8 (HEAD -> master)
Author: HeavyTiger <462857080@qq.com>
Date: Mon Jun 14 16:19:26 2021 +0800

The first commitment

commit dcd812278c90f6c66a622ae2e6202054d1740f80 (origin/master, origin/HEAD)
Author: HeavyTiger <462857080@qq.com>
Date: Mon Jun 14 16:10:24 2021 +0800

Initial commit

所以此时如何找到最新的版本号呢?可以使用git reflog命令查看commit记录,在reflog中记录了commit-id,因此可以很方便地向后进行跳转,找到最新commit的代码。

1
2
3
4
5
6
7
$ git reflog
4369702 (HEAD -> master) HEAD@{0}: reset: moving to HEAD^
a52f403 HEAD@{1}: reset: moving to a52f
4369702 (HEAD -> master) HEAD@{2}: reset: moving to 4369
a52f403 HEAD@{3}: commit: The second commitment
4369702 (HEAD -> master) HEAD@{4}: commit: The first commitment
dcd8122 (origin/master, origin/HEAD) HEAD@{5}: clone: from github.com:HeavyTiger/Learn_git.git

git的目录结构

git中加入了暂存区的概念,分为工作区,版本库中的暂存区,分支等概念。git版本库的模型如下图所示:

git版本库的模型

上图描述了 git 对象的在不同的生命周期中不同的存储位置,通过不同的 git 命令改变 git 对象的存储生命周期。

工作区(workspace)

就是我们当前工作空间,也就是我们当前能在本地文件夹下面看到的文件结构。初始化工作空间或者工作空间 clean 的时候,文件内容和 index 暂存区是一致的,随着修改,工作区文件在没有 add 到暂存区时候,工作区将和暂存区是不一致的。

暂存区 (index)

老版本概念也叫 Cache 区,就是文件暂时存放的地方,所有暂时存放在暂存区中的文件将随着一个 commit 一起提交到 local repository 此时 local repository 里面文件将完全被暂存区所取代。暂存区是 git 架构设计中非常重要和难理解的一部分。

本地仓库 (local repository)

git 是分布式版本控制系统,和其他版本控制系统不同的是他可以完全去中心化工作,你可以不用和中央服务器 (remote server) 进行通信,在本地即可进行全部离线操作,包括 log,history,commit,diff 等等。完成离线操作最核心是因为 git 有一个几乎和远程一样的本地仓库,所有本地离线操作都可以在本地完成,等需要的时候再和远程服务进行交互。

远程仓库 (remote repository)

中心化仓库,所有人共享,本地仓库会需要和远程仓库进行交互,也就能将其他所有人内容更新到本地仓库把自己内容上传分享给其他人。结构大体和本地仓库一样。

git命令后文件的变化

git命令后文件的变化

Git管理的是修改,当使用git add命令后,在工作区的第一次修改被放入暂存区,准备提交,但是,在工作区的第二次修改并没有放入暂存区,所以,git commit只负责把暂存区的修改提交了,也就是第一次的修改被提交了,第二次的修改不会被提交。

所以在每次使用git commit之前,所有修改过的文件都必须重新进行git add,将工作区(workspace)中的修改提交到暂存区(index)中,否则即使进行了git commit本地仓库中也将不会出现修改。

远程仓库

创建远程仓库

github或者gitee网站中注册帐号,创建Repository,作为本地的远程仓库。

方法一

在GitHub中,得到仓库的SSH链接或者HTTPS链接,将该链接手动绑定到本地。例如,本人学习git的项目的SSH链接为:git@github.com:HeavyTiger/Learn_git.git,故可以在本地的仓库中,使用bash输入git remote add origin git@github.com:HeavyTiger/Learn_git.git命令,使用此命令可以让远端仓库与本地仓库建立远程连接,而该远程仓库名叫做origin(初始建立的远程连接,一般都叫做origin,clone得到的仓库亦叫origin)。

下一步即可及那个本地库中的内容推送到远程库上:使用命令git push,第一次进行数据上传建议使用git push -u origin master加上了-u参数后,Git会将本地的master分支与远程的master分支进行关联,在之后的推送中即可简化命令git push origin master

使用该方法时,记得查看GitHub与本地的文件是否一致,GitHub在创建仓库时会添加readME.md以及.gitignore文件,若本地不存在这些文件,即两边的版本库不同步,在使用git push命令时会产生报错,因此建议使用此方法时,需要将本地的版本库与远程的版本库进行同步。使用git pull命令从远端进行拉取,将版本库与远端同步,之后即可正常的提交代码。因此不建议采用方法一中的方法。因为较为复杂,可以使用方法二中的方法。

方法二

先创建一个远程库,直接进行远程库克隆,即使用git clone命令将远程库与本地库直接关联并且同步数据,由于账户中保存了本机的公匙,因此可以直接将本机的文件进行推送,如果clone他人的项目,自然无法进行推送。如果有多个人协作开发,那么每个人各自从远程克隆一份就可以了。

你也许还注意到,GitHub给出的地址不止一个,还可以用https之类的协议进行上传,例如https://github.com/HeavyTiger/Learn_git.git 这样的地址。实际上,Git支持多种协议,默认的Git使用SSH进行连接,但也可以使用https等其他协议。

使用https除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用ssh协议而只能用https。

分支管理

分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。

现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。

创建与合并分支

创建分支

使用git checkout -b name创建分支并转到新建的分支上。例如创建dev分支,即可以使用git ckeckout -b dev创建dev分支并跳转到dev分支上。

使用git branch命令可以查看当前的所有分支,在当前分支前会有一个*号。

可以在dev分支上进行修改以及提交而不会影响master分支。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master)
$ git checkout -b dev
Switched to a new branch 'dev'

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ git branch
* dev
master

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ echo "Hello world" > hello.txt

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ cat hello.txt
Hello world

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ git add .

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ git commit -m "the first dev modify"
[dev a1d3fec] the first dev modify
1 file changed, 1 insertion(+)
create mode 100644 hello.txt

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ ls
README.md hello.txt test.txt

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master)
$ ls
README.md test.txt

可以看到对dev分支下新建了hello.txt并提交之后,转到master分支,并不存在dev分支下新建立的hello.txt文件。

但是若要转到master分支后本地工作区中的文件不被直接同步为dev分支下创建的内容,必须要执行git addgit commit命令,否则会导致出错,本地的工作区文件将在跳转后直接被同步为dev分支下修改的内容。

git stash暂存

如果不想使用git commit进行一次提交该如何操作?比方说,正在对dev分支进行修改,已经做出了部分修改,此时收到了新的命令,即再去另一个分支对bug进行修复,若此时不进行git commit去提交dev分支,在转换分支时会导致数据被合并到出现bug的分支上,回退会导致数据丢失。因此我们可以先使用git add .将工作区中的修改储存到暂存区(重要),再使用git stash命令进行暂存区的暂存。再处理完另一个分支的bug后,可以回退到dev分支下,进行数据的恢复,演示操作如下所示:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ ls
README.md hello.txt test.txt

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ echo "this is a temp file." > temp1.txt

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ echo "this is a temp file." > temp2.txt

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ ls
README.md hello.txt temp1.txt temp2.txt test.txt

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ git status
On branch dev
Untracked files:
(use "git add <file>..." to include in what will be committed)
temp1.txt
temp2.txt

nothing added to commit but untracked files present (use "git add" to track)

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ git add .

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ git stash
Saved working directory and index state WIP on dev: a1d3fec the first dev modify

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ git stash list
stash@{0}: WIP on dev: a1d3fec the first dev modify

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master)
$ ls
README.md test.txt

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master)
$ git checkout dev
Switched to branch 'dev'

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ ls
README.md hello.txt test.txt

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ git stash pop
On branch dev
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: temp1.txt
new file: temp2.txt

Dropped refs/stash@{0} (77cfbe0f5b75e14d8118f607d88d42c7e0ac3bc4)

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ ls
README.md hello.txt temp1.txt temp2.txt test.txt

可以看到此时的数据被成功恢复。

(储存)

  • 首先使用git add .
  • 再使用git stash save "备注"
  • 最后使用git stash list查看stash的情况是否正常

(恢复)

  • 当bug修复完毕时,使用git stash pop(当前现场需要在栈顶)
  • 若暂存多次,可以使用git stash apply stash@{1}命令进行恢复,
  • stash@{1}即stash内容的版本,可以使用git stash list进行查看,
    但是使用git stash apply恢复会导致stash内容不被删除,毕竟使用到了栈,需要手动使用git stash drop来删除。

快速修复其他分支的相同问题

同时,在修复bug的情景下,我们可能会想到修复的bug在其他分支上也存在,有没有办法仅仅将修改bug的位置同步到其他分支呢?为此,git提供了cherry-pick命令,允许进行相应的此操作,将一次提交中所做出的修改提交到当前分支上,找到fix bugcommit-id,对当前的分支使用命令git cherry-pick commit-id即可将修改的bug同步到当前的分支。git会自动给当前分支进行一次git commit操作。两次提交的commit-id不相同,原因是这两个commit只是改动相同,但确实是两个不同的commit。用git cherry-pick,我们就不需要在当前分支上手动再把修bug的过程重复一遍。

合并分支

合并分支需要使用到git merge branch命令,将其他的分支合并到HEAD所指向的分支上,如果有冲突,则无法合并,需要手动对冲突进行修复后才能进行合并。如果能够直接合并,会使用Fast-forward的方式进行合并。即直接将当前的分支指针(被合并的)指向合并的分支上,所以速度非常快。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ ls
README.md hello.txt test.txt

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master)
$ ls
README.md test.txt

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master)
$ git merge dev
Updating 4369702..a1d3fec
Fast-forward
hello.txt | 1 +
test.txt | 5 +++--
2 files changed, 4 insertions(+), 2 deletions(-)
create mode 100644 hello.txt

删除分支

在合并完分支后,对于无用的分支,即可直接删除,使用git branch -d dev命令即可将dev分支删除。

1
2
3
DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master)
$ git branch -d dev
Deleted branch dev (was a1d3fec).

合并冲突处理

首先,git在对分支进行合并的时候,可能会出现无法合并的情况。例如,同一篇作文,老师对开头进行了修改,而作者对结尾进行了修改,这种情况是可以直接进行合并的,使用git merge命令可以自动进行合并。但是如果老师修改了开头,作者也想修改开头,则没有办法对文章进行自动合并,需要作者或老师对文章开头的修改进行手动合并,在手动处理冲突完成后(即决定将谁对开头的修改作为整篇文章的修改),之后才能够进行文章的自动合并。举例说明如下:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ ls
README.md hello.txt test.txt

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ vim hello.txt

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ cat hello.txt
Hello world means ni hao in chinese!
I modified this file.

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ git commit -a -m "修改了hello.txt,对HelloWorld含义做出解释"
[dev f5228b6] 修改了hello.txt,对HelloWorld含义做出解释
1 file changed, 2 insertions(+), 1 deletion(-)

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ git log --pretty=oneline
f5228b6a8d1b5c52d3672369b13227217d5ea339 (HEAD -> dev) 修改了hello.txt,对HelloWorld含义做出解释
a1d3fec8d4b76de78627db0c9c41a9d0e16d0769 (origin/dev, master) the first dev modify
0ccccf8dd1b842302270dac680fa01fccbe3e002 the first modify in branch dev
4369702618f9fe107a9e2629446ce4e1d4b2a6a8 (origin/master, origin/HEAD) The first commitment
dcd812278c90f6c66a622ae2e6202054d1740f80 Initial commit

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (dev)
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 2 commits.
(use "git push" to publish your local commits)

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master)
$ ls
README.md hello.txt test.txt

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master)
$ vim hello.txt

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master)
$ cat hello.txt
Hello world, this is another way to modify.
ni hao!

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master)
$ git commit -a -m "在主分支中修改了hello.txt,欲展示合并冲突处理过程"
[master 42eede8] 在主分支中修改了hello.txt,欲展示合并冲突处理过程
1 file changed, 2 insertions(+), 1 deletion(-)

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master)
$ git log --pretty=oneline
42eede8aed5975b65ee866223ce642b3f8cc23e7 (HEAD -> master) 在主分支中修改了hello.txt,欲展示合并冲突处理过程
a1d3fec8d4b76de78627db0c9c41a9d0e16d0769 (origin/dev) the first dev modify
0ccccf8dd1b842302270dac680fa01fccbe3e002 the first modify in branch dev
4369702618f9fe107a9e2629446ce4e1d4b2a6a8 (origin/master, origin/HEAD) The first commitment
dcd812278c90f6c66a622ae2e6202054d1740f80 Initial commit

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master)
$ git log --graph --pretty=oneline --abbrev-commit --all
* 42eede8 (HEAD -> master) 在主分支中修改了hello.txt,欲展示合并冲突处理过程
| * f5228b6 (dev) 修改了hello.txt,对HelloWorld含义做出解释
|/
* a1d3fec (origin/dev) the first dev modify
* 0ccccf8 the first modify in branch dev
* 4369702 (origin/master, origin/HEAD) The first commitment
* dcd8122 Initial commit

可以使用git log --graph --pretty=oneline --abbrev-commit --all语句以图表的形式展示所有git commit的情况,其中:

  • --graph参数表示以图标的形式进行显示提交情况。
  • --pretty=oneline参数表示精简输出,每一次git commit提交均在一行内进行显示。
  • --abbrev-commit参数表示将commit-ID进行简写,将40个十六进制数的SHA1散列值进行简写,使用7位16进制数进行表示。
  • --all参数表示展示所有分支中的提交情况,否则只会展示当前分支下的提交情况

图中带有(origin/dev)等字样的表示在远程仓库中进行过提交,存在提交记录,可以直接进行恢复。

此时对修改进行合并,合并到主分支上,可以看到git提示我们在hello.txt中出现了冲突,自动合并失败,需要我们手动将HEAD所指向的分支中的文件修改后,直接进行提交,才可以进行合并。

1
2
3
4
5
DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master)
$ git merge dev
Auto-merging hello.txt
CONFLICT (content): Merge conflict in hello.txt
Automatic merge failed; fix conflicts and then commit the result.

查看当前路径下hello.txt文件中的内容,可以看到,git使用<<<<<<< HEAD ======= >>>>>>> dev将出现冲突的地方进行了标记,我们对冲突进行判断并保留,之后进行提交,即可完成合并的操作。

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
26
27
28
29
30
31
32
33
34
DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master|MERGING)
$ cat hello.txt
<<<<<<< HEAD
Hello world, this is another way to modify.
ni hao!
=======
Hello world means ni hao in chinese!
I modified this file.
>>>>>>> dev

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master|MERGING)
$ vim hello.txt

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master|MERGING)
$ cat hello.txt
Hello world, this is another way to modify.
ni hao!I modified this file.


DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master|MERGING)
$ git commit -a -m "conflict fixed"
[master 7373637] conflict fixed

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master)
$ git log --graph --pretty=oneline --abbrev-commit --all
* 7373637 (HEAD -> master) conflict fixed
|\
| * f5228b6 (dev) 修改了hello.txt,对HelloWorld含义做出解释
* | 42eede8 在主分支中修改了hello.txt,欲展示合并冲突处理过程
|/
* a1d3fec (origin/dev) the first dev modify
* 0ccccf8 the first modify in branch dev
* 4369702 (origin/master, origin/HEAD) The first commitment
* dcd8122 Initial commit

禁用Fast forward模式

通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。即如果不禁用,若可以以此模式合并,会直接将HEAD移动到最新的dev版本上,会导致分支的信息直接丢失,在多人开发的情况下这样是不好的,需要再强制进行一次git commit操作,则会生成两个节点,将HEAD指向新生成的节点即可。

如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master)
$ git log --graph --pretty=oneline
* 73736377bbeccfefe5121f2bab028b13784b66e4 (HEAD -> master) conflict fixed
|\
| * f5228b6a8d1b5c52d3672369b13227217d5ea339 修改了hello.txt,对HelloWorld含义做出解释
* | 42eede8aed5975b65ee866223ce642b3f8cc23e7 在主分支中修改了hello.txt,欲展示合并冲突处理过程
|/
* a1d3fec8d4b76de78627db0c9c41a9d0e16d0769 (origin/dev) the first dev modify
* 0ccccf8dd1b842302270dac680fa01fccbe3e002 the first modify in branch dev
* 4369702618f9fe107a9e2629446ce4e1d4b2a6a8 (origin/master, origin/HEAD) The first commitment
* dcd812278c90f6c66a622ae2e6202054d1740f80 Initial commit

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master)
$ git branch
develop
* master

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master)
$ git log --graph --pretty=oneline --all
* 20a793580c3af6a1f9117a2142e589ebffe2d3a3 (develop) 在develop分支中增加对noff.txt文件的修改
* 73736377bbeccfefe5121f2bab028b13784b66e4 (HEAD -> master) conflict fixed
|\
| * f5228b6a8d1b5c52d3672369b13227217d5ea339 修改了hello.txt,对HelloWorld含义做出解释
* | 42eede8aed5975b65ee866223ce642b3f8cc23e7 在主分支中修改了hello.txt,欲展示合并冲突处理过程
|/
* a1d3fec8d4b76de78627db0c9c41a9d0e16d0769 (origin/dev) the first dev modify
* 0ccccf8dd1b842302270dac680fa01fccbe3e002 the first modify in branch dev
* 4369702618f9fe107a9e2629446ce4e1d4b2a6a8 (origin/master, origin/HEAD) The first commitment
* dcd812278c90f6c66a622ae2e6202054d1740f80 Initial commit

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master)
$ git merge develop
Updating 7373637..20a7935
Fast-forward
noff.txt | 3 +++
1 file changed, 3 insertions(+)
create mode 100644 noff.txt

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master)
$ git log --graph --pretty=oneline --all
* 20a793580c3af6a1f9117a2142e589ebffe2d3a3 (HEAD -> master, develop) 在develop分支中增加对noff.txt文件的修改
* 73736377bbeccfefe5121f2bab028b13784b66e4 conflict fixed
|\
| * f5228b6a8d1b5c52d3672369b13227217d5ea339 修改了hello.txt,对HelloWorld含义做出解释
* | 42eede8aed5975b65ee866223ce642b3f8cc23e7 在主分支中修改了hello.txt,欲展示合并冲突处理过程
|/
* a1d3fec8d4b76de78627db0c9c41a9d0e16d0769 (origin/dev) the first dev modify
* 0ccccf8dd1b842302270dac680fa01fccbe3e002 the first modify in branch dev
* 4369702618f9fe107a9e2629446ce4e1d4b2a6a8 (origin/master, origin/HEAD) The first commitment
* dcd812278c90f6c66a622ae2e6202054d1740f80 Initial commit

在进行Fast forward之后,可以看到,当前的HEAD直接指向了master以及develop,相当于丢失了一次分支,我们再对develop分支进行一次更新提交,禁止Fast forward。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master)
$ git checkout develop
Switched to branch 'develop'

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (develop)
$ ls
README.md hello.txt noff.txt test.txt

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (develop)
$ vim noff.txt

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (develop)
$ git add .

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (develop)
$ git commit -m "尝试对develop分支进行修改,此次尝试不使用Fast forward合并"
[develop d6bc5fc] 尝试对develop分支进行修改,此次尝试不使用Fast forward合并
1 file changed, 2 insertions(+), 1 deletion(-)

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (develop)
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.
(use "git push" to publish your local commits)

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master)
$ git merge --no-ff -m "进制Fast forward模式合并" develop
Merge made by the 'recursive' strategy.
noff.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)

DCM@DESKTOP-GN199F8 MINGW64 /e/ProgramDemo/Learn_git (master)
$ git log --graph --pretty=oneline --abbrev-commit --all
* e0aa011 (HEAD -> master) 进制Fast forward模式合并
|\
| * d6bc5fc (develop) 尝试对develop分支进行修改,此次尝试不使用Fast forward合并
|/
* 20a7935 在develop分支中增加对noff.txt文件的修改
* 7373637 conflict fixed
|\
| * f5228b6 修改了hello.txt,对HelloWorld含义做出解释
* | 42eede8 在主分支中修改了hello.txt,欲展示合并冲突处理过程
|/
* a1d3fec (origin/dev) the first dev modify
* 0ccccf8 the first modify in branch dev
* 4369702 (origin/master, origin/HEAD) The first commitment
* dcd8122 Initial commit

可以看到再这次提交之后,使用-m参数进行了一次版本提交,因此保留了该分支存在的证据和记录,而之前没有使用--no-ff参数合并的develop版本已经找不到存在的证据了。合并分支时,加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward合并就看不出来曾经做过合并。

开发中的分支策略

在实际开发中,我们应该保证master分支的永远稳定,仅用来发布最新的使用版本,不允许直接在master分支上进行开发。

我们应该使用dev分支(即develop开发分支)进行开发,对于已经稳定的版本再合并到master分支上。

每个开发人员都有自己的分支,同时,也可以一个人开辟多条分支,进行修改与特性增加。在经过项目管理人员的批准后,时不时向dev分支上合并即可。


-------------本文到此结束 感谢您的阅读-------------
谢谢你请我喝肥宅快乐水(๑>ڡ<) ☆☆☆