假设我在一个分支上,而索引是脏的。我对文件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 -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
这将获得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进行检查,这不是您想要的。使用省略了refs/heads/
部分的%(refname:short)
可以很容易地解决这个问题。
在这里的假设示例中,您将得到的名称是name1
、name2
、name3
和name4
。因此,您将首先要求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
一般问题
当多个名称指向相同的基础提交时,就会出现一个问题。在这种情况下,您必须决定是要以相同的方式调整所有名称,即name2
和name3
都前进到提交β
的点,还是不需要:
如果确实想要这样做,则必须确保使用某些分支名称更新操作(
git branch -f
、git merge --ff-only
等)来更新所有指向特定提交的名称。否则,您将依赖于在一秒内完成所有提交,以便时间戳都匹配。如果您不希望这样做——如果您需要个性化名称,那么您必须确保
git commit
至少相隔一秒,这样它们才能获得唯一的时间戳。
如果您确定所有名称都指向不同的提交,那么这个问题就会消失。
其他需要考虑的是:
是否有任何名称指向有一个名为
x
的文件的某个现有提交?如果是这样,您将从我们从提交α
中提取的x
(在整个过程开始时,我们在当前分支上进行的第一次提交)覆盖此x
如果任何名称都有
x
——当然有,那就是我们最初所在的分支——那么x
是否与提交α
中的名称匹配?如果是这样,除非我们添加--allow-empty
,否则所提出的git commit
将失败。但在我们这里的特殊情况下,这可能是一件好事,因为这意味着我们可以避免使用特殊情况来测试$b
是否与$current_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/
部分)一起累积到一个集合中,并使用这些作为分支名称。
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