首页 > 解决方案 > 向所有分支添加/提交文件

问题描述

假设我在一个分支上并且索引很脏。我对文件 x 进行了更改,并且还进行了一些其他更改。

有没有办法将文件 x 添加到所有现有分支?像这样的东西:

    #!/usr/bin/env bash
    current_branch="$(git rev-parse --abbrev-ref HEAD)"
    git add .
    git commit -am "added x"
    git fetch origin
    git for-each-ref --format='%(refname)' refs/heads  | while read b ; do
       git checkout "$b" 
       git checkout "$current_branch" -- x
    done
    git checkout "$current_branch";  # finally, check out the original branch again

所以基本上它会检查所有分支,然后检查文件 x..所以我需要在每个分支上提交 b 还是不提交?为什么不?

标签: gitgit-checkout

解决方案


所以基本上[我的循环]检查所有分支,然后检查文件x..所以我需要在每个分支b上提交还是不提交?为什么不?

答案是否定的。你几乎可以肯定确实需要一些提交。

请记住,分支名称实际上是一个指针,指向一个特定的提交。其中一个名称HEAD附加了名称1,所有其他名称都指向某个提示提交。让我们画出这些提交和名称的图片。假设有四个名称和三个这样的分支提示提交:

  T  <-U  <-X   <--name1 (HEAD)
 /
...  <-V  <-Y   <--name2, name3
  \
   W  <-Z   <-- name4

这里的大写字母代表一些实际的提交哈希 ID。


1更准确地说,它最多HEAD附有一个名字。如果HEADdetached,它直接指向某个提交。在这种情况下git rev-parse --abbrev-ref HEAD,只会打印HEAD. 检查这一点通常是明智的,但如果您在工作时这样做并且确定您没有处于分离的 HEAD 上,则没有必要。


您的第一步是:

current_branch="$(git rev-parse --abbrev-ref HEAD)"
git add .
git commit -am "added x"

当前的分支name1,它指向 commit X。第一行设置current_branchname1。您当前的提交是 commit X:此提交的文件存在于您的indexwork-tree中,因为您git checkout name1在最近的某个时间运行并且从 commit 填充了 index 和 work-tree X

您用于将当前目录或任何子目录中的所有git add .文件复制到索引中, 2以便它们可以被提交。这包括您刚刚在工作树中创建的这个新文件。您的索引现在可以提交了。x


2更准确地说,这会将 (a) 已在索引中或 (b) 未被忽略的所有此类文件从工作树复制到索引中。此处的 (a) 部分由 (b) 部分暗示——根据定义,索引中的文件不会被忽略——但值得强调。


然后,第三行执行git commit. (不清楚为什么要git commit -a与 一起使用git add .,但-a如果需要,会在其他一些目录中添加文件。git add --all假设 Git 2.0 或更高版本,您可以同样运行 ,并省略-a。)假设它成功,3现在有一个新的附加提交X, 并name1指向这个新的提交,所以图片现在应该如下所示:

  T--U--X--α   <-- name1 (HEAD)
 /
...--V--Y   <-- name2, name3
  \
   W--Z   <-- name4

(我用完了罗马字母,所以这是提交 alpha。)


3在整个过程中,我们假设一切正常,或者如果命令失败,则该失败是好的。


您脚本中的下一个命令似乎在这里没有任何功能:

git fetch origin

