# HG changeset patch # User Ludovic Chabant # Date 1455823729 28800 # Node ID 6da45bb59fd0e3ac0e96472c0949e3fe2a8b807f Initial commit. diff -r 000000000000 -r 6da45bb59fd0 README.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README.md Thu Feb 18 11:28:49 2016 -0800 @@ -0,0 +1,32 @@ + +# Hg-Git Sync + +This is a small Python script that tries to fix the most common problems with +[Hg-Git][1]. It's not meant to be awesome, it's just meant to get me out of +trouble. + +If your `.hg/git-mapfile` is out of sync (pointing to bad commit hashes): + + cd path/to/your/repo + python hggit_sync.py + +This will rebuild the map file by looking at the commit history of both the +Mercurial and Git repos, and figure out (quite stupidly so far) how the hashes +correspond to each other. + +If you're in deeper trouble, however, like you get error messages about your +local Git mirror having hashes that the server doesn't know about: + + cd path/to/your/repo + python hggit_sync.py --rebuild git@github.com/whatever/something.git + +This will wipe your local Git mirror, re-fetch it from the given remote URL, and +rebuild the map file. + +Of course, this script is offered without any guarantees, may format your +hard-drive, yada yada. You know the drill when it comes to running random code +you found on the web! (I hope) + + +[1]: https://bitbucket.org/durin42/hg-git/src + diff -r 000000000000 -r 6da45bb59fd0 hggit_sync.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hggit_sync.py Thu Feb 18 11:28:49 2016 -0800 @@ -0,0 +1,116 @@ +import os +import os.path +import sys +import codecs +import shutil +import argparse +import subprocess + + +class CommitInfo(object): + def __init__(self): + self.nodeid = None + self.timestamp = None + self.description = None + + +def parse_commits(text): + commits = [] + cur_commit = None + for line in text.split('\n'): + if line == '': + if cur_commit: + commits.append(cur_commit) + cur_commit = None + continue + if cur_commit is None: + cur_commit = CommitInfo() + if cur_commit.nodeid is None: + id_and_date = line.split(' ', 1) + cur_commit.nodeid = id_and_date[0] + cur_commit.timestamp = int(id_and_date[1].split('.')[0]) + else: + cur_commit.description = line + return commits + + +def build_commit_map(commits1, commits2): + commits1 = sorted(commits1, key=lambda c: c.timestamp) + commits2 = sorted(commits2, key=lambda c: c.timestamp) + commit_map = dict(map(lambda c: (c.timestamp, (c, None)), commits1)) + for c in commits2: + entry = commit_map.get(c.timestamp, (None, None)) + entry = (entry[0], c) + commit_map[c.timestamp] = entry + return commit_map + + +def main(): + parser = argparse.ArgumentParser( + description="Helps you fix problems with hg-git. Maybe.", + epilog="Don't trust scripts you found on the web! Backup your stuff!") + parser.add_argument( + '--rebuild', + nargs=1, + metavar='REMOTE', + help="Rebuild the Git repo from the given remote URL.") + parser.add_argument( + 'mapfile', + metavar='MAPFILE', + help="The path to the mapfile to generate.") + res = parser.parse_args() + + hg_repo = os.getcwd() + if not os.path.exists(os.path.join(hg_repo, '.hg')): + print("You must run this in the root of a Mercurial repository.") + return 1 + + git_repo = os.path.join(hg_repo, '.hg', 'git') + if res.rebuild: + print("Removing existing Git repo...") + shutil.rmtree(git_repo) + print("Syncing it again into: %s" % git_repo) + git_output = subprocess.check_output([ + 'git', 'clone', '--bare', res.rebuild, git_repo]) + + if not os.path.exists(git_repo): + print("This Mercurial repository doesn't seem to have any Git mirror " + "to sync with.") + return 1 + + hg_output = subprocess.check_output([ + 'hg', 'log', + '--template', "{node} {date}\n{firstline(desc)}\n\n"]) + hg_commits = parse_commits(hg_output) + + os.chdir(git_repo) + git_output = subprocess.check_output([ + 'git', 'log', '--format=%H %ct%n%s%n%n']) + git_commits = parse_commits(git_output) + os.chdir(hg_repo) + + commit_map = build_commit_map(git_commits, hg_commits) + for key, val in commit_map.iteritems(): + if val[0] is None: + print("Mercurial commit '%s' (%s) has no Git mirror yet: %s" % + (val[1].nodeid, val[1].timestamp, val[1].description)) + if val[1] is None: + print("Git commit '%s' (%s) is new: %s" % + (val[0].nodeid, val[0].timestamp, val[0].description)) + + map_file = res.mapfile or os.path.join(hg_repo, '.hg', 'git-mapfile') + print("Saving map file: %s" % map_file) + with codecs.open(map_file, 'w', encoding='utf8') as fp: + for key, val in commit_map.iteritems(): + if val[0] is None or val[1] is None: + continue + fp.write(val[0].nodeid) + fp.write(' ') + fp.write(val[1].nodeid) + fp.write('\n') + + +if __name__ == '__main__': + res = main() + sys.exit(res) +