2

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?

MikeC8
  • 3,783
  • 4
  • 27
  • 33

3 Answers3

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 and receive.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.

Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • It won't stop determined user though - one can simply remove or disable hooks. The only true solution is to prohibit forced pushes on server side – mvp Nov 02 '14 at 18:46
  • @mvp I agree. There is no sure solution on the client side. And the server side can be a bit too restrictive. – VonC Nov 02 '14 at 18:49
0

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.

Niro
  • 433
  • 3
  • 21
  • Are fast-forward pushes the only thing that needs to be denied? I've read a lot of blog posts about how there are several or more "dangerous" operations...do they all result in fast forward pushes? – MikeC8 Nov 02 '14 at 18:19
  • 1
    Fast-forward pushes are fine - this is what you normally do. Bad ones are forced pushes – mvp Nov 02 '14 at 18:49
  • Indeed. The dangerous ones are the *non* fast-forward updates. – Magnus Bäck Nov 03 '14 at 06:42
0

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.

Jason Coco
  • 77,985
  • 20
  • 184
  • 180