Mercurial > hg-git-sync
annotate hggit_sync.py @ 1:e9f44d2deb94
Add some lame improvements to the map building.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Fri, 19 Feb 2016 16:01:24 -0800 |
parents | 6da45bb59fd0 |
children | 19156ccdc3e1 |
rev | line source |
---|---|
0 | 1 import os |
2 import os.path | |
3 import sys | |
4 import codecs | |
5 import shutil | |
6 import argparse | |
7 import subprocess | |
8 | |
9 | |
10 class CommitInfo(object): | |
11 def __init__(self): | |
12 self.nodeid = None | |
13 self.timestamp = None | |
14 self.description = None | |
15 | |
16 | |
17 def parse_commits(text): | |
18 commits = [] | |
19 cur_commit = None | |
20 for line in text.split('\n'): | |
21 if line == '': | |
22 if cur_commit: | |
23 commits.append(cur_commit) | |
24 cur_commit = None | |
25 continue | |
26 if cur_commit is None: | |
27 cur_commit = CommitInfo() | |
28 if cur_commit.nodeid is None: | |
29 id_and_date = line.split(' ', 1) | |
30 cur_commit.nodeid = id_and_date[0] | |
31 cur_commit.timestamp = int(id_and_date[1].split('.')[0]) | |
32 else: | |
33 cur_commit.description = line | |
34 return commits | |
35 | |
36 | |
37 def build_commit_map(commits1, commits2): | |
38 commits1 = sorted(commits1, key=lambda c: c.timestamp) | |
39 commits2 = sorted(commits2, key=lambda c: c.timestamp) | |
40 commit_map = dict(map(lambda c: (c.timestamp, (c, None)), commits1)) | |
1
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
41 orphan_commits2 = [] |
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
42 print("Building commit map...") |
0 | 43 for c in commits2: |
44 entry = commit_map.get(c.timestamp, (None, None)) | |
45 entry = (entry[0], c) | |
46 commit_map[c.timestamp] = entry | |
1
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
47 if entry[0] is None: |
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
48 orphan_commits2.append(c) |
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
49 |
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
50 if orphan_commits2: |
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
51 print("Fixing orphaned commits...") |
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
52 did_fix = True |
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
53 orphan_commits1 = [e[0] for e in commit_map.values() if e[1] is None] |
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
54 while did_fix: |
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
55 did_fix = False |
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
56 for c2 in orphan_commits2: |
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
57 for c1 in orphan_commits1: |
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
58 if c1.description.strip() == c2.description.strip(): |
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
59 print("Mapping '%s' to '%s'" % (c1.nodeid, c2.nodeid)) |
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
60 print(" Similar description: %s" % c1.description) |
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
61 print(" Timestamp difference: %d" % (c2.timestamp - c1.timestamp)) |
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
62 commit_map[c1.timestamp] = (c1, c2) |
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
63 orphan_commits1.remove(c1) |
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
64 orphan_commits2.remove(c2) |
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
65 did_fix = True |
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
66 break |
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
67 if did_fix: |
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
68 break |
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
69 |
0 | 70 return commit_map |
71 | |
72 | |
73 def main(): | |
74 parser = argparse.ArgumentParser( | |
75 description="Helps you fix problems with hg-git. Maybe.", | |
76 epilog="Don't trust scripts you found on the web! Backup your stuff!") | |
77 parser.add_argument( | |
78 '--rebuild', | |
79 nargs=1, | |
80 metavar='REMOTE', | |
81 help="Rebuild the Git repo from the given remote URL.") | |
82 parser.add_argument( | |
83 'mapfile', | |
84 metavar='MAPFILE', | |
1
e9f44d2deb94
Add some lame improvements to the map building.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
85 nargs='?', |
0 | 86 help="The path to the mapfile to generate.") |
87 res = parser.parse_args() | |
88 | |
89 hg_repo = os.getcwd() | |
90 if not os.path.exists(os.path.join(hg_repo, '.hg')): | |
91 print("You must run this in the root of a Mercurial repository.") | |
92 return 1 | |
93 | |
94 git_repo = os.path.join(hg_repo, '.hg', 'git') | |
95 if res.rebuild: | |
96 print("Removing existing Git repo...") | |
97 shutil.rmtree(git_repo) | |
98 print("Syncing it again into: %s" % git_repo) | |
99 git_output = subprocess.check_output([ | |
100 'git', 'clone', '--bare', res.rebuild, git_repo]) | |
101 | |
102 if not os.path.exists(git_repo): | |
103 print("This Mercurial repository doesn't seem to have any Git mirror " | |
104 "to sync with.") | |
105 return 1 | |
106 | |
107 hg_output = subprocess.check_output([ | |
108 'hg', 'log', | |
109 '--template', "{node} {date}\n{firstline(desc)}\n\n"]) | |
110 hg_commits = parse_commits(hg_output) | |
111 | |
112 os.chdir(git_repo) | |
113 git_output = subprocess.check_output([ | |
114 'git', 'log', '--format=%H %ct%n%s%n%n']) | |
115 git_commits = parse_commits(git_output) | |
116 os.chdir(hg_repo) | |
117 | |
118 commit_map = build_commit_map(git_commits, hg_commits) | |
119 for key, val in commit_map.iteritems(): | |
120 if val[0] is None: | |
121 print("Mercurial commit '%s' (%s) has no Git mirror yet: %s" % | |
122 (val[1].nodeid, val[1].timestamp, val[1].description)) | |
123 if val[1] is None: | |
124 print("Git commit '%s' (%s) is new: %s" % | |
125 (val[0].nodeid, val[0].timestamp, val[0].description)) | |
126 | |
127 map_file = res.mapfile or os.path.join(hg_repo, '.hg', 'git-mapfile') | |
128 print("Saving map file: %s" % map_file) | |
129 with codecs.open(map_file, 'w', encoding='utf8') as fp: | |
130 for key, val in commit_map.iteritems(): | |
131 if val[0] is None or val[1] is None: | |
132 continue | |
133 fp.write(val[0].nodeid) | |
134 fp.write(' ') | |
135 fp.write(val[1].nodeid) | |
136 fp.write('\n') | |
137 | |
138 | |
139 if __name__ == '__main__': | |
140 res = main() | |
141 sys.exit(res) | |
142 |