git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[RFD] Making "git push [--force/--delete]" safer?

From
Junio C Hamano <gitster@pobox.com>
Date
Jul 2, 2013, 20:57 UTC
Message-ID
<7vfvvwk7ce.fsf@alter.siamese.dyndns.org>
Consider these two scenarios.
1. If you are collaborating with others and you have arranged with
   the participants to rewind a shared branch, you would do
   something like this:
        $ git fetch origin branch
        ... fetch everything so that we won't lose anything ...
        $ git checkout -b rebase-work FETCH_HEAD
        $ git rebase -i
        $ git push origin +HEAD:branch
   The last step has to be "--force", as you are deliberately
   pushing a history that does not fast-forward.
2. If you know a branch you pushed over there has now been fully
   merged to the "trunk" and want to remove it, you would do this:
        $ git fetch origin branch:tmp-branch trunk:tmp-trunk
        ... double check to make sure branch is fully merged ...
        $ git merge-base --is-ancestor tmp-branch tmp-trunk; echo $?
        0
        ... good, branch is part of trunk ...
        $ git push origin --delete branch
   The last step would delete the branch, but you made sure it
   has been merged to the trunk, not to lose anybody's work.

But in either of these cases, if something happens at 'origin' to the branch you are forcing or deleting since you fetched to inspect it, you may end up losing other people's work.

 - In the first scenario, somebody who is unaware of the decision to
   rewind and rebuild the branch may attempt to push to the branch
   between the time you fetched to rebase it and the time you pushed
   to replace it with the result of the rebasing.
 - In the second scenario, somebody may have pushed a new change to
   the branch since you fetched to inspect.

We can make these pushes safer by optionally allowing the user to tell "git push" this:

        I am forcing/deleting, based on the assumption that the
        value of 'branch' is still at this object.  If that
        assumption no longer holds, i.e. if something happened to
        the branch since I started preparing for this push, please
        do not proceed and fail this push.

With such a mechanism, the first example would say "'branch' must be at $(git rev-parse --verify FETCH_HEAD)", and the second example would say "'branch' must be at $(git rev-parse --verify tmp-branch)".

The network protocol of "git push" conveys enough information to make this possible. An early part of the exchange goes like this:

        receiver -> sender
                list of <current object name, refname>
        sender -> receiver
                list of <current object name, new object name, refname>
        sender -> receiver
                packfile payload
                ...

When the "git push" at the last step of the above two examples contact the other end, we would immediately know what the current value of 'branch' is. We can locally fail the command if the value is different from what we expect.

