Add support for automagic hg revision identification and pushing

This commit is contained in:
Mark Nauwelaerts
2016-07-09 14:59:17 +02:00
parent f8709175bf
commit ebdd2f32ab
2 changed files with 159 additions and 3 deletions

View File

@@ -217,6 +217,7 @@ class ParserContext:
self.localref = None
self.remoteref = None
self.gitmarks = None
self.hghelper = None
class Parser:
@@ -907,6 +908,45 @@ def parse_commit(parser):
parsed_refs[ref] = None
return
# check if this is an hg commit we have in some other repo
gitmarks = parser.context.gitmarks
if gitmarks:
gitcommit = gitmarks.to_rev(commit_mark)
hgrev = get_rev_hg(gitcommit)
if hgrev:
hghelper = parser.context.hghelper
if not hghelper:
print "error %s rejected not pushing hg based commit %s" % (ref, gitcommit)
raise UserWarning("check-hg-commits")
# must be in some local repo
# find it and push it to the target local repo
# (rather than making a commit into it)
# probably not already in target repo, but let's make sure
if hgrev not in parser.repo:
srepo = hghelper.githgrepo.find_hg_repo(hgrev)
if not srepo:
# pretty bad, if identified as hg revision, we should have it somewhere
# but is possible if the originating repo has been removed now
# warn elaborately and fail given the current settings
description = "\n" \
"commit %s corresponds \nto hg revision %s,\n" \
"but could not find latter in any fetched hg repo.\n" \
"Please resolve the inconsistency or disable pushing hg commits" \
% (gitcommit, hgrev)
die(description)
warn('Pushing hg changeset %s for %s' % (hgrev, gitcommit))
# target is local repo so should have a root
# force push since otherwise forcibly commit anyway
# (and needed for multiple head case etc)
push(srepo, hg.peer(srepo.ui, {}, parser.repo.root), [hgbin(hgrev)], True)
else:
# could already be present, particularly in shared proxy repo
warn('Using hg changeset %s for %s' % (hgrev, gitcommit))
# track mark and are done here
parsed_refs[ref] = hgrev
marks.new_mark(hgrev, commit_mark)
return
def getfilectx(repo, memctx, f):
of = files[f]
if 'deleted' in of:
@@ -1420,20 +1460,48 @@ def do_push_refspec(parser, refspec):
marks = os.path.join(dirname, 'marks-git')
if os.path.exists(marks):
cmd.append('--import-marks=%s' % marks)
# optionally reuse existing hg commits in local repos
check_hg_commits = get_config('remote-hg.check-hg-commits').strip()
use_hg_commits = check_hg_commits in ('fail', 'push')
# no commit of marks if dry-dry_run
# and only commit if all went ok,
# otherwise some commits may no longer be exported next time/try around
tmpmarks = ''
if not dry_run:
if use_hg_commits or not dry_run:
tmpmarks = os.path.join(dirname, 'marks-git-%d' % (os.getpid()))
cmd.append('--export-marks=%s' % tmpmarks)
cmd.append(refs[0])
export = subprocess.Popen(cmd, stdin=None, stdout=subprocess.PIPE)
# a parameter would obviously be nicer here ...
force_push = force
ok = False
tmpfastexport = None
try:
ok = do_push_hg(Parser(parser.repo, export.stdout, ctx))
if use_hg_commits:
# we need the mapping from marks to commit
# so store the output first to a file (and marks get saved also),
# and then process that file
tmpfastexport = open(os.path.join(dirname, 'git-fast-export-%d' % (os.getpid())), 'w+b')
subprocess.check_call(cmd, stdin=None, stdout=tmpfastexport)
try:
import imp
ctx.hghelper = imp.load_source('hghelper', \
os.path.join(os.path.dirname(__file__), 'git-hg-helper'))
ctx.hghelper.init_git(gitdir)
ctx.gitmarks = ctx.hghelper.GitMarks(tmpmarks)
# let processing know it should not bother pushing if not requested
if check_hg_commits != 'push':
ctx.hghelper = None
except:
die("check-hg-commits setup failed; is git-hg-helper also installed?")
tmpfastexport.seek(0)
try:
ok = do_push_hg(Parser(parser.repo, tmpfastexport, ctx))
except UserWarning:
ok = False
else:
# simply feed fast-export directly to processing
export = subprocess.Popen(cmd, stdin=None, stdout=subprocess.PIPE)
ok = do_push_hg(Parser(parser.repo, export.stdout, ctx))
finally:
if tmpmarks and os.path.exists(tmpmarks):
if ok and not dry_run:
@@ -1441,6 +1509,9 @@ def do_push_refspec(parser, refspec):
os.rename(tmpmarks, marks)
else:
os.remove(tmpmarks)
if tmpfastexport and os.path.exists(tmpfastexport.name):
tmpfastexport.close()
os.remove(tmpfastexport.name)
def do_push(parser):
if os.environ.get('GIT_REMOTE_HG_DEBUG_PUSH'):

View File

@@ -61,4 +61,89 @@ test_expect_success 'source:dest bookmark' '
check_bookmark hgrepo feature-a one
'
setup_check_hg_commits_repo () {
(
rm -rf hgrepo* &&
hg init hgrepo &&
cd hgrepo &&
echo zero > content &&
hg add content &&
hg commit -m zero
) &&
git clone "hg::hgrepo" gitrepo &&
hg clone hgrepo hgrepo.second &&
(
cd gitrepo &&
git remote add second hg::../hgrepo.second &&
git fetch second
) &&
(
cd hgrepo &&
echo one > content &&
hg commit -m one &&
echo two > content &&
hg commit -m two &&
echo three > content &&
hg commit -m three &&
hg move content content-move &&
hg commit -m moved &&
hg move content-move content &&
hg commit -m restored
)
}
git config --global remote-hg.check-hg-commits fail
test_expect_success 'check-hg-commits with fail mode' '
test_when_finished "rm -rf gitrepo* hgrepo*" &&
setup_check_hg_commits_repo &&
(
cd gitrepo &&
git fetch origin &&
git reset --hard origin/master &&
! git push second master 2>../error
)
cat error &&
grep rejected error | grep hg
'
git config --global remote-hg.check-hg-commits push
# codepath for push is slightly different depending on shared proxy involved
# so tweak to test both
check_hg_commits_push () {
test_when_finished "rm -rf gitrepo* hgrepo*" &&
setup_check_hg_commits_repo &&
(
cd gitrepo &&
git fetch origin &&
git reset --hard origin/master &&
git push second master 2> ../error
) &&
cat error &&
grep "hg changeset" error &&
hg log -R hgrepo > expected &&
hg log -R hgrepo.second | grep -v bookmark > actual &&
test_cmp expected actual
}
unset GIT_REMOTE_HG_TEST_REMOTE
test_expect_success 'check-hg-commits with push mode - no local proxy' '
check_hg_commits_push
'
GIT_REMOTE_HG_TEST_REMOTE=1 &&
export GIT_REMOTE_HG_TEST_REMOTE
test_expect_success 'check-hg-commits with push mode - with local proxy' '
check_hg_commits_push
'
test_done