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

Mirror pushes back to hg #335

Open
riastradh opened this issue Oct 14, 2024 · 4 comments
Open

Mirror pushes back to hg #335

riastradh opened this issue Oct 14, 2024 · 4 comments

Comments

@riastradh
Copy link

Suppose I have an hg repository, say ssh://hg.example.com/src.

Developers who have installed git-cinnabar can interact with it that way. Can it be exposed to developers who don't have git-cinnabar installed, for push and pull access, say via an intermediate ssh://git.example.com/src.git?

Obviously a cron job or notify hook can trigger git fetch --mirror in ssh://git.example.com/src.git, and developers can git clone and pull from ssh://git.example.com/src.git to get and track a normal git repository, modulo the unconventional refnames. But what about push?

  1. I tried creating a pre-receive hook in ssh://git.example.com that does git push, but that fails because with git-cinnabar, git push to a remote hg repo updates some local refs too (presumably to store the hg hash <-> git hash correspondence):

    remote: error: update_ref failed for ref 'refs/cinnabar/refs/heads/branches/default/tip': ref updates forbidden inside quarantine environment

  2. I tried creating a post-receive hook in ssh://git.example.com that does git push, and while that allows the push to proceed, it's wrong because the git repo has already committed the ref updates before the hg repo has had a chance to review, and possibly reject, the push. So the git repo may wind up falsely accepting a push that it should have rejected, and committing ref updates for the errant changes. (Same problem with post-update, of course.)

Is there another approach that could make this work, or is there a fundamental reason blocking it?

@glandium
Copy link
Owner

remote: error: update_ref failed for ref 'refs/cinnabar/refs/heads/branches/default/tip': ref updates forbidden inside quarantine environment
Where does this error message come from exactly?

@riastradh
Copy link
Author

remote: error: update_ref failed for ref 'refs/cinnabar/refs/heads/branches/default/tip': ref updates forbidden inside quarantine environment Where does this error message come from exactly?

Here's the pre-receive hook I used:

#!/bin/sh

set -Ceu
set -x

cmd="git push origin"
while read old new ref stuff; do
	# XXX require fast-forward
	cmd="${cmd} ${new}:${ref}"
done
$cmd

Here's the output when I push to a git repository with that pre-receive hook, which had been cloned from an origin of hg::/path/to/hg/repo:

Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 335 bytes | 335.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0), pack-reused 0
remote: + cmd='git push origin'
remote: + read old new ref stuff
remote: + cmd='git push origin c882229d437b3afd2f2d5f1d9a5b1f6bfc04b10e:refs/heads/branches/default/tip'
remote: + read old new ref stuff
remote: + git push origin 'c882229d437b3afd2f2d5f1d9a5b1f6bfc04b10e:refs/heads/branches/default/tip'
remote: remote: adding changesets
remote: remote: adding manifests
remote: remote: adding file changes
remote: remote: added 1 changesets with 1 changes to 1 files
remote: error: update_ref failed for ref 'refs/cinnabar/refs/heads/branches/default/tip': ref updates forbidden inside quarantine environment
remote: To hg::/path/to/hg/repo
remote:    be0de8f..c882229  c882229d437b3afd2f2d5f1d9a5b1f6bfc04b10e -> branches/default/tip
remote: error: update_ref failed for ref 'refs/heads/branches/default/tip': ref updates forbidden inside quarantine environment
remote: fatal: called `Result::unwrap()` on an `Err` value: "ref updates forbidden inside quarantine environment"
remote: Run the command again with `git -c cinnabar.check=traceback <command>` to see the full traceback.
remote: error: git-remote-hg died of signal 6
To /path/to/git/repo
 ! [remote rejected] branches/default/tip -> branches/default/tip (pre-receive hook declined)
error: failed to push some refs to '/path/to/git/repo'

@glandium
Copy link
Owner

Presumably, even if git-cinnabar didn't do a ref update, your push would too, and that would be rejected as well. That is, if your origin was a git repo rather than a mercurial remote, I would expect you to get a similar error when the push tries to update a ref under refs/remotes/origin/. That it fails on refs/cinnabar/ stuff first, is kind of incidental.
You should probably try to get your setup working with a pure git repo first (possibly asking for the recommended setup on the git mailing list)

@riastradh
Copy link
Author

riastradh commented Dec 25, 2024

After some discussion on the git mailing list (https://lore.kernel.org/git/[email protected]/T/), and some further experimentation, it looks like:

  • The pure-git ref updates which fail in the quarantine environment of the pre-receive hook can be avoided by using git clone --bare instead of git clone --mirror so the repository doesn't have tracking refs that it tries to update on push. But it's also not quite right, even if the git-cinnabar ref updates could be avoided during the quarantine, because the pre-receive hook is too early to commit to a successful push -- subsequent parts of git-receive-pack might fail.
  • The update hook works fine for individual ref updates, but not for atomic batches of ref updates.
  • With some care to avoid recursion and limit it to pushes but not fetches, the reference-transaction hook can do this for atomic batches of ref updates -- but it'll take more effort than I have spent to determine whether the locking order arising from this is consistent and the potential for atomic batches of updates isn't important enough yet for me to pursue this at the moment.

The working update hook is essentially:

#!/bin/sh

ref=$1
old=$2
new=$3

case $ref in
refs/heads/*)
	;;
*)	echo 'only head updates are allowed' >&2
	exit 1
	;;
esac

exec 3>$GIT_DIR/cinnabridge.lock
flock 3 3<3

git push origin "$new:$ref" || exit $?

I may want to revisit atomic batches of ref updates in the future, but for now this will serve, together with setting receive.advertiseAtomic to false so clients aren't misled into thinking ref update batches might be atomic.

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