Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is it possible to avoid pushing new heads? #322

Open
tinloaf opened this issue Mar 25, 2024 · 2 comments
Open

Is it possible to avoid pushing new heads? #322

tinloaf opened this issue Mar 25, 2024 · 2 comments

Comments

@tinloaf
Copy link

tinloaf commented Mar 25, 2024

Hi,

First: thanks for providing git-cinnabar! I now see a realistic chance to avoid working with Mercurial at my job, that would be great.

The Problem

However, I have one problem that I cannot work around currently: Under certain (not so exotic, see below) circumstances, git-cinnabar does push new heads into the Mercurial repo without confirmation. Mercurial itself does not do this, a hg push that would create a new head in the current branch fails with an error "abort: push creates new remote head …"

With git-cinnabar, the behavior seems to depend on whether git already knows that it will push a new head at the time of push, i.e.:

1.) Situation A Say you have a git-cinnabar repository, and you are currently on a remote-tracking branch. You do git reset --hard 'HEAD^'. Now, your next commit would be a new head in Mercurial terms (or a non-fast-forward in git terms…). If you now make a modification, commit and push, git rejects the push with the usual 'this is not a fast-forward' warning. This is exactly the behavior I want.

2.) Situation B You have the same repository, but you do not do git reset --hard 'HEAD^'. So in your repository, it looks like your new commit neatly sits on top of the branch and should be a fast-forward. However, some other person has pushed to the branch since your last git pull, and thus your new commit actually is not a fast-forward, but it would require a pull-and-rebase. If you now run git push, git-cinnabar just seems to go ahead and push your commit as a new head without any confirmation or --force. (See full reproduction below).

Question

Is it possible to get git-cinnabar to also reject pushes in "Situation B" above, i.e., if the fact that a second head is being pushed is only discovered during push?

Full Reproduction

This is a minimal example to reproduce the problem locally.

Step 1: Create new hg repo and a commit

> mkdir hgrepo
> cd hgrepo
> hg init .
> echo "test" > hgtestfile
> hg add hgtestfile
> hg commit -m "Initial commit on hg"
changeset:   0:b9ca7780ad67
user:        Lukas Barth 
date:        Fri Mar 22 10:48:35 2024 +0100
summary:     Initial commit on hg

Step 2: Clone a git-cinnabar repository from it

> cd ..
> git clone hg::file://$(pwd)/hgrepo gitrepo # absolute path is necessary here
Cloning into 'gitrepo'...
Reading 1 changesets
Reading and importing 1 manifests
Reading and importing 1 revisions of 1 files
Importing 1 changesets
Updating metadata...
Unpacking objects: 100% (15/15), 1.53 KiB | 781.00 KiB/s, done.
It is recommended that you set "remote.origin.prune" or "fetch.prune" to "true".
  git config remote.origin.prune true
or
  git config fetch.prune true

> cd gitrepo
> git config remote.origin.prune true
> git --no-pager log
commit 50507633e44aba4d61a235a2a7f84acedd4b823c (HEAD -> branches/default/tip, origin/branches/default/tip, origin/HEAD)
Author: Lukas Barth 
Date:   Fri Mar 22 10:48:35 2024 +0100

    Initial commit on hg
> git status
On branch branches/default/tip
Your branch is up to date with 'origin/branches/default/tip'.

nothing to commit, working tree clean

Step 3: Add another commit to the hg repo

> cd ../hgrepo
> echo "second hg commit" > secondfile
> hg add secondfile
> hg commit -m "Second HG commit"

Step 4: Without pulling, create a commit in the git repo

> cd ../gitrepo
> echo "git change" > gitfile
> git add gitfile
> git commit -m "git change"
> git --no-pager log
commit f8af826004bd40e39d32c0f227ec50b655597c93 (HEAD -> branches/default/tip)
Author: Lukas Barth 
Date:   Fri Mar 22 10:54:31 2024 +0100

    git change

commit 50507633e44aba4d61a235a2a7f84acedd4b823c (origin/branches/default/tip, origin/HEAD)
Author: Lukas Barth 
Date:   Fri Mar 22 10:48:35 2024 +0100

    Initial commit on hg

Step 5: Push in the git repo

We're now in 'Situation B' from above. Let's push:

> git push
Bundling 1 changesets
Bundling 1 manifests
Bundling 1 revisions of 1 files
remote: adding changesets
remote: adding manifests
remote: adding file changes
remote: added 1 changesets with 1 changes to 1 files (+1 heads)
Updating metadata...To hg::hgrepo
 * [new branch]      branches/default/tip -> branches/default/tip

Unpacking objects: 100% (11/11), 1.55 KiB | 1.55 MiB/s, done.

The (+1 heads) in the log above already gives away what has happened: git-cinnabar has pushed a new head.

Aftermath

This is how the repo looks like from git's perspective. Note that the second mercurial commit is still not visible in git:

> git --no-pager log
commit f8af826004bd40e39d32c0f227ec50b655597c93 (HEAD -> branches/default/tip, origin/branches/default/tip, origin/HEAD)
Author: Lukas Barth 
Date:   Fri Mar 22 10:54:31 2024 +0100

    git change

commit 50507633e44aba4d61a235a2a7f84acedd4b823c
Author: Lukas Barth 
Date:   Fri Mar 22 10:48:35 2024 +0100

    Initial commit on hg

And even after pulling, the new commit does not become visible (because from the git-cinnabar perspective, which tracks each Mercurial-head in a git-branch, it is part of a different branch):

