|
@@
-1,1959
+1,1971
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
|
https://mercurial-scm.org/wiki/RebaseExtension
|
|
14
|
https://mercurial-scm.org/wiki/RebaseExtension
|
|
15
|
'''
|
|
15
|
'''
|
|
16
|
|
|
16
|
|
|
17
|
from __future__ import absolute_import
|
|
17
|
from __future__ import absolute_import
|
|
18
|
|
|
18
|
|
|
19
|
import errno
|
|
19
|
import errno
|
|
20
|
import os
|
|
20
|
import os
|
|
21
|
|
|
21
|
|
|
22
|
from mercurial.i18n import _
|
|
22
|
from mercurial.i18n import _
|
|
23
|
from mercurial.node import (
|
|
23
|
from mercurial.node import (
|
|
24
|
nullrev,
|
|
24
|
nullrev,
|
|
25
|
short,
|
|
25
|
short,
|
|
26
|
)
|
|
26
|
)
|
|
27
|
from mercurial import (
|
|
27
|
from mercurial import (
|
|
28
|
bookmarks,
|
|
28
|
bookmarks,
|
|
29
|
cmdutil,
|
|
29
|
cmdutil,
|
|
30
|
commands,
|
|
30
|
commands,
|
|
31
|
copies,
|
|
31
|
copies,
|
|
32
|
destutil,
|
|
32
|
destutil,
|
|
33
|
dirstateguard,
|
|
33
|
dirstateguard,
|
|
34
|
error,
|
|
34
|
error,
|
|
35
|
extensions,
|
|
35
|
extensions,
|
|
36
|
hg,
|
|
36
|
hg,
|
|
37
|
merge as mergemod,
|
|
37
|
merge as mergemod,
|
|
38
|
mergeutil,
|
|
38
|
mergeutil,
|
|
39
|
obsolete,
|
|
39
|
obsolete,
|
|
40
|
obsutil,
|
|
40
|
obsutil,
|
|
41
|
patch,
|
|
41
|
patch,
|
|
42
|
phases,
|
|
42
|
phases,
|
|
43
|
pycompat,
|
|
43
|
pycompat,
|
|
44
|
registrar,
|
|
44
|
registrar,
|
|
45
|
repair,
|
|
45
|
repair,
|
|
46
|
revset,
|
|
46
|
revset,
|
|
47
|
revsetlang,
|
|
47
|
revsetlang,
|
|
48
|
scmutil,
|
|
48
|
scmutil,
|
|
49
|
smartset,
|
|
49
|
smartset,
|
|
50
|
state as statemod,
|
|
50
|
state as statemod,
|
|
51
|
util,
|
|
51
|
util,
|
|
52
|
)
|
|
52
|
)
|
|
53
|
|
|
53
|
|
|
54
|
# The following constants are used throughout the rebase module. The ordering of
|
|
54
|
# The following constants are used throughout the rebase module. The ordering of
|
|
55
|
# their values must be maintained.
|
|
55
|
# their values must be maintained.
|
|
56
|
|
|
56
|
|
|
57
|
# Indicates that a revision needs to be rebased
|
|
57
|
# Indicates that a revision needs to be rebased
|
|
58
|
revtodo = -1
|
|
58
|
revtodo = -1
|
|
59
|
revtodostr = '-1'
|
|
59
|
revtodostr = '-1'
|
|
60
|
|
|
60
|
|
|
61
|
# legacy revstates no longer needed in current code
|
|
61
|
# legacy revstates no longer needed in current code
|
|
62
|
# -2: nullmerge, -3: revignored, -4: revprecursor, -5: revpruned
|
|
62
|
# -2: nullmerge, -3: revignored, -4: revprecursor, -5: revpruned
|
|
63
|
legacystates = {'-2', '-3', '-4', '-5'}
|
|
63
|
legacystates = {'-2', '-3', '-4', '-5'}
|
|
64
|
|
|
64
|
|
|
65
|
cmdtable = {}
|
|
65
|
cmdtable = {}
|
|
66
|
command = registrar.command(cmdtable)
|
|
66
|
command = registrar.command(cmdtable)
|
|
67
|
# Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
|
|
67
|
# Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
|
|
68
|
# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
|
|
68
|
# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
|
|
69
|
# be specifying the version(s) of Mercurial they are tested with, or
|
|
69
|
# be specifying the version(s) of Mercurial they are tested with, or
|
|
70
|
# leave the attribute unspecified.
|
|
70
|
# leave the attribute unspecified.
|
|
71
|
testedwith = 'ships-with-hg-core'
|
|
71
|
testedwith = 'ships-with-hg-core'
|
|
72
|
|
|
72
|
|
|
73
|
def _nothingtorebase():
|
|
73
|
def _nothingtorebase():
|
|
74
|
return 1
|
|
74
|
return 1
|
|
75
|
|
|
75
|
|
|
76
|
def _savegraft(ctx, extra):
|
|
76
|
def _savegraft(ctx, extra):
|
|
77
|
s = ctx.extra().get('source', None)
|
|
77
|
s = ctx.extra().get('source', None)
|
|
78
|
if s is not None:
|
|
78
|
if s is not None:
|
|
79
|
extra['source'] = s
|
|
79
|
extra['source'] = s
|
|
80
|
s = ctx.extra().get('intermediate-source', None)
|
|
80
|
s = ctx.extra().get('intermediate-source', None)
|
|
81
|
if s is not None:
|
|
81
|
if s is not None:
|
|
82
|
extra['intermediate-source'] = s
|
|
82
|
extra['intermediate-source'] = s
|
|
83
|
|
|
83
|
|
|
84
|
def _savebranch(ctx, extra):
|
|
84
|
def _savebranch(ctx, extra):
|
|
85
|
extra['branch'] = ctx.branch()
|
|
85
|
extra['branch'] = ctx.branch()
|
|
86
|
|
|
86
|
|
|
87
|
def _destrebase(repo, sourceset, destspace=None):
|
|
87
|
def _destrebase(repo, sourceset, destspace=None):
|
|
88
|
"""small wrapper around destmerge to pass the right extra args
|
|
88
|
"""small wrapper around destmerge to pass the right extra args
|
|
89
|
|
|
89
|
|
|
90
|
Please wrap destutil.destmerge instead."""
|
|
90
|
Please wrap destutil.destmerge instead."""
|
|
91
|
return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
|
|
91
|
return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
|
|
92
|
onheadcheck=False, destspace=destspace)
|
|
92
|
onheadcheck=False, destspace=destspace)
|
|
93
|
|
|
93
|
|
|
94
|
revsetpredicate = registrar.revsetpredicate()
|
|
94
|
revsetpredicate = registrar.revsetpredicate()
|
|
95
|
|
|
95
|
|
|
96
|
@revsetpredicate('_destrebase')
|
|
96
|
@revsetpredicate('_destrebase')
|
|
97
|
def _revsetdestrebase(repo, subset, x):
|
|
97
|
def _revsetdestrebase(repo, subset, x):
|
|
98
|
# ``_rebasedefaultdest()``
|
|
98
|
# ``_rebasedefaultdest()``
|
|
99
|
|
|
99
|
|
|
100
|
# default destination for rebase.
|
|
100
|
# default destination for rebase.
|
|
101
|
# # XXX: Currently private because I expect the signature to change.
|
|
101
|
# # XXX: Currently private because I expect the signature to change.
|
|
102
|
# # XXX: - bailing out in case of ambiguity vs returning all data.
|
|
102
|
# # XXX: - bailing out in case of ambiguity vs returning all data.
|
|
103
|
# i18n: "_rebasedefaultdest" is a keyword
|
|
103
|
# i18n: "_rebasedefaultdest" is a keyword
|
|
104
|
sourceset = None
|
|
104
|
sourceset = None
|
|
105
|
if x is not None:
|
|
105
|
if x is not None:
|
|
106
|
sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
|
|
106
|
sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
|
|
107
|
return subset & smartset.baseset([_destrebase(repo, sourceset)])
|
|
107
|
return subset & smartset.baseset([_destrebase(repo, sourceset)])
|
|
108
|
|
|
108
|
|
|
109
|
@revsetpredicate('_destautoorphanrebase')
|
|
109
|
@revsetpredicate('_destautoorphanrebase')
|
|
110
|
def _revsetdestautoorphanrebase(repo, subset, x):
|
|
110
|
def _revsetdestautoorphanrebase(repo, subset, x):
|
|
111
|
# ``_destautoorphanrebase()``
|
|
111
|
# ``_destautoorphanrebase()``
|
|
112
|
|
|
112
|
|
|
113
|
# automatic rebase destination for a single orphan revision.
|
|
113
|
# automatic rebase destination for a single orphan revision.
|
|
114
|
unfi = repo.unfiltered()
|
|
114
|
unfi = repo.unfiltered()
|
|
115
|
obsoleted = unfi.revs('obsolete()')
|
|
115
|
obsoleted = unfi.revs('obsolete()')
|
|
116
|
|
|
116
|
|
|
117
|
src = revset.getset(repo, subset, x).first()
|
|
117
|
src = revset.getset(repo, subset, x).first()
|
|
118
|
|
|
118
|
|
|
119
|
# Empty src or already obsoleted - Do not return a destination
|
|
119
|
# Empty src or already obsoleted - Do not return a destination
|
|
120
|
if not src or src in obsoleted:
|
|
120
|
if not src or src in obsoleted:
|
|
121
|
return smartset.baseset()
|
|
121
|
return smartset.baseset()
|
|
122
|
dests = destutil.orphanpossibledestination(repo, src)
|
|
122
|
dests = destutil.orphanpossibledestination(repo, src)
|
|
123
|
if len(dests) > 1:
|
|
123
|
if len(dests) > 1:
|
|
124
|
raise error.Abort(
|
|
124
|
raise error.Abort(
|
|
125
|
_("ambiguous automatic rebase: %r could end up on any of %r") % (
|
|
125
|
_("ambiguous automatic rebase: %r could end up on any of %r") % (
|
|
126
|
src, dests))
|
|
126
|
src, dests))
|
|
127
|
# We have zero or one destination, so we can just return here.
|
|
127
|
# We have zero or one destination, so we can just return here.
|
|
128
|
return smartset.baseset(dests)
|
|
128
|
return smartset.baseset(dests)
|
|
129
|
|
|
129
|
|
|
130
|
def _ctxdesc(ctx):
|
|
130
|
def _ctxdesc(ctx):
|
|
131
|
"""short description for a context"""
|
|
131
|
"""short description for a context"""
|
|
132
|
desc = '%d:%s "%s"' % (ctx.rev(), ctx,
|
|
132
|
desc = '%d:%s "%s"' % (ctx.rev(), ctx,
|
|
133
|
ctx.description().split('\n', 1)[0])
|
|
133
|
ctx.description().split('\n', 1)[0])
|
|
134
|
repo = ctx.repo()
|
|
134
|
repo = ctx.repo()
|
|
135
|
names = []
|
|
135
|
names = []
|
|
136
|
for nsname, ns in repo.names.iteritems():
|
|
136
|
for nsname, ns in repo.names.iteritems():
|
|
137
|
if nsname == 'branches':
|
|
137
|
if nsname == 'branches':
|
|
138
|
continue
|
|
138
|
continue
|
|
139
|
names.extend(ns.names(repo, ctx.node()))
|
|
139
|
names.extend(ns.names(repo, ctx.node()))
|
|
140
|
if names:
|
|
140
|
if names:
|
|
141
|
desc += ' (%s)' % ' '.join(names)
|
|
141
|
desc += ' (%s)' % ' '.join(names)
|
|
142
|
return desc
|
|
142
|
return desc
|
|
143
|
|
|
143
|
|
|
144
|
class rebaseruntime(object):
|
|
144
|
class rebaseruntime(object):
|
|
145
|
"""This class is a container for rebase runtime state"""
|
|
145
|
"""This class is a container for rebase runtime state"""
|
|
146
|
def __init__(self, repo, ui, inmemory=False, opts=None):
|
|
146
|
def __init__(self, repo, ui, inmemory=False, opts=None):
|
|
147
|
if opts is None:
|
|
147
|
if opts is None:
|
|
148
|
opts = {}
|
|
148
|
opts = {}
|
|
149
|
|
|
149
|
|
|
150
|
# prepared: whether we have rebasestate prepared or not. Currently it
|
|
150
|
# prepared: whether we have rebasestate prepared or not. Currently it
|
|
151
|
# decides whether "self.repo" is unfiltered or not.
|
|
151
|
# decides whether "self.repo" is unfiltered or not.
|
|
152
|
# The rebasestate has explicit hash to hash instructions not depending
|
|
152
|
# The rebasestate has explicit hash to hash instructions not depending
|
|
153
|
# on visibility. If rebasestate exists (in-memory or on-disk), use
|
|
153
|
# on visibility. If rebasestate exists (in-memory or on-disk), use
|
|
154
|
# unfiltered repo to avoid visibility issues.
|
|
154
|
# unfiltered repo to avoid visibility issues.
|
|
155
|
# Before knowing rebasestate (i.e. when starting a new rebase (not
|
|
155
|
# Before knowing rebasestate (i.e. when starting a new rebase (not
|
|
156
|
# --continue or --abort)), the original repo should be used so
|
|
156
|
# --continue or --abort)), the original repo should be used so
|
|
157
|
# visibility-dependent revsets are correct.
|
|
157
|
# visibility-dependent revsets are correct.
|
|
158
|
self.prepared = False
|
|
158
|
self.prepared = False
|
|
159
|
self._repo = repo
|
|
159
|
self._repo = repo
|
|
160
|
|
|
160
|
|
|
161
|
self.ui = ui
|
|
161
|
self.ui = ui
|
|
162
|
self.opts = opts
|
|
162
|
self.opts = opts
|
|
163
|
self.originalwd = None
|
|
163
|
self.originalwd = None
|
|
164
|
self.external = nullrev
|
|
164
|
self.external = nullrev
|
|
165
|
# Mapping between the old revision id and either what is the new rebased
|
|
165
|
# Mapping between the old revision id and either what is the new rebased
|
|
166
|
# revision or what needs to be done with the old revision. The state
|
|
166
|
# revision or what needs to be done with the old revision. The state
|
|
167
|
# dict will be what contains most of the rebase progress state.
|
|
167
|
# dict will be what contains most of the rebase progress state.
|
|
168
|
self.state = {}
|
|
168
|
self.state = {}
|
|
169
|
self.activebookmark = None
|
|
169
|
self.activebookmark = None
|
|
170
|
self.destmap = {}
|
|
170
|
self.destmap = {}
|
|
171
|
self.skipped = set()
|
|
171
|
self.skipped = set()
|
|
172
|
|
|
172
|
|
|
173
|
self.collapsef = opts.get('collapse', False)
|
|
173
|
self.collapsef = opts.get('collapse', False)
|
|
174
|
self.collapsemsg = cmdutil.logmessage(ui, opts)
|
|
174
|
self.collapsemsg = cmdutil.logmessage(ui, opts)
|
|
175
|
self.date = opts.get('date', None)
|
|
175
|
self.date = opts.get('date', None)
|
|
176
|
|
|
176
|
|
|
177
|
e = opts.get('extrafn') # internal, used by e.g. hgsubversion
|
|
177
|
e = opts.get('extrafn') # internal, used by e.g. hgsubversion
|
|
178
|
self.extrafns = [_savegraft]
|
|
178
|
self.extrafns = [_savegraft]
|
|
179
|
if e:
|
|
179
|
if e:
|
|
180
|
self.extrafns = [e]
|
|
180
|
self.extrafns = [e]
|
|
181
|
|
|
181
|
|
|
182
|
self.backupf = ui.configbool('rewrite', 'backup-bundle')
|
|
182
|
self.backupf = ui.configbool('rewrite', 'backup-bundle')
|
|
183
|
self.keepf = opts.get('keep', False)
|
|
183
|
self.keepf = opts.get('keep', False)
|
|
184
|
self.keepbranchesf = opts.get('keepbranches', False)
|
|
184
|
self.keepbranchesf = opts.get('keepbranches', False)
|
|
185
|
self.obsoletenotrebased = {}
|
|
185
|
self.obsoletenotrebased = {}
|
|
186
|
self.obsoletewithoutsuccessorindestination = set()
|
|
186
|
self.obsoletewithoutsuccessorindestination = set()
|
|
187
|
self.inmemory = inmemory
|
|
187
|
self.inmemory = inmemory
|
|
188
|
self.stateobj = statemod.cmdstate(repo, 'rebasestate')
|
|
188
|
self.stateobj = statemod.cmdstate(repo, 'rebasestate')
|
|
189
|
|
|
189
|
|
|
190
|
@property
|
|
190
|
@property
|
|
191
|
def repo(self):
|
|
191
|
def repo(self):
|
|
192
|
if self.prepared:
|
|
192
|
if self.prepared:
|
|
193
|
return self._repo.unfiltered()
|
|
193
|
return self._repo.unfiltered()
|
|
194
|
else:
|
|
194
|
else:
|
|
195
|
return self._repo
|
|
195
|
return self._repo
|
|
196
|
|
|
196
|
|
|
197
|
def storestatus(self, tr=None):
|
|
197
|
def storestatus(self, tr=None):
|
|
198
|
"""Store the current status to allow recovery"""
|
|
198
|
"""Store the current status to allow recovery"""
|
|
199
|
if tr:
|
|
199
|
if tr:
|
|
200
|
tr.addfilegenerator('rebasestate', ('rebasestate',),
|
|
200
|
tr.addfilegenerator('rebasestate', ('rebasestate',),
|
|
201
|
self._writestatus, location='plain')
|
|
201
|
self._writestatus, location='plain')
|
|
202
|
else:
|
|
202
|
else:
|
|
203
|
with self.repo.vfs("rebasestate", "w") as f:
|
|
203
|
with self.repo.vfs("rebasestate", "w") as f:
|
|
204
|
self._writestatus(f)
|
|
204
|
self._writestatus(f)
|
|
205
|
|
|
205
|
|
|
206
|
def _writestatus(self, f):
|
|
206
|
def _writestatus(self, f):
|
|
207
|
repo = self.repo
|
|
207
|
repo = self.repo
|
|
208
|
assert repo.filtername is None
|
|
208
|
assert repo.filtername is None
|
|
209
|
f.write(repo[self.originalwd].hex() + '\n')
|
|
209
|
f.write(repo[self.originalwd].hex() + '\n')
|
|
210
|
# was "dest". we now write dest per src root below.
|
|
210
|
# was "dest". we now write dest per src root below.
|
|
211
|
f.write('\n')
|
|
211
|
f.write('\n')
|
|
212
|
f.write(repo[self.external].hex() + '\n')
|
|
212
|
f.write(repo[self.external].hex() + '\n')
|
|
213
|
f.write('%d\n' % int(self.collapsef))
|
|
213
|
f.write('%d\n' % int(self.collapsef))
|
|
214
|
f.write('%d\n' % int(self.keepf))
|
|
214
|
f.write('%d\n' % int(self.keepf))
|
|
215
|
f.write('%d\n' % int(self.keepbranchesf))
|
|
215
|
f.write('%d\n' % int(self.keepbranchesf))
|
|
216
|
f.write('%s\n' % (self.activebookmark or ''))
|
|
216
|
f.write('%s\n' % (self.activebookmark or ''))
|
|
217
|
destmap = self.destmap
|
|
217
|
destmap = self.destmap
|
|
218
|
for d, v in self.state.iteritems():
|
|
218
|
for d, v in self.state.iteritems():
|
|
219
|
oldrev = repo[d].hex()
|
|
219
|
oldrev = repo[d].hex()
|
|
220
|
if v >= 0:
|
|
220
|
if v >= 0:
|
|
221
|
newrev = repo[v].hex()
|
|
221
|
newrev = repo[v].hex()
|
|
222
|
else:
|
|
222
|
else:
|
|
223
|
newrev = "%d" % v
|
|
223
|
newrev = "%d" % v
|
|
224
|
destnode = repo[destmap[d]].hex()
|
|
224
|
destnode = repo[destmap[d]].hex()
|
|
225
|
f.write("%s:%s:%s\n" % (oldrev, newrev, destnode))
|
|
225
|
f.write("%s:%s:%s\n" % (oldrev, newrev, destnode))
|
|
226
|
repo.ui.debug('rebase status stored\n')
|
|
226
|
repo.ui.debug('rebase status stored\n')
|
|
227
|
|
|
227
|
|
|
228
|
def restorestatus(self):
|
|
228
|
def restorestatus(self):
|
|
229
|
"""Restore a previously stored status"""
|
|
229
|
"""Restore a previously stored status"""
|
|
230
|
if not self.stateobj.exists():
|
|
230
|
if not self.stateobj.exists():
|
|
231
|
cmdutil.wrongtooltocontinue(self.repo, _('rebase'))
|
|
231
|
cmdutil.wrongtooltocontinue(self.repo, _('rebase'))
|
|
232
|
|
|
232
|
|
|
233
|
data = self._read()
|
|
233
|
data = self._read()
|
|
234
|
self.repo.ui.debug('rebase status resumed\n')
|
|
234
|
self.repo.ui.debug('rebase status resumed\n')
|
|
235
|
|
|
235
|
|
|
236
|
self.originalwd = data['originalwd']
|
|
236
|
self.originalwd = data['originalwd']
|
|
237
|
self.destmap = data['destmap']
|
|
237
|
self.destmap = data['destmap']
|
|
238
|
self.state = data['state']
|
|
238
|
self.state = data['state']
|
|
239
|
self.skipped = data['skipped']
|
|
239
|
self.skipped = data['skipped']
|
|
240
|
self.collapsef = data['collapse']
|
|
240
|
self.collapsef = data['collapse']
|
|
241
|
self.keepf = data['keep']
|
|
241
|
self.keepf = data['keep']
|
|
242
|
self.keepbranchesf = data['keepbranches']
|
|
242
|
self.keepbranchesf = data['keepbranches']
|
|
243
|
self.external = data['external']
|
|
243
|
self.external = data['external']
|
|
244
|
self.activebookmark = data['activebookmark']
|
|
244
|
self.activebookmark = data['activebookmark']
|
|
245
|
|
|
245
|
|
|
246
|
def _read(self):
|
|
246
|
def _read(self):
|
|
247
|
self.prepared = True
|
|
247
|
self.prepared = True
|
|
248
|
repo = self.repo
|
|
248
|
repo = self.repo
|
|
249
|
assert repo.filtername is None
|
|
249
|
assert repo.filtername is None
|
|
250
|
data = {'keepbranches': None, 'collapse': None, 'activebookmark': None,
|
|
250
|
data = {'keepbranches': None, 'collapse': None, 'activebookmark': None,
|
|
251
|
'external': nullrev, 'keep': None, 'originalwd': None}
|
|
251
|
'external': nullrev, 'keep': None, 'originalwd': None}
|
|
252
|
legacydest = None
|
|
252
|
legacydest = None
|
|
253
|
state = {}
|
|
253
|
state = {}
|
|
254
|
destmap = {}
|
|
254
|
destmap = {}
|
|
255
|
|
|
255
|
|
|
256
|
if True:
|
|
256
|
if True:
|
|
257
|
f = repo.vfs("rebasestate")
|
|
257
|
f = repo.vfs("rebasestate")
|
|
258
|
for i, l in enumerate(f.read().splitlines()):
|
|
258
|
for i, l in enumerate(f.read().splitlines()):
|
|
259
|
if i == 0:
|
|
259
|
if i == 0:
|
|
260
|
data['originalwd'] = repo[l].rev()
|
|
260
|
data['originalwd'] = repo[l].rev()
|
|
261
|
elif i == 1:
|
|
261
|
elif i == 1:
|
|
262
|
# this line should be empty in newer version. but legacy
|
|
262
|
# this line should be empty in newer version. but legacy
|
|
263
|
# clients may still use it
|
|
263
|
# clients may still use it
|
|
264
|
if l:
|
|
264
|
if l:
|
|
265
|
legacydest = repo[l].rev()
|
|
265
|
legacydest = repo[l].rev()
|
|
266
|
elif i == 2:
|
|
266
|
elif i == 2:
|
|
267
|
data['external'] = repo[l].rev()
|
|
267
|
data['external'] = repo[l].rev()
|
|
268
|
elif i == 3:
|
|
268
|
elif i == 3:
|
|
269
|
data['collapse'] = bool(int(l))
|
|
269
|
data['collapse'] = bool(int(l))
|
|
270
|
elif i == 4:
|
|
270
|
elif i == 4:
|
|
271
|
data['keep'] = bool(int(l))
|
|
271
|
data['keep'] = bool(int(l))
|
|
272
|
elif i == 5:
|
|
272
|
elif i == 5:
|
|
273
|
data['keepbranches'] = bool(int(l))
|
|
273
|
data['keepbranches'] = bool(int(l))
|
|
274
|
elif i == 6 and not (len(l) == 81 and ':' in l):
|
|
274
|
elif i == 6 and not (len(l) == 81 and ':' in l):
|
|
275
|
# line 6 is a recent addition, so for backwards
|
|
275
|
# line 6 is a recent addition, so for backwards
|
|
276
|
# compatibility check that the line doesn't look like the
|
|
276
|
# compatibility check that the line doesn't look like the
|
|
277
|
# oldrev:newrev lines
|
|
277
|
# oldrev:newrev lines
|
|
278
|
data['activebookmark'] = l
|
|
278
|
data['activebookmark'] = l
|
|
279
|
else:
|
|
279
|
else:
|
|
280
|
args = l.split(':')
|
|
280
|
args = l.split(':')
|
|
281
|
oldrev = repo[args[0]].rev()
|
|
281
|
oldrev = repo[args[0]].rev()
|
|
282
|
newrev = args[1]
|
|
282
|
newrev = args[1]
|
|
283
|
if newrev in legacystates:
|
|
283
|
if newrev in legacystates:
|
|
284
|
continue
|
|
284
|
continue
|
|
285
|
if len(args) > 2:
|
|
285
|
if len(args) > 2:
|
|
286
|
destrev = repo[args[2]].rev()
|
|
286
|
destrev = repo[args[2]].rev()
|
|
287
|
else:
|
|
287
|
else:
|
|
288
|
destrev = legacydest
|
|
288
|
destrev = legacydest
|
|
289
|
destmap[oldrev] = destrev
|
|
289
|
destmap[oldrev] = destrev
|
|
290
|
if newrev == revtodostr:
|
|
290
|
if newrev == revtodostr:
|
|
291
|
state[oldrev] = revtodo
|
|
291
|
state[oldrev] = revtodo
|
|
292
|
# Legacy compat special case
|
|
292
|
# Legacy compat special case
|
|
293
|
else:
|
|
293
|
else:
|
|
294
|
state[oldrev] = repo[newrev].rev()
|
|
294
|
state[oldrev] = repo[newrev].rev()
|
|
295
|
|
|
295
|
|
|
296
|
if data['keepbranches'] is None:
|
|
296
|
if data['keepbranches'] is None:
|
|
297
|
raise error.Abort(_('.hg/rebasestate is incomplete'))
|
|
297
|
raise error.Abort(_('.hg/rebasestate is incomplete'))
|
|
298
|
|
|
298
|
|
|
299
|
data['destmap'] = destmap
|
|
299
|
data['destmap'] = destmap
|
|
300
|
data['state'] = state
|
|
300
|
data['state'] = state
|
|
301
|
skipped = set()
|
|
301
|
skipped = set()
|
|
302
|
# recompute the set of skipped revs
|
|
302
|
# recompute the set of skipped revs
|
|
303
|
if not data['collapse']:
|
|
303
|
if not data['collapse']:
|
|
304
|
seen = set(destmap.values())
|
|
304
|
seen = set(destmap.values())
|
|
305
|
for old, new in sorted(state.items()):
|
|
305
|
for old, new in sorted(state.items()):
|
|
306
|
if new != revtodo and new in seen:
|
|
306
|
if new != revtodo and new in seen:
|
|
307
|
skipped.add(old)
|
|
307
|
skipped.add(old)
|
|
308
|
seen.add(new)
|
|
308
|
seen.add(new)
|
|
309
|
data['skipped'] = skipped
|
|
309
|
data['skipped'] = skipped
|
|
310
|
repo.ui.debug('computed skipped revs: %s\n' %
|
|
310
|
repo.ui.debug('computed skipped revs: %s\n' %
|
|
311
|
(' '.join('%d' % r for r in sorted(skipped)) or ''))
|
|
311
|
(' '.join('%d' % r for r in sorted(skipped)) or ''))
|
|
312
|
|
|
312
|
|
|
313
|
return data
|
|
313
|
return data
|
|
314
|
|
|
314
|
|
|
315
|
def _handleskippingobsolete(self, obsoleterevs, destmap):
|
|
315
|
def _handleskippingobsolete(self, obsoleterevs, destmap):
|
|
316
|
"""Compute structures necessary for skipping obsolete revisions
|
|
316
|
"""Compute structures necessary for skipping obsolete revisions
|
|
317
|
|
|
317
|
|
|
318
|
obsoleterevs: iterable of all obsolete revisions in rebaseset
|
|
318
|
obsoleterevs: iterable of all obsolete revisions in rebaseset
|
|
319
|
destmap: {srcrev: destrev} destination revisions
|
|
319
|
destmap: {srcrev: destrev} destination revisions
|
|
320
|
"""
|
|
320
|
"""
|
|
321
|
self.obsoletenotrebased = {}
|
|
321
|
self.obsoletenotrebased = {}
|
|
322
|
if not self.ui.configbool('experimental', 'rebaseskipobsolete'):
|
|
322
|
if not self.ui.configbool('experimental', 'rebaseskipobsolete'):
|
|
323
|
return
|
|
323
|
return
|
|
324
|
obsoleteset = set(obsoleterevs)
|
|
324
|
obsoleteset = set(obsoleterevs)
|
|
325
|
(self.obsoletenotrebased,
|
|
325
|
(self.obsoletenotrebased,
|
|
326
|
self.obsoletewithoutsuccessorindestination,
|
|
326
|
self.obsoletewithoutsuccessorindestination,
|
|
327
|
obsoleteextinctsuccessors) = _computeobsoletenotrebased(
|
|
327
|
obsoleteextinctsuccessors) = _computeobsoletenotrebased(
|
|
328
|
self.repo, obsoleteset, destmap)
|
|
328
|
self.repo, obsoleteset, destmap)
|
|
329
|
skippedset = set(self.obsoletenotrebased)
|
|
329
|
skippedset = set(self.obsoletenotrebased)
|
|
330
|
skippedset.update(self.obsoletewithoutsuccessorindestination)
|
|
330
|
skippedset.update(self.obsoletewithoutsuccessorindestination)
|
|
331
|
skippedset.update(obsoleteextinctsuccessors)
|
|
331
|
skippedset.update(obsoleteextinctsuccessors)
|
|
332
|
_checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
|
|
332
|
_checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
|
|
333
|
|
|
333
|
|
|
334
|
def _prepareabortorcontinue(self, isabort, backup=True, suppwarns=False):
|
|
334
|
def _prepareabortorcontinue(self, isabort, backup=True, suppwarns=False):
|
|
335
|
try:
|
|
335
|
try:
|
|
336
|
self.restorestatus()
|
|
336
|
self.restorestatus()
|
|
337
|
self.collapsemsg = restorecollapsemsg(self.repo, isabort)
|
|
337
|
self.collapsemsg = restorecollapsemsg(self.repo, isabort)
|
|
338
|
except error.RepoLookupError:
|
|
338
|
except error.RepoLookupError:
|
|
339
|
if isabort:
|
|
339
|
if isabort:
|
|
340
|
clearstatus(self.repo)
|
|
340
|
clearstatus(self.repo)
|
|
341
|
clearcollapsemsg(self.repo)
|
|
341
|
clearcollapsemsg(self.repo)
|
|
342
|
self.repo.ui.warn(_('rebase aborted (no revision is removed,'
|
|
342
|
self.repo.ui.warn(_('rebase aborted (no revision is removed,'
|
|
343
|
' only broken state is cleared)\n'))
|
|
343
|
' only broken state is cleared)\n'))
|
|
344
|
return 0
|
|
344
|
return 0
|
|
345
|
else:
|
|
345
|
else:
|
|
346
|
msg = _('cannot continue inconsistent rebase')
|
|
346
|
msg = _('cannot continue inconsistent rebase')
|
|
347
|
hint = _('use "hg rebase --abort" to clear broken state')
|
|
347
|
hint = _('use "hg rebase --abort" to clear broken state')
|
|
348
|
raise error.Abort(msg, hint=hint)
|
|
348
|
raise error.Abort(msg, hint=hint)
|
|
349
|
|
|
349
|
|
|
350
|
if isabort:
|
|
350
|
if isabort:
|
|
351
|
backup = backup and self.backupf
|
|
351
|
backup = backup and self.backupf
|
|
352
|
return self._abort(backup=backup, suppwarns=suppwarns)
|
|
352
|
return self._abort(backup=backup, suppwarns=suppwarns)
|
|
353
|
|
|
353
|
|
|
354
|
def _preparenewrebase(self, destmap):
|
|
354
|
def _preparenewrebase(self, destmap):
|
|
355
|
if not destmap:
|
|
355
|
if not destmap:
|
|
356
|
return _nothingtorebase()
|
|
356
|
return _nothingtorebase()
|
|
357
|
|
|
357
|
|
|
358
|
rebaseset = destmap.keys()
|
|
358
|
rebaseset = destmap.keys()
|
|
359
|
allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
|
|
359
|
allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
|
|
360
|
if (not (self.keepf or allowunstable)
|
|
360
|
if (not (self.keepf or allowunstable)
|
|
361
|
and self.repo.revs('first(children(%ld) - %ld)',
|
|
361
|
and self.repo.revs('first(children(%ld) - %ld)',
|
|
362
|
rebaseset, rebaseset)):
|
|
362
|
rebaseset, rebaseset)):
|
|
363
|
raise error.Abort(
|
|
363
|
raise error.Abort(
|
|
364
|
_("can't remove original changesets with"
|
|
364
|
_("can't remove original changesets with"
|
|
365
|
" unrebased descendants"),
|
|
365
|
" unrebased descendants"),
|
|
366
|
hint=_('use --keep to keep original changesets'))
|
|
366
|
hint=_('use --keep to keep original changesets'))
|
|
367
|
|
|
367
|
|
|
368
|
result = buildstate(self.repo, destmap, self.collapsef)
|
|
368
|
result = buildstate(self.repo, destmap, self.collapsef)
|
|
369
|
|
|
369
|
|
|
370
|
if not result:
|
|
370
|
if not result:
|
|
371
|
# Empty state built, nothing to rebase
|
|
371
|
# Empty state built, nothing to rebase
|
|
372
|
self.ui.status(_('nothing to rebase\n'))
|
|
372
|
self.ui.status(_('nothing to rebase\n'))
|
|
373
|
return _nothingtorebase()
|
|
373
|
return _nothingtorebase()
|
|
374
|
|
|
374
|
|
|
375
|
for root in self.repo.set('roots(%ld)', rebaseset):
|
|
375
|
for root in self.repo.set('roots(%ld)', rebaseset):
|
|
376
|
if not self.keepf and not root.mutable():
|
|
376
|
if not self.keepf and not root.mutable():
|
|
377
|
raise error.Abort(_("can't rebase public changeset %s")
|
|
377
|
raise error.Abort(_("can't rebase public changeset %s")
|
|
378
|
% root,
|
|
378
|
% root,
|
|
379
|
hint=_("see 'hg help phases' for details"))
|
|
379
|
hint=_("see 'hg help phases' for details"))
|
|
380
|
|
|
380
|
|
|
381
|
(self.originalwd, self.destmap, self.state) = result
|
|
381
|
(self.originalwd, self.destmap, self.state) = result
|
|
382
|
if self.collapsef:
|
|
382
|
if self.collapsef:
|
|
383
|
dests = set(self.destmap.values())
|
|
383
|
dests = set(self.destmap.values())
|
|
384
|
if len(dests) != 1:
|
|
384
|
if len(dests) != 1:
|
|
385
|
raise error.Abort(
|
|
385
|
raise error.Abort(
|
|
386
|
_('--collapse does not work with multiple destinations'))
|
|
386
|
_('--collapse does not work with multiple destinations'))
|
|
387
|
destrev = next(iter(dests))
|
|
387
|
destrev = next(iter(dests))
|
|
388
|
destancestors = self.repo.changelog.ancestors([destrev],
|
|
388
|
destancestors = self.repo.changelog.ancestors([destrev],
|
|
389
|
inclusive=True)
|
|
389
|
inclusive=True)
|
|
390
|
self.external = externalparent(self.repo, self.state, destancestors)
|
|
390
|
self.external = externalparent(self.repo, self.state, destancestors)
|
|
391
|
|
|
391
|
|
|
392
|
for destrev in sorted(set(destmap.values())):
|
|
392
|
for destrev in sorted(set(destmap.values())):
|
|
393
|
dest = self.repo[destrev]
|
|
393
|
dest = self.repo[destrev]
|
|
394
|
if dest.closesbranch() and not self.keepbranchesf:
|
|
394
|
if dest.closesbranch() and not self.keepbranchesf:
|
|
395
|
self.ui.status(_('reopening closed branch head %s\n') % dest)
|
|
395
|
self.ui.status(_('reopening closed branch head %s\n') % dest)
|
|
396
|
|
|
396
|
|
|
397
|
self.prepared = True
|
|
397
|
self.prepared = True
|
|
398
|
|
|
398
|
|
|
399
|
def _assignworkingcopy(self):
|
|
399
|
def _assignworkingcopy(self):
|
|
400
|
if self.inmemory:
|
|
400
|
if self.inmemory:
|
|
401
|
from mercurial.context import overlayworkingctx
|
|
401
|
from mercurial.context import overlayworkingctx
|
|
402
|
self.wctx = overlayworkingctx(self.repo)
|
|
402
|
self.wctx = overlayworkingctx(self.repo)
|
|
403
|
self.repo.ui.debug("rebasing in-memory\n")
|
|
403
|
self.repo.ui.debug("rebasing in-memory\n")
|
|
404
|
else:
|
|
404
|
else:
|
|
405
|
self.wctx = self.repo[None]
|
|
405
|
self.wctx = self.repo[None]
|
|
406
|
self.repo.ui.debug("rebasing on disk\n")
|
|
406
|
self.repo.ui.debug("rebasing on disk\n")
|
|
407
|
self.repo.ui.log("rebase",
|
|
407
|
self.repo.ui.log("rebase",
|
|
408
|
"using in-memory rebase: %r\n", self.inmemory,
|
|
408
|
"using in-memory rebase: %r\n", self.inmemory,
|
|
409
|
rebase_imm_used=self.inmemory)
|
|
409
|
rebase_imm_used=self.inmemory)
|
|
410
|
|
|
410
|
|
|
411
|
def _performrebase(self, tr):
|
|
411
|
def _performrebase(self, tr):
|
|
412
|
self._assignworkingcopy()
|
|
412
|
self._assignworkingcopy()
|
|
413
|
repo, ui = self.repo, self.ui
|
|
413
|
repo, ui = self.repo, self.ui
|
|
414
|
if self.keepbranchesf:
|
|
414
|
if self.keepbranchesf:
|
|
415
|
# insert _savebranch at the start of extrafns so if
|
|
415
|
# insert _savebranch at the start of extrafns so if
|
|
416
|
# there's a user-provided extrafn it can clobber branch if
|
|
416
|
# there's a user-provided extrafn it can clobber branch if
|
|
417
|
# desired
|
|
417
|
# desired
|
|
418
|
self.extrafns.insert(0, _savebranch)
|
|
418
|
self.extrafns.insert(0, _savebranch)
|
|
419
|
if self.collapsef:
|
|
419
|
if self.collapsef:
|
|
420
|
branches = set()
|
|
420
|
branches = set()
|
|
421
|
for rev in self.state:
|
|
421
|
for rev in self.state:
|
|
422
|
branches.add(repo[rev].branch())
|
|
422
|
branches.add(repo[rev].branch())
|
|
423
|
if len(branches) > 1:
|
|
423
|
if len(branches) > 1:
|
|
424
|
raise error.Abort(_('cannot collapse multiple named '
|
|
424
|
raise error.Abort(_('cannot collapse multiple named '
|
|
425
|
'branches'))
|
|
425
|
'branches'))
|
|
426
|
|
|
426
|
|
|
427
|
# Calculate self.obsoletenotrebased
|
|
427
|
# Calculate self.obsoletenotrebased
|
|
428
|
obsrevs = _filterobsoleterevs(self.repo, self.state)
|
|
428
|
obsrevs = _filterobsoleterevs(self.repo, self.state)
|
|
429
|
self._handleskippingobsolete(obsrevs, self.destmap)
|
|
429
|
self._handleskippingobsolete(obsrevs, self.destmap)
|
|
430
|
|
|
430
|
|
|
431
|
# Keep track of the active bookmarks in order to reset them later
|
|
431
|
# Keep track of the active bookmarks in order to reset them later
|
|
432
|
self.activebookmark = self.activebookmark or repo._activebookmark
|
|
432
|
self.activebookmark = self.activebookmark or repo._activebookmark
|
|
433
|
if self.activebookmark:
|
|
433
|
if self.activebookmark:
|
|
434
|
bookmarks.deactivate(repo)
|
|
434
|
bookmarks.deactivate(repo)
|
|
435
|
|
|
435
|
|
|
436
|
# Store the state before we begin so users can run 'hg rebase --abort'
|
|
436
|
# Store the state before we begin so users can run 'hg rebase --abort'
|
|
437
|
# if we fail before the transaction closes.
|
|
437
|
# if we fail before the transaction closes.
|
|
438
|
self.storestatus()
|
|
438
|
self.storestatus()
|
|
439
|
if tr:
|
|
439
|
if tr:
|
|
440
|
# When using single transaction, store state when transaction
|
|
440
|
# When using single transaction, store state when transaction
|
|
441
|
# commits.
|
|
441
|
# commits.
|
|
442
|
self.storestatus(tr)
|
|
442
|
self.storestatus(tr)
|
|
443
|
|
|
443
|
|
|
444
|
cands = [k for k, v in self.state.iteritems() if v == revtodo]
|
|
444
|
cands = [k for k, v in self.state.iteritems() if v == revtodo]
|
|
445
|
p = repo.ui.makeprogress(_("rebasing"), unit=_('changesets'),
|
|
445
|
p = repo.ui.makeprogress(_("rebasing"), unit=_('changesets'),
|
|
446
|
total=len(cands))
|
|
446
|
total=len(cands))
|
|
447
|
def progress(ctx):
|
|
447
|
def progress(ctx):
|
|
448
|
p.increment(item=("%d:%s" % (ctx.rev(), ctx)))
|
|
448
|
p.increment(item=("%d:%s" % (ctx.rev(), ctx)))
|
|
449
|
allowdivergence = self.ui.configbool(
|
|
449
|
allowdivergence = self.ui.configbool(
|
|
450
|
'experimental', 'evolution.allowdivergence')
|
|
450
|
'experimental', 'evolution.allowdivergence')
|
|
451
|
for subset in sortsource(self.destmap):
|
|
451
|
for subset in sortsource(self.destmap):
|
|
452
|
sortedrevs = self.repo.revs('sort(%ld, -topo)', subset)
|
|
452
|
sortedrevs = self.repo.revs('sort(%ld, -topo)', subset)
|
|
453
|
if not allowdivergence:
|
|
453
|
if not allowdivergence:
|
|
454
|
sortedrevs -= self.repo.revs(
|
|
454
|
sortedrevs -= self.repo.revs(
|
|
455
|
'descendants(%ld) and not %ld',
|
|
455
|
'descendants(%ld) and not %ld',
|
|
456
|
self.obsoletewithoutsuccessorindestination,
|
|
456
|
self.obsoletewithoutsuccessorindestination,
|
|
457
|
self.obsoletewithoutsuccessorindestination,
|
|
457
|
self.obsoletewithoutsuccessorindestination,
|
|
458
|
)
|
|
458
|
)
|
|
459
|
for rev in sortedrevs:
|
|
459
|
for rev in sortedrevs:
|
|
460
|
self._rebasenode(tr, rev, allowdivergence, progress)
|
|
460
|
self._rebasenode(tr, rev, allowdivergence, progress)
|
|
461
|
p.complete()
|
|
461
|
p.complete()
|
|
462
|
ui.note(_('rebase merging completed\n'))
|
|
462
|
ui.note(_('rebase merging completed\n'))
|
|
463
|
|
|
463
|
|
|
464
|
def _concludenode(self, rev, p1, p2, editor, commitmsg=None):
|
|
464
|
def _concludenode(self, rev, p1, p2, editor, commitmsg=None):
|
|
465
|
'''Commit the wd changes with parents p1 and p2.
|
|
465
|
'''Commit the wd changes with parents p1 and p2.
|
|
466
|
|
|
466
|
|
|
467
|
Reuse commit info from rev but also store useful information in extra.
|
|
467
|
Reuse commit info from rev but also store useful information in extra.
|
|
468
|
Return node of committed revision.'''
|
|
468
|
Return node of committed revision.'''
|
|
469
|
repo = self.repo
|
|
469
|
repo = self.repo
|
|
470
|
ctx = repo[rev]
|
|
470
|
ctx = repo[rev]
|
|
471
|
if commitmsg is None:
|
|
471
|
if commitmsg is None:
|
|
472
|
commitmsg = ctx.description()
|
|
472
|
commitmsg = ctx.description()
|
|
473
|
date = self.date
|
|
473
|
date = self.date
|
|
474
|
if date is None:
|
|
474
|
if date is None:
|
|
475
|
date = ctx.date()
|
|
475
|
date = ctx.date()
|
|
476
|
extra = {'rebase_source': ctx.hex()}
|
|
476
|
extra = {'rebase_source': ctx.hex()}
|
|
477
|
for c in self.extrafns:
|
|
477
|
for c in self.extrafns:
|
|
478
|
c(ctx, extra)
|
|
478
|
c(ctx, extra)
|
|
479
|
keepbranch = self.keepbranchesf and repo[p1].branch() != ctx.branch()
|
|
479
|
keepbranch = self.keepbranchesf and repo[p1].branch() != ctx.branch()
|
|
480
|
destphase = max(ctx.phase(), phases.draft)
|
|
480
|
destphase = max(ctx.phase(), phases.draft)
|
|
481
|
overrides = {('phases', 'new-commit'): destphase}
|
|
481
|
overrides = {('phases', 'new-commit'): destphase}
|
|
482
|
if keepbranch:
|
|
482
|
if keepbranch:
|
|
483
|
overrides[('ui', 'allowemptycommit')] = True
|
|
483
|
overrides[('ui', 'allowemptycommit')] = True
|
|
484
|
with repo.ui.configoverride(overrides, 'rebase'):
|
|
484
|
with repo.ui.configoverride(overrides, 'rebase'):
|
|
485
|
if self.inmemory:
|
|
485
|
if self.inmemory:
|
|
486
|
newnode = commitmemorynode(repo, p1, p2,
|
|
486
|
newnode = commitmemorynode(repo, p1, p2,
|
|
487
|
wctx=self.wctx,
|
|
487
|
wctx=self.wctx,
|
|
488
|
extra=extra,
|
|
488
|
extra=extra,
|
|
489
|
commitmsg=commitmsg,
|
|
489
|
commitmsg=commitmsg,
|
|
490
|
editor=editor,
|
|
490
|
editor=editor,
|
|
491
|
user=ctx.user(),
|
|
491
|
user=ctx.user(),
|
|
492
|
date=date)
|
|
492
|
date=date)
|
|
493
|
mergemod.mergestate.clean(repo)
|
|
493
|
mergemod.mergestate.clean(repo)
|
|
494
|
else:
|
|
494
|
else:
|
|
495
|
newnode = commitnode(repo, p1, p2,
|
|
495
|
newnode = commitnode(repo, p1, p2,
|
|
496
|
extra=extra,
|
|
496
|
extra=extra,
|
|
497
|
commitmsg=commitmsg,
|
|
497
|
commitmsg=commitmsg,
|
|
498
|
editor=editor,
|
|
498
|
editor=editor,
|
|
499
|
user=ctx.user(),
|
|
499
|
user=ctx.user(),
|
|
500
|
date=date)
|
|
500
|
date=date)
|
|
501
|
|
|
501
|
|
|
502
|
if newnode is None:
|
|
502
|
if newnode is None:
|
|
503
|
# If it ended up being a no-op commit, then the normal
|
|
503
|
# If it ended up being a no-op commit, then the normal
|
|
504
|
# merge state clean-up path doesn't happen, so do it
|
|
504
|
# merge state clean-up path doesn't happen, so do it
|
|
505
|
# here. Fix issue5494
|
|
505
|
# here. Fix issue5494
|
|
506
|
mergemod.mergestate.clean(repo)
|
|
506
|
mergemod.mergestate.clean(repo)
|
|
507
|
return newnode
|
|
507
|
return newnode
|
|
508
|
|
|
508
|
|
|
509
|
def _rebasenode(self, tr, rev, allowdivergence, progressfn):
|
|
509
|
def _rebasenode(self, tr, rev, allowdivergence, progressfn):
|
|
510
|
repo, ui, opts = self.repo, self.ui, self.opts
|
|
510
|
repo, ui, opts = self.repo, self.ui, self.opts
|
|
511
|
dest = self.destmap[rev]
|
|
511
|
dest = self.destmap[rev]
|
|
512
|
ctx = repo[rev]
|
|
512
|
ctx = repo[rev]
|
|
513
|
desc = _ctxdesc(ctx)
|
|
513
|
desc = _ctxdesc(ctx)
|
|
514
|
if self.state[rev] == rev:
|
|
514
|
if self.state[rev] == rev:
|
|
515
|
ui.status(_('already rebased %s\n') % desc)
|
|
515
|
ui.status(_('already rebased %s\n') % desc)
|
|
516
|
elif (not allowdivergence
|
|
516
|
elif (not allowdivergence
|
|
517
|
and rev in self.obsoletewithoutsuccessorindestination):
|
|
517
|
and rev in self.obsoletewithoutsuccessorindestination):
|
|
518
|
msg = _('note: not rebasing %s and its descendants as '
|
|
518
|
msg = _('note: not rebasing %s and its descendants as '
|
|
519
|
'this would cause divergence\n') % desc
|
|
519
|
'this would cause divergence\n') % desc
|
|
520
|
repo.ui.status(msg)
|
|
520
|
repo.ui.status(msg)
|
|
521
|
self.skipped.add(rev)
|
|
521
|
self.skipped.add(rev)
|
|
522
|
elif rev in self.obsoletenotrebased:
|
|
522
|
elif rev in self.obsoletenotrebased:
|
|
523
|
succ = self.obsoletenotrebased[rev]
|
|
523
|
succ = self.obsoletenotrebased[rev]
|
|
524
|
if succ is None:
|
|
524
|
if succ is None:
|
|
525
|
msg = _('note: not rebasing %s, it has no '
|
|
525
|
msg = _('note: not rebasing %s, it has no '
|
|
526
|
'successor\n') % desc
|
|
526
|
'successor\n') % desc
|
|
527
|
else:
|
|
527
|
else:
|
|
528
|
succdesc = _ctxdesc(repo[succ])
|
|
528
|
succdesc = _ctxdesc(repo[succ])
|
|
529
|
msg = (_('note: not rebasing %s, already in '
|
|
529
|
msg = (_('note: not rebasing %s, already in '
|
|
530
|
'destination as %s\n') % (desc, succdesc))
|
|
530
|
'destination as %s\n') % (desc, succdesc))
|
|
531
|
repo.ui.status(msg)
|
|
531
|
repo.ui.status(msg)
|
|
532
|
# Make clearrebased aware state[rev] is not a true successor
|
|
532
|
# Make clearrebased aware state[rev] is not a true successor
|
|
533
|
self.skipped.add(rev)
|
|
533
|
self.skipped.add(rev)
|
|
534
|
# Record rev as moved to its desired destination in self.state.
|
|
534
|
# Record rev as moved to its desired destination in self.state.
|
|
535
|
# This helps bookmark and working parent movement.
|
|
535
|
# This helps bookmark and working parent movement.
|
|
536
|
dest = max(adjustdest(repo, rev, self.destmap, self.state,
|
|
536
|
dest = max(adjustdest(repo, rev, self.destmap, self.state,
|
|
537
|
self.skipped))
|
|
537
|
self.skipped))
|
|
538
|
self.state[rev] = dest
|
|
538
|
self.state[rev] = dest
|
|
539
|
elif self.state[rev] == revtodo:
|
|
539
|
elif self.state[rev] == revtodo:
|
|
540
|
ui.status(_('rebasing %s\n') % desc)
|
|
540
|
ui.status(_('rebasing %s\n') % desc)
|
|
541
|
progressfn(ctx)
|
|
541
|
progressfn(ctx)
|
|
542
|
p1, p2, base = defineparents(repo, rev, self.destmap,
|
|
542
|
p1, p2, base = defineparents(repo, rev, self.destmap,
|
|
543
|
self.state, self.skipped,
|
|
543
|
self.state, self.skipped,
|
|
544
|
self.obsoletenotrebased)
|
|
544
|
self.obsoletenotrebased)
|
|
545
|
if not self.inmemory and len(repo[None].parents()) == 2:
|
|
545
|
if not self.inmemory and len(repo[None].parents()) == 2:
|
|
546
|
repo.ui.debug('resuming interrupted rebase\n')
|
|
546
|
repo.ui.debug('resuming interrupted rebase\n')
|
|
547
|
else:
|
|
547
|
else:
|
|
548
|
overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
|
|
548
|
overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
|
|
549
|
with ui.configoverride(overrides, 'rebase'):
|
|
549
|
with ui.configoverride(overrides, 'rebase'):
|
|
550
|
stats = rebasenode(repo, rev, p1, base, self.collapsef,
|
|
550
|
stats = rebasenode(repo, rev, p1, base, self.collapsef,
|
|
551
|
dest, wctx=self.wctx)
|
|
551
|
dest, wctx=self.wctx)
|
|
552
|
if stats.unresolvedcount > 0:
|
|
552
|
if stats.unresolvedcount > 0:
|
|
553
|
if self.inmemory:
|
|
553
|
if self.inmemory:
|
|
554
|
raise error.InMemoryMergeConflictsError()
|
|
554
|
raise error.InMemoryMergeConflictsError()
|
|
555
|
else:
|
|
555
|
else:
|
|
556
|
raise error.InterventionRequired(
|
|
556
|
raise error.InterventionRequired(
|
|
557
|
_('unresolved conflicts (see hg '
|
|
557
|
_('unresolved conflicts (see hg '
|
|
558
|
'resolve, then hg rebase --continue)'))
|
|
558
|
'resolve, then hg rebase --continue)'))
|
|
559
|
if not self.collapsef:
|
|
559
|
if not self.collapsef:
|
|
560
|
merging = p2 != nullrev
|
|
560
|
merging = p2 != nullrev
|
|
561
|
editform = cmdutil.mergeeditform(merging, 'rebase')
|
|
561
|
editform = cmdutil.mergeeditform(merging, 'rebase')
|
|
562
|
editor = cmdutil.getcommiteditor(editform=editform,
|
|
562
|
editor = cmdutil.getcommiteditor(editform=editform,
|
|
563
|
**pycompat.strkwargs(opts))
|
|
563
|
**pycompat.strkwargs(opts))
|
|
564
|
newnode = self._concludenode(rev, p1, p2, editor)
|
|
564
|
newnode = self._concludenode(rev, p1, p2, editor)
|
|
565
|
else:
|
|
565
|
else:
|
|
566
|
# Skip commit if we are collapsing
|
|
566
|
# Skip commit if we are collapsing
|
|
567
|
if self.inmemory:
|
|
567
|
if self.inmemory:
|
|
568
|
self.wctx.setbase(repo[p1])
|
|
568
|
self.wctx.setbase(repo[p1])
|
|
569
|
else:
|
|
569
|
else:
|
|
570
|
repo.setparents(repo[p1].node())
|
|
570
|
repo.setparents(repo[p1].node())
|
|
571
|
newnode = None
|
|
571
|
newnode = None
|
|
572
|
# Update the state
|
|
572
|
# Update the state
|
|
573
|
if newnode is not None:
|
|
573
|
if newnode is not None:
|
|
574
|
self.state[rev] = repo[newnode].rev()
|
|
574
|
self.state[rev] = repo[newnode].rev()
|
|
575
|
ui.debug('rebased as %s\n' % short(newnode))
|
|
575
|
ui.debug('rebased as %s\n' % short(newnode))
|
|
576
|
else:
|
|
576
|
else:
|
|
577
|
if not self.collapsef:
|
|
577
|
if not self.collapsef:
|
|
578
|
ui.warn(_('note: not rebasing %s, its destination already '
|
|
578
|
ui.warn(_('note: not rebasing %s, its destination already '
|
|
579
|
'has all its changes\n') % desc)
|
|
579
|
'has all its changes\n') % desc)
|
|
580
|
self.skipped.add(rev)
|
|
580
|
self.skipped.add(rev)
|
|
581
|
self.state[rev] = p1
|
|
581
|
self.state[rev] = p1
|
|
582
|
ui.debug('next revision set to %d\n' % p1)
|
|
582
|
ui.debug('next revision set to %d\n' % p1)
|
|
583
|
else:
|
|
583
|
else:
|
|
584
|
ui.status(_('already rebased %s as %s\n') %
|
|
584
|
ui.status(_('already rebased %s as %s\n') %
|
|
585
|
(desc, repo[self.state[rev]]))
|
|
585
|
(desc, repo[self.state[rev]]))
|
|
586
|
if not tr:
|
|
586
|
if not tr:
|
|
587
|
# When not using single transaction, store state after each
|
|
587
|
# When not using single transaction, store state after each
|
|
588
|
# commit is completely done. On InterventionRequired, we thus
|
|
588
|
# commit is completely done. On InterventionRequired, we thus
|
|
589
|
# won't store the status. Instead, we'll hit the "len(parents) == 2"
|
|
589
|
# won't store the status. Instead, we'll hit the "len(parents) == 2"
|
|
590
|
# case and realize that the commit was in progress.
|
|
590
|
# case and realize that the commit was in progress.
|
|
591
|
self.storestatus()
|
|
591
|
self.storestatus()
|
|
592
|
|
|
592
|
|
|
593
|
def _finishrebase(self):
|
|
593
|
def _finishrebase(self):
|
|
594
|
repo, ui, opts = self.repo, self.ui, self.opts
|
|
594
|
repo, ui, opts = self.repo, self.ui, self.opts
|
|
595
|
fm = ui.formatter('rebase', opts)
|
|
595
|
fm = ui.formatter('rebase', opts)
|
|
596
|
fm.startitem()
|
|
596
|
fm.startitem()
|
|
597
|
if self.collapsef:
|
|
597
|
if self.collapsef:
|
|
598
|
p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
|
|
598
|
p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
|
|
599
|
self.state, self.skipped,
|
|
599
|
self.state, self.skipped,
|
|
600
|
self.obsoletenotrebased)
|
|
600
|
self.obsoletenotrebased)
|
|
601
|
editopt = opts.get('edit')
|
|
601
|
editopt = opts.get('edit')
|
|
602
|
editform = 'rebase.collapse'
|
|
602
|
editform = 'rebase.collapse'
|
|
603
|
if self.collapsemsg:
|
|
603
|
if self.collapsemsg:
|
|
604
|
commitmsg = self.collapsemsg
|
|
604
|
commitmsg = self.collapsemsg
|
|
605
|
else:
|
|
605
|
else:
|
|
606
|
commitmsg = 'Collapsed revision'
|
|
606
|
commitmsg = 'Collapsed revision'
|
|
607
|
for rebased in sorted(self.state):
|
|
607
|
for rebased in sorted(self.state):
|
|
608
|
if rebased not in self.skipped:
|
|
608
|
if rebased not in self.skipped:
|
|
609
|
commitmsg += '\n* %s' % repo[rebased].description()
|
|
609
|
commitmsg += '\n* %s' % repo[rebased].description()
|
|
610
|
editopt = True
|
|
610
|
editopt = True
|
|
611
|
editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
|
|
611
|
editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
|
|
612
|
revtoreuse = max(self.state)
|
|
612
|
revtoreuse = max(self.state)
|
|
613
|
|
|
613
|
|
|
614
|
newnode = self._concludenode(revtoreuse, p1, self.external,
|
|
614
|
newnode = self._concludenode(revtoreuse, p1, self.external,
|
|
615
|
editor, commitmsg=commitmsg)
|
|
615
|
editor, commitmsg=commitmsg)
|
|
616
|
|
|
616
|
|
|
617
|
if newnode is not None:
|
|
617
|
if newnode is not None:
|
|
618
|
newrev = repo[newnode].rev()
|
|
618
|
newrev = repo[newnode].rev()
|
|
619
|
for oldrev in self.state:
|
|
619
|
for oldrev in self.state:
|
|
620
|
self.state[oldrev] = newrev
|
|
620
|
self.state[oldrev] = newrev
|
|
621
|
|
|
621
|
|
|
622
|
if 'qtip' in repo.tags():
|
|
622
|
if 'qtip' in repo.tags():
|
|
623
|
updatemq(repo, self.state, self.skipped,
|
|
623
|
updatemq(repo, self.state, self.skipped,
|
|
624
|
**pycompat.strkwargs(opts))
|
|
624
|
**pycompat.strkwargs(opts))
|
|
625
|
|
|
625
|
|
|
626
|
# restore original working directory
|
|
626
|
# restore original working directory
|
|
627
|
# (we do this before stripping)
|
|
627
|
# (we do this before stripping)
|
|
628
|
newwd = self.state.get(self.originalwd, self.originalwd)
|
|
628
|
newwd = self.state.get(self.originalwd, self.originalwd)
|
|
629
|
if newwd < 0:
|
|
629
|
if newwd < 0:
|
|
630
|
# original directory is a parent of rebase set root or ignored
|
|
630
|
# original directory is a parent of rebase set root or ignored
|
|
631
|
newwd = self.originalwd
|
|
631
|
newwd = self.originalwd
|
|
632
|
if newwd not in [c.rev() for c in repo[None].parents()]:
|
|
632
|
if newwd not in [c.rev() for c in repo[None].parents()]:
|
|
633
|
ui.note(_("update back to initial working directory parent\n"))
|
|
633
|
ui.note(_("update back to initial working directory parent\n"))
|
|
634
|
hg.updaterepo(repo, newwd, overwrite=False)
|
|
634
|
hg.updaterepo(repo, newwd, overwrite=False)
|
|
635
|
|
|
635
|
|
|
636
|
collapsedas = None
|
|
636
|
collapsedas = None
|
|
637
|
if self.collapsef and not self.keepf:
|
|
637
|
if self.collapsef and not self.keepf:
|
|
638
|
collapsedas = newnode
|
|
638
|
collapsedas = newnode
|
|
639
|
clearrebased(ui, repo, self.destmap, self.state, self.skipped,
|
|
639
|
clearrebased(ui, repo, self.destmap, self.state, self.skipped,
|
|
640
|
collapsedas, self.keepf, fm=fm, backup=self.backupf)
|
|
640
|
collapsedas, self.keepf, fm=fm, backup=self.backupf)
|
|
641
|
|
|
641
|
|
|
642
|
clearstatus(repo)
|
|
642
|
clearstatus(repo)
|
|
643
|
clearcollapsemsg(repo)
|
|
643
|
clearcollapsemsg(repo)
|
|
644
|
|
|
644
|
|
|
645
|
ui.note(_("rebase completed\n"))
|
|
645
|
ui.note(_("rebase completed\n"))
|
|
646
|
util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
|
|
646
|
util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
|
|
647
|
if self.skipped:
|
|
647
|
if self.skipped:
|
|
648
|
skippedlen = len(self.skipped)
|
|
648
|
skippedlen = len(self.skipped)
|
|
649
|
ui.note(_("%d revisions have been skipped\n") % skippedlen)
|
|
649
|
ui.note(_("%d revisions have been skipped\n") % skippedlen)
|
|
650
|
fm.end()
|
|
650
|
fm.end()
|
|
651
|
|
|
651
|
|
|
652
|
if (self.activebookmark and self.activebookmark in repo._bookmarks and
|
|
652
|
if (self.activebookmark and self.activebookmark in repo._bookmarks and
|
|
653
|
repo['.'].node() == repo._bookmarks[self.activebookmark]):
|
|
653
|
repo['.'].node() == repo._bookmarks[self.activebookmark]):
|
|
654
|
bookmarks.activate(repo, self.activebookmark)
|
|
654
|
bookmarks.activate(repo, self.activebookmark)
|
|
655
|
|
|
655
|
|
|
656
|
def _abort(self, backup=True, suppwarns=False):
|
|
656
|
def _abort(self, backup=True, suppwarns=False):
|
|
657
|
'''Restore the repository to its original state.'''
|
|
657
|
'''Restore the repository to its original state.'''
|
|
658
|
|
|
658
|
|
|
659
|
repo = self.repo
|
|
659
|
repo = self.repo
|
|
660
|
try:
|
|
660
|
try:
|
|
661
|
# If the first commits in the rebased set get skipped during the
|
|
661
|
# If the first commits in the rebased set get skipped during the
|
|
662
|
# rebase, their values within the state mapping will be the dest
|
|
662
|
# rebase, their values within the state mapping will be the dest
|
|
663
|
# rev id. The rebased list must must not contain the dest rev
|
|
663
|
# rev id. The rebased list must must not contain the dest rev
|
|
664
|
# (issue4896)
|
|
664
|
# (issue4896)
|
|
665
|
rebased = [s for r, s in self.state.items()
|
|
665
|
rebased = [s for r, s in self.state.items()
|
|
666
|
if s >= 0 and s != r and s != self.destmap[r]]
|
|
666
|
if s >= 0 and s != r and s != self.destmap[r]]
|
|
667
|
immutable = [d for d in rebased if not repo[d].mutable()]
|
|
667
|
immutable = [d for d in rebased if not repo[d].mutable()]
|
|
668
|
cleanup = True
|
|
668
|
cleanup = True
|
|
669
|
if immutable:
|
|
669
|
if immutable:
|
|
670
|
repo.ui.warn(_("warning: can't clean up public changesets %s\n")
|
|
670
|
repo.ui.warn(_("warning: can't clean up public changesets %s\n")
|
|
671
|
% ', '.join(bytes(repo[r]) for r in immutable),
|
|
671
|
% ', '.join(bytes(repo[r]) for r in immutable),
|
|
672
|
hint=_("see 'hg help phases' for details"))
|
|
672
|
hint=_("see 'hg help phases' for details"))
|
|
673
|
cleanup = False
|
|
673
|
cleanup = False
|
|
674
|
|
|
674
|
|
|
675
|
descendants = set()
|
|
675
|
descendants = set()
|
|
676
|
if rebased:
|
|
676
|
if rebased:
|
|
677
|
descendants = set(repo.changelog.descendants(rebased))
|
|
677
|
descendants = set(repo.changelog.descendants(rebased))
|
|
678
|
if descendants - set(rebased):
|
|
678
|
if descendants - set(rebased):
|
|
679
|
repo.ui.warn(_("warning: new changesets detected on "
|
|
679
|
repo.ui.warn(_("warning: new changesets detected on "
|
|
680
|
"destination branch, can't strip\n"))
|
|
680
|
"destination branch, can't strip\n"))
|
|
681
|
cleanup = False
|
|
681
|
cleanup = False
|
|
682
|
|
|
682
|
|
|
683
|
if cleanup:
|
|
683
|
if cleanup:
|
|
684
|
shouldupdate = False
|
|
684
|
shouldupdate = False
|
|
685
|
if rebased:
|
|
685
|
if rebased:
|
|
686
|
strippoints = [
|
|
686
|
strippoints = [
|
|
687
|
c.node() for c in repo.set('roots(%ld)', rebased)]
|
|
687
|
c.node() for c in repo.set('roots(%ld)', rebased)]
|
|
688
|
|
|
688
|
|
|
689
|
updateifonnodes = set(rebased)
|
|
689
|
updateifonnodes = set(rebased)
|
|
690
|
updateifonnodes.update(self.destmap.values())
|
|
690
|
updateifonnodes.update(self.destmap.values())
|
|
691
|
updateifonnodes.add(self.originalwd)
|
|
691
|
updateifonnodes.add(self.originalwd)
|
|
692
|
shouldupdate = repo['.'].rev() in updateifonnodes
|
|
692
|
shouldupdate = repo['.'].rev() in updateifonnodes
|
|
693
|
|
|
693
|
|
|
694
|
# Update away from the rebase if necessary
|
|
694
|
# Update away from the rebase if necessary
|
|
695
|
if shouldupdate or needupdate(repo, self.state):
|
|
695
|
if shouldupdate or needupdate(repo, self.state):
|
|
696
|
mergemod.update(repo, self.originalwd, branchmerge=False,
|
|
696
|
mergemod.update(repo, self.originalwd, branchmerge=False,
|
|
697
|
force=True)
|
|
697
|
force=True)
|
|
698
|
|
|
698
|
|
|
699
|
# Strip from the first rebased revision
|
|
699
|
# Strip from the first rebased revision
|
|
700
|
if rebased:
|
|
700
|
if rebased:
|
|
701
|
repair.strip(repo.ui, repo, strippoints, backup=backup)
|
|
701
|
repair.strip(repo.ui, repo, strippoints, backup=backup)
|
|
702
|
|
|
702
|
|
|
703
|
if self.activebookmark and self.activebookmark in repo._bookmarks:
|
|
703
|
if self.activebookmark and self.activebookmark in repo._bookmarks:
|
|
704
|
bookmarks.activate(repo, self.activebookmark)
|
|
704
|
bookmarks.activate(repo, self.activebookmark)
|
|
705
|
|
|
705
|
|
|
706
|
finally:
|
|
706
|
finally:
|
|
707
|
clearstatus(repo)
|
|
707
|
clearstatus(repo)
|
|
708
|
clearcollapsemsg(repo)
|
|
708
|
clearcollapsemsg(repo)
|
|
709
|
if not suppwarns:
|
|
709
|
if not suppwarns:
|
|
710
|
repo.ui.warn(_('rebase aborted\n'))
|
|
710
|
repo.ui.warn(_('rebase aborted\n'))
|
|
711
|
return 0
|
|
711
|
return 0
|
|
712
|
|
|
712
|
|
|
713
|
@command('rebase',
|
|
713
|
@command('rebase',
|
|
714
|
[('s', 'source', '',
|
|
714
|
[('s', 'source', '',
|
|
715
|
_('rebase the specified changeset and descendants'), _('REV')),
|
|
715
|
_('rebase the specified changeset and descendants'), _('REV')),
|
|
716
|
('b', 'base', '',
|
|
716
|
('b', 'base', '',
|
|
717
|
_('rebase everything from branching point of specified changeset'),
|
|
717
|
_('rebase everything from branching point of specified changeset'),
|
|
718
|
_('REV')),
|
|
718
|
_('REV')),
|
|
719
|
('r', 'rev', [],
|
|
719
|
('r', 'rev', [],
|
|
720
|
_('rebase these revisions'),
|
|
720
|
_('rebase these revisions'),
|
|
721
|
_('REV')),
|
|
721
|
_('REV')),
|
|
722
|
('d', 'dest', '',
|
|
722
|
('d', 'dest', '',
|
|
723
|
_('rebase onto the specified changeset'), _('REV')),
|
|
723
|
_('rebase onto the specified changeset'), _('REV')),
|
|
724
|
('', 'collapse', False, _('collapse the rebased changesets')),
|
|
724
|
('', 'collapse', False, _('collapse the rebased changesets')),
|
|
725
|
('m', 'message', '',
|
|
725
|
('m', 'message', '',
|
|
726
|
_('use text as collapse commit message'), _('TEXT')),
|
|
726
|
_('use text as collapse commit message'), _('TEXT')),
|
|
727
|
('e', 'edit', False, _('invoke editor on commit messages')),
|
|
727
|
('e', 'edit', False, _('invoke editor on commit messages')),
|
|
728
|
('l', 'logfile', '',
|
|
728
|
('l', 'logfile', '',
|
|
729
|
_('read collapse commit message from file'), _('FILE')),
|
|
729
|
_('read collapse commit message from file'), _('FILE')),
|
|
730
|
('k', 'keep', False, _('keep original changesets')),
|
|
730
|
('k', 'keep', False, _('keep original changesets')),
|
|
731
|
('', 'keepbranches', False, _('keep original branch names')),
|
|
731
|
('', 'keepbranches', False, _('keep original branch names')),
|
|
732
|
('D', 'detach', False, _('(DEPRECATED)')),
|
|
732
|
('D', 'detach', False, _('(DEPRECATED)')),
|
|
733
|
('i', 'interactive', False, _('(DEPRECATED)')),
|
|
733
|
('i', 'interactive', False, _('(DEPRECATED)')),
|
|
734
|
('t', 'tool', '', _('specify merge tool')),
|
|
734
|
('t', 'tool', '', _('specify merge tool')),
|
|
735
|
('', 'stop', False, _('stop interrupted rebase')),
|
|
735
|
('', 'stop', False, _('stop interrupted rebase')),
|
|
736
|
('c', 'continue', False, _('continue an interrupted rebase')),
|
|
736
|
('c', 'continue', False, _('continue an interrupted rebase')),
|
|
737
|
('a', 'abort', False, _('abort an interrupted rebase')),
|
|
737
|
('a', 'abort', False, _('abort an interrupted rebase')),
|
|
738
|
('', 'auto-orphans', '', _('automatically rebase orphan revisions '
|
|
738
|
('', 'auto-orphans', '', _('automatically rebase orphan revisions '
|
|
739
|
'in the specified revset (EXPERIMENTAL)')),
|
|
739
|
'in the specified revset (EXPERIMENTAL)')),
|
|
740
|
] + cmdutil.dryrunopts + cmdutil.formatteropts + cmdutil.confirmopts,
|
|
740
|
] + cmdutil.dryrunopts + cmdutil.formatteropts + cmdutil.confirmopts,
|
|
741
|
_('[-s REV | -b REV] [-d REV] [OPTION]'),
|
|
741
|
_('[-s REV | -b REV] [-d REV] [OPTION]'),
|
|
742
|
helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
|
|
742
|
helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
|
|
743
|
def rebase(ui, repo, **opts):
|
|
743
|
def rebase(ui, repo, **opts):
|
|
744
|
"""move changeset (and descendants) to a different branch
|
|
744
|
"""move changeset (and descendants) to a different branch
|
|
745
|
|
|
745
|
|
|
746
|
Rebase uses repeated merging to graft changesets from one part of
|
|
746
|
Rebase uses repeated merging to graft changesets from one part of
|
|
747
|
history (the source) onto another (the destination). This can be
|
|
747
|
history (the source) onto another (the destination). This can be
|
|
748
|
useful for linearizing *local* changes relative to a master
|
|
748
|
useful for linearizing *local* changes relative to a master
|
|
749
|
development tree.
|
|
749
|
development tree.
|
|
750
|
|
|
750
|
|
|
751
|
Published commits cannot be rebased (see :hg:`help phases`).
|
|
751
|
Published commits cannot be rebased (see :hg:`help phases`).
|
|
752
|
To copy commits, see :hg:`help graft`.
|
|
752
|
To copy commits, see :hg:`help graft`.
|
|
753
|
|
|
753
|
|
|
754
|
If you don't specify a destination changeset (``-d/--dest``), rebase
|
|
754
|
If you don't specify a destination changeset (``-d/--dest``), rebase
|
|
755
|
will use the same logic as :hg:`merge` to pick a destination. if
|
|
755
|
will use the same logic as :hg:`merge` to pick a destination. if
|
|
756
|
the current branch contains exactly one other head, the other head
|
|
756
|
the current branch contains exactly one other head, the other head
|
|
757
|
is merged with by default. Otherwise, an explicit revision with
|
|
757
|
is merged with by default. Otherwise, an explicit revision with
|
|
758
|
which to merge with must be provided. (destination changeset is not
|
|
758
|
which to merge with must be provided. (destination changeset is not
|
|
759
|
modified by rebasing, but new changesets are added as its
|
|
759
|
modified by rebasing, but new changesets are added as its
|
|
760
|
descendants.)
|
|
760
|
descendants.)
|
|
761
|
|
|
761
|
|
|
762
|
Here are the ways to select changesets:
|
|
762
|
Here are the ways to select changesets:
|
|
763
|
|
|
763
|
|
|
764
|
1. Explicitly select them using ``--rev``.
|
|
764
|
1. Explicitly select them using ``--rev``.
|
|
765
|
|
|
765
|
|
|
766
|
2. Use ``--source`` to select a root changeset and include all of its
|
|
766
|
2. Use ``--source`` to select a root changeset and include all of its
|
|
767
|
descendants.
|
|
767
|
descendants.
|
|
768
|
|
|
768
|
|
|
769
|
3. Use ``--base`` to select a changeset; rebase will find ancestors
|
|
769
|
3. Use ``--base`` to select a changeset; rebase will find ancestors
|
|
770
|
and their descendants which are not also ancestors of the destination.
|
|
770
|
and their descendants which are not also ancestors of the destination.
|
|
771
|
|
|
771
|
|
|
772
|
4. If you do not specify any of ``--rev``, ``--source``, or ``--base``,
|
|
772
|
4. If you do not specify any of ``--rev``, ``--source``, or ``--base``,
|
|
773
|
rebase will use ``--base .`` as above.
|
|
773
|
rebase will use ``--base .`` as above.
|
|
774
|
|
|
774
|
|
|
775
|
If ``--source`` or ``--rev`` is used, special names ``SRC`` and ``ALLSRC``
|
|
775
|
If ``--source`` or ``--rev`` is used, special names ``SRC`` and ``ALLSRC``
|
|
776
|
can be used in ``--dest``. Destination would be calculated per source
|
|
776
|
can be used in ``--dest``. Destination would be calculated per source
|
|
777
|
revision with ``SRC`` substituted by that single source revision and
|
|
777
|
revision with ``SRC`` substituted by that single source revision and
|
|
778
|
``ALLSRC`` substituted by all source revisions.
|
|
778
|
``ALLSRC`` substituted by all source revisions.
|
|
779
|
|
|
779
|
|
|
780
|
Rebase will destroy original changesets unless you use ``--keep``.
|
|
780
|
Rebase will destroy original changesets unless you use ``--keep``.
|
|
781
|
It will also move your bookmarks (even if you do).
|
|
781
|
It will also move your bookmarks (even if you do).
|
|
782
|
|
|
782
|
|
|
783
|
Some changesets may be dropped if they do not contribute changes
|
|
783
|
Some changesets may be dropped if they do not contribute changes
|
|
784
|
(e.g. merges from the destination branch).
|
|
784
|
(e.g. merges from the destination branch).
|
|
785
|
|
|
785
|
|
|
786
|
Unlike ``merge``, rebase will do nothing if you are at the branch tip of
|
|
786
|
Unlike ``merge``, rebase will do nothing if you are at the branch tip of
|
|
787
|
a named branch with two heads. You will need to explicitly specify source
|
|
787
|
a named branch with two heads. You will need to explicitly specify source
|
|
788
|
and/or destination.
|
|
788
|
and/or destination.
|
|
789
|
|
|
789
|
|
|
790
|
If you need to use a tool to automate merge/conflict decisions, you
|
|
790
|
If you need to use a tool to automate merge/conflict decisions, you
|
|
791
|
can specify one with ``--tool``, see :hg:`help merge-tools`.
|
|
791
|
can specify one with ``--tool``, see :hg:`help merge-tools`.
|
|
792
|
As a caveat: the tool will not be used to mediate when a file was
|
|
792
|
As a caveat: the tool will not be used to mediate when a file was
|
|
793
|
deleted, there is no hook presently available for this.
|
|
793
|
deleted, there is no hook presently available for this.
|
|
794
|
|
|
794
|
|
|
795
|
If a rebase is interrupted to manually resolve a conflict, it can be
|
|
795
|
If a rebase is interrupted to manually resolve a conflict, it can be
|
|
796
|
continued with --continue/-c, aborted with --abort/-a, or stopped with
|
|
796
|
continued with --continue/-c, aborted with --abort/-a, or stopped with
|
|
797
|
--stop.
|
|
797
|
--stop.
|
|
798
|
|
|
798
|
|
|
799
|
.. container:: verbose
|
|
799
|
.. container:: verbose
|
|
800
|
|
|
800
|
|
|
801
|
Examples:
|
|
801
|
Examples:
|
|
802
|
|
|
802
|
|
|
803
|
- move "local changes" (current commit back to branching point)
|
|
803
|
- move "local changes" (current commit back to branching point)
|
|
804
|
to the current branch tip after a pull::
|
|
804
|
to the current branch tip after a pull::
|
|
805
|
|
|
805
|
|
|
806
|
hg rebase
|
|
806
|
hg rebase
|
|
807
|
|
|
807
|
|
|
808
|
- move a single changeset to the stable branch::
|
|
808
|
- move a single changeset to the stable branch::
|
|
809
|
|
|
809
|
|
|
810
|
hg rebase -r 5f493448 -d stable
|
|
810
|
hg rebase -r 5f493448 -d stable
|
|
811
|
|
|
811
|
|
|
812
|
- splice a commit and all its descendants onto another part of history::
|
|
812
|
- splice a commit and all its descendants onto another part of history::
|
|
813
|
|
|
813
|
|
|
814
|
hg rebase --source c0c3 --dest 4cf9
|
|
814
|
hg rebase --source c0c3 --dest 4cf9
|
|
815
|
|
|
815
|
|
|
816
|
- rebase everything on a branch marked by a bookmark onto the
|
|
816
|
- rebase everything on a branch marked by a bookmark onto the
|
|
817
|
default branch::
|
|
817
|
default branch::
|
|
818
|
|
|
818
|
|
|
819
|
hg rebase --base myfeature --dest default
|
|
819
|
hg rebase --base myfeature --dest default
|
|
820
|
|
|
820
|
|
|
821
|
- collapse a sequence of changes into a single commit::
|
|
821
|
- collapse a sequence of changes into a single commit::
|
|
822
|
|
|
822
|
|
|
823
|
hg rebase --collapse -r 1520:1525 -d .
|
|
823
|
hg rebase --collapse -r 1520:1525 -d .
|
|
824
|
|
|
824
|
|
|
825
|
- move a named branch while preserving its name::
|
|
825
|
- move a named branch while preserving its name::
|
|
826
|
|
|
826
|
|
|
827
|
hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
|
|
827
|
hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
|
|
828
|
|
|
828
|
|
|
829
|
- stabilize orphaned changesets so history looks linear::
|
|
829
|
- stabilize orphaned changesets so history looks linear::
|
|
830
|
|
|
830
|
|
|
831
|
hg rebase -r 'orphan()-obsolete()'\
|
|
831
|
hg rebase -r 'orphan()-obsolete()'\
|
|
832
|
-d 'first(max((successors(max(roots(ALLSRC) & ::SRC)^)-obsolete())::) +\
|
|
832
|
-d 'first(max((successors(max(roots(ALLSRC) & ::SRC)^)-obsolete())::) +\
|
|
833
|
max(::((roots(ALLSRC) & ::SRC)^)-obsolete()))'
|
|
833
|
max(::((roots(ALLSRC) & ::SRC)^)-obsolete()))'
|
|
834
|
|
|
834
|
|
|
835
|
Configuration Options:
|
|
835
|
Configuration Options:
|
|
836
|
|
|
836
|
|
|
837
|
You can make rebase require a destination if you set the following config
|
|
837
|
You can make rebase require a destination if you set the following config
|
|
838
|
option::
|
|
838
|
option::
|
|
839
|
|
|
839
|
|
|
840
|
[commands]
|
|
840
|
[commands]
|
|
841
|
rebase.requiredest = True
|
|
841
|
rebase.requiredest = True
|
|
842
|
|
|
842
|
|
|
843
|
By default, rebase will close the transaction after each commit. For
|
|
843
|
By default, rebase will close the transaction after each commit. For
|
|
844
|
performance purposes, you can configure rebase to use a single transaction
|
|
844
|
performance purposes, you can configure rebase to use a single transaction
|
|
845
|
across the entire rebase. WARNING: This setting introduces a significant
|
|
845
|
across the entire rebase. WARNING: This setting introduces a significant
|
|
846
|
risk of losing the work you've done in a rebase if the rebase aborts
|
|
846
|
risk of losing the work you've done in a rebase if the rebase aborts
|
|
847
|
unexpectedly::
|
|
847
|
unexpectedly::
|
|
848
|
|
|
848
|
|
|
849
|
[rebase]
|
|
849
|
[rebase]
|
|
850
|
singletransaction = True
|
|
850
|
singletransaction = True
|
|
851
|
|
|
851
|
|
|
852
|
By default, rebase writes to the working copy, but you can configure it to
|
|
852
|
By default, rebase writes to the working copy, but you can configure it to
|
|
853
|
run in-memory for better performance. When the rebase is not moving the
|
|
853
|
run in-memory for better performance. When the rebase is not moving the
|
|
854
|
parent(s) of the working copy (AKA the "currently checked out changesets"),
|
|
854
|
parent(s) of the working copy (AKA the "currently checked out changesets"),
|
|
855
|
this may also allow it to run even if the working copy is dirty::
|
|
855
|
this may also allow it to run even if the working copy is dirty::
|
|
856
|
|
|
856
|
|
|
857
|
[rebase]
|
|
857
|
[rebase]
|
|
858
|
experimental.inmemory = True
|
|
858
|
experimental.inmemory = True
|
|
859
|
|
|
859
|
|
|
860
|
Return Values:
|
|
860
|
Return Values:
|
|
861
|
|
|
861
|
|
|
862
|
Returns 0 on success, 1 if nothing to rebase or there are
|
|
862
|
Returns 0 on success, 1 if nothing to rebase or there are
|
|
863
|
unresolved conflicts.
|
|
863
|
unresolved conflicts.
|
|
864
|
|
|
864
|
|
|
865
|
"""
|
|
865
|
"""
|
|
866
|
opts = pycompat.byteskwargs(opts)
|
|
866
|
opts = pycompat.byteskwargs(opts)
|
|
867
|
inmemory = ui.configbool('rebase', 'experimental.inmemory')
|
|
867
|
inmemory = ui.configbool('rebase', 'experimental.inmemory')
|
|
868
|
dryrun = opts.get('dry_run')
|
|
868
|
dryrun = opts.get('dry_run')
|
|
869
|
confirm = opts.get('confirm')
|
|
869
|
confirm = opts.get('confirm')
|
|
870
|
selactions = [k for k in ['abort', 'stop', 'continue'] if opts.get(k)]
|
|
870
|
selactions = [k for k in ['abort', 'stop', 'continue'] if opts.get(k)]
|
|
871
|
if len(selactions) > 1:
|
|
871
|
if len(selactions) > 1:
|
|
872
|
raise error.Abort(_('cannot use --%s with --%s')
|
|
872
|
raise error.Abort(_('cannot use --%s with --%s')
|
|
873
|
% tuple(selactions[:2]))
|
|
873
|
% tuple(selactions[:2]))
|
|
874
|
action = selactions[0] if selactions else None
|
|
874
|
action = selactions[0] if selactions else None
|
|
875
|
if dryrun and action:
|
|
875
|
if dryrun and action:
|
|
876
|
raise error.Abort(_('cannot specify both --dry-run and --%s') % action)
|
|
876
|
raise error.Abort(_('cannot specify both --dry-run and --%s') % action)
|
|
877
|
if confirm and action:
|
|
877
|
if confirm and action:
|
|
878
|
raise error.Abort(_('cannot specify both --confirm and --%s') % action)
|
|
878
|
raise error.Abort(_('cannot specify both --confirm and --%s') % action)
|
|
879
|
if dryrun and confirm:
|
|
879
|
if dryrun and confirm:
|
|
880
|
raise error.Abort(_('cannot specify both --confirm and --dry-run'))
|
|
880
|
raise error.Abort(_('cannot specify both --confirm and --dry-run'))
|
|
881
|
|
|
881
|
|
|
882
|
if action or repo.currenttransaction() is not None:
|
|
882
|
if action or repo.currenttransaction() is not None:
|
|
883
|
# in-memory rebase is not compatible with resuming rebases.
|
|
883
|
# in-memory rebase is not compatible with resuming rebases.
|
|
884
|
# (Or if it is run within a transaction, since the restart logic can
|
|
884
|
# (Or if it is run within a transaction, since the restart logic can
|
|
885
|
# fail the entire transaction.)
|
|
885
|
# fail the entire transaction.)
|
|
886
|
inmemory = False
|
|
886
|
inmemory = False
|
|
887
|
|
|
887
|
|
|
888
|
if opts.get('auto_orphans'):
|
|
888
|
if opts.get('auto_orphans'):
|
|
889
|
for key in opts:
|
|
889
|
for key in opts:
|
|
890
|
if key != 'auto_orphans' and opts.get(key):
|
|
890
|
if key != 'auto_orphans' and opts.get(key):
|
|
891
|
raise error.Abort(_('--auto-orphans is incompatible with %s') %
|
|
891
|
raise error.Abort(_('--auto-orphans is incompatible with %s') %
|
|
892
|
('--' + key))
|
|
892
|
('--' + key))
|
|
893
|
userrevs = list(repo.revs(opts.get('auto_orphans')))
|
|
893
|
userrevs = list(repo.revs(opts.get('auto_orphans')))
|
|
894
|
opts['rev'] = [revsetlang.formatspec('%ld and orphan()', userrevs)]
|
|
894
|
opts['rev'] = [revsetlang.formatspec('%ld and orphan()', userrevs)]
|
|
895
|
opts['dest'] = '_destautoorphanrebase(SRC)'
|
|
895
|
opts['dest'] = '_destautoorphanrebase(SRC)'
|
|
896
|
|
|
896
|
|
|
897
|
if dryrun or confirm:
|
|
897
|
if dryrun or confirm:
|
|
898
|
return _dryrunrebase(ui, repo, action, opts)
|
|
898
|
return _dryrunrebase(ui, repo, action, opts)
|
|
899
|
elif action == 'stop':
|
|
899
|
elif action == 'stop':
|
|
900
|
rbsrt = rebaseruntime(repo, ui)
|
|
900
|
rbsrt = rebaseruntime(repo, ui)
|
|
901
|
with repo.wlock(), repo.lock():
|
|
901
|
with repo.wlock(), repo.lock():
|
|
902
|
rbsrt.restorestatus()
|
|
902
|
rbsrt.restorestatus()
|
|
903
|
if rbsrt.collapsef:
|
|
903
|
if rbsrt.collapsef:
|
|
904
|
raise error.Abort(_("cannot stop in --collapse session"))
|
|
904
|
raise error.Abort(_("cannot stop in --collapse session"))
|
|
905
|
allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
|
|
905
|
allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
|
|
906
|
if not (rbsrt.keepf or allowunstable):
|
|
906
|
if not (rbsrt.keepf or allowunstable):
|
|
907
|
raise error.Abort(_("cannot remove original changesets with"
|
|
907
|
raise error.Abort(_("cannot remove original changesets with"
|
|
908
|
" unrebased descendants"),
|
|
908
|
" unrebased descendants"),
|
|
909
|
hint=_('either enable obsmarkers to allow unstable '
|
|
909
|
hint=_('either enable obsmarkers to allow unstable '
|
|
910
|
'revisions or use --keep to keep original '
|
|
910
|
'revisions or use --keep to keep original '
|
|
911
|
'changesets'))
|
|
911
|
'changesets'))
|
|
912
|
if needupdate(repo, rbsrt.state):
|
|
912
|
if needupdate(repo, rbsrt.state):
|
|
913
|
# update to the current working revision
|
|
913
|
# update to the current working revision
|
|
914
|
# to clear interrupted merge
|
|
914
|
# to clear interrupted merge
|
|
915
|
hg.updaterepo(repo, rbsrt.originalwd, overwrite=True)
|
|
915
|
hg.updaterepo(repo, rbsrt.originalwd, overwrite=True)
|
|
916
|
rbsrt._finishrebase()
|
|
916
|
rbsrt._finishrebase()
|
|
917
|
return 0
|
|
917
|
return 0
|
|
918
|
elif inmemory:
|
|
918
|
elif inmemory:
|
|
919
|
try:
|
|
919
|
try:
|
|
920
|
# in-memory merge doesn't support conflicts, so if we hit any, abort
|
|
920
|
# in-memory merge doesn't support conflicts, so if we hit any, abort
|
|
921
|
# and re-run as an on-disk merge.
|
|
921
|
# and re-run as an on-disk merge.
|
|
922
|
overrides = {('rebase', 'singletransaction'): True}
|
|
922
|
overrides = {('rebase', 'singletransaction'): True}
|
|
923
|
with ui.configoverride(overrides, 'rebase'):
|
|
923
|
with ui.configoverride(overrides, 'rebase'):
|
|
924
|
return _dorebase(ui, repo, action, opts, inmemory=inmemory)
|
|
924
|
return _dorebase(ui, repo, action, opts, inmemory=inmemory)
|
|
925
|
except error.InMemoryMergeConflictsError:
|
|
925
|
except error.InMemoryMergeConflictsError:
|
|
926
|
ui.warn(_('hit merge conflicts; re-running rebase without in-memory'
|
|
926
|
ui.warn(_('hit merge conflicts; re-running rebase without in-memory'
|
|
927
|
' merge\n'))
|
|
927
|
' merge\n'))
|
|
928
|
# TODO: Make in-memory merge not use the on-disk merge state, so
|
|
928
|
# TODO: Make in-memory merge not use the on-disk merge state, so
|
|
929
|
# we don't have to clean it here
|
|
929
|
# we don't have to clean it here
|
|
930
|
mergemod.mergestate.clean(repo)
|
|
930
|
mergemod.mergestate.clean(repo)
|
|
931
|
clearstatus(repo)
|
|
931
|
clearstatus(repo)
|
|
932
|
clearcollapsemsg(repo)
|
|
932
|
clearcollapsemsg(repo)
|
|
933
|
return _dorebase(ui, repo, action, opts, inmemory=False)
|
|
933
|
return _dorebase(ui, repo, action, opts, inmemory=False)
|
|
934
|
else:
|
|
934
|
else:
|
|
935
|
return _dorebase(ui, repo, action, opts)
|
|
935
|
return _dorebase(ui, repo, action, opts)
|
|
936
|
|
|
936
|
|
|
937
|
def _dryrunrebase(ui, repo, action, opts):
|
|
937
|
def _dryrunrebase(ui, repo, action, opts):
|
|
938
|
rbsrt = rebaseruntime(repo, ui, inmemory=True, opts=opts)
|
|
938
|
rbsrt = rebaseruntime(repo, ui, inmemory=True, opts=opts)
|
|
939
|
confirm = opts.get('confirm')
|
|
939
|
confirm = opts.get('confirm')
|
|
940
|
if confirm:
|
|
940
|
if confirm:
|
|
941
|
ui.status(_('starting in-memory rebase\n'))
|
|
941
|
ui.status(_('starting in-memory rebase\n'))
|
|
942
|
else:
|
|
942
|
else:
|
|
943
|
ui.status(_('starting dry-run rebase; repository will not be '
|
|
943
|
ui.status(_('starting dry-run rebase; repository will not be '
|
|
944
|
'changed\n'))
|
|
944
|
'changed\n'))
|
|
945
|
with repo.wlock(), repo.lock():
|
|
945
|
with repo.wlock(), repo.lock():
|
|
946
|
needsabort = True
|
|
946
|
needsabort = True
|
|
947
|
try:
|
|
947
|
try:
|
|
948
|
overrides = {('rebase', 'singletransaction'): True}
|
|
948
|
overrides = {('rebase', 'singletransaction'): True}
|
|
949
|
with ui.configoverride(overrides, 'rebase'):
|
|
949
|
with ui.configoverride(overrides, 'rebase'):
|
|
950
|
_origrebase(ui, repo, action, opts, rbsrt, inmemory=True,
|
|
950
|
_origrebase(ui, repo, action, opts, rbsrt, inmemory=True,
|
|
951
|
leaveunfinished=True)
|
|
951
|
leaveunfinished=True)
|
|
952
|
except error.InMemoryMergeConflictsError:
|
|
952
|
except error.InMemoryMergeConflictsError:
|
|
953
|
ui.status(_('hit a merge conflict\n'))
|
|
953
|
ui.status(_('hit a merge conflict\n'))
|
|
954
|
return 1
|
|
954
|
return 1
|
|
955
|
except error.Abort:
|
|
955
|
except error.Abort:
|
|
956
|
needsabort = False
|
|
956
|
needsabort = False
|
|
957
|
raise
|
|
957
|
raise
|
|
958
|
else:
|
|
958
|
else:
|
|
959
|
if confirm:
|
|
959
|
if confirm:
|
|
960
|
ui.status(_('rebase completed successfully\n'))
|
|
960
|
ui.status(_('rebase completed successfully\n'))
|
|
961
|
if not ui.promptchoice(_(b'apply changes (yn)?'
|
|
961
|
if not ui.promptchoice(_(b'apply changes (yn)?'
|
|
962
|
b'$$ &Yes $$ &No')):
|
|
962
|
b'$$ &Yes $$ &No')):
|
|
963
|
# finish unfinished rebase
|
|
963
|
# finish unfinished rebase
|
|
964
|
rbsrt._finishrebase()
|
|
964
|
rbsrt._finishrebase()
|
|
965
|
else:
|
|
965
|
else:
|
|
966
|
rbsrt._prepareabortorcontinue(isabort=True, backup=False,
|
|
966
|
rbsrt._prepareabortorcontinue(isabort=True, backup=False,
|
|
967
|
suppwarns=True)
|
|
967
|
suppwarns=True)
|
|
968
|
needsabort = False
|
|
968
|
needsabort = False
|
|
969
|
else:
|
|
969
|
else:
|
|
970
|
ui.status(_('dry-run rebase completed successfully; run without'
|
|
970
|
ui.status(_('dry-run rebase completed successfully; run without'
|
|
971
|
' -n/--dry-run to perform this rebase\n'))
|
|
971
|
' -n/--dry-run to perform this rebase\n'))
|
|
972
|
return 0
|
|
972
|
return 0
|
|
973
|
finally:
|
|
973
|
finally:
|
|
974
|
if needsabort:
|
|
974
|
if needsabort:
|
|
975
|
# no need to store backup in case of dryrun
|
|
975
|
# no need to store backup in case of dryrun
|
|
976
|
rbsrt._prepareabortorcontinue(isabort=True, backup=False,
|
|
976
|
rbsrt._prepareabortorcontinue(isabort=True, backup=False,
|
|
977
|
suppwarns=True)
|
|
977
|
suppwarns=True)
|
|
978
|
|
|
978
|
|
|
979
|
def _dorebase(ui, repo, action, opts, inmemory=False):
|
|
979
|
def _dorebase(ui, repo, action, opts, inmemory=False):
|
|
980
|
rbsrt = rebaseruntime(repo, ui, inmemory, opts)
|
|
980
|
rbsrt = rebaseruntime(repo, ui, inmemory, opts)
|
|
981
|
return _origrebase(ui, repo, action, opts, rbsrt, inmemory=inmemory)
|
|
981
|
return _origrebase(ui, repo, action, opts, rbsrt, inmemory=inmemory)
|
|
982
|
|
|
982
|
|
|
983
|
def _origrebase(ui, repo, action, opts, rbsrt, inmemory=False,
|
|
983
|
def _origrebase(ui, repo, action, opts, rbsrt, inmemory=False,
|
|
984
|
leaveunfinished=False):
|
|
984
|
leaveunfinished=False):
|
|
985
|
assert action != 'stop'
|
|
985
|
assert action != 'stop'
|
|
986
|
with repo.wlock(), repo.lock():
|
|
986
|
with repo.wlock(), repo.lock():
|
|
987
|
# Validate input and define rebasing points
|
|
987
|
# Validate input and define rebasing points
|
|
988
|
destf = opts.get('dest', None)
|
|
988
|
destf = opts.get('dest', None)
|
|
989
|
srcf = opts.get('source', None)
|
|
989
|
srcf = opts.get('source', None)
|
|
990
|
basef = opts.get('base', None)
|
|
990
|
basef = opts.get('base', None)
|
|
991
|
revf = opts.get('rev', [])
|
|
991
|
revf = opts.get('rev', [])
|
|
992
|
# search default destination in this space
|
|
992
|
# search default destination in this space
|
|
993
|
# used in the 'hg pull --rebase' case, see issue 5214.
|
|
993
|
# used in the 'hg pull --rebase' case, see issue 5214.
|
|
994
|
destspace = opts.get('_destspace')
|
|
994
|
destspace = opts.get('_destspace')
|
|
995
|
if opts.get('interactive'):
|
|
995
|
if opts.get('interactive'):
|
|
996
|
try:
|
|
996
|
try:
|
|
997
|
if extensions.find('histedit'):
|
|
997
|
if extensions.find('histedit'):
|
|
998
|
enablehistedit = ''
|
|
998
|
enablehistedit = ''
|
|
999
|
except KeyError:
|
|
999
|
except KeyError:
|
|
1000
|
enablehistedit = " --config extensions.histedit="
|
|
1000
|
enablehistedit = " --config extensions.histedit="
|
|
1001
|
help = "hg%s help -e histedit" % enablehistedit
|
|
1001
|
help = "hg%s help -e histedit" % enablehistedit
|
|
1002
|
msg = _("interactive history editing is supported by the "
|
|
1002
|
msg = _("interactive history editing is supported by the "
|
|
1003
|
"'histedit' extension (see \"%s\")") % help
|
|
1003
|
"'histedit' extension (see \"%s\")") % help
|
|
1004
|
raise error.Abort(msg)
|
|
1004
|
raise error.Abort(msg)
|
|
1005
|
|
|
1005
|
|
|
1006
|
if rbsrt.collapsemsg and not rbsrt.collapsef:
|
|
1006
|
if rbsrt.collapsemsg and not rbsrt.collapsef:
|
|
1007
|
raise error.Abort(
|
|
1007
|
raise error.Abort(
|
|
1008
|
_('message can only be specified with collapse'))
|
|
1008
|
_('message can only be specified with collapse'))
|
|
1009
|
|
|
1009
|
|
|
1010
|
if action:
|
|
1010
|
if action:
|
|
1011
|
if rbsrt.collapsef:
|
|
1011
|
if rbsrt.collapsef:
|
|
1012
|
raise error.Abort(
|
|
1012
|
raise error.Abort(
|
|
1013
|
_('cannot use collapse with continue or abort'))
|
|
1013
|
_('cannot use collapse with continue or abort'))
|
|
1014
|
if srcf or basef or destf:
|
|
1014
|
if srcf or basef or destf:
|
|
1015
|
raise error.Abort(
|
|
1015
|
raise error.Abort(
|
|
1016
|
_('abort and continue do not allow specifying revisions'))
|
|
1016
|
_('abort and continue do not allow specifying revisions'))
|
|
1017
|
if action == 'abort' and opts.get('tool', False):
|
|
1017
|
if action == 'abort' and opts.get('tool', False):
|
|
1018
|
ui.warn(_('tool option will be ignored\n'))
|
|
1018
|
ui.warn(_('tool option will be ignored\n'))
|
|
1019
|
if action == 'continue':
|
|
1019
|
if action == 'continue':
|
|
1020
|
ms = mergemod.mergestate.read(repo)
|
|
1020
|
ms = mergemod.mergestate.read(repo)
|
|
1021
|
mergeutil.checkunresolved(ms)
|
|
1021
|
mergeutil.checkunresolved(ms)
|
|
1022
|
|
|
1022
|
|
|
1023
|
retcode = rbsrt._prepareabortorcontinue(isabort=(action == 'abort'))
|
|
1023
|
retcode = rbsrt._prepareabortorcontinue(isabort=(action == 'abort'))
|
|
1024
|
if retcode is not None:
|
|
1024
|
if retcode is not None:
|
|
1025
|
return retcode
|
|
1025
|
return retcode
|
|
1026
|
else:
|
|
1026
|
else:
|
|
1027
|
destmap = _definedestmap(ui, repo, inmemory, destf, srcf, basef,
|
|
1027
|
destmap = _definedestmap(ui, repo, inmemory, destf, srcf, basef,
|
|
1028
|
revf, destspace=destspace)
|
|
1028
|
revf, destspace=destspace)
|
|
1029
|
retcode = rbsrt._preparenewrebase(destmap)
|
|
1029
|
retcode = rbsrt._preparenewrebase(destmap)
|
|
1030
|
if retcode is not None:
|
|
1030
|
if retcode is not None:
|
|
1031
|
return retcode
|
|
1031
|
return retcode
|
|
1032
|
storecollapsemsg(repo, rbsrt.collapsemsg)
|
|
1032
|
storecollapsemsg(repo, rbsrt.collapsemsg)
|
|
1033
|
|
|
1033
|
|
|
1034
|
tr = None
|
|
1034
|
tr = None
|
|
1035
|
|
|
1035
|
|
|
1036
|
singletr = ui.configbool('rebase', 'singletransaction')
|
|
1036
|
singletr = ui.configbool('rebase', 'singletransaction')
|
|
1037
|
if singletr:
|
|
1037
|
if singletr:
|
|
1038
|
tr = repo.transaction('rebase')
|
|
1038
|
tr = repo.transaction('rebase')
|
|
1039
|
|
|
1039
|
|
|
1040
|
# If `rebase.singletransaction` is enabled, wrap the entire operation in
|
|
1040
|
# If `rebase.singletransaction` is enabled, wrap the entire operation in
|
|
1041
|
# one transaction here. Otherwise, transactions are obtained when
|
|
1041
|
# one transaction here. Otherwise, transactions are obtained when
|
|
1042
|
# committing each node, which is slower but allows partial success.
|
|
1042
|
# committing each node, which is slower but allows partial success.
|
|
1043
|
with util.acceptintervention(tr):
|
|
1043
|
with util.acceptintervention(tr):
|
|
1044
|
# Same logic for the dirstate guard, except we don't create one when
|
|
1044
|
# Same logic for the dirstate guard, except we don't create one when
|
|
1045
|
# rebasing in-memory (it's not needed).
|
|
1045
|
# rebasing in-memory (it's not needed).
|
|
1046
|
dsguard = None
|
|
1046
|
dsguard = None
|
|
1047
|
if singletr and not inmemory:
|
|
1047
|
if singletr and not inmemory:
|
|
1048
|
dsguard = dirstateguard.dirstateguard(repo, 'rebase')
|
|
1048
|
dsguard = dirstateguard.dirstateguard(repo, 'rebase')
|
|
1049
|
with util.acceptintervention(dsguard):
|
|
1049
|
with util.acceptintervention(dsguard):
|
|
1050
|
rbsrt._performrebase(tr)
|
|
1050
|
rbsrt._performrebase(tr)
|
|
1051
|
if not leaveunfinished:
|
|
1051
|
if not leaveunfinished:
|
|
1052
|
rbsrt._finishrebase()
|
|
1052
|
rbsrt._finishrebase()
|
|
1053
|
|
|
1053
|
|
|
1054
|
def _definedestmap(ui, repo, inmemory, destf=None, srcf=None, basef=None,
|
|
1054
|
def _definedestmap(ui, repo, inmemory, destf=None, srcf=None, basef=None,
|
|
1055
|
revf=None, destspace=None):
|
|
1055
|
revf=None, destspace=None):
|
|
1056
|
"""use revisions argument to define destmap {srcrev: destrev}"""
|
|
1056
|
"""use revisions argument to define destmap {srcrev: destrev}"""
|
|
1057
|
if revf is None:
|
|
1057
|
if revf is None:
|
|
1058
|
revf = []
|
|
1058
|
revf = []
|
|
1059
|
|
|
1059
|
|
|
1060
|
# destspace is here to work around issues with `hg pull --rebase` see
|
|
1060
|
# destspace is here to work around issues with `hg pull --rebase` see
|
|
1061
|
# issue5214 for details
|
|
1061
|
# issue5214 for details
|
|
1062
|
if srcf and basef:
|
|
1062
|
if srcf and basef:
|
|
1063
|
raise error.Abort(_('cannot specify both a source and a base'))
|
|
1063
|
raise error.Abort(_('cannot specify both a source and a base'))
|
|
1064
|
if revf and basef:
|
|
1064
|
if revf and basef:
|
|
1065
|
raise error.Abort(_('cannot specify both a revision and a base'))
|
|
1065
|
raise error.Abort(_('cannot specify both a revision and a base'))
|
|
1066
|
if revf and srcf:
|
|
1066
|
if revf and srcf:
|
|
1067
|
raise error.Abort(_('cannot specify both a revision and a source'))
|
|
1067
|
raise error.Abort(_('cannot specify both a revision and a source'))
|
|
1068
|
|
|
1068
|
|
|
1069
|
if not inmemory:
|
|
1069
|
if not inmemory:
|
|
1070
|
cmdutil.checkunfinished(repo)
|
|
1070
|
cmdutil.checkunfinished(repo)
|
|
1071
|
cmdutil.bailifchanged(repo)
|
|
1071
|
cmdutil.bailifchanged(repo)
|
|
1072
|
|
|
1072
|
|
|
1073
|
if ui.configbool('commands', 'rebase.requiredest') and not destf:
|
|
1073
|
if ui.configbool('commands', 'rebase.requiredest') and not destf:
|
|
1074
|
raise error.Abort(_('you must specify a destination'),
|
|
1074
|
raise error.Abort(_('you must specify a destination'),
|
|
1075
|
hint=_('use: hg rebase -d REV'))
|
|
1075
|
hint=_('use: hg rebase -d REV'))
|
|
1076
|
|
|
1076
|
|
|
1077
|
dest = None
|
|
1077
|
dest = None
|
|
1078
|
|
|
1078
|
|
|
1079
|
if revf:
|
|
1079
|
if revf:
|
|
1080
|
rebaseset = scmutil.revrange(repo, revf)
|
|
1080
|
rebaseset = scmutil.revrange(repo, revf)
|
|
1081
|
if not rebaseset:
|
|
1081
|
if not rebaseset:
|
|
1082
|
ui.status(_('empty "rev" revision set - nothing to rebase\n'))
|
|
1082
|
ui.status(_('empty "rev" revision set - nothing to rebase\n'))
|
|
1083
|
return None
|
|
1083
|
return None
|
|
1084
|
elif srcf:
|
|
1084
|
elif srcf:
|
|
1085
|
src = scmutil.revrange(repo, [srcf])
|
|
1085
|
src = scmutil.revrange(repo, [srcf])
|
|
1086
|
if not src:
|
|
1086
|
if not src:
|
|
1087
|
ui.status(_('empty "source" revision set - nothing to rebase\n'))
|
|
1087
|
ui.status(_('empty "source" revision set - nothing to rebase\n'))
|
|
1088
|
return None
|
|
1088
|
return None
|
|
1089
|
rebaseset = repo.revs('(%ld)::', src)
|
|
1089
|
rebaseset = repo.revs('(%ld)::', src)
|
|
1090
|
assert rebaseset
|
|
1090
|
assert rebaseset
|
|
1091
|
else:
|
|
1091
|
else:
|
|
1092
|
base = scmutil.revrange(repo, [basef or '.'])
|
|
1092
|
base = scmutil.revrange(repo, [basef or '.'])
|
|
1093
|
if not base:
|
|
1093
|
if not base:
|
|
1094
|
ui.status(_('empty "base" revision set - '
|
|
1094
|
ui.status(_('empty "base" revision set - '
|
|
1095
|
"can't compute rebase set\n"))
|
|
1095
|
"can't compute rebase set\n"))
|
|
1096
|
return None
|
|
1096
|
return None
|
|
1097
|
if destf:
|
|
1097
|
if destf:
|
|
1098
|
# --base does not support multiple destinations
|
|
1098
|
# --base does not support multiple destinations
|
|
1099
|
dest = scmutil.revsingle(repo, destf)
|
|
1099
|
dest = scmutil.revsingle(repo, destf)
|
|
1100
|
else:
|
|
1100
|
else:
|
|
1101
|
dest = repo[_destrebase(repo, base, destspace=destspace)]
|
|
1101
|
dest = repo[_destrebase(repo, base, destspace=destspace)]
|
|
1102
|
destf = bytes(dest)
|
|
1102
|
destf = bytes(dest)
|
|
1103
|
|
|
1103
|
|
|
1104
|
roots = [] # selected children of branching points
|
|
1104
|
roots = [] # selected children of branching points
|
|
1105
|
bpbase = {} # {branchingpoint: [origbase]}
|
|
1105
|
bpbase = {} # {branchingpoint: [origbase]}
|
|
1106
|
for b in base: # group bases by branching points
|
|
1106
|
for b in base: # group bases by branching points
|
|
1107
|
bp = repo.revs('ancestor(%d, %d)', b, dest.rev()).first()
|
|
1107
|
bp = repo.revs('ancestor(%d, %d)', b, dest.rev()).first()
|
|
1108
|
bpbase[bp] = bpbase.get(bp, []) + [b]
|
|
1108
|
bpbase[bp] = bpbase.get(bp, []) + [b]
|
|
1109
|
if None in bpbase:
|
|
1109
|
if None in bpbase:
|
|
1110
|
# emulate the old behavior, showing "nothing to rebase" (a better
|
|
1110
|
# emulate the old behavior, showing "nothing to rebase" (a better
|
|
1111
|
# behavior may be abort with "cannot find branching point" error)
|
|
1111
|
# behavior may be abort with "cannot find branching point" error)
|
|
1112
|
bpbase.clear()
|
|
1112
|
bpbase.clear()
|
|
1113
|
for bp, bs in bpbase.iteritems(): # calculate roots
|
|
1113
|
for bp, bs in bpbase.iteritems(): # calculate roots
|
|
1114
|
roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
|
|
1114
|
roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
|
|
1115
|
|
|
1115
|
|
|
1116
|
rebaseset = repo.revs('%ld::', roots)
|
|
1116
|
rebaseset = repo.revs('%ld::', roots)
|
|
1117
|
|
|
1117
|
|
|
1118
|
if not rebaseset:
|
|
1118
|
if not rebaseset:
|
|
1119
|
# transform to list because smartsets are not comparable to
|
|
1119
|
# transform to list because smartsets are not comparable to
|
|
1120
|
# lists. This should be improved to honor laziness of
|
|
1120
|
# lists. This should be improved to honor laziness of
|
|
1121
|
# smartset.
|
|
1121
|
# smartset.
|
|
1122
|
if list(base) == [dest.rev()]:
|
|
1122
|
if list(base) == [dest.rev()]:
|
|
1123
|
if basef:
|
|
1123
|
if basef:
|
|
1124
|
ui.status(_('nothing to rebase - %s is both "base"'
|
|
1124
|
ui.status(_('nothing to rebase - %s is both "base"'
|
|
1125
|
' and destination\n') % dest)
|
|
1125
|
' and destination\n') % dest)
|
|
1126
|
else:
|
|
1126
|
else:
|
|
1127
|
ui.status(_('nothing to rebase - working directory '
|
|
1127
|
ui.status(_('nothing to rebase - working directory '
|
|
1128
|
'parent is also destination\n'))
|
|
1128
|
'parent is also destination\n'))
|
|
1129
|
elif not repo.revs('%ld - ::%d', base, dest.rev()):
|
|
1129
|
elif not repo.revs('%ld - ::%d', base, dest.rev()):
|
|
1130
|
if basef:
|
|
1130
|
if basef:
|
|
1131
|
ui.status(_('nothing to rebase - "base" %s is '
|
|
1131
|
ui.status(_('nothing to rebase - "base" %s is '
|
|
1132
|
'already an ancestor of destination '
|
|
1132
|
'already an ancestor of destination '
|
|
1133
|
'%s\n') %
|
|
1133
|
'%s\n') %
|
|
1134
|
('+'.join(bytes(repo[r]) for r in base),
|
|
1134
|
('+'.join(bytes(repo[r]) for r in base),
|
|
1135
|
dest))
|
|
1135
|
dest))
|
|
1136
|
else:
|
|
1136
|
else:
|
|
1137
|
ui.status(_('nothing to rebase - working '
|
|
1137
|
ui.status(_('nothing to rebase - working '
|
|
1138
|
'directory parent is already an '
|
|
1138
|
'directory parent is already an '
|
|
1139
|
'ancestor of destination %s\n') % dest)
|
|
1139
|
'ancestor of destination %s\n') % dest)
|
|
1140
|
else: # can it happen?
|
|
1140
|
else: # can it happen?
|
|
1141
|
ui.status(_('nothing to rebase from %s to %s\n') %
|
|
1141
|
ui.status(_('nothing to rebase from %s to %s\n') %
|
|
1142
|
('+'.join(bytes(repo[r]) for r in base), dest))
|
|
1142
|
('+'.join(bytes(repo[r]) for r in base), dest))
|
|
1143
|
return None
|
|
1143
|
return None
|
|
1144
|
|
|
1144
|
|
|
1145
|
rebasingwcp = repo['.'].rev() in rebaseset
|
|
1145
|
rebasingwcp = repo['.'].rev() in rebaseset
|
|
1146
|
ui.log("rebase", "rebasing working copy parent: %r\n", rebasingwcp,
|
|
1146
|
ui.log("rebase", "rebasing working copy parent: %r\n", rebasingwcp,
|
|
1147
|
rebase_rebasing_wcp=rebasingwcp)
|
|
1147
|
rebase_rebasing_wcp=rebasingwcp)
|
|
1148
|
if inmemory and rebasingwcp:
|
|
1148
|
if inmemory and rebasingwcp:
|
|
1149
|
# Check these since we did not before.
|
|
1149
|
# Check these since we did not before.
|
|
1150
|
cmdutil.checkunfinished(repo)
|
|
1150
|
cmdutil.checkunfinished(repo)
|
|
1151
|
cmdutil.bailifchanged(repo)
|
|
1151
|
cmdutil.bailifchanged(repo)
|
|
1152
|
|
|
1152
|
|
|
1153
|
if not destf:
|
|
1153
|
if not destf:
|
|
1154
|
dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
|
|
1154
|
dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
|
|
1155
|
destf = bytes(dest)
|
|
1155
|
destf = bytes(dest)
|
|
1156
|
|
|
1156
|
|
|
1157
|
allsrc = revsetlang.formatspec('%ld', rebaseset)
|
|
1157
|
allsrc = revsetlang.formatspec('%ld', rebaseset)
|
|
1158
|
alias = {'ALLSRC': allsrc}
|
|
1158
|
alias = {'ALLSRC': allsrc}
|
|
1159
|
|
|
1159
|
|
|
1160
|
if dest is None:
|
|
1160
|
if dest is None:
|
|
1161
|
try:
|
|
1161
|
try:
|
|
1162
|
# fast path: try to resolve dest without SRC alias
|
|
1162
|
# fast path: try to resolve dest without SRC alias
|
|
1163
|
dest = scmutil.revsingle(repo, destf, localalias=alias)
|
|
1163
|
dest = scmutil.revsingle(repo, destf, localalias=alias)
|
|
1164
|
except error.RepoLookupError:
|
|
1164
|
except error.RepoLookupError:
|
|
1165
|
# multi-dest path: resolve dest for each SRC separately
|
|
1165
|
# multi-dest path: resolve dest for each SRC separately
|
|
1166
|
destmap = {}
|
|
1166
|
destmap = {}
|
|
1167
|
for r in rebaseset:
|
|
1167
|
for r in rebaseset:
|
|
1168
|
alias['SRC'] = revsetlang.formatspec('%d', r)
|
|
1168
|
alias['SRC'] = revsetlang.formatspec('%d', r)
|
|
1169
|
# use repo.anyrevs instead of scmutil.revsingle because we
|
|
1169
|
# use repo.anyrevs instead of scmutil.revsingle because we
|
|
1170
|
# don't want to abort if destset is empty.
|
|
1170
|
# don't want to abort if destset is empty.
|
|
1171
|
destset = repo.anyrevs([destf], user=True, localalias=alias)
|
|
1171
|
destset = repo.anyrevs([destf], user=True, localalias=alias)
|
|
1172
|
size = len(destset)
|
|
1172
|
size = len(destset)
|
|
1173
|
if size == 1:
|
|
1173
|
if size == 1:
|
|
1174
|
destmap[r] = destset.first()
|
|
1174
|
destmap[r] = destset.first()
|
|
1175
|
elif size == 0:
|
|
1175
|
elif size == 0:
|
|
1176
|
ui.note(_('skipping %s - empty destination\n') % repo[r])
|
|
1176
|
ui.note(_('skipping %s - empty destination\n') % repo[r])
|
|
1177
|
else:
|
|
1177
|
else:
|
|
1178
|
raise error.Abort(_('rebase destination for %s is not '
|
|
1178
|
raise error.Abort(_('rebase destination for %s is not '
|
|
1179
|
'unique') % repo[r])
|
|
1179
|
'unique') % repo[r])
|
|
1180
|
|
|
1180
|
|
|
1181
|
if dest is not None:
|
|
1181
|
if dest is not None:
|
|
1182
|
# single-dest case: assign dest to each rev in rebaseset
|
|
1182
|
# single-dest case: assign dest to each rev in rebaseset
|
|
1183
|
destrev = dest.rev()
|
|
1183
|
destrev = dest.rev()
|
|
1184
|
destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
|
|
1184
|
destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
|
|
1185
|
|
|
1185
|
|
|
1186
|
if not destmap:
|
|
1186
|
if not destmap:
|
|
1187
|
ui.status(_('nothing to rebase - empty destination\n'))
|
|
1187
|
ui.status(_('nothing to rebase - empty destination\n'))
|
|
1188
|
return None
|
|
1188
|
return None
|
|
1189
|
|
|
1189
|
|
|
1190
|
return destmap
|
|
1190
|
return destmap
|
|
1191
|
|
|
1191
|
|
|
1192
|
def externalparent(repo, state, destancestors):
|
|
1192
|
def externalparent(repo, state, destancestors):
|
|
1193
|
"""Return the revision that should be used as the second parent
|
|
1193
|
"""Return the revision that should be used as the second parent
|
|
1194
|
when the revisions in state is collapsed on top of destancestors.
|
|
1194
|
when the revisions in state is collapsed on top of destancestors.
|
|
1195
|
Abort if there is more than one parent.
|
|
1195
|
Abort if there is more than one parent.
|
|
1196
|
"""
|
|
1196
|
"""
|
|
1197
|
parents = set()
|
|
1197
|
parents = set()
|
|
1198
|
source = min(state)
|
|
1198
|
source = min(state)
|
|
1199
|
for rev in state:
|
|
1199
|
for rev in state:
|
|
1200
|
if rev == source:
|
|
1200
|
if rev == source:
|
|
1201
|
continue
|
|
1201
|
continue
|
|
1202
|
for p in repo[rev].parents():
|
|
1202
|
for p in repo[rev].parents():
|
|
1203
|
if (p.rev() not in state
|
|
1203
|
if (p.rev() not in state
|
|
1204
|
and p.rev() not in destancestors):
|
|
1204
|
and p.rev() not in destancestors):
|
|
1205
|
parents.add(p.rev())
|
|
1205
|
parents.add(p.rev())
|
|
1206
|
if not parents:
|
|
1206
|
if not parents:
|
|
1207
|
return nullrev
|
|
1207
|
return nullrev
|
|
1208
|
if len(parents) == 1:
|
|
1208
|
if len(parents) == 1:
|
|
1209
|
return parents.pop()
|
|
1209
|
return parents.pop()
|
|
1210
|
raise error.Abort(_('unable to collapse on top of %d, there is more '
|
|
1210
|
raise error.Abort(_('unable to collapse on top of %d, there is more '
|
|
1211
|
'than one external parent: %s') %
|
|
1211
|
'than one external parent: %s') %
|
|
1212
|
(max(destancestors),
|
|
1212
|
(max(destancestors),
|
|
1213
|
', '.join("%d" % p for p in sorted(parents))))
|
|
1213
|
', '.join("%d" % p for p in sorted(parents))))
|
|
1214
|
|
|
1214
|
|
|
1215
|
def commitmemorynode(repo, p1, p2, wctx, editor, extra, user, date, commitmsg):
|
|
1215
|
def commitmemorynode(repo, p1, p2, wctx, editor, extra, user, date, commitmsg):
|
|
1216
|
'''Commit the memory changes with parents p1 and p2.
|
|
1216
|
'''Commit the memory changes with parents p1 and p2.
|
|
1217
|
Return node of committed revision.'''
|
|
1217
|
Return node of committed revision.'''
|
|
1218
|
# Replicates the empty check in ``repo.commit``.
|
|
1218
|
# Replicates the empty check in ``repo.commit``.
|
|
1219
|
if wctx.isempty() and not repo.ui.configbool('ui', 'allowemptycommit'):
|
|
1219
|
if wctx.isempty() and not repo.ui.configbool('ui', 'allowemptycommit'):
|
|
1220
|
return None
|
|
1220
|
return None
|
|
1221
|
|
|
1221
|
|
|
1222
|
# By convention, ``extra['branch']`` (set by extrafn) clobbers
|
|
1222
|
# By convention, ``extra['branch']`` (set by extrafn) clobbers
|
|
1223
|
# ``branch`` (used when passing ``--keepbranches``).
|
|
1223
|
# ``branch`` (used when passing ``--keepbranches``).
|
|
1224
|
branch = repo[p1].branch()
|
|
1224
|
branch = repo[p1].branch()
|
|
1225
|
if 'branch' in extra:
|
|
1225
|
if 'branch' in extra:
|
|
1226
|
branch = extra['branch']
|
|
1226
|
branch = extra['branch']
|
|
1227
|
|
|
1227
|
|
|
1228
|
memctx = wctx.tomemctx(commitmsg, parents=(p1, p2), date=date,
|
|
1228
|
memctx = wctx.tomemctx(commitmsg, parents=(p1, p2), date=date,
|
|
1229
|
extra=extra, user=user, branch=branch, editor=editor)
|
|
1229
|
extra=extra, user=user, branch=branch, editor=editor)
|
|
1230
|
commitres = repo.commitctx(memctx)
|
|
1230
|
commitres = repo.commitctx(memctx)
|
|
1231
|
wctx.clean() # Might be reused
|
|
1231
|
wctx.clean() # Might be reused
|
|
1232
|
return commitres
|
|
1232
|
return commitres
|
|
1233
|
|
|
1233
|
|
|
1234
|
def commitnode(repo, p1, p2, editor, extra, user, date, commitmsg):
|
|
1234
|
def commitnode(repo, p1, p2, editor, extra, user, date, commitmsg):
|
|
1235
|
'''Commit the wd changes with parents p1 and p2.
|
|
1235
|
'''Commit the wd changes with parents p1 and p2.
|
|
1236
|
Return node of committed revision.'''
|
|
1236
|
Return node of committed revision.'''
|
|
1237
|
dsguard = util.nullcontextmanager()
|
|
1237
|
dsguard = util.nullcontextmanager()
|
|
1238
|
if not repo.ui.configbool('rebase', 'singletransaction'):
|
|
1238
|
if not repo.ui.configbool('rebase', 'singletransaction'):
|
|
1239
|
dsguard = dirstateguard.dirstateguard(repo, 'rebase')
|
|
1239
|
dsguard = dirstateguard.dirstateguard(repo, 'rebase')
|
|
1240
|
with dsguard:
|
|
1240
|
with dsguard:
|
|
1241
|
repo.setparents(repo[p1].node(), repo[p2].node())
|
|
1241
|
repo.setparents(repo[p1].node(), repo[p2].node())
|
|
1242
|
|
|
1242
|
|
|
1243
|
# Commit might fail if unresolved files exist
|
|
1243
|
# Commit might fail if unresolved files exist
|
|
1244
|
newnode = repo.commit(text=commitmsg, user=user, date=date,
|
|
1244
|
newnode = repo.commit(text=commitmsg, user=user, date=date,
|
|
1245
|
extra=extra, editor=editor)
|
|
1245
|
extra=extra, editor=editor)
|
|
1246
|
|
|
1246
|
|
|
1247
|
repo.dirstate.setbranch(repo[newnode].branch())
|
|
1247
|
repo.dirstate.setbranch(repo[newnode].branch())
|
|
1248
|
return newnode
|
|
1248
|
return newnode
|
|
1249
|
|
|
1249
|
|
|
1250
|
def rebasenode(repo, rev, p1, base, collapse, dest, wctx):
|
|
1250
|
def rebasenode(repo, rev, p1, base, collapse, dest, wctx):
|
|
1251
|
'Rebase a single revision rev on top of p1 using base as merge ancestor'
|
|
1251
|
'Rebase a single revision rev on top of p1 using base as merge ancestor'
|
|
1252
|
# Merge phase
|
|
1252
|
# Merge phase
|
|
1253
|
# Update to destination and merge it with local
|
|
1253
|
# Update to destination and merge it with local
|
|
1254
|
if wctx.isinmemory():
|
|
1254
|
if wctx.isinmemory():
|
|
1255
|
wctx.setbase(repo[p1])
|
|
1255
|
wctx.setbase(repo[p1])
|
|
1256
|
else:
|
|
1256
|
else:
|
|
1257
|
if repo['.'].rev() != p1:
|
|
1257
|
if repo['.'].rev() != p1:
|
|
1258
|
repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
|
|
1258
|
repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
|
|
1259
|
mergemod.update(repo, p1, branchmerge=False, force=True)
|
|
1259
|
mergemod.update(repo, p1, branchmerge=False, force=True)
|
|
1260
|
else:
|
|
1260
|
else:
|
|
1261
|
repo.ui.debug(" already in destination\n")
|
|
1261
|
repo.ui.debug(" already in destination\n")
|
|
1262
|
# This is, alas, necessary to invalidate workingctx's manifest cache,
|
|
1262
|
# This is, alas, necessary to invalidate workingctx's manifest cache,
|
|
1263
|
# as well as other data we litter on it in other places.
|
|
1263
|
# as well as other data we litter on it in other places.
|
|
1264
|
wctx = repo[None]
|
|
1264
|
wctx = repo[None]
|
|
1265
|
repo.dirstate.write(repo.currenttransaction())
|
|
1265
|
repo.dirstate.write(repo.currenttransaction())
|
|
1266
|
repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
|
|
1266
|
repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
|
|
1267
|
if base is not None:
|
|
1267
|
if base is not None:
|
|
1268
|
repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
|
|
1268
|
repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
|
|
1269
|
# When collapsing in-place, the parent is the common ancestor, we
|
|
1269
|
# When collapsing in-place, the parent is the common ancestor, we
|
|
1270
|
# have to allow merging with it.
|
|
1270
|
# have to allow merging with it.
|
|
1271
|
stats = mergemod.update(repo, rev, branchmerge=True, force=True,
|
|
1271
|
stats = mergemod.update(repo, rev, branchmerge=True, force=True,
|
|
1272
|
ancestor=base, mergeancestor=collapse,
|
|
1272
|
ancestor=base, mergeancestor=collapse,
|
|
1273
|
labels=['dest', 'source'], wc=wctx)
|
|
1273
|
labels=['dest', 'source'], wc=wctx)
|
|
1274
|
if collapse:
|
|
1274
|
if collapse:
|
|
1275
|
copies.duplicatecopies(repo, wctx, rev, dest)
|
|
1275
|
copies.duplicatecopies(repo, wctx, rev, dest)
|
|
1276
|
else:
|
|
1276
|
else:
|
|
1277
|
# If we're not using --collapse, we need to
|
|
1277
|
# If we're not using --collapse, we need to
|
|
1278
|
# duplicate copies between the revision we're
|
|
1278
|
# duplicate copies between the revision we're
|
|
1279
|
# rebasing and its first parent, but *not*
|
|
1279
|
# rebasing and its first parent, but *not*
|
|
1280
|
# duplicate any copies that have already been
|
|
1280
|
# duplicate any copies that have already been
|
|
1281
|
# performed in the destination.
|
|
1281
|
# performed in the destination.
|
|
1282
|
p1rev = repo[rev].p1().rev()
|
|
1282
|
p1rev = repo[rev].p1().rev()
|
|
1283
|
copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
|
|
1283
|
copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
|
|
1284
|
return stats
|
|
1284
|
return stats
|
|
1285
|
|
|
1285
|
|
|
1286
|
def adjustdest(repo, rev, destmap, state, skipped):
|
|
1286
|
def adjustdest(repo, rev, destmap, state, skipped):
|
|
1287
|
r"""adjust rebase destination given the current rebase state
|
|
1287
|
r"""adjust rebase destination given the current rebase state
|
|
1288
|
|
|
1288
|
|
|
1289
|
rev is what is being rebased. Return a list of two revs, which are the
|
|
1289
|
rev is what is being rebased. Return a list of two revs, which are the
|
|
1290
|
adjusted destinations for rev's p1 and p2, respectively. If a parent is
|
|
1290
|
adjusted destinations for rev's p1 and p2, respectively. If a parent is
|
|
1291
|
nullrev, return dest without adjustment for it.
|
|
1291
|
nullrev, return dest without adjustment for it.
|
|
1292
|
|
|
1292
|
|
|
1293
|
For example, when doing rebasing B+E to F, C to G, rebase will first move B
|
|
1293
|
For example, when doing rebasing B+E to F, C to G, rebase will first move B
|
|
1294
|
to B1, and E's destination will be adjusted from F to B1.
|
|
1294
|
to B1, and E's destination will be adjusted from F to B1.
|
|
1295
|
|
|
1295
|
|
|
1296
|
B1 <- written during rebasing B
|
|
1296
|
B1 <- written during rebasing B
|
|
1297
|
|
|
|
1297
|
|
|
|
1298
|
F <- original destination of B, E
|
|
1298
|
F <- original destination of B, E
|
|
1299
|
|
|
|
1299
|
|
|
|
1300
|
| E <- rev, which is being rebased
|
|
1300
|
| E <- rev, which is being rebased
|
|
1301
|
| |
|
|
1301
|
| |
|
|
1302
|
| D <- prev, one parent of rev being checked
|
|
1302
|
| D <- prev, one parent of rev being checked
|
|
1303
|
| |
|
|
1303
|
| |
|
|
1304
|
| x <- skipped, ex. no successor or successor in (::dest)
|
|
1304
|
| x <- skipped, ex. no successor or successor in (::dest)
|
|
1305
|
| |
|
|
1305
|
| |
|
|
1306
|
| C <- rebased as C', different destination
|
|
1306
|
| C <- rebased as C', different destination
|
|
1307
|
| |
|
|
1307
|
| |
|
|
1308
|
| B <- rebased as B1 C'
|
|
1308
|
| B <- rebased as B1 C'
|
|
1309
|
|/ |
|
|
1309
|
|/ |
|
|
1310
|
A G <- destination of C, different
|
|
1310
|
A G <- destination of C, different
|
|
1311
|
|
|
1311
|
|
|
1312
|
Another example about merge changeset, rebase -r C+G+H -d K, rebase will
|
|
1312
|
Another example about merge changeset, rebase -r C+G+H -d K, rebase will
|
|
1313
|
first move C to C1, G to G1, and when it's checking H, the adjusted
|
|
1313
|
first move C to C1, G to G1, and when it's checking H, the adjusted
|
|
1314
|
destinations will be [C1, G1].
|
|
1314
|
destinations will be [C1, G1].
|
|
1315
|
|
|
1315
|
|
|
1316
|
H C1 G1
|
|
1316
|
H C1 G1
|
|
1317
|
/| | /
|
|
1317
|
/| | /
|
|
1318
|
F G |/
|
|
1318
|
F G |/
|
|
1319
|
K | | -> K
|
|
1319
|
K | | -> K
|
|
1320
|
| C D |
|
|
1320
|
| C D |
|
|
1321
|
| |/ |
|
|
1321
|
| |/ |
|
|
1322
|
| B | ...
|
|
1322
|
| B | ...
|
|
1323
|
|/ |/
|
|
1323
|
|/ |/
|
|
1324
|
A A
|
|
1324
|
A A
|
|
1325
|
|
|
1325
|
|
|
1326
|
Besides, adjust dest according to existing rebase information. For example,
|
|
1326
|
Besides, adjust dest according to existing rebase information. For example,
|
|
1327
|
|
|
1327
|
|
|
1328
|
B C D B needs to be rebased on top of C, C needs to be rebased on top
|
|
1328
|
B C D B needs to be rebased on top of C, C needs to be rebased on top
|
|
1329
|
\|/ of D. We will rebase C first.
|
|
1329
|
\|/ of D. We will rebase C first.
|
|
1330
|
A
|
|
1330
|
A
|
|
1331
|
|
|
1331
|
|
|
1332
|
C' After rebasing C, when considering B's destination, use C'
|
|
1332
|
C' After rebasing C, when considering B's destination, use C'
|
|
1333
|
| instead of the original C.
|
|
1333
|
| instead of the original C.
|
|
1334
|
B D
|
|
1334
|
B D
|
|
1335
|
\ /
|
|
1335
|
\ /
|
|
1336
|
A
|
|
1336
|
A
|
|
1337
|
"""
|
|
1337
|
"""
|
|
1338
|
# pick already rebased revs with same dest from state as interesting source
|
|
1338
|
# pick already rebased revs with same dest from state as interesting source
|
|
1339
|
dest = destmap[rev]
|
|
1339
|
dest = destmap[rev]
|
|
1340
|
source = [s for s, d in state.items()
|
|
1340
|
source = [s for s, d in state.items()
|
|
1341
|
if d > 0 and destmap[s] == dest and s not in skipped]
|
|
1341
|
if d > 0 and destmap[s] == dest and s not in skipped]
|
|
1342
|
|
|
1342
|
|
|
1343
|
result = []
|
|
1343
|
result = []
|
|
1344
|
for prev in repo.changelog.parentrevs(rev):
|
|
1344
|
for prev in repo.changelog.parentrevs(rev):
|
|
1345
|
adjusted = dest
|
|
1345
|
adjusted = dest
|
|
1346
|
if prev != nullrev:
|
|
1346
|
if prev != nullrev:
|
|
1347
|
candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
|
|
1347
|
candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
|
|
1348
|
if candidate is not None:
|
|
1348
|
if candidate is not None:
|
|
1349
|
adjusted = state[candidate]
|
|
1349
|
adjusted = state[candidate]
|
|
1350
|
if adjusted == dest and dest in state:
|
|
1350
|
if adjusted == dest and dest in state:
|
|
1351
|
adjusted = state[dest]
|
|
1351
|
adjusted = state[dest]
|
|
1352
|
if adjusted == revtodo:
|
|
1352
|
if adjusted == revtodo:
|
|
1353
|
# sortsource should produce an order that makes this impossible
|
|
1353
|
# sortsource should produce an order that makes this impossible
|
|
1354
|
raise error.ProgrammingError(
|
|
1354
|
raise error.ProgrammingError(
|
|
1355
|
'rev %d should be rebased already at this time' % dest)
|
|
1355
|
'rev %d should be rebased already at this time' % dest)
|
|
1356
|
result.append(adjusted)
|
|
1356
|
result.append(adjusted)
|
|
1357
|
return result
|
|
1357
|
return result
|
|
1358
|
|
|
1358
|
|
|
1359
|
def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
|
|
1359
|
def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
|
|
1360
|
"""
|
|
1360
|
"""
|
|
1361
|
Abort if rebase will create divergence or rebase is noop because of markers
|
|
1361
|
Abort if rebase will create divergence or rebase is noop because of markers
|
|
1362
|
|
|
1362
|
|
|
1363
|
`rebaseobsrevs`: set of obsolete revision in source
|
|
1363
|
`rebaseobsrevs`: set of obsolete revision in source
|
|
1364
|
`rebaseobsskipped`: set of revisions from source skipped because they have
|
|
1364
|
`rebaseobsskipped`: set of revisions from source skipped because they have
|
|
1365
|
successors in destination or no non-obsolete successor.
|
|
1365
|
successors in destination or no non-obsolete successor.
|
|
1366
|
"""
|
|
1366
|
"""
|
|
1367
|
# Obsolete node with successors not in dest leads to divergence
|
|
1367
|
# Obsolete node with successors not in dest leads to divergence
|
|
1368
|
divergenceok = ui.configbool('experimental',
|
|
1368
|
divergenceok = ui.configbool('experimental',
|
|
1369
|
'evolution.allowdivergence')
|
|
1369
|
'evolution.allowdivergence')
|
|
1370
|
divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
|
|
1370
|
divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
|
|
1371
|
|
|
1371
|
|
|
1372
|
if divergencebasecandidates and not divergenceok:
|
|
1372
|
if divergencebasecandidates and not divergenceok:
|
|
1373
|
divhashes = (bytes(repo[r])
|
|
1373
|
divhashes = (bytes(repo[r])
|
|
1374
|
for r in divergencebasecandidates)
|
|
1374
|
for r in divergencebasecandidates)
|
|
1375
|
msg = _("this rebase will cause "
|
|
1375
|
msg = _("this rebase will cause "
|
|
1376
|
"divergences from: %s")
|
|
1376
|
"divergences from: %s")
|
|
1377
|
h = _("to force the rebase please set "
|
|
1377
|
h = _("to force the rebase please set "
|
|
1378
|
"experimental.evolution.allowdivergence=True")
|
|
1378
|
"experimental.evolution.allowdivergence=True")
|
|
1379
|
raise error.Abort(msg % (",".join(divhashes),), hint=h)
|
|
1379
|
raise error.Abort(msg % (",".join(divhashes),), hint=h)
|
|
1380
|
|
|
1380
|
|
|
1381
|
def successorrevs(unfi, rev):
|
|
1381
|
def successorrevs(unfi, rev):
|
|
1382
|
"""yield revision numbers for successors of rev"""
|
|
1382
|
"""yield revision numbers for successors of rev"""
|
|
1383
|
assert unfi.filtername is None
|
|
1383
|
assert unfi.filtername is None
|
|
1384
|
nodemap = unfi.changelog.nodemap
|
|
1384
|
nodemap = unfi.changelog.nodemap
|
|
1385
|
for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
|
|
1385
|
for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
|
|
1386
|
if s in nodemap:
|
|
1386
|
if s in nodemap:
|
|
1387
|
yield nodemap[s]
|
|
1387
|
yield nodemap[s]
|
|
1388
|
|
|
1388
|
|
|
1389
|
def defineparents(repo, rev, destmap, state, skipped, obsskipped):
|
|
1389
|
def defineparents(repo, rev, destmap, state, skipped, obsskipped):
|
|
1390
|
"""Return new parents and optionally a merge base for rev being rebased
|
|
1390
|
"""Return new parents and optionally a merge base for rev being rebased
|
|
1391
|
|
|
1391
|
|
|
1392
|
The destination specified by "dest" cannot always be used directly because
|
|
1392
|
The destination specified by "dest" cannot always be used directly because
|
|
1393
|
previously rebase result could affect destination. For example,
|
|
1393
|
previously rebase result could affect destination. For example,
|
|
1394
|
|
|
1394
|
|
|
1395
|
D E rebase -r C+D+E -d B
|
|
1395
|
D E rebase -r C+D+E -d B
|
|
1396
|
|/ C will be rebased to C'
|
|
1396
|
|/ C will be rebased to C'
|
|
1397
|
B C D's new destination will be C' instead of B
|
|
1397
|
B C D's new destination will be C' instead of B
|
|
1398
|
|/ E's new destination will be C' instead of B
|
|
1398
|
|/ E's new destination will be C' instead of B
|
|
1399
|
A
|
|
1399
|
A
|
|
1400
|
|
|
1400
|
|
|
1401
|
The new parents of a merge is slightly more complicated. See the comment
|
|
1401
|
The new parents of a merge is slightly more complicated. See the comment
|
|
1402
|
block below.
|
|
1402
|
block below.
|
|
1403
|
"""
|
|
1403
|
"""
|
|
1404
|
# use unfiltered changelog since successorrevs may return filtered nodes
|
|
1404
|
# use unfiltered changelog since successorrevs may return filtered nodes
|
|
1405
|
assert repo.filtername is None
|
|
1405
|
assert repo.filtername is None
|
|
1406
|
cl = repo.changelog
|
|
1406
|
cl = repo.changelog
|
|
1407
|
isancestor = cl.isancestorrev
|
|
1407
|
isancestor = cl.isancestorrev
|
|
1408
|
|
|
1408
|
|
|
1409
|
dest = destmap[rev]
|
|
1409
|
dest = destmap[rev]
|
|
1410
|
oldps = repo.changelog.parentrevs(rev) # old parents
|
|
1410
|
oldps = repo.changelog.parentrevs(rev) # old parents
|
|
1411
|
newps = [nullrev, nullrev] # new parents
|
|
1411
|
newps = [nullrev, nullrev] # new parents
|
|
1412
|
dests = adjustdest(repo, rev, destmap, state, skipped)
|
|
1412
|
dests = adjustdest(repo, rev, destmap, state, skipped)
|
|
1413
|
bases = list(oldps) # merge base candidates, initially just old parents
|
|
1413
|
bases = list(oldps) # merge base candidates, initially just old parents
|
|
1414
|
|
|
1414
|
|
|
1415
|
if all(r == nullrev for r in oldps[1:]):
|
|
1415
|
if all(r == nullrev for r in oldps[1:]):
|
|
1416
|
# For non-merge changeset, just move p to adjusted dest as requested.
|
|
1416
|
# For non-merge changeset, just move p to adjusted dest as requested.
|
|
1417
|
newps[0] = dests[0]
|
|
1417
|
newps[0] = dests[0]
|
|
1418
|
else:
|
|
1418
|
else:
|
|
1419
|
# For merge changeset, if we move p to dests[i] unconditionally, both
|
|
1419
|
# For merge changeset, if we move p to dests[i] unconditionally, both
|
|
1420
|
# parents may change and the end result looks like "the merge loses a
|
|
1420
|
# parents may change and the end result looks like "the merge loses a
|
|
1421
|
# parent", which is a surprise. This is a limit because "--dest" only
|
|
1421
|
# parent", which is a surprise. This is a limit because "--dest" only
|
|
1422
|
# accepts one dest per src.
|
|
1422
|
# accepts one dest per src.
|
|
1423
|
#
|
|
1423
|
#
|
|
1424
|
# Therefore, only move p with reasonable conditions (in this order):
|
|
1424
|
# Therefore, only move p with reasonable conditions (in this order):
|
|
1425
|
# 1. use dest, if dest is a descendent of (p or one of p's successors)
|
|
1425
|
# 1. use dest, if dest is a descendent of (p or one of p's successors)
|
|
1426
|
# 2. use p's rebased result, if p is rebased (state[p] > 0)
|
|
1426
|
# 2. use p's rebased result, if p is rebased (state[p] > 0)
|
|
1427
|
#
|
|
1427
|
#
|
|
1428
|
# Comparing with adjustdest, the logic here does some additional work:
|
|
1428
|
# Comparing with adjustdest, the logic here does some additional work:
|
|
1429
|
# 1. decide which parents will not be moved towards dest
|
|
1429
|
# 1. decide which parents will not be moved towards dest
|
|
1430
|
# 2. if the above decision is "no", should a parent still be moved
|
|
1430
|
# 2. if the above decision is "no", should a parent still be moved
|
|
1431
|
# because it was rebased?
|
|
1431
|
# because it was rebased?
|
|
1432
|
#
|
|
1432
|
#
|
|
1433
|
# For example:
|
|
1433
|
# For example:
|
|
1434
|
#
|
|
1434
|
#
|
|
1435
|
# C # "rebase -r C -d D" is an error since none of the parents
|
|
1435
|
# C # "rebase -r C -d D" is an error since none of the parents
|
|
1436
|
# /| # can be moved. "rebase -r B+C -d D" will move C's parent
|
|
1436
|
# /| # can be moved. "rebase -r B+C -d D" will move C's parent
|
|
1437
|
# A B D # B (using rule "2."), since B will be rebased.
|
|
1437
|
# A B D # B (using rule "2."), since B will be rebased.
|
|
1438
|
#
|
|
1438
|
#
|
|
1439
|
# The loop tries to be not rely on the fact that a Mercurial node has
|
|
1439
|
# The loop tries to be not rely on the fact that a Mercurial node has
|
|
1440
|
# at most 2 parents.
|
|
1440
|
# at most 2 parents.
|
|
1441
|
for i, p in enumerate(oldps):
|
|
1441
|
for i, p in enumerate(oldps):
|
|
1442
|
np = p # new parent
|
|
1442
|
np = p # new parent
|
|
1443
|
if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
|
|
1443
|
if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
|
|
1444
|
np = dests[i]
|
|
1444
|
np = dests[i]
|
|
1445
|
elif p in state and state[p] > 0:
|
|
1445
|
elif p in state and state[p] > 0:
|
|
1446
|
np = state[p]
|
|
1446
|
np = state[p]
|
|
1447
|
|
|
1447
|
|
|
1448
|
# "bases" only record "special" merge bases that cannot be
|
|
1448
|
# "bases" only record "special" merge bases that cannot be
|
|
1449
|
# calculated from changelog DAG (i.e. isancestor(p, np) is False).
|
|
1449
|
# calculated from changelog DAG (i.e. isancestor(p, np) is False).
|
|
1450
|
# For example:
|
|
1450
|
# For example:
|
|
1451
|
#
|
|
1451
|
#
|
|
1452
|
# B' # rebase -s B -d D, when B was rebased to B'. dest for C
|
|
1452
|
# B' # rebase -s B -d D, when B was rebased to B'. dest for C
|
|
1453
|
# | C # is B', but merge base for C is B, instead of
|
|
1453
|
# | C # is B', but merge base for C is B, instead of
|
|
1454
|
# D | # changelog.ancestor(C, B') == A. If changelog DAG and
|
|
1454
|
# D | # changelog.ancestor(C, B') == A. If changelog DAG and
|
|
1455
|
# | B # "state" edges are merged (so there will be an edge from
|
|
1455
|
# | B # "state" edges are merged (so there will be an edge from
|
|
1456
|
# |/ # B to B'), the merge base is still ancestor(C, B') in
|
|
1456
|
# |/ # B to B'), the merge base is still ancestor(C, B') in
|
|
1457
|
# A # the merged graph.
|
|
1457
|
# A # the merged graph.
|
|
1458
|
#
|
|
1458
|
#
|
|
1459
|
# Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
|
|
1459
|
# Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
|
|
1460
|
# which uses "virtual null merge" to explain this situation.
|
|
1460
|
# which uses "virtual null merge" to explain this situation.
|
|
1461
|
if isancestor(p, np):
|
|
1461
|
if isancestor(p, np):
|
|
1462
|
bases[i] = nullrev
|
|
1462
|
bases[i] = nullrev
|
|
1463
|
|
|
1463
|
|
|
1464
|
# If one parent becomes an ancestor of the other, drop the ancestor
|
|
1464
|
# If one parent becomes an ancestor of the other, drop the ancestor
|
|
1465
|
for j, x in enumerate(newps[:i]):
|
|
1465
|
for j, x in enumerate(newps[:i]):
|
|
1466
|
if x == nullrev:
|
|
1466
|
if x == nullrev:
|
|
1467
|
continue
|
|
1467
|
continue
|
|
1468
|
if isancestor(np, x): # CASE-1
|
|
1468
|
if isancestor(np, x): # CASE-1
|
|
1469
|
np = nullrev
|
|
1469
|
np = nullrev
|
|
1470
|
elif isancestor(x, np): # CASE-2
|
|
1470
|
elif isancestor(x, np): # CASE-2
|
|
1471
|
newps[j] = np
|
|
1471
|
newps[j] = np
|
|
1472
|
np = nullrev
|
|
1472
|
np = nullrev
|
|
1473
|
# New parents forming an ancestor relationship does not
|
|
1473
|
# New parents forming an ancestor relationship does not
|
|
1474
|
# mean the old parents have a similar relationship. Do not
|
|
1474
|
# mean the old parents have a similar relationship. Do not
|
|
1475
|
# set bases[x] to nullrev.
|
|
1475
|
# set bases[x] to nullrev.
|
|
1476
|
bases[j], bases[i] = bases[i], bases[j]
|
|
1476
|
bases[j], bases[i] = bases[i], bases[j]
|
|
1477
|
|
|
1477
|
|
|
1478
|
newps[i] = np
|
|
1478
|
newps[i] = np
|
|
1479
|
|
|
1479
|
|
|
1480
|
# "rebasenode" updates to new p1, and the old p1 will be used as merge
|
|
1480
|
# "rebasenode" updates to new p1, and the old p1 will be used as merge
|
|
1481
|
# base. If only p2 changes, merging using unchanged p1 as merge base is
|
|
1481
|
# base. If only p2 changes, merging using unchanged p1 as merge base is
|
|
1482
|
# suboptimal. Therefore swap parents to make the merge sane.
|
|
1482
|
# suboptimal. Therefore swap parents to make the merge sane.
|
|
1483
|
if newps[1] != nullrev and oldps[0] == newps[0]:
|
|
1483
|
if newps[1] != nullrev and oldps[0] == newps[0]:
|
|
1484
|
assert len(newps) == 2 and len(oldps) == 2
|
|
1484
|
assert len(newps) == 2 and len(oldps) == 2
|
|
1485
|
newps.reverse()
|
|
1485
|
newps.reverse()
|
|
1486
|
bases.reverse()
|
|
1486
|
bases.reverse()
|
|
1487
|
|
|
1487
|
|
|
1488
|
# No parent change might be an error because we fail to make rev a
|
|
1488
|
# No parent change might be an error because we fail to make rev a
|
|
1489
|
# descendent of requested dest. This can happen, for example:
|
|
1489
|
# descendent of requested dest. This can happen, for example:
|
|
1490
|
#
|
|
1490
|
#
|
|
1491
|
# C # rebase -r C -d D
|
|
1491
|
# C # rebase -r C -d D
|
|
1492
|
# /| # None of A and B will be changed to D and rebase fails.
|
|
1492
|
# /| # None of A and B will be changed to D and rebase fails.
|
|
1493
|
# A B D
|
|
1493
|
# A B D
|
|
1494
|
if set(newps) == set(oldps) and dest not in newps:
|
|
1494
|
if set(newps) == set(oldps) and dest not in newps:
|
|
1495
|
raise error.Abort(_('cannot rebase %d:%s without '
|
|
1495
|
raise error.Abort(_('cannot rebase %d:%s without '
|
|
1496
|
'moving at least one of its parents')
|
|
1496
|
'moving at least one of its parents')
|
|
1497
|
% (rev, repo[rev]))
|
|
1497
|
% (rev, repo[rev]))
|
|
1498
|
|
|
1498
|
|
|
1499
|
# Source should not be ancestor of dest. The check here guarantees it's
|
|
1499
|
# Source should not be ancestor of dest. The check here guarantees it's
|
|
1500
|
# impossible. With multi-dest, the initial check does not cover complex
|
|
1500
|
# impossible. With multi-dest, the initial check does not cover complex
|
|
1501
|
# cases since we don't have abstractions to dry-run rebase cheaply.
|
|
1501
|
# cases since we don't have abstractions to dry-run rebase cheaply.
|
|
1502
|
if any(p != nullrev and isancestor(rev, p) for p in newps):
|
|
1502
|
if any(p != nullrev and isancestor(rev, p) for p in newps):
|
|
1503
|
raise error.Abort(_('source is ancestor of destination'))
|
|
1503
|
raise error.Abort(_('source is ancestor of destination'))
|
|
1504
|
|
|
1504
|
|
|
1505
|
# "rebasenode" updates to new p1, use the corresponding merge base.
|
|
1505
|
# "rebasenode" updates to new p1, use the corresponding merge base.
|
|
1506
|
if bases[0] != nullrev:
|
|
1506
|
if bases[0] != nullrev:
|
|
1507
|
base = bases[0]
|
|
1507
|
base = bases[0]
|
|
1508
|
else:
|
|
1508
|
else:
|
|
1509
|
base = None
|
|
1509
|
base = None
|
|
1510
|
|
|
1510
|
|
|
1511
|
# Check if the merge will contain unwanted changes. That may happen if
|
|
1511
|
# Check if the merge will contain unwanted changes. That may happen if
|
|
1512
|
# there are multiple special (non-changelog ancestor) merge bases, which
|
|
1512
|
# there are multiple special (non-changelog ancestor) merge bases, which
|
|
1513
|
# cannot be handled well by the 3-way merge algorithm. For example:
|
|
1513
|
# cannot be handled well by the 3-way merge algorithm. For example:
|
|
1514
|
#
|
|
1514
|
#
|
|
1515
|
# F
|
|
1515
|
# F
|
|
1516
|
# /|
|
|
1516
|
# /|
|
|
1517
|
# D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
|
|
1517
|
# D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
|
|
1518
|
# | | # as merge base, the difference between D and F will include
|
|
1518
|
# | | # as merge base, the difference between D and F will include
|
|
1519
|
# B C # C, so the rebased F will contain C surprisingly. If "E" was
|
|
1519
|
# B C # C, so the rebased F will contain C surprisingly. If "E" was
|
|
1520
|
# |/ # chosen, the rebased F will contain B.
|
|
1520
|
# |/ # chosen, the rebased F will contain B.
|
|
1521
|
# A Z
|
|
1521
|
# A Z
|
|
1522
|
#
|
|
1522
|
#
|
|
1523
|
# But our merge base candidates (D and E in above case) could still be
|
|
1523
|
# But our merge base candidates (D and E in above case) could still be
|
|
1524
|
# better than the default (ancestor(F, Z) == null). Therefore still
|
|
1524
|
# better than the default (ancestor(F, Z) == null). Therefore still
|
|
1525
|
# pick one (so choose p1 above).
|
|
1525
|
# pick one (so choose p1 above).
|
|
1526
|
if sum(1 for b in bases if b != nullrev) > 1:
|
|
1526
|
if sum(1 for b in bases if b != nullrev) > 1:
|
|
1527
|
unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
|
|
1527
|
unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
|
|
1528
|
for i, base in enumerate(bases):
|
|
1528
|
for i, base in enumerate(bases):
|
|
1529
|
if base == nullrev:
|
|
1529
|
if base == nullrev:
|
|
1530
|
continue
|
|
1530
|
continue
|
|
1531
|
# Revisions in the side (not chosen as merge base) branch that
|
|
1531
|
# Revisions in the side (not chosen as merge base) branch that
|
|
1532
|
# might contain "surprising" contents
|
|
1532
|
# might contain "surprising" contents
|
|
1533
|
siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
|
|
1533
|
siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
|
|
1534
|
bases, base, base, dest))
|
|
1534
|
bases, base, base, dest))
|
|
1535
|
|
|
1535
|
|
|
1536
|
# If those revisions are covered by rebaseset, the result is good.
|
|
1536
|
# If those revisions are covered by rebaseset, the result is good.
|
|
1537
|
# A merge in rebaseset would be considered to cover its ancestors.
|
|
1537
|
# A merge in rebaseset would be considered to cover its ancestors.
|
|
1538
|
if siderevs:
|
|
1538
|
if siderevs:
|
|
1539
|
rebaseset = [r for r, d in state.items()
|
|
1539
|
rebaseset = [r for r, d in state.items()
|
|
1540
|
if d > 0 and r not in obsskipped]
|
|
1540
|
if d > 0 and r not in obsskipped]
|
|
1541
|
merges = [r for r in rebaseset
|
|
1541
|
merges = [r for r in rebaseset
|
|
1542
|
if cl.parentrevs(r)[1] != nullrev]
|
|
1542
|
if cl.parentrevs(r)[1] != nullrev]
|
|
1543
|
unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
|
|
1543
|
unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
|
|
1544
|
siderevs, merges, rebaseset))
|
|
1544
|
siderevs, merges, rebaseset))
|
|
1545
|
|
|
1545
|
|
|
1546
|
# Choose a merge base that has a minimal number of unwanted revs.
|
|
1546
|
# Choose a merge base that has a minimal number of unwanted revs.
|
|
1547
|
l, i = min((len(revs), i)
|
|
1547
|
l, i = min((len(revs), i)
|
|
1548
|
for i, revs in enumerate(unwanted) if revs is not None)
|
|
1548
|
for i, revs in enumerate(unwanted) if revs is not None)
|
|
1549
|
base = bases[i]
|
|
1549
|
base = bases[i]
|
|
1550
|
|
|
1550
|
|
|
1551
|
# newps[0] should match merge base if possible. Currently, if newps[i]
|
|
1551
|
# newps[0] should match merge base if possible. Currently, if newps[i]
|
|
1552
|
# is nullrev, the only case is newps[i] and newps[j] (j < i), one is
|
|
1552
|
# is nullrev, the only case is newps[i] and newps[j] (j < i), one is
|
|
1553
|
# the other's ancestor. In that case, it's fine to not swap newps here.
|
|
1553
|
# the other's ancestor. In that case, it's fine to not swap newps here.
|
|
1554
|
# (see CASE-1 and CASE-2 above)
|
|
1554
|
# (see CASE-1 and CASE-2 above)
|
|
1555
|
if i != 0 and newps[i] != nullrev:
|
|
1555
|
if i != 0 and newps[i] != nullrev:
|
|
1556
|
newps[0], newps[i] = newps[i], newps[0]
|
|
1556
|
newps[0], newps[i] = newps[i], newps[0]
|
|
1557
|
|
|
1557
|
|
|
1558
|
# The merge will include unwanted revisions. Abort now. Revisit this if
|
|
1558
|
# The merge will include unwanted revisions. Abort now. Revisit this if
|
|
1559
|
# we have a more advanced merge algorithm that handles multiple bases.
|
|
1559
|
# we have a more advanced merge algorithm that handles multiple bases.
|
|
1560
|
if l > 0:
|
|
1560
|
if l > 0:
|
|
1561
|
unwanteddesc = _(' or ').join(
|
|
1561
|
unwanteddesc = _(' or ').join(
|
|
1562
|
(', '.join('%d:%s' % (r, repo[r]) for r in revs)
|
|
1562
|
(', '.join('%d:%s' % (r, repo[r]) for r in revs)
|
|
1563
|
for revs in unwanted if revs is not None))
|
|
1563
|
for revs in unwanted if revs is not None))
|
|
1564
|
raise error.Abort(
|
|
1564
|
raise error.Abort(
|
|
1565
|
_('rebasing %d:%s will include unwanted changes from %s')
|
|
1565
|
_('rebasing %d:%s will include unwanted changes from %s')
|
|
1566
|
% (rev, repo[rev], unwanteddesc))
|
|
1566
|
% (rev, repo[rev], unwanteddesc))
|
|
1567
|
|
|
1567
|
|
|
1568
|
repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
|
|
1568
|
repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
|
|
1569
|
|
|
1569
|
|
|
1570
|
return newps[0], newps[1], base
|
|
1570
|
return newps[0], newps[1], base
|
|
1571
|
|
|
1571
|
|
|
1572
|
def isagitpatch(repo, patchname):
|
|
1572
|
def isagitpatch(repo, patchname):
|
|
1573
|
'Return true if the given patch is in git format'
|
|
1573
|
'Return true if the given patch is in git format'
|
|
1574
|
mqpatch = os.path.join(repo.mq.path, patchname)
|
|
1574
|
mqpatch = os.path.join(repo.mq.path, patchname)
|
|
1575
|
for line in patch.linereader(open(mqpatch, 'rb')):
|
|
1575
|
for line in patch.linereader(open(mqpatch, 'rb')):
|
|
1576
|
if line.startswith('diff --git'):
|
|
1576
|
if line.startswith('diff --git'):
|
|
1577
|
return True
|
|
1577
|
return True
|
|
1578
|
return False
|
|
1578
|
return False
|
|
1579
|
|
|
1579
|
|
|
1580
|
def updatemq(repo, state, skipped, **opts):
|
|
1580
|
def updatemq(repo, state, skipped, **opts):
|
|
1581
|
'Update rebased mq patches - finalize and then import them'
|
|
1581
|
'Update rebased mq patches - finalize and then import them'
|
|
1582
|
mqrebase = {}
|
|
1582
|
mqrebase = {}
|
|
1583
|
mq = repo.mq
|
|
1583
|
mq = repo.mq
|
|
1584
|
original_series = mq.fullseries[:]
|
|
1584
|
original_series = mq.fullseries[:]
|
|
1585
|
skippedpatches = set()
|
|
1585
|
skippedpatches = set()
|
|
1586
|
|
|
1586
|
|
|
1587
|
for p in mq.applied:
|
|
1587
|
for p in mq.applied:
|
|
1588
|
rev = repo[p.node].rev()
|
|
1588
|
rev = repo[p.node].rev()
|
|
1589
|
if rev in state:
|
|
1589
|
if rev in state:
|
|
1590
|
repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
|
|
1590
|
repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
|
|
1591
|
(rev, p.name))
|
|
1591
|
(rev, p.name))
|
|
1592
|
mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
|
|
1592
|
mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
|
|
1593
|
else:
|
|
1593
|
else:
|
|
1594
|
# Applied but not rebased, not sure this should happen
|
|
1594
|
# Applied but not rebased, not sure this should happen
|
|
1595
|
skippedpatches.add(p.name)
|
|
1595
|
skippedpatches.add(p.name)
|
|
1596
|
|
|
1596
|
|
|
1597
|
if mqrebase:
|
|
1597
|
if mqrebase:
|
|
1598
|
mq.finish(repo, mqrebase.keys())
|
|
1598
|
mq.finish(repo, mqrebase.keys())
|
|
1599
|
|
|
1599
|
|
|
1600
|
# We must start import from the newest revision
|
|
1600
|
# We must start import from the newest revision
|
|
1601
|
for rev in sorted(mqrebase, reverse=True):
|
|
1601
|
for rev in sorted(mqrebase, reverse=True):
|
|
1602
|
if rev not in skipped:
|
|
1602
|
if rev not in skipped:
|
|
1603
|
name, isgit = mqrebase[rev]
|
|
1603
|
name, isgit = mqrebase[rev]
|
|
1604
|
repo.ui.note(_('updating mq patch %s to %d:%s\n') %
|
|
1604
|
repo.ui.note(_('updating mq patch %s to %d:%s\n') %
|
|
1605
|
(name, state[rev], repo[state[rev]]))
|
|
1605
|
(name, state[rev], repo[state[rev]]))
|
|
1606
|
mq.qimport(repo, (), patchname=name, git=isgit,
|
|
1606
|
mq.qimport(repo, (), patchname=name, git=isgit,
|
|
1607
|
rev=["%d" % state[rev]])
|
|
1607
|
rev=["%d" % state[rev]])
|
|
1608
|
else:
|
|
1608
|
else:
|
|
1609
|
# Rebased and skipped
|
|
1609
|
# Rebased and skipped
|
|
1610
|
skippedpatches.add(mqrebase[rev][0])
|
|
1610
|
skippedpatches.add(mqrebase[rev][0])
|
|
1611
|
|
|
1611
|
|
|
1612
|
# Patches were either applied and rebased and imported in
|
|
1612
|
# Patches were either applied and rebased and imported in
|
|
1613
|
# order, applied and removed or unapplied. Discard the removed
|
|
1613
|
# order, applied and removed or unapplied. Discard the removed
|
|
1614
|
# ones while preserving the original series order and guards.
|
|
1614
|
# ones while preserving the original series order and guards.
|
|
1615
|
newseries = [s for s in original_series
|
|
1615
|
newseries = [s for s in original_series
|
|
1616
|
if mq.guard_re.split(s, 1)[0] not in skippedpatches]
|
|
1616
|
if mq.guard_re.split(s, 1)[0] not in skippedpatches]
|
|
1617
|
mq.fullseries[:] = newseries
|
|
1617
|
mq.fullseries[:] = newseries
|
|
1618
|
mq.seriesdirty = True
|
|
1618
|
mq.seriesdirty = True
|
|
1619
|
mq.savedirty()
|
|
1619
|
mq.savedirty()
|
|
1620
|
|
|
1620
|
|
|
1621
|
def storecollapsemsg(repo, collapsemsg):
|
|
1621
|
def storecollapsemsg(repo, collapsemsg):
|
|
1622
|
'Store the collapse message to allow recovery'
|
|
1622
|
'Store the collapse message to allow recovery'
|
|
1623
|
collapsemsg = collapsemsg or ''
|
|
1623
|
collapsemsg = collapsemsg or ''
|
|
1624
|
f = repo.vfs("last-message.txt", "w")
|
|
1624
|
f = repo.vfs("last-message.txt", "w")
|
|
1625
|
f.write("%s\n" % collapsemsg)
|
|
1625
|
f.write("%s\n" % collapsemsg)
|
|
1626
|
f.close()
|
|
1626
|
f.close()
|
|
1627
|
|
|
1627
|
|
|
1628
|
def clearcollapsemsg(repo):
|
|
1628
|
def clearcollapsemsg(repo):
|
|
1629
|
'Remove collapse message file'
|
|
1629
|
'Remove collapse message file'
|
|
1630
|
repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
|
|
1630
|
repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
|
|
1631
|
|
|
1631
|
|
|
1632
|
def restorecollapsemsg(repo, isabort):
|
|
1632
|
def restorecollapsemsg(repo, isabort):
|
|
1633
|
'Restore previously stored collapse message'
|
|
1633
|
'Restore previously stored collapse message'
|
|
1634
|
try:
|
|
1634
|
try:
|
|
1635
|
f = repo.vfs("last-message.txt")
|
|
1635
|
f = repo.vfs("last-message.txt")
|
|
1636
|
collapsemsg = f.readline().strip()
|
|
1636
|
collapsemsg = f.readline().strip()
|
|
1637
|
f.close()
|
|
1637
|
f.close()
|
|
1638
|
except IOError as err:
|
|
1638
|
except IOError as err:
|
|
1639
|
if err.errno != errno.ENOENT:
|
|
1639
|
if err.errno != errno.ENOENT:
|
|
1640
|
raise
|
|
1640
|
raise
|
|
1641
|
if isabort:
|
|
1641
|
if isabort:
|
|
1642
|
# Oh well, just abort like normal
|
|
1642
|
# Oh well, just abort like normal
|
|
1643
|
collapsemsg = ''
|
|
1643
|
collapsemsg = ''
|
|
1644
|
else:
|
|
1644
|
else:
|
|
1645
|
raise error.Abort(_('missing .hg/last-message.txt for rebase'))
|
|
1645
|
raise error.Abort(_('missing .hg/last-message.txt for rebase'))
|
|
1646
|
return collapsemsg
|
|
1646
|
return collapsemsg
|
|
1647
|
|
|
1647
|
|
|
1648
|
def clearstatus(repo):
|
|
1648
|
def clearstatus(repo):
|
|
1649
|
'Remove the status files'
|
|
1649
|
'Remove the status files'
|
|
1650
|
# Make sure the active transaction won't write the state file
|
|
1650
|
# Make sure the active transaction won't write the state file
|
|
1651
|
tr = repo.currenttransaction()
|
|
1651
|
tr = repo.currenttransaction()
|
|
1652
|
if tr:
|
|
1652
|
if tr:
|
|
1653
|
tr.removefilegenerator('rebasestate')
|
|
1653
|
tr.removefilegenerator('rebasestate')
|
|
1654
|
repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
|
|
1654
|
repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
|
|
1655
|
|
|
1655
|
|
|
1656
|
def needupdate(repo, state):
|
|
1656
|
def needupdate(repo, state):
|
|
1657
|
'''check whether we should `update --clean` away from a merge, or if
|
|
1657
|
'''check whether we should `update --clean` away from a merge, or if
|
|
1658
|
somehow the working dir got forcibly updated, e.g. by older hg'''
|
|
1658
|
somehow the working dir got forcibly updated, e.g. by older hg'''
|
|
1659
|
parents = [p.rev() for p in repo[None].parents()]
|
|
1659
|
parents = [p.rev() for p in repo[None].parents()]
|
|
1660
|
|
|
1660
|
|
|
1661
|
# Are we in a merge state at all?
|
|
1661
|
# Are we in a merge state at all?
|
|
1662
|
if len(parents) < 2:
|
|
1662
|
if len(parents) < 2:
|
|
1663
|
return False
|
|
1663
|
return False
|
|
1664
|
|
|
1664
|
|
|
1665
|
# We should be standing on the first as-of-yet unrebased commit.
|
|
1665
|
# We should be standing on the first as-of-yet unrebased commit.
|
|
1666
|
firstunrebased = min([old for old, new in state.iteritems()
|
|
1666
|
firstunrebased = min([old for old, new in state.iteritems()
|
|
1667
|
if new == nullrev])
|
|
1667
|
if new == nullrev])
|
|
1668
|
if firstunrebased in parents:
|
|
1668
|
if firstunrebased in parents:
|
|
1669
|
return True
|
|
1669
|
return True
|
|
1670
|
|
|
1670
|
|
|
1671
|
return False
|
|
1671
|
return False
|
|
1672
|
|
|
1672
|
|
|
1673
|
def sortsource(destmap):
|
|
1673
|
def sortsource(destmap):
|
|
1674
|
"""yield source revisions in an order that we only rebase things once
|
|
1674
|
"""yield source revisions in an order that we only rebase things once
|
|
1675
|
|
|
1675
|
|
|
1676
|
If source and destination overlaps, we should filter out revisions
|
|
1676
|
If source and destination overlaps, we should filter out revisions
|
|
1677
|
depending on other revisions which hasn't been rebased yet.
|
|
1677
|
depending on other revisions which hasn't been rebased yet.
|
|
1678
|
|
|
1678
|
|
|
1679
|
Yield a sorted list of revisions each time.
|
|
1679
|
Yield a sorted list of revisions each time.
|
|
1680
|
|
|
1680
|
|
|
1681
|
For example, when rebasing A to B, B to C. This function yields [B], then
|
|
1681
|
For example, when rebasing A to B, B to C. This function yields [B], then
|
|
1682
|
[A], indicating B needs to be rebased first.
|
|
1682
|
[A], indicating B needs to be rebased first.
|
|
1683
|
|
|
1683
|
|
|
1684
|
Raise if there is a cycle so the rebase is impossible.
|
|
1684
|
Raise if there is a cycle so the rebase is impossible.
|
|
1685
|
"""
|
|
1685
|
"""
|
|
1686
|
srcset = set(destmap)
|
|
1686
|
srcset = set(destmap)
|
|
1687
|
while srcset:
|
|
1687
|
while srcset:
|
|
1688
|
srclist = sorted(srcset)
|
|
1688
|
srclist = sorted(srcset)
|
|
1689
|
result = []
|
|
1689
|
result = []
|
|
1690
|
for r in srclist:
|
|
1690
|
for r in srclist:
|
|
1691
|
if destmap[r] not in srcset:
|
|
1691
|
if destmap[r] not in srcset:
|
|
1692
|
result.append(r)
|
|
1692
|
result.append(r)
|
|
1693
|
if not result:
|
|
1693
|
if not result:
|
|
1694
|
raise error.Abort(_('source and destination form a cycle'))
|
|
1694
|
raise error.Abort(_('source and destination form a cycle'))
|
|
1695
|
srcset -= set(result)
|
|
1695
|
srcset -= set(result)
|
|
1696
|
yield result
|
|
1696
|
yield result
|
|
1697
|
|
|
1697
|
|
|
1698
|
def buildstate(repo, destmap, collapse):
|
|
1698
|
def buildstate(repo, destmap, collapse):
|
|
1699
|
'''Define which revisions are going to be rebased and where
|
|
1699
|
'''Define which revisions are going to be rebased and where
|
|
1700
|
|
|
1700
|
|
|
1701
|
repo: repo
|
|
1701
|
repo: repo
|
|
1702
|
destmap: {srcrev: destrev}
|
|
1702
|
destmap: {srcrev: destrev}
|
|
1703
|
'''
|
|
1703
|
'''
|
|
1704
|
rebaseset = destmap.keys()
|
|
1704
|
rebaseset = destmap.keys()
|
|
1705
|
originalwd = repo['.'].rev()
|
|
1705
|
originalwd = repo['.'].rev()
|
|
1706
|
|
|
1706
|
|
|
1707
|
# This check isn't strictly necessary, since mq detects commits over an
|
|
1707
|
# This check isn't strictly necessary, since mq detects commits over an
|
|
1708
|
# applied patch. But it prevents messing up the working directory when
|
|
1708
|
# applied patch. But it prevents messing up the working directory when
|
|
1709
|
# a partially completed rebase is blocked by mq.
|
|
1709
|
# a partially completed rebase is blocked by mq.
|
|
1710
|
if 'qtip' in repo.tags():
|
|
1710
|
if 'qtip' in repo.tags():
|
|
1711
|
mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
|
|
1711
|
mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
|
|
1712
|
if set(destmap.values()) & mqapplied:
|
|
1712
|
if set(destmap.values()) & mqapplied:
|
|
1713
|
raise error.Abort(_('cannot rebase onto an applied mq patch'))
|
|
1713
|
raise error.Abort(_('cannot rebase onto an applied mq patch'))
|
|
1714
|
|
|
1714
|
|
|
1715
|
# Get "cycle" error early by exhausting the generator.
|
|
1715
|
# Get "cycle" error early by exhausting the generator.
|
|
1716
|
sortedsrc = list(sortsource(destmap)) # a list of sorted revs
|
|
1716
|
sortedsrc = list(sortsource(destmap)) # a list of sorted revs
|
|
1717
|
if not sortedsrc:
|
|
1717
|
if not sortedsrc:
|
|
1718
|
raise error.Abort(_('no matching revisions'))
|
|
1718
|
raise error.Abort(_('no matching revisions'))
|
|
1719
|
|
|
1719
|
|
|
1720
|
# Only check the first batch of revisions to rebase not depending on other
|
|
1720
|
# Only check the first batch of revisions to rebase not depending on other
|
|
1721
|
# rebaseset. This means "source is ancestor of destination" for the second
|
|
1721
|
# rebaseset. This means "source is ancestor of destination" for the second
|
|
1722
|
# (and following) batches of revisions are not checked here. We rely on
|
|
1722
|
# (and following) batches of revisions are not checked here. We rely on
|
|
1723
|
# "defineparents" to do that check.
|
|
1723
|
# "defineparents" to do that check.
|
|
1724
|
roots = list(repo.set('roots(%ld)', sortedsrc[0]))
|
|
1724
|
roots = list(repo.set('roots(%ld)', sortedsrc[0]))
|
|
1725
|
if not roots:
|
|
1725
|
if not roots:
|
|
1726
|
raise error.Abort(_('no matching revisions'))
|
|
1726
|
raise error.Abort(_('no matching revisions'))
|
|
1727
|
def revof(r):
|
|
1727
|
def revof(r):
|
|
1728
|
return r.rev()
|
|
1728
|
return r.rev()
|
|
1729
|
roots = sorted(roots, key=revof)
|
|
1729
|
roots = sorted(roots, key=revof)
|
|
1730
|
state = dict.fromkeys(rebaseset, revtodo)
|
|
1730
|
state = dict.fromkeys(rebaseset, revtodo)
|
|
1731
|
emptyrebase = (len(sortedsrc) == 1)
|
|
1731
|
emptyrebase = (len(sortedsrc) == 1)
|
|
1732
|
for root in roots:
|
|
1732
|
for root in roots:
|
|
1733
|
dest = repo[destmap[root.rev()]]
|
|
1733
|
dest = repo[destmap[root.rev()]]
|
|
1734
|
commonbase = root.ancestor(dest)
|
|
1734
|
commonbase = root.ancestor(dest)
|
|
1735
|
if commonbase == root:
|
|
1735
|
if commonbase == root:
|
|
1736
|
raise error.Abort(_('source is ancestor of destination'))
|
|
1736
|
raise error.Abort(_('source is ancestor of destination'))
|
|
1737
|
if commonbase == dest:
|
|
1737
|
if commonbase == dest:
|
|
1738
|
wctx = repo[None]
|
|
1738
|
wctx = repo[None]
|
|
1739
|
if dest == wctx.p1():
|
|
1739
|
if dest == wctx.p1():
|
|
1740
|
# when rebasing to '.', it will use the current wd branch name
|
|
1740
|
# when rebasing to '.', it will use the current wd branch name
|
|
1741
|
samebranch = root.branch() == wctx.branch()
|
|
1741
|
samebranch = root.branch() == wctx.branch()
|
|
1742
|
else:
|
|
1742
|
else:
|
|
1743
|
samebranch = root.branch() == dest.branch()
|
|
1743
|
samebranch = root.branch() == dest.branch()
|
|
1744
|
if not collapse and samebranch and dest in root.parents():
|
|
1744
|
if not collapse and samebranch and dest in root.parents():
|
|
1745
|
# mark the revision as done by setting its new revision
|
|
1745
|
# mark the revision as done by setting its new revision
|
|
1746
|
# equal to its old (current) revisions
|
|
1746
|
# equal to its old (current) revisions
|
|
1747
|
state[root.rev()] = root.rev()
|
|
1747
|
state[root.rev()] = root.rev()
|
|
1748
|
repo.ui.debug('source is a child of destination\n')
|
|
1748
|
repo.ui.debug('source is a child of destination\n')
|
|
1749
|
continue
|
|
1749
|
continue
|
|
1750
|
|
|
1750
|
|
|
1751
|
emptyrebase = False
|
|
1751
|
emptyrebase = False
|
|
1752
|
repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
|
|
1752
|
repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
|
|
1753
|
if emptyrebase:
|
|
1753
|
if emptyrebase:
|
|
1754
|
return None
|
|
1754
|
return None
|
|
1755
|
for rev in sorted(state):
|
|
1755
|
for rev in sorted(state):
|
|
1756
|
parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
|
|
1756
|
parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
|
|
1757
|
# if all parents of this revision are done, then so is this revision
|
|
1757
|
# if all parents of this revision are done, then so is this revision
|
|
1758
|
if parents and all((state.get(p) == p for p in parents)):
|
|
1758
|
if parents and all((state.get(p) == p for p in parents)):
|
|
1759
|
state[rev] = rev
|
|
1759
|
state[rev] = rev
|
|
1760
|
return originalwd, destmap, state
|
|
1760
|
return originalwd, destmap, state
|
|
1761
|
|
|
1761
|
|
|
1762
|
def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None,
|
|
1762
|
def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None,
|
|
1763
|
keepf=False, fm=None, backup=True):
|
|
1763
|
keepf=False, fm=None, backup=True):
|
|
1764
|
"""dispose of rebased revision at the end of the rebase
|
|
1764
|
"""dispose of rebased revision at the end of the rebase
|
|
1765
|
|
|
1765
|
|
|
1766
|
If `collapsedas` is not None, the rebase was a collapse whose result if the
|
|
1766
|
If `collapsedas` is not None, the rebase was a collapse whose result if the
|
|
1767
|
`collapsedas` node.
|
|
1767
|
`collapsedas` node.
|
|
1768
|
|
|
1768
|
|
|
1769
|
If `keepf` is not True, the rebase has --keep set and no nodes should be
|
|
1769
|
If `keepf` is not True, the rebase has --keep set and no nodes should be
|
|
1770
|
removed (but bookmarks still need to be moved).
|
|
1770
|
removed (but bookmarks still need to be moved).
|
|
1771
|
|
|
1771
|
|
|
1772
|
If `backup` is False, no backup will be stored when stripping rebased
|
|
1772
|
If `backup` is False, no backup will be stored when stripping rebased
|
|
1773
|
revisions.
|
|
1773
|
revisions.
|
|
1774
|
"""
|
|
1774
|
"""
|
|
1775
|
tonode = repo.changelog.node
|
|
1775
|
tonode = repo.changelog.node
|
|
1776
|
replacements = {}
|
|
1776
|
replacements = {}
|
|
1777
|
moves = {}
|
|
1777
|
moves = {}
|
|
1778
|
stripcleanup = not obsolete.isenabled(repo, obsolete.createmarkersopt)
|
|
1778
|
stripcleanup = not obsolete.isenabled(repo, obsolete.createmarkersopt)
|
|
1779
|
|
|
1779
|
|
|
1780
|
collapsednodes = []
|
|
1780
|
collapsednodes = []
|
|
1781
|
for rev, newrev in sorted(state.items()):
|
|
1781
|
for rev, newrev in sorted(state.items()):
|
|
1782
|
if newrev >= 0 and newrev != rev:
|
|
1782
|
if newrev >= 0 and newrev != rev:
|
|
1783
|
oldnode = tonode(rev)
|
|
1783
|
oldnode = tonode(rev)
|
|
1784
|
newnode = collapsedas or tonode(newrev)
|
|
1784
|
newnode = collapsedas or tonode(newrev)
|
|
1785
|
moves[oldnode] = newnode
|
|
1785
|
moves[oldnode] = newnode
|
|
1786
|
if not keepf:
|
|
1786
|
if not keepf:
|
|
1787
|
succs = None
|
|
1787
|
succs = None
|
|
1788
|
if rev in skipped:
|
|
1788
|
if rev in skipped:
|
|
1789
|
if stripcleanup or not repo[rev].obsolete():
|
|
1789
|
if stripcleanup or not repo[rev].obsolete():
|
|
1790
|
succs = ()
|
|
1790
|
succs = ()
|
|
1791
|
elif collapsedas:
|
|
1791
|
elif collapsedas:
|
|
1792
|
collapsednodes.append(oldnode)
|
|
1792
|
collapsednodes.append(oldnode)
|
|
1793
|
else:
|
|
1793
|
else:
|
|
1794
|
succs = (newnode,)
|
|
1794
|
succs = (newnode,)
|
|
1795
|
if succs is not None:
|
|
1795
|
if succs is not None:
|
|
1796
|
replacements[(oldnode,)] = succs
|
|
1796
|
replacements[(oldnode,)] = succs
|
|
1797
|
if collapsednodes:
|
|
1797
|
if collapsednodes:
|
|
1798
|
replacements[tuple(collapsednodes)] = (collapsedas,)
|
|
1798
|
replacements[tuple(collapsednodes)] = (collapsedas,)
|
|
1799
|
scmutil.cleanupnodes(repo, replacements, 'rebase', moves, backup=backup)
|
|
1799
|
scmutil.cleanupnodes(repo, replacements, 'rebase', moves, backup=backup)
|
|
1800
|
if fm:
|
|
1800
|
if fm:
|
|
1801
|
hf = fm.hexfunc
|
|
1801
|
hf = fm.hexfunc
|
|
1802
|
fl = fm.formatlist
|
|
1802
|
fl = fm.formatlist
|
|
1803
|
fd = fm.formatdict
|
|
1803
|
fd = fm.formatdict
|
|
1804
|
changes = {}
|
|
1804
|
changes = {}
|
|
1805
|
for oldns, newn in replacements.iteritems():
|
|
1805
|
for oldns, newn in replacements.iteritems():
|
|
1806
|
for oldn in oldns:
|
|
1806
|
for oldn in oldns:
|
|
1807
|
changes[hf(oldn)] = fl([hf(n) for n in newn], name='node')
|
|
1807
|
changes[hf(oldn)] = fl([hf(n) for n in newn], name='node')
|
|
1808
|
nodechanges = fd(changes, key="oldnode", value="newnodes")
|
|
1808
|
nodechanges = fd(changes, key="oldnode", value="newnodes")
|
|
1809
|
fm.data(nodechanges=nodechanges)
|
|
1809
|
fm.data(nodechanges=nodechanges)
|
|
1810
|
|
|
1810
|
|
|
1811
|
def pullrebase(orig, ui, repo, *args, **opts):
|
|
1811
|
def pullrebase(orig, ui, repo, *args, **opts):
|
|
1812
|
'Call rebase after pull if the latter has been invoked with --rebase'
|
|
1812
|
'Call rebase after pull if the latter has been invoked with --rebase'
|
|
1813
|
if opts.get(r'rebase'):
|
|
1813
|
if opts.get(r'rebase'):
|
|
1814
|
if ui.configbool('commands', 'rebase.requiredest'):
|
|
1814
|
if ui.configbool('commands', 'rebase.requiredest'):
|
|
1815
|
msg = _('rebase destination required by configuration')
|
|
1815
|
msg = _('rebase destination required by configuration')
|
|
1816
|
hint = _('use hg pull followed by hg rebase -d DEST')
|
|
1816
|
hint = _('use hg pull followed by hg rebase -d DEST')
|
|
1817
|
raise error.Abort(msg, hint=hint)
|
|
1817
|
raise error.Abort(msg, hint=hint)
|
|
1818
|
|
|
1818
|
|
|
1819
|
with repo.wlock(), repo.lock():
|
|
1819
|
with repo.wlock(), repo.lock():
|
|
1820
|
if opts.get(r'update'):
|
|
1820
|
if opts.get(r'update'):
|
|
1821
|
del opts[r'update']
|
|
1821
|
del opts[r'update']
|
|
1822
|
ui.debug('--update and --rebase are not compatible, ignoring '
|
|
1822
|
ui.debug('--update and --rebase are not compatible, ignoring '
|
|
1823
|
'the update flag\n')
|
|
1823
|
'the update flag\n')
|
|
1824
|
|
|
1824
|
|
|
1825
|
cmdutil.checkunfinished(repo, skipmerge=True)
|
|
1825
|
cmdutil.checkunfinished(repo, skipmerge=True)
|
|
1826
|
cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
|
|
1826
|
cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
|
|
1827
|
'please commit or shelve your changes first'))
|
|
1827
|
'please commit or shelve your changes first'))
|
|
1828
|
|
|
1828
|
|
|
1829
|
revsprepull = len(repo)
|
|
1829
|
revsprepull = len(repo)
|
|
1830
|
origpostincoming = commands.postincoming
|
|
1830
|
origpostincoming = commands.postincoming
|
|
1831
|
def _dummy(*args, **kwargs):
|
|
1831
|
def _dummy(*args, **kwargs):
|
|
1832
|
pass
|
|
1832
|
pass
|
|
1833
|
commands.postincoming = _dummy
|
|
1833
|
commands.postincoming = _dummy
|
|
1834
|
try:
|
|
1834
|
try:
|
|
1835
|
ret = orig(ui, repo, *args, **opts)
|
|
1835
|
ret = orig(ui, repo, *args, **opts)
|
|
1836
|
finally:
|
|
1836
|
finally:
|
|
1837
|
commands.postincoming = origpostincoming
|
|
1837
|
commands.postincoming = origpostincoming
|
|
1838
|
revspostpull = len(repo)
|
|
1838
|
revspostpull = len(repo)
|
|
1839
|
if revspostpull > revsprepull:
|
|
1839
|
if revspostpull > revsprepull:
|
|
1840
|
# --rev option from pull conflict with rebase own --rev
|
|
1840
|
# --rev option from pull conflict with rebase own --rev
|
|
1841
|
# dropping it
|
|
1841
|
# dropping it
|
|
1842
|
if r'rev' in opts:
|
|
1842
|
if r'rev' in opts:
|
|
1843
|
del opts[r'rev']
|
|
1843
|
del opts[r'rev']
|
|
1844
|
# positional argument from pull conflicts with rebase's own
|
|
1844
|
# positional argument from pull conflicts with rebase's own
|
|
1845
|
# --source.
|
|
1845
|
# --source.
|
|
1846
|
if r'source' in opts:
|
|
1846
|
if r'source' in opts:
|
|
1847
|
del opts[r'source']
|
|
1847
|
del opts[r'source']
|
|
1848
|
# revsprepull is the len of the repo, not revnum of tip.
|
|
1848
|
# revsprepull is the len of the repo, not revnum of tip.
|
|
1849
|
destspace = list(repo.changelog.revs(start=revsprepull))
|
|
1849
|
destspace = list(repo.changelog.revs(start=revsprepull))
|
|
1850
|
opts[r'_destspace'] = destspace
|
|
1850
|
opts[r'_destspace'] = destspace
|
|
1851
|
try:
|
|
1851
|
try:
|
|
1852
|
rebase(ui, repo, **opts)
|
|
1852
|
rebase(ui, repo, **opts)
|
|
1853
|
except error.NoMergeDestAbort:
|
|
1853
|
except error.NoMergeDestAbort:
|
|
1854
|
# we can maybe update instead
|
|
1854
|
# we can maybe update instead
|
|
1855
|
rev, _a, _b = destutil.destupdate(repo)
|
|
1855
|
rev, _a, _b = destutil.destupdate(repo)
|
|
1856
|
if rev == repo['.'].rev():
|
|
1856
|
if rev == repo['.'].rev():
|
|
1857
|
ui.status(_('nothing to rebase\n'))
|
|
1857
|
ui.status(_('nothing to rebase\n'))
|
|
1858
|
else:
|
|
1858
|
else:
|
|
1859
|
ui.status(_('nothing to rebase - updating instead\n'))
|
|
1859
|
ui.status(_('nothing to rebase - updating instead\n'))
|
|
1860
|
# not passing argument to get the bare update behavior
|
|
1860
|
# not passing argument to get the bare update behavior
|
|
1861
|
# with warning and trumpets
|
|
1861
|
# with warning and trumpets
|
|
1862
|
commands.update(ui, repo)
|
|
1862
|
commands.update(ui, repo)
|
|
1863
|
else:
|
|
1863
|
else:
|
|
1864
|
if opts.get(r'tool'):
|
|
1864
|
if opts.get(r'tool'):
|
|
1865
|
raise error.Abort(_('--tool can only be used with --rebase'))
|
|
1865
|
raise error.Abort(_('--tool can only be used with --rebase'))
|
|
1866
|
ret = orig(ui, repo, *args, **opts)
|
|
1866
|
ret = orig(ui, repo, *args, **opts)
|
|
1867
|
|
|
1867
|
|
|
1868
|
return ret
|
|
1868
|
return ret
|
|
1869
|
|
|
1869
|
|
|
1870
|
def _filterobsoleterevs(repo, revs):
|
|
1870
|
def _filterobsoleterevs(repo, revs):
|
|
1871
|
"""returns a set of the obsolete revisions in revs"""
|
|
1871
|
"""returns a set of the obsolete revisions in revs"""
|
|
1872
|
return set(r for r in revs if repo[r].obsolete())
|
|
1872
|
return set(r for r in revs if repo[r].obsolete())
|
|
1873
|
|
|
1873
|
|
|
1874
|
def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
|
|
1874
|
def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
|
|
1875
|
"""Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
|
|
1875
|
"""Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
|
|
1876
|
|
|
1876
|
|
|
1877
|
`obsoletenotrebased` is a mapping mapping obsolete => successor for all
|
|
1877
|
`obsoletenotrebased` is a mapping mapping obsolete => successor for all
|
|
1878
|
obsolete nodes to be rebased given in `rebaseobsrevs`.
|
|
1878
|
obsolete nodes to be rebased given in `rebaseobsrevs`.
|
|
1879
|
|
|
1879
|
|
|
1880
|
`obsoletewithoutsuccessorindestination` is a set with obsolete revisions
|
|
1880
|
`obsoletewithoutsuccessorindestination` is a set with obsolete revisions
|
|
1881
|
without a successor in destination.
|
|
1881
|
without a successor in destination.
|
|
1882
|
|
|
1882
|
|
|
1883
|
`obsoleteextinctsuccessors` is a set of obsolete revisions with only
|
|
1883
|
`obsoleteextinctsuccessors` is a set of obsolete revisions with only
|
|
1884
|
obsolete successors.
|
|
1884
|
obsolete successors.
|
|
1885
|
"""
|
|
1885
|
"""
|
|
1886
|
obsoletenotrebased = {}
|
|
1886
|
obsoletenotrebased = {}
|
|
1887
|
obsoletewithoutsuccessorindestination = set()
|
|
1887
|
obsoletewithoutsuccessorindestination = set()
|
|
1888
|
obsoleteextinctsuccessors = set()
|
|
1888
|
obsoleteextinctsuccessors = set()
|
|
1889
|
|
|
1889
|
|
|
1890
|
assert repo.filtername is None
|
|
1890
|
assert repo.filtername is None
|
|
1891
|
cl = repo.changelog
|
|
1891
|
cl = repo.changelog
|
|
1892
|
nodemap = cl.nodemap
|
|
1892
|
nodemap = cl.nodemap
|
|
1893
|
extinctrevs = set(repo.revs('extinct()'))
|
|
1893
|
extinctrevs = set(repo.revs('extinct()'))
|
|
1894
|
for srcrev in rebaseobsrevs:
|
|
1894
|
for srcrev in rebaseobsrevs:
|
|
1895
|
srcnode = cl.node(srcrev)
|
|
1895
|
srcnode = cl.node(srcrev)
|
|
1896
|
# XXX: more advanced APIs are required to handle split correctly
|
|
1896
|
# XXX: more advanced APIs are required to handle split correctly
|
|
1897
|
successors = set(obsutil.allsuccessors(repo.obsstore, [srcnode]))
|
|
1897
|
successors = set(obsutil.allsuccessors(repo.obsstore, [srcnode]))
|
|
1898
|
# obsutil.allsuccessors includes node itself
|
|
1898
|
# obsutil.allsuccessors includes node itself
|
|
1899
|
successors.remove(srcnode)
|
|
1899
|
successors.remove(srcnode)
|
|
1900
|
succrevs = {nodemap[s] for s in successors if s in nodemap}
|
|
1900
|
succrevs = {nodemap[s] for s in successors if s in nodemap}
|
|
1901
|
if succrevs.issubset(extinctrevs):
|
|
1901
|
if succrevs.issubset(extinctrevs):
|
|
1902
|
# all successors are extinct
|
|
1902
|
# all successors are extinct
|
|
1903
|
obsoleteextinctsuccessors.add(srcrev)
|
|
1903
|
obsoleteextinctsuccessors.add(srcrev)
|
|
1904
|
if not successors:
|
|
1904
|
if not successors:
|
|
1905
|
# no successor
|
|
1905
|
# no successor
|
|
1906
|
obsoletenotrebased[srcrev] = None
|
|
1906
|
obsoletenotrebased[srcrev] = None
|
|
1907
|
else:
|
|
1907
|
else:
|
|
1908
|
dstrev = destmap[srcrev]
|
|
1908
|
dstrev = destmap[srcrev]
|
|
1909
|
for succrev in succrevs:
|
|
1909
|
for succrev in succrevs:
|
|
1910
|
if cl.isancestorrev(succrev, dstrev):
|
|
1910
|
if cl.isancestorrev(succrev, dstrev):
|
|
1911
|
obsoletenotrebased[srcrev] = succrev
|
|
1911
|
obsoletenotrebased[srcrev] = succrev
|
|
1912
|
break
|
|
1912
|
break
|
|
1913
|
else:
|
|
1913
|
else:
|
|
1914
|
# If 'srcrev' has a successor in rebase set but none in
|
|
1914
|
# If 'srcrev' has a successor in rebase set but none in
|
|
1915
|
# destination (which would be catched above), we shall skip it
|
|
1915
|
# destination (which would be catched above), we shall skip it
|
|
1916
|
# and its descendants to avoid divergence.
|
|
1916
|
# and its descendants to avoid divergence.
|
|
1917
|
if srcrev in extinctrevs or any(s in destmap for s in succrevs):
|
|
1917
|
if srcrev in extinctrevs or any(s in destmap for s in succrevs):
|
|
1918
|
obsoletewithoutsuccessorindestination.add(srcrev)
|
|
1918
|
obsoletewithoutsuccessorindestination.add(srcrev)
|
|
1919
|
|
|
1919
|
|
|
1920
|
return (
|
|
1920
|
return (
|
|
1921
|
obsoletenotrebased,
|
|
1921
|
obsoletenotrebased,
|
|
1922
|
obsoletewithoutsuccessorindestination,
|
|
1922
|
obsoletewithoutsuccessorindestination,
|
|
1923
|
obsoleteextinctsuccessors,
|
|
1923
|
obsoleteextinctsuccessors,
|
|
1924
|
)
|
|
1924
|
)
|
|
1925
|
|
|
1925
|
|
|
1926
|
def abortrebase(ui, repo):
|
|
1926
|
def abortrebase(ui, repo):
|
|
1927
|
with repo.wlock(), repo.lock():
|
|
1927
|
with repo.wlock(), repo.lock():
|
|
1928
|
rbsrt = rebaseruntime(repo, ui)
|
|
1928
|
rbsrt = rebaseruntime(repo, ui)
|
|
1929
|
rbsrt._prepareabortorcontinue(isabort=True)
|
|
1929
|
rbsrt._prepareabortorcontinue(isabort=True)
|
|
1930
|
|
|
1930
|
|
|
|
|
|
1931
|
def continuerebase(ui, repo):
|
|
|
|
|
1932
|
with repo.wlock(), repo.lock():
|
|
|
|
|
1933
|
rbsrt = rebaseruntime(repo, ui)
|
|
|
|
|
1934
|
ms = mergemod.mergestate.read(repo)
|
|
|
|
|
1935
|
mergeutil.checkunresolved(ms)
|
|
|
|
|
1936
|
retcode = rbsrt._prepareabortorcontinue(isabort=False)
|
|
|
|
|
1937
|
if retcode is not None:
|
|
|
|
|
1938
|
return retcode
|
|
|
|
|
1939
|
rbsrt._performrebase(None)
|
|
|
|
|
1940
|
rbsrt._finishrebase()
|
|
|
|
|
1941
|
|
|
1931
|
def summaryhook(ui, repo):
|
|
1942
|
def summaryhook(ui, repo):
|
|
1932
|
if not repo.vfs.exists('rebasestate'):
|
|
1943
|
if not repo.vfs.exists('rebasestate'):
|
|
1933
|
return
|
|
1944
|
return
|
|
1934
|
try:
|
|
1945
|
try:
|
|
1935
|
rbsrt = rebaseruntime(repo, ui, {})
|
|
1946
|
rbsrt = rebaseruntime(repo, ui, {})
|
|
1936
|
rbsrt.restorestatus()
|
|
1947
|
rbsrt.restorestatus()
|
|
1937
|
state = rbsrt.state
|
|
1948
|
state = rbsrt.state
|
|
1938
|
except error.RepoLookupError:
|
|
1949
|
except error.RepoLookupError:
|
|
1939
|
# i18n: column positioning for "hg summary"
|
|
1950
|
# i18n: column positioning for "hg summary"
|
|
1940
|
msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
|
|
1951
|
msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
|
|
1941
|
ui.write(msg)
|
|
1952
|
ui.write(msg)
|
|
1942
|
return
|
|
1953
|
return
|
|
1943
|
numrebased = len([i for i in state.itervalues() if i >= 0])
|
|
1954
|
numrebased = len([i for i in state.itervalues() if i >= 0])
|
|
1944
|
# i18n: column positioning for "hg summary"
|
|
1955
|
# i18n: column positioning for "hg summary"
|
|
1945
|
ui.write(_('rebase: %s, %s (rebase --continue)\n') %
|
|
1956
|
ui.write(_('rebase: %s, %s (rebase --continue)\n') %
|
|
1946
|
(ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
|
|
1957
|
(ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
|
|
1947
|
ui.label(_('%d remaining'), 'rebase.remaining') %
|
|
1958
|
ui.label(_('%d remaining'), 'rebase.remaining') %
|
|
1948
|
(len(state) - numrebased)))
|
|
1959
|
(len(state) - numrebased)))
|
|
1949
|
|
|
1960
|
|
|
1950
|
def uisetup(ui):
|
|
1961
|
def uisetup(ui):
|
|
1951
|
#Replace pull with a decorator to provide --rebase option
|
|
1962
|
#Replace pull with a decorator to provide --rebase option
|
|
1952
|
entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
|
|
1963
|
entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
|
|
1953
|
entry[1].append(('', 'rebase', None,
|
|
1964
|
entry[1].append(('', 'rebase', None,
|
|
1954
|
_("rebase working directory to branch head")))
|
|
1965
|
_("rebase working directory to branch head")))
|
|
1955
|
entry[1].append(('t', 'tool', '',
|
|
1966
|
entry[1].append(('t', 'tool', '',
|
|
1956
|
_("specify merge tool for rebase")))
|
|
1967
|
_("specify merge tool for rebase")))
|
|
1957
|
cmdutil.summaryhooks.add('rebase', summaryhook)
|
|
1968
|
cmdutil.summaryhooks.add('rebase', summaryhook)
|
|
1958
|
statemod.addunfinished('rebase', fname='rebasestate', stopflag=True,
|
|
1969
|
statemod.addunfinished('rebase', fname='rebasestate', stopflag=True,
|
|
1959
|
continueflag=True, abortfunc=abortrebase)
|
|
1970
|
continueflag=True, abortfunc=abortrebase,
|
|
|
|
|
1971
|
continuefunc=continuerebase)
|