git-remote-hg: improve sanitation of local repo urls
[git] / git_remote_helpers / hg / exporter.py
1 import binascii
2 import os.path
3 import sys
4
5
6 LF = '\n'
7 SP = ' '
8
9 class GitExporter(object):
10     def __init__(self, repo):
11         self.git_hg = repo.git_hg
12         self.repo = repo
13         self.gitmarksfile = os.path.join(repo.hash, 'git.marks')
14         self.prefix = repo.prefix
15         self.nullref = "0" * 40
16         self.next_id = 0
17         self.mapping = {}
18         self.debugging = True
19         self.use_options = False
20
21     def nextid(self):
22         self.next_id += 1
23         return self.next_id
24
25     def tohex(self, binhex):
26         return binascii.hexlify(binhex)
27
28     def mode(self, fctx):
29         flags = fctx.flags()
30
31         if 'l' in flags:
32           mode = '120000'
33         elif 'x' in flags:
34           mode = '100755'
35         else:
36           mode = '100644'
37
38         return mode
39
40     def parents(self, parents):
41         parents = [self.tohex(i.node()) for i in parents]
42         parents = [i for i in parents if i != self.nullref]
43         assert all(i in self.mapping for i in parents)
44         parents = [':%d' % self.mapping[i] for i in parents]
45
46         return parents
47
48     def ref(self, ctx):
49         return self.prefix + ctx.branch()
50
51     def write(self, *args):
52         msg = ''.join([str(i) for i in args])
53         sys.stdout.write(msg)
54
55     def debug(self, msg):
56         assert LF not in msg
57         self.write('#', SP, msg, LF)
58
59     def feature(self, feature, value=None):
60         if not self.use_features:
61             return
62
63         if value:
64             self.write('feature', SP, feature, '=', value, LF)
65         else:
66             self.write('feature', SP, feature, LF)
67
68     def option(self, option, value=None):
69         if not self.use_options:
70             return
71
72         if value:
73             self.write('option', SP, option, '=', value, LF)
74         else:
75             self.write('option', SP, option, LF)
76
77     def option_quiet(self):
78         self.option('quiet')
79
80     def feature_relative_marks(self):
81         self.feature('relative-marks')
82
83     def feature_export_marks(self, marks):
84         self.feature('export-marks', marks)
85
86     def feature_import_marks(self, marks):
87         self.feature('import-marks', marks)
88
89     def feature_force(self):
90         self.feature('force')
91
92     def progress(self, message):
93         self.write('progress', SP, message, LF)
94
95     def write_data(self, data):
96         count = len(data)
97         self.write('data', SP, count, LF)
98         self.write(data, LF)
99
100     def write_mark(self, idnum):
101         self.write('mark', SP, ':', idnum, LF)
102
103     def write_blob(self, data, idnum):
104         self.write('blob', LF)
105         self.write_mark(idnum)
106         self.write_data(data)
107
108     def write_file(self, ctx, file, idnum):
109         fctx = ctx.filectx(file)
110         data = fctx.data()
111
112         self.write_blob(data, idnum)
113
114     def write_commit(self, ref):
115         self.write('commit', SP, ref, LF)
116
117     def write_author(self, author):
118         self.write('author', SP, author, LF)
119
120     def write_committer(self, committer):
121         self.write('committer', SP, committer, LF)
122
123     def write_from(self, parent):
124         self.write('from', SP, parent, LF)
125
126     def write_merge(self, parent):
127         self.write('merge', SP, parent, LF)
128
129     def write_reset(self, ref, idnum):
130         self.write('reset', SP, ref, LF)
131         self.write('from', SP, ':', idnum, LF)
132
133     def write_parents(self, parents):
134         parents = self.parents(parents)
135
136         # first commit
137         if not parents:
138             return
139
140         parent = parents[0]
141
142         self.write_from(parent)
143
144         for parent in parents[1:]:
145             self.write_merge(parent)
146
147     def write_filedeleteall(self):
148         self.write('deleteall', LF)
149
150     def write_filedelete(self, ctx, name):
151         self.write('D', SP, name, LF)
152
153     def write_filemodify_mark(self, mode, name, mark):
154         self.write('M', SP, mode, SP, ':', mark, SP, name, LF)
155
156     def write_filemodify_inline(self, mode, name, data):
157         self.write('M', SP, mode, SP, 'inline', SP, name, LF)
158         self.write_data(data)
159
160     def write_filemodify(self, ctx, name):
161         fctx = ctx.filectx(name)
162         man = ctx.manifest()
163         nodesha = man[name]
164         hash = self.tohex(nodesha)
165         mode = self.mode(fctx)
166
167         if hash in self.mapping:
168             mark = self.mapping[hash]
169             self.write_filemodify_mark(mode, name, mark)
170         else:
171             data = fctx.data()
172             self.write_filemodify_inline(mode, name, data)
173
174     def write_files(self, ctx):
175         man = ctx.manifest()
176
177         if len(ctx.parents()) == 2:
178             self.write_filedeleteall()
179             for name in man:
180                 self.write_filemodify(ctx, name)
181         else:
182             for name in ctx.files():
183                 # file got deleted
184                 if name not in man:
185                     self.write_filedelete(ctx, name)
186                 else:
187                     self.write_filemodify(ctx, name)
188
189     def export_files(self, ctx):
190         man = ctx.manifest()
191
192         for name in [i for i in ctx.files() if i in man]:
193             idnum = self.nextid()
194             nodesha = man[name]
195             hash = self.tohex(nodesha)
196
197             self.write_file(ctx, name, idnum)
198             self.mapping[hash] = idnum
199
200     def export_commit(self, ctx, ref, idnum, msg, parents):
201         author = self.git_hg.get_author(ctx)
202         committer = self.git_hg.get_committer(ctx)
203         committer = committer if committer else author
204
205         self.debug('exporting commit')
206         self.write_commit(ref)
207         self.write_mark(idnum)
208         self.write_author(author)
209         self.write_committer(committer)
210         self.write_data(msg)
211         self.write_parents(parents)
212         self.write_files(ctx)
213         self.debug('commit exported')
214
215     def export_revision(self, ctx):
216         nodesha = ctx.node()
217         hash = self.tohex(nodesha)
218
219         if hash in self.mapping:
220             return False
221
222         self.export_files(ctx)
223
224         idnum = self.nextid()
225
226         ref = self.ref(ctx)
227         msg = self.git_hg.get_message(ctx)
228         parents = self.git_hg.get_parents(ctx)
229
230         self.export_commit(ctx, ref, idnum, msg, parents)
231         self.mapping[hash] = idnum
232
233         return True
234
235     def export_branch(self, name, rev):
236         ctx = self.repo.changectx(rev)
237         nodesha = ctx.node()
238         hash = self.tohex(nodesha)
239         idnum = self.mapping[hash]
240
241         ref = self.prefix + name
242
243         self.write_reset(ref, idnum)
244
245     def export_repo(self):
246         exported = printed= False
247
248         for rev in self.repo.changelog:
249             ctx = self.repo.changectx(rev)
250             exported = self.export_revision(ctx) or exported
251
252             if (exported and not printed) or (exported and rev%1000 == 0):
253                 self.progress("Exported revision %d.\n" % rev)
254                 printed = True
255
256     def write_marks(self, base):
257         dirname = self.repo.get_base_path(base)
258         path = os.path.join(dirname, 'hg.marks')
259         if not os.path.exists(dirname):
260             os.makedirs(dirname)
261         f = open(path, 'w') #self.repo.opener(self.marksfile, 'w', atomictemp=True)
262
263         second = lambda (a, b): b
264
265         for hash, mark in sorted(self.mapping.iteritems(), key=second):
266             f.write(':%d %s\n' % (mark, hash))
267
268         f.close() #f.rename()
269
270     def read_marks(self, base):
271         dirname = self.repo.get_base_path(base)
272         path = os.path.join(dirname, 'hg.marks')
273
274         if not os.path.exists(path):
275             sys.stderr.write("warning: cannot find " + path)
276             return
277
278         f = open(path) #self.repo.opener(self.marksfile)
279
280         marks = [i.strip().split(' ') for i in f.readlines()]
281
282         self.mapping = dict((i[1], int(i[0][1:])) for i in marks)
283         self.next_id = max(self.mapping.values())
284
285     def setup(self, resume, base, use_options, use_features):
286         self.use_options = use_options
287         self.use_features = use_features
288         self.option_quiet()
289
290         self.feature_force()
291         self.feature_relative_marks()
292         self.feature_export_marks(self.gitmarksfile)
293
294         dirname = self.repo.get_base_path(base)
295         path = os.path.abspath(os.path.join(dirname, 'git.marks'))
296
297         if resume and os.path.exists(path):
298             self.feature_import_marks(self.gitmarksfile)
299             self.read_marks(base)
300
301     def fast_export(self, base, resume=False, use_options=False, use_features=False):
302         self.setup(resume, base, use_options, use_features)
303
304         self.progress("Exporting '%s'.\n" % self.repo.path)
305
306         try:
307             self.export_repo()
308         except KeyboardInterrupt, e:
309             self.progress("Interrupted.")
310
311         self.write_marks(base)
312
313         self.progress("Done!\n")