问题
Common advice seems to be not to rebase, reset, or perform other "history-rewriting" operations after commits have been pushed.
The recommendation is to perform all these kinds of slicing and dicing on your own machine, but then once you've pushed it, to just let it be.
This makes sense.
But how can this policy be enforced? With multiple developers, how can we make sure that nobody does this?
It seems there should be a way for git to programmatically know if it is rewriting history on something that is already pushed.
If this isn't built in, it seems there should be a way to write a script that would check for this.
Ideally, people would not be permitted to do anything that rewrites history on something that was committed. It would return an error.
If that isn't possible, then at least people would be blocked from pushing anything that rewrites history.
Is this doable?
回答1:
One possible solution is a client side hook (pre-rebase):
One other thing we can do here is make sure the user doesn’t push non-fast-forwarded references. To get a reference that isn’t a fast-forward, you either have to rebase past a commit you’ve already pushed up or try pushing a different local branch up to the same remote branch.
Presumably, the server is already configured with
receive.denyDeletes
andreceive.denyNonFastForwards
to enforce this policy, so the only accidental thing you can try to catch is rebasing commits that have already been pushed.An example pre-rebase script that checks for that: It gets a list of all the commits you’re about to rewrite and checks whether they exist in any of your remote references.
If it sees one that is reachable from one of your remote references, it aborts the rebase.
#!/usr/bin/env ruby
base_branch = ARGV[0]
if ARGV[1]
topic_branch = ARGV[1]
else
topic_branch = "HEAD"
end
target_shas = `git rev-list #{base_branch}..#{topic_branch}`.split("\n")
remote_refs = `git branch -r`.split("\n").map { |r| r.strip }
target_shas.each do |sha|
remote_refs.each do |remote_ref|
shas_pushed = `git rev-list ^#{sha}^@ refs/remotes/#{remote_ref}`
if shas_pushed.split("\n").include?(sha)
puts "[POLICY] Commit #{sha} has already been pushed to #{remote_ref}"
exit 1
end
end
end
But that remains a client-side solution, which can be circumvented, and has to be deployed/activated repos by repos.
On the server side, this is not easy to enforce (beside the config receive.denyNonFastForwards
).
But since git 1.8.5, instead of a simple git push --force
(after rebase), you now can do a git push --force-with-lease.
You assume you took the lease on the ref when you fetched to decide what the rebased history should be, and you can push back only if the lease has not been broken.
At least, you know if your force push will cause problem or not.
But that remains something a user has to think to do.
回答2:
Adding to VonC's answer, you can actually ask github to deny fast forward pushs to your repository. I have did that a couple of times.
Just send them an email and ask them to do so.
回答3:
If you can control the server you use, it's possible. If you have your own github installation, you can deny forced pushes from the client. If not, you can request the github administration set this on your repository, and then it will be enforced.
来源:https://stackoverflow.com/questions/26702487/how-to-make-sure-teammates-dont-screw-up-the-git-repo-by-rewriting-history