##// END OF EJS Templates
rebase: preserve metadata from grafts of changes (issue4001)
Augie Fackler -
r19861:a69a77a8 default
parent child Browse files
Show More
@@ -1,859 +1,864 b''
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 http://mercurial.selenic.com/wiki/RebaseExtension
14 http://mercurial.selenic.com/wiki/RebaseExtension
15 '''
15 '''
16
16
17 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
17 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
18 from mercurial import extensions, patch, scmutil, phases, obsolete, error
18 from mercurial import extensions, patch, scmutil, phases, obsolete, error
19 from mercurial.commands import templateopts
19 from mercurial.commands import templateopts
20 from mercurial.node import nullrev
20 from mercurial.node import nullrev
21 from mercurial.lock import release
21 from mercurial.lock import release
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23 import os, errno
23 import os, errno
24
24
25 nullmerge = -2
25 nullmerge = -2
26 revignored = -3
26 revignored = -3
27
27
28 cmdtable = {}
28 cmdtable = {}
29 command = cmdutil.command(cmdtable)
29 command = cmdutil.command(cmdtable)
30 testedwith = 'internal'
30 testedwith = 'internal'
31
31
32 def _savegraft(ctx, extra):
33 s = ctx.extra().get('source', None)
34 if s is not None:
35 extra['source'] = s
36
32 def _savebranch(ctx, extra):
37 def _savebranch(ctx, extra):
33 extra['branch'] = ctx.branch()
38 extra['branch'] = ctx.branch()
34
39
35 def _makeextrafn(copiers):
40 def _makeextrafn(copiers):
36 """make an extrafn out of the given copy-functions.
41 """make an extrafn out of the given copy-functions.
37
42
38 A copy function takes a context and an extra dict, and mutates the
43 A copy function takes a context and an extra dict, and mutates the
39 extra dict as needed based on the given context.
44 extra dict as needed based on the given context.
40 """
45 """
41 def extrafn(ctx, extra):
46 def extrafn(ctx, extra):
42 for c in copiers:
47 for c in copiers:
43 c(ctx, extra)
48 c(ctx, extra)
44 return extrafn
49 return extrafn
45
50
46 @command('rebase',
51 @command('rebase',
47 [('s', 'source', '',
52 [('s', 'source', '',
48 _('rebase from the specified changeset'), _('REV')),
53 _('rebase from the specified changeset'), _('REV')),
49 ('b', 'base', '',
54 ('b', 'base', '',
50 _('rebase from the base of the specified changeset '
55 _('rebase from the base of the specified changeset '
51 '(up to greatest common ancestor of base and dest)'),
56 '(up to greatest common ancestor of base and dest)'),
52 _('REV')),
57 _('REV')),
53 ('r', 'rev', [],
58 ('r', 'rev', [],
54 _('rebase these revisions'),
59 _('rebase these revisions'),
55 _('REV')),
60 _('REV')),
56 ('d', 'dest', '',
61 ('d', 'dest', '',
57 _('rebase onto the specified changeset'), _('REV')),
62 _('rebase onto the specified changeset'), _('REV')),
58 ('', 'collapse', False, _('collapse the rebased changesets')),
63 ('', 'collapse', False, _('collapse the rebased changesets')),
59 ('m', 'message', '',
64 ('m', 'message', '',
60 _('use text as collapse commit message'), _('TEXT')),
65 _('use text as collapse commit message'), _('TEXT')),
61 ('e', 'edit', False, _('invoke editor on commit messages')),
66 ('e', 'edit', False, _('invoke editor on commit messages')),
62 ('l', 'logfile', '',
67 ('l', 'logfile', '',
63 _('read collapse commit message from file'), _('FILE')),
68 _('read collapse commit message from file'), _('FILE')),
64 ('', 'keep', False, _('keep original changesets')),
69 ('', 'keep', False, _('keep original changesets')),
65 ('', 'keepbranches', False, _('keep original branch names')),
70 ('', 'keepbranches', False, _('keep original branch names')),
66 ('D', 'detach', False, _('(DEPRECATED)')),
71 ('D', 'detach', False, _('(DEPRECATED)')),
67 ('t', 'tool', '', _('specify merge tool')),
72 ('t', 'tool', '', _('specify merge tool')),
68 ('c', 'continue', False, _('continue an interrupted rebase')),
73 ('c', 'continue', False, _('continue an interrupted rebase')),
69 ('a', 'abort', False, _('abort an interrupted rebase'))] +
74 ('a', 'abort', False, _('abort an interrupted rebase'))] +
70 templateopts,
75 templateopts,
71 _('[-s REV | -b REV] [-d REV] [OPTION]'))
76 _('[-s REV | -b REV] [-d REV] [OPTION]'))
72 def rebase(ui, repo, **opts):
77 def rebase(ui, repo, **opts):
73 """move changeset (and descendants) to a different branch
78 """move changeset (and descendants) to a different branch
74
79
75 Rebase uses repeated merging to graft changesets from one part of
80 Rebase uses repeated merging to graft changesets from one part of
76 history (the source) onto another (the destination). This can be
81 history (the source) onto another (the destination). This can be
77 useful for linearizing *local* changes relative to a master
82 useful for linearizing *local* changes relative to a master
78 development tree.
83 development tree.
79
84
80 You should not rebase changesets that have already been shared
85 You should not rebase changesets that have already been shared
81 with others. Doing so will force everybody else to perform the
86 with others. Doing so will force everybody else to perform the
82 same rebase or they will end up with duplicated changesets after
87 same rebase or they will end up with duplicated changesets after
83 pulling in your rebased changesets.
88 pulling in your rebased changesets.
84
89
85 In its default configuration, Mercurial will prevent you from
90 In its default configuration, Mercurial will prevent you from
86 rebasing published changes. See :hg:`help phases` for details.
91 rebasing published changes. See :hg:`help phases` for details.
87
92
88 If you don't specify a destination changeset (``-d/--dest``),
93 If you don't specify a destination changeset (``-d/--dest``),
89 rebase uses the current branch tip as the destination. (The
94 rebase uses the current branch tip as the destination. (The
90 destination changeset is not modified by rebasing, but new
95 destination changeset is not modified by rebasing, but new
91 changesets are added as its descendants.)
96 changesets are added as its descendants.)
92
97
93 You can specify which changesets to rebase in two ways: as a
98 You can specify which changesets to rebase in two ways: as a
94 "source" changeset or as a "base" changeset. Both are shorthand
99 "source" changeset or as a "base" changeset. Both are shorthand
95 for a topologically related set of changesets (the "source
100 for a topologically related set of changesets (the "source
96 branch"). If you specify source (``-s/--source``), rebase will
101 branch"). If you specify source (``-s/--source``), rebase will
97 rebase that changeset and all of its descendants onto dest. If you
102 rebase that changeset and all of its descendants onto dest. If you
98 specify base (``-b/--base``), rebase will select ancestors of base
103 specify base (``-b/--base``), rebase will select ancestors of base
99 back to but not including the common ancestor with dest. Thus,
104 back to but not including the common ancestor with dest. Thus,
100 ``-b`` is less precise but more convenient than ``-s``: you can
105 ``-b`` is less precise but more convenient than ``-s``: you can
101 specify any changeset in the source branch, and rebase will select
106 specify any changeset in the source branch, and rebase will select
102 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
107 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
103 uses the parent of the working directory as the base.
108 uses the parent of the working directory as the base.
104
109
105 For advanced usage, a third way is available through the ``--rev``
110 For advanced usage, a third way is available through the ``--rev``
106 option. It allows you to specify an arbitrary set of changesets to
111 option. It allows you to specify an arbitrary set of changesets to
107 rebase. Descendants of revs you specify with this option are not
112 rebase. Descendants of revs you specify with this option are not
108 automatically included in the rebase.
113 automatically included in the rebase.
109
114
110 By default, rebase recreates the changesets in the source branch
115 By default, rebase recreates the changesets in the source branch
111 as descendants of dest and then destroys the originals. Use
116 as descendants of dest and then destroys the originals. Use
112 ``--keep`` to preserve the original source changesets. Some
117 ``--keep`` to preserve the original source changesets. Some
113 changesets in the source branch (e.g. merges from the destination
118 changesets in the source branch (e.g. merges from the destination
114 branch) may be dropped if they no longer contribute any change.
119 branch) may be dropped if they no longer contribute any change.
115
120
116 One result of the rules for selecting the destination changeset
121 One result of the rules for selecting the destination changeset
117 and source branch is that, unlike ``merge``, rebase will do
122 and source branch is that, unlike ``merge``, rebase will do
118 nothing if you are at the branch tip of a named branch
123 nothing if you are at the branch tip of a named branch
119 with two heads. You need to explicitly specify source and/or
124 with two heads. You need to explicitly specify source and/or
120 destination (or ``update`` to the other head, if it's the head of
125 destination (or ``update`` to the other head, if it's the head of
121 the intended source branch).
126 the intended source branch).
122
127
123 If a rebase is interrupted to manually resolve a merge, it can be
128 If a rebase is interrupted to manually resolve a merge, it can be
124 continued with --continue/-c or aborted with --abort/-a.
129 continued with --continue/-c or aborted with --abort/-a.
125
130
126 Returns 0 on success, 1 if nothing to rebase.
131 Returns 0 on success, 1 if nothing to rebase.
127 """
132 """
128 originalwd = target = None
133 originalwd = target = None
129 activebookmark = None
134 activebookmark = None
130 external = nullrev
135 external = nullrev
131 state = {}
136 state = {}
132 skipped = set()
137 skipped = set()
133 targetancestors = set()
138 targetancestors = set()
134
139
135 editor = None
140 editor = None
136 if opts.get('edit'):
141 if opts.get('edit'):
137 editor = cmdutil.commitforceeditor
142 editor = cmdutil.commitforceeditor
138
143
139 lock = wlock = None
144 lock = wlock = None
140 try:
145 try:
141 wlock = repo.wlock()
146 wlock = repo.wlock()
142 lock = repo.lock()
147 lock = repo.lock()
143
148
144 # Validate input and define rebasing points
149 # Validate input and define rebasing points
145 destf = opts.get('dest', None)
150 destf = opts.get('dest', None)
146 srcf = opts.get('source', None)
151 srcf = opts.get('source', None)
147 basef = opts.get('base', None)
152 basef = opts.get('base', None)
148 revf = opts.get('rev', [])
153 revf = opts.get('rev', [])
149 contf = opts.get('continue')
154 contf = opts.get('continue')
150 abortf = opts.get('abort')
155 abortf = opts.get('abort')
151 collapsef = opts.get('collapse', False)
156 collapsef = opts.get('collapse', False)
152 collapsemsg = cmdutil.logmessage(ui, opts)
157 collapsemsg = cmdutil.logmessage(ui, opts)
153 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
158 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
154 extrafns = []
159 extrafns = [_savegraft]
155 if e:
160 if e:
156 extrafns = [e]
161 extrafns = [e]
157 keepf = opts.get('keep', False)
162 keepf = opts.get('keep', False)
158 keepbranchesf = opts.get('keepbranches', False)
163 keepbranchesf = opts.get('keepbranches', False)
159 # keepopen is not meant for use on the command line, but by
164 # keepopen is not meant for use on the command line, but by
160 # other extensions
165 # other extensions
161 keepopen = opts.get('keepopen', False)
166 keepopen = opts.get('keepopen', False)
162
167
163 if collapsemsg and not collapsef:
168 if collapsemsg and not collapsef:
164 raise util.Abort(
169 raise util.Abort(
165 _('message can only be specified with collapse'))
170 _('message can only be specified with collapse'))
166
171
167 if contf or abortf:
172 if contf or abortf:
168 if contf and abortf:
173 if contf and abortf:
169 raise util.Abort(_('cannot use both abort and continue'))
174 raise util.Abort(_('cannot use both abort and continue'))
170 if collapsef:
175 if collapsef:
171 raise util.Abort(
176 raise util.Abort(
172 _('cannot use collapse with continue or abort'))
177 _('cannot use collapse with continue or abort'))
173 if srcf or basef or destf:
178 if srcf or basef or destf:
174 raise util.Abort(
179 raise util.Abort(
175 _('abort and continue do not allow specifying revisions'))
180 _('abort and continue do not allow specifying revisions'))
176 if opts.get('tool', False):
181 if opts.get('tool', False):
177 ui.warn(_('tool option will be ignored\n'))
182 ui.warn(_('tool option will be ignored\n'))
178
183
179 try:
184 try:
180 (originalwd, target, state, skipped, collapsef, keepf,
185 (originalwd, target, state, skipped, collapsef, keepf,
181 keepbranchesf, external, activebookmark) = restorestatus(repo)
186 keepbranchesf, external, activebookmark) = restorestatus(repo)
182 except error.RepoLookupError:
187 except error.RepoLookupError:
183 if abortf:
188 if abortf:
184 clearstatus(repo)
189 clearstatus(repo)
185 repo.ui.warn(_('rebase aborted (no revision is removed,'
190 repo.ui.warn(_('rebase aborted (no revision is removed,'
186 ' only broken state is cleared)\n'))
191 ' only broken state is cleared)\n'))
187 return 0
192 return 0
188 else:
193 else:
189 msg = _('cannot continue inconsistent rebase')
194 msg = _('cannot continue inconsistent rebase')
190 hint = _('use "hg rebase --abort" to clear borken state')
195 hint = _('use "hg rebase --abort" to clear borken state')
191 raise util.Abort(msg, hint=hint)
196 raise util.Abort(msg, hint=hint)
192 if abortf:
197 if abortf:
193 return abort(repo, originalwd, target, state)
198 return abort(repo, originalwd, target, state)
194 else:
199 else:
195 if srcf and basef:
200 if srcf and basef:
196 raise util.Abort(_('cannot specify both a '
201 raise util.Abort(_('cannot specify both a '
197 'source and a base'))
202 'source and a base'))
198 if revf and basef:
203 if revf and basef:
199 raise util.Abort(_('cannot specify both a '
204 raise util.Abort(_('cannot specify both a '
200 'revision and a base'))
205 'revision and a base'))
201 if revf and srcf:
206 if revf and srcf:
202 raise util.Abort(_('cannot specify both a '
207 raise util.Abort(_('cannot specify both a '
203 'revision and a source'))
208 'revision and a source'))
204
209
205 cmdutil.checkunfinished(repo)
210 cmdutil.checkunfinished(repo)
206 cmdutil.bailifchanged(repo)
211 cmdutil.bailifchanged(repo)
207
212
208 if not destf:
213 if not destf:
209 # Destination defaults to the latest revision in the
214 # Destination defaults to the latest revision in the
210 # current branch
215 # current branch
211 branch = repo[None].branch()
216 branch = repo[None].branch()
212 dest = repo[branch]
217 dest = repo[branch]
213 else:
218 else:
214 dest = scmutil.revsingle(repo, destf)
219 dest = scmutil.revsingle(repo, destf)
215
220
216 if revf:
221 if revf:
217 rebaseset = scmutil.revrange(repo, revf)
222 rebaseset = scmutil.revrange(repo, revf)
218 elif srcf:
223 elif srcf:
219 src = scmutil.revrange(repo, [srcf])
224 src = scmutil.revrange(repo, [srcf])
220 rebaseset = repo.revs('(%ld)::', src)
225 rebaseset = repo.revs('(%ld)::', src)
221 else:
226 else:
222 base = scmutil.revrange(repo, [basef or '.'])
227 base = scmutil.revrange(repo, [basef or '.'])
223 rebaseset = repo.revs(
228 rebaseset = repo.revs(
224 '(children(ancestor(%ld, %d)) and ::(%ld))::',
229 '(children(ancestor(%ld, %d)) and ::(%ld))::',
225 base, dest, base)
230 base, dest, base)
226 if rebaseset:
231 if rebaseset:
227 root = min(rebaseset)
232 root = min(rebaseset)
228 else:
233 else:
229 root = None
234 root = None
230
235
231 if not rebaseset:
236 if not rebaseset:
232 repo.ui.debug('base is ancestor of destination\n')
237 repo.ui.debug('base is ancestor of destination\n')
233 result = None
238 result = None
234 elif (not (keepf or obsolete._enabled)
239 elif (not (keepf or obsolete._enabled)
235 and repo.revs('first(children(%ld) - %ld)',
240 and repo.revs('first(children(%ld) - %ld)',
236 rebaseset, rebaseset)):
241 rebaseset, rebaseset)):
237 raise util.Abort(
242 raise util.Abort(
238 _("can't remove original changesets with"
243 _("can't remove original changesets with"
239 " unrebased descendants"),
244 " unrebased descendants"),
240 hint=_('use --keep to keep original changesets'))
245 hint=_('use --keep to keep original changesets'))
241 else:
246 else:
242 result = buildstate(repo, dest, rebaseset, collapsef)
247 result = buildstate(repo, dest, rebaseset, collapsef)
243
248
244 if not result:
249 if not result:
245 # Empty state built, nothing to rebase
250 # Empty state built, nothing to rebase
246 ui.status(_('nothing to rebase\n'))
251 ui.status(_('nothing to rebase\n'))
247 return 1
252 return 1
248 elif not keepf and not repo[root].mutable():
253 elif not keepf and not repo[root].mutable():
249 raise util.Abort(_("can't rebase immutable changeset %s")
254 raise util.Abort(_("can't rebase immutable changeset %s")
250 % repo[root],
255 % repo[root],
251 hint=_('see hg help phases for details'))
256 hint=_('see hg help phases for details'))
252 else:
257 else:
253 originalwd, target, state = result
258 originalwd, target, state = result
254 if collapsef:
259 if collapsef:
255 targetancestors = repo.changelog.ancestors([target],
260 targetancestors = repo.changelog.ancestors([target],
256 inclusive=True)
261 inclusive=True)
257 external = checkexternal(repo, state, targetancestors)
262 external = checkexternal(repo, state, targetancestors)
258
263
259 if keepbranchesf:
264 if keepbranchesf:
260 # insert _savebranch at the start of extrafns so if
265 # insert _savebranch at the start of extrafns so if
261 # there's a user-provided extrafn it can clobber branch if
266 # there's a user-provided extrafn it can clobber branch if
262 # desired
267 # desired
263 extrafns.insert(0, _savebranch)
268 extrafns.insert(0, _savebranch)
264 if collapsef:
269 if collapsef:
265 branches = set()
270 branches = set()
266 for rev in state:
271 for rev in state:
267 branches.add(repo[rev].branch())
272 branches.add(repo[rev].branch())
268 if len(branches) > 1:
273 if len(branches) > 1:
269 raise util.Abort(_('cannot collapse multiple named '
274 raise util.Abort(_('cannot collapse multiple named '
270 'branches'))
275 'branches'))
271
276
272
277
273 # Rebase
278 # Rebase
274 if not targetancestors:
279 if not targetancestors:
275 targetancestors = repo.changelog.ancestors([target], inclusive=True)
280 targetancestors = repo.changelog.ancestors([target], inclusive=True)
276
281
277 # Keep track of the current bookmarks in order to reset them later
282 # Keep track of the current bookmarks in order to reset them later
278 currentbookmarks = repo._bookmarks.copy()
283 currentbookmarks = repo._bookmarks.copy()
279 activebookmark = activebookmark or repo._bookmarkcurrent
284 activebookmark = activebookmark or repo._bookmarkcurrent
280 if activebookmark:
285 if activebookmark:
281 bookmarks.unsetcurrent(repo)
286 bookmarks.unsetcurrent(repo)
282
287
283 extrafn = _makeextrafn(extrafns)
288 extrafn = _makeextrafn(extrafns)
284
289
285 sortedstate = sorted(state)
290 sortedstate = sorted(state)
286 total = len(sortedstate)
291 total = len(sortedstate)
287 pos = 0
292 pos = 0
288 for rev in sortedstate:
293 for rev in sortedstate:
289 pos += 1
294 pos += 1
290 if state[rev] == -1:
295 if state[rev] == -1:
291 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
296 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
292 _('changesets'), total)
297 _('changesets'), total)
293 p1, p2 = defineparents(repo, rev, target, state,
298 p1, p2 = defineparents(repo, rev, target, state,
294 targetancestors)
299 targetancestors)
295 storestatus(repo, originalwd, target, state, collapsef, keepf,
300 storestatus(repo, originalwd, target, state, collapsef, keepf,
296 keepbranchesf, external, activebookmark)
301 keepbranchesf, external, activebookmark)
297 if len(repo.parents()) == 2:
302 if len(repo.parents()) == 2:
298 repo.ui.debug('resuming interrupted rebase\n')
303 repo.ui.debug('resuming interrupted rebase\n')
299 else:
304 else:
300 try:
305 try:
301 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
306 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
302 stats = rebasenode(repo, rev, p1, state, collapsef)
307 stats = rebasenode(repo, rev, p1, state, collapsef)
303 if stats and stats[3] > 0:
308 if stats and stats[3] > 0:
304 raise error.InterventionRequired(
309 raise error.InterventionRequired(
305 _('unresolved conflicts (see hg '
310 _('unresolved conflicts (see hg '
306 'resolve, then hg rebase --continue)'))
311 'resolve, then hg rebase --continue)'))
307 finally:
312 finally:
308 ui.setconfig('ui', 'forcemerge', '')
313 ui.setconfig('ui', 'forcemerge', '')
309 cmdutil.duplicatecopies(repo, rev, target)
314 cmdutil.duplicatecopies(repo, rev, target)
310 if not collapsef:
315 if not collapsef:
311 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
316 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
312 editor=editor)
317 editor=editor)
313 else:
318 else:
314 # Skip commit if we are collapsing
319 # Skip commit if we are collapsing
315 repo.setparents(repo[p1].node())
320 repo.setparents(repo[p1].node())
316 newrev = None
321 newrev = None
317 # Update the state
322 # Update the state
318 if newrev is not None:
323 if newrev is not None:
319 state[rev] = repo[newrev].rev()
324 state[rev] = repo[newrev].rev()
320 else:
325 else:
321 if not collapsef:
326 if not collapsef:
322 ui.note(_('no changes, revision %d skipped\n') % rev)
327 ui.note(_('no changes, revision %d skipped\n') % rev)
323 ui.debug('next revision set to %s\n' % p1)
328 ui.debug('next revision set to %s\n' % p1)
324 skipped.add(rev)
329 skipped.add(rev)
325 state[rev] = p1
330 state[rev] = p1
326
331
327 ui.progress(_('rebasing'), None)
332 ui.progress(_('rebasing'), None)
328 ui.note(_('rebase merging completed\n'))
333 ui.note(_('rebase merging completed\n'))
329
334
330 if collapsef and not keepopen:
335 if collapsef and not keepopen:
331 p1, p2 = defineparents(repo, min(state), target,
336 p1, p2 = defineparents(repo, min(state), target,
332 state, targetancestors)
337 state, targetancestors)
333 if collapsemsg:
338 if collapsemsg:
334 commitmsg = collapsemsg
339 commitmsg = collapsemsg
335 else:
340 else:
336 commitmsg = 'Collapsed revision'
341 commitmsg = 'Collapsed revision'
337 for rebased in state:
342 for rebased in state:
338 if rebased not in skipped and state[rebased] > nullmerge:
343 if rebased not in skipped and state[rebased] > nullmerge:
339 commitmsg += '\n* %s' % repo[rebased].description()
344 commitmsg += '\n* %s' % repo[rebased].description()
340 commitmsg = ui.edit(commitmsg, repo.ui.username())
345 commitmsg = ui.edit(commitmsg, repo.ui.username())
341 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
346 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
342 extrafn=extrafn, editor=editor)
347 extrafn=extrafn, editor=editor)
343
348
344 if 'qtip' in repo.tags():
349 if 'qtip' in repo.tags():
345 updatemq(repo, state, skipped, **opts)
350 updatemq(repo, state, skipped, **opts)
346
351
347 if currentbookmarks:
352 if currentbookmarks:
348 # Nodeids are needed to reset bookmarks
353 # Nodeids are needed to reset bookmarks
349 nstate = {}
354 nstate = {}
350 for k, v in state.iteritems():
355 for k, v in state.iteritems():
351 if v > nullmerge:
356 if v > nullmerge:
352 nstate[repo[k].node()] = repo[v].node()
357 nstate[repo[k].node()] = repo[v].node()
353 # XXX this is the same as dest.node() for the non-continue path --
358 # XXX this is the same as dest.node() for the non-continue path --
354 # this should probably be cleaned up
359 # this should probably be cleaned up
355 targetnode = repo[target].node()
360 targetnode = repo[target].node()
356
361
357 if not keepf:
362 if not keepf:
358 collapsedas = None
363 collapsedas = None
359 if collapsef:
364 if collapsef:
360 collapsedas = newrev
365 collapsedas = newrev
361 clearrebased(ui, repo, state, skipped, collapsedas)
366 clearrebased(ui, repo, state, skipped, collapsedas)
362
367
363 if currentbookmarks:
368 if currentbookmarks:
364 updatebookmarks(repo, targetnode, nstate, currentbookmarks)
369 updatebookmarks(repo, targetnode, nstate, currentbookmarks)
365
370
366 clearstatus(repo)
371 clearstatus(repo)
367 ui.note(_("rebase completed\n"))
372 ui.note(_("rebase completed\n"))
368 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
373 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
369 if skipped:
374 if skipped:
370 ui.note(_("%d revisions have been skipped\n") % len(skipped))
375 ui.note(_("%d revisions have been skipped\n") % len(skipped))
371
376
372 if (activebookmark and
377 if (activebookmark and
373 repo['tip'].node() == repo._bookmarks[activebookmark]):
378 repo['tip'].node() == repo._bookmarks[activebookmark]):
374 bookmarks.setcurrent(repo, activebookmark)
379 bookmarks.setcurrent(repo, activebookmark)
375
380
376 finally:
381 finally:
377 release(lock, wlock)
382 release(lock, wlock)
378
383
379 def checkexternal(repo, state, targetancestors):
384 def checkexternal(repo, state, targetancestors):
380 """Check whether one or more external revisions need to be taken in
385 """Check whether one or more external revisions need to be taken in
381 consideration. In the latter case, abort.
386 consideration. In the latter case, abort.
382 """
387 """
383 external = nullrev
388 external = nullrev
384 source = min(state)
389 source = min(state)
385 for rev in state:
390 for rev in state:
386 if rev == source:
391 if rev == source:
387 continue
392 continue
388 # Check externals and fail if there are more than one
393 # Check externals and fail if there are more than one
389 for p in repo[rev].parents():
394 for p in repo[rev].parents():
390 if (p.rev() not in state
395 if (p.rev() not in state
391 and p.rev() not in targetancestors):
396 and p.rev() not in targetancestors):
392 if external != nullrev:
397 if external != nullrev:
393 raise util.Abort(_('unable to collapse, there is more '
398 raise util.Abort(_('unable to collapse, there is more '
394 'than one external parent'))
399 'than one external parent'))
395 external = p.rev()
400 external = p.rev()
396 return external
401 return external
397
402
398 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
403 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
399 'Commit the changes and store useful information in extra'
404 'Commit the changes and store useful information in extra'
400 try:
405 try:
401 repo.setparents(repo[p1].node(), repo[p2].node())
406 repo.setparents(repo[p1].node(), repo[p2].node())
402 ctx = repo[rev]
407 ctx = repo[rev]
403 if commitmsg is None:
408 if commitmsg is None:
404 commitmsg = ctx.description()
409 commitmsg = ctx.description()
405 extra = {'rebase_source': ctx.hex()}
410 extra = {'rebase_source': ctx.hex()}
406 if extrafn:
411 if extrafn:
407 extrafn(ctx, extra)
412 extrafn(ctx, extra)
408 # Commit might fail if unresolved files exist
413 # Commit might fail if unresolved files exist
409 newrev = repo.commit(text=commitmsg, user=ctx.user(),
414 newrev = repo.commit(text=commitmsg, user=ctx.user(),
410 date=ctx.date(), extra=extra, editor=editor)
415 date=ctx.date(), extra=extra, editor=editor)
411 repo.dirstate.setbranch(repo[newrev].branch())
416 repo.dirstate.setbranch(repo[newrev].branch())
412 targetphase = max(ctx.phase(), phases.draft)
417 targetphase = max(ctx.phase(), phases.draft)
413 # retractboundary doesn't overwrite upper phase inherited from parent
418 # retractboundary doesn't overwrite upper phase inherited from parent
414 newnode = repo[newrev].node()
419 newnode = repo[newrev].node()
415 if newnode:
420 if newnode:
416 phases.retractboundary(repo, targetphase, [newnode])
421 phases.retractboundary(repo, targetphase, [newnode])
417 return newrev
422 return newrev
418 except util.Abort:
423 except util.Abort:
419 # Invalidate the previous setparents
424 # Invalidate the previous setparents
420 repo.dirstate.invalidate()
425 repo.dirstate.invalidate()
421 raise
426 raise
422
427
423 def rebasenode(repo, rev, p1, state, collapse):
428 def rebasenode(repo, rev, p1, state, collapse):
424 'Rebase a single revision'
429 'Rebase a single revision'
425 # Merge phase
430 # Merge phase
426 # Update to target and merge it with local
431 # Update to target and merge it with local
427 if repo['.'].rev() != repo[p1].rev():
432 if repo['.'].rev() != repo[p1].rev():
428 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
433 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
429 merge.update(repo, p1, False, True, False)
434 merge.update(repo, p1, False, True, False)
430 else:
435 else:
431 repo.ui.debug(" already in target\n")
436 repo.ui.debug(" already in target\n")
432 repo.dirstate.write()
437 repo.dirstate.write()
433 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
438 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
434 base = None
439 base = None
435 if repo[rev].rev() != repo[min(state)].rev():
440 if repo[rev].rev() != repo[min(state)].rev():
436 base = repo[rev].p1().node()
441 base = repo[rev].p1().node()
437 # When collapsing in-place, the parent is the common ancestor, we
442 # When collapsing in-place, the parent is the common ancestor, we
438 # have to allow merging with it.
443 # have to allow merging with it.
439 return merge.update(repo, rev, True, True, False, base, collapse)
444 return merge.update(repo, rev, True, True, False, base, collapse)
440
445
441 def nearestrebased(repo, rev, state):
446 def nearestrebased(repo, rev, state):
442 """return the nearest ancestors of rev in the rebase result"""
447 """return the nearest ancestors of rev in the rebase result"""
443 rebased = [r for r in state if state[r] > nullmerge]
448 rebased = [r for r in state if state[r] > nullmerge]
444 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
449 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
445 if candidates:
450 if candidates:
446 return state[candidates[0]]
451 return state[candidates[0]]
447 else:
452 else:
448 return None
453 return None
449
454
450 def defineparents(repo, rev, target, state, targetancestors):
455 def defineparents(repo, rev, target, state, targetancestors):
451 'Return the new parent relationship of the revision that will be rebased'
456 'Return the new parent relationship of the revision that will be rebased'
452 parents = repo[rev].parents()
457 parents = repo[rev].parents()
453 p1 = p2 = nullrev
458 p1 = p2 = nullrev
454
459
455 P1n = parents[0].rev()
460 P1n = parents[0].rev()
456 if P1n in targetancestors:
461 if P1n in targetancestors:
457 p1 = target
462 p1 = target
458 elif P1n in state:
463 elif P1n in state:
459 if state[P1n] == nullmerge:
464 if state[P1n] == nullmerge:
460 p1 = target
465 p1 = target
461 elif state[P1n] == revignored:
466 elif state[P1n] == revignored:
462 p1 = nearestrebased(repo, P1n, state)
467 p1 = nearestrebased(repo, P1n, state)
463 if p1 is None:
468 if p1 is None:
464 p1 = target
469 p1 = target
465 else:
470 else:
466 p1 = state[P1n]
471 p1 = state[P1n]
467 else: # P1n external
472 else: # P1n external
468 p1 = target
473 p1 = target
469 p2 = P1n
474 p2 = P1n
470
475
471 if len(parents) == 2 and parents[1].rev() not in targetancestors:
476 if len(parents) == 2 and parents[1].rev() not in targetancestors:
472 P2n = parents[1].rev()
477 P2n = parents[1].rev()
473 # interesting second parent
478 # interesting second parent
474 if P2n in state:
479 if P2n in state:
475 if p1 == target: # P1n in targetancestors or external
480 if p1 == target: # P1n in targetancestors or external
476 p1 = state[P2n]
481 p1 = state[P2n]
477 elif state[P2n] == revignored:
482 elif state[P2n] == revignored:
478 p2 = nearestrebased(repo, P2n, state)
483 p2 = nearestrebased(repo, P2n, state)
479 if p2 is None:
484 if p2 is None:
480 # no ancestors rebased yet, detach
485 # no ancestors rebased yet, detach
481 p2 = target
486 p2 = target
482 else:
487 else:
483 p2 = state[P2n]
488 p2 = state[P2n]
484 else: # P2n external
489 else: # P2n external
485 if p2 != nullrev: # P1n external too => rev is a merged revision
490 if p2 != nullrev: # P1n external too => rev is a merged revision
486 raise util.Abort(_('cannot use revision %d as base, result '
491 raise util.Abort(_('cannot use revision %d as base, result '
487 'would have 3 parents') % rev)
492 'would have 3 parents') % rev)
488 p2 = P2n
493 p2 = P2n
489 repo.ui.debug(" future parents are %d and %d\n" %
494 repo.ui.debug(" future parents are %d and %d\n" %
490 (repo[p1].rev(), repo[p2].rev()))
495 (repo[p1].rev(), repo[p2].rev()))
491 return p1, p2
496 return p1, p2
492
497
493 def isagitpatch(repo, patchname):
498 def isagitpatch(repo, patchname):
494 'Return true if the given patch is in git format'
499 'Return true if the given patch is in git format'
495 mqpatch = os.path.join(repo.mq.path, patchname)
500 mqpatch = os.path.join(repo.mq.path, patchname)
496 for line in patch.linereader(file(mqpatch, 'rb')):
501 for line in patch.linereader(file(mqpatch, 'rb')):
497 if line.startswith('diff --git'):
502 if line.startswith('diff --git'):
498 return True
503 return True
499 return False
504 return False
500
505
501 def updatemq(repo, state, skipped, **opts):
506 def updatemq(repo, state, skipped, **opts):
502 'Update rebased mq patches - finalize and then import them'
507 'Update rebased mq patches - finalize and then import them'
503 mqrebase = {}
508 mqrebase = {}
504 mq = repo.mq
509 mq = repo.mq
505 original_series = mq.fullseries[:]
510 original_series = mq.fullseries[:]
506 skippedpatches = set()
511 skippedpatches = set()
507
512
508 for p in mq.applied:
513 for p in mq.applied:
509 rev = repo[p.node].rev()
514 rev = repo[p.node].rev()
510 if rev in state:
515 if rev in state:
511 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
516 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
512 (rev, p.name))
517 (rev, p.name))
513 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
518 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
514 else:
519 else:
515 # Applied but not rebased, not sure this should happen
520 # Applied but not rebased, not sure this should happen
516 skippedpatches.add(p.name)
521 skippedpatches.add(p.name)
517
522
518 if mqrebase:
523 if mqrebase:
519 mq.finish(repo, mqrebase.keys())
524 mq.finish(repo, mqrebase.keys())
520
525
521 # We must start import from the newest revision
526 # We must start import from the newest revision
522 for rev in sorted(mqrebase, reverse=True):
527 for rev in sorted(mqrebase, reverse=True):
523 if rev not in skipped:
528 if rev not in skipped:
524 name, isgit = mqrebase[rev]
529 name, isgit = mqrebase[rev]
525 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
530 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
526 mq.qimport(repo, (), patchname=name, git=isgit,
531 mq.qimport(repo, (), patchname=name, git=isgit,
527 rev=[str(state[rev])])
532 rev=[str(state[rev])])
528 else:
533 else:
529 # Rebased and skipped
534 # Rebased and skipped
530 skippedpatches.add(mqrebase[rev][0])
535 skippedpatches.add(mqrebase[rev][0])
531
536
532 # Patches were either applied and rebased and imported in
537 # Patches were either applied and rebased and imported in
533 # order, applied and removed or unapplied. Discard the removed
538 # order, applied and removed or unapplied. Discard the removed
534 # ones while preserving the original series order and guards.
539 # ones while preserving the original series order and guards.
535 newseries = [s for s in original_series
540 newseries = [s for s in original_series
536 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
541 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
537 mq.fullseries[:] = newseries
542 mq.fullseries[:] = newseries
538 mq.seriesdirty = True
543 mq.seriesdirty = True
539 mq.savedirty()
544 mq.savedirty()
540
545
541 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
546 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
542 'Move bookmarks to their correct changesets, and delete divergent ones'
547 'Move bookmarks to their correct changesets, and delete divergent ones'
543 marks = repo._bookmarks
548 marks = repo._bookmarks
544 for k, v in originalbookmarks.iteritems():
549 for k, v in originalbookmarks.iteritems():
545 if v in nstate:
550 if v in nstate:
546 # update the bookmarks for revs that have moved
551 # update the bookmarks for revs that have moved
547 marks[k] = nstate[v]
552 marks[k] = nstate[v]
548 bookmarks.deletedivergent(repo, [targetnode], k)
553 bookmarks.deletedivergent(repo, [targetnode], k)
549
554
550 marks.write()
555 marks.write()
551
556
552 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
557 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
553 external, activebookmark):
558 external, activebookmark):
554 'Store the current status to allow recovery'
559 'Store the current status to allow recovery'
555 f = repo.opener("rebasestate", "w")
560 f = repo.opener("rebasestate", "w")
556 f.write(repo[originalwd].hex() + '\n')
561 f.write(repo[originalwd].hex() + '\n')
557 f.write(repo[target].hex() + '\n')
562 f.write(repo[target].hex() + '\n')
558 f.write(repo[external].hex() + '\n')
563 f.write(repo[external].hex() + '\n')
559 f.write('%d\n' % int(collapse))
564 f.write('%d\n' % int(collapse))
560 f.write('%d\n' % int(keep))
565 f.write('%d\n' % int(keep))
561 f.write('%d\n' % int(keepbranches))
566 f.write('%d\n' % int(keepbranches))
562 f.write('%s\n' % (activebookmark or ''))
567 f.write('%s\n' % (activebookmark or ''))
563 for d, v in state.iteritems():
568 for d, v in state.iteritems():
564 oldrev = repo[d].hex()
569 oldrev = repo[d].hex()
565 if v > nullmerge:
570 if v > nullmerge:
566 newrev = repo[v].hex()
571 newrev = repo[v].hex()
567 else:
572 else:
568 newrev = v
573 newrev = v
569 f.write("%s:%s\n" % (oldrev, newrev))
574 f.write("%s:%s\n" % (oldrev, newrev))
570 f.close()
575 f.close()
571 repo.ui.debug('rebase status stored\n')
576 repo.ui.debug('rebase status stored\n')
572
577
573 def clearstatus(repo):
578 def clearstatus(repo):
574 'Remove the status files'
579 'Remove the status files'
575 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
580 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
576
581
577 def restorestatus(repo):
582 def restorestatus(repo):
578 'Restore a previously stored status'
583 'Restore a previously stored status'
579 try:
584 try:
580 target = None
585 target = None
581 collapse = False
586 collapse = False
582 external = nullrev
587 external = nullrev
583 activebookmark = None
588 activebookmark = None
584 state = {}
589 state = {}
585 f = repo.opener("rebasestate")
590 f = repo.opener("rebasestate")
586 for i, l in enumerate(f.read().splitlines()):
591 for i, l in enumerate(f.read().splitlines()):
587 if i == 0:
592 if i == 0:
588 originalwd = repo[l].rev()
593 originalwd = repo[l].rev()
589 elif i == 1:
594 elif i == 1:
590 target = repo[l].rev()
595 target = repo[l].rev()
591 elif i == 2:
596 elif i == 2:
592 external = repo[l].rev()
597 external = repo[l].rev()
593 elif i == 3:
598 elif i == 3:
594 collapse = bool(int(l))
599 collapse = bool(int(l))
595 elif i == 4:
600 elif i == 4:
596 keep = bool(int(l))
601 keep = bool(int(l))
597 elif i == 5:
602 elif i == 5:
598 keepbranches = bool(int(l))
603 keepbranches = bool(int(l))
599 elif i == 6 and not (len(l) == 81 and ':' in l):
604 elif i == 6 and not (len(l) == 81 and ':' in l):
600 # line 6 is a recent addition, so for backwards compatibility
605 # line 6 is a recent addition, so for backwards compatibility
601 # check that the line doesn't look like the oldrev:newrev lines
606 # check that the line doesn't look like the oldrev:newrev lines
602 activebookmark = l
607 activebookmark = l
603 else:
608 else:
604 oldrev, newrev = l.split(':')
609 oldrev, newrev = l.split(':')
605 if newrev in (str(nullmerge), str(revignored)):
610 if newrev in (str(nullmerge), str(revignored)):
606 state[repo[oldrev].rev()] = int(newrev)
611 state[repo[oldrev].rev()] = int(newrev)
607 else:
612 else:
608 state[repo[oldrev].rev()] = repo[newrev].rev()
613 state[repo[oldrev].rev()] = repo[newrev].rev()
609 skipped = set()
614 skipped = set()
610 # recompute the set of skipped revs
615 # recompute the set of skipped revs
611 if not collapse:
616 if not collapse:
612 seen = set([target])
617 seen = set([target])
613 for old, new in sorted(state.items()):
618 for old, new in sorted(state.items()):
614 if new != nullrev and new in seen:
619 if new != nullrev and new in seen:
615 skipped.add(old)
620 skipped.add(old)
616 seen.add(new)
621 seen.add(new)
617 repo.ui.debug('computed skipped revs: %s\n' % skipped)
622 repo.ui.debug('computed skipped revs: %s\n' % skipped)
618 repo.ui.debug('rebase status resumed\n')
623 repo.ui.debug('rebase status resumed\n')
619 return (originalwd, target, state, skipped,
624 return (originalwd, target, state, skipped,
620 collapse, keep, keepbranches, external, activebookmark)
625 collapse, keep, keepbranches, external, activebookmark)
621 except IOError, err:
626 except IOError, err:
622 if err.errno != errno.ENOENT:
627 if err.errno != errno.ENOENT:
623 raise
628 raise
624 raise util.Abort(_('no rebase in progress'))
629 raise util.Abort(_('no rebase in progress'))
625
630
626 def inrebase(repo, originalwd, state):
631 def inrebase(repo, originalwd, state):
627 '''check whether the workdir is in an interrupted rebase'''
632 '''check whether the workdir is in an interrupted rebase'''
628 parents = [p.rev() for p in repo.parents()]
633 parents = [p.rev() for p in repo.parents()]
629 if originalwd in parents:
634 if originalwd in parents:
630 return True
635 return True
631
636
632 for newrev in state.itervalues():
637 for newrev in state.itervalues():
633 if newrev in parents:
638 if newrev in parents:
634 return True
639 return True
635
640
636 return False
641 return False
637
642
638 def abort(repo, originalwd, target, state):
643 def abort(repo, originalwd, target, state):
639 'Restore the repository to its original state'
644 'Restore the repository to its original state'
640 dstates = [s for s in state.values() if s != nullrev]
645 dstates = [s for s in state.values() if s != nullrev]
641 immutable = [d for d in dstates if not repo[d].mutable()]
646 immutable = [d for d in dstates if not repo[d].mutable()]
642 cleanup = True
647 cleanup = True
643 if immutable:
648 if immutable:
644 repo.ui.warn(_("warning: can't clean up immutable changesets %s\n")
649 repo.ui.warn(_("warning: can't clean up immutable changesets %s\n")
645 % ', '.join(str(repo[r]) for r in immutable),
650 % ', '.join(str(repo[r]) for r in immutable),
646 hint=_('see hg help phases for details'))
651 hint=_('see hg help phases for details'))
647 cleanup = False
652 cleanup = False
648
653
649 descendants = set()
654 descendants = set()
650 if dstates:
655 if dstates:
651 descendants = set(repo.changelog.descendants(dstates))
656 descendants = set(repo.changelog.descendants(dstates))
652 if descendants - set(dstates):
657 if descendants - set(dstates):
653 repo.ui.warn(_("warning: new changesets detected on target branch, "
658 repo.ui.warn(_("warning: new changesets detected on target branch, "
654 "can't strip\n"))
659 "can't strip\n"))
655 cleanup = False
660 cleanup = False
656
661
657 if cleanup:
662 if cleanup:
658 # Update away from the rebase if necessary
663 # Update away from the rebase if necessary
659 if inrebase(repo, originalwd, state):
664 if inrebase(repo, originalwd, state):
660 merge.update(repo, repo[originalwd].rev(), False, True, False)
665 merge.update(repo, repo[originalwd].rev(), False, True, False)
661
666
662 # Strip from the first rebased revision
667 # Strip from the first rebased revision
663 rebased = filter(lambda x: x > -1 and x != target, state.values())
668 rebased = filter(lambda x: x > -1 and x != target, state.values())
664 if rebased:
669 if rebased:
665 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
670 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
666 # no backup of rebased cset versions needed
671 # no backup of rebased cset versions needed
667 repair.strip(repo.ui, repo, strippoints)
672 repair.strip(repo.ui, repo, strippoints)
668
673
669 clearstatus(repo)
674 clearstatus(repo)
670 repo.ui.warn(_('rebase aborted\n'))
675 repo.ui.warn(_('rebase aborted\n'))
671 return 0
676 return 0
672
677
673 def buildstate(repo, dest, rebaseset, collapse):
678 def buildstate(repo, dest, rebaseset, collapse):
674 '''Define which revisions are going to be rebased and where
679 '''Define which revisions are going to be rebased and where
675
680
676 repo: repo
681 repo: repo
677 dest: context
682 dest: context
678 rebaseset: set of rev
683 rebaseset: set of rev
679 '''
684 '''
680
685
681 # This check isn't strictly necessary, since mq detects commits over an
686 # This check isn't strictly necessary, since mq detects commits over an
682 # applied patch. But it prevents messing up the working directory when
687 # applied patch. But it prevents messing up the working directory when
683 # a partially completed rebase is blocked by mq.
688 # a partially completed rebase is blocked by mq.
684 if 'qtip' in repo.tags() and (dest.node() in
689 if 'qtip' in repo.tags() and (dest.node() in
685 [s.node for s in repo.mq.applied]):
690 [s.node for s in repo.mq.applied]):
686 raise util.Abort(_('cannot rebase onto an applied mq patch'))
691 raise util.Abort(_('cannot rebase onto an applied mq patch'))
687
692
688 roots = list(repo.set('roots(%ld)', rebaseset))
693 roots = list(repo.set('roots(%ld)', rebaseset))
689 if not roots:
694 if not roots:
690 raise util.Abort(_('no matching revisions'))
695 raise util.Abort(_('no matching revisions'))
691 roots.sort()
696 roots.sort()
692 state = {}
697 state = {}
693 detachset = set()
698 detachset = set()
694 for root in roots:
699 for root in roots:
695 commonbase = root.ancestor(dest)
700 commonbase = root.ancestor(dest)
696 if commonbase == root:
701 if commonbase == root:
697 raise util.Abort(_('source is ancestor of destination'))
702 raise util.Abort(_('source is ancestor of destination'))
698 if commonbase == dest:
703 if commonbase == dest:
699 samebranch = root.branch() == dest.branch()
704 samebranch = root.branch() == dest.branch()
700 if not collapse and samebranch and root in dest.children():
705 if not collapse and samebranch and root in dest.children():
701 repo.ui.debug('source is a child of destination\n')
706 repo.ui.debug('source is a child of destination\n')
702 return None
707 return None
703
708
704 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, roots))
709 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, roots))
705 state.update(dict.fromkeys(rebaseset, nullrev))
710 state.update(dict.fromkeys(rebaseset, nullrev))
706 # Rebase tries to turn <dest> into a parent of <root> while
711 # Rebase tries to turn <dest> into a parent of <root> while
707 # preserving the number of parents of rebased changesets:
712 # preserving the number of parents of rebased changesets:
708 #
713 #
709 # - A changeset with a single parent will always be rebased as a
714 # - A changeset with a single parent will always be rebased as a
710 # changeset with a single parent.
715 # changeset with a single parent.
711 #
716 #
712 # - A merge will be rebased as merge unless its parents are both
717 # - A merge will be rebased as merge unless its parents are both
713 # ancestors of <dest> or are themselves in the rebased set and
718 # ancestors of <dest> or are themselves in the rebased set and
714 # pruned while rebased.
719 # pruned while rebased.
715 #
720 #
716 # If one parent of <root> is an ancestor of <dest>, the rebased
721 # If one parent of <root> is an ancestor of <dest>, the rebased
717 # version of this parent will be <dest>. This is always true with
722 # version of this parent will be <dest>. This is always true with
718 # --base option.
723 # --base option.
719 #
724 #
720 # Otherwise, we need to *replace* the original parents with
725 # Otherwise, we need to *replace* the original parents with
721 # <dest>. This "detaches" the rebased set from its former location
726 # <dest>. This "detaches" the rebased set from its former location
722 # and rebases it onto <dest>. Changes introduced by ancestors of
727 # and rebases it onto <dest>. Changes introduced by ancestors of
723 # <root> not common with <dest> (the detachset, marked as
728 # <root> not common with <dest> (the detachset, marked as
724 # nullmerge) are "removed" from the rebased changesets.
729 # nullmerge) are "removed" from the rebased changesets.
725 #
730 #
726 # - If <root> has a single parent, set it to <dest>.
731 # - If <root> has a single parent, set it to <dest>.
727 #
732 #
728 # - If <root> is a merge, we cannot decide which parent to
733 # - If <root> is a merge, we cannot decide which parent to
729 # replace, the rebase operation is not clearly defined.
734 # replace, the rebase operation is not clearly defined.
730 #
735 #
731 # The table below sums up this behavior:
736 # The table below sums up this behavior:
732 #
737 #
733 # +------------------+----------------------+-------------------------+
738 # +------------------+----------------------+-------------------------+
734 # | | one parent | merge |
739 # | | one parent | merge |
735 # +------------------+----------------------+-------------------------+
740 # +------------------+----------------------+-------------------------+
736 # | parent in | new parent is <dest> | parents in ::<dest> are |
741 # | parent in | new parent is <dest> | parents in ::<dest> are |
737 # | ::<dest> | | remapped to <dest> |
742 # | ::<dest> | | remapped to <dest> |
738 # +------------------+----------------------+-------------------------+
743 # +------------------+----------------------+-------------------------+
739 # | unrelated source | new parent is <dest> | ambiguous, abort |
744 # | unrelated source | new parent is <dest> | ambiguous, abort |
740 # +------------------+----------------------+-------------------------+
745 # +------------------+----------------------+-------------------------+
741 #
746 #
742 # The actual abort is handled by `defineparents`
747 # The actual abort is handled by `defineparents`
743 if len(root.parents()) <= 1:
748 if len(root.parents()) <= 1:
744 # ancestors of <root> not ancestors of <dest>
749 # ancestors of <root> not ancestors of <dest>
745 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
750 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
746 [root.rev()]))
751 [root.rev()]))
747 for r in detachset:
752 for r in detachset:
748 if r not in state:
753 if r not in state:
749 state[r] = nullmerge
754 state[r] = nullmerge
750 if len(roots) > 1:
755 if len(roots) > 1:
751 # If we have multiple roots, we may have "hole" in the rebase set.
756 # If we have multiple roots, we may have "hole" in the rebase set.
752 # Rebase roots that descend from those "hole" should not be detached as
757 # Rebase roots that descend from those "hole" should not be detached as
753 # other root are. We use the special `revignored` to inform rebase that
758 # other root are. We use the special `revignored` to inform rebase that
754 # the revision should be ignored but that `defineparents` should search
759 # the revision should be ignored but that `defineparents` should search
755 # a rebase destination that make sense regarding rebased topology.
760 # a rebase destination that make sense regarding rebased topology.
756 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
761 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
757 for ignored in set(rebasedomain) - set(rebaseset):
762 for ignored in set(rebasedomain) - set(rebaseset):
758 state[ignored] = revignored
763 state[ignored] = revignored
759 return repo['.'].rev(), dest.rev(), state
764 return repo['.'].rev(), dest.rev(), state
760
765
761 def clearrebased(ui, repo, state, skipped, collapsedas=None):
766 def clearrebased(ui, repo, state, skipped, collapsedas=None):
762 """dispose of rebased revision at the end of the rebase
767 """dispose of rebased revision at the end of the rebase
763
768
764 If `collapsedas` is not None, the rebase was a collapse whose result if the
769 If `collapsedas` is not None, the rebase was a collapse whose result if the
765 `collapsedas` node."""
770 `collapsedas` node."""
766 if obsolete._enabled:
771 if obsolete._enabled:
767 markers = []
772 markers = []
768 for rev, newrev in sorted(state.items()):
773 for rev, newrev in sorted(state.items()):
769 if newrev >= 0:
774 if newrev >= 0:
770 if rev in skipped:
775 if rev in skipped:
771 succs = ()
776 succs = ()
772 elif collapsedas is not None:
777 elif collapsedas is not None:
773 succs = (repo[collapsedas],)
778 succs = (repo[collapsedas],)
774 else:
779 else:
775 succs = (repo[newrev],)
780 succs = (repo[newrev],)
776 markers.append((repo[rev], succs))
781 markers.append((repo[rev], succs))
777 if markers:
782 if markers:
778 obsolete.createmarkers(repo, markers)
783 obsolete.createmarkers(repo, markers)
779 else:
784 else:
780 rebased = [rev for rev in state if state[rev] > nullmerge]
785 rebased = [rev for rev in state if state[rev] > nullmerge]
781 if rebased:
786 if rebased:
782 stripped = []
787 stripped = []
783 for root in repo.set('roots(%ld)', rebased):
788 for root in repo.set('roots(%ld)', rebased):
784 if set(repo.changelog.descendants([root.rev()])) - set(state):
789 if set(repo.changelog.descendants([root.rev()])) - set(state):
785 ui.warn(_("warning: new changesets detected "
790 ui.warn(_("warning: new changesets detected "
786 "on source branch, not stripping\n"))
791 "on source branch, not stripping\n"))
787 else:
792 else:
788 stripped.append(root.node())
793 stripped.append(root.node())
789 if stripped:
794 if stripped:
790 # backup the old csets by default
795 # backup the old csets by default
791 repair.strip(ui, repo, stripped, "all")
796 repair.strip(ui, repo, stripped, "all")
792
797
793
798
794 def pullrebase(orig, ui, repo, *args, **opts):
799 def pullrebase(orig, ui, repo, *args, **opts):
795 'Call rebase after pull if the latter has been invoked with --rebase'
800 'Call rebase after pull if the latter has been invoked with --rebase'
796 if opts.get('rebase'):
801 if opts.get('rebase'):
797 if opts.get('update'):
802 if opts.get('update'):
798 del opts['update']
803 del opts['update']
799 ui.debug('--update and --rebase are not compatible, ignoring '
804 ui.debug('--update and --rebase are not compatible, ignoring '
800 'the update flag\n')
805 'the update flag\n')
801
806
802 movemarkfrom = repo['.'].node()
807 movemarkfrom = repo['.'].node()
803 revsprepull = len(repo)
808 revsprepull = len(repo)
804 origpostincoming = commands.postincoming
809 origpostincoming = commands.postincoming
805 def _dummy(*args, **kwargs):
810 def _dummy(*args, **kwargs):
806 pass
811 pass
807 commands.postincoming = _dummy
812 commands.postincoming = _dummy
808 try:
813 try:
809 orig(ui, repo, *args, **opts)
814 orig(ui, repo, *args, **opts)
810 finally:
815 finally:
811 commands.postincoming = origpostincoming
816 commands.postincoming = origpostincoming
812 revspostpull = len(repo)
817 revspostpull = len(repo)
813 if revspostpull > revsprepull:
818 if revspostpull > revsprepull:
814 # --rev option from pull conflict with rebase own --rev
819 # --rev option from pull conflict with rebase own --rev
815 # dropping it
820 # dropping it
816 if 'rev' in opts:
821 if 'rev' in opts:
817 del opts['rev']
822 del opts['rev']
818 rebase(ui, repo, **opts)
823 rebase(ui, repo, **opts)
819 branch = repo[None].branch()
824 branch = repo[None].branch()
820 dest = repo[branch].rev()
825 dest = repo[branch].rev()
821 if dest != repo['.'].rev():
826 if dest != repo['.'].rev():
822 # there was nothing to rebase we force an update
827 # there was nothing to rebase we force an update
823 hg.update(repo, dest)
828 hg.update(repo, dest)
824 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
829 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
825 ui.status(_("updating bookmark %s\n")
830 ui.status(_("updating bookmark %s\n")
826 % repo._bookmarkcurrent)
831 % repo._bookmarkcurrent)
827 else:
832 else:
828 if opts.get('tool'):
833 if opts.get('tool'):
829 raise util.Abort(_('--tool can only be used with --rebase'))
834 raise util.Abort(_('--tool can only be used with --rebase'))
830 orig(ui, repo, *args, **opts)
835 orig(ui, repo, *args, **opts)
831
836
832 def summaryhook(ui, repo):
837 def summaryhook(ui, repo):
833 if not os.path.exists(repo.join('rebasestate')):
838 if not os.path.exists(repo.join('rebasestate')):
834 return
839 return
835 try:
840 try:
836 state = restorestatus(repo)[2]
841 state = restorestatus(repo)[2]
837 except error.RepoLookupError:
842 except error.RepoLookupError:
838 # i18n: column positioning for "hg summary"
843 # i18n: column positioning for "hg summary"
839 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
844 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
840 ui.write(msg)
845 ui.write(msg)
841 return
846 return
842 numrebased = len([i for i in state.itervalues() if i != -1])
847 numrebased = len([i for i in state.itervalues() if i != -1])
843 # i18n: column positioning for "hg summary"
848 # i18n: column positioning for "hg summary"
844 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
849 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
845 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
850 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
846 ui.label(_('%d remaining'), 'rebase.remaining') %
851 ui.label(_('%d remaining'), 'rebase.remaining') %
847 (len(state) - numrebased)))
852 (len(state) - numrebased)))
848
853
849 def uisetup(ui):
854 def uisetup(ui):
850 'Replace pull with a decorator to provide --rebase option'
855 'Replace pull with a decorator to provide --rebase option'
851 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
856 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
852 entry[1].append(('', 'rebase', None,
857 entry[1].append(('', 'rebase', None,
853 _("rebase working directory to branch head")))
858 _("rebase working directory to branch head")))
854 entry[1].append(('t', 'tool', '',
859 entry[1].append(('t', 'tool', '',
855 _("specify merge tool for rebase")))
860 _("specify merge tool for rebase")))
856 cmdutil.summaryhooks.add('rebase', summaryhook)
861 cmdutil.summaryhooks.add('rebase', summaryhook)
857 cmdutil.unfinishedstates.append(
862 cmdutil.unfinishedstates.append(
858 ['rebasestate', False, False, _('rebase in progress'),
863 ['rebasestate', False, False, _('rebase in progress'),
859 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
864 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
@@ -1,458 +1,473 b''
1 ==========================
1 ==========================
2 Test rebase with obsolete
2 Test rebase with obsolete
3 ==========================
3 ==========================
4
4
5 Enable obsolete
5 Enable obsolete
6
6
7 $ cat > ${TESTTMP}/obs.py << EOF
7 $ cat > ${TESTTMP}/obs.py << EOF
8 > import mercurial.obsolete
8 > import mercurial.obsolete
9 > mercurial.obsolete._enabled = True
9 > mercurial.obsolete._enabled = True
10 > EOF
10 > EOF
11 $ cat >> $HGRCPATH << EOF
11 $ cat >> $HGRCPATH << EOF
12 > [ui]
12 > [ui]
13 > logtemplate= {rev}:{node|short} {desc|firstline}
13 > logtemplate= {rev}:{node|short} {desc|firstline}
14 > [phases]
14 > [phases]
15 > publish=False
15 > publish=False
16 > [extensions]'
16 > [extensions]'
17 > rebase=
17 > rebase=
18 >
18 >
19 > obs=${TESTTMP}/obs.py
19 > obs=${TESTTMP}/obs.py
20 > EOF
20 > EOF
21
21
22 Setup rebase canonical repo
22 Setup rebase canonical repo
23
23
24 $ hg init base
24 $ hg init base
25 $ cd base
25 $ cd base
26 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
26 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
27 adding changesets
27 adding changesets
28 adding manifests
28 adding manifests
29 adding file changes
29 adding file changes
30 added 8 changesets with 7 changes to 7 files (+2 heads)
30 added 8 changesets with 7 changes to 7 files (+2 heads)
31 (run 'hg heads' to see heads, 'hg merge' to merge)
31 (run 'hg heads' to see heads, 'hg merge' to merge)
32 $ hg up tip
32 $ hg up tip
33 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
33 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
34 $ hg log -G
34 $ hg log -G
35 @ 7:02de42196ebe H
35 @ 7:02de42196ebe H
36 |
36 |
37 | o 6:eea13746799a G
37 | o 6:eea13746799a G
38 |/|
38 |/|
39 o | 5:24b6387c8c8c F
39 o | 5:24b6387c8c8c F
40 | |
40 | |
41 | o 4:9520eea781bc E
41 | o 4:9520eea781bc E
42 |/
42 |/
43 | o 3:32af7686d403 D
43 | o 3:32af7686d403 D
44 | |
44 | |
45 | o 2:5fddd98957c8 C
45 | o 2:5fddd98957c8 C
46 | |
46 | |
47 | o 1:42ccdea3bb16 B
47 | o 1:42ccdea3bb16 B
48 |/
48 |/
49 o 0:cd010b8cd998 A
49 o 0:cd010b8cd998 A
50
50
51 $ cd ..
51 $ cd ..
52
52
53 simple rebase
53 simple rebase
54 ---------------------------------
54 ---------------------------------
55
55
56 $ hg clone base simple
56 $ hg clone base simple
57 updating to branch default
57 updating to branch default
58 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
58 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
59 $ cd simple
59 $ cd simple
60 $ hg up 32af7686d403
60 $ hg up 32af7686d403
61 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
61 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
62 $ hg rebase -d eea13746799a
62 $ hg rebase -d eea13746799a
63 $ hg log -G
63 $ hg log -G
64 @ 10:8eeb3c33ad33 D
64 @ 10:8eeb3c33ad33 D
65 |
65 |
66 o 9:2327fea05063 C
66 o 9:2327fea05063 C
67 |
67 |
68 o 8:e4e5be0395b2 B
68 o 8:e4e5be0395b2 B
69 |
69 |
70 | o 7:02de42196ebe H
70 | o 7:02de42196ebe H
71 | |
71 | |
72 o | 6:eea13746799a G
72 o | 6:eea13746799a G
73 |\|
73 |\|
74 | o 5:24b6387c8c8c F
74 | o 5:24b6387c8c8c F
75 | |
75 | |
76 o | 4:9520eea781bc E
76 o | 4:9520eea781bc E
77 |/
77 |/
78 o 0:cd010b8cd998 A
78 o 0:cd010b8cd998 A
79
79
80 $ hg log --hidden -G
80 $ hg log --hidden -G
81 @ 10:8eeb3c33ad33 D
81 @ 10:8eeb3c33ad33 D
82 |
82 |
83 o 9:2327fea05063 C
83 o 9:2327fea05063 C
84 |
84 |
85 o 8:e4e5be0395b2 B
85 o 8:e4e5be0395b2 B
86 |
86 |
87 | o 7:02de42196ebe H
87 | o 7:02de42196ebe H
88 | |
88 | |
89 o | 6:eea13746799a G
89 o | 6:eea13746799a G
90 |\|
90 |\|
91 | o 5:24b6387c8c8c F
91 | o 5:24b6387c8c8c F
92 | |
92 | |
93 o | 4:9520eea781bc E
93 o | 4:9520eea781bc E
94 |/
94 |/
95 | x 3:32af7686d403 D
95 | x 3:32af7686d403 D
96 | |
96 | |
97 | x 2:5fddd98957c8 C
97 | x 2:5fddd98957c8 C
98 | |
98 | |
99 | x 1:42ccdea3bb16 B
99 | x 1:42ccdea3bb16 B
100 |/
100 |/
101 o 0:cd010b8cd998 A
101 o 0:cd010b8cd998 A
102
102
103 $ hg debugobsolete
103 $ hg debugobsolete
104 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 e4e5be0395b2cbd471ed22a26b1b6a1a0658a794 0 {'date': '*', 'user': 'test'} (glob)
104 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 e4e5be0395b2cbd471ed22a26b1b6a1a0658a794 0 {'date': '*', 'user': 'test'} (glob)
105 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 2327fea05063f39961b14cb69435a9898dc9a245 0 {'date': '*', 'user': 'test'} (glob)
105 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 2327fea05063f39961b14cb69435a9898dc9a245 0 {'date': '*', 'user': 'test'} (glob)
106 32af7686d403cf45b5d95f2d70cebea587ac806a 8eeb3c33ad33d452c89e5dcf611c347f978fb42b 0 {'date': '*', 'user': 'test'} (glob)
106 32af7686d403cf45b5d95f2d70cebea587ac806a 8eeb3c33ad33d452c89e5dcf611c347f978fb42b 0 {'date': '*', 'user': 'test'} (glob)
107
107
108
108
109 $ cd ..
109 $ cd ..
110
110
111 empty changeset
111 empty changeset
112 ---------------------------------
112 ---------------------------------
113
113
114 $ hg clone base empty
114 $ hg clone base empty
115 updating to branch default
115 updating to branch default
116 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
116 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 $ cd empty
117 $ cd empty
118 $ hg up eea13746799a
118 $ hg up eea13746799a
119 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
119 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
120
120
121 We make a copy of both the first changeset in the rebased and some other in the
121 We make a copy of both the first changeset in the rebased and some other in the
122 set.
122 set.
123
123
124 $ hg graft 42ccdea3bb16 32af7686d403
124 $ hg graft 42ccdea3bb16 32af7686d403
125 grafting revision 1
125 grafting revision 1
126 grafting revision 3
126 grafting revision 3
127 $ hg rebase -s 42ccdea3bb16 -d .
127 $ hg rebase -s 42ccdea3bb16 -d .
128 $ hg log -G
128 $ hg log -G
129 @ 10:5ae4c968c6ac C
129 @ 10:5ae4c968c6ac C
130 |
130 |
131 o 9:08483444fef9 D
131 o 9:08483444fef9 D
132 |
132 |
133 o 8:8877864f1edb B
133 o 8:8877864f1edb B
134 |
134 |
135 | o 7:02de42196ebe H
135 | o 7:02de42196ebe H
136 | |
136 | |
137 o | 6:eea13746799a G
137 o | 6:eea13746799a G
138 |\|
138 |\|
139 | o 5:24b6387c8c8c F
139 | o 5:24b6387c8c8c F
140 | |
140 | |
141 o | 4:9520eea781bc E
141 o | 4:9520eea781bc E
142 |/
142 |/
143 o 0:cd010b8cd998 A
143 o 0:cd010b8cd998 A
144
144
145 $ hg log --hidden -G
145 $ hg log --hidden -G
146 @ 10:5ae4c968c6ac C
146 @ 10:5ae4c968c6ac C
147 |
147 |
148 o 9:08483444fef9 D
148 o 9:08483444fef9 D
149 |
149 |
150 o 8:8877864f1edb B
150 o 8:8877864f1edb B
151 |
151 |
152 | o 7:02de42196ebe H
152 | o 7:02de42196ebe H
153 | |
153 | |
154 o | 6:eea13746799a G
154 o | 6:eea13746799a G
155 |\|
155 |\|
156 | o 5:24b6387c8c8c F
156 | o 5:24b6387c8c8c F
157 | |
157 | |
158 o | 4:9520eea781bc E
158 o | 4:9520eea781bc E
159 |/
159 |/
160 | x 3:32af7686d403 D
160 | x 3:32af7686d403 D
161 | |
161 | |
162 | x 2:5fddd98957c8 C
162 | x 2:5fddd98957c8 C
163 | |
163 | |
164 | x 1:42ccdea3bb16 B
164 | x 1:42ccdea3bb16 B
165 |/
165 |/
166 o 0:cd010b8cd998 A
166 o 0:cd010b8cd998 A
167
167
168 $ hg debugobsolete
168 $ hg debugobsolete
169 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {'date': '*', 'user': 'test'} (glob)
169 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {'date': '*', 'user': 'test'} (glob)
170 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 {'date': '*', 'user': 'test'} (glob)
170 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 {'date': '*', 'user': 'test'} (glob)
171 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {'date': '*', 'user': 'test'} (glob)
171 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {'date': '*', 'user': 'test'} (glob)
172
172
173
173
174 More complex case were part of the rebase set were already rebased
174 More complex case were part of the rebase set were already rebased
175
175
176 $ hg rebase --rev 'desc(D)' --dest 'desc(H)'
176 $ hg rebase --rev 'desc(D)' --dest 'desc(H)'
177 $ hg debugobsolete
177 $ hg debugobsolete
178 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {'date': '*', 'user': 'test'} (glob)
178 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {'date': '*', 'user': 'test'} (glob)
179 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 {'date': '*', 'user': 'test'} (glob)
179 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 {'date': '*', 'user': 'test'} (glob)
180 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {'date': '*', 'user': 'test'} (glob)
180 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {'date': '*', 'user': 'test'} (glob)
181 08483444fef91d6224f6655ee586a65d263ad34c cbc07f26687521cecfc9a141bf5ecfc0fd2b8531 0 {'date': '* *', 'user': 'test'} (glob)
181 08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 {'date': '* *', 'user': 'test'} (glob)
182 $ hg log -G
182 $ hg log -G
183 @ 11:cbc07f266875 D
183 @ 11:4596109a6a43 D
184 |
184 |
185 | o 10:5ae4c968c6ac C
185 | o 10:5ae4c968c6ac C
186 | |
186 | |
187 | x 9:08483444fef9 D
187 | x 9:08483444fef9 D
188 | |
188 | |
189 | o 8:8877864f1edb B
189 | o 8:8877864f1edb B
190 | |
190 | |
191 o | 7:02de42196ebe H
191 o | 7:02de42196ebe H
192 | |
192 | |
193 | o 6:eea13746799a G
193 | o 6:eea13746799a G
194 |/|
194 |/|
195 o | 5:24b6387c8c8c F
195 o | 5:24b6387c8c8c F
196 | |
196 | |
197 | o 4:9520eea781bc E
197 | o 4:9520eea781bc E
198 |/
198 |/
199 o 0:cd010b8cd998 A
199 o 0:cd010b8cd998 A
200
200
201 $ hg rebase --source 'desc(B)' --dest 'tip'
201 $ hg rebase --source 'desc(B)' --dest 'tip'
202 $ hg debugobsolete
202 $ hg debugobsolete
203 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {'date': '* *', 'user': 'test'} (glob)
203 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {'date': '* *', 'user': 'test'} (glob)
204 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 {'date': '* *', 'user': 'test'} (glob)
204 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 {'date': '* *', 'user': 'test'} (glob)
205 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {'date': '* *', 'user': 'test'} (glob)
205 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {'date': '* *', 'user': 'test'} (glob)
206 08483444fef91d6224f6655ee586a65d263ad34c cbc07f26687521cecfc9a141bf5ecfc0fd2b8531 0 {'date': '* *', 'user': 'test'} (glob)
206 08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 {'date': '* *', 'user': 'test'} (glob)
207 8877864f1edb05d0e07dc4ba77b67a80a7b86672 b1861c79d66ec3aa1b607ac3c9fb819e38b12238 0 {'date': '* *', 'user': 'test'} (glob)
207 8877864f1edb05d0e07dc4ba77b67a80a7b86672 462a34d07e599b87ea08676a449373fe4e2e1347 0 {'date': '* *', 'user': 'test'} (glob)
208 08483444fef91d6224f6655ee586a65d263ad34c 0 {'date': '* *', 'user': 'test'} (glob)
208 08483444fef91d6224f6655ee586a65d263ad34c 0 {'date': '* *', 'user': 'test'} (glob)
209 5ae4c968c6aca831df823664e706c9d4aa34473d dd4be135457a404ce5541de427ae1d98a28f4acd 0 {'date': '* *', 'user': 'test'} (glob)
209 5ae4c968c6aca831df823664e706c9d4aa34473d 98f6af4ee9539e14da4465128f894c274900b6e5 0 {'date': '* *', 'user': 'test'} (glob)
210 $ hg log --rev 'divergent()'
210 $ hg log --rev 'divergent()'
211 $ hg log -G
211 $ hg log -G
212 @ 13:dd4be135457a C
212 @ 13:98f6af4ee953 C
213 |
213 |
214 o 12:b1861c79d66e B
214 o 12:462a34d07e59 B
215 |
215 |
216 o 11:cbc07f266875 D
216 o 11:4596109a6a43 D
217 |
217 |
218 o 7:02de42196ebe H
218 o 7:02de42196ebe H
219 |
219 |
220 | o 6:eea13746799a G
220 | o 6:eea13746799a G
221 |/|
221 |/|
222 o | 5:24b6387c8c8c F
222 o | 5:24b6387c8c8c F
223 | |
223 | |
224 | o 4:9520eea781bc E
224 | o 4:9520eea781bc E
225 |/
225 |/
226 o 0:cd010b8cd998 A
226 o 0:cd010b8cd998 A
227
227
228 $ hg log --style default --debug -r 4596109a6a4328c398bde3a4a3b6737cfade3003
229 changeset: 11:4596109a6a4328c398bde3a4a3b6737cfade3003
230 phase: draft
231 parent: 7:02de42196ebee42ef284b6780a87cdc96e8eaab6
232 parent: -1:0000000000000000000000000000000000000000
233 manifest: 11:a91006e3a02f1edf631f7018e6e5684cf27dd905
234 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
235 date: Sat Apr 30 15:24:48 2011 +0200
236 files+: D
237 extra: branch=default
238 extra: rebase_source=08483444fef91d6224f6655ee586a65d263ad34c
239 extra: source=32af7686d403cf45b5d95f2d70cebea587ac806a
240 description:
241 D
242
228
243
229 $ cd ..
244 $ cd ..
230
245
231 collapse rebase
246 collapse rebase
232 ---------------------------------
247 ---------------------------------
233
248
234 $ hg clone base collapse
249 $ hg clone base collapse
235 updating to branch default
250 updating to branch default
236 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
251 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
237 $ cd collapse
252 $ cd collapse
238 $ hg rebase -s 42ccdea3bb16 -d eea13746799a --collapse
253 $ hg rebase -s 42ccdea3bb16 -d eea13746799a --collapse
239 $ hg log -G
254 $ hg log -G
240 @ 8:4dc2197e807b Collapsed revision
255 @ 8:4dc2197e807b Collapsed revision
241 |
256 |
242 | o 7:02de42196ebe H
257 | o 7:02de42196ebe H
243 | |
258 | |
244 o | 6:eea13746799a G
259 o | 6:eea13746799a G
245 |\|
260 |\|
246 | o 5:24b6387c8c8c F
261 | o 5:24b6387c8c8c F
247 | |
262 | |
248 o | 4:9520eea781bc E
263 o | 4:9520eea781bc E
249 |/
264 |/
250 o 0:cd010b8cd998 A
265 o 0:cd010b8cd998 A
251
266
252 $ hg log --hidden -G
267 $ hg log --hidden -G
253 @ 8:4dc2197e807b Collapsed revision
268 @ 8:4dc2197e807b Collapsed revision
254 |
269 |
255 | o 7:02de42196ebe H
270 | o 7:02de42196ebe H
256 | |
271 | |
257 o | 6:eea13746799a G
272 o | 6:eea13746799a G
258 |\|
273 |\|
259 | o 5:24b6387c8c8c F
274 | o 5:24b6387c8c8c F
260 | |
275 | |
261 o | 4:9520eea781bc E
276 o | 4:9520eea781bc E
262 |/
277 |/
263 | x 3:32af7686d403 D
278 | x 3:32af7686d403 D
264 | |
279 | |
265 | x 2:5fddd98957c8 C
280 | x 2:5fddd98957c8 C
266 | |
281 | |
267 | x 1:42ccdea3bb16 B
282 | x 1:42ccdea3bb16 B
268 |/
283 |/
269 o 0:cd010b8cd998 A
284 o 0:cd010b8cd998 A
270
285
271 $ hg id --debug
286 $ hg id --debug
272 4dc2197e807bae9817f09905b50ab288be2dbbcf tip
287 4dc2197e807bae9817f09905b50ab288be2dbbcf tip
273 $ hg debugobsolete
288 $ hg debugobsolete
274 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 {'date': '*', 'user': 'test'} (glob)
289 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 {'date': '*', 'user': 'test'} (glob)
275 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 {'date': '*', 'user': 'test'} (glob)
290 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 {'date': '*', 'user': 'test'} (glob)
276 32af7686d403cf45b5d95f2d70cebea587ac806a 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 {'date': '*', 'user': 'test'} (glob)
291 32af7686d403cf45b5d95f2d70cebea587ac806a 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 {'date': '*', 'user': 'test'} (glob)
277
292
278 $ cd ..
293 $ cd ..
279
294
280 Rebase set has hidden descendants
295 Rebase set has hidden descendants
281 ---------------------------------
296 ---------------------------------
282
297
283 We rebase a changeset which has a hidden changeset. The hidden changeset must
298 We rebase a changeset which has a hidden changeset. The hidden changeset must
284 not be rebased.
299 not be rebased.
285
300
286 $ hg clone base hidden
301 $ hg clone base hidden
287 updating to branch default
302 updating to branch default
288 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
303 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
289 $ cd hidden
304 $ cd hidden
290 $ hg rebase -s 5fddd98957c8 -d eea13746799a
305 $ hg rebase -s 5fddd98957c8 -d eea13746799a
291 $ hg rebase -s 42ccdea3bb16 -d 02de42196ebe
306 $ hg rebase -s 42ccdea3bb16 -d 02de42196ebe
292 $ hg log -G
307 $ hg log -G
293 @ 10:7c6027df6a99 B
308 @ 10:7c6027df6a99 B
294 |
309 |
295 | o 9:cf44d2f5a9f4 D
310 | o 9:cf44d2f5a9f4 D
296 | |
311 | |
297 | o 8:e273c5e7d2d2 C
312 | o 8:e273c5e7d2d2 C
298 | |
313 | |
299 o | 7:02de42196ebe H
314 o | 7:02de42196ebe H
300 | |
315 | |
301 | o 6:eea13746799a G
316 | o 6:eea13746799a G
302 |/|
317 |/|
303 o | 5:24b6387c8c8c F
318 o | 5:24b6387c8c8c F
304 | |
319 | |
305 | o 4:9520eea781bc E
320 | o 4:9520eea781bc E
306 |/
321 |/
307 o 0:cd010b8cd998 A
322 o 0:cd010b8cd998 A
308
323
309 $ hg log --hidden -G
324 $ hg log --hidden -G
310 @ 10:7c6027df6a99 B
325 @ 10:7c6027df6a99 B
311 |
326 |
312 | o 9:cf44d2f5a9f4 D
327 | o 9:cf44d2f5a9f4 D
313 | |
328 | |
314 | o 8:e273c5e7d2d2 C
329 | o 8:e273c5e7d2d2 C
315 | |
330 | |
316 o | 7:02de42196ebe H
331 o | 7:02de42196ebe H
317 | |
332 | |
318 | o 6:eea13746799a G
333 | o 6:eea13746799a G
319 |/|
334 |/|
320 o | 5:24b6387c8c8c F
335 o | 5:24b6387c8c8c F
321 | |
336 | |
322 | o 4:9520eea781bc E
337 | o 4:9520eea781bc E
323 |/
338 |/
324 | x 3:32af7686d403 D
339 | x 3:32af7686d403 D
325 | |
340 | |
326 | x 2:5fddd98957c8 C
341 | x 2:5fddd98957c8 C
327 | |
342 | |
328 | x 1:42ccdea3bb16 B
343 | x 1:42ccdea3bb16 B
329 |/
344 |/
330 o 0:cd010b8cd998 A
345 o 0:cd010b8cd998 A
331
346
332 $ hg debugobsolete
347 $ hg debugobsolete
333 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b e273c5e7d2d29df783dce9f9eaa3ac4adc69c15d 0 {'date': '*', 'user': 'test'} (glob)
348 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b e273c5e7d2d29df783dce9f9eaa3ac4adc69c15d 0 {'date': '*', 'user': 'test'} (glob)
334 32af7686d403cf45b5d95f2d70cebea587ac806a cf44d2f5a9f4297a62be94cbdd3dff7c7dc54258 0 {'date': '*', 'user': 'test'} (glob)
349 32af7686d403cf45b5d95f2d70cebea587ac806a cf44d2f5a9f4297a62be94cbdd3dff7c7dc54258 0 {'date': '*', 'user': 'test'} (glob)
335 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 7c6027df6a99d93f461868e5433f63bde20b6dfb 0 {'date': '*', 'user': 'test'} (glob)
350 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 7c6027df6a99d93f461868e5433f63bde20b6dfb 0 {'date': '*', 'user': 'test'} (glob)
336
351
337 Test that rewriting leaving instability behind is allowed
352 Test that rewriting leaving instability behind is allowed
338 ---------------------------------------------------------------------
353 ---------------------------------------------------------------------
339
354
340 $ hg log -r 'children(8)'
355 $ hg log -r 'children(8)'
341 9:cf44d2f5a9f4 D (no-eol)
356 9:cf44d2f5a9f4 D (no-eol)
342 $ hg rebase -r 8
357 $ hg rebase -r 8
343 $ hg log -G
358 $ hg log -G
344 @ 11:0d8f238b634c C
359 @ 11:0d8f238b634c C
345 |
360 |
346 o 10:7c6027df6a99 B
361 o 10:7c6027df6a99 B
347 |
362 |
348 | o 9:cf44d2f5a9f4 D
363 | o 9:cf44d2f5a9f4 D
349 | |
364 | |
350 | x 8:e273c5e7d2d2 C
365 | x 8:e273c5e7d2d2 C
351 | |
366 | |
352 o | 7:02de42196ebe H
367 o | 7:02de42196ebe H
353 | |
368 | |
354 | o 6:eea13746799a G
369 | o 6:eea13746799a G
355 |/|
370 |/|
356 o | 5:24b6387c8c8c F
371 o | 5:24b6387c8c8c F
357 | |
372 | |
358 | o 4:9520eea781bc E
373 | o 4:9520eea781bc E
359 |/
374 |/
360 o 0:cd010b8cd998 A
375 o 0:cd010b8cd998 A
361
376
362
377
363
378
364 Test multiple root handling
379 Test multiple root handling
365 ------------------------------------
380 ------------------------------------
366
381
367 $ hg rebase --dest 4 --rev '7+11+9'
382 $ hg rebase --dest 4 --rev '7+11+9'
368 $ hg log -G
383 $ hg log -G
369 @ 14:1e8370e38cca C
384 @ 14:1e8370e38cca C
370 |
385 |
371 | o 13:102b4c1d889b D
386 | o 13:102b4c1d889b D
372 | |
387 | |
373 o | 12:bfe264faf697 H
388 o | 12:bfe264faf697 H
374 |/
389 |/
375 | o 10:7c6027df6a99 B
390 | o 10:7c6027df6a99 B
376 | |
391 | |
377 | x 7:02de42196ebe H
392 | x 7:02de42196ebe H
378 | |
393 | |
379 +---o 6:eea13746799a G
394 +---o 6:eea13746799a G
380 | |/
395 | |/
381 | o 5:24b6387c8c8c F
396 | o 5:24b6387c8c8c F
382 | |
397 | |
383 o | 4:9520eea781bc E
398 o | 4:9520eea781bc E
384 |/
399 |/
385 o 0:cd010b8cd998 A
400 o 0:cd010b8cd998 A
386
401
387 $ cd ..
402 $ cd ..
388
403
389 test on rebase dropping a merge
404 test on rebase dropping a merge
390
405
391 (setup)
406 (setup)
392
407
393 $ hg init dropmerge
408 $ hg init dropmerge
394 $ cd dropmerge
409 $ cd dropmerge
395 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
410 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
396 adding changesets
411 adding changesets
397 adding manifests
412 adding manifests
398 adding file changes
413 adding file changes
399 added 8 changesets with 7 changes to 7 files (+2 heads)
414 added 8 changesets with 7 changes to 7 files (+2 heads)
400 (run 'hg heads' to see heads, 'hg merge' to merge)
415 (run 'hg heads' to see heads, 'hg merge' to merge)
401 $ hg up 3
416 $ hg up 3
402 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
417 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
403 $ hg merge 7
418 $ hg merge 7
404 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
419 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
405 (branch merge, don't forget to commit)
420 (branch merge, don't forget to commit)
406 $ hg ci -m 'M'
421 $ hg ci -m 'M'
407 $ echo I > I
422 $ echo I > I
408 $ hg add I
423 $ hg add I
409 $ hg ci -m I
424 $ hg ci -m I
410 $ hg log -G
425 $ hg log -G
411 @ 9:4bde274eefcf I
426 @ 9:4bde274eefcf I
412 |
427 |
413 o 8:53a6a128b2b7 M
428 o 8:53a6a128b2b7 M
414 |\
429 |\
415 | o 7:02de42196ebe H
430 | o 7:02de42196ebe H
416 | |
431 | |
417 | | o 6:eea13746799a G
432 | | o 6:eea13746799a G
418 | |/|
433 | |/|
419 | o | 5:24b6387c8c8c F
434 | o | 5:24b6387c8c8c F
420 | | |
435 | | |
421 | | o 4:9520eea781bc E
436 | | o 4:9520eea781bc E
422 | |/
437 | |/
423 o | 3:32af7686d403 D
438 o | 3:32af7686d403 D
424 | |
439 | |
425 o | 2:5fddd98957c8 C
440 o | 2:5fddd98957c8 C
426 | |
441 | |
427 o | 1:42ccdea3bb16 B
442 o | 1:42ccdea3bb16 B
428 |/
443 |/
429 o 0:cd010b8cd998 A
444 o 0:cd010b8cd998 A
430
445
431 (actual test)
446 (actual test)
432
447
433 $ hg rebase --dest 6 --rev '((desc(H) + desc(D))::) - desc(M)'
448 $ hg rebase --dest 6 --rev '((desc(H) + desc(D))::) - desc(M)'
434 $ hg log -G
449 $ hg log -G
435 @ 12:acd174b7ab39 I
450 @ 12:acd174b7ab39 I
436 |
451 |
437 o 11:6c11a6218c97 H
452 o 11:6c11a6218c97 H
438 |
453 |
439 | o 10:b5313c85b22e D
454 | o 10:b5313c85b22e D
440 |/
455 |/
441 | o 8:53a6a128b2b7 M
456 | o 8:53a6a128b2b7 M
442 | |\
457 | |\
443 | | x 7:02de42196ebe H
458 | | x 7:02de42196ebe H
444 | | |
459 | | |
445 o---+ 6:eea13746799a G
460 o---+ 6:eea13746799a G
446 | | |
461 | | |
447 | | o 5:24b6387c8c8c F
462 | | o 5:24b6387c8c8c F
448 | | |
463 | | |
449 o---+ 4:9520eea781bc E
464 o---+ 4:9520eea781bc E
450 / /
465 / /
451 x | 3:32af7686d403 D
466 x | 3:32af7686d403 D
452 | |
467 | |
453 o | 2:5fddd98957c8 C
468 o | 2:5fddd98957c8 C
454 | |
469 | |
455 o | 1:42ccdea3bb16 B
470 o | 1:42ccdea3bb16 B
456 |/
471 |/
457 o 0:cd010b8cd998 A
472 o 0:cd010b8cd998 A
458
473
General Comments 0
You need to be logged in to leave comments. Login now