##// END OF EJS Templates
convert: handle closed branch heads in hg-hg conversion (issue2185)
Matt Mackall -
r11673:a2f11188 default
parent child Browse files
Show More
@@ -1,353 +1,355 b''
1 1 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
2 2 # Copyright 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
3 3 #
4 4 # This software may be used and distributed according to the terms of the
5 5 # GNU General Public License version 2 or any later version.
6 6
7 7 import shlex
8 8 from mercurial.i18n import _
9 9 from mercurial import util
10 10 from common import SKIPREV, converter_source
11 11
12 12 def rpairs(name):
13 13 e = len(name)
14 14 while e != -1:
15 15 yield name[:e], name[e + 1:]
16 16 e = name.rfind('/', 0, e)
17 17 yield '.', name
18 18
19 19 class filemapper(object):
20 20 '''Map and filter filenames when importing.
21 21 A name can be mapped to itself, a new name, or None (omit from new
22 22 repository).'''
23 23
24 24 def __init__(self, ui, path=None):
25 25 self.ui = ui
26 26 self.include = {}
27 27 self.exclude = {}
28 28 self.rename = {}
29 29 if path:
30 30 if self.parse(path):
31 31 raise util.Abort(_('errors in filemap'))
32 32
33 33 def parse(self, path):
34 34 errs = 0
35 35 def check(name, mapping, listname):
36 36 if name in mapping:
37 37 self.ui.warn(_('%s:%d: %r already in %s list\n') %
38 38 (lex.infile, lex.lineno, name, listname))
39 39 return 1
40 40 return 0
41 41 lex = shlex.shlex(open(path), path, True)
42 42 lex.wordchars += '!@#$%^&*()-=+[]{}|;:,./<>?'
43 43 cmd = lex.get_token()
44 44 while cmd:
45 45 if cmd == 'include':
46 46 name = lex.get_token()
47 47 errs += check(name, self.exclude, 'exclude')
48 48 self.include[name] = name
49 49 elif cmd == 'exclude':
50 50 name = lex.get_token()
51 51 errs += check(name, self.include, 'include')
52 52 errs += check(name, self.rename, 'rename')
53 53 self.exclude[name] = name
54 54 elif cmd == 'rename':
55 55 src = lex.get_token()
56 56 dest = lex.get_token()
57 57 errs += check(src, self.exclude, 'exclude')
58 58 self.rename[src] = dest
59 59 elif cmd == 'source':
60 60 errs += self.parse(lex.get_token())
61 61 else:
62 62 self.ui.warn(_('%s:%d: unknown directive %r\n') %
63 63 (lex.infile, lex.lineno, cmd))
64 64 errs += 1
65 65 cmd = lex.get_token()
66 66 return errs
67 67
68 68 def lookup(self, name, mapping):
69 69 for pre, suf in rpairs(name):
70 70 try:
71 71 return mapping[pre], pre, suf
72 72 except KeyError:
73 73 pass
74 74 return '', name, ''
75 75
76 76 def __call__(self, name):
77 77 if self.include:
78 78 inc = self.lookup(name, self.include)[0]
79 79 else:
80 80 inc = name
81 81 if self.exclude:
82 82 exc = self.lookup(name, self.exclude)[0]
83 83 else:
84 84 exc = ''
85 85 if (not self.include and exc) or (len(inc) <= len(exc)):
86 86 return None
87 87 newpre, pre, suf = self.lookup(name, self.rename)
88 88 if newpre:
89 89 if newpre == '.':
90 90 return suf
91 91 if suf:
92 92 return newpre + '/' + suf
93 93 return newpre
94 94 return name
95 95
96 96 def active(self):
97 97 return bool(self.include or self.exclude or self.rename)
98 98
99 99 # This class does two additional things compared to a regular source:
100 100 #
101 101 # - Filter and rename files. This is mostly wrapped by the filemapper
102 102 # class above. We hide the original filename in the revision that is
103 103 # returned by getchanges to be able to find things later in getfile.
104 104 #
105 105 # - Return only revisions that matter for the files we're interested in.
106 106 # This involves rewriting the parents of the original revision to
107 107 # create a graph that is restricted to those revisions.
108 108 #
109 109 # This set of revisions includes not only revisions that directly
110 110 # touch files we're interested in, but also merges that merge two
111 111 # or more interesting revisions.
112 112
113 113 class filemap_source(converter_source):
114 114 def __init__(self, ui, baseconverter, filemap):
115 115 super(filemap_source, self).__init__(ui)
116 116 self.base = baseconverter
117 117 self.filemapper = filemapper(ui, filemap)
118 118 self.commits = {}
119 119 # if a revision rev has parent p in the original revision graph, then
120 120 # rev will have parent self.parentmap[p] in the restricted graph.
121 121 self.parentmap = {}
122 122 # self.wantedancestors[rev] is the set of all ancestors of rev that
123 123 # are in the restricted graph.
124 124 self.wantedancestors = {}
125 125 self.convertedorder = None
126 126 self._rebuilt = False
127 127 self.origparents = {}
128 128 self.children = {}
129 129 self.seenchildren = {}
130 130
131 131 def before(self):
132 132 self.base.before()
133 133
134 134 def after(self):
135 135 self.base.after()
136 136
137 137 def setrevmap(self, revmap):
138 138 # rebuild our state to make things restartable
139 139 #
140 140 # To avoid calling getcommit for every revision that has already
141 141 # been converted, we rebuild only the parentmap, delaying the
142 142 # rebuild of wantedancestors until we need it (i.e. until a
143 143 # merge).
144 144 #
145 145 # We assume the order argument lists the revisions in
146 146 # topological order, so that we can infer which revisions were
147 147 # wanted by previous runs.
148 148 self._rebuilt = not revmap
149 149 seen = {SKIPREV: SKIPREV}
150 150 dummyset = set()
151 151 converted = []
152 152 for rev in revmap.order:
153 153 mapped = revmap[rev]
154 154 wanted = mapped not in seen
155 155 if wanted:
156 156 seen[mapped] = rev
157 157 self.parentmap[rev] = rev
158 158 else:
159 159 self.parentmap[rev] = seen[mapped]
160 160 self.wantedancestors[rev] = dummyset
161 161 arg = seen[mapped]
162 162 if arg == SKIPREV:
163 163 arg = None
164 164 converted.append((rev, wanted, arg))
165 165 self.convertedorder = converted
166 166 return self.base.setrevmap(revmap)
167 167
168 168 def rebuild(self):
169 169 if self._rebuilt:
170 170 return True
171 171 self._rebuilt = True
172 172 self.parentmap.clear()
173 173 self.wantedancestors.clear()
174 174 self.seenchildren.clear()
175 175 for rev, wanted, arg in self.convertedorder:
176 176 if rev not in self.origparents:
177 177 self.origparents[rev] = self.getcommit(rev).parents
178 178 if arg is not None:
179 179 self.children[arg] = self.children.get(arg, 0) + 1
180 180
181 181 for rev, wanted, arg in self.convertedorder:
182 182 parents = self.origparents[rev]
183 183 if wanted:
184 184 self.mark_wanted(rev, parents)
185 185 else:
186 186 self.mark_not_wanted(rev, arg)
187 187 self._discard(arg, *parents)
188 188
189 189 return True
190 190
191 191 def getheads(self):
192 192 return self.base.getheads()
193 193
194 194 def getcommit(self, rev):
195 195 # We want to save a reference to the commit objects to be able
196 196 # to rewrite their parents later on.
197 197 c = self.commits[rev] = self.base.getcommit(rev)
198 198 for p in c.parents:
199 199 self.children[p] = self.children.get(p, 0) + 1
200 200 return c
201 201
202 202 def _discard(self, *revs):
203 203 for r in revs:
204 204 if r is None:
205 205 continue
206 206 self.seenchildren[r] = self.seenchildren.get(r, 0) + 1
207 207 if self.seenchildren[r] == self.children[r]:
208 208 del self.wantedancestors[r]
209 209 del self.parentmap[r]
210 210 del self.seenchildren[r]
211 211 if self._rebuilt:
212 212 del self.children[r]
213 213
214 214 def wanted(self, rev, i):
215 215 # Return True if we're directly interested in rev.
216 216 #
217 217 # i is an index selecting one of the parents of rev (if rev
218 218 # has no parents, i is None). getchangedfiles will give us
219 219 # the list of files that are different in rev and in the parent
220 220 # indicated by i. If we're interested in any of these files,
221 221 # we're interested in rev.
222 222 try:
223 223 files = self.base.getchangedfiles(rev, i)
224 224 except NotImplementedError:
225 225 raise util.Abort(_("source repository doesn't support --filemap"))
226 226 for f in files:
227 227 if self.filemapper(f):
228 228 return True
229 229 return False
230 230
231 231 def mark_not_wanted(self, rev, p):
232 232 # Mark rev as not interesting and update data structures.
233 233
234 234 if p is None:
235 235 # A root revision. Use SKIPREV to indicate that it doesn't
236 236 # map to any revision in the restricted graph. Put SKIPREV
237 237 # in the set of wanted ancestors to simplify code elsewhere
238 238 self.parentmap[rev] = SKIPREV
239 239 self.wantedancestors[rev] = set((SKIPREV,))
240 240 return
241 241
242 242 # Reuse the data from our parent.
243 243 self.parentmap[rev] = self.parentmap[p]
244 244 self.wantedancestors[rev] = self.wantedancestors[p]
245 245
246 246 def mark_wanted(self, rev, parents):
247 247 # Mark rev ss wanted and update data structures.
248 248
249 249 # rev will be in the restricted graph, so children of rev in
250 250 # the original graph should still have rev as a parent in the
251 251 # restricted graph.
252 252 self.parentmap[rev] = rev
253 253
254 254 # The set of wanted ancestors of rev is the union of the sets
255 255 # of wanted ancestors of its parents. Plus rev itself.
256 256 wrev = set()
257 257 for p in parents:
258 258 wrev.update(self.wantedancestors[p])
259 259 wrev.add(rev)
260 260 self.wantedancestors[rev] = wrev
261 261
262 262 def getchanges(self, rev):
263 263 parents = self.commits[rev].parents
264 264 if len(parents) > 1:
265 265 self.rebuild()
266 266
267 267 # To decide whether we're interested in rev we:
268 268 #
269 269 # - calculate what parents rev will have if it turns out we're
270 270 # interested in it. If it's going to have more than 1 parent,
271 271 # we're interested in it.
272 272 #
273 273 # - otherwise, we'll compare it with the single parent we found.
274 274 # If any of the files we're interested in is different in the
275 275 # the two revisions, we're interested in rev.
276 276
277 277 # A parent p is interesting if its mapped version (self.parentmap[p]):
278 278 # - is not SKIPREV
279 279 # - is still not in the list of parents (we don't want duplicates)
280 280 # - is not an ancestor of the mapped versions of the other parents
281 281 mparents = []
282 282 wp = None
283 283 for i, p1 in enumerate(parents):
284 284 mp1 = self.parentmap[p1]
285 285 if mp1 == SKIPREV or mp1 in mparents:
286 286 continue
287 287 for p2 in parents:
288 288 if p1 == p2 or mp1 == self.parentmap[p2]:
289 289 continue
290 290 if mp1 in self.wantedancestors[p2]:
291 291 break
292 292 else:
293 293 mparents.append(mp1)
294 294 wp = i
295 295
296 296 if wp is None and parents:
297 297 wp = 0
298 298
299 299 self.origparents[rev] = parents
300 300
301 if len(mparents) < 2 and not self.wanted(rev, wp):
301 closed = 'close' in self.commits[rev].extra
302
303 if len(mparents) < 2 and not closed and not self.wanted(rev, wp):
302 304 # We don't want this revision.
303 305 # Update our state and tell the convert process to map this
304 306 # revision to the same revision its parent as mapped to.
305 307 p = None
306 308 if parents:
307 309 p = parents[wp]
308 310 self.mark_not_wanted(rev, p)
309 311 self.convertedorder.append((rev, False, p))
310 312 self._discard(*parents)
311 313 return self.parentmap[rev]
312 314
313 315 # We want this revision.
314 316 # Rewrite the parents of the commit object
315 317 self.commits[rev].parents = mparents
316 318 self.mark_wanted(rev, parents)
317 319 self.convertedorder.append((rev, True, None))
318 320 self._discard(*parents)
319 321
320 322 # Get the real changes and do the filtering/mapping. To be
321 323 # able to get the files later on in getfile, we hide the
322 324 # original filename in the rev part of the return value.
323 325 changes, copies = self.base.getchanges(rev)
324 326 newnames = {}
325 327 files = []
326 328 for f, r in changes:
327 329 newf = self.filemapper(f)
328 330 if newf:
329 331 files.append((newf, (f, r)))
330 332 newnames[f] = newf
331 333
332 334 ncopies = {}
333 335 for c in copies:
334 336 newc = self.filemapper(c)
335 337 if newc:
336 338 newsource = self.filemapper(copies[c])
337 339 if newsource:
338 340 ncopies[newc] = newsource
339 341
340 342 return files, ncopies
341 343
342 344 def getfile(self, name, rev):
343 345 realname, realrev = rev
344 346 return self.base.getfile(realname, realrev)
345 347
346 348 def gettags(self):
347 349 return self.base.gettags()
348 350
349 351 def hasnativeorder(self):
350 352 return self.base.hasnativeorder()
351 353
352 354 def lookuprev(self, rev):
353 355 return self.base.lookuprev(rev)
@@ -1,375 +1,376 b''
1 1 # hg.py - hg backend for convert extension
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 # Notes for hg->hg conversion:
9 9 #
10 10 # * Old versions of Mercurial didn't trim the whitespace from the ends
11 11 # of commit messages, but new versions do. Changesets created by
12 12 # those older versions, then converted, may thus have different
13 13 # hashes for changesets that are otherwise identical.
14 14 #
15 15 # * Using "--config convert.hg.saverev=true" will make the source
16 16 # identifier to be stored in the converted revision. This will cause
17 17 # the converted revision to have a different identity than the
18 18 # source.
19 19
20 20
21 21 import os, time, cStringIO
22 22 from mercurial.i18n import _
23 23 from mercurial.node import bin, hex, nullid
24 24 from mercurial import hg, util, context, error
25 25
26 26 from common import NoRepo, commit, converter_source, converter_sink
27 27
28 28 class mercurial_sink(converter_sink):
29 29 def __init__(self, ui, path):
30 30 converter_sink.__init__(self, ui, path)
31 31 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
32 32 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
33 33 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
34 34 self.lastbranch = None
35 35 if os.path.isdir(path) and len(os.listdir(path)) > 0:
36 36 try:
37 37 self.repo = hg.repository(self.ui, path)
38 38 if not self.repo.local():
39 39 raise NoRepo(_('%s is not a local Mercurial repository')
40 40 % path)
41 41 except error.RepoError, err:
42 42 ui.traceback()
43 43 raise NoRepo(err.args[0])
44 44 else:
45 45 try:
46 46 ui.status(_('initializing destination %s repository\n') % path)
47 47 self.repo = hg.repository(self.ui, path, create=True)
48 48 if not self.repo.local():
49 49 raise NoRepo(_('%s is not a local Mercurial repository')
50 50 % path)
51 51 self.created.append(path)
52 52 except error.RepoError:
53 53 ui.traceback()
54 54 raise NoRepo(_("could not create hg repository %s as sink")
55 55 % path)
56 56 self.lock = None
57 57 self.wlock = None
58 58 self.filemapmode = False
59 59
60 60 def before(self):
61 61 self.ui.debug('run hg sink pre-conversion action\n')
62 62 self.wlock = self.repo.wlock()
63 63 self.lock = self.repo.lock()
64 64
65 65 def after(self):
66 66 self.ui.debug('run hg sink post-conversion action\n')
67 67 if self.lock:
68 68 self.lock.release()
69 69 if self.wlock:
70 70 self.wlock.release()
71 71
72 72 def revmapfile(self):
73 73 return os.path.join(self.path, ".hg", "shamap")
74 74
75 75 def authorfile(self):
76 76 return os.path.join(self.path, ".hg", "authormap")
77 77
78 78 def getheads(self):
79 79 h = self.repo.changelog.heads()
80 80 return [hex(x) for x in h]
81 81
82 82 def setbranch(self, branch, pbranches):
83 83 if not self.clonebranches:
84 84 return
85 85
86 86 setbranch = (branch != self.lastbranch)
87 87 self.lastbranch = branch
88 88 if not branch:
89 89 branch = 'default'
90 90 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
91 91 pbranch = pbranches and pbranches[0][1] or 'default'
92 92
93 93 branchpath = os.path.join(self.path, branch)
94 94 if setbranch:
95 95 self.after()
96 96 try:
97 97 self.repo = hg.repository(self.ui, branchpath)
98 98 except:
99 99 self.repo = hg.repository(self.ui, branchpath, create=True)
100 100 self.before()
101 101
102 102 # pbranches may bring revisions from other branches (merge parents)
103 103 # Make sure we have them, or pull them.
104 104 missings = {}
105 105 for b in pbranches:
106 106 try:
107 107 self.repo.lookup(b[0])
108 108 except:
109 109 missings.setdefault(b[1], []).append(b[0])
110 110
111 111 if missings:
112 112 self.after()
113 113 for pbranch, heads in missings.iteritems():
114 114 pbranchpath = os.path.join(self.path, pbranch)
115 115 prepo = hg.repository(self.ui, pbranchpath)
116 116 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
117 117 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
118 118 self.before()
119 119
120 120 def _rewritetags(self, source, revmap, data):
121 121 fp = cStringIO.StringIO()
122 122 for line in data.splitlines():
123 123 s = line.split(' ', 1)
124 124 if len(s) != 2:
125 125 continue
126 126 revid = revmap.get(source.lookuprev(s[0]))
127 127 if not revid:
128 128 continue
129 129 fp.write('%s %s\n' % (revid, s[1]))
130 130 return fp.getvalue()
131 131
132 132 def putcommit(self, files, copies, parents, commit, source, revmap):
133 133
134 134 files = dict(files)
135 135 def getfilectx(repo, memctx, f):
136 136 v = files[f]
137 137 data, mode = source.getfile(f, v)
138 138 if f == '.hgtags':
139 139 data = self._rewritetags(source, revmap, data)
140 140 return context.memfilectx(f, data, 'l' in mode, 'x' in mode,
141 141 copies.get(f))
142 142
143 143 pl = []
144 144 for p in parents:
145 145 if p not in pl:
146 146 pl.append(p)
147 147 parents = pl
148 148 nparents = len(parents)
149 149 if self.filemapmode and nparents == 1:
150 150 m1node = self.repo.changelog.read(bin(parents[0]))[0]
151 151 parent = parents[0]
152 152
153 153 if len(parents) < 2:
154 154 parents.append(nullid)
155 155 if len(parents) < 2:
156 156 parents.append(nullid)
157 157 p2 = parents.pop(0)
158 158
159 159 text = commit.desc
160 160 extra = commit.extra.copy()
161 161 if self.branchnames and commit.branch:
162 162 extra['branch'] = commit.branch
163 163 if commit.rev:
164 164 extra['convert_revision'] = commit.rev
165 165
166 166 while parents:
167 167 p1 = p2
168 168 p2 = parents.pop(0)
169 169 ctx = context.memctx(self.repo, (p1, p2), text, files.keys(),
170 170 getfilectx, commit.author, commit.date, extra)
171 171 self.repo.commitctx(ctx)
172 172 text = "(octopus merge fixup)\n"
173 173 p2 = hex(self.repo.changelog.tip())
174 174
175 175 if self.filemapmode and nparents == 1:
176 176 man = self.repo.manifest
177 177 mnode = self.repo.changelog.read(bin(p2))[0]
178 if not man.cmp(m1node, man.revision(mnode)):
178 closed = 'close' in commit.extra
179 if not closed and not man.cmp(m1node, man.revision(mnode)):
179 180 self.ui.status(_("filtering out empty revision\n"))
180 181 self.repo.rollback()
181 182 return parent
182 183 return p2
183 184
184 185 def puttags(self, tags):
185 186 try:
186 187 parentctx = self.repo[self.tagsbranch]
187 188 tagparent = parentctx.node()
188 189 except error.RepoError:
189 190 parentctx = None
190 191 tagparent = nullid
191 192
192 193 try:
193 194 oldlines = sorted(parentctx['.hgtags'].data().splitlines(True))
194 195 except:
195 196 oldlines = []
196 197
197 198 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
198 199 if newlines == oldlines:
199 200 return None, None
200 201 data = "".join(newlines)
201 202 def getfilectx(repo, memctx, f):
202 203 return context.memfilectx(f, data, False, False, None)
203 204
204 205 self.ui.status(_("updating tags\n"))
205 206 date = "%s 0" % int(time.mktime(time.gmtime()))
206 207 extra = {'branch': self.tagsbranch}
207 208 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
208 209 [".hgtags"], getfilectx, "convert-repo", date,
209 210 extra)
210 211 self.repo.commitctx(ctx)
211 212 return hex(self.repo.changelog.tip()), hex(tagparent)
212 213
213 214 def setfilemapmode(self, active):
214 215 self.filemapmode = active
215 216
216 217 class mercurial_source(converter_source):
217 218 def __init__(self, ui, path, rev=None):
218 219 converter_source.__init__(self, ui, path, rev)
219 220 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
220 221 self.ignored = set()
221 222 self.saverev = ui.configbool('convert', 'hg.saverev', False)
222 223 try:
223 224 self.repo = hg.repository(self.ui, path)
224 225 # try to provoke an exception if this isn't really a hg
225 226 # repo, but some other bogus compatible-looking url
226 227 if not self.repo.local():
227 228 raise error.RepoError()
228 229 except error.RepoError:
229 230 ui.traceback()
230 231 raise NoRepo(_("%s is not a local Mercurial repository") % path)
231 232 self.lastrev = None
232 233 self.lastctx = None
233 234 self._changescache = None
234 235 self.convertfp = None
235 236 # Restrict converted revisions to startrev descendants
236 237 startnode = ui.config('convert', 'hg.startrev')
237 238 if startnode is not None:
238 239 try:
239 240 startnode = self.repo.lookup(startnode)
240 241 except error.RepoError:
241 242 raise util.Abort(_('%s is not a valid start revision')
242 243 % startnode)
243 244 startrev = self.repo.changelog.rev(startnode)
244 245 children = {startnode: 1}
245 246 for rev in self.repo.changelog.descendants(startrev):
246 247 children[self.repo.changelog.node(rev)] = 1
247 248 self.keep = children.__contains__
248 249 else:
249 250 self.keep = util.always
250 251
251 252 def changectx(self, rev):
252 253 if self.lastrev != rev:
253 254 self.lastctx = self.repo[rev]
254 255 self.lastrev = rev
255 256 return self.lastctx
256 257
257 258 def parents(self, ctx):
258 259 return [p for p in ctx.parents() if p and self.keep(p.node())]
259 260
260 261 def getheads(self):
261 262 if self.rev:
262 263 heads = [self.repo[self.rev].node()]
263 264 else:
264 265 heads = self.repo.heads()
265 266 return [hex(h) for h in heads if self.keep(h)]
266 267
267 268 def getfile(self, name, rev):
268 269 try:
269 270 fctx = self.changectx(rev)[name]
270 271 return fctx.data(), fctx.flags()
271 272 except error.LookupError, err:
272 273 raise IOError(err)
273 274
274 275 def getchanges(self, rev):
275 276 ctx = self.changectx(rev)
276 277 parents = self.parents(ctx)
277 278 if not parents:
278 279 files = sorted(ctx.manifest())
279 280 if self.ignoreerrors:
280 281 # calling getcopies() is a simple way to detect missing
281 282 # revlogs and populate self.ignored
282 283 self.getcopies(ctx, parents, files)
283 284 return [(f, rev) for f in files if f not in self.ignored], {}
284 285 if self._changescache and self._changescache[0] == rev:
285 286 m, a, r = self._changescache[1]
286 287 else:
287 288 m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3]
288 289 # getcopies() detects missing revlogs early, run it before
289 290 # filtering the changes.
290 291 copies = self.getcopies(ctx, parents, m + a)
291 292 changes = [(name, rev) for name in m + a + r
292 293 if name not in self.ignored]
293 294 return sorted(changes), copies
294 295
295 296 def getcopies(self, ctx, parents, files):
296 297 copies = {}
297 298 for name in files:
298 299 if name in self.ignored:
299 300 continue
300 301 try:
301 302 copysource, copynode = ctx.filectx(name).renamed()
302 303 if copysource in self.ignored or not self.keep(copynode):
303 304 continue
304 305 # Ignore copy sources not in parent revisions
305 306 found = False
306 307 for p in parents:
307 308 if copysource in p:
308 309 found = True
309 310 break
310 311 if not found:
311 312 continue
312 313 copies[name] = copysource
313 314 except TypeError:
314 315 pass
315 316 except error.LookupError, e:
316 317 if not self.ignoreerrors:
317 318 raise
318 319 self.ignored.add(name)
319 320 self.ui.warn(_('ignoring: %s\n') % e)
320 321 return copies
321 322
322 323 def getcommit(self, rev):
323 324 ctx = self.changectx(rev)
324 325 parents = [p.hex() for p in self.parents(ctx)]
325 326 if self.saverev:
326 327 crev = rev
327 328 else:
328 329 crev = None
329 330 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
330 331 desc=ctx.description(), rev=crev, parents=parents,
331 332 branch=ctx.branch(), extra=ctx.extra(),
332 333 sortkey=ctx.rev())
333 334
334 335 def gettags(self):
335 336 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
336 337 return dict([(name, hex(node)) for name, node in tags
337 338 if self.keep(node)])
338 339
339 340 def getchangedfiles(self, rev, i):
340 341 ctx = self.changectx(rev)
341 342 parents = self.parents(ctx)
342 343 if not parents and i is None:
343 344 i = 0
344 345 changes = [], ctx.manifest().keys(), []
345 346 else:
346 347 i = i or 0
347 348 changes = self.repo.status(parents[i].node(), ctx.node())[:3]
348 349 changes = [[f for f in l if f not in self.ignored] for l in changes]
349 350
350 351 if i == 0:
351 352 self._changescache = (rev, changes)
352 353
353 354 return changes[0] + changes[1] + changes[2]
354 355
355 356 def converted(self, rev, destrev):
356 357 if self.convertfp is None:
357 358 self.convertfp = open(os.path.join(self.path, '.hg', 'shamap'),
358 359 'a')
359 360 self.convertfp.write('%s %s\n' % (destrev, rev))
360 361 self.convertfp.flush()
361 362
362 363 def before(self):
363 364 self.ui.debug('run hg source pre-conversion action\n')
364 365
365 366 def after(self):
366 367 self.ui.debug('run hg source post-conversion action\n')
367 368
368 369 def hasnativeorder(self):
369 370 return True
370 371
371 372 def lookuprev(self, rev):
372 373 try:
373 374 return hex(self.repo.lookup(rev))
374 375 except error.RepoError:
375 376 return None
@@ -1,88 +1,92 b''
1 1 #!/bin/sh
2 2
3 3 cat >> $HGRCPATH <<EOF
4 4 [extensions]
5 5 convert=
6 6 [convert]
7 7 hg.saverev=False
8 8 EOF
9 9
10 10 hg init orig
11 11 cd orig
12 12
13 13 echo foo > foo
14 14 echo bar > bar
15 15 hg ci -qAm 'add foo bar' -d '0 0'
16 16
17 17 echo >> foo
18 18 hg ci -m 'change foo' -d '1 0'
19 19
20 20 hg up -qC 0
21 21 hg copy --after --force foo bar
22 22 hg copy foo baz
23 23 hg ci -m 'make bar and baz copies of foo' -d '2 0'
24 24
25 25 hg merge
26 26 hg ci -m 'merge local copy' -d '3 0'
27 27
28 28 hg up -C 1
29 29 hg merge 2
30 30 hg ci -m 'merge remote copy' -d '4 0'
31 31
32 32 chmod +x baz
33 33 hg ci -m 'mark baz executable' -d '5 0'
34 34
35 hg branch foo
36 hg ci -m 'branch foo' -d '6 0'
37 hg ci --close-branch -m 'close' -d '7 0'
38
35 39 cd ..
36 40 hg convert --datesort orig new 2>&1 | grep -v 'subversion python bindings could not be loaded'
37 41 cd new
38 42 hg out ../orig
39 43 cd ..
40 44
41 45 echo '% check shamap LF and CRLF handling'
42 46 cat > rewrite.py <<EOF
43 47 import sys
44 48 # Interlace LF and CRLF
45 49 lines = [(l.rstrip() + ((i % 2) and '\n' or '\r\n'))
46 50 for i, l in enumerate(file(sys.argv[1]))]
47 51 file(sys.argv[1], 'wb').write(''.join(lines))
48 52 EOF
49 53 python rewrite.py new/.hg/shamap
50 54 cd orig
51 55 hg up -qC 1
52 56 echo foo >> foo
53 57 hg ci -qm 'change foo again'
54 58 hg up -qC 2
55 59 echo foo >> foo
56 60 hg ci -qm 'change foo again again'
57 61 cd ..
58 62 hg convert --datesort orig new 2>&1 | grep -v 'subversion python bindings could not be loaded'
59 63
60 64 echo % init broken repository
61 65 hg init broken
62 66 cd broken
63 67 echo a >> a
64 68 echo b >> b
65 69 hg ci -qAm init
66 70 echo a >> a
67 71 echo b >> b
68 72 hg copy b c
69 73 hg ci -qAm changeall
70 74 hg up -qC 0
71 75 echo bc >> b
72 76 hg ci -m changebagain
73 77 HGMERGE=internal:local hg -q merge
74 78 hg ci -m merge
75 79 hg mv b d
76 80 hg ci -m moveb
77 81 echo % break it
78 82 rm .hg/store/data/b.*
79 83 cd ..
80 84
81 85 hg --config convert.hg.ignoreerrors=True convert broken fixed
82 86 hg -R fixed verify
83 87 echo '% manifest -r 0'
84 88 hg -R fixed manifest -r 0
85 89 echo '% manifest -r tip'
86 90 hg -R fixed manifest -r tip
87 91
88 92 true
@@ -1,52 +1,55 b''
1 1 created new head
2 2 merging baz and foo to baz
3 3 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
4 4 (branch merge, don't forget to commit)
5 5 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
6 6 merging foo and baz to baz
7 7 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
8 8 (branch merge, don't forget to commit)
9 9 created new head
10 marked working directory as branch foo
10 11 initializing destination new repository
11 12 scanning source...
12 13 sorting...
13 14 converting...
14 5 add foo bar
15 4 change foo
16 3 make bar and baz copies of foo
17 2 merge local copy
18 1 merge remote copy
19 0 mark baz executable
15 7 add foo bar
16 6 change foo
17 5 make bar and baz copies of foo
18 4 merge local copy
19 3 merge remote copy
20 2 mark baz executable
21 1 branch foo
22 0 close
20 23 comparing with ../orig
21 24 searching for changes
22 25 no changes found
23 26 % check shamap LF and CRLF handling
24 27 scanning source...
25 28 sorting...
26 29 converting...
27 30 1 change foo again again
28 31 0 change foo again
29 32 % init broken repository
30 33 created new head
31 34 % break it
32 35 initializing destination fixed repository
33 36 scanning source...
34 37 sorting...
35 38 converting...
36 39 4 init
37 40 ignoring: data/b.i@1e88685f5dde: no match found
38 41 3 changeall
39 42 2 changebagain
40 43 1 merge
41 44 0 moveb
42 45 checking changesets
43 46 checking manifests
44 47 crosschecking files in changesets and manifests
45 48 checking files
46 49 3 files, 5 changesets, 5 total revisions
47 50 % manifest -r 0
48 51 a
49 52 % manifest -r tip
50 53 a
51 54 c
52 55 d
General Comments 0
You need to be logged in to leave comments. Login now