Now at the syntax level, I see three possibilities to let the user express this new constraints:

  (1) Extend "refspec" syntax to "src:dst:expect", e.g.
      $ git push there HEAD:branch:deadbabecafe
      would say "update 'branch' with the object at my HEAD, only if
      the current value of 'branch' is deadbabecafe".
      An reservation I have against this syntax is that it does not
      mesh well with the "update the upstream of the currrent
      branch" and other modes, and instead you have to always spell
      three components out.  But perhaps requiring the precondition
      is rare enough that it may be acceptable.
  (2) Add --compare-and-swap=dst:expect parameters, e.g.
      $ git push --cas=master:deadbabecafe --cas=next:cafebabe ":"
      This removes the "reservation" I expressed against (1) above
      (i.e. we are doing a "matching" push in this example, but we
      will fail if 'master' and 'next' are not pointing at the
      expected objects).
  (3) Add a mechanism to call a custom validation script after "git
      push" reads the list of <current object name, refname> tuples,
      but before responding with the proposed update.  The script
      would be fed a list of <current object name, new object
      name, refname> tuples (i.e. what the sender _would_ tell the
      receiving end if there weren't this mechanism), and can tell
      "git push" to fail with its exit status.
      This would be the most flexible in that the validation does
      not have to be limited to "the ref must be still pointing at
      the object we expect" (aka compare-and-swap); the script could
      implement other semantics (e.g. "the ref must be pointing at
      the object or its ancestor").
      But it may be cumbersome to use and the added flexibility may
      not be worth it.
      - The way to specify the validation script could be an
        in-repository hook but then there will need a way to pass
        additional per-invocation parameters (in the earlier sample
        scenarios, values of FETCH_HEAD and tmp-branch).
      - Or it could be a "--validate-script=check.sh" option, and it
        is up to the caller how to tailor that check.sh script
        customized for this particular invocation (i.e. embedding
        the values of FETCH_HEAD and tmp-branch in the script in the
        earlier sample scenarios).

I am inclined to say, if we were to do this, we should do (2) among the above three.

But of course, others may have better ideas ;-).
Next: Johan Herland
Message 1 of 62 in “[RFD] Making "git push [--force/--delete]" safer?”
  1. Junio C HamanoJul 2, 2013
  2. Johan HerlandJul 2, 2013
  3. Johan HerlandJul 3, 2013
  4. Junio C HamanoJul 3, 2013
  5. Johan HerlandJul 3, 2013
  6. Jonathan del StrotherJul 3, 2013
  7. Johan HerlandJul 3, 2013
  8. Michael HaggertyJul 3, 2013
  9. Johannes SixtJul 3, 2013
  10. Junio C HamanoJul 3, 2013
  11. Johannes SixtJul 4, 2013
  12. Junio C HamanoJul 4, 2013
  13. Junio C HamanoJul 3, 2013
  14. Junio C HamanoJul 3, 2013
  15. Junio C HamanoJul 3, 2013
  16. 0/7 safer "push --force" with compare-and-swapJunio C Hamano, Jul 9, 2013
  17. 1/7 cache.h: move remote/connect API out of itJunio C Hamano, Jul 9, 2013
  18. 2/7 builtin/push.c: use OPT_BOOL, not OPT_BOOLEANJunio C Hamano, Jul 9, 2013
  19. 3/7 push: beginning of compare-and-swap "force/delete safety"Junio C Hamano, Jul 9, 2013
  20. 4/7 remote.c: add command line option parser for --lockrefJunio C Hamano, Jul 9, 2013
  21. John KeepingJul 16, 2013
  22. Junio C HamanoJul 17, 2013
  23. Junio C HamanoJul 17, 2013
  24. 5/7 push --lockref: implement logic to populate old_sha1_expect[]Junio C Hamano, Jul 9, 2013
  25. 6/7 t5533: test "push --lockref"Junio C Hamano, Jul 9, 2013
  26. 7/7 push: document --lockrefJunio C Hamano, Jul 9, 2013
  27. Aaron SchrabJul 9, 2013
  28. Junio C HamanoJul 9, 2013
  29. Johannes SixtJul 9, 2013
  30. Junio C HamanoJul 9, 2013
  31. Johannes SixtJul 9, 2013
  32. Junio C HamanoJul 9, 2013
  33. Junio C HamanoJul 9, 2013
  34. Johannes SixtJul 11, 2013
  35. Junio C HamanoJul 11, 2013
  36. Junio C HamanoJul 11, 2013
  37. Johannes SixtJul 12, 2013
  38. Junio C HamanoJul 12, 2013
  39. Johannes SixtJul 12, 2013
  40. Junio C HamanoJul 12, 2013
  41. Johannes SixtJul 13, 2013
  42. Junio C HamanoJul 13, 2013
  43. Junio C HamanoJul 13, 2013
  44. Johannes SixtJul 13, 2013
  45. John KeepingJul 14, 2013
  46. Johannes SixtJul 13, 2013
  47. Junio C HamanoJul 14, 2013
  48. Johannes SixtJul 14, 2013
  49. Jonathan NiederJul 14, 2013
  50. Jonathan NiederJul 14, 2013
  51. Johannes SixtJul 14, 2013
  52. Jonathan NiederJul 14, 2013
  53. Junio C HamanoJul 15, 2013
  54. Jonathan NiederJul 15, 2013
  55. Junio C HamanoJul 15, 2013
  56. Johannes SixtJul 15, 2013
  57. Junio C HamanoJul 15, 2013
  58. Default expectation of --lockrefJunio C Hamano, Jul 15, 2013
  59. Johannes SixtJul 15, 2013
  60. Marc BranchaudJul 9, 2013
  61. Michael HaggertyJul 9, 2013
  62. Junio C HamanoJul 9, 2013

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.