这将获得 Gitorigin所没有的新提交,并更新您的origin/*远程跟踪名称,但在此之后您不使用远程跟踪名称,那么为什么要在此时更新它们呢?

有问题的部分出现在循环中:

git for-each-ref --format='%(refname)' refs/heads  | while read b ; do
   git checkout "$b" 
   git checkout "$current_branch" -- x
   # proposed: git commit -m "some message"
done

首先,%(refname)输出将变为refs/heads/name1, refs/heads/name2,依此类推。 git checkout会将这些中的每一个都检查为分离的 HEAD,这不是您想要的。这很容易通过使用%(refname:short)which 省略refs/heads/部分来解决。

在我们这里的假设示例中,您将获得的名称是name1name2name3name4。因此,您将首先要求 Gitα再次提取提交——因为它已经存在,所以执行速度非常快——然后使用名称name1将文件提取x到索引和工作树中。那些也已经在那里了。

建议是添加git commit. 此特定操作git commit失败,并显示没有可提交的错误,在这种特殊情况下,这可能是您想要的:x在 branch 的提示提交中已经有一个具有正确内容的文件name1,即在 commit 中α

然后循环将继续git checkout name2,即,提交Y。这会将您的索引和工作树内容替换为从 commit 中提取的内容Y,并附HEAD加到 name name2。该行将从提交中git checkout name1 -- x提取文件到索引和工作树中,并且建议将进行新的提交,因此导致名称向前移动以指向这个新的提交。 请注意,名称继续指向 commit 让我们绘制新的提交,我们可以称之为(测试版):xαgit commitname2name3Yβ

    U--X--α   <-- name1 (HEAD)
   /
  T       β    <-- name2
 /       /
...--V--Y   <-- name3
  \
   W--Z   <-- name4

现在你的循环移动到name3,它仍然指向 commit Y,所以 Git 会将索引和工作树设置回它们刚才的方式,当你Y通过 name 签出提交时name2。Git 现在将像以前一样x从提交中提取文件,并进行另一个新的提交。α

这是事情变得非常有趣的地方!新提交与 commit 具有相同的β。它也有相同的作者和提交者。根据您构建消息的方式,它可能也具有与提交-m相同的日志消息β。如果 Git 在提交时使用的同一时间戳秒内进行此提交β,则新提交实际上是现有提交β并且一切都很好。

另一方面,如果 Git 花费了足够的时间使新提交获得不同的时间戳,则新提交与 commit 不同β。让我们假设这确实发生了,并且我们得到了 commit γ(gamma):

    U--X--α   <-- name1 (HEAD)
   /
  T       β    <-- name2
 /       /
...--V--Y--γ   <-- name3
  \
   W--Z   <-- name4

最后,循环将再次执行相同的过程 for name4,它当前指向提交Z,但最终将指向新的提交δ(增量):

    U--X--α   <-- name1 (HEAD)
   /
  T       β    <-- name2
 /       /
...--V--Y--γ   <-- name3
  \
   W--Z--δ   <-- name4

一般问题

当多个名称指向同一个底层提交时,就会出现一个问题。在这种情况下,您必须决定是否要以相同的方式调整所有名称 - 即,让两者都提前指向提交- 或者您是否想要它:name2name3β

  • 如果你确实想要这个,你必须确保你使用一些分支名称更新操作(git branch -f,git merge --ff-only等)来更新所有指向特定提交的名称。否则,您将依赖于在一秒钟内完成所有提交,以便时间戳将全部匹配。

  • 如果你想要这个——如果你需要名字来个性化,你必须确保你git commit的 s 至少相隔一秒,以便它们获得唯一的时间戳。

如果你确定你所有的名字都指向不同的提交,这个问题就会消失。

其他需要考虑的事情是:

  • 是否有任何名称指向某些具有名为 的文件的现有提交x如果是这样,您将从我们从提交中提取的内容中覆盖 (我们在当前分支上进行的第一次提交,在整个过程开始时。)xxα

  • 如果确实有任何名字x——肯定有,那就是我们最初所在的分支——那么它与commit 中的那个x 匹配α吗?如果是这样,git commit除非我们添加--allow-empty. 但在我们这里的特殊情况下,这可能是一件好事,因为这意味着我们可以避免使用特殊情况来测试是否$b匹配$current_branch

  • 你真的所有的分支名称......好吧,不清楚如何称呼这些东西。看看我们所说的“分支”到底是什么意思?详情。我们称它们为发展线。你有每个这样的行的(本地)分支名称吗?这可能就是您git fetch origin在这里的原因:这样您就可以累积所有origin/*远程跟踪名称的更新。

    如果您有origin/feature1and origin/feature2,此时您可能想要创建(本地)分支名称feature1and feature2,以便将文件添加x到这两个分支的提示提交中。您需要分支名称来记住新创建的提交。但是仅仅使用git for-each-refoverrefs/heads不会达到预期的结果:您可能想要使用git for-each-refover ,将名称减去部分与所有名称(当然减去部分)一起refs/remotes/origin累积到一个集合中,并将它们用作分支名称。origin/refs/headsrefs/heads/


推荐阅读