我想在本地和远程创建一个开发分支,而不会意外地干扰其他任何开发分支。创建本地分支很容易,不会受到任何竞争条件的影响,但安全地创建远程分支很棘手。
假设我想创建一个名为cleanup
的分支,但其他人可能有同样的想法,并在我之前创建了自己的名为cleanup
的分支。如果我只是git push --set-upstream origin cleanup
,也许它会创建一个新的远程分支,或者它可能会快速转发已经存在的分支。
如果远程分支已经存在,我希望git push ...
失败,这样我就可以选择不同的分支名称。
我已经知道了几个不完美的解决方案,例如git fetch
然后快速推送;这仍然受到竞争条件的影响。或者做一个git push
,注意远程分支是否存在于命令的输出中,然后尝试从不良情况中恢复;这种情况很混乱,在恢复期间会受到更糟糕的竞争条件的影响。也可以通过各种方式使用git push --force-with-lease
来做类似的事情,但最接近解决我问题的方法是拒绝创建分支而不是快速前进,这与我的目标相反。
UPDATE :理想情况下,解决方案对树中文件数或git历史记录长度没有算法复杂性依赖性。需要对repo进行新克隆或删除并重新检出工作树中所有文件的解决方案是不可接受的。
答案 0 :(得分:2)
function git-new-branch() {
# usage: git-new-branch branchname
git push origin $(git commit-tree -m "" $(git mktree <&-) <&-):refs/heads/$1 || return 1
git checkout -b $1
git push --set-upstream --force-with-lease origin $1
}
git mktree
和git commit-tree
读取stdin,因此请使用<%-
关闭这些进程的stdin。
git mktree
是幂等的,并打印空树的sha1。
git commit-tree
使用空树进行空提交,重要的是提交没有父项。 git commit-tree
也打印sha1,但它不是幂等的。提交包含您的user.name
和时间戳。此外,此提交作为孤立提交进入.git
目录。孤立提交最终会被git
自动收集;见git help gc
。 -m ""
表示提交消息为空。但这一切都不重要,因为这个提交将被使用一次并立即放弃。
第一个git push
将空提交推送到您提供的分支名称。如果分支已经存在,则保证被拒绝,因为空提交没有父项,因此推送它不可能导致快进。 (并且由于每次调用都会重新生成提交,因此任何地方的提交都不可能在远程仓库中。)
如果命令失败,|| return 1
将中止该功能。
git checkout -b
命令没有错误检查。此函数假设您还没有具有给定名称的本地分支。
第二个git push
使用--force-with-lease
,这意味着如果远程分支仍然是我们认为的那样,那么它只会被更新,这是我们刚刚推送的空提交。
感谢o11c和jthill的回答,为此解决方案提供了灵感。
答案 1 :(得分:0)
所以,你有一个广告牌回购,所有人都推动可能值得分享的WIP提交或其他任何东西。还有其他工作流程不会导致像您这样的问题,运行拉取请求等,但所有工作流程都有其缺点。缩放可能是海报板回购的缺点,但命名空间冲突并不难避免。
分支名称是repo-local。回购之间的任何通信都是方便和协作的问题;使用相同的名称通常很方便,但为分支机构设置本地名称或根本不共享它也常常很方便。
继续你在这里说的话,就在这里。
git push -u origin featureb:thejoshwolfe/featureb
第一次推动featureb
分支时,我会推:jthill/featureb
等。您的回购是您的,在您的回购跟踪中分支您所说的任何上游分支。
编辑:默认情况下,Git会拒绝推送不包含分支的所有当前引用的历史记录。如果两个人试图将冲突的历史推送到一个分支名称,git将拒绝其中的第二个。你可以使用--force-with-lease
在高容量的大中心站回购中进行安全推力,但还有其他方法可以避免这个问题。
要保留或取消新分支名称,请执行
git push origin $(git commit-tree -m - `:|git mktree` <&-):refs/heads/newbranch
如果这可以起到你的名字的作用,你现在可以使用git push -u origin +newbranch
安全地强制推送它。
答案 2 :(得分:-1)
使用新的根提交创建分支。由于root提交没有任何父母,因此不能是快进。
然后使用--force-with-lease
将临时根提交替换为您想要的实际提交。
编辑:自包含脚本以证明这有效:
#!/bin/bash
set -e
# make this script idempotent
rm -rf /tmp/git-remote-demo/
mkdir -p /tmp/git-remote-demo/
cd /tmp/git-remote-demo/
# create a new "remote" repo and a couple of clones.
# (i.e what most real-world repos already look like)
git init --bare ./remote.git/
echo
git clone ./remote.git user1/
(cd user1/;
echo 'this is a readme' > readme.txt
git add readme.txt
git commit -m 'initial commit'
echo 'it has been modified' >> readme.txt
git add readme.txt
git commit -m 'modify readme'
git push origin master
)
echo
git clone ./remote.git user2/
echo
# then each user does work on their part
for user in user1 user2
do
(
cd "$user"
echo "I am $user"
# work on a feature
echo "work by $user" > readme.txt
git add readme.txt
git commit -m "work by $user"
# interesting part starts here
empty_tree="$(true | git mktree)"
temp_commit="$(git commit-tree "$empty_tree" -m "temp commit for $user")"
# demonstrate that this works even for race conditions
git push origin "$temp_commit":refs/heads/race || echo "race push failed for $user"
# demonstrate that this works normally
{ git push origin "$temp_commit":refs/heads/full && git push origin master:full --force-with-lease; } || echo "full push failed for $user"
echo
)
done
echo "Remote branches:"
(cd remote.git && git branch -av)