##// END OF EJS Templates
convert: update source shamap when using filemap, just as when not using filemap...
Mads Kiilerich -
r19892:77872b00 default
parent child Browse files
Show More
@@ -1,411 +1,414 b''
1 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
1 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
2 # Copyright 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
2 # Copyright 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
3 #
3 #
4 # This software may be used and distributed according to the terms of the
4 # This software may be used and distributed according to the terms of the
5 # GNU General Public License version 2 or any later version.
5 # GNU General Public License version 2 or any later version.
6
6
7 import posixpath
7 import posixpath
8 import shlex
8 import shlex
9 from mercurial.i18n import _
9 from mercurial.i18n import _
10 from mercurial import util, error
10 from mercurial import util, error
11 from common import SKIPREV, converter_source
11 from common import SKIPREV, converter_source
12
12
13 def rpairs(name):
13 def rpairs(name):
14 e = len(name)
14 e = len(name)
15 while e != -1:
15 while e != -1:
16 yield name[:e], name[e + 1:]
16 yield name[:e], name[e + 1:]
17 e = name.rfind('/', 0, e)
17 e = name.rfind('/', 0, e)
18 yield '.', name
18 yield '.', name
19
19
20 def normalize(path):
20 def normalize(path):
21 ''' We use posixpath.normpath to support cross-platform path format.
21 ''' We use posixpath.normpath to support cross-platform path format.
22 However, it doesn't handle None input. So we wrap it up. '''
22 However, it doesn't handle None input. So we wrap it up. '''
23 if path is None:
23 if path is None:
24 return None
24 return None
25 return posixpath.normpath(path)
25 return posixpath.normpath(path)
26
26
27 class filemapper(object):
27 class filemapper(object):
28 '''Map and filter filenames when importing.
28 '''Map and filter filenames when importing.
29 A name can be mapped to itself, a new name, or None (omit from new
29 A name can be mapped to itself, a new name, or None (omit from new
30 repository).'''
30 repository).'''
31
31
32 def __init__(self, ui, path=None):
32 def __init__(self, ui, path=None):
33 self.ui = ui
33 self.ui = ui
34 self.include = {}
34 self.include = {}
35 self.exclude = {}
35 self.exclude = {}
36 self.rename = {}
36 self.rename = {}
37 if path:
37 if path:
38 if self.parse(path):
38 if self.parse(path):
39 raise util.Abort(_('errors in filemap'))
39 raise util.Abort(_('errors in filemap'))
40
40
41 def parse(self, path):
41 def parse(self, path):
42 errs = 0
42 errs = 0
43 def check(name, mapping, listname):
43 def check(name, mapping, listname):
44 if not name:
44 if not name:
45 self.ui.warn(_('%s:%d: path to %s is missing\n') %
45 self.ui.warn(_('%s:%d: path to %s is missing\n') %
46 (lex.infile, lex.lineno, listname))
46 (lex.infile, lex.lineno, listname))
47 return 1
47 return 1
48 if name in mapping:
48 if name in mapping:
49 self.ui.warn(_('%s:%d: %r already in %s list\n') %
49 self.ui.warn(_('%s:%d: %r already in %s list\n') %
50 (lex.infile, lex.lineno, name, listname))
50 (lex.infile, lex.lineno, name, listname))
51 return 1
51 return 1
52 if (name.startswith('/') or
52 if (name.startswith('/') or
53 name.endswith('/') or
53 name.endswith('/') or
54 '//' in name):
54 '//' in name):
55 self.ui.warn(_('%s:%d: superfluous / in %s %r\n') %
55 self.ui.warn(_('%s:%d: superfluous / in %s %r\n') %
56 (lex.infile, lex.lineno, listname, name))
56 (lex.infile, lex.lineno, listname, name))
57 return 1
57 return 1
58 return 0
58 return 0
59 lex = shlex.shlex(open(path), path, True)
59 lex = shlex.shlex(open(path), path, True)
60 lex.wordchars += '!@#$%^&*()-=+[]{}|;:,./<>?'
60 lex.wordchars += '!@#$%^&*()-=+[]{}|;:,./<>?'
61 cmd = lex.get_token()
61 cmd = lex.get_token()
62 while cmd:
62 while cmd:
63 if cmd == 'include':
63 if cmd == 'include':
64 name = normalize(lex.get_token())
64 name = normalize(lex.get_token())
65 errs += check(name, self.exclude, 'exclude')
65 errs += check(name, self.exclude, 'exclude')
66 self.include[name] = name
66 self.include[name] = name
67 elif cmd == 'exclude':
67 elif cmd == 'exclude':
68 name = normalize(lex.get_token())
68 name = normalize(lex.get_token())
69 errs += check(name, self.include, 'include')
69 errs += check(name, self.include, 'include')
70 errs += check(name, self.rename, 'rename')
70 errs += check(name, self.rename, 'rename')
71 self.exclude[name] = name
71 self.exclude[name] = name
72 elif cmd == 'rename':
72 elif cmd == 'rename':
73 src = normalize(lex.get_token())
73 src = normalize(lex.get_token())
74 dest = normalize(lex.get_token())
74 dest = normalize(lex.get_token())
75 errs += check(src, self.exclude, 'exclude')
75 errs += check(src, self.exclude, 'exclude')
76 self.rename[src] = dest
76 self.rename[src] = dest
77 elif cmd == 'source':
77 elif cmd == 'source':
78 errs += self.parse(normalize(lex.get_token()))
78 errs += self.parse(normalize(lex.get_token()))
79 else:
79 else:
80 self.ui.warn(_('%s:%d: unknown directive %r\n') %
80 self.ui.warn(_('%s:%d: unknown directive %r\n') %
81 (lex.infile, lex.lineno, cmd))
81 (lex.infile, lex.lineno, cmd))
82 errs += 1
82 errs += 1
83 cmd = lex.get_token()
83 cmd = lex.get_token()
84 return errs
84 return errs
85
85
86 def lookup(self, name, mapping):
86 def lookup(self, name, mapping):
87 name = normalize(name)
87 name = normalize(name)
88 for pre, suf in rpairs(name):
88 for pre, suf in rpairs(name):
89 try:
89 try:
90 return mapping[pre], pre, suf
90 return mapping[pre], pre, suf
91 except KeyError:
91 except KeyError:
92 pass
92 pass
93 return '', name, ''
93 return '', name, ''
94
94
95 def __call__(self, name):
95 def __call__(self, name):
96 if self.include:
96 if self.include:
97 inc = self.lookup(name, self.include)[0]
97 inc = self.lookup(name, self.include)[0]
98 else:
98 else:
99 inc = name
99 inc = name
100 if self.exclude:
100 if self.exclude:
101 exc = self.lookup(name, self.exclude)[0]
101 exc = self.lookup(name, self.exclude)[0]
102 else:
102 else:
103 exc = ''
103 exc = ''
104 if (not self.include and exc) or (len(inc) <= len(exc)):
104 if (not self.include and exc) or (len(inc) <= len(exc)):
105 return None
105 return None
106 newpre, pre, suf = self.lookup(name, self.rename)
106 newpre, pre, suf = self.lookup(name, self.rename)
107 if newpre:
107 if newpre:
108 if newpre == '.':
108 if newpre == '.':
109 return suf
109 return suf
110 if suf:
110 if suf:
111 if newpre.endswith('/'):
111 if newpre.endswith('/'):
112 return newpre + suf
112 return newpre + suf
113 return newpre + '/' + suf
113 return newpre + '/' + suf
114 return newpre
114 return newpre
115 return name
115 return name
116
116
117 def active(self):
117 def active(self):
118 return bool(self.include or self.exclude or self.rename)
118 return bool(self.include or self.exclude or self.rename)
119
119
120 # This class does two additional things compared to a regular source:
120 # This class does two additional things compared to a regular source:
121 #
121 #
122 # - Filter and rename files. This is mostly wrapped by the filemapper
122 # - Filter and rename files. This is mostly wrapped by the filemapper
123 # class above. We hide the original filename in the revision that is
123 # class above. We hide the original filename in the revision that is
124 # returned by getchanges to be able to find things later in getfile.
124 # returned by getchanges to be able to find things later in getfile.
125 #
125 #
126 # - Return only revisions that matter for the files we're interested in.
126 # - Return only revisions that matter for the files we're interested in.
127 # This involves rewriting the parents of the original revision to
127 # This involves rewriting the parents of the original revision to
128 # create a graph that is restricted to those revisions.
128 # create a graph that is restricted to those revisions.
129 #
129 #
130 # This set of revisions includes not only revisions that directly
130 # This set of revisions includes not only revisions that directly
131 # touch files we're interested in, but also merges that merge two
131 # touch files we're interested in, but also merges that merge two
132 # or more interesting revisions.
132 # or more interesting revisions.
133
133
134 class filemap_source(converter_source):
134 class filemap_source(converter_source):
135 def __init__(self, ui, baseconverter, filemap):
135 def __init__(self, ui, baseconverter, filemap):
136 super(filemap_source, self).__init__(ui)
136 super(filemap_source, self).__init__(ui)
137 self.base = baseconverter
137 self.base = baseconverter
138 self.filemapper = filemapper(ui, filemap)
138 self.filemapper = filemapper(ui, filemap)
139 self.commits = {}
139 self.commits = {}
140 # if a revision rev has parent p in the original revision graph, then
140 # if a revision rev has parent p in the original revision graph, then
141 # rev will have parent self.parentmap[p] in the restricted graph.
141 # rev will have parent self.parentmap[p] in the restricted graph.
142 self.parentmap = {}
142 self.parentmap = {}
143 # self.wantedancestors[rev] is the set of all ancestors of rev that
143 # self.wantedancestors[rev] is the set of all ancestors of rev that
144 # are in the restricted graph.
144 # are in the restricted graph.
145 self.wantedancestors = {}
145 self.wantedancestors = {}
146 self.convertedorder = None
146 self.convertedorder = None
147 self._rebuilt = False
147 self._rebuilt = False
148 self.origparents = {}
148 self.origparents = {}
149 self.children = {}
149 self.children = {}
150 self.seenchildren = {}
150 self.seenchildren = {}
151
151
152 def before(self):
152 def before(self):
153 self.base.before()
153 self.base.before()
154
154
155 def after(self):
155 def after(self):
156 self.base.after()
156 self.base.after()
157
157
158 def setrevmap(self, revmap):
158 def setrevmap(self, revmap):
159 # rebuild our state to make things restartable
159 # rebuild our state to make things restartable
160 #
160 #
161 # To avoid calling getcommit for every revision that has already
161 # To avoid calling getcommit for every revision that has already
162 # been converted, we rebuild only the parentmap, delaying the
162 # been converted, we rebuild only the parentmap, delaying the
163 # rebuild of wantedancestors until we need it (i.e. until a
163 # rebuild of wantedancestors until we need it (i.e. until a
164 # merge).
164 # merge).
165 #
165 #
166 # We assume the order argument lists the revisions in
166 # We assume the order argument lists the revisions in
167 # topological order, so that we can infer which revisions were
167 # topological order, so that we can infer which revisions were
168 # wanted by previous runs.
168 # wanted by previous runs.
169 self._rebuilt = not revmap
169 self._rebuilt = not revmap
170 seen = {SKIPREV: SKIPREV}
170 seen = {SKIPREV: SKIPREV}
171 dummyset = set()
171 dummyset = set()
172 converted = []
172 converted = []
173 for rev in revmap.order:
173 for rev in revmap.order:
174 mapped = revmap[rev]
174 mapped = revmap[rev]
175 wanted = mapped not in seen
175 wanted = mapped not in seen
176 if wanted:
176 if wanted:
177 seen[mapped] = rev
177 seen[mapped] = rev
178 self.parentmap[rev] = rev
178 self.parentmap[rev] = rev
179 else:
179 else:
180 self.parentmap[rev] = seen[mapped]
180 self.parentmap[rev] = seen[mapped]
181 self.wantedancestors[rev] = dummyset
181 self.wantedancestors[rev] = dummyset
182 arg = seen[mapped]
182 arg = seen[mapped]
183 if arg == SKIPREV:
183 if arg == SKIPREV:
184 arg = None
184 arg = None
185 converted.append((rev, wanted, arg))
185 converted.append((rev, wanted, arg))
186 self.convertedorder = converted
186 self.convertedorder = converted
187 return self.base.setrevmap(revmap)
187 return self.base.setrevmap(revmap)
188
188
189 def rebuild(self):
189 def rebuild(self):
190 if self._rebuilt:
190 if self._rebuilt:
191 return True
191 return True
192 self._rebuilt = True
192 self._rebuilt = True
193 self.parentmap.clear()
193 self.parentmap.clear()
194 self.wantedancestors.clear()
194 self.wantedancestors.clear()
195 self.seenchildren.clear()
195 self.seenchildren.clear()
196 for rev, wanted, arg in self.convertedorder:
196 for rev, wanted, arg in self.convertedorder:
197 if rev not in self.origparents:
197 if rev not in self.origparents:
198 try:
198 try:
199 self.origparents[rev] = self.getcommit(rev).parents
199 self.origparents[rev] = self.getcommit(rev).parents
200 except error.RepoLookupError:
200 except error.RepoLookupError:
201 self.ui.debug("unknown revmap source: %s\n" % rev)
201 self.ui.debug("unknown revmap source: %s\n" % rev)
202 continue
202 continue
203 if arg is not None:
203 if arg is not None:
204 self.children[arg] = self.children.get(arg, 0) + 1
204 self.children[arg] = self.children.get(arg, 0) + 1
205
205
206 for rev, wanted, arg in self.convertedorder:
206 for rev, wanted, arg in self.convertedorder:
207 try:
207 try:
208 parents = self.origparents[rev]
208 parents = self.origparents[rev]
209 except KeyError:
209 except KeyError:
210 continue # unknown revmap source
210 continue # unknown revmap source
211 if wanted:
211 if wanted:
212 self.mark_wanted(rev, parents)
212 self.mark_wanted(rev, parents)
213 else:
213 else:
214 self.mark_not_wanted(rev, arg)
214 self.mark_not_wanted(rev, arg)
215 self._discard(arg, *parents)
215 self._discard(arg, *parents)
216
216
217 return True
217 return True
218
218
219 def getheads(self):
219 def getheads(self):
220 return self.base.getheads()
220 return self.base.getheads()
221
221
222 def getcommit(self, rev):
222 def getcommit(self, rev):
223 # We want to save a reference to the commit objects to be able
223 # We want to save a reference to the commit objects to be able
224 # to rewrite their parents later on.
224 # to rewrite their parents later on.
225 c = self.commits[rev] = self.base.getcommit(rev)
225 c = self.commits[rev] = self.base.getcommit(rev)
226 for p in c.parents:
226 for p in c.parents:
227 self.children[p] = self.children.get(p, 0) + 1
227 self.children[p] = self.children.get(p, 0) + 1
228 return c
228 return c
229
229
230 def _cachedcommit(self, rev):
230 def _cachedcommit(self, rev):
231 if rev in self.commits:
231 if rev in self.commits:
232 return self.commits[rev]
232 return self.commits[rev]
233 return self.base.getcommit(rev)
233 return self.base.getcommit(rev)
234
234
235 def _discard(self, *revs):
235 def _discard(self, *revs):
236 for r in revs:
236 for r in revs:
237 if r is None:
237 if r is None:
238 continue
238 continue
239 self.seenchildren[r] = self.seenchildren.get(r, 0) + 1
239 self.seenchildren[r] = self.seenchildren.get(r, 0) + 1
240 if self.seenchildren[r] == self.children[r]:
240 if self.seenchildren[r] == self.children[r]:
241 self.wantedancestors.pop(r, None)
241 self.wantedancestors.pop(r, None)
242 self.parentmap.pop(r, None)
242 self.parentmap.pop(r, None)
243 del self.seenchildren[r]
243 del self.seenchildren[r]
244 if self._rebuilt:
244 if self._rebuilt:
245 del self.children[r]
245 del self.children[r]
246
246
247 def wanted(self, rev, i):
247 def wanted(self, rev, i):
248 # Return True if we're directly interested in rev.
248 # Return True if we're directly interested in rev.
249 #
249 #
250 # i is an index selecting one of the parents of rev (if rev
250 # i is an index selecting one of the parents of rev (if rev
251 # has no parents, i is None). getchangedfiles will give us
251 # has no parents, i is None). getchangedfiles will give us
252 # the list of files that are different in rev and in the parent
252 # the list of files that are different in rev and in the parent
253 # indicated by i. If we're interested in any of these files,
253 # indicated by i. If we're interested in any of these files,
254 # we're interested in rev.
254 # we're interested in rev.
255 try:
255 try:
256 files = self.base.getchangedfiles(rev, i)
256 files = self.base.getchangedfiles(rev, i)
257 except NotImplementedError:
257 except NotImplementedError:
258 raise util.Abort(_("source repository doesn't support --filemap"))
258 raise util.Abort(_("source repository doesn't support --filemap"))
259 for f in files:
259 for f in files:
260 if self.filemapper(f):
260 if self.filemapper(f):
261 return True
261 return True
262 return False
262 return False
263
263
264 def mark_not_wanted(self, rev, p):
264 def mark_not_wanted(self, rev, p):
265 # Mark rev as not interesting and update data structures.
265 # Mark rev as not interesting and update data structures.
266
266
267 if p is None:
267 if p is None:
268 # A root revision. Use SKIPREV to indicate that it doesn't
268 # A root revision. Use SKIPREV to indicate that it doesn't
269 # map to any revision in the restricted graph. Put SKIPREV
269 # map to any revision in the restricted graph. Put SKIPREV
270 # in the set of wanted ancestors to simplify code elsewhere
270 # in the set of wanted ancestors to simplify code elsewhere
271 self.parentmap[rev] = SKIPREV
271 self.parentmap[rev] = SKIPREV
272 self.wantedancestors[rev] = set((SKIPREV,))
272 self.wantedancestors[rev] = set((SKIPREV,))
273 return
273 return
274
274
275 # Reuse the data from our parent.
275 # Reuse the data from our parent.
276 self.parentmap[rev] = self.parentmap[p]
276 self.parentmap[rev] = self.parentmap[p]
277 self.wantedancestors[rev] = self.wantedancestors[p]
277 self.wantedancestors[rev] = self.wantedancestors[p]
278
278
279 def mark_wanted(self, rev, parents):
279 def mark_wanted(self, rev, parents):
280 # Mark rev ss wanted and update data structures.
280 # Mark rev ss wanted and update data structures.
281
281
282 # rev will be in the restricted graph, so children of rev in
282 # rev will be in the restricted graph, so children of rev in
283 # the original graph should still have rev as a parent in the
283 # the original graph should still have rev as a parent in the
284 # restricted graph.
284 # restricted graph.
285 self.parentmap[rev] = rev
285 self.parentmap[rev] = rev
286
286
287 # The set of wanted ancestors of rev is the union of the sets
287 # The set of wanted ancestors of rev is the union of the sets
288 # of wanted ancestors of its parents. Plus rev itself.
288 # of wanted ancestors of its parents. Plus rev itself.
289 wrev = set()
289 wrev = set()
290 for p in parents:
290 for p in parents:
291 if p in self.wantedancestors:
291 if p in self.wantedancestors:
292 wrev.update(self.wantedancestors[p])
292 wrev.update(self.wantedancestors[p])
293 else:
293 else:
294 self.ui.warn(_('warning: %s parent %s is missing\n') %
294 self.ui.warn(_('warning: %s parent %s is missing\n') %
295 (rev, p))
295 (rev, p))
296 wrev.add(rev)
296 wrev.add(rev)
297 self.wantedancestors[rev] = wrev
297 self.wantedancestors[rev] = wrev
298
298
299 def getchanges(self, rev):
299 def getchanges(self, rev):
300 parents = self.commits[rev].parents
300 parents = self.commits[rev].parents
301 if len(parents) > 1:
301 if len(parents) > 1:
302 self.rebuild()
302 self.rebuild()
303
303
304 # To decide whether we're interested in rev we:
304 # To decide whether we're interested in rev we:
305 #
305 #
306 # - calculate what parents rev will have if it turns out we're
306 # - calculate what parents rev will have if it turns out we're
307 # interested in it. If it's going to have more than 1 parent,
307 # interested in it. If it's going to have more than 1 parent,
308 # we're interested in it.
308 # we're interested in it.
309 #
309 #
310 # - otherwise, we'll compare it with the single parent we found.
310 # - otherwise, we'll compare it with the single parent we found.
311 # If any of the files we're interested in is different in the
311 # If any of the files we're interested in is different in the
312 # the two revisions, we're interested in rev.
312 # the two revisions, we're interested in rev.
313
313
314 # A parent p is interesting if its mapped version (self.parentmap[p]):
314 # A parent p is interesting if its mapped version (self.parentmap[p]):
315 # - is not SKIPREV
315 # - is not SKIPREV
316 # - is still not in the list of parents (we don't want duplicates)
316 # - is still not in the list of parents (we don't want duplicates)
317 # - is not an ancestor of the mapped versions of the other parents or
317 # - is not an ancestor of the mapped versions of the other parents or
318 # there is no parent in the same branch than the current revision.
318 # there is no parent in the same branch than the current revision.
319 mparents = []
319 mparents = []
320 knownparents = set()
320 knownparents = set()
321 branch = self.commits[rev].branch
321 branch = self.commits[rev].branch
322 hasbranchparent = False
322 hasbranchparent = False
323 for i, p1 in enumerate(parents):
323 for i, p1 in enumerate(parents):
324 mp1 = self.parentmap[p1]
324 mp1 = self.parentmap[p1]
325 if mp1 == SKIPREV or mp1 in knownparents:
325 if mp1 == SKIPREV or mp1 in knownparents:
326 continue
326 continue
327 isancestor = util.any(p2 for p2 in parents
327 isancestor = util.any(p2 for p2 in parents
328 if p1 != p2 and mp1 != self.parentmap[p2]
328 if p1 != p2 and mp1 != self.parentmap[p2]
329 and mp1 in self.wantedancestors[p2])
329 and mp1 in self.wantedancestors[p2])
330 if not isancestor and not hasbranchparent and len(parents) > 1:
330 if not isancestor and not hasbranchparent and len(parents) > 1:
331 # This could be expensive, avoid unnecessary calls.
331 # This could be expensive, avoid unnecessary calls.
332 if self._cachedcommit(p1).branch == branch:
332 if self._cachedcommit(p1).branch == branch:
333 hasbranchparent = True
333 hasbranchparent = True
334 mparents.append((p1, mp1, i, isancestor))
334 mparents.append((p1, mp1, i, isancestor))
335 knownparents.add(mp1)
335 knownparents.add(mp1)
336 # Discard parents ancestors of other parents if there is a
336 # Discard parents ancestors of other parents if there is a
337 # non-ancestor one on the same branch than current revision.
337 # non-ancestor one on the same branch than current revision.
338 if hasbranchparent:
338 if hasbranchparent:
339 mparents = [p for p in mparents if not p[3]]
339 mparents = [p for p in mparents if not p[3]]
340 wp = None
340 wp = None
341 if mparents:
341 if mparents:
342 wp = max(p[2] for p in mparents)
342 wp = max(p[2] for p in mparents)
343 mparents = [p[1] for p in mparents]
343 mparents = [p[1] for p in mparents]
344 elif parents:
344 elif parents:
345 wp = 0
345 wp = 0
346
346
347 self.origparents[rev] = parents
347 self.origparents[rev] = parents
348
348
349 closed = False
349 closed = False
350 if 'close' in self.commits[rev].extra:
350 if 'close' in self.commits[rev].extra:
351 # A branch closing revision is only useful if one of its
351 # A branch closing revision is only useful if one of its
352 # parents belong to the branch being closed
352 # parents belong to the branch being closed
353 pbranches = [self._cachedcommit(p).branch for p in mparents]
353 pbranches = [self._cachedcommit(p).branch for p in mparents]
354 if branch in pbranches:
354 if branch in pbranches:
355 closed = True
355 closed = True
356
356
357 if len(mparents) < 2 and not closed and not self.wanted(rev, wp):
357 if len(mparents) < 2 and not closed and not self.wanted(rev, wp):
358 # We don't want this revision.
358 # We don't want this revision.
359 # Update our state and tell the convert process to map this
359 # Update our state and tell the convert process to map this
360 # revision to the same revision its parent as mapped to.
360 # revision to the same revision its parent as mapped to.
361 p = None
361 p = None
362 if parents:
362 if parents:
363 p = parents[wp]
363 p = parents[wp]
364 self.mark_not_wanted(rev, p)
364 self.mark_not_wanted(rev, p)
365 self.convertedorder.append((rev, False, p))
365 self.convertedorder.append((rev, False, p))
366 self._discard(*parents)
366 self._discard(*parents)
367 return self.parentmap[rev]
367 return self.parentmap[rev]
368
368
369 # We want this revision.
369 # We want this revision.
370 # Rewrite the parents of the commit object
370 # Rewrite the parents of the commit object
371 self.commits[rev].parents = mparents
371 self.commits[rev].parents = mparents
372 self.mark_wanted(rev, parents)
372 self.mark_wanted(rev, parents)
373 self.convertedorder.append((rev, True, None))
373 self.convertedorder.append((rev, True, None))
374 self._discard(*parents)
374 self._discard(*parents)
375
375
376 # Get the real changes and do the filtering/mapping. To be
376 # Get the real changes and do the filtering/mapping. To be
377 # able to get the files later on in getfile, we hide the
377 # able to get the files later on in getfile, we hide the
378 # original filename in the rev part of the return value.
378 # original filename in the rev part of the return value.
379 changes, copies = self.base.getchanges(rev)
379 changes, copies = self.base.getchanges(rev)
380 files = {}
380 files = {}
381 for f, r in changes:
381 for f, r in changes:
382 newf = self.filemapper(f)
382 newf = self.filemapper(f)
383 if newf and (newf != f or newf not in files):
383 if newf and (newf != f or newf not in files):
384 files[newf] = (f, r)
384 files[newf] = (f, r)
385 files = sorted(files.items())
385 files = sorted(files.items())
386
386
387 ncopies = {}
387 ncopies = {}
388 for c in copies:
388 for c in copies:
389 newc = self.filemapper(c)
389 newc = self.filemapper(c)
390 if newc:
390 if newc:
391 newsource = self.filemapper(copies[c])
391 newsource = self.filemapper(copies[c])
392 if newsource:
392 if newsource:
393 ncopies[newc] = newsource
393 ncopies[newc] = newsource
394
394
395 return files, ncopies
395 return files, ncopies
396
396
397 def getfile(self, name, rev):
397 def getfile(self, name, rev):
398 realname, realrev = rev
398 realname, realrev = rev
399 return self.base.getfile(realname, realrev)
399 return self.base.getfile(realname, realrev)
400
400
401 def gettags(self):
401 def gettags(self):
402 return self.base.gettags()
402 return self.base.gettags()
403
403
404 def hasnativeorder(self):
404 def hasnativeorder(self):
405 return self.base.hasnativeorder()
405 return self.base.hasnativeorder()
406
406
407 def lookuprev(self, rev):
407 def lookuprev(self, rev):
408 return self.base.lookuprev(rev)
408 return self.base.lookuprev(rev)
409
409
410 def getbookmarks(self):
410 def getbookmarks(self):
411 return self.base.getbookmarks()
411 return self.base.getbookmarks()
412
413 def converted(self, rev, sinkrev):
414 self.base.converted(rev, sinkrev)
@@ -1,124 +1,392 b''
1
1
2 $ cat >> $HGRCPATH <<EOF
2 $ cat >> $HGRCPATH <<EOF
3 > [extensions]
3 > [extensions]
4 > convert=
4 > convert=
5 > [convert]
5 > [convert]
6 > hg.saverev=False
6 > hg.saverev=False
7 > EOF
7 > EOF
8 $ hg init orig
8 $ hg init orig
9 $ cd orig
9 $ cd orig
10 $ echo foo > foo
10 $ echo foo > foo
11 $ echo bar > bar
11 $ echo bar > bar
12 $ hg ci -qAm 'add foo and bar'
12 $ hg ci -qAm 'add foo and bar'
13 $ hg rm foo
13 $ hg rm foo
14 $ hg ci -m 'remove foo'
14 $ hg ci -m 'remove foo'
15 $ mkdir foo
15 $ mkdir foo
16 $ echo file > foo/file
16 $ echo file > foo/file
17 $ hg ci -qAm 'add foo/file'
17 $ hg ci -qAm 'add foo/file'
18 $ hg tag some-tag
18 $ hg tag some-tag
19 $ hg log
19 $ hg log
20 changeset: 3:593cbf6fb2b4
20 changeset: 3:593cbf6fb2b4
21 tag: tip
21 tag: tip
22 user: test
22 user: test
23 date: Thu Jan 01 00:00:00 1970 +0000
23 date: Thu Jan 01 00:00:00 1970 +0000
24 summary: Added tag some-tag for changeset ad681a868e44
24 summary: Added tag some-tag for changeset ad681a868e44
25
25
26 changeset: 2:ad681a868e44
26 changeset: 2:ad681a868e44
27 tag: some-tag
27 tag: some-tag
28 user: test
28 user: test
29 date: Thu Jan 01 00:00:00 1970 +0000
29 date: Thu Jan 01 00:00:00 1970 +0000
30 summary: add foo/file
30 summary: add foo/file
31
31
32 changeset: 1:cbba8ecc03b7
32 changeset: 1:cbba8ecc03b7
33 user: test
33 user: test
34 date: Thu Jan 01 00:00:00 1970 +0000
34 date: Thu Jan 01 00:00:00 1970 +0000
35 summary: remove foo
35 summary: remove foo
36
36
37 changeset: 0:327daa9251fa
37 changeset: 0:327daa9251fa
38 user: test
38 user: test
39 date: Thu Jan 01 00:00:00 1970 +0000
39 date: Thu Jan 01 00:00:00 1970 +0000
40 summary: add foo and bar
40 summary: add foo and bar
41
41
42 $ cd ..
42 $ cd ..
43 $ hg convert orig new 2>&1 | grep -v 'subversion python bindings could not be loaded'
43 $ hg convert orig new 2>&1 | grep -v 'subversion python bindings could not be loaded'
44 initializing destination new repository
44 initializing destination new repository
45 scanning source...
45 scanning source...
46 sorting...
46 sorting...
47 converting...
47 converting...
48 3 add foo and bar
48 3 add foo and bar
49 2 remove foo
49 2 remove foo
50 1 add foo/file
50 1 add foo/file
51 0 Added tag some-tag for changeset ad681a868e44
51 0 Added tag some-tag for changeset ad681a868e44
52 $ cd new
52 $ cd new
53 $ hg out ../orig
53 $ hg out ../orig
54 comparing with ../orig
54 comparing with ../orig
55 searching for changes
55 searching for changes
56 no changes found
56 no changes found
57 [1]
57 [1]
58
58
59 dirstate should be empty:
59 dirstate should be empty:
60
60
61 $ hg debugstate
61 $ hg debugstate
62 $ hg parents -q
62 $ hg parents -q
63 $ hg up -C
63 $ hg up -C
64 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
64 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
65 $ hg copy bar baz
65 $ hg copy bar baz
66
66
67 put something in the dirstate:
67 put something in the dirstate:
68
68
69 $ hg debugstate > debugstate
69 $ hg debugstate > debugstate
70 $ grep baz debugstate
70 $ grep baz debugstate
71 a 0 -1 unset baz
71 a 0 -1 unset baz
72 copy: bar -> baz
72 copy: bar -> baz
73
73
74 add a new revision in the original repo
74 add a new revision in the original repo
75
75
76 $ cd ../orig
76 $ cd ../orig
77 $ echo baz > baz
77 $ echo baz > baz
78 $ hg ci -qAm 'add baz'
78 $ hg ci -qAm 'add baz'
79 $ cd ..
79 $ cd ..
80 $ hg convert orig new 2>&1 | grep -v 'subversion python bindings could not be loaded'
80 $ hg convert orig new 2>&1 | grep -v 'subversion python bindings could not be loaded'
81 scanning source...
81 scanning source...
82 sorting...
82 sorting...
83 converting...
83 converting...
84 0 add baz
84 0 add baz
85 $ cd new
85 $ cd new
86 $ hg out ../orig
86 $ hg out ../orig
87 comparing with ../orig
87 comparing with ../orig
88 searching for changes
88 searching for changes
89 no changes found
89 no changes found
90 [1]
90 [1]
91
91
92 dirstate should be the same (no output below):
92 dirstate should be the same (no output below):
93
93
94 $ hg debugstate > new-debugstate
94 $ hg debugstate > new-debugstate
95 $ diff debugstate new-debugstate
95 $ diff debugstate new-debugstate
96
96
97 no copies
97 no copies
98
98
99 $ hg up -C
99 $ hg up -C
100 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
100 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
101 $ hg debugrename baz
101 $ hg debugrename baz
102 baz not renamed
102 baz not renamed
103 $ cd ..
103 $ cd ..
104
104
105 test tag rewriting
105 test tag rewriting
106
106
107 $ cat > filemap <<EOF
107 $ cat > filemap <<EOF
108 > exclude foo
108 > exclude foo
109 > EOF
109 > EOF
110 $ hg convert --filemap filemap orig new-filemap 2>&1 | grep -v 'subversion python bindings could not be loaded'
110 $ hg convert --filemap filemap orig new-filemap 2>&1 | grep -v 'subversion python bindings could not be loaded'
111 initializing destination new-filemap repository
111 initializing destination new-filemap repository
112 scanning source...
112 scanning source...
113 sorting...
113 sorting...
114 converting...
114 converting...
115 4 add foo and bar
115 4 add foo and bar
116 3 remove foo
116 3 remove foo
117 2 add foo/file
117 2 add foo/file
118 1 Added tag some-tag for changeset ad681a868e44
118 1 Added tag some-tag for changeset ad681a868e44
119 0 add baz
119 0 add baz
120 $ cd new-filemap
120 $ cd new-filemap
121 $ hg tags
121 $ hg tags
122 tip 2:6f4fd1df87fb
122 tip 2:6f4fd1df87fb
123 some-tag 0:ba8636729451
123 some-tag 0:ba8636729451
124 $ cd ..
124 $ cd ..
125
126
127 Test cases for hg-hg roundtrip
128
129 Helper
130
131 $ glog()
132 > {
133 > hg log -G --template '{rev} {node|short} "{desc}" files: {files}\n' $*
134 > }
135
136 Create a tricky source repo
137
138 $ hg init source
139 $ cd source
140
141 $ echo 0 > 0
142 $ hg ci -Aqm '0: add 0'
143 $ echo a > a
144 $ mkdir dir
145 $ echo b > dir/b
146 $ hg ci -qAm '1: add a and dir/b'
147 $ echo c > dir/c
148 $ hg ci -qAm '2: add dir/c'
149 $ hg copy a e
150 $ echo b >> b
151 $ hg ci -qAm '3: copy a to e, change b'
152 $ hg up -qr -3
153 $ echo a >> a
154 $ hg ci -qAm '4: change a'
155 $ hg merge
156 merging a and e to e
157 2 files updated, 1 files merged, 0 files removed, 0 files unresolved
158 (branch merge, don't forget to commit)
159 $ hg copy b dir/d
160 $ hg ci -qAm '5: merge 2 and 3, copy b to dir/d'
161 $ echo a >> a
162 $ hg ci -qAm '6: change a'
163
164 $ hg mani
165 0
166 a
167 b
168 dir/b
169 dir/c
170 dir/d
171 e
172 $ glog
173 @ 6 0613c8e59a3d "6: change a" files: a
174 |
175 o 5 717e9b37cdb7 "5: merge 2 and 3, copy b to dir/d" files: dir/d e
176 |\
177 | o 4 86a55cb968d5 "4: change a" files: a
178 | |
179 o | 3 0e6e235919dd "3: copy a to e, change b" files: b e
180 | |
181 o | 2 0394b0d5e4f7 "2: add dir/c" files: dir/c
182 |/
183 o 1 333546584845 "1: add a and dir/b" files: a dir/b
184 |
185 o 0 d1a24e2ebd23 "0: add 0" files: 0
186
187 $ cd ..
188
189 Convert excluding rev 0 and dir/ (and thus rev2):
190
191 $ cat << EOF > filemap
192 > exclude dir
193 > EOF
194
195 $ hg convert --filemap filemap source dest --config convert.hg.revs=1::
196 initializing destination dest repository
197 scanning source...
198 sorting...
199 converting...
200 5 1: add a and dir/b
201 4 2: add dir/c
202 3 3: copy a to e, change b
203 2 4: change a
204 1 5: merge 2 and 3, copy b to dir/d
205 0 6: change a
206
207 Verify that conversion skipped rev 2:
208
209 $ glog -R dest
210 o 4 78814e84a217 "6: change a" files: a
211 |
212 o 3 f7cff662c5e5 "5: merge 2 and 3, copy b to dir/d" files: e
213 |\
214 | o 2 ab40a95b0072 "4: change a" files: a
215 | |
216 o | 1 bd51f17597bf "3: copy a to e, change b" files: b e
217 |/
218 o 0 a4a1dae0fe35 "1: add a and dir/b" files: 0 a
219
220
221 Verify mapping correct in both directions:
222
223 $ cat source/.hg/shamap
224 a4a1dae0fe3514cefd9b8541b7abbc8f44f946d5 333546584845f70c4cfecb992341aaef0e708166
225 bd51f17597bf32268e68a560b206898c3960cda2 0e6e235919dd8e9285ba8eb5adf703af9ad99378
226 ab40a95b00725307e79c2fd271000aa8af9759f4 86a55cb968d51770cba2a1630d6cc637b574580a
227 f7cff662c5e581e6f3f1a85ffdd2bcb35825f6ba 717e9b37cdb7eb9917ca8e30aa3f986e6d5b177d
228 78814e84a217894517c2de392b903ed05e6871a4 0613c8e59a3ddb9789072ef52f1ed13496489bb4
229 $ cat dest/.hg/shamap
230 333546584845f70c4cfecb992341aaef0e708166 a4a1dae0fe3514cefd9b8541b7abbc8f44f946d5
231 0394b0d5e4f761ced559fd0bbdc6afc16cb3f7d1 a4a1dae0fe3514cefd9b8541b7abbc8f44f946d5
232 0e6e235919dd8e9285ba8eb5adf703af9ad99378 bd51f17597bf32268e68a560b206898c3960cda2
233 86a55cb968d51770cba2a1630d6cc637b574580a ab40a95b00725307e79c2fd271000aa8af9759f4
234 717e9b37cdb7eb9917ca8e30aa3f986e6d5b177d f7cff662c5e581e6f3f1a85ffdd2bcb35825f6ba
235 0613c8e59a3ddb9789072ef52f1ed13496489bb4 78814e84a217894517c2de392b903ed05e6871a4
236
237 Verify meta data converted correctly:
238
239 $ hg -R dest log -r 1 --debug -p --git
240 changeset: 1:bd51f17597bf32268e68a560b206898c3960cda2
241 phase: draft
242 parent: 0:a4a1dae0fe3514cefd9b8541b7abbc8f44f946d5
243 parent: -1:0000000000000000000000000000000000000000
244 manifest: 1:040c72ed9b101773c24ac314776bfc846943781f
245 user: test
246 date: Thu Jan 01 00:00:00 1970 +0000
247 files+: b e
248 extra: branch=default
249 description:
250 3: copy a to e, change b
251
252
253 diff --git a/b b/b
254 new file mode 100644
255 --- /dev/null
256 +++ b/b
257 @@ -0,0 +1,1 @@
258 +b
259 diff --git a/a b/e
260 copy from a
261 copy to e
262
263 Verify files included and excluded correctly:
264
265 $ hg -R dest manifest -r tip
266 0
267 a
268 b
269 e
270
271
272 Make changes in dest and convert back:
273
274 $ hg -R dest up -q
275 $ echo dest > dest/dest
276 $ hg -R dest ci -Aqm 'change in dest'
277 $ hg -R dest tip
278 changeset: 5:a2e0e3cc6d1d
279 tag: tip
280 user: test
281 date: Thu Jan 01 00:00:00 1970 +0000
282 summary: change in dest
283
284
285 (converting merges back after using a filemap will probably cause chaos so we
286 exclude merges.)
287
288 $ hg convert dest source --config convert.hg.revs='!merge()'
289 scanning source...
290 sorting...
291 converting...
292 0 change in dest
293
294 Verify the conversion back:
295
296 $ hg -R source log --debug -r tip
297 changeset: 7:e6d364a69ff1248b2099e603b0c145504cade6f0
298 tag: tip
299 phase: draft
300 parent: 6:0613c8e59a3ddb9789072ef52f1ed13496489bb4
301 parent: -1:0000000000000000000000000000000000000000
302 manifest: 7:aa3e9542f3b76d4f1f1b2e9c7ce9dbb48b6a95ec
303 user: test
304 date: Thu Jan 01 00:00:00 1970 +0000
305 files+: dest
306 extra: branch=default
307 description:
308 change in dest
309
310
311 Files that had been excluded are still present:
312
313 $ hg -R source manifest -r tip
314 0
315 a
316 b
317 dest
318 dir/b
319 dir/c
320 dir/d
321 e
322
323 More source changes
324
325 $ cd source
326 $ echo 1 >> a
327 $ hg ci -m '8: source first branch'
328 created new head
329 $ hg up -qr -2
330 $ echo 2 >> a
331 $ hg ci -m '9: source second branch'
332 $ hg merge -q --tool internal:local
333 $ hg ci -m '10: source merge'
334 $ echo >> a
335 $ hg ci -m '11: source change'
336
337 $ hg mani
338 0
339 a
340 b
341 dest
342 dir/b
343 dir/c
344 dir/d
345 e
346
347 $ glog -r 6:
348 @ 11 0c8927d1f7f4 "11: source change" files: a
349 |
350 o 10 9ccb7ee8d261 "10: source merge" files: a
351 |\
352 | o 9 f131b1518dba "9: source second branch" files: a
353 | |
354 o | 8 669cf0e74b50 "8: source first branch" files: a
355 | |
356 | o 7 e6d364a69ff1 "change in dest" files: dest
357 |/
358 o 6 0613c8e59a3d "6: change a" files: a
359 |
360 $ cd ..
361
362 $ hg convert --filemap filemap source dest --config convert.hg.revs=3:
363 scanning source...
364 sorting...
365 converting...
366 3 8: source first branch
367 2 9: source second branch
368 1 10: source merge
369 0 11: source change
370
371 $ glog -R dest
372 o 9 8432d597b263 "11: source change" files: a
373 |
374 o 8 632ffacdcd6f "10: source merge" files: a
375 |\
376 | o 7 049cfee90ee6 "9: source second branch" files: a
377 | |
378 o | 6 9b6845e036e5 "8: source first branch" files: a
379 | |
380 | @ 5 a2e0e3cc6d1d "change in dest" files: dest
381 |/
382 o 4 78814e84a217 "6: change a" files: a
383 |
384 o 3 f7cff662c5e5 "5: merge 2 and 3, copy b to dir/d" files: e
385 |\
386 | o 2 ab40a95b0072 "4: change a" files: a
387 | |
388 o | 1 bd51f17597bf "3: copy a to e, change b" files: b e
389 |/
390 o 0 a4a1dae0fe35 "1: add a and dir/b" files: 0 a
391
392 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now