##// END OF EJS Templates
rebase: clean up rebasestate from active transaction...
Jun Wu -
r33056:2312e70c default
parent child Browse files
Show More
@@ -1,1528 +1,1532
1 # rebase.py - rebasing feature for mercurial
1 # rebase.py - rebasing feature for mercurial
2 #
2 #
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
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 '''command to move sets of revisions to a different ancestor
8 '''command to move sets of revisions to a different ancestor
9
9
10 This extension lets you rebase changesets in an existing Mercurial
10 This extension lets you rebase changesets in an existing Mercurial
11 repository.
11 repository.
12
12
13 For more information:
13 For more information:
14 https://mercurial-scm.org/wiki/RebaseExtension
14 https://mercurial-scm.org/wiki/RebaseExtension
15 '''
15 '''
16
16
17 from __future__ import absolute_import
17 from __future__ import absolute_import
18
18
19 import errno
19 import errno
20 import os
20 import os
21
21
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23 from mercurial.node import (
23 from mercurial.node import (
24 hex,
24 hex,
25 nullid,
25 nullid,
26 nullrev,
26 nullrev,
27 short,
27 short,
28 )
28 )
29 from mercurial import (
29 from mercurial import (
30 bookmarks,
30 bookmarks,
31 cmdutil,
31 cmdutil,
32 commands,
32 commands,
33 copies,
33 copies,
34 destutil,
34 destutil,
35 dirstateguard,
35 dirstateguard,
36 error,
36 error,
37 extensions,
37 extensions,
38 hg,
38 hg,
39 lock,
39 lock,
40 merge as mergemod,
40 merge as mergemod,
41 mergeutil,
41 mergeutil,
42 obsolete,
42 obsolete,
43 patch,
43 patch,
44 phases,
44 phases,
45 registrar,
45 registrar,
46 repair,
46 repair,
47 repoview,
47 repoview,
48 revset,
48 revset,
49 scmutil,
49 scmutil,
50 smartset,
50 smartset,
51 util,
51 util,
52 )
52 )
53
53
54 release = lock.release
54 release = lock.release
55 templateopts = cmdutil.templateopts
55 templateopts = cmdutil.templateopts
56
56
57 # The following constants are used throughout the rebase module. The ordering of
57 # The following constants are used throughout the rebase module. The ordering of
58 # their values must be maintained.
58 # their values must be maintained.
59
59
60 # Indicates that a revision needs to be rebased
60 # Indicates that a revision needs to be rebased
61 revtodo = -1
61 revtodo = -1
62 nullmerge = -2
62 nullmerge = -2
63 revignored = -3
63 revignored = -3
64 # successor in rebase destination
64 # successor in rebase destination
65 revprecursor = -4
65 revprecursor = -4
66 # plain prune (no successor)
66 # plain prune (no successor)
67 revpruned = -5
67 revpruned = -5
68 revskipped = (revignored, revprecursor, revpruned)
68 revskipped = (revignored, revprecursor, revpruned)
69
69
70 cmdtable = {}
70 cmdtable = {}
71 command = registrar.command(cmdtable)
71 command = registrar.command(cmdtable)
72 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
72 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
73 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
73 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
74 # be specifying the version(s) of Mercurial they are tested with, or
74 # be specifying the version(s) of Mercurial they are tested with, or
75 # leave the attribute unspecified.
75 # leave the attribute unspecified.
76 testedwith = 'ships-with-hg-core'
76 testedwith = 'ships-with-hg-core'
77
77
78 def _nothingtorebase():
78 def _nothingtorebase():
79 return 1
79 return 1
80
80
81 def _savegraft(ctx, extra):
81 def _savegraft(ctx, extra):
82 s = ctx.extra().get('source', None)
82 s = ctx.extra().get('source', None)
83 if s is not None:
83 if s is not None:
84 extra['source'] = s
84 extra['source'] = s
85 s = ctx.extra().get('intermediate-source', None)
85 s = ctx.extra().get('intermediate-source', None)
86 if s is not None:
86 if s is not None:
87 extra['intermediate-source'] = s
87 extra['intermediate-source'] = s
88
88
89 def _savebranch(ctx, extra):
89 def _savebranch(ctx, extra):
90 extra['branch'] = ctx.branch()
90 extra['branch'] = ctx.branch()
91
91
92 def _makeextrafn(copiers):
92 def _makeextrafn(copiers):
93 """make an extrafn out of the given copy-functions.
93 """make an extrafn out of the given copy-functions.
94
94
95 A copy function takes a context and an extra dict, and mutates the
95 A copy function takes a context and an extra dict, and mutates the
96 extra dict as needed based on the given context.
96 extra dict as needed based on the given context.
97 """
97 """
98 def extrafn(ctx, extra):
98 def extrafn(ctx, extra):
99 for c in copiers:
99 for c in copiers:
100 c(ctx, extra)
100 c(ctx, extra)
101 return extrafn
101 return extrafn
102
102
103 def _destrebase(repo, sourceset, destspace=None):
103 def _destrebase(repo, sourceset, destspace=None):
104 """small wrapper around destmerge to pass the right extra args
104 """small wrapper around destmerge to pass the right extra args
105
105
106 Please wrap destutil.destmerge instead."""
106 Please wrap destutil.destmerge instead."""
107 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
107 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
108 onheadcheck=False, destspace=destspace)
108 onheadcheck=False, destspace=destspace)
109
109
110 revsetpredicate = registrar.revsetpredicate()
110 revsetpredicate = registrar.revsetpredicate()
111
111
112 @revsetpredicate('_destrebase')
112 @revsetpredicate('_destrebase')
113 def _revsetdestrebase(repo, subset, x):
113 def _revsetdestrebase(repo, subset, x):
114 # ``_rebasedefaultdest()``
114 # ``_rebasedefaultdest()``
115
115
116 # default destination for rebase.
116 # default destination for rebase.
117 # # XXX: Currently private because I expect the signature to change.
117 # # XXX: Currently private because I expect the signature to change.
118 # # XXX: - bailing out in case of ambiguity vs returning all data.
118 # # XXX: - bailing out in case of ambiguity vs returning all data.
119 # i18n: "_rebasedefaultdest" is a keyword
119 # i18n: "_rebasedefaultdest" is a keyword
120 sourceset = None
120 sourceset = None
121 if x is not None:
121 if x is not None:
122 sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
122 sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
123 return subset & smartset.baseset([_destrebase(repo, sourceset)])
123 return subset & smartset.baseset([_destrebase(repo, sourceset)])
124
124
125 class rebaseruntime(object):
125 class rebaseruntime(object):
126 """This class is a container for rebase runtime state"""
126 """This class is a container for rebase runtime state"""
127 def __init__(self, repo, ui, opts=None):
127 def __init__(self, repo, ui, opts=None):
128 if opts is None:
128 if opts is None:
129 opts = {}
129 opts = {}
130
130
131 self.repo = repo
131 self.repo = repo
132 self.ui = ui
132 self.ui = ui
133 self.opts = opts
133 self.opts = opts
134 self.originalwd = None
134 self.originalwd = None
135 self.external = nullrev
135 self.external = nullrev
136 # Mapping between the old revision id and either what is the new rebased
136 # Mapping between the old revision id and either what is the new rebased
137 # revision or what needs to be done with the old revision. The state
137 # revision or what needs to be done with the old revision. The state
138 # dict will be what contains most of the rebase progress state.
138 # dict will be what contains most of the rebase progress state.
139 self.state = {}
139 self.state = {}
140 self.activebookmark = None
140 self.activebookmark = None
141 self.currentbookmarks = None
141 self.currentbookmarks = None
142 self.dest = None
142 self.dest = None
143 self.skipped = set()
143 self.skipped = set()
144 self.destancestors = set()
144 self.destancestors = set()
145
145
146 self.collapsef = opts.get('collapse', False)
146 self.collapsef = opts.get('collapse', False)
147 self.collapsemsg = cmdutil.logmessage(ui, opts)
147 self.collapsemsg = cmdutil.logmessage(ui, opts)
148 self.date = opts.get('date', None)
148 self.date = opts.get('date', None)
149
149
150 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
150 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
151 self.extrafns = [_savegraft]
151 self.extrafns = [_savegraft]
152 if e:
152 if e:
153 self.extrafns = [e]
153 self.extrafns = [e]
154
154
155 self.keepf = opts.get('keep', False)
155 self.keepf = opts.get('keep', False)
156 self.keepbranchesf = opts.get('keepbranches', False)
156 self.keepbranchesf = opts.get('keepbranches', False)
157 # keepopen is not meant for use on the command line, but by
157 # keepopen is not meant for use on the command line, but by
158 # other extensions
158 # other extensions
159 self.keepopen = opts.get('keepopen', False)
159 self.keepopen = opts.get('keepopen', False)
160 self.obsoletenotrebased = {}
160 self.obsoletenotrebased = {}
161
161
162 def storestatus(self, tr=None):
162 def storestatus(self, tr=None):
163 """Store the current status to allow recovery"""
163 """Store the current status to allow recovery"""
164 if tr:
164 if tr:
165 tr.addfilegenerator('rebasestate', ('rebasestate',),
165 tr.addfilegenerator('rebasestate', ('rebasestate',),
166 self._writestatus, location='plain')
166 self._writestatus, location='plain')
167 else:
167 else:
168 with self.repo.vfs("rebasestate", "w") as f:
168 with self.repo.vfs("rebasestate", "w") as f:
169 self._writestatus(f)
169 self._writestatus(f)
170
170
171 def _writestatus(self, f):
171 def _writestatus(self, f):
172 repo = self.repo.unfiltered()
172 repo = self.repo.unfiltered()
173 f.write(repo[self.originalwd].hex() + '\n')
173 f.write(repo[self.originalwd].hex() + '\n')
174 f.write(repo[self.dest].hex() + '\n')
174 f.write(repo[self.dest].hex() + '\n')
175 f.write(repo[self.external].hex() + '\n')
175 f.write(repo[self.external].hex() + '\n')
176 f.write('%d\n' % int(self.collapsef))
176 f.write('%d\n' % int(self.collapsef))
177 f.write('%d\n' % int(self.keepf))
177 f.write('%d\n' % int(self.keepf))
178 f.write('%d\n' % int(self.keepbranchesf))
178 f.write('%d\n' % int(self.keepbranchesf))
179 f.write('%s\n' % (self.activebookmark or ''))
179 f.write('%s\n' % (self.activebookmark or ''))
180 for d, v in self.state.iteritems():
180 for d, v in self.state.iteritems():
181 oldrev = repo[d].hex()
181 oldrev = repo[d].hex()
182 if v >= 0:
182 if v >= 0:
183 newrev = repo[v].hex()
183 newrev = repo[v].hex()
184 elif v == revtodo:
184 elif v == revtodo:
185 # To maintain format compatibility, we have to use nullid.
185 # To maintain format compatibility, we have to use nullid.
186 # Please do remove this special case when upgrading the format.
186 # Please do remove this special case when upgrading the format.
187 newrev = hex(nullid)
187 newrev = hex(nullid)
188 else:
188 else:
189 newrev = v
189 newrev = v
190 f.write("%s:%s\n" % (oldrev, newrev))
190 f.write("%s:%s\n" % (oldrev, newrev))
191 repo.ui.debug('rebase status stored\n')
191 repo.ui.debug('rebase status stored\n')
192
192
193 def restorestatus(self):
193 def restorestatus(self):
194 """Restore a previously stored status"""
194 """Restore a previously stored status"""
195 repo = self.repo
195 repo = self.repo
196 keepbranches = None
196 keepbranches = None
197 dest = None
197 dest = None
198 collapse = False
198 collapse = False
199 external = nullrev
199 external = nullrev
200 activebookmark = None
200 activebookmark = None
201 state = {}
201 state = {}
202
202
203 try:
203 try:
204 f = repo.vfs("rebasestate")
204 f = repo.vfs("rebasestate")
205 for i, l in enumerate(f.read().splitlines()):
205 for i, l in enumerate(f.read().splitlines()):
206 if i == 0:
206 if i == 0:
207 originalwd = repo[l].rev()
207 originalwd = repo[l].rev()
208 elif i == 1:
208 elif i == 1:
209 dest = repo[l].rev()
209 dest = repo[l].rev()
210 elif i == 2:
210 elif i == 2:
211 external = repo[l].rev()
211 external = repo[l].rev()
212 elif i == 3:
212 elif i == 3:
213 collapse = bool(int(l))
213 collapse = bool(int(l))
214 elif i == 4:
214 elif i == 4:
215 keep = bool(int(l))
215 keep = bool(int(l))
216 elif i == 5:
216 elif i == 5:
217 keepbranches = bool(int(l))
217 keepbranches = bool(int(l))
218 elif i == 6 and not (len(l) == 81 and ':' in l):
218 elif i == 6 and not (len(l) == 81 and ':' in l):
219 # line 6 is a recent addition, so for backwards
219 # line 6 is a recent addition, so for backwards
220 # compatibility check that the line doesn't look like the
220 # compatibility check that the line doesn't look like the
221 # oldrev:newrev lines
221 # oldrev:newrev lines
222 activebookmark = l
222 activebookmark = l
223 else:
223 else:
224 oldrev, newrev = l.split(':')
224 oldrev, newrev = l.split(':')
225 if newrev in (str(nullmerge), str(revignored),
225 if newrev in (str(nullmerge), str(revignored),
226 str(revprecursor), str(revpruned)):
226 str(revprecursor), str(revpruned)):
227 state[repo[oldrev].rev()] = int(newrev)
227 state[repo[oldrev].rev()] = int(newrev)
228 elif newrev == nullid:
228 elif newrev == nullid:
229 state[repo[oldrev].rev()] = revtodo
229 state[repo[oldrev].rev()] = revtodo
230 # Legacy compat special case
230 # Legacy compat special case
231 else:
231 else:
232 state[repo[oldrev].rev()] = repo[newrev].rev()
232 state[repo[oldrev].rev()] = repo[newrev].rev()
233
233
234 except IOError as err:
234 except IOError as err:
235 if err.errno != errno.ENOENT:
235 if err.errno != errno.ENOENT:
236 raise
236 raise
237 cmdutil.wrongtooltocontinue(repo, _('rebase'))
237 cmdutil.wrongtooltocontinue(repo, _('rebase'))
238
238
239 if keepbranches is None:
239 if keepbranches is None:
240 raise error.Abort(_('.hg/rebasestate is incomplete'))
240 raise error.Abort(_('.hg/rebasestate is incomplete'))
241
241
242 skipped = set()
242 skipped = set()
243 # recompute the set of skipped revs
243 # recompute the set of skipped revs
244 if not collapse:
244 if not collapse:
245 seen = {dest}
245 seen = {dest}
246 for old, new in sorted(state.items()):
246 for old, new in sorted(state.items()):
247 if new != revtodo and new in seen:
247 if new != revtodo and new in seen:
248 skipped.add(old)
248 skipped.add(old)
249 seen.add(new)
249 seen.add(new)
250 repo.ui.debug('computed skipped revs: %s\n' %
250 repo.ui.debug('computed skipped revs: %s\n' %
251 (' '.join(str(r) for r in sorted(skipped)) or None))
251 (' '.join(str(r) for r in sorted(skipped)) or None))
252 repo.ui.debug('rebase status resumed\n')
252 repo.ui.debug('rebase status resumed\n')
253 _setrebasesetvisibility(repo, set(state.keys()) | {originalwd})
253 _setrebasesetvisibility(repo, set(state.keys()) | {originalwd})
254
254
255 self.originalwd = originalwd
255 self.originalwd = originalwd
256 self.dest = dest
256 self.dest = dest
257 self.state = state
257 self.state = state
258 self.skipped = skipped
258 self.skipped = skipped
259 self.collapsef = collapse
259 self.collapsef = collapse
260 self.keepf = keep
260 self.keepf = keep
261 self.keepbranchesf = keepbranches
261 self.keepbranchesf = keepbranches
262 self.external = external
262 self.external = external
263 self.activebookmark = activebookmark
263 self.activebookmark = activebookmark
264
264
265 def _handleskippingobsolete(self, rebaserevs, obsoleterevs, dest):
265 def _handleskippingobsolete(self, rebaserevs, obsoleterevs, dest):
266 """Compute structures necessary for skipping obsolete revisions
266 """Compute structures necessary for skipping obsolete revisions
267
267
268 rebaserevs: iterable of all revisions that are to be rebased
268 rebaserevs: iterable of all revisions that are to be rebased
269 obsoleterevs: iterable of all obsolete revisions in rebaseset
269 obsoleterevs: iterable of all obsolete revisions in rebaseset
270 dest: a destination revision for the rebase operation
270 dest: a destination revision for the rebase operation
271 """
271 """
272 self.obsoletenotrebased = {}
272 self.obsoletenotrebased = {}
273 if not self.ui.configbool('experimental', 'rebaseskipobsolete',
273 if not self.ui.configbool('experimental', 'rebaseskipobsolete',
274 default=True):
274 default=True):
275 return
275 return
276 rebaseset = set(rebaserevs)
276 rebaseset = set(rebaserevs)
277 obsoleteset = set(obsoleterevs)
277 obsoleteset = set(obsoleterevs)
278 self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
278 self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
279 obsoleteset, dest)
279 obsoleteset, dest)
280 skippedset = set(self.obsoletenotrebased)
280 skippedset = set(self.obsoletenotrebased)
281 _checkobsrebase(self.repo, self.ui, obsoleteset, rebaseset, skippedset)
281 _checkobsrebase(self.repo, self.ui, obsoleteset, rebaseset, skippedset)
282
282
283 def _prepareabortorcontinue(self, isabort):
283 def _prepareabortorcontinue(self, isabort):
284 try:
284 try:
285 self.restorestatus()
285 self.restorestatus()
286 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
286 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
287 except error.RepoLookupError:
287 except error.RepoLookupError:
288 if isabort:
288 if isabort:
289 clearstatus(self.repo)
289 clearstatus(self.repo)
290 clearcollapsemsg(self.repo)
290 clearcollapsemsg(self.repo)
291 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
291 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
292 ' only broken state is cleared)\n'))
292 ' only broken state is cleared)\n'))
293 return 0
293 return 0
294 else:
294 else:
295 msg = _('cannot continue inconsistent rebase')
295 msg = _('cannot continue inconsistent rebase')
296 hint = _('use "hg rebase --abort" to clear broken state')
296 hint = _('use "hg rebase --abort" to clear broken state')
297 raise error.Abort(msg, hint=hint)
297 raise error.Abort(msg, hint=hint)
298 if isabort:
298 if isabort:
299 return abort(self.repo, self.originalwd, self.dest,
299 return abort(self.repo, self.originalwd, self.dest,
300 self.state, activebookmark=self.activebookmark)
300 self.state, activebookmark=self.activebookmark)
301
301
302 obsrevs = (r for r, st in self.state.items() if st == revprecursor)
302 obsrevs = (r for r, st in self.state.items() if st == revprecursor)
303 self._handleskippingobsolete(self.state.keys(), obsrevs, self.dest)
303 self._handleskippingobsolete(self.state.keys(), obsrevs, self.dest)
304
304
305 def _preparenewrebase(self, dest, rebaseset):
305 def _preparenewrebase(self, dest, rebaseset):
306 if dest is None:
306 if dest is None:
307 return _nothingtorebase()
307 return _nothingtorebase()
308
308
309 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
309 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
310 if (not (self.keepf or allowunstable)
310 if (not (self.keepf or allowunstable)
311 and self.repo.revs('first(children(%ld) - %ld)',
311 and self.repo.revs('first(children(%ld) - %ld)',
312 rebaseset, rebaseset)):
312 rebaseset, rebaseset)):
313 raise error.Abort(
313 raise error.Abort(
314 _("can't remove original changesets with"
314 _("can't remove original changesets with"
315 " unrebased descendants"),
315 " unrebased descendants"),
316 hint=_('use --keep to keep original changesets'))
316 hint=_('use --keep to keep original changesets'))
317
317
318 obsrevs = _filterobsoleterevs(self.repo, set(rebaseset))
318 obsrevs = _filterobsoleterevs(self.repo, set(rebaseset))
319 self._handleskippingobsolete(rebaseset, obsrevs, dest)
319 self._handleskippingobsolete(rebaseset, obsrevs, dest)
320
320
321 result = buildstate(self.repo, dest, rebaseset, self.collapsef,
321 result = buildstate(self.repo, dest, rebaseset, self.collapsef,
322 self.obsoletenotrebased)
322 self.obsoletenotrebased)
323
323
324 if not result:
324 if not result:
325 # Empty state built, nothing to rebase
325 # Empty state built, nothing to rebase
326 self.ui.status(_('nothing to rebase\n'))
326 self.ui.status(_('nothing to rebase\n'))
327 return _nothingtorebase()
327 return _nothingtorebase()
328
328
329 for root in self.repo.set('roots(%ld)', rebaseset):
329 for root in self.repo.set('roots(%ld)', rebaseset):
330 if not self.keepf and not root.mutable():
330 if not self.keepf and not root.mutable():
331 raise error.Abort(_("can't rebase public changeset %s")
331 raise error.Abort(_("can't rebase public changeset %s")
332 % root,
332 % root,
333 hint=_("see 'hg help phases' for details"))
333 hint=_("see 'hg help phases' for details"))
334
334
335 (self.originalwd, self.dest, self.state) = result
335 (self.originalwd, self.dest, self.state) = result
336 if self.collapsef:
336 if self.collapsef:
337 self.destancestors = self.repo.changelog.ancestors(
337 self.destancestors = self.repo.changelog.ancestors(
338 [self.dest],
338 [self.dest],
339 inclusive=True)
339 inclusive=True)
340 self.external = externalparent(self.repo, self.state,
340 self.external = externalparent(self.repo, self.state,
341 self.destancestors)
341 self.destancestors)
342
342
343 if dest.closesbranch() and not self.keepbranchesf:
343 if dest.closesbranch() and not self.keepbranchesf:
344 self.ui.status(_('reopening closed branch head %s\n') % dest)
344 self.ui.status(_('reopening closed branch head %s\n') % dest)
345
345
346 def _performrebase(self, tr):
346 def _performrebase(self, tr):
347 repo, ui, opts = self.repo, self.ui, self.opts
347 repo, ui, opts = self.repo, self.ui, self.opts
348 if self.keepbranchesf:
348 if self.keepbranchesf:
349 # insert _savebranch at the start of extrafns so if
349 # insert _savebranch at the start of extrafns so if
350 # there's a user-provided extrafn it can clobber branch if
350 # there's a user-provided extrafn it can clobber branch if
351 # desired
351 # desired
352 self.extrafns.insert(0, _savebranch)
352 self.extrafns.insert(0, _savebranch)
353 if self.collapsef:
353 if self.collapsef:
354 branches = set()
354 branches = set()
355 for rev in self.state:
355 for rev in self.state:
356 branches.add(repo[rev].branch())
356 branches.add(repo[rev].branch())
357 if len(branches) > 1:
357 if len(branches) > 1:
358 raise error.Abort(_('cannot collapse multiple named '
358 raise error.Abort(_('cannot collapse multiple named '
359 'branches'))
359 'branches'))
360
360
361 # Rebase
361 # Rebase
362 if not self.destancestors:
362 if not self.destancestors:
363 self.destancestors = repo.changelog.ancestors([self.dest],
363 self.destancestors = repo.changelog.ancestors([self.dest],
364 inclusive=True)
364 inclusive=True)
365
365
366 # Keep track of the current bookmarks in order to reset them later
366 # Keep track of the current bookmarks in order to reset them later
367 self.currentbookmarks = repo._bookmarks.copy()
367 self.currentbookmarks = repo._bookmarks.copy()
368 self.activebookmark = self.activebookmark or repo._activebookmark
368 self.activebookmark = self.activebookmark or repo._activebookmark
369 if self.activebookmark:
369 if self.activebookmark:
370 bookmarks.deactivate(repo)
370 bookmarks.deactivate(repo)
371
371
372 # Store the state before we begin so users can run 'hg rebase --abort'
372 # Store the state before we begin so users can run 'hg rebase --abort'
373 # if we fail before the transaction closes.
373 # if we fail before the transaction closes.
374 self.storestatus()
374 self.storestatus()
375
375
376 sortedrevs = repo.revs('sort(%ld, -topo)', self.state)
376 sortedrevs = repo.revs('sort(%ld, -topo)', self.state)
377 cands = [k for k, v in self.state.iteritems() if v == revtodo]
377 cands = [k for k, v in self.state.iteritems() if v == revtodo]
378 total = len(cands)
378 total = len(cands)
379 pos = 0
379 pos = 0
380 for rev in sortedrevs:
380 for rev in sortedrevs:
381 ctx = repo[rev]
381 ctx = repo[rev]
382 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
382 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
383 ctx.description().split('\n', 1)[0])
383 ctx.description().split('\n', 1)[0])
384 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
384 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
385 if names:
385 if names:
386 desc += ' (%s)' % ' '.join(names)
386 desc += ' (%s)' % ' '.join(names)
387 if self.state[rev] == rev:
387 if self.state[rev] == rev:
388 ui.status(_('already rebased %s\n') % desc)
388 ui.status(_('already rebased %s\n') % desc)
389 elif self.state[rev] == revtodo:
389 elif self.state[rev] == revtodo:
390 pos += 1
390 pos += 1
391 ui.status(_('rebasing %s\n') % desc)
391 ui.status(_('rebasing %s\n') % desc)
392 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
392 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
393 _('changesets'), total)
393 _('changesets'), total)
394 p1, p2, base = defineparents(repo, rev, self.dest,
394 p1, p2, base = defineparents(repo, rev, self.dest,
395 self.state,
395 self.state,
396 self.destancestors,
396 self.destancestors,
397 self.obsoletenotrebased)
397 self.obsoletenotrebased)
398 self.storestatus(tr=tr)
398 self.storestatus(tr=tr)
399 storecollapsemsg(repo, self.collapsemsg)
399 storecollapsemsg(repo, self.collapsemsg)
400 if len(repo[None].parents()) == 2:
400 if len(repo[None].parents()) == 2:
401 repo.ui.debug('resuming interrupted rebase\n')
401 repo.ui.debug('resuming interrupted rebase\n')
402 else:
402 else:
403 try:
403 try:
404 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
404 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
405 'rebase')
405 'rebase')
406 stats = rebasenode(repo, rev, p1, base, self.state,
406 stats = rebasenode(repo, rev, p1, base, self.state,
407 self.collapsef, self.dest)
407 self.collapsef, self.dest)
408 if stats and stats[3] > 0:
408 if stats and stats[3] > 0:
409 raise error.InterventionRequired(
409 raise error.InterventionRequired(
410 _('unresolved conflicts (see hg '
410 _('unresolved conflicts (see hg '
411 'resolve, then hg rebase --continue)'))
411 'resolve, then hg rebase --continue)'))
412 finally:
412 finally:
413 ui.setconfig('ui', 'forcemerge', '', 'rebase')
413 ui.setconfig('ui', 'forcemerge', '', 'rebase')
414 if not self.collapsef:
414 if not self.collapsef:
415 merging = p2 != nullrev
415 merging = p2 != nullrev
416 editform = cmdutil.mergeeditform(merging, 'rebase')
416 editform = cmdutil.mergeeditform(merging, 'rebase')
417 editor = cmdutil.getcommiteditor(editform=editform, **opts)
417 editor = cmdutil.getcommiteditor(editform=editform, **opts)
418 newnode = concludenode(repo, rev, p1, p2,
418 newnode = concludenode(repo, rev, p1, p2,
419 extrafn=_makeextrafn(self.extrafns),
419 extrafn=_makeextrafn(self.extrafns),
420 editor=editor,
420 editor=editor,
421 keepbranches=self.keepbranchesf,
421 keepbranches=self.keepbranchesf,
422 date=self.date)
422 date=self.date)
423 if newnode is None:
423 if newnode is None:
424 # If it ended up being a no-op commit, then the normal
424 # If it ended up being a no-op commit, then the normal
425 # merge state clean-up path doesn't happen, so do it
425 # merge state clean-up path doesn't happen, so do it
426 # here. Fix issue5494
426 # here. Fix issue5494
427 mergemod.mergestate.clean(repo)
427 mergemod.mergestate.clean(repo)
428 else:
428 else:
429 # Skip commit if we are collapsing
429 # Skip commit if we are collapsing
430 repo.setparents(repo[p1].node())
430 repo.setparents(repo[p1].node())
431 newnode = None
431 newnode = None
432 # Update the state
432 # Update the state
433 if newnode is not None:
433 if newnode is not None:
434 self.state[rev] = repo[newnode].rev()
434 self.state[rev] = repo[newnode].rev()
435 ui.debug('rebased as %s\n' % short(newnode))
435 ui.debug('rebased as %s\n' % short(newnode))
436 else:
436 else:
437 if not self.collapsef:
437 if not self.collapsef:
438 ui.warn(_('note: rebase of %d:%s created no changes '
438 ui.warn(_('note: rebase of %d:%s created no changes '
439 'to commit\n') % (rev, ctx))
439 'to commit\n') % (rev, ctx))
440 self.skipped.add(rev)
440 self.skipped.add(rev)
441 self.state[rev] = p1
441 self.state[rev] = p1
442 ui.debug('next revision set to %s\n' % p1)
442 ui.debug('next revision set to %s\n' % p1)
443 elif self.state[rev] == nullmerge:
443 elif self.state[rev] == nullmerge:
444 ui.debug('ignoring null merge rebase of %s\n' % rev)
444 ui.debug('ignoring null merge rebase of %s\n' % rev)
445 elif self.state[rev] == revignored:
445 elif self.state[rev] == revignored:
446 ui.status(_('not rebasing ignored %s\n') % desc)
446 ui.status(_('not rebasing ignored %s\n') % desc)
447 elif self.state[rev] == revprecursor:
447 elif self.state[rev] == revprecursor:
448 destctx = repo[self.obsoletenotrebased[rev]]
448 destctx = repo[self.obsoletenotrebased[rev]]
449 descdest = '%d:%s "%s"' % (destctx.rev(), destctx,
449 descdest = '%d:%s "%s"' % (destctx.rev(), destctx,
450 destctx.description().split('\n', 1)[0])
450 destctx.description().split('\n', 1)[0])
451 msg = _('note: not rebasing %s, already in destination as %s\n')
451 msg = _('note: not rebasing %s, already in destination as %s\n')
452 ui.status(msg % (desc, descdest))
452 ui.status(msg % (desc, descdest))
453 elif self.state[rev] == revpruned:
453 elif self.state[rev] == revpruned:
454 msg = _('note: not rebasing %s, it has no successor\n')
454 msg = _('note: not rebasing %s, it has no successor\n')
455 ui.status(msg % desc)
455 ui.status(msg % desc)
456 else:
456 else:
457 ui.status(_('already rebased %s as %s\n') %
457 ui.status(_('already rebased %s as %s\n') %
458 (desc, repo[self.state[rev]]))
458 (desc, repo[self.state[rev]]))
459
459
460 ui.progress(_('rebasing'), None)
460 ui.progress(_('rebasing'), None)
461 ui.note(_('rebase merging completed\n'))
461 ui.note(_('rebase merging completed\n'))
462
462
463 def _finishrebase(self):
463 def _finishrebase(self):
464 repo, ui, opts = self.repo, self.ui, self.opts
464 repo, ui, opts = self.repo, self.ui, self.opts
465 if self.collapsef and not self.keepopen:
465 if self.collapsef and not self.keepopen:
466 p1, p2, _base = defineparents(repo, min(self.state),
466 p1, p2, _base = defineparents(repo, min(self.state),
467 self.dest, self.state,
467 self.dest, self.state,
468 self.destancestors,
468 self.destancestors,
469 self.obsoletenotrebased)
469 self.obsoletenotrebased)
470 editopt = opts.get('edit')
470 editopt = opts.get('edit')
471 editform = 'rebase.collapse'
471 editform = 'rebase.collapse'
472 if self.collapsemsg:
472 if self.collapsemsg:
473 commitmsg = self.collapsemsg
473 commitmsg = self.collapsemsg
474 else:
474 else:
475 commitmsg = 'Collapsed revision'
475 commitmsg = 'Collapsed revision'
476 for rebased in self.state:
476 for rebased in self.state:
477 if rebased not in self.skipped and\
477 if rebased not in self.skipped and\
478 self.state[rebased] > nullmerge:
478 self.state[rebased] > nullmerge:
479 commitmsg += '\n* %s' % repo[rebased].description()
479 commitmsg += '\n* %s' % repo[rebased].description()
480 editopt = True
480 editopt = True
481 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
481 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
482 revtoreuse = max(self.state)
482 revtoreuse = max(self.state)
483 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
483 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
484 try:
484 try:
485 newnode = concludenode(repo, revtoreuse, p1, self.external,
485 newnode = concludenode(repo, revtoreuse, p1, self.external,
486 commitmsg=commitmsg,
486 commitmsg=commitmsg,
487 extrafn=_makeextrafn(self.extrafns),
487 extrafn=_makeextrafn(self.extrafns),
488 editor=editor,
488 editor=editor,
489 keepbranches=self.keepbranchesf,
489 keepbranches=self.keepbranchesf,
490 date=self.date)
490 date=self.date)
491 dsguard.close()
491 dsguard.close()
492 release(dsguard)
492 release(dsguard)
493 except error.InterventionRequired:
493 except error.InterventionRequired:
494 dsguard.close()
494 dsguard.close()
495 release(dsguard)
495 release(dsguard)
496 raise
496 raise
497 except Exception:
497 except Exception:
498 release(dsguard)
498 release(dsguard)
499 raise
499 raise
500
500
501 if newnode is None:
501 if newnode is None:
502 newrev = self.dest
502 newrev = self.dest
503 else:
503 else:
504 newrev = repo[newnode].rev()
504 newrev = repo[newnode].rev()
505 for oldrev in self.state.iterkeys():
505 for oldrev in self.state.iterkeys():
506 if self.state[oldrev] > nullmerge:
506 if self.state[oldrev] > nullmerge:
507 self.state[oldrev] = newrev
507 self.state[oldrev] = newrev
508
508
509 if 'qtip' in repo.tags():
509 if 'qtip' in repo.tags():
510 updatemq(repo, self.state, self.skipped, **opts)
510 updatemq(repo, self.state, self.skipped, **opts)
511
511
512 if self.currentbookmarks:
512 if self.currentbookmarks:
513 # Nodeids are needed to reset bookmarks
513 # Nodeids are needed to reset bookmarks
514 nstate = {}
514 nstate = {}
515 for k, v in self.state.iteritems():
515 for k, v in self.state.iteritems():
516 if v > nullmerge and v != k:
516 if v > nullmerge and v != k:
517 nstate[repo[k].node()] = repo[v].node()
517 nstate[repo[k].node()] = repo[v].node()
518 elif v == revprecursor:
518 elif v == revprecursor:
519 succ = self.obsoletenotrebased[k]
519 succ = self.obsoletenotrebased[k]
520 nstate[repo[k].node()] = repo[succ].node()
520 nstate[repo[k].node()] = repo[succ].node()
521 # XXX this is the same as dest.node() for the non-continue path --
521 # XXX this is the same as dest.node() for the non-continue path --
522 # this should probably be cleaned up
522 # this should probably be cleaned up
523 destnode = repo[self.dest].node()
523 destnode = repo[self.dest].node()
524
524
525 # restore original working directory
525 # restore original working directory
526 # (we do this before stripping)
526 # (we do this before stripping)
527 newwd = self.state.get(self.originalwd, self.originalwd)
527 newwd = self.state.get(self.originalwd, self.originalwd)
528 if newwd == revprecursor:
528 if newwd == revprecursor:
529 newwd = self.obsoletenotrebased[self.originalwd]
529 newwd = self.obsoletenotrebased[self.originalwd]
530 elif newwd < 0:
530 elif newwd < 0:
531 # original directory is a parent of rebase set root or ignored
531 # original directory is a parent of rebase set root or ignored
532 newwd = self.originalwd
532 newwd = self.originalwd
533 if newwd not in [c.rev() for c in repo[None].parents()]:
533 if newwd not in [c.rev() for c in repo[None].parents()]:
534 ui.note(_("update back to initial working directory parent\n"))
534 ui.note(_("update back to initial working directory parent\n"))
535 hg.updaterepo(repo, newwd, False)
535 hg.updaterepo(repo, newwd, False)
536
536
537 if self.currentbookmarks:
537 if self.currentbookmarks:
538 with repo.transaction('bookmark') as tr:
538 with repo.transaction('bookmark') as tr:
539 updatebookmarks(repo, destnode, nstate,
539 updatebookmarks(repo, destnode, nstate,
540 self.currentbookmarks, tr)
540 self.currentbookmarks, tr)
541 if self.activebookmark not in repo._bookmarks:
541 if self.activebookmark not in repo._bookmarks:
542 # active bookmark was divergent one and has been deleted
542 # active bookmark was divergent one and has been deleted
543 self.activebookmark = None
543 self.activebookmark = None
544
544
545 if not self.keepf:
545 if not self.keepf:
546 collapsedas = None
546 collapsedas = None
547 if self.collapsef:
547 if self.collapsef:
548 collapsedas = newnode
548 collapsedas = newnode
549 clearrebased(ui, repo, self.state, self.skipped, collapsedas)
549 clearrebased(ui, repo, self.state, self.skipped, collapsedas)
550
550
551 clearstatus(repo)
551 clearstatus(repo)
552 clearcollapsemsg(repo)
552 clearcollapsemsg(repo)
553
553
554 ui.note(_("rebase completed\n"))
554 ui.note(_("rebase completed\n"))
555 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
555 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
556 if self.skipped:
556 if self.skipped:
557 skippedlen = len(self.skipped)
557 skippedlen = len(self.skipped)
558 ui.note(_("%d revisions have been skipped\n") % skippedlen)
558 ui.note(_("%d revisions have been skipped\n") % skippedlen)
559
559
560 if (self.activebookmark and
560 if (self.activebookmark and
561 repo['.'].node() == repo._bookmarks[self.activebookmark]):
561 repo['.'].node() == repo._bookmarks[self.activebookmark]):
562 bookmarks.activate(repo, self.activebookmark)
562 bookmarks.activate(repo, self.activebookmark)
563
563
564 @command('rebase',
564 @command('rebase',
565 [('s', 'source', '',
565 [('s', 'source', '',
566 _('rebase the specified changeset and descendants'), _('REV')),
566 _('rebase the specified changeset and descendants'), _('REV')),
567 ('b', 'base', '',
567 ('b', 'base', '',
568 _('rebase everything from branching point of specified changeset'),
568 _('rebase everything from branching point of specified changeset'),
569 _('REV')),
569 _('REV')),
570 ('r', 'rev', [],
570 ('r', 'rev', [],
571 _('rebase these revisions'),
571 _('rebase these revisions'),
572 _('REV')),
572 _('REV')),
573 ('d', 'dest', '',
573 ('d', 'dest', '',
574 _('rebase onto the specified changeset'), _('REV')),
574 _('rebase onto the specified changeset'), _('REV')),
575 ('', 'collapse', False, _('collapse the rebased changesets')),
575 ('', 'collapse', False, _('collapse the rebased changesets')),
576 ('m', 'message', '',
576 ('m', 'message', '',
577 _('use text as collapse commit message'), _('TEXT')),
577 _('use text as collapse commit message'), _('TEXT')),
578 ('e', 'edit', False, _('invoke editor on commit messages')),
578 ('e', 'edit', False, _('invoke editor on commit messages')),
579 ('l', 'logfile', '',
579 ('l', 'logfile', '',
580 _('read collapse commit message from file'), _('FILE')),
580 _('read collapse commit message from file'), _('FILE')),
581 ('k', 'keep', False, _('keep original changesets')),
581 ('k', 'keep', False, _('keep original changesets')),
582 ('', 'keepbranches', False, _('keep original branch names')),
582 ('', 'keepbranches', False, _('keep original branch names')),
583 ('D', 'detach', False, _('(DEPRECATED)')),
583 ('D', 'detach', False, _('(DEPRECATED)')),
584 ('i', 'interactive', False, _('(DEPRECATED)')),
584 ('i', 'interactive', False, _('(DEPRECATED)')),
585 ('t', 'tool', '', _('specify merge tool')),
585 ('t', 'tool', '', _('specify merge tool')),
586 ('c', 'continue', False, _('continue an interrupted rebase')),
586 ('c', 'continue', False, _('continue an interrupted rebase')),
587 ('a', 'abort', False, _('abort an interrupted rebase'))] +
587 ('a', 'abort', False, _('abort an interrupted rebase'))] +
588 templateopts,
588 templateopts,
589 _('[-s REV | -b REV] [-d REV] [OPTION]'))
589 _('[-s REV | -b REV] [-d REV] [OPTION]'))
590 def rebase(ui, repo, **opts):
590 def rebase(ui, repo, **opts):
591 """move changeset (and descendants) to a different branch
591 """move changeset (and descendants) to a different branch
592
592
593 Rebase uses repeated merging to graft changesets from one part of
593 Rebase uses repeated merging to graft changesets from one part of
594 history (the source) onto another (the destination). This can be
594 history (the source) onto another (the destination). This can be
595 useful for linearizing *local* changes relative to a master
595 useful for linearizing *local* changes relative to a master
596 development tree.
596 development tree.
597
597
598 Published commits cannot be rebased (see :hg:`help phases`).
598 Published commits cannot be rebased (see :hg:`help phases`).
599 To copy commits, see :hg:`help graft`.
599 To copy commits, see :hg:`help graft`.
600
600
601 If you don't specify a destination changeset (``-d/--dest``), rebase
601 If you don't specify a destination changeset (``-d/--dest``), rebase
602 will use the same logic as :hg:`merge` to pick a destination. if
602 will use the same logic as :hg:`merge` to pick a destination. if
603 the current branch contains exactly one other head, the other head
603 the current branch contains exactly one other head, the other head
604 is merged with by default. Otherwise, an explicit revision with
604 is merged with by default. Otherwise, an explicit revision with
605 which to merge with must be provided. (destination changeset is not
605 which to merge with must be provided. (destination changeset is not
606 modified by rebasing, but new changesets are added as its
606 modified by rebasing, but new changesets are added as its
607 descendants.)
607 descendants.)
608
608
609 Here are the ways to select changesets:
609 Here are the ways to select changesets:
610
610
611 1. Explicitly select them using ``--rev``.
611 1. Explicitly select them using ``--rev``.
612
612
613 2. Use ``--source`` to select a root changeset and include all of its
613 2. Use ``--source`` to select a root changeset and include all of its
614 descendants.
614 descendants.
615
615
616 3. Use ``--base`` to select a changeset; rebase will find ancestors
616 3. Use ``--base`` to select a changeset; rebase will find ancestors
617 and their descendants which are not also ancestors of the destination.
617 and their descendants which are not also ancestors of the destination.
618
618
619 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
619 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
620 rebase will use ``--base .`` as above.
620 rebase will use ``--base .`` as above.
621
621
622 Rebase will destroy original changesets unless you use ``--keep``.
622 Rebase will destroy original changesets unless you use ``--keep``.
623 It will also move your bookmarks (even if you do).
623 It will also move your bookmarks (even if you do).
624
624
625 Some changesets may be dropped if they do not contribute changes
625 Some changesets may be dropped if they do not contribute changes
626 (e.g. merges from the destination branch).
626 (e.g. merges from the destination branch).
627
627
628 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
628 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
629 a named branch with two heads. You will need to explicitly specify source
629 a named branch with two heads. You will need to explicitly specify source
630 and/or destination.
630 and/or destination.
631
631
632 If you need to use a tool to automate merge/conflict decisions, you
632 If you need to use a tool to automate merge/conflict decisions, you
633 can specify one with ``--tool``, see :hg:`help merge-tools`.
633 can specify one with ``--tool``, see :hg:`help merge-tools`.
634 As a caveat: the tool will not be used to mediate when a file was
634 As a caveat: the tool will not be used to mediate when a file was
635 deleted, there is no hook presently available for this.
635 deleted, there is no hook presently available for this.
636
636
637 If a rebase is interrupted to manually resolve a conflict, it can be
637 If a rebase is interrupted to manually resolve a conflict, it can be
638 continued with --continue/-c or aborted with --abort/-a.
638 continued with --continue/-c or aborted with --abort/-a.
639
639
640 .. container:: verbose
640 .. container:: verbose
641
641
642 Examples:
642 Examples:
643
643
644 - move "local changes" (current commit back to branching point)
644 - move "local changes" (current commit back to branching point)
645 to the current branch tip after a pull::
645 to the current branch tip after a pull::
646
646
647 hg rebase
647 hg rebase
648
648
649 - move a single changeset to the stable branch::
649 - move a single changeset to the stable branch::
650
650
651 hg rebase -r 5f493448 -d stable
651 hg rebase -r 5f493448 -d stable
652
652
653 - splice a commit and all its descendants onto another part of history::
653 - splice a commit and all its descendants onto another part of history::
654
654
655 hg rebase --source c0c3 --dest 4cf9
655 hg rebase --source c0c3 --dest 4cf9
656
656
657 - rebase everything on a branch marked by a bookmark onto the
657 - rebase everything on a branch marked by a bookmark onto the
658 default branch::
658 default branch::
659
659
660 hg rebase --base myfeature --dest default
660 hg rebase --base myfeature --dest default
661
661
662 - collapse a sequence of changes into a single commit::
662 - collapse a sequence of changes into a single commit::
663
663
664 hg rebase --collapse -r 1520:1525 -d .
664 hg rebase --collapse -r 1520:1525 -d .
665
665
666 - move a named branch while preserving its name::
666 - move a named branch while preserving its name::
667
667
668 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
668 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
669
669
670 Configuration Options:
670 Configuration Options:
671
671
672 You can make rebase require a destination if you set the following config
672 You can make rebase require a destination if you set the following config
673 option::
673 option::
674
674
675 [commands]
675 [commands]
676 rebase.requiredest = True
676 rebase.requiredest = True
677
677
678 Return Values:
678 Return Values:
679
679
680 Returns 0 on success, 1 if nothing to rebase or there are
680 Returns 0 on success, 1 if nothing to rebase or there are
681 unresolved conflicts.
681 unresolved conflicts.
682
682
683 """
683 """
684 rbsrt = rebaseruntime(repo, ui, opts)
684 rbsrt = rebaseruntime(repo, ui, opts)
685
685
686 with repo.wlock(), repo.lock():
686 with repo.wlock(), repo.lock():
687 # Validate input and define rebasing points
687 # Validate input and define rebasing points
688 destf = opts.get('dest', None)
688 destf = opts.get('dest', None)
689 srcf = opts.get('source', None)
689 srcf = opts.get('source', None)
690 basef = opts.get('base', None)
690 basef = opts.get('base', None)
691 revf = opts.get('rev', [])
691 revf = opts.get('rev', [])
692 # search default destination in this space
692 # search default destination in this space
693 # used in the 'hg pull --rebase' case, see issue 5214.
693 # used in the 'hg pull --rebase' case, see issue 5214.
694 destspace = opts.get('_destspace')
694 destspace = opts.get('_destspace')
695 contf = opts.get('continue')
695 contf = opts.get('continue')
696 abortf = opts.get('abort')
696 abortf = opts.get('abort')
697 if opts.get('interactive'):
697 if opts.get('interactive'):
698 try:
698 try:
699 if extensions.find('histedit'):
699 if extensions.find('histedit'):
700 enablehistedit = ''
700 enablehistedit = ''
701 except KeyError:
701 except KeyError:
702 enablehistedit = " --config extensions.histedit="
702 enablehistedit = " --config extensions.histedit="
703 help = "hg%s help -e histedit" % enablehistedit
703 help = "hg%s help -e histedit" % enablehistedit
704 msg = _("interactive history editing is supported by the "
704 msg = _("interactive history editing is supported by the "
705 "'histedit' extension (see \"%s\")") % help
705 "'histedit' extension (see \"%s\")") % help
706 raise error.Abort(msg)
706 raise error.Abort(msg)
707
707
708 if rbsrt.collapsemsg and not rbsrt.collapsef:
708 if rbsrt.collapsemsg and not rbsrt.collapsef:
709 raise error.Abort(
709 raise error.Abort(
710 _('message can only be specified with collapse'))
710 _('message can only be specified with collapse'))
711
711
712 if contf or abortf:
712 if contf or abortf:
713 if contf and abortf:
713 if contf and abortf:
714 raise error.Abort(_('cannot use both abort and continue'))
714 raise error.Abort(_('cannot use both abort and continue'))
715 if rbsrt.collapsef:
715 if rbsrt.collapsef:
716 raise error.Abort(
716 raise error.Abort(
717 _('cannot use collapse with continue or abort'))
717 _('cannot use collapse with continue or abort'))
718 if srcf or basef or destf:
718 if srcf or basef or destf:
719 raise error.Abort(
719 raise error.Abort(
720 _('abort and continue do not allow specifying revisions'))
720 _('abort and continue do not allow specifying revisions'))
721 if abortf and opts.get('tool', False):
721 if abortf and opts.get('tool', False):
722 ui.warn(_('tool option will be ignored\n'))
722 ui.warn(_('tool option will be ignored\n'))
723 if contf:
723 if contf:
724 ms = mergemod.mergestate.read(repo)
724 ms = mergemod.mergestate.read(repo)
725 mergeutil.checkunresolved(ms)
725 mergeutil.checkunresolved(ms)
726
726
727 retcode = rbsrt._prepareabortorcontinue(abortf)
727 retcode = rbsrt._prepareabortorcontinue(abortf)
728 if retcode is not None:
728 if retcode is not None:
729 return retcode
729 return retcode
730 else:
730 else:
731 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
731 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
732 destspace=destspace)
732 destspace=destspace)
733 retcode = rbsrt._preparenewrebase(dest, rebaseset)
733 retcode = rbsrt._preparenewrebase(dest, rebaseset)
734 if retcode is not None:
734 if retcode is not None:
735 return retcode
735 return retcode
736
736
737 with repo.transaction('rebase') as tr:
737 with repo.transaction('rebase') as tr:
738 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
738 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
739 try:
739 try:
740 rbsrt._performrebase(tr)
740 rbsrt._performrebase(tr)
741 dsguard.close()
741 dsguard.close()
742 release(dsguard)
742 release(dsguard)
743 except error.InterventionRequired:
743 except error.InterventionRequired:
744 dsguard.close()
744 dsguard.close()
745 release(dsguard)
745 release(dsguard)
746 tr.close()
746 tr.close()
747 raise
747 raise
748 except Exception:
748 except Exception:
749 release(dsguard)
749 release(dsguard)
750 raise
750 raise
751 rbsrt._finishrebase()
751 rbsrt._finishrebase()
752
752
753 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=None,
753 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=None,
754 destspace=None):
754 destspace=None):
755 """use revisions argument to define destination and rebase set
755 """use revisions argument to define destination and rebase set
756 """
756 """
757 if revf is None:
757 if revf is None:
758 revf = []
758 revf = []
759
759
760 # destspace is here to work around issues with `hg pull --rebase` see
760 # destspace is here to work around issues with `hg pull --rebase` see
761 # issue5214 for details
761 # issue5214 for details
762 if srcf and basef:
762 if srcf and basef:
763 raise error.Abort(_('cannot specify both a source and a base'))
763 raise error.Abort(_('cannot specify both a source and a base'))
764 if revf and basef:
764 if revf and basef:
765 raise error.Abort(_('cannot specify both a revision and a base'))
765 raise error.Abort(_('cannot specify both a revision and a base'))
766 if revf and srcf:
766 if revf and srcf:
767 raise error.Abort(_('cannot specify both a revision and a source'))
767 raise error.Abort(_('cannot specify both a revision and a source'))
768
768
769 cmdutil.checkunfinished(repo)
769 cmdutil.checkunfinished(repo)
770 cmdutil.bailifchanged(repo)
770 cmdutil.bailifchanged(repo)
771
771
772 if ui.configbool('commands', 'rebase.requiredest') and not destf:
772 if ui.configbool('commands', 'rebase.requiredest') and not destf:
773 raise error.Abort(_('you must specify a destination'),
773 raise error.Abort(_('you must specify a destination'),
774 hint=_('use: hg rebase -d REV'))
774 hint=_('use: hg rebase -d REV'))
775
775
776 if destf:
776 if destf:
777 dest = scmutil.revsingle(repo, destf)
777 dest = scmutil.revsingle(repo, destf)
778
778
779 if revf:
779 if revf:
780 rebaseset = scmutil.revrange(repo, revf)
780 rebaseset = scmutil.revrange(repo, revf)
781 if not rebaseset:
781 if not rebaseset:
782 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
782 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
783 return None, None
783 return None, None
784 elif srcf:
784 elif srcf:
785 src = scmutil.revrange(repo, [srcf])
785 src = scmutil.revrange(repo, [srcf])
786 if not src:
786 if not src:
787 ui.status(_('empty "source" revision set - nothing to rebase\n'))
787 ui.status(_('empty "source" revision set - nothing to rebase\n'))
788 return None, None
788 return None, None
789 rebaseset = repo.revs('(%ld)::', src)
789 rebaseset = repo.revs('(%ld)::', src)
790 assert rebaseset
790 assert rebaseset
791 else:
791 else:
792 base = scmutil.revrange(repo, [basef or '.'])
792 base = scmutil.revrange(repo, [basef or '.'])
793 if not base:
793 if not base:
794 ui.status(_('empty "base" revision set - '
794 ui.status(_('empty "base" revision set - '
795 "can't compute rebase set\n"))
795 "can't compute rebase set\n"))
796 return None, None
796 return None, None
797 if not destf:
797 if not destf:
798 dest = repo[_destrebase(repo, base, destspace=destspace)]
798 dest = repo[_destrebase(repo, base, destspace=destspace)]
799 destf = str(dest)
799 destf = str(dest)
800
800
801 roots = [] # selected children of branching points
801 roots = [] # selected children of branching points
802 bpbase = {} # {branchingpoint: [origbase]}
802 bpbase = {} # {branchingpoint: [origbase]}
803 for b in base: # group bases by branching points
803 for b in base: # group bases by branching points
804 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
804 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
805 bpbase[bp] = bpbase.get(bp, []) + [b]
805 bpbase[bp] = bpbase.get(bp, []) + [b]
806 if None in bpbase:
806 if None in bpbase:
807 # emulate the old behavior, showing "nothing to rebase" (a better
807 # emulate the old behavior, showing "nothing to rebase" (a better
808 # behavior may be abort with "cannot find branching point" error)
808 # behavior may be abort with "cannot find branching point" error)
809 bpbase.clear()
809 bpbase.clear()
810 for bp, bs in bpbase.iteritems(): # calculate roots
810 for bp, bs in bpbase.iteritems(): # calculate roots
811 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
811 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
812
812
813 rebaseset = repo.revs('%ld::', roots)
813 rebaseset = repo.revs('%ld::', roots)
814
814
815 if not rebaseset:
815 if not rebaseset:
816 # transform to list because smartsets are not comparable to
816 # transform to list because smartsets are not comparable to
817 # lists. This should be improved to honor laziness of
817 # lists. This should be improved to honor laziness of
818 # smartset.
818 # smartset.
819 if list(base) == [dest.rev()]:
819 if list(base) == [dest.rev()]:
820 if basef:
820 if basef:
821 ui.status(_('nothing to rebase - %s is both "base"'
821 ui.status(_('nothing to rebase - %s is both "base"'
822 ' and destination\n') % dest)
822 ' and destination\n') % dest)
823 else:
823 else:
824 ui.status(_('nothing to rebase - working directory '
824 ui.status(_('nothing to rebase - working directory '
825 'parent is also destination\n'))
825 'parent is also destination\n'))
826 elif not repo.revs('%ld - ::%d', base, dest):
826 elif not repo.revs('%ld - ::%d', base, dest):
827 if basef:
827 if basef:
828 ui.status(_('nothing to rebase - "base" %s is '
828 ui.status(_('nothing to rebase - "base" %s is '
829 'already an ancestor of destination '
829 'already an ancestor of destination '
830 '%s\n') %
830 '%s\n') %
831 ('+'.join(str(repo[r]) for r in base),
831 ('+'.join(str(repo[r]) for r in base),
832 dest))
832 dest))
833 else:
833 else:
834 ui.status(_('nothing to rebase - working '
834 ui.status(_('nothing to rebase - working '
835 'directory parent is already an '
835 'directory parent is already an '
836 'ancestor of destination %s\n') % dest)
836 'ancestor of destination %s\n') % dest)
837 else: # can it happen?
837 else: # can it happen?
838 ui.status(_('nothing to rebase from %s to %s\n') %
838 ui.status(_('nothing to rebase from %s to %s\n') %
839 ('+'.join(str(repo[r]) for r in base), dest))
839 ('+'.join(str(repo[r]) for r in base), dest))
840 return None, None
840 return None, None
841
841
842 if not destf:
842 if not destf:
843 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
843 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
844 destf = str(dest)
844 destf = str(dest)
845
845
846 return dest, rebaseset
846 return dest, rebaseset
847
847
848 def externalparent(repo, state, destancestors):
848 def externalparent(repo, state, destancestors):
849 """Return the revision that should be used as the second parent
849 """Return the revision that should be used as the second parent
850 when the revisions in state is collapsed on top of destancestors.
850 when the revisions in state is collapsed on top of destancestors.
851 Abort if there is more than one parent.
851 Abort if there is more than one parent.
852 """
852 """
853 parents = set()
853 parents = set()
854 source = min(state)
854 source = min(state)
855 for rev in state:
855 for rev in state:
856 if rev == source:
856 if rev == source:
857 continue
857 continue
858 for p in repo[rev].parents():
858 for p in repo[rev].parents():
859 if (p.rev() not in state
859 if (p.rev() not in state
860 and p.rev() not in destancestors):
860 and p.rev() not in destancestors):
861 parents.add(p.rev())
861 parents.add(p.rev())
862 if not parents:
862 if not parents:
863 return nullrev
863 return nullrev
864 if len(parents) == 1:
864 if len(parents) == 1:
865 return parents.pop()
865 return parents.pop()
866 raise error.Abort(_('unable to collapse on top of %s, there is more '
866 raise error.Abort(_('unable to collapse on top of %s, there is more '
867 'than one external parent: %s') %
867 'than one external parent: %s') %
868 (max(destancestors),
868 (max(destancestors),
869 ', '.join(str(p) for p in sorted(parents))))
869 ', '.join(str(p) for p in sorted(parents))))
870
870
871 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
871 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
872 keepbranches=False, date=None):
872 keepbranches=False, date=None):
873 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
873 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
874 but also store useful information in extra.
874 but also store useful information in extra.
875 Return node of committed revision.'''
875 Return node of committed revision.'''
876 repo.setparents(repo[p1].node(), repo[p2].node())
876 repo.setparents(repo[p1].node(), repo[p2].node())
877 ctx = repo[rev]
877 ctx = repo[rev]
878 if commitmsg is None:
878 if commitmsg is None:
879 commitmsg = ctx.description()
879 commitmsg = ctx.description()
880 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
880 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
881 extra = {'rebase_source': ctx.hex()}
881 extra = {'rebase_source': ctx.hex()}
882 if extrafn:
882 if extrafn:
883 extrafn(ctx, extra)
883 extrafn(ctx, extra)
884
884
885 destphase = max(ctx.phase(), phases.draft)
885 destphase = max(ctx.phase(), phases.draft)
886 overrides = {('phases', 'new-commit'): destphase}
886 overrides = {('phases', 'new-commit'): destphase}
887 with repo.ui.configoverride(overrides, 'rebase'):
887 with repo.ui.configoverride(overrides, 'rebase'):
888 if keepbranch:
888 if keepbranch:
889 repo.ui.setconfig('ui', 'allowemptycommit', True)
889 repo.ui.setconfig('ui', 'allowemptycommit', True)
890 # Commit might fail if unresolved files exist
890 # Commit might fail if unresolved files exist
891 if date is None:
891 if date is None:
892 date = ctx.date()
892 date = ctx.date()
893 newnode = repo.commit(text=commitmsg, user=ctx.user(),
893 newnode = repo.commit(text=commitmsg, user=ctx.user(),
894 date=date, extra=extra, editor=editor)
894 date=date, extra=extra, editor=editor)
895
895
896 repo.dirstate.setbranch(repo[newnode].branch())
896 repo.dirstate.setbranch(repo[newnode].branch())
897 return newnode
897 return newnode
898
898
899 def rebasenode(repo, rev, p1, base, state, collapse, dest):
899 def rebasenode(repo, rev, p1, base, state, collapse, dest):
900 'Rebase a single revision rev on top of p1 using base as merge ancestor'
900 'Rebase a single revision rev on top of p1 using base as merge ancestor'
901 # Merge phase
901 # Merge phase
902 # Update to destination and merge it with local
902 # Update to destination and merge it with local
903 if repo['.'].rev() != p1:
903 if repo['.'].rev() != p1:
904 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
904 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
905 mergemod.update(repo, p1, False, True)
905 mergemod.update(repo, p1, False, True)
906 else:
906 else:
907 repo.ui.debug(" already in destination\n")
907 repo.ui.debug(" already in destination\n")
908 repo.dirstate.write(repo.currenttransaction())
908 repo.dirstate.write(repo.currenttransaction())
909 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
909 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
910 if base is not None:
910 if base is not None:
911 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
911 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
912 # When collapsing in-place, the parent is the common ancestor, we
912 # When collapsing in-place, the parent is the common ancestor, we
913 # have to allow merging with it.
913 # have to allow merging with it.
914 stats = mergemod.update(repo, rev, True, True, base, collapse,
914 stats = mergemod.update(repo, rev, True, True, base, collapse,
915 labels=['dest', 'source'])
915 labels=['dest', 'source'])
916 if collapse:
916 if collapse:
917 copies.duplicatecopies(repo, rev, dest)
917 copies.duplicatecopies(repo, rev, dest)
918 else:
918 else:
919 # If we're not using --collapse, we need to
919 # If we're not using --collapse, we need to
920 # duplicate copies between the revision we're
920 # duplicate copies between the revision we're
921 # rebasing and its first parent, but *not*
921 # rebasing and its first parent, but *not*
922 # duplicate any copies that have already been
922 # duplicate any copies that have already been
923 # performed in the destination.
923 # performed in the destination.
924 p1rev = repo[rev].p1().rev()
924 p1rev = repo[rev].p1().rev()
925 copies.duplicatecopies(repo, rev, p1rev, skiprev=dest)
925 copies.duplicatecopies(repo, rev, p1rev, skiprev=dest)
926 return stats
926 return stats
927
927
928 def nearestrebased(repo, rev, state):
928 def nearestrebased(repo, rev, state):
929 """return the nearest ancestors of rev in the rebase result"""
929 """return the nearest ancestors of rev in the rebase result"""
930 rebased = [r for r in state if state[r] > nullmerge]
930 rebased = [r for r in state if state[r] > nullmerge]
931 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
931 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
932 if candidates:
932 if candidates:
933 return state[candidates.first()]
933 return state[candidates.first()]
934 else:
934 else:
935 return None
935 return None
936
936
937 def _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs, rebaseobsskipped):
937 def _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs, rebaseobsskipped):
938 """
938 """
939 Abort if rebase will create divergence or rebase is noop because of markers
939 Abort if rebase will create divergence or rebase is noop because of markers
940
940
941 `rebaseobsrevs`: set of obsolete revision in source
941 `rebaseobsrevs`: set of obsolete revision in source
942 `rebasesetrevs`: set of revisions to be rebased from source
942 `rebasesetrevs`: set of revisions to be rebased from source
943 `rebaseobsskipped`: set of revisions from source skipped because they have
943 `rebaseobsskipped`: set of revisions from source skipped because they have
944 successors in destination
944 successors in destination
945 """
945 """
946 # Obsolete node with successors not in dest leads to divergence
946 # Obsolete node with successors not in dest leads to divergence
947 divergenceok = ui.configbool('experimental',
947 divergenceok = ui.configbool('experimental',
948 'allowdivergence')
948 'allowdivergence')
949 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
949 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
950
950
951 if divergencebasecandidates and not divergenceok:
951 if divergencebasecandidates and not divergenceok:
952 divhashes = (str(repo[r])
952 divhashes = (str(repo[r])
953 for r in divergencebasecandidates)
953 for r in divergencebasecandidates)
954 msg = _("this rebase will cause "
954 msg = _("this rebase will cause "
955 "divergences from: %s")
955 "divergences from: %s")
956 h = _("to force the rebase please set "
956 h = _("to force the rebase please set "
957 "experimental.allowdivergence=True")
957 "experimental.allowdivergence=True")
958 raise error.Abort(msg % (",".join(divhashes),), hint=h)
958 raise error.Abort(msg % (",".join(divhashes),), hint=h)
959
959
960 def defineparents(repo, rev, dest, state, destancestors,
960 def defineparents(repo, rev, dest, state, destancestors,
961 obsoletenotrebased):
961 obsoletenotrebased):
962 'Return the new parent relationship of the revision that will be rebased'
962 'Return the new parent relationship of the revision that will be rebased'
963 parents = repo[rev].parents()
963 parents = repo[rev].parents()
964 p1 = p2 = nullrev
964 p1 = p2 = nullrev
965 rp1 = None
965 rp1 = None
966
966
967 p1n = parents[0].rev()
967 p1n = parents[0].rev()
968 if p1n in destancestors:
968 if p1n in destancestors:
969 p1 = dest
969 p1 = dest
970 elif p1n in state:
970 elif p1n in state:
971 if state[p1n] == nullmerge:
971 if state[p1n] == nullmerge:
972 p1 = dest
972 p1 = dest
973 elif state[p1n] in revskipped:
973 elif state[p1n] in revskipped:
974 p1 = nearestrebased(repo, p1n, state)
974 p1 = nearestrebased(repo, p1n, state)
975 if p1 is None:
975 if p1 is None:
976 p1 = dest
976 p1 = dest
977 else:
977 else:
978 p1 = state[p1n]
978 p1 = state[p1n]
979 else: # p1n external
979 else: # p1n external
980 p1 = dest
980 p1 = dest
981 p2 = p1n
981 p2 = p1n
982
982
983 if len(parents) == 2 and parents[1].rev() not in destancestors:
983 if len(parents) == 2 and parents[1].rev() not in destancestors:
984 p2n = parents[1].rev()
984 p2n = parents[1].rev()
985 # interesting second parent
985 # interesting second parent
986 if p2n in state:
986 if p2n in state:
987 if p1 == dest: # p1n in destancestors or external
987 if p1 == dest: # p1n in destancestors or external
988 p1 = state[p2n]
988 p1 = state[p2n]
989 if p1 == revprecursor:
989 if p1 == revprecursor:
990 rp1 = obsoletenotrebased[p2n]
990 rp1 = obsoletenotrebased[p2n]
991 elif state[p2n] in revskipped:
991 elif state[p2n] in revskipped:
992 p2 = nearestrebased(repo, p2n, state)
992 p2 = nearestrebased(repo, p2n, state)
993 if p2 is None:
993 if p2 is None:
994 # no ancestors rebased yet, detach
994 # no ancestors rebased yet, detach
995 p2 = dest
995 p2 = dest
996 else:
996 else:
997 p2 = state[p2n]
997 p2 = state[p2n]
998 else: # p2n external
998 else: # p2n external
999 if p2 != nullrev: # p1n external too => rev is a merged revision
999 if p2 != nullrev: # p1n external too => rev is a merged revision
1000 raise error.Abort(_('cannot use revision %d as base, result '
1000 raise error.Abort(_('cannot use revision %d as base, result '
1001 'would have 3 parents') % rev)
1001 'would have 3 parents') % rev)
1002 p2 = p2n
1002 p2 = p2n
1003 repo.ui.debug(" future parents are %d and %d\n" %
1003 repo.ui.debug(" future parents are %d and %d\n" %
1004 (repo[rp1 or p1].rev(), repo[p2].rev()))
1004 (repo[rp1 or p1].rev(), repo[p2].rev()))
1005
1005
1006 if not any(p.rev() in state for p in parents):
1006 if not any(p.rev() in state for p in parents):
1007 # Case (1) root changeset of a non-detaching rebase set.
1007 # Case (1) root changeset of a non-detaching rebase set.
1008 # Let the merge mechanism find the base itself.
1008 # Let the merge mechanism find the base itself.
1009 base = None
1009 base = None
1010 elif not repo[rev].p2():
1010 elif not repo[rev].p2():
1011 # Case (2) detaching the node with a single parent, use this parent
1011 # Case (2) detaching the node with a single parent, use this parent
1012 base = repo[rev].p1().rev()
1012 base = repo[rev].p1().rev()
1013 else:
1013 else:
1014 # Assuming there is a p1, this is the case where there also is a p2.
1014 # Assuming there is a p1, this is the case where there also is a p2.
1015 # We are thus rebasing a merge and need to pick the right merge base.
1015 # We are thus rebasing a merge and need to pick the right merge base.
1016 #
1016 #
1017 # Imagine we have:
1017 # Imagine we have:
1018 # - M: current rebase revision in this step
1018 # - M: current rebase revision in this step
1019 # - A: one parent of M
1019 # - A: one parent of M
1020 # - B: other parent of M
1020 # - B: other parent of M
1021 # - D: destination of this merge step (p1 var)
1021 # - D: destination of this merge step (p1 var)
1022 #
1022 #
1023 # Consider the case where D is a descendant of A or B and the other is
1023 # Consider the case where D is a descendant of A or B and the other is
1024 # 'outside'. In this case, the right merge base is the D ancestor.
1024 # 'outside'. In this case, the right merge base is the D ancestor.
1025 #
1025 #
1026 # An informal proof, assuming A is 'outside' and B is the D ancestor:
1026 # An informal proof, assuming A is 'outside' and B is the D ancestor:
1027 #
1027 #
1028 # If we pick B as the base, the merge involves:
1028 # If we pick B as the base, the merge involves:
1029 # - changes from B to M (actual changeset payload)
1029 # - changes from B to M (actual changeset payload)
1030 # - changes from B to D (induced by rebase) as D is a rebased
1030 # - changes from B to D (induced by rebase) as D is a rebased
1031 # version of B)
1031 # version of B)
1032 # Which exactly represent the rebase operation.
1032 # Which exactly represent the rebase operation.
1033 #
1033 #
1034 # If we pick A as the base, the merge involves:
1034 # If we pick A as the base, the merge involves:
1035 # - changes from A to M (actual changeset payload)
1035 # - changes from A to M (actual changeset payload)
1036 # - changes from A to D (with include changes between unrelated A and B
1036 # - changes from A to D (with include changes between unrelated A and B
1037 # plus changes induced by rebase)
1037 # plus changes induced by rebase)
1038 # Which does not represent anything sensible and creates a lot of
1038 # Which does not represent anything sensible and creates a lot of
1039 # conflicts. A is thus not the right choice - B is.
1039 # conflicts. A is thus not the right choice - B is.
1040 #
1040 #
1041 # Note: The base found in this 'proof' is only correct in the specified
1041 # Note: The base found in this 'proof' is only correct in the specified
1042 # case. This base does not make sense if is not D a descendant of A or B
1042 # case. This base does not make sense if is not D a descendant of A or B
1043 # or if the other is not parent 'outside' (especially not if the other
1043 # or if the other is not parent 'outside' (especially not if the other
1044 # parent has been rebased). The current implementation does not
1044 # parent has been rebased). The current implementation does not
1045 # make it feasible to consider different cases separately. In these
1045 # make it feasible to consider different cases separately. In these
1046 # other cases we currently just leave it to the user to correctly
1046 # other cases we currently just leave it to the user to correctly
1047 # resolve an impossible merge using a wrong ancestor.
1047 # resolve an impossible merge using a wrong ancestor.
1048 #
1048 #
1049 # xx, p1 could be -4, and both parents could probably be -4...
1049 # xx, p1 could be -4, and both parents could probably be -4...
1050 for p in repo[rev].parents():
1050 for p in repo[rev].parents():
1051 if state.get(p.rev()) == p1:
1051 if state.get(p.rev()) == p1:
1052 base = p.rev()
1052 base = p.rev()
1053 break
1053 break
1054 else: # fallback when base not found
1054 else: # fallback when base not found
1055 base = None
1055 base = None
1056
1056
1057 # Raise because this function is called wrong (see issue 4106)
1057 # Raise because this function is called wrong (see issue 4106)
1058 raise AssertionError('no base found to rebase on '
1058 raise AssertionError('no base found to rebase on '
1059 '(defineparents called wrong)')
1059 '(defineparents called wrong)')
1060 return rp1 or p1, p2, base
1060 return rp1 or p1, p2, base
1061
1061
1062 def isagitpatch(repo, patchname):
1062 def isagitpatch(repo, patchname):
1063 'Return true if the given patch is in git format'
1063 'Return true if the given patch is in git format'
1064 mqpatch = os.path.join(repo.mq.path, patchname)
1064 mqpatch = os.path.join(repo.mq.path, patchname)
1065 for line in patch.linereader(file(mqpatch, 'rb')):
1065 for line in patch.linereader(file(mqpatch, 'rb')):
1066 if line.startswith('diff --git'):
1066 if line.startswith('diff --git'):
1067 return True
1067 return True
1068 return False
1068 return False
1069
1069
1070 def updatemq(repo, state, skipped, **opts):
1070 def updatemq(repo, state, skipped, **opts):
1071 'Update rebased mq patches - finalize and then import them'
1071 'Update rebased mq patches - finalize and then import them'
1072 mqrebase = {}
1072 mqrebase = {}
1073 mq = repo.mq
1073 mq = repo.mq
1074 original_series = mq.fullseries[:]
1074 original_series = mq.fullseries[:]
1075 skippedpatches = set()
1075 skippedpatches = set()
1076
1076
1077 for p in mq.applied:
1077 for p in mq.applied:
1078 rev = repo[p.node].rev()
1078 rev = repo[p.node].rev()
1079 if rev in state:
1079 if rev in state:
1080 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1080 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1081 (rev, p.name))
1081 (rev, p.name))
1082 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1082 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1083 else:
1083 else:
1084 # Applied but not rebased, not sure this should happen
1084 # Applied but not rebased, not sure this should happen
1085 skippedpatches.add(p.name)
1085 skippedpatches.add(p.name)
1086
1086
1087 if mqrebase:
1087 if mqrebase:
1088 mq.finish(repo, mqrebase.keys())
1088 mq.finish(repo, mqrebase.keys())
1089
1089
1090 # We must start import from the newest revision
1090 # We must start import from the newest revision
1091 for rev in sorted(mqrebase, reverse=True):
1091 for rev in sorted(mqrebase, reverse=True):
1092 if rev not in skipped:
1092 if rev not in skipped:
1093 name, isgit = mqrebase[rev]
1093 name, isgit = mqrebase[rev]
1094 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1094 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1095 (name, state[rev], repo[state[rev]]))
1095 (name, state[rev], repo[state[rev]]))
1096 mq.qimport(repo, (), patchname=name, git=isgit,
1096 mq.qimport(repo, (), patchname=name, git=isgit,
1097 rev=[str(state[rev])])
1097 rev=[str(state[rev])])
1098 else:
1098 else:
1099 # Rebased and skipped
1099 # Rebased and skipped
1100 skippedpatches.add(mqrebase[rev][0])
1100 skippedpatches.add(mqrebase[rev][0])
1101
1101
1102 # Patches were either applied and rebased and imported in
1102 # Patches were either applied and rebased and imported in
1103 # order, applied and removed or unapplied. Discard the removed
1103 # order, applied and removed or unapplied. Discard the removed
1104 # ones while preserving the original series order and guards.
1104 # ones while preserving the original series order and guards.
1105 newseries = [s for s in original_series
1105 newseries = [s for s in original_series
1106 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1106 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1107 mq.fullseries[:] = newseries
1107 mq.fullseries[:] = newseries
1108 mq.seriesdirty = True
1108 mq.seriesdirty = True
1109 mq.savedirty()
1109 mq.savedirty()
1110
1110
1111 def updatebookmarks(repo, destnode, nstate, originalbookmarks, tr):
1111 def updatebookmarks(repo, destnode, nstate, originalbookmarks, tr):
1112 'Move bookmarks to their correct changesets, and delete divergent ones'
1112 'Move bookmarks to their correct changesets, and delete divergent ones'
1113 marks = repo._bookmarks
1113 marks = repo._bookmarks
1114 for k, v in originalbookmarks.iteritems():
1114 for k, v in originalbookmarks.iteritems():
1115 if v in nstate:
1115 if v in nstate:
1116 # update the bookmarks for revs that have moved
1116 # update the bookmarks for revs that have moved
1117 marks[k] = nstate[v]
1117 marks[k] = nstate[v]
1118 bookmarks.deletedivergent(repo, [destnode], k)
1118 bookmarks.deletedivergent(repo, [destnode], k)
1119 marks.recordchange(tr)
1119 marks.recordchange(tr)
1120
1120
1121 def storecollapsemsg(repo, collapsemsg):
1121 def storecollapsemsg(repo, collapsemsg):
1122 'Store the collapse message to allow recovery'
1122 'Store the collapse message to allow recovery'
1123 collapsemsg = collapsemsg or ''
1123 collapsemsg = collapsemsg or ''
1124 f = repo.vfs("last-message.txt", "w")
1124 f = repo.vfs("last-message.txt", "w")
1125 f.write("%s\n" % collapsemsg)
1125 f.write("%s\n" % collapsemsg)
1126 f.close()
1126 f.close()
1127
1127
1128 def clearcollapsemsg(repo):
1128 def clearcollapsemsg(repo):
1129 'Remove collapse message file'
1129 'Remove collapse message file'
1130 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1130 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1131
1131
1132 def restorecollapsemsg(repo, isabort):
1132 def restorecollapsemsg(repo, isabort):
1133 'Restore previously stored collapse message'
1133 'Restore previously stored collapse message'
1134 try:
1134 try:
1135 f = repo.vfs("last-message.txt")
1135 f = repo.vfs("last-message.txt")
1136 collapsemsg = f.readline().strip()
1136 collapsemsg = f.readline().strip()
1137 f.close()
1137 f.close()
1138 except IOError as err:
1138 except IOError as err:
1139 if err.errno != errno.ENOENT:
1139 if err.errno != errno.ENOENT:
1140 raise
1140 raise
1141 if isabort:
1141 if isabort:
1142 # Oh well, just abort like normal
1142 # Oh well, just abort like normal
1143 collapsemsg = ''
1143 collapsemsg = ''
1144 else:
1144 else:
1145 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1145 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1146 return collapsemsg
1146 return collapsemsg
1147
1147
1148 def clearstatus(repo):
1148 def clearstatus(repo):
1149 'Remove the status files'
1149 'Remove the status files'
1150 _clearrebasesetvisibiliy(repo)
1150 _clearrebasesetvisibiliy(repo)
1151 # Make sure the active transaction won't write the state file
1152 tr = repo.currenttransaction()
1153 if tr:
1154 tr.removefilegenerator('rebasestate')
1151 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1155 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1152
1156
1153 def needupdate(repo, state):
1157 def needupdate(repo, state):
1154 '''check whether we should `update --clean` away from a merge, or if
1158 '''check whether we should `update --clean` away from a merge, or if
1155 somehow the working dir got forcibly updated, e.g. by older hg'''
1159 somehow the working dir got forcibly updated, e.g. by older hg'''
1156 parents = [p.rev() for p in repo[None].parents()]
1160 parents = [p.rev() for p in repo[None].parents()]
1157
1161
1158 # Are we in a merge state at all?
1162 # Are we in a merge state at all?
1159 if len(parents) < 2:
1163 if len(parents) < 2:
1160 return False
1164 return False
1161
1165
1162 # We should be standing on the first as-of-yet unrebased commit.
1166 # We should be standing on the first as-of-yet unrebased commit.
1163 firstunrebased = min([old for old, new in state.iteritems()
1167 firstunrebased = min([old for old, new in state.iteritems()
1164 if new == nullrev])
1168 if new == nullrev])
1165 if firstunrebased in parents:
1169 if firstunrebased in parents:
1166 return True
1170 return True
1167
1171
1168 return False
1172 return False
1169
1173
1170 def abort(repo, originalwd, dest, state, activebookmark=None):
1174 def abort(repo, originalwd, dest, state, activebookmark=None):
1171 '''Restore the repository to its original state. Additional args:
1175 '''Restore the repository to its original state. Additional args:
1172
1176
1173 activebookmark: the name of the bookmark that should be active after the
1177 activebookmark: the name of the bookmark that should be active after the
1174 restore'''
1178 restore'''
1175
1179
1176 try:
1180 try:
1177 # If the first commits in the rebased set get skipped during the rebase,
1181 # If the first commits in the rebased set get skipped during the rebase,
1178 # their values within the state mapping will be the dest rev id. The
1182 # their values within the state mapping will be the dest rev id. The
1179 # dstates list must must not contain the dest rev (issue4896)
1183 # dstates list must must not contain the dest rev (issue4896)
1180 dstates = [s for s in state.values() if s >= 0 and s != dest]
1184 dstates = [s for s in state.values() if s >= 0 and s != dest]
1181 immutable = [d for d in dstates if not repo[d].mutable()]
1185 immutable = [d for d in dstates if not repo[d].mutable()]
1182 cleanup = True
1186 cleanup = True
1183 if immutable:
1187 if immutable:
1184 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1188 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1185 % ', '.join(str(repo[r]) for r in immutable),
1189 % ', '.join(str(repo[r]) for r in immutable),
1186 hint=_("see 'hg help phases' for details"))
1190 hint=_("see 'hg help phases' for details"))
1187 cleanup = False
1191 cleanup = False
1188
1192
1189 descendants = set()
1193 descendants = set()
1190 if dstates:
1194 if dstates:
1191 descendants = set(repo.changelog.descendants(dstates))
1195 descendants = set(repo.changelog.descendants(dstates))
1192 if descendants - set(dstates):
1196 if descendants - set(dstates):
1193 repo.ui.warn(_("warning: new changesets detected on destination "
1197 repo.ui.warn(_("warning: new changesets detected on destination "
1194 "branch, can't strip\n"))
1198 "branch, can't strip\n"))
1195 cleanup = False
1199 cleanup = False
1196
1200
1197 if cleanup:
1201 if cleanup:
1198 shouldupdate = False
1202 shouldupdate = False
1199 rebased = filter(lambda x: x >= 0 and x != dest, state.values())
1203 rebased = filter(lambda x: x >= 0 and x != dest, state.values())
1200 if rebased:
1204 if rebased:
1201 strippoints = [
1205 strippoints = [
1202 c.node() for c in repo.set('roots(%ld)', rebased)]
1206 c.node() for c in repo.set('roots(%ld)', rebased)]
1203
1207
1204 updateifonnodes = set(rebased)
1208 updateifonnodes = set(rebased)
1205 updateifonnodes.add(dest)
1209 updateifonnodes.add(dest)
1206 updateifonnodes.add(originalwd)
1210 updateifonnodes.add(originalwd)
1207 shouldupdate = repo['.'].rev() in updateifonnodes
1211 shouldupdate = repo['.'].rev() in updateifonnodes
1208
1212
1209 # Update away from the rebase if necessary
1213 # Update away from the rebase if necessary
1210 if shouldupdate or needupdate(repo, state):
1214 if shouldupdate or needupdate(repo, state):
1211 mergemod.update(repo, originalwd, False, True)
1215 mergemod.update(repo, originalwd, False, True)
1212
1216
1213 # Strip from the first rebased revision
1217 # Strip from the first rebased revision
1214 if rebased:
1218 if rebased:
1215 # no backup of rebased cset versions needed
1219 # no backup of rebased cset versions needed
1216 repair.strip(repo.ui, repo, strippoints)
1220 repair.strip(repo.ui, repo, strippoints)
1217
1221
1218 if activebookmark and activebookmark in repo._bookmarks:
1222 if activebookmark and activebookmark in repo._bookmarks:
1219 bookmarks.activate(repo, activebookmark)
1223 bookmarks.activate(repo, activebookmark)
1220
1224
1221 finally:
1225 finally:
1222 clearstatus(repo)
1226 clearstatus(repo)
1223 clearcollapsemsg(repo)
1227 clearcollapsemsg(repo)
1224 repo.ui.warn(_('rebase aborted\n'))
1228 repo.ui.warn(_('rebase aborted\n'))
1225 return 0
1229 return 0
1226
1230
1227 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1231 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1228 '''Define which revisions are going to be rebased and where
1232 '''Define which revisions are going to be rebased and where
1229
1233
1230 repo: repo
1234 repo: repo
1231 dest: context
1235 dest: context
1232 rebaseset: set of rev
1236 rebaseset: set of rev
1233 '''
1237 '''
1234 originalwd = repo['.'].rev()
1238 originalwd = repo['.'].rev()
1235 _setrebasesetvisibility(repo, set(rebaseset) | {originalwd})
1239 _setrebasesetvisibility(repo, set(rebaseset) | {originalwd})
1236
1240
1237 # This check isn't strictly necessary, since mq detects commits over an
1241 # This check isn't strictly necessary, since mq detects commits over an
1238 # applied patch. But it prevents messing up the working directory when
1242 # applied patch. But it prevents messing up the working directory when
1239 # a partially completed rebase is blocked by mq.
1243 # a partially completed rebase is blocked by mq.
1240 if 'qtip' in repo.tags() and (dest.node() in
1244 if 'qtip' in repo.tags() and (dest.node() in
1241 [s.node for s in repo.mq.applied]):
1245 [s.node for s in repo.mq.applied]):
1242 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1246 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1243
1247
1244 roots = list(repo.set('roots(%ld)', rebaseset))
1248 roots = list(repo.set('roots(%ld)', rebaseset))
1245 if not roots:
1249 if not roots:
1246 raise error.Abort(_('no matching revisions'))
1250 raise error.Abort(_('no matching revisions'))
1247 roots.sort()
1251 roots.sort()
1248 state = dict.fromkeys(rebaseset, revtodo)
1252 state = dict.fromkeys(rebaseset, revtodo)
1249 detachset = set()
1253 detachset = set()
1250 emptyrebase = True
1254 emptyrebase = True
1251 for root in roots:
1255 for root in roots:
1252 commonbase = root.ancestor(dest)
1256 commonbase = root.ancestor(dest)
1253 if commonbase == root:
1257 if commonbase == root:
1254 raise error.Abort(_('source is ancestor of destination'))
1258 raise error.Abort(_('source is ancestor of destination'))
1255 if commonbase == dest:
1259 if commonbase == dest:
1256 wctx = repo[None]
1260 wctx = repo[None]
1257 if dest == wctx.p1():
1261 if dest == wctx.p1():
1258 # when rebasing to '.', it will use the current wd branch name
1262 # when rebasing to '.', it will use the current wd branch name
1259 samebranch = root.branch() == wctx.branch()
1263 samebranch = root.branch() == wctx.branch()
1260 else:
1264 else:
1261 samebranch = root.branch() == dest.branch()
1265 samebranch = root.branch() == dest.branch()
1262 if not collapse and samebranch and dest in root.parents():
1266 if not collapse and samebranch and dest in root.parents():
1263 # mark the revision as done by setting its new revision
1267 # mark the revision as done by setting its new revision
1264 # equal to its old (current) revisions
1268 # equal to its old (current) revisions
1265 state[root.rev()] = root.rev()
1269 state[root.rev()] = root.rev()
1266 repo.ui.debug('source is a child of destination\n')
1270 repo.ui.debug('source is a child of destination\n')
1267 continue
1271 continue
1268
1272
1269 emptyrebase = False
1273 emptyrebase = False
1270 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1274 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1271 # Rebase tries to turn <dest> into a parent of <root> while
1275 # Rebase tries to turn <dest> into a parent of <root> while
1272 # preserving the number of parents of rebased changesets:
1276 # preserving the number of parents of rebased changesets:
1273 #
1277 #
1274 # - A changeset with a single parent will always be rebased as a
1278 # - A changeset with a single parent will always be rebased as a
1275 # changeset with a single parent.
1279 # changeset with a single parent.
1276 #
1280 #
1277 # - A merge will be rebased as merge unless its parents are both
1281 # - A merge will be rebased as merge unless its parents are both
1278 # ancestors of <dest> or are themselves in the rebased set and
1282 # ancestors of <dest> or are themselves in the rebased set and
1279 # pruned while rebased.
1283 # pruned while rebased.
1280 #
1284 #
1281 # If one parent of <root> is an ancestor of <dest>, the rebased
1285 # If one parent of <root> is an ancestor of <dest>, the rebased
1282 # version of this parent will be <dest>. This is always true with
1286 # version of this parent will be <dest>. This is always true with
1283 # --base option.
1287 # --base option.
1284 #
1288 #
1285 # Otherwise, we need to *replace* the original parents with
1289 # Otherwise, we need to *replace* the original parents with
1286 # <dest>. This "detaches" the rebased set from its former location
1290 # <dest>. This "detaches" the rebased set from its former location
1287 # and rebases it onto <dest>. Changes introduced by ancestors of
1291 # and rebases it onto <dest>. Changes introduced by ancestors of
1288 # <root> not common with <dest> (the detachset, marked as
1292 # <root> not common with <dest> (the detachset, marked as
1289 # nullmerge) are "removed" from the rebased changesets.
1293 # nullmerge) are "removed" from the rebased changesets.
1290 #
1294 #
1291 # - If <root> has a single parent, set it to <dest>.
1295 # - If <root> has a single parent, set it to <dest>.
1292 #
1296 #
1293 # - If <root> is a merge, we cannot decide which parent to
1297 # - If <root> is a merge, we cannot decide which parent to
1294 # replace, the rebase operation is not clearly defined.
1298 # replace, the rebase operation is not clearly defined.
1295 #
1299 #
1296 # The table below sums up this behavior:
1300 # The table below sums up this behavior:
1297 #
1301 #
1298 # +------------------+----------------------+-------------------------+
1302 # +------------------+----------------------+-------------------------+
1299 # | | one parent | merge |
1303 # | | one parent | merge |
1300 # +------------------+----------------------+-------------------------+
1304 # +------------------+----------------------+-------------------------+
1301 # | parent in | new parent is <dest> | parents in ::<dest> are |
1305 # | parent in | new parent is <dest> | parents in ::<dest> are |
1302 # | ::<dest> | | remapped to <dest> |
1306 # | ::<dest> | | remapped to <dest> |
1303 # +------------------+----------------------+-------------------------+
1307 # +------------------+----------------------+-------------------------+
1304 # | unrelated source | new parent is <dest> | ambiguous, abort |
1308 # | unrelated source | new parent is <dest> | ambiguous, abort |
1305 # +------------------+----------------------+-------------------------+
1309 # +------------------+----------------------+-------------------------+
1306 #
1310 #
1307 # The actual abort is handled by `defineparents`
1311 # The actual abort is handled by `defineparents`
1308 if len(root.parents()) <= 1:
1312 if len(root.parents()) <= 1:
1309 # ancestors of <root> not ancestors of <dest>
1313 # ancestors of <root> not ancestors of <dest>
1310 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1314 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1311 [root.rev()]))
1315 [root.rev()]))
1312 if emptyrebase:
1316 if emptyrebase:
1313 return None
1317 return None
1314 for rev in sorted(state):
1318 for rev in sorted(state):
1315 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1319 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1316 # if all parents of this revision are done, then so is this revision
1320 # if all parents of this revision are done, then so is this revision
1317 if parents and all((state.get(p) == p for p in parents)):
1321 if parents and all((state.get(p) == p for p in parents)):
1318 state[rev] = rev
1322 state[rev] = rev
1319 for r in detachset:
1323 for r in detachset:
1320 if r not in state:
1324 if r not in state:
1321 state[r] = nullmerge
1325 state[r] = nullmerge
1322 if len(roots) > 1:
1326 if len(roots) > 1:
1323 # If we have multiple roots, we may have "hole" in the rebase set.
1327 # If we have multiple roots, we may have "hole" in the rebase set.
1324 # Rebase roots that descend from those "hole" should not be detached as
1328 # Rebase roots that descend from those "hole" should not be detached as
1325 # other root are. We use the special `revignored` to inform rebase that
1329 # other root are. We use the special `revignored` to inform rebase that
1326 # the revision should be ignored but that `defineparents` should search
1330 # the revision should be ignored but that `defineparents` should search
1327 # a rebase destination that make sense regarding rebased topology.
1331 # a rebase destination that make sense regarding rebased topology.
1328 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1332 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1329 for ignored in set(rebasedomain) - set(rebaseset):
1333 for ignored in set(rebasedomain) - set(rebaseset):
1330 state[ignored] = revignored
1334 state[ignored] = revignored
1331 for r in obsoletenotrebased:
1335 for r in obsoletenotrebased:
1332 if obsoletenotrebased[r] is None:
1336 if obsoletenotrebased[r] is None:
1333 state[r] = revpruned
1337 state[r] = revpruned
1334 else:
1338 else:
1335 state[r] = revprecursor
1339 state[r] = revprecursor
1336 return originalwd, dest.rev(), state
1340 return originalwd, dest.rev(), state
1337
1341
1338 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1342 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1339 """dispose of rebased revision at the end of the rebase
1343 """dispose of rebased revision at the end of the rebase
1340
1344
1341 If `collapsedas` is not None, the rebase was a collapse whose result if the
1345 If `collapsedas` is not None, the rebase was a collapse whose result if the
1342 `collapsedas` node."""
1346 `collapsedas` node."""
1343 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1347 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1344 markers = []
1348 markers = []
1345 for rev, newrev in sorted(state.items()):
1349 for rev, newrev in sorted(state.items()):
1346 if newrev >= 0 and newrev != rev:
1350 if newrev >= 0 and newrev != rev:
1347 if rev in skipped:
1351 if rev in skipped:
1348 succs = ()
1352 succs = ()
1349 elif collapsedas is not None:
1353 elif collapsedas is not None:
1350 succs = (repo[collapsedas],)
1354 succs = (repo[collapsedas],)
1351 else:
1355 else:
1352 succs = (repo[newrev],)
1356 succs = (repo[newrev],)
1353 markers.append((repo[rev], succs))
1357 markers.append((repo[rev], succs))
1354 if markers:
1358 if markers:
1355 obsolete.createmarkers(repo, markers, operation='rebase')
1359 obsolete.createmarkers(repo, markers, operation='rebase')
1356 else:
1360 else:
1357 rebased = [rev for rev in state
1361 rebased = [rev for rev in state
1358 if state[rev] > nullmerge and state[rev] != rev]
1362 if state[rev] > nullmerge and state[rev] != rev]
1359 if rebased:
1363 if rebased:
1360 stripped = []
1364 stripped = []
1361 for root in repo.set('roots(%ld)', rebased):
1365 for root in repo.set('roots(%ld)', rebased):
1362 if set(repo.changelog.descendants([root.rev()])) - set(state):
1366 if set(repo.changelog.descendants([root.rev()])) - set(state):
1363 ui.warn(_("warning: new changesets detected "
1367 ui.warn(_("warning: new changesets detected "
1364 "on source branch, not stripping\n"))
1368 "on source branch, not stripping\n"))
1365 else:
1369 else:
1366 stripped.append(root.node())
1370 stripped.append(root.node())
1367 if stripped:
1371 if stripped:
1368 # backup the old csets by default
1372 # backup the old csets by default
1369 repair.strip(ui, repo, stripped, "all")
1373 repair.strip(ui, repo, stripped, "all")
1370
1374
1371
1375
1372 def pullrebase(orig, ui, repo, *args, **opts):
1376 def pullrebase(orig, ui, repo, *args, **opts):
1373 'Call rebase after pull if the latter has been invoked with --rebase'
1377 'Call rebase after pull if the latter has been invoked with --rebase'
1374 ret = None
1378 ret = None
1375 if opts.get('rebase'):
1379 if opts.get('rebase'):
1376 if ui.configbool('commands', 'rebase.requiredest'):
1380 if ui.configbool('commands', 'rebase.requiredest'):
1377 msg = _('rebase destination required by configuration')
1381 msg = _('rebase destination required by configuration')
1378 hint = _('use hg pull followed by hg rebase -d DEST')
1382 hint = _('use hg pull followed by hg rebase -d DEST')
1379 raise error.Abort(msg, hint=hint)
1383 raise error.Abort(msg, hint=hint)
1380
1384
1381 with repo.wlock(), repo.lock():
1385 with repo.wlock(), repo.lock():
1382 if opts.get('update'):
1386 if opts.get('update'):
1383 del opts['update']
1387 del opts['update']
1384 ui.debug('--update and --rebase are not compatible, ignoring '
1388 ui.debug('--update and --rebase are not compatible, ignoring '
1385 'the update flag\n')
1389 'the update flag\n')
1386
1390
1387 cmdutil.checkunfinished(repo)
1391 cmdutil.checkunfinished(repo)
1388 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1392 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1389 'please commit or shelve your changes first'))
1393 'please commit or shelve your changes first'))
1390
1394
1391 revsprepull = len(repo)
1395 revsprepull = len(repo)
1392 origpostincoming = commands.postincoming
1396 origpostincoming = commands.postincoming
1393 def _dummy(*args, **kwargs):
1397 def _dummy(*args, **kwargs):
1394 pass
1398 pass
1395 commands.postincoming = _dummy
1399 commands.postincoming = _dummy
1396 try:
1400 try:
1397 ret = orig(ui, repo, *args, **opts)
1401 ret = orig(ui, repo, *args, **opts)
1398 finally:
1402 finally:
1399 commands.postincoming = origpostincoming
1403 commands.postincoming = origpostincoming
1400 revspostpull = len(repo)
1404 revspostpull = len(repo)
1401 if revspostpull > revsprepull:
1405 if revspostpull > revsprepull:
1402 # --rev option from pull conflict with rebase own --rev
1406 # --rev option from pull conflict with rebase own --rev
1403 # dropping it
1407 # dropping it
1404 if 'rev' in opts:
1408 if 'rev' in opts:
1405 del opts['rev']
1409 del opts['rev']
1406 # positional argument from pull conflicts with rebase's own
1410 # positional argument from pull conflicts with rebase's own
1407 # --source.
1411 # --source.
1408 if 'source' in opts:
1412 if 'source' in opts:
1409 del opts['source']
1413 del opts['source']
1410 # revsprepull is the len of the repo, not revnum of tip.
1414 # revsprepull is the len of the repo, not revnum of tip.
1411 destspace = list(repo.changelog.revs(start=revsprepull))
1415 destspace = list(repo.changelog.revs(start=revsprepull))
1412 opts['_destspace'] = destspace
1416 opts['_destspace'] = destspace
1413 try:
1417 try:
1414 rebase(ui, repo, **opts)
1418 rebase(ui, repo, **opts)
1415 except error.NoMergeDestAbort:
1419 except error.NoMergeDestAbort:
1416 # we can maybe update instead
1420 # we can maybe update instead
1417 rev, _a, _b = destutil.destupdate(repo)
1421 rev, _a, _b = destutil.destupdate(repo)
1418 if rev == repo['.'].rev():
1422 if rev == repo['.'].rev():
1419 ui.status(_('nothing to rebase\n'))
1423 ui.status(_('nothing to rebase\n'))
1420 else:
1424 else:
1421 ui.status(_('nothing to rebase - updating instead\n'))
1425 ui.status(_('nothing to rebase - updating instead\n'))
1422 # not passing argument to get the bare update behavior
1426 # not passing argument to get the bare update behavior
1423 # with warning and trumpets
1427 # with warning and trumpets
1424 commands.update(ui, repo)
1428 commands.update(ui, repo)
1425 else:
1429 else:
1426 if opts.get('tool'):
1430 if opts.get('tool'):
1427 raise error.Abort(_('--tool can only be used with --rebase'))
1431 raise error.Abort(_('--tool can only be used with --rebase'))
1428 ret = orig(ui, repo, *args, **opts)
1432 ret = orig(ui, repo, *args, **opts)
1429
1433
1430 return ret
1434 return ret
1431
1435
1432 def _setrebasesetvisibility(repo, revs):
1436 def _setrebasesetvisibility(repo, revs):
1433 """store the currently rebased set on the repo object
1437 """store the currently rebased set on the repo object
1434
1438
1435 This is used by another function to prevent rebased revision to because
1439 This is used by another function to prevent rebased revision to because
1436 hidden (see issue4504)"""
1440 hidden (see issue4504)"""
1437 repo = repo.unfiltered()
1441 repo = repo.unfiltered()
1438 repo._rebaseset = revs
1442 repo._rebaseset = revs
1439 # invalidate cache if visibility changes
1443 # invalidate cache if visibility changes
1440 hiddens = repo.filteredrevcache.get('visible', set())
1444 hiddens = repo.filteredrevcache.get('visible', set())
1441 if revs & hiddens:
1445 if revs & hiddens:
1442 repo.invalidatevolatilesets()
1446 repo.invalidatevolatilesets()
1443
1447
1444 def _clearrebasesetvisibiliy(repo):
1448 def _clearrebasesetvisibiliy(repo):
1445 """remove rebaseset data from the repo"""
1449 """remove rebaseset data from the repo"""
1446 repo = repo.unfiltered()
1450 repo = repo.unfiltered()
1447 if '_rebaseset' in vars(repo):
1451 if '_rebaseset' in vars(repo):
1448 del repo._rebaseset
1452 del repo._rebaseset
1449
1453
1450 def _rebasedvisible(orig, repo):
1454 def _rebasedvisible(orig, repo):
1451 """ensure rebased revs stay visible (see issue4504)"""
1455 """ensure rebased revs stay visible (see issue4504)"""
1452 blockers = orig(repo)
1456 blockers = orig(repo)
1453 blockers.update(getattr(repo, '_rebaseset', ()))
1457 blockers.update(getattr(repo, '_rebaseset', ()))
1454 return blockers
1458 return blockers
1455
1459
1456 def _filterobsoleterevs(repo, revs):
1460 def _filterobsoleterevs(repo, revs):
1457 """returns a set of the obsolete revisions in revs"""
1461 """returns a set of the obsolete revisions in revs"""
1458 return set(r for r in revs if repo[r].obsolete())
1462 return set(r for r in revs if repo[r].obsolete())
1459
1463
1460 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1464 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1461 """return a mapping obsolete => successor for all obsolete nodes to be
1465 """return a mapping obsolete => successor for all obsolete nodes to be
1462 rebased that have a successors in the destination
1466 rebased that have a successors in the destination
1463
1467
1464 obsolete => None entries in the mapping indicate nodes with no successor"""
1468 obsolete => None entries in the mapping indicate nodes with no successor"""
1465 obsoletenotrebased = {}
1469 obsoletenotrebased = {}
1466
1470
1467 # Build a mapping successor => obsolete nodes for the obsolete
1471 # Build a mapping successor => obsolete nodes for the obsolete
1468 # nodes to be rebased
1472 # nodes to be rebased
1469 allsuccessors = {}
1473 allsuccessors = {}
1470 cl = repo.changelog
1474 cl = repo.changelog
1471 for r in rebaseobsrevs:
1475 for r in rebaseobsrevs:
1472 node = cl.node(r)
1476 node = cl.node(r)
1473 for s in obsolete.allsuccessors(repo.obsstore, [node]):
1477 for s in obsolete.allsuccessors(repo.obsstore, [node]):
1474 try:
1478 try:
1475 allsuccessors[cl.rev(s)] = cl.rev(node)
1479 allsuccessors[cl.rev(s)] = cl.rev(node)
1476 except LookupError:
1480 except LookupError:
1477 pass
1481 pass
1478
1482
1479 if allsuccessors:
1483 if allsuccessors:
1480 # Look for successors of obsolete nodes to be rebased among
1484 # Look for successors of obsolete nodes to be rebased among
1481 # the ancestors of dest
1485 # the ancestors of dest
1482 ancs = cl.ancestors([repo[dest].rev()],
1486 ancs = cl.ancestors([repo[dest].rev()],
1483 stoprev=min(allsuccessors),
1487 stoprev=min(allsuccessors),
1484 inclusive=True)
1488 inclusive=True)
1485 for s in allsuccessors:
1489 for s in allsuccessors:
1486 if s in ancs:
1490 if s in ancs:
1487 obsoletenotrebased[allsuccessors[s]] = s
1491 obsoletenotrebased[allsuccessors[s]] = s
1488 elif (s == allsuccessors[s] and
1492 elif (s == allsuccessors[s] and
1489 allsuccessors.values().count(s) == 1):
1493 allsuccessors.values().count(s) == 1):
1490 # plain prune
1494 # plain prune
1491 obsoletenotrebased[s] = None
1495 obsoletenotrebased[s] = None
1492
1496
1493 return obsoletenotrebased
1497 return obsoletenotrebased
1494
1498
1495 def summaryhook(ui, repo):
1499 def summaryhook(ui, repo):
1496 if not repo.vfs.exists('rebasestate'):
1500 if not repo.vfs.exists('rebasestate'):
1497 return
1501 return
1498 try:
1502 try:
1499 rbsrt = rebaseruntime(repo, ui, {})
1503 rbsrt = rebaseruntime(repo, ui, {})
1500 rbsrt.restorestatus()
1504 rbsrt.restorestatus()
1501 state = rbsrt.state
1505 state = rbsrt.state
1502 except error.RepoLookupError:
1506 except error.RepoLookupError:
1503 # i18n: column positioning for "hg summary"
1507 # i18n: column positioning for "hg summary"
1504 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1508 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1505 ui.write(msg)
1509 ui.write(msg)
1506 return
1510 return
1507 numrebased = len([i for i in state.itervalues() if i >= 0])
1511 numrebased = len([i for i in state.itervalues() if i >= 0])
1508 # i18n: column positioning for "hg summary"
1512 # i18n: column positioning for "hg summary"
1509 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1513 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1510 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1514 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1511 ui.label(_('%d remaining'), 'rebase.remaining') %
1515 ui.label(_('%d remaining'), 'rebase.remaining') %
1512 (len(state) - numrebased)))
1516 (len(state) - numrebased)))
1513
1517
1514 def uisetup(ui):
1518 def uisetup(ui):
1515 #Replace pull with a decorator to provide --rebase option
1519 #Replace pull with a decorator to provide --rebase option
1516 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1520 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1517 entry[1].append(('', 'rebase', None,
1521 entry[1].append(('', 'rebase', None,
1518 _("rebase working directory to branch head")))
1522 _("rebase working directory to branch head")))
1519 entry[1].append(('t', 'tool', '',
1523 entry[1].append(('t', 'tool', '',
1520 _("specify merge tool for rebase")))
1524 _("specify merge tool for rebase")))
1521 cmdutil.summaryhooks.add('rebase', summaryhook)
1525 cmdutil.summaryhooks.add('rebase', summaryhook)
1522 cmdutil.unfinishedstates.append(
1526 cmdutil.unfinishedstates.append(
1523 ['rebasestate', False, False, _('rebase in progress'),
1527 ['rebasestate', False, False, _('rebase in progress'),
1524 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1528 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1525 cmdutil.afterresolvedstates.append(
1529 cmdutil.afterresolvedstates.append(
1526 ['rebasestate', _('hg rebase --continue')])
1530 ['rebasestate', _('hg rebase --continue')])
1527 # ensure rebased rev are not hidden
1531 # ensure rebased rev are not hidden
1528 extensions.wrapfunction(repoview, 'pinnedrevs', _rebasedvisible)
1532 extensions.wrapfunction(repoview, 'pinnedrevs', _rebasedvisible)
@@ -1,607 +1,613
1 # transaction.py - simple journaling scheme for mercurial
1 # transaction.py - simple journaling scheme for mercurial
2 #
2 #
3 # This transaction scheme is intended to gracefully handle program
3 # This transaction scheme is intended to gracefully handle program
4 # errors and interruptions. More serious failures like system crashes
4 # errors and interruptions. More serious failures like system crashes
5 # can be recovered with an fsck-like tool. As the whole repository is
5 # can be recovered with an fsck-like tool. As the whole repository is
6 # effectively log-structured, this should amount to simply truncating
6 # effectively log-structured, this should amount to simply truncating
7 # anything that isn't referenced in the changelog.
7 # anything that isn't referenced in the changelog.
8 #
8 #
9 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
9 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
10 #
10 #
11 # This software may be used and distributed according to the terms of the
11 # This software may be used and distributed according to the terms of the
12 # GNU General Public License version 2 or any later version.
12 # GNU General Public License version 2 or any later version.
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import errno
16 import errno
17
17
18 from .i18n import _
18 from .i18n import _
19 from . import (
19 from . import (
20 error,
20 error,
21 util,
21 util,
22 )
22 )
23
23
24 version = 2
24 version = 2
25
25
26 # These are the file generators that should only be executed after the
26 # These are the file generators that should only be executed after the
27 # finalizers are done, since they rely on the output of the finalizers (like
27 # finalizers are done, since they rely on the output of the finalizers (like
28 # the changelog having been written).
28 # the changelog having been written).
29 postfinalizegenerators = {
29 postfinalizegenerators = {
30 'bookmarks',
30 'bookmarks',
31 'dirstate'
31 'dirstate'
32 }
32 }
33
33
34 gengroupall='all'
34 gengroupall='all'
35 gengroupprefinalize='prefinalize'
35 gengroupprefinalize='prefinalize'
36 gengrouppostfinalize='postfinalize'
36 gengrouppostfinalize='postfinalize'
37
37
38 def active(func):
38 def active(func):
39 def _active(self, *args, **kwds):
39 def _active(self, *args, **kwds):
40 if self.count == 0:
40 if self.count == 0:
41 raise error.Abort(_(
41 raise error.Abort(_(
42 'cannot use transaction when it is already committed/aborted'))
42 'cannot use transaction when it is already committed/aborted'))
43 return func(self, *args, **kwds)
43 return func(self, *args, **kwds)
44 return _active
44 return _active
45
45
46 def _playback(journal, report, opener, vfsmap, entries, backupentries,
46 def _playback(journal, report, opener, vfsmap, entries, backupentries,
47 unlink=True):
47 unlink=True):
48 for f, o, _ignore in entries:
48 for f, o, _ignore in entries:
49 if o or not unlink:
49 if o or not unlink:
50 try:
50 try:
51 fp = opener(f, 'a', checkambig=True)
51 fp = opener(f, 'a', checkambig=True)
52 fp.truncate(o)
52 fp.truncate(o)
53 fp.close()
53 fp.close()
54 except IOError:
54 except IOError:
55 report(_("failed to truncate %s\n") % f)
55 report(_("failed to truncate %s\n") % f)
56 raise
56 raise
57 else:
57 else:
58 try:
58 try:
59 opener.unlink(f)
59 opener.unlink(f)
60 except (IOError, OSError) as inst:
60 except (IOError, OSError) as inst:
61 if inst.errno != errno.ENOENT:
61 if inst.errno != errno.ENOENT:
62 raise
62 raise
63
63
64 backupfiles = []
64 backupfiles = []
65 for l, f, b, c in backupentries:
65 for l, f, b, c in backupentries:
66 if l not in vfsmap and c:
66 if l not in vfsmap and c:
67 report("couldn't handle %s: unknown cache location %s\n"
67 report("couldn't handle %s: unknown cache location %s\n"
68 % (b, l))
68 % (b, l))
69 vfs = vfsmap[l]
69 vfs = vfsmap[l]
70 try:
70 try:
71 if f and b:
71 if f and b:
72 filepath = vfs.join(f)
72 filepath = vfs.join(f)
73 backuppath = vfs.join(b)
73 backuppath = vfs.join(b)
74 try:
74 try:
75 util.copyfile(backuppath, filepath, checkambig=True)
75 util.copyfile(backuppath, filepath, checkambig=True)
76 backupfiles.append(b)
76 backupfiles.append(b)
77 except IOError:
77 except IOError:
78 report(_("failed to recover %s\n") % f)
78 report(_("failed to recover %s\n") % f)
79 else:
79 else:
80 target = f or b
80 target = f or b
81 try:
81 try:
82 vfs.unlink(target)
82 vfs.unlink(target)
83 except (IOError, OSError) as inst:
83 except (IOError, OSError) as inst:
84 if inst.errno != errno.ENOENT:
84 if inst.errno != errno.ENOENT:
85 raise
85 raise
86 except (IOError, OSError, error.Abort) as inst:
86 except (IOError, OSError, error.Abort) as inst:
87 if not c:
87 if not c:
88 raise
88 raise
89
89
90 backuppath = "%s.backupfiles" % journal
90 backuppath = "%s.backupfiles" % journal
91 if opener.exists(backuppath):
91 if opener.exists(backuppath):
92 opener.unlink(backuppath)
92 opener.unlink(backuppath)
93 opener.unlink(journal)
93 opener.unlink(journal)
94 try:
94 try:
95 for f in backupfiles:
95 for f in backupfiles:
96 if opener.exists(f):
96 if opener.exists(f):
97 opener.unlink(f)
97 opener.unlink(f)
98 except (IOError, OSError, error.Abort) as inst:
98 except (IOError, OSError, error.Abort) as inst:
99 # only pure backup file remains, it is sage to ignore any error
99 # only pure backup file remains, it is sage to ignore any error
100 pass
100 pass
101
101
102 class transaction(object):
102 class transaction(object):
103 def __init__(self, report, opener, vfsmap, journalname, undoname=None,
103 def __init__(self, report, opener, vfsmap, journalname, undoname=None,
104 after=None, createmode=None, validator=None, releasefn=None):
104 after=None, createmode=None, validator=None, releasefn=None):
105 """Begin a new transaction
105 """Begin a new transaction
106
106
107 Begins a new transaction that allows rolling back writes in the event of
107 Begins a new transaction that allows rolling back writes in the event of
108 an exception.
108 an exception.
109
109
110 * `after`: called after the transaction has been committed
110 * `after`: called after the transaction has been committed
111 * `createmode`: the mode of the journal file that will be created
111 * `createmode`: the mode of the journal file that will be created
112 * `releasefn`: called after releasing (with transaction and result)
112 * `releasefn`: called after releasing (with transaction and result)
113 """
113 """
114 self.count = 1
114 self.count = 1
115 self.usages = 1
115 self.usages = 1
116 self.report = report
116 self.report = report
117 # a vfs to the store content
117 # a vfs to the store content
118 self.opener = opener
118 self.opener = opener
119 # a map to access file in various {location -> vfs}
119 # a map to access file in various {location -> vfs}
120 vfsmap = vfsmap.copy()
120 vfsmap = vfsmap.copy()
121 vfsmap[''] = opener # set default value
121 vfsmap[''] = opener # set default value
122 self._vfsmap = vfsmap
122 self._vfsmap = vfsmap
123 self.after = after
123 self.after = after
124 self.entries = []
124 self.entries = []
125 self.map = {}
125 self.map = {}
126 self.journal = journalname
126 self.journal = journalname
127 self.undoname = undoname
127 self.undoname = undoname
128 self._queue = []
128 self._queue = []
129 # A callback to validate transaction content before closing it.
129 # A callback to validate transaction content before closing it.
130 # should raise exception is anything is wrong.
130 # should raise exception is anything is wrong.
131 # target user is repository hooks.
131 # target user is repository hooks.
132 if validator is None:
132 if validator is None:
133 validator = lambda tr: None
133 validator = lambda tr: None
134 self.validator = validator
134 self.validator = validator
135 # A callback to do something just after releasing transaction.
135 # A callback to do something just after releasing transaction.
136 if releasefn is None:
136 if releasefn is None:
137 releasefn = lambda tr, success: None
137 releasefn = lambda tr, success: None
138 self.releasefn = releasefn
138 self.releasefn = releasefn
139
139
140 # A dict dedicated to precisely tracking the changes introduced in the
140 # A dict dedicated to precisely tracking the changes introduced in the
141 # transaction.
141 # transaction.
142 self.changes = {}
142 self.changes = {}
143
143
144 # a dict of arguments to be passed to hooks
144 # a dict of arguments to be passed to hooks
145 self.hookargs = {}
145 self.hookargs = {}
146 self.file = opener.open(self.journal, "w")
146 self.file = opener.open(self.journal, "w")
147
147
148 # a list of ('location', 'path', 'backuppath', cache) entries.
148 # a list of ('location', 'path', 'backuppath', cache) entries.
149 # - if 'backuppath' is empty, no file existed at backup time
149 # - if 'backuppath' is empty, no file existed at backup time
150 # - if 'path' is empty, this is a temporary transaction file
150 # - if 'path' is empty, this is a temporary transaction file
151 # - if 'location' is not empty, the path is outside main opener reach.
151 # - if 'location' is not empty, the path is outside main opener reach.
152 # use 'location' value as a key in a vfsmap to find the right 'vfs'
152 # use 'location' value as a key in a vfsmap to find the right 'vfs'
153 # (cache is currently unused)
153 # (cache is currently unused)
154 self._backupentries = []
154 self._backupentries = []
155 self._backupmap = {}
155 self._backupmap = {}
156 self._backupjournal = "%s.backupfiles" % self.journal
156 self._backupjournal = "%s.backupfiles" % self.journal
157 self._backupsfile = opener.open(self._backupjournal, 'w')
157 self._backupsfile = opener.open(self._backupjournal, 'w')
158 self._backupsfile.write('%d\n' % version)
158 self._backupsfile.write('%d\n' % version)
159
159
160 if createmode is not None:
160 if createmode is not None:
161 opener.chmod(self.journal, createmode & 0o666)
161 opener.chmod(self.journal, createmode & 0o666)
162 opener.chmod(self._backupjournal, createmode & 0o666)
162 opener.chmod(self._backupjournal, createmode & 0o666)
163
163
164 # hold file generations to be performed on commit
164 # hold file generations to be performed on commit
165 self._filegenerators = {}
165 self._filegenerators = {}
166 # hold callback to write pending data for hooks
166 # hold callback to write pending data for hooks
167 self._pendingcallback = {}
167 self._pendingcallback = {}
168 # True is any pending data have been written ever
168 # True is any pending data have been written ever
169 self._anypending = False
169 self._anypending = False
170 # holds callback to call when writing the transaction
170 # holds callback to call when writing the transaction
171 self._finalizecallback = {}
171 self._finalizecallback = {}
172 # hold callback for post transaction close
172 # hold callback for post transaction close
173 self._postclosecallback = {}
173 self._postclosecallback = {}
174 # holds callbacks to call during abort
174 # holds callbacks to call during abort
175 self._abortcallback = {}
175 self._abortcallback = {}
176
176
177 def __del__(self):
177 def __del__(self):
178 if self.journal:
178 if self.journal:
179 self._abort()
179 self._abort()
180
180
181 @active
181 @active
182 def startgroup(self):
182 def startgroup(self):
183 """delay registration of file entry
183 """delay registration of file entry
184
184
185 This is used by strip to delay vision of strip offset. The transaction
185 This is used by strip to delay vision of strip offset. The transaction
186 sees either none or all of the strip actions to be done."""
186 sees either none or all of the strip actions to be done."""
187 self._queue.append([])
187 self._queue.append([])
188
188
189 @active
189 @active
190 def endgroup(self):
190 def endgroup(self):
191 """apply delayed registration of file entry.
191 """apply delayed registration of file entry.
192
192
193 This is used by strip to delay vision of strip offset. The transaction
193 This is used by strip to delay vision of strip offset. The transaction
194 sees either none or all of the strip actions to be done."""
194 sees either none or all of the strip actions to be done."""
195 q = self._queue.pop()
195 q = self._queue.pop()
196 for f, o, data in q:
196 for f, o, data in q:
197 self._addentry(f, o, data)
197 self._addentry(f, o, data)
198
198
199 @active
199 @active
200 def add(self, file, offset, data=None):
200 def add(self, file, offset, data=None):
201 """record the state of an append-only file before update"""
201 """record the state of an append-only file before update"""
202 if file in self.map or file in self._backupmap:
202 if file in self.map or file in self._backupmap:
203 return
203 return
204 if self._queue:
204 if self._queue:
205 self._queue[-1].append((file, offset, data))
205 self._queue[-1].append((file, offset, data))
206 return
206 return
207
207
208 self._addentry(file, offset, data)
208 self._addentry(file, offset, data)
209
209
210 def _addentry(self, file, offset, data):
210 def _addentry(self, file, offset, data):
211 """add a append-only entry to memory and on-disk state"""
211 """add a append-only entry to memory and on-disk state"""
212 if file in self.map or file in self._backupmap:
212 if file in self.map or file in self._backupmap:
213 return
213 return
214 self.entries.append((file, offset, data))
214 self.entries.append((file, offset, data))
215 self.map[file] = len(self.entries) - 1
215 self.map[file] = len(self.entries) - 1
216 # add enough data to the journal to do the truncate
216 # add enough data to the journal to do the truncate
217 self.file.write("%s\0%d\n" % (file, offset))
217 self.file.write("%s\0%d\n" % (file, offset))
218 self.file.flush()
218 self.file.flush()
219
219
220 @active
220 @active
221 def addbackup(self, file, hardlink=True, location=''):
221 def addbackup(self, file, hardlink=True, location=''):
222 """Adds a backup of the file to the transaction
222 """Adds a backup of the file to the transaction
223
223
224 Calling addbackup() creates a hardlink backup of the specified file
224 Calling addbackup() creates a hardlink backup of the specified file
225 that is used to recover the file in the event of the transaction
225 that is used to recover the file in the event of the transaction
226 aborting.
226 aborting.
227
227
228 * `file`: the file path, relative to .hg/store
228 * `file`: the file path, relative to .hg/store
229 * `hardlink`: use a hardlink to quickly create the backup
229 * `hardlink`: use a hardlink to quickly create the backup
230 """
230 """
231 if self._queue:
231 if self._queue:
232 msg = 'cannot use transaction.addbackup inside "group"'
232 msg = 'cannot use transaction.addbackup inside "group"'
233 raise error.ProgrammingError(msg)
233 raise error.ProgrammingError(msg)
234
234
235 if file in self.map or file in self._backupmap:
235 if file in self.map or file in self._backupmap:
236 return
236 return
237 vfs = self._vfsmap[location]
237 vfs = self._vfsmap[location]
238 dirname, filename = vfs.split(file)
238 dirname, filename = vfs.split(file)
239 backupfilename = "%s.backup.%s" % (self.journal, filename)
239 backupfilename = "%s.backup.%s" % (self.journal, filename)
240 backupfile = vfs.reljoin(dirname, backupfilename)
240 backupfile = vfs.reljoin(dirname, backupfilename)
241 if vfs.exists(file):
241 if vfs.exists(file):
242 filepath = vfs.join(file)
242 filepath = vfs.join(file)
243 backuppath = vfs.join(backupfile)
243 backuppath = vfs.join(backupfile)
244 util.copyfile(filepath, backuppath, hardlink=hardlink)
244 util.copyfile(filepath, backuppath, hardlink=hardlink)
245 else:
245 else:
246 backupfile = ''
246 backupfile = ''
247
247
248 self._addbackupentry((location, file, backupfile, False))
248 self._addbackupentry((location, file, backupfile, False))
249
249
250 def _addbackupentry(self, entry):
250 def _addbackupentry(self, entry):
251 """register a new backup entry and write it to disk"""
251 """register a new backup entry and write it to disk"""
252 self._backupentries.append(entry)
252 self._backupentries.append(entry)
253 self._backupmap[entry[1]] = len(self._backupentries) - 1
253 self._backupmap[entry[1]] = len(self._backupentries) - 1
254 self._backupsfile.write("%s\0%s\0%s\0%d\n" % entry)
254 self._backupsfile.write("%s\0%s\0%s\0%d\n" % entry)
255 self._backupsfile.flush()
255 self._backupsfile.flush()
256
256
257 @active
257 @active
258 def registertmp(self, tmpfile, location=''):
258 def registertmp(self, tmpfile, location=''):
259 """register a temporary transaction file
259 """register a temporary transaction file
260
260
261 Such files will be deleted when the transaction exits (on both
261 Such files will be deleted when the transaction exits (on both
262 failure and success).
262 failure and success).
263 """
263 """
264 self._addbackupentry((location, '', tmpfile, False))
264 self._addbackupentry((location, '', tmpfile, False))
265
265
266 @active
266 @active
267 def addfilegenerator(self, genid, filenames, genfunc, order=0,
267 def addfilegenerator(self, genid, filenames, genfunc, order=0,
268 location=''):
268 location=''):
269 """add a function to generates some files at transaction commit
269 """add a function to generates some files at transaction commit
270
270
271 The `genfunc` argument is a function capable of generating proper
271 The `genfunc` argument is a function capable of generating proper
272 content of each entry in the `filename` tuple.
272 content of each entry in the `filename` tuple.
273
273
274 At transaction close time, `genfunc` will be called with one file
274 At transaction close time, `genfunc` will be called with one file
275 object argument per entries in `filenames`.
275 object argument per entries in `filenames`.
276
276
277 The transaction itself is responsible for the backup, creation and
277 The transaction itself is responsible for the backup, creation and
278 final write of such file.
278 final write of such file.
279
279
280 The `genid` argument is used to ensure the same set of file is only
280 The `genid` argument is used to ensure the same set of file is only
281 generated once. Call to `addfilegenerator` for a `genid` already
281 generated once. Call to `addfilegenerator` for a `genid` already
282 present will overwrite the old entry.
282 present will overwrite the old entry.
283
283
284 The `order` argument may be used to control the order in which multiple
284 The `order` argument may be used to control the order in which multiple
285 generator will be executed.
285 generator will be executed.
286
286
287 The `location` arguments may be used to indicate the files are located
287 The `location` arguments may be used to indicate the files are located
288 outside of the the standard directory for transaction. It should match
288 outside of the the standard directory for transaction. It should match
289 one of the key of the `transaction.vfsmap` dictionary.
289 one of the key of the `transaction.vfsmap` dictionary.
290 """
290 """
291 # For now, we are unable to do proper backup and restore of custom vfs
291 # For now, we are unable to do proper backup and restore of custom vfs
292 # but for bookmarks that are handled outside this mechanism.
292 # but for bookmarks that are handled outside this mechanism.
293 self._filegenerators[genid] = (order, filenames, genfunc, location)
293 self._filegenerators[genid] = (order, filenames, genfunc, location)
294
294
295 @active
296 def removefilegenerator(self, genid):
297 """reverse of addfilegenerator, remove a file generator function"""
298 if genid in self._filegenerators:
299 del self._filegenerators[genid]
300
295 def _generatefiles(self, suffix='', group=gengroupall):
301 def _generatefiles(self, suffix='', group=gengroupall):
296 # write files registered for generation
302 # write files registered for generation
297 any = False
303 any = False
298 for id, entry in sorted(self._filegenerators.iteritems()):
304 for id, entry in sorted(self._filegenerators.iteritems()):
299 any = True
305 any = True
300 order, filenames, genfunc, location = entry
306 order, filenames, genfunc, location = entry
301
307
302 # for generation at closing, check if it's before or after finalize
308 # for generation at closing, check if it's before or after finalize
303 postfinalize = group == gengrouppostfinalize
309 postfinalize = group == gengrouppostfinalize
304 if (group != gengroupall and
310 if (group != gengroupall and
305 (id in postfinalizegenerators) != (postfinalize)):
311 (id in postfinalizegenerators) != (postfinalize)):
306 continue
312 continue
307
313
308 vfs = self._vfsmap[location]
314 vfs = self._vfsmap[location]
309 files = []
315 files = []
310 try:
316 try:
311 for name in filenames:
317 for name in filenames:
312 name += suffix
318 name += suffix
313 if suffix:
319 if suffix:
314 self.registertmp(name, location=location)
320 self.registertmp(name, location=location)
315 else:
321 else:
316 self.addbackup(name, location=location)
322 self.addbackup(name, location=location)
317 files.append(vfs(name, 'w', atomictemp=True,
323 files.append(vfs(name, 'w', atomictemp=True,
318 checkambig=not suffix))
324 checkambig=not suffix))
319 genfunc(*files)
325 genfunc(*files)
320 finally:
326 finally:
321 for f in files:
327 for f in files:
322 f.close()
328 f.close()
323 return any
329 return any
324
330
325 @active
331 @active
326 def find(self, file):
332 def find(self, file):
327 if file in self.map:
333 if file in self.map:
328 return self.entries[self.map[file]]
334 return self.entries[self.map[file]]
329 if file in self._backupmap:
335 if file in self._backupmap:
330 return self._backupentries[self._backupmap[file]]
336 return self._backupentries[self._backupmap[file]]
331 return None
337 return None
332
338
333 @active
339 @active
334 def replace(self, file, offset, data=None):
340 def replace(self, file, offset, data=None):
335 '''
341 '''
336 replace can only replace already committed entries
342 replace can only replace already committed entries
337 that are not pending in the queue
343 that are not pending in the queue
338 '''
344 '''
339
345
340 if file not in self.map:
346 if file not in self.map:
341 raise KeyError(file)
347 raise KeyError(file)
342 index = self.map[file]
348 index = self.map[file]
343 self.entries[index] = (file, offset, data)
349 self.entries[index] = (file, offset, data)
344 self.file.write("%s\0%d\n" % (file, offset))
350 self.file.write("%s\0%d\n" % (file, offset))
345 self.file.flush()
351 self.file.flush()
346
352
347 @active
353 @active
348 def nest(self):
354 def nest(self):
349 self.count += 1
355 self.count += 1
350 self.usages += 1
356 self.usages += 1
351 return self
357 return self
352
358
353 def release(self):
359 def release(self):
354 if self.count > 0:
360 if self.count > 0:
355 self.usages -= 1
361 self.usages -= 1
356 # if the transaction scopes are left without being closed, fail
362 # if the transaction scopes are left without being closed, fail
357 if self.count > 0 and self.usages == 0:
363 if self.count > 0 and self.usages == 0:
358 self._abort()
364 self._abort()
359
365
360 def __enter__(self):
366 def __enter__(self):
361 return self
367 return self
362
368
363 def __exit__(self, exc_type, exc_val, exc_tb):
369 def __exit__(self, exc_type, exc_val, exc_tb):
364 try:
370 try:
365 if exc_type is None:
371 if exc_type is None:
366 self.close()
372 self.close()
367 finally:
373 finally:
368 self.release()
374 self.release()
369
375
370 def running(self):
376 def running(self):
371 return self.count > 0
377 return self.count > 0
372
378
373 def addpending(self, category, callback):
379 def addpending(self, category, callback):
374 """add a callback to be called when the transaction is pending
380 """add a callback to be called when the transaction is pending
375
381
376 The transaction will be given as callback's first argument.
382 The transaction will be given as callback's first argument.
377
383
378 Category is a unique identifier to allow overwriting an old callback
384 Category is a unique identifier to allow overwriting an old callback
379 with a newer callback.
385 with a newer callback.
380 """
386 """
381 self._pendingcallback[category] = callback
387 self._pendingcallback[category] = callback
382
388
383 @active
389 @active
384 def writepending(self):
390 def writepending(self):
385 '''write pending file to temporary version
391 '''write pending file to temporary version
386
392
387 This is used to allow hooks to view a transaction before commit'''
393 This is used to allow hooks to view a transaction before commit'''
388 categories = sorted(self._pendingcallback)
394 categories = sorted(self._pendingcallback)
389 for cat in categories:
395 for cat in categories:
390 # remove callback since the data will have been flushed
396 # remove callback since the data will have been flushed
391 any = self._pendingcallback.pop(cat)(self)
397 any = self._pendingcallback.pop(cat)(self)
392 self._anypending = self._anypending or any
398 self._anypending = self._anypending or any
393 self._anypending |= self._generatefiles(suffix='.pending')
399 self._anypending |= self._generatefiles(suffix='.pending')
394 return self._anypending
400 return self._anypending
395
401
396 @active
402 @active
397 def addfinalize(self, category, callback):
403 def addfinalize(self, category, callback):
398 """add a callback to be called when the transaction is closed
404 """add a callback to be called when the transaction is closed
399
405
400 The transaction will be given as callback's first argument.
406 The transaction will be given as callback's first argument.
401
407
402 Category is a unique identifier to allow overwriting old callbacks with
408 Category is a unique identifier to allow overwriting old callbacks with
403 newer callbacks.
409 newer callbacks.
404 """
410 """
405 self._finalizecallback[category] = callback
411 self._finalizecallback[category] = callback
406
412
407 @active
413 @active
408 def addpostclose(self, category, callback):
414 def addpostclose(self, category, callback):
409 """add a callback to be called after the transaction is closed
415 """add a callback to be called after the transaction is closed
410
416
411 The transaction will be given as callback's first argument.
417 The transaction will be given as callback's first argument.
412
418
413 Category is a unique identifier to allow overwriting an old callback
419 Category is a unique identifier to allow overwriting an old callback
414 with a newer callback.
420 with a newer callback.
415 """
421 """
416 self._postclosecallback[category] = callback
422 self._postclosecallback[category] = callback
417
423
418 @active
424 @active
419 def addabort(self, category, callback):
425 def addabort(self, category, callback):
420 """add a callback to be called when the transaction is aborted.
426 """add a callback to be called when the transaction is aborted.
421
427
422 The transaction will be given as the first argument to the callback.
428 The transaction will be given as the first argument to the callback.
423
429
424 Category is a unique identifier to allow overwriting an old callback
430 Category is a unique identifier to allow overwriting an old callback
425 with a newer callback.
431 with a newer callback.
426 """
432 """
427 self._abortcallback[category] = callback
433 self._abortcallback[category] = callback
428
434
429 @active
435 @active
430 def close(self):
436 def close(self):
431 '''commit the transaction'''
437 '''commit the transaction'''
432 if self.count == 1:
438 if self.count == 1:
433 self.validator(self) # will raise exception if needed
439 self.validator(self) # will raise exception if needed
434 self.validator = None # Help prevent cycles.
440 self.validator = None # Help prevent cycles.
435 self._generatefiles(group=gengroupprefinalize)
441 self._generatefiles(group=gengroupprefinalize)
436 categories = sorted(self._finalizecallback)
442 categories = sorted(self._finalizecallback)
437 for cat in categories:
443 for cat in categories:
438 self._finalizecallback[cat](self)
444 self._finalizecallback[cat](self)
439 # Prevent double usage and help clear cycles.
445 # Prevent double usage and help clear cycles.
440 self._finalizecallback = None
446 self._finalizecallback = None
441 self._generatefiles(group=gengrouppostfinalize)
447 self._generatefiles(group=gengrouppostfinalize)
442
448
443 self.count -= 1
449 self.count -= 1
444 if self.count != 0:
450 if self.count != 0:
445 return
451 return
446 self.file.close()
452 self.file.close()
447 self._backupsfile.close()
453 self._backupsfile.close()
448 # cleanup temporary files
454 # cleanup temporary files
449 for l, f, b, c in self._backupentries:
455 for l, f, b, c in self._backupentries:
450 if l not in self._vfsmap and c:
456 if l not in self._vfsmap and c:
451 self.report("couldn't remove %s: unknown cache location %s\n"
457 self.report("couldn't remove %s: unknown cache location %s\n"
452 % (b, l))
458 % (b, l))
453 continue
459 continue
454 vfs = self._vfsmap[l]
460 vfs = self._vfsmap[l]
455 if not f and b and vfs.exists(b):
461 if not f and b and vfs.exists(b):
456 try:
462 try:
457 vfs.unlink(b)
463 vfs.unlink(b)
458 except (IOError, OSError, error.Abort) as inst:
464 except (IOError, OSError, error.Abort) as inst:
459 if not c:
465 if not c:
460 raise
466 raise
461 # Abort may be raise by read only opener
467 # Abort may be raise by read only opener
462 self.report("couldn't remove %s: %s\n"
468 self.report("couldn't remove %s: %s\n"
463 % (vfs.join(b), inst))
469 % (vfs.join(b), inst))
464 self.entries = []
470 self.entries = []
465 self._writeundo()
471 self._writeundo()
466 if self.after:
472 if self.after:
467 self.after()
473 self.after()
468 self.after = None # Help prevent cycles.
474 self.after = None # Help prevent cycles.
469 if self.opener.isfile(self._backupjournal):
475 if self.opener.isfile(self._backupjournal):
470 self.opener.unlink(self._backupjournal)
476 self.opener.unlink(self._backupjournal)
471 if self.opener.isfile(self.journal):
477 if self.opener.isfile(self.journal):
472 self.opener.unlink(self.journal)
478 self.opener.unlink(self.journal)
473 for l, _f, b, c in self._backupentries:
479 for l, _f, b, c in self._backupentries:
474 if l not in self._vfsmap and c:
480 if l not in self._vfsmap and c:
475 self.report("couldn't remove %s: unknown cache location"
481 self.report("couldn't remove %s: unknown cache location"
476 "%s\n" % (b, l))
482 "%s\n" % (b, l))
477 continue
483 continue
478 vfs = self._vfsmap[l]
484 vfs = self._vfsmap[l]
479 if b and vfs.exists(b):
485 if b and vfs.exists(b):
480 try:
486 try:
481 vfs.unlink(b)
487 vfs.unlink(b)
482 except (IOError, OSError, error.Abort) as inst:
488 except (IOError, OSError, error.Abort) as inst:
483 if not c:
489 if not c:
484 raise
490 raise
485 # Abort may be raise by read only opener
491 # Abort may be raise by read only opener
486 self.report("couldn't remove %s: %s\n"
492 self.report("couldn't remove %s: %s\n"
487 % (vfs.join(b), inst))
493 % (vfs.join(b), inst))
488 self._backupentries = []
494 self._backupentries = []
489 self.journal = None
495 self.journal = None
490
496
491 self.releasefn(self, True) # notify success of closing transaction
497 self.releasefn(self, True) # notify success of closing transaction
492 self.releasefn = None # Help prevent cycles.
498 self.releasefn = None # Help prevent cycles.
493
499
494 # run post close action
500 # run post close action
495 categories = sorted(self._postclosecallback)
501 categories = sorted(self._postclosecallback)
496 for cat in categories:
502 for cat in categories:
497 self._postclosecallback[cat](self)
503 self._postclosecallback[cat](self)
498 # Prevent double usage and help clear cycles.
504 # Prevent double usage and help clear cycles.
499 self._postclosecallback = None
505 self._postclosecallback = None
500
506
501 @active
507 @active
502 def abort(self):
508 def abort(self):
503 '''abort the transaction (generally called on error, or when the
509 '''abort the transaction (generally called on error, or when the
504 transaction is not explicitly committed before going out of
510 transaction is not explicitly committed before going out of
505 scope)'''
511 scope)'''
506 self._abort()
512 self._abort()
507
513
508 def _writeundo(self):
514 def _writeundo(self):
509 """write transaction data for possible future undo call"""
515 """write transaction data for possible future undo call"""
510 if self.undoname is None:
516 if self.undoname is None:
511 return
517 return
512 undobackupfile = self.opener.open("%s.backupfiles" % self.undoname, 'w')
518 undobackupfile = self.opener.open("%s.backupfiles" % self.undoname, 'w')
513 undobackupfile.write('%d\n' % version)
519 undobackupfile.write('%d\n' % version)
514 for l, f, b, c in self._backupentries:
520 for l, f, b, c in self._backupentries:
515 if not f: # temporary file
521 if not f: # temporary file
516 continue
522 continue
517 if not b:
523 if not b:
518 u = ''
524 u = ''
519 else:
525 else:
520 if l not in self._vfsmap and c:
526 if l not in self._vfsmap and c:
521 self.report("couldn't remove %s: unknown cache location"
527 self.report("couldn't remove %s: unknown cache location"
522 "%s\n" % (b, l))
528 "%s\n" % (b, l))
523 continue
529 continue
524 vfs = self._vfsmap[l]
530 vfs = self._vfsmap[l]
525 base, name = vfs.split(b)
531 base, name = vfs.split(b)
526 assert name.startswith(self.journal), name
532 assert name.startswith(self.journal), name
527 uname = name.replace(self.journal, self.undoname, 1)
533 uname = name.replace(self.journal, self.undoname, 1)
528 u = vfs.reljoin(base, uname)
534 u = vfs.reljoin(base, uname)
529 util.copyfile(vfs.join(b), vfs.join(u), hardlink=True)
535 util.copyfile(vfs.join(b), vfs.join(u), hardlink=True)
530 undobackupfile.write("%s\0%s\0%s\0%d\n" % (l, f, u, c))
536 undobackupfile.write("%s\0%s\0%s\0%d\n" % (l, f, u, c))
531 undobackupfile.close()
537 undobackupfile.close()
532
538
533
539
534 def _abort(self):
540 def _abort(self):
535 self.count = 0
541 self.count = 0
536 self.usages = 0
542 self.usages = 0
537 self.file.close()
543 self.file.close()
538 self._backupsfile.close()
544 self._backupsfile.close()
539
545
540 try:
546 try:
541 if not self.entries and not self._backupentries:
547 if not self.entries and not self._backupentries:
542 if self._backupjournal:
548 if self._backupjournal:
543 self.opener.unlink(self._backupjournal)
549 self.opener.unlink(self._backupjournal)
544 if self.journal:
550 if self.journal:
545 self.opener.unlink(self.journal)
551 self.opener.unlink(self.journal)
546 return
552 return
547
553
548 self.report(_("transaction abort!\n"))
554 self.report(_("transaction abort!\n"))
549
555
550 try:
556 try:
551 for cat in sorted(self._abortcallback):
557 for cat in sorted(self._abortcallback):
552 self._abortcallback[cat](self)
558 self._abortcallback[cat](self)
553 # Prevent double usage and help clear cycles.
559 # Prevent double usage and help clear cycles.
554 self._abortcallback = None
560 self._abortcallback = None
555 _playback(self.journal, self.report, self.opener, self._vfsmap,
561 _playback(self.journal, self.report, self.opener, self._vfsmap,
556 self.entries, self._backupentries, False)
562 self.entries, self._backupentries, False)
557 self.report(_("rollback completed\n"))
563 self.report(_("rollback completed\n"))
558 except BaseException:
564 except BaseException:
559 self.report(_("rollback failed - please run hg recover\n"))
565 self.report(_("rollback failed - please run hg recover\n"))
560 finally:
566 finally:
561 self.journal = None
567 self.journal = None
562 self.releasefn(self, False) # notify failure of transaction
568 self.releasefn(self, False) # notify failure of transaction
563 self.releasefn = None # Help prevent cycles.
569 self.releasefn = None # Help prevent cycles.
564
570
565 def rollback(opener, vfsmap, file, report):
571 def rollback(opener, vfsmap, file, report):
566 """Rolls back the transaction contained in the given file
572 """Rolls back the transaction contained in the given file
567
573
568 Reads the entries in the specified file, and the corresponding
574 Reads the entries in the specified file, and the corresponding
569 '*.backupfiles' file, to recover from an incomplete transaction.
575 '*.backupfiles' file, to recover from an incomplete transaction.
570
576
571 * `file`: a file containing a list of entries, specifying where
577 * `file`: a file containing a list of entries, specifying where
572 to truncate each file. The file should contain a list of
578 to truncate each file. The file should contain a list of
573 file\0offset pairs, delimited by newlines. The corresponding
579 file\0offset pairs, delimited by newlines. The corresponding
574 '*.backupfiles' file should contain a list of file\0backupfile
580 '*.backupfiles' file should contain a list of file\0backupfile
575 pairs, delimited by \0.
581 pairs, delimited by \0.
576 """
582 """
577 entries = []
583 entries = []
578 backupentries = []
584 backupentries = []
579
585
580 fp = opener.open(file)
586 fp = opener.open(file)
581 lines = fp.readlines()
587 lines = fp.readlines()
582 fp.close()
588 fp.close()
583 for l in lines:
589 for l in lines:
584 try:
590 try:
585 f, o = l.split('\0')
591 f, o = l.split('\0')
586 entries.append((f, int(o), None))
592 entries.append((f, int(o), None))
587 except ValueError:
593 except ValueError:
588 report(_("couldn't read journal entry %r!\n") % l)
594 report(_("couldn't read journal entry %r!\n") % l)
589
595
590 backupjournal = "%s.backupfiles" % file
596 backupjournal = "%s.backupfiles" % file
591 if opener.exists(backupjournal):
597 if opener.exists(backupjournal):
592 fp = opener.open(backupjournal)
598 fp = opener.open(backupjournal)
593 lines = fp.readlines()
599 lines = fp.readlines()
594 if lines:
600 if lines:
595 ver = lines[0][:-1]
601 ver = lines[0][:-1]
596 if ver == str(version):
602 if ver == str(version):
597 for line in lines[1:]:
603 for line in lines[1:]:
598 if line:
604 if line:
599 # Shave off the trailing newline
605 # Shave off the trailing newline
600 line = line[:-1]
606 line = line[:-1]
601 l, f, b, c = line.split('\0')
607 l, f, b, c = line.split('\0')
602 backupentries.append((l, f, b, bool(c)))
608 backupentries.append((l, f, b, bool(c)))
603 else:
609 else:
604 report(_("journal was created by a different version of "
610 report(_("journal was created by a different version of "
605 "Mercurial\n"))
611 "Mercurial\n"))
606
612
607 _playback(file, report, opener, vfsmap, entries, backupentries)
613 _playback(file, report, opener, vfsmap, entries, backupentries)
@@ -1,957 +1,957
1 $ cat >> $HGRCPATH <<EOF
1 $ cat >> $HGRCPATH <<EOF
2 > [extensions]
2 > [extensions]
3 > rebase=
3 > rebase=
4 > drawdag=$TESTDIR/drawdag.py
4 > drawdag=$TESTDIR/drawdag.py
5 >
5 >
6 > [phases]
6 > [phases]
7 > publish=False
7 > publish=False
8 >
8 >
9 > [alias]
9 > [alias]
10 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
10 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
11 > EOF
11 > EOF
12
12
13
13
14 $ hg init a
14 $ hg init a
15 $ cd a
15 $ cd a
16 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
16 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
17 adding changesets
17 adding changesets
18 adding manifests
18 adding manifests
19 adding file changes
19 adding file changes
20 added 8 changesets with 7 changes to 7 files (+2 heads)
20 added 8 changesets with 7 changes to 7 files (+2 heads)
21 (run 'hg heads' to see heads, 'hg merge' to merge)
21 (run 'hg heads' to see heads, 'hg merge' to merge)
22 $ hg up tip
22 $ hg up tip
23 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
23 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
24 $ cd ..
24 $ cd ..
25
25
26
26
27 Rebasing
27 Rebasing
28 D onto H - simple rebase:
28 D onto H - simple rebase:
29 (this also tests that editor is invoked if '--edit' is specified, and that we
29 (this also tests that editor is invoked if '--edit' is specified, and that we
30 can abort or warn for colliding untracked files)
30 can abort or warn for colliding untracked files)
31
31
32 $ hg clone -q -u . a a1
32 $ hg clone -q -u . a a1
33 $ cd a1
33 $ cd a1
34
34
35 $ hg tglog
35 $ hg tglog
36 @ 7: 'H'
36 @ 7: 'H'
37 |
37 |
38 | o 6: 'G'
38 | o 6: 'G'
39 |/|
39 |/|
40 o | 5: 'F'
40 o | 5: 'F'
41 | |
41 | |
42 | o 4: 'E'
42 | o 4: 'E'
43 |/
43 |/
44 | o 3: 'D'
44 | o 3: 'D'
45 | |
45 | |
46 | o 2: 'C'
46 | o 2: 'C'
47 | |
47 | |
48 | o 1: 'B'
48 | o 1: 'B'
49 |/
49 |/
50 o 0: 'A'
50 o 0: 'A'
51
51
52
52
53 $ hg status --rev "3^1" --rev 3
53 $ hg status --rev "3^1" --rev 3
54 A D
54 A D
55 $ echo collide > D
55 $ echo collide > D
56 $ HGEDITOR=cat hg rebase -s 3 -d 7 --edit --config merge.checkunknown=warn
56 $ HGEDITOR=cat hg rebase -s 3 -d 7 --edit --config merge.checkunknown=warn
57 rebasing 3:32af7686d403 "D"
57 rebasing 3:32af7686d403 "D"
58 D: replacing untracked file
58 D: replacing untracked file
59 D
59 D
60
60
61
61
62 HG: Enter commit message. Lines beginning with 'HG:' are removed.
62 HG: Enter commit message. Lines beginning with 'HG:' are removed.
63 HG: Leave message empty to abort commit.
63 HG: Leave message empty to abort commit.
64 HG: --
64 HG: --
65 HG: user: Nicolas Dumazet <nicdumz.commits@gmail.com>
65 HG: user: Nicolas Dumazet <nicdumz.commits@gmail.com>
66 HG: branch 'default'
66 HG: branch 'default'
67 HG: added D
67 HG: added D
68 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/32af7686d403-6f7dface-backup.hg (glob)
68 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/32af7686d403-6f7dface-backup.hg (glob)
69 $ cat D.orig
69 $ cat D.orig
70 collide
70 collide
71 $ rm D.orig
71 $ rm D.orig
72
72
73 $ hg tglog
73 $ hg tglog
74 o 7: 'D'
74 o 7: 'D'
75 |
75 |
76 @ 6: 'H'
76 @ 6: 'H'
77 |
77 |
78 | o 5: 'G'
78 | o 5: 'G'
79 |/|
79 |/|
80 o | 4: 'F'
80 o | 4: 'F'
81 | |
81 | |
82 | o 3: 'E'
82 | o 3: 'E'
83 |/
83 |/
84 | o 2: 'C'
84 | o 2: 'C'
85 | |
85 | |
86 | o 1: 'B'
86 | o 1: 'B'
87 |/
87 |/
88 o 0: 'A'
88 o 0: 'A'
89
89
90 $ cd ..
90 $ cd ..
91
91
92
92
93 D onto F - intermediate point:
93 D onto F - intermediate point:
94 (this also tests that editor is not invoked if '--edit' is not specified, and
94 (this also tests that editor is not invoked if '--edit' is not specified, and
95 that we can ignore for colliding untracked files)
95 that we can ignore for colliding untracked files)
96
96
97 $ hg clone -q -u . a a2
97 $ hg clone -q -u . a a2
98 $ cd a2
98 $ cd a2
99 $ echo collide > D
99 $ echo collide > D
100
100
101 $ HGEDITOR=cat hg rebase -s 3 -d 5 --config merge.checkunknown=ignore
101 $ HGEDITOR=cat hg rebase -s 3 -d 5 --config merge.checkunknown=ignore
102 rebasing 3:32af7686d403 "D"
102 rebasing 3:32af7686d403 "D"
103 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/32af7686d403-6f7dface-backup.hg (glob)
103 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/32af7686d403-6f7dface-backup.hg (glob)
104 $ cat D.orig
104 $ cat D.orig
105 collide
105 collide
106 $ rm D.orig
106 $ rm D.orig
107
107
108 $ hg tglog
108 $ hg tglog
109 o 7: 'D'
109 o 7: 'D'
110 |
110 |
111 | @ 6: 'H'
111 | @ 6: 'H'
112 |/
112 |/
113 | o 5: 'G'
113 | o 5: 'G'
114 |/|
114 |/|
115 o | 4: 'F'
115 o | 4: 'F'
116 | |
116 | |
117 | o 3: 'E'
117 | o 3: 'E'
118 |/
118 |/
119 | o 2: 'C'
119 | o 2: 'C'
120 | |
120 | |
121 | o 1: 'B'
121 | o 1: 'B'
122 |/
122 |/
123 o 0: 'A'
123 o 0: 'A'
124
124
125 $ cd ..
125 $ cd ..
126
126
127
127
128 E onto H - skip of G:
128 E onto H - skip of G:
129 (this also tests that we can overwrite untracked files and don't create backups
129 (this also tests that we can overwrite untracked files and don't create backups
130 if they have the same contents)
130 if they have the same contents)
131
131
132 $ hg clone -q -u . a a3
132 $ hg clone -q -u . a a3
133 $ cd a3
133 $ cd a3
134 $ hg cat -r 4 E | tee E
134 $ hg cat -r 4 E | tee E
135 E
135 E
136
136
137 $ hg rebase -s 4 -d 7
137 $ hg rebase -s 4 -d 7
138 rebasing 4:9520eea781bc "E"
138 rebasing 4:9520eea781bc "E"
139 rebasing 6:eea13746799a "G"
139 rebasing 6:eea13746799a "G"
140 note: rebase of 6:eea13746799a created no changes to commit
140 note: rebase of 6:eea13746799a created no changes to commit
141 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/9520eea781bc-fcd8edd4-backup.hg (glob)
141 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/9520eea781bc-fcd8edd4-backup.hg (glob)
142 $ f E.orig
142 $ f E.orig
143 E.orig: file not found
143 E.orig: file not found
144
144
145 $ hg tglog
145 $ hg tglog
146 o 6: 'E'
146 o 6: 'E'
147 |
147 |
148 @ 5: 'H'
148 @ 5: 'H'
149 |
149 |
150 o 4: 'F'
150 o 4: 'F'
151 |
151 |
152 | o 3: 'D'
152 | o 3: 'D'
153 | |
153 | |
154 | o 2: 'C'
154 | o 2: 'C'
155 | |
155 | |
156 | o 1: 'B'
156 | o 1: 'B'
157 |/
157 |/
158 o 0: 'A'
158 o 0: 'A'
159
159
160 $ cd ..
160 $ cd ..
161
161
162
162
163 F onto E - rebase of a branching point (skip G):
163 F onto E - rebase of a branching point (skip G):
164
164
165 $ hg clone -q -u . a a4
165 $ hg clone -q -u . a a4
166 $ cd a4
166 $ cd a4
167
167
168 $ hg rebase -s 5 -d 4
168 $ hg rebase -s 5 -d 4
169 rebasing 5:24b6387c8c8c "F"
169 rebasing 5:24b6387c8c8c "F"
170 rebasing 6:eea13746799a "G"
170 rebasing 6:eea13746799a "G"
171 note: rebase of 6:eea13746799a created no changes to commit
171 note: rebase of 6:eea13746799a created no changes to commit
172 rebasing 7:02de42196ebe "H" (tip)
172 rebasing 7:02de42196ebe "H" (tip)
173 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/24b6387c8c8c-c3fe765d-backup.hg (glob)
173 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/24b6387c8c8c-c3fe765d-backup.hg (glob)
174
174
175 $ hg tglog
175 $ hg tglog
176 @ 6: 'H'
176 @ 6: 'H'
177 |
177 |
178 o 5: 'F'
178 o 5: 'F'
179 |
179 |
180 o 4: 'E'
180 o 4: 'E'
181 |
181 |
182 | o 3: 'D'
182 | o 3: 'D'
183 | |
183 | |
184 | o 2: 'C'
184 | o 2: 'C'
185 | |
185 | |
186 | o 1: 'B'
186 | o 1: 'B'
187 |/
187 |/
188 o 0: 'A'
188 o 0: 'A'
189
189
190 $ cd ..
190 $ cd ..
191
191
192
192
193 G onto H - merged revision having a parent in ancestors of target:
193 G onto H - merged revision having a parent in ancestors of target:
194
194
195 $ hg clone -q -u . a a5
195 $ hg clone -q -u . a a5
196 $ cd a5
196 $ cd a5
197
197
198 $ hg rebase -s 6 -d 7
198 $ hg rebase -s 6 -d 7
199 rebasing 6:eea13746799a "G"
199 rebasing 6:eea13746799a "G"
200 saved backup bundle to $TESTTMP/a5/.hg/strip-backup/eea13746799a-883828ed-backup.hg (glob)
200 saved backup bundle to $TESTTMP/a5/.hg/strip-backup/eea13746799a-883828ed-backup.hg (glob)
201
201
202 $ hg tglog
202 $ hg tglog
203 o 7: 'G'
203 o 7: 'G'
204 |\
204 |\
205 | @ 6: 'H'
205 | @ 6: 'H'
206 | |
206 | |
207 | o 5: 'F'
207 | o 5: 'F'
208 | |
208 | |
209 o | 4: 'E'
209 o | 4: 'E'
210 |/
210 |/
211 | o 3: 'D'
211 | o 3: 'D'
212 | |
212 | |
213 | o 2: 'C'
213 | o 2: 'C'
214 | |
214 | |
215 | o 1: 'B'
215 | o 1: 'B'
216 |/
216 |/
217 o 0: 'A'
217 o 0: 'A'
218
218
219 $ cd ..
219 $ cd ..
220
220
221
221
222 F onto B - G maintains E as parent:
222 F onto B - G maintains E as parent:
223
223
224 $ hg clone -q -u . a a6
224 $ hg clone -q -u . a a6
225 $ cd a6
225 $ cd a6
226
226
227 $ hg rebase -s 5 -d 1
227 $ hg rebase -s 5 -d 1
228 rebasing 5:24b6387c8c8c "F"
228 rebasing 5:24b6387c8c8c "F"
229 rebasing 6:eea13746799a "G"
229 rebasing 6:eea13746799a "G"
230 rebasing 7:02de42196ebe "H" (tip)
230 rebasing 7:02de42196ebe "H" (tip)
231 saved backup bundle to $TESTTMP/a6/.hg/strip-backup/24b6387c8c8c-c3fe765d-backup.hg (glob)
231 saved backup bundle to $TESTTMP/a6/.hg/strip-backup/24b6387c8c8c-c3fe765d-backup.hg (glob)
232
232
233 $ hg tglog
233 $ hg tglog
234 @ 7: 'H'
234 @ 7: 'H'
235 |
235 |
236 | o 6: 'G'
236 | o 6: 'G'
237 |/|
237 |/|
238 o | 5: 'F'
238 o | 5: 'F'
239 | |
239 | |
240 | o 4: 'E'
240 | o 4: 'E'
241 | |
241 | |
242 | | o 3: 'D'
242 | | o 3: 'D'
243 | | |
243 | | |
244 +---o 2: 'C'
244 +---o 2: 'C'
245 | |
245 | |
246 o | 1: 'B'
246 o | 1: 'B'
247 |/
247 |/
248 o 0: 'A'
248 o 0: 'A'
249
249
250 $ cd ..
250 $ cd ..
251
251
252
252
253 These will fail (using --source):
253 These will fail (using --source):
254
254
255 G onto F - rebase onto an ancestor:
255 G onto F - rebase onto an ancestor:
256
256
257 $ hg clone -q -u . a a7
257 $ hg clone -q -u . a a7
258 $ cd a7
258 $ cd a7
259
259
260 $ hg rebase -s 6 -d 5
260 $ hg rebase -s 6 -d 5
261 nothing to rebase
261 nothing to rebase
262 [1]
262 [1]
263
263
264 F onto G - rebase onto a descendant:
264 F onto G - rebase onto a descendant:
265
265
266 $ hg rebase -s 5 -d 6
266 $ hg rebase -s 5 -d 6
267 abort: source is ancestor of destination
267 abort: source is ancestor of destination
268 [255]
268 [255]
269
269
270 G onto B - merge revision with both parents not in ancestors of target:
270 G onto B - merge revision with both parents not in ancestors of target:
271
271
272 $ hg rebase -s 6 -d 1
272 $ hg rebase -s 6 -d 1
273 rebasing 6:eea13746799a "G"
273 rebasing 6:eea13746799a "G"
274 transaction abort!
274 transaction abort!
275 rollback completed
275 rollback completed
276 abort: cannot use revision 6 as base, result would have 3 parents
276 abort: cannot use revision 6 as base, result would have 3 parents
277 [255]
277 [255]
278 $ hg rebase --abort
278 $ hg rebase --abort
279 rebase aborted
279 rebase aborted
280
280
281 These will abort gracefully (using --base):
281 These will abort gracefully (using --base):
282
282
283 G onto G - rebase onto same changeset:
283 G onto G - rebase onto same changeset:
284
284
285 $ hg rebase -b 6 -d 6
285 $ hg rebase -b 6 -d 6
286 nothing to rebase - eea13746799a is both "base" and destination
286 nothing to rebase - eea13746799a is both "base" and destination
287 [1]
287 [1]
288
288
289 G onto F - rebase onto an ancestor:
289 G onto F - rebase onto an ancestor:
290
290
291 $ hg rebase -b 6 -d 5
291 $ hg rebase -b 6 -d 5
292 nothing to rebase
292 nothing to rebase
293 [1]
293 [1]
294
294
295 F onto G - rebase onto a descendant:
295 F onto G - rebase onto a descendant:
296
296
297 $ hg rebase -b 5 -d 6
297 $ hg rebase -b 5 -d 6
298 nothing to rebase - "base" 24b6387c8c8c is already an ancestor of destination eea13746799a
298 nothing to rebase - "base" 24b6387c8c8c is already an ancestor of destination eea13746799a
299 [1]
299 [1]
300
300
301 C onto A - rebase onto an ancestor:
301 C onto A - rebase onto an ancestor:
302
302
303 $ hg rebase -d 0 -s 2
303 $ hg rebase -d 0 -s 2
304 rebasing 2:5fddd98957c8 "C"
304 rebasing 2:5fddd98957c8 "C"
305 rebasing 3:32af7686d403 "D"
305 rebasing 3:32af7686d403 "D"
306 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/5fddd98957c8-f9244fa1-backup.hg (glob)
306 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/5fddd98957c8-f9244fa1-backup.hg (glob)
307 $ hg tglog
307 $ hg tglog
308 o 7: 'D'
308 o 7: 'D'
309 |
309 |
310 o 6: 'C'
310 o 6: 'C'
311 |
311 |
312 | @ 5: 'H'
312 | @ 5: 'H'
313 | |
313 | |
314 | | o 4: 'G'
314 | | o 4: 'G'
315 | |/|
315 | |/|
316 | o | 3: 'F'
316 | o | 3: 'F'
317 |/ /
317 |/ /
318 | o 2: 'E'
318 | o 2: 'E'
319 |/
319 |/
320 | o 1: 'B'
320 | o 1: 'B'
321 |/
321 |/
322 o 0: 'A'
322 o 0: 'A'
323
323
324
324
325 Check rebasing public changeset
325 Check rebasing public changeset
326
326
327 $ hg pull --config phases.publish=True -q -r 6 . # update phase of 6
327 $ hg pull --config phases.publish=True -q -r 6 . # update phase of 6
328 $ hg rebase -d 0 -b 6
328 $ hg rebase -d 0 -b 6
329 nothing to rebase
329 nothing to rebase
330 [1]
330 [1]
331 $ hg rebase -d 5 -b 6
331 $ hg rebase -d 5 -b 6
332 abort: can't rebase public changeset e1c4361dd923
332 abort: can't rebase public changeset e1c4361dd923
333 (see 'hg help phases' for details)
333 (see 'hg help phases' for details)
334 [255]
334 [255]
335 $ hg rebase -d 5 -r '1 + (6::)'
335 $ hg rebase -d 5 -r '1 + (6::)'
336 abort: can't rebase public changeset e1c4361dd923
336 abort: can't rebase public changeset e1c4361dd923
337 (see 'hg help phases' for details)
337 (see 'hg help phases' for details)
338 [255]
338 [255]
339
339
340 $ hg rebase -d 5 -b 6 --keep
340 $ hg rebase -d 5 -b 6 --keep
341 rebasing 6:e1c4361dd923 "C"
341 rebasing 6:e1c4361dd923 "C"
342 rebasing 7:c9659aac0000 "D" (tip)
342 rebasing 7:c9659aac0000 "D" (tip)
343
343
344 Check rebasing mutable changeset
344 Check rebasing mutable changeset
345 Source phase greater or equal to destination phase: new changeset get the phase of source:
345 Source phase greater or equal to destination phase: new changeset get the phase of source:
346 $ hg id -n
346 $ hg id -n
347 5
347 5
348 $ hg rebase -s9 -d0
348 $ hg rebase -s9 -d0
349 rebasing 9:2b23e52411f4 "D" (tip)
349 rebasing 9:2b23e52411f4 "D" (tip)
350 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2b23e52411f4-f942decf-backup.hg (glob)
350 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2b23e52411f4-f942decf-backup.hg (glob)
351 $ hg id -n # check we updated back to parent
351 $ hg id -n # check we updated back to parent
352 5
352 5
353 $ hg log --template "{phase}\n" -r 9
353 $ hg log --template "{phase}\n" -r 9
354 draft
354 draft
355 $ hg rebase -s9 -d1
355 $ hg rebase -s9 -d1
356 rebasing 9:2cb10d0cfc6c "D" (tip)
356 rebasing 9:2cb10d0cfc6c "D" (tip)
357 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2cb10d0cfc6c-ddb0f256-backup.hg (glob)
357 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2cb10d0cfc6c-ddb0f256-backup.hg (glob)
358 $ hg log --template "{phase}\n" -r 9
358 $ hg log --template "{phase}\n" -r 9
359 draft
359 draft
360 $ hg phase --force --secret 9
360 $ hg phase --force --secret 9
361 $ hg rebase -s9 -d0
361 $ hg rebase -s9 -d0
362 rebasing 9:c5b12b67163a "D" (tip)
362 rebasing 9:c5b12b67163a "D" (tip)
363 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/c5b12b67163a-4e372053-backup.hg (glob)
363 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/c5b12b67163a-4e372053-backup.hg (glob)
364 $ hg log --template "{phase}\n" -r 9
364 $ hg log --template "{phase}\n" -r 9
365 secret
365 secret
366 $ hg rebase -s9 -d1
366 $ hg rebase -s9 -d1
367 rebasing 9:2a0524f868ac "D" (tip)
367 rebasing 9:2a0524f868ac "D" (tip)
368 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2a0524f868ac-cefd8574-backup.hg (glob)
368 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2a0524f868ac-cefd8574-backup.hg (glob)
369 $ hg log --template "{phase}\n" -r 9
369 $ hg log --template "{phase}\n" -r 9
370 secret
370 secret
371 Source phase lower than destination phase: new changeset get the phase of destination:
371 Source phase lower than destination phase: new changeset get the phase of destination:
372 $ hg rebase -s8 -d9
372 $ hg rebase -s8 -d9
373 rebasing 8:6d4f22462821 "C"
373 rebasing 8:6d4f22462821 "C"
374 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/6d4f22462821-3441f70b-backup.hg (glob)
374 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/6d4f22462821-3441f70b-backup.hg (glob)
375 $ hg log --template "{phase}\n" -r 'rev(9)'
375 $ hg log --template "{phase}\n" -r 'rev(9)'
376 secret
376 secret
377
377
378 $ cd ..
378 $ cd ..
379
379
380 Test for revset
380 Test for revset
381
381
382 We need a bit different graph
382 We need a bit different graph
383 All destination are B
383 All destination are B
384
384
385 $ hg init ah
385 $ hg init ah
386 $ cd ah
386 $ cd ah
387 $ hg unbundle "$TESTDIR/bundles/rebase-revset.hg"
387 $ hg unbundle "$TESTDIR/bundles/rebase-revset.hg"
388 adding changesets
388 adding changesets
389 adding manifests
389 adding manifests
390 adding file changes
390 adding file changes
391 added 9 changesets with 9 changes to 9 files (+2 heads)
391 added 9 changesets with 9 changes to 9 files (+2 heads)
392 (run 'hg heads' to see heads, 'hg merge' to merge)
392 (run 'hg heads' to see heads, 'hg merge' to merge)
393 $ hg tglog
393 $ hg tglog
394 o 8: 'I'
394 o 8: 'I'
395 |
395 |
396 o 7: 'H'
396 o 7: 'H'
397 |
397 |
398 o 6: 'G'
398 o 6: 'G'
399 |
399 |
400 | o 5: 'F'
400 | o 5: 'F'
401 | |
401 | |
402 | o 4: 'E'
402 | o 4: 'E'
403 |/
403 |/
404 o 3: 'D'
404 o 3: 'D'
405 |
405 |
406 o 2: 'C'
406 o 2: 'C'
407 |
407 |
408 | o 1: 'B'
408 | o 1: 'B'
409 |/
409 |/
410 o 0: 'A'
410 o 0: 'A'
411
411
412 $ cd ..
412 $ cd ..
413
413
414
414
415 Simple case with keep:
415 Simple case with keep:
416
416
417 Source on have two descendant heads but ask for one
417 Source on have two descendant heads but ask for one
418
418
419 $ hg clone -q -u . ah ah1
419 $ hg clone -q -u . ah ah1
420 $ cd ah1
420 $ cd ah1
421 $ hg rebase -r '2::8' -d 1
421 $ hg rebase -r '2::8' -d 1
422 abort: can't remove original changesets with unrebased descendants
422 abort: can't remove original changesets with unrebased descendants
423 (use --keep to keep original changesets)
423 (use --keep to keep original changesets)
424 [255]
424 [255]
425 $ hg rebase -r '2::8' -d 1 -k
425 $ hg rebase -r '2::8' -d 1 -k
426 rebasing 2:c9e50f6cdc55 "C"
426 rebasing 2:c9e50f6cdc55 "C"
427 rebasing 3:ffd453c31098 "D"
427 rebasing 3:ffd453c31098 "D"
428 rebasing 6:3d8a618087a7 "G"
428 rebasing 6:3d8a618087a7 "G"
429 rebasing 7:72434a4e60b0 "H"
429 rebasing 7:72434a4e60b0 "H"
430 rebasing 8:479ddb54a924 "I" (tip)
430 rebasing 8:479ddb54a924 "I" (tip)
431 $ hg tglog
431 $ hg tglog
432 o 13: 'I'
432 o 13: 'I'
433 |
433 |
434 o 12: 'H'
434 o 12: 'H'
435 |
435 |
436 o 11: 'G'
436 o 11: 'G'
437 |
437 |
438 o 10: 'D'
438 o 10: 'D'
439 |
439 |
440 o 9: 'C'
440 o 9: 'C'
441 |
441 |
442 | o 8: 'I'
442 | o 8: 'I'
443 | |
443 | |
444 | o 7: 'H'
444 | o 7: 'H'
445 | |
445 | |
446 | o 6: 'G'
446 | o 6: 'G'
447 | |
447 | |
448 | | o 5: 'F'
448 | | o 5: 'F'
449 | | |
449 | | |
450 | | o 4: 'E'
450 | | o 4: 'E'
451 | |/
451 | |/
452 | o 3: 'D'
452 | o 3: 'D'
453 | |
453 | |
454 | o 2: 'C'
454 | o 2: 'C'
455 | |
455 | |
456 o | 1: 'B'
456 o | 1: 'B'
457 |/
457 |/
458 o 0: 'A'
458 o 0: 'A'
459
459
460
460
461 $ cd ..
461 $ cd ..
462
462
463 Base on have one descendant heads we ask for but common ancestor have two
463 Base on have one descendant heads we ask for but common ancestor have two
464
464
465 $ hg clone -q -u . ah ah2
465 $ hg clone -q -u . ah ah2
466 $ cd ah2
466 $ cd ah2
467 $ hg rebase -r '3::8' -d 1
467 $ hg rebase -r '3::8' -d 1
468 abort: can't remove original changesets with unrebased descendants
468 abort: can't remove original changesets with unrebased descendants
469 (use --keep to keep original changesets)
469 (use --keep to keep original changesets)
470 [255]
470 [255]
471 $ hg rebase -r '3::8' -d 1 --keep
471 $ hg rebase -r '3::8' -d 1 --keep
472 rebasing 3:ffd453c31098 "D"
472 rebasing 3:ffd453c31098 "D"
473 rebasing 6:3d8a618087a7 "G"
473 rebasing 6:3d8a618087a7 "G"
474 rebasing 7:72434a4e60b0 "H"
474 rebasing 7:72434a4e60b0 "H"
475 rebasing 8:479ddb54a924 "I" (tip)
475 rebasing 8:479ddb54a924 "I" (tip)
476 $ hg tglog
476 $ hg tglog
477 o 12: 'I'
477 o 12: 'I'
478 |
478 |
479 o 11: 'H'
479 o 11: 'H'
480 |
480 |
481 o 10: 'G'
481 o 10: 'G'
482 |
482 |
483 o 9: 'D'
483 o 9: 'D'
484 |
484 |
485 | o 8: 'I'
485 | o 8: 'I'
486 | |
486 | |
487 | o 7: 'H'
487 | o 7: 'H'
488 | |
488 | |
489 | o 6: 'G'
489 | o 6: 'G'
490 | |
490 | |
491 | | o 5: 'F'
491 | | o 5: 'F'
492 | | |
492 | | |
493 | | o 4: 'E'
493 | | o 4: 'E'
494 | |/
494 | |/
495 | o 3: 'D'
495 | o 3: 'D'
496 | |
496 | |
497 | o 2: 'C'
497 | o 2: 'C'
498 | |
498 | |
499 o | 1: 'B'
499 o | 1: 'B'
500 |/
500 |/
501 o 0: 'A'
501 o 0: 'A'
502
502
503
503
504 $ cd ..
504 $ cd ..
505
505
506 rebase subset
506 rebase subset
507
507
508 $ hg clone -q -u . ah ah3
508 $ hg clone -q -u . ah ah3
509 $ cd ah3
509 $ cd ah3
510 $ hg rebase -r '3::7' -d 1
510 $ hg rebase -r '3::7' -d 1
511 abort: can't remove original changesets with unrebased descendants
511 abort: can't remove original changesets with unrebased descendants
512 (use --keep to keep original changesets)
512 (use --keep to keep original changesets)
513 [255]
513 [255]
514 $ hg rebase -r '3::7' -d 1 --keep
514 $ hg rebase -r '3::7' -d 1 --keep
515 rebasing 3:ffd453c31098 "D"
515 rebasing 3:ffd453c31098 "D"
516 rebasing 6:3d8a618087a7 "G"
516 rebasing 6:3d8a618087a7 "G"
517 rebasing 7:72434a4e60b0 "H"
517 rebasing 7:72434a4e60b0 "H"
518 $ hg tglog
518 $ hg tglog
519 o 11: 'H'
519 o 11: 'H'
520 |
520 |
521 o 10: 'G'
521 o 10: 'G'
522 |
522 |
523 o 9: 'D'
523 o 9: 'D'
524 |
524 |
525 | o 8: 'I'
525 | o 8: 'I'
526 | |
526 | |
527 | o 7: 'H'
527 | o 7: 'H'
528 | |
528 | |
529 | o 6: 'G'
529 | o 6: 'G'
530 | |
530 | |
531 | | o 5: 'F'
531 | | o 5: 'F'
532 | | |
532 | | |
533 | | o 4: 'E'
533 | | o 4: 'E'
534 | |/
534 | |/
535 | o 3: 'D'
535 | o 3: 'D'
536 | |
536 | |
537 | o 2: 'C'
537 | o 2: 'C'
538 | |
538 | |
539 o | 1: 'B'
539 o | 1: 'B'
540 |/
540 |/
541 o 0: 'A'
541 o 0: 'A'
542
542
543
543
544 $ cd ..
544 $ cd ..
545
545
546 rebase subset with multiple head
546 rebase subset with multiple head
547
547
548 $ hg clone -q -u . ah ah4
548 $ hg clone -q -u . ah ah4
549 $ cd ah4
549 $ cd ah4
550 $ hg rebase -r '3::(7+5)' -d 1
550 $ hg rebase -r '3::(7+5)' -d 1
551 abort: can't remove original changesets with unrebased descendants
551 abort: can't remove original changesets with unrebased descendants
552 (use --keep to keep original changesets)
552 (use --keep to keep original changesets)
553 [255]
553 [255]
554 $ hg rebase -r '3::(7+5)' -d 1 --keep
554 $ hg rebase -r '3::(7+5)' -d 1 --keep
555 rebasing 3:ffd453c31098 "D"
555 rebasing 3:ffd453c31098 "D"
556 rebasing 4:c01897464e7f "E"
556 rebasing 4:c01897464e7f "E"
557 rebasing 5:41bfcc75ed73 "F"
557 rebasing 5:41bfcc75ed73 "F"
558 rebasing 6:3d8a618087a7 "G"
558 rebasing 6:3d8a618087a7 "G"
559 rebasing 7:72434a4e60b0 "H"
559 rebasing 7:72434a4e60b0 "H"
560 $ hg tglog
560 $ hg tglog
561 o 13: 'H'
561 o 13: 'H'
562 |
562 |
563 o 12: 'G'
563 o 12: 'G'
564 |
564 |
565 | o 11: 'F'
565 | o 11: 'F'
566 | |
566 | |
567 | o 10: 'E'
567 | o 10: 'E'
568 |/
568 |/
569 o 9: 'D'
569 o 9: 'D'
570 |
570 |
571 | o 8: 'I'
571 | o 8: 'I'
572 | |
572 | |
573 | o 7: 'H'
573 | o 7: 'H'
574 | |
574 | |
575 | o 6: 'G'
575 | o 6: 'G'
576 | |
576 | |
577 | | o 5: 'F'
577 | | o 5: 'F'
578 | | |
578 | | |
579 | | o 4: 'E'
579 | | o 4: 'E'
580 | |/
580 | |/
581 | o 3: 'D'
581 | o 3: 'D'
582 | |
582 | |
583 | o 2: 'C'
583 | o 2: 'C'
584 | |
584 | |
585 o | 1: 'B'
585 o | 1: 'B'
586 |/
586 |/
587 o 0: 'A'
587 o 0: 'A'
588
588
589
589
590 $ cd ..
590 $ cd ..
591
591
592 More advanced tests
592 More advanced tests
593
593
594 rebase on ancestor with revset
594 rebase on ancestor with revset
595
595
596 $ hg clone -q -u . ah ah5
596 $ hg clone -q -u . ah ah5
597 $ cd ah5
597 $ cd ah5
598 $ hg rebase -r '6::' -d 2
598 $ hg rebase -r '6::' -d 2
599 rebasing 6:3d8a618087a7 "G"
599 rebasing 6:3d8a618087a7 "G"
600 rebasing 7:72434a4e60b0 "H"
600 rebasing 7:72434a4e60b0 "H"
601 rebasing 8:479ddb54a924 "I" (tip)
601 rebasing 8:479ddb54a924 "I" (tip)
602 saved backup bundle to $TESTTMP/ah5/.hg/strip-backup/3d8a618087a7-b4f73f31-backup.hg (glob)
602 saved backup bundle to $TESTTMP/ah5/.hg/strip-backup/3d8a618087a7-b4f73f31-backup.hg (glob)
603 $ hg tglog
603 $ hg tglog
604 o 8: 'I'
604 o 8: 'I'
605 |
605 |
606 o 7: 'H'
606 o 7: 'H'
607 |
607 |
608 o 6: 'G'
608 o 6: 'G'
609 |
609 |
610 | o 5: 'F'
610 | o 5: 'F'
611 | |
611 | |
612 | o 4: 'E'
612 | o 4: 'E'
613 | |
613 | |
614 | o 3: 'D'
614 | o 3: 'D'
615 |/
615 |/
616 o 2: 'C'
616 o 2: 'C'
617 |
617 |
618 | o 1: 'B'
618 | o 1: 'B'
619 |/
619 |/
620 o 0: 'A'
620 o 0: 'A'
621
621
622 $ cd ..
622 $ cd ..
623
623
624
624
625 rebase with multiple root.
625 rebase with multiple root.
626 We rebase E and G on B
626 We rebase E and G on B
627 We would expect heads are I, F if it was supported
627 We would expect heads are I, F if it was supported
628
628
629 $ hg clone -q -u . ah ah6
629 $ hg clone -q -u . ah ah6
630 $ cd ah6
630 $ cd ah6
631 $ hg rebase -r '(4+6)::' -d 1
631 $ hg rebase -r '(4+6)::' -d 1
632 rebasing 4:c01897464e7f "E"
632 rebasing 4:c01897464e7f "E"
633 rebasing 5:41bfcc75ed73 "F"
633 rebasing 5:41bfcc75ed73 "F"
634 rebasing 6:3d8a618087a7 "G"
634 rebasing 6:3d8a618087a7 "G"
635 rebasing 7:72434a4e60b0 "H"
635 rebasing 7:72434a4e60b0 "H"
636 rebasing 8:479ddb54a924 "I" (tip)
636 rebasing 8:479ddb54a924 "I" (tip)
637 saved backup bundle to $TESTTMP/ah6/.hg/strip-backup/3d8a618087a7-aae93a24-backup.hg (glob)
637 saved backup bundle to $TESTTMP/ah6/.hg/strip-backup/3d8a618087a7-aae93a24-backup.hg (glob)
638 $ hg tglog
638 $ hg tglog
639 o 8: 'I'
639 o 8: 'I'
640 |
640 |
641 o 7: 'H'
641 o 7: 'H'
642 |
642 |
643 o 6: 'G'
643 o 6: 'G'
644 |
644 |
645 | o 5: 'F'
645 | o 5: 'F'
646 | |
646 | |
647 | o 4: 'E'
647 | o 4: 'E'
648 |/
648 |/
649 | o 3: 'D'
649 | o 3: 'D'
650 | |
650 | |
651 | o 2: 'C'
651 | o 2: 'C'
652 | |
652 | |
653 o | 1: 'B'
653 o | 1: 'B'
654 |/
654 |/
655 o 0: 'A'
655 o 0: 'A'
656
656
657 $ cd ..
657 $ cd ..
658
658
659 More complex rebase with multiple roots
659 More complex rebase with multiple roots
660 each root have a different common ancestor with the destination and this is a detach
660 each root have a different common ancestor with the destination and this is a detach
661
661
662 (setup)
662 (setup)
663
663
664 $ hg clone -q -u . a a8
664 $ hg clone -q -u . a a8
665 $ cd a8
665 $ cd a8
666 $ echo I > I
666 $ echo I > I
667 $ hg add I
667 $ hg add I
668 $ hg commit -m I
668 $ hg commit -m I
669 $ hg up 4
669 $ hg up 4
670 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
670 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
671 $ echo I > J
671 $ echo I > J
672 $ hg add J
672 $ hg add J
673 $ hg commit -m J
673 $ hg commit -m J
674 created new head
674 created new head
675 $ echo I > K
675 $ echo I > K
676 $ hg add K
676 $ hg add K
677 $ hg commit -m K
677 $ hg commit -m K
678 $ hg tglog
678 $ hg tglog
679 @ 10: 'K'
679 @ 10: 'K'
680 |
680 |
681 o 9: 'J'
681 o 9: 'J'
682 |
682 |
683 | o 8: 'I'
683 | o 8: 'I'
684 | |
684 | |
685 | o 7: 'H'
685 | o 7: 'H'
686 | |
686 | |
687 +---o 6: 'G'
687 +---o 6: 'G'
688 | |/
688 | |/
689 | o 5: 'F'
689 | o 5: 'F'
690 | |
690 | |
691 o | 4: 'E'
691 o | 4: 'E'
692 |/
692 |/
693 | o 3: 'D'
693 | o 3: 'D'
694 | |
694 | |
695 | o 2: 'C'
695 | o 2: 'C'
696 | |
696 | |
697 | o 1: 'B'
697 | o 1: 'B'
698 |/
698 |/
699 o 0: 'A'
699 o 0: 'A'
700
700
701 (actual test)
701 (actual test)
702
702
703 $ hg rebase --dest 'desc(G)' --rev 'desc(K) + desc(I)'
703 $ hg rebase --dest 'desc(G)' --rev 'desc(K) + desc(I)'
704 rebasing 8:e7ec4e813ba6 "I"
704 rebasing 8:e7ec4e813ba6 "I"
705 rebasing 10:23a4ace37988 "K" (tip)
705 rebasing 10:23a4ace37988 "K" (tip)
706 saved backup bundle to $TESTTMP/a8/.hg/strip-backup/23a4ace37988-b06984b3-backup.hg (glob)
706 saved backup bundle to $TESTTMP/a8/.hg/strip-backup/23a4ace37988-b06984b3-backup.hg (glob)
707 $ hg log --rev 'children(desc(G))'
707 $ hg log --rev 'children(desc(G))'
708 changeset: 9:adb617877056
708 changeset: 9:adb617877056
709 parent: 6:eea13746799a
709 parent: 6:eea13746799a
710 user: test
710 user: test
711 date: Thu Jan 01 00:00:00 1970 +0000
711 date: Thu Jan 01 00:00:00 1970 +0000
712 summary: I
712 summary: I
713
713
714 changeset: 10:882431a34a0e
714 changeset: 10:882431a34a0e
715 tag: tip
715 tag: tip
716 parent: 6:eea13746799a
716 parent: 6:eea13746799a
717 user: test
717 user: test
718 date: Thu Jan 01 00:00:00 1970 +0000
718 date: Thu Jan 01 00:00:00 1970 +0000
719 summary: K
719 summary: K
720
720
721 $ hg tglog
721 $ hg tglog
722 @ 10: 'K'
722 @ 10: 'K'
723 |
723 |
724 | o 9: 'I'
724 | o 9: 'I'
725 |/
725 |/
726 | o 8: 'J'
726 | o 8: 'J'
727 | |
727 | |
728 | | o 7: 'H'
728 | | o 7: 'H'
729 | | |
729 | | |
730 o---+ 6: 'G'
730 o---+ 6: 'G'
731 |/ /
731 |/ /
732 | o 5: 'F'
732 | o 5: 'F'
733 | |
733 | |
734 o | 4: 'E'
734 o | 4: 'E'
735 |/
735 |/
736 | o 3: 'D'
736 | o 3: 'D'
737 | |
737 | |
738 | o 2: 'C'
738 | o 2: 'C'
739 | |
739 | |
740 | o 1: 'B'
740 | o 1: 'B'
741 |/
741 |/
742 o 0: 'A'
742 o 0: 'A'
743
743
744
744
745 Test that rebase is not confused by $CWD disappearing during rebase (issue4121)
745 Test that rebase is not confused by $CWD disappearing during rebase (issue4121)
746
746
747 $ cd ..
747 $ cd ..
748 $ hg init cwd-vanish
748 $ hg init cwd-vanish
749 $ cd cwd-vanish
749 $ cd cwd-vanish
750 $ touch initial-file
750 $ touch initial-file
751 $ hg add initial-file
751 $ hg add initial-file
752 $ hg commit -m 'initial commit'
752 $ hg commit -m 'initial commit'
753 $ touch dest-file
753 $ touch dest-file
754 $ hg add dest-file
754 $ hg add dest-file
755 $ hg commit -m 'dest commit'
755 $ hg commit -m 'dest commit'
756 $ hg up 0
756 $ hg up 0
757 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
757 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
758 $ touch other-file
758 $ touch other-file
759 $ hg add other-file
759 $ hg add other-file
760 $ hg commit -m 'first source commit'
760 $ hg commit -m 'first source commit'
761 created new head
761 created new head
762 $ mkdir subdir
762 $ mkdir subdir
763 $ cd subdir
763 $ cd subdir
764 $ touch subfile
764 $ touch subfile
765 $ hg add subfile
765 $ hg add subfile
766 $ hg commit -m 'second source with subdir'
766 $ hg commit -m 'second source with subdir'
767 #if rmcwd
767 #if rmcwd
768 $ hg rebase -b . -d 1 --traceback
768 $ hg rebase -b . -d 1 --traceback
769 rebasing 2:779a07b1b7a0 "first source commit"
769 rebasing 2:779a07b1b7a0 "first source commit"
770 current directory was removed
770 current directory was removed
771 (consider changing to repo root: $TESTTMP/cwd-vanish)
771 (consider changing to repo root: $TESTTMP/cwd-vanish)
772 rebasing 3:a7d6f3a00bf3 "second source with subdir" (tip)
772 rebasing 3:a7d6f3a00bf3 "second source with subdir" (tip)
773 saved backup bundle to $TESTTMP/cwd-vanish/.hg/strip-backup/779a07b1b7a0-853e0073-backup.hg (glob)
773 saved backup bundle to $TESTTMP/cwd-vanish/.hg/strip-backup/779a07b1b7a0-853e0073-backup.hg (glob)
774 #else
774 #else
775 $ hg rebase -b . -d 1 --traceback
775 $ hg rebase -b . -d 1 --traceback
776 rebasing 2:779a07b1b7a0 "first source commit"
776 rebasing 2:779a07b1b7a0 "first source commit"
777 rebasing 3:a7d6f3a00bf3 "second source with subdir" (tip)
777 rebasing 3:a7d6f3a00bf3 "second source with subdir" (tip)
778 saved backup bundle to $TESTTMP/cwd-vanish/.hg/strip-backup/779a07b1b7a0-853e0073-backup.hg (glob)
778 saved backup bundle to $TESTTMP/cwd-vanish/.hg/strip-backup/779a07b1b7a0-853e0073-backup.hg (glob)
779 #endif
779 #endif
780
780
781 Get back to the root of cwd-vanish. Note that even though `cd ..`
781 Get back to the root of cwd-vanish. Note that even though `cd ..`
782 works on most systems, it does not work on FreeBSD 10, so we use an
782 works on most systems, it does not work on FreeBSD 10, so we use an
783 absolute path to get back to the repository.
783 absolute path to get back to the repository.
784 $ cd $TESTTMP
784 $ cd $TESTTMP
785
785
786 Test that rebase is done in topo order (issue5370)
786 Test that rebase is done in topo order (issue5370)
787
787
788 $ hg init order
788 $ hg init order
789 $ cd order
789 $ cd order
790 $ touch a && hg add a && hg ci -m A
790 $ touch a && hg add a && hg ci -m A
791 $ touch b && hg add b && hg ci -m B
791 $ touch b && hg add b && hg ci -m B
792 $ touch c && hg add c && hg ci -m C
792 $ touch c && hg add c && hg ci -m C
793 $ hg up 1
793 $ hg up 1
794 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
794 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
795 $ touch d && hg add d && hg ci -m D
795 $ touch d && hg add d && hg ci -m D
796 created new head
796 created new head
797 $ hg up 2
797 $ hg up 2
798 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
798 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
799 $ touch e && hg add e && hg ci -m E
799 $ touch e && hg add e && hg ci -m E
800 $ hg up 3
800 $ hg up 3
801 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
801 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
802 $ touch f && hg add f && hg ci -m F
802 $ touch f && hg add f && hg ci -m F
803 $ hg up 0
803 $ hg up 0
804 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
804 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
805 $ touch g && hg add g && hg ci -m G
805 $ touch g && hg add g && hg ci -m G
806 created new head
806 created new head
807
807
808 $ hg tglog
808 $ hg tglog
809 @ 6: 'G'
809 @ 6: 'G'
810 |
810 |
811 | o 5: 'F'
811 | o 5: 'F'
812 | |
812 | |
813 | | o 4: 'E'
813 | | o 4: 'E'
814 | | |
814 | | |
815 | o | 3: 'D'
815 | o | 3: 'D'
816 | | |
816 | | |
817 | | o 2: 'C'
817 | | o 2: 'C'
818 | |/
818 | |/
819 | o 1: 'B'
819 | o 1: 'B'
820 |/
820 |/
821 o 0: 'A'
821 o 0: 'A'
822
822
823
823
824 $ hg rebase -s 1 -d 6
824 $ hg rebase -s 1 -d 6
825 rebasing 1:76035bbd54bd "B"
825 rebasing 1:76035bbd54bd "B"
826 rebasing 2:d84f5cfaaf14 "C"
826 rebasing 2:d84f5cfaaf14 "C"
827 rebasing 4:82ae8dc7a9b7 "E"
827 rebasing 4:82ae8dc7a9b7 "E"
828 rebasing 3:ab709c9f7171 "D"
828 rebasing 3:ab709c9f7171 "D"
829 rebasing 5:412b391de760 "F"
829 rebasing 5:412b391de760 "F"
830 saved backup bundle to $TESTTMP/order/.hg/strip-backup/76035bbd54bd-e341bc99-backup.hg (glob)
830 saved backup bundle to $TESTTMP/order/.hg/strip-backup/76035bbd54bd-e341bc99-backup.hg (glob)
831
831
832 $ hg tglog
832 $ hg tglog
833 o 6: 'F'
833 o 6: 'F'
834 |
834 |
835 o 5: 'D'
835 o 5: 'D'
836 |
836 |
837 | o 4: 'E'
837 | o 4: 'E'
838 | |
838 | |
839 | o 3: 'C'
839 | o 3: 'C'
840 |/
840 |/
841 o 2: 'B'
841 o 2: 'B'
842 |
842 |
843 @ 1: 'G'
843 @ 1: 'G'
844 |
844 |
845 o 0: 'A'
845 o 0: 'A'
846
846
847
847
848 Test experimental revset
848 Test experimental revset
849 ========================
849 ========================
850
850
851 $ cd ../cwd-vanish
851 $ cd ../cwd-vanish
852
852
853 Make the repo a bit more interesting
853 Make the repo a bit more interesting
854
854
855 $ hg up 1
855 $ hg up 1
856 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
856 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
857 $ echo aaa > aaa
857 $ echo aaa > aaa
858 $ hg add aaa
858 $ hg add aaa
859 $ hg commit -m aaa
859 $ hg commit -m aaa
860 created new head
860 created new head
861 $ hg log -G
861 $ hg log -G
862 @ changeset: 4:5f7bc9025ed2
862 @ changeset: 4:5f7bc9025ed2
863 | tag: tip
863 | tag: tip
864 | parent: 1:58d79cc1cf43
864 | parent: 1:58d79cc1cf43
865 | user: test
865 | user: test
866 | date: Thu Jan 01 00:00:00 1970 +0000
866 | date: Thu Jan 01 00:00:00 1970 +0000
867 | summary: aaa
867 | summary: aaa
868 |
868 |
869 | o changeset: 3:1910d5ff34ea
869 | o changeset: 3:1910d5ff34ea
870 | | user: test
870 | | user: test
871 | | date: Thu Jan 01 00:00:00 1970 +0000
871 | | date: Thu Jan 01 00:00:00 1970 +0000
872 | | summary: second source with subdir
872 | | summary: second source with subdir
873 | |
873 | |
874 | o changeset: 2:82901330b6ef
874 | o changeset: 2:82901330b6ef
875 |/ user: test
875 |/ user: test
876 | date: Thu Jan 01 00:00:00 1970 +0000
876 | date: Thu Jan 01 00:00:00 1970 +0000
877 | summary: first source commit
877 | summary: first source commit
878 |
878 |
879 o changeset: 1:58d79cc1cf43
879 o changeset: 1:58d79cc1cf43
880 | user: test
880 | user: test
881 | date: Thu Jan 01 00:00:00 1970 +0000
881 | date: Thu Jan 01 00:00:00 1970 +0000
882 | summary: dest commit
882 | summary: dest commit
883 |
883 |
884 o changeset: 0:e94b687f7da3
884 o changeset: 0:e94b687f7da3
885 user: test
885 user: test
886 date: Thu Jan 01 00:00:00 1970 +0000
886 date: Thu Jan 01 00:00:00 1970 +0000
887 summary: initial commit
887 summary: initial commit
888
888
889
889
890 Testing from lower head
890 Testing from lower head
891
891
892 $ hg up 3
892 $ hg up 3
893 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
893 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
894 $ hg log -r '_destrebase()'
894 $ hg log -r '_destrebase()'
895 changeset: 4:5f7bc9025ed2
895 changeset: 4:5f7bc9025ed2
896 tag: tip
896 tag: tip
897 parent: 1:58d79cc1cf43
897 parent: 1:58d79cc1cf43
898 user: test
898 user: test
899 date: Thu Jan 01 00:00:00 1970 +0000
899 date: Thu Jan 01 00:00:00 1970 +0000
900 summary: aaa
900 summary: aaa
901
901
902
902
903 Testing from upper head
903 Testing from upper head
904
904
905 $ hg log -r '_destrebase(4)'
905 $ hg log -r '_destrebase(4)'
906 changeset: 3:1910d5ff34ea
906 changeset: 3:1910d5ff34ea
907 user: test
907 user: test
908 date: Thu Jan 01 00:00:00 1970 +0000
908 date: Thu Jan 01 00:00:00 1970 +0000
909 summary: second source with subdir
909 summary: second source with subdir
910
910
911 $ hg up 4
911 $ hg up 4
912 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
912 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
913 $ hg log -r '_destrebase()'
913 $ hg log -r '_destrebase()'
914 changeset: 3:1910d5ff34ea
914 changeset: 3:1910d5ff34ea
915 user: test
915 user: test
916 date: Thu Jan 01 00:00:00 1970 +0000
916 date: Thu Jan 01 00:00:00 1970 +0000
917 summary: second source with subdir
917 summary: second source with subdir
918
918
919 Testing rebase being called inside another transaction
919 Testing rebase being called inside another transaction
920
920
921 $ cd $TESTTMP
921 $ cd $TESTTMP
922 $ hg init tr-state
922 $ hg init tr-state
923 $ cd tr-state
923 $ cd tr-state
924 $ cat > $TESTTMP/wraprebase.py <<EOF
924 $ cat > $TESTTMP/wraprebase.py <<EOF
925 > from __future__ import absolute_import
925 > from __future__ import absolute_import
926 > from mercurial import extensions
926 > from mercurial import extensions
927 > def _rebase(orig, ui, repo, *args, **kwargs):
927 > def _rebase(orig, ui, repo, *args, **kwargs):
928 > with repo.wlock():
928 > with repo.wlock():
929 > with repo.lock():
929 > with repo.lock():
930 > with repo.transaction('wrappedrebase'):
930 > with repo.transaction('wrappedrebase'):
931 > return orig(ui, repo, *args, **kwargs)
931 > return orig(ui, repo, *args, **kwargs)
932 > def wraprebase(loaded):
932 > def wraprebase(loaded):
933 > assert loaded
933 > assert loaded
934 > rebasemod = extensions.find('rebase')
934 > rebasemod = extensions.find('rebase')
935 > extensions.wrapcommand(rebasemod.cmdtable, 'rebase', _rebase)
935 > extensions.wrapcommand(rebasemod.cmdtable, 'rebase', _rebase)
936 > def extsetup(ui):
936 > def extsetup(ui):
937 > extensions.afterloaded('rebase', wraprebase)
937 > extensions.afterloaded('rebase', wraprebase)
938 > EOF
938 > EOF
939
939
940 $ cat >> .hg/hgrc <<EOF
940 $ cat >> .hg/hgrc <<EOF
941 > [extensions]
941 > [extensions]
942 > wraprebase=$TESTTMP/wraprebase.py
942 > wraprebase=$TESTTMP/wraprebase.py
943 > [experimental]
943 > [experimental]
944 > evolution=all
944 > evolution=all
945 > EOF
945 > EOF
946
946
947 $ hg debugdrawdag <<'EOS'
947 $ hg debugdrawdag <<'EOS'
948 > B C
948 > B C
949 > |/
949 > |/
950 > A
950 > A
951 > EOS
951 > EOS
952
952
953 $ hg rebase -s C -d B
953 $ hg rebase -s C -d B
954 rebasing 2:dc0947a82db8 "C" (C tip)
954 rebasing 2:dc0947a82db8 "C" (C tip)
955
955
956 $ [ -f .hg/rebasestate ] && echo 'WRONG: rebasestate should not exist'
956 $ [ -f .hg/rebasestate ] && echo 'WRONG: rebasestate should not exist'
957 WRONG: rebasestate should not exist
957 [1]
General Comments 0
You need to be logged in to leave comments. Login now