实际开发中,会在当前开发线路上拉出另外的开发线进行开发,比如软件功能已经比较稳定的话,在后续功能的开发过程中,就很可能会拉出独立的支线进行开发,待功能开发完毕后,再将该直线合入稳定的主线中。
当然实际开发中,会有不同的分支策略,这就需要使用到Git的分支。
分支是开发中启动一条单独的开发线的基本方法,使开发过程能够在多个方向上同时进行,并可能产生项目的不同版本。
版本库中的默认分支名称为master,大多数开发人员在该分支上保持版本库中最强大和最可靠的开发线。同时Git还支持给分支命名,分支命名需要遵守一些规则:
这些分支的命名规则是由git check-ref-format底层命令强制检测的,以确保分支名容易输入,且在.git目录和脚本中作为一个文件名是可用的。
从这里来看,分支和标签看起来有点相似。但标签和分支用于不同的目的,标签是一个静态的名字,其不随时间的推移而改变。而分支是动态的,并且随着每次提交而移动。
同时分支和标签可以使用相同的名称来命名,如果这样,就需要使用索引名全程来区分。如果不能正确地区分两者使用的区别,就应该避免使用相同的名称。
在任何给定的时间中,版本库中可能存在不同的分支,但是最多只有一个当前的或活动的分支。活动分支决定在工作目录中检出哪些文件。
默认情况下,master分支是活动分支,但可以把任何分支设置为活动分支。
每个分支在一个特定的版本库中必须有唯一的名字,该命名始终指向该分支上最近的提交。
一个分支的最近提交称之为该分支的头部head。
Git不会保持分支的起源信息。同时,分支名随着分支上新的提交而增量地向前移动,因此旧提交必须通过散列值或相对名来索引,而如果某个特定提交是特殊的,需要被标记,比如特定的时间点,项目的特定状态等,此时可以使用标签名进行标记。
而一个分支开始时的原始提交没有显示定义,因此该分支的原始提交可以通过从分叉出的新分支的源分支名使用算法找到:
git merge-base original-branch new-branch
因为分支名始终指向该分支上最近提交的版本,还可以将分支名当成一个指向特定提交的指针。同时分支又不仅仅包含该分支最近的提交,其包含了从项目开始到该分支最近提交的所有路径,该路径同时也可能包含其它分支。
每一个分支名和分支上提交的内容一样,都会存放在本地版本库中,而当将版本库提交给他人后,也可以发布或选择使用任意数量的分支和相关的可用提交。而如果复制版本库,分支名和分支名上的开发都将是复制版本库的副本的一部分。
创建分支需要基于版本库中现有的提交,因此需要指定哪次提交作为新分支的起始。
同时分支的生命周期也取决于用户的操作。
如果确定从哪个提交开始新分支时,只需要使用git branch命令:
git branch branch [starting-commit]
如果没有指定starting-commit,就默认为当前分支上的最近提交。
需要注意的是,git branch命令只是把分支名引进版本库,并没有改变工作目录使用新的分支。即没有工作目录文件发生变化,没有分支环境发生变化,也没有做出新的提交。也就是说,该命令只是在给定的提交上创建了一个命名的分支,而如果想要使用该分支,需要切换分支。
git branch命令可以列出版本库中的分支名:
- $ git branch
- * master
- other
这里该命令会显示两个特性分支,当前已检出的工作目录中的分支用*标记,另外一个分支名为other。
如果没有额外的参数,则只列出版本库中的特性分支,可以从-r选项列出远程追踪分支,用-a把特性分支和远程分支都列出来。
- $ git branch -a
- * master
- remotes/origin/HEAD -> origin/master
- remotes/origin/main
- remotes/origin/maint
- remotes/origin/master
- remotes/origin/next
- remotes/origin/seen
- remotes/origin/todo
git show-branch命令提供比git branch更为详细的输出,按时间以递序的形式列出对一个或多个分支有贡献的提交。与git branch一样,没有额外参数就列出特性分支,-r显示远程分支,-a显示所有分支。
- $ git show-branch
- * [master] Merge branch 'other'
- ! [other] commit file4
- --
- - [master] Merge branch 'other'
- *+ [other] commit file4
上边的输出被--分为两部分。上方列出了分支名,并用[]表示,每行分支名用!或*表示,*表示当前活动分支。同时每个分支都列出该分支最近提交的日志信息。
下部分表示每个分支的提交矩阵,同样每个提交后跟着该提交中的日志信息。如果有+,*,-在分支的列中,对应的提交就会在该分支中显示。+表示提交在一个分支中,*表示存在于当前活动分支的提交,-表示合并提交。
git show-branc命令会遍历所有显示的分支上的提交,直到其最近的共同提交处停止。在第一个共同提交处停止是默认启发策略,该行为也是合理的,到达这里也就能够通过上下文了解分支之间的相互关系。而如果想要更多提交历史记录,则可以使用--more选项来指定在共同提交后看到多少个额外的提交。
同时git show-branch还可以使用一组分支名来作为参数,以限制这些分支的历史记录显示。
- $ git show-branch master other
- * [master] Merge branch 'other'
- ! [other] commit file4
- --
- - [master] Merge branch 'other'
- *+ [other] commit file4
-
- $ git show-branch master
- [master] Merge branch 'other'
对比上面的显示差异,就可以看出两者的不同,同时该命令还支持通配符匹配,这样可以对比一组具有相同特征的分支的差异。
之前提到如何创建分支,而创建分支并不意味着可以直接在新分支上工作,这需要切换分支。
切换分支或要在不同的分支上开始工作,就需要git checkout命令,给定一个分支名,就会使该分支变成当前活动分支,它会改变工作目录以匹配给定分支的状态。
首先在空目录下执行下面的代码:
- git init
- echo abc > file1
- git add file1
- git commit -m "commit file1"
- echo abcd > file2
- git add file2
- git commit -m "commit file2"
- git branch other HEAD^
- git checkout other
- echo abcde > file3
- git add file3
- git commit -m "commit file3"
- echo abcdef > file4
- git add file4
- git commit -m "commit file4"
- git checkout master
- git merge master other
使用git branch查看:
- $ git branch
- * master
- other
此时存在两个分支,当前活动分支为master。
如果想要在other中工作,就可以执行:
- $ git checkout other
- Switched to branch 'other'
-
- $ git branch
- master
- * other
此时就切换到了other分支。
- $ git checkout master
- Switched to branch 'master'
-
- $ ls
- file1 file2 file3 file4
-
- $ git checkout other
- Switched to branch 'other'
-
- $ ls
- file1 file3 file4
而从上面的代码也可以看出,此时的工作命令已经发生了更新,以反映新分支的状态和内容。改变分支的影响主要有:
在检出分支时,Git会删除本地工作目录中数据的删除和修改,而工作目录中未被追踪的文件和目录则不会发生删除或修改。但是如果一个文件的本地修改不同于新分支上的变更,此时检出就会失败。
此时在master分支上,修改文件file2,然后检出other分支,此时会出现错误:
- $ git branch
- * master
- other
-
- $ echo 1111 > file2
-
- $ git checkout other
- error: Your local changes to the following files would be overwritten by checkout:
- file2
- Please commit your changes or stash them before you switch branches.
- Aborting
这是因为文件file2只存在于master分支,而不存在于other分支,这会导致检失败。
- $ git show other:file2
- fatal: path 'file2' exists on disk, but not in 'other'
-
- $ git show master:file2
- abcd
此时在master分支上,修改文件file1,然后检出other分支,则为:
- $ git branch
- * master
- other
-
- $ echo 1111 > file1
-
- $ git checkout other
- Switched to branch 'other'
- M file1
-
- $ git show master:file1
- abc
-
- $ git show other:file1
- abc
-
- $ git branch
- master
- * other
-
- $ cat file1
- 1111
可见此时该修改对于两个分支都是生效的,并同时修改了检出后分支的对应文件。
此时在master分支上,新建文件file5,然后检出other分支,则为:
- $ echo 1111 > file5
-
- $ git checkout other
- Switched to branch 'other'
-
- $ ls
- file1 file3 file4 file5
-
- $ git status
- On branch other
- Untracked files:
- (use "git add <file>..." to include in what will be committed)
- file5
-
- nothing added to commit but untracked files present (use "git add" to track)
-
此时文件file5出现在了分支other的工作目录,并显示为未追踪状态。
在看最后一种情况前,首先执行下述代码:
- git checkout other
- echo 1111 > file5
- git add file5
- git commit -m "commit file5"
此时文件file5只存在于分支other,而不在master。然后在master分支上,新建文件file5,然后检出other分支,则为:
- $ git branch
- * master
- other
-
- $ echo 2222 > file5
-
- $ git checkout other
- error: The following untracked working tree files would be overwritten by checkout:
- file5
- Please move or remove them before you switch branches.
- Aborting
-
- $ git show master:file5
- fatal: path 'file5' exists on disk, but not in 'master'
-
- $ git show other:file5
- 1111
这里也发生了错误,即master分支上未追踪的文件检出到other分支时,遇到同名文件,两者修改不同,便会拒绝检出。
这里可以总结为:
之前提到,活动分支工作目录的当前状态如果与检出分支相冲突,就需要将活动分支工作目录的修改和检出分支的文件合并。
因为活动分支和检出分支共有文件,在检出时会直接将修改带入到检出分支,这里主要看一下后两种情况。
这里的合并操作主要是使用git checkout命令的-m选项。
比如上文提到的第二种情况,会变为:
- $ git branch
- * master
- other
-
- $ echo 1111 > file2
-
- $ git checkout -m other
- warning: LF will be replaced by CRLF in file2.
- The file will have its original line endings in your working directory
- Switched to branch 'other'
- A file2
-
- $ git branch
- master
- * other
-
- $ cat file2
- 1111
此时,在活动分支修改的文件直接合并到检出分支。
比如上文提到的第三种情况,会变为:
- $ git branch
- * master
- other
-
- $ echo 2222 > file5
-
- $ git checkout -m other
- Switched to branch 'other'
- Already up to date!
-
- $ git branch
- master
- * other
-
- $ cat file5
- 1111
-
- $ git show master:file5
- fatal: path 'file5' exists on disk, but not in 'master'
-
- $ git show other:file5
- 1111
这里的现象却并不是想象的那样,修改并没有合并,而是other分支上的文件内容。
这些差异也说明了:在检出分支时,最好能够保证当前活动分支是干净的,然后再进行检出。
之前提到的创建分支和检出分支是分步操作的,Git也可以在创建分支的同时进行检出,此时需要使用git checkout的-b选项。
- $ git checkout -b temp
- Switched to a new branch 'temp'
-
- $ git branch
- master
- other
- * temp
这里直接创建了分支temp,并切换到了该分支。
通常情况下,可以直接指出分支名检出分支的头部。也就是说,默认情况下,git checkout会改变期望的分支头部。
同时,也可以检出任何提交,而不仅仅限于分支名,此时Git就会自动创建一种匿名分支,成为分离的HEAD(detached HEAD)。在下面的情况下,Git会创建一个分离的HEAD:
比如对于git源码:
- $ git branch
- * master
-
- $ git checkout v1.6.0
- Updating files: 100% (4338/4338), done.
- Note: switching to 'v1.6.0'.
-
- You are in 'detached HEAD' state. You can look around, make experimental
- changes and commit them, and you can discard any commits you make in this
- state without impacting any branches by switching back to a branch.
-
- If you want to create a new branch to retain commits you create, you may
- do so (now or later) by using -c with the switch command. Example:
-
- git switch -c <new-branch-name>
-
- Or undo this operation with:
-
- git switch -
-
- Turn off this advice by setting config variable advice.detachedHead to false
-
- HEAD is now at ea02eef096 GIT 1.6.0
-
- $ git branch
- * (HEAD detached at v1.6.0)
- master
-
- $ git checkout -b temp
- Switched to a new branch 'temp'
-
- $ git branch
- master
- * temp
刚开始只有master分支,然后检出标签v1.6.0,此时的分支多了(HEAD detached at v1.6.0),然后在该标签处创建新分支temp,此时的分离分支就变为了temp分支。
而如果创建分支后,觉得分支名命名的的不好,需要修改,或者如果该分支在后来的开发中觉得没有必要就可以删除该分支,此时通过git branch的-d选项。
- $ git branch
- master
- other
- * temp
-
- $ git branch -d temp
- error: Cannot delete branch 'temp' checked out at 'C:/Users/wood/Desktop/GIT/tmp'
-
- $ git checkout master
- Switched to branch 'master'
-
- $ git branch -d temp
- Deleted branch temp (was c7cf5fb).
-
- $ git branch
- * master
- other
虽然可以删除分支,但却不能在活动分支上删除活动分支。
因为master一般是主分支,会一直存在,可以在master上对其它分支进行删除。
而由于Git不会保持任何形式的关于分支名创建,移动,操作,合并或删除的历史记录,因此某个分支被删除了,该分支就没有了。
同时Git会删除不再被引用的提交和不能从某些命名的引用(分支名或标签名)可达的提交。如果想要保留这些提交,就必须将之合并到不同的分支,为其创建一个分支,或创建标签指向。否则没有它们的引用和提交,blob就不可达,会被git gc工具当作垃圾回收。
因此,删除分支需要在明确删除的后果之后进行操作。
而意外删除分支后,可以使用git reflog或git fsck等命令进行恢复。