向所有分支添加/提交文件 [英] Add / commit a file to all branches
问题描述
说我在一个分支上,索引很脏.我对文件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
执行相同的过程,当前指向δ
(增量):
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 commit
s 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
中覆盖 thisx
在当前分支上,在整个过程的开始进行.)
Do any of the names points to some existing commit that does have a file named
x
? If so, you'll overwrite thisx
from thex
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屋!