diff options
Diffstat (limited to 'contrib/remote-helpers/git-remote-hg')
-rwxr-xr-x | contrib/remote-helpers/git-remote-hg | 745 |
1 files changed, 597 insertions, 148 deletions
diff --git a/contrib/remote-helpers/git-remote-hg b/contrib/remote-helpers/git-remote-hg index 328c2dc76d..eb89ef6779 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, encoding, node, error, extensions, discovery, util import re import sys @@ -18,8 +21,21 @@ import json import shutil import subprocess import urllib +import atexit +import urlparse, hashlib +import time as ptime # +# If you want to see Mercurial revisions as Git commit notes: +# git config core.notesRef refs/notes/hg +# +# 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 want the equivalent of hg's clone/pull--insecure option: +# git config --global remote-hg.insecure true +# # If you want to switch to hg-git compatibility mode: # git config --global remote-hg.hg-git-compat true # @@ -35,10 +51,13 @@ import urllib # NAME_RE = re.compile('^([^<>]+)') -AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$') +AUTHOR_RE = re.compile('^([^<>]+?)? ?[<>]([^<>]*)(?:$|>)') +EMAIL_RE = re.compile(r'([^ \t<>]+@[^ \t<>]+)') AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$') RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)') +VERSION = 2 + def die(msg, *args): sys.stderr.write('ERROR: %s\n' % (msg % args)) sys.exit(1) @@ -56,22 +75,62 @@ def hgmode(mode): m = { '100755': 'x', '120000': 'l' } return m.get(mode, '') +def hghex(n): + return node.hex(n) + +def hgbin(n): + return node.bin(n) + +def hgref(ref): + return ref.replace('___', ' ') + +def gitref(ref): + return ref.replace(' ', '___') + +def check_version(*check): + if not hg_version: + return True + return hg_version >= check + def get_config(config): cmd = ['git', 'config', '--get', config] process = subprocess.Popen(cmd, stdout=subprocess.PIPE) output, _ = process.communicate() return output +def get_config_bool(config, default=False): + value = get_config(config).rstrip('\n') + if value == "true": + return True + elif value == "false": + return False + else: + return default + class Marks: - def __init__(self, path): + def __init__(self, path, repo): self.path = path + self.repo = repo + self.clear() + self.load() + + if self.version < VERSION: + if self.version == 1: + self.upgrade_one() + + # upgraded? + if self.version < VERSION: + self.clear() + self.version = VERSION + + def clear(self): self.tips = {} self.marks = {} self.rev_marks = {} self.last_mark = 0 - - self.load() + self.version = 0 + self.last_note = 0 def load(self): if not os.path.exists(self.path): @@ -82,12 +141,22 @@ class Marks: self.tips = tmp['tips'] self.marks = tmp['marks'] self.last_mark = tmp['last-mark'] + self.version = tmp.get('version', 1) + self.last_note = tmp.get('last-note', 0) for rev, mark in self.marks.iteritems(): - self.rev_marks[mark] = int(rev) + self.rev_marks[mark] = rev + + def upgrade_one(self): + def get_id(rev): + return hghex(self.repo.changelog.node(int(rev))) + self.tips = dict((name, get_id(rev)) for name, rev in self.tips.iteritems()) + self.marks = dict((get_id(rev), mark) for rev, mark in self.marks.iteritems()) + self.rev_marks = dict((mark, get_id(rev)) for mark, rev in self.rev_marks.iteritems()) + self.version = 2 def dict(self): - return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark } + return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark, 'version' : self.version, 'last-note' : self.last_note } def store(self): json.dump(self.dict(), open(self.path, 'w')) @@ -96,26 +165,30 @@ class Marks: return str(self.dict()) def from_rev(self, rev): - return self.marks[str(rev)] + return self.marks[rev] def to_rev(self, mark): - return self.rev_marks[mark] + return str(self.rev_marks[mark]) + + def next_mark(self): + self.last_mark += 1 + return self.last_mark def get_mark(self, rev): self.last_mark += 1 - self.marks[str(rev)] = self.last_mark + self.marks[rev] = self.last_mark return self.last_mark def new_mark(self, rev, mark): - self.marks[str(rev)] = mark + self.marks[rev] = mark self.rev_marks[mark] = rev self.last_mark = mark def is_marked(self, rev): - return self.marks.has_key(str(rev)) + return rev in self.marks def get_tip(self, branch): - return self.tips.get(branch, 0) + return str(self.tips[branch]) def set_tip(self, branch, tip): self.tips[branch] = tip @@ -160,8 +233,6 @@ class Parser: return sys.stdin.read(size) def get_author(self): - global bad_mail - ex = None m = RAW_AUTHOR_RE.match(self.line) if not m: @@ -188,19 +259,41 @@ class Parser: tz = ((tz / 100) * 3600) + ((tz % 100) * 60) return (user, int(date), -tz) -def export_file(fc): - d = fc.data() - print "M %s inline %s" % (gitmode(fc.flags()), fc.path()) - print "data %d" % len(d) - print d +def fix_file_path(path): + if not os.path.isabs(path): + return path + return os.path.relpath(path, '/') + +def export_files(files): + final = [] + for f in files: + fid = node.hex(f.filenode()) + + if fid in filenodes: + mark = filenodes[fid] + else: + mark = marks.next_mark() + filenodes[fid] = mark + d = f.data() + + print "blob" + print "mark :%u" % mark + print "data %d" % len(d) + print d + + path = fix_file_path(f.path()) + final.append((gitmode(f.flags()), mark, path)) + + return final def get_filechanges(repo, ctx, parent): modified = set() added = set() removed = set() + # load earliest manifest first for caching reasons + prev = parent.manifest().copy() cur = ctx.manifest() - prev = repo[parent].manifest().copy() for fn in cur: if fn in prev: @@ -221,9 +314,13 @@ def fixup_user_git(user): name = m.group(1) mail = m.group(2).strip() else: - m = NAME_RE.match(user) + m = EMAIL_RE.match(user) if m: - name = m.group(1).strip() + mail = m.group(1) + else: + m = NAME_RE.match(user) + if m: + name = m.group(1).strip() return (name, mail) def fixup_user_hg(user): @@ -248,8 +345,6 @@ def fixup_user_hg(user): return (name, mail) def fixup_user(user): - global mode, bad_mail - if mode == 'git': name, mail = fixup_user_git(user) else: @@ -262,53 +357,105 @@ def fixup_user(user): return '%s <%s>' % (name, mail) +def updatebookmarks(repo, peer): + remotemarks = peer.listkeys('bookmarks') + localmarks = repo._bookmarks + + if not remotemarks: + return + + for k, v in remotemarks.iteritems(): + localmarks[k] = hgbin(v) + + if hasattr(localmarks, 'write'): + localmarks.write() + else: + bookmarks.write(repo) + def get_repo(url, alias): - global dirname, peer + global peer myui = ui.ui() myui.setconfig('ui', 'interactive', 'off') + myui.fout = sys.stderr + + if get_config_bool('remote-hg.insecure'): + myui.setconfig('web', 'cacerts', '') - if hg.islocal(url): + extensions.loadall(myui) + + if hg.islocal(url) and not os.environ.get('GIT_REMOTE_HG_TEST_REMOTE'): repo = hg.repository(myui, url) + if not os.path.exists(dirname): + os.makedirs(dirname) else: + shared_path = os.path.join(gitdir, 'hg') + + # check and upgrade old organization + hg_path = os.path.join(shared_path, '.hg') + if os.path.exists(shared_path) and not os.path.exists(hg_path): + repos = os.listdir(shared_path) + for x in repos: + local_hg = os.path.join(shared_path, x, 'clone', '.hg') + if not os.path.exists(local_hg): + continue + if not os.path.exists(hg_path): + shutil.move(local_hg, hg_path) + shutil.rmtree(os.path.join(shared_path, x, 'clone')) + + # setup shared repo (if not there) + try: + hg.peer(myui, {}, shared_path, create=True) + except error.RepoError: + pass + + if not os.path.exists(dirname): + os.makedirs(dirname) + 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) - repo = dstpeer.local() + hg.share(myui, shared_path, local_path, update=False) else: - repo = hg.repository(myui, local_path) + # make sure the shared path is always up-to-date + util.writefile(os.path.join(local_path, '.hg', 'sharedpath'), hg_path) + + repo = hg.repository(myui, local_path) + try: peer = hg.peer(myui, {}, url) - repo.pull(peer, heads=None, force=True) + except: + die('Repository error') + repo.pull(peer, heads=None, force=True) + + updatebookmarks(repo, peer) return repo def rev_to_mark(rev): - global marks - return marks.from_rev(rev) + return marks.from_rev(rev.hex()) def mark_to_rev(mark): - global marks return marks.to_rev(mark) def export_ref(repo, name, kind, head): - global prefix, marks, mode - ename = '%s/%s' % (kind, name) - tip = marks.get_tip(ename) + try: + tip = marks.get_tip(ename) + tip = repo[tip].rev() + except: + tip = 0 - # mercurial takes too much time checking this - if tip and tip == head.rev(): - # nothing to do - return revs = xrange(tip, head.rev() + 1) - count = 0 - - revs = [rev for rev in revs if not marks.is_marked(rev)] + total = len(revs) for rev in revs: c = repo[rev] - (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(c.node()) + node = c.node() + + if marks.is_marked(c.hex()): + continue + + (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node) rev_branch = extra['branch'] author = "%s %d %s" % (fixup_user(user), time, gittz(tz)) @@ -318,7 +465,7 @@ def export_ref(repo, name, kind, head): else: committer = author - parents = [p for p in repo.changelog.parentrevs(rev) if p >= 0] + parents = [repo[p] for p in repo.changelog.parentrevs(rev) if p >= 0] if len(parents) == 0: modified = c.manifest().keys() @@ -326,6 +473,8 @@ def export_ref(repo, name, kind, head): else: modified, removed = get_filechanges(repo, c, parents[0]) + desc += '\n' + if mode == 'hg': extra_msg = '' @@ -349,15 +498,16 @@ def export_ref(repo, name, kind, head): else: extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value)) - desc += '\n' if extra_msg: desc += '\n--HG--\n' + extra_msg if len(parents) == 0 and rev: print 'reset %s/%s' % (prefix, ename) + modified_final = export_files(c.filectx(f) for f in modified) + print "commit %s/%s" % (prefix, ename) - print "mark :%d" % (marks.get_mark(rev)) + print "mark :%d" % (marks.get_mark(c.hex())) print "author %s" % (author) print "committer %s" % (committer) print "data %d" % (len(desc)) @@ -368,29 +518,53 @@ def export_ref(repo, name, kind, head): if len(parents) > 1: print "merge :%s" % (rev_to_mark(parents[1])) - for f in modified: - export_file(c.filectx(f)) for f in removed: - print "D %s" % (f) + print "D %s" % (fix_file_path(f)) + for f in modified_final: + print "M %s :%u %s" % f print - count += 1 - if (count % 100 == 0): - print "progress revision %d '%s' (%d/%d)" % (rev, name, count, len(revs)) - print "#############################################################" + progress = (rev - tip) + if (progress % 100 == 0): + print "progress revision %d '%s' (%d/%d)" % (rev, name, progress, total) # make sure the ref is updated print "reset %s/%s" % (prefix, ename) - print "from :%u" % rev_to_mark(rev) + print "from :%u" % rev_to_mark(head) print - marks.set_tip(ename, rev) + pending_revs = set(revs) - notes + if pending_revs: + note_mark = marks.next_mark() + ref = "refs/notes/hg" + + print "commit %s" % ref + print "mark :%d" % (note_mark) + print "committer remote-hg <> %d %s" % (ptime.time(), gittz(ptime.timezone)) + desc = "Notes for %s\n" % (name) + print "data %d" % (len(desc)) + print desc + if marks.last_note: + print "from :%u" % marks.last_note + + for rev in pending_revs: + notes.add(rev) + c = repo[rev] + print "N inline :%u" % rev_to_mark(c) + msg = c.hex() + print "data %d" % (len(msg)) + print msg + print + + marks.last_note = note_mark + + marks.set_tip(ename, head.hex()) def export_tag(repo, tag): - export_ref(repo, tag, 'tags', repo[tag]) + export_ref(repo, tag, 'tags', repo[hgref(tag)]) def export_bookmark(repo, bmark): - head = bmarks[bmark] + head = bmarks[hgref(bmark)] export_ref(repo, bmark, 'bookmarks', head) def export_branch(repo, branch): @@ -399,12 +573,9 @@ def export_branch(repo, branch): export_ref(repo, branch, 'branches', head) def export_head(repo): - global g_head export_ref(repo, g_head[0], 'bookmarks', g_head[1]) def do_capabilities(parser): - global prefix, dirname - print "import" print "export" print "refspec refs/heads/branches/*:%s/branches/*" % prefix @@ -416,73 +587,68 @@ def do_capabilities(parser): if os.path.exists(path): print "*import-marks %s" % path print "*export-marks %s" % path + print "option" print -def get_branch_tip(repo, branch): - global branches +def branch_tip(branch): + return branches[branch][-1] - heads = branches.get(branch, None) +def get_branch_tip(repo, branch): + heads = branches.get(hgref(branch), None) if not heads: return None # verify there's only one head if (len(heads) > 1): warn("Branch '%s' has more than one head, consider merging" % branch) - # older versions of mercurial don't have this - if hasattr(repo, "branchtip"): - return repo.branchtip(branch) + return branch_tip(hgref(branch)) return heads[0] def list_head(repo, cur): - global g_head, bmarks + global g_head, fake_bmark - head = bookmarks.readcurrent(repo) - if head: - node = repo[head] - else: - # fake bookmark from current branch - head = cur - node = repo['.'] - if not node: - node = repo['tip'] - if not node: - return - if head == 'default': - head = 'master' - bmarks[head] = node + if 'default' not in branches: + # empty repo + return + + node = repo[branch_tip('default')] + head = 'master' if not 'master' in bmarks else 'default' + fake_bmark = head + bmarks[head] = node + head = gitref(head) print "@refs/heads/%s HEAD" % head g_head = (head, node) def do_list(parser): - global branches, bmarks, mode, track_branches - repo = parser.repo for bmark, node in bookmarks.listbookmarks(repo).iteritems(): bmarks[bmark] = repo[node] cur = repo.dirstate.branch() + orig = peer if peer else repo + + for branch, heads in orig.branchmap().iteritems(): + # only open heads + heads = [h for h in heads if 'close' not in repo.changelog.read(h)[5]] + if heads: + branches[branch] = heads list_head(repo, cur) if track_branches: - for branch in repo.branchmap(): - heads = repo.branchheads(branch) - if len(heads): - branches[branch] = heads - for branch in branches: - print "? refs/heads/branches/%s" % branch + print "? refs/heads/branches/%s" % gitref(branch) for bmark in bmarks: - print "? refs/heads/%s" % bmark + print "? refs/heads/%s" % gitref(bmark) for tag, node in repo.tagslist(): if tag == 'tip': continue - print "? refs/tags/%s" % tag + print "? refs/tags/%s" % gitref(tag) print @@ -495,6 +661,7 @@ def do_import(parser): if os.path.exists(path): print "feature import-marks=%s" % path print "feature export-marks=%s" % path + print "feature force" sys.stdout.flush() tmp = encoding.encoding @@ -523,15 +690,12 @@ def do_import(parser): print 'done' def parse_blob(parser): - global blob_marks - parser.next() mark = parser.get_mark() parser.next() 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(): @@ -541,10 +705,12 @@ def get_merge_files(repo, p1, p2, files): f = { 'ctx' : repo[p1][e] } files[e] = f -def parse_commit(parser): - global marks, blob_marks, bmarks, parsed_refs - global mode +def c_style_unescape(string): + if string[0] == string[-1] == '"': + return string.decode('string-escape')[1:-1] + return string +def parse_commit(parser): from_mark = merge_mark = None ref = parser[1] @@ -567,6 +733,10 @@ def parse_commit(parser): if parser.check('merge'): die('octopus merges are not supported yet') + # fast-export adds an extra newline + if data[-1] == '\n': + data = data[:-1] + files = {} for line in parser: @@ -575,12 +745,18 @@ 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) + path = c_style_unescape(path) files[path] = f + # only export the commits if we are on an internal proxy repo + if dry_run and not peer: + parsed_refs[ref] = None + return + def getfilectx(repo, memctx, f): of = files[f] if 'deleted' in of: @@ -602,14 +778,14 @@ def parse_commit(parser): extra['committer'] = "%s %u %u" % committer if from_mark: - p1 = repo.changelog.node(mark_to_rev(from_mark)) + p1 = mark_to_rev(from_mark) else: - p1 = '\0' * 20 + p1 = '0' * 40 if merge_mark: - p2 = repo.changelog.node(mark_to_rev(merge_mark)) + p2 = mark_to_rev(merge_mark) else: - p2 = '\0' * 20 + p2 = '0' * 40 # # If files changed from any of the parents, hg wants to know, but in git if @@ -618,11 +794,16 @@ 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/'): + branch = ref[len('refs/heads/branches/'):] + extra['branch'] = hgref(branch) + 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 @@ -640,15 +821,12 @@ def parse_commit(parser): tmp = encoding.encoding encoding.encoding = 'utf-8' - node = repo.commitctx(ctx) + node = hghex(repo.commitctx(ctx)) encoding.encoding = tmp - rev = repo[node].rev() - parsed_refs[ref] = node - - marks.new_mark(rev, commit_mark) + marks.new_mark(node, commit_mark) def parse_reset(parser): ref = parser[1] @@ -662,8 +840,11 @@ def parse_reset(parser): from_mark = parser.get_mark() parser.next() - node = parser.repo.changelog.node(mark_to_rev(from_mark)) - parsed_refs[ref] = node + try: + rev = mark_to_rev(from_mark) + except KeyError: + rev = None + parsed_refs[ref] = rev def parse_tag(parser): name = parser[1] @@ -675,10 +856,173 @@ def parse_tag(parser): data = parser.get_data() parser.next() - # nothing to do + parsed_tags[name] = (tagger, data) + +def write_tag(repo, tag, node, msg, author): + branch = repo[node].branch() + tip = branch_tip(branch) + tip = repo[tip] + + def getfilectx(repo, memctx, f): + try: + fctx = tip.filectx(f) + data = fctx.data() + except error.ManifestLookupError: + data = "" + content = data + "%s %s\n" % (node, tag) + return context.memfilectx(f, content, False, False, None) + + p1 = tip.hex() + p2 = '0' * 40 + if author: + user, date, tz = author + date_tz = (date, tz) + else: + cmd = ['git', 'var', 'GIT_COMMITTER_IDENT'] + process = subprocess.Popen(cmd, stdout=subprocess.PIPE) + output, _ = process.communicate() + m = re.match('^.* <.*>', output) + if m: + user = m.group(0) + else: + user = repo.ui.username() + date_tz = None + + ctx = context.memctx(repo, (p1, p2), msg, + ['.hgtags'], getfilectx, + user, date_tz, {'branch' : branch}) + + tmp = encoding.encoding + encoding.encoding = 'utf-8' + + tagnode = repo.commitctx(ctx) + + encoding.encoding = tmp + + return (tagnode, branch) + +def checkheads_bmark(repo, ref, ctx): + bmark = ref[len('refs/heads/'):] + if not bmark in bmarks: + # new bmark + return True + + ctx_old = bmarks[bmark] + ctx_new = ctx + if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()): + if force_push: + print "ok %s forced update" % ref + else: + print "error %s non-fast forward" % ref + return False + + return True + +def checkheads(repo, remote, p_revs): + + remotemap = remote.branchmap() + if not remotemap: + # empty repo + return True + + new = {} + ret = True + + for node, ref in p_revs.iteritems(): + ctx = repo[node] + branch = ctx.branch() + if not branch in remotemap: + # new branch + continue + if not ref.startswith('refs/heads/branches'): + if ref.startswith('refs/heads/'): + if not checkheads_bmark(repo, ref, ctx): + ret = False + + # only check branches + continue + new.setdefault(branch, []).append(ctx.rev()) + + for branch, heads in new.iteritems(): + old = [repo.changelog.rev(x) for x in remotemap[branch]] + for rev in heads: + if check_version(2, 3): + ancestors = repo.changelog.ancestors([rev], stoprev=min(old)) + else: + ancestors = repo.changelog.ancestors(rev) + found = False + + for x in old: + if x in ancestors: + found = True + break + + if found: + continue + + node = repo.changelog.node(rev) + ref = p_revs[node] + if force_push: + print "ok %s forced update" % ref + else: + print "error %s non-fast forward" % ref + ret = False + + return ret + +def push_unsafe(repo, remote, parsed_refs, p_revs): + + force = force_push + + fci = discovery.findcommonincoming + commoninc = fci(repo, remote, force=force) + common, _, remoteheads = commoninc + + if not checkheads(repo, remote, p_revs): + return None + + cg = repo.getbundle('push', heads=list(p_revs), common=common) + + unbundle = remote.capable('unbundle') + if unbundle: + if force: + remoteheads = ['force'] + return remote.unbundle(cg, remoteheads, 'push') + else: + return remote.addchangegroup(cg, 'push', repo.url()) + +def push(repo, remote, parsed_refs, p_revs): + if hasattr(remote, 'canpush') and not remote.canpush(): + print "error cannot push" + + if not p_revs: + # nothing to push + return + + lock = None + unbundle = remote.capable('unbundle') + if not unbundle: + lock = remote.lock() + try: + ret = push_unsafe(repo, remote, parsed_refs, p_revs) + finally: + if lock is not None: + lock.release() + + return ret + +def check_tip(ref, kind, name, heads): + try: + ename = '%s/%s' % (kind, name) + tip = marks.get_tip(ename) + except KeyError: + return True + else: + return tip in heads def do_export(parser): - global parsed_refs, bmarks, peer + p_bmarks = [] + p_revs = {} parser.next() @@ -696,58 +1040,150 @@ def do_export(parser): else: die('unhandled export command: %s' % line) + need_fetch = False + for ref, node in parsed_refs.iteritems(): + bnode = hgbin(node) if node else None if ref.startswith('refs/heads/branches'): - pass + branch = ref[len('refs/heads/branches/'):] + if branch in branches and bnode in branches[branch]: + # up to date + continue + + if peer: + remotemap = peer.branchmap() + if remotemap and branch in remotemap: + heads = [hghex(e) for e in remotemap[branch]] + if not check_tip(ref, 'branches', branch, heads): + print "error %s fetch first" % ref + need_fetch = True + continue + + p_revs[bnode] = ref + 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): + new = node + old = bmarks[bmark].hex() if bmark in bmarks else '' + + if old == new: continue + + print "ok %s" % ref + if bmark != fake_bmark and \ + not (bmark == 'master' and bmark not in parser.repo._bookmarks): + p_bmarks.append((ref, bmark, old, new)) + + if peer: + remote_old = peer.listkeys('bookmarks').get(bmark) + if remote_old: + if not check_tip(ref, 'bookmarks', bmark, remote_old): + print "error %s fetch first" % ref + need_fetch = True + continue + + p_revs[bnode] = ref elif ref.startswith('refs/tags/'): + if dry_run: + print "ok %s" % ref + continue tag = ref[len('refs/tags/'):] - parser.repo.tag([tag], node, None, True, None, {}) + tag = hgref(tag) + author, msg = parsed_tags.get(tag, (None, None)) + if mode == 'git': + if not msg: + msg = 'Added tag %s for changeset %s' % (tag, node[:12]) + tagnode, branch = write_tag(parser.repo, tag, node, msg, author) + p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch) + else: + fp = parser.repo.opener('localtags', 'a') + fp.write('%s %s\n' % (node, tag)) + fp.close() + p_revs[bnode] = ref + print "ok %s" % ref else: # transport-helper/fast-export bugs continue - print "ok %s" % ref - print + if need_fetch: + print + return + + if dry_run: + if peer and not force_push: + checkheads(parser.repo, peer, p_revs) + print + return if peer: - parser.repo.push(peer, force=False) + if not push(parser.repo, peer, parsed_refs, p_revs): + # do not update bookmarks + print + return + + # update remote bookmarks + remote_bmarks = peer.listkeys('bookmarks') + for ref, bmark, old, new in p_bmarks: + if force_push: + old = remote_bmarks.get(bmark, '') + if not peer.pushkey('bookmarks', bmark, old, new): + print "error %s" % ref + else: + # update local bookmarks + for ref, bmark, old, new in p_bmarks: + if not bookmarks.pushbookmark(parser.repo, bmark, old, new): + print "error %s" % ref + + print + +def do_option(parser): + global dry_run, force_push + _, key, value = parser.line.split(' ') + if key == 'dry-run': + dry_run = (value == 'true') + print 'ok' + elif key == 'force': + force_push = (value == 'true') + print 'ok' + else: + print 'unsupported' def fix_path(alias, repo, orig_url): - repo_url = util.url(repo.url()) - url = util.url(orig_url) - if str(url) == str(repo_url): + url = urlparse.urlparse(orig_url, 'file') + if url.scheme != 'file' or os.path.isabs(os.path.expanduser(url.path)): return - cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % repo_url] + abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url) + cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url] subprocess.call(cmd) def main(args): - global prefix, dirname, branches, bmarks + global prefix, gitdir, 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 + global parsed_tags + global filenodes + global fake_bmark, hg_version + global dry_run + global notes, alias + + marks = None + is_tmp = False + gitdir = os.environ.get('GIT_DIR', None) + + if len(args) < 3: + die('Not enough arguments.') + + if not gitdir: + die('GIT_DIR not set') alias = args[1] url = args[2] peer = None - hg_git_compat = False - track_branches = 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 - except subprocess.CalledProcessError: - pass + hg_git_compat = get_config_bool('remote-hg.hg-git-compat') + track_branches = get_config_bool('remote-hg.track-branches', True) + force_push = False if hg_git_compat: mode = 'hg' @@ -760,16 +1196,22 @@ def main(args): if alias[4:] == url: is_tmp = True - alias = util.sha1(alias).hexdigest() - else: - is_tmp = False + alias = hashlib.sha1(alias).hexdigest() - gitdir = os.environ['GIT_DIR'] dirname = os.path.join(gitdir, 'hg', alias) branches = {} bmarks = {} blob_marks = {} parsed_refs = {} + parsed_tags = {} + filenodes = {} + fake_bmark = None + try: + hg_version = tuple(int(e) for e in util.version().split('.')) + except: + hg_version = None + dry_run = False + notes = set() repo = get_repo(url, alias) prefix = 'refs/hg/%s' % alias @@ -777,11 +1219,12 @@ def main(args): if not is_tmp: fix_path(alias, peer or repo, url) - if not os.path.exists(dirname): - os.makedirs(dirname) - marks_path = os.path.join(dirname, 'marks-hg') - marks = Marks(marks_path) + marks = Marks(marks_path, repo) + + if sys.platform == 'win32': + import msvcrt + msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) parser = Parser(repo) for line in parser: @@ -793,13 +1236,19 @@ def main(args): do_import(parser) elif parser.check('export'): do_export(parser) + elif parser.check('option'): + do_option(parser) else: 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)) |