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