diff --git a/bot_bottle/git_gate.py b/bot_bottle/git_gate.py index 0c3b09f..1338b80 100644 --- a/bot_bottle/git_gate.py +++ b/bot_bottle/git_gate.py @@ -300,6 +300,8 @@ while IFS=' ' read -r old new ref; do [ -z "$ref" ] && continue if [ "$new" = "$zero" ]; then refspec=":$ref" + elif [ "$old" != "$zero" ] && ! git merge-base --is-ancestor "$old" "$new" 2>/dev/null; then + refspec="+$new:$ref" else refspec="$new:$ref" fi diff --git a/tests/unit/test_git_gate.py b/tests/unit/test_git_gate.py index 11e96ea..2e1de27 100644 --- a/tests/unit/test_git_gate.py +++ b/tests/unit/test_git_gate.py @@ -181,6 +181,13 @@ class TestHookRender(unittest.TestCase): self.assertIn("BatchMode=yes", hook) self.assertIn("ConnectTimeout=", hook) + def test_force_push_uses_plus_refspec(self): + # A non-fast-forward push (old != zero, new not a descendant of old) + # must forward +$new:$ref so the upstream accepts the force push. + hook = git_gate_render_hook() + self.assertIn('git merge-base --is-ancestor "$old" "$new"', hook) + self.assertIn('refspec="+$new:$ref"', hook) + def test_forward_preserves_push_options(self): # Git exposes push options to pre-receive hooks as # GIT_PUSH_OPTION_COUNT + indexed GIT_PUSH_OPTION_N variables.