3 # Copyright (c) 2012 Felipe Contreras
6 # Inspired by Rocco Rutte's hg-fast-export
8 # Just copy to your ~/bin, or anywhere in your $PATH.
9 # Then you can clone with:
10 # git clone hg::/path/to/mercurial/repo/
12 from mercurial
import hg
, ui
, bookmarks
19 NAME_RE
= re
.compile('^([^<>]+)')
20 AUTHOR_RE
= re
.compile('^([^<>]+?)? ?<([^<>]+)>$')
23 sys
.stderr
.write('ERROR: %s\n' % (msg
% args
))
27 sys
.stderr
.write('WARNING: %s\n' % (msg
% args
))
30 return 'l' in flags
and '120000' or 'x' in flags
and '100755' or '100644'
33 return '%+03d%02d' % (-tz
/ 3600, -tz
% 3600 / 60)
37 def __init__(self
, path
):
46 if not os
.path
.exists(self
.path
):
49 tmp
= json
.load(open(self
.path
))
51 self
.tips
= tmp
['tips']
52 self
.marks
= tmp
['marks']
53 self
.last_mark
= tmp
['last-mark']
56 return { 'tips': self
.tips
, 'marks': self
.marks
, 'last-mark' : self
.last_mark
}
59 json
.dump(self
.dict(), open(self
.path
, 'w'))
62 return str(self
.dict())
64 def from_rev(self
, rev
):
65 return self
.marks
[str(rev
)]
67 def get_mark(self
, rev
):
69 self
.marks
[str(rev
)] = self
.last_mark
72 def is_marked(self
, rev
):
73 return self
.marks
.has_key(str(rev
))
75 def get_tip(self
, branch
):
76 return self
.tips
.get(branch
, 0)
78 def set_tip(self
, branch
, tip
):
79 self
.tips
[branch
] = tip
83 def __init__(self
, repo
):
85 self
.line
= self
.get_line()
88 return sys
.stdin
.readline().strip()
90 def __getitem__(self
, i
):
91 return self
.line
.split()[i
]
93 def check(self
, word
):
94 return self
.line
.startswith(word
)
96 def each_block(self
, separator
):
97 while self
.line
!= separator
:
99 self
.line
= self
.get_line()
102 return self
.each_block('')
105 self
.line
= self
.get_line()
106 if self
.line
== 'done':
111 print "M %s inline %s" % (gitmode(fc
.flags()), fc
.path())
112 print "data %d" % len(d
)
115 def get_filechanges(repo
, ctx
, parent
):
121 prev
= repo
[parent
].manifest().copy()
125 if (cur
.flags(fn
) != prev
.flags(fn
) or cur
[fn
] != prev
[fn
]):
130 removed |
= set(prev
.keys())
132 return added | modified
, removed
134 def fixup_user(user
):
135 user
= user
.replace('"', '')
137 m
= AUTHOR_RE
.match(user
)
140 mail
= m
.group(2).strip()
142 m
= NAME_RE
.match(user
)
144 name
= m
.group(1).strip()
151 return '%s <%s>' % (name
, mail
)
153 def get_repo(url
, alias
):
157 myui
.setconfig('ui', 'interactive', 'off')
160 repo
= hg
.repository(myui
, url
)
162 local_path
= os
.path
.join(dirname
, 'clone')
163 if not os
.path
.exists(local_path
):
164 peer
, dstpeer
= hg
.clone(myui
, {}, url
, local_path
, update
=False, pull
=True)
165 repo
= dstpeer
.local()
167 repo
= hg
.repository(myui
, local_path
)
168 peer
= hg
.peer(myui
, {}, url
)
169 repo
.pull(peer
, heads
=None, force
=True)
173 def rev_to_mark(rev
):
175 return marks
.from_rev(rev
)
177 def export_ref(repo
, name
, kind
, head
):
180 ename
= '%s/%s' % (kind
, name
)
181 tip
= marks
.get_tip(ename
)
183 # mercurial takes too much time checking this
184 if tip
and tip
== head
.rev():
187 revs
= repo
.revs('%u:%u' % (tip
, head
))
190 revs
= [rev
for rev
in revs
if not marks
.is_marked(rev
)]
195 (manifest
, user
, (time
, tz
), files
, desc
, extra
) = repo
.changelog
.read(c
.node())
196 rev_branch
= extra
['branch']
198 author
= "%s %d %s" % (fixup_user(user
), time
, gittz(tz
))
199 if 'committer' in extra
:
200 user
, time
, tz
= extra
['committer'].rsplit(' ', 2)
201 committer
= "%s %s %s" % (user
, time
, gittz(int(tz
)))
205 parents
= [p
for p
in repo
.changelog
.parentrevs(rev
) if p
>= 0]
207 if len(parents
) == 0:
208 modified
= c
.manifest().keys()
211 modified
, removed
= get_filechanges(repo
, c
, parents
[0])
213 if len(parents
) == 0 and rev
:
214 print 'reset %s/%s' % (prefix
, ename
)
216 print "commit %s/%s" % (prefix
, ename
)
217 print "mark :%d" % (marks
.get_mark(rev
))
218 print "author %s" % (author
)
219 print "committer %s" % (committer
)
220 print "data %d" % (len(desc
))
224 print "from :%s" % (rev_to_mark(parents
[0]))
226 print "merge :%s" % (rev_to_mark(parents
[1]))
229 export_file(c
.filectx(f
))
235 if (count
% 100 == 0):
236 print "progress revision %d '%s' (%d/%d)" % (rev
, name
, count
, len(revs
))
237 print "#############################################################"
239 # make sure the ref is updated
240 print "reset %s/%s" % (prefix
, ename
)
241 print "from :%u" % rev_to_mark(rev
)
244 marks
.set_tip(ename
, rev
)
246 def export_tag(repo
, tag
):
247 export_ref(repo
, tag
, 'tags', repo
[tag
])
249 def export_bookmark(repo
, bmark
):
251 export_ref(repo
, bmark
, 'bookmarks', head
)
253 def export_branch(repo
, branch
):
254 tip
= get_branch_tip(repo
, branch
)
256 export_ref(repo
, branch
, 'branches', head
)
258 def export_head(repo
):
260 export_ref(repo
, g_head
[0], 'bookmarks', g_head
[1])
262 def do_capabilities(parser
):
263 global prefix
, dirname
266 print "refspec refs/heads/branches/*:%s/branches/*" % prefix
267 print "refspec refs/heads/*:%s/bookmarks/*" % prefix
268 print "refspec refs/tags/*:%s/tags/*" % prefix
271 def get_branch_tip(repo
, branch
):
274 heads
= branches
.get(branch
, None)
278 # verify there's only one head
280 warn("Branch '%s' has more than one head, consider merging" % branch
)
281 # older versions of mercurial don't have this
282 if hasattr(repo
, "branchtip"):
283 return repo
.branchtip(branch
)
287 def list_head(repo
, cur
):
290 head
= bookmarks
.readcurrent(repo
)
294 print "@refs/heads/%s HEAD" % head
295 g_head
= (head
, node
)
298 global branches
, bmarks
301 for branch
in repo
.branchmap():
302 heads
= repo
.branchheads(branch
)
304 branches
[branch
] = heads
306 for bmark
, node
in bookmarks
.listbookmarks(repo
).iteritems():
307 bmarks
[bmark
] = repo
[node
]
309 cur
= repo
.dirstate
.branch()
312 for branch
in branches
:
313 print "? refs/heads/branches/%s" % branch
315 print "? refs/heads/%s" % bmark
317 for tag
, node
in repo
.tagslist():
320 print "? refs/tags/%s" % tag
324 def do_import(parser
):
327 path
= os
.path
.join(dirname
, 'marks-git')
330 if os
.path
.exists(path
):
331 print "feature import-marks=%s" % path
332 print "feature export-marks=%s" % path
335 # lets get all the import lines
336 while parser
.check('import'):
341 elif ref
.startswith('refs/heads/branches/'):
342 branch
= ref
[len('refs/heads/branches/'):]
343 export_branch(repo
, branch
)
344 elif ref
.startswith('refs/heads/'):
345 bmark
= ref
[len('refs/heads/'):]
346 export_bookmark(repo
, bmark
)
347 elif ref
.startswith('refs/tags/'):
348 tag
= ref
[len('refs/tags/'):]
349 export_tag(repo
, tag
)
356 global prefix
, dirname
, marks
, branches
, bmarks
361 gitdir
= os
.environ
['GIT_DIR']
362 dirname
= os
.path
.join(gitdir
, 'hg', alias
)
366 repo
= get_repo(url
, alias
)
367 prefix
= 'refs/hg/%s' % alias
369 if not os
.path
.exists(dirname
):
372 marks_path
= os
.path
.join(dirname
, 'marks-hg')
373 marks
= Marks(marks_path
)
375 parser
= Parser(repo
)
377 if parser
.check('capabilities'):
378 do_capabilities(parser
)
379 elif parser
.check('list'):
381 elif parser
.check('import'):
383 elif parser
.check('export'):
386 die('unhandled command: %s' % line
)
391 sys
.exit(main(sys
.argv
))