##// END OF EJS Templates
rebase: support "history-editing-backup" config option...
Sushil khanchi -
r38835:2002c193 default
parent child Browse files
Show More
@@ -0,0 +1,150 b''
1 $ cat << EOF >> $HGRCPATH
2 > [extensions]
3 > rebase=
4 > EOF
5
6 ==========================================
7 Test history-editing-backup config option |
8 ==========================================
9 Test with Pre-obsmarker rebase:
10 1) When config option is not set:
11 $ hg init repo1
12 $ cd repo1
13 $ echo a>a
14 $ hg ci -qAma
15 $ echo b>b
16 $ hg ci -qAmb
17 $ echo c>c
18 $ hg ci -qAmc
19 $ hg up 0 -q
20 $ echo d>d
21 $ hg ci -qAmd
22 $ echo e>e
23 $ hg ci -qAme
24 $ hg log -GT "{rev}: {firstline(desc)}\n"
25 @ 4: e
26 |
27 o 3: d
28 |
29 | o 2: c
30 | |
31 | o 1: b
32 |/
33 o 0: a
34
35 $ hg rebase -s 1 -d .
36 rebasing 1:d2ae7f538514 "b"
37 rebasing 2:177f92b77385 "c"
38 saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/d2ae7f538514-c7ed7a78-rebase.hg
39 $ hg log -GT "{rev}: {firstline(desc)}\n"
40 o 4: c
41 |
42 o 3: b
43 |
44 @ 2: e
45 |
46 o 1: d
47 |
48 o 0: a
49
50
51 2) When config option is set:
52 $ cat << EOF >> $HGRCPATH
53 > [ui]
54 > history-editing-backup = False
55 > EOF
56
57 $ echo f>f
58 $ hg ci -Aqmf
59 $ echo g>g
60 $ hg ci -Aqmg
61 $ hg log -GT "{rev}: {firstline(desc)}\n"
62 @ 6: g
63 |
64 o 5: f
65 |
66 | o 4: c
67 | |
68 | o 3: b
69 |/
70 o 2: e
71 |
72 o 1: d
73 |
74 o 0: a
75
76 $ hg rebase -s 3 -d .
77 rebasing 3:05bff2a95b12 "b"
78 rebasing 4:1762bde4404d "c"
79
80 $ hg log -GT "{rev}: {firstline(desc)}\n"
81 o 6: c
82 |
83 o 5: b
84 |
85 @ 4: g
86 |
87 o 3: f
88 |
89 o 2: e
90 |
91 o 1: d
92 |
93 o 0: a
94
95 Test when rebased revisions are stripped during abort:
96 ======================================================
97
98 $ echo conflict > c
99 $ hg ci -Am "conflict with c"
100 adding c
101 created new head
102 $ hg log -GT "{rev}: {firstline(desc)}\n"
103 @ 7: conflict with c
104 |
105 | o 6: c
106 | |
107 | o 5: b
108 |/
109 o 4: g
110 |
111 o 3: f
112 |
113 o 2: e
114 |
115 o 1: d
116 |
117 o 0: a
118
119 When history-editing-backup = True:
120 $ cat << EOF >> $HGRCPATH
121 > [ui]
122 > history-editing-backup = True
123 > EOF
124 $ hg rebase -s 5 -d .
125 rebasing 5:1f8148a544ee "b"
126 rebasing 6:f8bc7d28e573 "c"
127 merging c
128 warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
129 unresolved conflicts (see hg resolve, then hg rebase --continue)
130 [1]
131 $ hg rebase --abort
132 saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/818c1a43c916-2b644d96-backup.hg
133 rebase aborted
134
135 When history-editing-backup = False:
136 $ cat << EOF >> $HGRCPATH
137 > [ui]
138 > history-editing-backup = False
139 > EOF
140 $ hg rebase -s 5 -d .
141 rebasing 5:1f8148a544ee "b"
142 rebasing 6:f8bc7d28e573 "c"
143 merging c
144 warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
145 unresolved conflicts (see hg resolve, then hg rebase --continue)
146 [1]
147 $ hg rebase --abort
148 rebase aborted
149 $ cd ..
150
@@ -1,1911 +1,1922 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 """automatic rebase destination for a single orphan revision"""
111 """automatic rebase destination for a single orphan revision"""
112 unfi = repo.unfiltered()
112 unfi = repo.unfiltered()
113 obsoleted = unfi.revs('obsolete()')
113 obsoleted = unfi.revs('obsolete()')
114
114
115 src = revset.getset(repo, subset, x).first()
115 src = revset.getset(repo, subset, x).first()
116
116
117 # Empty src or already obsoleted - Do not return a destination
117 # Empty src or already obsoleted - Do not return a destination
118 if not src or src in obsoleted:
118 if not src or src in obsoleted:
119 return smartset.baseset()
119 return smartset.baseset()
120 dests = destutil.orphanpossibledestination(repo, src)
120 dests = destutil.orphanpossibledestination(repo, src)
121 if len(dests) > 1:
121 if len(dests) > 1:
122 raise error.Abort(
122 raise error.Abort(
123 _("ambiguous automatic rebase: %r could end up on any of %r") % (
123 _("ambiguous automatic rebase: %r could end up on any of %r") % (
124 src, dests))
124 src, dests))
125 # We have zero or one destination, so we can just return here.
125 # We have zero or one destination, so we can just return here.
126 return smartset.baseset(dests)
126 return smartset.baseset(dests)
127
127
128 def _ctxdesc(ctx):
128 def _ctxdesc(ctx):
129 """short description for a context"""
129 """short description for a context"""
130 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
130 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
131 ctx.description().split('\n', 1)[0])
131 ctx.description().split('\n', 1)[0])
132 repo = ctx.repo()
132 repo = ctx.repo()
133 names = []
133 names = []
134 for nsname, ns in repo.names.iteritems():
134 for nsname, ns in repo.names.iteritems():
135 if nsname == 'branches':
135 if nsname == 'branches':
136 continue
136 continue
137 names.extend(ns.names(repo, ctx.node()))
137 names.extend(ns.names(repo, ctx.node()))
138 if names:
138 if names:
139 desc += ' (%s)' % ' '.join(names)
139 desc += ' (%s)' % ' '.join(names)
140 return desc
140 return desc
141
141
142 class rebaseruntime(object):
142 class rebaseruntime(object):
143 """This class is a container for rebase runtime state"""
143 """This class is a container for rebase runtime state"""
144 def __init__(self, repo, ui, inmemory=False, opts=None):
144 def __init__(self, repo, ui, inmemory=False, opts=None):
145 if opts is None:
145 if opts is None:
146 opts = {}
146 opts = {}
147
147
148 # prepared: whether we have rebasestate prepared or not. Currently it
148 # prepared: whether we have rebasestate prepared or not. Currently it
149 # decides whether "self.repo" is unfiltered or not.
149 # decides whether "self.repo" is unfiltered or not.
150 # The rebasestate has explicit hash to hash instructions not depending
150 # The rebasestate has explicit hash to hash instructions not depending
151 # on visibility. If rebasestate exists (in-memory or on-disk), use
151 # on visibility. If rebasestate exists (in-memory or on-disk), use
152 # unfiltered repo to avoid visibility issues.
152 # unfiltered repo to avoid visibility issues.
153 # Before knowing rebasestate (i.e. when starting a new rebase (not
153 # Before knowing rebasestate (i.e. when starting a new rebase (not
154 # --continue or --abort)), the original repo should be used so
154 # --continue or --abort)), the original repo should be used so
155 # visibility-dependent revsets are correct.
155 # visibility-dependent revsets are correct.
156 self.prepared = False
156 self.prepared = False
157 self._repo = repo
157 self._repo = repo
158
158
159 self.ui = ui
159 self.ui = ui
160 self.opts = opts
160 self.opts = opts
161 self.originalwd = None
161 self.originalwd = None
162 self.external = nullrev
162 self.external = nullrev
163 # Mapping between the old revision id and either what is the new rebased
163 # Mapping between the old revision id and either what is the new rebased
164 # revision or what needs to be done with the old revision. The state
164 # revision or what needs to be done with the old revision. The state
165 # dict will be what contains most of the rebase progress state.
165 # dict will be what contains most of the rebase progress state.
166 self.state = {}
166 self.state = {}
167 self.activebookmark = None
167 self.activebookmark = None
168 self.destmap = {}
168 self.destmap = {}
169 self.skipped = set()
169 self.skipped = set()
170
170
171 self.collapsef = opts.get('collapse', False)
171 self.collapsef = opts.get('collapse', False)
172 self.collapsemsg = cmdutil.logmessage(ui, opts)
172 self.collapsemsg = cmdutil.logmessage(ui, opts)
173 self.date = opts.get('date', None)
173 self.date = opts.get('date', None)
174
174
175 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
175 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
176 self.extrafns = [_savegraft]
176 self.extrafns = [_savegraft]
177 if e:
177 if e:
178 self.extrafns = [e]
178 self.extrafns = [e]
179
179
180 self.keepf = opts.get('keep', False)
180 self.keepf = opts.get('keep', False)
181 self.keepbranchesf = opts.get('keepbranches', False)
181 self.keepbranchesf = opts.get('keepbranches', False)
182 self.obsoletenotrebased = {}
182 self.obsoletenotrebased = {}
183 self.obsoletewithoutsuccessorindestination = set()
183 self.obsoletewithoutsuccessorindestination = set()
184 self.inmemory = inmemory
184 self.inmemory = inmemory
185 self.stateobj = statemod.cmdstate(repo, 'rebasestate')
185 self.stateobj = statemod.cmdstate(repo, 'rebasestate')
186
186
187 @property
187 @property
188 def repo(self):
188 def repo(self):
189 if self.prepared:
189 if self.prepared:
190 return self._repo.unfiltered()
190 return self._repo.unfiltered()
191 else:
191 else:
192 return self._repo
192 return self._repo
193
193
194 def storestatus(self, tr=None):
194 def storestatus(self, tr=None):
195 """Store the current status to allow recovery"""
195 """Store the current status to allow recovery"""
196 if tr:
196 if tr:
197 tr.addfilegenerator('rebasestate', ('rebasestate',),
197 tr.addfilegenerator('rebasestate', ('rebasestate',),
198 self._writestatus, location='plain')
198 self._writestatus, location='plain')
199 else:
199 else:
200 with self.repo.vfs("rebasestate", "w") as f:
200 with self.repo.vfs("rebasestate", "w") as f:
201 self._writestatus(f)
201 self._writestatus(f)
202
202
203 def _writestatus(self, f):
203 def _writestatus(self, f):
204 repo = self.repo
204 repo = self.repo
205 assert repo.filtername is None
205 assert repo.filtername is None
206 f.write(repo[self.originalwd].hex() + '\n')
206 f.write(repo[self.originalwd].hex() + '\n')
207 # was "dest". we now write dest per src root below.
207 # was "dest". we now write dest per src root below.
208 f.write('\n')
208 f.write('\n')
209 f.write(repo[self.external].hex() + '\n')
209 f.write(repo[self.external].hex() + '\n')
210 f.write('%d\n' % int(self.collapsef))
210 f.write('%d\n' % int(self.collapsef))
211 f.write('%d\n' % int(self.keepf))
211 f.write('%d\n' % int(self.keepf))
212 f.write('%d\n' % int(self.keepbranchesf))
212 f.write('%d\n' % int(self.keepbranchesf))
213 f.write('%s\n' % (self.activebookmark or ''))
213 f.write('%s\n' % (self.activebookmark or ''))
214 destmap = self.destmap
214 destmap = self.destmap
215 for d, v in self.state.iteritems():
215 for d, v in self.state.iteritems():
216 oldrev = repo[d].hex()
216 oldrev = repo[d].hex()
217 if v >= 0:
217 if v >= 0:
218 newrev = repo[v].hex()
218 newrev = repo[v].hex()
219 else:
219 else:
220 newrev = "%d" % v
220 newrev = "%d" % v
221 destnode = repo[destmap[d]].hex()
221 destnode = repo[destmap[d]].hex()
222 f.write("%s:%s:%s\n" % (oldrev, newrev, destnode))
222 f.write("%s:%s:%s\n" % (oldrev, newrev, destnode))
223 repo.ui.debug('rebase status stored\n')
223 repo.ui.debug('rebase status stored\n')
224
224
225 def restorestatus(self):
225 def restorestatus(self):
226 """Restore a previously stored status"""
226 """Restore a previously stored status"""
227 if not self.stateobj.exists():
227 if not self.stateobj.exists():
228 cmdutil.wrongtooltocontinue(self.repo, _('rebase'))
228 cmdutil.wrongtooltocontinue(self.repo, _('rebase'))
229
229
230 data = self._read()
230 data = self._read()
231 self.repo.ui.debug('rebase status resumed\n')
231 self.repo.ui.debug('rebase status resumed\n')
232
232
233 self.originalwd = data['originalwd']
233 self.originalwd = data['originalwd']
234 self.destmap = data['destmap']
234 self.destmap = data['destmap']
235 self.state = data['state']
235 self.state = data['state']
236 self.skipped = data['skipped']
236 self.skipped = data['skipped']
237 self.collapsef = data['collapse']
237 self.collapsef = data['collapse']
238 self.keepf = data['keep']
238 self.keepf = data['keep']
239 self.keepbranchesf = data['keepbranches']
239 self.keepbranchesf = data['keepbranches']
240 self.external = data['external']
240 self.external = data['external']
241 self.activebookmark = data['activebookmark']
241 self.activebookmark = data['activebookmark']
242
242
243 def _read(self):
243 def _read(self):
244 self.prepared = True
244 self.prepared = True
245 repo = self.repo
245 repo = self.repo
246 assert repo.filtername is None
246 assert repo.filtername is None
247 data = {'keepbranches': None, 'collapse': None, 'activebookmark': None,
247 data = {'keepbranches': None, 'collapse': None, 'activebookmark': None,
248 'external': nullrev, 'keep': None, 'originalwd': None}
248 'external': nullrev, 'keep': None, 'originalwd': None}
249 legacydest = None
249 legacydest = None
250 state = {}
250 state = {}
251 destmap = {}
251 destmap = {}
252
252
253 if True:
253 if True:
254 f = repo.vfs("rebasestate")
254 f = repo.vfs("rebasestate")
255 for i, l in enumerate(f.read().splitlines()):
255 for i, l in enumerate(f.read().splitlines()):
256 if i == 0:
256 if i == 0:
257 data['originalwd'] = repo[l].rev()
257 data['originalwd'] = repo[l].rev()
258 elif i == 1:
258 elif i == 1:
259 # this line should be empty in newer version. but legacy
259 # this line should be empty in newer version. but legacy
260 # clients may still use it
260 # clients may still use it
261 if l:
261 if l:
262 legacydest = repo[l].rev()
262 legacydest = repo[l].rev()
263 elif i == 2:
263 elif i == 2:
264 data['external'] = repo[l].rev()
264 data['external'] = repo[l].rev()
265 elif i == 3:
265 elif i == 3:
266 data['collapse'] = bool(int(l))
266 data['collapse'] = bool(int(l))
267 elif i == 4:
267 elif i == 4:
268 data['keep'] = bool(int(l))
268 data['keep'] = bool(int(l))
269 elif i == 5:
269 elif i == 5:
270 data['keepbranches'] = bool(int(l))
270 data['keepbranches'] = bool(int(l))
271 elif i == 6 and not (len(l) == 81 and ':' in l):
271 elif i == 6 and not (len(l) == 81 and ':' in l):
272 # line 6 is a recent addition, so for backwards
272 # line 6 is a recent addition, so for backwards
273 # compatibility check that the line doesn't look like the
273 # compatibility check that the line doesn't look like the
274 # oldrev:newrev lines
274 # oldrev:newrev lines
275 data['activebookmark'] = l
275 data['activebookmark'] = l
276 else:
276 else:
277 args = l.split(':')
277 args = l.split(':')
278 oldrev = repo[args[0]].rev()
278 oldrev = repo[args[0]].rev()
279 newrev = args[1]
279 newrev = args[1]
280 if newrev in legacystates:
280 if newrev in legacystates:
281 continue
281 continue
282 if len(args) > 2:
282 if len(args) > 2:
283 destrev = repo[args[2]].rev()
283 destrev = repo[args[2]].rev()
284 else:
284 else:
285 destrev = legacydest
285 destrev = legacydest
286 destmap[oldrev] = destrev
286 destmap[oldrev] = destrev
287 if newrev == revtodostr:
287 if newrev == revtodostr:
288 state[oldrev] = revtodo
288 state[oldrev] = revtodo
289 # Legacy compat special case
289 # Legacy compat special case
290 else:
290 else:
291 state[oldrev] = repo[newrev].rev()
291 state[oldrev] = repo[newrev].rev()
292
292
293 if data['keepbranches'] is None:
293 if data['keepbranches'] is None:
294 raise error.Abort(_('.hg/rebasestate is incomplete'))
294 raise error.Abort(_('.hg/rebasestate is incomplete'))
295
295
296 data['destmap'] = destmap
296 data['destmap'] = destmap
297 data['state'] = state
297 data['state'] = state
298 skipped = set()
298 skipped = set()
299 # recompute the set of skipped revs
299 # recompute the set of skipped revs
300 if not data['collapse']:
300 if not data['collapse']:
301 seen = set(destmap.values())
301 seen = set(destmap.values())
302 for old, new in sorted(state.items()):
302 for old, new in sorted(state.items()):
303 if new != revtodo and new in seen:
303 if new != revtodo and new in seen:
304 skipped.add(old)
304 skipped.add(old)
305 seen.add(new)
305 seen.add(new)
306 data['skipped'] = skipped
306 data['skipped'] = skipped
307 repo.ui.debug('computed skipped revs: %s\n' %
307 repo.ui.debug('computed skipped revs: %s\n' %
308 (' '.join('%d' % r for r in sorted(skipped)) or ''))
308 (' '.join('%d' % r for r in sorted(skipped)) or ''))
309
309
310 return data
310 return data
311
311
312 def _handleskippingobsolete(self, obsoleterevs, destmap):
312 def _handleskippingobsolete(self, obsoleterevs, destmap):
313 """Compute structures necessary for skipping obsolete revisions
313 """Compute structures necessary for skipping obsolete revisions
314
314
315 obsoleterevs: iterable of all obsolete revisions in rebaseset
315 obsoleterevs: iterable of all obsolete revisions in rebaseset
316 destmap: {srcrev: destrev} destination revisions
316 destmap: {srcrev: destrev} destination revisions
317 """
317 """
318 self.obsoletenotrebased = {}
318 self.obsoletenotrebased = {}
319 if not self.ui.configbool('experimental', 'rebaseskipobsolete'):
319 if not self.ui.configbool('experimental', 'rebaseskipobsolete'):
320 return
320 return
321 obsoleteset = set(obsoleterevs)
321 obsoleteset = set(obsoleterevs)
322 (self.obsoletenotrebased,
322 (self.obsoletenotrebased,
323 self.obsoletewithoutsuccessorindestination,
323 self.obsoletewithoutsuccessorindestination,
324 obsoleteextinctsuccessors) = _computeobsoletenotrebased(
324 obsoleteextinctsuccessors) = _computeobsoletenotrebased(
325 self.repo, obsoleteset, destmap)
325 self.repo, obsoleteset, destmap)
326 skippedset = set(self.obsoletenotrebased)
326 skippedset = set(self.obsoletenotrebased)
327 skippedset.update(self.obsoletewithoutsuccessorindestination)
327 skippedset.update(self.obsoletewithoutsuccessorindestination)
328 skippedset.update(obsoleteextinctsuccessors)
328 skippedset.update(obsoleteextinctsuccessors)
329 _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
329 _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
330
330
331 def _prepareabortorcontinue(self, isabort, backup=True, suppwarns=False):
331 def _prepareabortorcontinue(self, isabort, backup=True, suppwarns=False):
332 try:
332 try:
333 self.restorestatus()
333 self.restorestatus()
334 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
334 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
335 except error.RepoLookupError:
335 except error.RepoLookupError:
336 if isabort:
336 if isabort:
337 clearstatus(self.repo)
337 clearstatus(self.repo)
338 clearcollapsemsg(self.repo)
338 clearcollapsemsg(self.repo)
339 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
339 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
340 ' only broken state is cleared)\n'))
340 ' only broken state is cleared)\n'))
341 return 0
341 return 0
342 else:
342 else:
343 msg = _('cannot continue inconsistent rebase')
343 msg = _('cannot continue inconsistent rebase')
344 hint = _('use "hg rebase --abort" to clear broken state')
344 hint = _('use "hg rebase --abort" to clear broken state')
345 raise error.Abort(msg, hint=hint)
345 raise error.Abort(msg, hint=hint)
346 if isabort:
346 if isabort:
347 return abort(self.repo, self.originalwd, self.destmap, self.state,
347 return abort(self.repo, self.originalwd, self.destmap, self.state,
348 activebookmark=self.activebookmark, backup=backup,
348 activebookmark=self.activebookmark, backup=backup,
349 suppwarns=suppwarns)
349 suppwarns=suppwarns)
350
350
351 def _preparenewrebase(self, destmap):
351 def _preparenewrebase(self, destmap):
352 if not destmap:
352 if not destmap:
353 return _nothingtorebase()
353 return _nothingtorebase()
354
354
355 rebaseset = destmap.keys()
355 rebaseset = destmap.keys()
356 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
356 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
357 if (not (self.keepf or allowunstable)
357 if (not (self.keepf or allowunstable)
358 and self.repo.revs('first(children(%ld) - %ld)',
358 and self.repo.revs('first(children(%ld) - %ld)',
359 rebaseset, rebaseset)):
359 rebaseset, rebaseset)):
360 raise error.Abort(
360 raise error.Abort(
361 _("can't remove original changesets with"
361 _("can't remove original changesets with"
362 " unrebased descendants"),
362 " unrebased descendants"),
363 hint=_('use --keep to keep original changesets'))
363 hint=_('use --keep to keep original changesets'))
364
364
365 result = buildstate(self.repo, destmap, self.collapsef)
365 result = buildstate(self.repo, destmap, self.collapsef)
366
366
367 if not result:
367 if not result:
368 # Empty state built, nothing to rebase
368 # Empty state built, nothing to rebase
369 self.ui.status(_('nothing to rebase\n'))
369 self.ui.status(_('nothing to rebase\n'))
370 return _nothingtorebase()
370 return _nothingtorebase()
371
371
372 for root in self.repo.set('roots(%ld)', rebaseset):
372 for root in self.repo.set('roots(%ld)', rebaseset):
373 if not self.keepf and not root.mutable():
373 if not self.keepf and not root.mutable():
374 raise error.Abort(_("can't rebase public changeset %s")
374 raise error.Abort(_("can't rebase public changeset %s")
375 % root,
375 % root,
376 hint=_("see 'hg help phases' for details"))
376 hint=_("see 'hg help phases' for details"))
377
377
378 (self.originalwd, self.destmap, self.state) = result
378 (self.originalwd, self.destmap, self.state) = result
379 if self.collapsef:
379 if self.collapsef:
380 dests = set(self.destmap.values())
380 dests = set(self.destmap.values())
381 if len(dests) != 1:
381 if len(dests) != 1:
382 raise error.Abort(
382 raise error.Abort(
383 _('--collapse does not work with multiple destinations'))
383 _('--collapse does not work with multiple destinations'))
384 destrev = next(iter(dests))
384 destrev = next(iter(dests))
385 destancestors = self.repo.changelog.ancestors([destrev],
385 destancestors = self.repo.changelog.ancestors([destrev],
386 inclusive=True)
386 inclusive=True)
387 self.external = externalparent(self.repo, self.state, destancestors)
387 self.external = externalparent(self.repo, self.state, destancestors)
388
388
389 for destrev in sorted(set(destmap.values())):
389 for destrev in sorted(set(destmap.values())):
390 dest = self.repo[destrev]
390 dest = self.repo[destrev]
391 if dest.closesbranch() and not self.keepbranchesf:
391 if dest.closesbranch() and not self.keepbranchesf:
392 self.ui.status(_('reopening closed branch head %s\n') % dest)
392 self.ui.status(_('reopening closed branch head %s\n') % dest)
393
393
394 self.prepared = True
394 self.prepared = True
395
395
396 def _assignworkingcopy(self):
396 def _assignworkingcopy(self):
397 if self.inmemory:
397 if self.inmemory:
398 from mercurial.context import overlayworkingctx
398 from mercurial.context import overlayworkingctx
399 self.wctx = overlayworkingctx(self.repo)
399 self.wctx = overlayworkingctx(self.repo)
400 self.repo.ui.debug("rebasing in-memory\n")
400 self.repo.ui.debug("rebasing in-memory\n")
401 else:
401 else:
402 self.wctx = self.repo[None]
402 self.wctx = self.repo[None]
403 self.repo.ui.debug("rebasing on disk\n")
403 self.repo.ui.debug("rebasing on disk\n")
404 self.repo.ui.log("rebase", "", rebase_imm_used=self.inmemory)
404 self.repo.ui.log("rebase", "", rebase_imm_used=self.inmemory)
405
405
406 def _performrebase(self, tr):
406 def _performrebase(self, tr):
407 self._assignworkingcopy()
407 self._assignworkingcopy()
408 repo, ui = self.repo, self.ui
408 repo, ui = self.repo, self.ui
409 if self.keepbranchesf:
409 if self.keepbranchesf:
410 # insert _savebranch at the start of extrafns so if
410 # insert _savebranch at the start of extrafns so if
411 # there's a user-provided extrafn it can clobber branch if
411 # there's a user-provided extrafn it can clobber branch if
412 # desired
412 # desired
413 self.extrafns.insert(0, _savebranch)
413 self.extrafns.insert(0, _savebranch)
414 if self.collapsef:
414 if self.collapsef:
415 branches = set()
415 branches = set()
416 for rev in self.state:
416 for rev in self.state:
417 branches.add(repo[rev].branch())
417 branches.add(repo[rev].branch())
418 if len(branches) > 1:
418 if len(branches) > 1:
419 raise error.Abort(_('cannot collapse multiple named '
419 raise error.Abort(_('cannot collapse multiple named '
420 'branches'))
420 'branches'))
421
421
422 # Calculate self.obsoletenotrebased
422 # Calculate self.obsoletenotrebased
423 obsrevs = _filterobsoleterevs(self.repo, self.state)
423 obsrevs = _filterobsoleterevs(self.repo, self.state)
424 self._handleskippingobsolete(obsrevs, self.destmap)
424 self._handleskippingobsolete(obsrevs, self.destmap)
425
425
426 # Keep track of the active bookmarks in order to reset them later
426 # Keep track of the active bookmarks in order to reset them later
427 self.activebookmark = self.activebookmark or repo._activebookmark
427 self.activebookmark = self.activebookmark or repo._activebookmark
428 if self.activebookmark:
428 if self.activebookmark:
429 bookmarks.deactivate(repo)
429 bookmarks.deactivate(repo)
430
430
431 # Store the state before we begin so users can run 'hg rebase --abort'
431 # Store the state before we begin so users can run 'hg rebase --abort'
432 # if we fail before the transaction closes.
432 # if we fail before the transaction closes.
433 self.storestatus()
433 self.storestatus()
434 if tr:
434 if tr:
435 # When using single transaction, store state when transaction
435 # When using single transaction, store state when transaction
436 # commits.
436 # commits.
437 self.storestatus(tr)
437 self.storestatus(tr)
438
438
439 cands = [k for k, v in self.state.iteritems() if v == revtodo]
439 cands = [k for k, v in self.state.iteritems() if v == revtodo]
440 p = repo.ui.makeprogress(_("rebasing"), unit=_('changesets'),
440 p = repo.ui.makeprogress(_("rebasing"), unit=_('changesets'),
441 total=len(cands))
441 total=len(cands))
442 def progress(ctx):
442 def progress(ctx):
443 p.increment(item=("%d:%s" % (ctx.rev(), ctx)))
443 p.increment(item=("%d:%s" % (ctx.rev(), ctx)))
444 allowdivergence = self.ui.configbool(
444 allowdivergence = self.ui.configbool(
445 'experimental', 'evolution.allowdivergence')
445 'experimental', 'evolution.allowdivergence')
446 for subset in sortsource(self.destmap):
446 for subset in sortsource(self.destmap):
447 sortedrevs = self.repo.revs('sort(%ld, -topo)', subset)
447 sortedrevs = self.repo.revs('sort(%ld, -topo)', subset)
448 if not allowdivergence:
448 if not allowdivergence:
449 sortedrevs -= self.repo.revs(
449 sortedrevs -= self.repo.revs(
450 'descendants(%ld) and not %ld',
450 'descendants(%ld) and not %ld',
451 self.obsoletewithoutsuccessorindestination,
451 self.obsoletewithoutsuccessorindestination,
452 self.obsoletewithoutsuccessorindestination,
452 self.obsoletewithoutsuccessorindestination,
453 )
453 )
454 for rev in sortedrevs:
454 for rev in sortedrevs:
455 self._rebasenode(tr, rev, allowdivergence, progress)
455 self._rebasenode(tr, rev, allowdivergence, progress)
456 p.complete()
456 p.complete()
457 ui.note(_('rebase merging completed\n'))
457 ui.note(_('rebase merging completed\n'))
458
458
459 def _concludenode(self, rev, p1, p2, editor, commitmsg=None):
459 def _concludenode(self, rev, p1, p2, editor, commitmsg=None):
460 '''Commit the wd changes with parents p1 and p2.
460 '''Commit the wd changes with parents p1 and p2.
461
461
462 Reuse commit info from rev but also store useful information in extra.
462 Reuse commit info from rev but also store useful information in extra.
463 Return node of committed revision.'''
463 Return node of committed revision.'''
464 repo = self.repo
464 repo = self.repo
465 ctx = repo[rev]
465 ctx = repo[rev]
466 if commitmsg is None:
466 if commitmsg is None:
467 commitmsg = ctx.description()
467 commitmsg = ctx.description()
468 date = self.date
468 date = self.date
469 if date is None:
469 if date is None:
470 date = ctx.date()
470 date = ctx.date()
471 extra = {'rebase_source': ctx.hex()}
471 extra = {'rebase_source': ctx.hex()}
472 for c in self.extrafns:
472 for c in self.extrafns:
473 c(ctx, extra)
473 c(ctx, extra)
474 keepbranch = self.keepbranchesf and repo[p1].branch() != ctx.branch()
474 keepbranch = self.keepbranchesf and repo[p1].branch() != ctx.branch()
475 destphase = max(ctx.phase(), phases.draft)
475 destphase = max(ctx.phase(), phases.draft)
476 overrides = {('phases', 'new-commit'): destphase}
476 overrides = {('phases', 'new-commit'): destphase}
477 if keepbranch:
477 if keepbranch:
478 overrides[('ui', 'allowemptycommit')] = True
478 overrides[('ui', 'allowemptycommit')] = True
479 with repo.ui.configoverride(overrides, 'rebase'):
479 with repo.ui.configoverride(overrides, 'rebase'):
480 if self.inmemory:
480 if self.inmemory:
481 newnode = commitmemorynode(repo, p1, p2,
481 newnode = commitmemorynode(repo, p1, p2,
482 wctx=self.wctx,
482 wctx=self.wctx,
483 extra=extra,
483 extra=extra,
484 commitmsg=commitmsg,
484 commitmsg=commitmsg,
485 editor=editor,
485 editor=editor,
486 user=ctx.user(),
486 user=ctx.user(),
487 date=date)
487 date=date)
488 mergemod.mergestate.clean(repo)
488 mergemod.mergestate.clean(repo)
489 else:
489 else:
490 newnode = commitnode(repo, p1, p2,
490 newnode = commitnode(repo, p1, p2,
491 extra=extra,
491 extra=extra,
492 commitmsg=commitmsg,
492 commitmsg=commitmsg,
493 editor=editor,
493 editor=editor,
494 user=ctx.user(),
494 user=ctx.user(),
495 date=date)
495 date=date)
496
496
497 if newnode is None:
497 if newnode is None:
498 # If it ended up being a no-op commit, then the normal
498 # If it ended up being a no-op commit, then the normal
499 # merge state clean-up path doesn't happen, so do it
499 # merge state clean-up path doesn't happen, so do it
500 # here. Fix issue5494
500 # here. Fix issue5494
501 mergemod.mergestate.clean(repo)
501 mergemod.mergestate.clean(repo)
502 return newnode
502 return newnode
503
503
504 def _rebasenode(self, tr, rev, allowdivergence, progressfn):
504 def _rebasenode(self, tr, rev, allowdivergence, progressfn):
505 repo, ui, opts = self.repo, self.ui, self.opts
505 repo, ui, opts = self.repo, self.ui, self.opts
506 dest = self.destmap[rev]
506 dest = self.destmap[rev]
507 ctx = repo[rev]
507 ctx = repo[rev]
508 desc = _ctxdesc(ctx)
508 desc = _ctxdesc(ctx)
509 if self.state[rev] == rev:
509 if self.state[rev] == rev:
510 ui.status(_('already rebased %s\n') % desc)
510 ui.status(_('already rebased %s\n') % desc)
511 elif (not allowdivergence
511 elif (not allowdivergence
512 and rev in self.obsoletewithoutsuccessorindestination):
512 and rev in self.obsoletewithoutsuccessorindestination):
513 msg = _('note: not rebasing %s and its descendants as '
513 msg = _('note: not rebasing %s and its descendants as '
514 'this would cause divergence\n') % desc
514 'this would cause divergence\n') % desc
515 repo.ui.status(msg)
515 repo.ui.status(msg)
516 self.skipped.add(rev)
516 self.skipped.add(rev)
517 elif rev in self.obsoletenotrebased:
517 elif rev in self.obsoletenotrebased:
518 succ = self.obsoletenotrebased[rev]
518 succ = self.obsoletenotrebased[rev]
519 if succ is None:
519 if succ is None:
520 msg = _('note: not rebasing %s, it has no '
520 msg = _('note: not rebasing %s, it has no '
521 'successor\n') % desc
521 'successor\n') % desc
522 else:
522 else:
523 succdesc = _ctxdesc(repo[succ])
523 succdesc = _ctxdesc(repo[succ])
524 msg = (_('note: not rebasing %s, already in '
524 msg = (_('note: not rebasing %s, already in '
525 'destination as %s\n') % (desc, succdesc))
525 'destination as %s\n') % (desc, succdesc))
526 repo.ui.status(msg)
526 repo.ui.status(msg)
527 # Make clearrebased aware state[rev] is not a true successor
527 # Make clearrebased aware state[rev] is not a true successor
528 self.skipped.add(rev)
528 self.skipped.add(rev)
529 # Record rev as moved to its desired destination in self.state.
529 # Record rev as moved to its desired destination in self.state.
530 # This helps bookmark and working parent movement.
530 # This helps bookmark and working parent movement.
531 dest = max(adjustdest(repo, rev, self.destmap, self.state,
531 dest = max(adjustdest(repo, rev, self.destmap, self.state,
532 self.skipped))
532 self.skipped))
533 self.state[rev] = dest
533 self.state[rev] = dest
534 elif self.state[rev] == revtodo:
534 elif self.state[rev] == revtodo:
535 ui.status(_('rebasing %s\n') % desc)
535 ui.status(_('rebasing %s\n') % desc)
536 progressfn(ctx)
536 progressfn(ctx)
537 p1, p2, base = defineparents(repo, rev, self.destmap,
537 p1, p2, base = defineparents(repo, rev, self.destmap,
538 self.state, self.skipped,
538 self.state, self.skipped,
539 self.obsoletenotrebased)
539 self.obsoletenotrebased)
540 if len(repo[None].parents()) == 2:
540 if len(repo[None].parents()) == 2:
541 repo.ui.debug('resuming interrupted rebase\n')
541 repo.ui.debug('resuming interrupted rebase\n')
542 else:
542 else:
543 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
543 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
544 with ui.configoverride(overrides, 'rebase'):
544 with ui.configoverride(overrides, 'rebase'):
545 stats = rebasenode(repo, rev, p1, base, self.collapsef,
545 stats = rebasenode(repo, rev, p1, base, self.collapsef,
546 dest, wctx=self.wctx)
546 dest, wctx=self.wctx)
547 if stats.unresolvedcount > 0:
547 if stats.unresolvedcount > 0:
548 if self.inmemory:
548 if self.inmemory:
549 raise error.InMemoryMergeConflictsError()
549 raise error.InMemoryMergeConflictsError()
550 else:
550 else:
551 raise error.InterventionRequired(
551 raise error.InterventionRequired(
552 _('unresolved conflicts (see hg '
552 _('unresolved conflicts (see hg '
553 'resolve, then hg rebase --continue)'))
553 'resolve, then hg rebase --continue)'))
554 if not self.collapsef:
554 if not self.collapsef:
555 merging = p2 != nullrev
555 merging = p2 != nullrev
556 editform = cmdutil.mergeeditform(merging, 'rebase')
556 editform = cmdutil.mergeeditform(merging, 'rebase')
557 editor = cmdutil.getcommiteditor(editform=editform,
557 editor = cmdutil.getcommiteditor(editform=editform,
558 **pycompat.strkwargs(opts))
558 **pycompat.strkwargs(opts))
559 newnode = self._concludenode(rev, p1, p2, editor)
559 newnode = self._concludenode(rev, p1, p2, editor)
560 else:
560 else:
561 # Skip commit if we are collapsing
561 # Skip commit if we are collapsing
562 if self.inmemory:
562 if self.inmemory:
563 self.wctx.setbase(repo[p1])
563 self.wctx.setbase(repo[p1])
564 else:
564 else:
565 repo.setparents(repo[p1].node())
565 repo.setparents(repo[p1].node())
566 newnode = None
566 newnode = None
567 # Update the state
567 # Update the state
568 if newnode is not None:
568 if newnode is not None:
569 self.state[rev] = repo[newnode].rev()
569 self.state[rev] = repo[newnode].rev()
570 ui.debug('rebased as %s\n' % short(newnode))
570 ui.debug('rebased as %s\n' % short(newnode))
571 else:
571 else:
572 if not self.collapsef:
572 if not self.collapsef:
573 ui.warn(_('note: rebase of %d:%s created no changes '
573 ui.warn(_('note: rebase of %d:%s created no changes '
574 'to commit\n') % (rev, ctx))
574 'to commit\n') % (rev, ctx))
575 self.skipped.add(rev)
575 self.skipped.add(rev)
576 self.state[rev] = p1
576 self.state[rev] = p1
577 ui.debug('next revision set to %d\n' % p1)
577 ui.debug('next revision set to %d\n' % p1)
578 else:
578 else:
579 ui.status(_('already rebased %s as %s\n') %
579 ui.status(_('already rebased %s as %s\n') %
580 (desc, repo[self.state[rev]]))
580 (desc, repo[self.state[rev]]))
581 if not tr:
581 if not tr:
582 # When not using single transaction, store state after each
582 # When not using single transaction, store state after each
583 # commit is completely done. On InterventionRequired, we thus
583 # commit is completely done. On InterventionRequired, we thus
584 # won't store the status. Instead, we'll hit the "len(parents) == 2"
584 # won't store the status. Instead, we'll hit the "len(parents) == 2"
585 # case and realize that the commit was in progress.
585 # case and realize that the commit was in progress.
586 self.storestatus()
586 self.storestatus()
587
587
588 def _finishrebase(self):
588 def _finishrebase(self, backup=True):
589 """
590 backup: if False, no backup will be stored when stripping rebased
591 revisions
592 """
589 repo, ui, opts = self.repo, self.ui, self.opts
593 repo, ui, opts = self.repo, self.ui, self.opts
590 fm = ui.formatter('rebase', opts)
594 fm = ui.formatter('rebase', opts)
591 fm.startitem()
595 fm.startitem()
592 if self.collapsef:
596 if self.collapsef:
593 p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
597 p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
594 self.state, self.skipped,
598 self.state, self.skipped,
595 self.obsoletenotrebased)
599 self.obsoletenotrebased)
596 editopt = opts.get('edit')
600 editopt = opts.get('edit')
597 editform = 'rebase.collapse'
601 editform = 'rebase.collapse'
598 if self.collapsemsg:
602 if self.collapsemsg:
599 commitmsg = self.collapsemsg
603 commitmsg = self.collapsemsg
600 else:
604 else:
601 commitmsg = 'Collapsed revision'
605 commitmsg = 'Collapsed revision'
602 for rebased in sorted(self.state):
606 for rebased in sorted(self.state):
603 if rebased not in self.skipped:
607 if rebased not in self.skipped:
604 commitmsg += '\n* %s' % repo[rebased].description()
608 commitmsg += '\n* %s' % repo[rebased].description()
605 editopt = True
609 editopt = True
606 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
610 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
607 revtoreuse = max(self.state)
611 revtoreuse = max(self.state)
608
612
609 newnode = self._concludenode(revtoreuse, p1, self.external,
613 newnode = self._concludenode(revtoreuse, p1, self.external,
610 editor, commitmsg=commitmsg)
614 editor, commitmsg=commitmsg)
611
615
612 if newnode is not None:
616 if newnode is not None:
613 newrev = repo[newnode].rev()
617 newrev = repo[newnode].rev()
614 for oldrev in self.state:
618 for oldrev in self.state:
615 self.state[oldrev] = newrev
619 self.state[oldrev] = newrev
616
620
617 if 'qtip' in repo.tags():
621 if 'qtip' in repo.tags():
618 updatemq(repo, self.state, self.skipped,
622 updatemq(repo, self.state, self.skipped,
619 **pycompat.strkwargs(opts))
623 **pycompat.strkwargs(opts))
620
624
621 # restore original working directory
625 # restore original working directory
622 # (we do this before stripping)
626 # (we do this before stripping)
623 newwd = self.state.get(self.originalwd, self.originalwd)
627 newwd = self.state.get(self.originalwd, self.originalwd)
624 if newwd < 0:
628 if newwd < 0:
625 # original directory is a parent of rebase set root or ignored
629 # original directory is a parent of rebase set root or ignored
626 newwd = self.originalwd
630 newwd = self.originalwd
627 if newwd not in [c.rev() for c in repo[None].parents()]:
631 if newwd not in [c.rev() for c in repo[None].parents()]:
628 ui.note(_("update back to initial working directory parent\n"))
632 ui.note(_("update back to initial working directory parent\n"))
629 hg.updaterepo(repo, newwd, overwrite=False)
633 hg.updaterepo(repo, newwd, overwrite=False)
630
634
631 collapsedas = None
635 collapsedas = None
632 if self.collapsef and not self.keepf:
636 if self.collapsef and not self.keepf:
633 collapsedas = newnode
637 collapsedas = newnode
634 clearrebased(ui, repo, self.destmap, self.state, self.skipped,
638 clearrebased(ui, repo, self.destmap, self.state, self.skipped,
635 collapsedas, self.keepf, fm=fm)
639 collapsedas, self.keepf, fm=fm, backup=backup)
636
640
637 clearstatus(repo)
641 clearstatus(repo)
638 clearcollapsemsg(repo)
642 clearcollapsemsg(repo)
639
643
640 ui.note(_("rebase completed\n"))
644 ui.note(_("rebase completed\n"))
641 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
645 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
642 if self.skipped:
646 if self.skipped:
643 skippedlen = len(self.skipped)
647 skippedlen = len(self.skipped)
644 ui.note(_("%d revisions have been skipped\n") % skippedlen)
648 ui.note(_("%d revisions have been skipped\n") % skippedlen)
645 fm.end()
649 fm.end()
646
650
647 if (self.activebookmark and self.activebookmark in repo._bookmarks and
651 if (self.activebookmark and self.activebookmark in repo._bookmarks and
648 repo['.'].node() == repo._bookmarks[self.activebookmark]):
652 repo['.'].node() == repo._bookmarks[self.activebookmark]):
649 bookmarks.activate(repo, self.activebookmark)
653 bookmarks.activate(repo, self.activebookmark)
650
654
651 @command('rebase',
655 @command('rebase',
652 [('s', 'source', '',
656 [('s', 'source', '',
653 _('rebase the specified changeset and descendants'), _('REV')),
657 _('rebase the specified changeset and descendants'), _('REV')),
654 ('b', 'base', '',
658 ('b', 'base', '',
655 _('rebase everything from branching point of specified changeset'),
659 _('rebase everything from branching point of specified changeset'),
656 _('REV')),
660 _('REV')),
657 ('r', 'rev', [],
661 ('r', 'rev', [],
658 _('rebase these revisions'),
662 _('rebase these revisions'),
659 _('REV')),
663 _('REV')),
660 ('d', 'dest', '',
664 ('d', 'dest', '',
661 _('rebase onto the specified changeset'), _('REV')),
665 _('rebase onto the specified changeset'), _('REV')),
662 ('', 'collapse', False, _('collapse the rebased changesets')),
666 ('', 'collapse', False, _('collapse the rebased changesets')),
663 ('m', 'message', '',
667 ('m', 'message', '',
664 _('use text as collapse commit message'), _('TEXT')),
668 _('use text as collapse commit message'), _('TEXT')),
665 ('e', 'edit', False, _('invoke editor on commit messages')),
669 ('e', 'edit', False, _('invoke editor on commit messages')),
666 ('l', 'logfile', '',
670 ('l', 'logfile', '',
667 _('read collapse commit message from file'), _('FILE')),
671 _('read collapse commit message from file'), _('FILE')),
668 ('k', 'keep', False, _('keep original changesets')),
672 ('k', 'keep', False, _('keep original changesets')),
669 ('', 'keepbranches', False, _('keep original branch names')),
673 ('', 'keepbranches', False, _('keep original branch names')),
670 ('D', 'detach', False, _('(DEPRECATED)')),
674 ('D', 'detach', False, _('(DEPRECATED)')),
671 ('i', 'interactive', False, _('(DEPRECATED)')),
675 ('i', 'interactive', False, _('(DEPRECATED)')),
672 ('t', 'tool', '', _('specify merge tool')),
676 ('t', 'tool', '', _('specify merge tool')),
673 ('c', 'continue', False, _('continue an interrupted rebase')),
677 ('c', 'continue', False, _('continue an interrupted rebase')),
674 ('a', 'abort', False, _('abort an interrupted rebase')),
678 ('a', 'abort', False, _('abort an interrupted rebase')),
675 ('', 'auto-orphans', '', _('automatically rebase orphan revisions '
679 ('', 'auto-orphans', '', _('automatically rebase orphan revisions '
676 'in the specified revset (EXPERIMENTAL)')),
680 'in the specified revset (EXPERIMENTAL)')),
677 ] + cmdutil.dryrunopts + cmdutil.formatteropts + cmdutil.confirmopts,
681 ] + cmdutil.dryrunopts + cmdutil.formatteropts + cmdutil.confirmopts,
678 _('[-s REV | -b REV] [-d REV] [OPTION]'))
682 _('[-s REV | -b REV] [-d REV] [OPTION]'))
679 def rebase(ui, repo, **opts):
683 def rebase(ui, repo, **opts):
680 """move changeset (and descendants) to a different branch
684 """move changeset (and descendants) to a different branch
681
685
682 Rebase uses repeated merging to graft changesets from one part of
686 Rebase uses repeated merging to graft changesets from one part of
683 history (the source) onto another (the destination). This can be
687 history (the source) onto another (the destination). This can be
684 useful for linearizing *local* changes relative to a master
688 useful for linearizing *local* changes relative to a master
685 development tree.
689 development tree.
686
690
687 Published commits cannot be rebased (see :hg:`help phases`).
691 Published commits cannot be rebased (see :hg:`help phases`).
688 To copy commits, see :hg:`help graft`.
692 To copy commits, see :hg:`help graft`.
689
693
690 If you don't specify a destination changeset (``-d/--dest``), rebase
694 If you don't specify a destination changeset (``-d/--dest``), rebase
691 will use the same logic as :hg:`merge` to pick a destination. if
695 will use the same logic as :hg:`merge` to pick a destination. if
692 the current branch contains exactly one other head, the other head
696 the current branch contains exactly one other head, the other head
693 is merged with by default. Otherwise, an explicit revision with
697 is merged with by default. Otherwise, an explicit revision with
694 which to merge with must be provided. (destination changeset is not
698 which to merge with must be provided. (destination changeset is not
695 modified by rebasing, but new changesets are added as its
699 modified by rebasing, but new changesets are added as its
696 descendants.)
700 descendants.)
697
701
698 Here are the ways to select changesets:
702 Here are the ways to select changesets:
699
703
700 1. Explicitly select them using ``--rev``.
704 1. Explicitly select them using ``--rev``.
701
705
702 2. Use ``--source`` to select a root changeset and include all of its
706 2. Use ``--source`` to select a root changeset and include all of its
703 descendants.
707 descendants.
704
708
705 3. Use ``--base`` to select a changeset; rebase will find ancestors
709 3. Use ``--base`` to select a changeset; rebase will find ancestors
706 and their descendants which are not also ancestors of the destination.
710 and their descendants which are not also ancestors of the destination.
707
711
708 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
712 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
709 rebase will use ``--base .`` as above.
713 rebase will use ``--base .`` as above.
710
714
711 If ``--source`` or ``--rev`` is used, special names ``SRC`` and ``ALLSRC``
715 If ``--source`` or ``--rev`` is used, special names ``SRC`` and ``ALLSRC``
712 can be used in ``--dest``. Destination would be calculated per source
716 can be used in ``--dest``. Destination would be calculated per source
713 revision with ``SRC`` substituted by that single source revision and
717 revision with ``SRC`` substituted by that single source revision and
714 ``ALLSRC`` substituted by all source revisions.
718 ``ALLSRC`` substituted by all source revisions.
715
719
716 Rebase will destroy original changesets unless you use ``--keep``.
720 Rebase will destroy original changesets unless you use ``--keep``.
717 It will also move your bookmarks (even if you do).
721 It will also move your bookmarks (even if you do).
718
722
719 Some changesets may be dropped if they do not contribute changes
723 Some changesets may be dropped if they do not contribute changes
720 (e.g. merges from the destination branch).
724 (e.g. merges from the destination branch).
721
725
722 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
726 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
723 a named branch with two heads. You will need to explicitly specify source
727 a named branch with two heads. You will need to explicitly specify source
724 and/or destination.
728 and/or destination.
725
729
726 If you need to use a tool to automate merge/conflict decisions, you
730 If you need to use a tool to automate merge/conflict decisions, you
727 can specify one with ``--tool``, see :hg:`help merge-tools`.
731 can specify one with ``--tool``, see :hg:`help merge-tools`.
728 As a caveat: the tool will not be used to mediate when a file was
732 As a caveat: the tool will not be used to mediate when a file was
729 deleted, there is no hook presently available for this.
733 deleted, there is no hook presently available for this.
730
734
731 If a rebase is interrupted to manually resolve a conflict, it can be
735 If a rebase is interrupted to manually resolve a conflict, it can be
732 continued with --continue/-c or aborted with --abort/-a.
736 continued with --continue/-c or aborted with --abort/-a.
733
737
734 .. container:: verbose
738 .. container:: verbose
735
739
736 Examples:
740 Examples:
737
741
738 - move "local changes" (current commit back to branching point)
742 - move "local changes" (current commit back to branching point)
739 to the current branch tip after a pull::
743 to the current branch tip after a pull::
740
744
741 hg rebase
745 hg rebase
742
746
743 - move a single changeset to the stable branch::
747 - move a single changeset to the stable branch::
744
748
745 hg rebase -r 5f493448 -d stable
749 hg rebase -r 5f493448 -d stable
746
750
747 - splice a commit and all its descendants onto another part of history::
751 - splice a commit and all its descendants onto another part of history::
748
752
749 hg rebase --source c0c3 --dest 4cf9
753 hg rebase --source c0c3 --dest 4cf9
750
754
751 - rebase everything on a branch marked by a bookmark onto the
755 - rebase everything on a branch marked by a bookmark onto the
752 default branch::
756 default branch::
753
757
754 hg rebase --base myfeature --dest default
758 hg rebase --base myfeature --dest default
755
759
756 - collapse a sequence of changes into a single commit::
760 - collapse a sequence of changes into a single commit::
757
761
758 hg rebase --collapse -r 1520:1525 -d .
762 hg rebase --collapse -r 1520:1525 -d .
759
763
760 - move a named branch while preserving its name::
764 - move a named branch while preserving its name::
761
765
762 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
766 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
763
767
764 - stabilize orphaned changesets so history looks linear::
768 - stabilize orphaned changesets so history looks linear::
765
769
766 hg rebase -r 'orphan()-obsolete()'\
770 hg rebase -r 'orphan()-obsolete()'\
767 -d 'first(max((successors(max(roots(ALLSRC) & ::SRC)^)-obsolete())::) +\
771 -d 'first(max((successors(max(roots(ALLSRC) & ::SRC)^)-obsolete())::) +\
768 max(::((roots(ALLSRC) & ::SRC)^)-obsolete()))'
772 max(::((roots(ALLSRC) & ::SRC)^)-obsolete()))'
769
773
770 Configuration Options:
774 Configuration Options:
771
775
772 You can make rebase require a destination if you set the following config
776 You can make rebase require a destination if you set the following config
773 option::
777 option::
774
778
775 [commands]
779 [commands]
776 rebase.requiredest = True
780 rebase.requiredest = True
777
781
778 By default, rebase will close the transaction after each commit. For
782 By default, rebase will close the transaction after each commit. For
779 performance purposes, you can configure rebase to use a single transaction
783 performance purposes, you can configure rebase to use a single transaction
780 across the entire rebase. WARNING: This setting introduces a significant
784 across the entire rebase. WARNING: This setting introduces a significant
781 risk of losing the work you've done in a rebase if the rebase aborts
785 risk of losing the work you've done in a rebase if the rebase aborts
782 unexpectedly::
786 unexpectedly::
783
787
784 [rebase]
788 [rebase]
785 singletransaction = True
789 singletransaction = True
786
790
787 By default, rebase writes to the working copy, but you can configure it to
791 By default, rebase writes to the working copy, but you can configure it to
788 run in-memory for for better performance, and to allow it to run if the
792 run in-memory for for better performance, and to allow it to run if the
789 working copy is dirty::
793 working copy is dirty::
790
794
791 [rebase]
795 [rebase]
792 experimental.inmemory = True
796 experimental.inmemory = True
793
797
794 Return Values:
798 Return Values:
795
799
796 Returns 0 on success, 1 if nothing to rebase or there are
800 Returns 0 on success, 1 if nothing to rebase or there are
797 unresolved conflicts.
801 unresolved conflicts.
798
802
799 """
803 """
800 opts = pycompat.byteskwargs(opts)
804 opts = pycompat.byteskwargs(opts)
801 inmemory = ui.configbool('rebase', 'experimental.inmemory')
805 inmemory = ui.configbool('rebase', 'experimental.inmemory')
802 dryrun = opts.get('dry_run')
806 dryrun = opts.get('dry_run')
803 if dryrun:
807 if dryrun:
804 if opts.get('abort'):
808 if opts.get('abort'):
805 raise error.Abort(_('cannot specify both --dry-run and --abort'))
809 raise error.Abort(_('cannot specify both --dry-run and --abort'))
806 if opts.get('continue'):
810 if opts.get('continue'):
807 raise error.Abort(_('cannot specify both --dry-run and --continue'))
811 raise error.Abort(_('cannot specify both --dry-run and --continue'))
808 if opts.get('confirm'):
812 if opts.get('confirm'):
809 dryrun = True
813 dryrun = True
810 if opts.get('dry_run'):
814 if opts.get('dry_run'):
811 raise error.Abort(_('cannot specify both --confirm and --dry-run'))
815 raise error.Abort(_('cannot specify both --confirm and --dry-run'))
812 if opts.get('abort'):
816 if opts.get('abort'):
813 raise error.Abort(_('cannot specify both --confirm and --abort'))
817 raise error.Abort(_('cannot specify both --confirm and --abort'))
814 if opts.get('continue'):
818 if opts.get('continue'):
815 raise error.Abort(_('cannot specify both --confirm and --continue'))
819 raise error.Abort(_('cannot specify both --confirm and --continue'))
816
820
817 if (opts.get('continue') or opts.get('abort') or
821 if (opts.get('continue') or opts.get('abort') or
818 repo.currenttransaction() is not None):
822 repo.currenttransaction() is not None):
819 # in-memory rebase is not compatible with resuming rebases.
823 # in-memory rebase is not compatible with resuming rebases.
820 # (Or if it is run within a transaction, since the restart logic can
824 # (Or if it is run within a transaction, since the restart logic can
821 # fail the entire transaction.)
825 # fail the entire transaction.)
822 inmemory = False
826 inmemory = False
823
827
824 if opts.get('auto_orphans'):
828 if opts.get('auto_orphans'):
825 for key in opts:
829 for key in opts:
826 if key != 'auto_orphans' and opts.get(key):
830 if key != 'auto_orphans' and opts.get(key):
827 raise error.Abort(_('--auto-orphans is incompatible with %s') %
831 raise error.Abort(_('--auto-orphans is incompatible with %s') %
828 ('--' + key))
832 ('--' + key))
829 userrevs = list(repo.revs(opts.get('auto_orphans')))
833 userrevs = list(repo.revs(opts.get('auto_orphans')))
830 opts['rev'] = [revsetlang.formatspec('%ld and orphan()', userrevs)]
834 opts['rev'] = [revsetlang.formatspec('%ld and orphan()', userrevs)]
831 opts['dest'] = '_destautoorphanrebase(SRC)'
835 opts['dest'] = '_destautoorphanrebase(SRC)'
836 backup = ui.configbool('ui', 'history-editing-backup')
837 opts['backup'] = backup
832
838
833 if dryrun:
839 if dryrun:
834 return _dryrunrebase(ui, repo, opts)
840 return _dryrunrebase(ui, repo, opts)
835 elif inmemory:
841 elif inmemory:
836 try:
842 try:
837 # in-memory merge doesn't support conflicts, so if we hit any, abort
843 # in-memory merge doesn't support conflicts, so if we hit any, abort
838 # and re-run as an on-disk merge.
844 # and re-run as an on-disk merge.
839 overrides = {('rebase', 'singletransaction'): True}
845 overrides = {('rebase', 'singletransaction'): True}
840 with ui.configoverride(overrides, 'rebase'):
846 with ui.configoverride(overrides, 'rebase'):
841 return _dorebase(ui, repo, opts, inmemory=inmemory)
847 return _dorebase(ui, repo, opts, inmemory=inmemory)
842 except error.InMemoryMergeConflictsError:
848 except error.InMemoryMergeConflictsError:
843 ui.warn(_('hit merge conflicts; re-running rebase without in-memory'
849 ui.warn(_('hit merge conflicts; re-running rebase without in-memory'
844 ' merge\n'))
850 ' merge\n'))
845 _dorebase(ui, repo, {'abort': True})
851 _dorebase(ui, repo, {'abort': True})
846 return _dorebase(ui, repo, opts, inmemory=False)
852 return _dorebase(ui, repo, opts, inmemory=False)
847 else:
853 else:
848 return _dorebase(ui, repo, opts)
854 return _dorebase(ui, repo, opts)
849
855
850 def _dryrunrebase(ui, repo, opts):
856 def _dryrunrebase(ui, repo, opts):
851 rbsrt = rebaseruntime(repo, ui, inmemory=True, opts=opts)
857 rbsrt = rebaseruntime(repo, ui, inmemory=True, opts=opts)
852 confirm = opts.get('confirm')
858 confirm = opts.get('confirm')
859 backup = opts.get('backup')
853 if confirm:
860 if confirm:
854 ui.status(_('starting in-memory rebase\n'))
861 ui.status(_('starting in-memory rebase\n'))
855 else:
862 else:
856 ui.status(_('starting dry-run rebase; repository will not be '
863 ui.status(_('starting dry-run rebase; repository will not be '
857 'changed\n'))
864 'changed\n'))
858 with repo.wlock(), repo.lock():
865 with repo.wlock(), repo.lock():
859 needsabort = True
866 needsabort = True
860 try:
867 try:
861 overrides = {('rebase', 'singletransaction'): True}
868 overrides = {('rebase', 'singletransaction'): True}
862 with ui.configoverride(overrides, 'rebase'):
869 with ui.configoverride(overrides, 'rebase'):
863 _origrebase(ui, repo, opts, rbsrt, inmemory=True,
870 _origrebase(ui, repo, opts, rbsrt, inmemory=True,
864 leaveunfinished=True)
871 leaveunfinished=True)
865 except error.InMemoryMergeConflictsError:
872 except error.InMemoryMergeConflictsError:
866 ui.status(_('hit a merge conflict\n'))
873 ui.status(_('hit a merge conflict\n'))
867 return 1
874 return 1
868 else:
875 else:
869 if confirm:
876 if confirm:
870 ui.status(_('rebase completed successfully\n'))
877 ui.status(_('rebase completed successfully\n'))
871 if not ui.promptchoice(_(b'apply changes (yn)?'
878 if not ui.promptchoice(_(b'apply changes (yn)?'
872 b'$$ &Yes $$ &No')):
879 b'$$ &Yes $$ &No')):
873 # finish unfinished rebase
880 # finish unfinished rebase
874 rbsrt._finishrebase()
881 rbsrt._finishrebase(backup=backup)
875 else:
882 else:
876 rbsrt._prepareabortorcontinue(isabort=True, backup=False,
883 rbsrt._prepareabortorcontinue(isabort=True, backup=False,
877 suppwarns=True)
884 suppwarns=True)
878 needsabort = False
885 needsabort = False
879 else:
886 else:
880 ui.status(_('dry-run rebase completed successfully; run without'
887 ui.status(_('dry-run rebase completed successfully; run without'
881 ' -n/--dry-run to perform this rebase\n'))
888 ' -n/--dry-run to perform this rebase\n'))
882 return 0
889 return 0
883 finally:
890 finally:
884 if needsabort:
891 if needsabort:
885 # no need to store backup in case of dryrun
892 # no need to store backup in case of dryrun
886 rbsrt._prepareabortorcontinue(isabort=True, backup=False,
893 rbsrt._prepareabortorcontinue(isabort=True, backup=False,
887 suppwarns=True)
894 suppwarns=True)
888
895
889 def _dorebase(ui, repo, opts, inmemory=False):
896 def _dorebase(ui, repo, opts, inmemory=False):
890 rbsrt = rebaseruntime(repo, ui, inmemory, opts)
897 rbsrt = rebaseruntime(repo, ui, inmemory, opts)
891 return _origrebase(ui, repo, opts, rbsrt, inmemory=inmemory)
898 return _origrebase(ui, repo, opts, rbsrt, inmemory=inmemory)
892
899
893 def _origrebase(ui, repo, opts, rbsrt, inmemory=False, leaveunfinished=False):
900 def _origrebase(ui, repo, opts, rbsrt, inmemory=False, leaveunfinished=False):
894 with repo.wlock(), repo.lock():
901 with repo.wlock(), repo.lock():
895 # Validate input and define rebasing points
902 # Validate input and define rebasing points
896 destf = opts.get('dest', None)
903 destf = opts.get('dest', None)
897 srcf = opts.get('source', None)
904 srcf = opts.get('source', None)
898 basef = opts.get('base', None)
905 basef = opts.get('base', None)
899 revf = opts.get('rev', [])
906 revf = opts.get('rev', [])
900 # search default destination in this space
907 # search default destination in this space
901 # used in the 'hg pull --rebase' case, see issue 5214.
908 # used in the 'hg pull --rebase' case, see issue 5214.
902 destspace = opts.get('_destspace')
909 destspace = opts.get('_destspace')
903 contf = opts.get('continue')
910 contf = opts.get('continue')
904 abortf = opts.get('abort')
911 abortf = opts.get('abort')
912 backup = opts.get('backup')
905 if opts.get('interactive'):
913 if opts.get('interactive'):
906 try:
914 try:
907 if extensions.find('histedit'):
915 if extensions.find('histedit'):
908 enablehistedit = ''
916 enablehistedit = ''
909 except KeyError:
917 except KeyError:
910 enablehistedit = " --config extensions.histedit="
918 enablehistedit = " --config extensions.histedit="
911 help = "hg%s help -e histedit" % enablehistedit
919 help = "hg%s help -e histedit" % enablehistedit
912 msg = _("interactive history editing is supported by the "
920 msg = _("interactive history editing is supported by the "
913 "'histedit' extension (see \"%s\")") % help
921 "'histedit' extension (see \"%s\")") % help
914 raise error.Abort(msg)
922 raise error.Abort(msg)
915
923
916 if rbsrt.collapsemsg and not rbsrt.collapsef:
924 if rbsrt.collapsemsg and not rbsrt.collapsef:
917 raise error.Abort(
925 raise error.Abort(
918 _('message can only be specified with collapse'))
926 _('message can only be specified with collapse'))
919
927
920 if contf or abortf:
928 if contf or abortf:
921 if contf and abortf:
929 if contf and abortf:
922 raise error.Abort(_('cannot use both abort and continue'))
930 raise error.Abort(_('cannot use both abort and continue'))
923 if rbsrt.collapsef:
931 if rbsrt.collapsef:
924 raise error.Abort(
932 raise error.Abort(
925 _('cannot use collapse with continue or abort'))
933 _('cannot use collapse with continue or abort'))
926 if srcf or basef or destf:
934 if srcf or basef or destf:
927 raise error.Abort(
935 raise error.Abort(
928 _('abort and continue do not allow specifying revisions'))
936 _('abort and continue do not allow specifying revisions'))
929 if abortf and opts.get('tool', False):
937 if abortf and opts.get('tool', False):
930 ui.warn(_('tool option will be ignored\n'))
938 ui.warn(_('tool option will be ignored\n'))
931 if contf:
939 if contf:
932 ms = mergemod.mergestate.read(repo)
940 ms = mergemod.mergestate.read(repo)
933 mergeutil.checkunresolved(ms)
941 mergeutil.checkunresolved(ms)
934
942
935 retcode = rbsrt._prepareabortorcontinue(abortf)
943 retcode = rbsrt._prepareabortorcontinue(abortf, backup=backup)
936 if retcode is not None:
944 if retcode is not None:
937 return retcode
945 return retcode
938 else:
946 else:
939 destmap = _definedestmap(ui, repo, inmemory, destf, srcf, basef,
947 destmap = _definedestmap(ui, repo, inmemory, destf, srcf, basef,
940 revf, destspace=destspace)
948 revf, destspace=destspace)
941 retcode = rbsrt._preparenewrebase(destmap)
949 retcode = rbsrt._preparenewrebase(destmap)
942 if retcode is not None:
950 if retcode is not None:
943 return retcode
951 return retcode
944 storecollapsemsg(repo, rbsrt.collapsemsg)
952 storecollapsemsg(repo, rbsrt.collapsemsg)
945
953
946 tr = None
954 tr = None
947
955
948 singletr = ui.configbool('rebase', 'singletransaction')
956 singletr = ui.configbool('rebase', 'singletransaction')
949 if singletr:
957 if singletr:
950 tr = repo.transaction('rebase')
958 tr = repo.transaction('rebase')
951
959
952 # If `rebase.singletransaction` is enabled, wrap the entire operation in
960 # If `rebase.singletransaction` is enabled, wrap the entire operation in
953 # one transaction here. Otherwise, transactions are obtained when
961 # one transaction here. Otherwise, transactions are obtained when
954 # committing each node, which is slower but allows partial success.
962 # committing each node, which is slower but allows partial success.
955 with util.acceptintervention(tr):
963 with util.acceptintervention(tr):
956 # Same logic for the dirstate guard, except we don't create one when
964 # Same logic for the dirstate guard, except we don't create one when
957 # rebasing in-memory (it's not needed).
965 # rebasing in-memory (it's not needed).
958 dsguard = None
966 dsguard = None
959 if singletr and not inmemory:
967 if singletr and not inmemory:
960 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
968 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
961 with util.acceptintervention(dsguard):
969 with util.acceptintervention(dsguard):
962 rbsrt._performrebase(tr)
970 rbsrt._performrebase(tr)
963 if not leaveunfinished:
971 if not leaveunfinished:
964 rbsrt._finishrebase()
972 rbsrt._finishrebase(backup=backup)
965
973
966 def _definedestmap(ui, repo, inmemory, destf=None, srcf=None, basef=None,
974 def _definedestmap(ui, repo, inmemory, destf=None, srcf=None, basef=None,
967 revf=None, destspace=None):
975 revf=None, destspace=None):
968 """use revisions argument to define destmap {srcrev: destrev}"""
976 """use revisions argument to define destmap {srcrev: destrev}"""
969 if revf is None:
977 if revf is None:
970 revf = []
978 revf = []
971
979
972 # destspace is here to work around issues with `hg pull --rebase` see
980 # destspace is here to work around issues with `hg pull --rebase` see
973 # issue5214 for details
981 # issue5214 for details
974 if srcf and basef:
982 if srcf and basef:
975 raise error.Abort(_('cannot specify both a source and a base'))
983 raise error.Abort(_('cannot specify both a source and a base'))
976 if revf and basef:
984 if revf and basef:
977 raise error.Abort(_('cannot specify both a revision and a base'))
985 raise error.Abort(_('cannot specify both a revision and a base'))
978 if revf and srcf:
986 if revf and srcf:
979 raise error.Abort(_('cannot specify both a revision and a source'))
987 raise error.Abort(_('cannot specify both a revision and a source'))
980
988
981 if not inmemory:
989 if not inmemory:
982 cmdutil.checkunfinished(repo)
990 cmdutil.checkunfinished(repo)
983 cmdutil.bailifchanged(repo)
991 cmdutil.bailifchanged(repo)
984
992
985 if ui.configbool('commands', 'rebase.requiredest') and not destf:
993 if ui.configbool('commands', 'rebase.requiredest') and not destf:
986 raise error.Abort(_('you must specify a destination'),
994 raise error.Abort(_('you must specify a destination'),
987 hint=_('use: hg rebase -d REV'))
995 hint=_('use: hg rebase -d REV'))
988
996
989 dest = None
997 dest = None
990
998
991 if revf:
999 if revf:
992 rebaseset = scmutil.revrange(repo, revf)
1000 rebaseset = scmutil.revrange(repo, revf)
993 if not rebaseset:
1001 if not rebaseset:
994 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
1002 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
995 return None
1003 return None
996 elif srcf:
1004 elif srcf:
997 src = scmutil.revrange(repo, [srcf])
1005 src = scmutil.revrange(repo, [srcf])
998 if not src:
1006 if not src:
999 ui.status(_('empty "source" revision set - nothing to rebase\n'))
1007 ui.status(_('empty "source" revision set - nothing to rebase\n'))
1000 return None
1008 return None
1001 rebaseset = repo.revs('(%ld)::', src)
1009 rebaseset = repo.revs('(%ld)::', src)
1002 assert rebaseset
1010 assert rebaseset
1003 else:
1011 else:
1004 base = scmutil.revrange(repo, [basef or '.'])
1012 base = scmutil.revrange(repo, [basef or '.'])
1005 if not base:
1013 if not base:
1006 ui.status(_('empty "base" revision set - '
1014 ui.status(_('empty "base" revision set - '
1007 "can't compute rebase set\n"))
1015 "can't compute rebase set\n"))
1008 return None
1016 return None
1009 if destf:
1017 if destf:
1010 # --base does not support multiple destinations
1018 # --base does not support multiple destinations
1011 dest = scmutil.revsingle(repo, destf)
1019 dest = scmutil.revsingle(repo, destf)
1012 else:
1020 else:
1013 dest = repo[_destrebase(repo, base, destspace=destspace)]
1021 dest = repo[_destrebase(repo, base, destspace=destspace)]
1014 destf = bytes(dest)
1022 destf = bytes(dest)
1015
1023
1016 roots = [] # selected children of branching points
1024 roots = [] # selected children of branching points
1017 bpbase = {} # {branchingpoint: [origbase]}
1025 bpbase = {} # {branchingpoint: [origbase]}
1018 for b in base: # group bases by branching points
1026 for b in base: # group bases by branching points
1019 bp = repo.revs('ancestor(%d, %d)', b, dest.rev()).first()
1027 bp = repo.revs('ancestor(%d, %d)', b, dest.rev()).first()
1020 bpbase[bp] = bpbase.get(bp, []) + [b]
1028 bpbase[bp] = bpbase.get(bp, []) + [b]
1021 if None in bpbase:
1029 if None in bpbase:
1022 # emulate the old behavior, showing "nothing to rebase" (a better
1030 # emulate the old behavior, showing "nothing to rebase" (a better
1023 # behavior may be abort with "cannot find branching point" error)
1031 # behavior may be abort with "cannot find branching point" error)
1024 bpbase.clear()
1032 bpbase.clear()
1025 for bp, bs in bpbase.iteritems(): # calculate roots
1033 for bp, bs in bpbase.iteritems(): # calculate roots
1026 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
1034 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
1027
1035
1028 rebaseset = repo.revs('%ld::', roots)
1036 rebaseset = repo.revs('%ld::', roots)
1029
1037
1030 if not rebaseset:
1038 if not rebaseset:
1031 # transform to list because smartsets are not comparable to
1039 # transform to list because smartsets are not comparable to
1032 # lists. This should be improved to honor laziness of
1040 # lists. This should be improved to honor laziness of
1033 # smartset.
1041 # smartset.
1034 if list(base) == [dest.rev()]:
1042 if list(base) == [dest.rev()]:
1035 if basef:
1043 if basef:
1036 ui.status(_('nothing to rebase - %s is both "base"'
1044 ui.status(_('nothing to rebase - %s is both "base"'
1037 ' and destination\n') % dest)
1045 ' and destination\n') % dest)
1038 else:
1046 else:
1039 ui.status(_('nothing to rebase - working directory '
1047 ui.status(_('nothing to rebase - working directory '
1040 'parent is also destination\n'))
1048 'parent is also destination\n'))
1041 elif not repo.revs('%ld - ::%d', base, dest.rev()):
1049 elif not repo.revs('%ld - ::%d', base, dest.rev()):
1042 if basef:
1050 if basef:
1043 ui.status(_('nothing to rebase - "base" %s is '
1051 ui.status(_('nothing to rebase - "base" %s is '
1044 'already an ancestor of destination '
1052 'already an ancestor of destination '
1045 '%s\n') %
1053 '%s\n') %
1046 ('+'.join(bytes(repo[r]) for r in base),
1054 ('+'.join(bytes(repo[r]) for r in base),
1047 dest))
1055 dest))
1048 else:
1056 else:
1049 ui.status(_('nothing to rebase - working '
1057 ui.status(_('nothing to rebase - working '
1050 'directory parent is already an '
1058 'directory parent is already an '
1051 'ancestor of destination %s\n') % dest)
1059 'ancestor of destination %s\n') % dest)
1052 else: # can it happen?
1060 else: # can it happen?
1053 ui.status(_('nothing to rebase from %s to %s\n') %
1061 ui.status(_('nothing to rebase from %s to %s\n') %
1054 ('+'.join(bytes(repo[r]) for r in base), dest))
1062 ('+'.join(bytes(repo[r]) for r in base), dest))
1055 return None
1063 return None
1056
1064
1057 rebasingwcp = repo['.'].rev() in rebaseset
1065 rebasingwcp = repo['.'].rev() in rebaseset
1058 ui.log("rebase", "", rebase_rebasing_wcp=rebasingwcp)
1066 ui.log("rebase", "", rebase_rebasing_wcp=rebasingwcp)
1059 if inmemory and rebasingwcp:
1067 if inmemory and rebasingwcp:
1060 # Check these since we did not before.
1068 # Check these since we did not before.
1061 cmdutil.checkunfinished(repo)
1069 cmdutil.checkunfinished(repo)
1062 cmdutil.bailifchanged(repo)
1070 cmdutil.bailifchanged(repo)
1063
1071
1064 if not destf:
1072 if not destf:
1065 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
1073 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
1066 destf = bytes(dest)
1074 destf = bytes(dest)
1067
1075
1068 allsrc = revsetlang.formatspec('%ld', rebaseset)
1076 allsrc = revsetlang.formatspec('%ld', rebaseset)
1069 alias = {'ALLSRC': allsrc}
1077 alias = {'ALLSRC': allsrc}
1070
1078
1071 if dest is None:
1079 if dest is None:
1072 try:
1080 try:
1073 # fast path: try to resolve dest without SRC alias
1081 # fast path: try to resolve dest without SRC alias
1074 dest = scmutil.revsingle(repo, destf, localalias=alias)
1082 dest = scmutil.revsingle(repo, destf, localalias=alias)
1075 except error.RepoLookupError:
1083 except error.RepoLookupError:
1076 # multi-dest path: resolve dest for each SRC separately
1084 # multi-dest path: resolve dest for each SRC separately
1077 destmap = {}
1085 destmap = {}
1078 for r in rebaseset:
1086 for r in rebaseset:
1079 alias['SRC'] = revsetlang.formatspec('%d', r)
1087 alias['SRC'] = revsetlang.formatspec('%d', r)
1080 # use repo.anyrevs instead of scmutil.revsingle because we
1088 # use repo.anyrevs instead of scmutil.revsingle because we
1081 # don't want to abort if destset is empty.
1089 # don't want to abort if destset is empty.
1082 destset = repo.anyrevs([destf], user=True, localalias=alias)
1090 destset = repo.anyrevs([destf], user=True, localalias=alias)
1083 size = len(destset)
1091 size = len(destset)
1084 if size == 1:
1092 if size == 1:
1085 destmap[r] = destset.first()
1093 destmap[r] = destset.first()
1086 elif size == 0:
1094 elif size == 0:
1087 ui.note(_('skipping %s - empty destination\n') % repo[r])
1095 ui.note(_('skipping %s - empty destination\n') % repo[r])
1088 else:
1096 else:
1089 raise error.Abort(_('rebase destination for %s is not '
1097 raise error.Abort(_('rebase destination for %s is not '
1090 'unique') % repo[r])
1098 'unique') % repo[r])
1091
1099
1092 if dest is not None:
1100 if dest is not None:
1093 # single-dest case: assign dest to each rev in rebaseset
1101 # single-dest case: assign dest to each rev in rebaseset
1094 destrev = dest.rev()
1102 destrev = dest.rev()
1095 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
1103 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
1096
1104
1097 if not destmap:
1105 if not destmap:
1098 ui.status(_('nothing to rebase - empty destination\n'))
1106 ui.status(_('nothing to rebase - empty destination\n'))
1099 return None
1107 return None
1100
1108
1101 return destmap
1109 return destmap
1102
1110
1103 def externalparent(repo, state, destancestors):
1111 def externalparent(repo, state, destancestors):
1104 """Return the revision that should be used as the second parent
1112 """Return the revision that should be used as the second parent
1105 when the revisions in state is collapsed on top of destancestors.
1113 when the revisions in state is collapsed on top of destancestors.
1106 Abort if there is more than one parent.
1114 Abort if there is more than one parent.
1107 """
1115 """
1108 parents = set()
1116 parents = set()
1109 source = min(state)
1117 source = min(state)
1110 for rev in state:
1118 for rev in state:
1111 if rev == source:
1119 if rev == source:
1112 continue
1120 continue
1113 for p in repo[rev].parents():
1121 for p in repo[rev].parents():
1114 if (p.rev() not in state
1122 if (p.rev() not in state
1115 and p.rev() not in destancestors):
1123 and p.rev() not in destancestors):
1116 parents.add(p.rev())
1124 parents.add(p.rev())
1117 if not parents:
1125 if not parents:
1118 return nullrev
1126 return nullrev
1119 if len(parents) == 1:
1127 if len(parents) == 1:
1120 return parents.pop()
1128 return parents.pop()
1121 raise error.Abort(_('unable to collapse on top of %d, there is more '
1129 raise error.Abort(_('unable to collapse on top of %d, there is more '
1122 'than one external parent: %s') %
1130 'than one external parent: %s') %
1123 (max(destancestors),
1131 (max(destancestors),
1124 ', '.join("%d" % p for p in sorted(parents))))
1132 ', '.join("%d" % p for p in sorted(parents))))
1125
1133
1126 def commitmemorynode(repo, p1, p2, wctx, editor, extra, user, date, commitmsg):
1134 def commitmemorynode(repo, p1, p2, wctx, editor, extra, user, date, commitmsg):
1127 '''Commit the memory changes with parents p1 and p2.
1135 '''Commit the memory changes with parents p1 and p2.
1128 Return node of committed revision.'''
1136 Return node of committed revision.'''
1129 # Replicates the empty check in ``repo.commit``.
1137 # Replicates the empty check in ``repo.commit``.
1130 if wctx.isempty() and not repo.ui.configbool('ui', 'allowemptycommit'):
1138 if wctx.isempty() and not repo.ui.configbool('ui', 'allowemptycommit'):
1131 return None
1139 return None
1132
1140
1133 # By convention, ``extra['branch']`` (set by extrafn) clobbers
1141 # By convention, ``extra['branch']`` (set by extrafn) clobbers
1134 # ``branch`` (used when passing ``--keepbranches``).
1142 # ``branch`` (used when passing ``--keepbranches``).
1135 branch = repo[p1].branch()
1143 branch = repo[p1].branch()
1136 if 'branch' in extra:
1144 if 'branch' in extra:
1137 branch = extra['branch']
1145 branch = extra['branch']
1138
1146
1139 memctx = wctx.tomemctx(commitmsg, parents=(p1, p2), date=date,
1147 memctx = wctx.tomemctx(commitmsg, parents=(p1, p2), date=date,
1140 extra=extra, user=user, branch=branch, editor=editor)
1148 extra=extra, user=user, branch=branch, editor=editor)
1141 commitres = repo.commitctx(memctx)
1149 commitres = repo.commitctx(memctx)
1142 wctx.clean() # Might be reused
1150 wctx.clean() # Might be reused
1143 return commitres
1151 return commitres
1144
1152
1145 def commitnode(repo, p1, p2, editor, extra, user, date, commitmsg):
1153 def commitnode(repo, p1, p2, editor, extra, user, date, commitmsg):
1146 '''Commit the wd changes with parents p1 and p2.
1154 '''Commit the wd changes with parents p1 and p2.
1147 Return node of committed revision.'''
1155 Return node of committed revision.'''
1148 dsguard = util.nullcontextmanager()
1156 dsguard = util.nullcontextmanager()
1149 if not repo.ui.configbool('rebase', 'singletransaction'):
1157 if not repo.ui.configbool('rebase', 'singletransaction'):
1150 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
1158 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
1151 with dsguard:
1159 with dsguard:
1152 repo.setparents(repo[p1].node(), repo[p2].node())
1160 repo.setparents(repo[p1].node(), repo[p2].node())
1153
1161
1154 # Commit might fail if unresolved files exist
1162 # Commit might fail if unresolved files exist
1155 newnode = repo.commit(text=commitmsg, user=user, date=date,
1163 newnode = repo.commit(text=commitmsg, user=user, date=date,
1156 extra=extra, editor=editor)
1164 extra=extra, editor=editor)
1157
1165
1158 repo.dirstate.setbranch(repo[newnode].branch())
1166 repo.dirstate.setbranch(repo[newnode].branch())
1159 return newnode
1167 return newnode
1160
1168
1161 def rebasenode(repo, rev, p1, base, collapse, dest, wctx):
1169 def rebasenode(repo, rev, p1, base, collapse, dest, wctx):
1162 'Rebase a single revision rev on top of p1 using base as merge ancestor'
1170 'Rebase a single revision rev on top of p1 using base as merge ancestor'
1163 # Merge phase
1171 # Merge phase
1164 # Update to destination and merge it with local
1172 # Update to destination and merge it with local
1165 if wctx.isinmemory():
1173 if wctx.isinmemory():
1166 wctx.setbase(repo[p1])
1174 wctx.setbase(repo[p1])
1167 else:
1175 else:
1168 if repo['.'].rev() != p1:
1176 if repo['.'].rev() != p1:
1169 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
1177 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
1170 mergemod.update(repo, p1, False, True)
1178 mergemod.update(repo, p1, False, True)
1171 else:
1179 else:
1172 repo.ui.debug(" already in destination\n")
1180 repo.ui.debug(" already in destination\n")
1173 # This is, alas, necessary to invalidate workingctx's manifest cache,
1181 # This is, alas, necessary to invalidate workingctx's manifest cache,
1174 # as well as other data we litter on it in other places.
1182 # as well as other data we litter on it in other places.
1175 wctx = repo[None]
1183 wctx = repo[None]
1176 repo.dirstate.write(repo.currenttransaction())
1184 repo.dirstate.write(repo.currenttransaction())
1177 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
1185 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
1178 if base is not None:
1186 if base is not None:
1179 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
1187 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
1180 # When collapsing in-place, the parent is the common ancestor, we
1188 # When collapsing in-place, the parent is the common ancestor, we
1181 # have to allow merging with it.
1189 # have to allow merging with it.
1182 stats = mergemod.update(repo, rev, True, True, base, collapse,
1190 stats = mergemod.update(repo, rev, True, True, base, collapse,
1183 labels=['dest', 'source'], wc=wctx)
1191 labels=['dest', 'source'], wc=wctx)
1184 if collapse:
1192 if collapse:
1185 copies.duplicatecopies(repo, wctx, rev, dest)
1193 copies.duplicatecopies(repo, wctx, rev, dest)
1186 else:
1194 else:
1187 # If we're not using --collapse, we need to
1195 # If we're not using --collapse, we need to
1188 # duplicate copies between the revision we're
1196 # duplicate copies between the revision we're
1189 # rebasing and its first parent, but *not*
1197 # rebasing and its first parent, but *not*
1190 # duplicate any copies that have already been
1198 # duplicate any copies that have already been
1191 # performed in the destination.
1199 # performed in the destination.
1192 p1rev = repo[rev].p1().rev()
1200 p1rev = repo[rev].p1().rev()
1193 copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
1201 copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
1194 return stats
1202 return stats
1195
1203
1196 def adjustdest(repo, rev, destmap, state, skipped):
1204 def adjustdest(repo, rev, destmap, state, skipped):
1197 """adjust rebase destination given the current rebase state
1205 """adjust rebase destination given the current rebase state
1198
1206
1199 rev is what is being rebased. Return a list of two revs, which are the
1207 rev is what is being rebased. Return a list of two revs, which are the
1200 adjusted destinations for rev's p1 and p2, respectively. If a parent is
1208 adjusted destinations for rev's p1 and p2, respectively. If a parent is
1201 nullrev, return dest without adjustment for it.
1209 nullrev, return dest without adjustment for it.
1202
1210
1203 For example, when doing rebasing B+E to F, C to G, rebase will first move B
1211 For example, when doing rebasing B+E to F, C to G, rebase will first move B
1204 to B1, and E's destination will be adjusted from F to B1.
1212 to B1, and E's destination will be adjusted from F to B1.
1205
1213
1206 B1 <- written during rebasing B
1214 B1 <- written during rebasing B
1207 |
1215 |
1208 F <- original destination of B, E
1216 F <- original destination of B, E
1209 |
1217 |
1210 | E <- rev, which is being rebased
1218 | E <- rev, which is being rebased
1211 | |
1219 | |
1212 | D <- prev, one parent of rev being checked
1220 | D <- prev, one parent of rev being checked
1213 | |
1221 | |
1214 | x <- skipped, ex. no successor or successor in (::dest)
1222 | x <- skipped, ex. no successor or successor in (::dest)
1215 | |
1223 | |
1216 | C <- rebased as C', different destination
1224 | C <- rebased as C', different destination
1217 | |
1225 | |
1218 | B <- rebased as B1 C'
1226 | B <- rebased as B1 C'
1219 |/ |
1227 |/ |
1220 A G <- destination of C, different
1228 A G <- destination of C, different
1221
1229
1222 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
1230 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
1223 first move C to C1, G to G1, and when it's checking H, the adjusted
1231 first move C to C1, G to G1, and when it's checking H, the adjusted
1224 destinations will be [C1, G1].
1232 destinations will be [C1, G1].
1225
1233
1226 H C1 G1
1234 H C1 G1
1227 /| | /
1235 /| | /
1228 F G |/
1236 F G |/
1229 K | | -> K
1237 K | | -> K
1230 | C D |
1238 | C D |
1231 | |/ |
1239 | |/ |
1232 | B | ...
1240 | B | ...
1233 |/ |/
1241 |/ |/
1234 A A
1242 A A
1235
1243
1236 Besides, adjust dest according to existing rebase information. For example,
1244 Besides, adjust dest according to existing rebase information. For example,
1237
1245
1238 B C D B needs to be rebased on top of C, C needs to be rebased on top
1246 B C D B needs to be rebased on top of C, C needs to be rebased on top
1239 \|/ of D. We will rebase C first.
1247 \|/ of D. We will rebase C first.
1240 A
1248 A
1241
1249
1242 C' After rebasing C, when considering B's destination, use C'
1250 C' After rebasing C, when considering B's destination, use C'
1243 | instead of the original C.
1251 | instead of the original C.
1244 B D
1252 B D
1245 \ /
1253 \ /
1246 A
1254 A
1247 """
1255 """
1248 # pick already rebased revs with same dest from state as interesting source
1256 # pick already rebased revs with same dest from state as interesting source
1249 dest = destmap[rev]
1257 dest = destmap[rev]
1250 source = [s for s, d in state.items()
1258 source = [s for s, d in state.items()
1251 if d > 0 and destmap[s] == dest and s not in skipped]
1259 if d > 0 and destmap[s] == dest and s not in skipped]
1252
1260
1253 result = []
1261 result = []
1254 for prev in repo.changelog.parentrevs(rev):
1262 for prev in repo.changelog.parentrevs(rev):
1255 adjusted = dest
1263 adjusted = dest
1256 if prev != nullrev:
1264 if prev != nullrev:
1257 candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
1265 candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
1258 if candidate is not None:
1266 if candidate is not None:
1259 adjusted = state[candidate]
1267 adjusted = state[candidate]
1260 if adjusted == dest and dest in state:
1268 if adjusted == dest and dest in state:
1261 adjusted = state[dest]
1269 adjusted = state[dest]
1262 if adjusted == revtodo:
1270 if adjusted == revtodo:
1263 # sortsource should produce an order that makes this impossible
1271 # sortsource should produce an order that makes this impossible
1264 raise error.ProgrammingError(
1272 raise error.ProgrammingError(
1265 'rev %d should be rebased already at this time' % dest)
1273 'rev %d should be rebased already at this time' % dest)
1266 result.append(adjusted)
1274 result.append(adjusted)
1267 return result
1275 return result
1268
1276
1269 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
1277 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
1270 """
1278 """
1271 Abort if rebase will create divergence or rebase is noop because of markers
1279 Abort if rebase will create divergence or rebase is noop because of markers
1272
1280
1273 `rebaseobsrevs`: set of obsolete revision in source
1281 `rebaseobsrevs`: set of obsolete revision in source
1274 `rebaseobsskipped`: set of revisions from source skipped because they have
1282 `rebaseobsskipped`: set of revisions from source skipped because they have
1275 successors in destination or no non-obsolete successor.
1283 successors in destination or no non-obsolete successor.
1276 """
1284 """
1277 # Obsolete node with successors not in dest leads to divergence
1285 # Obsolete node with successors not in dest leads to divergence
1278 divergenceok = ui.configbool('experimental',
1286 divergenceok = ui.configbool('experimental',
1279 'evolution.allowdivergence')
1287 'evolution.allowdivergence')
1280 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
1288 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
1281
1289
1282 if divergencebasecandidates and not divergenceok:
1290 if divergencebasecandidates and not divergenceok:
1283 divhashes = (bytes(repo[r])
1291 divhashes = (bytes(repo[r])
1284 for r in divergencebasecandidates)
1292 for r in divergencebasecandidates)
1285 msg = _("this rebase will cause "
1293 msg = _("this rebase will cause "
1286 "divergences from: %s")
1294 "divergences from: %s")
1287 h = _("to force the rebase please set "
1295 h = _("to force the rebase please set "
1288 "experimental.evolution.allowdivergence=True")
1296 "experimental.evolution.allowdivergence=True")
1289 raise error.Abort(msg % (",".join(divhashes),), hint=h)
1297 raise error.Abort(msg % (",".join(divhashes),), hint=h)
1290
1298
1291 def successorrevs(unfi, rev):
1299 def successorrevs(unfi, rev):
1292 """yield revision numbers for successors of rev"""
1300 """yield revision numbers for successors of rev"""
1293 assert unfi.filtername is None
1301 assert unfi.filtername is None
1294 nodemap = unfi.changelog.nodemap
1302 nodemap = unfi.changelog.nodemap
1295 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
1303 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
1296 if s in nodemap:
1304 if s in nodemap:
1297 yield nodemap[s]
1305 yield nodemap[s]
1298
1306
1299 def defineparents(repo, rev, destmap, state, skipped, obsskipped):
1307 def defineparents(repo, rev, destmap, state, skipped, obsskipped):
1300 """Return new parents and optionally a merge base for rev being rebased
1308 """Return new parents and optionally a merge base for rev being rebased
1301
1309
1302 The destination specified by "dest" cannot always be used directly because
1310 The destination specified by "dest" cannot always be used directly because
1303 previously rebase result could affect destination. For example,
1311 previously rebase result could affect destination. For example,
1304
1312
1305 D E rebase -r C+D+E -d B
1313 D E rebase -r C+D+E -d B
1306 |/ C will be rebased to C'
1314 |/ C will be rebased to C'
1307 B C D's new destination will be C' instead of B
1315 B C D's new destination will be C' instead of B
1308 |/ E's new destination will be C' instead of B
1316 |/ E's new destination will be C' instead of B
1309 A
1317 A
1310
1318
1311 The new parents of a merge is slightly more complicated. See the comment
1319 The new parents of a merge is slightly more complicated. See the comment
1312 block below.
1320 block below.
1313 """
1321 """
1314 # use unfiltered changelog since successorrevs may return filtered nodes
1322 # use unfiltered changelog since successorrevs may return filtered nodes
1315 assert repo.filtername is None
1323 assert repo.filtername is None
1316 cl = repo.changelog
1324 cl = repo.changelog
1317 isancestor = cl.isancestorrev
1325 isancestor = cl.isancestorrev
1318
1326
1319 dest = destmap[rev]
1327 dest = destmap[rev]
1320 oldps = repo.changelog.parentrevs(rev) # old parents
1328 oldps = repo.changelog.parentrevs(rev) # old parents
1321 newps = [nullrev, nullrev] # new parents
1329 newps = [nullrev, nullrev] # new parents
1322 dests = adjustdest(repo, rev, destmap, state, skipped)
1330 dests = adjustdest(repo, rev, destmap, state, skipped)
1323 bases = list(oldps) # merge base candidates, initially just old parents
1331 bases = list(oldps) # merge base candidates, initially just old parents
1324
1332
1325 if all(r == nullrev for r in oldps[1:]):
1333 if all(r == nullrev for r in oldps[1:]):
1326 # For non-merge changeset, just move p to adjusted dest as requested.
1334 # For non-merge changeset, just move p to adjusted dest as requested.
1327 newps[0] = dests[0]
1335 newps[0] = dests[0]
1328 else:
1336 else:
1329 # For merge changeset, if we move p to dests[i] unconditionally, both
1337 # For merge changeset, if we move p to dests[i] unconditionally, both
1330 # parents may change and the end result looks like "the merge loses a
1338 # parents may change and the end result looks like "the merge loses a
1331 # parent", which is a surprise. This is a limit because "--dest" only
1339 # parent", which is a surprise. This is a limit because "--dest" only
1332 # accepts one dest per src.
1340 # accepts one dest per src.
1333 #
1341 #
1334 # Therefore, only move p with reasonable conditions (in this order):
1342 # Therefore, only move p with reasonable conditions (in this order):
1335 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1343 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1336 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1344 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1337 #
1345 #
1338 # Comparing with adjustdest, the logic here does some additional work:
1346 # Comparing with adjustdest, the logic here does some additional work:
1339 # 1. decide which parents will not be moved towards dest
1347 # 1. decide which parents will not be moved towards dest
1340 # 2. if the above decision is "no", should a parent still be moved
1348 # 2. if the above decision is "no", should a parent still be moved
1341 # because it was rebased?
1349 # because it was rebased?
1342 #
1350 #
1343 # For example:
1351 # For example:
1344 #
1352 #
1345 # C # "rebase -r C -d D" is an error since none of the parents
1353 # C # "rebase -r C -d D" is an error since none of the parents
1346 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1354 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1347 # A B D # B (using rule "2."), since B will be rebased.
1355 # A B D # B (using rule "2."), since B will be rebased.
1348 #
1356 #
1349 # The loop tries to be not rely on the fact that a Mercurial node has
1357 # The loop tries to be not rely on the fact that a Mercurial node has
1350 # at most 2 parents.
1358 # at most 2 parents.
1351 for i, p in enumerate(oldps):
1359 for i, p in enumerate(oldps):
1352 np = p # new parent
1360 np = p # new parent
1353 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1361 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1354 np = dests[i]
1362 np = dests[i]
1355 elif p in state and state[p] > 0:
1363 elif p in state and state[p] > 0:
1356 np = state[p]
1364 np = state[p]
1357
1365
1358 # "bases" only record "special" merge bases that cannot be
1366 # "bases" only record "special" merge bases that cannot be
1359 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1367 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1360 # For example:
1368 # For example:
1361 #
1369 #
1362 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1370 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1363 # | C # is B', but merge base for C is B, instead of
1371 # | C # is B', but merge base for C is B, instead of
1364 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1372 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1365 # | B # "state" edges are merged (so there will be an edge from
1373 # | B # "state" edges are merged (so there will be an edge from
1366 # |/ # B to B'), the merge base is still ancestor(C, B') in
1374 # |/ # B to B'), the merge base is still ancestor(C, B') in
1367 # A # the merged graph.
1375 # A # the merged graph.
1368 #
1376 #
1369 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1377 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1370 # which uses "virtual null merge" to explain this situation.
1378 # which uses "virtual null merge" to explain this situation.
1371 if isancestor(p, np):
1379 if isancestor(p, np):
1372 bases[i] = nullrev
1380 bases[i] = nullrev
1373
1381
1374 # If one parent becomes an ancestor of the other, drop the ancestor
1382 # If one parent becomes an ancestor of the other, drop the ancestor
1375 for j, x in enumerate(newps[:i]):
1383 for j, x in enumerate(newps[:i]):
1376 if x == nullrev:
1384 if x == nullrev:
1377 continue
1385 continue
1378 if isancestor(np, x): # CASE-1
1386 if isancestor(np, x): # CASE-1
1379 np = nullrev
1387 np = nullrev
1380 elif isancestor(x, np): # CASE-2
1388 elif isancestor(x, np): # CASE-2
1381 newps[j] = np
1389 newps[j] = np
1382 np = nullrev
1390 np = nullrev
1383 # New parents forming an ancestor relationship does not
1391 # New parents forming an ancestor relationship does not
1384 # mean the old parents have a similar relationship. Do not
1392 # mean the old parents have a similar relationship. Do not
1385 # set bases[x] to nullrev.
1393 # set bases[x] to nullrev.
1386 bases[j], bases[i] = bases[i], bases[j]
1394 bases[j], bases[i] = bases[i], bases[j]
1387
1395
1388 newps[i] = np
1396 newps[i] = np
1389
1397
1390 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1398 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1391 # base. If only p2 changes, merging using unchanged p1 as merge base is
1399 # base. If only p2 changes, merging using unchanged p1 as merge base is
1392 # suboptimal. Therefore swap parents to make the merge sane.
1400 # suboptimal. Therefore swap parents to make the merge sane.
1393 if newps[1] != nullrev and oldps[0] == newps[0]:
1401 if newps[1] != nullrev and oldps[0] == newps[0]:
1394 assert len(newps) == 2 and len(oldps) == 2
1402 assert len(newps) == 2 and len(oldps) == 2
1395 newps.reverse()
1403 newps.reverse()
1396 bases.reverse()
1404 bases.reverse()
1397
1405
1398 # No parent change might be an error because we fail to make rev a
1406 # No parent change might be an error because we fail to make rev a
1399 # descendent of requested dest. This can happen, for example:
1407 # descendent of requested dest. This can happen, for example:
1400 #
1408 #
1401 # C # rebase -r C -d D
1409 # C # rebase -r C -d D
1402 # /| # None of A and B will be changed to D and rebase fails.
1410 # /| # None of A and B will be changed to D and rebase fails.
1403 # A B D
1411 # A B D
1404 if set(newps) == set(oldps) and dest not in newps:
1412 if set(newps) == set(oldps) and dest not in newps:
1405 raise error.Abort(_('cannot rebase %d:%s without '
1413 raise error.Abort(_('cannot rebase %d:%s without '
1406 'moving at least one of its parents')
1414 'moving at least one of its parents')
1407 % (rev, repo[rev]))
1415 % (rev, repo[rev]))
1408
1416
1409 # Source should not be ancestor of dest. The check here guarantees it's
1417 # Source should not be ancestor of dest. The check here guarantees it's
1410 # impossible. With multi-dest, the initial check does not cover complex
1418 # impossible. With multi-dest, the initial check does not cover complex
1411 # cases since we don't have abstractions to dry-run rebase cheaply.
1419 # cases since we don't have abstractions to dry-run rebase cheaply.
1412 if any(p != nullrev and isancestor(rev, p) for p in newps):
1420 if any(p != nullrev and isancestor(rev, p) for p in newps):
1413 raise error.Abort(_('source is ancestor of destination'))
1421 raise error.Abort(_('source is ancestor of destination'))
1414
1422
1415 # "rebasenode" updates to new p1, use the corresponding merge base.
1423 # "rebasenode" updates to new p1, use the corresponding merge base.
1416 if bases[0] != nullrev:
1424 if bases[0] != nullrev:
1417 base = bases[0]
1425 base = bases[0]
1418 else:
1426 else:
1419 base = None
1427 base = None
1420
1428
1421 # Check if the merge will contain unwanted changes. That may happen if
1429 # Check if the merge will contain unwanted changes. That may happen if
1422 # there are multiple special (non-changelog ancestor) merge bases, which
1430 # there are multiple special (non-changelog ancestor) merge bases, which
1423 # cannot be handled well by the 3-way merge algorithm. For example:
1431 # cannot be handled well by the 3-way merge algorithm. For example:
1424 #
1432 #
1425 # F
1433 # F
1426 # /|
1434 # /|
1427 # D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
1435 # D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
1428 # | | # as merge base, the difference between D and F will include
1436 # | | # as merge base, the difference between D and F will include
1429 # B C # C, so the rebased F will contain C surprisingly. If "E" was
1437 # B C # C, so the rebased F will contain C surprisingly. If "E" was
1430 # |/ # chosen, the rebased F will contain B.
1438 # |/ # chosen, the rebased F will contain B.
1431 # A Z
1439 # A Z
1432 #
1440 #
1433 # But our merge base candidates (D and E in above case) could still be
1441 # But our merge base candidates (D and E in above case) could still be
1434 # better than the default (ancestor(F, Z) == null). Therefore still
1442 # better than the default (ancestor(F, Z) == null). Therefore still
1435 # pick one (so choose p1 above).
1443 # pick one (so choose p1 above).
1436 if sum(1 for b in bases if b != nullrev) > 1:
1444 if sum(1 for b in bases if b != nullrev) > 1:
1437 unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
1445 unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
1438 for i, base in enumerate(bases):
1446 for i, base in enumerate(bases):
1439 if base == nullrev:
1447 if base == nullrev:
1440 continue
1448 continue
1441 # Revisions in the side (not chosen as merge base) branch that
1449 # Revisions in the side (not chosen as merge base) branch that
1442 # might contain "surprising" contents
1450 # might contain "surprising" contents
1443 siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
1451 siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
1444 bases, base, base, dest))
1452 bases, base, base, dest))
1445
1453
1446 # If those revisions are covered by rebaseset, the result is good.
1454 # If those revisions are covered by rebaseset, the result is good.
1447 # A merge in rebaseset would be considered to cover its ancestors.
1455 # A merge in rebaseset would be considered to cover its ancestors.
1448 if siderevs:
1456 if siderevs:
1449 rebaseset = [r for r, d in state.items()
1457 rebaseset = [r for r, d in state.items()
1450 if d > 0 and r not in obsskipped]
1458 if d > 0 and r not in obsskipped]
1451 merges = [r for r in rebaseset
1459 merges = [r for r in rebaseset
1452 if cl.parentrevs(r)[1] != nullrev]
1460 if cl.parentrevs(r)[1] != nullrev]
1453 unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
1461 unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
1454 siderevs, merges, rebaseset))
1462 siderevs, merges, rebaseset))
1455
1463
1456 # Choose a merge base that has a minimal number of unwanted revs.
1464 # Choose a merge base that has a minimal number of unwanted revs.
1457 l, i = min((len(revs), i)
1465 l, i = min((len(revs), i)
1458 for i, revs in enumerate(unwanted) if revs is not None)
1466 for i, revs in enumerate(unwanted) if revs is not None)
1459 base = bases[i]
1467 base = bases[i]
1460
1468
1461 # newps[0] should match merge base if possible. Currently, if newps[i]
1469 # newps[0] should match merge base if possible. Currently, if newps[i]
1462 # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
1470 # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
1463 # the other's ancestor. In that case, it's fine to not swap newps here.
1471 # the other's ancestor. In that case, it's fine to not swap newps here.
1464 # (see CASE-1 and CASE-2 above)
1472 # (see CASE-1 and CASE-2 above)
1465 if i != 0 and newps[i] != nullrev:
1473 if i != 0 and newps[i] != nullrev:
1466 newps[0], newps[i] = newps[i], newps[0]
1474 newps[0], newps[i] = newps[i], newps[0]
1467
1475
1468 # The merge will include unwanted revisions. Abort now. Revisit this if
1476 # The merge will include unwanted revisions. Abort now. Revisit this if
1469 # we have a more advanced merge algorithm that handles multiple bases.
1477 # we have a more advanced merge algorithm that handles multiple bases.
1470 if l > 0:
1478 if l > 0:
1471 unwanteddesc = _(' or ').join(
1479 unwanteddesc = _(' or ').join(
1472 (', '.join('%d:%s' % (r, repo[r]) for r in revs)
1480 (', '.join('%d:%s' % (r, repo[r]) for r in revs)
1473 for revs in unwanted if revs is not None))
1481 for revs in unwanted if revs is not None))
1474 raise error.Abort(
1482 raise error.Abort(
1475 _('rebasing %d:%s will include unwanted changes from %s')
1483 _('rebasing %d:%s will include unwanted changes from %s')
1476 % (rev, repo[rev], unwanteddesc))
1484 % (rev, repo[rev], unwanteddesc))
1477
1485
1478 repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
1486 repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
1479
1487
1480 return newps[0], newps[1], base
1488 return newps[0], newps[1], base
1481
1489
1482 def isagitpatch(repo, patchname):
1490 def isagitpatch(repo, patchname):
1483 'Return true if the given patch is in git format'
1491 'Return true if the given patch is in git format'
1484 mqpatch = os.path.join(repo.mq.path, patchname)
1492 mqpatch = os.path.join(repo.mq.path, patchname)
1485 for line in patch.linereader(open(mqpatch, 'rb')):
1493 for line in patch.linereader(open(mqpatch, 'rb')):
1486 if line.startswith('diff --git'):
1494 if line.startswith('diff --git'):
1487 return True
1495 return True
1488 return False
1496 return False
1489
1497
1490 def updatemq(repo, state, skipped, **opts):
1498 def updatemq(repo, state, skipped, **opts):
1491 'Update rebased mq patches - finalize and then import them'
1499 'Update rebased mq patches - finalize and then import them'
1492 mqrebase = {}
1500 mqrebase = {}
1493 mq = repo.mq
1501 mq = repo.mq
1494 original_series = mq.fullseries[:]
1502 original_series = mq.fullseries[:]
1495 skippedpatches = set()
1503 skippedpatches = set()
1496
1504
1497 for p in mq.applied:
1505 for p in mq.applied:
1498 rev = repo[p.node].rev()
1506 rev = repo[p.node].rev()
1499 if rev in state:
1507 if rev in state:
1500 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1508 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1501 (rev, p.name))
1509 (rev, p.name))
1502 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1510 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1503 else:
1511 else:
1504 # Applied but not rebased, not sure this should happen
1512 # Applied but not rebased, not sure this should happen
1505 skippedpatches.add(p.name)
1513 skippedpatches.add(p.name)
1506
1514
1507 if mqrebase:
1515 if mqrebase:
1508 mq.finish(repo, mqrebase.keys())
1516 mq.finish(repo, mqrebase.keys())
1509
1517
1510 # We must start import from the newest revision
1518 # We must start import from the newest revision
1511 for rev in sorted(mqrebase, reverse=True):
1519 for rev in sorted(mqrebase, reverse=True):
1512 if rev not in skipped:
1520 if rev not in skipped:
1513 name, isgit = mqrebase[rev]
1521 name, isgit = mqrebase[rev]
1514 repo.ui.note(_('updating mq patch %s to %d:%s\n') %
1522 repo.ui.note(_('updating mq patch %s to %d:%s\n') %
1515 (name, state[rev], repo[state[rev]]))
1523 (name, state[rev], repo[state[rev]]))
1516 mq.qimport(repo, (), patchname=name, git=isgit,
1524 mq.qimport(repo, (), patchname=name, git=isgit,
1517 rev=["%d" % state[rev]])
1525 rev=["%d" % state[rev]])
1518 else:
1526 else:
1519 # Rebased and skipped
1527 # Rebased and skipped
1520 skippedpatches.add(mqrebase[rev][0])
1528 skippedpatches.add(mqrebase[rev][0])
1521
1529
1522 # Patches were either applied and rebased and imported in
1530 # Patches were either applied and rebased and imported in
1523 # order, applied and removed or unapplied. Discard the removed
1531 # order, applied and removed or unapplied. Discard the removed
1524 # ones while preserving the original series order and guards.
1532 # ones while preserving the original series order and guards.
1525 newseries = [s for s in original_series
1533 newseries = [s for s in original_series
1526 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1534 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1527 mq.fullseries[:] = newseries
1535 mq.fullseries[:] = newseries
1528 mq.seriesdirty = True
1536 mq.seriesdirty = True
1529 mq.savedirty()
1537 mq.savedirty()
1530
1538
1531 def storecollapsemsg(repo, collapsemsg):
1539 def storecollapsemsg(repo, collapsemsg):
1532 'Store the collapse message to allow recovery'
1540 'Store the collapse message to allow recovery'
1533 collapsemsg = collapsemsg or ''
1541 collapsemsg = collapsemsg or ''
1534 f = repo.vfs("last-message.txt", "w")
1542 f = repo.vfs("last-message.txt", "w")
1535 f.write("%s\n" % collapsemsg)
1543 f.write("%s\n" % collapsemsg)
1536 f.close()
1544 f.close()
1537
1545
1538 def clearcollapsemsg(repo):
1546 def clearcollapsemsg(repo):
1539 'Remove collapse message file'
1547 'Remove collapse message file'
1540 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1548 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1541
1549
1542 def restorecollapsemsg(repo, isabort):
1550 def restorecollapsemsg(repo, isabort):
1543 'Restore previously stored collapse message'
1551 'Restore previously stored collapse message'
1544 try:
1552 try:
1545 f = repo.vfs("last-message.txt")
1553 f = repo.vfs("last-message.txt")
1546 collapsemsg = f.readline().strip()
1554 collapsemsg = f.readline().strip()
1547 f.close()
1555 f.close()
1548 except IOError as err:
1556 except IOError as err:
1549 if err.errno != errno.ENOENT:
1557 if err.errno != errno.ENOENT:
1550 raise
1558 raise
1551 if isabort:
1559 if isabort:
1552 # Oh well, just abort like normal
1560 # Oh well, just abort like normal
1553 collapsemsg = ''
1561 collapsemsg = ''
1554 else:
1562 else:
1555 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1563 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1556 return collapsemsg
1564 return collapsemsg
1557
1565
1558 def clearstatus(repo):
1566 def clearstatus(repo):
1559 'Remove the status files'
1567 'Remove the status files'
1560 # Make sure the active transaction won't write the state file
1568 # Make sure the active transaction won't write the state file
1561 tr = repo.currenttransaction()
1569 tr = repo.currenttransaction()
1562 if tr:
1570 if tr:
1563 tr.removefilegenerator('rebasestate')
1571 tr.removefilegenerator('rebasestate')
1564 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1572 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1565
1573
1566 def needupdate(repo, state):
1574 def needupdate(repo, state):
1567 '''check whether we should `update --clean` away from a merge, or if
1575 '''check whether we should `update --clean` away from a merge, or if
1568 somehow the working dir got forcibly updated, e.g. by older hg'''
1576 somehow the working dir got forcibly updated, e.g. by older hg'''
1569 parents = [p.rev() for p in repo[None].parents()]
1577 parents = [p.rev() for p in repo[None].parents()]
1570
1578
1571 # Are we in a merge state at all?
1579 # Are we in a merge state at all?
1572 if len(parents) < 2:
1580 if len(parents) < 2:
1573 return False
1581 return False
1574
1582
1575 # We should be standing on the first as-of-yet unrebased commit.
1583 # We should be standing on the first as-of-yet unrebased commit.
1576 firstunrebased = min([old for old, new in state.iteritems()
1584 firstunrebased = min([old for old, new in state.iteritems()
1577 if new == nullrev])
1585 if new == nullrev])
1578 if firstunrebased in parents:
1586 if firstunrebased in parents:
1579 return True
1587 return True
1580
1588
1581 return False
1589 return False
1582
1590
1583 def abort(repo, originalwd, destmap, state, activebookmark=None, backup=True,
1591 def abort(repo, originalwd, destmap, state, activebookmark=None, backup=True,
1584 suppwarns=False):
1592 suppwarns=False):
1585 '''Restore the repository to its original state. Additional args:
1593 '''Restore the repository to its original state. Additional args:
1586
1594
1587 activebookmark: the name of the bookmark that should be active after the
1595 activebookmark: the name of the bookmark that should be active after the
1588 restore'''
1596 restore'''
1589
1597
1590 try:
1598 try:
1591 # If the first commits in the rebased set get skipped during the rebase,
1599 # If the first commits in the rebased set get skipped during the rebase,
1592 # their values within the state mapping will be the dest rev id. The
1600 # their values within the state mapping will be the dest rev id. The
1593 # rebased list must must not contain the dest rev (issue4896)
1601 # rebased list must must not contain the dest rev (issue4896)
1594 rebased = [s for r, s in state.items()
1602 rebased = [s for r, s in state.items()
1595 if s >= 0 and s != r and s != destmap[r]]
1603 if s >= 0 and s != r and s != destmap[r]]
1596 immutable = [d for d in rebased if not repo[d].mutable()]
1604 immutable = [d for d in rebased if not repo[d].mutable()]
1597 cleanup = True
1605 cleanup = True
1598 if immutable:
1606 if immutable:
1599 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1607 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1600 % ', '.join(bytes(repo[r]) for r in immutable),
1608 % ', '.join(bytes(repo[r]) for r in immutable),
1601 hint=_("see 'hg help phases' for details"))
1609 hint=_("see 'hg help phases' for details"))
1602 cleanup = False
1610 cleanup = False
1603
1611
1604 descendants = set()
1612 descendants = set()
1605 if rebased:
1613 if rebased:
1606 descendants = set(repo.changelog.descendants(rebased))
1614 descendants = set(repo.changelog.descendants(rebased))
1607 if descendants - set(rebased):
1615 if descendants - set(rebased):
1608 repo.ui.warn(_("warning: new changesets detected on destination "
1616 repo.ui.warn(_("warning: new changesets detected on destination "
1609 "branch, can't strip\n"))
1617 "branch, can't strip\n"))
1610 cleanup = False
1618 cleanup = False
1611
1619
1612 if cleanup:
1620 if cleanup:
1613 shouldupdate = False
1621 shouldupdate = False
1614 if rebased:
1622 if rebased:
1615 strippoints = [
1623 strippoints = [
1616 c.node() for c in repo.set('roots(%ld)', rebased)]
1624 c.node() for c in repo.set('roots(%ld)', rebased)]
1617
1625
1618 updateifonnodes = set(rebased)
1626 updateifonnodes = set(rebased)
1619 updateifonnodes.update(destmap.values())
1627 updateifonnodes.update(destmap.values())
1620 updateifonnodes.add(originalwd)
1628 updateifonnodes.add(originalwd)
1621 shouldupdate = repo['.'].rev() in updateifonnodes
1629 shouldupdate = repo['.'].rev() in updateifonnodes
1622
1630
1623 # Update away from the rebase if necessary
1631 # Update away from the rebase if necessary
1624 if shouldupdate or needupdate(repo, state):
1632 if shouldupdate or needupdate(repo, state):
1625 mergemod.update(repo, originalwd, False, True)
1633 mergemod.update(repo, originalwd, False, True)
1626
1634
1627 # Strip from the first rebased revision
1635 # Strip from the first rebased revision
1628 if rebased:
1636 if rebased:
1629 repair.strip(repo.ui, repo, strippoints, backup=backup)
1637 repair.strip(repo.ui, repo, strippoints, backup=backup)
1630
1638
1631 if activebookmark and activebookmark in repo._bookmarks:
1639 if activebookmark and activebookmark in repo._bookmarks:
1632 bookmarks.activate(repo, activebookmark)
1640 bookmarks.activate(repo, activebookmark)
1633
1641
1634 finally:
1642 finally:
1635 clearstatus(repo)
1643 clearstatus(repo)
1636 clearcollapsemsg(repo)
1644 clearcollapsemsg(repo)
1637 if not suppwarns:
1645 if not suppwarns:
1638 repo.ui.warn(_('rebase aborted\n'))
1646 repo.ui.warn(_('rebase aborted\n'))
1639 return 0
1647 return 0
1640
1648
1641 def sortsource(destmap):
1649 def sortsource(destmap):
1642 """yield source revisions in an order that we only rebase things once
1650 """yield source revisions in an order that we only rebase things once
1643
1651
1644 If source and destination overlaps, we should filter out revisions
1652 If source and destination overlaps, we should filter out revisions
1645 depending on other revisions which hasn't been rebased yet.
1653 depending on other revisions which hasn't been rebased yet.
1646
1654
1647 Yield a sorted list of revisions each time.
1655 Yield a sorted list of revisions each time.
1648
1656
1649 For example, when rebasing A to B, B to C. This function yields [B], then
1657 For example, when rebasing A to B, B to C. This function yields [B], then
1650 [A], indicating B needs to be rebased first.
1658 [A], indicating B needs to be rebased first.
1651
1659
1652 Raise if there is a cycle so the rebase is impossible.
1660 Raise if there is a cycle so the rebase is impossible.
1653 """
1661 """
1654 srcset = set(destmap)
1662 srcset = set(destmap)
1655 while srcset:
1663 while srcset:
1656 srclist = sorted(srcset)
1664 srclist = sorted(srcset)
1657 result = []
1665 result = []
1658 for r in srclist:
1666 for r in srclist:
1659 if destmap[r] not in srcset:
1667 if destmap[r] not in srcset:
1660 result.append(r)
1668 result.append(r)
1661 if not result:
1669 if not result:
1662 raise error.Abort(_('source and destination form a cycle'))
1670 raise error.Abort(_('source and destination form a cycle'))
1663 srcset -= set(result)
1671 srcset -= set(result)
1664 yield result
1672 yield result
1665
1673
1666 def buildstate(repo, destmap, collapse):
1674 def buildstate(repo, destmap, collapse):
1667 '''Define which revisions are going to be rebased and where
1675 '''Define which revisions are going to be rebased and where
1668
1676
1669 repo: repo
1677 repo: repo
1670 destmap: {srcrev: destrev}
1678 destmap: {srcrev: destrev}
1671 '''
1679 '''
1672 rebaseset = destmap.keys()
1680 rebaseset = destmap.keys()
1673 originalwd = repo['.'].rev()
1681 originalwd = repo['.'].rev()
1674
1682
1675 # This check isn't strictly necessary, since mq detects commits over an
1683 # This check isn't strictly necessary, since mq detects commits over an
1676 # applied patch. But it prevents messing up the working directory when
1684 # applied patch. But it prevents messing up the working directory when
1677 # a partially completed rebase is blocked by mq.
1685 # a partially completed rebase is blocked by mq.
1678 if 'qtip' in repo.tags():
1686 if 'qtip' in repo.tags():
1679 mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
1687 mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
1680 if set(destmap.values()) & mqapplied:
1688 if set(destmap.values()) & mqapplied:
1681 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1689 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1682
1690
1683 # Get "cycle" error early by exhausting the generator.
1691 # Get "cycle" error early by exhausting the generator.
1684 sortedsrc = list(sortsource(destmap)) # a list of sorted revs
1692 sortedsrc = list(sortsource(destmap)) # a list of sorted revs
1685 if not sortedsrc:
1693 if not sortedsrc:
1686 raise error.Abort(_('no matching revisions'))
1694 raise error.Abort(_('no matching revisions'))
1687
1695
1688 # Only check the first batch of revisions to rebase not depending on other
1696 # Only check the first batch of revisions to rebase not depending on other
1689 # rebaseset. This means "source is ancestor of destination" for the second
1697 # rebaseset. This means "source is ancestor of destination" for the second
1690 # (and following) batches of revisions are not checked here. We rely on
1698 # (and following) batches of revisions are not checked here. We rely on
1691 # "defineparents" to do that check.
1699 # "defineparents" to do that check.
1692 roots = list(repo.set('roots(%ld)', sortedsrc[0]))
1700 roots = list(repo.set('roots(%ld)', sortedsrc[0]))
1693 if not roots:
1701 if not roots:
1694 raise error.Abort(_('no matching revisions'))
1702 raise error.Abort(_('no matching revisions'))
1695 def revof(r):
1703 def revof(r):
1696 return r.rev()
1704 return r.rev()
1697 roots = sorted(roots, key=revof)
1705 roots = sorted(roots, key=revof)
1698 state = dict.fromkeys(rebaseset, revtodo)
1706 state = dict.fromkeys(rebaseset, revtodo)
1699 emptyrebase = (len(sortedsrc) == 1)
1707 emptyrebase = (len(sortedsrc) == 1)
1700 for root in roots:
1708 for root in roots:
1701 dest = repo[destmap[root.rev()]]
1709 dest = repo[destmap[root.rev()]]
1702 commonbase = root.ancestor(dest)
1710 commonbase = root.ancestor(dest)
1703 if commonbase == root:
1711 if commonbase == root:
1704 raise error.Abort(_('source is ancestor of destination'))
1712 raise error.Abort(_('source is ancestor of destination'))
1705 if commonbase == dest:
1713 if commonbase == dest:
1706 wctx = repo[None]
1714 wctx = repo[None]
1707 if dest == wctx.p1():
1715 if dest == wctx.p1():
1708 # when rebasing to '.', it will use the current wd branch name
1716 # when rebasing to '.', it will use the current wd branch name
1709 samebranch = root.branch() == wctx.branch()
1717 samebranch = root.branch() == wctx.branch()
1710 else:
1718 else:
1711 samebranch = root.branch() == dest.branch()
1719 samebranch = root.branch() == dest.branch()
1712 if not collapse and samebranch and dest in root.parents():
1720 if not collapse and samebranch and dest in root.parents():
1713 # mark the revision as done by setting its new revision
1721 # mark the revision as done by setting its new revision
1714 # equal to its old (current) revisions
1722 # equal to its old (current) revisions
1715 state[root.rev()] = root.rev()
1723 state[root.rev()] = root.rev()
1716 repo.ui.debug('source is a child of destination\n')
1724 repo.ui.debug('source is a child of destination\n')
1717 continue
1725 continue
1718
1726
1719 emptyrebase = False
1727 emptyrebase = False
1720 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1728 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1721 if emptyrebase:
1729 if emptyrebase:
1722 return None
1730 return None
1723 for rev in sorted(state):
1731 for rev in sorted(state):
1724 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1732 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1725 # if all parents of this revision are done, then so is this revision
1733 # if all parents of this revision are done, then so is this revision
1726 if parents and all((state.get(p) == p for p in parents)):
1734 if parents and all((state.get(p) == p for p in parents)):
1727 state[rev] = rev
1735 state[rev] = rev
1728 return originalwd, destmap, state
1736 return originalwd, destmap, state
1729
1737
1730 def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None,
1738 def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None,
1731 keepf=False, fm=None):
1739 keepf=False, fm=None, backup=True):
1732 """dispose of rebased revision at the end of the rebase
1740 """dispose of rebased revision at the end of the rebase
1733
1741
1734 If `collapsedas` is not None, the rebase was a collapse whose result if the
1742 If `collapsedas` is not None, the rebase was a collapse whose result if the
1735 `collapsedas` node.
1743 `collapsedas` node.
1736
1744
1737 If `keepf` is not True, the rebase has --keep set and no nodes should be
1745 If `keepf` is not True, the rebase has --keep set and no nodes should be
1738 removed (but bookmarks still need to be moved).
1746 removed (but bookmarks still need to be moved).
1747
1748 If `backup` is False, no backup will be stored when stripping rebased
1749 revisions.
1739 """
1750 """
1740 tonode = repo.changelog.node
1751 tonode = repo.changelog.node
1741 replacements = {}
1752 replacements = {}
1742 moves = {}
1753 moves = {}
1743 for rev, newrev in sorted(state.items()):
1754 for rev, newrev in sorted(state.items()):
1744 if newrev >= 0 and newrev != rev:
1755 if newrev >= 0 and newrev != rev:
1745 oldnode = tonode(rev)
1756 oldnode = tonode(rev)
1746 newnode = collapsedas or tonode(newrev)
1757 newnode = collapsedas or tonode(newrev)
1747 moves[oldnode] = newnode
1758 moves[oldnode] = newnode
1748 if not keepf:
1759 if not keepf:
1749 if rev in skipped:
1760 if rev in skipped:
1750 succs = ()
1761 succs = ()
1751 else:
1762 else:
1752 succs = (newnode,)
1763 succs = (newnode,)
1753 replacements[oldnode] = succs
1764 replacements[oldnode] = succs
1754 scmutil.cleanupnodes(repo, replacements, 'rebase', moves)
1765 scmutil.cleanupnodes(repo, replacements, 'rebase', moves, backup=backup)
1755 if fm:
1766 if fm:
1756 hf = fm.hexfunc
1767 hf = fm.hexfunc
1757 fl = fm.formatlist
1768 fl = fm.formatlist
1758 fd = fm.formatdict
1769 fd = fm.formatdict
1759 nodechanges = fd({hf(oldn): fl([hf(n) for n in newn], name='node')
1770 nodechanges = fd({hf(oldn): fl([hf(n) for n in newn], name='node')
1760 for oldn, newn in replacements.iteritems()},
1771 for oldn, newn in replacements.iteritems()},
1761 key="oldnode", value="newnodes")
1772 key="oldnode", value="newnodes")
1762 fm.data(nodechanges=nodechanges)
1773 fm.data(nodechanges=nodechanges)
1763
1774
1764 def pullrebase(orig, ui, repo, *args, **opts):
1775 def pullrebase(orig, ui, repo, *args, **opts):
1765 'Call rebase after pull if the latter has been invoked with --rebase'
1776 'Call rebase after pull if the latter has been invoked with --rebase'
1766 ret = None
1777 ret = None
1767 if opts.get(r'rebase'):
1778 if opts.get(r'rebase'):
1768 if ui.configbool('commands', 'rebase.requiredest'):
1779 if ui.configbool('commands', 'rebase.requiredest'):
1769 msg = _('rebase destination required by configuration')
1780 msg = _('rebase destination required by configuration')
1770 hint = _('use hg pull followed by hg rebase -d DEST')
1781 hint = _('use hg pull followed by hg rebase -d DEST')
1771 raise error.Abort(msg, hint=hint)
1782 raise error.Abort(msg, hint=hint)
1772
1783
1773 with repo.wlock(), repo.lock():
1784 with repo.wlock(), repo.lock():
1774 if opts.get(r'update'):
1785 if opts.get(r'update'):
1775 del opts[r'update']
1786 del opts[r'update']
1776 ui.debug('--update and --rebase are not compatible, ignoring '
1787 ui.debug('--update and --rebase are not compatible, ignoring '
1777 'the update flag\n')
1788 'the update flag\n')
1778
1789
1779 cmdutil.checkunfinished(repo)
1790 cmdutil.checkunfinished(repo)
1780 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1791 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1781 'please commit or shelve your changes first'))
1792 'please commit or shelve your changes first'))
1782
1793
1783 revsprepull = len(repo)
1794 revsprepull = len(repo)
1784 origpostincoming = commands.postincoming
1795 origpostincoming = commands.postincoming
1785 def _dummy(*args, **kwargs):
1796 def _dummy(*args, **kwargs):
1786 pass
1797 pass
1787 commands.postincoming = _dummy
1798 commands.postincoming = _dummy
1788 try:
1799 try:
1789 ret = orig(ui, repo, *args, **opts)
1800 ret = orig(ui, repo, *args, **opts)
1790 finally:
1801 finally:
1791 commands.postincoming = origpostincoming
1802 commands.postincoming = origpostincoming
1792 revspostpull = len(repo)
1803 revspostpull = len(repo)
1793 if revspostpull > revsprepull:
1804 if revspostpull > revsprepull:
1794 # --rev option from pull conflict with rebase own --rev
1805 # --rev option from pull conflict with rebase own --rev
1795 # dropping it
1806 # dropping it
1796 if r'rev' in opts:
1807 if r'rev' in opts:
1797 del opts[r'rev']
1808 del opts[r'rev']
1798 # positional argument from pull conflicts with rebase's own
1809 # positional argument from pull conflicts with rebase's own
1799 # --source.
1810 # --source.
1800 if r'source' in opts:
1811 if r'source' in opts:
1801 del opts[r'source']
1812 del opts[r'source']
1802 # revsprepull is the len of the repo, not revnum of tip.
1813 # revsprepull is the len of the repo, not revnum of tip.
1803 destspace = list(repo.changelog.revs(start=revsprepull))
1814 destspace = list(repo.changelog.revs(start=revsprepull))
1804 opts[r'_destspace'] = destspace
1815 opts[r'_destspace'] = destspace
1805 try:
1816 try:
1806 rebase(ui, repo, **opts)
1817 rebase(ui, repo, **opts)
1807 except error.NoMergeDestAbort:
1818 except error.NoMergeDestAbort:
1808 # we can maybe update instead
1819 # we can maybe update instead
1809 rev, _a, _b = destutil.destupdate(repo)
1820 rev, _a, _b = destutil.destupdate(repo)
1810 if rev == repo['.'].rev():
1821 if rev == repo['.'].rev():
1811 ui.status(_('nothing to rebase\n'))
1822 ui.status(_('nothing to rebase\n'))
1812 else:
1823 else:
1813 ui.status(_('nothing to rebase - updating instead\n'))
1824 ui.status(_('nothing to rebase - updating instead\n'))
1814 # not passing argument to get the bare update behavior
1825 # not passing argument to get the bare update behavior
1815 # with warning and trumpets
1826 # with warning and trumpets
1816 commands.update(ui, repo)
1827 commands.update(ui, repo)
1817 else:
1828 else:
1818 if opts.get(r'tool'):
1829 if opts.get(r'tool'):
1819 raise error.Abort(_('--tool can only be used with --rebase'))
1830 raise error.Abort(_('--tool can only be used with --rebase'))
1820 ret = orig(ui, repo, *args, **opts)
1831 ret = orig(ui, repo, *args, **opts)
1821
1832
1822 return ret
1833 return ret
1823
1834
1824 def _filterobsoleterevs(repo, revs):
1835 def _filterobsoleterevs(repo, revs):
1825 """returns a set of the obsolete revisions in revs"""
1836 """returns a set of the obsolete revisions in revs"""
1826 return set(r for r in revs if repo[r].obsolete())
1837 return set(r for r in revs if repo[r].obsolete())
1827
1838
1828 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
1839 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
1829 """Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
1840 """Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
1830
1841
1831 `obsoletenotrebased` is a mapping mapping obsolete => successor for all
1842 `obsoletenotrebased` is a mapping mapping obsolete => successor for all
1832 obsolete nodes to be rebased given in `rebaseobsrevs`.
1843 obsolete nodes to be rebased given in `rebaseobsrevs`.
1833
1844
1834 `obsoletewithoutsuccessorindestination` is a set with obsolete revisions
1845 `obsoletewithoutsuccessorindestination` is a set with obsolete revisions
1835 without a successor in destination.
1846 without a successor in destination.
1836
1847
1837 `obsoleteextinctsuccessors` is a set of obsolete revisions with only
1848 `obsoleteextinctsuccessors` is a set of obsolete revisions with only
1838 obsolete successors.
1849 obsolete successors.
1839 """
1850 """
1840 obsoletenotrebased = {}
1851 obsoletenotrebased = {}
1841 obsoletewithoutsuccessorindestination = set([])
1852 obsoletewithoutsuccessorindestination = set([])
1842 obsoleteextinctsuccessors = set([])
1853 obsoleteextinctsuccessors = set([])
1843
1854
1844 assert repo.filtername is None
1855 assert repo.filtername is None
1845 cl = repo.changelog
1856 cl = repo.changelog
1846 nodemap = cl.nodemap
1857 nodemap = cl.nodemap
1847 extinctrevs = set(repo.revs('extinct()'))
1858 extinctrevs = set(repo.revs('extinct()'))
1848 for srcrev in rebaseobsrevs:
1859 for srcrev in rebaseobsrevs:
1849 srcnode = cl.node(srcrev)
1860 srcnode = cl.node(srcrev)
1850 # XXX: more advanced APIs are required to handle split correctly
1861 # XXX: more advanced APIs are required to handle split correctly
1851 successors = set(obsutil.allsuccessors(repo.obsstore, [srcnode]))
1862 successors = set(obsutil.allsuccessors(repo.obsstore, [srcnode]))
1852 # obsutil.allsuccessors includes node itself
1863 # obsutil.allsuccessors includes node itself
1853 successors.remove(srcnode)
1864 successors.remove(srcnode)
1854 succrevs = {nodemap[s] for s in successors if s in nodemap}
1865 succrevs = {nodemap[s] for s in successors if s in nodemap}
1855 if succrevs.issubset(extinctrevs):
1866 if succrevs.issubset(extinctrevs):
1856 # all successors are extinct
1867 # all successors are extinct
1857 obsoleteextinctsuccessors.add(srcrev)
1868 obsoleteextinctsuccessors.add(srcrev)
1858 if not successors:
1869 if not successors:
1859 # no successor
1870 # no successor
1860 obsoletenotrebased[srcrev] = None
1871 obsoletenotrebased[srcrev] = None
1861 else:
1872 else:
1862 dstrev = destmap[srcrev]
1873 dstrev = destmap[srcrev]
1863 for succrev in succrevs:
1874 for succrev in succrevs:
1864 if cl.isancestorrev(succrev, dstrev):
1875 if cl.isancestorrev(succrev, dstrev):
1865 obsoletenotrebased[srcrev] = succrev
1876 obsoletenotrebased[srcrev] = succrev
1866 break
1877 break
1867 else:
1878 else:
1868 # If 'srcrev' has a successor in rebase set but none in
1879 # If 'srcrev' has a successor in rebase set but none in
1869 # destination (which would be catched above), we shall skip it
1880 # destination (which would be catched above), we shall skip it
1870 # and its descendants to avoid divergence.
1881 # and its descendants to avoid divergence.
1871 if any(s in destmap for s in succrevs):
1882 if any(s in destmap for s in succrevs):
1872 obsoletewithoutsuccessorindestination.add(srcrev)
1883 obsoletewithoutsuccessorindestination.add(srcrev)
1873
1884
1874 return (
1885 return (
1875 obsoletenotrebased,
1886 obsoletenotrebased,
1876 obsoletewithoutsuccessorindestination,
1887 obsoletewithoutsuccessorindestination,
1877 obsoleteextinctsuccessors,
1888 obsoleteextinctsuccessors,
1878 )
1889 )
1879
1890
1880 def summaryhook(ui, repo):
1891 def summaryhook(ui, repo):
1881 if not repo.vfs.exists('rebasestate'):
1892 if not repo.vfs.exists('rebasestate'):
1882 return
1893 return
1883 try:
1894 try:
1884 rbsrt = rebaseruntime(repo, ui, {})
1895 rbsrt = rebaseruntime(repo, ui, {})
1885 rbsrt.restorestatus()
1896 rbsrt.restorestatus()
1886 state = rbsrt.state
1897 state = rbsrt.state
1887 except error.RepoLookupError:
1898 except error.RepoLookupError:
1888 # i18n: column positioning for "hg summary"
1899 # i18n: column positioning for "hg summary"
1889 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1900 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1890 ui.write(msg)
1901 ui.write(msg)
1891 return
1902 return
1892 numrebased = len([i for i in state.itervalues() if i >= 0])
1903 numrebased = len([i for i in state.itervalues() if i >= 0])
1893 # i18n: column positioning for "hg summary"
1904 # i18n: column positioning for "hg summary"
1894 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1905 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1895 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1906 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1896 ui.label(_('%d remaining'), 'rebase.remaining') %
1907 ui.label(_('%d remaining'), 'rebase.remaining') %
1897 (len(state) - numrebased)))
1908 (len(state) - numrebased)))
1898
1909
1899 def uisetup(ui):
1910 def uisetup(ui):
1900 #Replace pull with a decorator to provide --rebase option
1911 #Replace pull with a decorator to provide --rebase option
1901 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1912 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1902 entry[1].append(('', 'rebase', None,
1913 entry[1].append(('', 'rebase', None,
1903 _("rebase working directory to branch head")))
1914 _("rebase working directory to branch head")))
1904 entry[1].append(('t', 'tool', '',
1915 entry[1].append(('t', 'tool', '',
1905 _("specify merge tool for rebase")))
1916 _("specify merge tool for rebase")))
1906 cmdutil.summaryhooks.add('rebase', summaryhook)
1917 cmdutil.summaryhooks.add('rebase', summaryhook)
1907 cmdutil.unfinishedstates.append(
1918 cmdutil.unfinishedstates.append(
1908 ['rebasestate', False, False, _('rebase in progress'),
1919 ['rebasestate', False, False, _('rebase in progress'),
1909 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1920 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1910 cmdutil.afterresolvedstates.append(
1921 cmdutil.afterresolvedstates.append(
1911 ['rebasestate', _('hg rebase --continue')])
1922 ['rebasestate', _('hg rebase --continue')])
@@ -1,437 +1,437 b''
1 # repair.py - functions for repository repair for mercurial
1 # repair.py - functions for repository repair for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 # Copyright 2007 Matt Mackall
4 # Copyright 2007 Matt Mackall
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import errno
11 import errno
12 import hashlib
12 import hashlib
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 hex,
16 hex,
17 short,
17 short,
18 )
18 )
19 from . import (
19 from . import (
20 bundle2,
20 bundle2,
21 changegroup,
21 changegroup,
22 discovery,
22 discovery,
23 error,
23 error,
24 exchange,
24 exchange,
25 obsolete,
25 obsolete,
26 obsutil,
26 obsutil,
27 pycompat,
27 pycompat,
28 util,
28 util,
29 )
29 )
30 from .utils import (
30 from .utils import (
31 stringutil,
31 stringutil,
32 )
32 )
33
33
34 def backupbundle(repo, bases, heads, node, suffix, compress=True,
34 def backupbundle(repo, bases, heads, node, suffix, compress=True,
35 obsolescence=True):
35 obsolescence=True):
36 """create a bundle with the specified revisions as a backup"""
36 """create a bundle with the specified revisions as a backup"""
37
37
38 backupdir = "strip-backup"
38 backupdir = "strip-backup"
39 vfs = repo.vfs
39 vfs = repo.vfs
40 if not vfs.isdir(backupdir):
40 if not vfs.isdir(backupdir):
41 vfs.mkdir(backupdir)
41 vfs.mkdir(backupdir)
42
42
43 # Include a hash of all the nodes in the filename for uniqueness
43 # Include a hash of all the nodes in the filename for uniqueness
44 allcommits = repo.set('%ln::%ln', bases, heads)
44 allcommits = repo.set('%ln::%ln', bases, heads)
45 allhashes = sorted(c.hex() for c in allcommits)
45 allhashes = sorted(c.hex() for c in allcommits)
46 totalhash = hashlib.sha1(''.join(allhashes)).digest()
46 totalhash = hashlib.sha1(''.join(allhashes)).digest()
47 name = "%s/%s-%s-%s.hg" % (backupdir, short(node),
47 name = "%s/%s-%s-%s.hg" % (backupdir, short(node),
48 hex(totalhash[:4]), suffix)
48 hex(totalhash[:4]), suffix)
49
49
50 cgversion = changegroup.localversion(repo)
50 cgversion = changegroup.localversion(repo)
51 comp = None
51 comp = None
52 if cgversion != '01':
52 if cgversion != '01':
53 bundletype = "HG20"
53 bundletype = "HG20"
54 if compress:
54 if compress:
55 comp = 'BZ'
55 comp = 'BZ'
56 elif compress:
56 elif compress:
57 bundletype = "HG10BZ"
57 bundletype = "HG10BZ"
58 else:
58 else:
59 bundletype = "HG10UN"
59 bundletype = "HG10UN"
60
60
61 outgoing = discovery.outgoing(repo, missingroots=bases, missingheads=heads)
61 outgoing = discovery.outgoing(repo, missingroots=bases, missingheads=heads)
62 contentopts = {
62 contentopts = {
63 'cg.version': cgversion,
63 'cg.version': cgversion,
64 'obsolescence': obsolescence,
64 'obsolescence': obsolescence,
65 'phases': True,
65 'phases': True,
66 }
66 }
67 return bundle2.writenewbundle(repo.ui, repo, 'strip', name, bundletype,
67 return bundle2.writenewbundle(repo.ui, repo, 'strip', name, bundletype,
68 outgoing, contentopts, vfs, compression=comp)
68 outgoing, contentopts, vfs, compression=comp)
69
69
70 def _collectfiles(repo, striprev):
70 def _collectfiles(repo, striprev):
71 """find out the filelogs affected by the strip"""
71 """find out the filelogs affected by the strip"""
72 files = set()
72 files = set()
73
73
74 for x in pycompat.xrange(striprev, len(repo)):
74 for x in pycompat.xrange(striprev, len(repo)):
75 files.update(repo[x].files())
75 files.update(repo[x].files())
76
76
77 return sorted(files)
77 return sorted(files)
78
78
79 def _collectrevlog(revlog, striprev):
79 def _collectrevlog(revlog, striprev):
80 _, brokenset = revlog.getstrippoint(striprev)
80 _, brokenset = revlog.getstrippoint(striprev)
81 return [revlog.linkrev(r) for r in brokenset]
81 return [revlog.linkrev(r) for r in brokenset]
82
82
83 def _collectmanifest(repo, striprev):
83 def _collectmanifest(repo, striprev):
84 return _collectrevlog(repo.manifestlog._revlog, striprev)
84 return _collectrevlog(repo.manifestlog._revlog, striprev)
85
85
86 def _collectbrokencsets(repo, files, striprev):
86 def _collectbrokencsets(repo, files, striprev):
87 """return the changesets which will be broken by the truncation"""
87 """return the changesets which will be broken by the truncation"""
88 s = set()
88 s = set()
89
89
90 s.update(_collectmanifest(repo, striprev))
90 s.update(_collectmanifest(repo, striprev))
91 for fname in files:
91 for fname in files:
92 s.update(_collectrevlog(repo.file(fname), striprev))
92 s.update(_collectrevlog(repo.file(fname), striprev))
93
93
94 return s
94 return s
95
95
96 def strip(ui, repo, nodelist, backup=True, topic='backup'):
96 def strip(ui, repo, nodelist, backup=True, topic='backup'):
97 # This function requires the caller to lock the repo, but it operates
97 # This function requires the caller to lock the repo, but it operates
98 # within a transaction of its own, and thus requires there to be no current
98 # within a transaction of its own, and thus requires there to be no current
99 # transaction when it is called.
99 # transaction when it is called.
100 if repo.currenttransaction() is not None:
100 if repo.currenttransaction() is not None:
101 raise error.ProgrammingError('cannot strip from inside a transaction')
101 raise error.ProgrammingError('cannot strip from inside a transaction')
102
102
103 # Simple way to maintain backwards compatibility for this
103 # Simple way to maintain backwards compatibility for this
104 # argument.
104 # argument.
105 if backup in ['none', 'strip']:
105 if backup in ['none', 'strip']:
106 backup = False
106 backup = False
107
107
108 repo = repo.unfiltered()
108 repo = repo.unfiltered()
109 repo.destroying()
109 repo.destroying()
110
110
111 cl = repo.changelog
111 cl = repo.changelog
112 # TODO handle undo of merge sets
112 # TODO handle undo of merge sets
113 if isinstance(nodelist, str):
113 if isinstance(nodelist, str):
114 nodelist = [nodelist]
114 nodelist = [nodelist]
115 striplist = [cl.rev(node) for node in nodelist]
115 striplist = [cl.rev(node) for node in nodelist]
116 striprev = min(striplist)
116 striprev = min(striplist)
117
117
118 files = _collectfiles(repo, striprev)
118 files = _collectfiles(repo, striprev)
119 saverevs = _collectbrokencsets(repo, files, striprev)
119 saverevs = _collectbrokencsets(repo, files, striprev)
120
120
121 # Some revisions with rev > striprev may not be descendants of striprev.
121 # Some revisions with rev > striprev may not be descendants of striprev.
122 # We have to find these revisions and put them in a bundle, so that
122 # We have to find these revisions and put them in a bundle, so that
123 # we can restore them after the truncations.
123 # we can restore them after the truncations.
124 # To create the bundle we use repo.changegroupsubset which requires
124 # To create the bundle we use repo.changegroupsubset which requires
125 # the list of heads and bases of the set of interesting revisions.
125 # the list of heads and bases of the set of interesting revisions.
126 # (head = revision in the set that has no descendant in the set;
126 # (head = revision in the set that has no descendant in the set;
127 # base = revision in the set that has no ancestor in the set)
127 # base = revision in the set that has no ancestor in the set)
128 tostrip = set(striplist)
128 tostrip = set(striplist)
129 saveheads = set(saverevs)
129 saveheads = set(saverevs)
130 for r in cl.revs(start=striprev + 1):
130 for r in cl.revs(start=striprev + 1):
131 if any(p in tostrip for p in cl.parentrevs(r)):
131 if any(p in tostrip for p in cl.parentrevs(r)):
132 tostrip.add(r)
132 tostrip.add(r)
133
133
134 if r not in tostrip:
134 if r not in tostrip:
135 saverevs.add(r)
135 saverevs.add(r)
136 saveheads.difference_update(cl.parentrevs(r))
136 saveheads.difference_update(cl.parentrevs(r))
137 saveheads.add(r)
137 saveheads.add(r)
138 saveheads = [cl.node(r) for r in saveheads]
138 saveheads = [cl.node(r) for r in saveheads]
139
139
140 # compute base nodes
140 # compute base nodes
141 if saverevs:
141 if saverevs:
142 descendants = set(cl.descendants(saverevs))
142 descendants = set(cl.descendants(saverevs))
143 saverevs.difference_update(descendants)
143 saverevs.difference_update(descendants)
144 savebases = [cl.node(r) for r in saverevs]
144 savebases = [cl.node(r) for r in saverevs]
145 stripbases = [cl.node(r) for r in tostrip]
145 stripbases = [cl.node(r) for r in tostrip]
146
146
147 stripobsidx = obsmarkers = ()
147 stripobsidx = obsmarkers = ()
148 if repo.ui.configbool('devel', 'strip-obsmarkers'):
148 if repo.ui.configbool('devel', 'strip-obsmarkers'):
149 obsmarkers = obsutil.exclusivemarkers(repo, stripbases)
149 obsmarkers = obsutil.exclusivemarkers(repo, stripbases)
150 if obsmarkers:
150 if obsmarkers:
151 stripobsidx = [i for i, m in enumerate(repo.obsstore)
151 stripobsidx = [i for i, m in enumerate(repo.obsstore)
152 if m in obsmarkers]
152 if m in obsmarkers]
153
153
154 # For a set s, max(parents(s) - s) is the same as max(heads(::s - s)), but
154 # For a set s, max(parents(s) - s) is the same as max(heads(::s - s)), but
155 # is much faster
155 # is much faster
156 newbmtarget = repo.revs('max(parents(%ld) - (%ld))', tostrip, tostrip)
156 newbmtarget = repo.revs('max(parents(%ld) - (%ld))', tostrip, tostrip)
157 if newbmtarget:
157 if newbmtarget:
158 newbmtarget = repo[newbmtarget.first()].node()
158 newbmtarget = repo[newbmtarget.first()].node()
159 else:
159 else:
160 newbmtarget = '.'
160 newbmtarget = '.'
161
161
162 bm = repo._bookmarks
162 bm = repo._bookmarks
163 updatebm = []
163 updatebm = []
164 for m in bm:
164 for m in bm:
165 rev = repo[bm[m]].rev()
165 rev = repo[bm[m]].rev()
166 if rev in tostrip:
166 if rev in tostrip:
167 updatebm.append(m)
167 updatebm.append(m)
168
168
169 # create a changegroup for all the branches we need to keep
169 # create a changegroup for all the branches we need to keep
170 backupfile = None
170 backupfile = None
171 vfs = repo.vfs
171 vfs = repo.vfs
172 node = nodelist[-1]
172 node = nodelist[-1]
173 if backup:
173 if backup:
174 backupfile = backupbundle(repo, stripbases, cl.heads(), node, topic)
174 backupfile = backupbundle(repo, stripbases, cl.heads(), node, topic)
175 repo.ui.status(_("saved backup bundle to %s\n") %
175 repo.ui.status(_("saved backup bundle to %s\n") %
176 vfs.join(backupfile))
176 vfs.join(backupfile))
177 repo.ui.log("backupbundle", "saved backup bundle to %s\n",
177 repo.ui.log("backupbundle", "saved backup bundle to %s\n",
178 vfs.join(backupfile))
178 vfs.join(backupfile))
179 tmpbundlefile = None
179 tmpbundlefile = None
180 if saveheads:
180 if saveheads:
181 # do not compress temporary bundle if we remove it from disk later
181 # do not compress temporary bundle if we remove it from disk later
182 #
182 #
183 # We do not include obsolescence, it might re-introduce prune markers
183 # We do not include obsolescence, it might re-introduce prune markers
184 # we are trying to strip. This is harmless since the stripped markers
184 # we are trying to strip. This is harmless since the stripped markers
185 # are already backed up and we did not touched the markers for the
185 # are already backed up and we did not touched the markers for the
186 # saved changesets.
186 # saved changesets.
187 tmpbundlefile = backupbundle(repo, savebases, saveheads, node, 'temp',
187 tmpbundlefile = backupbundle(repo, savebases, saveheads, node, 'temp',
188 compress=False, obsolescence=False)
188 compress=False, obsolescence=False)
189
189
190 with ui.uninterruptable():
190 with ui.uninterruptable():
191 try:
191 try:
192 with repo.transaction("strip") as tr:
192 with repo.transaction("strip") as tr:
193 offset = len(tr.entries)
193 offset = len(tr.entries)
194
194
195 tr.startgroup()
195 tr.startgroup()
196 cl.strip(striprev, tr)
196 cl.strip(striprev, tr)
197 stripmanifest(repo, striprev, tr, files)
197 stripmanifest(repo, striprev, tr, files)
198
198
199 for fn in files:
199 for fn in files:
200 repo.file(fn).strip(striprev, tr)
200 repo.file(fn).strip(striprev, tr)
201 tr.endgroup()
201 tr.endgroup()
202
202
203 for i in pycompat.xrange(offset, len(tr.entries)):
203 for i in pycompat.xrange(offset, len(tr.entries)):
204 file, troffset, ignore = tr.entries[i]
204 file, troffset, ignore = tr.entries[i]
205 with repo.svfs(file, 'a', checkambig=True) as fp:
205 with repo.svfs(file, 'a', checkambig=True) as fp:
206 fp.truncate(troffset)
206 fp.truncate(troffset)
207 if troffset == 0:
207 if troffset == 0:
208 repo.store.markremoved(file)
208 repo.store.markremoved(file)
209
209
210 deleteobsmarkers(repo.obsstore, stripobsidx)
210 deleteobsmarkers(repo.obsstore, stripobsidx)
211 del repo.obsstore
211 del repo.obsstore
212 repo.invalidatevolatilesets()
212 repo.invalidatevolatilesets()
213 repo._phasecache.filterunknown(repo)
213 repo._phasecache.filterunknown(repo)
214
214
215 if tmpbundlefile:
215 if tmpbundlefile:
216 ui.note(_("adding branch\n"))
216 ui.note(_("adding branch\n"))
217 f = vfs.open(tmpbundlefile, "rb")
217 f = vfs.open(tmpbundlefile, "rb")
218 gen = exchange.readbundle(ui, f, tmpbundlefile, vfs)
218 gen = exchange.readbundle(ui, f, tmpbundlefile, vfs)
219 if not repo.ui.verbose:
219 if not repo.ui.verbose:
220 # silence internal shuffling chatter
220 # silence internal shuffling chatter
221 repo.ui.pushbuffer()
221 repo.ui.pushbuffer()
222 tmpbundleurl = 'bundle:' + vfs.join(tmpbundlefile)
222 tmpbundleurl = 'bundle:' + vfs.join(tmpbundlefile)
223 txnname = 'strip'
223 txnname = 'strip'
224 if not isinstance(gen, bundle2.unbundle20):
224 if not isinstance(gen, bundle2.unbundle20):
225 txnname = "strip\n%s" % util.hidepassword(tmpbundleurl)
225 txnname = "strip\n%s" % util.hidepassword(tmpbundleurl)
226 with repo.transaction(txnname) as tr:
226 with repo.transaction(txnname) as tr:
227 bundle2.applybundle(repo, gen, tr, source='strip',
227 bundle2.applybundle(repo, gen, tr, source='strip',
228 url=tmpbundleurl)
228 url=tmpbundleurl)
229 if not repo.ui.verbose:
229 if not repo.ui.verbose:
230 repo.ui.popbuffer()
230 repo.ui.popbuffer()
231 f.close()
231 f.close()
232
232
233 with repo.transaction('repair') as tr:
233 with repo.transaction('repair') as tr:
234 bmchanges = [(m, repo[newbmtarget].node()) for m in updatebm]
234 bmchanges = [(m, repo[newbmtarget].node()) for m in updatebm]
235 bm.applychanges(repo, tr, bmchanges)
235 bm.applychanges(repo, tr, bmchanges)
236
236
237 # remove undo files
237 # remove undo files
238 for undovfs, undofile in repo.undofiles():
238 for undovfs, undofile in repo.undofiles():
239 try:
239 try:
240 undovfs.unlink(undofile)
240 undovfs.unlink(undofile)
241 except OSError as e:
241 except OSError as e:
242 if e.errno != errno.ENOENT:
242 if e.errno != errno.ENOENT:
243 ui.warn(_('error removing %s: %s\n') %
243 ui.warn(_('error removing %s: %s\n') %
244 (undovfs.join(undofile),
244 (undovfs.join(undofile),
245 stringutil.forcebytestr(e)))
245 stringutil.forcebytestr(e)))
246
246
247 except: # re-raises
247 except: # re-raises
248 if backupfile:
248 if backupfile:
249 ui.warn(_("strip failed, backup bundle stored in '%s'\n")
249 ui.warn(_("strip failed, backup bundle stored in '%s'\n")
250 % vfs.join(backupfile))
250 % vfs.join(backupfile))
251 if tmpbundlefile:
251 if tmpbundlefile:
252 ui.warn(_("strip failed, unrecovered changes stored in '%s'\n")
252 ui.warn(_("strip failed, unrecovered changes stored in '%s'\n")
253 % vfs.join(tmpbundlefile))
253 % vfs.join(tmpbundlefile))
254 ui.warn(_("(fix the problem, then recover the changesets with "
254 ui.warn(_("(fix the problem, then recover the changesets with "
255 "\"hg unbundle '%s'\")\n") % vfs.join(tmpbundlefile))
255 "\"hg unbundle '%s'\")\n") % vfs.join(tmpbundlefile))
256 raise
256 raise
257 else:
257 else:
258 if tmpbundlefile:
258 if tmpbundlefile:
259 # Remove temporary bundle only if there were no exceptions
259 # Remove temporary bundle only if there were no exceptions
260 vfs.unlink(tmpbundlefile)
260 vfs.unlink(tmpbundlefile)
261
261
262 repo.destroyed()
262 repo.destroyed()
263 # return the backup file path (or None if 'backup' was False) so
263 # return the backup file path (or None if 'backup' was False) so
264 # extensions can use it
264 # extensions can use it
265 return backupfile
265 return backupfile
266
266
267 def safestriproots(ui, repo, nodes):
267 def safestriproots(ui, repo, nodes):
268 """return list of roots of nodes where descendants are covered by nodes"""
268 """return list of roots of nodes where descendants are covered by nodes"""
269 torev = repo.unfiltered().changelog.rev
269 torev = repo.unfiltered().changelog.rev
270 revs = set(torev(n) for n in nodes)
270 revs = set(torev(n) for n in nodes)
271 # tostrip = wanted - unsafe = wanted - ancestors(orphaned)
271 # tostrip = wanted - unsafe = wanted - ancestors(orphaned)
272 # orphaned = affected - wanted
272 # orphaned = affected - wanted
273 # affected = descendants(roots(wanted))
273 # affected = descendants(roots(wanted))
274 # wanted = revs
274 # wanted = revs
275 tostrip = set(repo.revs('%ld-(::((roots(%ld)::)-%ld))', revs, revs, revs))
275 tostrip = set(repo.revs('%ld-(::((roots(%ld)::)-%ld))', revs, revs, revs))
276 notstrip = revs - tostrip
276 notstrip = revs - tostrip
277 if notstrip:
277 if notstrip:
278 nodestr = ', '.join(sorted(short(repo[n].node()) for n in notstrip))
278 nodestr = ', '.join(sorted(short(repo[n].node()) for n in notstrip))
279 ui.warn(_('warning: orphaned descendants detected, '
279 ui.warn(_('warning: orphaned descendants detected, '
280 'not stripping %s\n') % nodestr)
280 'not stripping %s\n') % nodestr)
281 return [c.node() for c in repo.set('roots(%ld)', tostrip)]
281 return [c.node() for c in repo.set('roots(%ld)', tostrip)]
282
282
283 class stripcallback(object):
283 class stripcallback(object):
284 """used as a transaction postclose callback"""
284 """used as a transaction postclose callback"""
285
285
286 def __init__(self, ui, repo, backup, topic):
286 def __init__(self, ui, repo, backup, topic):
287 self.ui = ui
287 self.ui = ui
288 self.repo = repo
288 self.repo = repo
289 self.backup = backup
289 self.backup = backup
290 self.topic = topic or 'backup'
290 self.topic = topic or 'backup'
291 self.nodelist = []
291 self.nodelist = []
292
292
293 def addnodes(self, nodes):
293 def addnodes(self, nodes):
294 self.nodelist.extend(nodes)
294 self.nodelist.extend(nodes)
295
295
296 def __call__(self, tr):
296 def __call__(self, tr):
297 roots = safestriproots(self.ui, self.repo, self.nodelist)
297 roots = safestriproots(self.ui, self.repo, self.nodelist)
298 if roots:
298 if roots:
299 strip(self.ui, self.repo, roots, self.backup, self.topic)
299 strip(self.ui, self.repo, roots, self.backup, self.topic)
300
300
301 def delayedstrip(ui, repo, nodelist, topic=None):
301 def delayedstrip(ui, repo, nodelist, topic=None, backup=True):
302 """like strip, but works inside transaction and won't strip irreverent revs
302 """like strip, but works inside transaction and won't strip irreverent revs
303
303
304 nodelist must explicitly contain all descendants. Otherwise a warning will
304 nodelist must explicitly contain all descendants. Otherwise a warning will
305 be printed that some nodes are not stripped.
305 be printed that some nodes are not stripped.
306
306
307 Always do a backup. The last non-None "topic" will be used as the backup
307 Will do a backup if `backup` is True. The last non-None "topic" will be
308 topic name. The default backup topic name is "backup".
308 used as the backup topic name. The default backup topic name is "backup".
309 """
309 """
310 tr = repo.currenttransaction()
310 tr = repo.currenttransaction()
311 if not tr:
311 if not tr:
312 nodes = safestriproots(ui, repo, nodelist)
312 nodes = safestriproots(ui, repo, nodelist)
313 return strip(ui, repo, nodes, True, topic)
313 return strip(ui, repo, nodes, backup=backup, topic=topic)
314 # transaction postclose callbacks are called in alphabet order.
314 # transaction postclose callbacks are called in alphabet order.
315 # use '\xff' as prefix so we are likely to be called last.
315 # use '\xff' as prefix so we are likely to be called last.
316 callback = tr.getpostclose('\xffstrip')
316 callback = tr.getpostclose('\xffstrip')
317 if callback is None:
317 if callback is None:
318 callback = stripcallback(ui, repo, True, topic)
318 callback = stripcallback(ui, repo, backup=backup, topic=topic)
319 tr.addpostclose('\xffstrip', callback)
319 tr.addpostclose('\xffstrip', callback)
320 if topic:
320 if topic:
321 callback.topic = topic
321 callback.topic = topic
322 callback.addnodes(nodelist)
322 callback.addnodes(nodelist)
323
323
324 def stripmanifest(repo, striprev, tr, files):
324 def stripmanifest(repo, striprev, tr, files):
325 revlog = repo.manifestlog._revlog
325 revlog = repo.manifestlog._revlog
326 revlog.strip(striprev, tr)
326 revlog.strip(striprev, tr)
327 striptrees(repo, tr, striprev, files)
327 striptrees(repo, tr, striprev, files)
328
328
329 def striptrees(repo, tr, striprev, files):
329 def striptrees(repo, tr, striprev, files):
330 if 'treemanifest' in repo.requirements: # safe but unnecessary
330 if 'treemanifest' in repo.requirements: # safe but unnecessary
331 # otherwise
331 # otherwise
332 for unencoded, encoded, size in repo.store.datafiles():
332 for unencoded, encoded, size in repo.store.datafiles():
333 if (unencoded.startswith('meta/') and
333 if (unencoded.startswith('meta/') and
334 unencoded.endswith('00manifest.i')):
334 unencoded.endswith('00manifest.i')):
335 dir = unencoded[5:-12]
335 dir = unencoded[5:-12]
336 repo.manifestlog._revlog.dirlog(dir).strip(striprev, tr)
336 repo.manifestlog._revlog.dirlog(dir).strip(striprev, tr)
337
337
338 def rebuildfncache(ui, repo):
338 def rebuildfncache(ui, repo):
339 """Rebuilds the fncache file from repo history.
339 """Rebuilds the fncache file from repo history.
340
340
341 Missing entries will be added. Extra entries will be removed.
341 Missing entries will be added. Extra entries will be removed.
342 """
342 """
343 repo = repo.unfiltered()
343 repo = repo.unfiltered()
344
344
345 if 'fncache' not in repo.requirements:
345 if 'fncache' not in repo.requirements:
346 ui.warn(_('(not rebuilding fncache because repository does not '
346 ui.warn(_('(not rebuilding fncache because repository does not '
347 'support fncache)\n'))
347 'support fncache)\n'))
348 return
348 return
349
349
350 with repo.lock():
350 with repo.lock():
351 fnc = repo.store.fncache
351 fnc = repo.store.fncache
352 # Trigger load of fncache.
352 # Trigger load of fncache.
353 if 'irrelevant' in fnc:
353 if 'irrelevant' in fnc:
354 pass
354 pass
355
355
356 oldentries = set(fnc.entries)
356 oldentries = set(fnc.entries)
357 newentries = set()
357 newentries = set()
358 seenfiles = set()
358 seenfiles = set()
359
359
360 progress = ui.makeprogress(_('rebuilding'), unit=_('changesets'),
360 progress = ui.makeprogress(_('rebuilding'), unit=_('changesets'),
361 total=len(repo))
361 total=len(repo))
362 for rev in repo:
362 for rev in repo:
363 progress.update(rev)
363 progress.update(rev)
364
364
365 ctx = repo[rev]
365 ctx = repo[rev]
366 for f in ctx.files():
366 for f in ctx.files():
367 # This is to minimize I/O.
367 # This is to minimize I/O.
368 if f in seenfiles:
368 if f in seenfiles:
369 continue
369 continue
370 seenfiles.add(f)
370 seenfiles.add(f)
371
371
372 i = 'data/%s.i' % f
372 i = 'data/%s.i' % f
373 d = 'data/%s.d' % f
373 d = 'data/%s.d' % f
374
374
375 if repo.store._exists(i):
375 if repo.store._exists(i):
376 newentries.add(i)
376 newentries.add(i)
377 if repo.store._exists(d):
377 if repo.store._exists(d):
378 newentries.add(d)
378 newentries.add(d)
379
379
380 progress.complete()
380 progress.complete()
381
381
382 if 'treemanifest' in repo.requirements: # safe but unnecessary otherwise
382 if 'treemanifest' in repo.requirements: # safe but unnecessary otherwise
383 for dir in util.dirs(seenfiles):
383 for dir in util.dirs(seenfiles):
384 i = 'meta/%s/00manifest.i' % dir
384 i = 'meta/%s/00manifest.i' % dir
385 d = 'meta/%s/00manifest.d' % dir
385 d = 'meta/%s/00manifest.d' % dir
386
386
387 if repo.store._exists(i):
387 if repo.store._exists(i):
388 newentries.add(i)
388 newentries.add(i)
389 if repo.store._exists(d):
389 if repo.store._exists(d):
390 newentries.add(d)
390 newentries.add(d)
391
391
392 addcount = len(newentries - oldentries)
392 addcount = len(newentries - oldentries)
393 removecount = len(oldentries - newentries)
393 removecount = len(oldentries - newentries)
394 for p in sorted(oldentries - newentries):
394 for p in sorted(oldentries - newentries):
395 ui.write(_('removing %s\n') % p)
395 ui.write(_('removing %s\n') % p)
396 for p in sorted(newentries - oldentries):
396 for p in sorted(newentries - oldentries):
397 ui.write(_('adding %s\n') % p)
397 ui.write(_('adding %s\n') % p)
398
398
399 if addcount or removecount:
399 if addcount or removecount:
400 ui.write(_('%d items added, %d removed from fncache\n') %
400 ui.write(_('%d items added, %d removed from fncache\n') %
401 (addcount, removecount))
401 (addcount, removecount))
402 fnc.entries = newentries
402 fnc.entries = newentries
403 fnc._dirty = True
403 fnc._dirty = True
404
404
405 with repo.transaction('fncache') as tr:
405 with repo.transaction('fncache') as tr:
406 fnc.write(tr)
406 fnc.write(tr)
407 else:
407 else:
408 ui.write(_('fncache already up to date\n'))
408 ui.write(_('fncache already up to date\n'))
409
409
410 def deleteobsmarkers(obsstore, indices):
410 def deleteobsmarkers(obsstore, indices):
411 """Delete some obsmarkers from obsstore and return how many were deleted
411 """Delete some obsmarkers from obsstore and return how many were deleted
412
412
413 'indices' is a list of ints which are the indices
413 'indices' is a list of ints which are the indices
414 of the markers to be deleted.
414 of the markers to be deleted.
415
415
416 Every invocation of this function completely rewrites the obsstore file,
416 Every invocation of this function completely rewrites the obsstore file,
417 skipping the markers we want to be removed. The new temporary file is
417 skipping the markers we want to be removed. The new temporary file is
418 created, remaining markers are written there and on .close() this file
418 created, remaining markers are written there and on .close() this file
419 gets atomically renamed to obsstore, thus guaranteeing consistency."""
419 gets atomically renamed to obsstore, thus guaranteeing consistency."""
420 if not indices:
420 if not indices:
421 # we don't want to rewrite the obsstore with the same content
421 # we don't want to rewrite the obsstore with the same content
422 return
422 return
423
423
424 left = []
424 left = []
425 current = obsstore._all
425 current = obsstore._all
426 n = 0
426 n = 0
427 for i, m in enumerate(current):
427 for i, m in enumerate(current):
428 if i in indices:
428 if i in indices:
429 n += 1
429 n += 1
430 continue
430 continue
431 left.append(m)
431 left.append(m)
432
432
433 newobsstorefile = obsstore.svfs('obsstore', 'w', atomictemp=True)
433 newobsstorefile = obsstore.svfs('obsstore', 'w', atomictemp=True)
434 for bytes in obsolete.encodemarkers(left, True, obsstore._version):
434 for bytes in obsolete.encodemarkers(left, True, obsstore._version):
435 newobsstorefile.write(bytes)
435 newobsstorefile.write(bytes)
436 newobsstorefile.close()
436 newobsstorefile.close()
437 return n
437 return n
@@ -1,1700 +1,1701 b''
1 # scmutil.py - Mercurial core utility functions
1 # scmutil.py - Mercurial core utility functions
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright Matt Mackall <mpm@selenic.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 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import glob
11 import glob
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import re
14 import re
15 import socket
15 import socket
16 import subprocess
16 import subprocess
17 import weakref
17 import weakref
18
18
19 from .i18n import _
19 from .i18n import _
20 from .node import (
20 from .node import (
21 bin,
21 bin,
22 hex,
22 hex,
23 nullid,
23 nullid,
24 short,
24 short,
25 wdirid,
25 wdirid,
26 wdirrev,
26 wdirrev,
27 )
27 )
28
28
29 from . import (
29 from . import (
30 encoding,
30 encoding,
31 error,
31 error,
32 match as matchmod,
32 match as matchmod,
33 obsolete,
33 obsolete,
34 obsutil,
34 obsutil,
35 pathutil,
35 pathutil,
36 phases,
36 phases,
37 pycompat,
37 pycompat,
38 revsetlang,
38 revsetlang,
39 similar,
39 similar,
40 url,
40 url,
41 util,
41 util,
42 vfs,
42 vfs,
43 )
43 )
44
44
45 from .utils import (
45 from .utils import (
46 procutil,
46 procutil,
47 stringutil,
47 stringutil,
48 )
48 )
49
49
50 if pycompat.iswindows:
50 if pycompat.iswindows:
51 from . import scmwindows as scmplatform
51 from . import scmwindows as scmplatform
52 else:
52 else:
53 from . import scmposix as scmplatform
53 from . import scmposix as scmplatform
54
54
55 termsize = scmplatform.termsize
55 termsize = scmplatform.termsize
56
56
57 class status(tuple):
57 class status(tuple):
58 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
58 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
59 and 'ignored' properties are only relevant to the working copy.
59 and 'ignored' properties are only relevant to the working copy.
60 '''
60 '''
61
61
62 __slots__ = ()
62 __slots__ = ()
63
63
64 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
64 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
65 clean):
65 clean):
66 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
66 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
67 ignored, clean))
67 ignored, clean))
68
68
69 @property
69 @property
70 def modified(self):
70 def modified(self):
71 '''files that have been modified'''
71 '''files that have been modified'''
72 return self[0]
72 return self[0]
73
73
74 @property
74 @property
75 def added(self):
75 def added(self):
76 '''files that have been added'''
76 '''files that have been added'''
77 return self[1]
77 return self[1]
78
78
79 @property
79 @property
80 def removed(self):
80 def removed(self):
81 '''files that have been removed'''
81 '''files that have been removed'''
82 return self[2]
82 return self[2]
83
83
84 @property
84 @property
85 def deleted(self):
85 def deleted(self):
86 '''files that are in the dirstate, but have been deleted from the
86 '''files that are in the dirstate, but have been deleted from the
87 working copy (aka "missing")
87 working copy (aka "missing")
88 '''
88 '''
89 return self[3]
89 return self[3]
90
90
91 @property
91 @property
92 def unknown(self):
92 def unknown(self):
93 '''files not in the dirstate that are not ignored'''
93 '''files not in the dirstate that are not ignored'''
94 return self[4]
94 return self[4]
95
95
96 @property
96 @property
97 def ignored(self):
97 def ignored(self):
98 '''files not in the dirstate that are ignored (by _dirignore())'''
98 '''files not in the dirstate that are ignored (by _dirignore())'''
99 return self[5]
99 return self[5]
100
100
101 @property
101 @property
102 def clean(self):
102 def clean(self):
103 '''files that have not been modified'''
103 '''files that have not been modified'''
104 return self[6]
104 return self[6]
105
105
106 def __repr__(self, *args, **kwargs):
106 def __repr__(self, *args, **kwargs):
107 return ((r'<status modified=%s, added=%s, removed=%s, deleted=%s, '
107 return ((r'<status modified=%s, added=%s, removed=%s, deleted=%s, '
108 r'unknown=%s, ignored=%s, clean=%s>') %
108 r'unknown=%s, ignored=%s, clean=%s>') %
109 tuple(pycompat.sysstr(stringutil.pprint(v)) for v in self))
109 tuple(pycompat.sysstr(stringutil.pprint(v)) for v in self))
110
110
111 def itersubrepos(ctx1, ctx2):
111 def itersubrepos(ctx1, ctx2):
112 """find subrepos in ctx1 or ctx2"""
112 """find subrepos in ctx1 or ctx2"""
113 # Create a (subpath, ctx) mapping where we prefer subpaths from
113 # Create a (subpath, ctx) mapping where we prefer subpaths from
114 # ctx1. The subpaths from ctx2 are important when the .hgsub file
114 # ctx1. The subpaths from ctx2 are important when the .hgsub file
115 # has been modified (in ctx2) but not yet committed (in ctx1).
115 # has been modified (in ctx2) but not yet committed (in ctx1).
116 subpaths = dict.fromkeys(ctx2.substate, ctx2)
116 subpaths = dict.fromkeys(ctx2.substate, ctx2)
117 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
117 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
118
118
119 missing = set()
119 missing = set()
120
120
121 for subpath in ctx2.substate:
121 for subpath in ctx2.substate:
122 if subpath not in ctx1.substate:
122 if subpath not in ctx1.substate:
123 del subpaths[subpath]
123 del subpaths[subpath]
124 missing.add(subpath)
124 missing.add(subpath)
125
125
126 for subpath, ctx in sorted(subpaths.iteritems()):
126 for subpath, ctx in sorted(subpaths.iteritems()):
127 yield subpath, ctx.sub(subpath)
127 yield subpath, ctx.sub(subpath)
128
128
129 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
129 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
130 # status and diff will have an accurate result when it does
130 # status and diff will have an accurate result when it does
131 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
131 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
132 # against itself.
132 # against itself.
133 for subpath in missing:
133 for subpath in missing:
134 yield subpath, ctx2.nullsub(subpath, ctx1)
134 yield subpath, ctx2.nullsub(subpath, ctx1)
135
135
136 def nochangesfound(ui, repo, excluded=None):
136 def nochangesfound(ui, repo, excluded=None):
137 '''Report no changes for push/pull, excluded is None or a list of
137 '''Report no changes for push/pull, excluded is None or a list of
138 nodes excluded from the push/pull.
138 nodes excluded from the push/pull.
139 '''
139 '''
140 secretlist = []
140 secretlist = []
141 if excluded:
141 if excluded:
142 for n in excluded:
142 for n in excluded:
143 ctx = repo[n]
143 ctx = repo[n]
144 if ctx.phase() >= phases.secret and not ctx.extinct():
144 if ctx.phase() >= phases.secret and not ctx.extinct():
145 secretlist.append(n)
145 secretlist.append(n)
146
146
147 if secretlist:
147 if secretlist:
148 ui.status(_("no changes found (ignored %d secret changesets)\n")
148 ui.status(_("no changes found (ignored %d secret changesets)\n")
149 % len(secretlist))
149 % len(secretlist))
150 else:
150 else:
151 ui.status(_("no changes found\n"))
151 ui.status(_("no changes found\n"))
152
152
153 def callcatch(ui, func):
153 def callcatch(ui, func):
154 """call func() with global exception handling
154 """call func() with global exception handling
155
155
156 return func() if no exception happens. otherwise do some error handling
156 return func() if no exception happens. otherwise do some error handling
157 and return an exit code accordingly. does not handle all exceptions.
157 and return an exit code accordingly. does not handle all exceptions.
158 """
158 """
159 try:
159 try:
160 try:
160 try:
161 return func()
161 return func()
162 except: # re-raises
162 except: # re-raises
163 ui.traceback()
163 ui.traceback()
164 raise
164 raise
165 # Global exception handling, alphabetically
165 # Global exception handling, alphabetically
166 # Mercurial-specific first, followed by built-in and library exceptions
166 # Mercurial-specific first, followed by built-in and library exceptions
167 except error.LockHeld as inst:
167 except error.LockHeld as inst:
168 if inst.errno == errno.ETIMEDOUT:
168 if inst.errno == errno.ETIMEDOUT:
169 reason = _('timed out waiting for lock held by %r') % inst.locker
169 reason = _('timed out waiting for lock held by %r') % inst.locker
170 else:
170 else:
171 reason = _('lock held by %r') % inst.locker
171 reason = _('lock held by %r') % inst.locker
172 ui.error(_("abort: %s: %s\n") % (
172 ui.error(_("abort: %s: %s\n") % (
173 inst.desc or stringutil.forcebytestr(inst.filename), reason))
173 inst.desc or stringutil.forcebytestr(inst.filename), reason))
174 if not inst.locker:
174 if not inst.locker:
175 ui.error(_("(lock might be very busy)\n"))
175 ui.error(_("(lock might be very busy)\n"))
176 except error.LockUnavailable as inst:
176 except error.LockUnavailable as inst:
177 ui.error(_("abort: could not lock %s: %s\n") %
177 ui.error(_("abort: could not lock %s: %s\n") %
178 (inst.desc or stringutil.forcebytestr(inst.filename),
178 (inst.desc or stringutil.forcebytestr(inst.filename),
179 encoding.strtolocal(inst.strerror)))
179 encoding.strtolocal(inst.strerror)))
180 except error.OutOfBandError as inst:
180 except error.OutOfBandError as inst:
181 if inst.args:
181 if inst.args:
182 msg = _("abort: remote error:\n")
182 msg = _("abort: remote error:\n")
183 else:
183 else:
184 msg = _("abort: remote error\n")
184 msg = _("abort: remote error\n")
185 ui.error(msg)
185 ui.error(msg)
186 if inst.args:
186 if inst.args:
187 ui.error(''.join(inst.args))
187 ui.error(''.join(inst.args))
188 if inst.hint:
188 if inst.hint:
189 ui.error('(%s)\n' % inst.hint)
189 ui.error('(%s)\n' % inst.hint)
190 except error.RepoError as inst:
190 except error.RepoError as inst:
191 ui.error(_("abort: %s!\n") % inst)
191 ui.error(_("abort: %s!\n") % inst)
192 if inst.hint:
192 if inst.hint:
193 ui.error(_("(%s)\n") % inst.hint)
193 ui.error(_("(%s)\n") % inst.hint)
194 except error.ResponseError as inst:
194 except error.ResponseError as inst:
195 ui.error(_("abort: %s") % inst.args[0])
195 ui.error(_("abort: %s") % inst.args[0])
196 msg = inst.args[1]
196 msg = inst.args[1]
197 if isinstance(msg, type(u'')):
197 if isinstance(msg, type(u'')):
198 msg = pycompat.sysbytes(msg)
198 msg = pycompat.sysbytes(msg)
199 if not isinstance(msg, bytes):
199 if not isinstance(msg, bytes):
200 ui.error(" %r\n" % (msg,))
200 ui.error(" %r\n" % (msg,))
201 elif not msg:
201 elif not msg:
202 ui.error(_(" empty string\n"))
202 ui.error(_(" empty string\n"))
203 else:
203 else:
204 ui.error("\n%r\n" % pycompat.bytestr(stringutil.ellipsis(msg)))
204 ui.error("\n%r\n" % pycompat.bytestr(stringutil.ellipsis(msg)))
205 except error.CensoredNodeError as inst:
205 except error.CensoredNodeError as inst:
206 ui.error(_("abort: file censored %s!\n") % inst)
206 ui.error(_("abort: file censored %s!\n") % inst)
207 except error.RevlogError as inst:
207 except error.RevlogError as inst:
208 ui.error(_("abort: %s!\n") % inst)
208 ui.error(_("abort: %s!\n") % inst)
209 except error.InterventionRequired as inst:
209 except error.InterventionRequired as inst:
210 ui.error("%s\n" % inst)
210 ui.error("%s\n" % inst)
211 if inst.hint:
211 if inst.hint:
212 ui.error(_("(%s)\n") % inst.hint)
212 ui.error(_("(%s)\n") % inst.hint)
213 return 1
213 return 1
214 except error.WdirUnsupported:
214 except error.WdirUnsupported:
215 ui.error(_("abort: working directory revision cannot be specified\n"))
215 ui.error(_("abort: working directory revision cannot be specified\n"))
216 except error.Abort as inst:
216 except error.Abort as inst:
217 ui.error(_("abort: %s\n") % inst)
217 ui.error(_("abort: %s\n") % inst)
218 if inst.hint:
218 if inst.hint:
219 ui.error(_("(%s)\n") % inst.hint)
219 ui.error(_("(%s)\n") % inst.hint)
220 except ImportError as inst:
220 except ImportError as inst:
221 ui.error(_("abort: %s!\n") % stringutil.forcebytestr(inst))
221 ui.error(_("abort: %s!\n") % stringutil.forcebytestr(inst))
222 m = stringutil.forcebytestr(inst).split()[-1]
222 m = stringutil.forcebytestr(inst).split()[-1]
223 if m in "mpatch bdiff".split():
223 if m in "mpatch bdiff".split():
224 ui.error(_("(did you forget to compile extensions?)\n"))
224 ui.error(_("(did you forget to compile extensions?)\n"))
225 elif m in "zlib".split():
225 elif m in "zlib".split():
226 ui.error(_("(is your Python install correct?)\n"))
226 ui.error(_("(is your Python install correct?)\n"))
227 except IOError as inst:
227 except IOError as inst:
228 if util.safehasattr(inst, "code"):
228 if util.safehasattr(inst, "code"):
229 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst))
229 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst))
230 elif util.safehasattr(inst, "reason"):
230 elif util.safehasattr(inst, "reason"):
231 try: # usually it is in the form (errno, strerror)
231 try: # usually it is in the form (errno, strerror)
232 reason = inst.reason.args[1]
232 reason = inst.reason.args[1]
233 except (AttributeError, IndexError):
233 except (AttributeError, IndexError):
234 # it might be anything, for example a string
234 # it might be anything, for example a string
235 reason = inst.reason
235 reason = inst.reason
236 if isinstance(reason, pycompat.unicode):
236 if isinstance(reason, pycompat.unicode):
237 # SSLError of Python 2.7.9 contains a unicode
237 # SSLError of Python 2.7.9 contains a unicode
238 reason = encoding.unitolocal(reason)
238 reason = encoding.unitolocal(reason)
239 ui.error(_("abort: error: %s\n") % reason)
239 ui.error(_("abort: error: %s\n") % reason)
240 elif (util.safehasattr(inst, "args")
240 elif (util.safehasattr(inst, "args")
241 and inst.args and inst.args[0] == errno.EPIPE):
241 and inst.args and inst.args[0] == errno.EPIPE):
242 pass
242 pass
243 elif getattr(inst, "strerror", None):
243 elif getattr(inst, "strerror", None):
244 if getattr(inst, "filename", None):
244 if getattr(inst, "filename", None):
245 ui.error(_("abort: %s: %s\n") % (
245 ui.error(_("abort: %s: %s\n") % (
246 encoding.strtolocal(inst.strerror),
246 encoding.strtolocal(inst.strerror),
247 stringutil.forcebytestr(inst.filename)))
247 stringutil.forcebytestr(inst.filename)))
248 else:
248 else:
249 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
249 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
250 else:
250 else:
251 raise
251 raise
252 except OSError as inst:
252 except OSError as inst:
253 if getattr(inst, "filename", None) is not None:
253 if getattr(inst, "filename", None) is not None:
254 ui.error(_("abort: %s: '%s'\n") % (
254 ui.error(_("abort: %s: '%s'\n") % (
255 encoding.strtolocal(inst.strerror),
255 encoding.strtolocal(inst.strerror),
256 stringutil.forcebytestr(inst.filename)))
256 stringutil.forcebytestr(inst.filename)))
257 else:
257 else:
258 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
258 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
259 except MemoryError:
259 except MemoryError:
260 ui.error(_("abort: out of memory\n"))
260 ui.error(_("abort: out of memory\n"))
261 except SystemExit as inst:
261 except SystemExit as inst:
262 # Commands shouldn't sys.exit directly, but give a return code.
262 # Commands shouldn't sys.exit directly, but give a return code.
263 # Just in case catch this and and pass exit code to caller.
263 # Just in case catch this and and pass exit code to caller.
264 return inst.code
264 return inst.code
265 except socket.error as inst:
265 except socket.error as inst:
266 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst.args[-1]))
266 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst.args[-1]))
267
267
268 return -1
268 return -1
269
269
270 def checknewlabel(repo, lbl, kind):
270 def checknewlabel(repo, lbl, kind):
271 # Do not use the "kind" parameter in ui output.
271 # Do not use the "kind" parameter in ui output.
272 # It makes strings difficult to translate.
272 # It makes strings difficult to translate.
273 if lbl in ['tip', '.', 'null']:
273 if lbl in ['tip', '.', 'null']:
274 raise error.Abort(_("the name '%s' is reserved") % lbl)
274 raise error.Abort(_("the name '%s' is reserved") % lbl)
275 for c in (':', '\0', '\n', '\r'):
275 for c in (':', '\0', '\n', '\r'):
276 if c in lbl:
276 if c in lbl:
277 raise error.Abort(
277 raise error.Abort(
278 _("%r cannot be used in a name") % pycompat.bytestr(c))
278 _("%r cannot be used in a name") % pycompat.bytestr(c))
279 try:
279 try:
280 int(lbl)
280 int(lbl)
281 raise error.Abort(_("cannot use an integer as a name"))
281 raise error.Abort(_("cannot use an integer as a name"))
282 except ValueError:
282 except ValueError:
283 pass
283 pass
284 if lbl.strip() != lbl:
284 if lbl.strip() != lbl:
285 raise error.Abort(_("leading or trailing whitespace in name %r") % lbl)
285 raise error.Abort(_("leading or trailing whitespace in name %r") % lbl)
286
286
287 def checkfilename(f):
287 def checkfilename(f):
288 '''Check that the filename f is an acceptable filename for a tracked file'''
288 '''Check that the filename f is an acceptable filename for a tracked file'''
289 if '\r' in f or '\n' in f:
289 if '\r' in f or '\n' in f:
290 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
290 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
291 % pycompat.bytestr(f))
291 % pycompat.bytestr(f))
292
292
293 def checkportable(ui, f):
293 def checkportable(ui, f):
294 '''Check if filename f is portable and warn or abort depending on config'''
294 '''Check if filename f is portable and warn or abort depending on config'''
295 checkfilename(f)
295 checkfilename(f)
296 abort, warn = checkportabilityalert(ui)
296 abort, warn = checkportabilityalert(ui)
297 if abort or warn:
297 if abort or warn:
298 msg = util.checkwinfilename(f)
298 msg = util.checkwinfilename(f)
299 if msg:
299 if msg:
300 msg = "%s: %s" % (msg, procutil.shellquote(f))
300 msg = "%s: %s" % (msg, procutil.shellquote(f))
301 if abort:
301 if abort:
302 raise error.Abort(msg)
302 raise error.Abort(msg)
303 ui.warn(_("warning: %s\n") % msg)
303 ui.warn(_("warning: %s\n") % msg)
304
304
305 def checkportabilityalert(ui):
305 def checkportabilityalert(ui):
306 '''check if the user's config requests nothing, a warning, or abort for
306 '''check if the user's config requests nothing, a warning, or abort for
307 non-portable filenames'''
307 non-portable filenames'''
308 val = ui.config('ui', 'portablefilenames')
308 val = ui.config('ui', 'portablefilenames')
309 lval = val.lower()
309 lval = val.lower()
310 bval = stringutil.parsebool(val)
310 bval = stringutil.parsebool(val)
311 abort = pycompat.iswindows or lval == 'abort'
311 abort = pycompat.iswindows or lval == 'abort'
312 warn = bval or lval == 'warn'
312 warn = bval or lval == 'warn'
313 if bval is None and not (warn or abort or lval == 'ignore'):
313 if bval is None and not (warn or abort or lval == 'ignore'):
314 raise error.ConfigError(
314 raise error.ConfigError(
315 _("ui.portablefilenames value is invalid ('%s')") % val)
315 _("ui.portablefilenames value is invalid ('%s')") % val)
316 return abort, warn
316 return abort, warn
317
317
318 class casecollisionauditor(object):
318 class casecollisionauditor(object):
319 def __init__(self, ui, abort, dirstate):
319 def __init__(self, ui, abort, dirstate):
320 self._ui = ui
320 self._ui = ui
321 self._abort = abort
321 self._abort = abort
322 allfiles = '\0'.join(dirstate._map)
322 allfiles = '\0'.join(dirstate._map)
323 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
323 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
324 self._dirstate = dirstate
324 self._dirstate = dirstate
325 # The purpose of _newfiles is so that we don't complain about
325 # The purpose of _newfiles is so that we don't complain about
326 # case collisions if someone were to call this object with the
326 # case collisions if someone were to call this object with the
327 # same filename twice.
327 # same filename twice.
328 self._newfiles = set()
328 self._newfiles = set()
329
329
330 def __call__(self, f):
330 def __call__(self, f):
331 if f in self._newfiles:
331 if f in self._newfiles:
332 return
332 return
333 fl = encoding.lower(f)
333 fl = encoding.lower(f)
334 if fl in self._loweredfiles and f not in self._dirstate:
334 if fl in self._loweredfiles and f not in self._dirstate:
335 msg = _('possible case-folding collision for %s') % f
335 msg = _('possible case-folding collision for %s') % f
336 if self._abort:
336 if self._abort:
337 raise error.Abort(msg)
337 raise error.Abort(msg)
338 self._ui.warn(_("warning: %s\n") % msg)
338 self._ui.warn(_("warning: %s\n") % msg)
339 self._loweredfiles.add(fl)
339 self._loweredfiles.add(fl)
340 self._newfiles.add(f)
340 self._newfiles.add(f)
341
341
342 def filteredhash(repo, maxrev):
342 def filteredhash(repo, maxrev):
343 """build hash of filtered revisions in the current repoview.
343 """build hash of filtered revisions in the current repoview.
344
344
345 Multiple caches perform up-to-date validation by checking that the
345 Multiple caches perform up-to-date validation by checking that the
346 tiprev and tipnode stored in the cache file match the current repository.
346 tiprev and tipnode stored in the cache file match the current repository.
347 However, this is not sufficient for validating repoviews because the set
347 However, this is not sufficient for validating repoviews because the set
348 of revisions in the view may change without the repository tiprev and
348 of revisions in the view may change without the repository tiprev and
349 tipnode changing.
349 tipnode changing.
350
350
351 This function hashes all the revs filtered from the view and returns
351 This function hashes all the revs filtered from the view and returns
352 that SHA-1 digest.
352 that SHA-1 digest.
353 """
353 """
354 cl = repo.changelog
354 cl = repo.changelog
355 if not cl.filteredrevs:
355 if not cl.filteredrevs:
356 return None
356 return None
357 key = None
357 key = None
358 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
358 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
359 if revs:
359 if revs:
360 s = hashlib.sha1()
360 s = hashlib.sha1()
361 for rev in revs:
361 for rev in revs:
362 s.update('%d;' % rev)
362 s.update('%d;' % rev)
363 key = s.digest()
363 key = s.digest()
364 return key
364 return key
365
365
366 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
366 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
367 '''yield every hg repository under path, always recursively.
367 '''yield every hg repository under path, always recursively.
368 The recurse flag will only control recursion into repo working dirs'''
368 The recurse flag will only control recursion into repo working dirs'''
369 def errhandler(err):
369 def errhandler(err):
370 if err.filename == path:
370 if err.filename == path:
371 raise err
371 raise err
372 samestat = getattr(os.path, 'samestat', None)
372 samestat = getattr(os.path, 'samestat', None)
373 if followsym and samestat is not None:
373 if followsym and samestat is not None:
374 def adddir(dirlst, dirname):
374 def adddir(dirlst, dirname):
375 dirstat = os.stat(dirname)
375 dirstat = os.stat(dirname)
376 match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst)
376 match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst)
377 if not match:
377 if not match:
378 dirlst.append(dirstat)
378 dirlst.append(dirstat)
379 return not match
379 return not match
380 else:
380 else:
381 followsym = False
381 followsym = False
382
382
383 if (seen_dirs is None) and followsym:
383 if (seen_dirs is None) and followsym:
384 seen_dirs = []
384 seen_dirs = []
385 adddir(seen_dirs, path)
385 adddir(seen_dirs, path)
386 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
386 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
387 dirs.sort()
387 dirs.sort()
388 if '.hg' in dirs:
388 if '.hg' in dirs:
389 yield root # found a repository
389 yield root # found a repository
390 qroot = os.path.join(root, '.hg', 'patches')
390 qroot = os.path.join(root, '.hg', 'patches')
391 if os.path.isdir(os.path.join(qroot, '.hg')):
391 if os.path.isdir(os.path.join(qroot, '.hg')):
392 yield qroot # we have a patch queue repo here
392 yield qroot # we have a patch queue repo here
393 if recurse:
393 if recurse:
394 # avoid recursing inside the .hg directory
394 # avoid recursing inside the .hg directory
395 dirs.remove('.hg')
395 dirs.remove('.hg')
396 else:
396 else:
397 dirs[:] = [] # don't descend further
397 dirs[:] = [] # don't descend further
398 elif followsym:
398 elif followsym:
399 newdirs = []
399 newdirs = []
400 for d in dirs:
400 for d in dirs:
401 fname = os.path.join(root, d)
401 fname = os.path.join(root, d)
402 if adddir(seen_dirs, fname):
402 if adddir(seen_dirs, fname):
403 if os.path.islink(fname):
403 if os.path.islink(fname):
404 for hgname in walkrepos(fname, True, seen_dirs):
404 for hgname in walkrepos(fname, True, seen_dirs):
405 yield hgname
405 yield hgname
406 else:
406 else:
407 newdirs.append(d)
407 newdirs.append(d)
408 dirs[:] = newdirs
408 dirs[:] = newdirs
409
409
410 def binnode(ctx):
410 def binnode(ctx):
411 """Return binary node id for a given basectx"""
411 """Return binary node id for a given basectx"""
412 node = ctx.node()
412 node = ctx.node()
413 if node is None:
413 if node is None:
414 return wdirid
414 return wdirid
415 return node
415 return node
416
416
417 def intrev(ctx):
417 def intrev(ctx):
418 """Return integer for a given basectx that can be used in comparison or
418 """Return integer for a given basectx that can be used in comparison or
419 arithmetic operation"""
419 arithmetic operation"""
420 rev = ctx.rev()
420 rev = ctx.rev()
421 if rev is None:
421 if rev is None:
422 return wdirrev
422 return wdirrev
423 return rev
423 return rev
424
424
425 def formatchangeid(ctx):
425 def formatchangeid(ctx):
426 """Format changectx as '{rev}:{node|formatnode}', which is the default
426 """Format changectx as '{rev}:{node|formatnode}', which is the default
427 template provided by logcmdutil.changesettemplater"""
427 template provided by logcmdutil.changesettemplater"""
428 repo = ctx.repo()
428 repo = ctx.repo()
429 return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
429 return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
430
430
431 def formatrevnode(ui, rev, node):
431 def formatrevnode(ui, rev, node):
432 """Format given revision and node depending on the current verbosity"""
432 """Format given revision and node depending on the current verbosity"""
433 if ui.debugflag:
433 if ui.debugflag:
434 hexfunc = hex
434 hexfunc = hex
435 else:
435 else:
436 hexfunc = short
436 hexfunc = short
437 return '%d:%s' % (rev, hexfunc(node))
437 return '%d:%s' % (rev, hexfunc(node))
438
438
439 def resolvehexnodeidprefix(repo, prefix):
439 def resolvehexnodeidprefix(repo, prefix):
440 # Uses unfiltered repo because it's faster when prefix is ambiguous/
440 # Uses unfiltered repo because it's faster when prefix is ambiguous/
441 # This matches the shortesthexnodeidprefix() function below.
441 # This matches the shortesthexnodeidprefix() function below.
442 node = repo.unfiltered().changelog._partialmatch(prefix)
442 node = repo.unfiltered().changelog._partialmatch(prefix)
443 if node is None:
443 if node is None:
444 return
444 return
445 repo.changelog.rev(node) # make sure node isn't filtered
445 repo.changelog.rev(node) # make sure node isn't filtered
446 return node
446 return node
447
447
448 def shortesthexnodeidprefix(repo, node, minlength=1):
448 def shortesthexnodeidprefix(repo, node, minlength=1):
449 """Find the shortest unambiguous prefix that matches hexnode."""
449 """Find the shortest unambiguous prefix that matches hexnode."""
450 # _partialmatch() of filtered changelog could take O(len(repo)) time,
450 # _partialmatch() of filtered changelog could take O(len(repo)) time,
451 # which would be unacceptably slow. so we look for hash collision in
451 # which would be unacceptably slow. so we look for hash collision in
452 # unfiltered space, which means some hashes may be slightly longer.
452 # unfiltered space, which means some hashes may be slightly longer.
453 cl = repo.unfiltered().changelog
453 cl = repo.unfiltered().changelog
454
454
455 def isrev(prefix):
455 def isrev(prefix):
456 try:
456 try:
457 i = int(prefix)
457 i = int(prefix)
458 # if we are a pure int, then starting with zero will not be
458 # if we are a pure int, then starting with zero will not be
459 # confused as a rev; or, obviously, if the int is larger
459 # confused as a rev; or, obviously, if the int is larger
460 # than the value of the tip rev
460 # than the value of the tip rev
461 if prefix[0:1] == b'0' or i > len(cl):
461 if prefix[0:1] == b'0' or i > len(cl):
462 return False
462 return False
463 return True
463 return True
464 except ValueError:
464 except ValueError:
465 return False
465 return False
466
466
467 def disambiguate(prefix):
467 def disambiguate(prefix):
468 """Disambiguate against revnums."""
468 """Disambiguate against revnums."""
469 hexnode = hex(node)
469 hexnode = hex(node)
470 for length in range(len(prefix), len(hexnode) + 1):
470 for length in range(len(prefix), len(hexnode) + 1):
471 prefix = hexnode[:length]
471 prefix = hexnode[:length]
472 if not isrev(prefix):
472 if not isrev(prefix):
473 return prefix
473 return prefix
474
474
475 try:
475 try:
476 return disambiguate(cl.shortest(node, minlength))
476 return disambiguate(cl.shortest(node, minlength))
477 except error.LookupError:
477 except error.LookupError:
478 raise error.RepoLookupError()
478 raise error.RepoLookupError()
479
479
480 def isrevsymbol(repo, symbol):
480 def isrevsymbol(repo, symbol):
481 """Checks if a symbol exists in the repo.
481 """Checks if a symbol exists in the repo.
482
482
483 See revsymbol() for details. Raises error.LookupError if the symbol is an
483 See revsymbol() for details. Raises error.LookupError if the symbol is an
484 ambiguous nodeid prefix.
484 ambiguous nodeid prefix.
485 """
485 """
486 try:
486 try:
487 revsymbol(repo, symbol)
487 revsymbol(repo, symbol)
488 return True
488 return True
489 except error.RepoLookupError:
489 except error.RepoLookupError:
490 return False
490 return False
491
491
492 def revsymbol(repo, symbol):
492 def revsymbol(repo, symbol):
493 """Returns a context given a single revision symbol (as string).
493 """Returns a context given a single revision symbol (as string).
494
494
495 This is similar to revsingle(), but accepts only a single revision symbol,
495 This is similar to revsingle(), but accepts only a single revision symbol,
496 i.e. things like ".", "tip", "1234", "deadbeef", "my-bookmark" work, but
496 i.e. things like ".", "tip", "1234", "deadbeef", "my-bookmark" work, but
497 not "max(public())".
497 not "max(public())".
498 """
498 """
499 if not isinstance(symbol, bytes):
499 if not isinstance(symbol, bytes):
500 msg = ("symbol (%s of type %s) was not a string, did you mean "
500 msg = ("symbol (%s of type %s) was not a string, did you mean "
501 "repo[symbol]?" % (symbol, type(symbol)))
501 "repo[symbol]?" % (symbol, type(symbol)))
502 raise error.ProgrammingError(msg)
502 raise error.ProgrammingError(msg)
503 try:
503 try:
504 if symbol in ('.', 'tip', 'null'):
504 if symbol in ('.', 'tip', 'null'):
505 return repo[symbol]
505 return repo[symbol]
506
506
507 try:
507 try:
508 r = int(symbol)
508 r = int(symbol)
509 if '%d' % r != symbol:
509 if '%d' % r != symbol:
510 raise ValueError
510 raise ValueError
511 l = len(repo.changelog)
511 l = len(repo.changelog)
512 if r < 0:
512 if r < 0:
513 r += l
513 r += l
514 if r < 0 or r >= l and r != wdirrev:
514 if r < 0 or r >= l and r != wdirrev:
515 raise ValueError
515 raise ValueError
516 return repo[r]
516 return repo[r]
517 except error.FilteredIndexError:
517 except error.FilteredIndexError:
518 raise
518 raise
519 except (ValueError, OverflowError, IndexError):
519 except (ValueError, OverflowError, IndexError):
520 pass
520 pass
521
521
522 if len(symbol) == 40:
522 if len(symbol) == 40:
523 try:
523 try:
524 node = bin(symbol)
524 node = bin(symbol)
525 rev = repo.changelog.rev(node)
525 rev = repo.changelog.rev(node)
526 return repo[rev]
526 return repo[rev]
527 except error.FilteredLookupError:
527 except error.FilteredLookupError:
528 raise
528 raise
529 except (TypeError, LookupError):
529 except (TypeError, LookupError):
530 pass
530 pass
531
531
532 # look up bookmarks through the name interface
532 # look up bookmarks through the name interface
533 try:
533 try:
534 node = repo.names.singlenode(repo, symbol)
534 node = repo.names.singlenode(repo, symbol)
535 rev = repo.changelog.rev(node)
535 rev = repo.changelog.rev(node)
536 return repo[rev]
536 return repo[rev]
537 except KeyError:
537 except KeyError:
538 pass
538 pass
539
539
540 node = resolvehexnodeidprefix(repo, symbol)
540 node = resolvehexnodeidprefix(repo, symbol)
541 if node is not None:
541 if node is not None:
542 rev = repo.changelog.rev(node)
542 rev = repo.changelog.rev(node)
543 return repo[rev]
543 return repo[rev]
544
544
545 raise error.RepoLookupError(_("unknown revision '%s'") % symbol)
545 raise error.RepoLookupError(_("unknown revision '%s'") % symbol)
546
546
547 except error.WdirUnsupported:
547 except error.WdirUnsupported:
548 return repo[None]
548 return repo[None]
549 except (error.FilteredIndexError, error.FilteredLookupError,
549 except (error.FilteredIndexError, error.FilteredLookupError,
550 error.FilteredRepoLookupError):
550 error.FilteredRepoLookupError):
551 raise _filterederror(repo, symbol)
551 raise _filterederror(repo, symbol)
552
552
553 def _filterederror(repo, changeid):
553 def _filterederror(repo, changeid):
554 """build an exception to be raised about a filtered changeid
554 """build an exception to be raised about a filtered changeid
555
555
556 This is extracted in a function to help extensions (eg: evolve) to
556 This is extracted in a function to help extensions (eg: evolve) to
557 experiment with various message variants."""
557 experiment with various message variants."""
558 if repo.filtername.startswith('visible'):
558 if repo.filtername.startswith('visible'):
559
559
560 # Check if the changeset is obsolete
560 # Check if the changeset is obsolete
561 unfilteredrepo = repo.unfiltered()
561 unfilteredrepo = repo.unfiltered()
562 ctx = revsymbol(unfilteredrepo, changeid)
562 ctx = revsymbol(unfilteredrepo, changeid)
563
563
564 # If the changeset is obsolete, enrich the message with the reason
564 # If the changeset is obsolete, enrich the message with the reason
565 # that made this changeset not visible
565 # that made this changeset not visible
566 if ctx.obsolete():
566 if ctx.obsolete():
567 msg = obsutil._getfilteredreason(repo, changeid, ctx)
567 msg = obsutil._getfilteredreason(repo, changeid, ctx)
568 else:
568 else:
569 msg = _("hidden revision '%s'") % changeid
569 msg = _("hidden revision '%s'") % changeid
570
570
571 hint = _('use --hidden to access hidden revisions')
571 hint = _('use --hidden to access hidden revisions')
572
572
573 return error.FilteredRepoLookupError(msg, hint=hint)
573 return error.FilteredRepoLookupError(msg, hint=hint)
574 msg = _("filtered revision '%s' (not in '%s' subset)")
574 msg = _("filtered revision '%s' (not in '%s' subset)")
575 msg %= (changeid, repo.filtername)
575 msg %= (changeid, repo.filtername)
576 return error.FilteredRepoLookupError(msg)
576 return error.FilteredRepoLookupError(msg)
577
577
578 def revsingle(repo, revspec, default='.', localalias=None):
578 def revsingle(repo, revspec, default='.', localalias=None):
579 if not revspec and revspec != 0:
579 if not revspec and revspec != 0:
580 return repo[default]
580 return repo[default]
581
581
582 l = revrange(repo, [revspec], localalias=localalias)
582 l = revrange(repo, [revspec], localalias=localalias)
583 if not l:
583 if not l:
584 raise error.Abort(_('empty revision set'))
584 raise error.Abort(_('empty revision set'))
585 return repo[l.last()]
585 return repo[l.last()]
586
586
587 def _pairspec(revspec):
587 def _pairspec(revspec):
588 tree = revsetlang.parse(revspec)
588 tree = revsetlang.parse(revspec)
589 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
589 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
590
590
591 def revpair(repo, revs):
591 def revpair(repo, revs):
592 if not revs:
592 if not revs:
593 return repo['.'], repo[None]
593 return repo['.'], repo[None]
594
594
595 l = revrange(repo, revs)
595 l = revrange(repo, revs)
596
596
597 if not l:
597 if not l:
598 first = second = None
598 first = second = None
599 elif l.isascending():
599 elif l.isascending():
600 first = l.min()
600 first = l.min()
601 second = l.max()
601 second = l.max()
602 elif l.isdescending():
602 elif l.isdescending():
603 first = l.max()
603 first = l.max()
604 second = l.min()
604 second = l.min()
605 else:
605 else:
606 first = l.first()
606 first = l.first()
607 second = l.last()
607 second = l.last()
608
608
609 if first is None:
609 if first is None:
610 raise error.Abort(_('empty revision range'))
610 raise error.Abort(_('empty revision range'))
611 if (first == second and len(revs) >= 2
611 if (first == second and len(revs) >= 2
612 and not all(revrange(repo, [r]) for r in revs)):
612 and not all(revrange(repo, [r]) for r in revs)):
613 raise error.Abort(_('empty revision on one side of range'))
613 raise error.Abort(_('empty revision on one side of range'))
614
614
615 # if top-level is range expression, the result must always be a pair
615 # if top-level is range expression, the result must always be a pair
616 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
616 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
617 return repo[first], repo[None]
617 return repo[first], repo[None]
618
618
619 return repo[first], repo[second]
619 return repo[first], repo[second]
620
620
621 def revrange(repo, specs, localalias=None):
621 def revrange(repo, specs, localalias=None):
622 """Execute 1 to many revsets and return the union.
622 """Execute 1 to many revsets and return the union.
623
623
624 This is the preferred mechanism for executing revsets using user-specified
624 This is the preferred mechanism for executing revsets using user-specified
625 config options, such as revset aliases.
625 config options, such as revset aliases.
626
626
627 The revsets specified by ``specs`` will be executed via a chained ``OR``
627 The revsets specified by ``specs`` will be executed via a chained ``OR``
628 expression. If ``specs`` is empty, an empty result is returned.
628 expression. If ``specs`` is empty, an empty result is returned.
629
629
630 ``specs`` can contain integers, in which case they are assumed to be
630 ``specs`` can contain integers, in which case they are assumed to be
631 revision numbers.
631 revision numbers.
632
632
633 It is assumed the revsets are already formatted. If you have arguments
633 It is assumed the revsets are already formatted. If you have arguments
634 that need to be expanded in the revset, call ``revsetlang.formatspec()``
634 that need to be expanded in the revset, call ``revsetlang.formatspec()``
635 and pass the result as an element of ``specs``.
635 and pass the result as an element of ``specs``.
636
636
637 Specifying a single revset is allowed.
637 Specifying a single revset is allowed.
638
638
639 Returns a ``revset.abstractsmartset`` which is a list-like interface over
639 Returns a ``revset.abstractsmartset`` which is a list-like interface over
640 integer revisions.
640 integer revisions.
641 """
641 """
642 allspecs = []
642 allspecs = []
643 for spec in specs:
643 for spec in specs:
644 if isinstance(spec, int):
644 if isinstance(spec, int):
645 spec = revsetlang.formatspec('rev(%d)', spec)
645 spec = revsetlang.formatspec('rev(%d)', spec)
646 allspecs.append(spec)
646 allspecs.append(spec)
647 return repo.anyrevs(allspecs, user=True, localalias=localalias)
647 return repo.anyrevs(allspecs, user=True, localalias=localalias)
648
648
649 def meaningfulparents(repo, ctx):
649 def meaningfulparents(repo, ctx):
650 """Return list of meaningful (or all if debug) parentrevs for rev.
650 """Return list of meaningful (or all if debug) parentrevs for rev.
651
651
652 For merges (two non-nullrev revisions) both parents are meaningful.
652 For merges (two non-nullrev revisions) both parents are meaningful.
653 Otherwise the first parent revision is considered meaningful if it
653 Otherwise the first parent revision is considered meaningful if it
654 is not the preceding revision.
654 is not the preceding revision.
655 """
655 """
656 parents = ctx.parents()
656 parents = ctx.parents()
657 if len(parents) > 1:
657 if len(parents) > 1:
658 return parents
658 return parents
659 if repo.ui.debugflag:
659 if repo.ui.debugflag:
660 return [parents[0], repo['null']]
660 return [parents[0], repo['null']]
661 if parents[0].rev() >= intrev(ctx) - 1:
661 if parents[0].rev() >= intrev(ctx) - 1:
662 return []
662 return []
663 return parents
663 return parents
664
664
665 def expandpats(pats):
665 def expandpats(pats):
666 '''Expand bare globs when running on windows.
666 '''Expand bare globs when running on windows.
667 On posix we assume it already has already been done by sh.'''
667 On posix we assume it already has already been done by sh.'''
668 if not util.expandglobs:
668 if not util.expandglobs:
669 return list(pats)
669 return list(pats)
670 ret = []
670 ret = []
671 for kindpat in pats:
671 for kindpat in pats:
672 kind, pat = matchmod._patsplit(kindpat, None)
672 kind, pat = matchmod._patsplit(kindpat, None)
673 if kind is None:
673 if kind is None:
674 try:
674 try:
675 globbed = glob.glob(pat)
675 globbed = glob.glob(pat)
676 except re.error:
676 except re.error:
677 globbed = [pat]
677 globbed = [pat]
678 if globbed:
678 if globbed:
679 ret.extend(globbed)
679 ret.extend(globbed)
680 continue
680 continue
681 ret.append(kindpat)
681 ret.append(kindpat)
682 return ret
682 return ret
683
683
684 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
684 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
685 badfn=None):
685 badfn=None):
686 '''Return a matcher and the patterns that were used.
686 '''Return a matcher and the patterns that were used.
687 The matcher will warn about bad matches, unless an alternate badfn callback
687 The matcher will warn about bad matches, unless an alternate badfn callback
688 is provided.'''
688 is provided.'''
689 if pats == ("",):
689 if pats == ("",):
690 pats = []
690 pats = []
691 if opts is None:
691 if opts is None:
692 opts = {}
692 opts = {}
693 if not globbed and default == 'relpath':
693 if not globbed and default == 'relpath':
694 pats = expandpats(pats or [])
694 pats = expandpats(pats or [])
695
695
696 def bad(f, msg):
696 def bad(f, msg):
697 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
697 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
698
698
699 if badfn is None:
699 if badfn is None:
700 badfn = bad
700 badfn = bad
701
701
702 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
702 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
703 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
703 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
704
704
705 if m.always():
705 if m.always():
706 pats = []
706 pats = []
707 return m, pats
707 return m, pats
708
708
709 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
709 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
710 badfn=None):
710 badfn=None):
711 '''Return a matcher that will warn about bad matches.'''
711 '''Return a matcher that will warn about bad matches.'''
712 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
712 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
713
713
714 def matchall(repo):
714 def matchall(repo):
715 '''Return a matcher that will efficiently match everything.'''
715 '''Return a matcher that will efficiently match everything.'''
716 return matchmod.always(repo.root, repo.getcwd())
716 return matchmod.always(repo.root, repo.getcwd())
717
717
718 def matchfiles(repo, files, badfn=None):
718 def matchfiles(repo, files, badfn=None):
719 '''Return a matcher that will efficiently match exactly these files.'''
719 '''Return a matcher that will efficiently match exactly these files.'''
720 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
720 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
721
721
722 def parsefollowlinespattern(repo, rev, pat, msg):
722 def parsefollowlinespattern(repo, rev, pat, msg):
723 """Return a file name from `pat` pattern suitable for usage in followlines
723 """Return a file name from `pat` pattern suitable for usage in followlines
724 logic.
724 logic.
725 """
725 """
726 if not matchmod.patkind(pat):
726 if not matchmod.patkind(pat):
727 return pathutil.canonpath(repo.root, repo.getcwd(), pat)
727 return pathutil.canonpath(repo.root, repo.getcwd(), pat)
728 else:
728 else:
729 ctx = repo[rev]
729 ctx = repo[rev]
730 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
730 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
731 files = [f for f in ctx if m(f)]
731 files = [f for f in ctx if m(f)]
732 if len(files) != 1:
732 if len(files) != 1:
733 raise error.ParseError(msg)
733 raise error.ParseError(msg)
734 return files[0]
734 return files[0]
735
735
736 def origpath(ui, repo, filepath):
736 def origpath(ui, repo, filepath):
737 '''customize where .orig files are created
737 '''customize where .orig files are created
738
738
739 Fetch user defined path from config file: [ui] origbackuppath = <path>
739 Fetch user defined path from config file: [ui] origbackuppath = <path>
740 Fall back to default (filepath with .orig suffix) if not specified
740 Fall back to default (filepath with .orig suffix) if not specified
741 '''
741 '''
742 origbackuppath = ui.config('ui', 'origbackuppath')
742 origbackuppath = ui.config('ui', 'origbackuppath')
743 if not origbackuppath:
743 if not origbackuppath:
744 return filepath + ".orig"
744 return filepath + ".orig"
745
745
746 # Convert filepath from an absolute path into a path inside the repo.
746 # Convert filepath from an absolute path into a path inside the repo.
747 filepathfromroot = util.normpath(os.path.relpath(filepath,
747 filepathfromroot = util.normpath(os.path.relpath(filepath,
748 start=repo.root))
748 start=repo.root))
749
749
750 origvfs = vfs.vfs(repo.wjoin(origbackuppath))
750 origvfs = vfs.vfs(repo.wjoin(origbackuppath))
751 origbackupdir = origvfs.dirname(filepathfromroot)
751 origbackupdir = origvfs.dirname(filepathfromroot)
752 if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
752 if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
753 ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
753 ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
754
754
755 # Remove any files that conflict with the backup file's path
755 # Remove any files that conflict with the backup file's path
756 for f in reversed(list(util.finddirs(filepathfromroot))):
756 for f in reversed(list(util.finddirs(filepathfromroot))):
757 if origvfs.isfileorlink(f):
757 if origvfs.isfileorlink(f):
758 ui.note(_('removing conflicting file: %s\n')
758 ui.note(_('removing conflicting file: %s\n')
759 % origvfs.join(f))
759 % origvfs.join(f))
760 origvfs.unlink(f)
760 origvfs.unlink(f)
761 break
761 break
762
762
763 origvfs.makedirs(origbackupdir)
763 origvfs.makedirs(origbackupdir)
764
764
765 if origvfs.isdir(filepathfromroot) and not origvfs.islink(filepathfromroot):
765 if origvfs.isdir(filepathfromroot) and not origvfs.islink(filepathfromroot):
766 ui.note(_('removing conflicting directory: %s\n')
766 ui.note(_('removing conflicting directory: %s\n')
767 % origvfs.join(filepathfromroot))
767 % origvfs.join(filepathfromroot))
768 origvfs.rmtree(filepathfromroot, forcibly=True)
768 origvfs.rmtree(filepathfromroot, forcibly=True)
769
769
770 return origvfs.join(filepathfromroot)
770 return origvfs.join(filepathfromroot)
771
771
772 class _containsnode(object):
772 class _containsnode(object):
773 """proxy __contains__(node) to container.__contains__ which accepts revs"""
773 """proxy __contains__(node) to container.__contains__ which accepts revs"""
774
774
775 def __init__(self, repo, revcontainer):
775 def __init__(self, repo, revcontainer):
776 self._torev = repo.changelog.rev
776 self._torev = repo.changelog.rev
777 self._revcontains = revcontainer.__contains__
777 self._revcontains = revcontainer.__contains__
778
778
779 def __contains__(self, node):
779 def __contains__(self, node):
780 return self._revcontains(self._torev(node))
780 return self._revcontains(self._torev(node))
781
781
782 def cleanupnodes(repo, replacements, operation, moves=None, metadata=None,
782 def cleanupnodes(repo, replacements, operation, moves=None, metadata=None,
783 fixphase=False, targetphase=None):
783 fixphase=False, targetphase=None, backup=True):
784 """do common cleanups when old nodes are replaced by new nodes
784 """do common cleanups when old nodes are replaced by new nodes
785
785
786 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
786 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
787 (we might also want to move working directory parent in the future)
787 (we might also want to move working directory parent in the future)
788
788
789 By default, bookmark moves are calculated automatically from 'replacements',
789 By default, bookmark moves are calculated automatically from 'replacements',
790 but 'moves' can be used to override that. Also, 'moves' may include
790 but 'moves' can be used to override that. Also, 'moves' may include
791 additional bookmark moves that should not have associated obsmarkers.
791 additional bookmark moves that should not have associated obsmarkers.
792
792
793 replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
793 replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
794 have replacements. operation is a string, like "rebase".
794 have replacements. operation is a string, like "rebase".
795
795
796 metadata is dictionary containing metadata to be stored in obsmarker if
796 metadata is dictionary containing metadata to be stored in obsmarker if
797 obsolescence is enabled.
797 obsolescence is enabled.
798 """
798 """
799 assert fixphase or targetphase is None
799 assert fixphase or targetphase is None
800 if not replacements and not moves:
800 if not replacements and not moves:
801 return
801 return
802
802
803 # translate mapping's other forms
803 # translate mapping's other forms
804 if not util.safehasattr(replacements, 'items'):
804 if not util.safehasattr(replacements, 'items'):
805 replacements = {n: () for n in replacements}
805 replacements = {n: () for n in replacements}
806
806
807 # Calculate bookmark movements
807 # Calculate bookmark movements
808 if moves is None:
808 if moves is None:
809 moves = {}
809 moves = {}
810 # Unfiltered repo is needed since nodes in replacements might be hidden.
810 # Unfiltered repo is needed since nodes in replacements might be hidden.
811 unfi = repo.unfiltered()
811 unfi = repo.unfiltered()
812 for oldnode, newnodes in replacements.items():
812 for oldnode, newnodes in replacements.items():
813 if oldnode in moves:
813 if oldnode in moves:
814 continue
814 continue
815 if len(newnodes) > 1:
815 if len(newnodes) > 1:
816 # usually a split, take the one with biggest rev number
816 # usually a split, take the one with biggest rev number
817 newnode = next(unfi.set('max(%ln)', newnodes)).node()
817 newnode = next(unfi.set('max(%ln)', newnodes)).node()
818 elif len(newnodes) == 0:
818 elif len(newnodes) == 0:
819 # move bookmark backwards
819 # move bookmark backwards
820 roots = list(unfi.set('max((::%n) - %ln)', oldnode,
820 roots = list(unfi.set('max((::%n) - %ln)', oldnode,
821 list(replacements)))
821 list(replacements)))
822 if roots:
822 if roots:
823 newnode = roots[0].node()
823 newnode = roots[0].node()
824 else:
824 else:
825 newnode = nullid
825 newnode = nullid
826 else:
826 else:
827 newnode = newnodes[0]
827 newnode = newnodes[0]
828 moves[oldnode] = newnode
828 moves[oldnode] = newnode
829
829
830 allnewnodes = [n for ns in replacements.values() for n in ns]
830 allnewnodes = [n for ns in replacements.values() for n in ns]
831 toretract = {}
831 toretract = {}
832 toadvance = {}
832 toadvance = {}
833 if fixphase:
833 if fixphase:
834 precursors = {}
834 precursors = {}
835 for oldnode, newnodes in replacements.items():
835 for oldnode, newnodes in replacements.items():
836 for newnode in newnodes:
836 for newnode in newnodes:
837 precursors.setdefault(newnode, []).append(oldnode)
837 precursors.setdefault(newnode, []).append(oldnode)
838
838
839 allnewnodes.sort(key=lambda n: unfi[n].rev())
839 allnewnodes.sort(key=lambda n: unfi[n].rev())
840 newphases = {}
840 newphases = {}
841 def phase(ctx):
841 def phase(ctx):
842 return newphases.get(ctx.node(), ctx.phase())
842 return newphases.get(ctx.node(), ctx.phase())
843 for newnode in allnewnodes:
843 for newnode in allnewnodes:
844 ctx = unfi[newnode]
844 ctx = unfi[newnode]
845 parentphase = max(phase(p) for p in ctx.parents())
845 parentphase = max(phase(p) for p in ctx.parents())
846 if targetphase is None:
846 if targetphase is None:
847 oldphase = max(unfi[oldnode].phase()
847 oldphase = max(unfi[oldnode].phase()
848 for oldnode in precursors[newnode])
848 for oldnode in precursors[newnode])
849 newphase = max(oldphase, parentphase)
849 newphase = max(oldphase, parentphase)
850 else:
850 else:
851 newphase = max(targetphase, parentphase)
851 newphase = max(targetphase, parentphase)
852 newphases[newnode] = newphase
852 newphases[newnode] = newphase
853 if newphase > ctx.phase():
853 if newphase > ctx.phase():
854 toretract.setdefault(newphase, []).append(newnode)
854 toretract.setdefault(newphase, []).append(newnode)
855 elif newphase < ctx.phase():
855 elif newphase < ctx.phase():
856 toadvance.setdefault(newphase, []).append(newnode)
856 toadvance.setdefault(newphase, []).append(newnode)
857
857
858 with repo.transaction('cleanup') as tr:
858 with repo.transaction('cleanup') as tr:
859 # Move bookmarks
859 # Move bookmarks
860 bmarks = repo._bookmarks
860 bmarks = repo._bookmarks
861 bmarkchanges = []
861 bmarkchanges = []
862 for oldnode, newnode in moves.items():
862 for oldnode, newnode in moves.items():
863 oldbmarks = repo.nodebookmarks(oldnode)
863 oldbmarks = repo.nodebookmarks(oldnode)
864 if not oldbmarks:
864 if not oldbmarks:
865 continue
865 continue
866 from . import bookmarks # avoid import cycle
866 from . import bookmarks # avoid import cycle
867 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
867 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
868 (pycompat.rapply(pycompat.maybebytestr, oldbmarks),
868 (pycompat.rapply(pycompat.maybebytestr, oldbmarks),
869 hex(oldnode), hex(newnode)))
869 hex(oldnode), hex(newnode)))
870 # Delete divergent bookmarks being parents of related newnodes
870 # Delete divergent bookmarks being parents of related newnodes
871 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
871 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
872 allnewnodes, newnode, oldnode)
872 allnewnodes, newnode, oldnode)
873 deletenodes = _containsnode(repo, deleterevs)
873 deletenodes = _containsnode(repo, deleterevs)
874 for name in oldbmarks:
874 for name in oldbmarks:
875 bmarkchanges.append((name, newnode))
875 bmarkchanges.append((name, newnode))
876 for b in bookmarks.divergent2delete(repo, deletenodes, name):
876 for b in bookmarks.divergent2delete(repo, deletenodes, name):
877 bmarkchanges.append((b, None))
877 bmarkchanges.append((b, None))
878
878
879 if bmarkchanges:
879 if bmarkchanges:
880 bmarks.applychanges(repo, tr, bmarkchanges)
880 bmarks.applychanges(repo, tr, bmarkchanges)
881
881
882 for phase, nodes in toretract.items():
882 for phase, nodes in toretract.items():
883 phases.retractboundary(repo, tr, phase, nodes)
883 phases.retractboundary(repo, tr, phase, nodes)
884 for phase, nodes in toadvance.items():
884 for phase, nodes in toadvance.items():
885 phases.advanceboundary(repo, tr, phase, nodes)
885 phases.advanceboundary(repo, tr, phase, nodes)
886
886
887 # Obsolete or strip nodes
887 # Obsolete or strip nodes
888 if obsolete.isenabled(repo, obsolete.createmarkersopt):
888 if obsolete.isenabled(repo, obsolete.createmarkersopt):
889 # If a node is already obsoleted, and we want to obsolete it
889 # If a node is already obsoleted, and we want to obsolete it
890 # without a successor, skip that obssolete request since it's
890 # without a successor, skip that obssolete request since it's
891 # unnecessary. That's the "if s or not isobs(n)" check below.
891 # unnecessary. That's the "if s or not isobs(n)" check below.
892 # Also sort the node in topology order, that might be useful for
892 # Also sort the node in topology order, that might be useful for
893 # some obsstore logic.
893 # some obsstore logic.
894 # NOTE: the filtering and sorting might belong to createmarkers.
894 # NOTE: the filtering and sorting might belong to createmarkers.
895 isobs = unfi.obsstore.successors.__contains__
895 isobs = unfi.obsstore.successors.__contains__
896 torev = unfi.changelog.rev
896 torev = unfi.changelog.rev
897 sortfunc = lambda ns: torev(ns[0])
897 sortfunc = lambda ns: torev(ns[0])
898 rels = [(unfi[n], tuple(unfi[m] for m in s))
898 rels = [(unfi[n], tuple(unfi[m] for m in s))
899 for n, s in sorted(replacements.items(), key=sortfunc)
899 for n, s in sorted(replacements.items(), key=sortfunc)
900 if s or not isobs(n)]
900 if s or not isobs(n)]
901 if rels:
901 if rels:
902 obsolete.createmarkers(repo, rels, operation=operation,
902 obsolete.createmarkers(repo, rels, operation=operation,
903 metadata=metadata)
903 metadata=metadata)
904 else:
904 else:
905 from . import repair # avoid import cycle
905 from . import repair # avoid import cycle
906 tostrip = list(replacements)
906 tostrip = list(replacements)
907 if tostrip:
907 if tostrip:
908 repair.delayedstrip(repo.ui, repo, tostrip, operation)
908 repair.delayedstrip(repo.ui, repo, tostrip, operation,
909 backup=backup)
909
910
910 def addremove(repo, matcher, prefix, opts=None):
911 def addremove(repo, matcher, prefix, opts=None):
911 if opts is None:
912 if opts is None:
912 opts = {}
913 opts = {}
913 m = matcher
914 m = matcher
914 dry_run = opts.get('dry_run')
915 dry_run = opts.get('dry_run')
915 try:
916 try:
916 similarity = float(opts.get('similarity') or 0)
917 similarity = float(opts.get('similarity') or 0)
917 except ValueError:
918 except ValueError:
918 raise error.Abort(_('similarity must be a number'))
919 raise error.Abort(_('similarity must be a number'))
919 if similarity < 0 or similarity > 100:
920 if similarity < 0 or similarity > 100:
920 raise error.Abort(_('similarity must be between 0 and 100'))
921 raise error.Abort(_('similarity must be between 0 and 100'))
921 similarity /= 100.0
922 similarity /= 100.0
922
923
923 ret = 0
924 ret = 0
924 join = lambda f: os.path.join(prefix, f)
925 join = lambda f: os.path.join(prefix, f)
925
926
926 wctx = repo[None]
927 wctx = repo[None]
927 for subpath in sorted(wctx.substate):
928 for subpath in sorted(wctx.substate):
928 submatch = matchmod.subdirmatcher(subpath, m)
929 submatch = matchmod.subdirmatcher(subpath, m)
929 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
930 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
930 sub = wctx.sub(subpath)
931 sub = wctx.sub(subpath)
931 try:
932 try:
932 if sub.addremove(submatch, prefix, opts):
933 if sub.addremove(submatch, prefix, opts):
933 ret = 1
934 ret = 1
934 except error.LookupError:
935 except error.LookupError:
935 repo.ui.status(_("skipping missing subrepository: %s\n")
936 repo.ui.status(_("skipping missing subrepository: %s\n")
936 % join(subpath))
937 % join(subpath))
937
938
938 rejected = []
939 rejected = []
939 def badfn(f, msg):
940 def badfn(f, msg):
940 if f in m.files():
941 if f in m.files():
941 m.bad(f, msg)
942 m.bad(f, msg)
942 rejected.append(f)
943 rejected.append(f)
943
944
944 badmatch = matchmod.badmatch(m, badfn)
945 badmatch = matchmod.badmatch(m, badfn)
945 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
946 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
946 badmatch)
947 badmatch)
947
948
948 unknownset = set(unknown + forgotten)
949 unknownset = set(unknown + forgotten)
949 toprint = unknownset.copy()
950 toprint = unknownset.copy()
950 toprint.update(deleted)
951 toprint.update(deleted)
951 for abs in sorted(toprint):
952 for abs in sorted(toprint):
952 if repo.ui.verbose or not m.exact(abs):
953 if repo.ui.verbose or not m.exact(abs):
953 if abs in unknownset:
954 if abs in unknownset:
954 status = _('adding %s\n') % m.uipath(abs)
955 status = _('adding %s\n') % m.uipath(abs)
955 else:
956 else:
956 status = _('removing %s\n') % m.uipath(abs)
957 status = _('removing %s\n') % m.uipath(abs)
957 repo.ui.status(status)
958 repo.ui.status(status)
958
959
959 renames = _findrenames(repo, m, added + unknown, removed + deleted,
960 renames = _findrenames(repo, m, added + unknown, removed + deleted,
960 similarity)
961 similarity)
961
962
962 if not dry_run:
963 if not dry_run:
963 _markchanges(repo, unknown + forgotten, deleted, renames)
964 _markchanges(repo, unknown + forgotten, deleted, renames)
964
965
965 for f in rejected:
966 for f in rejected:
966 if f in m.files():
967 if f in m.files():
967 return 1
968 return 1
968 return ret
969 return ret
969
970
970 def marktouched(repo, files, similarity=0.0):
971 def marktouched(repo, files, similarity=0.0):
971 '''Assert that files have somehow been operated upon. files are relative to
972 '''Assert that files have somehow been operated upon. files are relative to
972 the repo root.'''
973 the repo root.'''
973 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
974 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
974 rejected = []
975 rejected = []
975
976
976 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
977 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
977
978
978 if repo.ui.verbose:
979 if repo.ui.verbose:
979 unknownset = set(unknown + forgotten)
980 unknownset = set(unknown + forgotten)
980 toprint = unknownset.copy()
981 toprint = unknownset.copy()
981 toprint.update(deleted)
982 toprint.update(deleted)
982 for abs in sorted(toprint):
983 for abs in sorted(toprint):
983 if abs in unknownset:
984 if abs in unknownset:
984 status = _('adding %s\n') % abs
985 status = _('adding %s\n') % abs
985 else:
986 else:
986 status = _('removing %s\n') % abs
987 status = _('removing %s\n') % abs
987 repo.ui.status(status)
988 repo.ui.status(status)
988
989
989 renames = _findrenames(repo, m, added + unknown, removed + deleted,
990 renames = _findrenames(repo, m, added + unknown, removed + deleted,
990 similarity)
991 similarity)
991
992
992 _markchanges(repo, unknown + forgotten, deleted, renames)
993 _markchanges(repo, unknown + forgotten, deleted, renames)
993
994
994 for f in rejected:
995 for f in rejected:
995 if f in m.files():
996 if f in m.files():
996 return 1
997 return 1
997 return 0
998 return 0
998
999
999 def _interestingfiles(repo, matcher):
1000 def _interestingfiles(repo, matcher):
1000 '''Walk dirstate with matcher, looking for files that addremove would care
1001 '''Walk dirstate with matcher, looking for files that addremove would care
1001 about.
1002 about.
1002
1003
1003 This is different from dirstate.status because it doesn't care about
1004 This is different from dirstate.status because it doesn't care about
1004 whether files are modified or clean.'''
1005 whether files are modified or clean.'''
1005 added, unknown, deleted, removed, forgotten = [], [], [], [], []
1006 added, unknown, deleted, removed, forgotten = [], [], [], [], []
1006 audit_path = pathutil.pathauditor(repo.root, cached=True)
1007 audit_path = pathutil.pathauditor(repo.root, cached=True)
1007
1008
1008 ctx = repo[None]
1009 ctx = repo[None]
1009 dirstate = repo.dirstate
1010 dirstate = repo.dirstate
1010 walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
1011 walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
1011 unknown=True, ignored=False, full=False)
1012 unknown=True, ignored=False, full=False)
1012 for abs, st in walkresults.iteritems():
1013 for abs, st in walkresults.iteritems():
1013 dstate = dirstate[abs]
1014 dstate = dirstate[abs]
1014 if dstate == '?' and audit_path.check(abs):
1015 if dstate == '?' and audit_path.check(abs):
1015 unknown.append(abs)
1016 unknown.append(abs)
1016 elif dstate != 'r' and not st:
1017 elif dstate != 'r' and not st:
1017 deleted.append(abs)
1018 deleted.append(abs)
1018 elif dstate == 'r' and st:
1019 elif dstate == 'r' and st:
1019 forgotten.append(abs)
1020 forgotten.append(abs)
1020 # for finding renames
1021 # for finding renames
1021 elif dstate == 'r' and not st:
1022 elif dstate == 'r' and not st:
1022 removed.append(abs)
1023 removed.append(abs)
1023 elif dstate == 'a':
1024 elif dstate == 'a':
1024 added.append(abs)
1025 added.append(abs)
1025
1026
1026 return added, unknown, deleted, removed, forgotten
1027 return added, unknown, deleted, removed, forgotten
1027
1028
1028 def _findrenames(repo, matcher, added, removed, similarity):
1029 def _findrenames(repo, matcher, added, removed, similarity):
1029 '''Find renames from removed files to added ones.'''
1030 '''Find renames from removed files to added ones.'''
1030 renames = {}
1031 renames = {}
1031 if similarity > 0:
1032 if similarity > 0:
1032 for old, new, score in similar.findrenames(repo, added, removed,
1033 for old, new, score in similar.findrenames(repo, added, removed,
1033 similarity):
1034 similarity):
1034 if (repo.ui.verbose or not matcher.exact(old)
1035 if (repo.ui.verbose or not matcher.exact(old)
1035 or not matcher.exact(new)):
1036 or not matcher.exact(new)):
1036 repo.ui.status(_('recording removal of %s as rename to %s '
1037 repo.ui.status(_('recording removal of %s as rename to %s '
1037 '(%d%% similar)\n') %
1038 '(%d%% similar)\n') %
1038 (matcher.rel(old), matcher.rel(new),
1039 (matcher.rel(old), matcher.rel(new),
1039 score * 100))
1040 score * 100))
1040 renames[new] = old
1041 renames[new] = old
1041 return renames
1042 return renames
1042
1043
1043 def _markchanges(repo, unknown, deleted, renames):
1044 def _markchanges(repo, unknown, deleted, renames):
1044 '''Marks the files in unknown as added, the files in deleted as removed,
1045 '''Marks the files in unknown as added, the files in deleted as removed,
1045 and the files in renames as copied.'''
1046 and the files in renames as copied.'''
1046 wctx = repo[None]
1047 wctx = repo[None]
1047 with repo.wlock():
1048 with repo.wlock():
1048 wctx.forget(deleted)
1049 wctx.forget(deleted)
1049 wctx.add(unknown)
1050 wctx.add(unknown)
1050 for new, old in renames.iteritems():
1051 for new, old in renames.iteritems():
1051 wctx.copy(old, new)
1052 wctx.copy(old, new)
1052
1053
1053 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
1054 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
1054 """Update the dirstate to reflect the intent of copying src to dst. For
1055 """Update the dirstate to reflect the intent of copying src to dst. For
1055 different reasons it might not end with dst being marked as copied from src.
1056 different reasons it might not end with dst being marked as copied from src.
1056 """
1057 """
1057 origsrc = repo.dirstate.copied(src) or src
1058 origsrc = repo.dirstate.copied(src) or src
1058 if dst == origsrc: # copying back a copy?
1059 if dst == origsrc: # copying back a copy?
1059 if repo.dirstate[dst] not in 'mn' and not dryrun:
1060 if repo.dirstate[dst] not in 'mn' and not dryrun:
1060 repo.dirstate.normallookup(dst)
1061 repo.dirstate.normallookup(dst)
1061 else:
1062 else:
1062 if repo.dirstate[origsrc] == 'a' and origsrc == src:
1063 if repo.dirstate[origsrc] == 'a' and origsrc == src:
1063 if not ui.quiet:
1064 if not ui.quiet:
1064 ui.warn(_("%s has not been committed yet, so no copy "
1065 ui.warn(_("%s has not been committed yet, so no copy "
1065 "data will be stored for %s.\n")
1066 "data will be stored for %s.\n")
1066 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
1067 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
1067 if repo.dirstate[dst] in '?r' and not dryrun:
1068 if repo.dirstate[dst] in '?r' and not dryrun:
1068 wctx.add([dst])
1069 wctx.add([dst])
1069 elif not dryrun:
1070 elif not dryrun:
1070 wctx.copy(origsrc, dst)
1071 wctx.copy(origsrc, dst)
1071
1072
1072 def readrequires(opener, supported):
1073 def readrequires(opener, supported):
1073 '''Reads and parses .hg/requires and checks if all entries found
1074 '''Reads and parses .hg/requires and checks if all entries found
1074 are in the list of supported features.'''
1075 are in the list of supported features.'''
1075 requirements = set(opener.read("requires").splitlines())
1076 requirements = set(opener.read("requires").splitlines())
1076 missings = []
1077 missings = []
1077 for r in requirements:
1078 for r in requirements:
1078 if r not in supported:
1079 if r not in supported:
1079 if not r or not r[0:1].isalnum():
1080 if not r or not r[0:1].isalnum():
1080 raise error.RequirementError(_(".hg/requires file is corrupt"))
1081 raise error.RequirementError(_(".hg/requires file is corrupt"))
1081 missings.append(r)
1082 missings.append(r)
1082 missings.sort()
1083 missings.sort()
1083 if missings:
1084 if missings:
1084 raise error.RequirementError(
1085 raise error.RequirementError(
1085 _("repository requires features unknown to this Mercurial: %s")
1086 _("repository requires features unknown to this Mercurial: %s")
1086 % " ".join(missings),
1087 % " ".join(missings),
1087 hint=_("see https://mercurial-scm.org/wiki/MissingRequirement"
1088 hint=_("see https://mercurial-scm.org/wiki/MissingRequirement"
1088 " for more information"))
1089 " for more information"))
1089 return requirements
1090 return requirements
1090
1091
1091 def writerequires(opener, requirements):
1092 def writerequires(opener, requirements):
1092 with opener('requires', 'w') as fp:
1093 with opener('requires', 'w') as fp:
1093 for r in sorted(requirements):
1094 for r in sorted(requirements):
1094 fp.write("%s\n" % r)
1095 fp.write("%s\n" % r)
1095
1096
1096 class filecachesubentry(object):
1097 class filecachesubentry(object):
1097 def __init__(self, path, stat):
1098 def __init__(self, path, stat):
1098 self.path = path
1099 self.path = path
1099 self.cachestat = None
1100 self.cachestat = None
1100 self._cacheable = None
1101 self._cacheable = None
1101
1102
1102 if stat:
1103 if stat:
1103 self.cachestat = filecachesubentry.stat(self.path)
1104 self.cachestat = filecachesubentry.stat(self.path)
1104
1105
1105 if self.cachestat:
1106 if self.cachestat:
1106 self._cacheable = self.cachestat.cacheable()
1107 self._cacheable = self.cachestat.cacheable()
1107 else:
1108 else:
1108 # None means we don't know yet
1109 # None means we don't know yet
1109 self._cacheable = None
1110 self._cacheable = None
1110
1111
1111 def refresh(self):
1112 def refresh(self):
1112 if self.cacheable():
1113 if self.cacheable():
1113 self.cachestat = filecachesubentry.stat(self.path)
1114 self.cachestat = filecachesubentry.stat(self.path)
1114
1115
1115 def cacheable(self):
1116 def cacheable(self):
1116 if self._cacheable is not None:
1117 if self._cacheable is not None:
1117 return self._cacheable
1118 return self._cacheable
1118
1119
1119 # we don't know yet, assume it is for now
1120 # we don't know yet, assume it is for now
1120 return True
1121 return True
1121
1122
1122 def changed(self):
1123 def changed(self):
1123 # no point in going further if we can't cache it
1124 # no point in going further if we can't cache it
1124 if not self.cacheable():
1125 if not self.cacheable():
1125 return True
1126 return True
1126
1127
1127 newstat = filecachesubentry.stat(self.path)
1128 newstat = filecachesubentry.stat(self.path)
1128
1129
1129 # we may not know if it's cacheable yet, check again now
1130 # we may not know if it's cacheable yet, check again now
1130 if newstat and self._cacheable is None:
1131 if newstat and self._cacheable is None:
1131 self._cacheable = newstat.cacheable()
1132 self._cacheable = newstat.cacheable()
1132
1133
1133 # check again
1134 # check again
1134 if not self._cacheable:
1135 if not self._cacheable:
1135 return True
1136 return True
1136
1137
1137 if self.cachestat != newstat:
1138 if self.cachestat != newstat:
1138 self.cachestat = newstat
1139 self.cachestat = newstat
1139 return True
1140 return True
1140 else:
1141 else:
1141 return False
1142 return False
1142
1143
1143 @staticmethod
1144 @staticmethod
1144 def stat(path):
1145 def stat(path):
1145 try:
1146 try:
1146 return util.cachestat(path)
1147 return util.cachestat(path)
1147 except OSError as e:
1148 except OSError as e:
1148 if e.errno != errno.ENOENT:
1149 if e.errno != errno.ENOENT:
1149 raise
1150 raise
1150
1151
1151 class filecacheentry(object):
1152 class filecacheentry(object):
1152 def __init__(self, paths, stat=True):
1153 def __init__(self, paths, stat=True):
1153 self._entries = []
1154 self._entries = []
1154 for path in paths:
1155 for path in paths:
1155 self._entries.append(filecachesubentry(path, stat))
1156 self._entries.append(filecachesubentry(path, stat))
1156
1157
1157 def changed(self):
1158 def changed(self):
1158 '''true if any entry has changed'''
1159 '''true if any entry has changed'''
1159 for entry in self._entries:
1160 for entry in self._entries:
1160 if entry.changed():
1161 if entry.changed():
1161 return True
1162 return True
1162 return False
1163 return False
1163
1164
1164 def refresh(self):
1165 def refresh(self):
1165 for entry in self._entries:
1166 for entry in self._entries:
1166 entry.refresh()
1167 entry.refresh()
1167
1168
1168 class filecache(object):
1169 class filecache(object):
1169 """A property like decorator that tracks files under .hg/ for updates.
1170 """A property like decorator that tracks files under .hg/ for updates.
1170
1171
1171 On first access, the files defined as arguments are stat()ed and the
1172 On first access, the files defined as arguments are stat()ed and the
1172 results cached. The decorated function is called. The results are stashed
1173 results cached. The decorated function is called. The results are stashed
1173 away in a ``_filecache`` dict on the object whose method is decorated.
1174 away in a ``_filecache`` dict on the object whose method is decorated.
1174
1175
1175 On subsequent access, the cached result is returned.
1176 On subsequent access, the cached result is returned.
1176
1177
1177 On external property set operations, stat() calls are performed and the new
1178 On external property set operations, stat() calls are performed and the new
1178 value is cached.
1179 value is cached.
1179
1180
1180 On property delete operations, cached data is removed.
1181 On property delete operations, cached data is removed.
1181
1182
1182 When using the property API, cached data is always returned, if available:
1183 When using the property API, cached data is always returned, if available:
1183 no stat() is performed to check if the file has changed and if the function
1184 no stat() is performed to check if the file has changed and if the function
1184 needs to be called to reflect file changes.
1185 needs to be called to reflect file changes.
1185
1186
1186 Others can muck about with the state of the ``_filecache`` dict. e.g. they
1187 Others can muck about with the state of the ``_filecache`` dict. e.g. they
1187 can populate an entry before the property's getter is called. In this case,
1188 can populate an entry before the property's getter is called. In this case,
1188 entries in ``_filecache`` will be used during property operations,
1189 entries in ``_filecache`` will be used during property operations,
1189 if available. If the underlying file changes, it is up to external callers
1190 if available. If the underlying file changes, it is up to external callers
1190 to reflect this by e.g. calling ``delattr(obj, attr)`` to remove the cached
1191 to reflect this by e.g. calling ``delattr(obj, attr)`` to remove the cached
1191 method result as well as possibly calling ``del obj._filecache[attr]`` to
1192 method result as well as possibly calling ``del obj._filecache[attr]`` to
1192 remove the ``filecacheentry``.
1193 remove the ``filecacheentry``.
1193 """
1194 """
1194
1195
1195 def __init__(self, *paths):
1196 def __init__(self, *paths):
1196 self.paths = paths
1197 self.paths = paths
1197
1198
1198 def join(self, obj, fname):
1199 def join(self, obj, fname):
1199 """Used to compute the runtime path of a cached file.
1200 """Used to compute the runtime path of a cached file.
1200
1201
1201 Users should subclass filecache and provide their own version of this
1202 Users should subclass filecache and provide their own version of this
1202 function to call the appropriate join function on 'obj' (an instance
1203 function to call the appropriate join function on 'obj' (an instance
1203 of the class that its member function was decorated).
1204 of the class that its member function was decorated).
1204 """
1205 """
1205 raise NotImplementedError
1206 raise NotImplementedError
1206
1207
1207 def __call__(self, func):
1208 def __call__(self, func):
1208 self.func = func
1209 self.func = func
1209 self.sname = func.__name__
1210 self.sname = func.__name__
1210 self.name = pycompat.sysbytes(self.sname)
1211 self.name = pycompat.sysbytes(self.sname)
1211 return self
1212 return self
1212
1213
1213 def __get__(self, obj, type=None):
1214 def __get__(self, obj, type=None):
1214 # if accessed on the class, return the descriptor itself.
1215 # if accessed on the class, return the descriptor itself.
1215 if obj is None:
1216 if obj is None:
1216 return self
1217 return self
1217 # do we need to check if the file changed?
1218 # do we need to check if the file changed?
1218 if self.sname in obj.__dict__:
1219 if self.sname in obj.__dict__:
1219 assert self.name in obj._filecache, self.name
1220 assert self.name in obj._filecache, self.name
1220 return obj.__dict__[self.sname]
1221 return obj.__dict__[self.sname]
1221
1222
1222 entry = obj._filecache.get(self.name)
1223 entry = obj._filecache.get(self.name)
1223
1224
1224 if entry:
1225 if entry:
1225 if entry.changed():
1226 if entry.changed():
1226 entry.obj = self.func(obj)
1227 entry.obj = self.func(obj)
1227 else:
1228 else:
1228 paths = [self.join(obj, path) for path in self.paths]
1229 paths = [self.join(obj, path) for path in self.paths]
1229
1230
1230 # We stat -before- creating the object so our cache doesn't lie if
1231 # We stat -before- creating the object so our cache doesn't lie if
1231 # a writer modified between the time we read and stat
1232 # a writer modified between the time we read and stat
1232 entry = filecacheentry(paths, True)
1233 entry = filecacheentry(paths, True)
1233 entry.obj = self.func(obj)
1234 entry.obj = self.func(obj)
1234
1235
1235 obj._filecache[self.name] = entry
1236 obj._filecache[self.name] = entry
1236
1237
1237 obj.__dict__[self.sname] = entry.obj
1238 obj.__dict__[self.sname] = entry.obj
1238 return entry.obj
1239 return entry.obj
1239
1240
1240 def __set__(self, obj, value):
1241 def __set__(self, obj, value):
1241 if self.name not in obj._filecache:
1242 if self.name not in obj._filecache:
1242 # we add an entry for the missing value because X in __dict__
1243 # we add an entry for the missing value because X in __dict__
1243 # implies X in _filecache
1244 # implies X in _filecache
1244 paths = [self.join(obj, path) for path in self.paths]
1245 paths = [self.join(obj, path) for path in self.paths]
1245 ce = filecacheentry(paths, False)
1246 ce = filecacheentry(paths, False)
1246 obj._filecache[self.name] = ce
1247 obj._filecache[self.name] = ce
1247 else:
1248 else:
1248 ce = obj._filecache[self.name]
1249 ce = obj._filecache[self.name]
1249
1250
1250 ce.obj = value # update cached copy
1251 ce.obj = value # update cached copy
1251 obj.__dict__[self.sname] = value # update copy returned by obj.x
1252 obj.__dict__[self.sname] = value # update copy returned by obj.x
1252
1253
1253 def __delete__(self, obj):
1254 def __delete__(self, obj):
1254 try:
1255 try:
1255 del obj.__dict__[self.sname]
1256 del obj.__dict__[self.sname]
1256 except KeyError:
1257 except KeyError:
1257 raise AttributeError(self.sname)
1258 raise AttributeError(self.sname)
1258
1259
1259 def extdatasource(repo, source):
1260 def extdatasource(repo, source):
1260 """Gather a map of rev -> value dict from the specified source
1261 """Gather a map of rev -> value dict from the specified source
1261
1262
1262 A source spec is treated as a URL, with a special case shell: type
1263 A source spec is treated as a URL, with a special case shell: type
1263 for parsing the output from a shell command.
1264 for parsing the output from a shell command.
1264
1265
1265 The data is parsed as a series of newline-separated records where
1266 The data is parsed as a series of newline-separated records where
1266 each record is a revision specifier optionally followed by a space
1267 each record is a revision specifier optionally followed by a space
1267 and a freeform string value. If the revision is known locally, it
1268 and a freeform string value. If the revision is known locally, it
1268 is converted to a rev, otherwise the record is skipped.
1269 is converted to a rev, otherwise the record is skipped.
1269
1270
1270 Note that both key and value are treated as UTF-8 and converted to
1271 Note that both key and value are treated as UTF-8 and converted to
1271 the local encoding. This allows uniformity between local and
1272 the local encoding. This allows uniformity between local and
1272 remote data sources.
1273 remote data sources.
1273 """
1274 """
1274
1275
1275 spec = repo.ui.config("extdata", source)
1276 spec = repo.ui.config("extdata", source)
1276 if not spec:
1277 if not spec:
1277 raise error.Abort(_("unknown extdata source '%s'") % source)
1278 raise error.Abort(_("unknown extdata source '%s'") % source)
1278
1279
1279 data = {}
1280 data = {}
1280 src = proc = None
1281 src = proc = None
1281 try:
1282 try:
1282 if spec.startswith("shell:"):
1283 if spec.startswith("shell:"):
1283 # external commands should be run relative to the repo root
1284 # external commands should be run relative to the repo root
1284 cmd = spec[6:]
1285 cmd = spec[6:]
1285 proc = subprocess.Popen(cmd, shell=True, bufsize=-1,
1286 proc = subprocess.Popen(cmd, shell=True, bufsize=-1,
1286 close_fds=procutil.closefds,
1287 close_fds=procutil.closefds,
1287 stdout=subprocess.PIPE, cwd=repo.root)
1288 stdout=subprocess.PIPE, cwd=repo.root)
1288 src = proc.stdout
1289 src = proc.stdout
1289 else:
1290 else:
1290 # treat as a URL or file
1291 # treat as a URL or file
1291 src = url.open(repo.ui, spec)
1292 src = url.open(repo.ui, spec)
1292 for l in src:
1293 for l in src:
1293 if " " in l:
1294 if " " in l:
1294 k, v = l.strip().split(" ", 1)
1295 k, v = l.strip().split(" ", 1)
1295 else:
1296 else:
1296 k, v = l.strip(), ""
1297 k, v = l.strip(), ""
1297
1298
1298 k = encoding.tolocal(k)
1299 k = encoding.tolocal(k)
1299 try:
1300 try:
1300 data[revsingle(repo, k).rev()] = encoding.tolocal(v)
1301 data[revsingle(repo, k).rev()] = encoding.tolocal(v)
1301 except (error.LookupError, error.RepoLookupError):
1302 except (error.LookupError, error.RepoLookupError):
1302 pass # we ignore data for nodes that don't exist locally
1303 pass # we ignore data for nodes that don't exist locally
1303 finally:
1304 finally:
1304 if proc:
1305 if proc:
1305 proc.communicate()
1306 proc.communicate()
1306 if src:
1307 if src:
1307 src.close()
1308 src.close()
1308 if proc and proc.returncode != 0:
1309 if proc and proc.returncode != 0:
1309 raise error.Abort(_("extdata command '%s' failed: %s")
1310 raise error.Abort(_("extdata command '%s' failed: %s")
1310 % (cmd, procutil.explainexit(proc.returncode)))
1311 % (cmd, procutil.explainexit(proc.returncode)))
1311
1312
1312 return data
1313 return data
1313
1314
1314 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
1315 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
1315 if lock is None:
1316 if lock is None:
1316 raise error.LockInheritanceContractViolation(
1317 raise error.LockInheritanceContractViolation(
1317 'lock can only be inherited while held')
1318 'lock can only be inherited while held')
1318 if environ is None:
1319 if environ is None:
1319 environ = {}
1320 environ = {}
1320 with lock.inherit() as locker:
1321 with lock.inherit() as locker:
1321 environ[envvar] = locker
1322 environ[envvar] = locker
1322 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
1323 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
1323
1324
1324 def wlocksub(repo, cmd, *args, **kwargs):
1325 def wlocksub(repo, cmd, *args, **kwargs):
1325 """run cmd as a subprocess that allows inheriting repo's wlock
1326 """run cmd as a subprocess that allows inheriting repo's wlock
1326
1327
1327 This can only be called while the wlock is held. This takes all the
1328 This can only be called while the wlock is held. This takes all the
1328 arguments that ui.system does, and returns the exit code of the
1329 arguments that ui.system does, and returns the exit code of the
1329 subprocess."""
1330 subprocess."""
1330 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
1331 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
1331 **kwargs)
1332 **kwargs)
1332
1333
1333 class progress(object):
1334 class progress(object):
1334 def __init__(self, ui, topic, unit="", total=None):
1335 def __init__(self, ui, topic, unit="", total=None):
1335 self.ui = ui
1336 self.ui = ui
1336 self.pos = 0
1337 self.pos = 0
1337 self.topic = topic
1338 self.topic = topic
1338 self.unit = unit
1339 self.unit = unit
1339 self.total = total
1340 self.total = total
1340
1341
1341 def __enter__(self):
1342 def __enter__(self):
1342 return self
1343 return self
1343
1344
1344 def __exit__(self, exc_type, exc_value, exc_tb):
1345 def __exit__(self, exc_type, exc_value, exc_tb):
1345 self.complete()
1346 self.complete()
1346
1347
1347 def update(self, pos, item="", total=None):
1348 def update(self, pos, item="", total=None):
1348 assert pos is not None
1349 assert pos is not None
1349 if total:
1350 if total:
1350 self.total = total
1351 self.total = total
1351 self.pos = pos
1352 self.pos = pos
1352 self._print(item)
1353 self._print(item)
1353
1354
1354 def increment(self, step=1, item="", total=None):
1355 def increment(self, step=1, item="", total=None):
1355 self.update(self.pos + step, item, total)
1356 self.update(self.pos + step, item, total)
1356
1357
1357 def complete(self):
1358 def complete(self):
1358 self.ui.progress(self.topic, None)
1359 self.ui.progress(self.topic, None)
1359
1360
1360 def _print(self, item):
1361 def _print(self, item):
1361 self.ui.progress(self.topic, self.pos, item, self.unit,
1362 self.ui.progress(self.topic, self.pos, item, self.unit,
1362 self.total)
1363 self.total)
1363
1364
1364 def gdinitconfig(ui):
1365 def gdinitconfig(ui):
1365 """helper function to know if a repo should be created as general delta
1366 """helper function to know if a repo should be created as general delta
1366 """
1367 """
1367 # experimental config: format.generaldelta
1368 # experimental config: format.generaldelta
1368 return (ui.configbool('format', 'generaldelta')
1369 return (ui.configbool('format', 'generaldelta')
1369 or ui.configbool('format', 'usegeneraldelta')
1370 or ui.configbool('format', 'usegeneraldelta')
1370 or ui.configbool('format', 'sparse-revlog'))
1371 or ui.configbool('format', 'sparse-revlog'))
1371
1372
1372 def gddeltaconfig(ui):
1373 def gddeltaconfig(ui):
1373 """helper function to know if incoming delta should be optimised
1374 """helper function to know if incoming delta should be optimised
1374 """
1375 """
1375 # experimental config: format.generaldelta
1376 # experimental config: format.generaldelta
1376 return ui.configbool('format', 'generaldelta')
1377 return ui.configbool('format', 'generaldelta')
1377
1378
1378 class simplekeyvaluefile(object):
1379 class simplekeyvaluefile(object):
1379 """A simple file with key=value lines
1380 """A simple file with key=value lines
1380
1381
1381 Keys must be alphanumerics and start with a letter, values must not
1382 Keys must be alphanumerics and start with a letter, values must not
1382 contain '\n' characters"""
1383 contain '\n' characters"""
1383 firstlinekey = '__firstline'
1384 firstlinekey = '__firstline'
1384
1385
1385 def __init__(self, vfs, path, keys=None):
1386 def __init__(self, vfs, path, keys=None):
1386 self.vfs = vfs
1387 self.vfs = vfs
1387 self.path = path
1388 self.path = path
1388
1389
1389 def read(self, firstlinenonkeyval=False):
1390 def read(self, firstlinenonkeyval=False):
1390 """Read the contents of a simple key-value file
1391 """Read the contents of a simple key-value file
1391
1392
1392 'firstlinenonkeyval' indicates whether the first line of file should
1393 'firstlinenonkeyval' indicates whether the first line of file should
1393 be treated as a key-value pair or reuturned fully under the
1394 be treated as a key-value pair or reuturned fully under the
1394 __firstline key."""
1395 __firstline key."""
1395 lines = self.vfs.readlines(self.path)
1396 lines = self.vfs.readlines(self.path)
1396 d = {}
1397 d = {}
1397 if firstlinenonkeyval:
1398 if firstlinenonkeyval:
1398 if not lines:
1399 if not lines:
1399 e = _("empty simplekeyvalue file")
1400 e = _("empty simplekeyvalue file")
1400 raise error.CorruptedState(e)
1401 raise error.CorruptedState(e)
1401 # we don't want to include '\n' in the __firstline
1402 # we don't want to include '\n' in the __firstline
1402 d[self.firstlinekey] = lines[0][:-1]
1403 d[self.firstlinekey] = lines[0][:-1]
1403 del lines[0]
1404 del lines[0]
1404
1405
1405 try:
1406 try:
1406 # the 'if line.strip()' part prevents us from failing on empty
1407 # the 'if line.strip()' part prevents us from failing on empty
1407 # lines which only contain '\n' therefore are not skipped
1408 # lines which only contain '\n' therefore are not skipped
1408 # by 'if line'
1409 # by 'if line'
1409 updatedict = dict(line[:-1].split('=', 1) for line in lines
1410 updatedict = dict(line[:-1].split('=', 1) for line in lines
1410 if line.strip())
1411 if line.strip())
1411 if self.firstlinekey in updatedict:
1412 if self.firstlinekey in updatedict:
1412 e = _("%r can't be used as a key")
1413 e = _("%r can't be used as a key")
1413 raise error.CorruptedState(e % self.firstlinekey)
1414 raise error.CorruptedState(e % self.firstlinekey)
1414 d.update(updatedict)
1415 d.update(updatedict)
1415 except ValueError as e:
1416 except ValueError as e:
1416 raise error.CorruptedState(str(e))
1417 raise error.CorruptedState(str(e))
1417 return d
1418 return d
1418
1419
1419 def write(self, data, firstline=None):
1420 def write(self, data, firstline=None):
1420 """Write key=>value mapping to a file
1421 """Write key=>value mapping to a file
1421 data is a dict. Keys must be alphanumerical and start with a letter.
1422 data is a dict. Keys must be alphanumerical and start with a letter.
1422 Values must not contain newline characters.
1423 Values must not contain newline characters.
1423
1424
1424 If 'firstline' is not None, it is written to file before
1425 If 'firstline' is not None, it is written to file before
1425 everything else, as it is, not in a key=value form"""
1426 everything else, as it is, not in a key=value form"""
1426 lines = []
1427 lines = []
1427 if firstline is not None:
1428 if firstline is not None:
1428 lines.append('%s\n' % firstline)
1429 lines.append('%s\n' % firstline)
1429
1430
1430 for k, v in data.items():
1431 for k, v in data.items():
1431 if k == self.firstlinekey:
1432 if k == self.firstlinekey:
1432 e = "key name '%s' is reserved" % self.firstlinekey
1433 e = "key name '%s' is reserved" % self.firstlinekey
1433 raise error.ProgrammingError(e)
1434 raise error.ProgrammingError(e)
1434 if not k[0:1].isalpha():
1435 if not k[0:1].isalpha():
1435 e = "keys must start with a letter in a key-value file"
1436 e = "keys must start with a letter in a key-value file"
1436 raise error.ProgrammingError(e)
1437 raise error.ProgrammingError(e)
1437 if not k.isalnum():
1438 if not k.isalnum():
1438 e = "invalid key name in a simple key-value file"
1439 e = "invalid key name in a simple key-value file"
1439 raise error.ProgrammingError(e)
1440 raise error.ProgrammingError(e)
1440 if '\n' in v:
1441 if '\n' in v:
1441 e = "invalid value in a simple key-value file"
1442 e = "invalid value in a simple key-value file"
1442 raise error.ProgrammingError(e)
1443 raise error.ProgrammingError(e)
1443 lines.append("%s=%s\n" % (k, v))
1444 lines.append("%s=%s\n" % (k, v))
1444 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1445 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1445 fp.write(''.join(lines))
1446 fp.write(''.join(lines))
1446
1447
1447 _reportobsoletedsource = [
1448 _reportobsoletedsource = [
1448 'debugobsolete',
1449 'debugobsolete',
1449 'pull',
1450 'pull',
1450 'push',
1451 'push',
1451 'serve',
1452 'serve',
1452 'unbundle',
1453 'unbundle',
1453 ]
1454 ]
1454
1455
1455 _reportnewcssource = [
1456 _reportnewcssource = [
1456 'pull',
1457 'pull',
1457 'unbundle',
1458 'unbundle',
1458 ]
1459 ]
1459
1460
1460 def prefetchfiles(repo, revs, match):
1461 def prefetchfiles(repo, revs, match):
1461 """Invokes the registered file prefetch functions, allowing extensions to
1462 """Invokes the registered file prefetch functions, allowing extensions to
1462 ensure the corresponding files are available locally, before the command
1463 ensure the corresponding files are available locally, before the command
1463 uses them."""
1464 uses them."""
1464 if match:
1465 if match:
1465 # The command itself will complain about files that don't exist, so
1466 # The command itself will complain about files that don't exist, so
1466 # don't duplicate the message.
1467 # don't duplicate the message.
1467 match = matchmod.badmatch(match, lambda fn, msg: None)
1468 match = matchmod.badmatch(match, lambda fn, msg: None)
1468 else:
1469 else:
1469 match = matchall(repo)
1470 match = matchall(repo)
1470
1471
1471 fileprefetchhooks(repo, revs, match)
1472 fileprefetchhooks(repo, revs, match)
1472
1473
1473 # a list of (repo, revs, match) prefetch functions
1474 # a list of (repo, revs, match) prefetch functions
1474 fileprefetchhooks = util.hooks()
1475 fileprefetchhooks = util.hooks()
1475
1476
1476 # A marker that tells the evolve extension to suppress its own reporting
1477 # A marker that tells the evolve extension to suppress its own reporting
1477 _reportstroubledchangesets = True
1478 _reportstroubledchangesets = True
1478
1479
1479 def registersummarycallback(repo, otr, txnname=''):
1480 def registersummarycallback(repo, otr, txnname=''):
1480 """register a callback to issue a summary after the transaction is closed
1481 """register a callback to issue a summary after the transaction is closed
1481 """
1482 """
1482 def txmatch(sources):
1483 def txmatch(sources):
1483 return any(txnname.startswith(source) for source in sources)
1484 return any(txnname.startswith(source) for source in sources)
1484
1485
1485 categories = []
1486 categories = []
1486
1487
1487 def reportsummary(func):
1488 def reportsummary(func):
1488 """decorator for report callbacks."""
1489 """decorator for report callbacks."""
1489 # The repoview life cycle is shorter than the one of the actual
1490 # The repoview life cycle is shorter than the one of the actual
1490 # underlying repository. So the filtered object can die before the
1491 # underlying repository. So the filtered object can die before the
1491 # weakref is used leading to troubles. We keep a reference to the
1492 # weakref is used leading to troubles. We keep a reference to the
1492 # unfiltered object and restore the filtering when retrieving the
1493 # unfiltered object and restore the filtering when retrieving the
1493 # repository through the weakref.
1494 # repository through the weakref.
1494 filtername = repo.filtername
1495 filtername = repo.filtername
1495 reporef = weakref.ref(repo.unfiltered())
1496 reporef = weakref.ref(repo.unfiltered())
1496 def wrapped(tr):
1497 def wrapped(tr):
1497 repo = reporef()
1498 repo = reporef()
1498 if filtername:
1499 if filtername:
1499 repo = repo.filtered(filtername)
1500 repo = repo.filtered(filtername)
1500 func(repo, tr)
1501 func(repo, tr)
1501 newcat = '%02i-txnreport' % len(categories)
1502 newcat = '%02i-txnreport' % len(categories)
1502 otr.addpostclose(newcat, wrapped)
1503 otr.addpostclose(newcat, wrapped)
1503 categories.append(newcat)
1504 categories.append(newcat)
1504 return wrapped
1505 return wrapped
1505
1506
1506 if txmatch(_reportobsoletedsource):
1507 if txmatch(_reportobsoletedsource):
1507 @reportsummary
1508 @reportsummary
1508 def reportobsoleted(repo, tr):
1509 def reportobsoleted(repo, tr):
1509 obsoleted = obsutil.getobsoleted(repo, tr)
1510 obsoleted = obsutil.getobsoleted(repo, tr)
1510 if obsoleted:
1511 if obsoleted:
1511 repo.ui.status(_('obsoleted %i changesets\n')
1512 repo.ui.status(_('obsoleted %i changesets\n')
1512 % len(obsoleted))
1513 % len(obsoleted))
1513
1514
1514 if (obsolete.isenabled(repo, obsolete.createmarkersopt) and
1515 if (obsolete.isenabled(repo, obsolete.createmarkersopt) and
1515 repo.ui.configbool('experimental', 'evolution.report-instabilities')):
1516 repo.ui.configbool('experimental', 'evolution.report-instabilities')):
1516 instabilitytypes = [
1517 instabilitytypes = [
1517 ('orphan', 'orphan'),
1518 ('orphan', 'orphan'),
1518 ('phase-divergent', 'phasedivergent'),
1519 ('phase-divergent', 'phasedivergent'),
1519 ('content-divergent', 'contentdivergent'),
1520 ('content-divergent', 'contentdivergent'),
1520 ]
1521 ]
1521
1522
1522 def getinstabilitycounts(repo):
1523 def getinstabilitycounts(repo):
1523 filtered = repo.changelog.filteredrevs
1524 filtered = repo.changelog.filteredrevs
1524 counts = {}
1525 counts = {}
1525 for instability, revset in instabilitytypes:
1526 for instability, revset in instabilitytypes:
1526 counts[instability] = len(set(obsolete.getrevs(repo, revset)) -
1527 counts[instability] = len(set(obsolete.getrevs(repo, revset)) -
1527 filtered)
1528 filtered)
1528 return counts
1529 return counts
1529
1530
1530 oldinstabilitycounts = getinstabilitycounts(repo)
1531 oldinstabilitycounts = getinstabilitycounts(repo)
1531 @reportsummary
1532 @reportsummary
1532 def reportnewinstabilities(repo, tr):
1533 def reportnewinstabilities(repo, tr):
1533 newinstabilitycounts = getinstabilitycounts(repo)
1534 newinstabilitycounts = getinstabilitycounts(repo)
1534 for instability, revset in instabilitytypes:
1535 for instability, revset in instabilitytypes:
1535 delta = (newinstabilitycounts[instability] -
1536 delta = (newinstabilitycounts[instability] -
1536 oldinstabilitycounts[instability])
1537 oldinstabilitycounts[instability])
1537 msg = getinstabilitymessage(delta, instability)
1538 msg = getinstabilitymessage(delta, instability)
1538 if msg:
1539 if msg:
1539 repo.ui.warn(msg)
1540 repo.ui.warn(msg)
1540
1541
1541 if txmatch(_reportnewcssource):
1542 if txmatch(_reportnewcssource):
1542 @reportsummary
1543 @reportsummary
1543 def reportnewcs(repo, tr):
1544 def reportnewcs(repo, tr):
1544 """Report the range of new revisions pulled/unbundled."""
1545 """Report the range of new revisions pulled/unbundled."""
1545 newrevs = tr.changes.get('revs', pycompat.xrange(0, 0))
1546 newrevs = tr.changes.get('revs', pycompat.xrange(0, 0))
1546 if not newrevs:
1547 if not newrevs:
1547 return
1548 return
1548
1549
1549 # Compute the bounds of new revisions' range, excluding obsoletes.
1550 # Compute the bounds of new revisions' range, excluding obsoletes.
1550 unfi = repo.unfiltered()
1551 unfi = repo.unfiltered()
1551 revs = unfi.revs('%ld and not obsolete()', newrevs)
1552 revs = unfi.revs('%ld and not obsolete()', newrevs)
1552 if not revs:
1553 if not revs:
1553 # Got only obsoletes.
1554 # Got only obsoletes.
1554 return
1555 return
1555 minrev, maxrev = repo[revs.min()], repo[revs.max()]
1556 minrev, maxrev = repo[revs.min()], repo[revs.max()]
1556
1557
1557 if minrev == maxrev:
1558 if minrev == maxrev:
1558 revrange = minrev
1559 revrange = minrev
1559 else:
1560 else:
1560 revrange = '%s:%s' % (minrev, maxrev)
1561 revrange = '%s:%s' % (minrev, maxrev)
1561 repo.ui.status(_('new changesets %s\n') % revrange)
1562 repo.ui.status(_('new changesets %s\n') % revrange)
1562
1563
1563 @reportsummary
1564 @reportsummary
1564 def reportphasechanges(repo, tr):
1565 def reportphasechanges(repo, tr):
1565 """Report statistics of phase changes for changesets pre-existing
1566 """Report statistics of phase changes for changesets pre-existing
1566 pull/unbundle.
1567 pull/unbundle.
1567 """
1568 """
1568 newrevs = tr.changes.get('revs', pycompat.xrange(0, 0))
1569 newrevs = tr.changes.get('revs', pycompat.xrange(0, 0))
1569 phasetracking = tr.changes.get('phases', {})
1570 phasetracking = tr.changes.get('phases', {})
1570 if not phasetracking:
1571 if not phasetracking:
1571 return
1572 return
1572 published = [
1573 published = [
1573 rev for rev, (old, new) in phasetracking.iteritems()
1574 rev for rev, (old, new) in phasetracking.iteritems()
1574 if new == phases.public and rev not in newrevs
1575 if new == phases.public and rev not in newrevs
1575 ]
1576 ]
1576 if not published:
1577 if not published:
1577 return
1578 return
1578 repo.ui.status(_('%d local changesets published\n')
1579 repo.ui.status(_('%d local changesets published\n')
1579 % len(published))
1580 % len(published))
1580
1581
1581 def getinstabilitymessage(delta, instability):
1582 def getinstabilitymessage(delta, instability):
1582 """function to return the message to show warning about new instabilities
1583 """function to return the message to show warning about new instabilities
1583
1584
1584 exists as a separate function so that extension can wrap to show more
1585 exists as a separate function so that extension can wrap to show more
1585 information like how to fix instabilities"""
1586 information like how to fix instabilities"""
1586 if delta > 0:
1587 if delta > 0:
1587 return _('%i new %s changesets\n') % (delta, instability)
1588 return _('%i new %s changesets\n') % (delta, instability)
1588
1589
1589 def nodesummaries(repo, nodes, maxnumnodes=4):
1590 def nodesummaries(repo, nodes, maxnumnodes=4):
1590 if len(nodes) <= maxnumnodes or repo.ui.verbose:
1591 if len(nodes) <= maxnumnodes or repo.ui.verbose:
1591 return ' '.join(short(h) for h in nodes)
1592 return ' '.join(short(h) for h in nodes)
1592 first = ' '.join(short(h) for h in nodes[:maxnumnodes])
1593 first = ' '.join(short(h) for h in nodes[:maxnumnodes])
1593 return _("%s and %d others") % (first, len(nodes) - maxnumnodes)
1594 return _("%s and %d others") % (first, len(nodes) - maxnumnodes)
1594
1595
1595 def enforcesinglehead(repo, tr, desc):
1596 def enforcesinglehead(repo, tr, desc):
1596 """check that no named branch has multiple heads"""
1597 """check that no named branch has multiple heads"""
1597 if desc in ('strip', 'repair'):
1598 if desc in ('strip', 'repair'):
1598 # skip the logic during strip
1599 # skip the logic during strip
1599 return
1600 return
1600 visible = repo.filtered('visible')
1601 visible = repo.filtered('visible')
1601 # possible improvement: we could restrict the check to affected branch
1602 # possible improvement: we could restrict the check to affected branch
1602 for name, heads in visible.branchmap().iteritems():
1603 for name, heads in visible.branchmap().iteritems():
1603 if len(heads) > 1:
1604 if len(heads) > 1:
1604 msg = _('rejecting multiple heads on branch "%s"')
1605 msg = _('rejecting multiple heads on branch "%s"')
1605 msg %= name
1606 msg %= name
1606 hint = _('%d heads: %s')
1607 hint = _('%d heads: %s')
1607 hint %= (len(heads), nodesummaries(repo, heads))
1608 hint %= (len(heads), nodesummaries(repo, heads))
1608 raise error.Abort(msg, hint=hint)
1609 raise error.Abort(msg, hint=hint)
1609
1610
1610 def wrapconvertsink(sink):
1611 def wrapconvertsink(sink):
1611 """Allow extensions to wrap the sink returned by convcmd.convertsink()
1612 """Allow extensions to wrap the sink returned by convcmd.convertsink()
1612 before it is used, whether or not the convert extension was formally loaded.
1613 before it is used, whether or not the convert extension was formally loaded.
1613 """
1614 """
1614 return sink
1615 return sink
1615
1616
1616 def unhidehashlikerevs(repo, specs, hiddentype):
1617 def unhidehashlikerevs(repo, specs, hiddentype):
1617 """parse the user specs and unhide changesets whose hash or revision number
1618 """parse the user specs and unhide changesets whose hash or revision number
1618 is passed.
1619 is passed.
1619
1620
1620 hiddentype can be: 1) 'warn': warn while unhiding changesets
1621 hiddentype can be: 1) 'warn': warn while unhiding changesets
1621 2) 'nowarn': don't warn while unhiding changesets
1622 2) 'nowarn': don't warn while unhiding changesets
1622
1623
1623 returns a repo object with the required changesets unhidden
1624 returns a repo object with the required changesets unhidden
1624 """
1625 """
1625 if not repo.filtername or not repo.ui.configbool('experimental',
1626 if not repo.filtername or not repo.ui.configbool('experimental',
1626 'directaccess'):
1627 'directaccess'):
1627 return repo
1628 return repo
1628
1629
1629 if repo.filtername not in ('visible', 'visible-hidden'):
1630 if repo.filtername not in ('visible', 'visible-hidden'):
1630 return repo
1631 return repo
1631
1632
1632 symbols = set()
1633 symbols = set()
1633 for spec in specs:
1634 for spec in specs:
1634 try:
1635 try:
1635 tree = revsetlang.parse(spec)
1636 tree = revsetlang.parse(spec)
1636 except error.ParseError: # will be reported by scmutil.revrange()
1637 except error.ParseError: # will be reported by scmutil.revrange()
1637 continue
1638 continue
1638
1639
1639 symbols.update(revsetlang.gethashlikesymbols(tree))
1640 symbols.update(revsetlang.gethashlikesymbols(tree))
1640
1641
1641 if not symbols:
1642 if not symbols:
1642 return repo
1643 return repo
1643
1644
1644 revs = _getrevsfromsymbols(repo, symbols)
1645 revs = _getrevsfromsymbols(repo, symbols)
1645
1646
1646 if not revs:
1647 if not revs:
1647 return repo
1648 return repo
1648
1649
1649 if hiddentype == 'warn':
1650 if hiddentype == 'warn':
1650 unfi = repo.unfiltered()
1651 unfi = repo.unfiltered()
1651 revstr = ", ".join([pycompat.bytestr(unfi[l]) for l in revs])
1652 revstr = ", ".join([pycompat.bytestr(unfi[l]) for l in revs])
1652 repo.ui.warn(_("warning: accessing hidden changesets for write "
1653 repo.ui.warn(_("warning: accessing hidden changesets for write "
1653 "operation: %s\n") % revstr)
1654 "operation: %s\n") % revstr)
1654
1655
1655 # we have to use new filtername to separate branch/tags cache until we can
1656 # we have to use new filtername to separate branch/tags cache until we can
1656 # disbale these cache when revisions are dynamically pinned.
1657 # disbale these cache when revisions are dynamically pinned.
1657 return repo.filtered('visible-hidden', revs)
1658 return repo.filtered('visible-hidden', revs)
1658
1659
1659 def _getrevsfromsymbols(repo, symbols):
1660 def _getrevsfromsymbols(repo, symbols):
1660 """parse the list of symbols and returns a set of revision numbers of hidden
1661 """parse the list of symbols and returns a set of revision numbers of hidden
1661 changesets present in symbols"""
1662 changesets present in symbols"""
1662 revs = set()
1663 revs = set()
1663 unfi = repo.unfiltered()
1664 unfi = repo.unfiltered()
1664 unficl = unfi.changelog
1665 unficl = unfi.changelog
1665 cl = repo.changelog
1666 cl = repo.changelog
1666 tiprev = len(unficl)
1667 tiprev = len(unficl)
1667 allowrevnums = repo.ui.configbool('experimental', 'directaccess.revnums')
1668 allowrevnums = repo.ui.configbool('experimental', 'directaccess.revnums')
1668 for s in symbols:
1669 for s in symbols:
1669 try:
1670 try:
1670 n = int(s)
1671 n = int(s)
1671 if n <= tiprev:
1672 if n <= tiprev:
1672 if not allowrevnums:
1673 if not allowrevnums:
1673 continue
1674 continue
1674 else:
1675 else:
1675 if n not in cl:
1676 if n not in cl:
1676 revs.add(n)
1677 revs.add(n)
1677 continue
1678 continue
1678 except ValueError:
1679 except ValueError:
1679 pass
1680 pass
1680
1681
1681 try:
1682 try:
1682 s = resolvehexnodeidprefix(unfi, s)
1683 s = resolvehexnodeidprefix(unfi, s)
1683 except (error.LookupError, error.WdirUnsupported):
1684 except (error.LookupError, error.WdirUnsupported):
1684 s = None
1685 s = None
1685
1686
1686 if s is not None:
1687 if s is not None:
1687 rev = unficl.rev(s)
1688 rev = unficl.rev(s)
1688 if rev not in cl:
1689 if rev not in cl:
1689 revs.add(rev)
1690 revs.add(rev)
1690
1691
1691 return revs
1692 return revs
1692
1693
1693 def bookmarkrevs(repo, mark):
1694 def bookmarkrevs(repo, mark):
1694 """
1695 """
1695 Select revisions reachable by a given bookmark
1696 Select revisions reachable by a given bookmark
1696 """
1697 """
1697 return repo.revs("ancestors(bookmark(%s)) - "
1698 return repo.revs("ancestors(bookmark(%s)) - "
1698 "ancestors(head() and not bookmark(%s)) - "
1699 "ancestors(head() and not bookmark(%s)) - "
1699 "ancestors(bookmark() and not bookmark(%s))",
1700 "ancestors(bookmark() and not bookmark(%s))",
1700 mark, mark, mark)
1701 mark, mark, mark)
General Comments 0
You need to be logged in to leave comments. Login now