向所有分支添加/提交文件



假设我在一个分支上,而索引是脏的。我对文件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上提交,还是不提交?为什么不呢?

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

答案是否定和肯定。几乎可以肯定,您确实需要一些提交。

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

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

W  <-Z   <-- name4

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


1更准确地说,最多一个名称上附加了HEAD。如果HEAD分离,它将直接指向某个提交。在这种情况下,git rev-parse --abbrev-ref HEAD将仅打印HEAD。检查一下这一点通常是明智的,尽管如果你在工作时这样做,并且确信自己没有处于独立状态,那么就没有必要了。


您的第一步是:

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

您的当前分支name1,它指向提交X。第一行将current_branch设置为name1。您的当前提交是提交X:此提交的文件存在于索引工作树中,因为您在最近的某个时间点运行了git checkout name1,并从提交X填充了索引和工作树。

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


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


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

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

W--Z   <-- name4

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


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


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

git fetch origin

这将获得origin上的Git没有的新提交,并更新您的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/name1refs/heads/name2等。git checkout将把它们中的每一个作为分离的HEAD进行检查,这不是您想要的。使用省略了refs/heads/部分的%(refname:short)可以很容易地解决这个问题。

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

建议增加git commit。这个特定的git commit会失败,并出现一个错误,说明没有什么要提交的,在这种特殊情况下,这可能是您想要的:在分支name1的提示提交中,即在提交α中,已经有一个文件x具有正确的内容。

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

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

W--Z   <-- name4

现在,您的循环移到name3,它仍然指向提交Y,因此Git将把索引和工作树设置回刚才的样子,当时您已经通过名称name2签出了提交Y。Git现在将像以前一样从提交α中提取文件x,并进行另一次新的提交。

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

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

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

W--Z   <-- name4

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

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

W--Z--δ   <-- name4

一般问题

当多个名称指向相同的基础提交时,就会出现一个问题。在这种情况下,您必须决定是要以相同的方式调整所有名称,即name2name3都前进到提交β的点,还是不需要:

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

  • 如果您不希望这样做——如果您需要个性化名称,那么您必须确保git commit至少相隔一秒,这样它们才能获得唯一的时间戳。

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

其他需要考虑的是:

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

  • 如果任何名称都有x——当然有,那就是我们最初所在的分支——那么x是否与提交α中的名称匹配?如果是这样,除非我们添加--allow-empty,否则所提出的git commit将失败。但在我们这里的特殊情况下,这可能是一件好事,因为这意味着我们可以避免使用特殊情况来测试$b是否与$current_branch匹配。

  • 你真的所有…都有分支名称吗。。。嗯,现在还不清楚该怎么称呼这些东西。看看我们所说的";分支"?详细信息。让我们称之为开发线。你有每条这样的线路的(当地)分支机构名称吗?这可能就是为什么您在这里有git fetch origin:这样您就可以累积所有origin/*远程跟踪名称的更新。

    如果您有origin/feature1origin/feature2,此时您可能希望创建(本地)分支名称feature1feature2,以便将文件x添加到这两个分支的提示提交中。您需要分支名称来记住新创建的提交。但是,仅仅使用git for-each-ref而不是refs/heads并不能实现所需的结果:您可能希望使用git for-each-ref而不是refs/remotes/origin,将减去origin/部分的名称与所有refs/heads名称(当然也减去refs/heads/部分)一起累积到一个集合中,并使用这些作为分支名称。

for remote in git branch -r; do git checkout  —track $remote ; <make your changes> ;  git add . ; git commit -a -m “change commit” ; git push origin $remote ; done

最新更新