Git版本控制管理——分支

实际开发中,会在当前开发线路上拉出另外的开发线进行开发,比如软件功能已经比较稳定的话,在后续功能的开发过程中,就很可能会拉出独立的支线进行开发,待功能开发完毕后,再将该直线合入稳定的主线中。

当然实际开发中,会有不同的分支策略,这就需要使用到Git的分支。

分支是开发中启动一条单独的开发线的基本方法,使开发过程能够在多个方向上同时进行,并可能产生项目的不同版本。

分支名

版本库中的默认分支名称为master,大多数开发人员在该分支上保持版本库中最强大和最可靠的开发线。同时Git还支持给分支命名,分支命名需要遵守一些规则:

  • 可以使用/创建分层的命名方案,但该分支名不能以/结尾
  • 分支名不能以-开头
  • 以/分割的组件不能以.开头
  • 分支名的任何地方不能包含两个连续的点..
  • 分支名不能包含任何空格或其它空白字符
  • 分支名中不能包含具有特殊含义的字符,包括~,^,:,?,*,[
  • 分支名中不能包含ASCII码控制字符

这些分支的命名规则是由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,并切换到了该分支。

分离HEAD分支

通常情况下,可以直接指出分支名检出分支的头部。也就是说,默认情况下,git checkout会改变期望的分支头部。

同时,也可以检出任何提交,而不仅仅限于分支名,此时Git就会自动创建一种匿名分支,成为分离的HEAD(detached HEAD)。在下面的情况下,Git会创建一个分离的HEAD:

  • 检出的提交不是分支的头部
  • 检出一个追踪分支
  • 检出标签引用的提交
  • 启动git bisect操作
  • 使用git submodule update命令

比如对于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等命令进行恢复。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值