
commit1表示一个初始提交,包含所有的文件内容,在这个示例中是 A B C 三个文件。
commit2表示在初始提交后的一个提交,仅对文件 B 进行了修改,该提交中包含完整的文件 B,以及对其他文件(A 和 B)的引用(因为它们没有修改)。
Git 中对每次提交的真实存储结构是:

图源:
最左侧的 commit 表示一次提交,包含表示提交唯一身份的 SHA1 值,以及提交信息(作者、提交人)等。它指向一个表示该次提交的文件树的数据块,这个文件树记录该次提交涉及哪些文件的修改,以及每个修改后的文件的 SHA1 值,并且指向对应的文件的数据块,具体文件的数据块包含了修改后的文件的完整内容。
每次提交都会基于至少一个父提交(初始提交除外):

图源:
基于这样的设计,就可以还原任意提交时的完整代码。
在 Git 中,分支仅仅是一个指向某个提交的指针:

图源:
在上图中,v1.0是一个 tag,它也是指向了某个提交记录的指针。master是一个分支,指向一个提交。HEAD是一个特殊指针,当它指向某个分支时,就可以在这个分支指向的提交记录上添加新的提交。
创建分支
通过命令创建分支:
git branch testing
这会基于当前分支创建一个新分支,实际上就是在当前分支指向的提交上,创建一个新的分支指针,指向这个提交记录:

HEAD 是一个特殊指针,通常指向当前分支。可以在查看日志时使用--decorate参数,此时日志会显示 HEAD 指针指向,以及指向相同提交的其他分支:
❯ git log --oneline --decorate
9ce35ac (HEAD -> testing, temp-v1.0) add something
26eb547 (tag: v1.0) add classroom
51fc7ad add user module
1e074de move test
59ef81c delete file
9d6f179 test
这里表示HEAD指向当前分支testing,testing指向提交记录9ce35ac,并且temp-v1.0分支同样指向这个提交记录。
可以用以下图表表示:

分支切换
通过命令切换到其它分支:
git checkout temp-v1.0
此时 HEAD 指针会指向新的分支:

如果此时进行一次提交:
❯ git add .
❯ git commit -m 'add school'
再查看日志:

现在的提交记录树可以表示为:

切换回testing分支进行一次提交:
❯ git checkout testing
❯ git add .
❯ git commit -m 'modify teacher'
以分支树的方式查看提交记录:
❯ git log --oneline --decorate --graph --all

可以看到两个分支从9ce35ac这次提交后出现了分叉:

分支合并
从 main 分支创建一个新分支,并进行两次提交:
❯ git checkout main
❯ git branch feature/1
❯ git checkout feature/1
❯ git add .
❯ git commit -m 'modify teacher'
❯ git add .
❯ git commit -m 'modify teacher'
可以通过
git switch -c feature/1更方便的创建并切换分支。
现在的分支情况可以表示为:

假设我们需要进行一次热修复:
❯ git checkout main ❯ git branch hot_fix/modify_student ❯ git checkout hot_fix/modify_student ❯ git add . ❯ git commit -m 'modify user'

如果这次热修复经过了测试,需要合并到主分支上线:
❯ git checkout main
❯ git merge hot_fix/modify_student
Updating ce57e0e..70ac933
Fast-forward
ch1/demo/src/main/java/cn/icexmoon/learngit/demo/entity/User.java | 1 +
1 file changed, 1 insertion(+)
在主分支合并热修复分支的时候,我们可以看到提示信息中出现Fast-forward的字样,这是因为它们存在一个共同祖先提交,并且可以很容易地移动main分支的指针到热修复分支进行合并,合并后:

但是如果需要合并两个已经产生分叉了的分支,就不会出现Fast-forward,而是需要通过三方合并实现:
❯ git merge feature/1

可以看到,这种情况下的合并操作产生了一个新的提交记录,并且指向两个父提交,通过这种方式将两个已经产生分叉的分支关联了起来。
现在特性分支和热修复分支都已经合并进了主分支,所以一般我们会删除这两个分支:
❯ git branch -d feature/1
❯ git branch -d hot_fix/modify_student
解决冲突
如果合并分支时,存在同一个文件被两个分支都修改过的情况,会产生冲突:
❯ git merge feature/2
Auto-merging ch1/demo/src/main/java/cn/icexmoon/learngit/demo/entity/Teacher.java
CONFLICT (content): Merge conflict in ch1/demo/src/main/java/cn/icexmoon/learngit/demo/entity/Teacher.java
Automatic merge failed; fix conflicts and then commit the result.
此时产生冲突的文件会被修改为:
@Data
public class Teacher {
private Long id;
private String name;
private String title;
private Integer age;
<<<<<<< HEAD
private School school;
=======
private ClassRoom classRoom;
>>>>>>> feature/2
}
这里<<<<<<< HEAD和=======之间的是当前分支的内容,=======和>>>>>>> feature/2之间的是将要合并的目标分支的内容。
可以通过 IDE 方便的解决冲突,比如 Idea:

也可以使用 git 调用电脑中的 diff 工具:
❯ git mergetool
This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
tortoisemerge emerge vimdiff nvimdiff
Merging:
ch1/demo/src/main/java/cn/icexmoon/learngit/demo/entity/Teacher.java
Normal merge conflict for 'ch1/demo/src/main/java/cn/icexmoon/learngit/demo/entity/Teacher.java':
{local}: modified file
{remote}: modified file
Hit return to start merge resolution tool (tortoisemerge):
我的电脑上安装了 TortoiseGit,因此调用了它的 diff 工具。
The End.

文章评论