向所有分支添加/提交文件 [英] Add / commit a file to all branches

查看:95
本文介绍了向所有分支添加/提交文件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

说我在一个分支上,索引很脏.我对文件x进行了更改,并且也进行了其他一些更改.

Say I am on a branch and the index is dirty. I made changes to a file x, and I have some other changes too.

是否可以将文件x添加到所有现有分支?像这样:

Is there a way to add file x to all existing branches? Something like this:

    #!/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..so,我需要在每个分支b上提交还是不提交?为什么不呢?

so basically it checks out all branches and then checks out the file x..so I need to commit on each branch b or no? why not?

推荐答案

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

so basically [my loop] checks out all branches and then checks out the file x..so I need to commit on each branch b or no? why not?

答案是否定的.您几乎可以肯定需要 some 提交.

The answer is both no and yes. You almost certainly do need some commits.

请记住,分支 name 实际上是一个 pointer ,指向一个特定的提交.这些名称之一的名称 HEAD 附加在 上, 1 ,所有其他名称均指向某个尖端提交.让我们画出这些提交和名称的图片.假设有四个名称,以及三个这样的分支提示提交:

Remember, a branch name is in effect a pointer, pointing to one specific commit. One of these names has the name HEAD attached to it,1 and all the other names point to some tip commit. Let's draw a picture of these commits and names. Let's say that there are four names, and three such branch-tip commits:

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

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

The uppercase letters here stand in for some actual commit hash IDs.

1 更准确地说,最多一个名称附加了 HEAD .如果 HEAD detached ,则它直接指向某个提交.在这种情况下, git rev-parse --abbrev-ref HEAD 只会打印 HEAD .通常,进行检查是很明智的,尽管如果您在工作时正在执行此操作,并且可以确定自己不在独立的HEAD上,则没有必要.

1More precisely, at most one name has HEAD attached to it. If HEAD is detached, it points directly to some commit. In this case git rev-parse --abbrev-ref HEAD will just print HEAD. It's often wise to check for this, though if you're doing this as you work and know for sure that you are not on a detached HEAD, there's no need.

您的第一步是:

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

您的当前分支 name1 ,它指向提交 X .第一行将 current_branch 设置为 name1 .您的当前提交是提交 X :该提交的文件存在于您的 index 和您的工作树中,事实是您在最近某个时候运行了 git checkout name1 ,并通过提交 X 填充了索引和工作树.

Your current branch is name1, which points to commit X. The first line sets current_branch to name1. Your current commit is commit X: this commit's files exist in your index and in your work-tree, due to the fact that you ran git checkout name1 at some point in the recent past and that filled in both the index and the work-tree from commit X.

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

You use git add . to copy all files in the current directory or any sub-directory into the index,2 so that they can be committed. This includes this new file x that you just created in your work-tree. Your index is now ready for committing.

2 更准确地说,这会将所有这样的文件从工作树复制到索引中,这些文件(a)已在索引中,或(b)被忽略.(b)部分暗示了这里的(a)部分-索引中的文件在定义上并没有被忽略-但值得强调.

2More precisely, this copies into the index, from the work-tree, all such files that are (a) already in the index, or (b) not ignored. Part (a) here is implied by part (b)—a file that's in the index is by definition not ignored—but it is worth emphasizing.

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

The third line, then, does the git commit. (It's not clear why you use git commit -a along with git add ., but -a would add files in some other directories if needed. You might equally run git add --all, assuming Git 2.0 or later, and leave out the -a.) Assuming it succeeds,3 there's now a new additional commit after X, and name1 points to this new commit, so the picture should now look like:

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

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

(I ran out of Roman letters so this is commit alpha.)

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

3All throughout this, we're assuming everything works, or that if a command fails, that this failure is good.

您的脚本中的下一个命令似乎在这里不起作用:

Your next command in your script seems to have no function here:

git fetch origin

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

This will obtain new commits that the Git at origin has that you don't, and update your origin/* remote-tracking names, but you do not use the remote-tracking names after this point, so why update them at this point?

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

The problematic parts occur here, in the loop:

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)可以轻松解决此问题,该代码省略了 refs/heads/部分.

First, the %(refname) output is going to read refs/heads/name1, refs/heads/name2, and so on. git checkout will check each of these out as a detached HEAD, which is not what you want. That's easily fixed by using %(refname:short) which omits the refs/heads/ part.

在我们的假设示例中,您将获得的名称是 name1 name2 name3 name4 .因此,您将首先要求Git再次提取提交α(由于它已经存在,因此执行起来非常快),然后使用名称 name1 提取文件 x 进入索引和工作树.这些也已经在那里.

The names you will get, in our hypothetical example here, are name1, name2, name3, and name4. So you will start by asking Git to extract commit α again—which goes very fast since it's already there—and then use the name name1 to extract file x into the index and work-tree. Those, too, are already there.

建议是添加 git commit .这个特定的 git commit 失败,并显示一条错误,指出没有要提交的内容,在这种情况下可能就是您想要的:已经有一个文件x 在分支 name1 的尖端提交中具有正确的内容,即在提交α中.

The proposal is to add git commit. This particular git commit would fail with an error saying that there is nothing to commit, which in this particular case is probably what you want: there is already a file x with the correct content in the tip commit of branch name1, i.e., in commit α.

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

