git-remote-hg: add hgexport, an hg-fast-export equivalent
[git/dscho.git] / git_remote_helpers / hg / hgexport.py
blob8a255a45d2f80dd89a0f766f7ac931abb8e8bf9e
1 import binascii
2 import os.path
3 import sys
6 LF = '\n'
7 SP = ' '
10 class HgExportGenerator(object):
11 def __init__(self, repo):
12 self.git_hg = repo.git_hg
13 self.repo = repo
14 self.prefix = repo.prefix
15 self.nullref = "0" * 40
16 self.next_id = 0
17 self.mapping = {}
18 self.debugging = True
20 def nextid(self):
21 self.next_id += 1
22 return self.next_id
24 def tohex(self, binhex):
25 return binascii.hexlify(binhex)
27 def mode(self, fctx):
28 flags = fctx.flags()
30 if 'l' in flags:
31 mode = '120000'
32 elif 'x' in flags:
33 mode = '100755'
34 else:
35 mode = '100644'
37 return mode
39 def parents(self, parents):
40 parents = [self.tohex(i.node()) for i in parents]
41 parents = [i for i in parents if i != self.nullref]
42 assert all(i in self.mapping for i in parents)
43 parents = [':%d' % self.mapping[i] for i in parents]
45 return parents
47 def ref(self, ctx):
48 return self.prefix + ctx.branch()
50 def write(self, *args):
51 msg = ''.join([str(i) for i in args])
52 sys.stdout.write(msg)
54 def debug(self, msg):
55 assert LF not in msg
56 self.write('#', SP, msg, LF)
58 def feature(self, feature, value=None):
59 if value:
60 self.write('feature', SP, feature, '=', value, LF)
61 else:
62 self.write('feature', SP, feature, LF)
64 def option(self, option, value=None):
65 if value:
66 self.write('option', SP, option, '=', value, LF)
67 else:
68 self.write('option', SP, option, LF)
70 def option_quiet(self):
71 self.option('quiet')
73 def feature_relative_marks(self):
74 self.feature('relative-marks')
76 def feature_export_marks(self, marks):
77 self.feature('export-marks', marks)
79 def feature_import_marks(self, marks):
80 self.feature('import-marks', marks)
82 def feature_force(self):
83 self.feature('force')
85 def progress(self, message):
86 self.write('progress', SP, message, LF)
88 def write_data(self, data):
89 count = len(data)
90 self.write('data', SP, count, LF)
91 self.write(data, LF)
93 def write_mark(self, idnum):
94 self.write('mark', SP, ':', idnum, LF)
96 def write_blob(self, data, idnum):
97 self.write('blob', LF)
98 self.write_mark(idnum)
99 self.write_data(data)
101 def write_file(self, ctx, file, idnum):
102 fctx = ctx.filectx(file)
103 data = fctx.data()
105 self.write_blob(data, idnum)
107 def write_commit(self, ref):
108 self.write('commit', SP, ref, LF)
110 def write_author(self, author):
111 self.write('author', SP, author, LF)
113 def write_committer(self, committer):
114 self.write('committer', SP, committer, LF)
116 def write_from(self, parent):
117 self.write('from', SP, parent, LF)
119 def write_merge(self, parent):
120 self.write('merge', SP, parent, LF)
122 def write_reset(self, ref, idnum):
123 self.write('reset', SP, ref, LF)
124 self.write('from', SP, ':', idnum, LF)
126 def write_parents(self, parents):
127 parents = self.parents(parents)
129 # first commit
130 if not parents:
131 return
133 parent = parents[0]
135 self.write_from(parent)
137 for parent in parents[1:]:
138 self.write_merge(parent)
140 def write_filedeleteall(self):
141 self.write('deleteall', LF)
143 def write_filedelete(self, ctx, name):
144 self.write('D', SP, name, LF)
146 def write_filemodify_mark(self, mode, name, mark):
147 self.write('M', SP, mode, SP, ':', mark, SP, name, LF)
149 def write_filemodify_inline(self, mode, name, data):
150 self.write('M', SP, mode, SP, 'inline', SP, name, LF)
151 self.write_data(data)
153 def write_filemodify(self, ctx, name):
154 fctx = ctx.filectx(name)
155 man = ctx.manifest()
156 nodesha = man[name]
157 hash = self.tohex(nodesha)
158 mode = self.mode(fctx)
160 if hash in self.mapping:
161 mark = self.mapping[hash]
162 self.write_filemodify_mark(mode, name, mark)
163 else:
164 data = fctx.data()
165 self.write_filemodify_inline(mode, name, data)
167 def write_files(self, ctx):
168 man = ctx.manifest()
170 if len(ctx.parents()) == 2:
171 self.write_filedeleteall()
172 for name in man:
173 self.write_filemodify(ctx, name)
174 else:
175 for name in ctx.files():
176 # file got deleted
177 if name not in man:
178 self.write_filedelete(ctx, name)
179 else:
180 self.write_filemodify(ctx, name)
182 def export_files(self, ctx):
183 man = ctx.manifest()
185 for name in [i for i in ctx.files() if i in man]:
186 idnum = self.nextid()
187 nodesha = man[name]
188 hash = self.tohex(nodesha)
190 self.write_file(ctx, name, idnum)
191 self.mapping[hash] = idnum
193 def export_commit(self, ctx, ref, idnum, msg, parents):
194 author = self.git_hg.get_author(ctx)
195 committer = self.git_hg.get_committer(ctx)
196 committer = committer if committer else author
198 self.debug('exporting commit')
199 self.write_commit(ref)
200 self.write_mark(idnum)
201 self.write_author(author)
202 self.write_committer(committer)
203 self.write_data(msg)
204 self.write_parents(parents)
205 self.write_files(ctx)
206 self.debug('commit exported')
208 def export_revision(self, ctx):
209 nodesha = ctx.node()
210 hash = self.tohex(nodesha)
212 if hash in self.mapping:
213 return False
215 self.export_files(ctx)
217 idnum = self.nextid()
219 ref = self.ref(ctx)
220 msg = self.git_hg.get_message(ctx)
221 parents = self.git_hg.get_parents(ctx)
223 self.export_commit(ctx, ref, idnum, msg, parents)
224 self.mapping[hash] = idnum
226 return True
228 def export_branch(self, name, rev):
229 ctx = self.repo.changectx(rev)
230 nodesha = ctx.node()
231 hash = self.tohex(nodesha)
232 idnum = self.mapping[hash]
234 ref = self.prefix + name
236 self.write_reset(ref, idnum)
238 def export_repo(self, refs):
239 self.option_quiet()
240 self.feature_force()
242 exported = printed = False
244 for rev in self.repo.changelog:
245 ctx = self.repo.changectx(rev)
246 exported = self.export_revision(ctx) or exported
248 if (exported and not printed) or (exported and rev%1000 == 0):
249 self.progress("Exported revision %d.\n" % rev)
250 printed = True
252 def write_marks(self, base):
253 dirname = self.repo.get_base_path(base)
254 path = os.path.join(dirname, 'hg.marks')
255 if not os.path.exists(dirname):
256 os.makedirs(dirname)
257 f = open(path, 'w') #self.repo.opener(self.marksfile, 'w', atomictemp=True)
259 second = lambda (a, b): b
261 for hash, mark in sorted(self.mapping.iteritems(), key=second):
262 f.write(':%d %s\n' % (mark, hash))
264 f.close() #f.rename()
266 def read_marks(self, base):
267 dirname = self.repo.get_base_path(base)
268 path = os.path.join(dirname, 'hg.marks')
270 if not os.path.exists(path):
271 sys.stderr.write("warning: cannot find " + path)
272 return
274 f = open(path) #self.repo.opener(self.marksfile)
276 marks = [i.strip().split(' ') for i in f.readlines()]
278 self.mapping = dict((i[1], int(i[0][1:])) for i in marks)
279 self.next_id = max(self.mapping.values())