push: document & test --force-with-lease with multiple remotes

Document & test for cases where there are two remotes pointing to the same URL, and a background fetch & subsequent `git push --force-with-lease` shouldn't clobber un-updated references we haven't fetched. Some editors like Microsoft's VSC have a feature to auto-fetch in the background, this bypasses the protections offered by --force-with-lease & --force-with-lease=<refname>, as noted in the documentation being added here. See the 'Tools that do an automatic fetch defeat "git push --force-with-lease"' (<1491617750.2149.10.camel@mattmccutchen.net>) git mailing list thread for more details. Jakub Narębski suggested this method of adding another remote to bypass this edge case, document that & add a test for it. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Ævar Arnfjörð Bjarmason committed Apr 19, 2017 at 09:22 UTC f17d642d3b0fa64879d59b311e596949f2a1f6d2
2 files changed +70
Documentation/git-push.txt
+41
@@ -217,6 +217,47 @@ with this feature.
217 +
218 "--no-force-with-lease" will cancel all the previous --force-with-lease on the
219 command line.
220 ++
221 +A general note on safety: supplying this option without an expected
222 +value, i.e. as `--force-with-lease` or `--force-with-lease=<refname>`
223 +interacts very badly with anything that implicitly runs `git fetch` on
224 +the remote to be pushed to in the background, e.g. `git fetch origin`
225 +on your repository in a cronjob.
226 ++
227 +The protection it offers over `--force` is ensuring that subsequent
228 +changes your work wasn't based on aren't clobbered, but this is
229 +trivially defeated if some background process is updating refs in the
230 +background. We don't have anything except the remote tracking info to
231 +go by as a heuristic for refs you're expected to have seen & are
232 +willing to clobber.
233 ++
234 +If your editor or some other system is running `git fetch` in the
235 +background for you a way to mitigate this is to simply set up another
236 +remote:
237 ++
238 + git remote add origin-push $(git config remote.origin.url)
239 + git fetch origin-push
240 ++
241 +Now when the background process runs `git fetch origin` the references
242 +on `origin-push` won't be updated, and thus commands like:
243 ++
244 + git push --force-with-lease origin-push
245 ++
246 +Will fail unless you manually run `git fetch origin-push`. This method
247 +is of course entirely defeated by something that runs `git fetch
248 +--all`, in that case you'd need to either disable it or do something
249 +more tedious like:
250 ++
251 + git fetch # update 'master' from remote
252 + git tag base master # mark our base point
253 + git rebase -i master # rewrite some commits
254 + git push --force-with-lease=master:base master:master
255 ++
256 +I.e. create a `base` tag for versions of the upstream code that you've
257 +seen and are willing to overwrite, then rewrite history, and finally
258 +force push changes to `master` if the remote version is still at
259 +`base`, regardless of what your local `remotes/origin/master` has been
260 +updated to in the background.
261
262 -f::
263 --force::
t/t5533-push-cas.sh
+29
@@ -229,4 +229,33 @@ test_expect_success 'new branch already exists' '
229 )
230 '
231
232 +test_expect_success 'background updates of REMOTE can be mitigated with a non-updated REMOTE-push' '
233 + rm -rf src dst &&
234 + git init --bare src.bare &&
235 + test_when_finished "rm -rf src.bare" &&
236 + git clone --no-local src.bare dst &&
237 + test_when_finished "rm -rf dst" &&
238 + (
239 + cd dst &&
240 + test_commit G &&
241 + git remote add origin-push ../src.bare &&
242 + git push origin-push master:master
243 + ) &&
244 + git clone --no-local src.bare dst2 &&
245 + test_when_finished "rm -rf dst2" &&
246 + (
247 + cd dst2 &&
248 + test_commit H &&
249 + git push
250 + ) &&
251 + (
252 + cd dst &&
253 + test_commit I &&
254 + git fetch origin &&
255 + test_must_fail git push --force-with-lease origin-push &&
256 + git fetch origin-push &&
257 + git push --force-with-lease origin-push
258 + )
259 +'
260 +
261 test_done