全局重置 git 分支(针对所有用户)

Reset a git branch globally (for all users)

在我们当前的工作流程中,我们有 2 个主要 git 分支:

master - 稳定版分支

testing - 每个人都测试了他们的代码

现在每个开发人员都为他们开发的每个功能创建新的分支。当他们完成后,他们将其合并到 testing,当我们的 QA 说可以进行时,他们将他们的分支合并到 master 并部署到生产中。

随着时间的推移,我们的 testing 分支被从未投入生产的提交所污染。废弃的功能、重写而不是固定的东西和其他东西。

为了保持 mastertesting 处于某种程度的一致状态,我们希望不时地 "reset" testing。现在,我们通过完全删除 testing 并将其从 master.

重新分支来实现

这里的大问题是我们需要确保每个开发人员也删除他的本地 testing 分支并签出它的新副本。 如果一位开发人员忘记这样做并再次进行测试,我们试图摆脱的所有脏提交都会回来。

有没有什么方法可以在服务器上以分发给所有用户的方式重置分支?

一个可接受的解决方案是将 testing 分支置于一种状态,如果不在本地进行重置,任何人都无法再向其推送。但是我想不出办法。

mastertesting 之间创建差异并恢复提交不是一个选项,因为这会阻止这些提交中的每一个再次进入测试。

理想情况下,我有一个脚本可以定期执行此重置,并且每个用户的本地环境都不需要交互(git pull 除外)。

As time passes, our testing branch get polluted with commits that never make it to production. Abandoned features, stuff that got rewritten instead of fixed and other things.

这怎么可能?很明显,如果一个特性被放弃了,那么你也应该从你的测试分支中删除它,因为它似乎是你的看门人。基本上,如果你说你的测试分支随着时间的推移而受到污染,那么它就违背了测试分支的全部目的,因为现在你正在测试一些不代表你想要推送到生产的代码的东西。

如果有什么东西没有成功,那么开发人员应该恢复他的更改并将提交推送到测试分支,在那里更改也被恢复。

在您的方案中,您应该全部或全部从测试合并到生产。

简短的回答是 "no, you can't do that"。

请记住,每个克隆都是一个完整的独立实体1,除了它的 origin 和(取决于克隆选项)一些初始的 b运行ch 状态。2 一旦有人拿起一个名为 testing 的 b运行ch 并调用它 origin/testing:

  • 他们有你让他们拥有的承诺;和
  • 他们有一个名为 origin/testing 的引用 ("remote-tracking branch"),当他们连接到远程 [=11] 时,他们的 git 将自动更新,甚至根据指示修剪(删除) =].

到目前为止一切顺利,这个 "automatic prune" 动作听起来很棒。如果你能说服他们将 remote.origin.prune 设置为 true:

$ git config remote.origin.prune true

然后一旦你删除你的 b运行ch 命名为testing他们的 origin/testing 将在下一个 git fetch origin 时自动消失。

他们 创建一个名为 testing 的 b运行ch 时,问题就出现了。他们的 git 不会 删除这个 b运行ch 除非并且直到 他们 要求它。就他们git而言,他们的私生子运行是他们的私生子运行。您无法说服他们的 git 删除他们的私人 testing,就像您无法说服他们的 git 删除他们的私人 experiment-22 一样。他们创造了它;这是他们的资料库;他们控制它。

(请注意,他们还控制自动修剪,因为他们可以随时 git config remote.origin.prune 设置,或 false。该设置是意味着他们的方便,而不是你的 - 它与他们的 remote.origin.fetch 设置一起使用,他们更改这些设置以便他们的 git fetch 改变它的功能;它的初始默认设置是他们在 运行 时创建的git clone.)

可以继续此模型,前提是您让所有开发人员自行控制删除或清理此 b运行ch 标签。但这不是要走的路。相反,您应该使用另一种模型:为您的开发人员创建一个新的 differentb运行ch 标签,用于您正在进行的新的(和不同的)开发线。

例如,您可能将 dev-feature-X 作为一个临时 b运行ch,您的开发人员可以共享它来处理功能 X。当您全部完成后,您可以保留或随意删除它,您的开发人员会自动(使用修剪设置)或不随意删除。同时,您创建了 dev-feature-Y 作为临时 b运行ch,您的开发人员可以共享它来处理功能 Y,等等。


1忽略特殊情况,例如 "shallow" 克隆,至少不适用于此处。

2如果你克隆时没有 --mirror,源的 b运行ches 变成你的远程 b运行ches,你没有local b运行ches 直到你签出一个(通常是 master,通常是 clone 命令的最后一步)。此外,克隆看不到源的挂钩,因此不会克隆这些挂钩。 .git 目录中也没有任何其他特殊状态,例如 .git/info 中的项目。但是,None 其中影响了普通 b运行ch 用法的原则。

一种选择是通过以特殊方式合并到主分支来重置开发分支的状态。

git checkout master
git checkout -b new_testing
git merge -s ours testing # this creates a merge commit, but
                          # its tree is that of the current work-tree
                          # which in our case is the same as master
git checkout testing
git merge ours_testing
git branch -d new_testing

我们需要创建临时 new_testing 分支,因为合并策略 ours 保留当前树而不是另一棵树,并且没有等效的 theirs 策略。

在这之后你会得到一个像

这样的分支结构
*         (testing) merge
|\
| *       (master) last commit on master
* |       last commit on testing
| |

但是测试的内容会和master的内容一致。

这样做的好处是任何在本地进行测试的人都承诺 在 last commit on testing 之后发生的事情将能够像往常一样将他们的更改重新设置到 origin/testing

因为这不应该打断通常的开发流程,所以没有理由不能经常(每晚?)完成它。

If one developer forgets to [rebase] and pushes to testing again, all the dirty commits [from an abandoned testing tip] that we are trying to get rid of are back.

您无法控制其他人的存储库中发生的事情,但您可以控制他们推送到您的存储库中的内容。

An acceptable solution would also be putting the testing branch in a state where nobody can push to it anymore without doing a reset locally. But I can't think of a way how to do it.

此预接收挂钩将拒绝通过合并引入不需要的历史记录的推送:

#!/bin/sh
#  Do not permit merges from unwanted history
#set -x
err=0
while read old new ref; do              # for each pushed ref

        [[ ${old%[^0]*} = $old ]] && continue # new branches aren't checked.

        nomerge=$(git for-each-ref refs/do-not-merge --format='%(objectname)^!')

        if [[ $( git rev-list --count --ancestry-path --boundary $old..$new $nomerge
         ) != $( git rev-list --count --ancestry-path --boundary $old..$new ) ]]; then
                echo "$ref doesn't allow merges from outdated history"
                err=1
        fi
done
exit $err

# why it works:

# if adding nomerge commits' parents as ancestors has any effect, then the
# nomerge commits are reachable without going through $old, i.e. they're 
# in some merged history. So check whether adding the abandoned commits as
# explicit ancestors to the push makes them show up, and refuse it if so.

要标记不需要的提交,请在 refs/do-not-merge 下引用它们,例如

git config alias.no-further-merges-from \
  '!f() { git update-ref "refs/do-not-merge/-@`date +%Y-%m-%dT%H%M%S`" ""; }; f'

所以放弃testing的仪式是

git no-further-merges-from testing
git checkout -B testing master

如果您想标记以前放弃的提示,您可以通过 sha 或任何其他表达式来引用它们,例如

git no-further-merges-from 'testing@{last october 31}'