假设我在分支上并且索引很脏。我对文件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..so我需要提交每个分支b或否?为什么不?
所以基本上[我的循环]检查所有分支,然后检出文件x..so我需要在每个分支b或不提交?为什么不?
答案是否定和是。你几乎肯定需要一些提交。
请记住,分支名称实际上是指向一个特定提交的指针。其中一个名称附加了名称HEAD
,1和所有其他名称指向一些提示。让我们画出这些提交和名称的图片。假设有四个名称和三个这样的分支提示:
T <-U <-X <--name1 (HEAD)
/
... <-V <-Y <--name2, name3
\
W <-Z <-- name4
这里的大写字母代表一些实际的提交哈希ID。
准确地说,最多一个名字附有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 -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。)
在整个过程中,我们假设一切正常,或者如果命令失败,那么这种失败是好的。
脚本中的下一个命令似乎没有任何功能:
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/name1
,refs/heads/name2
等。 git checkout
将把这些作为一个独立的HEAD进行检查,这不是你想要的。通过使用省略%(refname:short)
部分的refs/heads/
可以很容易地解决这个问题。
在我们假设的例子中,你将得到的名字是name1
,name2
,name3
和name4
。因此,您将首先要求Git再次提取提交α
- 由于它已经存在,因此速度非常快 - 然后使用名称name1
将文件x
提取到索引和工作树中。那些也已经存在。
建议是添加git commit
。这个特殊的git commit
将会失败,并显示没有任何内容可以提交,在这种特殊情况下可能是你想要的:在分支x
的提示中已经有一个文件name1
,其中包含正确的内容,即在提交α
中。
然后循环将继续到git checkout name2
,即提交Y
。这将使用从提交Y
中提取的内容替换索引和工作树内容,并将HEAD
附加到名称name2
。 git checkout name1 -- x
行将提交文件x
从提交α
提取到索引和工作树,并且提议的git commit
将进行新的提交,因此导致名称name2
向前移动以指向此新提交。请注意,名称name3
继续指向提交Y
。让我们在新的提交中绘制,我们可以称之为β
(beta):
U--X--α <-- name1 (HEAD)
/
T β <-- name2
/ /
...--V--Y <-- name3
\
W--Z <-- name4
现在你的循环移动到name3
,它仍指向提交Y
,所以Git会将索引和工作树设置回原来的状态,当你通过名称Y
提交name2
时。 Git现在将像之前一样从commit x
中提取文件α
,并再次进行新的提交。
这是事情变得非常有趣的地方!新提交与提交β
具有相同的树。它也有相同的作者和提交者。根据您构建-m
消息的方式,它可能与commit β
具有相同的日志消息。如果Git在提交β
时使用的时间戳与第二次相同,那么新提交实际上是现有的提交β
,一切都很好。
另一方面,如果Git花费足够的时间使新提交获得不同的时间戳,则新提交与提交β
不同。让我们假设这确实发生了,我们得到提交γ
(gamma):
U--X--α <-- name1 (HEAD)
/
T β <-- name2
/ /
...--V--Y--γ <-- name3
\
W--Z <-- name4
最后,循环将再次为name4
执行相同的过程,Z
当前指向提交δ
但最终将指向新的提交 U--X--α <-- name1 (HEAD)
/
T β <-- name2
/ /
...--V--Y--γ <-- name3
\
W--Z--δ <-- name4
(delta):
name2
当多个名称指向相同的基础提交时,会出现一个问题。在这种情况下,您必须决定是否要以相同的方式调整所有名称 - 即,让name3
和β
前进指向提交git branch -f
- 或者您是否不想要它:
git merge --ff-only
,git commit
等)来更新指向特定提交的所有名称。否则,您将依赖于在一秒钟内完成所有提交,以便时间戳全部匹配。x
s至少间隔一秒,以便他们获得独特的时间戳。如果您确定所有名称都指向不同的提交,则此问题就会消失。
要考虑的其他事情是这些:
x
的文件的现有提交?如果是这样,你将从我们从提交x
(我们在当前分支上,在整个过程开始时进行的第一次提交)中提取的α
中覆盖此x
。)x
-one肯定有,那就是我们首先处于分支的那个 - 那么α
是否与提交git commit
中的那个相匹配?如果是这样,除非我们添加--allow-empty
,否则建议的$b
将失败。但在我们这里的特殊情况下,这可能是一件好事,因为这意味着我们可以避免有一个特殊情况来测试$current_branch
是否匹配What exactly do we mean by "branch"?。git fetch origin
。我们称之为发展线。你有每个这样的线的(本地)分支名称吗?这可能就是为什么你在这里有origin/*
:这样你就可以累积所有origin/feature1
远程跟踪名称的更新。
如果你有origin/feature2
和feature1
,你可能在这一点上想要创建(本地)分支名称feature2
和x
,以便将文件git for-each-ref
添加到这两个分支的提示中。您需要分支名称来记住新创建的提交。但是仅仅使用refs/heads
而不是git for-each-ref
将无法实现预期的结果:你可能想要使用refs/remotes/origin
而不是origin/
,将名称减去refs/heads
部分与所有refs/heads/
名称(当然减去qazxswpoi部分)一起累积,并使用那些作为分支名称。