##// END OF EJS Templates
rebase: write series file without removed mq patches...
Mads Kiilerich -
r15904:7d28d6a6 default
parent child Browse files
Show More
@@ -1,645 +1,649 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
18 from mercurial import extensions, patch, scmutil
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
29
30 @command('rebase',
30 @command('rebase',
31 [('s', 'source', '',
31 [('s', 'source', '',
32 _('rebase from the specified changeset'), _('REV')),
32 _('rebase from the specified changeset'), _('REV')),
33 ('b', 'base', '',
33 ('b', 'base', '',
34 _('rebase from the base of the specified changeset '
34 _('rebase from the base of the specified changeset '
35 '(up to greatest common ancestor of base and dest)'),
35 '(up to greatest common ancestor of base and dest)'),
36 _('REV')),
36 _('REV')),
37 ('r', 'rev', [],
37 ('r', 'rev', [],
38 _('rebase these revisions'),
38 _('rebase these revisions'),
39 _('REV')),
39 _('REV')),
40 ('d', 'dest', '',
40 ('d', 'dest', '',
41 _('rebase onto the specified changeset'), _('REV')),
41 _('rebase onto the specified changeset'), _('REV')),
42 ('', 'collapse', False, _('collapse the rebased changesets')),
42 ('', 'collapse', False, _('collapse the rebased changesets')),
43 ('m', 'message', '',
43 ('m', 'message', '',
44 _('use text as collapse commit message'), _('TEXT')),
44 _('use text as collapse commit message'), _('TEXT')),
45 ('e', 'edit', False, _('invoke editor on commit messages')),
45 ('e', 'edit', False, _('invoke editor on commit messages')),
46 ('l', 'logfile', '',
46 ('l', 'logfile', '',
47 _('read collapse commit message from file'), _('FILE')),
47 _('read collapse commit message from file'), _('FILE')),
48 ('', 'keep', False, _('keep original changesets')),
48 ('', 'keep', False, _('keep original changesets')),
49 ('', 'keepbranches', False, _('keep original branch names')),
49 ('', 'keepbranches', False, _('keep original branch names')),
50 ('D', 'detach', False, _('force detaching of source from its original '
50 ('D', 'detach', False, _('force detaching of source from its original '
51 'branch')),
51 'branch')),
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 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
56 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
57 'hg rebase {-a|-c}'))
57 'hg rebase {-a|-c}'))
58 def rebase(ui, repo, **opts):
58 def rebase(ui, repo, **opts):
59 """move changeset (and descendants) to a different branch
59 """move changeset (and descendants) to a different branch
60
60
61 Rebase uses repeated merging to graft changesets from one part of
61 Rebase uses repeated merging to graft changesets from one part of
62 history (the source) onto another (the destination). This can be
62 history (the source) onto another (the destination). This can be
63 useful for linearizing *local* changes relative to a master
63 useful for linearizing *local* changes relative to a master
64 development tree.
64 development tree.
65
65
66 You should not rebase changesets that have already been shared
66 You should not rebase changesets that have already been shared
67 with others. Doing so will force everybody else to perform the
67 with others. Doing so will force everybody else to perform the
68 same rebase or they will end up with duplicated changesets after
68 same rebase or they will end up with duplicated changesets after
69 pulling in your rebased changesets.
69 pulling in your rebased changesets.
70
70
71 If you don't specify a destination changeset (``-d/--dest``),
71 If you don't specify a destination changeset (``-d/--dest``),
72 rebase uses the tipmost head of the current named branch as the
72 rebase uses the tipmost head of the current named branch as the
73 destination. (The destination changeset is not modified by
73 destination. (The destination changeset is not modified by
74 rebasing, but new changesets are added as its descendants.)
74 rebasing, but new changesets are added as its descendants.)
75
75
76 You can specify which changesets to rebase in two ways: as a
76 You can specify which changesets to rebase in two ways: as a
77 "source" changeset or as a "base" changeset. Both are shorthand
77 "source" changeset or as a "base" changeset. Both are shorthand
78 for a topologically related set of changesets (the "source
78 for a topologically related set of changesets (the "source
79 branch"). If you specify source (``-s/--source``), rebase will
79 branch"). If you specify source (``-s/--source``), rebase will
80 rebase that changeset and all of its descendants onto dest. If you
80 rebase that changeset and all of its descendants onto dest. If you
81 specify base (``-b/--base``), rebase will select ancestors of base
81 specify base (``-b/--base``), rebase will select ancestors of base
82 back to but not including the common ancestor with dest. Thus,
82 back to but not including the common ancestor with dest. Thus,
83 ``-b`` is less precise but more convenient than ``-s``: you can
83 ``-b`` is less precise but more convenient than ``-s``: you can
84 specify any changeset in the source branch, and rebase will select
84 specify any changeset in the source branch, and rebase will select
85 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
85 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
86 uses the parent of the working directory as the base.
86 uses the parent of the working directory as the base.
87
87
88 By default, rebase recreates the changesets in the source branch
88 By default, rebase recreates the changesets in the source branch
89 as descendants of dest and then destroys the originals. Use
89 as descendants of dest and then destroys the originals. Use
90 ``--keep`` to preserve the original source changesets. Some
90 ``--keep`` to preserve the original source changesets. Some
91 changesets in the source branch (e.g. merges from the destination
91 changesets in the source branch (e.g. merges from the destination
92 branch) may be dropped if they no longer contribute any change.
92 branch) may be dropped if they no longer contribute any change.
93
93
94 One result of the rules for selecting the destination changeset
94 One result of the rules for selecting the destination changeset
95 and source branch is that, unlike ``merge``, rebase will do
95 and source branch is that, unlike ``merge``, rebase will do
96 nothing if you are at the latest (tipmost) head of a named branch
96 nothing if you are at the latest (tipmost) head of a named branch
97 with two heads. You need to explicitly specify source and/or
97 with two heads. You need to explicitly specify source and/or
98 destination (or ``update`` to the other head, if it's the head of
98 destination (or ``update`` to the other head, if it's the head of
99 the intended source branch).
99 the intended source branch).
100
100
101 If a rebase is interrupted to manually resolve a merge, it can be
101 If a rebase is interrupted to manually resolve a merge, it can be
102 continued with --continue/-c or aborted with --abort/-a.
102 continued with --continue/-c or aborted with --abort/-a.
103
103
104 Returns 0 on success, 1 if nothing to rebase.
104 Returns 0 on success, 1 if nothing to rebase.
105 """
105 """
106 originalwd = target = None
106 originalwd = target = None
107 external = nullrev
107 external = nullrev
108 state = {}
108 state = {}
109 skipped = set()
109 skipped = set()
110 targetancestors = set()
110 targetancestors = set()
111
111
112 editor = None
112 editor = None
113 if opts.get('edit'):
113 if opts.get('edit'):
114 editor = cmdutil.commitforceeditor
114 editor = cmdutil.commitforceeditor
115
115
116 lock = wlock = None
116 lock = wlock = None
117 try:
117 try:
118 wlock = repo.wlock()
118 wlock = repo.wlock()
119 lock = repo.lock()
119 lock = repo.lock()
120
120
121 # Validate input and define rebasing points
121 # Validate input and define rebasing points
122 destf = opts.get('dest', None)
122 destf = opts.get('dest', None)
123 srcf = opts.get('source', None)
123 srcf = opts.get('source', None)
124 basef = opts.get('base', None)
124 basef = opts.get('base', None)
125 revf = opts.get('rev', [])
125 revf = opts.get('rev', [])
126 contf = opts.get('continue')
126 contf = opts.get('continue')
127 abortf = opts.get('abort')
127 abortf = opts.get('abort')
128 collapsef = opts.get('collapse', False)
128 collapsef = opts.get('collapse', False)
129 collapsemsg = cmdutil.logmessage(ui, opts)
129 collapsemsg = cmdutil.logmessage(ui, opts)
130 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
130 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
131 keepf = opts.get('keep', False)
131 keepf = opts.get('keep', False)
132 keepbranchesf = opts.get('keepbranches', False)
132 keepbranchesf = opts.get('keepbranches', False)
133 detachf = opts.get('detach', False)
133 detachf = opts.get('detach', False)
134 # keepopen is not meant for use on the command line, but by
134 # keepopen is not meant for use on the command line, but by
135 # other extensions
135 # other extensions
136 keepopen = opts.get('keepopen', False)
136 keepopen = opts.get('keepopen', False)
137
137
138 if collapsemsg and not collapsef:
138 if collapsemsg and not collapsef:
139 raise util.Abort(
139 raise util.Abort(
140 _('message can only be specified with collapse'))
140 _('message can only be specified with collapse'))
141
141
142 if contf or abortf:
142 if contf or abortf:
143 if contf and abortf:
143 if contf and abortf:
144 raise util.Abort(_('cannot use both abort and continue'))
144 raise util.Abort(_('cannot use both abort and continue'))
145 if collapsef:
145 if collapsef:
146 raise util.Abort(
146 raise util.Abort(
147 _('cannot use collapse with continue or abort'))
147 _('cannot use collapse with continue or abort'))
148 if detachf:
148 if detachf:
149 raise util.Abort(_('cannot use detach with continue or abort'))
149 raise util.Abort(_('cannot use detach with continue or abort'))
150 if srcf or basef or destf:
150 if srcf or basef or destf:
151 raise util.Abort(
151 raise util.Abort(
152 _('abort and continue do not allow specifying revisions'))
152 _('abort and continue do not allow specifying revisions'))
153 if opts.get('tool', False):
153 if opts.get('tool', False):
154 ui.warn(_('tool option will be ignored\n'))
154 ui.warn(_('tool option will be ignored\n'))
155
155
156 (originalwd, target, state, skipped, collapsef, keepf,
156 (originalwd, target, state, skipped, collapsef, keepf,
157 keepbranchesf, external) = restorestatus(repo)
157 keepbranchesf, external) = restorestatus(repo)
158 if abortf:
158 if abortf:
159 return abort(repo, originalwd, target, state)
159 return abort(repo, originalwd, target, state)
160 else:
160 else:
161 if srcf and basef:
161 if srcf and basef:
162 raise util.Abort(_('cannot specify both a '
162 raise util.Abort(_('cannot specify both a '
163 'source and a base'))
163 'source and a base'))
164 if revf and basef:
164 if revf and basef:
165 raise util.Abort(_('cannot specify both a '
165 raise util.Abort(_('cannot specify both a '
166 'revision and a base'))
166 'revision and a base'))
167 if revf and srcf:
167 if revf and srcf:
168 raise util.Abort(_('cannot specify both a '
168 raise util.Abort(_('cannot specify both a '
169 'revision and a source'))
169 'revision and a source'))
170 if detachf:
170 if detachf:
171 if not (srcf or revf):
171 if not (srcf or revf):
172 raise util.Abort(
172 raise util.Abort(
173 _('detach requires a revision to be specified'))
173 _('detach requires a revision to be specified'))
174 if basef:
174 if basef:
175 raise util.Abort(_('cannot specify a base with detach'))
175 raise util.Abort(_('cannot specify a base with detach'))
176
176
177 cmdutil.bailifchanged(repo)
177 cmdutil.bailifchanged(repo)
178
178
179 if not destf:
179 if not destf:
180 # Destination defaults to the latest revision in the
180 # Destination defaults to the latest revision in the
181 # current branch
181 # current branch
182 branch = repo[None].branch()
182 branch = repo[None].branch()
183 dest = repo[branch]
183 dest = repo[branch]
184 else:
184 else:
185 dest = repo[destf]
185 dest = repo[destf]
186
186
187 if revf:
187 if revf:
188 rebaseset = repo.revs('%lr', revf)
188 rebaseset = repo.revs('%lr', revf)
189 elif srcf:
189 elif srcf:
190 src = scmutil.revrange(repo, [srcf])
190 src = scmutil.revrange(repo, [srcf])
191 rebaseset = repo.revs('(%ld)::', src)
191 rebaseset = repo.revs('(%ld)::', src)
192 else:
192 else:
193 base = scmutil.revrange(repo, [basef or '.'])
193 base = scmutil.revrange(repo, [basef or '.'])
194 rebaseset = repo.revs(
194 rebaseset = repo.revs(
195 '(children(ancestor(%ld, %d)) and ::(%ld))::',
195 '(children(ancestor(%ld, %d)) and ::(%ld))::',
196 base, dest, base)
196 base, dest, base)
197
197
198 if rebaseset:
198 if rebaseset:
199 root = min(rebaseset)
199 root = min(rebaseset)
200 else:
200 else:
201 root = None
201 root = None
202
202
203 if not rebaseset:
203 if not rebaseset:
204 repo.ui.debug('base is ancestor of destination')
204 repo.ui.debug('base is ancestor of destination')
205 result = None
205 result = None
206 elif not keepf and list(repo.revs('first(children(%ld) - %ld)',
206 elif not keepf and list(repo.revs('first(children(%ld) - %ld)',
207 rebaseset, rebaseset)):
207 rebaseset, rebaseset)):
208 raise util.Abort(
208 raise util.Abort(
209 _("can't remove original changesets with"
209 _("can't remove original changesets with"
210 " unrebased descendants"),
210 " unrebased descendants"),
211 hint=_('use --keep to keep original changesets'))
211 hint=_('use --keep to keep original changesets'))
212 elif not keepf and not repo[root].mutable():
212 elif not keepf and not repo[root].mutable():
213 raise util.Abort(_("Can't rebase immutable changeset %s")
213 raise util.Abort(_("Can't rebase immutable changeset %s")
214 % repo[root],
214 % repo[root],
215 hint=_('see hg help phases for details'))
215 hint=_('see hg help phases for details'))
216 else:
216 else:
217 result = buildstate(repo, dest, rebaseset, detachf)
217 result = buildstate(repo, dest, rebaseset, detachf)
218
218
219 if not result:
219 if not result:
220 # Empty state built, nothing to rebase
220 # Empty state built, nothing to rebase
221 ui.status(_('nothing to rebase\n'))
221 ui.status(_('nothing to rebase\n'))
222 return 1
222 return 1
223 else:
223 else:
224 originalwd, target, state = result
224 originalwd, target, state = result
225 if collapsef:
225 if collapsef:
226 targetancestors = set(repo.changelog.ancestors(target))
226 targetancestors = set(repo.changelog.ancestors(target))
227 targetancestors.add(target)
227 targetancestors.add(target)
228 external = checkexternal(repo, state, targetancestors)
228 external = checkexternal(repo, state, targetancestors)
229
229
230 if keepbranchesf:
230 if keepbranchesf:
231 assert not extrafn, 'cannot use both keepbranches and extrafn'
231 assert not extrafn, 'cannot use both keepbranches and extrafn'
232 def extrafn(ctx, extra):
232 def extrafn(ctx, extra):
233 extra['branch'] = ctx.branch()
233 extra['branch'] = ctx.branch()
234 if collapsef:
234 if collapsef:
235 branches = set()
235 branches = set()
236 for rev in state:
236 for rev in state:
237 branches.add(repo[rev].branch())
237 branches.add(repo[rev].branch())
238 if len(branches) > 1:
238 if len(branches) > 1:
239 raise util.Abort(_('cannot collapse multiple named '
239 raise util.Abort(_('cannot collapse multiple named '
240 'branches'))
240 'branches'))
241
241
242
242
243 # Rebase
243 # Rebase
244 if not targetancestors:
244 if not targetancestors:
245 targetancestors = set(repo.changelog.ancestors(target))
245 targetancestors = set(repo.changelog.ancestors(target))
246 targetancestors.add(target)
246 targetancestors.add(target)
247
247
248 # Keep track of the current bookmarks in order to reset them later
248 # Keep track of the current bookmarks in order to reset them later
249 currentbookmarks = repo._bookmarks.copy()
249 currentbookmarks = repo._bookmarks.copy()
250
250
251 sortedstate = sorted(state)
251 sortedstate = sorted(state)
252 total = len(sortedstate)
252 total = len(sortedstate)
253 pos = 0
253 pos = 0
254 for rev in sortedstate:
254 for rev in sortedstate:
255 pos += 1
255 pos += 1
256 if state[rev] == -1:
256 if state[rev] == -1:
257 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
257 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
258 _('changesets'), total)
258 _('changesets'), total)
259 storestatus(repo, originalwd, target, state, collapsef, keepf,
259 storestatus(repo, originalwd, target, state, collapsef, keepf,
260 keepbranchesf, external)
260 keepbranchesf, external)
261 p1, p2 = defineparents(repo, rev, target, state,
261 p1, p2 = defineparents(repo, rev, target, state,
262 targetancestors)
262 targetancestors)
263 if len(repo.parents()) == 2:
263 if len(repo.parents()) == 2:
264 repo.ui.debug('resuming interrupted rebase\n')
264 repo.ui.debug('resuming interrupted rebase\n')
265 else:
265 else:
266 try:
266 try:
267 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
267 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
268 stats = rebasenode(repo, rev, p1, state)
268 stats = rebasenode(repo, rev, p1, state)
269 if stats and stats[3] > 0:
269 if stats and stats[3] > 0:
270 raise util.Abort(_('unresolved conflicts (see hg '
270 raise util.Abort(_('unresolved conflicts (see hg '
271 'resolve, then hg rebase --continue)'))
271 'resolve, then hg rebase --continue)'))
272 finally:
272 finally:
273 ui.setconfig('ui', 'forcemerge', '')
273 ui.setconfig('ui', 'forcemerge', '')
274 cmdutil.duplicatecopies(repo, rev, target)
274 cmdutil.duplicatecopies(repo, rev, target)
275 if not collapsef:
275 if not collapsef:
276 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
276 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
277 editor=editor)
277 editor=editor)
278 else:
278 else:
279 # Skip commit if we are collapsing
279 # Skip commit if we are collapsing
280 repo.dirstate.setparents(repo[p1].node())
280 repo.dirstate.setparents(repo[p1].node())
281 newrev = None
281 newrev = None
282 # Update the state
282 # Update the state
283 if newrev is not None:
283 if newrev is not None:
284 state[rev] = repo[newrev].rev()
284 state[rev] = repo[newrev].rev()
285 else:
285 else:
286 if not collapsef:
286 if not collapsef:
287 ui.note(_('no changes, revision %d skipped\n') % rev)
287 ui.note(_('no changes, revision %d skipped\n') % rev)
288 ui.debug('next revision set to %s\n' % p1)
288 ui.debug('next revision set to %s\n' % p1)
289 skipped.add(rev)
289 skipped.add(rev)
290 state[rev] = p1
290 state[rev] = p1
291
291
292 ui.progress(_('rebasing'), None)
292 ui.progress(_('rebasing'), None)
293 ui.note(_('rebase merging completed\n'))
293 ui.note(_('rebase merging completed\n'))
294
294
295 if collapsef and not keepopen:
295 if collapsef and not keepopen:
296 p1, p2 = defineparents(repo, min(state), target,
296 p1, p2 = defineparents(repo, min(state), target,
297 state, targetancestors)
297 state, targetancestors)
298 if collapsemsg:
298 if collapsemsg:
299 commitmsg = collapsemsg
299 commitmsg = collapsemsg
300 else:
300 else:
301 commitmsg = 'Collapsed revision'
301 commitmsg = 'Collapsed revision'
302 for rebased in state:
302 for rebased in state:
303 if rebased not in skipped and state[rebased] != nullmerge:
303 if rebased not in skipped and state[rebased] != nullmerge:
304 commitmsg += '\n* %s' % repo[rebased].description()
304 commitmsg += '\n* %s' % repo[rebased].description()
305 commitmsg = ui.edit(commitmsg, repo.ui.username())
305 commitmsg = ui.edit(commitmsg, repo.ui.username())
306 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
306 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
307 extrafn=extrafn, editor=editor)
307 extrafn=extrafn, editor=editor)
308
308
309 if 'qtip' in repo.tags():
309 if 'qtip' in repo.tags():
310 updatemq(repo, state, skipped, **opts)
310 updatemq(repo, state, skipped, **opts)
311
311
312 if currentbookmarks:
312 if currentbookmarks:
313 # Nodeids are needed to reset bookmarks
313 # Nodeids are needed to reset bookmarks
314 nstate = {}
314 nstate = {}
315 for k, v in state.iteritems():
315 for k, v in state.iteritems():
316 if v != nullmerge:
316 if v != nullmerge:
317 nstate[repo[k].node()] = repo[v].node()
317 nstate[repo[k].node()] = repo[v].node()
318
318
319 if not keepf:
319 if not keepf:
320 # Remove no more useful revisions
320 # Remove no more useful revisions
321 rebased = [rev for rev in state if state[rev] != nullmerge]
321 rebased = [rev for rev in state if state[rev] != nullmerge]
322 if rebased:
322 if rebased:
323 if set(repo.changelog.descendants(min(rebased))) - set(state):
323 if set(repo.changelog.descendants(min(rebased))) - set(state):
324 ui.warn(_("warning: new changesets detected "
324 ui.warn(_("warning: new changesets detected "
325 "on source branch, not stripping\n"))
325 "on source branch, not stripping\n"))
326 else:
326 else:
327 # backup the old csets by default
327 # backup the old csets by default
328 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
328 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
329
329
330 if currentbookmarks:
330 if currentbookmarks:
331 updatebookmarks(repo, nstate, currentbookmarks, **opts)
331 updatebookmarks(repo, nstate, currentbookmarks, **opts)
332
332
333 clearstatus(repo)
333 clearstatus(repo)
334 ui.note(_("rebase completed\n"))
334 ui.note(_("rebase completed\n"))
335 if os.path.exists(repo.sjoin('undo')):
335 if os.path.exists(repo.sjoin('undo')):
336 util.unlinkpath(repo.sjoin('undo'))
336 util.unlinkpath(repo.sjoin('undo'))
337 if skipped:
337 if skipped:
338 ui.note(_("%d revisions have been skipped\n") % len(skipped))
338 ui.note(_("%d revisions have been skipped\n") % len(skipped))
339 finally:
339 finally:
340 release(lock, wlock)
340 release(lock, wlock)
341
341
342 def checkexternal(repo, state, targetancestors):
342 def checkexternal(repo, state, targetancestors):
343 """Check whether one or more external revisions need to be taken in
343 """Check whether one or more external revisions need to be taken in
344 consideration. In the latter case, abort.
344 consideration. In the latter case, abort.
345 """
345 """
346 external = nullrev
346 external = nullrev
347 source = min(state)
347 source = min(state)
348 for rev in state:
348 for rev in state:
349 if rev == source:
349 if rev == source:
350 continue
350 continue
351 # Check externals and fail if there are more than one
351 # Check externals and fail if there are more than one
352 for p in repo[rev].parents():
352 for p in repo[rev].parents():
353 if (p.rev() not in state
353 if (p.rev() not in state
354 and p.rev() not in targetancestors):
354 and p.rev() not in targetancestors):
355 if external != nullrev:
355 if external != nullrev:
356 raise util.Abort(_('unable to collapse, there is more '
356 raise util.Abort(_('unable to collapse, there is more '
357 'than one external parent'))
357 'than one external parent'))
358 external = p.rev()
358 external = p.rev()
359 return external
359 return external
360
360
361 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
361 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
362 'Commit the changes and store useful information in extra'
362 'Commit the changes and store useful information in extra'
363 try:
363 try:
364 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
364 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
365 ctx = repo[rev]
365 ctx = repo[rev]
366 if commitmsg is None:
366 if commitmsg is None:
367 commitmsg = ctx.description()
367 commitmsg = ctx.description()
368 extra = {'rebase_source': ctx.hex()}
368 extra = {'rebase_source': ctx.hex()}
369 if extrafn:
369 if extrafn:
370 extrafn(ctx, extra)
370 extrafn(ctx, extra)
371 # Commit might fail if unresolved files exist
371 # Commit might fail if unresolved files exist
372 newrev = repo.commit(text=commitmsg, user=ctx.user(),
372 newrev = repo.commit(text=commitmsg, user=ctx.user(),
373 date=ctx.date(), extra=extra, editor=editor)
373 date=ctx.date(), extra=extra, editor=editor)
374 repo.dirstate.setbranch(repo[newrev].branch())
374 repo.dirstate.setbranch(repo[newrev].branch())
375 return newrev
375 return newrev
376 except util.Abort:
376 except util.Abort:
377 # Invalidate the previous setparents
377 # Invalidate the previous setparents
378 repo.dirstate.invalidate()
378 repo.dirstate.invalidate()
379 raise
379 raise
380
380
381 def rebasenode(repo, rev, p1, state):
381 def rebasenode(repo, rev, p1, state):
382 'Rebase a single revision'
382 'Rebase a single revision'
383 # Merge phase
383 # Merge phase
384 # Update to target and merge it with local
384 # Update to target and merge it with local
385 if repo['.'].rev() != repo[p1].rev():
385 if repo['.'].rev() != repo[p1].rev():
386 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
386 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
387 merge.update(repo, p1, False, True, False)
387 merge.update(repo, p1, False, True, False)
388 else:
388 else:
389 repo.ui.debug(" already in target\n")
389 repo.ui.debug(" already in target\n")
390 repo.dirstate.write()
390 repo.dirstate.write()
391 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
391 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
392 base = None
392 base = None
393 if repo[rev].rev() != repo[min(state)].rev():
393 if repo[rev].rev() != repo[min(state)].rev():
394 base = repo[rev].p1().node()
394 base = repo[rev].p1().node()
395 return merge.update(repo, rev, True, True, False, base)
395 return merge.update(repo, rev, True, True, False, base)
396
396
397 def defineparents(repo, rev, target, state, targetancestors):
397 def defineparents(repo, rev, target, state, targetancestors):
398 'Return the new parent relationship of the revision that will be rebased'
398 'Return the new parent relationship of the revision that will be rebased'
399 parents = repo[rev].parents()
399 parents = repo[rev].parents()
400 p1 = p2 = nullrev
400 p1 = p2 = nullrev
401
401
402 P1n = parents[0].rev()
402 P1n = parents[0].rev()
403 if P1n in targetancestors:
403 if P1n in targetancestors:
404 p1 = target
404 p1 = target
405 elif P1n in state:
405 elif P1n in state:
406 if state[P1n] == nullmerge:
406 if state[P1n] == nullmerge:
407 p1 = target
407 p1 = target
408 else:
408 else:
409 p1 = state[P1n]
409 p1 = state[P1n]
410 else: # P1n external
410 else: # P1n external
411 p1 = target
411 p1 = target
412 p2 = P1n
412 p2 = P1n
413
413
414 if len(parents) == 2 and parents[1].rev() not in targetancestors:
414 if len(parents) == 2 and parents[1].rev() not in targetancestors:
415 P2n = parents[1].rev()
415 P2n = parents[1].rev()
416 # interesting second parent
416 # interesting second parent
417 if P2n in state:
417 if P2n in state:
418 if p1 == target: # P1n in targetancestors or external
418 if p1 == target: # P1n in targetancestors or external
419 p1 = state[P2n]
419 p1 = state[P2n]
420 else:
420 else:
421 p2 = state[P2n]
421 p2 = state[P2n]
422 else: # P2n external
422 else: # P2n external
423 if p2 != nullrev: # P1n external too => rev is a merged revision
423 if p2 != nullrev: # P1n external too => rev is a merged revision
424 raise util.Abort(_('cannot use revision %d as base, result '
424 raise util.Abort(_('cannot use revision %d as base, result '
425 'would have 3 parents') % rev)
425 'would have 3 parents') % rev)
426 p2 = P2n
426 p2 = P2n
427 repo.ui.debug(" future parents are %d and %d\n" %
427 repo.ui.debug(" future parents are %d and %d\n" %
428 (repo[p1].rev(), repo[p2].rev()))
428 (repo[p1].rev(), repo[p2].rev()))
429 return p1, p2
429 return p1, p2
430
430
431 def isagitpatch(repo, patchname):
431 def isagitpatch(repo, patchname):
432 'Return true if the given patch is in git format'
432 'Return true if the given patch is in git format'
433 mqpatch = os.path.join(repo.mq.path, patchname)
433 mqpatch = os.path.join(repo.mq.path, patchname)
434 for line in patch.linereader(file(mqpatch, 'rb')):
434 for line in patch.linereader(file(mqpatch, 'rb')):
435 if line.startswith('diff --git'):
435 if line.startswith('diff --git'):
436 return True
436 return True
437 return False
437 return False
438
438
439 def updatemq(repo, state, skipped, **opts):
439 def updatemq(repo, state, skipped, **opts):
440 'Update rebased mq patches - finalize and then import them'
440 'Update rebased mq patches - finalize and then import them'
441 mqrebase = {}
441 mqrebase = {}
442 mq = repo.mq
442 mq = repo.mq
443 original_series = mq.fullseries[:]
443 original_series = mq.fullseries[:]
444
444
445 for p in mq.applied:
445 for p in mq.applied:
446 rev = repo[p.node].rev()
446 rev = repo[p.node].rev()
447 if rev in state:
447 if rev in state:
448 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
448 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
449 (rev, p.name))
449 (rev, p.name))
450 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
450 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
451
451
452 if mqrebase:
452 if mqrebase:
453 mq.finish(repo, mqrebase.keys())
453 mq.finish(repo, mqrebase.keys())
454
454
455 # We must start import from the newest revision
455 # We must start import from the newest revision
456 for rev in sorted(mqrebase, reverse=True):
456 for rev in sorted(mqrebase, reverse=True):
457 if rev not in skipped:
457 if rev not in skipped:
458 name, isgit = mqrebase[rev]
458 name, isgit = mqrebase[rev]
459 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
459 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
460 mq.qimport(repo, (), patchname=name, git=isgit,
460 mq.qimport(repo, (), patchname=name, git=isgit,
461 rev=[str(state[rev])])
461 rev=[str(state[rev])])
462
462
463 # restore old series to preserve guards
463 # restore missing guards
464 mq.fullseries = original_series
464 for s in original_series:
465 mq.series_dirty = True
465 pname = mq.guard_re.split(s, 1)[0]
466 if pname in mq.fullseries:
467 repo.ui.debug('restoring guard for patch %s' % (pname))
468 mq.fullseries[mq.fullseries.index(pname)] = s
469 mq.series_dirty = True
466 mq.savedirty()
470 mq.savedirty()
467
471
468 def updatebookmarks(repo, nstate, originalbookmarks, **opts):
472 def updatebookmarks(repo, nstate, originalbookmarks, **opts):
469 'Move bookmarks to their correct changesets'
473 'Move bookmarks to their correct changesets'
470 current = repo._bookmarkcurrent
474 current = repo._bookmarkcurrent
471 for k, v in originalbookmarks.iteritems():
475 for k, v in originalbookmarks.iteritems():
472 if v in nstate:
476 if v in nstate:
473 if nstate[v] != nullmerge:
477 if nstate[v] != nullmerge:
474 # reset the pointer if the bookmark was moved incorrectly
478 # reset the pointer if the bookmark was moved incorrectly
475 if k != current:
479 if k != current:
476 repo._bookmarks[k] = nstate[v]
480 repo._bookmarks[k] = nstate[v]
477
481
478 bookmarks.write(repo)
482 bookmarks.write(repo)
479
483
480 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
484 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
481 external):
485 external):
482 'Store the current status to allow recovery'
486 'Store the current status to allow recovery'
483 f = repo.opener("rebasestate", "w")
487 f = repo.opener("rebasestate", "w")
484 f.write(repo[originalwd].hex() + '\n')
488 f.write(repo[originalwd].hex() + '\n')
485 f.write(repo[target].hex() + '\n')
489 f.write(repo[target].hex() + '\n')
486 f.write(repo[external].hex() + '\n')
490 f.write(repo[external].hex() + '\n')
487 f.write('%d\n' % int(collapse))
491 f.write('%d\n' % int(collapse))
488 f.write('%d\n' % int(keep))
492 f.write('%d\n' % int(keep))
489 f.write('%d\n' % int(keepbranches))
493 f.write('%d\n' % int(keepbranches))
490 for d, v in state.iteritems():
494 for d, v in state.iteritems():
491 oldrev = repo[d].hex()
495 oldrev = repo[d].hex()
492 if v != nullmerge:
496 if v != nullmerge:
493 newrev = repo[v].hex()
497 newrev = repo[v].hex()
494 else:
498 else:
495 newrev = v
499 newrev = v
496 f.write("%s:%s\n" % (oldrev, newrev))
500 f.write("%s:%s\n" % (oldrev, newrev))
497 f.close()
501 f.close()
498 repo.ui.debug('rebase status stored\n')
502 repo.ui.debug('rebase status stored\n')
499
503
500 def clearstatus(repo):
504 def clearstatus(repo):
501 'Remove the status files'
505 'Remove the status files'
502 if os.path.exists(repo.join("rebasestate")):
506 if os.path.exists(repo.join("rebasestate")):
503 util.unlinkpath(repo.join("rebasestate"))
507 util.unlinkpath(repo.join("rebasestate"))
504
508
505 def restorestatus(repo):
509 def restorestatus(repo):
506 'Restore a previously stored status'
510 'Restore a previously stored status'
507 try:
511 try:
508 target = None
512 target = None
509 collapse = False
513 collapse = False
510 external = nullrev
514 external = nullrev
511 state = {}
515 state = {}
512 f = repo.opener("rebasestate")
516 f = repo.opener("rebasestate")
513 for i, l in enumerate(f.read().splitlines()):
517 for i, l in enumerate(f.read().splitlines()):
514 if i == 0:
518 if i == 0:
515 originalwd = repo[l].rev()
519 originalwd = repo[l].rev()
516 elif i == 1:
520 elif i == 1:
517 target = repo[l].rev()
521 target = repo[l].rev()
518 elif i == 2:
522 elif i == 2:
519 external = repo[l].rev()
523 external = repo[l].rev()
520 elif i == 3:
524 elif i == 3:
521 collapse = bool(int(l))
525 collapse = bool(int(l))
522 elif i == 4:
526 elif i == 4:
523 keep = bool(int(l))
527 keep = bool(int(l))
524 elif i == 5:
528 elif i == 5:
525 keepbranches = bool(int(l))
529 keepbranches = bool(int(l))
526 else:
530 else:
527 oldrev, newrev = l.split(':')
531 oldrev, newrev = l.split(':')
528 if newrev != str(nullmerge):
532 if newrev != str(nullmerge):
529 state[repo[oldrev].rev()] = repo[newrev].rev()
533 state[repo[oldrev].rev()] = repo[newrev].rev()
530 else:
534 else:
531 state[repo[oldrev].rev()] = int(newrev)
535 state[repo[oldrev].rev()] = int(newrev)
532 skipped = set()
536 skipped = set()
533 # recompute the set of skipped revs
537 # recompute the set of skipped revs
534 if not collapse:
538 if not collapse:
535 seen = set([target])
539 seen = set([target])
536 for old, new in sorted(state.items()):
540 for old, new in sorted(state.items()):
537 if new != nullrev and new in seen:
541 if new != nullrev and new in seen:
538 skipped.add(old)
542 skipped.add(old)
539 seen.add(new)
543 seen.add(new)
540 repo.ui.debug('computed skipped revs: %s\n' % skipped)
544 repo.ui.debug('computed skipped revs: %s\n' % skipped)
541 repo.ui.debug('rebase status resumed\n')
545 repo.ui.debug('rebase status resumed\n')
542 return (originalwd, target, state, skipped,
546 return (originalwd, target, state, skipped,
543 collapse, keep, keepbranches, external)
547 collapse, keep, keepbranches, external)
544 except IOError, err:
548 except IOError, err:
545 if err.errno != errno.ENOENT:
549 if err.errno != errno.ENOENT:
546 raise
550 raise
547 raise util.Abort(_('no rebase in progress'))
551 raise util.Abort(_('no rebase in progress'))
548
552
549 def abort(repo, originalwd, target, state):
553 def abort(repo, originalwd, target, state):
550 'Restore the repository to its original state'
554 'Restore the repository to its original state'
551 if set(repo.changelog.descendants(target)) - set(state.values()):
555 if set(repo.changelog.descendants(target)) - set(state.values()):
552 repo.ui.warn(_("warning: new changesets detected on target branch, "
556 repo.ui.warn(_("warning: new changesets detected on target branch, "
553 "can't abort\n"))
557 "can't abort\n"))
554 return -1
558 return -1
555 else:
559 else:
556 # Strip from the first rebased revision
560 # Strip from the first rebased revision
557 merge.update(repo, repo[originalwd].rev(), False, True, False)
561 merge.update(repo, repo[originalwd].rev(), False, True, False)
558 rebased = filter(lambda x: x > -1 and x != target, state.values())
562 rebased = filter(lambda x: x > -1 and x != target, state.values())
559 if rebased:
563 if rebased:
560 strippoint = min(rebased)
564 strippoint = min(rebased)
561 # no backup of rebased cset versions needed
565 # no backup of rebased cset versions needed
562 repair.strip(repo.ui, repo, repo[strippoint].node())
566 repair.strip(repo.ui, repo, repo[strippoint].node())
563 clearstatus(repo)
567 clearstatus(repo)
564 repo.ui.warn(_('rebase aborted\n'))
568 repo.ui.warn(_('rebase aborted\n'))
565 return 0
569 return 0
566
570
567 def buildstate(repo, dest, rebaseset, detach):
571 def buildstate(repo, dest, rebaseset, detach):
568 '''Define which revisions are going to be rebased and where
572 '''Define which revisions are going to be rebased and where
569
573
570 repo: repo
574 repo: repo
571 dest: context
575 dest: context
572 rebaseset: set of rev
576 rebaseset: set of rev
573 detach: boolean'''
577 detach: boolean'''
574
578
575 # This check isn't strictly necessary, since mq detects commits over an
579 # This check isn't strictly necessary, since mq detects commits over an
576 # applied patch. But it prevents messing up the working directory when
580 # applied patch. But it prevents messing up the working directory when
577 # a partially completed rebase is blocked by mq.
581 # a partially completed rebase is blocked by mq.
578 if 'qtip' in repo.tags() and (dest.node() in
582 if 'qtip' in repo.tags() and (dest.node() in
579 [s.node for s in repo.mq.applied]):
583 [s.node for s in repo.mq.applied]):
580 raise util.Abort(_('cannot rebase onto an applied mq patch'))
584 raise util.Abort(_('cannot rebase onto an applied mq patch'))
581
585
582 detachset = set()
586 detachset = set()
583 roots = list(repo.set('roots(%ld)', rebaseset))
587 roots = list(repo.set('roots(%ld)', rebaseset))
584 if not roots:
588 if not roots:
585 raise util.Abort(_('no matching revisions'))
589 raise util.Abort(_('no matching revisions'))
586 if len(roots) > 1:
590 if len(roots) > 1:
587 raise util.Abort(_("can't rebase multiple roots"))
591 raise util.Abort(_("can't rebase multiple roots"))
588 root = roots[0]
592 root = roots[0]
589
593
590 commonbase = root.ancestor(dest)
594 commonbase = root.ancestor(dest)
591 if commonbase == root:
595 if commonbase == root:
592 raise util.Abort(_('source is ancestor of destination'))
596 raise util.Abort(_('source is ancestor of destination'))
593 if commonbase == dest:
597 if commonbase == dest:
594 samebranch = root.branch() == dest.branch()
598 samebranch = root.branch() == dest.branch()
595 if samebranch and root in dest.children():
599 if samebranch and root in dest.children():
596 repo.ui.debug('source is a child of destination')
600 repo.ui.debug('source is a child of destination')
597 return None
601 return None
598 # rebase on ancestor, force detach
602 # rebase on ancestor, force detach
599 detach = True
603 detach = True
600 if detach:
604 if detach:
601 detachset = repo.revs('::%d - ::%d - %d', root, commonbase, root)
605 detachset = repo.revs('::%d - ::%d - %d', root, commonbase, root)
602
606
603 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, root))
607 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, root))
604 state = dict.fromkeys(rebaseset, nullrev)
608 state = dict.fromkeys(rebaseset, nullrev)
605 state.update(dict.fromkeys(detachset, nullmerge))
609 state.update(dict.fromkeys(detachset, nullmerge))
606 return repo['.'].rev(), dest.rev(), state
610 return repo['.'].rev(), dest.rev(), state
607
611
608 def pullrebase(orig, ui, repo, *args, **opts):
612 def pullrebase(orig, ui, repo, *args, **opts):
609 'Call rebase after pull if the latter has been invoked with --rebase'
613 'Call rebase after pull if the latter has been invoked with --rebase'
610 if opts.get('rebase'):
614 if opts.get('rebase'):
611 if opts.get('update'):
615 if opts.get('update'):
612 del opts['update']
616 del opts['update']
613 ui.debug('--update and --rebase are not compatible, ignoring '
617 ui.debug('--update and --rebase are not compatible, ignoring '
614 'the update flag\n')
618 'the update flag\n')
615
619
616 cmdutil.bailifchanged(repo)
620 cmdutil.bailifchanged(repo)
617 revsprepull = len(repo)
621 revsprepull = len(repo)
618 origpostincoming = commands.postincoming
622 origpostincoming = commands.postincoming
619 def _dummy(*args, **kwargs):
623 def _dummy(*args, **kwargs):
620 pass
624 pass
621 commands.postincoming = _dummy
625 commands.postincoming = _dummy
622 try:
626 try:
623 orig(ui, repo, *args, **opts)
627 orig(ui, repo, *args, **opts)
624 finally:
628 finally:
625 commands.postincoming = origpostincoming
629 commands.postincoming = origpostincoming
626 revspostpull = len(repo)
630 revspostpull = len(repo)
627 if revspostpull > revsprepull:
631 if revspostpull > revsprepull:
628 rebase(ui, repo, **opts)
632 rebase(ui, repo, **opts)
629 branch = repo[None].branch()
633 branch = repo[None].branch()
630 dest = repo[branch].rev()
634 dest = repo[branch].rev()
631 if dest != repo['.'].rev():
635 if dest != repo['.'].rev():
632 # there was nothing to rebase we force an update
636 # there was nothing to rebase we force an update
633 hg.update(repo, dest)
637 hg.update(repo, dest)
634 else:
638 else:
635 if opts.get('tool'):
639 if opts.get('tool'):
636 raise util.Abort(_('--tool can only be used with --rebase'))
640 raise util.Abort(_('--tool can only be used with --rebase'))
637 orig(ui, repo, *args, **opts)
641 orig(ui, repo, *args, **opts)
638
642
639 def uisetup(ui):
643 def uisetup(ui):
640 'Replace pull with a decorator to provide --rebase option'
644 'Replace pull with a decorator to provide --rebase option'
641 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
645 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
642 entry[1].append(('', 'rebase', None,
646 entry[1].append(('', 'rebase', None,
643 _("rebase working directory to branch head")))
647 _("rebase working directory to branch head")))
644 entry[1].append(('t', 'tool', '',
648 entry[1].append(('t', 'tool', '',
645 _("specify merge tool for rebase")))
649 _("specify merge tool for rebase")))
@@ -1,315 +1,327 b''
1 $ cat >> $HGRCPATH <<EOF
1 $ cat >> $HGRCPATH <<EOF
2 > [extensions]
2 > [extensions]
3 > graphlog=
3 > graphlog=
4 > rebase=
4 > rebase=
5 > mq=
5 > mq=
6 >
6 >
7 > [mq]
7 > [mq]
8 > plain=true
8 > plain=true
9 >
9 >
10 > [alias]
10 > [alias]
11 > tglog = log -G --template "{rev}: '{desc}' tags: {tags}\n"
11 > tglog = log -G --template "{rev}: '{desc}' tags: {tags}\n"
12 > EOF
12 > EOF
13
13
14
14
15 $ hg init a
15 $ hg init a
16 $ cd a
16 $ cd a
17 $ hg qinit -c
17 $ hg qinit -c
18
18
19 $ echo c1 > f
19 $ echo c1 > f
20 $ hg add f
20 $ hg add f
21 $ hg ci -m C1
21 $ hg ci -m C1
22
22
23 $ echo r1 > f
23 $ echo r1 > f
24 $ hg ci -m R1
24 $ hg ci -m R1
25
25
26 $ hg up -q 0
26 $ hg up -q 0
27
27
28 $ hg qnew f.patch
28 $ hg qnew f.patch
29 $ echo mq1 > f
29 $ echo mq1 > f
30 $ hg qref -m P0
30 $ hg qref -m P0
31
31
32 $ hg qnew f2.patch
32 $ hg qnew f2.patch
33 $ echo mq2 > f
33 $ echo mq2 > f
34 $ hg qref -m P1
34 $ hg qref -m P1
35
35
36 $ hg tglog
36 $ hg tglog
37 @ 3: 'P1' tags: f2.patch qtip tip
37 @ 3: 'P1' tags: f2.patch qtip tip
38 |
38 |
39 o 2: 'P0' tags: f.patch qbase
39 o 2: 'P0' tags: f.patch qbase
40 |
40 |
41 | o 1: 'R1' tags:
41 | o 1: 'R1' tags:
42 |/
42 |/
43 o 0: 'C1' tags: qparent
43 o 0: 'C1' tags: qparent
44
44
45
45
46 Rebase - try to rebase on an applied mq patch:
46 Rebase - try to rebase on an applied mq patch:
47
47
48 $ hg rebase -s 1 -d 3
48 $ hg rebase -s 1 -d 3
49 abort: cannot rebase onto an applied mq patch
49 abort: cannot rebase onto an applied mq patch
50 [255]
50 [255]
51
51
52 Rebase - same thing, but mq patch is default dest:
52 Rebase - same thing, but mq patch is default dest:
53
53
54 $ hg up -q 1
54 $ hg up -q 1
55 $ hg rebase
55 $ hg rebase
56 abort: cannot rebase onto an applied mq patch
56 abort: cannot rebase onto an applied mq patch
57 [255]
57 [255]
58 $ hg up -q qtip
58 $ hg up -q qtip
59
59
60 Rebase - generate a conflict:
60 Rebase - generate a conflict:
61
61
62 $ hg rebase -s 2 -d 1
62 $ hg rebase -s 2 -d 1
63 merging f
63 merging f
64 warning: conflicts during merge.
64 warning: conflicts during merge.
65 merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
65 merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
66 abort: unresolved conflicts (see hg resolve, then hg rebase --continue)
66 abort: unresolved conflicts (see hg resolve, then hg rebase --continue)
67 [255]
67 [255]
68
68
69 Fix the 1st conflict:
69 Fix the 1st conflict:
70
70
71 $ echo mq1r1 > f
71 $ echo mq1r1 > f
72 $ hg resolve -m f
72 $ hg resolve -m f
73 $ hg rebase -c
73 $ hg rebase -c
74 merging f
74 merging f
75 warning: conflicts during merge.
75 warning: conflicts during merge.
76 merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
76 merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
77 abort: unresolved conflicts (see hg resolve, then hg rebase --continue)
77 abort: unresolved conflicts (see hg resolve, then hg rebase --continue)
78 [255]
78 [255]
79
79
80 Fix the 2nd conflict:
80 Fix the 2nd conflict:
81
81
82 $ echo mq1r1mq2 > f
82 $ echo mq1r1mq2 > f
83 $ hg resolve -m f
83 $ hg resolve -m f
84 $ hg rebase -c
84 $ hg rebase -c
85 saved backup bundle to $TESTTMP/a/.hg/strip-backup/*-backup.hg (glob)
85 saved backup bundle to $TESTTMP/a/.hg/strip-backup/*-backup.hg (glob)
86
86
87 $ hg tglog
87 $ hg tglog
88 @ 3: 'P1' tags: f2.patch qtip tip
88 @ 3: 'P1' tags: f2.patch qtip tip
89 |
89 |
90 o 2: 'P0' tags: f.patch qbase
90 o 2: 'P0' tags: f.patch qbase
91 |
91 |
92 o 1: 'R1' tags: qparent
92 o 1: 'R1' tags: qparent
93 |
93 |
94 o 0: 'C1' tags:
94 o 0: 'C1' tags:
95
95
96 $ hg up -q qbase
96 $ hg up -q qbase
97
97
98 $ cat f
98 $ cat f
99 mq1r1
99 mq1r1
100
100
101 $ cat .hg/patches/f.patch
101 $ cat .hg/patches/f.patch
102 # HG changeset patch
102 # HG changeset patch
103 # User test
103 # User test
104 # Date ?????????? ? (glob)
104 # Date ?????????? ? (glob)
105 # Node ID ???????????????????????????????????????? (glob)
105 # Node ID ???????????????????????????????????????? (glob)
106 # Parent bac9ed9960d8992bcad75864a879fa76cadaf1b0
106 # Parent bac9ed9960d8992bcad75864a879fa76cadaf1b0
107 P0
107 P0
108
108
109 diff -r bac9ed9960d8 -r ???????????? f (glob)
109 diff -r bac9ed9960d8 -r ???????????? f (glob)
110 --- a/f Thu Jan 01 00:00:00 1970 +0000
110 --- a/f Thu Jan 01 00:00:00 1970 +0000
111 +++ b/f ??? ??? ?? ??:??:?? ???? ????? (glob)
111 +++ b/f ??? ??? ?? ??:??:?? ???? ????? (glob)
112 @@ -1,1 +1,1 @@
112 @@ -1,1 +1,1 @@
113 -r1
113 -r1
114 +mq1r1
114 +mq1r1
115
115
116 Update to qtip:
116 Update to qtip:
117
117
118 $ hg up -q qtip
118 $ hg up -q qtip
119
119
120 $ cat f
120 $ cat f
121 mq1r1mq2
121 mq1r1mq2
122
122
123 $ cat .hg/patches/f2.patch
123 $ cat .hg/patches/f2.patch
124 # HG changeset patch
124 # HG changeset patch
125 # User test
125 # User test
126 # Date ?????????? ? (glob)
126 # Date ?????????? ? (glob)
127 # Node ID ???????????????????????????????????????? (glob)
127 # Node ID ???????????????????????????????????????? (glob)
128 # Parent ???????????????????????????????????????? (glob)
128 # Parent ???????????????????????????????????????? (glob)
129 P1
129 P1
130
130
131 diff -r ???????????? -r ???????????? f (glob)
131 diff -r ???????????? -r ???????????? f (glob)
132 --- a/f ??? ??? ?? ??:??:?? ???? ????? (glob)
132 --- a/f ??? ??? ?? ??:??:?? ???? ????? (glob)
133 +++ b/f ??? ??? ?? ??:??:?? ???? ????? (glob)
133 +++ b/f ??? ??? ?? ??:??:?? ???? ????? (glob)
134 @@ -1,1 +1,1 @@
134 @@ -1,1 +1,1 @@
135 -mq1r1
135 -mq1r1
136 +mq1r1mq2
136 +mq1r1mq2
137
137
138 Adding one git-style patch and one normal:
138 Adding one git-style patch and one normal:
139
139
140 $ hg qpop -a
140 $ hg qpop -a
141 popping f2.patch
141 popping f2.patch
142 popping f.patch
142 popping f.patch
143 patch queue now empty
143 patch queue now empty
144
144
145 $ rm -fr .hg/patches
145 $ rm -fr .hg/patches
146 $ hg qinit -c
146 $ hg qinit -c
147
147
148 $ hg up -q 0
148 $ hg up -q 0
149
149
150 $ hg qnew --git f_git.patch
150 $ hg qnew --git f_git.patch
151 $ echo mq1 > p
151 $ echo mq1 > p
152 $ hg add p
152 $ hg add p
153 $ hg qref --git -m 'P0 (git)'
153 $ hg qref --git -m 'P0 (git)'
154
154
155 $ hg qnew f.patch
155 $ hg qnew f.patch
156 $ echo mq2 > p
156 $ echo mq2 > p
157 $ hg qref -m P1
157 $ hg qref -m P1
158 $ hg qci -m 'save patch state'
158 $ hg qci -m 'save patch state'
159
159
160 $ hg qseries -s
160 $ hg qseries -s
161 f_git.patch: P0 (git)
161 f_git.patch: P0 (git)
162 f.patch: P1
162 f.patch: P1
163
163
164 $ hg -R .hg/patches manifest
164 $ hg -R .hg/patches manifest
165 .hgignore
165 .hgignore
166 f.patch
166 f.patch
167 f_git.patch
167 f_git.patch
168 series
168 series
169
169
170 $ cat .hg/patches/f_git.patch
170 $ cat .hg/patches/f_git.patch
171 P0 (git)
171 P0 (git)
172
172
173 diff --git a/p b/p
173 diff --git a/p b/p
174 new file mode 100644
174 new file mode 100644
175 --- /dev/null
175 --- /dev/null
176 +++ b/p
176 +++ b/p
177 @@ -0,0 +1,1 @@
177 @@ -0,0 +1,1 @@
178 +mq1
178 +mq1
179
179
180 $ cat .hg/patches/f.patch
180 $ cat .hg/patches/f.patch
181 P1
181 P1
182
182
183 diff -r ???????????? p (glob)
183 diff -r ???????????? p (glob)
184 --- a/p ??? ??? ?? ??:??:?? ???? ????? (glob)
184 --- a/p ??? ??? ?? ??:??:?? ???? ????? (glob)
185 +++ b/p ??? ??? ?? ??:??:?? ???? ????? (glob)
185 +++ b/p ??? ??? ?? ??:??:?? ???? ????? (glob)
186 @@ -1,1 +1,1 @@
186 @@ -1,1 +1,1 @@
187 -mq1
187 -mq1
188 +mq2
188 +mq2
189
189
190
190
191 Rebase the applied mq patches:
191 Rebase the applied mq patches:
192
192
193 $ hg rebase -s 2 -d 1
193 $ hg rebase -s 2 -d 1
194 saved backup bundle to $TESTTMP/a/.hg/strip-backup/*-backup.hg (glob)
194 saved backup bundle to $TESTTMP/a/.hg/strip-backup/*-backup.hg (glob)
195
195
196 $ hg qci -m 'save patch state'
196 $ hg qci -m 'save patch state'
197
197
198 $ hg qseries -s
198 $ hg qseries -s
199 f_git.patch: P0 (git)
199 f_git.patch: P0 (git)
200 f.patch: P1
200 f.patch: P1
201
201
202 $ hg -R .hg/patches manifest
202 $ hg -R .hg/patches manifest
203 .hgignore
203 .hgignore
204 f.patch
204 f.patch
205 f_git.patch
205 f_git.patch
206 series
206 series
207
207
208 $ cat .hg/patches/f_git.patch
208 $ cat .hg/patches/f_git.patch
209 # HG changeset patch
209 # HG changeset patch
210 # User test
210 # User test
211 # Date ?????????? ? (glob)
211 # Date ?????????? ? (glob)
212 # Node ID ???????????????????????????????????????? (glob)
212 # Node ID ???????????????????????????????????????? (glob)
213 # Parent bac9ed9960d8992bcad75864a879fa76cadaf1b0
213 # Parent bac9ed9960d8992bcad75864a879fa76cadaf1b0
214 P0 (git)
214 P0 (git)
215
215
216 diff --git a/p b/p
216 diff --git a/p b/p
217 new file mode 100644
217 new file mode 100644
218 --- /dev/null
218 --- /dev/null
219 +++ b/p
219 +++ b/p
220 @@ -0,0 +1,1 @@
220 @@ -0,0 +1,1 @@
221 +mq1
221 +mq1
222
222
223 $ cat .hg/patches/f.patch
223 $ cat .hg/patches/f.patch
224 # HG changeset patch
224 # HG changeset patch
225 # User test
225 # User test
226 # Date ?????????? ? (glob)
226 # Date ?????????? ? (glob)
227 # Node ID ???????????????????????????????????????? (glob)
227 # Node ID ???????????????????????????????????????? (glob)
228 # Parent ???????????????????????????????????????? (glob)
228 # Parent ???????????????????????????????????????? (glob)
229 P1
229 P1
230
230
231 diff -r ???????????? -r ???????????? p (glob)
231 diff -r ???????????? -r ???????????? p (glob)
232 --- a/p ??? ??? ?? ??:??:?? ???? ????? (glob)
232 --- a/p ??? ??? ?? ??:??:?? ???? ????? (glob)
233 +++ b/p ??? ??? ?? ??:??:?? ???? ????? (glob)
233 +++ b/p ??? ??? ?? ??:??:?? ???? ????? (glob)
234 @@ -1,1 +1,1 @@
234 @@ -1,1 +1,1 @@
235 -mq1
235 -mq1
236 +mq2
236 +mq2
237
237
238
238
239 Rebase with guards
239 Rebase with guards
240
240
241 $ hg init foo
241 $ hg init foo
242 $ cd foo
242 $ cd foo
243 $ echo a > a
243 $ echo a > a
244 $ hg ci -Am a
244 $ hg ci -Am a
245 adding a
245 adding a
246
246
247 Create mq repo with guarded patches foo and bar:
247 Create mq repo with guarded patches foo and bar and empty patch:
248
248
249 $ hg qinit
249 $ hg qinit
250 $ hg qnew foo
250 $ hg qnew foo
251 $ hg qguard foo +baz
251 $ hg qguard foo +baz
252 $ echo foo > foo
252 $ echo foo > foo
253 $ hg add foo
253 $ hg add foo
254 $ hg qref
254 $ hg qref
255 $ hg qpop
255 $ hg qpop
256 popping foo
256 popping foo
257 patch queue now empty
257 patch queue now empty
258
258
259 $ hg qnew empty-important -m 'important commit message'
260
259 $ hg qnew bar
261 $ hg qnew bar
260 $ hg qguard bar +baz
262 $ hg qguard bar +baz
261 $ echo bar > bar
263 $ echo bar > bar
262 $ hg add bar
264 $ hg add bar
263 $ hg qref
265 $ hg qref
264
266
265 $ hg qguard -l
267 $ hg qguard -l
268 empty-important: unguarded
266 bar: +baz
269 bar: +baz
267 foo: +baz
270 foo: +baz
268
271
269 $ hg tglog
272 $ hg tglog
270 @ 1:* '[mq]: bar' tags: bar qbase qtip tip (glob)
273 @ 2: '[mq]: bar' tags: bar qtip tip
271 |
274 |
272 o 0:* 'a' tags: qparent (glob)
275 o 1: 'important commit message' tags: empty-important qbase
276 |
277 o 0: 'a' tags: qparent
273
278
274 Create new head to rebase bar onto:
279 Create new head to rebase bar onto:
275
280
276 $ hg up -C 0
281 $ hg up -C 0
277 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
282 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
278 $ echo b > b
283 $ echo b > b
279 $ hg add b
284 $ hg add b
280 $ hg ci -m b
285 $ hg ci -m b
281 created new head
286 created new head
282 $ hg up -C 1
287 $ hg up -C 2
283 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
288 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
284 $ echo a >> a
289 $ echo a >> a
285 $ hg qref
290 $ hg qref
286
291
287 $ hg tglog
292 $ hg tglog
288 @ 2:* '[mq]: bar' tags: bar qbase qtip tip (glob)
293 @ 3: '[mq]: bar' tags: bar qtip tip
289 |
294 |
290 | o 1:* 'b' tags: (glob)
295 | o 2: 'b' tags:
296 | |
297 o | 1: 'important commit message' tags: empty-important qbase
291 |/
298 |/
292 o 0:* 'a' tags: qparent (glob)
299 o 0: 'a' tags: qparent
293
300
294
301
295 Rebase bar (make sure series order is preserved):
302 Rebase bar (make sure series order is preserved and empty-important also is
303 removed from the series):
296
304
297 $ hg qseries
305 $ hg qseries
306 empty-important
307 bar
308 foo
309 $ [ -f .hg/patches/empty-important ]
310 $ hg -q rebase -d 2
311 $ hg qseries
298 bar
312 bar
299 foo
313 foo
300 $ hg -q rebase -d 1
314 $ [ -f .hg/patches/empty-important ]
301 $ hg qseries
315 [1]
302 bar
303 foo
304
316
305 $ hg qguard -l
317 $ hg qguard -l
306 bar: +baz
318 bar: +baz
307 foo: +baz
319 foo: +baz
308
320
309 $ hg tglog
321 $ hg tglog
310 @ 2:* '[mq]: bar' tags: bar qbase qtip tip (glob)
322 @ 2:* '[mq]: bar' tags: bar qbase qtip tip (glob)
311 |
323 |
312 o 1:* 'b' tags: qparent (glob)
324 o 1:* 'b' tags: qparent (glob)
313 |
325 |
314 o 0:* 'a' tags: (glob)
326 o 0:* 'a' tags: (glob)
315
327
General Comments 0
You need to be logged in to leave comments. Login now