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