##// END OF EJS Templates
convert: Support Mercurial as a source, as well as a sink
Bryan O'Sullivan -
r5013:6c1029aa default
parent child Browse files
Show More
@@ -1,353 +1,354
1 # convert.py Foreign SCM converter
1 # convert.py Foreign SCM converter
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from common import NoRepo, converter_source, converter_sink
8 from common import NoRepo, converter_source, converter_sink
9 from cvs import convert_cvs
9 from cvs import convert_cvs
10 from git import convert_git
10 from git import convert_git
11 from hg import convert_mercurial
11 from hg import mercurial_source, mercurial_sink
12 from subversion import convert_svn
12 from subversion import convert_svn
13
13
14 import os, shutil
14 import os, shutil
15 from mercurial import hg, ui, util, commands
15 from mercurial import hg, ui, util, commands
16
16
17 commands.norepo += " convert"
17 commands.norepo += " convert"
18
18
19 converters = [convert_cvs, convert_git, convert_svn, convert_mercurial]
19 converters = [convert_cvs, convert_git, convert_svn, mercurial_source,
20 mercurial_sink]
20
21
21 def convertsource(ui, path, **opts):
22 def convertsource(ui, path, **opts):
22 for c in converters:
23 for c in converters:
23 if not hasattr(c, 'getcommit'):
24 if not hasattr(c, 'getcommit'):
24 continue
25 continue
25 try:
26 try:
26 return c(ui, path, **opts)
27 return c(ui, path, **opts)
27 except NoRepo:
28 except NoRepo:
28 pass
29 pass
29 raise util.Abort('%s: unknown repository type' % path)
30 raise util.Abort('%s: unknown repository type' % path)
30
31
31 def convertsink(ui, path):
32 def convertsink(ui, path):
32 if not os.path.isdir(path):
33 if not os.path.isdir(path):
33 raise util.Abort("%s: not a directory" % path)
34 raise util.Abort("%s: not a directory" % path)
34 for c in converters:
35 for c in converters:
35 if not hasattr(c, 'putcommit'):
36 if not hasattr(c, 'putcommit'):
36 continue
37 continue
37 try:
38 try:
38 return c(ui, path)
39 return c(ui, path)
39 except NoRepo:
40 except NoRepo:
40 pass
41 pass
41 raise util.Abort('%s: unknown repository type' % path)
42 raise util.Abort('%s: unknown repository type' % path)
42
43
43 class convert(object):
44 class convert(object):
44 def __init__(self, ui, source, dest, revmapfile, opts):
45 def __init__(self, ui, source, dest, revmapfile, opts):
45
46
46 self.source = source
47 self.source = source
47 self.dest = dest
48 self.dest = dest
48 self.ui = ui
49 self.ui = ui
49 self.opts = opts
50 self.opts = opts
50 self.commitcache = {}
51 self.commitcache = {}
51 self.revmapfile = revmapfile
52 self.revmapfile = revmapfile
52 self.revmapfilefd = None
53 self.revmapfilefd = None
53 self.authors = {}
54 self.authors = {}
54 self.authorfile = None
55 self.authorfile = None
55
56
56 self.map = {}
57 self.map = {}
57 try:
58 try:
58 origrevmapfile = open(self.revmapfile, 'r')
59 origrevmapfile = open(self.revmapfile, 'r')
59 for l in origrevmapfile:
60 for l in origrevmapfile:
60 sv, dv = l[:-1].split()
61 sv, dv = l[:-1].split()
61 self.map[sv] = dv
62 self.map[sv] = dv
62 origrevmapfile.close()
63 origrevmapfile.close()
63 except IOError:
64 except IOError:
64 pass
65 pass
65
66
66 # Read first the dst author map if any
67 # Read first the dst author map if any
67 authorfile = self.dest.authorfile()
68 authorfile = self.dest.authorfile()
68 if authorfile and os.path.exists(authorfile):
69 if authorfile and os.path.exists(authorfile):
69 self.readauthormap(authorfile)
70 self.readauthormap(authorfile)
70 # Extend/Override with new author map if necessary
71 # Extend/Override with new author map if necessary
71 if opts.get('authors'):
72 if opts.get('authors'):
72 self.readauthormap(opts.get('authors'))
73 self.readauthormap(opts.get('authors'))
73 self.authorfile = self.dest.authorfile()
74 self.authorfile = self.dest.authorfile()
74
75
75 def walktree(self, heads):
76 def walktree(self, heads):
76 '''Return a mapping that identifies the uncommitted parents of every
77 '''Return a mapping that identifies the uncommitted parents of every
77 uncommitted changeset.'''
78 uncommitted changeset.'''
78 visit = heads
79 visit = heads
79 known = {}
80 known = {}
80 parents = {}
81 parents = {}
81 while visit:
82 while visit:
82 n = visit.pop(0)
83 n = visit.pop(0)
83 if n in known or n in self.map: continue
84 if n in known or n in self.map: continue
84 known[n] = 1
85 known[n] = 1
85 self.commitcache[n] = self.source.getcommit(n)
86 self.commitcache[n] = self.source.getcommit(n)
86 cp = self.commitcache[n].parents
87 cp = self.commitcache[n].parents
87 parents[n] = []
88 parents[n] = []
88 for p in cp:
89 for p in cp:
89 parents[n].append(p)
90 parents[n].append(p)
90 visit.append(p)
91 visit.append(p)
91
92
92 return parents
93 return parents
93
94
94 def toposort(self, parents):
95 def toposort(self, parents):
95 '''Return an ordering such that every uncommitted changeset is
96 '''Return an ordering such that every uncommitted changeset is
96 preceeded by all its uncommitted ancestors.'''
97 preceeded by all its uncommitted ancestors.'''
97 visit = parents.keys()
98 visit = parents.keys()
98 seen = {}
99 seen = {}
99 children = {}
100 children = {}
100
101
101 while visit:
102 while visit:
102 n = visit.pop(0)
103 n = visit.pop(0)
103 if n in seen: continue
104 if n in seen: continue
104 seen[n] = 1
105 seen[n] = 1
105 # Ensure that nodes without parents are present in the 'children'
106 # Ensure that nodes without parents are present in the 'children'
106 # mapping.
107 # mapping.
107 children.setdefault(n, [])
108 children.setdefault(n, [])
108 for p in parents[n]:
109 for p in parents[n]:
109 if not p in self.map:
110 if not p in self.map:
110 visit.append(p)
111 visit.append(p)
111 children.setdefault(p, []).append(n)
112 children.setdefault(p, []).append(n)
112
113
113 s = []
114 s = []
114 removed = {}
115 removed = {}
115 visit = children.keys()
116 visit = children.keys()
116 while visit:
117 while visit:
117 n = visit.pop(0)
118 n = visit.pop(0)
118 if n in removed: continue
119 if n in removed: continue
119 dep = 0
120 dep = 0
120 if n in parents:
121 if n in parents:
121 for p in parents[n]:
122 for p in parents[n]:
122 if p in self.map: continue
123 if p in self.map: continue
123 if p not in removed:
124 if p not in removed:
124 # we're still dependent
125 # we're still dependent
125 visit.append(n)
126 visit.append(n)
126 dep = 1
127 dep = 1
127 break
128 break
128
129
129 if not dep:
130 if not dep:
130 # all n's parents are in the list
131 # all n's parents are in the list
131 removed[n] = 1
132 removed[n] = 1
132 if n not in self.map:
133 if n not in self.map:
133 s.append(n)
134 s.append(n)
134 if n in children:
135 if n in children:
135 for c in children[n]:
136 for c in children[n]:
136 visit.insert(0, c)
137 visit.insert(0, c)
137
138
138 if self.opts.get('datesort'):
139 if self.opts.get('datesort'):
139 depth = {}
140 depth = {}
140 for n in s:
141 for n in s:
141 depth[n] = 0
142 depth[n] = 0
142 pl = [p for p in self.commitcache[n].parents
143 pl = [p for p in self.commitcache[n].parents
143 if p not in self.map]
144 if p not in self.map]
144 if pl:
145 if pl:
145 depth[n] = max([depth[p] for p in pl]) + 1
146 depth[n] = max([depth[p] for p in pl]) + 1
146
147
147 s = [(depth[n], self.commitcache[n].date, n) for n in s]
148 s = [(depth[n], self.commitcache[n].date, n) for n in s]
148 s.sort()
149 s.sort()
149 s = [e[2] for e in s]
150 s = [e[2] for e in s]
150
151
151 return s
152 return s
152
153
153 def mapentry(self, src, dst):
154 def mapentry(self, src, dst):
154 if self.revmapfilefd is None:
155 if self.revmapfilefd is None:
155 try:
156 try:
156 self.revmapfilefd = open(self.revmapfile, "a")
157 self.revmapfilefd = open(self.revmapfile, "a")
157 except IOError, (errno, strerror):
158 except IOError, (errno, strerror):
158 raise util.Abort("Could not open map file %s: %s, %s\n" % (self.revmapfile, errno, strerror))
159 raise util.Abort("Could not open map file %s: %s, %s\n" % (self.revmapfile, errno, strerror))
159 self.map[src] = dst
160 self.map[src] = dst
160 self.revmapfilefd.write("%s %s\n" % (src, dst))
161 self.revmapfilefd.write("%s %s\n" % (src, dst))
161 self.revmapfilefd.flush()
162 self.revmapfilefd.flush()
162
163
163 def writeauthormap(self):
164 def writeauthormap(self):
164 authorfile = self.authorfile
165 authorfile = self.authorfile
165 if authorfile:
166 if authorfile:
166 self.ui.status('Writing author map file %s\n' % authorfile)
167 self.ui.status('Writing author map file %s\n' % authorfile)
167 ofile = open(authorfile, 'w+')
168 ofile = open(authorfile, 'w+')
168 for author in self.authors:
169 for author in self.authors:
169 ofile.write("%s=%s\n" % (author, self.authors[author]))
170 ofile.write("%s=%s\n" % (author, self.authors[author]))
170 ofile.close()
171 ofile.close()
171
172
172 def readauthormap(self, authorfile):
173 def readauthormap(self, authorfile):
173 afile = open(authorfile, 'r')
174 afile = open(authorfile, 'r')
174 for line in afile:
175 for line in afile:
175 try:
176 try:
176 srcauthor = line.split('=')[0].strip()
177 srcauthor = line.split('=')[0].strip()
177 dstauthor = line.split('=')[1].strip()
178 dstauthor = line.split('=')[1].strip()
178 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
179 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
179 self.ui.status(
180 self.ui.status(
180 'Overriding mapping for author %s, was %s, will be %s\n'
181 'Overriding mapping for author %s, was %s, will be %s\n'
181 % (srcauthor, self.authors[srcauthor], dstauthor))
182 % (srcauthor, self.authors[srcauthor], dstauthor))
182 else:
183 else:
183 self.ui.debug('Mapping author %s to %s\n'
184 self.ui.debug('Mapping author %s to %s\n'
184 % (srcauthor, dstauthor))
185 % (srcauthor, dstauthor))
185 self.authors[srcauthor] = dstauthor
186 self.authors[srcauthor] = dstauthor
186 except IndexError:
187 except IndexError:
187 self.ui.warn(
188 self.ui.warn(
188 'Ignoring bad line in author file map %s: %s\n'
189 'Ignoring bad line in author file map %s: %s\n'
189 % (authorfile, line))
190 % (authorfile, line))
190 afile.close()
191 afile.close()
191
192
192 def copy(self, rev):
193 def copy(self, rev):
193 c = self.commitcache[rev]
194 c = self.commitcache[rev]
194 files = self.source.getchanges(rev)
195 files = self.source.getchanges(rev)
195
196
196 do_copies = hasattr(self.dest, 'copyfile')
197 do_copies = hasattr(self.dest, 'copyfile')
197
198
198 for f, v in files:
199 for f, v in files:
199 try:
200 try:
200 data = self.source.getfile(f, v)
201 data = self.source.getfile(f, v)
201 except IOError, inst:
202 except IOError, inst:
202 self.dest.delfile(f)
203 self.dest.delfile(f)
203 else:
204 else:
204 e = self.source.getmode(f, v)
205 e = self.source.getmode(f, v)
205 self.dest.putfile(f, e, data)
206 self.dest.putfile(f, e, data)
206 if do_copies:
207 if do_copies:
207 if f in c.copies:
208 if f in c.copies:
208 # Merely marks that a copy happened.
209 # Merely marks that a copy happened.
209 self.dest.copyfile(c.copies[f], f)
210 self.dest.copyfile(c.copies[f], f)
210
211
211
212
212 r = [self.map[v] for v in c.parents]
213 r = [self.map[v] for v in c.parents]
213 f = [f for f, v in files]
214 f = [f for f, v in files]
214 newnode = self.dest.putcommit(f, r, c)
215 newnode = self.dest.putcommit(f, r, c)
215 self.mapentry(rev, newnode)
216 self.mapentry(rev, newnode)
216
217
217 def convert(self):
218 def convert(self):
218 try:
219 try:
219 self.source.setrevmap(self.map)
220 self.source.setrevmap(self.map)
220 self.ui.status("scanning source...\n")
221 self.ui.status("scanning source...\n")
221 heads = self.source.getheads()
222 heads = self.source.getheads()
222 parents = self.walktree(heads)
223 parents = self.walktree(heads)
223 self.ui.status("sorting...\n")
224 self.ui.status("sorting...\n")
224 t = self.toposort(parents)
225 t = self.toposort(parents)
225 num = len(t)
226 num = len(t)
226 c = None
227 c = None
227
228
228 self.ui.status("converting...\n")
229 self.ui.status("converting...\n")
229 for c in t:
230 for c in t:
230 num -= 1
231 num -= 1
231 desc = self.commitcache[c].desc
232 desc = self.commitcache[c].desc
232 if "\n" in desc:
233 if "\n" in desc:
233 desc = desc.splitlines()[0]
234 desc = desc.splitlines()[0]
234 author = self.commitcache[c].author
235 author = self.commitcache[c].author
235 author = self.authors.get(author, author)
236 author = self.authors.get(author, author)
236 self.commitcache[c].author = author
237 self.commitcache[c].author = author
237 self.ui.status("%d %s\n" % (num, desc))
238 self.ui.status("%d %s\n" % (num, desc))
238 self.copy(c)
239 self.copy(c)
239
240
240 tags = self.source.gettags()
241 tags = self.source.gettags()
241 ctags = {}
242 ctags = {}
242 for k in tags:
243 for k in tags:
243 v = tags[k]
244 v = tags[k]
244 if v in self.map:
245 if v in self.map:
245 ctags[k] = self.map[v]
246 ctags[k] = self.map[v]
246
247
247 if c and ctags:
248 if c and ctags:
248 nrev = self.dest.puttags(ctags)
249 nrev = self.dest.puttags(ctags)
249 # write another hash correspondence to override the previous
250 # write another hash correspondence to override the previous
250 # one so we don't end up with extra tag heads
251 # one so we don't end up with extra tag heads
251 if nrev:
252 if nrev:
252 self.mapentry(c, nrev)
253 self.mapentry(c, nrev)
253
254
254 self.writeauthormap()
255 self.writeauthormap()
255 finally:
256 finally:
256 self.cleanup()
257 self.cleanup()
257
258
258 def cleanup(self):
259 def cleanup(self):
259 if self.revmapfilefd:
260 if self.revmapfilefd:
260 self.revmapfilefd.close()
261 self.revmapfilefd.close()
261
262
262 def _convert(ui, src, dest=None, revmapfile=None, **opts):
263 def _convert(ui, src, dest=None, revmapfile=None, **opts):
263 """Convert a foreign SCM repository to a Mercurial one.
264 """Convert a foreign SCM repository to a Mercurial one.
264
265
265 Accepted source formats:
266 Accepted source formats:
266 - GIT
267 - GIT
267 - CVS
268 - CVS
268 - SVN
269 - SVN
269
270
270 Accepted destination formats:
271 Accepted destination formats:
271 - Mercurial
272 - Mercurial
272
273
273 If no revision is given, all revisions will be converted. Otherwise,
274 If no revision is given, all revisions will be converted. Otherwise,
274 convert will only import up to the named revision (given in a format
275 convert will only import up to the named revision (given in a format
275 understood by the source).
276 understood by the source).
276
277
277 If no destination directory name is specified, it defaults to the
278 If no destination directory name is specified, it defaults to the
278 basename of the source with '-hg' appended. If the destination
279 basename of the source with '-hg' appended. If the destination
279 repository doesn't exist, it will be created.
280 repository doesn't exist, it will be created.
280
281
281 If <revmapfile> isn't given, it will be put in a default location
282 If <revmapfile> isn't given, it will be put in a default location
282 (<dest>/.hg/shamap by default). The <revmapfile> is a simple text
283 (<dest>/.hg/shamap by default). The <revmapfile> is a simple text
283 file that maps each source commit ID to the destination ID for
284 file that maps each source commit ID to the destination ID for
284 that revision, like so:
285 that revision, like so:
285 <source ID> <destination ID>
286 <source ID> <destination ID>
286
287
287 If the file doesn't exist, it's automatically created. It's updated
288 If the file doesn't exist, it's automatically created. It's updated
288 on each commit copied, so convert-repo can be interrupted and can
289 on each commit copied, so convert-repo can be interrupted and can
289 be run repeatedly to copy new commits.
290 be run repeatedly to copy new commits.
290
291
291 The [username mapping] file is a simple text file that maps each source
292 The [username mapping] file is a simple text file that maps each source
292 commit author to a destination commit author. It is handy for source SCMs
293 commit author to a destination commit author. It is handy for source SCMs
293 that use unix logins to identify authors (eg: CVS). One line per author
294 that use unix logins to identify authors (eg: CVS). One line per author
294 mapping and the line format is:
295 mapping and the line format is:
295 srcauthor=whatever string you want
296 srcauthor=whatever string you want
296 """
297 """
297
298
298 util._encoding = 'UTF-8'
299 util._encoding = 'UTF-8'
299
300
300 if not dest:
301 if not dest:
301 dest = hg.defaultdest(src) + "-hg"
302 dest = hg.defaultdest(src) + "-hg"
302 ui.status("assuming destination %s\n" % dest)
303 ui.status("assuming destination %s\n" % dest)
303
304
304 # Try to be smart and initalize things when required
305 # Try to be smart and initalize things when required
305 created = False
306 created = False
306 if os.path.isdir(dest):
307 if os.path.isdir(dest):
307 if len(os.listdir(dest)) > 0:
308 if len(os.listdir(dest)) > 0:
308 try:
309 try:
309 hg.repository(ui, dest)
310 hg.repository(ui, dest)
310 ui.status("destination %s is a Mercurial repository\n" % dest)
311 ui.status("destination %s is a Mercurial repository\n" % dest)
311 except hg.RepoError:
312 except hg.RepoError:
312 raise util.Abort(
313 raise util.Abort(
313 "destination directory %s is not empty.\n"
314 "destination directory %s is not empty.\n"
314 "Please specify an empty directory to be initialized\n"
315 "Please specify an empty directory to be initialized\n"
315 "or an already initialized mercurial repository"
316 "or an already initialized mercurial repository"
316 % dest)
317 % dest)
317 else:
318 else:
318 ui.status("initializing destination %s repository\n" % dest)
319 ui.status("initializing destination %s repository\n" % dest)
319 hg.repository(ui, dest, create=True)
320 hg.repository(ui, dest, create=True)
320 created = True
321 created = True
321 elif os.path.exists(dest):
322 elif os.path.exists(dest):
322 raise util.Abort("destination %s exists and is not a directory" % dest)
323 raise util.Abort("destination %s exists and is not a directory" % dest)
323 else:
324 else:
324 ui.status("initializing destination %s repository\n" % dest)
325 ui.status("initializing destination %s repository\n" % dest)
325 hg.repository(ui, dest, create=True)
326 hg.repository(ui, dest, create=True)
326 created = True
327 created = True
327
328
328 destc = convertsink(ui, dest)
329 destc = convertsink(ui, dest)
329
330
330 try:
331 try:
331 srcc = convertsource(ui, src, rev=opts.get('rev'))
332 srcc = convertsource(ui, src, rev=opts.get('rev'))
332 except Exception:
333 except Exception:
333 if created:
334 if created:
334 shutil.rmtree(dest, True)
335 shutil.rmtree(dest, True)
335 raise
336 raise
336
337
337 if not revmapfile:
338 if not revmapfile:
338 try:
339 try:
339 revmapfile = destc.revmapfile()
340 revmapfile = destc.revmapfile()
340 except:
341 except:
341 revmapfile = os.path.join(destc, "map")
342 revmapfile = os.path.join(destc, "map")
342
343
343 c = convert(ui, srcc, destc, revmapfile, opts)
344 c = convert(ui, srcc, destc, revmapfile, opts)
344 c.convert()
345 c.convert()
345
346
346 cmdtable = {
347 cmdtable = {
347 "convert":
348 "convert":
348 (_convert,
349 (_convert,
349 [('A', 'authors', '', 'username mapping filename'),
350 [('A', 'authors', '', 'username mapping filename'),
350 ('r', 'rev', '', 'import up to target revision REV'),
351 ('r', 'rev', '', 'import up to target revision REV'),
351 ('', 'datesort', None, 'try to sort changesets by date')],
352 ('', 'datesort', None, 'try to sort changesets by date')],
352 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
353 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
353 }
354 }
@@ -1,97 +1,159
1 # hg backend for convert extension
1 # hg backend for convert extension
2
2
3 # Note for hg->hg conversion: Old versions of Mercurial didn't trim
4 # the whitespace from the ends of commit messages, but new versions
5 # do. Changesets created by those older versions, then converted, may
6 # thus have different hashes for changesets that are otherwise
7 # identical.
8
9
3 import os, time
10 import os, time
4 from mercurial import hg
11 from mercurial.node import *
12 from mercurial import hg, revlog, util
5
13
6 from common import NoRepo, converter_sink
14 from common import NoRepo, commit, converter_source, converter_sink
7
15
8 class convert_mercurial(converter_sink):
16 class mercurial_sink(converter_sink):
9 def __init__(self, ui, path):
17 def __init__(self, ui, path):
10 self.path = path
18 self.path = path
11 self.ui = ui
19 self.ui = ui
12 try:
20 try:
13 self.repo = hg.repository(self.ui, path)
21 self.repo = hg.repository(self.ui, path)
14 except:
22 except:
15 raise NoRepo("could open hg repo %s" % path)
23 raise NoRepo("could not open hg repo %s as sink" % path)
16
24
17 def revmapfile(self):
25 def revmapfile(self):
18 return os.path.join(self.path, ".hg", "shamap")
26 return os.path.join(self.path, ".hg", "shamap")
19
27
20 def authorfile(self):
28 def authorfile(self):
21 return os.path.join(self.path, ".hg", "authormap")
29 return os.path.join(self.path, ".hg", "authormap")
22
30
23 def getheads(self):
31 def getheads(self):
24 h = self.repo.changelog.heads()
32 h = self.repo.changelog.heads()
25 return [ hg.hex(x) for x in h ]
33 return [ hg.hex(x) for x in h ]
26
34
27 def putfile(self, f, e, data):
35 def putfile(self, f, e, data):
28 self.repo.wwrite(f, data, e)
36 self.repo.wwrite(f, data, e)
29 if f not in self.repo.dirstate:
37 if f not in self.repo.dirstate:
30 self.repo.dirstate.add(f)
38 self.repo.dirstate.add(f)
31
39
32 def copyfile(self, source, dest):
40 def copyfile(self, source, dest):
33 self.repo.copy(source, dest)
41 self.repo.copy(source, dest)
34
42
35 def delfile(self, f):
43 def delfile(self, f):
36 try:
44 try:
37 os.unlink(self.repo.wjoin(f))
45 os.unlink(self.repo.wjoin(f))
38 #self.repo.remove([f])
46 #self.repo.remove([f])
39 except:
47 except:
40 pass
48 pass
41
49
42 def putcommit(self, files, parents, commit):
50 def putcommit(self, files, parents, commit):
43 seen = {}
51 seen = {}
44 pl = []
52 pl = []
45 for p in parents:
53 for p in parents:
46 if p not in seen:
54 if p not in seen:
47 pl.append(p)
55 pl.append(p)
48 seen[p] = 1
56 seen[p] = 1
49 parents = pl
57 parents = pl
50
58
51 if len(parents) < 2: parents.append("0" * 40)
59 if len(parents) < 2: parents.append("0" * 40)
52 if len(parents) < 2: parents.append("0" * 40)
60 if len(parents) < 2: parents.append("0" * 40)
53 p2 = parents.pop(0)
61 p2 = parents.pop(0)
54
62
55 text = commit.desc
63 text = commit.desc
56 extra = {}
64 extra = {}
57 if commit.branch:
65 if commit.branch:
58 extra['branch'] = commit.branch
66 extra['branch'] = commit.branch
59 if commit.rev:
67 if commit.rev:
60 extra['convert_revision'] = commit.rev
68 extra['convert_revision'] = commit.rev
61
69
62 while parents:
70 while parents:
63 p1 = p2
71 p1 = p2
64 p2 = parents.pop(0)
72 p2 = parents.pop(0)
65 a = self.repo.rawcommit(files, text, commit.author, commit.date,
73 a = self.repo.rawcommit(files, text, commit.author, commit.date,
66 hg.bin(p1), hg.bin(p2), extra=extra)
74 hg.bin(p1), hg.bin(p2), extra=extra)
67 text = "(octopus merge fixup)\n"
75 text = "(octopus merge fixup)\n"
68 p2 = hg.hex(self.repo.changelog.tip())
76 p2 = hg.hex(self.repo.changelog.tip())
69
77
70 return p2
78 return p2
71
79
72 def puttags(self, tags):
80 def puttags(self, tags):
73 try:
81 try:
74 old = self.repo.wfile(".hgtags").read()
82 old = self.repo.wfile(".hgtags").read()
75 oldlines = old.splitlines(1)
83 oldlines = old.splitlines(1)
76 oldlines.sort()
84 oldlines.sort()
77 except:
85 except:
78 oldlines = []
86 oldlines = []
79
87
80 k = tags.keys()
88 k = tags.keys()
81 k.sort()
89 k.sort()
82 newlines = []
90 newlines = []
83 for tag in k:
91 for tag in k:
84 newlines.append("%s %s\n" % (tags[tag], tag))
92 newlines.append("%s %s\n" % (tags[tag], tag))
85
93
86 newlines.sort()
94 newlines.sort()
87
95
88 if newlines != oldlines:
96 if newlines != oldlines:
89 self.ui.status("updating tags\n")
97 self.ui.status("updating tags\n")
90 f = self.repo.wfile(".hgtags", "w")
98 f = self.repo.wfile(".hgtags", "w")
91 f.write("".join(newlines))
99 f.write("".join(newlines))
92 f.close()
100 f.close()
93 if not oldlines: self.repo.add([".hgtags"])
101 if not oldlines: self.repo.add([".hgtags"])
94 date = "%s 0" % int(time.mktime(time.gmtime()))
102 date = "%s 0" % int(time.mktime(time.gmtime()))
95 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
103 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
96 date, self.repo.changelog.tip(), hg.nullid)
104 date, self.repo.changelog.tip(), hg.nullid)
97 return hg.hex(self.repo.changelog.tip())
105 return hg.hex(self.repo.changelog.tip())
106
107 class mercurial_source(converter_source):
108 def __init__(self, ui, path, rev=None):
109 converter_source.__init__(self, ui, path, rev)
110 self.repo = hg.repository(self.ui, path)
111 self.lastrev = None
112 self.lastctx = None
113
114 def changectx(self, rev):
115 if self.lastrev != rev:
116 self.lastctx = self.repo.changectx(rev)
117 self.lastrev = rev
118 return self.lastctx
119
120 def getheads(self):
121 return [hex(node) for node in self.repo.heads()]
122
123 def getfile(self, name, rev):
124 try:
125 return self.changectx(rev).filectx(name).data()
126 except revlog.LookupError, err:
127 raise IOError(err)
128
129 def getmode(self, name, rev):
130 m = self.changectx(rev).manifest()
131 return (m.execf(name) and 'x' or '') + (m.linkf(name) and 'l' or '')
132
133 def getchanges(self, rev):
134 ctx = self.changectx(rev)
135 m, a, r = self.repo.status(ctx.parents()[0].node(), ctx.node())[:3]
136 changes = [(name, rev) for name in m + a + r]
137 changes.sort()
138 return changes
139
140 def getcopies(self, ctx):
141 added = self.repo.status(ctx.parents()[0].node(), ctx.node())[1]
142 copies = {}
143 for name in added:
144 try:
145 copies[name] = ctx.filectx(name).renamed()[0]
146 except TypeError:
147 pass
148 return copies
149
150 def getcommit(self, rev):
151 ctx = self.changectx(rev)
152 parents = [hex(p.node()) for p in ctx.parents() if p.node() != nullid]
153 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
154 desc=ctx.description(), parents=parents,
155 branch=ctx.branch(), copies=self.getcopies(ctx))
156
157 def gettags(self):
158 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
159 return dict([(name, hex(node)) for name, node in tags])
General Comments 0
You need to be logged in to leave comments. Login now