# For remote repositories a local clone is stored in
# "$GIT_DIR/hg/origin/clone/.hg/".
-from mercurial import hg, ui, bookmarks, context, encoding, node, error, extensions
+from mercurial import hg, ui, bookmarks, context, encoding, node, error, extensions, discovery, util
import re
import sys
AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$')
RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)')
-VERSION = 1
+VERSION = 2
def die(msg, *args):
sys.stderr.write('ERROR: %s\n' % (msg % args))
m = { '100755': 'x', '120000': 'l' }
return m.get(mode, '')
-def hghex(node):
- return hg.node.hex(node)
+def hghex(n):
+ return node.hex(n)
-def hgbin(node):
- return hg.node.bin(node)
+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)
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:
- self.clear()
- 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.version = tmp.get('version', 1)
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, 'version' : self.version }
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]
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 str(rev) in self.marks
+ return rev in self.marks
def get_tip(self, branch):
- return self.tips.get(branch, 0)
+ return self.tips.get(branch, None)
def set_tip(self, branch, tip):
self.tips[branch] = tip
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
extensions.loadall(myui)
- if hg.islocal(url):
+ 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)
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
ename = '%s/%s' % (kind, name)
tip = marks.get_tip(ename)
+ if tip and tip in repo:
+ tip = repo[tip].rev()
+ else:
+ tip = 0
revs = xrange(tip, head.rev() + 1)
- count = 0
+ total = len(revs)
for rev in revs:
node = c.node()
if marks.is_marked(c.hex()):
- count += 1
continue
(manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node)
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))
print desc
if len(parents) > 0:
- print "from :%s" % (rev_to_mark(parents[0].rev()))
+ print "from :%s" % (rev_to_mark(parents[0]))
if len(parents) > 1:
- print "merge :%s" % (rev_to_mark(parents[1].rev()))
+ print "merge :%s" % (rev_to_mark(parents[1]))
for f in modified_final:
print "M %s :%u %s" % f
print "D %s" % (fix_file_path(f))
print
- count += 1
- if (count % 100 == 0):
- print "progress revision %d '%s' (%d/%d)" % (rev, name, count, len(revs))
+ 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(head.rev())
+ print "from :%u" % rev_to_mark(head)
print
- marks.set_tip(ename, head.rev())
+ marks.set_tip(ename, head.hex())
def export_tag(repo, tag):
export_ref(repo, tag, 'tags', repo[hgref(tag)])
return heads[0]
def list_head(repo, cur):
- global g_head, bmarks
+ global g_head, bmarks, 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 repo:
+ # empty repo
+ return
+
+ node = repo['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
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
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
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):
global parsed_refs
from_mark = parser.get_mark()
parser.next()
- node = parser.repo.changelog.node(mark_to_rev(from_mark))
- parsed_refs[ref] = hghex(node)
+ rev = mark_to_rev(from_mark)
+ parsed_refs[ref] = rev
def parse_tag(parser):
name = parser[1]
return context.memfilectx(f, content, False, False, None)
p1 = tip.hex()
- p2 = '\0' * 20
+ p2 = '0' * 40
if not author:
author = (None, 0, 0)
user, date, tz = author
encoding.encoding = tmp
- return tagnode
+ return (tagnode, branch)
+
+def checkheads_bmark(repo, ref, ctx):
+ if force_push:
+ return True
+
+ 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()):
+ 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)
+ print "error %s non-fast forward" % p_revs[node]
+ 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 force and 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 do_export(parser):
global parsed_refs, bmarks, peer
p_bmarks = []
+ p_revs = {}
parser.next()
if branch in branches and bnode in branches[branch]:
# up to date
continue
+ p_revs[bnode] = ref
print "ok %s" % ref
elif ref.startswith('refs/heads/'):
bmark = ref[len('refs/heads/'):]
- p_bmarks.append((bmark, node))
- continue
+ 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))
+
+ p_revs[bnode] = ref
elif ref.startswith('refs/tags/'):
tag = ref[len('refs/tags/'):]
tag = hgref(tag)
if mode == 'git':
if not msg:
msg = 'Added tag %s for changeset %s' % (tag, node[:12]);
- write_tag(parser.repo, tag, node, msg, author)
+ 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
if peer:
- parser.repo.push(peer, force=force_push, newbranch=True)
- remote_bmarks = peer.listkeys('bookmarks')
-
- # handle bookmarks
- for bmark, node in p_bmarks:
- ref = 'refs/heads/' + bmark
- new = node
-
- if bmark in bmarks:
- old = bmarks[bmark].hex()
- else:
- old = ''
-
- if old == new:
- continue
-
- if bmark == 'master' and 'master' not in parser.repo._bookmarks:
- # fake bookmark
- print "ok %s" % ref
- continue
- elif bookmarks.pushbookmark(parser.repo, bmark, old, new):
- # updated locally
- pass
- else:
- print "error %s" % ref
- continue
+ if not push(parser.repo, peer, parsed_refs, p_revs):
+ # do not update bookmarks
+ print
+ return
- if peer:
- old = remote_bmarks.get(bmark, '')
+ # 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
- continue
-
- print "ok %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
global track_branches, force_push, is_tmp
global parsed_tags
global filenodes
+ global fake_bmark, hg_version
alias = args[1]
url = args[2]
marks = None
parsed_tags = {}
filenodes = {}
+ fake_bmark = None
+ try:
+ hg_version = tuple(int(e) for e in util.version().split('.'))
+ except:
+ hg_version = None
repo = get_repo(url, alias)
prefix = 'refs/hg/%s' % alias
fix_path(alias, peer or repo, url)
marks_path = os.path.join(dirname, 'marks-hg')
- marks = Marks(marks_path)
+ marks = Marks(marks_path, repo)
if sys.platform == 'win32':
import msvcrt