Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Git --force-with-lease with + in branch (refspec)

Tags:

git

github

On current Git, is there a material difference between git push --force-with-lease origin +somebranch, git push --force-with-lease origin somebranch and git push origin +somebranch (without a plus)? All three seem to do the same thing.

I tried to look for documentation. I tried to look at refspec in documentation, but I don't know if there is a difference and if so, which would one should prefer by default when I like to pull to work branch via git pull --rebase origin master, for instance.

like image 918
Veksi Avatar asked Feb 06 '23 20:02

Veksi


2 Answers

This is a good question; the documentation is a bit ambiguous, and the source is very confusing (the actual application of force flags is widely scattered).

One answer is clear enough. Here is what the git push documentation says, with my boldface added:

--[no-]force-with-lease
--force-with-lease=<refname>
--force-with-lease=<refname>:<expect>

      Usually, "git push" refuses to update a remote ref that is not an ancestor of the local ref used to overwrite it.

      This option overrides this restriction if the current value of the remote ref is the expected value. "git push" fails otherwise.

      Imagine that you have to rebase what you have already published. You will have to bypass the "must fast-forward" rule in order to replace the history you originally published with the rebased history. If somebody else built on top of your original history while you are rebasing, the tip of the branch at the remote may advance with her commit, and blindly pushing with --force will lose her work.

      This option allows you to say that you expect the history you are updating is what you rebased and want to replace. If the remote ref still points at the commit you specified, you can be sure that no other people did anything to the ref. It is like taking a "lease" on the ref without explicitly locking it, and the remote ref is updated only if the "lease" is still valid.

      --force-with-lease alone, without specifying the details, will protect all remote refs that are going to be updated by requiring their current value to be the same as the remote-tracking branch we have for them.

      --force-with-lease=<refname>, without specifying the expected value, will protect the named ref (alone), if it is going to be updated, by requiring its current value to be the same as the remote-tracking branch we have for it.

      --force-with-lease=<refname>:<expect> will protect the named ref (alone), if it is going to be updated, by requiring its current value to be the same as the specified value (which is allowed to be different from the remote-tracking branch we have for the refname, or we do not even have to have such a remote-tracking branch when this form is used).

      Note that all forms other than --force-with-lease=<refname>:<expect> that specifies the expected current value of the ref explicitly are still experimental and their semantics may change as we gain experience with this feature.

      "--no-force-with-lease" will cancel all the previous --force-with-lease on the command line.

Hence, if the compare-and-swap option1 is supported by the transport, and you have written --force-with-lease and not --no-force-with-lease, then all updates, forced or not, use the lease mode.

The --no-force-with-lease, however, clears out stored-up push_cas_option structure, and it's not immediately obvious to me when those stored-up value are applied to each refspec.

Using an explicit <refname> also clearly protects only the one reference, regardless of any force flag set for it.

Precisely what happens when the underlying transport lacks support for compare-and-swap is also not clear to me. Fortunately GitHub's Git servers support it, making this just a distraction if you're specifically referring to GitHub.


1Internally, the Git source code uses the macro CAS_OPT_NAME: the function of force-with-lease is inspired by modern CPUs' compare-and-swap instructions, which atomically test whether some variable2 is set to a predicted value, replacing it with a new value if so, and also returning, in some form, the actual value found in the variable.

This may set condition codes, if the CPU architecture uses condition codes, but in most if not all cases you get the old value so that you can retry the compare-and-swap if appropriate. For instance, to implement atomic add-one, you can loop with: load r1,(r0); label: add r1,1,r2; cas r1,r2,(r0); bne label; to implement atomic-test-and-set of bit 2: load r1,(r0); label: or r1,4,r2; cas r1,r2,(r0); bne label; and so on. This method is used on Intel Pentium and SPARC systems, for instance.

Some CPUs use the cache machinery instead. If the closest-to-CPU cache has shared vs exclusive modes (e.g., MESI or MOESI) we can use a "load linked" or "load locked" instruction followed by a "store conditional" instruction. The conditional store succeeds only if the cache line is still exclusively owned by the current CPU. In this case, we must re-do the initial locked-load of the variable, and our loops look more like: label: ll r1,(r0); add 1,r1; sc (r0),r1; bne label. This is used on PowerPC and MIPS architectures.

2Typically the variable in question is a memory location, often with alignment constraints, even on CPUs that nominally support unaligned memory. For instance, on the Intel Haswell, a compare-and-swap-8-byte instruction will run to completion on a 4-byte boundary, but it will not actually be atomic. I discovered this the hard way when a colleague's memory allocator provided only 4-byte alignment. :-)

like image 54
torek Avatar answered Feb 09 '23 10:02

torek


which would one should prefer by default when I like to pull to work branch via git pull --rebase origin master?

I reported on force-with-lease in 2013 for git 1.8.5, and March 2016 for git 2.8".

I would say... none of them.
A pull --rebase is done to avoid having to force push (with or without lease) anything.

I simply set (since git 2.6)

git config pull.rebase true
git config rebase.autoStash true

That allows me do do some simple git pull, followed by simple git push (no forced push involved)

like image 29
VonC Avatar answered Feb 09 '23 12:02

VonC