FIXME: hotpatch for compatibility with latest hg
[git] / git_remote_helpers / hg / hgexport.py
1 import binascii
2 import os.path
3 import sys
4
5
6 LF = '\n'
7 SP = ' '
8
9
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
19
20     def nextid(self):
21         self.next_id += 1
22         return self.next_id
23
24     def tohex(self, binhex):
25         return binascii.hexlify(binhex)
26
27     def mode(self, fctx):
28         flags = fctx.flags()
29
30         if 'l' in flags:
31           mode = '120000'
32         elif 'x' in flags:
33           mode = '100755'
34         else:
35           mode = '100644'
36
37         return mode
38
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]
44
45         return parents
46
47     def ref(self, ctx):
48         return self.prefix + ctx.branch()
49
50     def write(self, *args):
51         msg = ''.join([str(i) for i in args])
52         sys.stdout.write(msg)
53
54     def debug(self, msg):
55         assert LF not in msg
56         self.write('#', SP, msg, LF)
57
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)
63
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)
69
70     def option_quiet(self):
71         self.option('quiet')
72
73     def feature_relative_marks(self):
74         self.feature('relative-marks')
75
76     def feature_export_marks(self, marks):
77         self.feature('export-marks', marks)
78
79     def feature_import_marks(self, marks):
80         self.feature('import-marks', marks)
81
82     def feature_force(self):
83         self.feature('force')
84
85     def progress(self, message):
86         self.write('progress', SP, message, LF)
87
88     def write_data(self, data):
89         count = len(data)
90         self.write('data', SP, count, LF)
91         self.write(data, LF)
92
93     def write_mark(self, idnum):
94         self.write('mark', SP, ':', idnum, LF)
95
96     def write_blob(self, data, idnum):
97         self.write('blob', LF)
98         self.write_mark(idnum)
99         self.write_data(data)
100
101     def write_file(self, ctx, file, idnum):
102         fctx = ctx.filectx(file)
103         data = fctx.data()
104
105         self.write_blob(data, idnum)
106
107     def write_commit(self, ref):
108         self.write('commit', SP, ref, LF)
109
110     def write_author(self, author):
111         self.write('author', SP, author, LF)
112
113     def write_committer(self, committer):
114         self.write('committer', SP, committer, LF)
115
116     def write_from(self, parent):
117         self.write('from', SP, parent, LF)
118
119     def write_merge(self, parent):
120         self.write('merge', SP, parent, LF)
121
122     def write_reset(self, ref, idnum):
123         self.write('reset', SP, ref, LF)
124         self.write('from', SP, ':', idnum, LF)
125
126     def write_parents(self, parents):
127         parents = self.parents(parents)
128
129         # first commit
130         if not parents:
131             return
132
133         parent = parents[0]
134
135         self.write_from(parent)
136
137         for parent in parents[1:]:
138             self.write_merge(parent)
139
140     def write_filedeleteall(self):
141         self.write('deleteall', LF)
142
143     def write_filedelete(self, ctx, name):
144         self.write('D', SP, name, LF)
145
146     def write_filemodify_mark(self, mode, name, mark):
147         self.write('M', SP, mode, SP, ':', mark, SP, name, LF)
148
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)
152
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)
159
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)
166
167     def write_files(self, ctx):
168         man = ctx.manifest()
169
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)
181
182     def export_files(self, ctx):
183         man = ctx.manifest()
184
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)
189
190             self.write_file(ctx, name, idnum)
191             self.mapping[hash] = idnum
192
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
197
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')
207
208     def export_revision(self, ctx):
209         nodesha = ctx.node()
210         hash = self.tohex(nodesha)
211
212         if hash in self.mapping:
213             return False
214
215         self.export_files(ctx)
216
217         idnum = self.nextid()
218
219         ref = self.ref(ctx)
220         msg = self.git_hg.get_message(ctx)
221         parents = self.git_hg.get_parents(ctx)
222
223         self.export_commit(ctx, ref, idnum, msg, parents)
224         self.mapping[hash] = idnum
225
226         return True
227
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]
233
234         ref = self.prefix + name
235
236         self.write_reset(ref, idnum)
237
238     def export_repo(self, refs):
239         self.option_quiet()
240         self.feature_force()
241
242         exported = printed = False
243
244         for rev in self.repo.changelog:
245             ctx = self.repo.changectx(rev)
246             exported = self.export_revision(ctx) or exported
247
248             if (exported and not printed) or (exported and rev%1000 == 0):
249                 self.progress("Exported revision %d.\n" % rev)
250                 printed = True
251
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)
258
259         second = lambda (a, b): b
260
261         for hash, mark in sorted(self.mapping.iteritems(), key=second):
262             f.write(':%d %s\n' % (mark, hash))
263
264         f.close() #f.rename()
265
266     def read_marks(self, base):
267         dirname = self.repo.get_base_path(base)
268         path = os.path.join(dirname, 'hg.marks')
269
270         if not os.path.exists(path):
271             sys.stderr.write("warning: cannot find " + path)
272             return
273
274         f = open(path) #self.repo.opener(self.marksfile)
275
276         marks = [i.strip().split(' ') for i in f.readlines()]
277
278         self.mapping = dict((i[1], int(i[0][1:])) for i in marks)
279         self.next_id = max(self.mapping.values())
280