The loop would then go on to git checkout name2, i.e., commit Y. This would replace your index and work-tree contents with those extracted from commit Y, and attach HEAD to the name name2. The git checkout name1 -- x line would extract file x from commit α into the index and work-tree, and the proposed git commit would make a new commit and hence cause the name name2 to move forward to point to this new commit. Note that the name name3 continues to point to commit Y. Let's draw in the new commit, which we can call β (beta):

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

现在,您的循环继续前进到 name3 ,它仍指向提交 Y ,因此Git会将索引和工作树设置回原来的状态以前,当您通过名称 name2 提交了 Y 时已签出.Git现在将像以前一样从提交α中提取文件 x ,并进行另一个新提交.

Now your loop moves on to name3, which still points to commit Y, so Git will set the index and work-tree back to the way they were a moment ago, when you had commit Y checked out via the name name2. Git will now extract file x from commit α just as before, and make another new commit.

这是非常有趣的地方!新提交与提交β具有相同的 tree .它还具有相同的作者和提交者.根据您构造 -m 消息的方式,它可能也具有与提交β相同的 log消息.如果Git在进行提交β时所用的时间戳第二秒中进行了提交,则新的提交实际上是现有的提交β一切都很好.

This is where things get very interesting! The new commit has the same tree as commit β. It also has the same author and committer. It may, depending on how you construct your -m message, have the same log message as commit β as well. If Git makes this commit in the same time stamp second that it used when making commit β, the new commit is actually the existing commit β and all is well.

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

On the other hand, if Git takes enough time that the new commit gets a different time stamp, the new commit is different from commit β. Let's assume that this does happen, and that we get commit γ (gamma):

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

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

Finally, the loop will do this same process yet again for name4, which currently points to commit Z but will end up pointing to a new commit δ (delta):

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

一般问题

当一个以上的名称指向 same 底层提交时,就会出现一个问题.在这种情况下,您必须决定是否要以相同的方式调整所有名称,即具有 name2 name3 都指向提交β或是否想要它:

The general problems

One issue here comes about when more than one name points to the same underlying commit. In this case you must decide whether you want to adjust all names in the same way—i.e., to have name2 and name3 both advance to point to commit β—or whether you don't want it:

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

  • If you do want this, you must make sure that either you use some branch-name-updating operation (git branch -f, git merge --ff-only, etc) to update all the names that point to the specific commit. Otherwise you are relying on getting all the commits done within one second, so that the time stamps will all match.

如果您想要此功能(如果您需要按名称进行个性化设置),则必须确保您的 git commit 接受相隔至少一秒钟,以便它们获得唯一的时间戳.

If you don't want this—if you need the names to individualize, as it were—you must make sure that your git commits take place at least one second apart, so that they get unique time stamps.

如果您确定所有名称都指向不同提交,则此问题将消失.

If you're sure that all your names point to different commits, this problem goes away.

要考虑的其他事情是这些:

The other things to think about are these:

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

  • Do any of the names points to some existing commit that does have a file named x? If so, you'll overwrite this x from the x we extract from commit α (the first commit we make, on the current branch, at the start of the entire process.)

如果任何名称​​ do 具有 x (当然有,那是我们最初所在的分支),那么会 x match 提交α的那个?如果是这样,除非我们添加-allow-empty ,否则建议的 git commit 将失败.但是在我们这里的特殊情况下,这可能是一件好事,因为这意味着我们可以避免使用特殊情况来测试 $ b 是否与 $ current_branch 相匹配.

If any names do have x—one certainly does, that being the one that is the branch we were on in the first place—then does that x match the one in commit α? If so, the proposed git commit will fail unless we add --allow-empty. But in our particular case here, that's probably a good thing, since it means we can avoid having a special case to test whether $b matches $current_branch.

您实际上是否为所有……都拥有分支名称...好吧,尚不清楚该怎么称呼这些东西.有关详细信息,请参见分支"到底是什么意思?.我们称它们为"发展路线".每个这样的行都有(本地)分支名称吗?这可能就是为什么在这里使用 git fetch origin 的原因:这样您就可以累积所有 origin/* 远程跟踪名称的更新.

Do you actually have branch names for all the ... well, it's not clear what to call these things. See What exactly do we mean by "branch"? for details. Let's call them lines of development. Do you have a (local) branch name for each such line? This might be why you have git fetch origin in here: so that you can accumulate an update on all your origin/* remote-tracking names.

如果您具有 origin/feature1 origin/feature2 ,则此时可能要创建(本地)分支名称 feature1 feature2 ,以便将文件 x 添加到这两个分支的尖端提交中.您将需要分支名称来记住新创建的提交.但是,仅在 refs/heads 上使用 git for-each-ref 并不能达到预期的结果:您可能想对 git for-each-ref refs/remotes/origin 上的code>,将减去 origin/部分的名称与所有 refs/heads 名称(减去/heads/部分),并使用那些作为分支名称.

If you have origin/feature1 and origin/feature2, you might, at this point, want to create (local) branch names feature1 and feature2, so as to add file x to the tip commits of these two branches. You'll need branch names to remember the newly created commits. But just using git for-each-ref over refs/heads will not accomplish the desired result: you might want to use git for-each-ref over refs/remotes/origin, accumulate the names minus the origin/ part into a set along with all the refs/heads names (minus the refs/heads/ parts of course), and use those as the branch names.

这篇关于向所有分支添加/提交文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