Link and not merge first branch of incremental import
[fast-export/barak.git] / hg2git.py
blobb00a82ee4c47ec2857534822670673faad3b98ae
1 #!/usr/bin/env python
3 # Copyright (c) 2007 Rocco Rutte <pdmef@gmx.net>
4 # License: GPLv2
6 """hg2git.py - A mercurial-to-git filter for git-fast-import(1)
7 Usage: hg2git.py <hg repo url> <marks file> <heads file> <tip file>
8 """
10 from mercurial import repo,hg,cmdutil,util,ui,revlog
11 from tempfile import mkstemp
12 import re
13 import sys
14 import os
16 # silly regex to see if user field has email address
17 user_re=re.compile('[^<]+ <[^>]+>$')
18 # git branch for hg's default 'HEAD' branch
19 cfg_master='master'
20 # insert 'checkpoint' command after this many commits
21 cfg_checkpoint_count=1000
23 def usage(ret):
24 sys.stderr.write(__doc__)
25 return ret
27 def setup_repo(url):
28 myui=ui.ui()
29 return myui,hg.repository(myui,url)
31 def get_changeset(ui,repo,revision):
32 def get_branch(name):
33 if name=='HEAD':
34 name=cfg_master
35 return name
36 def fixup_user(user):
37 if user_re.match(user)==None:
38 if '@' not in user:
39 return user+' <none@none>'
40 return user+' <'+user+'>'
41 return user
42 node=repo.lookup(revision)
43 (manifest,user,(time,timezone),files,desc,extra)=repo.changelog.read(node)
44 tz="%+03d%02d" % (-timezone / 3600, ((-timezone % 3600) / 60))
45 branch=get_branch(extra.get('branch','master'))
46 return (manifest,fixup_user(user),(time,tz),files,desc,branch,extra)
48 def gitmode(x):
49 return x and '100755' or '100644'
51 def wr(msg=''):
52 print msg
53 #map(lambda x: sys.stderr.write('\t[%s]\n' % x),msg.split('\n'))
55 def checkpoint(count):
56 count=count+1
57 if count%cfg_checkpoint_count==0:
58 sys.stderr.write("Checkpoint after %d commits\n" % count)
59 wr('checkpoint')
60 wr()
61 return count
63 def get_parent_mark(parent,marks):
64 p=marks.get(str(parent),None)
65 if p==None:
66 # if we didn't see parent previously, assume we saw it in this run
67 p=':%d' % (parent+1)
68 return p
70 def export_commit(ui,repo,revision,marks,heads,last,max,count):
71 sys.stderr.write('Exporting revision %d (tip %d) as [:%d]\n' % (revision,max,revision+1))
73 (_,user,(time,timezone),files,desc,branch,_)=get_changeset(ui,repo,revision)
74 parents=repo.changelog.parentrevs(revision)
76 # we need this later to write out tags
77 marks[str(revision)]=':%d'%(revision+1)
79 wr('commit refs/heads/%s' % branch)
80 wr('mark :%d' % (revision+1))
81 wr('committer %s %d %s' % (user,time,timezone))
82 wr('data %d' % (len(desc)+1)) # wtf?
83 wr(desc)
84 wr()
86 src=heads.get(branch,'')
87 link=''
88 if src!='':
89 # if we have a cached head, this is an incremental import: initialize it
90 # and kill reference so we won't init it again
91 wr('from %s' % src)
92 heads[branch]=''
93 sys.stderr.write('Initializing branch [%s] to parent [%s]\n' %
94 (branch,src))
95 link=src # avoid making a merge commit for incremental import
96 elif not heads.has_key(branch) and revision>0:
97 # newly created branch and not the first one: connect to parent
98 tmp=get_parent_mark(parents[0],marks)
99 wr('from %s' % tmp)
100 sys.stderr.write('Link new branch [%s] to parent [%s]\n' %
101 (branch,tmp))
102 link=tmp # avoid making a merge commit for branch fork
104 if parents:
105 l=last.get(branch,revision)
106 for p in parents:
107 # 1) as this commit implicitely is the child of the most recent
108 # commit of this branch, ignore this parent
109 # 2) ignore nonexistent parents
110 # 3) merge otherwise
111 if p==l or p==revision or p<0:
112 continue
113 tmp=get_parent_mark(p,marks)
114 # if we fork off a branch, don't merge via 'merge' as we have
115 # 'from' already above
116 if tmp==link:
117 continue
118 sys.stderr.write('Merging branch [%s] with parent [%s] from [r%d]\n' %
119 (branch,tmp,p))
120 wr('merge %s' % tmp)
122 last[branch]=revision
123 heads[branch]=''
125 # just wipe the branch clean, all full manifest contents
126 wr('deleteall')
128 ctx=repo.changectx(str(revision))
129 man=ctx.manifest()
131 #for f in man.keys():
132 # fctx=ctx.filectx(f)
133 # d=fctx.data()
134 # wr('M %s inline %s' % (gitmode(man.execf(f)),f))
135 # wr('data %d' % len(d)) # had some trouble with size()
136 # wr(d)
138 for fctx in ctx.filectxs():
139 f=fctx.path()
140 d=fctx.data()
141 wr('M %s inline %s' % (gitmode(man.execf(f)),f))
142 wr('data %d' % len(d)) # had some trouble with size()
143 wr(d)
145 wr()
146 return checkpoint(count)
148 def export_tags(ui,repo,cache,count):
149 l=repo.tagslist()
150 for tag,node in l:
151 if tag=='tip':
152 continue
153 rev=repo.changelog.rev(node)
154 ref=cache.get(str(rev),None)
155 if ref==None:
156 sys.stderr.write('Failed to find reference for creating tag'
157 ' %s at r%d\n' % (tag,rev))
158 continue
159 (_,user,(time,timezone),_,desc,branch,_)=get_changeset(ui,repo,rev)
160 sys.stderr.write('Exporting tag [%s] at [hg r%d] [git %s]\n' % (tag,rev,ref))
161 wr('tag %s' % tag)
162 wr('from %s' % ref)
163 wr('tagger %s %d %s' % (user,time,timezone))
164 msg='hg2git created tag %s for hg revision %d on branch %s on (summary):\n\t%s' % (tag,
165 rev,branch,desc.split('\n')[0])
166 wr('data %d' % (len(msg)+1))
167 wr(msg)
168 wr()
169 count=checkpoint(count)
170 return count
172 def load_cache(filename):
173 cache={}
174 if not os.path.exists(filename):
175 return cache
176 f=open(filename,'r')
178 for line in f.readlines():
179 l+=1
180 fields=line.split(' ')
181 if fields==None or not len(fields)==2 or fields[0][0]!=':':
182 sys.stderr.write('Invalid file format in [%s], line %d\n' % (filename,l))
183 continue
184 # put key:value in cache, key without ^:
185 cache[fields[0][1:]]=fields[1].split('\n')[0]
186 f.close()
187 return cache
189 def save_cache(filename,cache):
190 f=open(filename,'w+')
191 map(lambda x: f.write(':%s %s\n' % (str(x),str(cache.get(x)))),cache.keys())
192 f.close()
194 def verify_heads(ui,repo,cache):
195 def getsha1(branch):
196 f=open(os.getenv('GIT_DIR','/dev/null')+'/refs/heads/'+branch)
197 sha1=f.readlines()[0].split('\n')[0]
198 f.close()
199 return sha1
201 for b in cache.keys():
202 sys.stderr.write('Verifying branch [%s]\n' % b)
203 sha1=getsha1(b)
204 c=cache.get(b)
205 if sha1!=c:
206 sys.stderr.write('Warning: Branch [%s] modified outside hg2git:'
207 '\n%s (repo) != %s (cache)\n' % (b,sha1,c))
208 return True
210 if __name__=='__main__':
211 if len(sys.argv)!=6: sys.exit(usage(1))
212 repourl,m,marksfile,headsfile,tipfile=sys.argv[1:]
213 _max=int(m)
215 marks_cache=load_cache(marksfile)
216 heads_cache=load_cache(headsfile)
217 state_cache=load_cache(tipfile)
219 ui,repo=setup_repo(repourl)
221 if not verify_heads(ui,repo,heads_cache):
222 sys.exit(1)
224 tip=repo.changelog.count()
226 min=int(state_cache.get('tip',0))
227 max=_max
228 if _max<0:
229 max=tip
231 c=int(state_cache.get('count',0))
232 last={}
233 for rev in range(min,max):
234 c=export_commit(ui,repo,rev,marks_cache,heads_cache,last,tip,c)
236 c=export_tags(ui,repo,marks_cache,c)
238 state_cache['tip']=max
239 state_cache['count']=c
240 state_cache['repo']=repourl
241 save_cache(tipfile,state_cache)