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
  ...
This commit is contained in:
Junio C Hamano 2013-04-21 18:39:58 -07:00
commit 37d32de72a
4 changed files with 148 additions and 29 deletions

View file

@ -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))

View file

@ -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 &&

View file

@ -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

View file

@ -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 <wells@example.com>" &&
author_test beta "test" "test <unknown>" &&
author_test beta "test <test@example.com> (comment)" "test <unknown>" &&
author_test gamma "<test@example.com>" "Unknown <test@example.com>" &&
author_test delta "name<test@example.com>" "name <test@example.com>" &&
author_test epsilon "name <test@example.com" "name <unknown>" &&
author_test zeta " test " "test <unknown>" &&
author_test eta "test < test@example.com >" "test <test@example.com>" &&
author_test theta "test >test@example.com>" "test <unknown>" &&
author_test iota "test < test <at> example <dot> com>" "test <unknown>" &&
author_test kappa "test@example.com" "test@example.com <unknown>"
) &&
git clone "hg::$PWD/hgrepo" gitrepo &&
git --git-dir=gitrepo/.git log --reverse --format="%an <%ae>" > actual &&
test_cmp expected actual
'
test_done