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