> git pull
Reading 1 changesets
Reading and importing 1 manifests
Reading and importing 1 revisions of 1 files
Importing 1 changesets
Updating metadata...
Unpacking objects: 100% (14/14), 2.09 KiB | 1.04 MiB/s, done.
Checking 1 imported file root and head revisions
From hg::hgrepo
 * [new branch]      branches/default/9f2f25ebf99890d6282b33cc588ba17758d73cb8 -> origin/branches/default/9f2f25ebf99890d6282b33cc588ba17758d73cb8
Already up to date.
> git --no-pager log
commit f8af826004bd40e39d32c0f227ec50b655597c93 (HEAD -> branches/default/tip, origin/branches/default/tip, origin/HEAD)
Author: Lukas Barth 
Date:   Fri Mar 22 10:54:31 2024 +0100

    git change

commit 50507633e44aba4d61a235a2a7f84acedd4b823c
Author: Lukas Barth 
Date:   Fri Mar 22 10:48:35 2024 +0100

    Initial commit on hg

The hg repo now has a branch with two heads:

> cd ../hgrepo
> hg log --pager=off
changeset:   2:339433b7588b
tag:         tip
parent:      0:b9ca7780ad67
user:        Lukas Barth 
date:        Fri Mar 22 10:54:31 2024 +0100
summary:     git change

changeset:   1:9f2f25ebf998
user:        Lukas Barth 
date:        Fri Mar 22 10:53:33 2024 +0100
summary:     Second HG commit

changeset:   0:b9ca7780ad67
user:        Lukas Barth 
date:        Fri Mar 22 10:48:35 2024 +0100
summary:     Initial commit on hg
> hg heads
changeset:   2:339433b7588b
tag:         tip
parent:      0:b9ca7780ad67
user:        Lukas Barth 
date:        Fri Mar 22 10:54:31 2024 +0100
summary:     git change

changeset:   1:9f2f25ebf998
user:        Lukas Barth 
date:        Fri Mar 22 10:53:33 2024 +0100
summary:     Second HG commit
@glandium
Copy link
Owner

To some extent, it's a problem with git. But it's one we can do something about.

We do detect this situation already, but only when the remote is fully up-to-date before pushing. So if you fetch before pushing, you won't have this problem (as a workaround for now).

Here's a patch to the test to make the problem happen in the test suite.

diff --git a/tests/push-refs.t b/tests/push-refs.t
index fa3094f5..d3ea9de1 100755
--- a/tests/push-refs.t
+++ b/tests/push-refs.t
@@ -98,13 +98,21 @@ Pushing `a` and `c` to the default branch.
   To hg::.*/push-refs.t/repo-from-git (re)
    * [new branch]      7688446e0a5d5b6108443632be74c9bca72d31b1 -> branches/default/tip
 
-  $ git -C repo-git2 push origin 7688446e0a5d5b6108443632be74c9bca72d31b1:refs/heads/branches/default/tip
+  $ git -C repo-git2 push origin 8b86a58578d5270969543e287634e3a2f122a338:refs/heads/branches/default/tip
   remote: adding changesets
   remote: adding manifests
   remote: adding file changes
-  remote: added 2 changesets with 2 changes to 2 files
+  remote: added 1 changesets with 1 changes to 1 files
   To hg::.*/push-refs.t/repo-from-git2 (re)
-   * [new branch]      7688446e0a5d5b6108443632be74c9bca72d31b1 -> branches/default/tip
+   * [new branch]      8b86a58578d5270969543e287634e3a2f122a338 -> branches/default/tip
+
+  $ git -c cinnabar.data=never -C repo-git2 push origin 7688446e0a5d5b6108443632be74c9bca72d31b1:refs/heads/branches/default/tip
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 1 changes to 1 files
+  To hg::.*/push-refs.t/repo-from-git2 (re)
+     8b86a58..7688446  7688446e0a5d5b6108443632be74c9bca72d31b1 -> branches/default/tip
 
   $ git -C repo-git ls-remote hg::$REPO-from-hg
   7688446e0a5d5b6108443632be74c9bca72d31b1     HEAD
@@ -117,8 +125,8 @@ Pushing `a` and `c` to the default branch.
 
   $ git -C repo-git2 ls-remote
   From hg::.*/push-refs.t/repo-from-git2 (re)
-  7688446e0a5d5b6108443632be74c9bca72d31b1     HEAD
-  7688446e0a5d5b6108443632be74c9bca72d31b1     refs/heads/branches/default/tip
+  0000000000000000000000000000000000000000     HEAD
+  0000000000000000000000000000000000000000     refs/heads/branches/default/tip
 
 Pushing `b` fails because it would add a new head to the branch.
 

I'm not sure when I'll get to actually fixing the issue.

@tinloaf
Copy link
Author

tinloaf commented Apr 23, 2024

Thanks for having a look at this! The problem is compounded by the fact that our HG server rejects pushing new heads, and I'm not sure whether git-cinnabar handles rejected pushes correctly (whatever that would be…)

However, I did look at the hg source, and I'm not sure anymore whether hg itself is actually able to detect the problem 100% of the time. It I read my notes correctly, they also only do that check on the client side (in discovery.py:checkheads()) after refreshing the data from the repository (i.e., a sort-of-fetch). So, if just the correct race condition appears, I think the check in hg also fails to find this condition. I'm not sure if there is even a way of doing this server-side during the push (without having the server just reject the push as ours does).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants