【问题标题】:What precisely happens when deleting a branch in Git在 Git 中删除分支时会发生什么
【发布时间】:2016-01-19 16:03:02
【问题描述】:

我一直在寻找这个问题的答案有一段时间了,但我仍然不能完全确定它的答案。 我发现的有关删除分支的大多数信息都只是手册中所说的内容的副本(例如here on SO)。 我认为一个核心问题是我不确切知道 Git 中的分支是什么(尽管有很多文章声称对此进行了解释)。 我发现的唯一有用的信息是在this SO answer 和使用 git 安装的文档。

我的问题:

当我运行git branch -d BRANCH_NAME时,

  1. 幕后发生了什么?
  2. 外部发生了什么变化,即我与存储库的交互如何变化?
  3. 这些变化中的任何一个都可以被视为历史上的变化吗?

还有git branch -D BRANCH_NAME 的相同子问题。

我目前的理解:

首先我对一个分支的看法: 根据上下文,术语分支指的是指向某个提交的指针(严格称为分支头),或导致该提交的提交列表

对于git branch -d BRANCH_NAME,我认为会发生什么(但我不确定):

  1. 指向分支头的指针被移除,不再存在
  2. 我再也看不到任何列表中的分支,也无法再切换到它或从它分支(尽管我想我可以在该提交处创建一个新分支,以有效地实现这些目标)
  3. 可能不是:提交仍然存在,只是不再用分支名称标记?

我认为会发生什么,git branch -D BRANCH_NAME

  1. 指向分支头的指针被删除,所有不在其他分支上的提交也被删除
  2. 我在任何列表中都看不到分支,我无法再切换到它或从它分支,或以任何方式检索代码
  3. 是的:分支中的提交丢失了

【问题讨论】:

    标签: git branch git-branch


    【解决方案1】:

    无论你是使用git branch -d 还是git branch -D 删除,git 都不会删除提交,而只会删除分支引用。请继续阅读以了解这意味着什么。

    首先,我们将设置一个简单的演示历史记录。

    $ touch initial ; git add initial ; git commit -m 'Initial commit'
    [master (root-commit) 2182bb2] Initial commit
     1 file changed, 0 insertions(+), 0 deletions(-)
     create mode 100644 initial
    
    $ git checkout -b mybranch
    Switched to a new branch 'mybranch'
    

    此时,mastermybranch 都指向同一个 提交,我们至少可以通过两种方式进行验证。

    $ git lola
    * 2182bb2 (HEAD -> mybranch, master) Initial commit
    

    请注意,git lola 是一个非标准但非常有用的别名, 相当于

    $ git log --graph --decorate --pretty=oneline --abbrev-commit --all
    * 2182bb2 (HEAD -> mybranch, master) Initial commit
    

    mybranch 上创建新提交后,我们将查看另一种方式。

    $ touch mybranch ; git add mybranch ; git commit -m 'My branch'
    [mybranch 7143aa4] My branch
     1 file changed, 0 insertions(+), 0 deletions(-)
     create mode 100644 mybranch
    

    这样做之后,我们确实在多个分支上进行了多次提交。

    $ git lola
    * 7143aa4 (HEAD -> mybranch) My branch
    * 2182bb2 (master) Initial commit
    

    我们现在可以窥探一下 git 如何在后台实现这一点。

    $ ls -R .git/refs
    .git/refs:
    heads  tags
    
    .git/refs/heads:
    master  mybranch
    
    .git/refs/tags:
    

    很好奇有与我们的分支同名的文件。 往里面看,我们看到了

    $ cat .git/refs/heads/master .git/refs/heads/mybranch
    2182bb2d5a0a7f57d0b74e95d37e208dac41f95b
    2182bb2d5a0a7f57d0b74e95d37e208dac41f95b
    

    所以 git 将 refs 实现为名称匹配的特定位置的文件 包含 SHA1 的分支名称 某些提交的哈希值。请注意,git lola (2182bb2) 输出中的缩写散列是上述 cat 输出的前导前缀。

    将 git refs 视为简单的指针,为存储库历史记录中的特定提交提供人类可读的名称。

    现在,如果我们切换回 master 和 zap mybranch

    $ git checkout master ; git branch -D mybranch
    Switched to branch 'master'
    Deleted branch mybranch (was 7143aa4).
    

    我们看到 ref 不见了

    $ ls -R .git/refs
    .git/refs:
    heads  tags
    
    .git/refs/heads:
    master
    
    .git/refs/tags:
    

    但提交仍然存在。

    $ git show --pretty=oneline 7143aa4
    7143aa477735382e7a0ed11c9e4b66c1f27583df My branch
    diff --git a/mybranch b/mybranch
    new file mode 100644
    index 0000000..e69de29
    

    如果你想返回mybranch,你只需要运行

    $ git checkout -b mybranch 7143aa4
    Switched to a new branch 'mybranch'
    

    $ git branch mybranch 7143aa4
    

    取决于,正如它们各自输出的差异所表明的那样, 是否要切换到分支。在后一种情况下, 你留在当前分支的地方,git lola 看起来像

    $ git lola
    * 7143aa4 (mybranch) My branch
    * 2182bb2 (HEAD -> master) Initial commit
    

    是的,即使在您删除了使它们保持活动状态的指针之后,您的提交也会短暂停留。这在意外删除的情况下非常有用。另见git refloggit gc


    请注意,您的存储库中的 SHA1 哈希值会有所不同 因为您的姓名和电子邮件地址至少会有所不同 从我用过的。

    为了完整起见,-d-D 之间的区别是小写版本稍微安全一些。

    -d
    --delete

    删除一个分支。如果没有使用--track--set-upstream 设置上游,则该分支必须完全合并到其上游分支中,或者在HEAD 中。

    -D

    --delete --force 的快捷方式。

    -f
    --force

    如果 branchname 已经存在,则将 branchname 重置为 startpoint。没有-f git 分支拒绝更改现有分支。结合-d(或--delete),允许删除分支,无论其合并状态如何……

    【讨论】:

    • 我没有比花一小时浏览所有文件夹并打开 .git 目录中的所有文件以轻松了解它的工作原理更好的建议了。这真的很简单而且做得很好。要探索“对象”子目录,请使用 git cat-file -p 后跟 sha1。它确实有助于每天使用以了解 git 的工作原理......
    • 需要注意的是 git 不会永远保留提交。只有在有一些引用方式(直接或间接)时,提交才会无限期地存在。
    【解决方案2】:

    在分支移除后,提交仍然存在,直到下一次垃圾回收。 git branch -d(和-D)打印缩写提交哈希,您可以将其用作git loggit checkoutgit branch的参数,这使您可以恢复已删除的分支。

    -d-D 之间的唯一区别是 git branch -d 不会让您删除未合并的分支(即可能会丢失提交)。

    【讨论】: