From: Junio C Hamano Date: Mon, 22 Apr 2013 01:39:58 +0000 (-0700) Subject: Merge branch 'fc/remote-hg' X-Git-Tag: v1.8.3-rc0~38 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/37d32de72a495b8f8ea985fc97ad7ee8d1d82b06?hp=4b35b007a6bd8b76bd37589fa397c12265935029 Merge branch 'fc/remote-hg' Updates remote-hg helper (in contrib/). * fc/remote-hg: (21 commits) remote-hg: activate graphlog extension for hg_log() remote-hg: fix bad file paths remote-hg: document location of stored hg repository remote-hg: fix bad state issue remote-hg: add 'insecure' option remote-hg: add simple mail test remote-hg: add basic author tests remote-hg: show more proper errors remote-hg: force remote push remote-hg: push to the appropriate branch remote-hg: update tags globally remote-hg: update remote bookmarks remote-hg: refactor export remote-hg: split bookmark handling remote-hg: redirect buggy mercurial output remote-hg: trivial test cleanups remote-hg: make sure fake bookmarks are updated remote-hg: fix for files with spaces remote-hg: properly report errors on bookmark pushes remote-hg: add missing config variable in doc ... --- diff --git a/contrib/remote-helpers/git-remote-hg b/contrib/remote-helpers/git-remote-hg index 45f6c80d45..548133121d 100755 --- a/contrib/remote-helpers/git-remote-hg +++ b/contrib/remote-helpers/git-remote-hg @@ -8,8 +8,11 @@ # Just copy to your ~/bin, or anywhere in your $PATH. # Then you can clone with: # git clone hg::/path/to/mercurial/repo/ +# +# For remote repositories a local clone is stored in +# "$GIT_DIR/hg/origin/clone/.hg/". -from mercurial import hg, ui, bookmarks, context, util, encoding +from mercurial import hg, ui, bookmarks, context, util, encoding, node, error import re import sys @@ -18,11 +21,22 @@ import json import shutil import subprocess import urllib +import atexit # # If you want to switch to hg-git compatibility mode: # git config --global remote-hg.hg-git-compat true # +# If you are not in hg-git-compat mode and want to disable the tracking of +# named branches: +# git config --global remote-hg.track-branches false +# +# If you don't want to force pushes (and thus risk creating new remote heads): +# git config --global remote-hg.force-push false +# +# If you want the equivalent of hg's clone/pull--insecure option: +# git config remote-hg.insecure true +# # git: # Sensible defaults for git. # hg bookmarks are exported as git branches, hg branches are prefixed @@ -56,6 +70,9 @@ def hgmode(mode): m = { '100755': 'x', '120000': 'l' } return m.get(mode, '') +def hghex(node): + return hg.node.hex(node) + def get_config(config): cmd = ['git', 'config', '--get', config] process = subprocess.Popen(cmd, stdout=subprocess.PIPE) @@ -188,9 +205,15 @@ class Parser: tz = ((tz / 100) * 3600) + ((tz % 100) * 60) return (user, int(date), -tz) +def fix_file_path(path): + if not os.path.isabs(path): + return path + return os.path.relpath(path, '/') + def export_file(fc): d = fc.data() - print "M %s inline %s" % (gitmode(fc.flags()), fc.path()) + path = fix_file_path(fc.path()) + print "M %s inline %s" % (gitmode(fc.flags()), path) print "data %d" % len(d) print d @@ -267,17 +290,30 @@ def get_repo(url, alias): myui = ui.ui() myui.setconfig('ui', 'interactive', 'off') + myui.fout = sys.stderr + + try: + if get_config('remote-hg.insecure') == 'true\n': + myui.setconfig('web', 'cacerts', '') + except subprocess.CalledProcessError: + pass if hg.islocal(url): repo = hg.repository(myui, url) else: local_path = os.path.join(dirname, 'clone') if not os.path.exists(local_path): - peer, dstpeer = hg.clone(myui, {}, url, local_path, update=False, pull=True) + try: + peer, dstpeer = hg.clone(myui, {}, url, local_path, update=True, pull=True) + except: + die('Repository error') repo = dstpeer.local() else: repo = hg.repository(myui, local_path) - peer = hg.peer(myui, {}, url) + try: + peer = hg.peer(myui, {}, url) + except: + die('Repository error') repo.pull(peer, heads=None, force=True) return repo @@ -372,7 +408,7 @@ def export_ref(repo, name, kind, head): for f in modified: export_file(c.filectx(f)) for f in removed: - print "D %s" % (f) + print "D %s" % (fix_file_path(f)) print count += 1 @@ -532,7 +568,6 @@ def parse_blob(parser): data = parser.get_data() blob_marks[mark] = data parser.next() - return def get_merge_files(repo, p1, p2, files): for e in repo[p1].files(): @@ -543,7 +578,7 @@ def get_merge_files(repo, p1, p2, files): files[e] = f def parse_commit(parser): - global marks, blob_marks, bmarks, parsed_refs + global marks, blob_marks, parsed_refs global mode from_mark = merge_mark = None @@ -576,7 +611,7 @@ def parse_commit(parser): mark = int(mark_ref[1:]) f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] } elif parser.check('D'): - t, path = line.split(' ') + t, path = line.split(' ', 1) f = { 'deleted' : True } else: die('Unknown file command: %s' % line) @@ -619,11 +654,15 @@ def parse_commit(parser): if merge_mark: get_merge_files(repo, p1, p2, files) + # Check if the ref is supposed to be a named branch + if ref.startswith('refs/heads/branches/'): + extra['branch'] = ref[len('refs/heads/branches/'):] + if mode == 'hg': i = data.find('\n--HG--\n') if i >= 0: tmp = data[i + len('\n--HG--\n'):].strip() - for k, v in [e.split(' : ') for e in tmp.split('\n')]: + for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]: if k == 'rename': old, new = v.split(' => ', 1) files[new]['rename'] = old @@ -648,10 +687,11 @@ def parse_commit(parser): rev = repo[node].rev() parsed_refs[ref] = node - marks.new_mark(rev, commit_mark) def parse_reset(parser): + global parsed_refs + ref = parser[1] parser.next() # ugh @@ -681,6 +721,8 @@ def parse_tag(parser): def do_export(parser): global parsed_refs, bmarks, peer + p_bmarks = [] + parser.next() for line in parser.each_block('done'): @@ -699,28 +741,55 @@ def do_export(parser): for ref, node in parsed_refs.iteritems(): if ref.startswith('refs/heads/branches'): - pass + print "ok %s" % ref elif ref.startswith('refs/heads/'): bmark = ref[len('refs/heads/'):] - if bmark in bmarks: - old = bmarks[bmark].hex() - else: - old = '' - if not bookmarks.pushbookmark(parser.repo, bmark, old, node): - continue + p_bmarks.append((bmark, node)) + continue elif ref.startswith('refs/tags/'): tag = ref[len('refs/tags/'):] - parser.repo.tag([tag], node, None, True, None, {}) + if mode == 'git': + msg = 'Added tag %s for changeset %s' % (tag, hghex(node[:6])); + parser.repo.tag([tag], node, msg, False, None, {}) + else: + parser.repo.tag([tag], node, None, True, None, {}) + print "ok %s" % ref else: # transport-helper/fast-export bugs continue + + if peer: + parser.repo.push(peer, force=force_push) + + # handle bookmarks + for bmark, node in p_bmarks: + ref = 'refs/heads/' + bmark + new = hghex(node) + + if bmark in bmarks: + old = bmarks[bmark].hex() + else: + old = '' + + if bmark == 'master' and 'master' not in parser.repo._bookmarks: + # fake bookmark + pass + elif bookmarks.pushbookmark(parser.repo, bmark, old, new): + # updated locally + pass + else: + print "error %s" % ref + continue + + if peer: + if not peer.pushkey('bookmarks', bmark, old, new): + print "error %s" % ref + continue + print "ok %s" % ref print - if peer: - parser.repo.push(peer, force=False) - def fix_path(alias, repo, orig_url): repo_url = util.url(repo.url()) url = util.url(orig_url) @@ -733,7 +802,7 @@ def main(args): global prefix, dirname, branches, bmarks global marks, blob_marks, parsed_refs global peer, mode, bad_mail, bad_name - global track_branches + global track_branches, force_push, is_tmp alias = args[1] url = args[2] @@ -741,12 +810,16 @@ def main(args): hg_git_compat = False track_branches = True + force_push = True + try: if get_config('remote-hg.hg-git-compat') == 'true\n': hg_git_compat = True track_branches = False if get_config('remote-hg.track-branches') == 'false\n': track_branches = False + if get_config('remote-hg.force-push') == 'false\n': + force_push = False except subprocess.CalledProcessError: pass @@ -771,6 +844,7 @@ def main(args): bmarks = {} blob_marks = {} parsed_refs = {} + marks = None repo = get_repo(url, alias) prefix = 'refs/hg/%s' % alias @@ -798,9 +872,13 @@ def main(args): die('unhandled command: %s' % line) sys.stdout.flush() +def bye(): + if not marks: + return if not is_tmp: marks.store() else: shutil.rmtree(dirname) +atexit.register(bye) sys.exit(main(sys.argv)) diff --git a/contrib/remote-helpers/test-hg-bidi.sh b/contrib/remote-helpers/test-hg-bidi.sh index 1d61982436..f36895311e 100755 --- a/contrib/remote-helpers/test-hg-bidi.sh +++ b/contrib/remote-helpers/test-hg-bidi.sh @@ -22,7 +22,6 @@ fi # clone to a git repo git_clone () { - hg -R $1 bookmark -f -r tip master && git clone -q "hg::$PWD/$1" $2 } @@ -30,6 +29,7 @@ git_clone () { hg_clone () { ( hg init $2 && + hg -R $2 bookmark -i master && cd $1 && git push -q "hg::$PWD/../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' ) && @@ -50,7 +50,8 @@ hg_push () { } hg_log () { - hg -R $1 log --graph --debug | grep -v 'tag: *default/' + hg -R $1 log --graph --debug >log && + grep -v 'tag: *default/' log } setup () { @@ -62,6 +63,8 @@ setup () { echo "commit = -d \"0 0\"" echo "debugrawcommit = -d \"0 0\"" echo "tag = -d \"0 0\"" + echo "[extensions]" + echo "graphlog =" ) >> "$HOME"/.hgrc && git config --global remote-hg.hg-git-compat true @@ -200,8 +203,8 @@ test_expect_success 'hg branch' ' hg_push hgrepo gitrepo && hg_clone gitrepo hgrepo2 && - : TODO, avoid "master" bookmark && - (cd hgrepo2 && hg checkout gamma) && + : Back to the common revision && + (cd hgrepo && hg checkout default) && hg_log hgrepo > expected && hg_log hgrepo2 > actual && diff --git a/contrib/remote-helpers/test-hg-hg-git.sh b/contrib/remote-helpers/test-hg-hg-git.sh index 3f253b7de7..253e65aaa8 100755 --- a/contrib/remote-helpers/test-hg-hg-git.sh +++ b/contrib/remote-helpers/test-hg-hg-git.sh @@ -27,7 +27,6 @@ fi # clone to a git repo with git git_clone_git () { - hg -R $1 bookmark -f -r tip master && git clone -q "hg::$PWD/$1" $2 } @@ -35,6 +34,7 @@ git_clone_git () { hg_clone_git () { ( hg init $2 && + hg -R $2 bookmark -i master && cd $1 && git push -q "hg::$PWD/../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' ) && @@ -47,7 +47,7 @@ git_clone_hg () { ( git init -q $2 && cd $1 && - hg bookmark -f -r tip master && + hg bookmark -i -f -r tip master && hg -q push -r master ../$2 || true ) } @@ -78,7 +78,8 @@ hg_push_hg () { } hg_log () { - hg -R $1 log --graph --debug | grep -v 'tag: *default/' + hg -R $1 log --graph --debug >log && + grep -v 'tag: *default/' log } git_log () { @@ -97,6 +98,7 @@ setup () { echo "[extensions]" echo "hgext.bookmarks =" echo "hggit =" + echo "graphlog =" ) >> "$HOME"/.hgrc && git config --global receive.denycurrentbranch warn git config --global remote-hg.hg-git-compat true diff --git a/contrib/remote-helpers/test-hg.sh b/contrib/remote-helpers/test-hg.sh index 7bb81f2f8e..d5b873051f 100755 --- a/contrib/remote-helpers/test-hg.sh +++ b/contrib/remote-helpers/test-hg.sh @@ -118,4 +118,40 @@ test_expect_success 'update bookmark' ' hg -R hgrepo bookmarks | egrep "devel[ ]+3:" ' +author_test () { + echo $1 >> content && + hg commit -u "$2" -m "add $1" && + echo "$3" >> ../expected +} + +test_expect_success 'authors' ' + mkdir -p tmp && cd tmp && + test_when_finished "cd .. && rm -rf tmp" && + + ( + hg init hgrepo && + cd hgrepo && + + touch content && + hg add content && + + author_test alpha "" "H G Wells " && + author_test beta "test" "test " && + author_test beta "test (comment)" "test " && + author_test gamma "" "Unknown " && + author_test delta "name" "name " && + author_test epsilon "name " && + author_test zeta " test " "test " && + author_test eta "test < test@example.com >" "test " && + author_test theta "test >test@example.com>" "test " && + author_test iota "test < test example com>" "test " && + author_test kappa "test@example.com" "test@example.com " + ) && + + git clone "hg::$PWD/hgrepo" gitrepo && + git --git-dir=gitrepo/.git log --reverse --format="%an <%ae>" > actual && + + test_cmp expected actual +' + test_done