##// END OF EJS Templates
cleanup: pass in overwrite flag to hg.updaterepo() as named argument...
Yuya Nishihara -
r38527:39db5a01 default
parent child Browse files
Show More
@@ -1,1654 +1,1654 b''
1 # histedit.py - interactive history editing for mercurial
1 # histedit.py - interactive history editing for mercurial
2 #
2 #
3 # Copyright 2009 Augie Fackler <raf@durin42.com>
3 # Copyright 2009 Augie Fackler <raf@durin42.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 """interactive history editing
7 """interactive history editing
8
8
9 With this extension installed, Mercurial gains one new command: histedit. Usage
9 With this extension installed, Mercurial gains one new command: histedit. Usage
10 is as follows, assuming the following history::
10 is as follows, assuming the following history::
11
11
12 @ 3[tip] 7c2fd3b9020c 2009-04-27 18:04 -0500 durin42
12 @ 3[tip] 7c2fd3b9020c 2009-04-27 18:04 -0500 durin42
13 | Add delta
13 | Add delta
14 |
14 |
15 o 2 030b686bedc4 2009-04-27 18:04 -0500 durin42
15 o 2 030b686bedc4 2009-04-27 18:04 -0500 durin42
16 | Add gamma
16 | Add gamma
17 |
17 |
18 o 1 c561b4e977df 2009-04-27 18:04 -0500 durin42
18 o 1 c561b4e977df 2009-04-27 18:04 -0500 durin42
19 | Add beta
19 | Add beta
20 |
20 |
21 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
21 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
22 Add alpha
22 Add alpha
23
23
24 If you were to run ``hg histedit c561b4e977df``, you would see the following
24 If you were to run ``hg histedit c561b4e977df``, you would see the following
25 file open in your editor::
25 file open in your editor::
26
26
27 pick c561b4e977df Add beta
27 pick c561b4e977df Add beta
28 pick 030b686bedc4 Add gamma
28 pick 030b686bedc4 Add gamma
29 pick 7c2fd3b9020c Add delta
29 pick 7c2fd3b9020c Add delta
30
30
31 # Edit history between c561b4e977df and 7c2fd3b9020c
31 # Edit history between c561b4e977df and 7c2fd3b9020c
32 #
32 #
33 # Commits are listed from least to most recent
33 # Commits are listed from least to most recent
34 #
34 #
35 # Commands:
35 # Commands:
36 # p, pick = use commit
36 # p, pick = use commit
37 # e, edit = use commit, but stop for amending
37 # e, edit = use commit, but stop for amending
38 # f, fold = use commit, but combine it with the one above
38 # f, fold = use commit, but combine it with the one above
39 # r, roll = like fold, but discard this commit's description and date
39 # r, roll = like fold, but discard this commit's description and date
40 # d, drop = remove commit from history
40 # d, drop = remove commit from history
41 # m, mess = edit commit message without changing commit content
41 # m, mess = edit commit message without changing commit content
42 # b, base = checkout changeset and apply further changesets from there
42 # b, base = checkout changeset and apply further changesets from there
43 #
43 #
44
44
45 In this file, lines beginning with ``#`` are ignored. You must specify a rule
45 In this file, lines beginning with ``#`` are ignored. You must specify a rule
46 for each revision in your history. For example, if you had meant to add gamma
46 for each revision in your history. For example, if you had meant to add gamma
47 before beta, and then wanted to add delta in the same revision as beta, you
47 before beta, and then wanted to add delta in the same revision as beta, you
48 would reorganize the file to look like this::
48 would reorganize the file to look like this::
49
49
50 pick 030b686bedc4 Add gamma
50 pick 030b686bedc4 Add gamma
51 pick c561b4e977df Add beta
51 pick c561b4e977df Add beta
52 fold 7c2fd3b9020c Add delta
52 fold 7c2fd3b9020c Add delta
53
53
54 # Edit history between c561b4e977df and 7c2fd3b9020c
54 # Edit history between c561b4e977df and 7c2fd3b9020c
55 #
55 #
56 # Commits are listed from least to most recent
56 # Commits are listed from least to most recent
57 #
57 #
58 # Commands:
58 # Commands:
59 # p, pick = use commit
59 # p, pick = use commit
60 # e, edit = use commit, but stop for amending
60 # e, edit = use commit, but stop for amending
61 # f, fold = use commit, but combine it with the one above
61 # f, fold = use commit, but combine it with the one above
62 # r, roll = like fold, but discard this commit's description and date
62 # r, roll = like fold, but discard this commit's description and date
63 # d, drop = remove commit from history
63 # d, drop = remove commit from history
64 # m, mess = edit commit message without changing commit content
64 # m, mess = edit commit message without changing commit content
65 # b, base = checkout changeset and apply further changesets from there
65 # b, base = checkout changeset and apply further changesets from there
66 #
66 #
67
67
68 At which point you close the editor and ``histedit`` starts working. When you
68 At which point you close the editor and ``histedit`` starts working. When you
69 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
69 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
70 those revisions together, offering you a chance to clean up the commit message::
70 those revisions together, offering you a chance to clean up the commit message::
71
71
72 Add beta
72 Add beta
73 ***
73 ***
74 Add delta
74 Add delta
75
75
76 Edit the commit message to your liking, then close the editor. The date used
76 Edit the commit message to your liking, then close the editor. The date used
77 for the commit will be the later of the two commits' dates. For this example,
77 for the commit will be the later of the two commits' dates. For this example,
78 let's assume that the commit message was changed to ``Add beta and delta.``
78 let's assume that the commit message was changed to ``Add beta and delta.``
79 After histedit has run and had a chance to remove any old or temporary
79 After histedit has run and had a chance to remove any old or temporary
80 revisions it needed, the history looks like this::
80 revisions it needed, the history looks like this::
81
81
82 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
82 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
83 | Add beta and delta.
83 | Add beta and delta.
84 |
84 |
85 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
85 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
86 | Add gamma
86 | Add gamma
87 |
87 |
88 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
88 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
89 Add alpha
89 Add alpha
90
90
91 Note that ``histedit`` does *not* remove any revisions (even its own temporary
91 Note that ``histedit`` does *not* remove any revisions (even its own temporary
92 ones) until after it has completed all the editing operations, so it will
92 ones) until after it has completed all the editing operations, so it will
93 probably perform several strip operations when it's done. For the above example,
93 probably perform several strip operations when it's done. For the above example,
94 it had to run strip twice. Strip can be slow depending on a variety of factors,
94 it had to run strip twice. Strip can be slow depending on a variety of factors,
95 so you might need to be a little patient. You can choose to keep the original
95 so you might need to be a little patient. You can choose to keep the original
96 revisions by passing the ``--keep`` flag.
96 revisions by passing the ``--keep`` flag.
97
97
98 The ``edit`` operation will drop you back to a command prompt,
98 The ``edit`` operation will drop you back to a command prompt,
99 allowing you to edit files freely, or even use ``hg record`` to commit
99 allowing you to edit files freely, or even use ``hg record`` to commit
100 some changes as a separate commit. When you're done, any remaining
100 some changes as a separate commit. When you're done, any remaining
101 uncommitted changes will be committed as well. When done, run ``hg
101 uncommitted changes will be committed as well. When done, run ``hg
102 histedit --continue`` to finish this step. If there are uncommitted
102 histedit --continue`` to finish this step. If there are uncommitted
103 changes, you'll be prompted for a new commit message, but the default
103 changes, you'll be prompted for a new commit message, but the default
104 commit message will be the original message for the ``edit`` ed
104 commit message will be the original message for the ``edit`` ed
105 revision, and the date of the original commit will be preserved.
105 revision, and the date of the original commit will be preserved.
106
106
107 The ``message`` operation will give you a chance to revise a commit
107 The ``message`` operation will give you a chance to revise a commit
108 message without changing the contents. It's a shortcut for doing
108 message without changing the contents. It's a shortcut for doing
109 ``edit`` immediately followed by `hg histedit --continue``.
109 ``edit`` immediately followed by `hg histedit --continue``.
110
110
111 If ``histedit`` encounters a conflict when moving a revision (while
111 If ``histedit`` encounters a conflict when moving a revision (while
112 handling ``pick`` or ``fold``), it'll stop in a similar manner to
112 handling ``pick`` or ``fold``), it'll stop in a similar manner to
113 ``edit`` with the difference that it won't prompt you for a commit
113 ``edit`` with the difference that it won't prompt you for a commit
114 message when done. If you decide at this point that you don't like how
114 message when done. If you decide at this point that you don't like how
115 much work it will be to rearrange history, or that you made a mistake,
115 much work it will be to rearrange history, or that you made a mistake,
116 you can use ``hg histedit --abort`` to abandon the new changes you
116 you can use ``hg histedit --abort`` to abandon the new changes you
117 have made and return to the state before you attempted to edit your
117 have made and return to the state before you attempted to edit your
118 history.
118 history.
119
119
120 If we clone the histedit-ed example repository above and add four more
120 If we clone the histedit-ed example repository above and add four more
121 changes, such that we have the following history::
121 changes, such that we have the following history::
122
122
123 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
123 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
124 | Add theta
124 | Add theta
125 |
125 |
126 o 5 140988835471 2009-04-27 18:04 -0500 stefan
126 o 5 140988835471 2009-04-27 18:04 -0500 stefan
127 | Add eta
127 | Add eta
128 |
128 |
129 o 4 122930637314 2009-04-27 18:04 -0500 stefan
129 o 4 122930637314 2009-04-27 18:04 -0500 stefan
130 | Add zeta
130 | Add zeta
131 |
131 |
132 o 3 836302820282 2009-04-27 18:04 -0500 stefan
132 o 3 836302820282 2009-04-27 18:04 -0500 stefan
133 | Add epsilon
133 | Add epsilon
134 |
134 |
135 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
135 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
136 | Add beta and delta.
136 | Add beta and delta.
137 |
137 |
138 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
138 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
139 | Add gamma
139 | Add gamma
140 |
140 |
141 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
141 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
142 Add alpha
142 Add alpha
143
143
144 If you run ``hg histedit --outgoing`` on the clone then it is the same
144 If you run ``hg histedit --outgoing`` on the clone then it is the same
145 as running ``hg histedit 836302820282``. If you need plan to push to a
145 as running ``hg histedit 836302820282``. If you need plan to push to a
146 repository that Mercurial does not detect to be related to the source
146 repository that Mercurial does not detect to be related to the source
147 repo, you can add a ``--force`` option.
147 repo, you can add a ``--force`` option.
148
148
149 Config
149 Config
150 ------
150 ------
151
151
152 Histedit rule lines are truncated to 80 characters by default. You
152 Histedit rule lines are truncated to 80 characters by default. You
153 can customize this behavior by setting a different length in your
153 can customize this behavior by setting a different length in your
154 configuration file::
154 configuration file::
155
155
156 [histedit]
156 [histedit]
157 linelen = 120 # truncate rule lines at 120 characters
157 linelen = 120 # truncate rule lines at 120 characters
158
158
159 ``hg histedit`` attempts to automatically choose an appropriate base
159 ``hg histedit`` attempts to automatically choose an appropriate base
160 revision to use. To change which base revision is used, define a
160 revision to use. To change which base revision is used, define a
161 revset in your configuration file::
161 revset in your configuration file::
162
162
163 [histedit]
163 [histedit]
164 defaultrev = only(.) & draft()
164 defaultrev = only(.) & draft()
165
165
166 By default each edited revision needs to be present in histedit commands.
166 By default each edited revision needs to be present in histedit commands.
167 To remove revision you need to use ``drop`` operation. You can configure
167 To remove revision you need to use ``drop`` operation. You can configure
168 the drop to be implicit for missing commits by adding::
168 the drop to be implicit for missing commits by adding::
169
169
170 [histedit]
170 [histedit]
171 dropmissing = True
171 dropmissing = True
172
172
173 By default, histedit will close the transaction after each action. For
173 By default, histedit will close the transaction after each action. For
174 performance purposes, you can configure histedit to use a single transaction
174 performance purposes, you can configure histedit to use a single transaction
175 across the entire histedit. WARNING: This setting introduces a significant risk
175 across the entire histedit. WARNING: This setting introduces a significant risk
176 of losing the work you've done in a histedit if the histedit aborts
176 of losing the work you've done in a histedit if the histedit aborts
177 unexpectedly::
177 unexpectedly::
178
178
179 [histedit]
179 [histedit]
180 singletransaction = True
180 singletransaction = True
181
181
182 """
182 """
183
183
184 from __future__ import absolute_import
184 from __future__ import absolute_import
185
185
186 import os
186 import os
187
187
188 from mercurial.i18n import _
188 from mercurial.i18n import _
189 from mercurial import (
189 from mercurial import (
190 bundle2,
190 bundle2,
191 cmdutil,
191 cmdutil,
192 context,
192 context,
193 copies,
193 copies,
194 destutil,
194 destutil,
195 discovery,
195 discovery,
196 error,
196 error,
197 exchange,
197 exchange,
198 extensions,
198 extensions,
199 hg,
199 hg,
200 lock,
200 lock,
201 merge as mergemod,
201 merge as mergemod,
202 mergeutil,
202 mergeutil,
203 node,
203 node,
204 obsolete,
204 obsolete,
205 pycompat,
205 pycompat,
206 registrar,
206 registrar,
207 repair,
207 repair,
208 scmutil,
208 scmutil,
209 state as statemod,
209 state as statemod,
210 util,
210 util,
211 )
211 )
212 from mercurial.utils import (
212 from mercurial.utils import (
213 stringutil,
213 stringutil,
214 )
214 )
215
215
216 pickle = util.pickle
216 pickle = util.pickle
217 release = lock.release
217 release = lock.release
218 cmdtable = {}
218 cmdtable = {}
219 command = registrar.command(cmdtable)
219 command = registrar.command(cmdtable)
220
220
221 configtable = {}
221 configtable = {}
222 configitem = registrar.configitem(configtable)
222 configitem = registrar.configitem(configtable)
223 configitem('experimental', 'histedit.autoverb',
223 configitem('experimental', 'histedit.autoverb',
224 default=False,
224 default=False,
225 )
225 )
226 configitem('histedit', 'defaultrev',
226 configitem('histedit', 'defaultrev',
227 default=None,
227 default=None,
228 )
228 )
229 configitem('histedit', 'dropmissing',
229 configitem('histedit', 'dropmissing',
230 default=False,
230 default=False,
231 )
231 )
232 configitem('histedit', 'linelen',
232 configitem('histedit', 'linelen',
233 default=80,
233 default=80,
234 )
234 )
235 configitem('histedit', 'singletransaction',
235 configitem('histedit', 'singletransaction',
236 default=False,
236 default=False,
237 )
237 )
238
238
239 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
239 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
240 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
240 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
241 # be specifying the version(s) of Mercurial they are tested with, or
241 # be specifying the version(s) of Mercurial they are tested with, or
242 # leave the attribute unspecified.
242 # leave the attribute unspecified.
243 testedwith = 'ships-with-hg-core'
243 testedwith = 'ships-with-hg-core'
244
244
245 actiontable = {}
245 actiontable = {}
246 primaryactions = set()
246 primaryactions = set()
247 secondaryactions = set()
247 secondaryactions = set()
248 tertiaryactions = set()
248 tertiaryactions = set()
249 internalactions = set()
249 internalactions = set()
250
250
251 def geteditcomment(ui, first, last):
251 def geteditcomment(ui, first, last):
252 """ construct the editor comment
252 """ construct the editor comment
253 The comment includes::
253 The comment includes::
254 - an intro
254 - an intro
255 - sorted primary commands
255 - sorted primary commands
256 - sorted short commands
256 - sorted short commands
257 - sorted long commands
257 - sorted long commands
258 - additional hints
258 - additional hints
259
259
260 Commands are only included once.
260 Commands are only included once.
261 """
261 """
262 intro = _("""Edit history between %s and %s
262 intro = _("""Edit history between %s and %s
263
263
264 Commits are listed from least to most recent
264 Commits are listed from least to most recent
265
265
266 You can reorder changesets by reordering the lines
266 You can reorder changesets by reordering the lines
267
267
268 Commands:
268 Commands:
269 """)
269 """)
270 actions = []
270 actions = []
271 def addverb(v):
271 def addverb(v):
272 a = actiontable[v]
272 a = actiontable[v]
273 lines = a.message.split("\n")
273 lines = a.message.split("\n")
274 if len(a.verbs):
274 if len(a.verbs):
275 v = ', '.join(sorted(a.verbs, key=lambda v: len(v)))
275 v = ', '.join(sorted(a.verbs, key=lambda v: len(v)))
276 actions.append(" %s = %s" % (v, lines[0]))
276 actions.append(" %s = %s" % (v, lines[0]))
277 actions.extend([' %s' for l in lines[1:]])
277 actions.extend([' %s' for l in lines[1:]])
278
278
279 for v in (
279 for v in (
280 sorted(primaryactions) +
280 sorted(primaryactions) +
281 sorted(secondaryactions) +
281 sorted(secondaryactions) +
282 sorted(tertiaryactions)
282 sorted(tertiaryactions)
283 ):
283 ):
284 addverb(v)
284 addverb(v)
285 actions.append('')
285 actions.append('')
286
286
287 hints = []
287 hints = []
288 if ui.configbool('histedit', 'dropmissing'):
288 if ui.configbool('histedit', 'dropmissing'):
289 hints.append("Deleting a changeset from the list "
289 hints.append("Deleting a changeset from the list "
290 "will DISCARD it from the edited history!")
290 "will DISCARD it from the edited history!")
291
291
292 lines = (intro % (first, last)).split('\n') + actions + hints
292 lines = (intro % (first, last)).split('\n') + actions + hints
293
293
294 return ''.join(['# %s\n' % l if l else '#\n' for l in lines])
294 return ''.join(['# %s\n' % l if l else '#\n' for l in lines])
295
295
296 class histeditstate(object):
296 class histeditstate(object):
297 def __init__(self, repo, parentctxnode=None, actions=None, keep=None,
297 def __init__(self, repo, parentctxnode=None, actions=None, keep=None,
298 topmost=None, replacements=None, lock=None, wlock=None):
298 topmost=None, replacements=None, lock=None, wlock=None):
299 self.repo = repo
299 self.repo = repo
300 self.actions = actions
300 self.actions = actions
301 self.keep = keep
301 self.keep = keep
302 self.topmost = topmost
302 self.topmost = topmost
303 self.parentctxnode = parentctxnode
303 self.parentctxnode = parentctxnode
304 self.lock = lock
304 self.lock = lock
305 self.wlock = wlock
305 self.wlock = wlock
306 self.backupfile = None
306 self.backupfile = None
307 self.stateobj = statemod.cmdstate(repo, 'histedit-state')
307 self.stateobj = statemod.cmdstate(repo, 'histedit-state')
308 if replacements is None:
308 if replacements is None:
309 self.replacements = []
309 self.replacements = []
310 else:
310 else:
311 self.replacements = replacements
311 self.replacements = replacements
312
312
313 def read(self):
313 def read(self):
314 """Load histedit state from disk and set fields appropriately."""
314 """Load histedit state from disk and set fields appropriately."""
315 if not self.stateobj.exists():
315 if not self.stateobj.exists():
316 cmdutil.wrongtooltocontinue(self.repo, _('histedit'))
316 cmdutil.wrongtooltocontinue(self.repo, _('histedit'))
317
317
318 data = self._read()
318 data = self._read()
319
319
320 self.parentctxnode = data['parentctxnode']
320 self.parentctxnode = data['parentctxnode']
321 actions = parserules(data['rules'], self)
321 actions = parserules(data['rules'], self)
322 self.actions = actions
322 self.actions = actions
323 self.keep = data['keep']
323 self.keep = data['keep']
324 self.topmost = data['topmost']
324 self.topmost = data['topmost']
325 self.replacements = data['replacements']
325 self.replacements = data['replacements']
326 self.backupfile = data['backupfile']
326 self.backupfile = data['backupfile']
327
327
328 def _read(self):
328 def _read(self):
329 fp = self.repo.vfs.read('histedit-state')
329 fp = self.repo.vfs.read('histedit-state')
330 if fp.startswith('v1\n'):
330 if fp.startswith('v1\n'):
331 data = self._load()
331 data = self._load()
332 parentctxnode, rules, keep, topmost, replacements, backupfile = data
332 parentctxnode, rules, keep, topmost, replacements, backupfile = data
333 else:
333 else:
334 data = pickle.loads(fp)
334 data = pickle.loads(fp)
335 parentctxnode, rules, keep, topmost, replacements = data
335 parentctxnode, rules, keep, topmost, replacements = data
336 backupfile = None
336 backupfile = None
337 rules = "\n".join(["%s %s" % (verb, rest) for [verb, rest] in rules])
337 rules = "\n".join(["%s %s" % (verb, rest) for [verb, rest] in rules])
338
338
339 return {'parentctxnode': parentctxnode, "rules": rules, "keep": keep,
339 return {'parentctxnode': parentctxnode, "rules": rules, "keep": keep,
340 "topmost": topmost, "replacements": replacements,
340 "topmost": topmost, "replacements": replacements,
341 "backupfile": backupfile}
341 "backupfile": backupfile}
342
342
343 def write(self, tr=None):
343 def write(self, tr=None):
344 if tr:
344 if tr:
345 tr.addfilegenerator('histedit-state', ('histedit-state',),
345 tr.addfilegenerator('histedit-state', ('histedit-state',),
346 self._write, location='plain')
346 self._write, location='plain')
347 else:
347 else:
348 with self.repo.vfs("histedit-state", "w") as f:
348 with self.repo.vfs("histedit-state", "w") as f:
349 self._write(f)
349 self._write(f)
350
350
351 def _write(self, fp):
351 def _write(self, fp):
352 fp.write('v1\n')
352 fp.write('v1\n')
353 fp.write('%s\n' % node.hex(self.parentctxnode))
353 fp.write('%s\n' % node.hex(self.parentctxnode))
354 fp.write('%s\n' % node.hex(self.topmost))
354 fp.write('%s\n' % node.hex(self.topmost))
355 fp.write('%s\n' % ('True' if self.keep else 'False'))
355 fp.write('%s\n' % ('True' if self.keep else 'False'))
356 fp.write('%d\n' % len(self.actions))
356 fp.write('%d\n' % len(self.actions))
357 for action in self.actions:
357 for action in self.actions:
358 fp.write('%s\n' % action.tostate())
358 fp.write('%s\n' % action.tostate())
359 fp.write('%d\n' % len(self.replacements))
359 fp.write('%d\n' % len(self.replacements))
360 for replacement in self.replacements:
360 for replacement in self.replacements:
361 fp.write('%s%s\n' % (node.hex(replacement[0]), ''.join(node.hex(r)
361 fp.write('%s%s\n' % (node.hex(replacement[0]), ''.join(node.hex(r)
362 for r in replacement[1])))
362 for r in replacement[1])))
363 backupfile = self.backupfile
363 backupfile = self.backupfile
364 if not backupfile:
364 if not backupfile:
365 backupfile = ''
365 backupfile = ''
366 fp.write('%s\n' % backupfile)
366 fp.write('%s\n' % backupfile)
367
367
368 def _load(self):
368 def _load(self):
369 fp = self.repo.vfs('histedit-state', 'r')
369 fp = self.repo.vfs('histedit-state', 'r')
370 lines = [l[:-1] for l in fp.readlines()]
370 lines = [l[:-1] for l in fp.readlines()]
371
371
372 index = 0
372 index = 0
373 lines[index] # version number
373 lines[index] # version number
374 index += 1
374 index += 1
375
375
376 parentctxnode = node.bin(lines[index])
376 parentctxnode = node.bin(lines[index])
377 index += 1
377 index += 1
378
378
379 topmost = node.bin(lines[index])
379 topmost = node.bin(lines[index])
380 index += 1
380 index += 1
381
381
382 keep = lines[index] == 'True'
382 keep = lines[index] == 'True'
383 index += 1
383 index += 1
384
384
385 # Rules
385 # Rules
386 rules = []
386 rules = []
387 rulelen = int(lines[index])
387 rulelen = int(lines[index])
388 index += 1
388 index += 1
389 for i in xrange(rulelen):
389 for i in xrange(rulelen):
390 ruleaction = lines[index]
390 ruleaction = lines[index]
391 index += 1
391 index += 1
392 rule = lines[index]
392 rule = lines[index]
393 index += 1
393 index += 1
394 rules.append((ruleaction, rule))
394 rules.append((ruleaction, rule))
395
395
396 # Replacements
396 # Replacements
397 replacements = []
397 replacements = []
398 replacementlen = int(lines[index])
398 replacementlen = int(lines[index])
399 index += 1
399 index += 1
400 for i in xrange(replacementlen):
400 for i in xrange(replacementlen):
401 replacement = lines[index]
401 replacement = lines[index]
402 original = node.bin(replacement[:40])
402 original = node.bin(replacement[:40])
403 succ = [node.bin(replacement[i:i + 40]) for i in
403 succ = [node.bin(replacement[i:i + 40]) for i in
404 range(40, len(replacement), 40)]
404 range(40, len(replacement), 40)]
405 replacements.append((original, succ))
405 replacements.append((original, succ))
406 index += 1
406 index += 1
407
407
408 backupfile = lines[index]
408 backupfile = lines[index]
409 index += 1
409 index += 1
410
410
411 fp.close()
411 fp.close()
412
412
413 return parentctxnode, rules, keep, topmost, replacements, backupfile
413 return parentctxnode, rules, keep, topmost, replacements, backupfile
414
414
415 def clear(self):
415 def clear(self):
416 if self.inprogress():
416 if self.inprogress():
417 self.repo.vfs.unlink('histedit-state')
417 self.repo.vfs.unlink('histedit-state')
418
418
419 def inprogress(self):
419 def inprogress(self):
420 return self.repo.vfs.exists('histedit-state')
420 return self.repo.vfs.exists('histedit-state')
421
421
422
422
423 class histeditaction(object):
423 class histeditaction(object):
424 def __init__(self, state, node):
424 def __init__(self, state, node):
425 self.state = state
425 self.state = state
426 self.repo = state.repo
426 self.repo = state.repo
427 self.node = node
427 self.node = node
428
428
429 @classmethod
429 @classmethod
430 def fromrule(cls, state, rule):
430 def fromrule(cls, state, rule):
431 """Parses the given rule, returning an instance of the histeditaction.
431 """Parses the given rule, returning an instance of the histeditaction.
432 """
432 """
433 ruleid = rule.strip().split(' ', 1)[0]
433 ruleid = rule.strip().split(' ', 1)[0]
434 # ruleid can be anything from rev numbers, hashes, "bookmarks" etc
434 # ruleid can be anything from rev numbers, hashes, "bookmarks" etc
435 # Check for validation of rule ids and get the rulehash
435 # Check for validation of rule ids and get the rulehash
436 try:
436 try:
437 rev = node.bin(ruleid)
437 rev = node.bin(ruleid)
438 except TypeError:
438 except TypeError:
439 try:
439 try:
440 _ctx = scmutil.revsingle(state.repo, ruleid)
440 _ctx = scmutil.revsingle(state.repo, ruleid)
441 rulehash = _ctx.hex()
441 rulehash = _ctx.hex()
442 rev = node.bin(rulehash)
442 rev = node.bin(rulehash)
443 except error.RepoLookupError:
443 except error.RepoLookupError:
444 raise error.ParseError(_("invalid changeset %s") % ruleid)
444 raise error.ParseError(_("invalid changeset %s") % ruleid)
445 return cls(state, rev)
445 return cls(state, rev)
446
446
447 def verify(self, prev, expected, seen):
447 def verify(self, prev, expected, seen):
448 """ Verifies semantic correctness of the rule"""
448 """ Verifies semantic correctness of the rule"""
449 repo = self.repo
449 repo = self.repo
450 ha = node.hex(self.node)
450 ha = node.hex(self.node)
451 self.node = scmutil.resolvehexnodeidprefix(repo, ha)
451 self.node = scmutil.resolvehexnodeidprefix(repo, ha)
452 if self.node is None:
452 if self.node is None:
453 raise error.ParseError(_('unknown changeset %s listed') % ha[:12])
453 raise error.ParseError(_('unknown changeset %s listed') % ha[:12])
454 self._verifynodeconstraints(prev, expected, seen)
454 self._verifynodeconstraints(prev, expected, seen)
455
455
456 def _verifynodeconstraints(self, prev, expected, seen):
456 def _verifynodeconstraints(self, prev, expected, seen):
457 # by default command need a node in the edited list
457 # by default command need a node in the edited list
458 if self.node not in expected:
458 if self.node not in expected:
459 raise error.ParseError(_('%s "%s" changeset was not a candidate')
459 raise error.ParseError(_('%s "%s" changeset was not a candidate')
460 % (self.verb, node.short(self.node)),
460 % (self.verb, node.short(self.node)),
461 hint=_('only use listed changesets'))
461 hint=_('only use listed changesets'))
462 # and only one command per node
462 # and only one command per node
463 if self.node in seen:
463 if self.node in seen:
464 raise error.ParseError(_('duplicated command for changeset %s') %
464 raise error.ParseError(_('duplicated command for changeset %s') %
465 node.short(self.node))
465 node.short(self.node))
466
466
467 def torule(self):
467 def torule(self):
468 """build a histedit rule line for an action
468 """build a histedit rule line for an action
469
469
470 by default lines are in the form:
470 by default lines are in the form:
471 <hash> <rev> <summary>
471 <hash> <rev> <summary>
472 """
472 """
473 ctx = self.repo[self.node]
473 ctx = self.repo[self.node]
474 summary = _getsummary(ctx)
474 summary = _getsummary(ctx)
475 line = '%s %s %d %s' % (self.verb, ctx, ctx.rev(), summary)
475 line = '%s %s %d %s' % (self.verb, ctx, ctx.rev(), summary)
476 # trim to 75 columns by default so it's not stupidly wide in my editor
476 # trim to 75 columns by default so it's not stupidly wide in my editor
477 # (the 5 more are left for verb)
477 # (the 5 more are left for verb)
478 maxlen = self.repo.ui.configint('histedit', 'linelen')
478 maxlen = self.repo.ui.configint('histedit', 'linelen')
479 maxlen = max(maxlen, 22) # avoid truncating hash
479 maxlen = max(maxlen, 22) # avoid truncating hash
480 return stringutil.ellipsis(line, maxlen)
480 return stringutil.ellipsis(line, maxlen)
481
481
482 def tostate(self):
482 def tostate(self):
483 """Print an action in format used by histedit state files
483 """Print an action in format used by histedit state files
484 (the first line is a verb, the remainder is the second)
484 (the first line is a verb, the remainder is the second)
485 """
485 """
486 return "%s\n%s" % (self.verb, node.hex(self.node))
486 return "%s\n%s" % (self.verb, node.hex(self.node))
487
487
488 def run(self):
488 def run(self):
489 """Runs the action. The default behavior is simply apply the action's
489 """Runs the action. The default behavior is simply apply the action's
490 rulectx onto the current parentctx."""
490 rulectx onto the current parentctx."""
491 self.applychange()
491 self.applychange()
492 self.continuedirty()
492 self.continuedirty()
493 return self.continueclean()
493 return self.continueclean()
494
494
495 def applychange(self):
495 def applychange(self):
496 """Applies the changes from this action's rulectx onto the current
496 """Applies the changes from this action's rulectx onto the current
497 parentctx, but does not commit them."""
497 parentctx, but does not commit them."""
498 repo = self.repo
498 repo = self.repo
499 rulectx = repo[self.node]
499 rulectx = repo[self.node]
500 repo.ui.pushbuffer(error=True, labeled=True)
500 repo.ui.pushbuffer(error=True, labeled=True)
501 hg.update(repo, self.state.parentctxnode, quietempty=True)
501 hg.update(repo, self.state.parentctxnode, quietempty=True)
502 stats = applychanges(repo.ui, repo, rulectx, {})
502 stats = applychanges(repo.ui, repo, rulectx, {})
503 repo.dirstate.setbranch(rulectx.branch())
503 repo.dirstate.setbranch(rulectx.branch())
504 if stats.unresolvedcount:
504 if stats.unresolvedcount:
505 buf = repo.ui.popbuffer()
505 buf = repo.ui.popbuffer()
506 repo.ui.write(buf)
506 repo.ui.write(buf)
507 raise error.InterventionRequired(
507 raise error.InterventionRequired(
508 _('Fix up the change (%s %s)') %
508 _('Fix up the change (%s %s)') %
509 (self.verb, node.short(self.node)),
509 (self.verb, node.short(self.node)),
510 hint=_('hg histedit --continue to resume'))
510 hint=_('hg histedit --continue to resume'))
511 else:
511 else:
512 repo.ui.popbuffer()
512 repo.ui.popbuffer()
513
513
514 def continuedirty(self):
514 def continuedirty(self):
515 """Continues the action when changes have been applied to the working
515 """Continues the action when changes have been applied to the working
516 copy. The default behavior is to commit the dirty changes."""
516 copy. The default behavior is to commit the dirty changes."""
517 repo = self.repo
517 repo = self.repo
518 rulectx = repo[self.node]
518 rulectx = repo[self.node]
519
519
520 editor = self.commiteditor()
520 editor = self.commiteditor()
521 commit = commitfuncfor(repo, rulectx)
521 commit = commitfuncfor(repo, rulectx)
522
522
523 commit(text=rulectx.description(), user=rulectx.user(),
523 commit(text=rulectx.description(), user=rulectx.user(),
524 date=rulectx.date(), extra=rulectx.extra(), editor=editor)
524 date=rulectx.date(), extra=rulectx.extra(), editor=editor)
525
525
526 def commiteditor(self):
526 def commiteditor(self):
527 """The editor to be used to edit the commit message."""
527 """The editor to be used to edit the commit message."""
528 return False
528 return False
529
529
530 def continueclean(self):
530 def continueclean(self):
531 """Continues the action when the working copy is clean. The default
531 """Continues the action when the working copy is clean. The default
532 behavior is to accept the current commit as the new version of the
532 behavior is to accept the current commit as the new version of the
533 rulectx."""
533 rulectx."""
534 ctx = self.repo['.']
534 ctx = self.repo['.']
535 if ctx.node() == self.state.parentctxnode:
535 if ctx.node() == self.state.parentctxnode:
536 self.repo.ui.warn(_('%s: skipping changeset (no changes)\n') %
536 self.repo.ui.warn(_('%s: skipping changeset (no changes)\n') %
537 node.short(self.node))
537 node.short(self.node))
538 return ctx, [(self.node, tuple())]
538 return ctx, [(self.node, tuple())]
539 if ctx.node() == self.node:
539 if ctx.node() == self.node:
540 # Nothing changed
540 # Nothing changed
541 return ctx, []
541 return ctx, []
542 return ctx, [(self.node, (ctx.node(),))]
542 return ctx, [(self.node, (ctx.node(),))]
543
543
544 def commitfuncfor(repo, src):
544 def commitfuncfor(repo, src):
545 """Build a commit function for the replacement of <src>
545 """Build a commit function for the replacement of <src>
546
546
547 This function ensure we apply the same treatment to all changesets.
547 This function ensure we apply the same treatment to all changesets.
548
548
549 - Add a 'histedit_source' entry in extra.
549 - Add a 'histedit_source' entry in extra.
550
550
551 Note that fold has its own separated logic because its handling is a bit
551 Note that fold has its own separated logic because its handling is a bit
552 different and not easily factored out of the fold method.
552 different and not easily factored out of the fold method.
553 """
553 """
554 phasemin = src.phase()
554 phasemin = src.phase()
555 def commitfunc(**kwargs):
555 def commitfunc(**kwargs):
556 overrides = {('phases', 'new-commit'): phasemin}
556 overrides = {('phases', 'new-commit'): phasemin}
557 with repo.ui.configoverride(overrides, 'histedit'):
557 with repo.ui.configoverride(overrides, 'histedit'):
558 extra = kwargs.get(r'extra', {}).copy()
558 extra = kwargs.get(r'extra', {}).copy()
559 extra['histedit_source'] = src.hex()
559 extra['histedit_source'] = src.hex()
560 kwargs[r'extra'] = extra
560 kwargs[r'extra'] = extra
561 return repo.commit(**kwargs)
561 return repo.commit(**kwargs)
562 return commitfunc
562 return commitfunc
563
563
564 def applychanges(ui, repo, ctx, opts):
564 def applychanges(ui, repo, ctx, opts):
565 """Merge changeset from ctx (only) in the current working directory"""
565 """Merge changeset from ctx (only) in the current working directory"""
566 wcpar = repo.dirstate.parents()[0]
566 wcpar = repo.dirstate.parents()[0]
567 if ctx.p1().node() == wcpar:
567 if ctx.p1().node() == wcpar:
568 # edits are "in place" we do not need to make any merge,
568 # edits are "in place" we do not need to make any merge,
569 # just applies changes on parent for editing
569 # just applies changes on parent for editing
570 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
570 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
571 stats = mergemod.updateresult(0, 0, 0, 0)
571 stats = mergemod.updateresult(0, 0, 0, 0)
572 else:
572 else:
573 try:
573 try:
574 # ui.forcemerge is an internal variable, do not document
574 # ui.forcemerge is an internal variable, do not document
575 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
575 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
576 'histedit')
576 'histedit')
577 stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'histedit'])
577 stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'histedit'])
578 finally:
578 finally:
579 repo.ui.setconfig('ui', 'forcemerge', '', 'histedit')
579 repo.ui.setconfig('ui', 'forcemerge', '', 'histedit')
580 return stats
580 return stats
581
581
582 def collapse(repo, firstctx, lastctx, commitopts, skipprompt=False):
582 def collapse(repo, firstctx, lastctx, commitopts, skipprompt=False):
583 """collapse the set of revisions from first to last as new one.
583 """collapse the set of revisions from first to last as new one.
584
584
585 Expected commit options are:
585 Expected commit options are:
586 - message
586 - message
587 - date
587 - date
588 - username
588 - username
589 Commit message is edited in all cases.
589 Commit message is edited in all cases.
590
590
591 This function works in memory."""
591 This function works in memory."""
592 ctxs = list(repo.set('%d::%d', firstctx.rev(), lastctx.rev()))
592 ctxs = list(repo.set('%d::%d', firstctx.rev(), lastctx.rev()))
593 if not ctxs:
593 if not ctxs:
594 return None
594 return None
595 for c in ctxs:
595 for c in ctxs:
596 if not c.mutable():
596 if not c.mutable():
597 raise error.ParseError(
597 raise error.ParseError(
598 _("cannot fold into public change %s") % node.short(c.node()))
598 _("cannot fold into public change %s") % node.short(c.node()))
599 base = firstctx.parents()[0]
599 base = firstctx.parents()[0]
600
600
601 # commit a new version of the old changeset, including the update
601 # commit a new version of the old changeset, including the update
602 # collect all files which might be affected
602 # collect all files which might be affected
603 files = set()
603 files = set()
604 for ctx in ctxs:
604 for ctx in ctxs:
605 files.update(ctx.files())
605 files.update(ctx.files())
606
606
607 # Recompute copies (avoid recording a -> b -> a)
607 # Recompute copies (avoid recording a -> b -> a)
608 copied = copies.pathcopies(base, lastctx)
608 copied = copies.pathcopies(base, lastctx)
609
609
610 # prune files which were reverted by the updates
610 # prune files which were reverted by the updates
611 files = [f for f in files if not cmdutil.samefile(f, lastctx, base)]
611 files = [f for f in files if not cmdutil.samefile(f, lastctx, base)]
612 # commit version of these files as defined by head
612 # commit version of these files as defined by head
613 headmf = lastctx.manifest()
613 headmf = lastctx.manifest()
614 def filectxfn(repo, ctx, path):
614 def filectxfn(repo, ctx, path):
615 if path in headmf:
615 if path in headmf:
616 fctx = lastctx[path]
616 fctx = lastctx[path]
617 flags = fctx.flags()
617 flags = fctx.flags()
618 mctx = context.memfilectx(repo, ctx,
618 mctx = context.memfilectx(repo, ctx,
619 fctx.path(), fctx.data(),
619 fctx.path(), fctx.data(),
620 islink='l' in flags,
620 islink='l' in flags,
621 isexec='x' in flags,
621 isexec='x' in flags,
622 copied=copied.get(path))
622 copied=copied.get(path))
623 return mctx
623 return mctx
624 return None
624 return None
625
625
626 if commitopts.get('message'):
626 if commitopts.get('message'):
627 message = commitopts['message']
627 message = commitopts['message']
628 else:
628 else:
629 message = firstctx.description()
629 message = firstctx.description()
630 user = commitopts.get('user')
630 user = commitopts.get('user')
631 date = commitopts.get('date')
631 date = commitopts.get('date')
632 extra = commitopts.get('extra')
632 extra = commitopts.get('extra')
633
633
634 parents = (firstctx.p1().node(), firstctx.p2().node())
634 parents = (firstctx.p1().node(), firstctx.p2().node())
635 editor = None
635 editor = None
636 if not skipprompt:
636 if not skipprompt:
637 editor = cmdutil.getcommiteditor(edit=True, editform='histedit.fold')
637 editor = cmdutil.getcommiteditor(edit=True, editform='histedit.fold')
638 new = context.memctx(repo,
638 new = context.memctx(repo,
639 parents=parents,
639 parents=parents,
640 text=message,
640 text=message,
641 files=files,
641 files=files,
642 filectxfn=filectxfn,
642 filectxfn=filectxfn,
643 user=user,
643 user=user,
644 date=date,
644 date=date,
645 extra=extra,
645 extra=extra,
646 editor=editor)
646 editor=editor)
647 return repo.commitctx(new)
647 return repo.commitctx(new)
648
648
649 def _isdirtywc(repo):
649 def _isdirtywc(repo):
650 return repo[None].dirty(missing=True)
650 return repo[None].dirty(missing=True)
651
651
652 def abortdirty():
652 def abortdirty():
653 raise error.Abort(_('working copy has pending changes'),
653 raise error.Abort(_('working copy has pending changes'),
654 hint=_('amend, commit, or revert them and run histedit '
654 hint=_('amend, commit, or revert them and run histedit '
655 '--continue, or abort with histedit --abort'))
655 '--continue, or abort with histedit --abort'))
656
656
657 def action(verbs, message, priority=False, internal=False):
657 def action(verbs, message, priority=False, internal=False):
658 def wrap(cls):
658 def wrap(cls):
659 assert not priority or not internal
659 assert not priority or not internal
660 verb = verbs[0]
660 verb = verbs[0]
661 if priority:
661 if priority:
662 primaryactions.add(verb)
662 primaryactions.add(verb)
663 elif internal:
663 elif internal:
664 internalactions.add(verb)
664 internalactions.add(verb)
665 elif len(verbs) > 1:
665 elif len(verbs) > 1:
666 secondaryactions.add(verb)
666 secondaryactions.add(verb)
667 else:
667 else:
668 tertiaryactions.add(verb)
668 tertiaryactions.add(verb)
669
669
670 cls.verb = verb
670 cls.verb = verb
671 cls.verbs = verbs
671 cls.verbs = verbs
672 cls.message = message
672 cls.message = message
673 for verb in verbs:
673 for verb in verbs:
674 actiontable[verb] = cls
674 actiontable[verb] = cls
675 return cls
675 return cls
676 return wrap
676 return wrap
677
677
678 @action(['pick', 'p'],
678 @action(['pick', 'p'],
679 _('use commit'),
679 _('use commit'),
680 priority=True)
680 priority=True)
681 class pick(histeditaction):
681 class pick(histeditaction):
682 def run(self):
682 def run(self):
683 rulectx = self.repo[self.node]
683 rulectx = self.repo[self.node]
684 if rulectx.parents()[0].node() == self.state.parentctxnode:
684 if rulectx.parents()[0].node() == self.state.parentctxnode:
685 self.repo.ui.debug('node %s unchanged\n' % node.short(self.node))
685 self.repo.ui.debug('node %s unchanged\n' % node.short(self.node))
686 return rulectx, []
686 return rulectx, []
687
687
688 return super(pick, self).run()
688 return super(pick, self).run()
689
689
690 @action(['edit', 'e'],
690 @action(['edit', 'e'],
691 _('use commit, but stop for amending'),
691 _('use commit, but stop for amending'),
692 priority=True)
692 priority=True)
693 class edit(histeditaction):
693 class edit(histeditaction):
694 def run(self):
694 def run(self):
695 repo = self.repo
695 repo = self.repo
696 rulectx = repo[self.node]
696 rulectx = repo[self.node]
697 hg.update(repo, self.state.parentctxnode, quietempty=True)
697 hg.update(repo, self.state.parentctxnode, quietempty=True)
698 applychanges(repo.ui, repo, rulectx, {})
698 applychanges(repo.ui, repo, rulectx, {})
699 raise error.InterventionRequired(
699 raise error.InterventionRequired(
700 _('Editing (%s), you may commit or record as needed now.')
700 _('Editing (%s), you may commit or record as needed now.')
701 % node.short(self.node),
701 % node.short(self.node),
702 hint=_('hg histedit --continue to resume'))
702 hint=_('hg histedit --continue to resume'))
703
703
704 def commiteditor(self):
704 def commiteditor(self):
705 return cmdutil.getcommiteditor(edit=True, editform='histedit.edit')
705 return cmdutil.getcommiteditor(edit=True, editform='histedit.edit')
706
706
707 @action(['fold', 'f'],
707 @action(['fold', 'f'],
708 _('use commit, but combine it with the one above'))
708 _('use commit, but combine it with the one above'))
709 class fold(histeditaction):
709 class fold(histeditaction):
710 def verify(self, prev, expected, seen):
710 def verify(self, prev, expected, seen):
711 """ Verifies semantic correctness of the fold rule"""
711 """ Verifies semantic correctness of the fold rule"""
712 super(fold, self).verify(prev, expected, seen)
712 super(fold, self).verify(prev, expected, seen)
713 repo = self.repo
713 repo = self.repo
714 if not prev:
714 if not prev:
715 c = repo[self.node].parents()[0]
715 c = repo[self.node].parents()[0]
716 elif not prev.verb in ('pick', 'base'):
716 elif not prev.verb in ('pick', 'base'):
717 return
717 return
718 else:
718 else:
719 c = repo[prev.node]
719 c = repo[prev.node]
720 if not c.mutable():
720 if not c.mutable():
721 raise error.ParseError(
721 raise error.ParseError(
722 _("cannot fold into public change %s") % node.short(c.node()))
722 _("cannot fold into public change %s") % node.short(c.node()))
723
723
724
724
725 def continuedirty(self):
725 def continuedirty(self):
726 repo = self.repo
726 repo = self.repo
727 rulectx = repo[self.node]
727 rulectx = repo[self.node]
728
728
729 commit = commitfuncfor(repo, rulectx)
729 commit = commitfuncfor(repo, rulectx)
730 commit(text='fold-temp-revision %s' % node.short(self.node),
730 commit(text='fold-temp-revision %s' % node.short(self.node),
731 user=rulectx.user(), date=rulectx.date(),
731 user=rulectx.user(), date=rulectx.date(),
732 extra=rulectx.extra())
732 extra=rulectx.extra())
733
733
734 def continueclean(self):
734 def continueclean(self):
735 repo = self.repo
735 repo = self.repo
736 ctx = repo['.']
736 ctx = repo['.']
737 rulectx = repo[self.node]
737 rulectx = repo[self.node]
738 parentctxnode = self.state.parentctxnode
738 parentctxnode = self.state.parentctxnode
739 if ctx.node() == parentctxnode:
739 if ctx.node() == parentctxnode:
740 repo.ui.warn(_('%s: empty changeset\n') %
740 repo.ui.warn(_('%s: empty changeset\n') %
741 node.short(self.node))
741 node.short(self.node))
742 return ctx, [(self.node, (parentctxnode,))]
742 return ctx, [(self.node, (parentctxnode,))]
743
743
744 parentctx = repo[parentctxnode]
744 parentctx = repo[parentctxnode]
745 newcommits = set(c.node() for c in repo.set('(%d::. - %d)',
745 newcommits = set(c.node() for c in repo.set('(%d::. - %d)',
746 parentctx.rev(),
746 parentctx.rev(),
747 parentctx.rev()))
747 parentctx.rev()))
748 if not newcommits:
748 if not newcommits:
749 repo.ui.warn(_('%s: cannot fold - working copy is not a '
749 repo.ui.warn(_('%s: cannot fold - working copy is not a '
750 'descendant of previous commit %s\n') %
750 'descendant of previous commit %s\n') %
751 (node.short(self.node), node.short(parentctxnode)))
751 (node.short(self.node), node.short(parentctxnode)))
752 return ctx, [(self.node, (ctx.node(),))]
752 return ctx, [(self.node, (ctx.node(),))]
753
753
754 middlecommits = newcommits.copy()
754 middlecommits = newcommits.copy()
755 middlecommits.discard(ctx.node())
755 middlecommits.discard(ctx.node())
756
756
757 return self.finishfold(repo.ui, repo, parentctx, rulectx, ctx.node(),
757 return self.finishfold(repo.ui, repo, parentctx, rulectx, ctx.node(),
758 middlecommits)
758 middlecommits)
759
759
760 def skipprompt(self):
760 def skipprompt(self):
761 """Returns true if the rule should skip the message editor.
761 """Returns true if the rule should skip the message editor.
762
762
763 For example, 'fold' wants to show an editor, but 'rollup'
763 For example, 'fold' wants to show an editor, but 'rollup'
764 doesn't want to.
764 doesn't want to.
765 """
765 """
766 return False
766 return False
767
767
768 def mergedescs(self):
768 def mergedescs(self):
769 """Returns true if the rule should merge messages of multiple changes.
769 """Returns true if the rule should merge messages of multiple changes.
770
770
771 This exists mainly so that 'rollup' rules can be a subclass of
771 This exists mainly so that 'rollup' rules can be a subclass of
772 'fold'.
772 'fold'.
773 """
773 """
774 return True
774 return True
775
775
776 def firstdate(self):
776 def firstdate(self):
777 """Returns true if the rule should preserve the date of the first
777 """Returns true if the rule should preserve the date of the first
778 change.
778 change.
779
779
780 This exists mainly so that 'rollup' rules can be a subclass of
780 This exists mainly so that 'rollup' rules can be a subclass of
781 'fold'.
781 'fold'.
782 """
782 """
783 return False
783 return False
784
784
785 def finishfold(self, ui, repo, ctx, oldctx, newnode, internalchanges):
785 def finishfold(self, ui, repo, ctx, oldctx, newnode, internalchanges):
786 parent = ctx.parents()[0].node()
786 parent = ctx.parents()[0].node()
787 hg.updaterepo(repo, parent, False)
787 hg.updaterepo(repo, parent, overwrite=False)
788 ### prepare new commit data
788 ### prepare new commit data
789 commitopts = {}
789 commitopts = {}
790 commitopts['user'] = ctx.user()
790 commitopts['user'] = ctx.user()
791 # commit message
791 # commit message
792 if not self.mergedescs():
792 if not self.mergedescs():
793 newmessage = ctx.description()
793 newmessage = ctx.description()
794 else:
794 else:
795 newmessage = '\n***\n'.join(
795 newmessage = '\n***\n'.join(
796 [ctx.description()] +
796 [ctx.description()] +
797 [repo[r].description() for r in internalchanges] +
797 [repo[r].description() for r in internalchanges] +
798 [oldctx.description()]) + '\n'
798 [oldctx.description()]) + '\n'
799 commitopts['message'] = newmessage
799 commitopts['message'] = newmessage
800 # date
800 # date
801 if self.firstdate():
801 if self.firstdate():
802 commitopts['date'] = ctx.date()
802 commitopts['date'] = ctx.date()
803 else:
803 else:
804 commitopts['date'] = max(ctx.date(), oldctx.date())
804 commitopts['date'] = max(ctx.date(), oldctx.date())
805 extra = ctx.extra().copy()
805 extra = ctx.extra().copy()
806 # histedit_source
806 # histedit_source
807 # note: ctx is likely a temporary commit but that the best we can do
807 # note: ctx is likely a temporary commit but that the best we can do
808 # here. This is sufficient to solve issue3681 anyway.
808 # here. This is sufficient to solve issue3681 anyway.
809 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
809 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
810 commitopts['extra'] = extra
810 commitopts['extra'] = extra
811 phasemin = max(ctx.phase(), oldctx.phase())
811 phasemin = max(ctx.phase(), oldctx.phase())
812 overrides = {('phases', 'new-commit'): phasemin}
812 overrides = {('phases', 'new-commit'): phasemin}
813 with repo.ui.configoverride(overrides, 'histedit'):
813 with repo.ui.configoverride(overrides, 'histedit'):
814 n = collapse(repo, ctx, repo[newnode], commitopts,
814 n = collapse(repo, ctx, repo[newnode], commitopts,
815 skipprompt=self.skipprompt())
815 skipprompt=self.skipprompt())
816 if n is None:
816 if n is None:
817 return ctx, []
817 return ctx, []
818 hg.updaterepo(repo, n, False)
818 hg.updaterepo(repo, n, overwrite=False)
819 replacements = [(oldctx.node(), (newnode,)),
819 replacements = [(oldctx.node(), (newnode,)),
820 (ctx.node(), (n,)),
820 (ctx.node(), (n,)),
821 (newnode, (n,)),
821 (newnode, (n,)),
822 ]
822 ]
823 for ich in internalchanges:
823 for ich in internalchanges:
824 replacements.append((ich, (n,)))
824 replacements.append((ich, (n,)))
825 return repo[n], replacements
825 return repo[n], replacements
826
826
827 @action(['base', 'b'],
827 @action(['base', 'b'],
828 _('checkout changeset and apply further changesets from there'))
828 _('checkout changeset and apply further changesets from there'))
829 class base(histeditaction):
829 class base(histeditaction):
830
830
831 def run(self):
831 def run(self):
832 if self.repo['.'].node() != self.node:
832 if self.repo['.'].node() != self.node:
833 mergemod.update(self.repo, self.node, False, True)
833 mergemod.update(self.repo, self.node, False, True)
834 # branchmerge, force)
834 # branchmerge, force)
835 return self.continueclean()
835 return self.continueclean()
836
836
837 def continuedirty(self):
837 def continuedirty(self):
838 abortdirty()
838 abortdirty()
839
839
840 def continueclean(self):
840 def continueclean(self):
841 basectx = self.repo['.']
841 basectx = self.repo['.']
842 return basectx, []
842 return basectx, []
843
843
844 def _verifynodeconstraints(self, prev, expected, seen):
844 def _verifynodeconstraints(self, prev, expected, seen):
845 # base can only be use with a node not in the edited set
845 # base can only be use with a node not in the edited set
846 if self.node in expected:
846 if self.node in expected:
847 msg = _('%s "%s" changeset was an edited list candidate')
847 msg = _('%s "%s" changeset was an edited list candidate')
848 raise error.ParseError(
848 raise error.ParseError(
849 msg % (self.verb, node.short(self.node)),
849 msg % (self.verb, node.short(self.node)),
850 hint=_('base must only use unlisted changesets'))
850 hint=_('base must only use unlisted changesets'))
851
851
852 @action(['_multifold'],
852 @action(['_multifold'],
853 _(
853 _(
854 """fold subclass used for when multiple folds happen in a row
854 """fold subclass used for when multiple folds happen in a row
855
855
856 We only want to fire the editor for the folded message once when
856 We only want to fire the editor for the folded message once when
857 (say) four changes are folded down into a single change. This is
857 (say) four changes are folded down into a single change. This is
858 similar to rollup, but we should preserve both messages so that
858 similar to rollup, but we should preserve both messages so that
859 when the last fold operation runs we can show the user all the
859 when the last fold operation runs we can show the user all the
860 commit messages in their editor.
860 commit messages in their editor.
861 """),
861 """),
862 internal=True)
862 internal=True)
863 class _multifold(fold):
863 class _multifold(fold):
864 def skipprompt(self):
864 def skipprompt(self):
865 return True
865 return True
866
866
867 @action(["roll", "r"],
867 @action(["roll", "r"],
868 _("like fold, but discard this commit's description and date"))
868 _("like fold, but discard this commit's description and date"))
869 class rollup(fold):
869 class rollup(fold):
870 def mergedescs(self):
870 def mergedescs(self):
871 return False
871 return False
872
872
873 def skipprompt(self):
873 def skipprompt(self):
874 return True
874 return True
875
875
876 def firstdate(self):
876 def firstdate(self):
877 return True
877 return True
878
878
879 @action(["drop", "d"],
879 @action(["drop", "d"],
880 _('remove commit from history'))
880 _('remove commit from history'))
881 class drop(histeditaction):
881 class drop(histeditaction):
882 def run(self):
882 def run(self):
883 parentctx = self.repo[self.state.parentctxnode]
883 parentctx = self.repo[self.state.parentctxnode]
884 return parentctx, [(self.node, tuple())]
884 return parentctx, [(self.node, tuple())]
885
885
886 @action(["mess", "m"],
886 @action(["mess", "m"],
887 _('edit commit message without changing commit content'),
887 _('edit commit message without changing commit content'),
888 priority=True)
888 priority=True)
889 class message(histeditaction):
889 class message(histeditaction):
890 def commiteditor(self):
890 def commiteditor(self):
891 return cmdutil.getcommiteditor(edit=True, editform='histedit.mess')
891 return cmdutil.getcommiteditor(edit=True, editform='histedit.mess')
892
892
893 def findoutgoing(ui, repo, remote=None, force=False, opts=None):
893 def findoutgoing(ui, repo, remote=None, force=False, opts=None):
894 """utility function to find the first outgoing changeset
894 """utility function to find the first outgoing changeset
895
895
896 Used by initialization code"""
896 Used by initialization code"""
897 if opts is None:
897 if opts is None:
898 opts = {}
898 opts = {}
899 dest = ui.expandpath(remote or 'default-push', remote or 'default')
899 dest = ui.expandpath(remote or 'default-push', remote or 'default')
900 dest, branches = hg.parseurl(dest, None)[:2]
900 dest, branches = hg.parseurl(dest, None)[:2]
901 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
901 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
902
902
903 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
903 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
904 other = hg.peer(repo, opts, dest)
904 other = hg.peer(repo, opts, dest)
905
905
906 if revs:
906 if revs:
907 revs = [repo.lookup(rev) for rev in revs]
907 revs = [repo.lookup(rev) for rev in revs]
908
908
909 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
909 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
910 if not outgoing.missing:
910 if not outgoing.missing:
911 raise error.Abort(_('no outgoing ancestors'))
911 raise error.Abort(_('no outgoing ancestors'))
912 roots = list(repo.revs("roots(%ln)", outgoing.missing))
912 roots = list(repo.revs("roots(%ln)", outgoing.missing))
913 if 1 < len(roots):
913 if 1 < len(roots):
914 msg = _('there are ambiguous outgoing revisions')
914 msg = _('there are ambiguous outgoing revisions')
915 hint = _("see 'hg help histedit' for more detail")
915 hint = _("see 'hg help histedit' for more detail")
916 raise error.Abort(msg, hint=hint)
916 raise error.Abort(msg, hint=hint)
917 return repo[roots[0]].node()
917 return repo[roots[0]].node()
918
918
919 @command('histedit',
919 @command('histedit',
920 [('', 'commands', '',
920 [('', 'commands', '',
921 _('read history edits from the specified file'), _('FILE')),
921 _('read history edits from the specified file'), _('FILE')),
922 ('c', 'continue', False, _('continue an edit already in progress')),
922 ('c', 'continue', False, _('continue an edit already in progress')),
923 ('', 'edit-plan', False, _('edit remaining actions list')),
923 ('', 'edit-plan', False, _('edit remaining actions list')),
924 ('k', 'keep', False,
924 ('k', 'keep', False,
925 _("don't strip old nodes after edit is complete")),
925 _("don't strip old nodes after edit is complete")),
926 ('', 'abort', False, _('abort an edit in progress')),
926 ('', 'abort', False, _('abort an edit in progress')),
927 ('o', 'outgoing', False, _('changesets not found in destination')),
927 ('o', 'outgoing', False, _('changesets not found in destination')),
928 ('f', 'force', False,
928 ('f', 'force', False,
929 _('force outgoing even for unrelated repositories')),
929 _('force outgoing even for unrelated repositories')),
930 ('r', 'rev', [], _('first revision to be edited'), _('REV'))] +
930 ('r', 'rev', [], _('first revision to be edited'), _('REV'))] +
931 cmdutil.formatteropts,
931 cmdutil.formatteropts,
932 _("[OPTIONS] ([ANCESTOR] | --outgoing [URL])"))
932 _("[OPTIONS] ([ANCESTOR] | --outgoing [URL])"))
933 def histedit(ui, repo, *freeargs, **opts):
933 def histedit(ui, repo, *freeargs, **opts):
934 """interactively edit changeset history
934 """interactively edit changeset history
935
935
936 This command lets you edit a linear series of changesets (up to
936 This command lets you edit a linear series of changesets (up to
937 and including the working directory, which should be clean).
937 and including the working directory, which should be clean).
938 You can:
938 You can:
939
939
940 - `pick` to [re]order a changeset
940 - `pick` to [re]order a changeset
941
941
942 - `drop` to omit changeset
942 - `drop` to omit changeset
943
943
944 - `mess` to reword the changeset commit message
944 - `mess` to reword the changeset commit message
945
945
946 - `fold` to combine it with the preceding changeset (using the later date)
946 - `fold` to combine it with the preceding changeset (using the later date)
947
947
948 - `roll` like fold, but discarding this commit's description and date
948 - `roll` like fold, but discarding this commit's description and date
949
949
950 - `edit` to edit this changeset (preserving date)
950 - `edit` to edit this changeset (preserving date)
951
951
952 - `base` to checkout changeset and apply further changesets from there
952 - `base` to checkout changeset and apply further changesets from there
953
953
954 There are a number of ways to select the root changeset:
954 There are a number of ways to select the root changeset:
955
955
956 - Specify ANCESTOR directly
956 - Specify ANCESTOR directly
957
957
958 - Use --outgoing -- it will be the first linear changeset not
958 - Use --outgoing -- it will be the first linear changeset not
959 included in destination. (See :hg:`help config.paths.default-push`)
959 included in destination. (See :hg:`help config.paths.default-push`)
960
960
961 - Otherwise, the value from the "histedit.defaultrev" config option
961 - Otherwise, the value from the "histedit.defaultrev" config option
962 is used as a revset to select the base revision when ANCESTOR is not
962 is used as a revset to select the base revision when ANCESTOR is not
963 specified. The first revision returned by the revset is used. By
963 specified. The first revision returned by the revset is used. By
964 default, this selects the editable history that is unique to the
964 default, this selects the editable history that is unique to the
965 ancestry of the working directory.
965 ancestry of the working directory.
966
966
967 .. container:: verbose
967 .. container:: verbose
968
968
969 If you use --outgoing, this command will abort if there are ambiguous
969 If you use --outgoing, this command will abort if there are ambiguous
970 outgoing revisions. For example, if there are multiple branches
970 outgoing revisions. For example, if there are multiple branches
971 containing outgoing revisions.
971 containing outgoing revisions.
972
972
973 Use "min(outgoing() and ::.)" or similar revset specification
973 Use "min(outgoing() and ::.)" or similar revset specification
974 instead of --outgoing to specify edit target revision exactly in
974 instead of --outgoing to specify edit target revision exactly in
975 such ambiguous situation. See :hg:`help revsets` for detail about
975 such ambiguous situation. See :hg:`help revsets` for detail about
976 selecting revisions.
976 selecting revisions.
977
977
978 .. container:: verbose
978 .. container:: verbose
979
979
980 Examples:
980 Examples:
981
981
982 - A number of changes have been made.
982 - A number of changes have been made.
983 Revision 3 is no longer needed.
983 Revision 3 is no longer needed.
984
984
985 Start history editing from revision 3::
985 Start history editing from revision 3::
986
986
987 hg histedit -r 3
987 hg histedit -r 3
988
988
989 An editor opens, containing the list of revisions,
989 An editor opens, containing the list of revisions,
990 with specific actions specified::
990 with specific actions specified::
991
991
992 pick 5339bf82f0ca 3 Zworgle the foobar
992 pick 5339bf82f0ca 3 Zworgle the foobar
993 pick 8ef592ce7cc4 4 Bedazzle the zerlog
993 pick 8ef592ce7cc4 4 Bedazzle the zerlog
994 pick 0a9639fcda9d 5 Morgify the cromulancy
994 pick 0a9639fcda9d 5 Morgify the cromulancy
995
995
996 Additional information about the possible actions
996 Additional information about the possible actions
997 to take appears below the list of revisions.
997 to take appears below the list of revisions.
998
998
999 To remove revision 3 from the history,
999 To remove revision 3 from the history,
1000 its action (at the beginning of the relevant line)
1000 its action (at the beginning of the relevant line)
1001 is changed to 'drop'::
1001 is changed to 'drop'::
1002
1002
1003 drop 5339bf82f0ca 3 Zworgle the foobar
1003 drop 5339bf82f0ca 3 Zworgle the foobar
1004 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1004 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1005 pick 0a9639fcda9d 5 Morgify the cromulancy
1005 pick 0a9639fcda9d 5 Morgify the cromulancy
1006
1006
1007 - A number of changes have been made.
1007 - A number of changes have been made.
1008 Revision 2 and 4 need to be swapped.
1008 Revision 2 and 4 need to be swapped.
1009
1009
1010 Start history editing from revision 2::
1010 Start history editing from revision 2::
1011
1011
1012 hg histedit -r 2
1012 hg histedit -r 2
1013
1013
1014 An editor opens, containing the list of revisions,
1014 An editor opens, containing the list of revisions,
1015 with specific actions specified::
1015 with specific actions specified::
1016
1016
1017 pick 252a1af424ad 2 Blorb a morgwazzle
1017 pick 252a1af424ad 2 Blorb a morgwazzle
1018 pick 5339bf82f0ca 3 Zworgle the foobar
1018 pick 5339bf82f0ca 3 Zworgle the foobar
1019 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1019 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1020
1020
1021 To swap revision 2 and 4, its lines are swapped
1021 To swap revision 2 and 4, its lines are swapped
1022 in the editor::
1022 in the editor::
1023
1023
1024 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1024 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1025 pick 5339bf82f0ca 3 Zworgle the foobar
1025 pick 5339bf82f0ca 3 Zworgle the foobar
1026 pick 252a1af424ad 2 Blorb a morgwazzle
1026 pick 252a1af424ad 2 Blorb a morgwazzle
1027
1027
1028 Returns 0 on success, 1 if user intervention is required (not only
1028 Returns 0 on success, 1 if user intervention is required (not only
1029 for intentional "edit" command, but also for resolving unexpected
1029 for intentional "edit" command, but also for resolving unexpected
1030 conflicts).
1030 conflicts).
1031 """
1031 """
1032 state = histeditstate(repo)
1032 state = histeditstate(repo)
1033 try:
1033 try:
1034 state.wlock = repo.wlock()
1034 state.wlock = repo.wlock()
1035 state.lock = repo.lock()
1035 state.lock = repo.lock()
1036 _histedit(ui, repo, state, *freeargs, **opts)
1036 _histedit(ui, repo, state, *freeargs, **opts)
1037 finally:
1037 finally:
1038 release(state.lock, state.wlock)
1038 release(state.lock, state.wlock)
1039
1039
1040 goalcontinue = 'continue'
1040 goalcontinue = 'continue'
1041 goalabort = 'abort'
1041 goalabort = 'abort'
1042 goaleditplan = 'edit-plan'
1042 goaleditplan = 'edit-plan'
1043 goalnew = 'new'
1043 goalnew = 'new'
1044
1044
1045 def _getgoal(opts):
1045 def _getgoal(opts):
1046 if opts.get('continue'):
1046 if opts.get('continue'):
1047 return goalcontinue
1047 return goalcontinue
1048 if opts.get('abort'):
1048 if opts.get('abort'):
1049 return goalabort
1049 return goalabort
1050 if opts.get('edit_plan'):
1050 if opts.get('edit_plan'):
1051 return goaleditplan
1051 return goaleditplan
1052 return goalnew
1052 return goalnew
1053
1053
1054 def _readfile(ui, path):
1054 def _readfile(ui, path):
1055 if path == '-':
1055 if path == '-':
1056 with ui.timeblockedsection('histedit'):
1056 with ui.timeblockedsection('histedit'):
1057 return ui.fin.read()
1057 return ui.fin.read()
1058 else:
1058 else:
1059 with open(path, 'rb') as f:
1059 with open(path, 'rb') as f:
1060 return f.read()
1060 return f.read()
1061
1061
1062 def _validateargs(ui, repo, state, freeargs, opts, goal, rules, revs):
1062 def _validateargs(ui, repo, state, freeargs, opts, goal, rules, revs):
1063 # TODO only abort if we try to histedit mq patches, not just
1063 # TODO only abort if we try to histedit mq patches, not just
1064 # blanket if mq patches are applied somewhere
1064 # blanket if mq patches are applied somewhere
1065 mq = getattr(repo, 'mq', None)
1065 mq = getattr(repo, 'mq', None)
1066 if mq and mq.applied:
1066 if mq and mq.applied:
1067 raise error.Abort(_('source has mq patches applied'))
1067 raise error.Abort(_('source has mq patches applied'))
1068
1068
1069 # basic argument incompatibility processing
1069 # basic argument incompatibility processing
1070 outg = opts.get('outgoing')
1070 outg = opts.get('outgoing')
1071 editplan = opts.get('edit_plan')
1071 editplan = opts.get('edit_plan')
1072 abort = opts.get('abort')
1072 abort = opts.get('abort')
1073 force = opts.get('force')
1073 force = opts.get('force')
1074 if force and not outg:
1074 if force and not outg:
1075 raise error.Abort(_('--force only allowed with --outgoing'))
1075 raise error.Abort(_('--force only allowed with --outgoing'))
1076 if goal == 'continue':
1076 if goal == 'continue':
1077 if any((outg, abort, revs, freeargs, rules, editplan)):
1077 if any((outg, abort, revs, freeargs, rules, editplan)):
1078 raise error.Abort(_('no arguments allowed with --continue'))
1078 raise error.Abort(_('no arguments allowed with --continue'))
1079 elif goal == 'abort':
1079 elif goal == 'abort':
1080 if any((outg, revs, freeargs, rules, editplan)):
1080 if any((outg, revs, freeargs, rules, editplan)):
1081 raise error.Abort(_('no arguments allowed with --abort'))
1081 raise error.Abort(_('no arguments allowed with --abort'))
1082 elif goal == 'edit-plan':
1082 elif goal == 'edit-plan':
1083 if any((outg, revs, freeargs)):
1083 if any((outg, revs, freeargs)):
1084 raise error.Abort(_('only --commands argument allowed with '
1084 raise error.Abort(_('only --commands argument allowed with '
1085 '--edit-plan'))
1085 '--edit-plan'))
1086 else:
1086 else:
1087 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
1087 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
1088 raise error.Abort(_('history edit already in progress, try '
1088 raise error.Abort(_('history edit already in progress, try '
1089 '--continue or --abort'))
1089 '--continue or --abort'))
1090 if outg:
1090 if outg:
1091 if revs:
1091 if revs:
1092 raise error.Abort(_('no revisions allowed with --outgoing'))
1092 raise error.Abort(_('no revisions allowed with --outgoing'))
1093 if len(freeargs) > 1:
1093 if len(freeargs) > 1:
1094 raise error.Abort(
1094 raise error.Abort(
1095 _('only one repo argument allowed with --outgoing'))
1095 _('only one repo argument allowed with --outgoing'))
1096 else:
1096 else:
1097 revs.extend(freeargs)
1097 revs.extend(freeargs)
1098 if len(revs) == 0:
1098 if len(revs) == 0:
1099 defaultrev = destutil.desthistedit(ui, repo)
1099 defaultrev = destutil.desthistedit(ui, repo)
1100 if defaultrev is not None:
1100 if defaultrev is not None:
1101 revs.append(defaultrev)
1101 revs.append(defaultrev)
1102
1102
1103 if len(revs) != 1:
1103 if len(revs) != 1:
1104 raise error.Abort(
1104 raise error.Abort(
1105 _('histedit requires exactly one ancestor revision'))
1105 _('histedit requires exactly one ancestor revision'))
1106
1106
1107 def _histedit(ui, repo, state, *freeargs, **opts):
1107 def _histedit(ui, repo, state, *freeargs, **opts):
1108 opts = pycompat.byteskwargs(opts)
1108 opts = pycompat.byteskwargs(opts)
1109 fm = ui.formatter('histedit', opts)
1109 fm = ui.formatter('histedit', opts)
1110 fm.startitem()
1110 fm.startitem()
1111 goal = _getgoal(opts)
1111 goal = _getgoal(opts)
1112 revs = opts.get('rev', [])
1112 revs = opts.get('rev', [])
1113 rules = opts.get('commands', '')
1113 rules = opts.get('commands', '')
1114 state.keep = opts.get('keep', False)
1114 state.keep = opts.get('keep', False)
1115
1115
1116 _validateargs(ui, repo, state, freeargs, opts, goal, rules, revs)
1116 _validateargs(ui, repo, state, freeargs, opts, goal, rules, revs)
1117
1117
1118 # rebuild state
1118 # rebuild state
1119 if goal == goalcontinue:
1119 if goal == goalcontinue:
1120 state.read()
1120 state.read()
1121 state = bootstrapcontinue(ui, state, opts)
1121 state = bootstrapcontinue(ui, state, opts)
1122 elif goal == goaleditplan:
1122 elif goal == goaleditplan:
1123 _edithisteditplan(ui, repo, state, rules)
1123 _edithisteditplan(ui, repo, state, rules)
1124 return
1124 return
1125 elif goal == goalabort:
1125 elif goal == goalabort:
1126 _aborthistedit(ui, repo, state)
1126 _aborthistedit(ui, repo, state)
1127 return
1127 return
1128 else:
1128 else:
1129 # goal == goalnew
1129 # goal == goalnew
1130 _newhistedit(ui, repo, state, revs, freeargs, opts)
1130 _newhistedit(ui, repo, state, revs, freeargs, opts)
1131
1131
1132 _continuehistedit(ui, repo, state)
1132 _continuehistedit(ui, repo, state)
1133 _finishhistedit(ui, repo, state, fm)
1133 _finishhistedit(ui, repo, state, fm)
1134 fm.end()
1134 fm.end()
1135
1135
1136 def _continuehistedit(ui, repo, state):
1136 def _continuehistedit(ui, repo, state):
1137 """This function runs after either:
1137 """This function runs after either:
1138 - bootstrapcontinue (if the goal is 'continue')
1138 - bootstrapcontinue (if the goal is 'continue')
1139 - _newhistedit (if the goal is 'new')
1139 - _newhistedit (if the goal is 'new')
1140 """
1140 """
1141 # preprocess rules so that we can hide inner folds from the user
1141 # preprocess rules so that we can hide inner folds from the user
1142 # and only show one editor
1142 # and only show one editor
1143 actions = state.actions[:]
1143 actions = state.actions[:]
1144 for idx, (action, nextact) in enumerate(
1144 for idx, (action, nextact) in enumerate(
1145 zip(actions, actions[1:] + [None])):
1145 zip(actions, actions[1:] + [None])):
1146 if action.verb == 'fold' and nextact and nextact.verb == 'fold':
1146 if action.verb == 'fold' and nextact and nextact.verb == 'fold':
1147 state.actions[idx].__class__ = _multifold
1147 state.actions[idx].__class__ = _multifold
1148
1148
1149 # Force an initial state file write, so the user can run --abort/continue
1149 # Force an initial state file write, so the user can run --abort/continue
1150 # even if there's an exception before the first transaction serialize.
1150 # even if there's an exception before the first transaction serialize.
1151 state.write()
1151 state.write()
1152
1152
1153 tr = None
1153 tr = None
1154 # Don't use singletransaction by default since it rolls the entire
1154 # Don't use singletransaction by default since it rolls the entire
1155 # transaction back if an unexpected exception happens (like a
1155 # transaction back if an unexpected exception happens (like a
1156 # pretxncommit hook throws, or the user aborts the commit msg editor).
1156 # pretxncommit hook throws, or the user aborts the commit msg editor).
1157 if ui.configbool("histedit", "singletransaction"):
1157 if ui.configbool("histedit", "singletransaction"):
1158 # Don't use a 'with' for the transaction, since actions may close
1158 # Don't use a 'with' for the transaction, since actions may close
1159 # and reopen a transaction. For example, if the action executes an
1159 # and reopen a transaction. For example, if the action executes an
1160 # external process it may choose to commit the transaction first.
1160 # external process it may choose to commit the transaction first.
1161 tr = repo.transaction('histedit')
1161 tr = repo.transaction('histedit')
1162 progress = ui.makeprogress(_("editing"), unit=_('changes'),
1162 progress = ui.makeprogress(_("editing"), unit=_('changes'),
1163 total=len(state.actions))
1163 total=len(state.actions))
1164 with progress, util.acceptintervention(tr):
1164 with progress, util.acceptintervention(tr):
1165 while state.actions:
1165 while state.actions:
1166 state.write(tr=tr)
1166 state.write(tr=tr)
1167 actobj = state.actions[0]
1167 actobj = state.actions[0]
1168 progress.increment(item=actobj.torule())
1168 progress.increment(item=actobj.torule())
1169 ui.debug('histedit: processing %s %s\n' % (actobj.verb,\
1169 ui.debug('histedit: processing %s %s\n' % (actobj.verb,\
1170 actobj.torule()))
1170 actobj.torule()))
1171 parentctx, replacement_ = actobj.run()
1171 parentctx, replacement_ = actobj.run()
1172 state.parentctxnode = parentctx.node()
1172 state.parentctxnode = parentctx.node()
1173 state.replacements.extend(replacement_)
1173 state.replacements.extend(replacement_)
1174 state.actions.pop(0)
1174 state.actions.pop(0)
1175
1175
1176 state.write()
1176 state.write()
1177
1177
1178 def _finishhistedit(ui, repo, state, fm):
1178 def _finishhistedit(ui, repo, state, fm):
1179 """This action runs when histedit is finishing its session"""
1179 """This action runs when histedit is finishing its session"""
1180 hg.updaterepo(repo, state.parentctxnode, False)
1180 hg.updaterepo(repo, state.parentctxnode, overwrite=False)
1181
1181
1182 mapping, tmpnodes, created, ntm = processreplacement(state)
1182 mapping, tmpnodes, created, ntm = processreplacement(state)
1183 if mapping:
1183 if mapping:
1184 for prec, succs in mapping.iteritems():
1184 for prec, succs in mapping.iteritems():
1185 if not succs:
1185 if not succs:
1186 ui.debug('histedit: %s is dropped\n' % node.short(prec))
1186 ui.debug('histedit: %s is dropped\n' % node.short(prec))
1187 else:
1187 else:
1188 ui.debug('histedit: %s is replaced by %s\n' % (
1188 ui.debug('histedit: %s is replaced by %s\n' % (
1189 node.short(prec), node.short(succs[0])))
1189 node.short(prec), node.short(succs[0])))
1190 if len(succs) > 1:
1190 if len(succs) > 1:
1191 m = 'histedit: %s'
1191 m = 'histedit: %s'
1192 for n in succs[1:]:
1192 for n in succs[1:]:
1193 ui.debug(m % node.short(n))
1193 ui.debug(m % node.short(n))
1194
1194
1195 if not state.keep:
1195 if not state.keep:
1196 if mapping:
1196 if mapping:
1197 movetopmostbookmarks(repo, state.topmost, ntm)
1197 movetopmostbookmarks(repo, state.topmost, ntm)
1198 # TODO update mq state
1198 # TODO update mq state
1199 else:
1199 else:
1200 mapping = {}
1200 mapping = {}
1201
1201
1202 for n in tmpnodes:
1202 for n in tmpnodes:
1203 mapping[n] = ()
1203 mapping[n] = ()
1204
1204
1205 # remove entries about unknown nodes
1205 # remove entries about unknown nodes
1206 nodemap = repo.unfiltered().changelog.nodemap
1206 nodemap = repo.unfiltered().changelog.nodemap
1207 mapping = {k: v for k, v in mapping.items()
1207 mapping = {k: v for k, v in mapping.items()
1208 if k in nodemap and all(n in nodemap for n in v)}
1208 if k in nodemap and all(n in nodemap for n in v)}
1209 scmutil.cleanupnodes(repo, mapping, 'histedit')
1209 scmutil.cleanupnodes(repo, mapping, 'histedit')
1210 hf = fm.hexfunc
1210 hf = fm.hexfunc
1211 fl = fm.formatlist
1211 fl = fm.formatlist
1212 fd = fm.formatdict
1212 fd = fm.formatdict
1213 nodechanges = fd({hf(oldn): fl([hf(n) for n in newn], name='node')
1213 nodechanges = fd({hf(oldn): fl([hf(n) for n in newn], name='node')
1214 for oldn, newn in mapping.iteritems()},
1214 for oldn, newn in mapping.iteritems()},
1215 key="oldnode", value="newnodes")
1215 key="oldnode", value="newnodes")
1216 fm.data(nodechanges=nodechanges)
1216 fm.data(nodechanges=nodechanges)
1217
1217
1218 state.clear()
1218 state.clear()
1219 if os.path.exists(repo.sjoin('undo')):
1219 if os.path.exists(repo.sjoin('undo')):
1220 os.unlink(repo.sjoin('undo'))
1220 os.unlink(repo.sjoin('undo'))
1221 if repo.vfs.exists('histedit-last-edit.txt'):
1221 if repo.vfs.exists('histedit-last-edit.txt'):
1222 repo.vfs.unlink('histedit-last-edit.txt')
1222 repo.vfs.unlink('histedit-last-edit.txt')
1223
1223
1224 def _aborthistedit(ui, repo, state):
1224 def _aborthistedit(ui, repo, state):
1225 try:
1225 try:
1226 state.read()
1226 state.read()
1227 __, leafs, tmpnodes, __ = processreplacement(state)
1227 __, leafs, tmpnodes, __ = processreplacement(state)
1228 ui.debug('restore wc to old parent %s\n'
1228 ui.debug('restore wc to old parent %s\n'
1229 % node.short(state.topmost))
1229 % node.short(state.topmost))
1230
1230
1231 # Recover our old commits if necessary
1231 # Recover our old commits if necessary
1232 if not state.topmost in repo and state.backupfile:
1232 if not state.topmost in repo and state.backupfile:
1233 backupfile = repo.vfs.join(state.backupfile)
1233 backupfile = repo.vfs.join(state.backupfile)
1234 f = hg.openpath(ui, backupfile)
1234 f = hg.openpath(ui, backupfile)
1235 gen = exchange.readbundle(ui, f, backupfile)
1235 gen = exchange.readbundle(ui, f, backupfile)
1236 with repo.transaction('histedit.abort') as tr:
1236 with repo.transaction('histedit.abort') as tr:
1237 bundle2.applybundle(repo, gen, tr, source='histedit',
1237 bundle2.applybundle(repo, gen, tr, source='histedit',
1238 url='bundle:' + backupfile)
1238 url='bundle:' + backupfile)
1239
1239
1240 os.remove(backupfile)
1240 os.remove(backupfile)
1241
1241
1242 # check whether we should update away
1242 # check whether we should update away
1243 if repo.unfiltered().revs('parents() and (%n or %ln::)',
1243 if repo.unfiltered().revs('parents() and (%n or %ln::)',
1244 state.parentctxnode, leafs | tmpnodes):
1244 state.parentctxnode, leafs | tmpnodes):
1245 hg.clean(repo, state.topmost, show_stats=True, quietempty=True)
1245 hg.clean(repo, state.topmost, show_stats=True, quietempty=True)
1246 cleanupnode(ui, repo, tmpnodes)
1246 cleanupnode(ui, repo, tmpnodes)
1247 cleanupnode(ui, repo, leafs)
1247 cleanupnode(ui, repo, leafs)
1248 except Exception:
1248 except Exception:
1249 if state.inprogress():
1249 if state.inprogress():
1250 ui.warn(_('warning: encountered an exception during histedit '
1250 ui.warn(_('warning: encountered an exception during histedit '
1251 '--abort; the repository may not have been completely '
1251 '--abort; the repository may not have been completely '
1252 'cleaned up\n'))
1252 'cleaned up\n'))
1253 raise
1253 raise
1254 finally:
1254 finally:
1255 state.clear()
1255 state.clear()
1256
1256
1257 def _edithisteditplan(ui, repo, state, rules):
1257 def _edithisteditplan(ui, repo, state, rules):
1258 state.read()
1258 state.read()
1259 if not rules:
1259 if not rules:
1260 comment = geteditcomment(ui,
1260 comment = geteditcomment(ui,
1261 node.short(state.parentctxnode),
1261 node.short(state.parentctxnode),
1262 node.short(state.topmost))
1262 node.short(state.topmost))
1263 rules = ruleeditor(repo, ui, state.actions, comment)
1263 rules = ruleeditor(repo, ui, state.actions, comment)
1264 else:
1264 else:
1265 rules = _readfile(ui, rules)
1265 rules = _readfile(ui, rules)
1266 actions = parserules(rules, state)
1266 actions = parserules(rules, state)
1267 ctxs = [repo[act.node] \
1267 ctxs = [repo[act.node] \
1268 for act in state.actions if act.node]
1268 for act in state.actions if act.node]
1269 warnverifyactions(ui, repo, actions, state, ctxs)
1269 warnverifyactions(ui, repo, actions, state, ctxs)
1270 state.actions = actions
1270 state.actions = actions
1271 state.write()
1271 state.write()
1272
1272
1273 def _newhistedit(ui, repo, state, revs, freeargs, opts):
1273 def _newhistedit(ui, repo, state, revs, freeargs, opts):
1274 outg = opts.get('outgoing')
1274 outg = opts.get('outgoing')
1275 rules = opts.get('commands', '')
1275 rules = opts.get('commands', '')
1276 force = opts.get('force')
1276 force = opts.get('force')
1277
1277
1278 cmdutil.checkunfinished(repo)
1278 cmdutil.checkunfinished(repo)
1279 cmdutil.bailifchanged(repo)
1279 cmdutil.bailifchanged(repo)
1280
1280
1281 topmost, empty = repo.dirstate.parents()
1281 topmost, empty = repo.dirstate.parents()
1282 if outg:
1282 if outg:
1283 if freeargs:
1283 if freeargs:
1284 remote = freeargs[0]
1284 remote = freeargs[0]
1285 else:
1285 else:
1286 remote = None
1286 remote = None
1287 root = findoutgoing(ui, repo, remote, force, opts)
1287 root = findoutgoing(ui, repo, remote, force, opts)
1288 else:
1288 else:
1289 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs)))
1289 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs)))
1290 if len(rr) != 1:
1290 if len(rr) != 1:
1291 raise error.Abort(_('The specified revisions must have '
1291 raise error.Abort(_('The specified revisions must have '
1292 'exactly one common root'))
1292 'exactly one common root'))
1293 root = rr[0].node()
1293 root = rr[0].node()
1294
1294
1295 revs = between(repo, root, topmost, state.keep)
1295 revs = between(repo, root, topmost, state.keep)
1296 if not revs:
1296 if not revs:
1297 raise error.Abort(_('%s is not an ancestor of working directory') %
1297 raise error.Abort(_('%s is not an ancestor of working directory') %
1298 node.short(root))
1298 node.short(root))
1299
1299
1300 ctxs = [repo[r] for r in revs]
1300 ctxs = [repo[r] for r in revs]
1301 if not rules:
1301 if not rules:
1302 comment = geteditcomment(ui, node.short(root), node.short(topmost))
1302 comment = geteditcomment(ui, node.short(root), node.short(topmost))
1303 actions = [pick(state, r) for r in revs]
1303 actions = [pick(state, r) for r in revs]
1304 rules = ruleeditor(repo, ui, actions, comment)
1304 rules = ruleeditor(repo, ui, actions, comment)
1305 else:
1305 else:
1306 rules = _readfile(ui, rules)
1306 rules = _readfile(ui, rules)
1307 actions = parserules(rules, state)
1307 actions = parserules(rules, state)
1308 warnverifyactions(ui, repo, actions, state, ctxs)
1308 warnverifyactions(ui, repo, actions, state, ctxs)
1309
1309
1310 parentctxnode = repo[root].parents()[0].node()
1310 parentctxnode = repo[root].parents()[0].node()
1311
1311
1312 state.parentctxnode = parentctxnode
1312 state.parentctxnode = parentctxnode
1313 state.actions = actions
1313 state.actions = actions
1314 state.topmost = topmost
1314 state.topmost = topmost
1315 state.replacements = []
1315 state.replacements = []
1316
1316
1317 ui.log("histedit", "%d actions to histedit", len(actions),
1317 ui.log("histedit", "%d actions to histedit", len(actions),
1318 histedit_num_actions=len(actions))
1318 histedit_num_actions=len(actions))
1319
1319
1320 # Create a backup so we can always abort completely.
1320 # Create a backup so we can always abort completely.
1321 backupfile = None
1321 backupfile = None
1322 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1322 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1323 backupfile = repair.backupbundle(repo, [parentctxnode],
1323 backupfile = repair.backupbundle(repo, [parentctxnode],
1324 [topmost], root, 'histedit')
1324 [topmost], root, 'histedit')
1325 state.backupfile = backupfile
1325 state.backupfile = backupfile
1326
1326
1327 def _getsummary(ctx):
1327 def _getsummary(ctx):
1328 # a common pattern is to extract the summary but default to the empty
1328 # a common pattern is to extract the summary but default to the empty
1329 # string
1329 # string
1330 summary = ctx.description() or ''
1330 summary = ctx.description() or ''
1331 if summary:
1331 if summary:
1332 summary = summary.splitlines()[0]
1332 summary = summary.splitlines()[0]
1333 return summary
1333 return summary
1334
1334
1335 def bootstrapcontinue(ui, state, opts):
1335 def bootstrapcontinue(ui, state, opts):
1336 repo = state.repo
1336 repo = state.repo
1337
1337
1338 ms = mergemod.mergestate.read(repo)
1338 ms = mergemod.mergestate.read(repo)
1339 mergeutil.checkunresolved(ms)
1339 mergeutil.checkunresolved(ms)
1340
1340
1341 if state.actions:
1341 if state.actions:
1342 actobj = state.actions.pop(0)
1342 actobj = state.actions.pop(0)
1343
1343
1344 if _isdirtywc(repo):
1344 if _isdirtywc(repo):
1345 actobj.continuedirty()
1345 actobj.continuedirty()
1346 if _isdirtywc(repo):
1346 if _isdirtywc(repo):
1347 abortdirty()
1347 abortdirty()
1348
1348
1349 parentctx, replacements = actobj.continueclean()
1349 parentctx, replacements = actobj.continueclean()
1350
1350
1351 state.parentctxnode = parentctx.node()
1351 state.parentctxnode = parentctx.node()
1352 state.replacements.extend(replacements)
1352 state.replacements.extend(replacements)
1353
1353
1354 return state
1354 return state
1355
1355
1356 def between(repo, old, new, keep):
1356 def between(repo, old, new, keep):
1357 """select and validate the set of revision to edit
1357 """select and validate the set of revision to edit
1358
1358
1359 When keep is false, the specified set can't have children."""
1359 When keep is false, the specified set can't have children."""
1360 revs = repo.revs('%n::%n', old, new)
1360 revs = repo.revs('%n::%n', old, new)
1361 if revs and not keep:
1361 if revs and not keep:
1362 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
1362 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
1363 repo.revs('(%ld::) - (%ld)', revs, revs)):
1363 repo.revs('(%ld::) - (%ld)', revs, revs)):
1364 raise error.Abort(_('can only histedit a changeset together '
1364 raise error.Abort(_('can only histedit a changeset together '
1365 'with all its descendants'))
1365 'with all its descendants'))
1366 if repo.revs('(%ld) and merge()', revs):
1366 if repo.revs('(%ld) and merge()', revs):
1367 raise error.Abort(_('cannot edit history that contains merges'))
1367 raise error.Abort(_('cannot edit history that contains merges'))
1368 root = repo[revs.first()] # list is already sorted by repo.revs()
1368 root = repo[revs.first()] # list is already sorted by repo.revs()
1369 if not root.mutable():
1369 if not root.mutable():
1370 raise error.Abort(_('cannot edit public changeset: %s') % root,
1370 raise error.Abort(_('cannot edit public changeset: %s') % root,
1371 hint=_("see 'hg help phases' for details"))
1371 hint=_("see 'hg help phases' for details"))
1372 return pycompat.maplist(repo.changelog.node, revs)
1372 return pycompat.maplist(repo.changelog.node, revs)
1373
1373
1374 def ruleeditor(repo, ui, actions, editcomment=""):
1374 def ruleeditor(repo, ui, actions, editcomment=""):
1375 """open an editor to edit rules
1375 """open an editor to edit rules
1376
1376
1377 rules are in the format [ [act, ctx], ...] like in state.rules
1377 rules are in the format [ [act, ctx], ...] like in state.rules
1378 """
1378 """
1379 if repo.ui.configbool("experimental", "histedit.autoverb"):
1379 if repo.ui.configbool("experimental", "histedit.autoverb"):
1380 newact = util.sortdict()
1380 newact = util.sortdict()
1381 for act in actions:
1381 for act in actions:
1382 ctx = repo[act.node]
1382 ctx = repo[act.node]
1383 summary = _getsummary(ctx)
1383 summary = _getsummary(ctx)
1384 fword = summary.split(' ', 1)[0].lower()
1384 fword = summary.split(' ', 1)[0].lower()
1385 added = False
1385 added = False
1386
1386
1387 # if it doesn't end with the special character '!' just skip this
1387 # if it doesn't end with the special character '!' just skip this
1388 if fword.endswith('!'):
1388 if fword.endswith('!'):
1389 fword = fword[:-1]
1389 fword = fword[:-1]
1390 if fword in primaryactions | secondaryactions | tertiaryactions:
1390 if fword in primaryactions | secondaryactions | tertiaryactions:
1391 act.verb = fword
1391 act.verb = fword
1392 # get the target summary
1392 # get the target summary
1393 tsum = summary[len(fword) + 1:].lstrip()
1393 tsum = summary[len(fword) + 1:].lstrip()
1394 # safe but slow: reverse iterate over the actions so we
1394 # safe but slow: reverse iterate over the actions so we
1395 # don't clash on two commits having the same summary
1395 # don't clash on two commits having the same summary
1396 for na, l in reversed(list(newact.iteritems())):
1396 for na, l in reversed(list(newact.iteritems())):
1397 actx = repo[na.node]
1397 actx = repo[na.node]
1398 asum = _getsummary(actx)
1398 asum = _getsummary(actx)
1399 if asum == tsum:
1399 if asum == tsum:
1400 added = True
1400 added = True
1401 l.append(act)
1401 l.append(act)
1402 break
1402 break
1403
1403
1404 if not added:
1404 if not added:
1405 newact[act] = []
1405 newact[act] = []
1406
1406
1407 # copy over and flatten the new list
1407 # copy over and flatten the new list
1408 actions = []
1408 actions = []
1409 for na, l in newact.iteritems():
1409 for na, l in newact.iteritems():
1410 actions.append(na)
1410 actions.append(na)
1411 actions += l
1411 actions += l
1412
1412
1413 rules = '\n'.join([act.torule() for act in actions])
1413 rules = '\n'.join([act.torule() for act in actions])
1414 rules += '\n\n'
1414 rules += '\n\n'
1415 rules += editcomment
1415 rules += editcomment
1416 rules = ui.edit(rules, ui.username(), {'prefix': 'histedit'},
1416 rules = ui.edit(rules, ui.username(), {'prefix': 'histedit'},
1417 repopath=repo.path, action='histedit')
1417 repopath=repo.path, action='histedit')
1418
1418
1419 # Save edit rules in .hg/histedit-last-edit.txt in case
1419 # Save edit rules in .hg/histedit-last-edit.txt in case
1420 # the user needs to ask for help after something
1420 # the user needs to ask for help after something
1421 # surprising happens.
1421 # surprising happens.
1422 with repo.vfs('histedit-last-edit.txt', 'wb') as f:
1422 with repo.vfs('histedit-last-edit.txt', 'wb') as f:
1423 f.write(rules)
1423 f.write(rules)
1424
1424
1425 return rules
1425 return rules
1426
1426
1427 def parserules(rules, state):
1427 def parserules(rules, state):
1428 """Read the histedit rules string and return list of action objects """
1428 """Read the histedit rules string and return list of action objects """
1429 rules = [l for l in (r.strip() for r in rules.splitlines())
1429 rules = [l for l in (r.strip() for r in rules.splitlines())
1430 if l and not l.startswith('#')]
1430 if l and not l.startswith('#')]
1431 actions = []
1431 actions = []
1432 for r in rules:
1432 for r in rules:
1433 if ' ' not in r:
1433 if ' ' not in r:
1434 raise error.ParseError(_('malformed line "%s"') % r)
1434 raise error.ParseError(_('malformed line "%s"') % r)
1435 verb, rest = r.split(' ', 1)
1435 verb, rest = r.split(' ', 1)
1436
1436
1437 if verb not in actiontable:
1437 if verb not in actiontable:
1438 raise error.ParseError(_('unknown action "%s"') % verb)
1438 raise error.ParseError(_('unknown action "%s"') % verb)
1439
1439
1440 action = actiontable[verb].fromrule(state, rest)
1440 action = actiontable[verb].fromrule(state, rest)
1441 actions.append(action)
1441 actions.append(action)
1442 return actions
1442 return actions
1443
1443
1444 def warnverifyactions(ui, repo, actions, state, ctxs):
1444 def warnverifyactions(ui, repo, actions, state, ctxs):
1445 try:
1445 try:
1446 verifyactions(actions, state, ctxs)
1446 verifyactions(actions, state, ctxs)
1447 except error.ParseError:
1447 except error.ParseError:
1448 if repo.vfs.exists('histedit-last-edit.txt'):
1448 if repo.vfs.exists('histedit-last-edit.txt'):
1449 ui.warn(_('warning: histedit rules saved '
1449 ui.warn(_('warning: histedit rules saved '
1450 'to: .hg/histedit-last-edit.txt\n'))
1450 'to: .hg/histedit-last-edit.txt\n'))
1451 raise
1451 raise
1452
1452
1453 def verifyactions(actions, state, ctxs):
1453 def verifyactions(actions, state, ctxs):
1454 """Verify that there exists exactly one action per given changeset and
1454 """Verify that there exists exactly one action per given changeset and
1455 other constraints.
1455 other constraints.
1456
1456
1457 Will abort if there are to many or too few rules, a malformed rule,
1457 Will abort if there are to many or too few rules, a malformed rule,
1458 or a rule on a changeset outside of the user-given range.
1458 or a rule on a changeset outside of the user-given range.
1459 """
1459 """
1460 expected = set(c.node() for c in ctxs)
1460 expected = set(c.node() for c in ctxs)
1461 seen = set()
1461 seen = set()
1462 prev = None
1462 prev = None
1463
1463
1464 if actions and actions[0].verb in ['roll', 'fold']:
1464 if actions and actions[0].verb in ['roll', 'fold']:
1465 raise error.ParseError(_('first changeset cannot use verb "%s"') %
1465 raise error.ParseError(_('first changeset cannot use verb "%s"') %
1466 actions[0].verb)
1466 actions[0].verb)
1467
1467
1468 for action in actions:
1468 for action in actions:
1469 action.verify(prev, expected, seen)
1469 action.verify(prev, expected, seen)
1470 prev = action
1470 prev = action
1471 if action.node is not None:
1471 if action.node is not None:
1472 seen.add(action.node)
1472 seen.add(action.node)
1473 missing = sorted(expected - seen) # sort to stabilize output
1473 missing = sorted(expected - seen) # sort to stabilize output
1474
1474
1475 if state.repo.ui.configbool('histedit', 'dropmissing'):
1475 if state.repo.ui.configbool('histedit', 'dropmissing'):
1476 if len(actions) == 0:
1476 if len(actions) == 0:
1477 raise error.ParseError(_('no rules provided'),
1477 raise error.ParseError(_('no rules provided'),
1478 hint=_('use strip extension to remove commits'))
1478 hint=_('use strip extension to remove commits'))
1479
1479
1480 drops = [drop(state, n) for n in missing]
1480 drops = [drop(state, n) for n in missing]
1481 # put the in the beginning so they execute immediately and
1481 # put the in the beginning so they execute immediately and
1482 # don't show in the edit-plan in the future
1482 # don't show in the edit-plan in the future
1483 actions[:0] = drops
1483 actions[:0] = drops
1484 elif missing:
1484 elif missing:
1485 raise error.ParseError(_('missing rules for changeset %s') %
1485 raise error.ParseError(_('missing rules for changeset %s') %
1486 node.short(missing[0]),
1486 node.short(missing[0]),
1487 hint=_('use "drop %s" to discard, see also: '
1487 hint=_('use "drop %s" to discard, see also: '
1488 "'hg help -e histedit.config'")
1488 "'hg help -e histedit.config'")
1489 % node.short(missing[0]))
1489 % node.short(missing[0]))
1490
1490
1491 def adjustreplacementsfrommarkers(repo, oldreplacements):
1491 def adjustreplacementsfrommarkers(repo, oldreplacements):
1492 """Adjust replacements from obsolescence markers
1492 """Adjust replacements from obsolescence markers
1493
1493
1494 Replacements structure is originally generated based on
1494 Replacements structure is originally generated based on
1495 histedit's state and does not account for changes that are
1495 histedit's state and does not account for changes that are
1496 not recorded there. This function fixes that by adding
1496 not recorded there. This function fixes that by adding
1497 data read from obsolescence markers"""
1497 data read from obsolescence markers"""
1498 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1498 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1499 return oldreplacements
1499 return oldreplacements
1500
1500
1501 unfi = repo.unfiltered()
1501 unfi = repo.unfiltered()
1502 nm = unfi.changelog.nodemap
1502 nm = unfi.changelog.nodemap
1503 obsstore = repo.obsstore
1503 obsstore = repo.obsstore
1504 newreplacements = list(oldreplacements)
1504 newreplacements = list(oldreplacements)
1505 oldsuccs = [r[1] for r in oldreplacements]
1505 oldsuccs = [r[1] for r in oldreplacements]
1506 # successors that have already been added to succstocheck once
1506 # successors that have already been added to succstocheck once
1507 seensuccs = set().union(*oldsuccs) # create a set from an iterable of tuples
1507 seensuccs = set().union(*oldsuccs) # create a set from an iterable of tuples
1508 succstocheck = list(seensuccs)
1508 succstocheck = list(seensuccs)
1509 while succstocheck:
1509 while succstocheck:
1510 n = succstocheck.pop()
1510 n = succstocheck.pop()
1511 missing = nm.get(n) is None
1511 missing = nm.get(n) is None
1512 markers = obsstore.successors.get(n, ())
1512 markers = obsstore.successors.get(n, ())
1513 if missing and not markers:
1513 if missing and not markers:
1514 # dead end, mark it as such
1514 # dead end, mark it as such
1515 newreplacements.append((n, ()))
1515 newreplacements.append((n, ()))
1516 for marker in markers:
1516 for marker in markers:
1517 nsuccs = marker[1]
1517 nsuccs = marker[1]
1518 newreplacements.append((n, nsuccs))
1518 newreplacements.append((n, nsuccs))
1519 for nsucc in nsuccs:
1519 for nsucc in nsuccs:
1520 if nsucc not in seensuccs:
1520 if nsucc not in seensuccs:
1521 seensuccs.add(nsucc)
1521 seensuccs.add(nsucc)
1522 succstocheck.append(nsucc)
1522 succstocheck.append(nsucc)
1523
1523
1524 return newreplacements
1524 return newreplacements
1525
1525
1526 def processreplacement(state):
1526 def processreplacement(state):
1527 """process the list of replacements to return
1527 """process the list of replacements to return
1528
1528
1529 1) the final mapping between original and created nodes
1529 1) the final mapping between original and created nodes
1530 2) the list of temporary node created by histedit
1530 2) the list of temporary node created by histedit
1531 3) the list of new commit created by histedit"""
1531 3) the list of new commit created by histedit"""
1532 replacements = adjustreplacementsfrommarkers(state.repo, state.replacements)
1532 replacements = adjustreplacementsfrommarkers(state.repo, state.replacements)
1533 allsuccs = set()
1533 allsuccs = set()
1534 replaced = set()
1534 replaced = set()
1535 fullmapping = {}
1535 fullmapping = {}
1536 # initialize basic set
1536 # initialize basic set
1537 # fullmapping records all operations recorded in replacement
1537 # fullmapping records all operations recorded in replacement
1538 for rep in replacements:
1538 for rep in replacements:
1539 allsuccs.update(rep[1])
1539 allsuccs.update(rep[1])
1540 replaced.add(rep[0])
1540 replaced.add(rep[0])
1541 fullmapping.setdefault(rep[0], set()).update(rep[1])
1541 fullmapping.setdefault(rep[0], set()).update(rep[1])
1542 new = allsuccs - replaced
1542 new = allsuccs - replaced
1543 tmpnodes = allsuccs & replaced
1543 tmpnodes = allsuccs & replaced
1544 # Reduce content fullmapping into direct relation between original nodes
1544 # Reduce content fullmapping into direct relation between original nodes
1545 # and final node created during history edition
1545 # and final node created during history edition
1546 # Dropped changeset are replaced by an empty list
1546 # Dropped changeset are replaced by an empty list
1547 toproceed = set(fullmapping)
1547 toproceed = set(fullmapping)
1548 final = {}
1548 final = {}
1549 while toproceed:
1549 while toproceed:
1550 for x in list(toproceed):
1550 for x in list(toproceed):
1551 succs = fullmapping[x]
1551 succs = fullmapping[x]
1552 for s in list(succs):
1552 for s in list(succs):
1553 if s in toproceed:
1553 if s in toproceed:
1554 # non final node with unknown closure
1554 # non final node with unknown closure
1555 # We can't process this now
1555 # We can't process this now
1556 break
1556 break
1557 elif s in final:
1557 elif s in final:
1558 # non final node, replace with closure
1558 # non final node, replace with closure
1559 succs.remove(s)
1559 succs.remove(s)
1560 succs.update(final[s])
1560 succs.update(final[s])
1561 else:
1561 else:
1562 final[x] = succs
1562 final[x] = succs
1563 toproceed.remove(x)
1563 toproceed.remove(x)
1564 # remove tmpnodes from final mapping
1564 # remove tmpnodes from final mapping
1565 for n in tmpnodes:
1565 for n in tmpnodes:
1566 del final[n]
1566 del final[n]
1567 # we expect all changes involved in final to exist in the repo
1567 # we expect all changes involved in final to exist in the repo
1568 # turn `final` into list (topologically sorted)
1568 # turn `final` into list (topologically sorted)
1569 nm = state.repo.changelog.nodemap
1569 nm = state.repo.changelog.nodemap
1570 for prec, succs in final.items():
1570 for prec, succs in final.items():
1571 final[prec] = sorted(succs, key=nm.get)
1571 final[prec] = sorted(succs, key=nm.get)
1572
1572
1573 # computed topmost element (necessary for bookmark)
1573 # computed topmost element (necessary for bookmark)
1574 if new:
1574 if new:
1575 newtopmost = sorted(new, key=state.repo.changelog.rev)[-1]
1575 newtopmost = sorted(new, key=state.repo.changelog.rev)[-1]
1576 elif not final:
1576 elif not final:
1577 # Nothing rewritten at all. we won't need `newtopmost`
1577 # Nothing rewritten at all. we won't need `newtopmost`
1578 # It is the same as `oldtopmost` and `processreplacement` know it
1578 # It is the same as `oldtopmost` and `processreplacement` know it
1579 newtopmost = None
1579 newtopmost = None
1580 else:
1580 else:
1581 # every body died. The newtopmost is the parent of the root.
1581 # every body died. The newtopmost is the parent of the root.
1582 r = state.repo.changelog.rev
1582 r = state.repo.changelog.rev
1583 newtopmost = state.repo[sorted(final, key=r)[0]].p1().node()
1583 newtopmost = state.repo[sorted(final, key=r)[0]].p1().node()
1584
1584
1585 return final, tmpnodes, new, newtopmost
1585 return final, tmpnodes, new, newtopmost
1586
1586
1587 def movetopmostbookmarks(repo, oldtopmost, newtopmost):
1587 def movetopmostbookmarks(repo, oldtopmost, newtopmost):
1588 """Move bookmark from oldtopmost to newly created topmost
1588 """Move bookmark from oldtopmost to newly created topmost
1589
1589
1590 This is arguably a feature and we may only want that for the active
1590 This is arguably a feature and we may only want that for the active
1591 bookmark. But the behavior is kept compatible with the old version for now.
1591 bookmark. But the behavior is kept compatible with the old version for now.
1592 """
1592 """
1593 if not oldtopmost or not newtopmost:
1593 if not oldtopmost or not newtopmost:
1594 return
1594 return
1595 oldbmarks = repo.nodebookmarks(oldtopmost)
1595 oldbmarks = repo.nodebookmarks(oldtopmost)
1596 if oldbmarks:
1596 if oldbmarks:
1597 with repo.lock(), repo.transaction('histedit') as tr:
1597 with repo.lock(), repo.transaction('histedit') as tr:
1598 marks = repo._bookmarks
1598 marks = repo._bookmarks
1599 changes = []
1599 changes = []
1600 for name in oldbmarks:
1600 for name in oldbmarks:
1601 changes.append((name, newtopmost))
1601 changes.append((name, newtopmost))
1602 marks.applychanges(repo, tr, changes)
1602 marks.applychanges(repo, tr, changes)
1603
1603
1604 def cleanupnode(ui, repo, nodes):
1604 def cleanupnode(ui, repo, nodes):
1605 """strip a group of nodes from the repository
1605 """strip a group of nodes from the repository
1606
1606
1607 The set of node to strip may contains unknown nodes."""
1607 The set of node to strip may contains unknown nodes."""
1608 with repo.lock():
1608 with repo.lock():
1609 # do not let filtering get in the way of the cleanse
1609 # do not let filtering get in the way of the cleanse
1610 # we should probably get rid of obsolescence marker created during the
1610 # we should probably get rid of obsolescence marker created during the
1611 # histedit, but we currently do not have such information.
1611 # histedit, but we currently do not have such information.
1612 repo = repo.unfiltered()
1612 repo = repo.unfiltered()
1613 # Find all nodes that need to be stripped
1613 # Find all nodes that need to be stripped
1614 # (we use %lr instead of %ln to silently ignore unknown items)
1614 # (we use %lr instead of %ln to silently ignore unknown items)
1615 nm = repo.changelog.nodemap
1615 nm = repo.changelog.nodemap
1616 nodes = sorted(n for n in nodes if n in nm)
1616 nodes = sorted(n for n in nodes if n in nm)
1617 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
1617 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
1618 if roots:
1618 if roots:
1619 repair.strip(ui, repo, roots)
1619 repair.strip(ui, repo, roots)
1620
1620
1621 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
1621 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
1622 if isinstance(nodelist, str):
1622 if isinstance(nodelist, str):
1623 nodelist = [nodelist]
1623 nodelist = [nodelist]
1624 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
1624 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
1625 state = histeditstate(repo)
1625 state = histeditstate(repo)
1626 state.read()
1626 state.read()
1627 histedit_nodes = {action.node for action
1627 histedit_nodes = {action.node for action
1628 in state.actions if action.node}
1628 in state.actions if action.node}
1629 common_nodes = histedit_nodes & set(nodelist)
1629 common_nodes = histedit_nodes & set(nodelist)
1630 if common_nodes:
1630 if common_nodes:
1631 raise error.Abort(_("histedit in progress, can't strip %s")
1631 raise error.Abort(_("histedit in progress, can't strip %s")
1632 % ', '.join(node.short(x) for x in common_nodes))
1632 % ', '.join(node.short(x) for x in common_nodes))
1633 return orig(ui, repo, nodelist, *args, **kwargs)
1633 return orig(ui, repo, nodelist, *args, **kwargs)
1634
1634
1635 extensions.wrapfunction(repair, 'strip', stripwrapper)
1635 extensions.wrapfunction(repair, 'strip', stripwrapper)
1636
1636
1637 def summaryhook(ui, repo):
1637 def summaryhook(ui, repo):
1638 if not os.path.exists(repo.vfs.join('histedit-state')):
1638 if not os.path.exists(repo.vfs.join('histedit-state')):
1639 return
1639 return
1640 state = histeditstate(repo)
1640 state = histeditstate(repo)
1641 state.read()
1641 state.read()
1642 if state.actions:
1642 if state.actions:
1643 # i18n: column positioning for "hg summary"
1643 # i18n: column positioning for "hg summary"
1644 ui.write(_('hist: %s (histedit --continue)\n') %
1644 ui.write(_('hist: %s (histedit --continue)\n') %
1645 (ui.label(_('%d remaining'), 'histedit.remaining') %
1645 (ui.label(_('%d remaining'), 'histedit.remaining') %
1646 len(state.actions)))
1646 len(state.actions)))
1647
1647
1648 def extsetup(ui):
1648 def extsetup(ui):
1649 cmdutil.summaryhooks.add('histedit', summaryhook)
1649 cmdutil.summaryhooks.add('histedit', summaryhook)
1650 cmdutil.unfinishedstates.append(
1650 cmdutil.unfinishedstates.append(
1651 ['histedit-state', False, True, _('histedit in progress'),
1651 ['histedit-state', False, True, _('histedit in progress'),
1652 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
1652 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
1653 cmdutil.afterresolvedstates.append(
1653 cmdutil.afterresolvedstates.append(
1654 ['histedit-state', _('hg histedit --continue')])
1654 ['histedit-state', _('hg histedit --continue')])
@@ -1,1888 +1,1888 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 lock,
37 lock,
38 merge as mergemod,
38 merge as mergemod,
39 mergeutil,
39 mergeutil,
40 obsolete,
40 obsolete,
41 obsutil,
41 obsutil,
42 patch,
42 patch,
43 phases,
43 phases,
44 pycompat,
44 pycompat,
45 registrar,
45 registrar,
46 repair,
46 repair,
47 revset,
47 revset,
48 revsetlang,
48 revsetlang,
49 scmutil,
49 scmutil,
50 smartset,
50 smartset,
51 util,
51 util,
52 )
52 )
53
53
54 release = lock.release
54 release = lock.release
55
55
56 # The following constants are used throughout the rebase module. The ordering of
56 # The following constants are used throughout the rebase module. The ordering of
57 # their values must be maintained.
57 # their values must be maintained.
58
58
59 # Indicates that a revision needs to be rebased
59 # Indicates that a revision needs to be rebased
60 revtodo = -1
60 revtodo = -1
61 revtodostr = '-1'
61 revtodostr = '-1'
62
62
63 # legacy revstates no longer needed in current code
63 # legacy revstates no longer needed in current code
64 # -2: nullmerge, -3: revignored, -4: revprecursor, -5: revpruned
64 # -2: nullmerge, -3: revignored, -4: revprecursor, -5: revpruned
65 legacystates = {'-2', '-3', '-4', '-5'}
65 legacystates = {'-2', '-3', '-4', '-5'}
66
66
67 cmdtable = {}
67 cmdtable = {}
68 command = registrar.command(cmdtable)
68 command = registrar.command(cmdtable)
69 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
69 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
70 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
70 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
71 # be specifying the version(s) of Mercurial they are tested with, or
71 # be specifying the version(s) of Mercurial they are tested with, or
72 # leave the attribute unspecified.
72 # leave the attribute unspecified.
73 testedwith = 'ships-with-hg-core'
73 testedwith = 'ships-with-hg-core'
74
74
75 def _nothingtorebase():
75 def _nothingtorebase():
76 return 1
76 return 1
77
77
78 def _savegraft(ctx, extra):
78 def _savegraft(ctx, extra):
79 s = ctx.extra().get('source', None)
79 s = ctx.extra().get('source', None)
80 if s is not None:
80 if s is not None:
81 extra['source'] = s
81 extra['source'] = s
82 s = ctx.extra().get('intermediate-source', None)
82 s = ctx.extra().get('intermediate-source', None)
83 if s is not None:
83 if s is not None:
84 extra['intermediate-source'] = s
84 extra['intermediate-source'] = s
85
85
86 def _savebranch(ctx, extra):
86 def _savebranch(ctx, extra):
87 extra['branch'] = ctx.branch()
87 extra['branch'] = ctx.branch()
88
88
89 def _destrebase(repo, sourceset, destspace=None):
89 def _destrebase(repo, sourceset, destspace=None):
90 """small wrapper around destmerge to pass the right extra args
90 """small wrapper around destmerge to pass the right extra args
91
91
92 Please wrap destutil.destmerge instead."""
92 Please wrap destutil.destmerge instead."""
93 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
93 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
94 onheadcheck=False, destspace=destspace)
94 onheadcheck=False, destspace=destspace)
95
95
96 revsetpredicate = registrar.revsetpredicate()
96 revsetpredicate = registrar.revsetpredicate()
97
97
98 @revsetpredicate('_destrebase')
98 @revsetpredicate('_destrebase')
99 def _revsetdestrebase(repo, subset, x):
99 def _revsetdestrebase(repo, subset, x):
100 # ``_rebasedefaultdest()``
100 # ``_rebasedefaultdest()``
101
101
102 # default destination for rebase.
102 # default destination for rebase.
103 # # XXX: Currently private because I expect the signature to change.
103 # # XXX: Currently private because I expect the signature to change.
104 # # XXX: - bailing out in case of ambiguity vs returning all data.
104 # # XXX: - bailing out in case of ambiguity vs returning all data.
105 # i18n: "_rebasedefaultdest" is a keyword
105 # i18n: "_rebasedefaultdest" is a keyword
106 sourceset = None
106 sourceset = None
107 if x is not None:
107 if x is not None:
108 sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
108 sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
109 return subset & smartset.baseset([_destrebase(repo, sourceset)])
109 return subset & smartset.baseset([_destrebase(repo, sourceset)])
110
110
111 @revsetpredicate('_destautoorphanrebase')
111 @revsetpredicate('_destautoorphanrebase')
112 def _revsetdestautoorphanrebase(repo, subset, x):
112 def _revsetdestautoorphanrebase(repo, subset, x):
113 """automatic rebase destination for a single orphan revision"""
113 """automatic rebase destination for a single orphan revision"""
114 unfi = repo.unfiltered()
114 unfi = repo.unfiltered()
115 obsoleted = unfi.revs('obsolete()')
115 obsoleted = unfi.revs('obsolete()')
116
116
117 src = revset.getset(repo, subset, x).first()
117 src = revset.getset(repo, subset, x).first()
118
118
119 # Empty src or already obsoleted - Do not return a destination
119 # Empty src or already obsoleted - Do not return a destination
120 if not src or src in obsoleted:
120 if not src or src in obsoleted:
121 return smartset.baseset()
121 return smartset.baseset()
122 dests = destutil.orphanpossibledestination(repo, src)
122 dests = destutil.orphanpossibledestination(repo, src)
123 if len(dests) > 1:
123 if len(dests) > 1:
124 raise error.Abort(
124 raise error.Abort(
125 _("ambiguous automatic rebase: %r could end up on any of %r") % (
125 _("ambiguous automatic rebase: %r could end up on any of %r") % (
126 src, dests))
126 src, dests))
127 # We have zero or one destination, so we can just return here.
127 # We have zero or one destination, so we can just return here.
128 return smartset.baseset(dests)
128 return smartset.baseset(dests)
129
129
130 def _ctxdesc(ctx):
130 def _ctxdesc(ctx):
131 """short description for a context"""
131 """short description for a context"""
132 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
132 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
133 ctx.description().split('\n', 1)[0])
133 ctx.description().split('\n', 1)[0])
134 repo = ctx.repo()
134 repo = ctx.repo()
135 names = []
135 names = []
136 for nsname, ns in repo.names.iteritems():
136 for nsname, ns in repo.names.iteritems():
137 if nsname == 'branches':
137 if nsname == 'branches':
138 continue
138 continue
139 names.extend(ns.names(repo, ctx.node()))
139 names.extend(ns.names(repo, ctx.node()))
140 if names:
140 if names:
141 desc += ' (%s)' % ' '.join(names)
141 desc += ' (%s)' % ' '.join(names)
142 return desc
142 return desc
143
143
144 class rebaseruntime(object):
144 class rebaseruntime(object):
145 """This class is a container for rebase runtime state"""
145 """This class is a container for rebase runtime state"""
146 def __init__(self, repo, ui, inmemory=False, opts=None):
146 def __init__(self, repo, ui, inmemory=False, opts=None):
147 if opts is None:
147 if opts is None:
148 opts = {}
148 opts = {}
149
149
150 # prepared: whether we have rebasestate prepared or not. Currently it
150 # prepared: whether we have rebasestate prepared or not. Currently it
151 # decides whether "self.repo" is unfiltered or not.
151 # decides whether "self.repo" is unfiltered or not.
152 # The rebasestate has explicit hash to hash instructions not depending
152 # The rebasestate has explicit hash to hash instructions not depending
153 # on visibility. If rebasestate exists (in-memory or on-disk), use
153 # on visibility. If rebasestate exists (in-memory or on-disk), use
154 # unfiltered repo to avoid visibility issues.
154 # unfiltered repo to avoid visibility issues.
155 # Before knowing rebasestate (i.e. when starting a new rebase (not
155 # Before knowing rebasestate (i.e. when starting a new rebase (not
156 # --continue or --abort)), the original repo should be used so
156 # --continue or --abort)), the original repo should be used so
157 # visibility-dependent revsets are correct.
157 # visibility-dependent revsets are correct.
158 self.prepared = False
158 self.prepared = False
159 self._repo = repo
159 self._repo = repo
160
160
161 self.ui = ui
161 self.ui = ui
162 self.opts = opts
162 self.opts = opts
163 self.originalwd = None
163 self.originalwd = None
164 self.external = nullrev
164 self.external = nullrev
165 # Mapping between the old revision id and either what is the new rebased
165 # Mapping between the old revision id and either what is the new rebased
166 # revision or what needs to be done with the old revision. The state
166 # revision or what needs to be done with the old revision. The state
167 # dict will be what contains most of the rebase progress state.
167 # dict will be what contains most of the rebase progress state.
168 self.state = {}
168 self.state = {}
169 self.activebookmark = None
169 self.activebookmark = None
170 self.destmap = {}
170 self.destmap = {}
171 self.skipped = set()
171 self.skipped = set()
172
172
173 self.collapsef = opts.get('collapse', False)
173 self.collapsef = opts.get('collapse', False)
174 self.collapsemsg = cmdutil.logmessage(ui, opts)
174 self.collapsemsg = cmdutil.logmessage(ui, opts)
175 self.date = opts.get('date', None)
175 self.date = opts.get('date', None)
176
176
177 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
177 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
178 self.extrafns = [_savegraft]
178 self.extrafns = [_savegraft]
179 if e:
179 if e:
180 self.extrafns = [e]
180 self.extrafns = [e]
181
181
182 self.keepf = opts.get('keep', False)
182 self.keepf = opts.get('keep', False)
183 self.keepbranchesf = opts.get('keepbranches', False)
183 self.keepbranchesf = opts.get('keepbranches', False)
184 self.obsoletenotrebased = {}
184 self.obsoletenotrebased = {}
185 self.obsoletewithoutsuccessorindestination = set()
185 self.obsoletewithoutsuccessorindestination = set()
186 self.inmemory = inmemory
186 self.inmemory = inmemory
187
187
188 @property
188 @property
189 def repo(self):
189 def repo(self):
190 if self.prepared:
190 if self.prepared:
191 return self._repo.unfiltered()
191 return self._repo.unfiltered()
192 else:
192 else:
193 return self._repo
193 return self._repo
194
194
195 def storestatus(self, tr=None):
195 def storestatus(self, tr=None):
196 """Store the current status to allow recovery"""
196 """Store the current status to allow recovery"""
197 if tr:
197 if tr:
198 tr.addfilegenerator('rebasestate', ('rebasestate',),
198 tr.addfilegenerator('rebasestate', ('rebasestate',),
199 self._writestatus, location='plain')
199 self._writestatus, location='plain')
200 else:
200 else:
201 with self.repo.vfs("rebasestate", "w") as f:
201 with self.repo.vfs("rebasestate", "w") as f:
202 self._writestatus(f)
202 self._writestatus(f)
203
203
204 def _writestatus(self, f):
204 def _writestatus(self, f):
205 repo = self.repo
205 repo = self.repo
206 assert repo.filtername is None
206 assert repo.filtername is None
207 f.write(repo[self.originalwd].hex() + '\n')
207 f.write(repo[self.originalwd].hex() + '\n')
208 # was "dest". we now write dest per src root below.
208 # was "dest". we now write dest per src root below.
209 f.write('\n')
209 f.write('\n')
210 f.write(repo[self.external].hex() + '\n')
210 f.write(repo[self.external].hex() + '\n')
211 f.write('%d\n' % int(self.collapsef))
211 f.write('%d\n' % int(self.collapsef))
212 f.write('%d\n' % int(self.keepf))
212 f.write('%d\n' % int(self.keepf))
213 f.write('%d\n' % int(self.keepbranchesf))
213 f.write('%d\n' % int(self.keepbranchesf))
214 f.write('%s\n' % (self.activebookmark or ''))
214 f.write('%s\n' % (self.activebookmark or ''))
215 destmap = self.destmap
215 destmap = self.destmap
216 for d, v in self.state.iteritems():
216 for d, v in self.state.iteritems():
217 oldrev = repo[d].hex()
217 oldrev = repo[d].hex()
218 if v >= 0:
218 if v >= 0:
219 newrev = repo[v].hex()
219 newrev = repo[v].hex()
220 else:
220 else:
221 newrev = "%d" % v
221 newrev = "%d" % v
222 destnode = repo[destmap[d]].hex()
222 destnode = repo[destmap[d]].hex()
223 f.write("%s:%s:%s\n" % (oldrev, newrev, destnode))
223 f.write("%s:%s:%s\n" % (oldrev, newrev, destnode))
224 repo.ui.debug('rebase status stored\n')
224 repo.ui.debug('rebase status stored\n')
225
225
226 def restorestatus(self):
226 def restorestatus(self):
227 """Restore a previously stored status"""
227 """Restore a previously stored status"""
228 self.prepared = True
228 self.prepared = True
229 repo = self.repo
229 repo = self.repo
230 assert repo.filtername is None
230 assert repo.filtername is None
231 keepbranches = None
231 keepbranches = None
232 legacydest = None
232 legacydest = None
233 collapse = False
233 collapse = False
234 external = nullrev
234 external = nullrev
235 activebookmark = None
235 activebookmark = None
236 state = {}
236 state = {}
237 destmap = {}
237 destmap = {}
238
238
239 try:
239 try:
240 f = repo.vfs("rebasestate")
240 f = repo.vfs("rebasestate")
241 for i, l in enumerate(f.read().splitlines()):
241 for i, l in enumerate(f.read().splitlines()):
242 if i == 0:
242 if i == 0:
243 originalwd = repo[l].rev()
243 originalwd = repo[l].rev()
244 elif i == 1:
244 elif i == 1:
245 # this line should be empty in newer version. but legacy
245 # this line should be empty in newer version. but legacy
246 # clients may still use it
246 # clients may still use it
247 if l:
247 if l:
248 legacydest = repo[l].rev()
248 legacydest = repo[l].rev()
249 elif i == 2:
249 elif i == 2:
250 external = repo[l].rev()
250 external = repo[l].rev()
251 elif i == 3:
251 elif i == 3:
252 collapse = bool(int(l))
252 collapse = bool(int(l))
253 elif i == 4:
253 elif i == 4:
254 keep = bool(int(l))
254 keep = bool(int(l))
255 elif i == 5:
255 elif i == 5:
256 keepbranches = bool(int(l))
256 keepbranches = bool(int(l))
257 elif i == 6 and not (len(l) == 81 and ':' in l):
257 elif i == 6 and not (len(l) == 81 and ':' in l):
258 # line 6 is a recent addition, so for backwards
258 # line 6 is a recent addition, so for backwards
259 # compatibility check that the line doesn't look like the
259 # compatibility check that the line doesn't look like the
260 # oldrev:newrev lines
260 # oldrev:newrev lines
261 activebookmark = l
261 activebookmark = l
262 else:
262 else:
263 args = l.split(':')
263 args = l.split(':')
264 oldrev = repo[args[0]].rev()
264 oldrev = repo[args[0]].rev()
265 newrev = args[1]
265 newrev = args[1]
266 if newrev in legacystates:
266 if newrev in legacystates:
267 continue
267 continue
268 if len(args) > 2:
268 if len(args) > 2:
269 destrev = repo[args[2]].rev()
269 destrev = repo[args[2]].rev()
270 else:
270 else:
271 destrev = legacydest
271 destrev = legacydest
272 destmap[oldrev] = destrev
272 destmap[oldrev] = destrev
273 if newrev == revtodostr:
273 if newrev == revtodostr:
274 state[oldrev] = revtodo
274 state[oldrev] = revtodo
275 # Legacy compat special case
275 # Legacy compat special case
276 else:
276 else:
277 state[oldrev] = repo[newrev].rev()
277 state[oldrev] = repo[newrev].rev()
278
278
279 except IOError as err:
279 except IOError as err:
280 if err.errno != errno.ENOENT:
280 if err.errno != errno.ENOENT:
281 raise
281 raise
282 cmdutil.wrongtooltocontinue(repo, _('rebase'))
282 cmdutil.wrongtooltocontinue(repo, _('rebase'))
283
283
284 if keepbranches is None:
284 if keepbranches is None:
285 raise error.Abort(_('.hg/rebasestate is incomplete'))
285 raise error.Abort(_('.hg/rebasestate is incomplete'))
286
286
287 skipped = set()
287 skipped = set()
288 # recompute the set of skipped revs
288 # recompute the set of skipped revs
289 if not collapse:
289 if not collapse:
290 seen = set(destmap.values())
290 seen = set(destmap.values())
291 for old, new in sorted(state.items()):
291 for old, new in sorted(state.items()):
292 if new != revtodo and new in seen:
292 if new != revtodo and new in seen:
293 skipped.add(old)
293 skipped.add(old)
294 seen.add(new)
294 seen.add(new)
295 repo.ui.debug('computed skipped revs: %s\n' %
295 repo.ui.debug('computed skipped revs: %s\n' %
296 (' '.join('%d' % r for r in sorted(skipped)) or ''))
296 (' '.join('%d' % r for r in sorted(skipped)) or ''))
297 repo.ui.debug('rebase status resumed\n')
297 repo.ui.debug('rebase status resumed\n')
298
298
299 self.originalwd = originalwd
299 self.originalwd = originalwd
300 self.destmap = destmap
300 self.destmap = destmap
301 self.state = state
301 self.state = state
302 self.skipped = skipped
302 self.skipped = skipped
303 self.collapsef = collapse
303 self.collapsef = collapse
304 self.keepf = keep
304 self.keepf = keep
305 self.keepbranchesf = keepbranches
305 self.keepbranchesf = keepbranches
306 self.external = external
306 self.external = external
307 self.activebookmark = activebookmark
307 self.activebookmark = activebookmark
308
308
309 def _handleskippingobsolete(self, obsoleterevs, destmap):
309 def _handleskippingobsolete(self, obsoleterevs, destmap):
310 """Compute structures necessary for skipping obsolete revisions
310 """Compute structures necessary for skipping obsolete revisions
311
311
312 obsoleterevs: iterable of all obsolete revisions in rebaseset
312 obsoleterevs: iterable of all obsolete revisions in rebaseset
313 destmap: {srcrev: destrev} destination revisions
313 destmap: {srcrev: destrev} destination revisions
314 """
314 """
315 self.obsoletenotrebased = {}
315 self.obsoletenotrebased = {}
316 if not self.ui.configbool('experimental', 'rebaseskipobsolete'):
316 if not self.ui.configbool('experimental', 'rebaseskipobsolete'):
317 return
317 return
318 obsoleteset = set(obsoleterevs)
318 obsoleteset = set(obsoleterevs)
319 (self.obsoletenotrebased,
319 (self.obsoletenotrebased,
320 self.obsoletewithoutsuccessorindestination,
320 self.obsoletewithoutsuccessorindestination,
321 obsoleteextinctsuccessors) = _computeobsoletenotrebased(
321 obsoleteextinctsuccessors) = _computeobsoletenotrebased(
322 self.repo, obsoleteset, destmap)
322 self.repo, obsoleteset, destmap)
323 skippedset = set(self.obsoletenotrebased)
323 skippedset = set(self.obsoletenotrebased)
324 skippedset.update(self.obsoletewithoutsuccessorindestination)
324 skippedset.update(self.obsoletewithoutsuccessorindestination)
325 skippedset.update(obsoleteextinctsuccessors)
325 skippedset.update(obsoleteextinctsuccessors)
326 _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
326 _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
327
327
328 def _prepareabortorcontinue(self, isabort, backup=True, suppwarns=False):
328 def _prepareabortorcontinue(self, isabort, backup=True, suppwarns=False):
329 try:
329 try:
330 self.restorestatus()
330 self.restorestatus()
331 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
331 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
332 except error.RepoLookupError:
332 except error.RepoLookupError:
333 if isabort:
333 if isabort:
334 clearstatus(self.repo)
334 clearstatus(self.repo)
335 clearcollapsemsg(self.repo)
335 clearcollapsemsg(self.repo)
336 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
336 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
337 ' only broken state is cleared)\n'))
337 ' only broken state is cleared)\n'))
338 return 0
338 return 0
339 else:
339 else:
340 msg = _('cannot continue inconsistent rebase')
340 msg = _('cannot continue inconsistent rebase')
341 hint = _('use "hg rebase --abort" to clear broken state')
341 hint = _('use "hg rebase --abort" to clear broken state')
342 raise error.Abort(msg, hint=hint)
342 raise error.Abort(msg, hint=hint)
343 if isabort:
343 if isabort:
344 return abort(self.repo, self.originalwd, self.destmap, self.state,
344 return abort(self.repo, self.originalwd, self.destmap, self.state,
345 activebookmark=self.activebookmark, backup=backup,
345 activebookmark=self.activebookmark, backup=backup,
346 suppwarns=suppwarns)
346 suppwarns=suppwarns)
347
347
348 def _preparenewrebase(self, destmap):
348 def _preparenewrebase(self, destmap):
349 if not destmap:
349 if not destmap:
350 return _nothingtorebase()
350 return _nothingtorebase()
351
351
352 rebaseset = destmap.keys()
352 rebaseset = destmap.keys()
353 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
353 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
354 if (not (self.keepf or allowunstable)
354 if (not (self.keepf or allowunstable)
355 and self.repo.revs('first(children(%ld) - %ld)',
355 and self.repo.revs('first(children(%ld) - %ld)',
356 rebaseset, rebaseset)):
356 rebaseset, rebaseset)):
357 raise error.Abort(
357 raise error.Abort(
358 _("can't remove original changesets with"
358 _("can't remove original changesets with"
359 " unrebased descendants"),
359 " unrebased descendants"),
360 hint=_('use --keep to keep original changesets'))
360 hint=_('use --keep to keep original changesets'))
361
361
362 result = buildstate(self.repo, destmap, self.collapsef)
362 result = buildstate(self.repo, destmap, self.collapsef)
363
363
364 if not result:
364 if not result:
365 # Empty state built, nothing to rebase
365 # Empty state built, nothing to rebase
366 self.ui.status(_('nothing to rebase\n'))
366 self.ui.status(_('nothing to rebase\n'))
367 return _nothingtorebase()
367 return _nothingtorebase()
368
368
369 for root in self.repo.set('roots(%ld)', rebaseset):
369 for root in self.repo.set('roots(%ld)', rebaseset):
370 if not self.keepf and not root.mutable():
370 if not self.keepf and not root.mutable():
371 raise error.Abort(_("can't rebase public changeset %s")
371 raise error.Abort(_("can't rebase public changeset %s")
372 % root,
372 % root,
373 hint=_("see 'hg help phases' for details"))
373 hint=_("see 'hg help phases' for details"))
374
374
375 (self.originalwd, self.destmap, self.state) = result
375 (self.originalwd, self.destmap, self.state) = result
376 if self.collapsef:
376 if self.collapsef:
377 dests = set(self.destmap.values())
377 dests = set(self.destmap.values())
378 if len(dests) != 1:
378 if len(dests) != 1:
379 raise error.Abort(
379 raise error.Abort(
380 _('--collapse does not work with multiple destinations'))
380 _('--collapse does not work with multiple destinations'))
381 destrev = next(iter(dests))
381 destrev = next(iter(dests))
382 destancestors = self.repo.changelog.ancestors([destrev],
382 destancestors = self.repo.changelog.ancestors([destrev],
383 inclusive=True)
383 inclusive=True)
384 self.external = externalparent(self.repo, self.state, destancestors)
384 self.external = externalparent(self.repo, self.state, destancestors)
385
385
386 for destrev in sorted(set(destmap.values())):
386 for destrev in sorted(set(destmap.values())):
387 dest = self.repo[destrev]
387 dest = self.repo[destrev]
388 if dest.closesbranch() and not self.keepbranchesf:
388 if dest.closesbranch() and not self.keepbranchesf:
389 self.ui.status(_('reopening closed branch head %s\n') % dest)
389 self.ui.status(_('reopening closed branch head %s\n') % dest)
390
390
391 self.prepared = True
391 self.prepared = True
392
392
393 def _assignworkingcopy(self):
393 def _assignworkingcopy(self):
394 if self.inmemory:
394 if self.inmemory:
395 from mercurial.context import overlayworkingctx
395 from mercurial.context import overlayworkingctx
396 self.wctx = overlayworkingctx(self.repo)
396 self.wctx = overlayworkingctx(self.repo)
397 self.repo.ui.debug("rebasing in-memory\n")
397 self.repo.ui.debug("rebasing in-memory\n")
398 else:
398 else:
399 self.wctx = self.repo[None]
399 self.wctx = self.repo[None]
400 self.repo.ui.debug("rebasing on disk\n")
400 self.repo.ui.debug("rebasing on disk\n")
401 self.repo.ui.log("rebase", "", rebase_imm_used=self.inmemory)
401 self.repo.ui.log("rebase", "", rebase_imm_used=self.inmemory)
402
402
403 def _performrebase(self, tr):
403 def _performrebase(self, tr):
404 self._assignworkingcopy()
404 self._assignworkingcopy()
405 repo, ui = self.repo, self.ui
405 repo, ui = self.repo, self.ui
406 if self.keepbranchesf:
406 if self.keepbranchesf:
407 # insert _savebranch at the start of extrafns so if
407 # insert _savebranch at the start of extrafns so if
408 # there's a user-provided extrafn it can clobber branch if
408 # there's a user-provided extrafn it can clobber branch if
409 # desired
409 # desired
410 self.extrafns.insert(0, _savebranch)
410 self.extrafns.insert(0, _savebranch)
411 if self.collapsef:
411 if self.collapsef:
412 branches = set()
412 branches = set()
413 for rev in self.state:
413 for rev in self.state:
414 branches.add(repo[rev].branch())
414 branches.add(repo[rev].branch())
415 if len(branches) > 1:
415 if len(branches) > 1:
416 raise error.Abort(_('cannot collapse multiple named '
416 raise error.Abort(_('cannot collapse multiple named '
417 'branches'))
417 'branches'))
418
418
419 # Calculate self.obsoletenotrebased
419 # Calculate self.obsoletenotrebased
420 obsrevs = _filterobsoleterevs(self.repo, self.state)
420 obsrevs = _filterobsoleterevs(self.repo, self.state)
421 self._handleskippingobsolete(obsrevs, self.destmap)
421 self._handleskippingobsolete(obsrevs, self.destmap)
422
422
423 # Keep track of the active bookmarks in order to reset them later
423 # Keep track of the active bookmarks in order to reset them later
424 self.activebookmark = self.activebookmark or repo._activebookmark
424 self.activebookmark = self.activebookmark or repo._activebookmark
425 if self.activebookmark:
425 if self.activebookmark:
426 bookmarks.deactivate(repo)
426 bookmarks.deactivate(repo)
427
427
428 # Store the state before we begin so users can run 'hg rebase --abort'
428 # Store the state before we begin so users can run 'hg rebase --abort'
429 # if we fail before the transaction closes.
429 # if we fail before the transaction closes.
430 self.storestatus()
430 self.storestatus()
431 if tr:
431 if tr:
432 # When using single transaction, store state when transaction
432 # When using single transaction, store state when transaction
433 # commits.
433 # commits.
434 self.storestatus(tr)
434 self.storestatus(tr)
435
435
436 cands = [k for k, v in self.state.iteritems() if v == revtodo]
436 cands = [k for k, v in self.state.iteritems() if v == revtodo]
437 p = repo.ui.makeprogress(_("rebasing"), unit=_('changesets'),
437 p = repo.ui.makeprogress(_("rebasing"), unit=_('changesets'),
438 total=len(cands))
438 total=len(cands))
439 def progress(ctx):
439 def progress(ctx):
440 p.increment(item=("%d:%s" % (ctx.rev(), ctx)))
440 p.increment(item=("%d:%s" % (ctx.rev(), ctx)))
441 allowdivergence = self.ui.configbool(
441 allowdivergence = self.ui.configbool(
442 'experimental', 'evolution.allowdivergence')
442 'experimental', 'evolution.allowdivergence')
443 for subset in sortsource(self.destmap):
443 for subset in sortsource(self.destmap):
444 sortedrevs = self.repo.revs('sort(%ld, -topo)', subset)
444 sortedrevs = self.repo.revs('sort(%ld, -topo)', subset)
445 if not allowdivergence:
445 if not allowdivergence:
446 sortedrevs -= self.repo.revs(
446 sortedrevs -= self.repo.revs(
447 'descendants(%ld) and not %ld',
447 'descendants(%ld) and not %ld',
448 self.obsoletewithoutsuccessorindestination,
448 self.obsoletewithoutsuccessorindestination,
449 self.obsoletewithoutsuccessorindestination,
449 self.obsoletewithoutsuccessorindestination,
450 )
450 )
451 for rev in sortedrevs:
451 for rev in sortedrevs:
452 self._rebasenode(tr, rev, allowdivergence, progress)
452 self._rebasenode(tr, rev, allowdivergence, progress)
453 p.complete()
453 p.complete()
454 ui.note(_('rebase merging completed\n'))
454 ui.note(_('rebase merging completed\n'))
455
455
456 def _concludenode(self, rev, p1, p2, editor, commitmsg=None):
456 def _concludenode(self, rev, p1, p2, editor, commitmsg=None):
457 '''Commit the wd changes with parents p1 and p2.
457 '''Commit the wd changes with parents p1 and p2.
458
458
459 Reuse commit info from rev but also store useful information in extra.
459 Reuse commit info from rev but also store useful information in extra.
460 Return node of committed revision.'''
460 Return node of committed revision.'''
461 repo = self.repo
461 repo = self.repo
462 ctx = repo[rev]
462 ctx = repo[rev]
463 if commitmsg is None:
463 if commitmsg is None:
464 commitmsg = ctx.description()
464 commitmsg = ctx.description()
465 date = self.date
465 date = self.date
466 if date is None:
466 if date is None:
467 date = ctx.date()
467 date = ctx.date()
468 extra = {'rebase_source': ctx.hex()}
468 extra = {'rebase_source': ctx.hex()}
469 for c in self.extrafns:
469 for c in self.extrafns:
470 c(ctx, extra)
470 c(ctx, extra)
471 keepbranch = self.keepbranchesf and repo[p1].branch() != ctx.branch()
471 keepbranch = self.keepbranchesf and repo[p1].branch() != ctx.branch()
472 destphase = max(ctx.phase(), phases.draft)
472 destphase = max(ctx.phase(), phases.draft)
473 overrides = {('phases', 'new-commit'): destphase}
473 overrides = {('phases', 'new-commit'): destphase}
474 if keepbranch:
474 if keepbranch:
475 overrides[('ui', 'allowemptycommit')] = True
475 overrides[('ui', 'allowemptycommit')] = True
476 with repo.ui.configoverride(overrides, 'rebase'):
476 with repo.ui.configoverride(overrides, 'rebase'):
477 if self.inmemory:
477 if self.inmemory:
478 newnode = commitmemorynode(repo, p1, p2,
478 newnode = commitmemorynode(repo, p1, p2,
479 wctx=self.wctx,
479 wctx=self.wctx,
480 extra=extra,
480 extra=extra,
481 commitmsg=commitmsg,
481 commitmsg=commitmsg,
482 editor=editor,
482 editor=editor,
483 user=ctx.user(),
483 user=ctx.user(),
484 date=date)
484 date=date)
485 mergemod.mergestate.clean(repo)
485 mergemod.mergestate.clean(repo)
486 else:
486 else:
487 newnode = commitnode(repo, p1, p2,
487 newnode = commitnode(repo, p1, p2,
488 extra=extra,
488 extra=extra,
489 commitmsg=commitmsg,
489 commitmsg=commitmsg,
490 editor=editor,
490 editor=editor,
491 user=ctx.user(),
491 user=ctx.user(),
492 date=date)
492 date=date)
493
493
494 if newnode is None:
494 if newnode is None:
495 # If it ended up being a no-op commit, then the normal
495 # If it ended up being a no-op commit, then the normal
496 # merge state clean-up path doesn't happen, so do it
496 # merge state clean-up path doesn't happen, so do it
497 # here. Fix issue5494
497 # here. Fix issue5494
498 mergemod.mergestate.clean(repo)
498 mergemod.mergestate.clean(repo)
499 return newnode
499 return newnode
500
500
501 def _rebasenode(self, tr, rev, allowdivergence, progressfn):
501 def _rebasenode(self, tr, rev, allowdivergence, progressfn):
502 repo, ui, opts = self.repo, self.ui, self.opts
502 repo, ui, opts = self.repo, self.ui, self.opts
503 dest = self.destmap[rev]
503 dest = self.destmap[rev]
504 ctx = repo[rev]
504 ctx = repo[rev]
505 desc = _ctxdesc(ctx)
505 desc = _ctxdesc(ctx)
506 if self.state[rev] == rev:
506 if self.state[rev] == rev:
507 ui.status(_('already rebased %s\n') % desc)
507 ui.status(_('already rebased %s\n') % desc)
508 elif (not allowdivergence
508 elif (not allowdivergence
509 and rev in self.obsoletewithoutsuccessorindestination):
509 and rev in self.obsoletewithoutsuccessorindestination):
510 msg = _('note: not rebasing %s and its descendants as '
510 msg = _('note: not rebasing %s and its descendants as '
511 'this would cause divergence\n') % desc
511 'this would cause divergence\n') % desc
512 repo.ui.status(msg)
512 repo.ui.status(msg)
513 self.skipped.add(rev)
513 self.skipped.add(rev)
514 elif rev in self.obsoletenotrebased:
514 elif rev in self.obsoletenotrebased:
515 succ = self.obsoletenotrebased[rev]
515 succ = self.obsoletenotrebased[rev]
516 if succ is None:
516 if succ is None:
517 msg = _('note: not rebasing %s, it has no '
517 msg = _('note: not rebasing %s, it has no '
518 'successor\n') % desc
518 'successor\n') % desc
519 else:
519 else:
520 succdesc = _ctxdesc(repo[succ])
520 succdesc = _ctxdesc(repo[succ])
521 msg = (_('note: not rebasing %s, already in '
521 msg = (_('note: not rebasing %s, already in '
522 'destination as %s\n') % (desc, succdesc))
522 'destination as %s\n') % (desc, succdesc))
523 repo.ui.status(msg)
523 repo.ui.status(msg)
524 # Make clearrebased aware state[rev] is not a true successor
524 # Make clearrebased aware state[rev] is not a true successor
525 self.skipped.add(rev)
525 self.skipped.add(rev)
526 # Record rev as moved to its desired destination in self.state.
526 # Record rev as moved to its desired destination in self.state.
527 # This helps bookmark and working parent movement.
527 # This helps bookmark and working parent movement.
528 dest = max(adjustdest(repo, rev, self.destmap, self.state,
528 dest = max(adjustdest(repo, rev, self.destmap, self.state,
529 self.skipped))
529 self.skipped))
530 self.state[rev] = dest
530 self.state[rev] = dest
531 elif self.state[rev] == revtodo:
531 elif self.state[rev] == revtodo:
532 ui.status(_('rebasing %s\n') % desc)
532 ui.status(_('rebasing %s\n') % desc)
533 progressfn(ctx)
533 progressfn(ctx)
534 p1, p2, base = defineparents(repo, rev, self.destmap,
534 p1, p2, base = defineparents(repo, rev, self.destmap,
535 self.state, self.skipped,
535 self.state, self.skipped,
536 self.obsoletenotrebased)
536 self.obsoletenotrebased)
537 if len(repo[None].parents()) == 2:
537 if len(repo[None].parents()) == 2:
538 repo.ui.debug('resuming interrupted rebase\n')
538 repo.ui.debug('resuming interrupted rebase\n')
539 else:
539 else:
540 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
540 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
541 with ui.configoverride(overrides, 'rebase'):
541 with ui.configoverride(overrides, 'rebase'):
542 stats = rebasenode(repo, rev, p1, base, self.collapsef,
542 stats = rebasenode(repo, rev, p1, base, self.collapsef,
543 dest, wctx=self.wctx)
543 dest, wctx=self.wctx)
544 if stats.unresolvedcount > 0:
544 if stats.unresolvedcount > 0:
545 if self.inmemory:
545 if self.inmemory:
546 raise error.InMemoryMergeConflictsError()
546 raise error.InMemoryMergeConflictsError()
547 else:
547 else:
548 raise error.InterventionRequired(
548 raise error.InterventionRequired(
549 _('unresolved conflicts (see hg '
549 _('unresolved conflicts (see hg '
550 'resolve, then hg rebase --continue)'))
550 'resolve, then hg rebase --continue)'))
551 if not self.collapsef:
551 if not self.collapsef:
552 merging = p2 != nullrev
552 merging = p2 != nullrev
553 editform = cmdutil.mergeeditform(merging, 'rebase')
553 editform = cmdutil.mergeeditform(merging, 'rebase')
554 editor = cmdutil.getcommiteditor(editform=editform,
554 editor = cmdutil.getcommiteditor(editform=editform,
555 **pycompat.strkwargs(opts))
555 **pycompat.strkwargs(opts))
556 newnode = self._concludenode(rev, p1, p2, editor)
556 newnode = self._concludenode(rev, p1, p2, editor)
557 else:
557 else:
558 # Skip commit if we are collapsing
558 # Skip commit if we are collapsing
559 if self.inmemory:
559 if self.inmemory:
560 self.wctx.setbase(repo[p1])
560 self.wctx.setbase(repo[p1])
561 else:
561 else:
562 repo.setparents(repo[p1].node())
562 repo.setparents(repo[p1].node())
563 newnode = None
563 newnode = None
564 # Update the state
564 # Update the state
565 if newnode is not None:
565 if newnode is not None:
566 self.state[rev] = repo[newnode].rev()
566 self.state[rev] = repo[newnode].rev()
567 ui.debug('rebased as %s\n' % short(newnode))
567 ui.debug('rebased as %s\n' % short(newnode))
568 else:
568 else:
569 if not self.collapsef:
569 if not self.collapsef:
570 ui.warn(_('note: rebase of %d:%s created no changes '
570 ui.warn(_('note: rebase of %d:%s created no changes '
571 'to commit\n') % (rev, ctx))
571 'to commit\n') % (rev, ctx))
572 self.skipped.add(rev)
572 self.skipped.add(rev)
573 self.state[rev] = p1
573 self.state[rev] = p1
574 ui.debug('next revision set to %d\n' % p1)
574 ui.debug('next revision set to %d\n' % p1)
575 else:
575 else:
576 ui.status(_('already rebased %s as %s\n') %
576 ui.status(_('already rebased %s as %s\n') %
577 (desc, repo[self.state[rev]]))
577 (desc, repo[self.state[rev]]))
578 if not tr:
578 if not tr:
579 # When not using single transaction, store state after each
579 # When not using single transaction, store state after each
580 # commit is completely done. On InterventionRequired, we thus
580 # commit is completely done. On InterventionRequired, we thus
581 # won't store the status. Instead, we'll hit the "len(parents) == 2"
581 # won't store the status. Instead, we'll hit the "len(parents) == 2"
582 # case and realize that the commit was in progress.
582 # case and realize that the commit was in progress.
583 self.storestatus()
583 self.storestatus()
584
584
585 def _finishrebase(self):
585 def _finishrebase(self):
586 repo, ui, opts = self.repo, self.ui, self.opts
586 repo, ui, opts = self.repo, self.ui, self.opts
587 fm = ui.formatter('rebase', opts)
587 fm = ui.formatter('rebase', opts)
588 fm.startitem()
588 fm.startitem()
589 if self.collapsef:
589 if self.collapsef:
590 p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
590 p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
591 self.state, self.skipped,
591 self.state, self.skipped,
592 self.obsoletenotrebased)
592 self.obsoletenotrebased)
593 editopt = opts.get('edit')
593 editopt = opts.get('edit')
594 editform = 'rebase.collapse'
594 editform = 'rebase.collapse'
595 if self.collapsemsg:
595 if self.collapsemsg:
596 commitmsg = self.collapsemsg
596 commitmsg = self.collapsemsg
597 else:
597 else:
598 commitmsg = 'Collapsed revision'
598 commitmsg = 'Collapsed revision'
599 for rebased in sorted(self.state):
599 for rebased in sorted(self.state):
600 if rebased not in self.skipped:
600 if rebased not in self.skipped:
601 commitmsg += '\n* %s' % repo[rebased].description()
601 commitmsg += '\n* %s' % repo[rebased].description()
602 editopt = True
602 editopt = True
603 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
603 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
604 revtoreuse = max(self.state)
604 revtoreuse = max(self.state)
605
605
606 newnode = self._concludenode(revtoreuse, p1, self.external,
606 newnode = self._concludenode(revtoreuse, p1, self.external,
607 editor, commitmsg=commitmsg)
607 editor, commitmsg=commitmsg)
608
608
609 if newnode is not None:
609 if newnode is not None:
610 newrev = repo[newnode].rev()
610 newrev = repo[newnode].rev()
611 for oldrev in self.state:
611 for oldrev in self.state:
612 self.state[oldrev] = newrev
612 self.state[oldrev] = newrev
613
613
614 if 'qtip' in repo.tags():
614 if 'qtip' in repo.tags():
615 updatemq(repo, self.state, self.skipped,
615 updatemq(repo, self.state, self.skipped,
616 **pycompat.strkwargs(opts))
616 **pycompat.strkwargs(opts))
617
617
618 # restore original working directory
618 # restore original working directory
619 # (we do this before stripping)
619 # (we do this before stripping)
620 newwd = self.state.get(self.originalwd, self.originalwd)
620 newwd = self.state.get(self.originalwd, self.originalwd)
621 if newwd < 0:
621 if newwd < 0:
622 # original directory is a parent of rebase set root or ignored
622 # original directory is a parent of rebase set root or ignored
623 newwd = self.originalwd
623 newwd = self.originalwd
624 if newwd not in [c.rev() for c in repo[None].parents()]:
624 if newwd not in [c.rev() for c in repo[None].parents()]:
625 ui.note(_("update back to initial working directory parent\n"))
625 ui.note(_("update back to initial working directory parent\n"))
626 hg.updaterepo(repo, newwd, False)
626 hg.updaterepo(repo, newwd, overwrite=False)
627
627
628 collapsedas = None
628 collapsedas = None
629 if self.collapsef and not self.keepf:
629 if self.collapsef and not self.keepf:
630 collapsedas = newnode
630 collapsedas = newnode
631 clearrebased(ui, repo, self.destmap, self.state, self.skipped,
631 clearrebased(ui, repo, self.destmap, self.state, self.skipped,
632 collapsedas, self.keepf, fm=fm)
632 collapsedas, self.keepf, fm=fm)
633
633
634 clearstatus(repo)
634 clearstatus(repo)
635 clearcollapsemsg(repo)
635 clearcollapsemsg(repo)
636
636
637 ui.note(_("rebase completed\n"))
637 ui.note(_("rebase completed\n"))
638 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
638 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
639 if self.skipped:
639 if self.skipped:
640 skippedlen = len(self.skipped)
640 skippedlen = len(self.skipped)
641 ui.note(_("%d revisions have been skipped\n") % skippedlen)
641 ui.note(_("%d revisions have been skipped\n") % skippedlen)
642 fm.end()
642 fm.end()
643
643
644 if (self.activebookmark and self.activebookmark in repo._bookmarks and
644 if (self.activebookmark and self.activebookmark in repo._bookmarks and
645 repo['.'].node() == repo._bookmarks[self.activebookmark]):
645 repo['.'].node() == repo._bookmarks[self.activebookmark]):
646 bookmarks.activate(repo, self.activebookmark)
646 bookmarks.activate(repo, self.activebookmark)
647
647
648 @command('rebase',
648 @command('rebase',
649 [('s', 'source', '',
649 [('s', 'source', '',
650 _('rebase the specified changeset and descendants'), _('REV')),
650 _('rebase the specified changeset and descendants'), _('REV')),
651 ('b', 'base', '',
651 ('b', 'base', '',
652 _('rebase everything from branching point of specified changeset'),
652 _('rebase everything from branching point of specified changeset'),
653 _('REV')),
653 _('REV')),
654 ('r', 'rev', [],
654 ('r', 'rev', [],
655 _('rebase these revisions'),
655 _('rebase these revisions'),
656 _('REV')),
656 _('REV')),
657 ('d', 'dest', '',
657 ('d', 'dest', '',
658 _('rebase onto the specified changeset'), _('REV')),
658 _('rebase onto the specified changeset'), _('REV')),
659 ('', 'collapse', False, _('collapse the rebased changesets')),
659 ('', 'collapse', False, _('collapse the rebased changesets')),
660 ('m', 'message', '',
660 ('m', 'message', '',
661 _('use text as collapse commit message'), _('TEXT')),
661 _('use text as collapse commit message'), _('TEXT')),
662 ('e', 'edit', False, _('invoke editor on commit messages')),
662 ('e', 'edit', False, _('invoke editor on commit messages')),
663 ('l', 'logfile', '',
663 ('l', 'logfile', '',
664 _('read collapse commit message from file'), _('FILE')),
664 _('read collapse commit message from file'), _('FILE')),
665 ('k', 'keep', False, _('keep original changesets')),
665 ('k', 'keep', False, _('keep original changesets')),
666 ('', 'keepbranches', False, _('keep original branch names')),
666 ('', 'keepbranches', False, _('keep original branch names')),
667 ('D', 'detach', False, _('(DEPRECATED)')),
667 ('D', 'detach', False, _('(DEPRECATED)')),
668 ('i', 'interactive', False, _('(DEPRECATED)')),
668 ('i', 'interactive', False, _('(DEPRECATED)')),
669 ('t', 'tool', '', _('specify merge tool')),
669 ('t', 'tool', '', _('specify merge tool')),
670 ('c', 'continue', False, _('continue an interrupted rebase')),
670 ('c', 'continue', False, _('continue an interrupted rebase')),
671 ('a', 'abort', False, _('abort an interrupted rebase')),
671 ('a', 'abort', False, _('abort an interrupted rebase')),
672 ('', 'auto-orphans', '', _('automatically rebase orphan revisions '
672 ('', 'auto-orphans', '', _('automatically rebase orphan revisions '
673 'in the specified revset (EXPERIMENTAL)')),
673 'in the specified revset (EXPERIMENTAL)')),
674 ] + cmdutil.dryrunopts + cmdutil.formatteropts,
674 ] + cmdutil.dryrunopts + cmdutil.formatteropts,
675 _('[-s REV | -b REV] [-d REV] [OPTION]'))
675 _('[-s REV | -b REV] [-d REV] [OPTION]'))
676 def rebase(ui, repo, **opts):
676 def rebase(ui, repo, **opts):
677 """move changeset (and descendants) to a different branch
677 """move changeset (and descendants) to a different branch
678
678
679 Rebase uses repeated merging to graft changesets from one part of
679 Rebase uses repeated merging to graft changesets from one part of
680 history (the source) onto another (the destination). This can be
680 history (the source) onto another (the destination). This can be
681 useful for linearizing *local* changes relative to a master
681 useful for linearizing *local* changes relative to a master
682 development tree.
682 development tree.
683
683
684 Published commits cannot be rebased (see :hg:`help phases`).
684 Published commits cannot be rebased (see :hg:`help phases`).
685 To copy commits, see :hg:`help graft`.
685 To copy commits, see :hg:`help graft`.
686
686
687 If you don't specify a destination changeset (``-d/--dest``), rebase
687 If you don't specify a destination changeset (``-d/--dest``), rebase
688 will use the same logic as :hg:`merge` to pick a destination. if
688 will use the same logic as :hg:`merge` to pick a destination. if
689 the current branch contains exactly one other head, the other head
689 the current branch contains exactly one other head, the other head
690 is merged with by default. Otherwise, an explicit revision with
690 is merged with by default. Otherwise, an explicit revision with
691 which to merge with must be provided. (destination changeset is not
691 which to merge with must be provided. (destination changeset is not
692 modified by rebasing, but new changesets are added as its
692 modified by rebasing, but new changesets are added as its
693 descendants.)
693 descendants.)
694
694
695 Here are the ways to select changesets:
695 Here are the ways to select changesets:
696
696
697 1. Explicitly select them using ``--rev``.
697 1. Explicitly select them using ``--rev``.
698
698
699 2. Use ``--source`` to select a root changeset and include all of its
699 2. Use ``--source`` to select a root changeset and include all of its
700 descendants.
700 descendants.
701
701
702 3. Use ``--base`` to select a changeset; rebase will find ancestors
702 3. Use ``--base`` to select a changeset; rebase will find ancestors
703 and their descendants which are not also ancestors of the destination.
703 and their descendants which are not also ancestors of the destination.
704
704
705 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
705 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
706 rebase will use ``--base .`` as above.
706 rebase will use ``--base .`` as above.
707
707
708 If ``--source`` or ``--rev`` is used, special names ``SRC`` and ``ALLSRC``
708 If ``--source`` or ``--rev`` is used, special names ``SRC`` and ``ALLSRC``
709 can be used in ``--dest``. Destination would be calculated per source
709 can be used in ``--dest``. Destination would be calculated per source
710 revision with ``SRC`` substituted by that single source revision and
710 revision with ``SRC`` substituted by that single source revision and
711 ``ALLSRC`` substituted by all source revisions.
711 ``ALLSRC`` substituted by all source revisions.
712
712
713 Rebase will destroy original changesets unless you use ``--keep``.
713 Rebase will destroy original changesets unless you use ``--keep``.
714 It will also move your bookmarks (even if you do).
714 It will also move your bookmarks (even if you do).
715
715
716 Some changesets may be dropped if they do not contribute changes
716 Some changesets may be dropped if they do not contribute changes
717 (e.g. merges from the destination branch).
717 (e.g. merges from the destination branch).
718
718
719 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
719 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
720 a named branch with two heads. You will need to explicitly specify source
720 a named branch with two heads. You will need to explicitly specify source
721 and/or destination.
721 and/or destination.
722
722
723 If you need to use a tool to automate merge/conflict decisions, you
723 If you need to use a tool to automate merge/conflict decisions, you
724 can specify one with ``--tool``, see :hg:`help merge-tools`.
724 can specify one with ``--tool``, see :hg:`help merge-tools`.
725 As a caveat: the tool will not be used to mediate when a file was
725 As a caveat: the tool will not be used to mediate when a file was
726 deleted, there is no hook presently available for this.
726 deleted, there is no hook presently available for this.
727
727
728 If a rebase is interrupted to manually resolve a conflict, it can be
728 If a rebase is interrupted to manually resolve a conflict, it can be
729 continued with --continue/-c or aborted with --abort/-a.
729 continued with --continue/-c or aborted with --abort/-a.
730
730
731 .. container:: verbose
731 .. container:: verbose
732
732
733 Examples:
733 Examples:
734
734
735 - move "local changes" (current commit back to branching point)
735 - move "local changes" (current commit back to branching point)
736 to the current branch tip after a pull::
736 to the current branch tip after a pull::
737
737
738 hg rebase
738 hg rebase
739
739
740 - move a single changeset to the stable branch::
740 - move a single changeset to the stable branch::
741
741
742 hg rebase -r 5f493448 -d stable
742 hg rebase -r 5f493448 -d stable
743
743
744 - splice a commit and all its descendants onto another part of history::
744 - splice a commit and all its descendants onto another part of history::
745
745
746 hg rebase --source c0c3 --dest 4cf9
746 hg rebase --source c0c3 --dest 4cf9
747
747
748 - rebase everything on a branch marked by a bookmark onto the
748 - rebase everything on a branch marked by a bookmark onto the
749 default branch::
749 default branch::
750
750
751 hg rebase --base myfeature --dest default
751 hg rebase --base myfeature --dest default
752
752
753 - collapse a sequence of changes into a single commit::
753 - collapse a sequence of changes into a single commit::
754
754
755 hg rebase --collapse -r 1520:1525 -d .
755 hg rebase --collapse -r 1520:1525 -d .
756
756
757 - move a named branch while preserving its name::
757 - move a named branch while preserving its name::
758
758
759 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
759 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
760
760
761 - stabilize orphaned changesets so history looks linear::
761 - stabilize orphaned changesets so history looks linear::
762
762
763 hg rebase -r 'orphan()-obsolete()'\
763 hg rebase -r 'orphan()-obsolete()'\
764 -d 'first(max((successors(max(roots(ALLSRC) & ::SRC)^)-obsolete())::) +\
764 -d 'first(max((successors(max(roots(ALLSRC) & ::SRC)^)-obsolete())::) +\
765 max(::((roots(ALLSRC) & ::SRC)^)-obsolete()))'
765 max(::((roots(ALLSRC) & ::SRC)^)-obsolete()))'
766
766
767 Configuration Options:
767 Configuration Options:
768
768
769 You can make rebase require a destination if you set the following config
769 You can make rebase require a destination if you set the following config
770 option::
770 option::
771
771
772 [commands]
772 [commands]
773 rebase.requiredest = True
773 rebase.requiredest = True
774
774
775 By default, rebase will close the transaction after each commit. For
775 By default, rebase will close the transaction after each commit. For
776 performance purposes, you can configure rebase to use a single transaction
776 performance purposes, you can configure rebase to use a single transaction
777 across the entire rebase. WARNING: This setting introduces a significant
777 across the entire rebase. WARNING: This setting introduces a significant
778 risk of losing the work you've done in a rebase if the rebase aborts
778 risk of losing the work you've done in a rebase if the rebase aborts
779 unexpectedly::
779 unexpectedly::
780
780
781 [rebase]
781 [rebase]
782 singletransaction = True
782 singletransaction = True
783
783
784 By default, rebase writes to the working copy, but you can configure it to
784 By default, rebase writes to the working copy, but you can configure it to
785 run in-memory for for better performance, and to allow it to run if the
785 run in-memory for for better performance, and to allow it to run if the
786 working copy is dirty::
786 working copy is dirty::
787
787
788 [rebase]
788 [rebase]
789 experimental.inmemory = True
789 experimental.inmemory = True
790
790
791 Return Values:
791 Return Values:
792
792
793 Returns 0 on success, 1 if nothing to rebase or there are
793 Returns 0 on success, 1 if nothing to rebase or there are
794 unresolved conflicts.
794 unresolved conflicts.
795
795
796 """
796 """
797 opts = pycompat.byteskwargs(opts)
797 opts = pycompat.byteskwargs(opts)
798 inmemory = ui.configbool('rebase', 'experimental.inmemory')
798 inmemory = ui.configbool('rebase', 'experimental.inmemory')
799 dryrun = opts.get('dry_run')
799 dryrun = opts.get('dry_run')
800 if dryrun:
800 if dryrun:
801 if opts.get('abort'):
801 if opts.get('abort'):
802 raise error.Abort(_('cannot specify both --dry-run and --abort'))
802 raise error.Abort(_('cannot specify both --dry-run and --abort'))
803 if opts.get('continue'):
803 if opts.get('continue'):
804 raise error.Abort(_('cannot specify both --dry-run and --continue'))
804 raise error.Abort(_('cannot specify both --dry-run and --continue'))
805
805
806 if (opts.get('continue') or opts.get('abort') or
806 if (opts.get('continue') or opts.get('abort') or
807 repo.currenttransaction() is not None):
807 repo.currenttransaction() is not None):
808 # in-memory rebase is not compatible with resuming rebases.
808 # in-memory rebase is not compatible with resuming rebases.
809 # (Or if it is run within a transaction, since the restart logic can
809 # (Or if it is run within a transaction, since the restart logic can
810 # fail the entire transaction.)
810 # fail the entire transaction.)
811 inmemory = False
811 inmemory = False
812
812
813 if opts.get('auto_orphans'):
813 if opts.get('auto_orphans'):
814 for key in opts:
814 for key in opts:
815 if key != 'auto_orphans' and opts.get(key):
815 if key != 'auto_orphans' and opts.get(key):
816 raise error.Abort(_('--auto-orphans is incompatible with %s') %
816 raise error.Abort(_('--auto-orphans is incompatible with %s') %
817 ('--' + key))
817 ('--' + key))
818 userrevs = list(repo.revs(opts.get('auto_orphans')))
818 userrevs = list(repo.revs(opts.get('auto_orphans')))
819 opts['rev'] = [revsetlang.formatspec('%ld and orphan()', userrevs)]
819 opts['rev'] = [revsetlang.formatspec('%ld and orphan()', userrevs)]
820 opts['dest'] = '_destautoorphanrebase(SRC)'
820 opts['dest'] = '_destautoorphanrebase(SRC)'
821
821
822 if dryrun:
822 if dryrun:
823 return _dryrunrebase(ui, repo, opts)
823 return _dryrunrebase(ui, repo, opts)
824 elif inmemory:
824 elif inmemory:
825 try:
825 try:
826 # in-memory merge doesn't support conflicts, so if we hit any, abort
826 # in-memory merge doesn't support conflicts, so if we hit any, abort
827 # and re-run as an on-disk merge.
827 # and re-run as an on-disk merge.
828 overrides = {('rebase', 'singletransaction'): True}
828 overrides = {('rebase', 'singletransaction'): True}
829 with ui.configoverride(overrides, 'rebase'):
829 with ui.configoverride(overrides, 'rebase'):
830 return _dorebase(ui, repo, opts, inmemory=inmemory)
830 return _dorebase(ui, repo, opts, inmemory=inmemory)
831 except error.InMemoryMergeConflictsError:
831 except error.InMemoryMergeConflictsError:
832 ui.warn(_('hit merge conflicts; re-running rebase without in-memory'
832 ui.warn(_('hit merge conflicts; re-running rebase without in-memory'
833 ' merge\n'))
833 ' merge\n'))
834 _dorebase(ui, repo, {'abort': True})
834 _dorebase(ui, repo, {'abort': True})
835 return _dorebase(ui, repo, opts, inmemory=False)
835 return _dorebase(ui, repo, opts, inmemory=False)
836 else:
836 else:
837 return _dorebase(ui, repo, opts)
837 return _dorebase(ui, repo, opts)
838
838
839 def _dryrunrebase(ui, repo, opts):
839 def _dryrunrebase(ui, repo, opts):
840 rbsrt = rebaseruntime(repo, ui, inmemory=True, opts=opts)
840 rbsrt = rebaseruntime(repo, ui, inmemory=True, opts=opts)
841 with repo.wlock(), repo.lock():
841 with repo.wlock(), repo.lock():
842 try:
842 try:
843 overrides = {('rebase', 'singletransaction'): True}
843 overrides = {('rebase', 'singletransaction'): True}
844 with ui.configoverride(overrides, 'rebase'):
844 with ui.configoverride(overrides, 'rebase'):
845 _origrebase(ui, repo, opts, rbsrt, inmemory=True,
845 _origrebase(ui, repo, opts, rbsrt, inmemory=True,
846 leaveunfinished=True)
846 leaveunfinished=True)
847 except error.InMemoryMergeConflictsError:
847 except error.InMemoryMergeConflictsError:
848 ui.status(_('hit a merge conflict\n'))
848 ui.status(_('hit a merge conflict\n'))
849 return 1
849 return 1
850 else:
850 else:
851 ui.status(_('there will be no conflict, you can rebase\n'))
851 ui.status(_('there will be no conflict, you can rebase\n'))
852 return 0
852 return 0
853 finally:
853 finally:
854 # no need to store backup in case of dryrun
854 # no need to store backup in case of dryrun
855 rbsrt._prepareabortorcontinue(isabort=True, backup=False,
855 rbsrt._prepareabortorcontinue(isabort=True, backup=False,
856 suppwarns=True)
856 suppwarns=True)
857
857
858 def _dorebase(ui, repo, opts, inmemory=False):
858 def _dorebase(ui, repo, opts, inmemory=False):
859 rbsrt = rebaseruntime(repo, ui, inmemory, opts)
859 rbsrt = rebaseruntime(repo, ui, inmemory, opts)
860 return _origrebase(ui, repo, opts, rbsrt, inmemory=inmemory)
860 return _origrebase(ui, repo, opts, rbsrt, inmemory=inmemory)
861
861
862 def _origrebase(ui, repo, opts, rbsrt, inmemory=False, leaveunfinished=False):
862 def _origrebase(ui, repo, opts, rbsrt, inmemory=False, leaveunfinished=False):
863 with repo.wlock(), repo.lock():
863 with repo.wlock(), repo.lock():
864 # Validate input and define rebasing points
864 # Validate input and define rebasing points
865 destf = opts.get('dest', None)
865 destf = opts.get('dest', None)
866 srcf = opts.get('source', None)
866 srcf = opts.get('source', None)
867 basef = opts.get('base', None)
867 basef = opts.get('base', None)
868 revf = opts.get('rev', [])
868 revf = opts.get('rev', [])
869 # search default destination in this space
869 # search default destination in this space
870 # used in the 'hg pull --rebase' case, see issue 5214.
870 # used in the 'hg pull --rebase' case, see issue 5214.
871 destspace = opts.get('_destspace')
871 destspace = opts.get('_destspace')
872 contf = opts.get('continue')
872 contf = opts.get('continue')
873 abortf = opts.get('abort')
873 abortf = opts.get('abort')
874 if opts.get('interactive'):
874 if opts.get('interactive'):
875 try:
875 try:
876 if extensions.find('histedit'):
876 if extensions.find('histedit'):
877 enablehistedit = ''
877 enablehistedit = ''
878 except KeyError:
878 except KeyError:
879 enablehistedit = " --config extensions.histedit="
879 enablehistedit = " --config extensions.histedit="
880 help = "hg%s help -e histedit" % enablehistedit
880 help = "hg%s help -e histedit" % enablehistedit
881 msg = _("interactive history editing is supported by the "
881 msg = _("interactive history editing is supported by the "
882 "'histedit' extension (see \"%s\")") % help
882 "'histedit' extension (see \"%s\")") % help
883 raise error.Abort(msg)
883 raise error.Abort(msg)
884
884
885 if rbsrt.collapsemsg and not rbsrt.collapsef:
885 if rbsrt.collapsemsg and not rbsrt.collapsef:
886 raise error.Abort(
886 raise error.Abort(
887 _('message can only be specified with collapse'))
887 _('message can only be specified with collapse'))
888
888
889 if contf or abortf:
889 if contf or abortf:
890 if contf and abortf:
890 if contf and abortf:
891 raise error.Abort(_('cannot use both abort and continue'))
891 raise error.Abort(_('cannot use both abort and continue'))
892 if rbsrt.collapsef:
892 if rbsrt.collapsef:
893 raise error.Abort(
893 raise error.Abort(
894 _('cannot use collapse with continue or abort'))
894 _('cannot use collapse with continue or abort'))
895 if srcf or basef or destf:
895 if srcf or basef or destf:
896 raise error.Abort(
896 raise error.Abort(
897 _('abort and continue do not allow specifying revisions'))
897 _('abort and continue do not allow specifying revisions'))
898 if abortf and opts.get('tool', False):
898 if abortf and opts.get('tool', False):
899 ui.warn(_('tool option will be ignored\n'))
899 ui.warn(_('tool option will be ignored\n'))
900 if contf:
900 if contf:
901 ms = mergemod.mergestate.read(repo)
901 ms = mergemod.mergestate.read(repo)
902 mergeutil.checkunresolved(ms)
902 mergeutil.checkunresolved(ms)
903
903
904 retcode = rbsrt._prepareabortorcontinue(abortf)
904 retcode = rbsrt._prepareabortorcontinue(abortf)
905 if retcode is not None:
905 if retcode is not None:
906 return retcode
906 return retcode
907 else:
907 else:
908 destmap = _definedestmap(ui, repo, inmemory, destf, srcf, basef,
908 destmap = _definedestmap(ui, repo, inmemory, destf, srcf, basef,
909 revf, destspace=destspace)
909 revf, destspace=destspace)
910 retcode = rbsrt._preparenewrebase(destmap)
910 retcode = rbsrt._preparenewrebase(destmap)
911 if retcode is not None:
911 if retcode is not None:
912 return retcode
912 return retcode
913 storecollapsemsg(repo, rbsrt.collapsemsg)
913 storecollapsemsg(repo, rbsrt.collapsemsg)
914
914
915 tr = None
915 tr = None
916
916
917 singletr = ui.configbool('rebase', 'singletransaction')
917 singletr = ui.configbool('rebase', 'singletransaction')
918 if singletr:
918 if singletr:
919 tr = repo.transaction('rebase')
919 tr = repo.transaction('rebase')
920
920
921 # If `rebase.singletransaction` is enabled, wrap the entire operation in
921 # If `rebase.singletransaction` is enabled, wrap the entire operation in
922 # one transaction here. Otherwise, transactions are obtained when
922 # one transaction here. Otherwise, transactions are obtained when
923 # committing each node, which is slower but allows partial success.
923 # committing each node, which is slower but allows partial success.
924 with util.acceptintervention(tr):
924 with util.acceptintervention(tr):
925 # Same logic for the dirstate guard, except we don't create one when
925 # Same logic for the dirstate guard, except we don't create one when
926 # rebasing in-memory (it's not needed).
926 # rebasing in-memory (it's not needed).
927 dsguard = None
927 dsguard = None
928 if singletr and not inmemory:
928 if singletr and not inmemory:
929 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
929 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
930 with util.acceptintervention(dsguard):
930 with util.acceptintervention(dsguard):
931 rbsrt._performrebase(tr)
931 rbsrt._performrebase(tr)
932 if not leaveunfinished:
932 if not leaveunfinished:
933 rbsrt._finishrebase()
933 rbsrt._finishrebase()
934
934
935 def _definedestmap(ui, repo, inmemory, destf=None, srcf=None, basef=None,
935 def _definedestmap(ui, repo, inmemory, destf=None, srcf=None, basef=None,
936 revf=None, destspace=None):
936 revf=None, destspace=None):
937 """use revisions argument to define destmap {srcrev: destrev}"""
937 """use revisions argument to define destmap {srcrev: destrev}"""
938 if revf is None:
938 if revf is None:
939 revf = []
939 revf = []
940
940
941 # destspace is here to work around issues with `hg pull --rebase` see
941 # destspace is here to work around issues with `hg pull --rebase` see
942 # issue5214 for details
942 # issue5214 for details
943 if srcf and basef:
943 if srcf and basef:
944 raise error.Abort(_('cannot specify both a source and a base'))
944 raise error.Abort(_('cannot specify both a source and a base'))
945 if revf and basef:
945 if revf and basef:
946 raise error.Abort(_('cannot specify both a revision and a base'))
946 raise error.Abort(_('cannot specify both a revision and a base'))
947 if revf and srcf:
947 if revf and srcf:
948 raise error.Abort(_('cannot specify both a revision and a source'))
948 raise error.Abort(_('cannot specify both a revision and a source'))
949
949
950 if not inmemory:
950 if not inmemory:
951 cmdutil.checkunfinished(repo)
951 cmdutil.checkunfinished(repo)
952 cmdutil.bailifchanged(repo)
952 cmdutil.bailifchanged(repo)
953
953
954 if ui.configbool('commands', 'rebase.requiredest') and not destf:
954 if ui.configbool('commands', 'rebase.requiredest') and not destf:
955 raise error.Abort(_('you must specify a destination'),
955 raise error.Abort(_('you must specify a destination'),
956 hint=_('use: hg rebase -d REV'))
956 hint=_('use: hg rebase -d REV'))
957
957
958 dest = None
958 dest = None
959
959
960 if revf:
960 if revf:
961 rebaseset = scmutil.revrange(repo, revf)
961 rebaseset = scmutil.revrange(repo, revf)
962 if not rebaseset:
962 if not rebaseset:
963 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
963 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
964 return None
964 return None
965 elif srcf:
965 elif srcf:
966 src = scmutil.revrange(repo, [srcf])
966 src = scmutil.revrange(repo, [srcf])
967 if not src:
967 if not src:
968 ui.status(_('empty "source" revision set - nothing to rebase\n'))
968 ui.status(_('empty "source" revision set - nothing to rebase\n'))
969 return None
969 return None
970 rebaseset = repo.revs('(%ld)::', src)
970 rebaseset = repo.revs('(%ld)::', src)
971 assert rebaseset
971 assert rebaseset
972 else:
972 else:
973 base = scmutil.revrange(repo, [basef or '.'])
973 base = scmutil.revrange(repo, [basef or '.'])
974 if not base:
974 if not base:
975 ui.status(_('empty "base" revision set - '
975 ui.status(_('empty "base" revision set - '
976 "can't compute rebase set\n"))
976 "can't compute rebase set\n"))
977 return None
977 return None
978 if destf:
978 if destf:
979 # --base does not support multiple destinations
979 # --base does not support multiple destinations
980 dest = scmutil.revsingle(repo, destf)
980 dest = scmutil.revsingle(repo, destf)
981 else:
981 else:
982 dest = repo[_destrebase(repo, base, destspace=destspace)]
982 dest = repo[_destrebase(repo, base, destspace=destspace)]
983 destf = bytes(dest)
983 destf = bytes(dest)
984
984
985 roots = [] # selected children of branching points
985 roots = [] # selected children of branching points
986 bpbase = {} # {branchingpoint: [origbase]}
986 bpbase = {} # {branchingpoint: [origbase]}
987 for b in base: # group bases by branching points
987 for b in base: # group bases by branching points
988 bp = repo.revs('ancestor(%d, %d)', b, dest.rev()).first()
988 bp = repo.revs('ancestor(%d, %d)', b, dest.rev()).first()
989 bpbase[bp] = bpbase.get(bp, []) + [b]
989 bpbase[bp] = bpbase.get(bp, []) + [b]
990 if None in bpbase:
990 if None in bpbase:
991 # emulate the old behavior, showing "nothing to rebase" (a better
991 # emulate the old behavior, showing "nothing to rebase" (a better
992 # behavior may be abort with "cannot find branching point" error)
992 # behavior may be abort with "cannot find branching point" error)
993 bpbase.clear()
993 bpbase.clear()
994 for bp, bs in bpbase.iteritems(): # calculate roots
994 for bp, bs in bpbase.iteritems(): # calculate roots
995 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
995 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
996
996
997 rebaseset = repo.revs('%ld::', roots)
997 rebaseset = repo.revs('%ld::', roots)
998
998
999 if not rebaseset:
999 if not rebaseset:
1000 # transform to list because smartsets are not comparable to
1000 # transform to list because smartsets are not comparable to
1001 # lists. This should be improved to honor laziness of
1001 # lists. This should be improved to honor laziness of
1002 # smartset.
1002 # smartset.
1003 if list(base) == [dest.rev()]:
1003 if list(base) == [dest.rev()]:
1004 if basef:
1004 if basef:
1005 ui.status(_('nothing to rebase - %s is both "base"'
1005 ui.status(_('nothing to rebase - %s is both "base"'
1006 ' and destination\n') % dest)
1006 ' and destination\n') % dest)
1007 else:
1007 else:
1008 ui.status(_('nothing to rebase - working directory '
1008 ui.status(_('nothing to rebase - working directory '
1009 'parent is also destination\n'))
1009 'parent is also destination\n'))
1010 elif not repo.revs('%ld - ::%d', base, dest.rev()):
1010 elif not repo.revs('%ld - ::%d', base, dest.rev()):
1011 if basef:
1011 if basef:
1012 ui.status(_('nothing to rebase - "base" %s is '
1012 ui.status(_('nothing to rebase - "base" %s is '
1013 'already an ancestor of destination '
1013 'already an ancestor of destination '
1014 '%s\n') %
1014 '%s\n') %
1015 ('+'.join(bytes(repo[r]) for r in base),
1015 ('+'.join(bytes(repo[r]) for r in base),
1016 dest))
1016 dest))
1017 else:
1017 else:
1018 ui.status(_('nothing to rebase - working '
1018 ui.status(_('nothing to rebase - working '
1019 'directory parent is already an '
1019 'directory parent is already an '
1020 'ancestor of destination %s\n') % dest)
1020 'ancestor of destination %s\n') % dest)
1021 else: # can it happen?
1021 else: # can it happen?
1022 ui.status(_('nothing to rebase from %s to %s\n') %
1022 ui.status(_('nothing to rebase from %s to %s\n') %
1023 ('+'.join(bytes(repo[r]) for r in base), dest))
1023 ('+'.join(bytes(repo[r]) for r in base), dest))
1024 return None
1024 return None
1025
1025
1026 rebasingwcp = repo['.'].rev() in rebaseset
1026 rebasingwcp = repo['.'].rev() in rebaseset
1027 ui.log("rebase", "", rebase_rebasing_wcp=rebasingwcp)
1027 ui.log("rebase", "", rebase_rebasing_wcp=rebasingwcp)
1028 if inmemory and rebasingwcp:
1028 if inmemory and rebasingwcp:
1029 # Check these since we did not before.
1029 # Check these since we did not before.
1030 cmdutil.checkunfinished(repo)
1030 cmdutil.checkunfinished(repo)
1031 cmdutil.bailifchanged(repo)
1031 cmdutil.bailifchanged(repo)
1032
1032
1033 if not destf:
1033 if not destf:
1034 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
1034 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
1035 destf = bytes(dest)
1035 destf = bytes(dest)
1036
1036
1037 allsrc = revsetlang.formatspec('%ld', rebaseset)
1037 allsrc = revsetlang.formatspec('%ld', rebaseset)
1038 alias = {'ALLSRC': allsrc}
1038 alias = {'ALLSRC': allsrc}
1039
1039
1040 if dest is None:
1040 if dest is None:
1041 try:
1041 try:
1042 # fast path: try to resolve dest without SRC alias
1042 # fast path: try to resolve dest without SRC alias
1043 dest = scmutil.revsingle(repo, destf, localalias=alias)
1043 dest = scmutil.revsingle(repo, destf, localalias=alias)
1044 except error.RepoLookupError:
1044 except error.RepoLookupError:
1045 # multi-dest path: resolve dest for each SRC separately
1045 # multi-dest path: resolve dest for each SRC separately
1046 destmap = {}
1046 destmap = {}
1047 for r in rebaseset:
1047 for r in rebaseset:
1048 alias['SRC'] = revsetlang.formatspec('%d', r)
1048 alias['SRC'] = revsetlang.formatspec('%d', r)
1049 # use repo.anyrevs instead of scmutil.revsingle because we
1049 # use repo.anyrevs instead of scmutil.revsingle because we
1050 # don't want to abort if destset is empty.
1050 # don't want to abort if destset is empty.
1051 destset = repo.anyrevs([destf], user=True, localalias=alias)
1051 destset = repo.anyrevs([destf], user=True, localalias=alias)
1052 size = len(destset)
1052 size = len(destset)
1053 if size == 1:
1053 if size == 1:
1054 destmap[r] = destset.first()
1054 destmap[r] = destset.first()
1055 elif size == 0:
1055 elif size == 0:
1056 ui.note(_('skipping %s - empty destination\n') % repo[r])
1056 ui.note(_('skipping %s - empty destination\n') % repo[r])
1057 else:
1057 else:
1058 raise error.Abort(_('rebase destination for %s is not '
1058 raise error.Abort(_('rebase destination for %s is not '
1059 'unique') % repo[r])
1059 'unique') % repo[r])
1060
1060
1061 if dest is not None:
1061 if dest is not None:
1062 # single-dest case: assign dest to each rev in rebaseset
1062 # single-dest case: assign dest to each rev in rebaseset
1063 destrev = dest.rev()
1063 destrev = dest.rev()
1064 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
1064 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
1065
1065
1066 if not destmap:
1066 if not destmap:
1067 ui.status(_('nothing to rebase - empty destination\n'))
1067 ui.status(_('nothing to rebase - empty destination\n'))
1068 return None
1068 return None
1069
1069
1070 return destmap
1070 return destmap
1071
1071
1072 def externalparent(repo, state, destancestors):
1072 def externalparent(repo, state, destancestors):
1073 """Return the revision that should be used as the second parent
1073 """Return the revision that should be used as the second parent
1074 when the revisions in state is collapsed on top of destancestors.
1074 when the revisions in state is collapsed on top of destancestors.
1075 Abort if there is more than one parent.
1075 Abort if there is more than one parent.
1076 """
1076 """
1077 parents = set()
1077 parents = set()
1078 source = min(state)
1078 source = min(state)
1079 for rev in state:
1079 for rev in state:
1080 if rev == source:
1080 if rev == source:
1081 continue
1081 continue
1082 for p in repo[rev].parents():
1082 for p in repo[rev].parents():
1083 if (p.rev() not in state
1083 if (p.rev() not in state
1084 and p.rev() not in destancestors):
1084 and p.rev() not in destancestors):
1085 parents.add(p.rev())
1085 parents.add(p.rev())
1086 if not parents:
1086 if not parents:
1087 return nullrev
1087 return nullrev
1088 if len(parents) == 1:
1088 if len(parents) == 1:
1089 return parents.pop()
1089 return parents.pop()
1090 raise error.Abort(_('unable to collapse on top of %d, there is more '
1090 raise error.Abort(_('unable to collapse on top of %d, there is more '
1091 'than one external parent: %s') %
1091 'than one external parent: %s') %
1092 (max(destancestors),
1092 (max(destancestors),
1093 ', '.join("%d" % p for p in sorted(parents))))
1093 ', '.join("%d" % p for p in sorted(parents))))
1094
1094
1095 def commitmemorynode(repo, p1, p2, wctx, editor, extra, user, date, commitmsg):
1095 def commitmemorynode(repo, p1, p2, wctx, editor, extra, user, date, commitmsg):
1096 '''Commit the memory changes with parents p1 and p2.
1096 '''Commit the memory changes with parents p1 and p2.
1097 Return node of committed revision.'''
1097 Return node of committed revision.'''
1098 # Replicates the empty check in ``repo.commit``.
1098 # Replicates the empty check in ``repo.commit``.
1099 if wctx.isempty() and not repo.ui.configbool('ui', 'allowemptycommit'):
1099 if wctx.isempty() and not repo.ui.configbool('ui', 'allowemptycommit'):
1100 return None
1100 return None
1101
1101
1102 # By convention, ``extra['branch']`` (set by extrafn) clobbers
1102 # By convention, ``extra['branch']`` (set by extrafn) clobbers
1103 # ``branch`` (used when passing ``--keepbranches``).
1103 # ``branch`` (used when passing ``--keepbranches``).
1104 branch = repo[p1].branch()
1104 branch = repo[p1].branch()
1105 if 'branch' in extra:
1105 if 'branch' in extra:
1106 branch = extra['branch']
1106 branch = extra['branch']
1107
1107
1108 memctx = wctx.tomemctx(commitmsg, parents=(p1, p2), date=date,
1108 memctx = wctx.tomemctx(commitmsg, parents=(p1, p2), date=date,
1109 extra=extra, user=user, branch=branch, editor=editor)
1109 extra=extra, user=user, branch=branch, editor=editor)
1110 commitres = repo.commitctx(memctx)
1110 commitres = repo.commitctx(memctx)
1111 wctx.clean() # Might be reused
1111 wctx.clean() # Might be reused
1112 return commitres
1112 return commitres
1113
1113
1114 def commitnode(repo, p1, p2, editor, extra, user, date, commitmsg):
1114 def commitnode(repo, p1, p2, editor, extra, user, date, commitmsg):
1115 '''Commit the wd changes with parents p1 and p2.
1115 '''Commit the wd changes with parents p1 and p2.
1116 Return node of committed revision.'''
1116 Return node of committed revision.'''
1117 dsguard = util.nullcontextmanager()
1117 dsguard = util.nullcontextmanager()
1118 if not repo.ui.configbool('rebase', 'singletransaction'):
1118 if not repo.ui.configbool('rebase', 'singletransaction'):
1119 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
1119 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
1120 with dsguard:
1120 with dsguard:
1121 repo.setparents(repo[p1].node(), repo[p2].node())
1121 repo.setparents(repo[p1].node(), repo[p2].node())
1122
1122
1123 # Commit might fail if unresolved files exist
1123 # Commit might fail if unresolved files exist
1124 newnode = repo.commit(text=commitmsg, user=user, date=date,
1124 newnode = repo.commit(text=commitmsg, user=user, date=date,
1125 extra=extra, editor=editor)
1125 extra=extra, editor=editor)
1126
1126
1127 repo.dirstate.setbranch(repo[newnode].branch())
1127 repo.dirstate.setbranch(repo[newnode].branch())
1128 return newnode
1128 return newnode
1129
1129
1130 def rebasenode(repo, rev, p1, base, collapse, dest, wctx):
1130 def rebasenode(repo, rev, p1, base, collapse, dest, wctx):
1131 'Rebase a single revision rev on top of p1 using base as merge ancestor'
1131 'Rebase a single revision rev on top of p1 using base as merge ancestor'
1132 # Merge phase
1132 # Merge phase
1133 # Update to destination and merge it with local
1133 # Update to destination and merge it with local
1134 if wctx.isinmemory():
1134 if wctx.isinmemory():
1135 wctx.setbase(repo[p1])
1135 wctx.setbase(repo[p1])
1136 else:
1136 else:
1137 if repo['.'].rev() != p1:
1137 if repo['.'].rev() != p1:
1138 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
1138 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
1139 mergemod.update(repo, p1, False, True)
1139 mergemod.update(repo, p1, False, True)
1140 else:
1140 else:
1141 repo.ui.debug(" already in destination\n")
1141 repo.ui.debug(" already in destination\n")
1142 # This is, alas, necessary to invalidate workingctx's manifest cache,
1142 # This is, alas, necessary to invalidate workingctx's manifest cache,
1143 # as well as other data we litter on it in other places.
1143 # as well as other data we litter on it in other places.
1144 wctx = repo[None]
1144 wctx = repo[None]
1145 repo.dirstate.write(repo.currenttransaction())
1145 repo.dirstate.write(repo.currenttransaction())
1146 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
1146 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
1147 if base is not None:
1147 if base is not None:
1148 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
1148 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
1149 # When collapsing in-place, the parent is the common ancestor, we
1149 # When collapsing in-place, the parent is the common ancestor, we
1150 # have to allow merging with it.
1150 # have to allow merging with it.
1151 stats = mergemod.update(repo, rev, True, True, base, collapse,
1151 stats = mergemod.update(repo, rev, True, True, base, collapse,
1152 labels=['dest', 'source'], wc=wctx)
1152 labels=['dest', 'source'], wc=wctx)
1153 if collapse:
1153 if collapse:
1154 copies.duplicatecopies(repo, wctx, rev, dest)
1154 copies.duplicatecopies(repo, wctx, rev, dest)
1155 else:
1155 else:
1156 # If we're not using --collapse, we need to
1156 # If we're not using --collapse, we need to
1157 # duplicate copies between the revision we're
1157 # duplicate copies between the revision we're
1158 # rebasing and its first parent, but *not*
1158 # rebasing and its first parent, but *not*
1159 # duplicate any copies that have already been
1159 # duplicate any copies that have already been
1160 # performed in the destination.
1160 # performed in the destination.
1161 p1rev = repo[rev].p1().rev()
1161 p1rev = repo[rev].p1().rev()
1162 copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
1162 copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
1163 return stats
1163 return stats
1164
1164
1165 def adjustdest(repo, rev, destmap, state, skipped):
1165 def adjustdest(repo, rev, destmap, state, skipped):
1166 """adjust rebase destination given the current rebase state
1166 """adjust rebase destination given the current rebase state
1167
1167
1168 rev is what is being rebased. Return a list of two revs, which are the
1168 rev is what is being rebased. Return a list of two revs, which are the
1169 adjusted destinations for rev's p1 and p2, respectively. If a parent is
1169 adjusted destinations for rev's p1 and p2, respectively. If a parent is
1170 nullrev, return dest without adjustment for it.
1170 nullrev, return dest without adjustment for it.
1171
1171
1172 For example, when doing rebasing B+E to F, C to G, rebase will first move B
1172 For example, when doing rebasing B+E to F, C to G, rebase will first move B
1173 to B1, and E's destination will be adjusted from F to B1.
1173 to B1, and E's destination will be adjusted from F to B1.
1174
1174
1175 B1 <- written during rebasing B
1175 B1 <- written during rebasing B
1176 |
1176 |
1177 F <- original destination of B, E
1177 F <- original destination of B, E
1178 |
1178 |
1179 | E <- rev, which is being rebased
1179 | E <- rev, which is being rebased
1180 | |
1180 | |
1181 | D <- prev, one parent of rev being checked
1181 | D <- prev, one parent of rev being checked
1182 | |
1182 | |
1183 | x <- skipped, ex. no successor or successor in (::dest)
1183 | x <- skipped, ex. no successor or successor in (::dest)
1184 | |
1184 | |
1185 | C <- rebased as C', different destination
1185 | C <- rebased as C', different destination
1186 | |
1186 | |
1187 | B <- rebased as B1 C'
1187 | B <- rebased as B1 C'
1188 |/ |
1188 |/ |
1189 A G <- destination of C, different
1189 A G <- destination of C, different
1190
1190
1191 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
1191 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
1192 first move C to C1, G to G1, and when it's checking H, the adjusted
1192 first move C to C1, G to G1, and when it's checking H, the adjusted
1193 destinations will be [C1, G1].
1193 destinations will be [C1, G1].
1194
1194
1195 H C1 G1
1195 H C1 G1
1196 /| | /
1196 /| | /
1197 F G |/
1197 F G |/
1198 K | | -> K
1198 K | | -> K
1199 | C D |
1199 | C D |
1200 | |/ |
1200 | |/ |
1201 | B | ...
1201 | B | ...
1202 |/ |/
1202 |/ |/
1203 A A
1203 A A
1204
1204
1205 Besides, adjust dest according to existing rebase information. For example,
1205 Besides, adjust dest according to existing rebase information. For example,
1206
1206
1207 B C D B needs to be rebased on top of C, C needs to be rebased on top
1207 B C D B needs to be rebased on top of C, C needs to be rebased on top
1208 \|/ of D. We will rebase C first.
1208 \|/ of D. We will rebase C first.
1209 A
1209 A
1210
1210
1211 C' After rebasing C, when considering B's destination, use C'
1211 C' After rebasing C, when considering B's destination, use C'
1212 | instead of the original C.
1212 | instead of the original C.
1213 B D
1213 B D
1214 \ /
1214 \ /
1215 A
1215 A
1216 """
1216 """
1217 # pick already rebased revs with same dest from state as interesting source
1217 # pick already rebased revs with same dest from state as interesting source
1218 dest = destmap[rev]
1218 dest = destmap[rev]
1219 source = [s for s, d in state.items()
1219 source = [s for s, d in state.items()
1220 if d > 0 and destmap[s] == dest and s not in skipped]
1220 if d > 0 and destmap[s] == dest and s not in skipped]
1221
1221
1222 result = []
1222 result = []
1223 for prev in repo.changelog.parentrevs(rev):
1223 for prev in repo.changelog.parentrevs(rev):
1224 adjusted = dest
1224 adjusted = dest
1225 if prev != nullrev:
1225 if prev != nullrev:
1226 candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
1226 candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
1227 if candidate is not None:
1227 if candidate is not None:
1228 adjusted = state[candidate]
1228 adjusted = state[candidate]
1229 if adjusted == dest and dest in state:
1229 if adjusted == dest and dest in state:
1230 adjusted = state[dest]
1230 adjusted = state[dest]
1231 if adjusted == revtodo:
1231 if adjusted == revtodo:
1232 # sortsource should produce an order that makes this impossible
1232 # sortsource should produce an order that makes this impossible
1233 raise error.ProgrammingError(
1233 raise error.ProgrammingError(
1234 'rev %d should be rebased already at this time' % dest)
1234 'rev %d should be rebased already at this time' % dest)
1235 result.append(adjusted)
1235 result.append(adjusted)
1236 return result
1236 return result
1237
1237
1238 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
1238 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
1239 """
1239 """
1240 Abort if rebase will create divergence or rebase is noop because of markers
1240 Abort if rebase will create divergence or rebase is noop because of markers
1241
1241
1242 `rebaseobsrevs`: set of obsolete revision in source
1242 `rebaseobsrevs`: set of obsolete revision in source
1243 `rebaseobsskipped`: set of revisions from source skipped because they have
1243 `rebaseobsskipped`: set of revisions from source skipped because they have
1244 successors in destination or no non-obsolete successor.
1244 successors in destination or no non-obsolete successor.
1245 """
1245 """
1246 # Obsolete node with successors not in dest leads to divergence
1246 # Obsolete node with successors not in dest leads to divergence
1247 divergenceok = ui.configbool('experimental',
1247 divergenceok = ui.configbool('experimental',
1248 'evolution.allowdivergence')
1248 'evolution.allowdivergence')
1249 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
1249 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
1250
1250
1251 if divergencebasecandidates and not divergenceok:
1251 if divergencebasecandidates and not divergenceok:
1252 divhashes = (bytes(repo[r])
1252 divhashes = (bytes(repo[r])
1253 for r in divergencebasecandidates)
1253 for r in divergencebasecandidates)
1254 msg = _("this rebase will cause "
1254 msg = _("this rebase will cause "
1255 "divergences from: %s")
1255 "divergences from: %s")
1256 h = _("to force the rebase please set "
1256 h = _("to force the rebase please set "
1257 "experimental.evolution.allowdivergence=True")
1257 "experimental.evolution.allowdivergence=True")
1258 raise error.Abort(msg % (",".join(divhashes),), hint=h)
1258 raise error.Abort(msg % (",".join(divhashes),), hint=h)
1259
1259
1260 def successorrevs(unfi, rev):
1260 def successorrevs(unfi, rev):
1261 """yield revision numbers for successors of rev"""
1261 """yield revision numbers for successors of rev"""
1262 assert unfi.filtername is None
1262 assert unfi.filtername is None
1263 nodemap = unfi.changelog.nodemap
1263 nodemap = unfi.changelog.nodemap
1264 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
1264 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
1265 if s in nodemap:
1265 if s in nodemap:
1266 yield nodemap[s]
1266 yield nodemap[s]
1267
1267
1268 def defineparents(repo, rev, destmap, state, skipped, obsskipped):
1268 def defineparents(repo, rev, destmap, state, skipped, obsskipped):
1269 """Return new parents and optionally a merge base for rev being rebased
1269 """Return new parents and optionally a merge base for rev being rebased
1270
1270
1271 The destination specified by "dest" cannot always be used directly because
1271 The destination specified by "dest" cannot always be used directly because
1272 previously rebase result could affect destination. For example,
1272 previously rebase result could affect destination. For example,
1273
1273
1274 D E rebase -r C+D+E -d B
1274 D E rebase -r C+D+E -d B
1275 |/ C will be rebased to C'
1275 |/ C will be rebased to C'
1276 B C D's new destination will be C' instead of B
1276 B C D's new destination will be C' instead of B
1277 |/ E's new destination will be C' instead of B
1277 |/ E's new destination will be C' instead of B
1278 A
1278 A
1279
1279
1280 The new parents of a merge is slightly more complicated. See the comment
1280 The new parents of a merge is slightly more complicated. See the comment
1281 block below.
1281 block below.
1282 """
1282 """
1283 # use unfiltered changelog since successorrevs may return filtered nodes
1283 # use unfiltered changelog since successorrevs may return filtered nodes
1284 assert repo.filtername is None
1284 assert repo.filtername is None
1285 cl = repo.changelog
1285 cl = repo.changelog
1286 def isancestor(a, b):
1286 def isancestor(a, b):
1287 # take revision numbers instead of nodes
1287 # take revision numbers instead of nodes
1288 if a == b:
1288 if a == b:
1289 return True
1289 return True
1290 elif a > b:
1290 elif a > b:
1291 return False
1291 return False
1292 return cl.isancestor(cl.node(a), cl.node(b))
1292 return cl.isancestor(cl.node(a), cl.node(b))
1293
1293
1294 dest = destmap[rev]
1294 dest = destmap[rev]
1295 oldps = repo.changelog.parentrevs(rev) # old parents
1295 oldps = repo.changelog.parentrevs(rev) # old parents
1296 newps = [nullrev, nullrev] # new parents
1296 newps = [nullrev, nullrev] # new parents
1297 dests = adjustdest(repo, rev, destmap, state, skipped)
1297 dests = adjustdest(repo, rev, destmap, state, skipped)
1298 bases = list(oldps) # merge base candidates, initially just old parents
1298 bases = list(oldps) # merge base candidates, initially just old parents
1299
1299
1300 if all(r == nullrev for r in oldps[1:]):
1300 if all(r == nullrev for r in oldps[1:]):
1301 # For non-merge changeset, just move p to adjusted dest as requested.
1301 # For non-merge changeset, just move p to adjusted dest as requested.
1302 newps[0] = dests[0]
1302 newps[0] = dests[0]
1303 else:
1303 else:
1304 # For merge changeset, if we move p to dests[i] unconditionally, both
1304 # For merge changeset, if we move p to dests[i] unconditionally, both
1305 # parents may change and the end result looks like "the merge loses a
1305 # parents may change and the end result looks like "the merge loses a
1306 # parent", which is a surprise. This is a limit because "--dest" only
1306 # parent", which is a surprise. This is a limit because "--dest" only
1307 # accepts one dest per src.
1307 # accepts one dest per src.
1308 #
1308 #
1309 # Therefore, only move p with reasonable conditions (in this order):
1309 # Therefore, only move p with reasonable conditions (in this order):
1310 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1310 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1311 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1311 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1312 #
1312 #
1313 # Comparing with adjustdest, the logic here does some additional work:
1313 # Comparing with adjustdest, the logic here does some additional work:
1314 # 1. decide which parents will not be moved towards dest
1314 # 1. decide which parents will not be moved towards dest
1315 # 2. if the above decision is "no", should a parent still be moved
1315 # 2. if the above decision is "no", should a parent still be moved
1316 # because it was rebased?
1316 # because it was rebased?
1317 #
1317 #
1318 # For example:
1318 # For example:
1319 #
1319 #
1320 # C # "rebase -r C -d D" is an error since none of the parents
1320 # C # "rebase -r C -d D" is an error since none of the parents
1321 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1321 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1322 # A B D # B (using rule "2."), since B will be rebased.
1322 # A B D # B (using rule "2."), since B will be rebased.
1323 #
1323 #
1324 # The loop tries to be not rely on the fact that a Mercurial node has
1324 # The loop tries to be not rely on the fact that a Mercurial node has
1325 # at most 2 parents.
1325 # at most 2 parents.
1326 for i, p in enumerate(oldps):
1326 for i, p in enumerate(oldps):
1327 np = p # new parent
1327 np = p # new parent
1328 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1328 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1329 np = dests[i]
1329 np = dests[i]
1330 elif p in state and state[p] > 0:
1330 elif p in state and state[p] > 0:
1331 np = state[p]
1331 np = state[p]
1332
1332
1333 # "bases" only record "special" merge bases that cannot be
1333 # "bases" only record "special" merge bases that cannot be
1334 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1334 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1335 # For example:
1335 # For example:
1336 #
1336 #
1337 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1337 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1338 # | C # is B', but merge base for C is B, instead of
1338 # | C # is B', but merge base for C is B, instead of
1339 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1339 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1340 # | B # "state" edges are merged (so there will be an edge from
1340 # | B # "state" edges are merged (so there will be an edge from
1341 # |/ # B to B'), the merge base is still ancestor(C, B') in
1341 # |/ # B to B'), the merge base is still ancestor(C, B') in
1342 # A # the merged graph.
1342 # A # the merged graph.
1343 #
1343 #
1344 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1344 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1345 # which uses "virtual null merge" to explain this situation.
1345 # which uses "virtual null merge" to explain this situation.
1346 if isancestor(p, np):
1346 if isancestor(p, np):
1347 bases[i] = nullrev
1347 bases[i] = nullrev
1348
1348
1349 # If one parent becomes an ancestor of the other, drop the ancestor
1349 # If one parent becomes an ancestor of the other, drop the ancestor
1350 for j, x in enumerate(newps[:i]):
1350 for j, x in enumerate(newps[:i]):
1351 if x == nullrev:
1351 if x == nullrev:
1352 continue
1352 continue
1353 if isancestor(np, x): # CASE-1
1353 if isancestor(np, x): # CASE-1
1354 np = nullrev
1354 np = nullrev
1355 elif isancestor(x, np): # CASE-2
1355 elif isancestor(x, np): # CASE-2
1356 newps[j] = np
1356 newps[j] = np
1357 np = nullrev
1357 np = nullrev
1358 # New parents forming an ancestor relationship does not
1358 # New parents forming an ancestor relationship does not
1359 # mean the old parents have a similar relationship. Do not
1359 # mean the old parents have a similar relationship. Do not
1360 # set bases[x] to nullrev.
1360 # set bases[x] to nullrev.
1361 bases[j], bases[i] = bases[i], bases[j]
1361 bases[j], bases[i] = bases[i], bases[j]
1362
1362
1363 newps[i] = np
1363 newps[i] = np
1364
1364
1365 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1365 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1366 # base. If only p2 changes, merging using unchanged p1 as merge base is
1366 # base. If only p2 changes, merging using unchanged p1 as merge base is
1367 # suboptimal. Therefore swap parents to make the merge sane.
1367 # suboptimal. Therefore swap parents to make the merge sane.
1368 if newps[1] != nullrev and oldps[0] == newps[0]:
1368 if newps[1] != nullrev and oldps[0] == newps[0]:
1369 assert len(newps) == 2 and len(oldps) == 2
1369 assert len(newps) == 2 and len(oldps) == 2
1370 newps.reverse()
1370 newps.reverse()
1371 bases.reverse()
1371 bases.reverse()
1372
1372
1373 # No parent change might be an error because we fail to make rev a
1373 # No parent change might be an error because we fail to make rev a
1374 # descendent of requested dest. This can happen, for example:
1374 # descendent of requested dest. This can happen, for example:
1375 #
1375 #
1376 # C # rebase -r C -d D
1376 # C # rebase -r C -d D
1377 # /| # None of A and B will be changed to D and rebase fails.
1377 # /| # None of A and B will be changed to D and rebase fails.
1378 # A B D
1378 # A B D
1379 if set(newps) == set(oldps) and dest not in newps:
1379 if set(newps) == set(oldps) and dest not in newps:
1380 raise error.Abort(_('cannot rebase %d:%s without '
1380 raise error.Abort(_('cannot rebase %d:%s without '
1381 'moving at least one of its parents')
1381 'moving at least one of its parents')
1382 % (rev, repo[rev]))
1382 % (rev, repo[rev]))
1383
1383
1384 # Source should not be ancestor of dest. The check here guarantees it's
1384 # Source should not be ancestor of dest. The check here guarantees it's
1385 # impossible. With multi-dest, the initial check does not cover complex
1385 # impossible. With multi-dest, the initial check does not cover complex
1386 # cases since we don't have abstractions to dry-run rebase cheaply.
1386 # cases since we don't have abstractions to dry-run rebase cheaply.
1387 if any(p != nullrev and isancestor(rev, p) for p in newps):
1387 if any(p != nullrev and isancestor(rev, p) for p in newps):
1388 raise error.Abort(_('source is ancestor of destination'))
1388 raise error.Abort(_('source is ancestor of destination'))
1389
1389
1390 # "rebasenode" updates to new p1, use the corresponding merge base.
1390 # "rebasenode" updates to new p1, use the corresponding merge base.
1391 if bases[0] != nullrev:
1391 if bases[0] != nullrev:
1392 base = bases[0]
1392 base = bases[0]
1393 else:
1393 else:
1394 base = None
1394 base = None
1395
1395
1396 # Check if the merge will contain unwanted changes. That may happen if
1396 # Check if the merge will contain unwanted changes. That may happen if
1397 # there are multiple special (non-changelog ancestor) merge bases, which
1397 # there are multiple special (non-changelog ancestor) merge bases, which
1398 # cannot be handled well by the 3-way merge algorithm. For example:
1398 # cannot be handled well by the 3-way merge algorithm. For example:
1399 #
1399 #
1400 # F
1400 # F
1401 # /|
1401 # /|
1402 # D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
1402 # D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
1403 # | | # as merge base, the difference between D and F will include
1403 # | | # as merge base, the difference between D and F will include
1404 # B C # C, so the rebased F will contain C surprisingly. If "E" was
1404 # B C # C, so the rebased F will contain C surprisingly. If "E" was
1405 # |/ # chosen, the rebased F will contain B.
1405 # |/ # chosen, the rebased F will contain B.
1406 # A Z
1406 # A Z
1407 #
1407 #
1408 # But our merge base candidates (D and E in above case) could still be
1408 # But our merge base candidates (D and E in above case) could still be
1409 # better than the default (ancestor(F, Z) == null). Therefore still
1409 # better than the default (ancestor(F, Z) == null). Therefore still
1410 # pick one (so choose p1 above).
1410 # pick one (so choose p1 above).
1411 if sum(1 for b in bases if b != nullrev) > 1:
1411 if sum(1 for b in bases if b != nullrev) > 1:
1412 unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
1412 unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
1413 for i, base in enumerate(bases):
1413 for i, base in enumerate(bases):
1414 if base == nullrev:
1414 if base == nullrev:
1415 continue
1415 continue
1416 # Revisions in the side (not chosen as merge base) branch that
1416 # Revisions in the side (not chosen as merge base) branch that
1417 # might contain "surprising" contents
1417 # might contain "surprising" contents
1418 siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
1418 siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
1419 bases, base, base, dest))
1419 bases, base, base, dest))
1420
1420
1421 # If those revisions are covered by rebaseset, the result is good.
1421 # If those revisions are covered by rebaseset, the result is good.
1422 # A merge in rebaseset would be considered to cover its ancestors.
1422 # A merge in rebaseset would be considered to cover its ancestors.
1423 if siderevs:
1423 if siderevs:
1424 rebaseset = [r for r, d in state.items()
1424 rebaseset = [r for r, d in state.items()
1425 if d > 0 and r not in obsskipped]
1425 if d > 0 and r not in obsskipped]
1426 merges = [r for r in rebaseset
1426 merges = [r for r in rebaseset
1427 if cl.parentrevs(r)[1] != nullrev]
1427 if cl.parentrevs(r)[1] != nullrev]
1428 unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
1428 unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
1429 siderevs, merges, rebaseset))
1429 siderevs, merges, rebaseset))
1430
1430
1431 # Choose a merge base that has a minimal number of unwanted revs.
1431 # Choose a merge base that has a minimal number of unwanted revs.
1432 l, i = min((len(revs), i)
1432 l, i = min((len(revs), i)
1433 for i, revs in enumerate(unwanted) if revs is not None)
1433 for i, revs in enumerate(unwanted) if revs is not None)
1434 base = bases[i]
1434 base = bases[i]
1435
1435
1436 # newps[0] should match merge base if possible. Currently, if newps[i]
1436 # newps[0] should match merge base if possible. Currently, if newps[i]
1437 # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
1437 # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
1438 # the other's ancestor. In that case, it's fine to not swap newps here.
1438 # the other's ancestor. In that case, it's fine to not swap newps here.
1439 # (see CASE-1 and CASE-2 above)
1439 # (see CASE-1 and CASE-2 above)
1440 if i != 0 and newps[i] != nullrev:
1440 if i != 0 and newps[i] != nullrev:
1441 newps[0], newps[i] = newps[i], newps[0]
1441 newps[0], newps[i] = newps[i], newps[0]
1442
1442
1443 # The merge will include unwanted revisions. Abort now. Revisit this if
1443 # The merge will include unwanted revisions. Abort now. Revisit this if
1444 # we have a more advanced merge algorithm that handles multiple bases.
1444 # we have a more advanced merge algorithm that handles multiple bases.
1445 if l > 0:
1445 if l > 0:
1446 unwanteddesc = _(' or ').join(
1446 unwanteddesc = _(' or ').join(
1447 (', '.join('%d:%s' % (r, repo[r]) for r in revs)
1447 (', '.join('%d:%s' % (r, repo[r]) for r in revs)
1448 for revs in unwanted if revs is not None))
1448 for revs in unwanted if revs is not None))
1449 raise error.Abort(
1449 raise error.Abort(
1450 _('rebasing %d:%s will include unwanted changes from %s')
1450 _('rebasing %d:%s will include unwanted changes from %s')
1451 % (rev, repo[rev], unwanteddesc))
1451 % (rev, repo[rev], unwanteddesc))
1452
1452
1453 repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
1453 repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
1454
1454
1455 return newps[0], newps[1], base
1455 return newps[0], newps[1], base
1456
1456
1457 def isagitpatch(repo, patchname):
1457 def isagitpatch(repo, patchname):
1458 'Return true if the given patch is in git format'
1458 'Return true if the given patch is in git format'
1459 mqpatch = os.path.join(repo.mq.path, patchname)
1459 mqpatch = os.path.join(repo.mq.path, patchname)
1460 for line in patch.linereader(open(mqpatch, 'rb')):
1460 for line in patch.linereader(open(mqpatch, 'rb')):
1461 if line.startswith('diff --git'):
1461 if line.startswith('diff --git'):
1462 return True
1462 return True
1463 return False
1463 return False
1464
1464
1465 def updatemq(repo, state, skipped, **opts):
1465 def updatemq(repo, state, skipped, **opts):
1466 'Update rebased mq patches - finalize and then import them'
1466 'Update rebased mq patches - finalize and then import them'
1467 mqrebase = {}
1467 mqrebase = {}
1468 mq = repo.mq
1468 mq = repo.mq
1469 original_series = mq.fullseries[:]
1469 original_series = mq.fullseries[:]
1470 skippedpatches = set()
1470 skippedpatches = set()
1471
1471
1472 for p in mq.applied:
1472 for p in mq.applied:
1473 rev = repo[p.node].rev()
1473 rev = repo[p.node].rev()
1474 if rev in state:
1474 if rev in state:
1475 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1475 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1476 (rev, p.name))
1476 (rev, p.name))
1477 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1477 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1478 else:
1478 else:
1479 # Applied but not rebased, not sure this should happen
1479 # Applied but not rebased, not sure this should happen
1480 skippedpatches.add(p.name)
1480 skippedpatches.add(p.name)
1481
1481
1482 if mqrebase:
1482 if mqrebase:
1483 mq.finish(repo, mqrebase.keys())
1483 mq.finish(repo, mqrebase.keys())
1484
1484
1485 # We must start import from the newest revision
1485 # We must start import from the newest revision
1486 for rev in sorted(mqrebase, reverse=True):
1486 for rev in sorted(mqrebase, reverse=True):
1487 if rev not in skipped:
1487 if rev not in skipped:
1488 name, isgit = mqrebase[rev]
1488 name, isgit = mqrebase[rev]
1489 repo.ui.note(_('updating mq patch %s to %d:%s\n') %
1489 repo.ui.note(_('updating mq patch %s to %d:%s\n') %
1490 (name, state[rev], repo[state[rev]]))
1490 (name, state[rev], repo[state[rev]]))
1491 mq.qimport(repo, (), patchname=name, git=isgit,
1491 mq.qimport(repo, (), patchname=name, git=isgit,
1492 rev=["%d" % state[rev]])
1492 rev=["%d" % state[rev]])
1493 else:
1493 else:
1494 # Rebased and skipped
1494 # Rebased and skipped
1495 skippedpatches.add(mqrebase[rev][0])
1495 skippedpatches.add(mqrebase[rev][0])
1496
1496
1497 # Patches were either applied and rebased and imported in
1497 # Patches were either applied and rebased and imported in
1498 # order, applied and removed or unapplied. Discard the removed
1498 # order, applied and removed or unapplied. Discard the removed
1499 # ones while preserving the original series order and guards.
1499 # ones while preserving the original series order and guards.
1500 newseries = [s for s in original_series
1500 newseries = [s for s in original_series
1501 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1501 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1502 mq.fullseries[:] = newseries
1502 mq.fullseries[:] = newseries
1503 mq.seriesdirty = True
1503 mq.seriesdirty = True
1504 mq.savedirty()
1504 mq.savedirty()
1505
1505
1506 def storecollapsemsg(repo, collapsemsg):
1506 def storecollapsemsg(repo, collapsemsg):
1507 'Store the collapse message to allow recovery'
1507 'Store the collapse message to allow recovery'
1508 collapsemsg = collapsemsg or ''
1508 collapsemsg = collapsemsg or ''
1509 f = repo.vfs("last-message.txt", "w")
1509 f = repo.vfs("last-message.txt", "w")
1510 f.write("%s\n" % collapsemsg)
1510 f.write("%s\n" % collapsemsg)
1511 f.close()
1511 f.close()
1512
1512
1513 def clearcollapsemsg(repo):
1513 def clearcollapsemsg(repo):
1514 'Remove collapse message file'
1514 'Remove collapse message file'
1515 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1515 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1516
1516
1517 def restorecollapsemsg(repo, isabort):
1517 def restorecollapsemsg(repo, isabort):
1518 'Restore previously stored collapse message'
1518 'Restore previously stored collapse message'
1519 try:
1519 try:
1520 f = repo.vfs("last-message.txt")
1520 f = repo.vfs("last-message.txt")
1521 collapsemsg = f.readline().strip()
1521 collapsemsg = f.readline().strip()
1522 f.close()
1522 f.close()
1523 except IOError as err:
1523 except IOError as err:
1524 if err.errno != errno.ENOENT:
1524 if err.errno != errno.ENOENT:
1525 raise
1525 raise
1526 if isabort:
1526 if isabort:
1527 # Oh well, just abort like normal
1527 # Oh well, just abort like normal
1528 collapsemsg = ''
1528 collapsemsg = ''
1529 else:
1529 else:
1530 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1530 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1531 return collapsemsg
1531 return collapsemsg
1532
1532
1533 def clearstatus(repo):
1533 def clearstatus(repo):
1534 'Remove the status files'
1534 'Remove the status files'
1535 # Make sure the active transaction won't write the state file
1535 # Make sure the active transaction won't write the state file
1536 tr = repo.currenttransaction()
1536 tr = repo.currenttransaction()
1537 if tr:
1537 if tr:
1538 tr.removefilegenerator('rebasestate')
1538 tr.removefilegenerator('rebasestate')
1539 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1539 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1540
1540
1541 def needupdate(repo, state):
1541 def needupdate(repo, state):
1542 '''check whether we should `update --clean` away from a merge, or if
1542 '''check whether we should `update --clean` away from a merge, or if
1543 somehow the working dir got forcibly updated, e.g. by older hg'''
1543 somehow the working dir got forcibly updated, e.g. by older hg'''
1544 parents = [p.rev() for p in repo[None].parents()]
1544 parents = [p.rev() for p in repo[None].parents()]
1545
1545
1546 # Are we in a merge state at all?
1546 # Are we in a merge state at all?
1547 if len(parents) < 2:
1547 if len(parents) < 2:
1548 return False
1548 return False
1549
1549
1550 # We should be standing on the first as-of-yet unrebased commit.
1550 # We should be standing on the first as-of-yet unrebased commit.
1551 firstunrebased = min([old for old, new in state.iteritems()
1551 firstunrebased = min([old for old, new in state.iteritems()
1552 if new == nullrev])
1552 if new == nullrev])
1553 if firstunrebased in parents:
1553 if firstunrebased in parents:
1554 return True
1554 return True
1555
1555
1556 return False
1556 return False
1557
1557
1558 def abort(repo, originalwd, destmap, state, activebookmark=None, backup=True,
1558 def abort(repo, originalwd, destmap, state, activebookmark=None, backup=True,
1559 suppwarns=False):
1559 suppwarns=False):
1560 '''Restore the repository to its original state. Additional args:
1560 '''Restore the repository to its original state. Additional args:
1561
1561
1562 activebookmark: the name of the bookmark that should be active after the
1562 activebookmark: the name of the bookmark that should be active after the
1563 restore'''
1563 restore'''
1564
1564
1565 try:
1565 try:
1566 # If the first commits in the rebased set get skipped during the rebase,
1566 # If the first commits in the rebased set get skipped during the rebase,
1567 # their values within the state mapping will be the dest rev id. The
1567 # their values within the state mapping will be the dest rev id. The
1568 # rebased list must must not contain the dest rev (issue4896)
1568 # rebased list must must not contain the dest rev (issue4896)
1569 rebased = [s for r, s in state.items()
1569 rebased = [s for r, s in state.items()
1570 if s >= 0 and s != r and s != destmap[r]]
1570 if s >= 0 and s != r and s != destmap[r]]
1571 immutable = [d for d in rebased if not repo[d].mutable()]
1571 immutable = [d for d in rebased if not repo[d].mutable()]
1572 cleanup = True
1572 cleanup = True
1573 if immutable:
1573 if immutable:
1574 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1574 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1575 % ', '.join(bytes(repo[r]) for r in immutable),
1575 % ', '.join(bytes(repo[r]) for r in immutable),
1576 hint=_("see 'hg help phases' for details"))
1576 hint=_("see 'hg help phases' for details"))
1577 cleanup = False
1577 cleanup = False
1578
1578
1579 descendants = set()
1579 descendants = set()
1580 if rebased:
1580 if rebased:
1581 descendants = set(repo.changelog.descendants(rebased))
1581 descendants = set(repo.changelog.descendants(rebased))
1582 if descendants - set(rebased):
1582 if descendants - set(rebased):
1583 repo.ui.warn(_("warning: new changesets detected on destination "
1583 repo.ui.warn(_("warning: new changesets detected on destination "
1584 "branch, can't strip\n"))
1584 "branch, can't strip\n"))
1585 cleanup = False
1585 cleanup = False
1586
1586
1587 if cleanup:
1587 if cleanup:
1588 shouldupdate = False
1588 shouldupdate = False
1589 if rebased:
1589 if rebased:
1590 strippoints = [
1590 strippoints = [
1591 c.node() for c in repo.set('roots(%ld)', rebased)]
1591 c.node() for c in repo.set('roots(%ld)', rebased)]
1592
1592
1593 updateifonnodes = set(rebased)
1593 updateifonnodes = set(rebased)
1594 updateifonnodes.update(destmap.values())
1594 updateifonnodes.update(destmap.values())
1595 updateifonnodes.add(originalwd)
1595 updateifonnodes.add(originalwd)
1596 shouldupdate = repo['.'].rev() in updateifonnodes
1596 shouldupdate = repo['.'].rev() in updateifonnodes
1597
1597
1598 # Update away from the rebase if necessary
1598 # Update away from the rebase if necessary
1599 if shouldupdate or needupdate(repo, state):
1599 if shouldupdate or needupdate(repo, state):
1600 mergemod.update(repo, originalwd, False, True)
1600 mergemod.update(repo, originalwd, False, True)
1601
1601
1602 # Strip from the first rebased revision
1602 # Strip from the first rebased revision
1603 if rebased:
1603 if rebased:
1604 repair.strip(repo.ui, repo, strippoints, backup=backup)
1604 repair.strip(repo.ui, repo, strippoints, backup=backup)
1605
1605
1606 if activebookmark and activebookmark in repo._bookmarks:
1606 if activebookmark and activebookmark in repo._bookmarks:
1607 bookmarks.activate(repo, activebookmark)
1607 bookmarks.activate(repo, activebookmark)
1608
1608
1609 finally:
1609 finally:
1610 clearstatus(repo)
1610 clearstatus(repo)
1611 clearcollapsemsg(repo)
1611 clearcollapsemsg(repo)
1612 if not suppwarns:
1612 if not suppwarns:
1613 repo.ui.warn(_('rebase aborted\n'))
1613 repo.ui.warn(_('rebase aborted\n'))
1614 return 0
1614 return 0
1615
1615
1616 def sortsource(destmap):
1616 def sortsource(destmap):
1617 """yield source revisions in an order that we only rebase things once
1617 """yield source revisions in an order that we only rebase things once
1618
1618
1619 If source and destination overlaps, we should filter out revisions
1619 If source and destination overlaps, we should filter out revisions
1620 depending on other revisions which hasn't been rebased yet.
1620 depending on other revisions which hasn't been rebased yet.
1621
1621
1622 Yield a sorted list of revisions each time.
1622 Yield a sorted list of revisions each time.
1623
1623
1624 For example, when rebasing A to B, B to C. This function yields [B], then
1624 For example, when rebasing A to B, B to C. This function yields [B], then
1625 [A], indicating B needs to be rebased first.
1625 [A], indicating B needs to be rebased first.
1626
1626
1627 Raise if there is a cycle so the rebase is impossible.
1627 Raise if there is a cycle so the rebase is impossible.
1628 """
1628 """
1629 srcset = set(destmap)
1629 srcset = set(destmap)
1630 while srcset:
1630 while srcset:
1631 srclist = sorted(srcset)
1631 srclist = sorted(srcset)
1632 result = []
1632 result = []
1633 for r in srclist:
1633 for r in srclist:
1634 if destmap[r] not in srcset:
1634 if destmap[r] not in srcset:
1635 result.append(r)
1635 result.append(r)
1636 if not result:
1636 if not result:
1637 raise error.Abort(_('source and destination form a cycle'))
1637 raise error.Abort(_('source and destination form a cycle'))
1638 srcset -= set(result)
1638 srcset -= set(result)
1639 yield result
1639 yield result
1640
1640
1641 def buildstate(repo, destmap, collapse):
1641 def buildstate(repo, destmap, collapse):
1642 '''Define which revisions are going to be rebased and where
1642 '''Define which revisions are going to be rebased and where
1643
1643
1644 repo: repo
1644 repo: repo
1645 destmap: {srcrev: destrev}
1645 destmap: {srcrev: destrev}
1646 '''
1646 '''
1647 rebaseset = destmap.keys()
1647 rebaseset = destmap.keys()
1648 originalwd = repo['.'].rev()
1648 originalwd = repo['.'].rev()
1649
1649
1650 # This check isn't strictly necessary, since mq detects commits over an
1650 # This check isn't strictly necessary, since mq detects commits over an
1651 # applied patch. But it prevents messing up the working directory when
1651 # applied patch. But it prevents messing up the working directory when
1652 # a partially completed rebase is blocked by mq.
1652 # a partially completed rebase is blocked by mq.
1653 if 'qtip' in repo.tags():
1653 if 'qtip' in repo.tags():
1654 mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
1654 mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
1655 if set(destmap.values()) & mqapplied:
1655 if set(destmap.values()) & mqapplied:
1656 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1656 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1657
1657
1658 # Get "cycle" error early by exhausting the generator.
1658 # Get "cycle" error early by exhausting the generator.
1659 sortedsrc = list(sortsource(destmap)) # a list of sorted revs
1659 sortedsrc = list(sortsource(destmap)) # a list of sorted revs
1660 if not sortedsrc:
1660 if not sortedsrc:
1661 raise error.Abort(_('no matching revisions'))
1661 raise error.Abort(_('no matching revisions'))
1662
1662
1663 # Only check the first batch of revisions to rebase not depending on other
1663 # Only check the first batch of revisions to rebase not depending on other
1664 # rebaseset. This means "source is ancestor of destination" for the second
1664 # rebaseset. This means "source is ancestor of destination" for the second
1665 # (and following) batches of revisions are not checked here. We rely on
1665 # (and following) batches of revisions are not checked here. We rely on
1666 # "defineparents" to do that check.
1666 # "defineparents" to do that check.
1667 roots = list(repo.set('roots(%ld)', sortedsrc[0]))
1667 roots = list(repo.set('roots(%ld)', sortedsrc[0]))
1668 if not roots:
1668 if not roots:
1669 raise error.Abort(_('no matching revisions'))
1669 raise error.Abort(_('no matching revisions'))
1670 def revof(r):
1670 def revof(r):
1671 return r.rev()
1671 return r.rev()
1672 roots = sorted(roots, key=revof)
1672 roots = sorted(roots, key=revof)
1673 state = dict.fromkeys(rebaseset, revtodo)
1673 state = dict.fromkeys(rebaseset, revtodo)
1674 emptyrebase = (len(sortedsrc) == 1)
1674 emptyrebase = (len(sortedsrc) == 1)
1675 for root in roots:
1675 for root in roots:
1676 dest = repo[destmap[root.rev()]]
1676 dest = repo[destmap[root.rev()]]
1677 commonbase = root.ancestor(dest)
1677 commonbase = root.ancestor(dest)
1678 if commonbase == root:
1678 if commonbase == root:
1679 raise error.Abort(_('source is ancestor of destination'))
1679 raise error.Abort(_('source is ancestor of destination'))
1680 if commonbase == dest:
1680 if commonbase == dest:
1681 wctx = repo[None]
1681 wctx = repo[None]
1682 if dest == wctx.p1():
1682 if dest == wctx.p1():
1683 # when rebasing to '.', it will use the current wd branch name
1683 # when rebasing to '.', it will use the current wd branch name
1684 samebranch = root.branch() == wctx.branch()
1684 samebranch = root.branch() == wctx.branch()
1685 else:
1685 else:
1686 samebranch = root.branch() == dest.branch()
1686 samebranch = root.branch() == dest.branch()
1687 if not collapse and samebranch and dest in root.parents():
1687 if not collapse and samebranch and dest in root.parents():
1688 # mark the revision as done by setting its new revision
1688 # mark the revision as done by setting its new revision
1689 # equal to its old (current) revisions
1689 # equal to its old (current) revisions
1690 state[root.rev()] = root.rev()
1690 state[root.rev()] = root.rev()
1691 repo.ui.debug('source is a child of destination\n')
1691 repo.ui.debug('source is a child of destination\n')
1692 continue
1692 continue
1693
1693
1694 emptyrebase = False
1694 emptyrebase = False
1695 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1695 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1696 if emptyrebase:
1696 if emptyrebase:
1697 return None
1697 return None
1698 for rev in sorted(state):
1698 for rev in sorted(state):
1699 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1699 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1700 # if all parents of this revision are done, then so is this revision
1700 # if all parents of this revision are done, then so is this revision
1701 if parents and all((state.get(p) == p for p in parents)):
1701 if parents and all((state.get(p) == p for p in parents)):
1702 state[rev] = rev
1702 state[rev] = rev
1703 return originalwd, destmap, state
1703 return originalwd, destmap, state
1704
1704
1705 def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None,
1705 def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None,
1706 keepf=False, fm=None):
1706 keepf=False, fm=None):
1707 """dispose of rebased revision at the end of the rebase
1707 """dispose of rebased revision at the end of the rebase
1708
1708
1709 If `collapsedas` is not None, the rebase was a collapse whose result if the
1709 If `collapsedas` is not None, the rebase was a collapse whose result if the
1710 `collapsedas` node.
1710 `collapsedas` node.
1711
1711
1712 If `keepf` is not True, the rebase has --keep set and no nodes should be
1712 If `keepf` is not True, the rebase has --keep set and no nodes should be
1713 removed (but bookmarks still need to be moved).
1713 removed (but bookmarks still need to be moved).
1714 """
1714 """
1715 tonode = repo.changelog.node
1715 tonode = repo.changelog.node
1716 replacements = {}
1716 replacements = {}
1717 moves = {}
1717 moves = {}
1718 for rev, newrev in sorted(state.items()):
1718 for rev, newrev in sorted(state.items()):
1719 if newrev >= 0 and newrev != rev:
1719 if newrev >= 0 and newrev != rev:
1720 oldnode = tonode(rev)
1720 oldnode = tonode(rev)
1721 newnode = collapsedas or tonode(newrev)
1721 newnode = collapsedas or tonode(newrev)
1722 moves[oldnode] = newnode
1722 moves[oldnode] = newnode
1723 if not keepf:
1723 if not keepf:
1724 if rev in skipped:
1724 if rev in skipped:
1725 succs = ()
1725 succs = ()
1726 else:
1726 else:
1727 succs = (newnode,)
1727 succs = (newnode,)
1728 replacements[oldnode] = succs
1728 replacements[oldnode] = succs
1729 scmutil.cleanupnodes(repo, replacements, 'rebase', moves)
1729 scmutil.cleanupnodes(repo, replacements, 'rebase', moves)
1730 if fm:
1730 if fm:
1731 hf = fm.hexfunc
1731 hf = fm.hexfunc
1732 fl = fm.formatlist
1732 fl = fm.formatlist
1733 fd = fm.formatdict
1733 fd = fm.formatdict
1734 nodechanges = fd({hf(oldn): fl([hf(n) for n in newn], name='node')
1734 nodechanges = fd({hf(oldn): fl([hf(n) for n in newn], name='node')
1735 for oldn, newn in replacements.iteritems()},
1735 for oldn, newn in replacements.iteritems()},
1736 key="oldnode", value="newnodes")
1736 key="oldnode", value="newnodes")
1737 fm.data(nodechanges=nodechanges)
1737 fm.data(nodechanges=nodechanges)
1738
1738
1739 def pullrebase(orig, ui, repo, *args, **opts):
1739 def pullrebase(orig, ui, repo, *args, **opts):
1740 'Call rebase after pull if the latter has been invoked with --rebase'
1740 'Call rebase after pull if the latter has been invoked with --rebase'
1741 ret = None
1741 ret = None
1742 if opts.get(r'rebase'):
1742 if opts.get(r'rebase'):
1743 if ui.configbool('commands', 'rebase.requiredest'):
1743 if ui.configbool('commands', 'rebase.requiredest'):
1744 msg = _('rebase destination required by configuration')
1744 msg = _('rebase destination required by configuration')
1745 hint = _('use hg pull followed by hg rebase -d DEST')
1745 hint = _('use hg pull followed by hg rebase -d DEST')
1746 raise error.Abort(msg, hint=hint)
1746 raise error.Abort(msg, hint=hint)
1747
1747
1748 with repo.wlock(), repo.lock():
1748 with repo.wlock(), repo.lock():
1749 if opts.get(r'update'):
1749 if opts.get(r'update'):
1750 del opts[r'update']
1750 del opts[r'update']
1751 ui.debug('--update and --rebase are not compatible, ignoring '
1751 ui.debug('--update and --rebase are not compatible, ignoring '
1752 'the update flag\n')
1752 'the update flag\n')
1753
1753
1754 cmdutil.checkunfinished(repo)
1754 cmdutil.checkunfinished(repo)
1755 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1755 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1756 'please commit or shelve your changes first'))
1756 'please commit or shelve your changes first'))
1757
1757
1758 revsprepull = len(repo)
1758 revsprepull = len(repo)
1759 origpostincoming = commands.postincoming
1759 origpostincoming = commands.postincoming
1760 def _dummy(*args, **kwargs):
1760 def _dummy(*args, **kwargs):
1761 pass
1761 pass
1762 commands.postincoming = _dummy
1762 commands.postincoming = _dummy
1763 try:
1763 try:
1764 ret = orig(ui, repo, *args, **opts)
1764 ret = orig(ui, repo, *args, **opts)
1765 finally:
1765 finally:
1766 commands.postincoming = origpostincoming
1766 commands.postincoming = origpostincoming
1767 revspostpull = len(repo)
1767 revspostpull = len(repo)
1768 if revspostpull > revsprepull:
1768 if revspostpull > revsprepull:
1769 # --rev option from pull conflict with rebase own --rev
1769 # --rev option from pull conflict with rebase own --rev
1770 # dropping it
1770 # dropping it
1771 if r'rev' in opts:
1771 if r'rev' in opts:
1772 del opts[r'rev']
1772 del opts[r'rev']
1773 # positional argument from pull conflicts with rebase's own
1773 # positional argument from pull conflicts with rebase's own
1774 # --source.
1774 # --source.
1775 if r'source' in opts:
1775 if r'source' in opts:
1776 del opts[r'source']
1776 del opts[r'source']
1777 # revsprepull is the len of the repo, not revnum of tip.
1777 # revsprepull is the len of the repo, not revnum of tip.
1778 destspace = list(repo.changelog.revs(start=revsprepull))
1778 destspace = list(repo.changelog.revs(start=revsprepull))
1779 opts[r'_destspace'] = destspace
1779 opts[r'_destspace'] = destspace
1780 try:
1780 try:
1781 rebase(ui, repo, **opts)
1781 rebase(ui, repo, **opts)
1782 except error.NoMergeDestAbort:
1782 except error.NoMergeDestAbort:
1783 # we can maybe update instead
1783 # we can maybe update instead
1784 rev, _a, _b = destutil.destupdate(repo)
1784 rev, _a, _b = destutil.destupdate(repo)
1785 if rev == repo['.'].rev():
1785 if rev == repo['.'].rev():
1786 ui.status(_('nothing to rebase\n'))
1786 ui.status(_('nothing to rebase\n'))
1787 else:
1787 else:
1788 ui.status(_('nothing to rebase - updating instead\n'))
1788 ui.status(_('nothing to rebase - updating instead\n'))
1789 # not passing argument to get the bare update behavior
1789 # not passing argument to get the bare update behavior
1790 # with warning and trumpets
1790 # with warning and trumpets
1791 commands.update(ui, repo)
1791 commands.update(ui, repo)
1792 else:
1792 else:
1793 if opts.get(r'tool'):
1793 if opts.get(r'tool'):
1794 raise error.Abort(_('--tool can only be used with --rebase'))
1794 raise error.Abort(_('--tool can only be used with --rebase'))
1795 ret = orig(ui, repo, *args, **opts)
1795 ret = orig(ui, repo, *args, **opts)
1796
1796
1797 return ret
1797 return ret
1798
1798
1799 def _filterobsoleterevs(repo, revs):
1799 def _filterobsoleterevs(repo, revs):
1800 """returns a set of the obsolete revisions in revs"""
1800 """returns a set of the obsolete revisions in revs"""
1801 return set(r for r in revs if repo[r].obsolete())
1801 return set(r for r in revs if repo[r].obsolete())
1802
1802
1803 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
1803 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
1804 """Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
1804 """Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
1805
1805
1806 `obsoletenotrebased` is a mapping mapping obsolete => successor for all
1806 `obsoletenotrebased` is a mapping mapping obsolete => successor for all
1807 obsolete nodes to be rebased given in `rebaseobsrevs`.
1807 obsolete nodes to be rebased given in `rebaseobsrevs`.
1808
1808
1809 `obsoletewithoutsuccessorindestination` is a set with obsolete revisions
1809 `obsoletewithoutsuccessorindestination` is a set with obsolete revisions
1810 without a successor in destination.
1810 without a successor in destination.
1811
1811
1812 `obsoleteextinctsuccessors` is a set of obsolete revisions with only
1812 `obsoleteextinctsuccessors` is a set of obsolete revisions with only
1813 obsolete successors.
1813 obsolete successors.
1814 """
1814 """
1815 obsoletenotrebased = {}
1815 obsoletenotrebased = {}
1816 obsoletewithoutsuccessorindestination = set([])
1816 obsoletewithoutsuccessorindestination = set([])
1817 obsoleteextinctsuccessors = set([])
1817 obsoleteextinctsuccessors = set([])
1818
1818
1819 assert repo.filtername is None
1819 assert repo.filtername is None
1820 cl = repo.changelog
1820 cl = repo.changelog
1821 nodemap = cl.nodemap
1821 nodemap = cl.nodemap
1822 extinctnodes = set(cl.node(r) for r in repo.revs('extinct()'))
1822 extinctnodes = set(cl.node(r) for r in repo.revs('extinct()'))
1823 for srcrev in rebaseobsrevs:
1823 for srcrev in rebaseobsrevs:
1824 srcnode = cl.node(srcrev)
1824 srcnode = cl.node(srcrev)
1825 destnode = cl.node(destmap[srcrev])
1825 destnode = cl.node(destmap[srcrev])
1826 # XXX: more advanced APIs are required to handle split correctly
1826 # XXX: more advanced APIs are required to handle split correctly
1827 successors = set(obsutil.allsuccessors(repo.obsstore, [srcnode]))
1827 successors = set(obsutil.allsuccessors(repo.obsstore, [srcnode]))
1828 # obsutil.allsuccessors includes node itself
1828 # obsutil.allsuccessors includes node itself
1829 successors.remove(srcnode)
1829 successors.remove(srcnode)
1830 if successors.issubset(extinctnodes):
1830 if successors.issubset(extinctnodes):
1831 # all successors are extinct
1831 # all successors are extinct
1832 obsoleteextinctsuccessors.add(srcrev)
1832 obsoleteextinctsuccessors.add(srcrev)
1833 if not successors:
1833 if not successors:
1834 # no successor
1834 # no successor
1835 obsoletenotrebased[srcrev] = None
1835 obsoletenotrebased[srcrev] = None
1836 else:
1836 else:
1837 for succnode in successors:
1837 for succnode in successors:
1838 if succnode not in nodemap:
1838 if succnode not in nodemap:
1839 continue
1839 continue
1840 if cl.isancestor(succnode, destnode):
1840 if cl.isancestor(succnode, destnode):
1841 obsoletenotrebased[srcrev] = nodemap[succnode]
1841 obsoletenotrebased[srcrev] = nodemap[succnode]
1842 break
1842 break
1843 else:
1843 else:
1844 # If 'srcrev' has a successor in rebase set but none in
1844 # If 'srcrev' has a successor in rebase set but none in
1845 # destination (which would be catched above), we shall skip it
1845 # destination (which would be catched above), we shall skip it
1846 # and its descendants to avoid divergence.
1846 # and its descendants to avoid divergence.
1847 if any(nodemap[s] in destmap for s in successors
1847 if any(nodemap[s] in destmap for s in successors
1848 if s in nodemap):
1848 if s in nodemap):
1849 obsoletewithoutsuccessorindestination.add(srcrev)
1849 obsoletewithoutsuccessorindestination.add(srcrev)
1850
1850
1851 return (
1851 return (
1852 obsoletenotrebased,
1852 obsoletenotrebased,
1853 obsoletewithoutsuccessorindestination,
1853 obsoletewithoutsuccessorindestination,
1854 obsoleteextinctsuccessors,
1854 obsoleteextinctsuccessors,
1855 )
1855 )
1856
1856
1857 def summaryhook(ui, repo):
1857 def summaryhook(ui, repo):
1858 if not repo.vfs.exists('rebasestate'):
1858 if not repo.vfs.exists('rebasestate'):
1859 return
1859 return
1860 try:
1860 try:
1861 rbsrt = rebaseruntime(repo, ui, {})
1861 rbsrt = rebaseruntime(repo, ui, {})
1862 rbsrt.restorestatus()
1862 rbsrt.restorestatus()
1863 state = rbsrt.state
1863 state = rbsrt.state
1864 except error.RepoLookupError:
1864 except error.RepoLookupError:
1865 # i18n: column positioning for "hg summary"
1865 # i18n: column positioning for "hg summary"
1866 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1866 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1867 ui.write(msg)
1867 ui.write(msg)
1868 return
1868 return
1869 numrebased = len([i for i in state.itervalues() if i >= 0])
1869 numrebased = len([i for i in state.itervalues() if i >= 0])
1870 # i18n: column positioning for "hg summary"
1870 # i18n: column positioning for "hg summary"
1871 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1871 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1872 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1872 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1873 ui.label(_('%d remaining'), 'rebase.remaining') %
1873 ui.label(_('%d remaining'), 'rebase.remaining') %
1874 (len(state) - numrebased)))
1874 (len(state) - numrebased)))
1875
1875
1876 def uisetup(ui):
1876 def uisetup(ui):
1877 #Replace pull with a decorator to provide --rebase option
1877 #Replace pull with a decorator to provide --rebase option
1878 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1878 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1879 entry[1].append(('', 'rebase', None,
1879 entry[1].append(('', 'rebase', None,
1880 _("rebase working directory to branch head")))
1880 _("rebase working directory to branch head")))
1881 entry[1].append(('t', 'tool', '',
1881 entry[1].append(('t', 'tool', '',
1882 _("specify merge tool for rebase")))
1882 _("specify merge tool for rebase")))
1883 cmdutil.summaryhooks.add('rebase', summaryhook)
1883 cmdutil.summaryhooks.add('rebase', summaryhook)
1884 cmdutil.unfinishedstates.append(
1884 cmdutil.unfinishedstates.append(
1885 ['rebasestate', False, False, _('rebase in progress'),
1885 ['rebasestate', False, False, _('rebase in progress'),
1886 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1886 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1887 cmdutil.afterresolvedstates.append(
1887 cmdutil.afterresolvedstates.append(
1888 ['rebasestate', _('hg rebase --continue')])
1888 ['rebasestate', _('hg rebase --continue')])
@@ -1,1078 +1,1078 b''
1 # shelve.py - save/restore working directory state
1 # shelve.py - save/restore working directory state
2 #
2 #
3 # Copyright 2013 Facebook, Inc.
3 # Copyright 2013 Facebook, Inc.
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 """save and restore changes to the working directory
8 """save and restore changes to the working directory
9
9
10 The "hg shelve" command saves changes made to the working directory
10 The "hg shelve" command saves changes made to the working directory
11 and reverts those changes, resetting the working directory to a clean
11 and reverts those changes, resetting the working directory to a clean
12 state.
12 state.
13
13
14 Later on, the "hg unshelve" command restores the changes saved by "hg
14 Later on, the "hg unshelve" command restores the changes saved by "hg
15 shelve". Changes can be restored even after updating to a different
15 shelve". Changes can be restored even after updating to a different
16 parent, in which case Mercurial's merge machinery will resolve any
16 parent, in which case Mercurial's merge machinery will resolve any
17 conflicts if necessary.
17 conflicts if necessary.
18
18
19 You can have more than one shelved change outstanding at a time; each
19 You can have more than one shelved change outstanding at a time; each
20 shelved change has a distinct name. For details, see the help for "hg
20 shelved change has a distinct name. For details, see the help for "hg
21 shelve".
21 shelve".
22 """
22 """
23 from __future__ import absolute_import
23 from __future__ import absolute_import
24
24
25 import collections
25 import collections
26 import errno
26 import errno
27 import itertools
27 import itertools
28 import stat
28 import stat
29
29
30 from mercurial.i18n import _
30 from mercurial.i18n import _
31 from mercurial import (
31 from mercurial import (
32 bookmarks,
32 bookmarks,
33 bundle2,
33 bundle2,
34 bundlerepo,
34 bundlerepo,
35 changegroup,
35 changegroup,
36 cmdutil,
36 cmdutil,
37 discovery,
37 discovery,
38 error,
38 error,
39 exchange,
39 exchange,
40 hg,
40 hg,
41 lock as lockmod,
41 lock as lockmod,
42 mdiff,
42 mdiff,
43 merge,
43 merge,
44 node as nodemod,
44 node as nodemod,
45 patch,
45 patch,
46 phases,
46 phases,
47 pycompat,
47 pycompat,
48 registrar,
48 registrar,
49 repair,
49 repair,
50 scmutil,
50 scmutil,
51 templatefilters,
51 templatefilters,
52 util,
52 util,
53 vfs as vfsmod,
53 vfs as vfsmod,
54 )
54 )
55
55
56 from . import (
56 from . import (
57 rebase,
57 rebase,
58 )
58 )
59 from mercurial.utils import (
59 from mercurial.utils import (
60 dateutil,
60 dateutil,
61 stringutil,
61 stringutil,
62 )
62 )
63
63
64 cmdtable = {}
64 cmdtable = {}
65 command = registrar.command(cmdtable)
65 command = registrar.command(cmdtable)
66 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
66 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
67 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
67 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
68 # be specifying the version(s) of Mercurial they are tested with, or
68 # be specifying the version(s) of Mercurial they are tested with, or
69 # leave the attribute unspecified.
69 # leave the attribute unspecified.
70 testedwith = 'ships-with-hg-core'
70 testedwith = 'ships-with-hg-core'
71
71
72 configtable = {}
72 configtable = {}
73 configitem = registrar.configitem(configtable)
73 configitem = registrar.configitem(configtable)
74
74
75 configitem('shelve', 'maxbackups',
75 configitem('shelve', 'maxbackups',
76 default=10,
76 default=10,
77 )
77 )
78
78
79 backupdir = 'shelve-backup'
79 backupdir = 'shelve-backup'
80 shelvedir = 'shelved'
80 shelvedir = 'shelved'
81 shelvefileextensions = ['hg', 'patch', 'oshelve']
81 shelvefileextensions = ['hg', 'patch', 'oshelve']
82 # universal extension is present in all types of shelves
82 # universal extension is present in all types of shelves
83 patchextension = 'patch'
83 patchextension = 'patch'
84
84
85 # we never need the user, so we use a
85 # we never need the user, so we use a
86 # generic user for all shelve operations
86 # generic user for all shelve operations
87 shelveuser = 'shelve@localhost'
87 shelveuser = 'shelve@localhost'
88
88
89 class shelvedfile(object):
89 class shelvedfile(object):
90 """Helper for the file storing a single shelve
90 """Helper for the file storing a single shelve
91
91
92 Handles common functions on shelve files (.hg/.patch) using
92 Handles common functions on shelve files (.hg/.patch) using
93 the vfs layer"""
93 the vfs layer"""
94 def __init__(self, repo, name, filetype=None):
94 def __init__(self, repo, name, filetype=None):
95 self.repo = repo
95 self.repo = repo
96 self.name = name
96 self.name = name
97 self.vfs = vfsmod.vfs(repo.vfs.join(shelvedir))
97 self.vfs = vfsmod.vfs(repo.vfs.join(shelvedir))
98 self.backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
98 self.backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
99 self.ui = self.repo.ui
99 self.ui = self.repo.ui
100 if filetype:
100 if filetype:
101 self.fname = name + '.' + filetype
101 self.fname = name + '.' + filetype
102 else:
102 else:
103 self.fname = name
103 self.fname = name
104
104
105 def exists(self):
105 def exists(self):
106 return self.vfs.exists(self.fname)
106 return self.vfs.exists(self.fname)
107
107
108 def filename(self):
108 def filename(self):
109 return self.vfs.join(self.fname)
109 return self.vfs.join(self.fname)
110
110
111 def backupfilename(self):
111 def backupfilename(self):
112 def gennames(base):
112 def gennames(base):
113 yield base
113 yield base
114 base, ext = base.rsplit('.', 1)
114 base, ext = base.rsplit('.', 1)
115 for i in itertools.count(1):
115 for i in itertools.count(1):
116 yield '%s-%d.%s' % (base, i, ext)
116 yield '%s-%d.%s' % (base, i, ext)
117
117
118 name = self.backupvfs.join(self.fname)
118 name = self.backupvfs.join(self.fname)
119 for n in gennames(name):
119 for n in gennames(name):
120 if not self.backupvfs.exists(n):
120 if not self.backupvfs.exists(n):
121 return n
121 return n
122
122
123 def movetobackup(self):
123 def movetobackup(self):
124 if not self.backupvfs.isdir():
124 if not self.backupvfs.isdir():
125 self.backupvfs.makedir()
125 self.backupvfs.makedir()
126 util.rename(self.filename(), self.backupfilename())
126 util.rename(self.filename(), self.backupfilename())
127
127
128 def stat(self):
128 def stat(self):
129 return self.vfs.stat(self.fname)
129 return self.vfs.stat(self.fname)
130
130
131 def opener(self, mode='rb'):
131 def opener(self, mode='rb'):
132 try:
132 try:
133 return self.vfs(self.fname, mode)
133 return self.vfs(self.fname, mode)
134 except IOError as err:
134 except IOError as err:
135 if err.errno != errno.ENOENT:
135 if err.errno != errno.ENOENT:
136 raise
136 raise
137 raise error.Abort(_("shelved change '%s' not found") % self.name)
137 raise error.Abort(_("shelved change '%s' not found") % self.name)
138
138
139 def applybundle(self):
139 def applybundle(self):
140 fp = self.opener()
140 fp = self.opener()
141 try:
141 try:
142 gen = exchange.readbundle(self.repo.ui, fp, self.fname, self.vfs)
142 gen = exchange.readbundle(self.repo.ui, fp, self.fname, self.vfs)
143 bundle2.applybundle(self.repo, gen, self.repo.currenttransaction(),
143 bundle2.applybundle(self.repo, gen, self.repo.currenttransaction(),
144 source='unshelve',
144 source='unshelve',
145 url='bundle:' + self.vfs.join(self.fname),
145 url='bundle:' + self.vfs.join(self.fname),
146 targetphase=phases.secret)
146 targetphase=phases.secret)
147 finally:
147 finally:
148 fp.close()
148 fp.close()
149
149
150 def bundlerepo(self):
150 def bundlerepo(self):
151 return bundlerepo.bundlerepository(self.repo.baseui, self.repo.root,
151 return bundlerepo.bundlerepository(self.repo.baseui, self.repo.root,
152 self.vfs.join(self.fname))
152 self.vfs.join(self.fname))
153 def writebundle(self, bases, node):
153 def writebundle(self, bases, node):
154 cgversion = changegroup.safeversion(self.repo)
154 cgversion = changegroup.safeversion(self.repo)
155 if cgversion == '01':
155 if cgversion == '01':
156 btype = 'HG10BZ'
156 btype = 'HG10BZ'
157 compression = None
157 compression = None
158 else:
158 else:
159 btype = 'HG20'
159 btype = 'HG20'
160 compression = 'BZ'
160 compression = 'BZ'
161
161
162 outgoing = discovery.outgoing(self.repo, missingroots=bases,
162 outgoing = discovery.outgoing(self.repo, missingroots=bases,
163 missingheads=[node])
163 missingheads=[node])
164 cg = changegroup.makechangegroup(self.repo, outgoing, cgversion,
164 cg = changegroup.makechangegroup(self.repo, outgoing, cgversion,
165 'shelve')
165 'shelve')
166
166
167 bundle2.writebundle(self.ui, cg, self.fname, btype, self.vfs,
167 bundle2.writebundle(self.ui, cg, self.fname, btype, self.vfs,
168 compression=compression)
168 compression=compression)
169
169
170 def writeobsshelveinfo(self, info):
170 def writeobsshelveinfo(self, info):
171 scmutil.simplekeyvaluefile(self.vfs, self.fname).write(info)
171 scmutil.simplekeyvaluefile(self.vfs, self.fname).write(info)
172
172
173 def readobsshelveinfo(self):
173 def readobsshelveinfo(self):
174 return scmutil.simplekeyvaluefile(self.vfs, self.fname).read()
174 return scmutil.simplekeyvaluefile(self.vfs, self.fname).read()
175
175
176 class shelvedstate(object):
176 class shelvedstate(object):
177 """Handle persistence during unshelving operations.
177 """Handle persistence during unshelving operations.
178
178
179 Handles saving and restoring a shelved state. Ensures that different
179 Handles saving and restoring a shelved state. Ensures that different
180 versions of a shelved state are possible and handles them appropriately.
180 versions of a shelved state are possible and handles them appropriately.
181 """
181 """
182 _version = 2
182 _version = 2
183 _filename = 'shelvedstate'
183 _filename = 'shelvedstate'
184 _keep = 'keep'
184 _keep = 'keep'
185 _nokeep = 'nokeep'
185 _nokeep = 'nokeep'
186 # colon is essential to differentiate from a real bookmark name
186 # colon is essential to differentiate from a real bookmark name
187 _noactivebook = ':no-active-bookmark'
187 _noactivebook = ':no-active-bookmark'
188
188
189 @classmethod
189 @classmethod
190 def _verifyandtransform(cls, d):
190 def _verifyandtransform(cls, d):
191 """Some basic shelvestate syntactic verification and transformation"""
191 """Some basic shelvestate syntactic verification and transformation"""
192 try:
192 try:
193 d['originalwctx'] = nodemod.bin(d['originalwctx'])
193 d['originalwctx'] = nodemod.bin(d['originalwctx'])
194 d['pendingctx'] = nodemod.bin(d['pendingctx'])
194 d['pendingctx'] = nodemod.bin(d['pendingctx'])
195 d['parents'] = [nodemod.bin(h)
195 d['parents'] = [nodemod.bin(h)
196 for h in d['parents'].split(' ')]
196 for h in d['parents'].split(' ')]
197 d['nodestoremove'] = [nodemod.bin(h)
197 d['nodestoremove'] = [nodemod.bin(h)
198 for h in d['nodestoremove'].split(' ')]
198 for h in d['nodestoremove'].split(' ')]
199 except (ValueError, TypeError, KeyError) as err:
199 except (ValueError, TypeError, KeyError) as err:
200 raise error.CorruptedState(pycompat.bytestr(err))
200 raise error.CorruptedState(pycompat.bytestr(err))
201
201
202 @classmethod
202 @classmethod
203 def _getversion(cls, repo):
203 def _getversion(cls, repo):
204 """Read version information from shelvestate file"""
204 """Read version information from shelvestate file"""
205 fp = repo.vfs(cls._filename)
205 fp = repo.vfs(cls._filename)
206 try:
206 try:
207 version = int(fp.readline().strip())
207 version = int(fp.readline().strip())
208 except ValueError as err:
208 except ValueError as err:
209 raise error.CorruptedState(pycompat.bytestr(err))
209 raise error.CorruptedState(pycompat.bytestr(err))
210 finally:
210 finally:
211 fp.close()
211 fp.close()
212 return version
212 return version
213
213
214 @classmethod
214 @classmethod
215 def _readold(cls, repo):
215 def _readold(cls, repo):
216 """Read the old position-based version of a shelvestate file"""
216 """Read the old position-based version of a shelvestate file"""
217 # Order is important, because old shelvestate file uses it
217 # Order is important, because old shelvestate file uses it
218 # to detemine values of fields (i.g. name is on the second line,
218 # to detemine values of fields (i.g. name is on the second line,
219 # originalwctx is on the third and so forth). Please do not change.
219 # originalwctx is on the third and so forth). Please do not change.
220 keys = ['version', 'name', 'originalwctx', 'pendingctx', 'parents',
220 keys = ['version', 'name', 'originalwctx', 'pendingctx', 'parents',
221 'nodestoremove', 'branchtorestore', 'keep', 'activebook']
221 'nodestoremove', 'branchtorestore', 'keep', 'activebook']
222 # this is executed only seldomly, so it is not a big deal
222 # this is executed only seldomly, so it is not a big deal
223 # that we open this file twice
223 # that we open this file twice
224 fp = repo.vfs(cls._filename)
224 fp = repo.vfs(cls._filename)
225 d = {}
225 d = {}
226 try:
226 try:
227 for key in keys:
227 for key in keys:
228 d[key] = fp.readline().strip()
228 d[key] = fp.readline().strip()
229 finally:
229 finally:
230 fp.close()
230 fp.close()
231 return d
231 return d
232
232
233 @classmethod
233 @classmethod
234 def load(cls, repo):
234 def load(cls, repo):
235 version = cls._getversion(repo)
235 version = cls._getversion(repo)
236 if version < cls._version:
236 if version < cls._version:
237 d = cls._readold(repo)
237 d = cls._readold(repo)
238 elif version == cls._version:
238 elif version == cls._version:
239 d = scmutil.simplekeyvaluefile(repo.vfs, cls._filename)\
239 d = scmutil.simplekeyvaluefile(repo.vfs, cls._filename)\
240 .read(firstlinenonkeyval=True)
240 .read(firstlinenonkeyval=True)
241 else:
241 else:
242 raise error.Abort(_('this version of shelve is incompatible '
242 raise error.Abort(_('this version of shelve is incompatible '
243 'with the version used in this repo'))
243 'with the version used in this repo'))
244
244
245 cls._verifyandtransform(d)
245 cls._verifyandtransform(d)
246 try:
246 try:
247 obj = cls()
247 obj = cls()
248 obj.name = d['name']
248 obj.name = d['name']
249 obj.wctx = repo[d['originalwctx']]
249 obj.wctx = repo[d['originalwctx']]
250 obj.pendingctx = repo[d['pendingctx']]
250 obj.pendingctx = repo[d['pendingctx']]
251 obj.parents = d['parents']
251 obj.parents = d['parents']
252 obj.nodestoremove = d['nodestoremove']
252 obj.nodestoremove = d['nodestoremove']
253 obj.branchtorestore = d.get('branchtorestore', '')
253 obj.branchtorestore = d.get('branchtorestore', '')
254 obj.keep = d.get('keep') == cls._keep
254 obj.keep = d.get('keep') == cls._keep
255 obj.activebookmark = ''
255 obj.activebookmark = ''
256 if d.get('activebook', '') != cls._noactivebook:
256 if d.get('activebook', '') != cls._noactivebook:
257 obj.activebookmark = d.get('activebook', '')
257 obj.activebookmark = d.get('activebook', '')
258 except (error.RepoLookupError, KeyError) as err:
258 except (error.RepoLookupError, KeyError) as err:
259 raise error.CorruptedState(pycompat.bytestr(err))
259 raise error.CorruptedState(pycompat.bytestr(err))
260
260
261 return obj
261 return obj
262
262
263 @classmethod
263 @classmethod
264 def save(cls, repo, name, originalwctx, pendingctx, nodestoremove,
264 def save(cls, repo, name, originalwctx, pendingctx, nodestoremove,
265 branchtorestore, keep=False, activebook=''):
265 branchtorestore, keep=False, activebook=''):
266 info = {
266 info = {
267 "name": name,
267 "name": name,
268 "originalwctx": nodemod.hex(originalwctx.node()),
268 "originalwctx": nodemod.hex(originalwctx.node()),
269 "pendingctx": nodemod.hex(pendingctx.node()),
269 "pendingctx": nodemod.hex(pendingctx.node()),
270 "parents": ' '.join([nodemod.hex(p)
270 "parents": ' '.join([nodemod.hex(p)
271 for p in repo.dirstate.parents()]),
271 for p in repo.dirstate.parents()]),
272 "nodestoremove": ' '.join([nodemod.hex(n)
272 "nodestoremove": ' '.join([nodemod.hex(n)
273 for n in nodestoremove]),
273 for n in nodestoremove]),
274 "branchtorestore": branchtorestore,
274 "branchtorestore": branchtorestore,
275 "keep": cls._keep if keep else cls._nokeep,
275 "keep": cls._keep if keep else cls._nokeep,
276 "activebook": activebook or cls._noactivebook
276 "activebook": activebook or cls._noactivebook
277 }
277 }
278 scmutil.simplekeyvaluefile(repo.vfs, cls._filename)\
278 scmutil.simplekeyvaluefile(repo.vfs, cls._filename)\
279 .write(info, firstline=("%d" % cls._version))
279 .write(info, firstline=("%d" % cls._version))
280
280
281 @classmethod
281 @classmethod
282 def clear(cls, repo):
282 def clear(cls, repo):
283 repo.vfs.unlinkpath(cls._filename, ignoremissing=True)
283 repo.vfs.unlinkpath(cls._filename, ignoremissing=True)
284
284
285 def cleanupoldbackups(repo):
285 def cleanupoldbackups(repo):
286 vfs = vfsmod.vfs(repo.vfs.join(backupdir))
286 vfs = vfsmod.vfs(repo.vfs.join(backupdir))
287 maxbackups = repo.ui.configint('shelve', 'maxbackups')
287 maxbackups = repo.ui.configint('shelve', 'maxbackups')
288 hgfiles = [f for f in vfs.listdir()
288 hgfiles = [f for f in vfs.listdir()
289 if f.endswith('.' + patchextension)]
289 if f.endswith('.' + patchextension)]
290 hgfiles = sorted([(vfs.stat(f)[stat.ST_MTIME], f) for f in hgfiles])
290 hgfiles = sorted([(vfs.stat(f)[stat.ST_MTIME], f) for f in hgfiles])
291 if 0 < maxbackups and maxbackups < len(hgfiles):
291 if 0 < maxbackups and maxbackups < len(hgfiles):
292 bordermtime = hgfiles[-maxbackups][0]
292 bordermtime = hgfiles[-maxbackups][0]
293 else:
293 else:
294 bordermtime = None
294 bordermtime = None
295 for mtime, f in hgfiles[:len(hgfiles) - maxbackups]:
295 for mtime, f in hgfiles[:len(hgfiles) - maxbackups]:
296 if mtime == bordermtime:
296 if mtime == bordermtime:
297 # keep it, because timestamp can't decide exact order of backups
297 # keep it, because timestamp can't decide exact order of backups
298 continue
298 continue
299 base = f[:-(1 + len(patchextension))]
299 base = f[:-(1 + len(patchextension))]
300 for ext in shelvefileextensions:
300 for ext in shelvefileextensions:
301 vfs.tryunlink(base + '.' + ext)
301 vfs.tryunlink(base + '.' + ext)
302
302
303 def _backupactivebookmark(repo):
303 def _backupactivebookmark(repo):
304 activebookmark = repo._activebookmark
304 activebookmark = repo._activebookmark
305 if activebookmark:
305 if activebookmark:
306 bookmarks.deactivate(repo)
306 bookmarks.deactivate(repo)
307 return activebookmark
307 return activebookmark
308
308
309 def _restoreactivebookmark(repo, mark):
309 def _restoreactivebookmark(repo, mark):
310 if mark:
310 if mark:
311 bookmarks.activate(repo, mark)
311 bookmarks.activate(repo, mark)
312
312
313 def _aborttransaction(repo):
313 def _aborttransaction(repo):
314 '''Abort current transaction for shelve/unshelve, but keep dirstate
314 '''Abort current transaction for shelve/unshelve, but keep dirstate
315 '''
315 '''
316 tr = repo.currenttransaction()
316 tr = repo.currenttransaction()
317 backupname = 'dirstate.shelve'
317 backupname = 'dirstate.shelve'
318 repo.dirstate.savebackup(tr, backupname)
318 repo.dirstate.savebackup(tr, backupname)
319 tr.abort()
319 tr.abort()
320 repo.dirstate.restorebackup(None, backupname)
320 repo.dirstate.restorebackup(None, backupname)
321
321
322 def createcmd(ui, repo, pats, opts):
322 def createcmd(ui, repo, pats, opts):
323 """subcommand that creates a new shelve"""
323 """subcommand that creates a new shelve"""
324 with repo.wlock():
324 with repo.wlock():
325 cmdutil.checkunfinished(repo)
325 cmdutil.checkunfinished(repo)
326 return _docreatecmd(ui, repo, pats, opts)
326 return _docreatecmd(ui, repo, pats, opts)
327
327
328 def getshelvename(repo, parent, opts):
328 def getshelvename(repo, parent, opts):
329 """Decide on the name this shelve is going to have"""
329 """Decide on the name this shelve is going to have"""
330 def gennames():
330 def gennames():
331 yield label
331 yield label
332 for i in itertools.count(1):
332 for i in itertools.count(1):
333 yield '%s-%02d' % (label, i)
333 yield '%s-%02d' % (label, i)
334 name = opts.get('name')
334 name = opts.get('name')
335 label = repo._activebookmark or parent.branch() or 'default'
335 label = repo._activebookmark or parent.branch() or 'default'
336 # slashes aren't allowed in filenames, therefore we rename it
336 # slashes aren't allowed in filenames, therefore we rename it
337 label = label.replace('/', '_')
337 label = label.replace('/', '_')
338 label = label.replace('\\', '_')
338 label = label.replace('\\', '_')
339 # filenames must not start with '.' as it should not be hidden
339 # filenames must not start with '.' as it should not be hidden
340 if label.startswith('.'):
340 if label.startswith('.'):
341 label = label.replace('.', '_', 1)
341 label = label.replace('.', '_', 1)
342
342
343 if name:
343 if name:
344 if shelvedfile(repo, name, patchextension).exists():
344 if shelvedfile(repo, name, patchextension).exists():
345 e = _("a shelved change named '%s' already exists") % name
345 e = _("a shelved change named '%s' already exists") % name
346 raise error.Abort(e)
346 raise error.Abort(e)
347
347
348 # ensure we are not creating a subdirectory or a hidden file
348 # ensure we are not creating a subdirectory or a hidden file
349 if '/' in name or '\\' in name:
349 if '/' in name or '\\' in name:
350 raise error.Abort(_('shelved change names can not contain slashes'))
350 raise error.Abort(_('shelved change names can not contain slashes'))
351 if name.startswith('.'):
351 if name.startswith('.'):
352 raise error.Abort(_("shelved change names can not start with '.'"))
352 raise error.Abort(_("shelved change names can not start with '.'"))
353
353
354 else:
354 else:
355 for n in gennames():
355 for n in gennames():
356 if not shelvedfile(repo, n, patchextension).exists():
356 if not shelvedfile(repo, n, patchextension).exists():
357 name = n
357 name = n
358 break
358 break
359
359
360 return name
360 return name
361
361
362 def mutableancestors(ctx):
362 def mutableancestors(ctx):
363 """return all mutable ancestors for ctx (included)
363 """return all mutable ancestors for ctx (included)
364
364
365 Much faster than the revset ancestors(ctx) & draft()"""
365 Much faster than the revset ancestors(ctx) & draft()"""
366 seen = {nodemod.nullrev}
366 seen = {nodemod.nullrev}
367 visit = collections.deque()
367 visit = collections.deque()
368 visit.append(ctx)
368 visit.append(ctx)
369 while visit:
369 while visit:
370 ctx = visit.popleft()
370 ctx = visit.popleft()
371 yield ctx.node()
371 yield ctx.node()
372 for parent in ctx.parents():
372 for parent in ctx.parents():
373 rev = parent.rev()
373 rev = parent.rev()
374 if rev not in seen:
374 if rev not in seen:
375 seen.add(rev)
375 seen.add(rev)
376 if parent.mutable():
376 if parent.mutable():
377 visit.append(parent)
377 visit.append(parent)
378
378
379 def getcommitfunc(extra, interactive, editor=False):
379 def getcommitfunc(extra, interactive, editor=False):
380 def commitfunc(ui, repo, message, match, opts):
380 def commitfunc(ui, repo, message, match, opts):
381 hasmq = util.safehasattr(repo, 'mq')
381 hasmq = util.safehasattr(repo, 'mq')
382 if hasmq:
382 if hasmq:
383 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
383 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
384 overrides = {('phases', 'new-commit'): phases.secret}
384 overrides = {('phases', 'new-commit'): phases.secret}
385 try:
385 try:
386 editor_ = False
386 editor_ = False
387 if editor:
387 if editor:
388 editor_ = cmdutil.getcommiteditor(editform='shelve.shelve',
388 editor_ = cmdutil.getcommiteditor(editform='shelve.shelve',
389 **pycompat.strkwargs(opts))
389 **pycompat.strkwargs(opts))
390 with repo.ui.configoverride(overrides):
390 with repo.ui.configoverride(overrides):
391 return repo.commit(message, shelveuser, opts.get('date'),
391 return repo.commit(message, shelveuser, opts.get('date'),
392 match, editor=editor_, extra=extra)
392 match, editor=editor_, extra=extra)
393 finally:
393 finally:
394 if hasmq:
394 if hasmq:
395 repo.mq.checkapplied = saved
395 repo.mq.checkapplied = saved
396
396
397 def interactivecommitfunc(ui, repo, *pats, **opts):
397 def interactivecommitfunc(ui, repo, *pats, **opts):
398 opts = pycompat.byteskwargs(opts)
398 opts = pycompat.byteskwargs(opts)
399 match = scmutil.match(repo['.'], pats, {})
399 match = scmutil.match(repo['.'], pats, {})
400 message = opts['message']
400 message = opts['message']
401 return commitfunc(ui, repo, message, match, opts)
401 return commitfunc(ui, repo, message, match, opts)
402
402
403 return interactivecommitfunc if interactive else commitfunc
403 return interactivecommitfunc if interactive else commitfunc
404
404
405 def _nothingtoshelvemessaging(ui, repo, pats, opts):
405 def _nothingtoshelvemessaging(ui, repo, pats, opts):
406 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
406 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
407 if stat.deleted:
407 if stat.deleted:
408 ui.status(_("nothing changed (%d missing files, see "
408 ui.status(_("nothing changed (%d missing files, see "
409 "'hg status')\n") % len(stat.deleted))
409 "'hg status')\n") % len(stat.deleted))
410 else:
410 else:
411 ui.status(_("nothing changed\n"))
411 ui.status(_("nothing changed\n"))
412
412
413 def _shelvecreatedcommit(repo, node, name):
413 def _shelvecreatedcommit(repo, node, name):
414 bases = list(mutableancestors(repo[node]))
414 bases = list(mutableancestors(repo[node]))
415 shelvedfile(repo, name, 'hg').writebundle(bases, node)
415 shelvedfile(repo, name, 'hg').writebundle(bases, node)
416 with shelvedfile(repo, name, patchextension).opener('wb') as fp:
416 with shelvedfile(repo, name, patchextension).opener('wb') as fp:
417 cmdutil.exportfile(repo, [node], fp, opts=mdiff.diffopts(git=True))
417 cmdutil.exportfile(repo, [node], fp, opts=mdiff.diffopts(git=True))
418
418
419 def _includeunknownfiles(repo, pats, opts, extra):
419 def _includeunknownfiles(repo, pats, opts, extra):
420 s = repo.status(match=scmutil.match(repo[None], pats, opts),
420 s = repo.status(match=scmutil.match(repo[None], pats, opts),
421 unknown=True)
421 unknown=True)
422 if s.unknown:
422 if s.unknown:
423 extra['shelve_unknown'] = '\0'.join(s.unknown)
423 extra['shelve_unknown'] = '\0'.join(s.unknown)
424 repo[None].add(s.unknown)
424 repo[None].add(s.unknown)
425
425
426 def _finishshelve(repo):
426 def _finishshelve(repo):
427 _aborttransaction(repo)
427 _aborttransaction(repo)
428
428
429 def _docreatecmd(ui, repo, pats, opts):
429 def _docreatecmd(ui, repo, pats, opts):
430 wctx = repo[None]
430 wctx = repo[None]
431 parents = wctx.parents()
431 parents = wctx.parents()
432 if len(parents) > 1:
432 if len(parents) > 1:
433 raise error.Abort(_('cannot shelve while merging'))
433 raise error.Abort(_('cannot shelve while merging'))
434 parent = parents[0]
434 parent = parents[0]
435 origbranch = wctx.branch()
435 origbranch = wctx.branch()
436
436
437 if parent.node() != nodemod.nullid:
437 if parent.node() != nodemod.nullid:
438 desc = "changes to: %s" % parent.description().split('\n', 1)[0]
438 desc = "changes to: %s" % parent.description().split('\n', 1)[0]
439 else:
439 else:
440 desc = '(changes in empty repository)'
440 desc = '(changes in empty repository)'
441
441
442 if not opts.get('message'):
442 if not opts.get('message'):
443 opts['message'] = desc
443 opts['message'] = desc
444
444
445 lock = tr = activebookmark = None
445 lock = tr = activebookmark = None
446 try:
446 try:
447 lock = repo.lock()
447 lock = repo.lock()
448
448
449 # use an uncommitted transaction to generate the bundle to avoid
449 # use an uncommitted transaction to generate the bundle to avoid
450 # pull races. ensure we don't print the abort message to stderr.
450 # pull races. ensure we don't print the abort message to stderr.
451 tr = repo.transaction('commit', report=lambda x: None)
451 tr = repo.transaction('commit', report=lambda x: None)
452
452
453 interactive = opts.get('interactive', False)
453 interactive = opts.get('interactive', False)
454 includeunknown = (opts.get('unknown', False) and
454 includeunknown = (opts.get('unknown', False) and
455 not opts.get('addremove', False))
455 not opts.get('addremove', False))
456
456
457 name = getshelvename(repo, parent, opts)
457 name = getshelvename(repo, parent, opts)
458 activebookmark = _backupactivebookmark(repo)
458 activebookmark = _backupactivebookmark(repo)
459 extra = {}
459 extra = {}
460 if includeunknown:
460 if includeunknown:
461 _includeunknownfiles(repo, pats, opts, extra)
461 _includeunknownfiles(repo, pats, opts, extra)
462
462
463 if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts):
463 if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts):
464 # In non-bare shelve we don't store newly created branch
464 # In non-bare shelve we don't store newly created branch
465 # at bundled commit
465 # at bundled commit
466 repo.dirstate.setbranch(repo['.'].branch())
466 repo.dirstate.setbranch(repo['.'].branch())
467
467
468 commitfunc = getcommitfunc(extra, interactive, editor=True)
468 commitfunc = getcommitfunc(extra, interactive, editor=True)
469 if not interactive:
469 if not interactive:
470 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
470 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
471 else:
471 else:
472 node = cmdutil.dorecord(ui, repo, commitfunc, None,
472 node = cmdutil.dorecord(ui, repo, commitfunc, None,
473 False, cmdutil.recordfilter, *pats,
473 False, cmdutil.recordfilter, *pats,
474 **pycompat.strkwargs(opts))
474 **pycompat.strkwargs(opts))
475 if not node:
475 if not node:
476 _nothingtoshelvemessaging(ui, repo, pats, opts)
476 _nothingtoshelvemessaging(ui, repo, pats, opts)
477 return 1
477 return 1
478
478
479 _shelvecreatedcommit(repo, node, name)
479 _shelvecreatedcommit(repo, node, name)
480
480
481 if ui.formatted():
481 if ui.formatted():
482 desc = stringutil.ellipsis(desc, ui.termwidth())
482 desc = stringutil.ellipsis(desc, ui.termwidth())
483 ui.status(_('shelved as %s\n') % name)
483 ui.status(_('shelved as %s\n') % name)
484 hg.update(repo, parent.node())
484 hg.update(repo, parent.node())
485 if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts):
485 if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts):
486 repo.dirstate.setbranch(origbranch)
486 repo.dirstate.setbranch(origbranch)
487
487
488 _finishshelve(repo)
488 _finishshelve(repo)
489 finally:
489 finally:
490 _restoreactivebookmark(repo, activebookmark)
490 _restoreactivebookmark(repo, activebookmark)
491 lockmod.release(tr, lock)
491 lockmod.release(tr, lock)
492
492
493 def _isbareshelve(pats, opts):
493 def _isbareshelve(pats, opts):
494 return (not pats
494 return (not pats
495 and not opts.get('interactive', False)
495 and not opts.get('interactive', False)
496 and not opts.get('include', False)
496 and not opts.get('include', False)
497 and not opts.get('exclude', False))
497 and not opts.get('exclude', False))
498
498
499 def _iswctxonnewbranch(repo):
499 def _iswctxonnewbranch(repo):
500 return repo[None].branch() != repo['.'].branch()
500 return repo[None].branch() != repo['.'].branch()
501
501
502 def cleanupcmd(ui, repo):
502 def cleanupcmd(ui, repo):
503 """subcommand that deletes all shelves"""
503 """subcommand that deletes all shelves"""
504
504
505 with repo.wlock():
505 with repo.wlock():
506 for (name, _type) in repo.vfs.readdir(shelvedir):
506 for (name, _type) in repo.vfs.readdir(shelvedir):
507 suffix = name.rsplit('.', 1)[-1]
507 suffix = name.rsplit('.', 1)[-1]
508 if suffix in shelvefileextensions:
508 if suffix in shelvefileextensions:
509 shelvedfile(repo, name).movetobackup()
509 shelvedfile(repo, name).movetobackup()
510 cleanupoldbackups(repo)
510 cleanupoldbackups(repo)
511
511
512 def deletecmd(ui, repo, pats):
512 def deletecmd(ui, repo, pats):
513 """subcommand that deletes a specific shelve"""
513 """subcommand that deletes a specific shelve"""
514 if not pats:
514 if not pats:
515 raise error.Abort(_('no shelved changes specified!'))
515 raise error.Abort(_('no shelved changes specified!'))
516 with repo.wlock():
516 with repo.wlock():
517 try:
517 try:
518 for name in pats:
518 for name in pats:
519 for suffix in shelvefileextensions:
519 for suffix in shelvefileextensions:
520 shfile = shelvedfile(repo, name, suffix)
520 shfile = shelvedfile(repo, name, suffix)
521 # patch file is necessary, as it should
521 # patch file is necessary, as it should
522 # be present for any kind of shelve,
522 # be present for any kind of shelve,
523 # but the .hg file is optional as in future we
523 # but the .hg file is optional as in future we
524 # will add obsolete shelve with does not create a
524 # will add obsolete shelve with does not create a
525 # bundle
525 # bundle
526 if shfile.exists() or suffix == patchextension:
526 if shfile.exists() or suffix == patchextension:
527 shfile.movetobackup()
527 shfile.movetobackup()
528 cleanupoldbackups(repo)
528 cleanupoldbackups(repo)
529 except OSError as err:
529 except OSError as err:
530 if err.errno != errno.ENOENT:
530 if err.errno != errno.ENOENT:
531 raise
531 raise
532 raise error.Abort(_("shelved change '%s' not found") % name)
532 raise error.Abort(_("shelved change '%s' not found") % name)
533
533
534 def listshelves(repo):
534 def listshelves(repo):
535 """return all shelves in repo as list of (time, filename)"""
535 """return all shelves in repo as list of (time, filename)"""
536 try:
536 try:
537 names = repo.vfs.readdir(shelvedir)
537 names = repo.vfs.readdir(shelvedir)
538 except OSError as err:
538 except OSError as err:
539 if err.errno != errno.ENOENT:
539 if err.errno != errno.ENOENT:
540 raise
540 raise
541 return []
541 return []
542 info = []
542 info = []
543 for (name, _type) in names:
543 for (name, _type) in names:
544 pfx, sfx = name.rsplit('.', 1)
544 pfx, sfx = name.rsplit('.', 1)
545 if not pfx or sfx != patchextension:
545 if not pfx or sfx != patchextension:
546 continue
546 continue
547 st = shelvedfile(repo, name).stat()
547 st = shelvedfile(repo, name).stat()
548 info.append((st[stat.ST_MTIME], shelvedfile(repo, pfx).filename()))
548 info.append((st[stat.ST_MTIME], shelvedfile(repo, pfx).filename()))
549 return sorted(info, reverse=True)
549 return sorted(info, reverse=True)
550
550
551 def listcmd(ui, repo, pats, opts):
551 def listcmd(ui, repo, pats, opts):
552 """subcommand that displays the list of shelves"""
552 """subcommand that displays the list of shelves"""
553 pats = set(pats)
553 pats = set(pats)
554 width = 80
554 width = 80
555 if not ui.plain():
555 if not ui.plain():
556 width = ui.termwidth()
556 width = ui.termwidth()
557 namelabel = 'shelve.newest'
557 namelabel = 'shelve.newest'
558 ui.pager('shelve')
558 ui.pager('shelve')
559 for mtime, name in listshelves(repo):
559 for mtime, name in listshelves(repo):
560 sname = util.split(name)[1]
560 sname = util.split(name)[1]
561 if pats and sname not in pats:
561 if pats and sname not in pats:
562 continue
562 continue
563 ui.write(sname, label=namelabel)
563 ui.write(sname, label=namelabel)
564 namelabel = 'shelve.name'
564 namelabel = 'shelve.name'
565 if ui.quiet:
565 if ui.quiet:
566 ui.write('\n')
566 ui.write('\n')
567 continue
567 continue
568 ui.write(' ' * (16 - len(sname)))
568 ui.write(' ' * (16 - len(sname)))
569 used = 16
569 used = 16
570 date = dateutil.makedate(mtime)
570 date = dateutil.makedate(mtime)
571 age = '(%s)' % templatefilters.age(date, abbrev=True)
571 age = '(%s)' % templatefilters.age(date, abbrev=True)
572 ui.write(age, label='shelve.age')
572 ui.write(age, label='shelve.age')
573 ui.write(' ' * (12 - len(age)))
573 ui.write(' ' * (12 - len(age)))
574 used += 12
574 used += 12
575 with open(name + '.' + patchextension, 'rb') as fp:
575 with open(name + '.' + patchextension, 'rb') as fp:
576 while True:
576 while True:
577 line = fp.readline()
577 line = fp.readline()
578 if not line:
578 if not line:
579 break
579 break
580 if not line.startswith('#'):
580 if not line.startswith('#'):
581 desc = line.rstrip()
581 desc = line.rstrip()
582 if ui.formatted():
582 if ui.formatted():
583 desc = stringutil.ellipsis(desc, width - used)
583 desc = stringutil.ellipsis(desc, width - used)
584 ui.write(desc)
584 ui.write(desc)
585 break
585 break
586 ui.write('\n')
586 ui.write('\n')
587 if not (opts['patch'] or opts['stat']):
587 if not (opts['patch'] or opts['stat']):
588 continue
588 continue
589 difflines = fp.readlines()
589 difflines = fp.readlines()
590 if opts['patch']:
590 if opts['patch']:
591 for chunk, label in patch.difflabel(iter, difflines):
591 for chunk, label in patch.difflabel(iter, difflines):
592 ui.write(chunk, label=label)
592 ui.write(chunk, label=label)
593 if opts['stat']:
593 if opts['stat']:
594 for chunk, label in patch.diffstatui(difflines, width=width):
594 for chunk, label in patch.diffstatui(difflines, width=width):
595 ui.write(chunk, label=label)
595 ui.write(chunk, label=label)
596
596
597 def patchcmds(ui, repo, pats, opts, subcommand):
597 def patchcmds(ui, repo, pats, opts, subcommand):
598 """subcommand that displays shelves"""
598 """subcommand that displays shelves"""
599 if len(pats) == 0:
599 if len(pats) == 0:
600 raise error.Abort(_("--%s expects at least one shelf") % subcommand)
600 raise error.Abort(_("--%s expects at least one shelf") % subcommand)
601
601
602 for shelfname in pats:
602 for shelfname in pats:
603 if not shelvedfile(repo, shelfname, patchextension).exists():
603 if not shelvedfile(repo, shelfname, patchextension).exists():
604 raise error.Abort(_("cannot find shelf %s") % shelfname)
604 raise error.Abort(_("cannot find shelf %s") % shelfname)
605
605
606 listcmd(ui, repo, pats, opts)
606 listcmd(ui, repo, pats, opts)
607
607
608 def checkparents(repo, state):
608 def checkparents(repo, state):
609 """check parent while resuming an unshelve"""
609 """check parent while resuming an unshelve"""
610 if state.parents != repo.dirstate.parents():
610 if state.parents != repo.dirstate.parents():
611 raise error.Abort(_('working directory parents do not match unshelve '
611 raise error.Abort(_('working directory parents do not match unshelve '
612 'state'))
612 'state'))
613
613
614 def pathtofiles(repo, files):
614 def pathtofiles(repo, files):
615 cwd = repo.getcwd()
615 cwd = repo.getcwd()
616 return [repo.pathto(f, cwd) for f in files]
616 return [repo.pathto(f, cwd) for f in files]
617
617
618 def unshelveabort(ui, repo, state, opts):
618 def unshelveabort(ui, repo, state, opts):
619 """subcommand that abort an in-progress unshelve"""
619 """subcommand that abort an in-progress unshelve"""
620 with repo.lock():
620 with repo.lock():
621 try:
621 try:
622 checkparents(repo, state)
622 checkparents(repo, state)
623
623
624 merge.update(repo, state.pendingctx, False, True)
624 merge.update(repo, state.pendingctx, False, True)
625 if (state.activebookmark
625 if (state.activebookmark
626 and state.activebookmark in repo._bookmarks):
626 and state.activebookmark in repo._bookmarks):
627 bookmarks.activate(repo, state.activebookmark)
627 bookmarks.activate(repo, state.activebookmark)
628
628
629 if repo.vfs.exists('unshelverebasestate'):
629 if repo.vfs.exists('unshelverebasestate'):
630 repo.vfs.rename('unshelverebasestate', 'rebasestate')
630 repo.vfs.rename('unshelverebasestate', 'rebasestate')
631 rebase.clearstatus(repo)
631 rebase.clearstatus(repo)
632
632
633 mergefiles(ui, repo, state.wctx, state.pendingctx)
633 mergefiles(ui, repo, state.wctx, state.pendingctx)
634 repair.strip(ui, repo, state.nodestoremove, backup=False,
634 repair.strip(ui, repo, state.nodestoremove, backup=False,
635 topic='shelve')
635 topic='shelve')
636 finally:
636 finally:
637 shelvedstate.clear(repo)
637 shelvedstate.clear(repo)
638 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
638 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
639
639
640 def mergefiles(ui, repo, wctx, shelvectx):
640 def mergefiles(ui, repo, wctx, shelvectx):
641 """updates to wctx and merges the changes from shelvectx into the
641 """updates to wctx and merges the changes from shelvectx into the
642 dirstate."""
642 dirstate."""
643 with ui.configoverride({('ui', 'quiet'): True}):
643 with ui.configoverride({('ui', 'quiet'): True}):
644 hg.update(repo, wctx.node())
644 hg.update(repo, wctx.node())
645 files = []
645 files = []
646 files.extend(shelvectx.files())
646 files.extend(shelvectx.files())
647 files.extend(shelvectx.parents()[0].files())
647 files.extend(shelvectx.parents()[0].files())
648
648
649 # revert will overwrite unknown files, so move them out of the way
649 # revert will overwrite unknown files, so move them out of the way
650 for file in repo.status(unknown=True).unknown:
650 for file in repo.status(unknown=True).unknown:
651 if file in files:
651 if file in files:
652 util.rename(file, scmutil.origpath(ui, repo, file))
652 util.rename(file, scmutil.origpath(ui, repo, file))
653 ui.pushbuffer(True)
653 ui.pushbuffer(True)
654 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
654 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
655 *pathtofiles(repo, files),
655 *pathtofiles(repo, files),
656 **{r'no_backup': True})
656 **{r'no_backup': True})
657 ui.popbuffer()
657 ui.popbuffer()
658
658
659 def restorebranch(ui, repo, branchtorestore):
659 def restorebranch(ui, repo, branchtorestore):
660 if branchtorestore and branchtorestore != repo.dirstate.branch():
660 if branchtorestore and branchtorestore != repo.dirstate.branch():
661 repo.dirstate.setbranch(branchtorestore)
661 repo.dirstate.setbranch(branchtorestore)
662 ui.status(_('marked working directory as branch %s\n')
662 ui.status(_('marked working directory as branch %s\n')
663 % branchtorestore)
663 % branchtorestore)
664
664
665 def unshelvecleanup(ui, repo, name, opts):
665 def unshelvecleanup(ui, repo, name, opts):
666 """remove related files after an unshelve"""
666 """remove related files after an unshelve"""
667 if not opts.get('keep'):
667 if not opts.get('keep'):
668 for filetype in shelvefileextensions:
668 for filetype in shelvefileextensions:
669 shfile = shelvedfile(repo, name, filetype)
669 shfile = shelvedfile(repo, name, filetype)
670 if shfile.exists():
670 if shfile.exists():
671 shfile.movetobackup()
671 shfile.movetobackup()
672 cleanupoldbackups(repo)
672 cleanupoldbackups(repo)
673
673
674 def unshelvecontinue(ui, repo, state, opts):
674 def unshelvecontinue(ui, repo, state, opts):
675 """subcommand to continue an in-progress unshelve"""
675 """subcommand to continue an in-progress unshelve"""
676 # We're finishing off a merge. First parent is our original
676 # We're finishing off a merge. First parent is our original
677 # parent, second is the temporary "fake" commit we're unshelving.
677 # parent, second is the temporary "fake" commit we're unshelving.
678 with repo.lock():
678 with repo.lock():
679 checkparents(repo, state)
679 checkparents(repo, state)
680 ms = merge.mergestate.read(repo)
680 ms = merge.mergestate.read(repo)
681 if list(ms.unresolved()):
681 if list(ms.unresolved()):
682 raise error.Abort(
682 raise error.Abort(
683 _("unresolved conflicts, can't continue"),
683 _("unresolved conflicts, can't continue"),
684 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
684 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
685
685
686 shelvectx = repo[state.parents[1]]
686 shelvectx = repo[state.parents[1]]
687 pendingctx = state.pendingctx
687 pendingctx = state.pendingctx
688
688
689 overrides = {('phases', 'new-commit'): phases.secret}
689 overrides = {('phases', 'new-commit'): phases.secret}
690 with repo.ui.configoverride(overrides, 'unshelve'):
690 with repo.ui.configoverride(overrides, 'unshelve'):
691 with repo.dirstate.parentchange():
691 with repo.dirstate.parentchange():
692 repo.setparents(state.parents[0], nodemod.nullid)
692 repo.setparents(state.parents[0], nodemod.nullid)
693 newnode = repo.commit(text=shelvectx.description(),
693 newnode = repo.commit(text=shelvectx.description(),
694 extra=shelvectx.extra(),
694 extra=shelvectx.extra(),
695 user=shelvectx.user(),
695 user=shelvectx.user(),
696 date=shelvectx.date())
696 date=shelvectx.date())
697
697
698 if newnode is None:
698 if newnode is None:
699 # If it ended up being a no-op commit, then the normal
699 # If it ended up being a no-op commit, then the normal
700 # merge state clean-up path doesn't happen, so do it
700 # merge state clean-up path doesn't happen, so do it
701 # here. Fix issue5494
701 # here. Fix issue5494
702 merge.mergestate.clean(repo)
702 merge.mergestate.clean(repo)
703 shelvectx = state.pendingctx
703 shelvectx = state.pendingctx
704 msg = _('note: unshelved changes already existed '
704 msg = _('note: unshelved changes already existed '
705 'in the working copy\n')
705 'in the working copy\n')
706 ui.status(msg)
706 ui.status(msg)
707 else:
707 else:
708 # only strip the shelvectx if we produced one
708 # only strip the shelvectx if we produced one
709 state.nodestoremove.append(newnode)
709 state.nodestoremove.append(newnode)
710 shelvectx = repo[newnode]
710 shelvectx = repo[newnode]
711
711
712 hg.updaterepo(repo, pendingctx.node(), False)
712 hg.updaterepo(repo, pendingctx.node(), overwrite=False)
713
713
714 if repo.vfs.exists('unshelverebasestate'):
714 if repo.vfs.exists('unshelverebasestate'):
715 repo.vfs.rename('unshelverebasestate', 'rebasestate')
715 repo.vfs.rename('unshelverebasestate', 'rebasestate')
716 rebase.clearstatus(repo)
716 rebase.clearstatus(repo)
717
717
718 mergefiles(ui, repo, state.wctx, shelvectx)
718 mergefiles(ui, repo, state.wctx, shelvectx)
719 restorebranch(ui, repo, state.branchtorestore)
719 restorebranch(ui, repo, state.branchtorestore)
720
720
721 repair.strip(ui, repo, state.nodestoremove, backup=False,
721 repair.strip(ui, repo, state.nodestoremove, backup=False,
722 topic='shelve')
722 topic='shelve')
723 _restoreactivebookmark(repo, state.activebookmark)
723 _restoreactivebookmark(repo, state.activebookmark)
724 shelvedstate.clear(repo)
724 shelvedstate.clear(repo)
725 unshelvecleanup(ui, repo, state.name, opts)
725 unshelvecleanup(ui, repo, state.name, opts)
726 ui.status(_("unshelve of '%s' complete\n") % state.name)
726 ui.status(_("unshelve of '%s' complete\n") % state.name)
727
727
728 def _commitworkingcopychanges(ui, repo, opts, tmpwctx):
728 def _commitworkingcopychanges(ui, repo, opts, tmpwctx):
729 """Temporarily commit working copy changes before moving unshelve commit"""
729 """Temporarily commit working copy changes before moving unshelve commit"""
730 # Store pending changes in a commit and remember added in case a shelve
730 # Store pending changes in a commit and remember added in case a shelve
731 # contains unknown files that are part of the pending change
731 # contains unknown files that are part of the pending change
732 s = repo.status()
732 s = repo.status()
733 addedbefore = frozenset(s.added)
733 addedbefore = frozenset(s.added)
734 if not (s.modified or s.added or s.removed):
734 if not (s.modified or s.added or s.removed):
735 return tmpwctx, addedbefore
735 return tmpwctx, addedbefore
736 ui.status(_("temporarily committing pending changes "
736 ui.status(_("temporarily committing pending changes "
737 "(restore with 'hg unshelve --abort')\n"))
737 "(restore with 'hg unshelve --abort')\n"))
738 commitfunc = getcommitfunc(extra=None, interactive=False,
738 commitfunc = getcommitfunc(extra=None, interactive=False,
739 editor=False)
739 editor=False)
740 tempopts = {}
740 tempopts = {}
741 tempopts['message'] = "pending changes temporary commit"
741 tempopts['message'] = "pending changes temporary commit"
742 tempopts['date'] = opts.get('date')
742 tempopts['date'] = opts.get('date')
743 with ui.configoverride({('ui', 'quiet'): True}):
743 with ui.configoverride({('ui', 'quiet'): True}):
744 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
744 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
745 tmpwctx = repo[node]
745 tmpwctx = repo[node]
746 return tmpwctx, addedbefore
746 return tmpwctx, addedbefore
747
747
748 def _unshelverestorecommit(ui, repo, basename):
748 def _unshelverestorecommit(ui, repo, basename):
749 """Recreate commit in the repository during the unshelve"""
749 """Recreate commit in the repository during the unshelve"""
750 with ui.configoverride({('ui', 'quiet'): True}):
750 with ui.configoverride({('ui', 'quiet'): True}):
751 shelvedfile(repo, basename, 'hg').applybundle()
751 shelvedfile(repo, basename, 'hg').applybundle()
752 shelvectx = repo['tip']
752 shelvectx = repo['tip']
753 return repo, shelvectx
753 return repo, shelvectx
754
754
755 def _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, basename, pctx,
755 def _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, basename, pctx,
756 tmpwctx, shelvectx, branchtorestore,
756 tmpwctx, shelvectx, branchtorestore,
757 activebookmark):
757 activebookmark):
758 """Rebase restored commit from its original location to a destination"""
758 """Rebase restored commit from its original location to a destination"""
759 # If the shelve is not immediately on top of the commit
759 # If the shelve is not immediately on top of the commit
760 # we'll be merging with, rebase it to be on top.
760 # we'll be merging with, rebase it to be on top.
761 if tmpwctx.node() == shelvectx.parents()[0].node():
761 if tmpwctx.node() == shelvectx.parents()[0].node():
762 return shelvectx
762 return shelvectx
763
763
764 ui.status(_('rebasing shelved changes\n'))
764 ui.status(_('rebasing shelved changes\n'))
765 try:
765 try:
766 rebase.rebase(ui, repo, **{
766 rebase.rebase(ui, repo, **{
767 r'rev': [shelvectx.rev()],
767 r'rev': [shelvectx.rev()],
768 r'dest': "%d" % tmpwctx.rev(),
768 r'dest': "%d" % tmpwctx.rev(),
769 r'keep': True,
769 r'keep': True,
770 r'tool': opts.get('tool', ''),
770 r'tool': opts.get('tool', ''),
771 })
771 })
772 except error.InterventionRequired:
772 except error.InterventionRequired:
773 tr.close()
773 tr.close()
774
774
775 nodestoremove = [repo.changelog.node(rev)
775 nodestoremove = [repo.changelog.node(rev)
776 for rev in xrange(oldtiprev, len(repo))]
776 for rev in xrange(oldtiprev, len(repo))]
777 shelvedstate.save(repo, basename, pctx, tmpwctx, nodestoremove,
777 shelvedstate.save(repo, basename, pctx, tmpwctx, nodestoremove,
778 branchtorestore, opts.get('keep'), activebookmark)
778 branchtorestore, opts.get('keep'), activebookmark)
779
779
780 repo.vfs.rename('rebasestate', 'unshelverebasestate')
780 repo.vfs.rename('rebasestate', 'unshelverebasestate')
781 raise error.InterventionRequired(
781 raise error.InterventionRequired(
782 _("unresolved conflicts (see 'hg resolve', then "
782 _("unresolved conflicts (see 'hg resolve', then "
783 "'hg unshelve --continue')"))
783 "'hg unshelve --continue')"))
784
784
785 # refresh ctx after rebase completes
785 # refresh ctx after rebase completes
786 shelvectx = repo['tip']
786 shelvectx = repo['tip']
787
787
788 if tmpwctx not in shelvectx.parents():
788 if tmpwctx not in shelvectx.parents():
789 # rebase was a no-op, so it produced no child commit
789 # rebase was a no-op, so it produced no child commit
790 shelvectx = tmpwctx
790 shelvectx = tmpwctx
791 return shelvectx
791 return shelvectx
792
792
793 def _forgetunknownfiles(repo, shelvectx, addedbefore):
793 def _forgetunknownfiles(repo, shelvectx, addedbefore):
794 # Forget any files that were unknown before the shelve, unknown before
794 # Forget any files that were unknown before the shelve, unknown before
795 # unshelve started, but are now added.
795 # unshelve started, but are now added.
796 shelveunknown = shelvectx.extra().get('shelve_unknown')
796 shelveunknown = shelvectx.extra().get('shelve_unknown')
797 if not shelveunknown:
797 if not shelveunknown:
798 return
798 return
799 shelveunknown = frozenset(shelveunknown.split('\0'))
799 shelveunknown = frozenset(shelveunknown.split('\0'))
800 addedafter = frozenset(repo.status().added)
800 addedafter = frozenset(repo.status().added)
801 toforget = (addedafter & shelveunknown) - addedbefore
801 toforget = (addedafter & shelveunknown) - addedbefore
802 repo[None].forget(toforget)
802 repo[None].forget(toforget)
803
803
804 def _finishunshelve(repo, oldtiprev, tr, activebookmark):
804 def _finishunshelve(repo, oldtiprev, tr, activebookmark):
805 _restoreactivebookmark(repo, activebookmark)
805 _restoreactivebookmark(repo, activebookmark)
806 # The transaction aborting will strip all the commits for us,
806 # The transaction aborting will strip all the commits for us,
807 # but it doesn't update the inmemory structures, so addchangegroup
807 # but it doesn't update the inmemory structures, so addchangegroup
808 # hooks still fire and try to operate on the missing commits.
808 # hooks still fire and try to operate on the missing commits.
809 # Clean up manually to prevent this.
809 # Clean up manually to prevent this.
810 repo.unfiltered().changelog.strip(oldtiprev, tr)
810 repo.unfiltered().changelog.strip(oldtiprev, tr)
811 _aborttransaction(repo)
811 _aborttransaction(repo)
812
812
813 def _checkunshelveuntrackedproblems(ui, repo, shelvectx):
813 def _checkunshelveuntrackedproblems(ui, repo, shelvectx):
814 """Check potential problems which may result from working
814 """Check potential problems which may result from working
815 copy having untracked changes."""
815 copy having untracked changes."""
816 wcdeleted = set(repo.status().deleted)
816 wcdeleted = set(repo.status().deleted)
817 shelvetouched = set(shelvectx.files())
817 shelvetouched = set(shelvectx.files())
818 intersection = wcdeleted.intersection(shelvetouched)
818 intersection = wcdeleted.intersection(shelvetouched)
819 if intersection:
819 if intersection:
820 m = _("shelved change touches missing files")
820 m = _("shelved change touches missing files")
821 hint = _("run hg status to see which files are missing")
821 hint = _("run hg status to see which files are missing")
822 raise error.Abort(m, hint=hint)
822 raise error.Abort(m, hint=hint)
823
823
824 @command('unshelve',
824 @command('unshelve',
825 [('a', 'abort', None,
825 [('a', 'abort', None,
826 _('abort an incomplete unshelve operation')),
826 _('abort an incomplete unshelve operation')),
827 ('c', 'continue', None,
827 ('c', 'continue', None,
828 _('continue an incomplete unshelve operation')),
828 _('continue an incomplete unshelve operation')),
829 ('k', 'keep', None,
829 ('k', 'keep', None,
830 _('keep shelve after unshelving')),
830 _('keep shelve after unshelving')),
831 ('n', 'name', '',
831 ('n', 'name', '',
832 _('restore shelved change with given name'), _('NAME')),
832 _('restore shelved change with given name'), _('NAME')),
833 ('t', 'tool', '', _('specify merge tool')),
833 ('t', 'tool', '', _('specify merge tool')),
834 ('', 'date', '',
834 ('', 'date', '',
835 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
835 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
836 _('hg unshelve [[-n] SHELVED]'))
836 _('hg unshelve [[-n] SHELVED]'))
837 def unshelve(ui, repo, *shelved, **opts):
837 def unshelve(ui, repo, *shelved, **opts):
838 """restore a shelved change to the working directory
838 """restore a shelved change to the working directory
839
839
840 This command accepts an optional name of a shelved change to
840 This command accepts an optional name of a shelved change to
841 restore. If none is given, the most recent shelved change is used.
841 restore. If none is given, the most recent shelved change is used.
842
842
843 If a shelved change is applied successfully, the bundle that
843 If a shelved change is applied successfully, the bundle that
844 contains the shelved changes is moved to a backup location
844 contains the shelved changes is moved to a backup location
845 (.hg/shelve-backup).
845 (.hg/shelve-backup).
846
846
847 Since you can restore a shelved change on top of an arbitrary
847 Since you can restore a shelved change on top of an arbitrary
848 commit, it is possible that unshelving will result in a conflict
848 commit, it is possible that unshelving will result in a conflict
849 between your changes and the commits you are unshelving onto. If
849 between your changes and the commits you are unshelving onto. If
850 this occurs, you must resolve the conflict, then use
850 this occurs, you must resolve the conflict, then use
851 ``--continue`` to complete the unshelve operation. (The bundle
851 ``--continue`` to complete the unshelve operation. (The bundle
852 will not be moved until you successfully complete the unshelve.)
852 will not be moved until you successfully complete the unshelve.)
853
853
854 (Alternatively, you can use ``--abort`` to abandon an unshelve
854 (Alternatively, you can use ``--abort`` to abandon an unshelve
855 that causes a conflict. This reverts the unshelved changes, and
855 that causes a conflict. This reverts the unshelved changes, and
856 leaves the bundle in place.)
856 leaves the bundle in place.)
857
857
858 If bare shelved change(when no files are specified, without interactive,
858 If bare shelved change(when no files are specified, without interactive,
859 include and exclude option) was done on newly created branch it would
859 include and exclude option) was done on newly created branch it would
860 restore branch information to the working directory.
860 restore branch information to the working directory.
861
861
862 After a successful unshelve, the shelved changes are stored in a
862 After a successful unshelve, the shelved changes are stored in a
863 backup directory. Only the N most recent backups are kept. N
863 backup directory. Only the N most recent backups are kept. N
864 defaults to 10 but can be overridden using the ``shelve.maxbackups``
864 defaults to 10 but can be overridden using the ``shelve.maxbackups``
865 configuration option.
865 configuration option.
866
866
867 .. container:: verbose
867 .. container:: verbose
868
868
869 Timestamp in seconds is used to decide order of backups. More
869 Timestamp in seconds is used to decide order of backups. More
870 than ``maxbackups`` backups are kept, if same timestamp
870 than ``maxbackups`` backups are kept, if same timestamp
871 prevents from deciding exact order of them, for safety.
871 prevents from deciding exact order of them, for safety.
872 """
872 """
873 with repo.wlock():
873 with repo.wlock():
874 return _dounshelve(ui, repo, *shelved, **opts)
874 return _dounshelve(ui, repo, *shelved, **opts)
875
875
876 def _dounshelve(ui, repo, *shelved, **opts):
876 def _dounshelve(ui, repo, *shelved, **opts):
877 opts = pycompat.byteskwargs(opts)
877 opts = pycompat.byteskwargs(opts)
878 abortf = opts.get('abort')
878 abortf = opts.get('abort')
879 continuef = opts.get('continue')
879 continuef = opts.get('continue')
880 if not abortf and not continuef:
880 if not abortf and not continuef:
881 cmdutil.checkunfinished(repo)
881 cmdutil.checkunfinished(repo)
882 shelved = list(shelved)
882 shelved = list(shelved)
883 if opts.get("name"):
883 if opts.get("name"):
884 shelved.append(opts["name"])
884 shelved.append(opts["name"])
885
885
886 if abortf or continuef:
886 if abortf or continuef:
887 if abortf and continuef:
887 if abortf and continuef:
888 raise error.Abort(_('cannot use both abort and continue'))
888 raise error.Abort(_('cannot use both abort and continue'))
889 if shelved:
889 if shelved:
890 raise error.Abort(_('cannot combine abort/continue with '
890 raise error.Abort(_('cannot combine abort/continue with '
891 'naming a shelved change'))
891 'naming a shelved change'))
892 if abortf and opts.get('tool', False):
892 if abortf and opts.get('tool', False):
893 ui.warn(_('tool option will be ignored\n'))
893 ui.warn(_('tool option will be ignored\n'))
894
894
895 try:
895 try:
896 state = shelvedstate.load(repo)
896 state = shelvedstate.load(repo)
897 if opts.get('keep') is None:
897 if opts.get('keep') is None:
898 opts['keep'] = state.keep
898 opts['keep'] = state.keep
899 except IOError as err:
899 except IOError as err:
900 if err.errno != errno.ENOENT:
900 if err.errno != errno.ENOENT:
901 raise
901 raise
902 cmdutil.wrongtooltocontinue(repo, _('unshelve'))
902 cmdutil.wrongtooltocontinue(repo, _('unshelve'))
903 except error.CorruptedState as err:
903 except error.CorruptedState as err:
904 ui.debug(pycompat.bytestr(err) + '\n')
904 ui.debug(pycompat.bytestr(err) + '\n')
905 if continuef:
905 if continuef:
906 msg = _('corrupted shelved state file')
906 msg = _('corrupted shelved state file')
907 hint = _('please run hg unshelve --abort to abort unshelve '
907 hint = _('please run hg unshelve --abort to abort unshelve '
908 'operation')
908 'operation')
909 raise error.Abort(msg, hint=hint)
909 raise error.Abort(msg, hint=hint)
910 elif abortf:
910 elif abortf:
911 msg = _('could not read shelved state file, your working copy '
911 msg = _('could not read shelved state file, your working copy '
912 'may be in an unexpected state\nplease update to some '
912 'may be in an unexpected state\nplease update to some '
913 'commit\n')
913 'commit\n')
914 ui.warn(msg)
914 ui.warn(msg)
915 shelvedstate.clear(repo)
915 shelvedstate.clear(repo)
916 return
916 return
917
917
918 if abortf:
918 if abortf:
919 return unshelveabort(ui, repo, state, opts)
919 return unshelveabort(ui, repo, state, opts)
920 elif continuef:
920 elif continuef:
921 return unshelvecontinue(ui, repo, state, opts)
921 return unshelvecontinue(ui, repo, state, opts)
922 elif len(shelved) > 1:
922 elif len(shelved) > 1:
923 raise error.Abort(_('can only unshelve one change at a time'))
923 raise error.Abort(_('can only unshelve one change at a time'))
924 elif not shelved:
924 elif not shelved:
925 shelved = listshelves(repo)
925 shelved = listshelves(repo)
926 if not shelved:
926 if not shelved:
927 raise error.Abort(_('no shelved changes to apply!'))
927 raise error.Abort(_('no shelved changes to apply!'))
928 basename = util.split(shelved[0][1])[1]
928 basename = util.split(shelved[0][1])[1]
929 ui.status(_("unshelving change '%s'\n") % basename)
929 ui.status(_("unshelving change '%s'\n") % basename)
930 else:
930 else:
931 basename = shelved[0]
931 basename = shelved[0]
932
932
933 if not shelvedfile(repo, basename, patchextension).exists():
933 if not shelvedfile(repo, basename, patchextension).exists():
934 raise error.Abort(_("shelved change '%s' not found") % basename)
934 raise error.Abort(_("shelved change '%s' not found") % basename)
935
935
936 lock = tr = None
936 lock = tr = None
937 try:
937 try:
938 lock = repo.lock()
938 lock = repo.lock()
939 tr = repo.transaction('unshelve', report=lambda x: None)
939 tr = repo.transaction('unshelve', report=lambda x: None)
940 oldtiprev = len(repo)
940 oldtiprev = len(repo)
941
941
942 pctx = repo['.']
942 pctx = repo['.']
943 tmpwctx = pctx
943 tmpwctx = pctx
944 # The goal is to have a commit structure like so:
944 # The goal is to have a commit structure like so:
945 # ...-> pctx -> tmpwctx -> shelvectx
945 # ...-> pctx -> tmpwctx -> shelvectx
946 # where tmpwctx is an optional commit with the user's pending changes
946 # where tmpwctx is an optional commit with the user's pending changes
947 # and shelvectx is the unshelved changes. Then we merge it all down
947 # and shelvectx is the unshelved changes. Then we merge it all down
948 # to the original pctx.
948 # to the original pctx.
949
949
950 activebookmark = _backupactivebookmark(repo)
950 activebookmark = _backupactivebookmark(repo)
951 tmpwctx, addedbefore = _commitworkingcopychanges(ui, repo, opts,
951 tmpwctx, addedbefore = _commitworkingcopychanges(ui, repo, opts,
952 tmpwctx)
952 tmpwctx)
953 repo, shelvectx = _unshelverestorecommit(ui, repo, basename)
953 repo, shelvectx = _unshelverestorecommit(ui, repo, basename)
954 _checkunshelveuntrackedproblems(ui, repo, shelvectx)
954 _checkunshelveuntrackedproblems(ui, repo, shelvectx)
955 branchtorestore = ''
955 branchtorestore = ''
956 if shelvectx.branch() != shelvectx.p1().branch():
956 if shelvectx.branch() != shelvectx.p1().branch():
957 branchtorestore = shelvectx.branch()
957 branchtorestore = shelvectx.branch()
958
958
959 shelvectx = _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev,
959 shelvectx = _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev,
960 basename, pctx, tmpwctx,
960 basename, pctx, tmpwctx,
961 shelvectx, branchtorestore,
961 shelvectx, branchtorestore,
962 activebookmark)
962 activebookmark)
963 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
963 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
964 with ui.configoverride(overrides, 'unshelve'):
964 with ui.configoverride(overrides, 'unshelve'):
965 mergefiles(ui, repo, pctx, shelvectx)
965 mergefiles(ui, repo, pctx, shelvectx)
966 restorebranch(ui, repo, branchtorestore)
966 restorebranch(ui, repo, branchtorestore)
967 _forgetunknownfiles(repo, shelvectx, addedbefore)
967 _forgetunknownfiles(repo, shelvectx, addedbefore)
968
968
969 shelvedstate.clear(repo)
969 shelvedstate.clear(repo)
970 _finishunshelve(repo, oldtiprev, tr, activebookmark)
970 _finishunshelve(repo, oldtiprev, tr, activebookmark)
971 unshelvecleanup(ui, repo, basename, opts)
971 unshelvecleanup(ui, repo, basename, opts)
972 finally:
972 finally:
973 if tr:
973 if tr:
974 tr.release()
974 tr.release()
975 lockmod.release(lock)
975 lockmod.release(lock)
976
976
977 @command('shelve',
977 @command('shelve',
978 [('A', 'addremove', None,
978 [('A', 'addremove', None,
979 _('mark new/missing files as added/removed before shelving')),
979 _('mark new/missing files as added/removed before shelving')),
980 ('u', 'unknown', None,
980 ('u', 'unknown', None,
981 _('store unknown files in the shelve')),
981 _('store unknown files in the shelve')),
982 ('', 'cleanup', None,
982 ('', 'cleanup', None,
983 _('delete all shelved changes')),
983 _('delete all shelved changes')),
984 ('', 'date', '',
984 ('', 'date', '',
985 _('shelve with the specified commit date'), _('DATE')),
985 _('shelve with the specified commit date'), _('DATE')),
986 ('d', 'delete', None,
986 ('d', 'delete', None,
987 _('delete the named shelved change(s)')),
987 _('delete the named shelved change(s)')),
988 ('e', 'edit', False,
988 ('e', 'edit', False,
989 _('invoke editor on commit messages')),
989 _('invoke editor on commit messages')),
990 ('l', 'list', None,
990 ('l', 'list', None,
991 _('list current shelves')),
991 _('list current shelves')),
992 ('m', 'message', '',
992 ('m', 'message', '',
993 _('use text as shelve message'), _('TEXT')),
993 _('use text as shelve message'), _('TEXT')),
994 ('n', 'name', '',
994 ('n', 'name', '',
995 _('use the given name for the shelved commit'), _('NAME')),
995 _('use the given name for the shelved commit'), _('NAME')),
996 ('p', 'patch', None,
996 ('p', 'patch', None,
997 _('show patch')),
997 _('show patch')),
998 ('i', 'interactive', None,
998 ('i', 'interactive', None,
999 _('interactive mode, only works while creating a shelve')),
999 _('interactive mode, only works while creating a shelve')),
1000 ('', 'stat', None,
1000 ('', 'stat', None,
1001 _('output diffstat-style summary of changes'))] + cmdutil.walkopts,
1001 _('output diffstat-style summary of changes'))] + cmdutil.walkopts,
1002 _('hg shelve [OPTION]... [FILE]...'))
1002 _('hg shelve [OPTION]... [FILE]...'))
1003 def shelvecmd(ui, repo, *pats, **opts):
1003 def shelvecmd(ui, repo, *pats, **opts):
1004 '''save and set aside changes from the working directory
1004 '''save and set aside changes from the working directory
1005
1005
1006 Shelving takes files that "hg status" reports as not clean, saves
1006 Shelving takes files that "hg status" reports as not clean, saves
1007 the modifications to a bundle (a shelved change), and reverts the
1007 the modifications to a bundle (a shelved change), and reverts the
1008 files so that their state in the working directory becomes clean.
1008 files so that their state in the working directory becomes clean.
1009
1009
1010 To restore these changes to the working directory, using "hg
1010 To restore these changes to the working directory, using "hg
1011 unshelve"; this will work even if you switch to a different
1011 unshelve"; this will work even if you switch to a different
1012 commit.
1012 commit.
1013
1013
1014 When no files are specified, "hg shelve" saves all not-clean
1014 When no files are specified, "hg shelve" saves all not-clean
1015 files. If specific files or directories are named, only changes to
1015 files. If specific files or directories are named, only changes to
1016 those files are shelved.
1016 those files are shelved.
1017
1017
1018 In bare shelve (when no files are specified, without interactive,
1018 In bare shelve (when no files are specified, without interactive,
1019 include and exclude option), shelving remembers information if the
1019 include and exclude option), shelving remembers information if the
1020 working directory was on newly created branch, in other words working
1020 working directory was on newly created branch, in other words working
1021 directory was on different branch than its first parent. In this
1021 directory was on different branch than its first parent. In this
1022 situation unshelving restores branch information to the working directory.
1022 situation unshelving restores branch information to the working directory.
1023
1023
1024 Each shelved change has a name that makes it easier to find later.
1024 Each shelved change has a name that makes it easier to find later.
1025 The name of a shelved change defaults to being based on the active
1025 The name of a shelved change defaults to being based on the active
1026 bookmark, or if there is no active bookmark, the current named
1026 bookmark, or if there is no active bookmark, the current named
1027 branch. To specify a different name, use ``--name``.
1027 branch. To specify a different name, use ``--name``.
1028
1028
1029 To see a list of existing shelved changes, use the ``--list``
1029 To see a list of existing shelved changes, use the ``--list``
1030 option. For each shelved change, this will print its name, age,
1030 option. For each shelved change, this will print its name, age,
1031 and description; use ``--patch`` or ``--stat`` for more details.
1031 and description; use ``--patch`` or ``--stat`` for more details.
1032
1032
1033 To delete specific shelved changes, use ``--delete``. To delete
1033 To delete specific shelved changes, use ``--delete``. To delete
1034 all shelved changes, use ``--cleanup``.
1034 all shelved changes, use ``--cleanup``.
1035 '''
1035 '''
1036 opts = pycompat.byteskwargs(opts)
1036 opts = pycompat.byteskwargs(opts)
1037 allowables = [
1037 allowables = [
1038 ('addremove', {'create'}), # 'create' is pseudo action
1038 ('addremove', {'create'}), # 'create' is pseudo action
1039 ('unknown', {'create'}),
1039 ('unknown', {'create'}),
1040 ('cleanup', {'cleanup'}),
1040 ('cleanup', {'cleanup'}),
1041 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
1041 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
1042 ('delete', {'delete'}),
1042 ('delete', {'delete'}),
1043 ('edit', {'create'}),
1043 ('edit', {'create'}),
1044 ('list', {'list'}),
1044 ('list', {'list'}),
1045 ('message', {'create'}),
1045 ('message', {'create'}),
1046 ('name', {'create'}),
1046 ('name', {'create'}),
1047 ('patch', {'patch', 'list'}),
1047 ('patch', {'patch', 'list'}),
1048 ('stat', {'stat', 'list'}),
1048 ('stat', {'stat', 'list'}),
1049 ]
1049 ]
1050 def checkopt(opt):
1050 def checkopt(opt):
1051 if opts.get(opt):
1051 if opts.get(opt):
1052 for i, allowable in allowables:
1052 for i, allowable in allowables:
1053 if opts[i] and opt not in allowable:
1053 if opts[i] and opt not in allowable:
1054 raise error.Abort(_("options '--%s' and '--%s' may not be "
1054 raise error.Abort(_("options '--%s' and '--%s' may not be "
1055 "used together") % (opt, i))
1055 "used together") % (opt, i))
1056 return True
1056 return True
1057 if checkopt('cleanup'):
1057 if checkopt('cleanup'):
1058 if pats:
1058 if pats:
1059 raise error.Abort(_("cannot specify names when using '--cleanup'"))
1059 raise error.Abort(_("cannot specify names when using '--cleanup'"))
1060 return cleanupcmd(ui, repo)
1060 return cleanupcmd(ui, repo)
1061 elif checkopt('delete'):
1061 elif checkopt('delete'):
1062 return deletecmd(ui, repo, pats)
1062 return deletecmd(ui, repo, pats)
1063 elif checkopt('list'):
1063 elif checkopt('list'):
1064 return listcmd(ui, repo, pats, opts)
1064 return listcmd(ui, repo, pats, opts)
1065 elif checkopt('patch'):
1065 elif checkopt('patch'):
1066 return patchcmds(ui, repo, pats, opts, subcommand='patch')
1066 return patchcmds(ui, repo, pats, opts, subcommand='patch')
1067 elif checkopt('stat'):
1067 elif checkopt('stat'):
1068 return patchcmds(ui, repo, pats, opts, subcommand='stat')
1068 return patchcmds(ui, repo, pats, opts, subcommand='stat')
1069 else:
1069 else:
1070 return createcmd(ui, repo, pats, opts)
1070 return createcmd(ui, repo, pats, opts)
1071
1071
1072 def extsetup(ui):
1072 def extsetup(ui):
1073 cmdutil.unfinishedstates.append(
1073 cmdutil.unfinishedstates.append(
1074 [shelvedstate._filename, False, False,
1074 [shelvedstate._filename, False, False,
1075 _('unshelve already in progress'),
1075 _('unshelve already in progress'),
1076 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
1076 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
1077 cmdutil.afterresolvedstates.append(
1077 cmdutil.afterresolvedstates.append(
1078 [shelvedstate._filename, _('hg unshelve --continue')])
1078 [shelvedstate._filename, _('hg unshelve --continue')])
@@ -1,5821 +1,5821 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 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 difflib
10 import difflib
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14 import sys
14 import sys
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 hex,
18 hex,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 short,
21 short,
22 )
22 )
23 from . import (
23 from . import (
24 archival,
24 archival,
25 bookmarks,
25 bookmarks,
26 bundle2,
26 bundle2,
27 changegroup,
27 changegroup,
28 cmdutil,
28 cmdutil,
29 copies,
29 copies,
30 debugcommands as debugcommandsmod,
30 debugcommands as debugcommandsmod,
31 destutil,
31 destutil,
32 dirstateguard,
32 dirstateguard,
33 discovery,
33 discovery,
34 encoding,
34 encoding,
35 error,
35 error,
36 exchange,
36 exchange,
37 extensions,
37 extensions,
38 formatter,
38 formatter,
39 graphmod,
39 graphmod,
40 hbisect,
40 hbisect,
41 help,
41 help,
42 hg,
42 hg,
43 logcmdutil,
43 logcmdutil,
44 merge as mergemod,
44 merge as mergemod,
45 obsolete,
45 obsolete,
46 obsutil,
46 obsutil,
47 patch,
47 patch,
48 phases,
48 phases,
49 pycompat,
49 pycompat,
50 rcutil,
50 rcutil,
51 registrar,
51 registrar,
52 repair,
52 repair,
53 revsetlang,
53 revsetlang,
54 rewriteutil,
54 rewriteutil,
55 scmutil,
55 scmutil,
56 server,
56 server,
57 state as statemod,
57 state as statemod,
58 streamclone,
58 streamclone,
59 tags as tagsmod,
59 tags as tagsmod,
60 templatekw,
60 templatekw,
61 ui as uimod,
61 ui as uimod,
62 util,
62 util,
63 wireprotoserver,
63 wireprotoserver,
64 )
64 )
65 from .utils import (
65 from .utils import (
66 dateutil,
66 dateutil,
67 stringutil,
67 stringutil,
68 )
68 )
69
69
70 table = {}
70 table = {}
71 table.update(debugcommandsmod.command._table)
71 table.update(debugcommandsmod.command._table)
72
72
73 command = registrar.command(table)
73 command = registrar.command(table)
74 INTENT_READONLY = registrar.INTENT_READONLY
74 INTENT_READONLY = registrar.INTENT_READONLY
75
75
76 # common command options
76 # common command options
77
77
78 globalopts = [
78 globalopts = [
79 ('R', 'repository', '',
79 ('R', 'repository', '',
80 _('repository root directory or name of overlay bundle file'),
80 _('repository root directory or name of overlay bundle file'),
81 _('REPO')),
81 _('REPO')),
82 ('', 'cwd', '',
82 ('', 'cwd', '',
83 _('change working directory'), _('DIR')),
83 _('change working directory'), _('DIR')),
84 ('y', 'noninteractive', None,
84 ('y', 'noninteractive', None,
85 _('do not prompt, automatically pick the first choice for all prompts')),
85 _('do not prompt, automatically pick the first choice for all prompts')),
86 ('q', 'quiet', None, _('suppress output')),
86 ('q', 'quiet', None, _('suppress output')),
87 ('v', 'verbose', None, _('enable additional output')),
87 ('v', 'verbose', None, _('enable additional output')),
88 ('', 'color', '',
88 ('', 'color', '',
89 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
89 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
90 # and should not be translated
90 # and should not be translated
91 _("when to colorize (boolean, always, auto, never, or debug)"),
91 _("when to colorize (boolean, always, auto, never, or debug)"),
92 _('TYPE')),
92 _('TYPE')),
93 ('', 'config', [],
93 ('', 'config', [],
94 _('set/override config option (use \'section.name=value\')'),
94 _('set/override config option (use \'section.name=value\')'),
95 _('CONFIG')),
95 _('CONFIG')),
96 ('', 'debug', None, _('enable debugging output')),
96 ('', 'debug', None, _('enable debugging output')),
97 ('', 'debugger', None, _('start debugger')),
97 ('', 'debugger', None, _('start debugger')),
98 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
98 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
99 _('ENCODE')),
99 _('ENCODE')),
100 ('', 'encodingmode', encoding.encodingmode,
100 ('', 'encodingmode', encoding.encodingmode,
101 _('set the charset encoding mode'), _('MODE')),
101 _('set the charset encoding mode'), _('MODE')),
102 ('', 'traceback', None, _('always print a traceback on exception')),
102 ('', 'traceback', None, _('always print a traceback on exception')),
103 ('', 'time', None, _('time how long the command takes')),
103 ('', 'time', None, _('time how long the command takes')),
104 ('', 'profile', None, _('print command execution profile')),
104 ('', 'profile', None, _('print command execution profile')),
105 ('', 'version', None, _('output version information and exit')),
105 ('', 'version', None, _('output version information and exit')),
106 ('h', 'help', None, _('display help and exit')),
106 ('h', 'help', None, _('display help and exit')),
107 ('', 'hidden', False, _('consider hidden changesets')),
107 ('', 'hidden', False, _('consider hidden changesets')),
108 ('', 'pager', 'auto',
108 ('', 'pager', 'auto',
109 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
109 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
110 ]
110 ]
111
111
112 dryrunopts = cmdutil.dryrunopts
112 dryrunopts = cmdutil.dryrunopts
113 remoteopts = cmdutil.remoteopts
113 remoteopts = cmdutil.remoteopts
114 walkopts = cmdutil.walkopts
114 walkopts = cmdutil.walkopts
115 commitopts = cmdutil.commitopts
115 commitopts = cmdutil.commitopts
116 commitopts2 = cmdutil.commitopts2
116 commitopts2 = cmdutil.commitopts2
117 formatteropts = cmdutil.formatteropts
117 formatteropts = cmdutil.formatteropts
118 templateopts = cmdutil.templateopts
118 templateopts = cmdutil.templateopts
119 logopts = cmdutil.logopts
119 logopts = cmdutil.logopts
120 diffopts = cmdutil.diffopts
120 diffopts = cmdutil.diffopts
121 diffwsopts = cmdutil.diffwsopts
121 diffwsopts = cmdutil.diffwsopts
122 diffopts2 = cmdutil.diffopts2
122 diffopts2 = cmdutil.diffopts2
123 mergetoolopts = cmdutil.mergetoolopts
123 mergetoolopts = cmdutil.mergetoolopts
124 similarityopts = cmdutil.similarityopts
124 similarityopts = cmdutil.similarityopts
125 subrepoopts = cmdutil.subrepoopts
125 subrepoopts = cmdutil.subrepoopts
126 debugrevlogopts = cmdutil.debugrevlogopts
126 debugrevlogopts = cmdutil.debugrevlogopts
127
127
128 # Commands start here, listed alphabetically
128 # Commands start here, listed alphabetically
129
129
130 @command('^add',
130 @command('^add',
131 walkopts + subrepoopts + dryrunopts,
131 walkopts + subrepoopts + dryrunopts,
132 _('[OPTION]... [FILE]...'),
132 _('[OPTION]... [FILE]...'),
133 inferrepo=True)
133 inferrepo=True)
134 def add(ui, repo, *pats, **opts):
134 def add(ui, repo, *pats, **opts):
135 """add the specified files on the next commit
135 """add the specified files on the next commit
136
136
137 Schedule files to be version controlled and added to the
137 Schedule files to be version controlled and added to the
138 repository.
138 repository.
139
139
140 The files will be added to the repository at the next commit. To
140 The files will be added to the repository at the next commit. To
141 undo an add before that, see :hg:`forget`.
141 undo an add before that, see :hg:`forget`.
142
142
143 If no names are given, add all files to the repository (except
143 If no names are given, add all files to the repository (except
144 files matching ``.hgignore``).
144 files matching ``.hgignore``).
145
145
146 .. container:: verbose
146 .. container:: verbose
147
147
148 Examples:
148 Examples:
149
149
150 - New (unknown) files are added
150 - New (unknown) files are added
151 automatically by :hg:`add`::
151 automatically by :hg:`add`::
152
152
153 $ ls
153 $ ls
154 foo.c
154 foo.c
155 $ hg status
155 $ hg status
156 ? foo.c
156 ? foo.c
157 $ hg add
157 $ hg add
158 adding foo.c
158 adding foo.c
159 $ hg status
159 $ hg status
160 A foo.c
160 A foo.c
161
161
162 - Specific files to be added can be specified::
162 - Specific files to be added can be specified::
163
163
164 $ ls
164 $ ls
165 bar.c foo.c
165 bar.c foo.c
166 $ hg status
166 $ hg status
167 ? bar.c
167 ? bar.c
168 ? foo.c
168 ? foo.c
169 $ hg add bar.c
169 $ hg add bar.c
170 $ hg status
170 $ hg status
171 A bar.c
171 A bar.c
172 ? foo.c
172 ? foo.c
173
173
174 Returns 0 if all files are successfully added.
174 Returns 0 if all files are successfully added.
175 """
175 """
176
176
177 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
177 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
178 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
178 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
179 return rejected and 1 or 0
179 return rejected and 1 or 0
180
180
181 @command('addremove',
181 @command('addremove',
182 similarityopts + subrepoopts + walkopts + dryrunopts,
182 similarityopts + subrepoopts + walkopts + dryrunopts,
183 _('[OPTION]... [FILE]...'),
183 _('[OPTION]... [FILE]...'),
184 inferrepo=True)
184 inferrepo=True)
185 def addremove(ui, repo, *pats, **opts):
185 def addremove(ui, repo, *pats, **opts):
186 """add all new files, delete all missing files
186 """add all new files, delete all missing files
187
187
188 Add all new files and remove all missing files from the
188 Add all new files and remove all missing files from the
189 repository.
189 repository.
190
190
191 Unless names are given, new files are ignored if they match any of
191 Unless names are given, new files are ignored if they match any of
192 the patterns in ``.hgignore``. As with add, these changes take
192 the patterns in ``.hgignore``. As with add, these changes take
193 effect at the next commit.
193 effect at the next commit.
194
194
195 Use the -s/--similarity option to detect renamed files. This
195 Use the -s/--similarity option to detect renamed files. This
196 option takes a percentage between 0 (disabled) and 100 (files must
196 option takes a percentage between 0 (disabled) and 100 (files must
197 be identical) as its parameter. With a parameter greater than 0,
197 be identical) as its parameter. With a parameter greater than 0,
198 this compares every removed file with every added file and records
198 this compares every removed file with every added file and records
199 those similar enough as renames. Detecting renamed files this way
199 those similar enough as renames. Detecting renamed files this way
200 can be expensive. After using this option, :hg:`status -C` can be
200 can be expensive. After using this option, :hg:`status -C` can be
201 used to check which files were identified as moved or renamed. If
201 used to check which files were identified as moved or renamed. If
202 not specified, -s/--similarity defaults to 100 and only renames of
202 not specified, -s/--similarity defaults to 100 and only renames of
203 identical files are detected.
203 identical files are detected.
204
204
205 .. container:: verbose
205 .. container:: verbose
206
206
207 Examples:
207 Examples:
208
208
209 - A number of files (bar.c and foo.c) are new,
209 - A number of files (bar.c and foo.c) are new,
210 while foobar.c has been removed (without using :hg:`remove`)
210 while foobar.c has been removed (without using :hg:`remove`)
211 from the repository::
211 from the repository::
212
212
213 $ ls
213 $ ls
214 bar.c foo.c
214 bar.c foo.c
215 $ hg status
215 $ hg status
216 ! foobar.c
216 ! foobar.c
217 ? bar.c
217 ? bar.c
218 ? foo.c
218 ? foo.c
219 $ hg addremove
219 $ hg addremove
220 adding bar.c
220 adding bar.c
221 adding foo.c
221 adding foo.c
222 removing foobar.c
222 removing foobar.c
223 $ hg status
223 $ hg status
224 A bar.c
224 A bar.c
225 A foo.c
225 A foo.c
226 R foobar.c
226 R foobar.c
227
227
228 - A file foobar.c was moved to foo.c without using :hg:`rename`.
228 - A file foobar.c was moved to foo.c without using :hg:`rename`.
229 Afterwards, it was edited slightly::
229 Afterwards, it was edited slightly::
230
230
231 $ ls
231 $ ls
232 foo.c
232 foo.c
233 $ hg status
233 $ hg status
234 ! foobar.c
234 ! foobar.c
235 ? foo.c
235 ? foo.c
236 $ hg addremove --similarity 90
236 $ hg addremove --similarity 90
237 removing foobar.c
237 removing foobar.c
238 adding foo.c
238 adding foo.c
239 recording removal of foobar.c as rename to foo.c (94% similar)
239 recording removal of foobar.c as rename to foo.c (94% similar)
240 $ hg status -C
240 $ hg status -C
241 A foo.c
241 A foo.c
242 foobar.c
242 foobar.c
243 R foobar.c
243 R foobar.c
244
244
245 Returns 0 if all files are successfully added.
245 Returns 0 if all files are successfully added.
246 """
246 """
247 opts = pycompat.byteskwargs(opts)
247 opts = pycompat.byteskwargs(opts)
248 if not opts.get('similarity'):
248 if not opts.get('similarity'):
249 opts['similarity'] = '100'
249 opts['similarity'] = '100'
250 matcher = scmutil.match(repo[None], pats, opts)
250 matcher = scmutil.match(repo[None], pats, opts)
251 return scmutil.addremove(repo, matcher, "", opts)
251 return scmutil.addremove(repo, matcher, "", opts)
252
252
253 @command('^annotate|blame',
253 @command('^annotate|blame',
254 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
254 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
255 ('', 'follow', None,
255 ('', 'follow', None,
256 _('follow copies/renames and list the filename (DEPRECATED)')),
256 _('follow copies/renames and list the filename (DEPRECATED)')),
257 ('', 'no-follow', None, _("don't follow copies and renames")),
257 ('', 'no-follow', None, _("don't follow copies and renames")),
258 ('a', 'text', None, _('treat all files as text')),
258 ('a', 'text', None, _('treat all files as text')),
259 ('u', 'user', None, _('list the author (long with -v)')),
259 ('u', 'user', None, _('list the author (long with -v)')),
260 ('f', 'file', None, _('list the filename')),
260 ('f', 'file', None, _('list the filename')),
261 ('d', 'date', None, _('list the date (short with -q)')),
261 ('d', 'date', None, _('list the date (short with -q)')),
262 ('n', 'number', None, _('list the revision number (default)')),
262 ('n', 'number', None, _('list the revision number (default)')),
263 ('c', 'changeset', None, _('list the changeset')),
263 ('c', 'changeset', None, _('list the changeset')),
264 ('l', 'line-number', None, _('show line number at the first appearance')),
264 ('l', 'line-number', None, _('show line number at the first appearance')),
265 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
265 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
266 ] + diffwsopts + walkopts + formatteropts,
266 ] + diffwsopts + walkopts + formatteropts,
267 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
267 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
268 inferrepo=True)
268 inferrepo=True)
269 def annotate(ui, repo, *pats, **opts):
269 def annotate(ui, repo, *pats, **opts):
270 """show changeset information by line for each file
270 """show changeset information by line for each file
271
271
272 List changes in files, showing the revision id responsible for
272 List changes in files, showing the revision id responsible for
273 each line.
273 each line.
274
274
275 This command is useful for discovering when a change was made and
275 This command is useful for discovering when a change was made and
276 by whom.
276 by whom.
277
277
278 If you include --file, --user, or --date, the revision number is
278 If you include --file, --user, or --date, the revision number is
279 suppressed unless you also include --number.
279 suppressed unless you also include --number.
280
280
281 Without the -a/--text option, annotate will avoid processing files
281 Without the -a/--text option, annotate will avoid processing files
282 it detects as binary. With -a, annotate will annotate the file
282 it detects as binary. With -a, annotate will annotate the file
283 anyway, although the results will probably be neither useful
283 anyway, although the results will probably be neither useful
284 nor desirable.
284 nor desirable.
285
285
286 Returns 0 on success.
286 Returns 0 on success.
287 """
287 """
288 opts = pycompat.byteskwargs(opts)
288 opts = pycompat.byteskwargs(opts)
289 if not pats:
289 if not pats:
290 raise error.Abort(_('at least one filename or pattern is required'))
290 raise error.Abort(_('at least one filename or pattern is required'))
291
291
292 if opts.get('follow'):
292 if opts.get('follow'):
293 # --follow is deprecated and now just an alias for -f/--file
293 # --follow is deprecated and now just an alias for -f/--file
294 # to mimic the behavior of Mercurial before version 1.5
294 # to mimic the behavior of Mercurial before version 1.5
295 opts['file'] = True
295 opts['file'] = True
296
296
297 rev = opts.get('rev')
297 rev = opts.get('rev')
298 if rev:
298 if rev:
299 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
299 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
300 ctx = scmutil.revsingle(repo, rev)
300 ctx = scmutil.revsingle(repo, rev)
301
301
302 rootfm = ui.formatter('annotate', opts)
302 rootfm = ui.formatter('annotate', opts)
303 if ui.quiet:
303 if ui.quiet:
304 datefunc = dateutil.shortdate
304 datefunc = dateutil.shortdate
305 else:
305 else:
306 datefunc = dateutil.datestr
306 datefunc = dateutil.datestr
307 if ctx.rev() is None:
307 if ctx.rev() is None:
308 def hexfn(node):
308 def hexfn(node):
309 if node is None:
309 if node is None:
310 return None
310 return None
311 else:
311 else:
312 return rootfm.hexfunc(node)
312 return rootfm.hexfunc(node)
313 if opts.get('changeset'):
313 if opts.get('changeset'):
314 # omit "+" suffix which is appended to node hex
314 # omit "+" suffix which is appended to node hex
315 def formatrev(rev):
315 def formatrev(rev):
316 if rev is None:
316 if rev is None:
317 return '%d' % ctx.p1().rev()
317 return '%d' % ctx.p1().rev()
318 else:
318 else:
319 return '%d' % rev
319 return '%d' % rev
320 else:
320 else:
321 def formatrev(rev):
321 def formatrev(rev):
322 if rev is None:
322 if rev is None:
323 return '%d+' % ctx.p1().rev()
323 return '%d+' % ctx.p1().rev()
324 else:
324 else:
325 return '%d ' % rev
325 return '%d ' % rev
326 def formathex(hex):
326 def formathex(hex):
327 if hex is None:
327 if hex is None:
328 return '%s+' % rootfm.hexfunc(ctx.p1().node())
328 return '%s+' % rootfm.hexfunc(ctx.p1().node())
329 else:
329 else:
330 return '%s ' % hex
330 return '%s ' % hex
331 else:
331 else:
332 hexfn = rootfm.hexfunc
332 hexfn = rootfm.hexfunc
333 formatrev = formathex = pycompat.bytestr
333 formatrev = formathex = pycompat.bytestr
334
334
335 opmap = [('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
335 opmap = [('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
336 ('rev', ' ', lambda x: x.fctx.rev(), formatrev),
336 ('rev', ' ', lambda x: x.fctx.rev(), formatrev),
337 ('node', ' ', lambda x: hexfn(x.fctx.node()), formathex),
337 ('node', ' ', lambda x: hexfn(x.fctx.node()), formathex),
338 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
338 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
339 ('file', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
339 ('file', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
340 ('line_number', ':', lambda x: x.lineno, pycompat.bytestr),
340 ('line_number', ':', lambda x: x.lineno, pycompat.bytestr),
341 ]
341 ]
342 opnamemap = {'rev': 'number', 'node': 'changeset'}
342 opnamemap = {'rev': 'number', 'node': 'changeset'}
343
343
344 if (not opts.get('user') and not opts.get('changeset')
344 if (not opts.get('user') and not opts.get('changeset')
345 and not opts.get('date') and not opts.get('file')):
345 and not opts.get('date') and not opts.get('file')):
346 opts['number'] = True
346 opts['number'] = True
347
347
348 linenumber = opts.get('line_number') is not None
348 linenumber = opts.get('line_number') is not None
349 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
349 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
350 raise error.Abort(_('at least one of -n/-c is required for -l'))
350 raise error.Abort(_('at least one of -n/-c is required for -l'))
351
351
352 ui.pager('annotate')
352 ui.pager('annotate')
353
353
354 if rootfm.isplain():
354 if rootfm.isplain():
355 def makefunc(get, fmt):
355 def makefunc(get, fmt):
356 return lambda x: fmt(get(x))
356 return lambda x: fmt(get(x))
357 else:
357 else:
358 def makefunc(get, fmt):
358 def makefunc(get, fmt):
359 return get
359 return get
360 datahint = rootfm.datahint()
360 datahint = rootfm.datahint()
361 funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap
361 funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap
362 if opts.get(opnamemap.get(fn, fn)) or fn in datahint]
362 if opts.get(opnamemap.get(fn, fn)) or fn in datahint]
363 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
363 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
364 fields = ' '.join(fn for fn, sep, get, fmt in opmap
364 fields = ' '.join(fn for fn, sep, get, fmt in opmap
365 if opts.get(opnamemap.get(fn, fn)) or fn in datahint)
365 if opts.get(opnamemap.get(fn, fn)) or fn in datahint)
366
366
367 def bad(x, y):
367 def bad(x, y):
368 raise error.Abort("%s: %s" % (x, y))
368 raise error.Abort("%s: %s" % (x, y))
369
369
370 m = scmutil.match(ctx, pats, opts, badfn=bad)
370 m = scmutil.match(ctx, pats, opts, badfn=bad)
371
371
372 follow = not opts.get('no_follow')
372 follow = not opts.get('no_follow')
373 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
373 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
374 whitespace=True)
374 whitespace=True)
375 skiprevs = opts.get('skip')
375 skiprevs = opts.get('skip')
376 if skiprevs:
376 if skiprevs:
377 skiprevs = scmutil.revrange(repo, skiprevs)
377 skiprevs = scmutil.revrange(repo, skiprevs)
378
378
379 for abs in ctx.walk(m):
379 for abs in ctx.walk(m):
380 fctx = ctx[abs]
380 fctx = ctx[abs]
381 rootfm.startitem()
381 rootfm.startitem()
382 rootfm.data(abspath=abs, path=m.rel(abs))
382 rootfm.data(abspath=abs, path=m.rel(abs))
383 if not opts.get('text') and fctx.isbinary():
383 if not opts.get('text') and fctx.isbinary():
384 rootfm.plain(_("%s: binary file\n")
384 rootfm.plain(_("%s: binary file\n")
385 % ((pats and m.rel(abs)) or abs))
385 % ((pats and m.rel(abs)) or abs))
386 continue
386 continue
387
387
388 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
388 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
389 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
389 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
390 diffopts=diffopts)
390 diffopts=diffopts)
391 if not lines:
391 if not lines:
392 fm.end()
392 fm.end()
393 continue
393 continue
394 formats = []
394 formats = []
395 pieces = []
395 pieces = []
396
396
397 for f, sep in funcmap:
397 for f, sep in funcmap:
398 l = [f(n) for n in lines]
398 l = [f(n) for n in lines]
399 if fm.isplain():
399 if fm.isplain():
400 sizes = [encoding.colwidth(x) for x in l]
400 sizes = [encoding.colwidth(x) for x in l]
401 ml = max(sizes)
401 ml = max(sizes)
402 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
402 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
403 else:
403 else:
404 formats.append(['%s' for x in l])
404 formats.append(['%s' for x in l])
405 pieces.append(l)
405 pieces.append(l)
406
406
407 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
407 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
408 fm.startitem()
408 fm.startitem()
409 fm.context(fctx=n.fctx)
409 fm.context(fctx=n.fctx)
410 fm.write(fields, "".join(f), *p)
410 fm.write(fields, "".join(f), *p)
411 if n.skip:
411 if n.skip:
412 fmt = "* %s"
412 fmt = "* %s"
413 else:
413 else:
414 fmt = ": %s"
414 fmt = ": %s"
415 fm.write('line', fmt, n.text)
415 fm.write('line', fmt, n.text)
416
416
417 if not lines[-1].text.endswith('\n'):
417 if not lines[-1].text.endswith('\n'):
418 fm.plain('\n')
418 fm.plain('\n')
419 fm.end()
419 fm.end()
420
420
421 rootfm.end()
421 rootfm.end()
422
422
423 @command('archive',
423 @command('archive',
424 [('', 'no-decode', None, _('do not pass files through decoders')),
424 [('', 'no-decode', None, _('do not pass files through decoders')),
425 ('p', 'prefix', '', _('directory prefix for files in archive'),
425 ('p', 'prefix', '', _('directory prefix for files in archive'),
426 _('PREFIX')),
426 _('PREFIX')),
427 ('r', 'rev', '', _('revision to distribute'), _('REV')),
427 ('r', 'rev', '', _('revision to distribute'), _('REV')),
428 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
428 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
429 ] + subrepoopts + walkopts,
429 ] + subrepoopts + walkopts,
430 _('[OPTION]... DEST'))
430 _('[OPTION]... DEST'))
431 def archive(ui, repo, dest, **opts):
431 def archive(ui, repo, dest, **opts):
432 '''create an unversioned archive of a repository revision
432 '''create an unversioned archive of a repository revision
433
433
434 By default, the revision used is the parent of the working
434 By default, the revision used is the parent of the working
435 directory; use -r/--rev to specify a different revision.
435 directory; use -r/--rev to specify a different revision.
436
436
437 The archive type is automatically detected based on file
437 The archive type is automatically detected based on file
438 extension (to override, use -t/--type).
438 extension (to override, use -t/--type).
439
439
440 .. container:: verbose
440 .. container:: verbose
441
441
442 Examples:
442 Examples:
443
443
444 - create a zip file containing the 1.0 release::
444 - create a zip file containing the 1.0 release::
445
445
446 hg archive -r 1.0 project-1.0.zip
446 hg archive -r 1.0 project-1.0.zip
447
447
448 - create a tarball excluding .hg files::
448 - create a tarball excluding .hg files::
449
449
450 hg archive project.tar.gz -X ".hg*"
450 hg archive project.tar.gz -X ".hg*"
451
451
452 Valid types are:
452 Valid types are:
453
453
454 :``files``: a directory full of files (default)
454 :``files``: a directory full of files (default)
455 :``tar``: tar archive, uncompressed
455 :``tar``: tar archive, uncompressed
456 :``tbz2``: tar archive, compressed using bzip2
456 :``tbz2``: tar archive, compressed using bzip2
457 :``tgz``: tar archive, compressed using gzip
457 :``tgz``: tar archive, compressed using gzip
458 :``uzip``: zip archive, uncompressed
458 :``uzip``: zip archive, uncompressed
459 :``zip``: zip archive, compressed using deflate
459 :``zip``: zip archive, compressed using deflate
460
460
461 The exact name of the destination archive or directory is given
461 The exact name of the destination archive or directory is given
462 using a format string; see :hg:`help export` for details.
462 using a format string; see :hg:`help export` for details.
463
463
464 Each member added to an archive file has a directory prefix
464 Each member added to an archive file has a directory prefix
465 prepended. Use -p/--prefix to specify a format string for the
465 prepended. Use -p/--prefix to specify a format string for the
466 prefix. The default is the basename of the archive, with suffixes
466 prefix. The default is the basename of the archive, with suffixes
467 removed.
467 removed.
468
468
469 Returns 0 on success.
469 Returns 0 on success.
470 '''
470 '''
471
471
472 opts = pycompat.byteskwargs(opts)
472 opts = pycompat.byteskwargs(opts)
473 rev = opts.get('rev')
473 rev = opts.get('rev')
474 if rev:
474 if rev:
475 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
475 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
476 ctx = scmutil.revsingle(repo, rev)
476 ctx = scmutil.revsingle(repo, rev)
477 if not ctx:
477 if not ctx:
478 raise error.Abort(_('no working directory: please specify a revision'))
478 raise error.Abort(_('no working directory: please specify a revision'))
479 node = ctx.node()
479 node = ctx.node()
480 dest = cmdutil.makefilename(ctx, dest)
480 dest = cmdutil.makefilename(ctx, dest)
481 if os.path.realpath(dest) == repo.root:
481 if os.path.realpath(dest) == repo.root:
482 raise error.Abort(_('repository root cannot be destination'))
482 raise error.Abort(_('repository root cannot be destination'))
483
483
484 kind = opts.get('type') or archival.guesskind(dest) or 'files'
484 kind = opts.get('type') or archival.guesskind(dest) or 'files'
485 prefix = opts.get('prefix')
485 prefix = opts.get('prefix')
486
486
487 if dest == '-':
487 if dest == '-':
488 if kind == 'files':
488 if kind == 'files':
489 raise error.Abort(_('cannot archive plain files to stdout'))
489 raise error.Abort(_('cannot archive plain files to stdout'))
490 dest = cmdutil.makefileobj(ctx, dest)
490 dest = cmdutil.makefileobj(ctx, dest)
491 if not prefix:
491 if not prefix:
492 prefix = os.path.basename(repo.root) + '-%h'
492 prefix = os.path.basename(repo.root) + '-%h'
493
493
494 prefix = cmdutil.makefilename(ctx, prefix)
494 prefix = cmdutil.makefilename(ctx, prefix)
495 match = scmutil.match(ctx, [], opts)
495 match = scmutil.match(ctx, [], opts)
496 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
496 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
497 match, prefix, subrepos=opts.get('subrepos'))
497 match, prefix, subrepos=opts.get('subrepos'))
498
498
499 @command('backout',
499 @command('backout',
500 [('', 'merge', None, _('merge with old dirstate parent after backout')),
500 [('', 'merge', None, _('merge with old dirstate parent after backout')),
501 ('', 'commit', None,
501 ('', 'commit', None,
502 _('commit if no conflicts were encountered (DEPRECATED)')),
502 _('commit if no conflicts were encountered (DEPRECATED)')),
503 ('', 'no-commit', None, _('do not commit')),
503 ('', 'no-commit', None, _('do not commit')),
504 ('', 'parent', '',
504 ('', 'parent', '',
505 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
505 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
506 ('r', 'rev', '', _('revision to backout'), _('REV')),
506 ('r', 'rev', '', _('revision to backout'), _('REV')),
507 ('e', 'edit', False, _('invoke editor on commit messages')),
507 ('e', 'edit', False, _('invoke editor on commit messages')),
508 ] + mergetoolopts + walkopts + commitopts + commitopts2,
508 ] + mergetoolopts + walkopts + commitopts + commitopts2,
509 _('[OPTION]... [-r] REV'))
509 _('[OPTION]... [-r] REV'))
510 def backout(ui, repo, node=None, rev=None, **opts):
510 def backout(ui, repo, node=None, rev=None, **opts):
511 '''reverse effect of earlier changeset
511 '''reverse effect of earlier changeset
512
512
513 Prepare a new changeset with the effect of REV undone in the
513 Prepare a new changeset with the effect of REV undone in the
514 current working directory. If no conflicts were encountered,
514 current working directory. If no conflicts were encountered,
515 it will be committed immediately.
515 it will be committed immediately.
516
516
517 If REV is the parent of the working directory, then this new changeset
517 If REV is the parent of the working directory, then this new changeset
518 is committed automatically (unless --no-commit is specified).
518 is committed automatically (unless --no-commit is specified).
519
519
520 .. note::
520 .. note::
521
521
522 :hg:`backout` cannot be used to fix either an unwanted or
522 :hg:`backout` cannot be used to fix either an unwanted or
523 incorrect merge.
523 incorrect merge.
524
524
525 .. container:: verbose
525 .. container:: verbose
526
526
527 Examples:
527 Examples:
528
528
529 - Reverse the effect of the parent of the working directory.
529 - Reverse the effect of the parent of the working directory.
530 This backout will be committed immediately::
530 This backout will be committed immediately::
531
531
532 hg backout -r .
532 hg backout -r .
533
533
534 - Reverse the effect of previous bad revision 23::
534 - Reverse the effect of previous bad revision 23::
535
535
536 hg backout -r 23
536 hg backout -r 23
537
537
538 - Reverse the effect of previous bad revision 23 and
538 - Reverse the effect of previous bad revision 23 and
539 leave changes uncommitted::
539 leave changes uncommitted::
540
540
541 hg backout -r 23 --no-commit
541 hg backout -r 23 --no-commit
542 hg commit -m "Backout revision 23"
542 hg commit -m "Backout revision 23"
543
543
544 By default, the pending changeset will have one parent,
544 By default, the pending changeset will have one parent,
545 maintaining a linear history. With --merge, the pending
545 maintaining a linear history. With --merge, the pending
546 changeset will instead have two parents: the old parent of the
546 changeset will instead have two parents: the old parent of the
547 working directory and a new child of REV that simply undoes REV.
547 working directory and a new child of REV that simply undoes REV.
548
548
549 Before version 1.7, the behavior without --merge was equivalent
549 Before version 1.7, the behavior without --merge was equivalent
550 to specifying --merge followed by :hg:`update --clean .` to
550 to specifying --merge followed by :hg:`update --clean .` to
551 cancel the merge and leave the child of REV as a head to be
551 cancel the merge and leave the child of REV as a head to be
552 merged separately.
552 merged separately.
553
553
554 See :hg:`help dates` for a list of formats valid for -d/--date.
554 See :hg:`help dates` for a list of formats valid for -d/--date.
555
555
556 See :hg:`help revert` for a way to restore files to the state
556 See :hg:`help revert` for a way to restore files to the state
557 of another revision.
557 of another revision.
558
558
559 Returns 0 on success, 1 if nothing to backout or there are unresolved
559 Returns 0 on success, 1 if nothing to backout or there are unresolved
560 files.
560 files.
561 '''
561 '''
562 with repo.wlock(), repo.lock():
562 with repo.wlock(), repo.lock():
563 return _dobackout(ui, repo, node, rev, **opts)
563 return _dobackout(ui, repo, node, rev, **opts)
564
564
565 def _dobackout(ui, repo, node=None, rev=None, **opts):
565 def _dobackout(ui, repo, node=None, rev=None, **opts):
566 opts = pycompat.byteskwargs(opts)
566 opts = pycompat.byteskwargs(opts)
567 if opts.get('commit') and opts.get('no_commit'):
567 if opts.get('commit') and opts.get('no_commit'):
568 raise error.Abort(_("cannot use --commit with --no-commit"))
568 raise error.Abort(_("cannot use --commit with --no-commit"))
569 if opts.get('merge') and opts.get('no_commit'):
569 if opts.get('merge') and opts.get('no_commit'):
570 raise error.Abort(_("cannot use --merge with --no-commit"))
570 raise error.Abort(_("cannot use --merge with --no-commit"))
571
571
572 if rev and node:
572 if rev and node:
573 raise error.Abort(_("please specify just one revision"))
573 raise error.Abort(_("please specify just one revision"))
574
574
575 if not rev:
575 if not rev:
576 rev = node
576 rev = node
577
577
578 if not rev:
578 if not rev:
579 raise error.Abort(_("please specify a revision to backout"))
579 raise error.Abort(_("please specify a revision to backout"))
580
580
581 date = opts.get('date')
581 date = opts.get('date')
582 if date:
582 if date:
583 opts['date'] = dateutil.parsedate(date)
583 opts['date'] = dateutil.parsedate(date)
584
584
585 cmdutil.checkunfinished(repo)
585 cmdutil.checkunfinished(repo)
586 cmdutil.bailifchanged(repo)
586 cmdutil.bailifchanged(repo)
587 node = scmutil.revsingle(repo, rev).node()
587 node = scmutil.revsingle(repo, rev).node()
588
588
589 op1, op2 = repo.dirstate.parents()
589 op1, op2 = repo.dirstate.parents()
590 if not repo.changelog.isancestor(node, op1):
590 if not repo.changelog.isancestor(node, op1):
591 raise error.Abort(_('cannot backout change that is not an ancestor'))
591 raise error.Abort(_('cannot backout change that is not an ancestor'))
592
592
593 p1, p2 = repo.changelog.parents(node)
593 p1, p2 = repo.changelog.parents(node)
594 if p1 == nullid:
594 if p1 == nullid:
595 raise error.Abort(_('cannot backout a change with no parents'))
595 raise error.Abort(_('cannot backout a change with no parents'))
596 if p2 != nullid:
596 if p2 != nullid:
597 if not opts.get('parent'):
597 if not opts.get('parent'):
598 raise error.Abort(_('cannot backout a merge changeset'))
598 raise error.Abort(_('cannot backout a merge changeset'))
599 p = repo.lookup(opts['parent'])
599 p = repo.lookup(opts['parent'])
600 if p not in (p1, p2):
600 if p not in (p1, p2):
601 raise error.Abort(_('%s is not a parent of %s') %
601 raise error.Abort(_('%s is not a parent of %s') %
602 (short(p), short(node)))
602 (short(p), short(node)))
603 parent = p
603 parent = p
604 else:
604 else:
605 if opts.get('parent'):
605 if opts.get('parent'):
606 raise error.Abort(_('cannot use --parent on non-merge changeset'))
606 raise error.Abort(_('cannot use --parent on non-merge changeset'))
607 parent = p1
607 parent = p1
608
608
609 # the backout should appear on the same branch
609 # the backout should appear on the same branch
610 branch = repo.dirstate.branch()
610 branch = repo.dirstate.branch()
611 bheads = repo.branchheads(branch)
611 bheads = repo.branchheads(branch)
612 rctx = scmutil.revsingle(repo, hex(parent))
612 rctx = scmutil.revsingle(repo, hex(parent))
613 if not opts.get('merge') and op1 != node:
613 if not opts.get('merge') and op1 != node:
614 with dirstateguard.dirstateguard(repo, 'backout'):
614 with dirstateguard.dirstateguard(repo, 'backout'):
615 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
615 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
616 with ui.configoverride(overrides, 'backout'):
616 with ui.configoverride(overrides, 'backout'):
617 stats = mergemod.update(repo, parent, True, True, node, False)
617 stats = mergemod.update(repo, parent, True, True, node, False)
618 repo.setparents(op1, op2)
618 repo.setparents(op1, op2)
619 hg._showstats(repo, stats)
619 hg._showstats(repo, stats)
620 if stats.unresolvedcount:
620 if stats.unresolvedcount:
621 repo.ui.status(_("use 'hg resolve' to retry unresolved "
621 repo.ui.status(_("use 'hg resolve' to retry unresolved "
622 "file merges\n"))
622 "file merges\n"))
623 return 1
623 return 1
624 else:
624 else:
625 hg.clean(repo, node, show_stats=False)
625 hg.clean(repo, node, show_stats=False)
626 repo.dirstate.setbranch(branch)
626 repo.dirstate.setbranch(branch)
627 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
627 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
628
628
629 if opts.get('no_commit'):
629 if opts.get('no_commit'):
630 msg = _("changeset %s backed out, "
630 msg = _("changeset %s backed out, "
631 "don't forget to commit.\n")
631 "don't forget to commit.\n")
632 ui.status(msg % short(node))
632 ui.status(msg % short(node))
633 return 0
633 return 0
634
634
635 def commitfunc(ui, repo, message, match, opts):
635 def commitfunc(ui, repo, message, match, opts):
636 editform = 'backout'
636 editform = 'backout'
637 e = cmdutil.getcommiteditor(editform=editform,
637 e = cmdutil.getcommiteditor(editform=editform,
638 **pycompat.strkwargs(opts))
638 **pycompat.strkwargs(opts))
639 if not message:
639 if not message:
640 # we don't translate commit messages
640 # we don't translate commit messages
641 message = "Backed out changeset %s" % short(node)
641 message = "Backed out changeset %s" % short(node)
642 e = cmdutil.getcommiteditor(edit=True, editform=editform)
642 e = cmdutil.getcommiteditor(edit=True, editform=editform)
643 return repo.commit(message, opts.get('user'), opts.get('date'),
643 return repo.commit(message, opts.get('user'), opts.get('date'),
644 match, editor=e)
644 match, editor=e)
645 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
645 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
646 if not newnode:
646 if not newnode:
647 ui.status(_("nothing changed\n"))
647 ui.status(_("nothing changed\n"))
648 return 1
648 return 1
649 cmdutil.commitstatus(repo, newnode, branch, bheads)
649 cmdutil.commitstatus(repo, newnode, branch, bheads)
650
650
651 def nice(node):
651 def nice(node):
652 return '%d:%s' % (repo.changelog.rev(node), short(node))
652 return '%d:%s' % (repo.changelog.rev(node), short(node))
653 ui.status(_('changeset %s backs out changeset %s\n') %
653 ui.status(_('changeset %s backs out changeset %s\n') %
654 (nice(repo.changelog.tip()), nice(node)))
654 (nice(repo.changelog.tip()), nice(node)))
655 if opts.get('merge') and op1 != node:
655 if opts.get('merge') and op1 != node:
656 hg.clean(repo, op1, show_stats=False)
656 hg.clean(repo, op1, show_stats=False)
657 ui.status(_('merging with changeset %s\n')
657 ui.status(_('merging with changeset %s\n')
658 % nice(repo.changelog.tip()))
658 % nice(repo.changelog.tip()))
659 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
659 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
660 with ui.configoverride(overrides, 'backout'):
660 with ui.configoverride(overrides, 'backout'):
661 return hg.merge(repo, hex(repo.changelog.tip()))
661 return hg.merge(repo, hex(repo.changelog.tip()))
662 return 0
662 return 0
663
663
664 @command('bisect',
664 @command('bisect',
665 [('r', 'reset', False, _('reset bisect state')),
665 [('r', 'reset', False, _('reset bisect state')),
666 ('g', 'good', False, _('mark changeset good')),
666 ('g', 'good', False, _('mark changeset good')),
667 ('b', 'bad', False, _('mark changeset bad')),
667 ('b', 'bad', False, _('mark changeset bad')),
668 ('s', 'skip', False, _('skip testing changeset')),
668 ('s', 'skip', False, _('skip testing changeset')),
669 ('e', 'extend', False, _('extend the bisect range')),
669 ('e', 'extend', False, _('extend the bisect range')),
670 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
670 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
671 ('U', 'noupdate', False, _('do not update to target'))],
671 ('U', 'noupdate', False, _('do not update to target'))],
672 _("[-gbsr] [-U] [-c CMD] [REV]"))
672 _("[-gbsr] [-U] [-c CMD] [REV]"))
673 def bisect(ui, repo, rev=None, extra=None, command=None,
673 def bisect(ui, repo, rev=None, extra=None, command=None,
674 reset=None, good=None, bad=None, skip=None, extend=None,
674 reset=None, good=None, bad=None, skip=None, extend=None,
675 noupdate=None):
675 noupdate=None):
676 """subdivision search of changesets
676 """subdivision search of changesets
677
677
678 This command helps to find changesets which introduce problems. To
678 This command helps to find changesets which introduce problems. To
679 use, mark the earliest changeset you know exhibits the problem as
679 use, mark the earliest changeset you know exhibits the problem as
680 bad, then mark the latest changeset which is free from the problem
680 bad, then mark the latest changeset which is free from the problem
681 as good. Bisect will update your working directory to a revision
681 as good. Bisect will update your working directory to a revision
682 for testing (unless the -U/--noupdate option is specified). Once
682 for testing (unless the -U/--noupdate option is specified). Once
683 you have performed tests, mark the working directory as good or
683 you have performed tests, mark the working directory as good or
684 bad, and bisect will either update to another candidate changeset
684 bad, and bisect will either update to another candidate changeset
685 or announce that it has found the bad revision.
685 or announce that it has found the bad revision.
686
686
687 As a shortcut, you can also use the revision argument to mark a
687 As a shortcut, you can also use the revision argument to mark a
688 revision as good or bad without checking it out first.
688 revision as good or bad without checking it out first.
689
689
690 If you supply a command, it will be used for automatic bisection.
690 If you supply a command, it will be used for automatic bisection.
691 The environment variable HG_NODE will contain the ID of the
691 The environment variable HG_NODE will contain the ID of the
692 changeset being tested. The exit status of the command will be
692 changeset being tested. The exit status of the command will be
693 used to mark revisions as good or bad: status 0 means good, 125
693 used to mark revisions as good or bad: status 0 means good, 125
694 means to skip the revision, 127 (command not found) will abort the
694 means to skip the revision, 127 (command not found) will abort the
695 bisection, and any other non-zero exit status means the revision
695 bisection, and any other non-zero exit status means the revision
696 is bad.
696 is bad.
697
697
698 .. container:: verbose
698 .. container:: verbose
699
699
700 Some examples:
700 Some examples:
701
701
702 - start a bisection with known bad revision 34, and good revision 12::
702 - start a bisection with known bad revision 34, and good revision 12::
703
703
704 hg bisect --bad 34
704 hg bisect --bad 34
705 hg bisect --good 12
705 hg bisect --good 12
706
706
707 - advance the current bisection by marking current revision as good or
707 - advance the current bisection by marking current revision as good or
708 bad::
708 bad::
709
709
710 hg bisect --good
710 hg bisect --good
711 hg bisect --bad
711 hg bisect --bad
712
712
713 - mark the current revision, or a known revision, to be skipped (e.g. if
713 - mark the current revision, or a known revision, to be skipped (e.g. if
714 that revision is not usable because of another issue)::
714 that revision is not usable because of another issue)::
715
715
716 hg bisect --skip
716 hg bisect --skip
717 hg bisect --skip 23
717 hg bisect --skip 23
718
718
719 - skip all revisions that do not touch directories ``foo`` or ``bar``::
719 - skip all revisions that do not touch directories ``foo`` or ``bar``::
720
720
721 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
721 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
722
722
723 - forget the current bisection::
723 - forget the current bisection::
724
724
725 hg bisect --reset
725 hg bisect --reset
726
726
727 - use 'make && make tests' to automatically find the first broken
727 - use 'make && make tests' to automatically find the first broken
728 revision::
728 revision::
729
729
730 hg bisect --reset
730 hg bisect --reset
731 hg bisect --bad 34
731 hg bisect --bad 34
732 hg bisect --good 12
732 hg bisect --good 12
733 hg bisect --command "make && make tests"
733 hg bisect --command "make && make tests"
734
734
735 - see all changesets whose states are already known in the current
735 - see all changesets whose states are already known in the current
736 bisection::
736 bisection::
737
737
738 hg log -r "bisect(pruned)"
738 hg log -r "bisect(pruned)"
739
739
740 - see the changeset currently being bisected (especially useful
740 - see the changeset currently being bisected (especially useful
741 if running with -U/--noupdate)::
741 if running with -U/--noupdate)::
742
742
743 hg log -r "bisect(current)"
743 hg log -r "bisect(current)"
744
744
745 - see all changesets that took part in the current bisection::
745 - see all changesets that took part in the current bisection::
746
746
747 hg log -r "bisect(range)"
747 hg log -r "bisect(range)"
748
748
749 - you can even get a nice graph::
749 - you can even get a nice graph::
750
750
751 hg log --graph -r "bisect(range)"
751 hg log --graph -r "bisect(range)"
752
752
753 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
753 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
754
754
755 Returns 0 on success.
755 Returns 0 on success.
756 """
756 """
757 # backward compatibility
757 # backward compatibility
758 if rev in "good bad reset init".split():
758 if rev in "good bad reset init".split():
759 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
759 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
760 cmd, rev, extra = rev, extra, None
760 cmd, rev, extra = rev, extra, None
761 if cmd == "good":
761 if cmd == "good":
762 good = True
762 good = True
763 elif cmd == "bad":
763 elif cmd == "bad":
764 bad = True
764 bad = True
765 else:
765 else:
766 reset = True
766 reset = True
767 elif extra:
767 elif extra:
768 raise error.Abort(_('incompatible arguments'))
768 raise error.Abort(_('incompatible arguments'))
769
769
770 incompatibles = {
770 incompatibles = {
771 '--bad': bad,
771 '--bad': bad,
772 '--command': bool(command),
772 '--command': bool(command),
773 '--extend': extend,
773 '--extend': extend,
774 '--good': good,
774 '--good': good,
775 '--reset': reset,
775 '--reset': reset,
776 '--skip': skip,
776 '--skip': skip,
777 }
777 }
778
778
779 enabled = [x for x in incompatibles if incompatibles[x]]
779 enabled = [x for x in incompatibles if incompatibles[x]]
780
780
781 if len(enabled) > 1:
781 if len(enabled) > 1:
782 raise error.Abort(_('%s and %s are incompatible') %
782 raise error.Abort(_('%s and %s are incompatible') %
783 tuple(sorted(enabled)[0:2]))
783 tuple(sorted(enabled)[0:2]))
784
784
785 if reset:
785 if reset:
786 hbisect.resetstate(repo)
786 hbisect.resetstate(repo)
787 return
787 return
788
788
789 state = hbisect.load_state(repo)
789 state = hbisect.load_state(repo)
790
790
791 # update state
791 # update state
792 if good or bad or skip:
792 if good or bad or skip:
793 if rev:
793 if rev:
794 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
794 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
795 else:
795 else:
796 nodes = [repo.lookup('.')]
796 nodes = [repo.lookup('.')]
797 if good:
797 if good:
798 state['good'] += nodes
798 state['good'] += nodes
799 elif bad:
799 elif bad:
800 state['bad'] += nodes
800 state['bad'] += nodes
801 elif skip:
801 elif skip:
802 state['skip'] += nodes
802 state['skip'] += nodes
803 hbisect.save_state(repo, state)
803 hbisect.save_state(repo, state)
804 if not (state['good'] and state['bad']):
804 if not (state['good'] and state['bad']):
805 return
805 return
806
806
807 def mayupdate(repo, node, show_stats=True):
807 def mayupdate(repo, node, show_stats=True):
808 """common used update sequence"""
808 """common used update sequence"""
809 if noupdate:
809 if noupdate:
810 return
810 return
811 cmdutil.checkunfinished(repo)
811 cmdutil.checkunfinished(repo)
812 cmdutil.bailifchanged(repo)
812 cmdutil.bailifchanged(repo)
813 return hg.clean(repo, node, show_stats=show_stats)
813 return hg.clean(repo, node, show_stats=show_stats)
814
814
815 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
815 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
816
816
817 if command:
817 if command:
818 changesets = 1
818 changesets = 1
819 if noupdate:
819 if noupdate:
820 try:
820 try:
821 node = state['current'][0]
821 node = state['current'][0]
822 except LookupError:
822 except LookupError:
823 raise error.Abort(_('current bisect revision is unknown - '
823 raise error.Abort(_('current bisect revision is unknown - '
824 'start a new bisect to fix'))
824 'start a new bisect to fix'))
825 else:
825 else:
826 node, p2 = repo.dirstate.parents()
826 node, p2 = repo.dirstate.parents()
827 if p2 != nullid:
827 if p2 != nullid:
828 raise error.Abort(_('current bisect revision is a merge'))
828 raise error.Abort(_('current bisect revision is a merge'))
829 if rev:
829 if rev:
830 node = repo[scmutil.revsingle(repo, rev, node)].node()
830 node = repo[scmutil.revsingle(repo, rev, node)].node()
831 try:
831 try:
832 while changesets:
832 while changesets:
833 # update state
833 # update state
834 state['current'] = [node]
834 state['current'] = [node]
835 hbisect.save_state(repo, state)
835 hbisect.save_state(repo, state)
836 status = ui.system(command, environ={'HG_NODE': hex(node)},
836 status = ui.system(command, environ={'HG_NODE': hex(node)},
837 blockedtag='bisect_check')
837 blockedtag='bisect_check')
838 if status == 125:
838 if status == 125:
839 transition = "skip"
839 transition = "skip"
840 elif status == 0:
840 elif status == 0:
841 transition = "good"
841 transition = "good"
842 # status < 0 means process was killed
842 # status < 0 means process was killed
843 elif status == 127:
843 elif status == 127:
844 raise error.Abort(_("failed to execute %s") % command)
844 raise error.Abort(_("failed to execute %s") % command)
845 elif status < 0:
845 elif status < 0:
846 raise error.Abort(_("%s killed") % command)
846 raise error.Abort(_("%s killed") % command)
847 else:
847 else:
848 transition = "bad"
848 transition = "bad"
849 state[transition].append(node)
849 state[transition].append(node)
850 ctx = repo[node]
850 ctx = repo[node]
851 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
851 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
852 transition))
852 transition))
853 hbisect.checkstate(state)
853 hbisect.checkstate(state)
854 # bisect
854 # bisect
855 nodes, changesets, bgood = hbisect.bisect(repo, state)
855 nodes, changesets, bgood = hbisect.bisect(repo, state)
856 # update to next check
856 # update to next check
857 node = nodes[0]
857 node = nodes[0]
858 mayupdate(repo, node, show_stats=False)
858 mayupdate(repo, node, show_stats=False)
859 finally:
859 finally:
860 state['current'] = [node]
860 state['current'] = [node]
861 hbisect.save_state(repo, state)
861 hbisect.save_state(repo, state)
862 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
862 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
863 return
863 return
864
864
865 hbisect.checkstate(state)
865 hbisect.checkstate(state)
866
866
867 # actually bisect
867 # actually bisect
868 nodes, changesets, good = hbisect.bisect(repo, state)
868 nodes, changesets, good = hbisect.bisect(repo, state)
869 if extend:
869 if extend:
870 if not changesets:
870 if not changesets:
871 extendnode = hbisect.extendrange(repo, state, nodes, good)
871 extendnode = hbisect.extendrange(repo, state, nodes, good)
872 if extendnode is not None:
872 if extendnode is not None:
873 ui.write(_("Extending search to changeset %d:%s\n")
873 ui.write(_("Extending search to changeset %d:%s\n")
874 % (extendnode.rev(), extendnode))
874 % (extendnode.rev(), extendnode))
875 state['current'] = [extendnode.node()]
875 state['current'] = [extendnode.node()]
876 hbisect.save_state(repo, state)
876 hbisect.save_state(repo, state)
877 return mayupdate(repo, extendnode.node())
877 return mayupdate(repo, extendnode.node())
878 raise error.Abort(_("nothing to extend"))
878 raise error.Abort(_("nothing to extend"))
879
879
880 if changesets == 0:
880 if changesets == 0:
881 hbisect.printresult(ui, repo, state, displayer, nodes, good)
881 hbisect.printresult(ui, repo, state, displayer, nodes, good)
882 else:
882 else:
883 assert len(nodes) == 1 # only a single node can be tested next
883 assert len(nodes) == 1 # only a single node can be tested next
884 node = nodes[0]
884 node = nodes[0]
885 # compute the approximate number of remaining tests
885 # compute the approximate number of remaining tests
886 tests, size = 0, 2
886 tests, size = 0, 2
887 while size <= changesets:
887 while size <= changesets:
888 tests, size = tests + 1, size * 2
888 tests, size = tests + 1, size * 2
889 rev = repo.changelog.rev(node)
889 rev = repo.changelog.rev(node)
890 ui.write(_("Testing changeset %d:%s "
890 ui.write(_("Testing changeset %d:%s "
891 "(%d changesets remaining, ~%d tests)\n")
891 "(%d changesets remaining, ~%d tests)\n")
892 % (rev, short(node), changesets, tests))
892 % (rev, short(node), changesets, tests))
893 state['current'] = [node]
893 state['current'] = [node]
894 hbisect.save_state(repo, state)
894 hbisect.save_state(repo, state)
895 return mayupdate(repo, node)
895 return mayupdate(repo, node)
896
896
897 @command('bookmarks|bookmark',
897 @command('bookmarks|bookmark',
898 [('f', 'force', False, _('force')),
898 [('f', 'force', False, _('force')),
899 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
899 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
900 ('d', 'delete', False, _('delete a given bookmark')),
900 ('d', 'delete', False, _('delete a given bookmark')),
901 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
901 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
902 ('i', 'inactive', False, _('mark a bookmark inactive')),
902 ('i', 'inactive', False, _('mark a bookmark inactive')),
903 ] + formatteropts,
903 ] + formatteropts,
904 _('hg bookmarks [OPTIONS]... [NAME]...'))
904 _('hg bookmarks [OPTIONS]... [NAME]...'))
905 def bookmark(ui, repo, *names, **opts):
905 def bookmark(ui, repo, *names, **opts):
906 '''create a new bookmark or list existing bookmarks
906 '''create a new bookmark or list existing bookmarks
907
907
908 Bookmarks are labels on changesets to help track lines of development.
908 Bookmarks are labels on changesets to help track lines of development.
909 Bookmarks are unversioned and can be moved, renamed and deleted.
909 Bookmarks are unversioned and can be moved, renamed and deleted.
910 Deleting or moving a bookmark has no effect on the associated changesets.
910 Deleting or moving a bookmark has no effect on the associated changesets.
911
911
912 Creating or updating to a bookmark causes it to be marked as 'active'.
912 Creating or updating to a bookmark causes it to be marked as 'active'.
913 The active bookmark is indicated with a '*'.
913 The active bookmark is indicated with a '*'.
914 When a commit is made, the active bookmark will advance to the new commit.
914 When a commit is made, the active bookmark will advance to the new commit.
915 A plain :hg:`update` will also advance an active bookmark, if possible.
915 A plain :hg:`update` will also advance an active bookmark, if possible.
916 Updating away from a bookmark will cause it to be deactivated.
916 Updating away from a bookmark will cause it to be deactivated.
917
917
918 Bookmarks can be pushed and pulled between repositories (see
918 Bookmarks can be pushed and pulled between repositories (see
919 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
919 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
920 diverged, a new 'divergent bookmark' of the form 'name@path' will
920 diverged, a new 'divergent bookmark' of the form 'name@path' will
921 be created. Using :hg:`merge` will resolve the divergence.
921 be created. Using :hg:`merge` will resolve the divergence.
922
922
923 Specifying bookmark as '.' to -m or -d options is equivalent to specifying
923 Specifying bookmark as '.' to -m or -d options is equivalent to specifying
924 the active bookmark's name.
924 the active bookmark's name.
925
925
926 A bookmark named '@' has the special property that :hg:`clone` will
926 A bookmark named '@' has the special property that :hg:`clone` will
927 check it out by default if it exists.
927 check it out by default if it exists.
928
928
929 .. container:: verbose
929 .. container:: verbose
930
930
931 Examples:
931 Examples:
932
932
933 - create an active bookmark for a new line of development::
933 - create an active bookmark for a new line of development::
934
934
935 hg book new-feature
935 hg book new-feature
936
936
937 - create an inactive bookmark as a place marker::
937 - create an inactive bookmark as a place marker::
938
938
939 hg book -i reviewed
939 hg book -i reviewed
940
940
941 - create an inactive bookmark on another changeset::
941 - create an inactive bookmark on another changeset::
942
942
943 hg book -r .^ tested
943 hg book -r .^ tested
944
944
945 - rename bookmark turkey to dinner::
945 - rename bookmark turkey to dinner::
946
946
947 hg book -m turkey dinner
947 hg book -m turkey dinner
948
948
949 - move the '@' bookmark from another branch::
949 - move the '@' bookmark from another branch::
950
950
951 hg book -f @
951 hg book -f @
952 '''
952 '''
953 force = opts.get(r'force')
953 force = opts.get(r'force')
954 rev = opts.get(r'rev')
954 rev = opts.get(r'rev')
955 delete = opts.get(r'delete')
955 delete = opts.get(r'delete')
956 rename = opts.get(r'rename')
956 rename = opts.get(r'rename')
957 inactive = opts.get(r'inactive')
957 inactive = opts.get(r'inactive')
958
958
959 if delete and rename:
959 if delete and rename:
960 raise error.Abort(_("--delete and --rename are incompatible"))
960 raise error.Abort(_("--delete and --rename are incompatible"))
961 if delete and rev:
961 if delete and rev:
962 raise error.Abort(_("--rev is incompatible with --delete"))
962 raise error.Abort(_("--rev is incompatible with --delete"))
963 if rename and rev:
963 if rename and rev:
964 raise error.Abort(_("--rev is incompatible with --rename"))
964 raise error.Abort(_("--rev is incompatible with --rename"))
965 if not names and (delete or rev):
965 if not names and (delete or rev):
966 raise error.Abort(_("bookmark name required"))
966 raise error.Abort(_("bookmark name required"))
967
967
968 if delete or rename or names or inactive:
968 if delete or rename or names or inactive:
969 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
969 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
970 if delete:
970 if delete:
971 names = pycompat.maplist(repo._bookmarks.expandname, names)
971 names = pycompat.maplist(repo._bookmarks.expandname, names)
972 bookmarks.delete(repo, tr, names)
972 bookmarks.delete(repo, tr, names)
973 elif rename:
973 elif rename:
974 if not names:
974 if not names:
975 raise error.Abort(_("new bookmark name required"))
975 raise error.Abort(_("new bookmark name required"))
976 elif len(names) > 1:
976 elif len(names) > 1:
977 raise error.Abort(_("only one new bookmark name allowed"))
977 raise error.Abort(_("only one new bookmark name allowed"))
978 rename = repo._bookmarks.expandname(rename)
978 rename = repo._bookmarks.expandname(rename)
979 bookmarks.rename(repo, tr, rename, names[0], force, inactive)
979 bookmarks.rename(repo, tr, rename, names[0], force, inactive)
980 elif names:
980 elif names:
981 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
981 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
982 elif inactive:
982 elif inactive:
983 if len(repo._bookmarks) == 0:
983 if len(repo._bookmarks) == 0:
984 ui.status(_("no bookmarks set\n"))
984 ui.status(_("no bookmarks set\n"))
985 elif not repo._activebookmark:
985 elif not repo._activebookmark:
986 ui.status(_("no active bookmark\n"))
986 ui.status(_("no active bookmark\n"))
987 else:
987 else:
988 bookmarks.deactivate(repo)
988 bookmarks.deactivate(repo)
989 else: # show bookmarks
989 else: # show bookmarks
990 bookmarks.printbookmarks(ui, repo, **opts)
990 bookmarks.printbookmarks(ui, repo, **opts)
991
991
992 @command('branch',
992 @command('branch',
993 [('f', 'force', None,
993 [('f', 'force', None,
994 _('set branch name even if it shadows an existing branch')),
994 _('set branch name even if it shadows an existing branch')),
995 ('C', 'clean', None, _('reset branch name to parent branch name')),
995 ('C', 'clean', None, _('reset branch name to parent branch name')),
996 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
996 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
997 ],
997 ],
998 _('[-fC] [NAME]'))
998 _('[-fC] [NAME]'))
999 def branch(ui, repo, label=None, **opts):
999 def branch(ui, repo, label=None, **opts):
1000 """set or show the current branch name
1000 """set or show the current branch name
1001
1001
1002 .. note::
1002 .. note::
1003
1003
1004 Branch names are permanent and global. Use :hg:`bookmark` to create a
1004 Branch names are permanent and global. Use :hg:`bookmark` to create a
1005 light-weight bookmark instead. See :hg:`help glossary` for more
1005 light-weight bookmark instead. See :hg:`help glossary` for more
1006 information about named branches and bookmarks.
1006 information about named branches and bookmarks.
1007
1007
1008 With no argument, show the current branch name. With one argument,
1008 With no argument, show the current branch name. With one argument,
1009 set the working directory branch name (the branch will not exist
1009 set the working directory branch name (the branch will not exist
1010 in the repository until the next commit). Standard practice
1010 in the repository until the next commit). Standard practice
1011 recommends that primary development take place on the 'default'
1011 recommends that primary development take place on the 'default'
1012 branch.
1012 branch.
1013
1013
1014 Unless -f/--force is specified, branch will not let you set a
1014 Unless -f/--force is specified, branch will not let you set a
1015 branch name that already exists.
1015 branch name that already exists.
1016
1016
1017 Use -C/--clean to reset the working directory branch to that of
1017 Use -C/--clean to reset the working directory branch to that of
1018 the parent of the working directory, negating a previous branch
1018 the parent of the working directory, negating a previous branch
1019 change.
1019 change.
1020
1020
1021 Use the command :hg:`update` to switch to an existing branch. Use
1021 Use the command :hg:`update` to switch to an existing branch. Use
1022 :hg:`commit --close-branch` to mark this branch head as closed.
1022 :hg:`commit --close-branch` to mark this branch head as closed.
1023 When all heads of a branch are closed, the branch will be
1023 When all heads of a branch are closed, the branch will be
1024 considered closed.
1024 considered closed.
1025
1025
1026 Returns 0 on success.
1026 Returns 0 on success.
1027 """
1027 """
1028 opts = pycompat.byteskwargs(opts)
1028 opts = pycompat.byteskwargs(opts)
1029 revs = opts.get('rev')
1029 revs = opts.get('rev')
1030 if label:
1030 if label:
1031 label = label.strip()
1031 label = label.strip()
1032
1032
1033 if not opts.get('clean') and not label:
1033 if not opts.get('clean') and not label:
1034 if revs:
1034 if revs:
1035 raise error.Abort(_("no branch name specified for the revisions"))
1035 raise error.Abort(_("no branch name specified for the revisions"))
1036 ui.write("%s\n" % repo.dirstate.branch())
1036 ui.write("%s\n" % repo.dirstate.branch())
1037 return
1037 return
1038
1038
1039 with repo.wlock():
1039 with repo.wlock():
1040 if opts.get('clean'):
1040 if opts.get('clean'):
1041 label = repo[None].p1().branch()
1041 label = repo[None].p1().branch()
1042 repo.dirstate.setbranch(label)
1042 repo.dirstate.setbranch(label)
1043 ui.status(_('reset working directory to branch %s\n') % label)
1043 ui.status(_('reset working directory to branch %s\n') % label)
1044 elif label:
1044 elif label:
1045
1045
1046 scmutil.checknewlabel(repo, label, 'branch')
1046 scmutil.checknewlabel(repo, label, 'branch')
1047 if revs:
1047 if revs:
1048 return cmdutil.changebranch(ui, repo, revs, label)
1048 return cmdutil.changebranch(ui, repo, revs, label)
1049
1049
1050 if not opts.get('force') and label in repo.branchmap():
1050 if not opts.get('force') and label in repo.branchmap():
1051 if label not in [p.branch() for p in repo[None].parents()]:
1051 if label not in [p.branch() for p in repo[None].parents()]:
1052 raise error.Abort(_('a branch of the same name already'
1052 raise error.Abort(_('a branch of the same name already'
1053 ' exists'),
1053 ' exists'),
1054 # i18n: "it" refers to an existing branch
1054 # i18n: "it" refers to an existing branch
1055 hint=_("use 'hg update' to switch to it"))
1055 hint=_("use 'hg update' to switch to it"))
1056
1056
1057 repo.dirstate.setbranch(label)
1057 repo.dirstate.setbranch(label)
1058 ui.status(_('marked working directory as branch %s\n') % label)
1058 ui.status(_('marked working directory as branch %s\n') % label)
1059
1059
1060 # find any open named branches aside from default
1060 # find any open named branches aside from default
1061 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1061 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1062 if n != "default" and not c]
1062 if n != "default" and not c]
1063 if not others:
1063 if not others:
1064 ui.status(_('(branches are permanent and global, '
1064 ui.status(_('(branches are permanent and global, '
1065 'did you want a bookmark?)\n'))
1065 'did you want a bookmark?)\n'))
1066
1066
1067 @command('branches',
1067 @command('branches',
1068 [('a', 'active', False,
1068 [('a', 'active', False,
1069 _('show only branches that have unmerged heads (DEPRECATED)')),
1069 _('show only branches that have unmerged heads (DEPRECATED)')),
1070 ('c', 'closed', False, _('show normal and closed branches')),
1070 ('c', 'closed', False, _('show normal and closed branches')),
1071 ] + formatteropts,
1071 ] + formatteropts,
1072 _('[-c]'),
1072 _('[-c]'),
1073 intents={INTENT_READONLY})
1073 intents={INTENT_READONLY})
1074 def branches(ui, repo, active=False, closed=False, **opts):
1074 def branches(ui, repo, active=False, closed=False, **opts):
1075 """list repository named branches
1075 """list repository named branches
1076
1076
1077 List the repository's named branches, indicating which ones are
1077 List the repository's named branches, indicating which ones are
1078 inactive. If -c/--closed is specified, also list branches which have
1078 inactive. If -c/--closed is specified, also list branches which have
1079 been marked closed (see :hg:`commit --close-branch`).
1079 been marked closed (see :hg:`commit --close-branch`).
1080
1080
1081 Use the command :hg:`update` to switch to an existing branch.
1081 Use the command :hg:`update` to switch to an existing branch.
1082
1082
1083 Returns 0.
1083 Returns 0.
1084 """
1084 """
1085
1085
1086 opts = pycompat.byteskwargs(opts)
1086 opts = pycompat.byteskwargs(opts)
1087 ui.pager('branches')
1087 ui.pager('branches')
1088 fm = ui.formatter('branches', opts)
1088 fm = ui.formatter('branches', opts)
1089 hexfunc = fm.hexfunc
1089 hexfunc = fm.hexfunc
1090
1090
1091 allheads = set(repo.heads())
1091 allheads = set(repo.heads())
1092 branches = []
1092 branches = []
1093 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1093 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1094 isactive = False
1094 isactive = False
1095 if not isclosed:
1095 if not isclosed:
1096 openheads = set(repo.branchmap().iteropen(heads))
1096 openheads = set(repo.branchmap().iteropen(heads))
1097 isactive = bool(openheads & allheads)
1097 isactive = bool(openheads & allheads)
1098 branches.append((tag, repo[tip], isactive, not isclosed))
1098 branches.append((tag, repo[tip], isactive, not isclosed))
1099 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1099 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1100 reverse=True)
1100 reverse=True)
1101
1101
1102 for tag, ctx, isactive, isopen in branches:
1102 for tag, ctx, isactive, isopen in branches:
1103 if active and not isactive:
1103 if active and not isactive:
1104 continue
1104 continue
1105 if isactive:
1105 if isactive:
1106 label = 'branches.active'
1106 label = 'branches.active'
1107 notice = ''
1107 notice = ''
1108 elif not isopen:
1108 elif not isopen:
1109 if not closed:
1109 if not closed:
1110 continue
1110 continue
1111 label = 'branches.closed'
1111 label = 'branches.closed'
1112 notice = _(' (closed)')
1112 notice = _(' (closed)')
1113 else:
1113 else:
1114 label = 'branches.inactive'
1114 label = 'branches.inactive'
1115 notice = _(' (inactive)')
1115 notice = _(' (inactive)')
1116 current = (tag == repo.dirstate.branch())
1116 current = (tag == repo.dirstate.branch())
1117 if current:
1117 if current:
1118 label = 'branches.current'
1118 label = 'branches.current'
1119
1119
1120 fm.startitem()
1120 fm.startitem()
1121 fm.write('branch', '%s', tag, label=label)
1121 fm.write('branch', '%s', tag, label=label)
1122 rev = ctx.rev()
1122 rev = ctx.rev()
1123 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1123 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1124 fmt = ' ' * padsize + ' %d:%s'
1124 fmt = ' ' * padsize + ' %d:%s'
1125 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1125 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1126 label='log.changeset changeset.%s' % ctx.phasestr())
1126 label='log.changeset changeset.%s' % ctx.phasestr())
1127 fm.context(ctx=ctx)
1127 fm.context(ctx=ctx)
1128 fm.data(active=isactive, closed=not isopen, current=current)
1128 fm.data(active=isactive, closed=not isopen, current=current)
1129 if not ui.quiet:
1129 if not ui.quiet:
1130 fm.plain(notice)
1130 fm.plain(notice)
1131 fm.plain('\n')
1131 fm.plain('\n')
1132 fm.end()
1132 fm.end()
1133
1133
1134 @command('bundle',
1134 @command('bundle',
1135 [('f', 'force', None, _('run even when the destination is unrelated')),
1135 [('f', 'force', None, _('run even when the destination is unrelated')),
1136 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1136 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1137 _('REV')),
1137 _('REV')),
1138 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1138 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1139 _('BRANCH')),
1139 _('BRANCH')),
1140 ('', 'base', [],
1140 ('', 'base', [],
1141 _('a base changeset assumed to be available at the destination'),
1141 _('a base changeset assumed to be available at the destination'),
1142 _('REV')),
1142 _('REV')),
1143 ('a', 'all', None, _('bundle all changesets in the repository')),
1143 ('a', 'all', None, _('bundle all changesets in the repository')),
1144 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1144 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1145 ] + remoteopts,
1145 ] + remoteopts,
1146 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1146 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1147 def bundle(ui, repo, fname, dest=None, **opts):
1147 def bundle(ui, repo, fname, dest=None, **opts):
1148 """create a bundle file
1148 """create a bundle file
1149
1149
1150 Generate a bundle file containing data to be transferred to another
1150 Generate a bundle file containing data to be transferred to another
1151 repository.
1151 repository.
1152
1152
1153 To create a bundle containing all changesets, use -a/--all
1153 To create a bundle containing all changesets, use -a/--all
1154 (or --base null). Otherwise, hg assumes the destination will have
1154 (or --base null). Otherwise, hg assumes the destination will have
1155 all the nodes you specify with --base parameters. Otherwise, hg
1155 all the nodes you specify with --base parameters. Otherwise, hg
1156 will assume the repository has all the nodes in destination, or
1156 will assume the repository has all the nodes in destination, or
1157 default-push/default if no destination is specified, where destination
1157 default-push/default if no destination is specified, where destination
1158 is the repository you provide through DEST option.
1158 is the repository you provide through DEST option.
1159
1159
1160 You can change bundle format with the -t/--type option. See
1160 You can change bundle format with the -t/--type option. See
1161 :hg:`help bundlespec` for documentation on this format. By default,
1161 :hg:`help bundlespec` for documentation on this format. By default,
1162 the most appropriate format is used and compression defaults to
1162 the most appropriate format is used and compression defaults to
1163 bzip2.
1163 bzip2.
1164
1164
1165 The bundle file can then be transferred using conventional means
1165 The bundle file can then be transferred using conventional means
1166 and applied to another repository with the unbundle or pull
1166 and applied to another repository with the unbundle or pull
1167 command. This is useful when direct push and pull are not
1167 command. This is useful when direct push and pull are not
1168 available or when exporting an entire repository is undesirable.
1168 available or when exporting an entire repository is undesirable.
1169
1169
1170 Applying bundles preserves all changeset contents including
1170 Applying bundles preserves all changeset contents including
1171 permissions, copy/rename information, and revision history.
1171 permissions, copy/rename information, and revision history.
1172
1172
1173 Returns 0 on success, 1 if no changes found.
1173 Returns 0 on success, 1 if no changes found.
1174 """
1174 """
1175 opts = pycompat.byteskwargs(opts)
1175 opts = pycompat.byteskwargs(opts)
1176 revs = None
1176 revs = None
1177 if 'rev' in opts:
1177 if 'rev' in opts:
1178 revstrings = opts['rev']
1178 revstrings = opts['rev']
1179 revs = scmutil.revrange(repo, revstrings)
1179 revs = scmutil.revrange(repo, revstrings)
1180 if revstrings and not revs:
1180 if revstrings and not revs:
1181 raise error.Abort(_('no commits to bundle'))
1181 raise error.Abort(_('no commits to bundle'))
1182
1182
1183 bundletype = opts.get('type', 'bzip2').lower()
1183 bundletype = opts.get('type', 'bzip2').lower()
1184 try:
1184 try:
1185 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1185 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1186 except error.UnsupportedBundleSpecification as e:
1186 except error.UnsupportedBundleSpecification as e:
1187 raise error.Abort(pycompat.bytestr(e),
1187 raise error.Abort(pycompat.bytestr(e),
1188 hint=_("see 'hg help bundlespec' for supported "
1188 hint=_("see 'hg help bundlespec' for supported "
1189 "values for --type"))
1189 "values for --type"))
1190 cgversion = bundlespec.contentopts["cg.version"]
1190 cgversion = bundlespec.contentopts["cg.version"]
1191
1191
1192 # Packed bundles are a pseudo bundle format for now.
1192 # Packed bundles are a pseudo bundle format for now.
1193 if cgversion == 's1':
1193 if cgversion == 's1':
1194 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1194 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1195 hint=_("use 'hg debugcreatestreamclonebundle'"))
1195 hint=_("use 'hg debugcreatestreamclonebundle'"))
1196
1196
1197 if opts.get('all'):
1197 if opts.get('all'):
1198 if dest:
1198 if dest:
1199 raise error.Abort(_("--all is incompatible with specifying "
1199 raise error.Abort(_("--all is incompatible with specifying "
1200 "a destination"))
1200 "a destination"))
1201 if opts.get('base'):
1201 if opts.get('base'):
1202 ui.warn(_("ignoring --base because --all was specified\n"))
1202 ui.warn(_("ignoring --base because --all was specified\n"))
1203 base = ['null']
1203 base = ['null']
1204 else:
1204 else:
1205 base = scmutil.revrange(repo, opts.get('base'))
1205 base = scmutil.revrange(repo, opts.get('base'))
1206 if cgversion not in changegroup.supportedoutgoingversions(repo):
1206 if cgversion not in changegroup.supportedoutgoingversions(repo):
1207 raise error.Abort(_("repository does not support bundle version %s") %
1207 raise error.Abort(_("repository does not support bundle version %s") %
1208 cgversion)
1208 cgversion)
1209
1209
1210 if base:
1210 if base:
1211 if dest:
1211 if dest:
1212 raise error.Abort(_("--base is incompatible with specifying "
1212 raise error.Abort(_("--base is incompatible with specifying "
1213 "a destination"))
1213 "a destination"))
1214 common = [repo[rev].node() for rev in base]
1214 common = [repo[rev].node() for rev in base]
1215 heads = [repo[r].node() for r in revs] if revs else None
1215 heads = [repo[r].node() for r in revs] if revs else None
1216 outgoing = discovery.outgoing(repo, common, heads)
1216 outgoing = discovery.outgoing(repo, common, heads)
1217 else:
1217 else:
1218 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1218 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1219 dest, branches = hg.parseurl(dest, opts.get('branch'))
1219 dest, branches = hg.parseurl(dest, opts.get('branch'))
1220 other = hg.peer(repo, opts, dest)
1220 other = hg.peer(repo, opts, dest)
1221 revs = [repo[r].hex() for r in revs]
1221 revs = [repo[r].hex() for r in revs]
1222 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1222 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1223 heads = revs and map(repo.lookup, revs) or revs
1223 heads = revs and map(repo.lookup, revs) or revs
1224 outgoing = discovery.findcommonoutgoing(repo, other,
1224 outgoing = discovery.findcommonoutgoing(repo, other,
1225 onlyheads=heads,
1225 onlyheads=heads,
1226 force=opts.get('force'),
1226 force=opts.get('force'),
1227 portable=True)
1227 portable=True)
1228
1228
1229 if not outgoing.missing:
1229 if not outgoing.missing:
1230 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1230 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1231 return 1
1231 return 1
1232
1232
1233 if cgversion == '01': #bundle1
1233 if cgversion == '01': #bundle1
1234 bversion = 'HG10' + bundlespec.wirecompression
1234 bversion = 'HG10' + bundlespec.wirecompression
1235 bcompression = None
1235 bcompression = None
1236 elif cgversion in ('02', '03'):
1236 elif cgversion in ('02', '03'):
1237 bversion = 'HG20'
1237 bversion = 'HG20'
1238 bcompression = bundlespec.wirecompression
1238 bcompression = bundlespec.wirecompression
1239 else:
1239 else:
1240 raise error.ProgrammingError(
1240 raise error.ProgrammingError(
1241 'bundle: unexpected changegroup version %s' % cgversion)
1241 'bundle: unexpected changegroup version %s' % cgversion)
1242
1242
1243 # TODO compression options should be derived from bundlespec parsing.
1243 # TODO compression options should be derived from bundlespec parsing.
1244 # This is a temporary hack to allow adjusting bundle compression
1244 # This is a temporary hack to allow adjusting bundle compression
1245 # level without a) formalizing the bundlespec changes to declare it
1245 # level without a) formalizing the bundlespec changes to declare it
1246 # b) introducing a command flag.
1246 # b) introducing a command flag.
1247 compopts = {}
1247 compopts = {}
1248 complevel = ui.configint('experimental',
1248 complevel = ui.configint('experimental',
1249 'bundlecomplevel.' + bundlespec.compression)
1249 'bundlecomplevel.' + bundlespec.compression)
1250 if complevel is None:
1250 if complevel is None:
1251 complevel = ui.configint('experimental', 'bundlecomplevel')
1251 complevel = ui.configint('experimental', 'bundlecomplevel')
1252 if complevel is not None:
1252 if complevel is not None:
1253 compopts['level'] = complevel
1253 compopts['level'] = complevel
1254
1254
1255 # Allow overriding the bundling of obsmarker in phases through
1255 # Allow overriding the bundling of obsmarker in phases through
1256 # configuration while we don't have a bundle version that include them
1256 # configuration while we don't have a bundle version that include them
1257 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1257 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1258 bundlespec.contentopts['obsolescence'] = True
1258 bundlespec.contentopts['obsolescence'] = True
1259 if repo.ui.configbool('experimental', 'bundle-phases'):
1259 if repo.ui.configbool('experimental', 'bundle-phases'):
1260 bundlespec.contentopts['phases'] = True
1260 bundlespec.contentopts['phases'] = True
1261
1261
1262 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1262 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1263 bundlespec.contentopts, compression=bcompression,
1263 bundlespec.contentopts, compression=bcompression,
1264 compopts=compopts)
1264 compopts=compopts)
1265
1265
1266 @command('cat',
1266 @command('cat',
1267 [('o', 'output', '',
1267 [('o', 'output', '',
1268 _('print output to file with formatted name'), _('FORMAT')),
1268 _('print output to file with formatted name'), _('FORMAT')),
1269 ('r', 'rev', '', _('print the given revision'), _('REV')),
1269 ('r', 'rev', '', _('print the given revision'), _('REV')),
1270 ('', 'decode', None, _('apply any matching decode filter')),
1270 ('', 'decode', None, _('apply any matching decode filter')),
1271 ] + walkopts + formatteropts,
1271 ] + walkopts + formatteropts,
1272 _('[OPTION]... FILE...'),
1272 _('[OPTION]... FILE...'),
1273 inferrepo=True,
1273 inferrepo=True,
1274 intents={INTENT_READONLY})
1274 intents={INTENT_READONLY})
1275 def cat(ui, repo, file1, *pats, **opts):
1275 def cat(ui, repo, file1, *pats, **opts):
1276 """output the current or given revision of files
1276 """output the current or given revision of files
1277
1277
1278 Print the specified files as they were at the given revision. If
1278 Print the specified files as they were at the given revision. If
1279 no revision is given, the parent of the working directory is used.
1279 no revision is given, the parent of the working directory is used.
1280
1280
1281 Output may be to a file, in which case the name of the file is
1281 Output may be to a file, in which case the name of the file is
1282 given using a template string. See :hg:`help templates`. In addition
1282 given using a template string. See :hg:`help templates`. In addition
1283 to the common template keywords, the following formatting rules are
1283 to the common template keywords, the following formatting rules are
1284 supported:
1284 supported:
1285
1285
1286 :``%%``: literal "%" character
1286 :``%%``: literal "%" character
1287 :``%s``: basename of file being printed
1287 :``%s``: basename of file being printed
1288 :``%d``: dirname of file being printed, or '.' if in repository root
1288 :``%d``: dirname of file being printed, or '.' if in repository root
1289 :``%p``: root-relative path name of file being printed
1289 :``%p``: root-relative path name of file being printed
1290 :``%H``: changeset hash (40 hexadecimal digits)
1290 :``%H``: changeset hash (40 hexadecimal digits)
1291 :``%R``: changeset revision number
1291 :``%R``: changeset revision number
1292 :``%h``: short-form changeset hash (12 hexadecimal digits)
1292 :``%h``: short-form changeset hash (12 hexadecimal digits)
1293 :``%r``: zero-padded changeset revision number
1293 :``%r``: zero-padded changeset revision number
1294 :``%b``: basename of the exporting repository
1294 :``%b``: basename of the exporting repository
1295 :``\\``: literal "\\" character
1295 :``\\``: literal "\\" character
1296
1296
1297 Returns 0 on success.
1297 Returns 0 on success.
1298 """
1298 """
1299 opts = pycompat.byteskwargs(opts)
1299 opts = pycompat.byteskwargs(opts)
1300 rev = opts.get('rev')
1300 rev = opts.get('rev')
1301 if rev:
1301 if rev:
1302 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1302 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1303 ctx = scmutil.revsingle(repo, rev)
1303 ctx = scmutil.revsingle(repo, rev)
1304 m = scmutil.match(ctx, (file1,) + pats, opts)
1304 m = scmutil.match(ctx, (file1,) + pats, opts)
1305 fntemplate = opts.pop('output', '')
1305 fntemplate = opts.pop('output', '')
1306 if cmdutil.isstdiofilename(fntemplate):
1306 if cmdutil.isstdiofilename(fntemplate):
1307 fntemplate = ''
1307 fntemplate = ''
1308
1308
1309 if fntemplate:
1309 if fntemplate:
1310 fm = formatter.nullformatter(ui, 'cat', opts)
1310 fm = formatter.nullformatter(ui, 'cat', opts)
1311 else:
1311 else:
1312 ui.pager('cat')
1312 ui.pager('cat')
1313 fm = ui.formatter('cat', opts)
1313 fm = ui.formatter('cat', opts)
1314 with fm:
1314 with fm:
1315 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1315 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1316 **pycompat.strkwargs(opts))
1316 **pycompat.strkwargs(opts))
1317
1317
1318 @command('^clone',
1318 @command('^clone',
1319 [('U', 'noupdate', None, _('the clone will include an empty working '
1319 [('U', 'noupdate', None, _('the clone will include an empty working '
1320 'directory (only a repository)')),
1320 'directory (only a repository)')),
1321 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1321 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1322 _('REV')),
1322 _('REV')),
1323 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1323 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1324 ' and its ancestors'), _('REV')),
1324 ' and its ancestors'), _('REV')),
1325 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1325 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1326 ' changesets and their ancestors'), _('BRANCH')),
1326 ' changesets and their ancestors'), _('BRANCH')),
1327 ('', 'pull', None, _('use pull protocol to copy metadata')),
1327 ('', 'pull', None, _('use pull protocol to copy metadata')),
1328 ('', 'uncompressed', None,
1328 ('', 'uncompressed', None,
1329 _('an alias to --stream (DEPRECATED)')),
1329 _('an alias to --stream (DEPRECATED)')),
1330 ('', 'stream', None,
1330 ('', 'stream', None,
1331 _('clone with minimal data processing')),
1331 _('clone with minimal data processing')),
1332 ] + remoteopts,
1332 ] + remoteopts,
1333 _('[OPTION]... SOURCE [DEST]'),
1333 _('[OPTION]... SOURCE [DEST]'),
1334 norepo=True)
1334 norepo=True)
1335 def clone(ui, source, dest=None, **opts):
1335 def clone(ui, source, dest=None, **opts):
1336 """make a copy of an existing repository
1336 """make a copy of an existing repository
1337
1337
1338 Create a copy of an existing repository in a new directory.
1338 Create a copy of an existing repository in a new directory.
1339
1339
1340 If no destination directory name is specified, it defaults to the
1340 If no destination directory name is specified, it defaults to the
1341 basename of the source.
1341 basename of the source.
1342
1342
1343 The location of the source is added to the new repository's
1343 The location of the source is added to the new repository's
1344 ``.hg/hgrc`` file, as the default to be used for future pulls.
1344 ``.hg/hgrc`` file, as the default to be used for future pulls.
1345
1345
1346 Only local paths and ``ssh://`` URLs are supported as
1346 Only local paths and ``ssh://`` URLs are supported as
1347 destinations. For ``ssh://`` destinations, no working directory or
1347 destinations. For ``ssh://`` destinations, no working directory or
1348 ``.hg/hgrc`` will be created on the remote side.
1348 ``.hg/hgrc`` will be created on the remote side.
1349
1349
1350 If the source repository has a bookmark called '@' set, that
1350 If the source repository has a bookmark called '@' set, that
1351 revision will be checked out in the new repository by default.
1351 revision will be checked out in the new repository by default.
1352
1352
1353 To check out a particular version, use -u/--update, or
1353 To check out a particular version, use -u/--update, or
1354 -U/--noupdate to create a clone with no working directory.
1354 -U/--noupdate to create a clone with no working directory.
1355
1355
1356 To pull only a subset of changesets, specify one or more revisions
1356 To pull only a subset of changesets, specify one or more revisions
1357 identifiers with -r/--rev or branches with -b/--branch. The
1357 identifiers with -r/--rev or branches with -b/--branch. The
1358 resulting clone will contain only the specified changesets and
1358 resulting clone will contain only the specified changesets and
1359 their ancestors. These options (or 'clone src#rev dest') imply
1359 their ancestors. These options (or 'clone src#rev dest') imply
1360 --pull, even for local source repositories.
1360 --pull, even for local source repositories.
1361
1361
1362 In normal clone mode, the remote normalizes repository data into a common
1362 In normal clone mode, the remote normalizes repository data into a common
1363 exchange format and the receiving end translates this data into its local
1363 exchange format and the receiving end translates this data into its local
1364 storage format. --stream activates a different clone mode that essentially
1364 storage format. --stream activates a different clone mode that essentially
1365 copies repository files from the remote with minimal data processing. This
1365 copies repository files from the remote with minimal data processing. This
1366 significantly reduces the CPU cost of a clone both remotely and locally.
1366 significantly reduces the CPU cost of a clone both remotely and locally.
1367 However, it often increases the transferred data size by 30-40%. This can
1367 However, it often increases the transferred data size by 30-40%. This can
1368 result in substantially faster clones where I/O throughput is plentiful,
1368 result in substantially faster clones where I/O throughput is plentiful,
1369 especially for larger repositories. A side-effect of --stream clones is
1369 especially for larger repositories. A side-effect of --stream clones is
1370 that storage settings and requirements on the remote are applied locally:
1370 that storage settings and requirements on the remote are applied locally:
1371 a modern client may inherit legacy or inefficient storage used by the
1371 a modern client may inherit legacy or inefficient storage used by the
1372 remote or a legacy Mercurial client may not be able to clone from a
1372 remote or a legacy Mercurial client may not be able to clone from a
1373 modern Mercurial remote.
1373 modern Mercurial remote.
1374
1374
1375 .. note::
1375 .. note::
1376
1376
1377 Specifying a tag will include the tagged changeset but not the
1377 Specifying a tag will include the tagged changeset but not the
1378 changeset containing the tag.
1378 changeset containing the tag.
1379
1379
1380 .. container:: verbose
1380 .. container:: verbose
1381
1381
1382 For efficiency, hardlinks are used for cloning whenever the
1382 For efficiency, hardlinks are used for cloning whenever the
1383 source and destination are on the same filesystem (note this
1383 source and destination are on the same filesystem (note this
1384 applies only to the repository data, not to the working
1384 applies only to the repository data, not to the working
1385 directory). Some filesystems, such as AFS, implement hardlinking
1385 directory). Some filesystems, such as AFS, implement hardlinking
1386 incorrectly, but do not report errors. In these cases, use the
1386 incorrectly, but do not report errors. In these cases, use the
1387 --pull option to avoid hardlinking.
1387 --pull option to avoid hardlinking.
1388
1388
1389 Mercurial will update the working directory to the first applicable
1389 Mercurial will update the working directory to the first applicable
1390 revision from this list:
1390 revision from this list:
1391
1391
1392 a) null if -U or the source repository has no changesets
1392 a) null if -U or the source repository has no changesets
1393 b) if -u . and the source repository is local, the first parent of
1393 b) if -u . and the source repository is local, the first parent of
1394 the source repository's working directory
1394 the source repository's working directory
1395 c) the changeset specified with -u (if a branch name, this means the
1395 c) the changeset specified with -u (if a branch name, this means the
1396 latest head of that branch)
1396 latest head of that branch)
1397 d) the changeset specified with -r
1397 d) the changeset specified with -r
1398 e) the tipmost head specified with -b
1398 e) the tipmost head specified with -b
1399 f) the tipmost head specified with the url#branch source syntax
1399 f) the tipmost head specified with the url#branch source syntax
1400 g) the revision marked with the '@' bookmark, if present
1400 g) the revision marked with the '@' bookmark, if present
1401 h) the tipmost head of the default branch
1401 h) the tipmost head of the default branch
1402 i) tip
1402 i) tip
1403
1403
1404 When cloning from servers that support it, Mercurial may fetch
1404 When cloning from servers that support it, Mercurial may fetch
1405 pre-generated data from a server-advertised URL or inline from the
1405 pre-generated data from a server-advertised URL or inline from the
1406 same stream. When this is done, hooks operating on incoming changesets
1406 same stream. When this is done, hooks operating on incoming changesets
1407 and changegroups may fire more than once, once for each pre-generated
1407 and changegroups may fire more than once, once for each pre-generated
1408 bundle and as well as for any additional remaining data. In addition,
1408 bundle and as well as for any additional remaining data. In addition,
1409 if an error occurs, the repository may be rolled back to a partial
1409 if an error occurs, the repository may be rolled back to a partial
1410 clone. This behavior may change in future releases.
1410 clone. This behavior may change in future releases.
1411 See :hg:`help -e clonebundles` for more.
1411 See :hg:`help -e clonebundles` for more.
1412
1412
1413 Examples:
1413 Examples:
1414
1414
1415 - clone a remote repository to a new directory named hg/::
1415 - clone a remote repository to a new directory named hg/::
1416
1416
1417 hg clone https://www.mercurial-scm.org/repo/hg/
1417 hg clone https://www.mercurial-scm.org/repo/hg/
1418
1418
1419 - create a lightweight local clone::
1419 - create a lightweight local clone::
1420
1420
1421 hg clone project/ project-feature/
1421 hg clone project/ project-feature/
1422
1422
1423 - clone from an absolute path on an ssh server (note double-slash)::
1423 - clone from an absolute path on an ssh server (note double-slash)::
1424
1424
1425 hg clone ssh://user@server//home/projects/alpha/
1425 hg clone ssh://user@server//home/projects/alpha/
1426
1426
1427 - do a streaming clone while checking out a specified version::
1427 - do a streaming clone while checking out a specified version::
1428
1428
1429 hg clone --stream http://server/repo -u 1.5
1429 hg clone --stream http://server/repo -u 1.5
1430
1430
1431 - create a repository without changesets after a particular revision::
1431 - create a repository without changesets after a particular revision::
1432
1432
1433 hg clone -r 04e544 experimental/ good/
1433 hg clone -r 04e544 experimental/ good/
1434
1434
1435 - clone (and track) a particular named branch::
1435 - clone (and track) a particular named branch::
1436
1436
1437 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1437 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1438
1438
1439 See :hg:`help urls` for details on specifying URLs.
1439 See :hg:`help urls` for details on specifying URLs.
1440
1440
1441 Returns 0 on success.
1441 Returns 0 on success.
1442 """
1442 """
1443 opts = pycompat.byteskwargs(opts)
1443 opts = pycompat.byteskwargs(opts)
1444 if opts.get('noupdate') and opts.get('updaterev'):
1444 if opts.get('noupdate') and opts.get('updaterev'):
1445 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1445 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1446
1446
1447 r = hg.clone(ui, opts, source, dest,
1447 r = hg.clone(ui, opts, source, dest,
1448 pull=opts.get('pull'),
1448 pull=opts.get('pull'),
1449 stream=opts.get('stream') or opts.get('uncompressed'),
1449 stream=opts.get('stream') or opts.get('uncompressed'),
1450 revs=opts.get('rev'),
1450 revs=opts.get('rev'),
1451 update=opts.get('updaterev') or not opts.get('noupdate'),
1451 update=opts.get('updaterev') or not opts.get('noupdate'),
1452 branch=opts.get('branch'),
1452 branch=opts.get('branch'),
1453 shareopts=opts.get('shareopts'))
1453 shareopts=opts.get('shareopts'))
1454
1454
1455 return r is None
1455 return r is None
1456
1456
1457 @command('^commit|ci',
1457 @command('^commit|ci',
1458 [('A', 'addremove', None,
1458 [('A', 'addremove', None,
1459 _('mark new/missing files as added/removed before committing')),
1459 _('mark new/missing files as added/removed before committing')),
1460 ('', 'close-branch', None,
1460 ('', 'close-branch', None,
1461 _('mark a branch head as closed')),
1461 _('mark a branch head as closed')),
1462 ('', 'amend', None, _('amend the parent of the working directory')),
1462 ('', 'amend', None, _('amend the parent of the working directory')),
1463 ('s', 'secret', None, _('use the secret phase for committing')),
1463 ('s', 'secret', None, _('use the secret phase for committing')),
1464 ('e', 'edit', None, _('invoke editor on commit messages')),
1464 ('e', 'edit', None, _('invoke editor on commit messages')),
1465 ('i', 'interactive', None, _('use interactive mode')),
1465 ('i', 'interactive', None, _('use interactive mode')),
1466 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1466 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1467 _('[OPTION]... [FILE]...'),
1467 _('[OPTION]... [FILE]...'),
1468 inferrepo=True)
1468 inferrepo=True)
1469 def commit(ui, repo, *pats, **opts):
1469 def commit(ui, repo, *pats, **opts):
1470 """commit the specified files or all outstanding changes
1470 """commit the specified files or all outstanding changes
1471
1471
1472 Commit changes to the given files into the repository. Unlike a
1472 Commit changes to the given files into the repository. Unlike a
1473 centralized SCM, this operation is a local operation. See
1473 centralized SCM, this operation is a local operation. See
1474 :hg:`push` for a way to actively distribute your changes.
1474 :hg:`push` for a way to actively distribute your changes.
1475
1475
1476 If a list of files is omitted, all changes reported by :hg:`status`
1476 If a list of files is omitted, all changes reported by :hg:`status`
1477 will be committed.
1477 will be committed.
1478
1478
1479 If you are committing the result of a merge, do not provide any
1479 If you are committing the result of a merge, do not provide any
1480 filenames or -I/-X filters.
1480 filenames or -I/-X filters.
1481
1481
1482 If no commit message is specified, Mercurial starts your
1482 If no commit message is specified, Mercurial starts your
1483 configured editor where you can enter a message. In case your
1483 configured editor where you can enter a message. In case your
1484 commit fails, you will find a backup of your message in
1484 commit fails, you will find a backup of your message in
1485 ``.hg/last-message.txt``.
1485 ``.hg/last-message.txt``.
1486
1486
1487 The --close-branch flag can be used to mark the current branch
1487 The --close-branch flag can be used to mark the current branch
1488 head closed. When all heads of a branch are closed, the branch
1488 head closed. When all heads of a branch are closed, the branch
1489 will be considered closed and no longer listed.
1489 will be considered closed and no longer listed.
1490
1490
1491 The --amend flag can be used to amend the parent of the
1491 The --amend flag can be used to amend the parent of the
1492 working directory with a new commit that contains the changes
1492 working directory with a new commit that contains the changes
1493 in the parent in addition to those currently reported by :hg:`status`,
1493 in the parent in addition to those currently reported by :hg:`status`,
1494 if there are any. The old commit is stored in a backup bundle in
1494 if there are any. The old commit is stored in a backup bundle in
1495 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1495 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1496 on how to restore it).
1496 on how to restore it).
1497
1497
1498 Message, user and date are taken from the amended commit unless
1498 Message, user and date are taken from the amended commit unless
1499 specified. When a message isn't specified on the command line,
1499 specified. When a message isn't specified on the command line,
1500 the editor will open with the message of the amended commit.
1500 the editor will open with the message of the amended commit.
1501
1501
1502 It is not possible to amend public changesets (see :hg:`help phases`)
1502 It is not possible to amend public changesets (see :hg:`help phases`)
1503 or changesets that have children.
1503 or changesets that have children.
1504
1504
1505 See :hg:`help dates` for a list of formats valid for -d/--date.
1505 See :hg:`help dates` for a list of formats valid for -d/--date.
1506
1506
1507 Returns 0 on success, 1 if nothing changed.
1507 Returns 0 on success, 1 if nothing changed.
1508
1508
1509 .. container:: verbose
1509 .. container:: verbose
1510
1510
1511 Examples:
1511 Examples:
1512
1512
1513 - commit all files ending in .py::
1513 - commit all files ending in .py::
1514
1514
1515 hg commit --include "set:**.py"
1515 hg commit --include "set:**.py"
1516
1516
1517 - commit all non-binary files::
1517 - commit all non-binary files::
1518
1518
1519 hg commit --exclude "set:binary()"
1519 hg commit --exclude "set:binary()"
1520
1520
1521 - amend the current commit and set the date to now::
1521 - amend the current commit and set the date to now::
1522
1522
1523 hg commit --amend --date now
1523 hg commit --amend --date now
1524 """
1524 """
1525 with repo.wlock(), repo.lock():
1525 with repo.wlock(), repo.lock():
1526 return _docommit(ui, repo, *pats, **opts)
1526 return _docommit(ui, repo, *pats, **opts)
1527
1527
1528 def _docommit(ui, repo, *pats, **opts):
1528 def _docommit(ui, repo, *pats, **opts):
1529 if opts.get(r'interactive'):
1529 if opts.get(r'interactive'):
1530 opts.pop(r'interactive')
1530 opts.pop(r'interactive')
1531 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1531 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1532 cmdutil.recordfilter, *pats,
1532 cmdutil.recordfilter, *pats,
1533 **opts)
1533 **opts)
1534 # ret can be 0 (no changes to record) or the value returned by
1534 # ret can be 0 (no changes to record) or the value returned by
1535 # commit(), 1 if nothing changed or None on success.
1535 # commit(), 1 if nothing changed or None on success.
1536 return 1 if ret == 0 else ret
1536 return 1 if ret == 0 else ret
1537
1537
1538 opts = pycompat.byteskwargs(opts)
1538 opts = pycompat.byteskwargs(opts)
1539 if opts.get('subrepos'):
1539 if opts.get('subrepos'):
1540 if opts.get('amend'):
1540 if opts.get('amend'):
1541 raise error.Abort(_('cannot amend with --subrepos'))
1541 raise error.Abort(_('cannot amend with --subrepos'))
1542 # Let --subrepos on the command line override config setting.
1542 # Let --subrepos on the command line override config setting.
1543 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1543 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1544
1544
1545 cmdutil.checkunfinished(repo, commit=True)
1545 cmdutil.checkunfinished(repo, commit=True)
1546
1546
1547 branch = repo[None].branch()
1547 branch = repo[None].branch()
1548 bheads = repo.branchheads(branch)
1548 bheads = repo.branchheads(branch)
1549
1549
1550 extra = {}
1550 extra = {}
1551 if opts.get('close_branch'):
1551 if opts.get('close_branch'):
1552 extra['close'] = '1'
1552 extra['close'] = '1'
1553
1553
1554 if not bheads:
1554 if not bheads:
1555 raise error.Abort(_('can only close branch heads'))
1555 raise error.Abort(_('can only close branch heads'))
1556 elif opts.get('amend'):
1556 elif opts.get('amend'):
1557 if repo[None].parents()[0].p1().branch() != branch and \
1557 if repo[None].parents()[0].p1().branch() != branch and \
1558 repo[None].parents()[0].p2().branch() != branch:
1558 repo[None].parents()[0].p2().branch() != branch:
1559 raise error.Abort(_('can only close branch heads'))
1559 raise error.Abort(_('can only close branch heads'))
1560
1560
1561 if opts.get('amend'):
1561 if opts.get('amend'):
1562 if ui.configbool('ui', 'commitsubrepos'):
1562 if ui.configbool('ui', 'commitsubrepos'):
1563 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1563 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1564
1564
1565 old = repo['.']
1565 old = repo['.']
1566 rewriteutil.precheck(repo, [old.rev()], 'amend')
1566 rewriteutil.precheck(repo, [old.rev()], 'amend')
1567
1567
1568 # Currently histedit gets confused if an amend happens while histedit
1568 # Currently histedit gets confused if an amend happens while histedit
1569 # is in progress. Since we have a checkunfinished command, we are
1569 # is in progress. Since we have a checkunfinished command, we are
1570 # temporarily honoring it.
1570 # temporarily honoring it.
1571 #
1571 #
1572 # Note: eventually this guard will be removed. Please do not expect
1572 # Note: eventually this guard will be removed. Please do not expect
1573 # this behavior to remain.
1573 # this behavior to remain.
1574 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1574 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1575 cmdutil.checkunfinished(repo)
1575 cmdutil.checkunfinished(repo)
1576
1576
1577 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1577 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1578 if node == old.node():
1578 if node == old.node():
1579 ui.status(_("nothing changed\n"))
1579 ui.status(_("nothing changed\n"))
1580 return 1
1580 return 1
1581 else:
1581 else:
1582 def commitfunc(ui, repo, message, match, opts):
1582 def commitfunc(ui, repo, message, match, opts):
1583 overrides = {}
1583 overrides = {}
1584 if opts.get('secret'):
1584 if opts.get('secret'):
1585 overrides[('phases', 'new-commit')] = 'secret'
1585 overrides[('phases', 'new-commit')] = 'secret'
1586
1586
1587 baseui = repo.baseui
1587 baseui = repo.baseui
1588 with baseui.configoverride(overrides, 'commit'):
1588 with baseui.configoverride(overrides, 'commit'):
1589 with ui.configoverride(overrides, 'commit'):
1589 with ui.configoverride(overrides, 'commit'):
1590 editform = cmdutil.mergeeditform(repo[None],
1590 editform = cmdutil.mergeeditform(repo[None],
1591 'commit.normal')
1591 'commit.normal')
1592 editor = cmdutil.getcommiteditor(
1592 editor = cmdutil.getcommiteditor(
1593 editform=editform, **pycompat.strkwargs(opts))
1593 editform=editform, **pycompat.strkwargs(opts))
1594 return repo.commit(message,
1594 return repo.commit(message,
1595 opts.get('user'),
1595 opts.get('user'),
1596 opts.get('date'),
1596 opts.get('date'),
1597 match,
1597 match,
1598 editor=editor,
1598 editor=editor,
1599 extra=extra)
1599 extra=extra)
1600
1600
1601 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1601 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1602
1602
1603 if not node:
1603 if not node:
1604 stat = cmdutil.postcommitstatus(repo, pats, opts)
1604 stat = cmdutil.postcommitstatus(repo, pats, opts)
1605 if stat[3]:
1605 if stat[3]:
1606 ui.status(_("nothing changed (%d missing files, see "
1606 ui.status(_("nothing changed (%d missing files, see "
1607 "'hg status')\n") % len(stat[3]))
1607 "'hg status')\n") % len(stat[3]))
1608 else:
1608 else:
1609 ui.status(_("nothing changed\n"))
1609 ui.status(_("nothing changed\n"))
1610 return 1
1610 return 1
1611
1611
1612 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1612 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1613
1613
1614 @command('config|showconfig|debugconfig',
1614 @command('config|showconfig|debugconfig',
1615 [('u', 'untrusted', None, _('show untrusted configuration options')),
1615 [('u', 'untrusted', None, _('show untrusted configuration options')),
1616 ('e', 'edit', None, _('edit user config')),
1616 ('e', 'edit', None, _('edit user config')),
1617 ('l', 'local', None, _('edit repository config')),
1617 ('l', 'local', None, _('edit repository config')),
1618 ('g', 'global', None, _('edit global config'))] + formatteropts,
1618 ('g', 'global', None, _('edit global config'))] + formatteropts,
1619 _('[-u] [NAME]...'),
1619 _('[-u] [NAME]...'),
1620 optionalrepo=True,
1620 optionalrepo=True,
1621 intents={INTENT_READONLY})
1621 intents={INTENT_READONLY})
1622 def config(ui, repo, *values, **opts):
1622 def config(ui, repo, *values, **opts):
1623 """show combined config settings from all hgrc files
1623 """show combined config settings from all hgrc files
1624
1624
1625 With no arguments, print names and values of all config items.
1625 With no arguments, print names and values of all config items.
1626
1626
1627 With one argument of the form section.name, print just the value
1627 With one argument of the form section.name, print just the value
1628 of that config item.
1628 of that config item.
1629
1629
1630 With multiple arguments, print names and values of all config
1630 With multiple arguments, print names and values of all config
1631 items with matching section names or section.names.
1631 items with matching section names or section.names.
1632
1632
1633 With --edit, start an editor on the user-level config file. With
1633 With --edit, start an editor on the user-level config file. With
1634 --global, edit the system-wide config file. With --local, edit the
1634 --global, edit the system-wide config file. With --local, edit the
1635 repository-level config file.
1635 repository-level config file.
1636
1636
1637 With --debug, the source (filename and line number) is printed
1637 With --debug, the source (filename and line number) is printed
1638 for each config item.
1638 for each config item.
1639
1639
1640 See :hg:`help config` for more information about config files.
1640 See :hg:`help config` for more information about config files.
1641
1641
1642 Returns 0 on success, 1 if NAME does not exist.
1642 Returns 0 on success, 1 if NAME does not exist.
1643
1643
1644 """
1644 """
1645
1645
1646 opts = pycompat.byteskwargs(opts)
1646 opts = pycompat.byteskwargs(opts)
1647 if opts.get('edit') or opts.get('local') or opts.get('global'):
1647 if opts.get('edit') or opts.get('local') or opts.get('global'):
1648 if opts.get('local') and opts.get('global'):
1648 if opts.get('local') and opts.get('global'):
1649 raise error.Abort(_("can't use --local and --global together"))
1649 raise error.Abort(_("can't use --local and --global together"))
1650
1650
1651 if opts.get('local'):
1651 if opts.get('local'):
1652 if not repo:
1652 if not repo:
1653 raise error.Abort(_("can't use --local outside a repository"))
1653 raise error.Abort(_("can't use --local outside a repository"))
1654 paths = [repo.vfs.join('hgrc')]
1654 paths = [repo.vfs.join('hgrc')]
1655 elif opts.get('global'):
1655 elif opts.get('global'):
1656 paths = rcutil.systemrcpath()
1656 paths = rcutil.systemrcpath()
1657 else:
1657 else:
1658 paths = rcutil.userrcpath()
1658 paths = rcutil.userrcpath()
1659
1659
1660 for f in paths:
1660 for f in paths:
1661 if os.path.exists(f):
1661 if os.path.exists(f):
1662 break
1662 break
1663 else:
1663 else:
1664 if opts.get('global'):
1664 if opts.get('global'):
1665 samplehgrc = uimod.samplehgrcs['global']
1665 samplehgrc = uimod.samplehgrcs['global']
1666 elif opts.get('local'):
1666 elif opts.get('local'):
1667 samplehgrc = uimod.samplehgrcs['local']
1667 samplehgrc = uimod.samplehgrcs['local']
1668 else:
1668 else:
1669 samplehgrc = uimod.samplehgrcs['user']
1669 samplehgrc = uimod.samplehgrcs['user']
1670
1670
1671 f = paths[0]
1671 f = paths[0]
1672 fp = open(f, "wb")
1672 fp = open(f, "wb")
1673 fp.write(util.tonativeeol(samplehgrc))
1673 fp.write(util.tonativeeol(samplehgrc))
1674 fp.close()
1674 fp.close()
1675
1675
1676 editor = ui.geteditor()
1676 editor = ui.geteditor()
1677 ui.system("%s \"%s\"" % (editor, f),
1677 ui.system("%s \"%s\"" % (editor, f),
1678 onerr=error.Abort, errprefix=_("edit failed"),
1678 onerr=error.Abort, errprefix=_("edit failed"),
1679 blockedtag='config_edit')
1679 blockedtag='config_edit')
1680 return
1680 return
1681 ui.pager('config')
1681 ui.pager('config')
1682 fm = ui.formatter('config', opts)
1682 fm = ui.formatter('config', opts)
1683 for t, f in rcutil.rccomponents():
1683 for t, f in rcutil.rccomponents():
1684 if t == 'path':
1684 if t == 'path':
1685 ui.debug('read config from: %s\n' % f)
1685 ui.debug('read config from: %s\n' % f)
1686 elif t == 'items':
1686 elif t == 'items':
1687 for section, name, value, source in f:
1687 for section, name, value, source in f:
1688 ui.debug('set config by: %s\n' % source)
1688 ui.debug('set config by: %s\n' % source)
1689 else:
1689 else:
1690 raise error.ProgrammingError('unknown rctype: %s' % t)
1690 raise error.ProgrammingError('unknown rctype: %s' % t)
1691 untrusted = bool(opts.get('untrusted'))
1691 untrusted = bool(opts.get('untrusted'))
1692
1692
1693 selsections = selentries = []
1693 selsections = selentries = []
1694 if values:
1694 if values:
1695 selsections = [v for v in values if '.' not in v]
1695 selsections = [v for v in values if '.' not in v]
1696 selentries = [v for v in values if '.' in v]
1696 selentries = [v for v in values if '.' in v]
1697 uniquesel = (len(selentries) == 1 and not selsections)
1697 uniquesel = (len(selentries) == 1 and not selsections)
1698 selsections = set(selsections)
1698 selsections = set(selsections)
1699 selentries = set(selentries)
1699 selentries = set(selentries)
1700
1700
1701 matched = False
1701 matched = False
1702 for section, name, value in ui.walkconfig(untrusted=untrusted):
1702 for section, name, value in ui.walkconfig(untrusted=untrusted):
1703 source = ui.configsource(section, name, untrusted)
1703 source = ui.configsource(section, name, untrusted)
1704 value = pycompat.bytestr(value)
1704 value = pycompat.bytestr(value)
1705 if fm.isplain():
1705 if fm.isplain():
1706 source = source or 'none'
1706 source = source or 'none'
1707 value = value.replace('\n', '\\n')
1707 value = value.replace('\n', '\\n')
1708 entryname = section + '.' + name
1708 entryname = section + '.' + name
1709 if values and not (section in selsections or entryname in selentries):
1709 if values and not (section in selsections or entryname in selentries):
1710 continue
1710 continue
1711 fm.startitem()
1711 fm.startitem()
1712 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1712 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1713 if uniquesel:
1713 if uniquesel:
1714 fm.data(name=entryname)
1714 fm.data(name=entryname)
1715 fm.write('value', '%s\n', value)
1715 fm.write('value', '%s\n', value)
1716 else:
1716 else:
1717 fm.write('name value', '%s=%s\n', entryname, value)
1717 fm.write('name value', '%s=%s\n', entryname, value)
1718 matched = True
1718 matched = True
1719 fm.end()
1719 fm.end()
1720 if matched:
1720 if matched:
1721 return 0
1721 return 0
1722 return 1
1722 return 1
1723
1723
1724 @command('copy|cp',
1724 @command('copy|cp',
1725 [('A', 'after', None, _('record a copy that has already occurred')),
1725 [('A', 'after', None, _('record a copy that has already occurred')),
1726 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1726 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1727 ] + walkopts + dryrunopts,
1727 ] + walkopts + dryrunopts,
1728 _('[OPTION]... [SOURCE]... DEST'))
1728 _('[OPTION]... [SOURCE]... DEST'))
1729 def copy(ui, repo, *pats, **opts):
1729 def copy(ui, repo, *pats, **opts):
1730 """mark files as copied for the next commit
1730 """mark files as copied for the next commit
1731
1731
1732 Mark dest as having copies of source files. If dest is a
1732 Mark dest as having copies of source files. If dest is a
1733 directory, copies are put in that directory. If dest is a file,
1733 directory, copies are put in that directory. If dest is a file,
1734 the source must be a single file.
1734 the source must be a single file.
1735
1735
1736 By default, this command copies the contents of files as they
1736 By default, this command copies the contents of files as they
1737 exist in the working directory. If invoked with -A/--after, the
1737 exist in the working directory. If invoked with -A/--after, the
1738 operation is recorded, but no copying is performed.
1738 operation is recorded, but no copying is performed.
1739
1739
1740 This command takes effect with the next commit. To undo a copy
1740 This command takes effect with the next commit. To undo a copy
1741 before that, see :hg:`revert`.
1741 before that, see :hg:`revert`.
1742
1742
1743 Returns 0 on success, 1 if errors are encountered.
1743 Returns 0 on success, 1 if errors are encountered.
1744 """
1744 """
1745 opts = pycompat.byteskwargs(opts)
1745 opts = pycompat.byteskwargs(opts)
1746 with repo.wlock(False):
1746 with repo.wlock(False):
1747 return cmdutil.copy(ui, repo, pats, opts)
1747 return cmdutil.copy(ui, repo, pats, opts)
1748
1748
1749 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1749 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1750 def debugcommands(ui, cmd='', *args):
1750 def debugcommands(ui, cmd='', *args):
1751 """list all available commands and options"""
1751 """list all available commands and options"""
1752 for cmd, vals in sorted(table.iteritems()):
1752 for cmd, vals in sorted(table.iteritems()):
1753 cmd = cmd.split('|')[0].strip('^')
1753 cmd = cmd.split('|')[0].strip('^')
1754 opts = ', '.join([i[1] for i in vals[1]])
1754 opts = ', '.join([i[1] for i in vals[1]])
1755 ui.write('%s: %s\n' % (cmd, opts))
1755 ui.write('%s: %s\n' % (cmd, opts))
1756
1756
1757 @command('debugcomplete',
1757 @command('debugcomplete',
1758 [('o', 'options', None, _('show the command options'))],
1758 [('o', 'options', None, _('show the command options'))],
1759 _('[-o] CMD'),
1759 _('[-o] CMD'),
1760 norepo=True)
1760 norepo=True)
1761 def debugcomplete(ui, cmd='', **opts):
1761 def debugcomplete(ui, cmd='', **opts):
1762 """returns the completion list associated with the given command"""
1762 """returns the completion list associated with the given command"""
1763
1763
1764 if opts.get(r'options'):
1764 if opts.get(r'options'):
1765 options = []
1765 options = []
1766 otables = [globalopts]
1766 otables = [globalopts]
1767 if cmd:
1767 if cmd:
1768 aliases, entry = cmdutil.findcmd(cmd, table, False)
1768 aliases, entry = cmdutil.findcmd(cmd, table, False)
1769 otables.append(entry[1])
1769 otables.append(entry[1])
1770 for t in otables:
1770 for t in otables:
1771 for o in t:
1771 for o in t:
1772 if "(DEPRECATED)" in o[3]:
1772 if "(DEPRECATED)" in o[3]:
1773 continue
1773 continue
1774 if o[0]:
1774 if o[0]:
1775 options.append('-%s' % o[0])
1775 options.append('-%s' % o[0])
1776 options.append('--%s' % o[1])
1776 options.append('--%s' % o[1])
1777 ui.write("%s\n" % "\n".join(options))
1777 ui.write("%s\n" % "\n".join(options))
1778 return
1778 return
1779
1779
1780 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1780 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1781 if ui.verbose:
1781 if ui.verbose:
1782 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1782 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1783 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1783 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1784
1784
1785 @command('^diff',
1785 @command('^diff',
1786 [('r', 'rev', [], _('revision'), _('REV')),
1786 [('r', 'rev', [], _('revision'), _('REV')),
1787 ('c', 'change', '', _('change made by revision'), _('REV'))
1787 ('c', 'change', '', _('change made by revision'), _('REV'))
1788 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1788 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1789 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1789 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1790 inferrepo=True,
1790 inferrepo=True,
1791 intents={INTENT_READONLY})
1791 intents={INTENT_READONLY})
1792 def diff(ui, repo, *pats, **opts):
1792 def diff(ui, repo, *pats, **opts):
1793 """diff repository (or selected files)
1793 """diff repository (or selected files)
1794
1794
1795 Show differences between revisions for the specified files.
1795 Show differences between revisions for the specified files.
1796
1796
1797 Differences between files are shown using the unified diff format.
1797 Differences between files are shown using the unified diff format.
1798
1798
1799 .. note::
1799 .. note::
1800
1800
1801 :hg:`diff` may generate unexpected results for merges, as it will
1801 :hg:`diff` may generate unexpected results for merges, as it will
1802 default to comparing against the working directory's first
1802 default to comparing against the working directory's first
1803 parent changeset if no revisions are specified.
1803 parent changeset if no revisions are specified.
1804
1804
1805 When two revision arguments are given, then changes are shown
1805 When two revision arguments are given, then changes are shown
1806 between those revisions. If only one revision is specified then
1806 between those revisions. If only one revision is specified then
1807 that revision is compared to the working directory, and, when no
1807 that revision is compared to the working directory, and, when no
1808 revisions are specified, the working directory files are compared
1808 revisions are specified, the working directory files are compared
1809 to its first parent.
1809 to its first parent.
1810
1810
1811 Alternatively you can specify -c/--change with a revision to see
1811 Alternatively you can specify -c/--change with a revision to see
1812 the changes in that changeset relative to its first parent.
1812 the changes in that changeset relative to its first parent.
1813
1813
1814 Without the -a/--text option, diff will avoid generating diffs of
1814 Without the -a/--text option, diff will avoid generating diffs of
1815 files it detects as binary. With -a, diff will generate a diff
1815 files it detects as binary. With -a, diff will generate a diff
1816 anyway, probably with undesirable results.
1816 anyway, probably with undesirable results.
1817
1817
1818 Use the -g/--git option to generate diffs in the git extended diff
1818 Use the -g/--git option to generate diffs in the git extended diff
1819 format. For more information, read :hg:`help diffs`.
1819 format. For more information, read :hg:`help diffs`.
1820
1820
1821 .. container:: verbose
1821 .. container:: verbose
1822
1822
1823 Examples:
1823 Examples:
1824
1824
1825 - compare a file in the current working directory to its parent::
1825 - compare a file in the current working directory to its parent::
1826
1826
1827 hg diff foo.c
1827 hg diff foo.c
1828
1828
1829 - compare two historical versions of a directory, with rename info::
1829 - compare two historical versions of a directory, with rename info::
1830
1830
1831 hg diff --git -r 1.0:1.2 lib/
1831 hg diff --git -r 1.0:1.2 lib/
1832
1832
1833 - get change stats relative to the last change on some date::
1833 - get change stats relative to the last change on some date::
1834
1834
1835 hg diff --stat -r "date('may 2')"
1835 hg diff --stat -r "date('may 2')"
1836
1836
1837 - diff all newly-added files that contain a keyword::
1837 - diff all newly-added files that contain a keyword::
1838
1838
1839 hg diff "set:added() and grep(GNU)"
1839 hg diff "set:added() and grep(GNU)"
1840
1840
1841 - compare a revision and its parents::
1841 - compare a revision and its parents::
1842
1842
1843 hg diff -c 9353 # compare against first parent
1843 hg diff -c 9353 # compare against first parent
1844 hg diff -r 9353^:9353 # same using revset syntax
1844 hg diff -r 9353^:9353 # same using revset syntax
1845 hg diff -r 9353^2:9353 # compare against the second parent
1845 hg diff -r 9353^2:9353 # compare against the second parent
1846
1846
1847 Returns 0 on success.
1847 Returns 0 on success.
1848 """
1848 """
1849
1849
1850 opts = pycompat.byteskwargs(opts)
1850 opts = pycompat.byteskwargs(opts)
1851 revs = opts.get('rev')
1851 revs = opts.get('rev')
1852 change = opts.get('change')
1852 change = opts.get('change')
1853 stat = opts.get('stat')
1853 stat = opts.get('stat')
1854 reverse = opts.get('reverse')
1854 reverse = opts.get('reverse')
1855
1855
1856 if revs and change:
1856 if revs and change:
1857 msg = _('cannot specify --rev and --change at the same time')
1857 msg = _('cannot specify --rev and --change at the same time')
1858 raise error.Abort(msg)
1858 raise error.Abort(msg)
1859 elif change:
1859 elif change:
1860 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
1860 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
1861 ctx2 = scmutil.revsingle(repo, change, None)
1861 ctx2 = scmutil.revsingle(repo, change, None)
1862 ctx1 = ctx2.p1()
1862 ctx1 = ctx2.p1()
1863 else:
1863 else:
1864 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
1864 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
1865 ctx1, ctx2 = scmutil.revpair(repo, revs)
1865 ctx1, ctx2 = scmutil.revpair(repo, revs)
1866 node1, node2 = ctx1.node(), ctx2.node()
1866 node1, node2 = ctx1.node(), ctx2.node()
1867
1867
1868 if reverse:
1868 if reverse:
1869 node1, node2 = node2, node1
1869 node1, node2 = node2, node1
1870
1870
1871 diffopts = patch.diffallopts(ui, opts)
1871 diffopts = patch.diffallopts(ui, opts)
1872 m = scmutil.match(ctx2, pats, opts)
1872 m = scmutil.match(ctx2, pats, opts)
1873 ui.pager('diff')
1873 ui.pager('diff')
1874 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1874 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1875 listsubrepos=opts.get('subrepos'),
1875 listsubrepos=opts.get('subrepos'),
1876 root=opts.get('root'))
1876 root=opts.get('root'))
1877
1877
1878 @command('^export',
1878 @command('^export',
1879 [('B', 'bookmark', '',
1879 [('B', 'bookmark', '',
1880 _('export changes only reachable by given bookmark')),
1880 _('export changes only reachable by given bookmark')),
1881 ('o', 'output', '',
1881 ('o', 'output', '',
1882 _('print output to file with formatted name'), _('FORMAT')),
1882 _('print output to file with formatted name'), _('FORMAT')),
1883 ('', 'switch-parent', None, _('diff against the second parent')),
1883 ('', 'switch-parent', None, _('diff against the second parent')),
1884 ('r', 'rev', [], _('revisions to export'), _('REV')),
1884 ('r', 'rev', [], _('revisions to export'), _('REV')),
1885 ] + diffopts + formatteropts,
1885 ] + diffopts + formatteropts,
1886 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
1886 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
1887 intents={INTENT_READONLY})
1887 intents={INTENT_READONLY})
1888 def export(ui, repo, *changesets, **opts):
1888 def export(ui, repo, *changesets, **opts):
1889 """dump the header and diffs for one or more changesets
1889 """dump the header and diffs for one or more changesets
1890
1890
1891 Print the changeset header and diffs for one or more revisions.
1891 Print the changeset header and diffs for one or more revisions.
1892 If no revision is given, the parent of the working directory is used.
1892 If no revision is given, the parent of the working directory is used.
1893
1893
1894 The information shown in the changeset header is: author, date,
1894 The information shown in the changeset header is: author, date,
1895 branch name (if non-default), changeset hash, parent(s) and commit
1895 branch name (if non-default), changeset hash, parent(s) and commit
1896 comment.
1896 comment.
1897
1897
1898 .. note::
1898 .. note::
1899
1899
1900 :hg:`export` may generate unexpected diff output for merge
1900 :hg:`export` may generate unexpected diff output for merge
1901 changesets, as it will compare the merge changeset against its
1901 changesets, as it will compare the merge changeset against its
1902 first parent only.
1902 first parent only.
1903
1903
1904 Output may be to a file, in which case the name of the file is
1904 Output may be to a file, in which case the name of the file is
1905 given using a template string. See :hg:`help templates`. In addition
1905 given using a template string. See :hg:`help templates`. In addition
1906 to the common template keywords, the following formatting rules are
1906 to the common template keywords, the following formatting rules are
1907 supported:
1907 supported:
1908
1908
1909 :``%%``: literal "%" character
1909 :``%%``: literal "%" character
1910 :``%H``: changeset hash (40 hexadecimal digits)
1910 :``%H``: changeset hash (40 hexadecimal digits)
1911 :``%N``: number of patches being generated
1911 :``%N``: number of patches being generated
1912 :``%R``: changeset revision number
1912 :``%R``: changeset revision number
1913 :``%b``: basename of the exporting repository
1913 :``%b``: basename of the exporting repository
1914 :``%h``: short-form changeset hash (12 hexadecimal digits)
1914 :``%h``: short-form changeset hash (12 hexadecimal digits)
1915 :``%m``: first line of the commit message (only alphanumeric characters)
1915 :``%m``: first line of the commit message (only alphanumeric characters)
1916 :``%n``: zero-padded sequence number, starting at 1
1916 :``%n``: zero-padded sequence number, starting at 1
1917 :``%r``: zero-padded changeset revision number
1917 :``%r``: zero-padded changeset revision number
1918 :``\\``: literal "\\" character
1918 :``\\``: literal "\\" character
1919
1919
1920 Without the -a/--text option, export will avoid generating diffs
1920 Without the -a/--text option, export will avoid generating diffs
1921 of files it detects as binary. With -a, export will generate a
1921 of files it detects as binary. With -a, export will generate a
1922 diff anyway, probably with undesirable results.
1922 diff anyway, probably with undesirable results.
1923
1923
1924 With -B/--bookmark changesets reachable by the given bookmark are
1924 With -B/--bookmark changesets reachable by the given bookmark are
1925 selected.
1925 selected.
1926
1926
1927 Use the -g/--git option to generate diffs in the git extended diff
1927 Use the -g/--git option to generate diffs in the git extended diff
1928 format. See :hg:`help diffs` for more information.
1928 format. See :hg:`help diffs` for more information.
1929
1929
1930 With the --switch-parent option, the diff will be against the
1930 With the --switch-parent option, the diff will be against the
1931 second parent. It can be useful to review a merge.
1931 second parent. It can be useful to review a merge.
1932
1932
1933 .. container:: verbose
1933 .. container:: verbose
1934
1934
1935 Examples:
1935 Examples:
1936
1936
1937 - use export and import to transplant a bugfix to the current
1937 - use export and import to transplant a bugfix to the current
1938 branch::
1938 branch::
1939
1939
1940 hg export -r 9353 | hg import -
1940 hg export -r 9353 | hg import -
1941
1941
1942 - export all the changesets between two revisions to a file with
1942 - export all the changesets between two revisions to a file with
1943 rename information::
1943 rename information::
1944
1944
1945 hg export --git -r 123:150 > changes.txt
1945 hg export --git -r 123:150 > changes.txt
1946
1946
1947 - split outgoing changes into a series of patches with
1947 - split outgoing changes into a series of patches with
1948 descriptive names::
1948 descriptive names::
1949
1949
1950 hg export -r "outgoing()" -o "%n-%m.patch"
1950 hg export -r "outgoing()" -o "%n-%m.patch"
1951
1951
1952 Returns 0 on success.
1952 Returns 0 on success.
1953 """
1953 """
1954 opts = pycompat.byteskwargs(opts)
1954 opts = pycompat.byteskwargs(opts)
1955 bookmark = opts.get('bookmark')
1955 bookmark = opts.get('bookmark')
1956 changesets += tuple(opts.get('rev', []))
1956 changesets += tuple(opts.get('rev', []))
1957
1957
1958 if bookmark and changesets:
1958 if bookmark and changesets:
1959 raise error.Abort(_("-r and -B are mutually exclusive"))
1959 raise error.Abort(_("-r and -B are mutually exclusive"))
1960
1960
1961 if bookmark:
1961 if bookmark:
1962 if bookmark not in repo._bookmarks:
1962 if bookmark not in repo._bookmarks:
1963 raise error.Abort(_("bookmark '%s' not found") % bookmark)
1963 raise error.Abort(_("bookmark '%s' not found") % bookmark)
1964
1964
1965 revs = scmutil.bookmarkrevs(repo, bookmark)
1965 revs = scmutil.bookmarkrevs(repo, bookmark)
1966 else:
1966 else:
1967 if not changesets:
1967 if not changesets:
1968 changesets = ['.']
1968 changesets = ['.']
1969
1969
1970 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
1970 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
1971 revs = scmutil.revrange(repo, changesets)
1971 revs = scmutil.revrange(repo, changesets)
1972
1972
1973 if not revs:
1973 if not revs:
1974 raise error.Abort(_("export requires at least one changeset"))
1974 raise error.Abort(_("export requires at least one changeset"))
1975 if len(revs) > 1:
1975 if len(revs) > 1:
1976 ui.note(_('exporting patches:\n'))
1976 ui.note(_('exporting patches:\n'))
1977 else:
1977 else:
1978 ui.note(_('exporting patch:\n'))
1978 ui.note(_('exporting patch:\n'))
1979
1979
1980 fntemplate = opts.get('output')
1980 fntemplate = opts.get('output')
1981 if cmdutil.isstdiofilename(fntemplate):
1981 if cmdutil.isstdiofilename(fntemplate):
1982 fntemplate = ''
1982 fntemplate = ''
1983
1983
1984 if fntemplate:
1984 if fntemplate:
1985 fm = formatter.nullformatter(ui, 'export', opts)
1985 fm = formatter.nullformatter(ui, 'export', opts)
1986 else:
1986 else:
1987 ui.pager('export')
1987 ui.pager('export')
1988 fm = ui.formatter('export', opts)
1988 fm = ui.formatter('export', opts)
1989 with fm:
1989 with fm:
1990 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
1990 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
1991 switch_parent=opts.get('switch_parent'),
1991 switch_parent=opts.get('switch_parent'),
1992 opts=patch.diffallopts(ui, opts))
1992 opts=patch.diffallopts(ui, opts))
1993
1993
1994 @command('files',
1994 @command('files',
1995 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
1995 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
1996 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
1996 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
1997 ] + walkopts + formatteropts + subrepoopts,
1997 ] + walkopts + formatteropts + subrepoopts,
1998 _('[OPTION]... [FILE]...'),
1998 _('[OPTION]... [FILE]...'),
1999 intents={INTENT_READONLY})
1999 intents={INTENT_READONLY})
2000 def files(ui, repo, *pats, **opts):
2000 def files(ui, repo, *pats, **opts):
2001 """list tracked files
2001 """list tracked files
2002
2002
2003 Print files under Mercurial control in the working directory or
2003 Print files under Mercurial control in the working directory or
2004 specified revision for given files (excluding removed files).
2004 specified revision for given files (excluding removed files).
2005 Files can be specified as filenames or filesets.
2005 Files can be specified as filenames or filesets.
2006
2006
2007 If no files are given to match, this command prints the names
2007 If no files are given to match, this command prints the names
2008 of all files under Mercurial control.
2008 of all files under Mercurial control.
2009
2009
2010 .. container:: verbose
2010 .. container:: verbose
2011
2011
2012 Examples:
2012 Examples:
2013
2013
2014 - list all files under the current directory::
2014 - list all files under the current directory::
2015
2015
2016 hg files .
2016 hg files .
2017
2017
2018 - shows sizes and flags for current revision::
2018 - shows sizes and flags for current revision::
2019
2019
2020 hg files -vr .
2020 hg files -vr .
2021
2021
2022 - list all files named README::
2022 - list all files named README::
2023
2023
2024 hg files -I "**/README"
2024 hg files -I "**/README"
2025
2025
2026 - list all binary files::
2026 - list all binary files::
2027
2027
2028 hg files "set:binary()"
2028 hg files "set:binary()"
2029
2029
2030 - find files containing a regular expression::
2030 - find files containing a regular expression::
2031
2031
2032 hg files "set:grep('bob')"
2032 hg files "set:grep('bob')"
2033
2033
2034 - search tracked file contents with xargs and grep::
2034 - search tracked file contents with xargs and grep::
2035
2035
2036 hg files -0 | xargs -0 grep foo
2036 hg files -0 | xargs -0 grep foo
2037
2037
2038 See :hg:`help patterns` and :hg:`help filesets` for more information
2038 See :hg:`help patterns` and :hg:`help filesets` for more information
2039 on specifying file patterns.
2039 on specifying file patterns.
2040
2040
2041 Returns 0 if a match is found, 1 otherwise.
2041 Returns 0 if a match is found, 1 otherwise.
2042
2042
2043 """
2043 """
2044
2044
2045 opts = pycompat.byteskwargs(opts)
2045 opts = pycompat.byteskwargs(opts)
2046 rev = opts.get('rev')
2046 rev = opts.get('rev')
2047 if rev:
2047 if rev:
2048 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2048 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2049 ctx = scmutil.revsingle(repo, rev, None)
2049 ctx = scmutil.revsingle(repo, rev, None)
2050
2050
2051 end = '\n'
2051 end = '\n'
2052 if opts.get('print0'):
2052 if opts.get('print0'):
2053 end = '\0'
2053 end = '\0'
2054 fmt = '%s' + end
2054 fmt = '%s' + end
2055
2055
2056 m = scmutil.match(ctx, pats, opts)
2056 m = scmutil.match(ctx, pats, opts)
2057 ui.pager('files')
2057 ui.pager('files')
2058 with ui.formatter('files', opts) as fm:
2058 with ui.formatter('files', opts) as fm:
2059 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2059 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2060
2060
2061 @command(
2061 @command(
2062 '^forget',
2062 '^forget',
2063 [('i', 'interactive', None, _('use interactive mode')),
2063 [('i', 'interactive', None, _('use interactive mode')),
2064 ] + walkopts + dryrunopts,
2064 ] + walkopts + dryrunopts,
2065 _('[OPTION]... FILE...'), inferrepo=True)
2065 _('[OPTION]... FILE...'), inferrepo=True)
2066 def forget(ui, repo, *pats, **opts):
2066 def forget(ui, repo, *pats, **opts):
2067 """forget the specified files on the next commit
2067 """forget the specified files on the next commit
2068
2068
2069 Mark the specified files so they will no longer be tracked
2069 Mark the specified files so they will no longer be tracked
2070 after the next commit.
2070 after the next commit.
2071
2071
2072 This only removes files from the current branch, not from the
2072 This only removes files from the current branch, not from the
2073 entire project history, and it does not delete them from the
2073 entire project history, and it does not delete them from the
2074 working directory.
2074 working directory.
2075
2075
2076 To delete the file from the working directory, see :hg:`remove`.
2076 To delete the file from the working directory, see :hg:`remove`.
2077
2077
2078 To undo a forget before the next commit, see :hg:`add`.
2078 To undo a forget before the next commit, see :hg:`add`.
2079
2079
2080 .. container:: verbose
2080 .. container:: verbose
2081
2081
2082 Examples:
2082 Examples:
2083
2083
2084 - forget newly-added binary files::
2084 - forget newly-added binary files::
2085
2085
2086 hg forget "set:added() and binary()"
2086 hg forget "set:added() and binary()"
2087
2087
2088 - forget files that would be excluded by .hgignore::
2088 - forget files that would be excluded by .hgignore::
2089
2089
2090 hg forget "set:hgignore()"
2090 hg forget "set:hgignore()"
2091
2091
2092 Returns 0 on success.
2092 Returns 0 on success.
2093 """
2093 """
2094
2094
2095 opts = pycompat.byteskwargs(opts)
2095 opts = pycompat.byteskwargs(opts)
2096 if not pats:
2096 if not pats:
2097 raise error.Abort(_('no files specified'))
2097 raise error.Abort(_('no files specified'))
2098
2098
2099 m = scmutil.match(repo[None], pats, opts)
2099 m = scmutil.match(repo[None], pats, opts)
2100 dryrun, interactive = opts.get('dry_run'), opts.get('interactive')
2100 dryrun, interactive = opts.get('dry_run'), opts.get('interactive')
2101 rejected = cmdutil.forget(ui, repo, m, prefix="",
2101 rejected = cmdutil.forget(ui, repo, m, prefix="",
2102 explicitonly=False, dryrun=dryrun,
2102 explicitonly=False, dryrun=dryrun,
2103 interactive=interactive)[0]
2103 interactive=interactive)[0]
2104 return rejected and 1 or 0
2104 return rejected and 1 or 0
2105
2105
2106 @command(
2106 @command(
2107 'graft',
2107 'graft',
2108 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2108 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2109 ('c', 'continue', False, _('resume interrupted graft')),
2109 ('c', 'continue', False, _('resume interrupted graft')),
2110 ('', 'stop', False, _('stop interrupted graft')),
2110 ('', 'stop', False, _('stop interrupted graft')),
2111 ('', 'abort', False, _('abort interrupted graft')),
2111 ('', 'abort', False, _('abort interrupted graft')),
2112 ('e', 'edit', False, _('invoke editor on commit messages')),
2112 ('e', 'edit', False, _('invoke editor on commit messages')),
2113 ('', 'log', None, _('append graft info to log message')),
2113 ('', 'log', None, _('append graft info to log message')),
2114 ('', 'no-commit', None,
2114 ('', 'no-commit', None,
2115 _("don't commit, just apply the changes in working directory")),
2115 _("don't commit, just apply the changes in working directory")),
2116 ('f', 'force', False, _('force graft')),
2116 ('f', 'force', False, _('force graft')),
2117 ('D', 'currentdate', False,
2117 ('D', 'currentdate', False,
2118 _('record the current date as commit date')),
2118 _('record the current date as commit date')),
2119 ('U', 'currentuser', False,
2119 ('U', 'currentuser', False,
2120 _('record the current user as committer'), _('DATE'))]
2120 _('record the current user as committer'), _('DATE'))]
2121 + commitopts2 + mergetoolopts + dryrunopts,
2121 + commitopts2 + mergetoolopts + dryrunopts,
2122 _('[OPTION]... [-r REV]... REV...'))
2122 _('[OPTION]... [-r REV]... REV...'))
2123 def graft(ui, repo, *revs, **opts):
2123 def graft(ui, repo, *revs, **opts):
2124 '''copy changes from other branches onto the current branch
2124 '''copy changes from other branches onto the current branch
2125
2125
2126 This command uses Mercurial's merge logic to copy individual
2126 This command uses Mercurial's merge logic to copy individual
2127 changes from other branches without merging branches in the
2127 changes from other branches without merging branches in the
2128 history graph. This is sometimes known as 'backporting' or
2128 history graph. This is sometimes known as 'backporting' or
2129 'cherry-picking'. By default, graft will copy user, date, and
2129 'cherry-picking'. By default, graft will copy user, date, and
2130 description from the source changesets.
2130 description from the source changesets.
2131
2131
2132 Changesets that are ancestors of the current revision, that have
2132 Changesets that are ancestors of the current revision, that have
2133 already been grafted, or that are merges will be skipped.
2133 already been grafted, or that are merges will be skipped.
2134
2134
2135 If --log is specified, log messages will have a comment appended
2135 If --log is specified, log messages will have a comment appended
2136 of the form::
2136 of the form::
2137
2137
2138 (grafted from CHANGESETHASH)
2138 (grafted from CHANGESETHASH)
2139
2139
2140 If --force is specified, revisions will be grafted even if they
2140 If --force is specified, revisions will be grafted even if they
2141 are already ancestors of, or have been grafted to, the destination.
2141 are already ancestors of, or have been grafted to, the destination.
2142 This is useful when the revisions have since been backed out.
2142 This is useful when the revisions have since been backed out.
2143
2143
2144 If a graft merge results in conflicts, the graft process is
2144 If a graft merge results in conflicts, the graft process is
2145 interrupted so that the current merge can be manually resolved.
2145 interrupted so that the current merge can be manually resolved.
2146 Once all conflicts are addressed, the graft process can be
2146 Once all conflicts are addressed, the graft process can be
2147 continued with the -c/--continue option.
2147 continued with the -c/--continue option.
2148
2148
2149 The -c/--continue option reapplies all the earlier options.
2149 The -c/--continue option reapplies all the earlier options.
2150
2150
2151 .. container:: verbose
2151 .. container:: verbose
2152
2152
2153 Examples:
2153 Examples:
2154
2154
2155 - copy a single change to the stable branch and edit its description::
2155 - copy a single change to the stable branch and edit its description::
2156
2156
2157 hg update stable
2157 hg update stable
2158 hg graft --edit 9393
2158 hg graft --edit 9393
2159
2159
2160 - graft a range of changesets with one exception, updating dates::
2160 - graft a range of changesets with one exception, updating dates::
2161
2161
2162 hg graft -D "2085::2093 and not 2091"
2162 hg graft -D "2085::2093 and not 2091"
2163
2163
2164 - continue a graft after resolving conflicts::
2164 - continue a graft after resolving conflicts::
2165
2165
2166 hg graft -c
2166 hg graft -c
2167
2167
2168 - show the source of a grafted changeset::
2168 - show the source of a grafted changeset::
2169
2169
2170 hg log --debug -r .
2170 hg log --debug -r .
2171
2171
2172 - show revisions sorted by date::
2172 - show revisions sorted by date::
2173
2173
2174 hg log -r "sort(all(), date)"
2174 hg log -r "sort(all(), date)"
2175
2175
2176 See :hg:`help revisions` for more about specifying revisions.
2176 See :hg:`help revisions` for more about specifying revisions.
2177
2177
2178 Returns 0 on successful completion.
2178 Returns 0 on successful completion.
2179 '''
2179 '''
2180 with repo.wlock():
2180 with repo.wlock():
2181 return _dograft(ui, repo, *revs, **opts)
2181 return _dograft(ui, repo, *revs, **opts)
2182
2182
2183 def _dograft(ui, repo, *revs, **opts):
2183 def _dograft(ui, repo, *revs, **opts):
2184 opts = pycompat.byteskwargs(opts)
2184 opts = pycompat.byteskwargs(opts)
2185 if revs and opts.get('rev'):
2185 if revs and opts.get('rev'):
2186 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2186 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2187 'revision ordering!\n'))
2187 'revision ordering!\n'))
2188
2188
2189 revs = list(revs)
2189 revs = list(revs)
2190 revs.extend(opts.get('rev'))
2190 revs.extend(opts.get('rev'))
2191 # a dict of data to be stored in state file
2191 # a dict of data to be stored in state file
2192 statedata = {}
2192 statedata = {}
2193 # list of new nodes created by ongoing graft
2193 # list of new nodes created by ongoing graft
2194 statedata['newnodes'] = []
2194 statedata['newnodes'] = []
2195
2195
2196 if not opts.get('user') and opts.get('currentuser'):
2196 if not opts.get('user') and opts.get('currentuser'):
2197 opts['user'] = ui.username()
2197 opts['user'] = ui.username()
2198 if not opts.get('date') and opts.get('currentdate'):
2198 if not opts.get('date') and opts.get('currentdate'):
2199 opts['date'] = "%d %d" % dateutil.makedate()
2199 opts['date'] = "%d %d" % dateutil.makedate()
2200
2200
2201 editor = cmdutil.getcommiteditor(editform='graft',
2201 editor = cmdutil.getcommiteditor(editform='graft',
2202 **pycompat.strkwargs(opts))
2202 **pycompat.strkwargs(opts))
2203
2203
2204 cont = False
2204 cont = False
2205 if opts.get('no_commit'):
2205 if opts.get('no_commit'):
2206 if opts.get('edit'):
2206 if opts.get('edit'):
2207 raise error.Abort(_("cannot specify --no-commit and "
2207 raise error.Abort(_("cannot specify --no-commit and "
2208 "--edit together"))
2208 "--edit together"))
2209 if opts.get('currentuser'):
2209 if opts.get('currentuser'):
2210 raise error.Abort(_("cannot specify --no-commit and "
2210 raise error.Abort(_("cannot specify --no-commit and "
2211 "--currentuser together"))
2211 "--currentuser together"))
2212 if opts.get('currentdate'):
2212 if opts.get('currentdate'):
2213 raise error.Abort(_("cannot specify --no-commit and "
2213 raise error.Abort(_("cannot specify --no-commit and "
2214 "--currentdate together"))
2214 "--currentdate together"))
2215 if opts.get('log'):
2215 if opts.get('log'):
2216 raise error.Abort(_("cannot specify --no-commit and "
2216 raise error.Abort(_("cannot specify --no-commit and "
2217 "--log together"))
2217 "--log together"))
2218
2218
2219 graftstate = statemod.cmdstate(repo, 'graftstate')
2219 graftstate = statemod.cmdstate(repo, 'graftstate')
2220
2220
2221 if opts.get('stop'):
2221 if opts.get('stop'):
2222 if opts.get('continue'):
2222 if opts.get('continue'):
2223 raise error.Abort(_("cannot use '--continue' and "
2223 raise error.Abort(_("cannot use '--continue' and "
2224 "'--stop' together"))
2224 "'--stop' together"))
2225 if opts.get('abort'):
2225 if opts.get('abort'):
2226 raise error.Abort(_("cannot use '--abort' and '--stop' together"))
2226 raise error.Abort(_("cannot use '--abort' and '--stop' together"))
2227
2227
2228 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2228 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2229 opts.get('date'), opts.get('currentdate'),
2229 opts.get('date'), opts.get('currentdate'),
2230 opts.get('currentuser'), opts.get('rev'))):
2230 opts.get('currentuser'), opts.get('rev'))):
2231 raise error.Abort(_("cannot specify any other flag with '--stop'"))
2231 raise error.Abort(_("cannot specify any other flag with '--stop'"))
2232 return _stopgraft(ui, repo, graftstate)
2232 return _stopgraft(ui, repo, graftstate)
2233 elif opts.get('abort'):
2233 elif opts.get('abort'):
2234 if opts.get('continue'):
2234 if opts.get('continue'):
2235 raise error.Abort(_("cannot use '--continue' and "
2235 raise error.Abort(_("cannot use '--continue' and "
2236 "'--abort' together"))
2236 "'--abort' together"))
2237 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2237 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2238 opts.get('date'), opts.get('currentdate'),
2238 opts.get('date'), opts.get('currentdate'),
2239 opts.get('currentuser'), opts.get('rev'))):
2239 opts.get('currentuser'), opts.get('rev'))):
2240 raise error.Abort(_("cannot specify any other flag with '--abort'"))
2240 raise error.Abort(_("cannot specify any other flag with '--abort'"))
2241
2241
2242 return _abortgraft(ui, repo, graftstate)
2242 return _abortgraft(ui, repo, graftstate)
2243 elif opts.get('continue'):
2243 elif opts.get('continue'):
2244 cont = True
2244 cont = True
2245 if revs:
2245 if revs:
2246 raise error.Abort(_("can't specify --continue and revisions"))
2246 raise error.Abort(_("can't specify --continue and revisions"))
2247 # read in unfinished revisions
2247 # read in unfinished revisions
2248 if graftstate.exists():
2248 if graftstate.exists():
2249 statedata = _readgraftstate(repo, graftstate)
2249 statedata = _readgraftstate(repo, graftstate)
2250 if statedata.get('date'):
2250 if statedata.get('date'):
2251 opts['date'] = statedata['date']
2251 opts['date'] = statedata['date']
2252 if statedata.get('user'):
2252 if statedata.get('user'):
2253 opts['user'] = statedata['user']
2253 opts['user'] = statedata['user']
2254 if statedata.get('log'):
2254 if statedata.get('log'):
2255 opts['log'] = True
2255 opts['log'] = True
2256 if statedata.get('no_commit'):
2256 if statedata.get('no_commit'):
2257 opts['no_commit'] = statedata.get('no_commit')
2257 opts['no_commit'] = statedata.get('no_commit')
2258 nodes = statedata['nodes']
2258 nodes = statedata['nodes']
2259 revs = [repo[node].rev() for node in nodes]
2259 revs = [repo[node].rev() for node in nodes]
2260 else:
2260 else:
2261 cmdutil.wrongtooltocontinue(repo, _('graft'))
2261 cmdutil.wrongtooltocontinue(repo, _('graft'))
2262 else:
2262 else:
2263 if not revs:
2263 if not revs:
2264 raise error.Abort(_('no revisions specified'))
2264 raise error.Abort(_('no revisions specified'))
2265 cmdutil.checkunfinished(repo)
2265 cmdutil.checkunfinished(repo)
2266 cmdutil.bailifchanged(repo)
2266 cmdutil.bailifchanged(repo)
2267 revs = scmutil.revrange(repo, revs)
2267 revs = scmutil.revrange(repo, revs)
2268
2268
2269 skipped = set()
2269 skipped = set()
2270 # check for merges
2270 # check for merges
2271 for rev in repo.revs('%ld and merge()', revs):
2271 for rev in repo.revs('%ld and merge()', revs):
2272 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2272 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2273 skipped.add(rev)
2273 skipped.add(rev)
2274 revs = [r for r in revs if r not in skipped]
2274 revs = [r for r in revs if r not in skipped]
2275 if not revs:
2275 if not revs:
2276 return -1
2276 return -1
2277
2277
2278 # Don't check in the --continue case, in effect retaining --force across
2278 # Don't check in the --continue case, in effect retaining --force across
2279 # --continues. That's because without --force, any revisions we decided to
2279 # --continues. That's because without --force, any revisions we decided to
2280 # skip would have been filtered out here, so they wouldn't have made their
2280 # skip would have been filtered out here, so they wouldn't have made their
2281 # way to the graftstate. With --force, any revisions we would have otherwise
2281 # way to the graftstate. With --force, any revisions we would have otherwise
2282 # skipped would not have been filtered out, and if they hadn't been applied
2282 # skipped would not have been filtered out, and if they hadn't been applied
2283 # already, they'd have been in the graftstate.
2283 # already, they'd have been in the graftstate.
2284 if not (cont or opts.get('force')):
2284 if not (cont or opts.get('force')):
2285 # check for ancestors of dest branch
2285 # check for ancestors of dest branch
2286 crev = repo['.'].rev()
2286 crev = repo['.'].rev()
2287 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2287 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2288 # XXX make this lazy in the future
2288 # XXX make this lazy in the future
2289 # don't mutate while iterating, create a copy
2289 # don't mutate while iterating, create a copy
2290 for rev in list(revs):
2290 for rev in list(revs):
2291 if rev in ancestors:
2291 if rev in ancestors:
2292 ui.warn(_('skipping ancestor revision %d:%s\n') %
2292 ui.warn(_('skipping ancestor revision %d:%s\n') %
2293 (rev, repo[rev]))
2293 (rev, repo[rev]))
2294 # XXX remove on list is slow
2294 # XXX remove on list is slow
2295 revs.remove(rev)
2295 revs.remove(rev)
2296 if not revs:
2296 if not revs:
2297 return -1
2297 return -1
2298
2298
2299 # analyze revs for earlier grafts
2299 # analyze revs for earlier grafts
2300 ids = {}
2300 ids = {}
2301 for ctx in repo.set("%ld", revs):
2301 for ctx in repo.set("%ld", revs):
2302 ids[ctx.hex()] = ctx.rev()
2302 ids[ctx.hex()] = ctx.rev()
2303 n = ctx.extra().get('source')
2303 n = ctx.extra().get('source')
2304 if n:
2304 if n:
2305 ids[n] = ctx.rev()
2305 ids[n] = ctx.rev()
2306
2306
2307 # check ancestors for earlier grafts
2307 # check ancestors for earlier grafts
2308 ui.debug('scanning for duplicate grafts\n')
2308 ui.debug('scanning for duplicate grafts\n')
2309
2309
2310 # The only changesets we can be sure doesn't contain grafts of any
2310 # The only changesets we can be sure doesn't contain grafts of any
2311 # revs, are the ones that are common ancestors of *all* revs:
2311 # revs, are the ones that are common ancestors of *all* revs:
2312 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2312 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2313 ctx = repo[rev]
2313 ctx = repo[rev]
2314 n = ctx.extra().get('source')
2314 n = ctx.extra().get('source')
2315 if n in ids:
2315 if n in ids:
2316 try:
2316 try:
2317 r = repo[n].rev()
2317 r = repo[n].rev()
2318 except error.RepoLookupError:
2318 except error.RepoLookupError:
2319 r = None
2319 r = None
2320 if r in revs:
2320 if r in revs:
2321 ui.warn(_('skipping revision %d:%s '
2321 ui.warn(_('skipping revision %d:%s '
2322 '(already grafted to %d:%s)\n')
2322 '(already grafted to %d:%s)\n')
2323 % (r, repo[r], rev, ctx))
2323 % (r, repo[r], rev, ctx))
2324 revs.remove(r)
2324 revs.remove(r)
2325 elif ids[n] in revs:
2325 elif ids[n] in revs:
2326 if r is None:
2326 if r is None:
2327 ui.warn(_('skipping already grafted revision %d:%s '
2327 ui.warn(_('skipping already grafted revision %d:%s '
2328 '(%d:%s also has unknown origin %s)\n')
2328 '(%d:%s also has unknown origin %s)\n')
2329 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2329 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2330 else:
2330 else:
2331 ui.warn(_('skipping already grafted revision %d:%s '
2331 ui.warn(_('skipping already grafted revision %d:%s '
2332 '(%d:%s also has origin %d:%s)\n')
2332 '(%d:%s also has origin %d:%s)\n')
2333 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2333 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2334 revs.remove(ids[n])
2334 revs.remove(ids[n])
2335 elif ctx.hex() in ids:
2335 elif ctx.hex() in ids:
2336 r = ids[ctx.hex()]
2336 r = ids[ctx.hex()]
2337 ui.warn(_('skipping already grafted revision %d:%s '
2337 ui.warn(_('skipping already grafted revision %d:%s '
2338 '(was grafted from %d:%s)\n') %
2338 '(was grafted from %d:%s)\n') %
2339 (r, repo[r], rev, ctx))
2339 (r, repo[r], rev, ctx))
2340 revs.remove(r)
2340 revs.remove(r)
2341 if not revs:
2341 if not revs:
2342 return -1
2342 return -1
2343
2343
2344 if opts.get('no_commit'):
2344 if opts.get('no_commit'):
2345 statedata['no_commit'] = True
2345 statedata['no_commit'] = True
2346 for pos, ctx in enumerate(repo.set("%ld", revs)):
2346 for pos, ctx in enumerate(repo.set("%ld", revs)):
2347 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2347 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2348 ctx.description().split('\n', 1)[0])
2348 ctx.description().split('\n', 1)[0])
2349 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2349 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2350 if names:
2350 if names:
2351 desc += ' (%s)' % ' '.join(names)
2351 desc += ' (%s)' % ' '.join(names)
2352 ui.status(_('grafting %s\n') % desc)
2352 ui.status(_('grafting %s\n') % desc)
2353 if opts.get('dry_run'):
2353 if opts.get('dry_run'):
2354 continue
2354 continue
2355
2355
2356 source = ctx.extra().get('source')
2356 source = ctx.extra().get('source')
2357 extra = {}
2357 extra = {}
2358 if source:
2358 if source:
2359 extra['source'] = source
2359 extra['source'] = source
2360 extra['intermediate-source'] = ctx.hex()
2360 extra['intermediate-source'] = ctx.hex()
2361 else:
2361 else:
2362 extra['source'] = ctx.hex()
2362 extra['source'] = ctx.hex()
2363 user = ctx.user()
2363 user = ctx.user()
2364 if opts.get('user'):
2364 if opts.get('user'):
2365 user = opts['user']
2365 user = opts['user']
2366 statedata['user'] = user
2366 statedata['user'] = user
2367 date = ctx.date()
2367 date = ctx.date()
2368 if opts.get('date'):
2368 if opts.get('date'):
2369 date = opts['date']
2369 date = opts['date']
2370 statedata['date'] = date
2370 statedata['date'] = date
2371 message = ctx.description()
2371 message = ctx.description()
2372 if opts.get('log'):
2372 if opts.get('log'):
2373 message += '\n(grafted from %s)' % ctx.hex()
2373 message += '\n(grafted from %s)' % ctx.hex()
2374 statedata['log'] = True
2374 statedata['log'] = True
2375
2375
2376 # we don't merge the first commit when continuing
2376 # we don't merge the first commit when continuing
2377 if not cont:
2377 if not cont:
2378 # perform the graft merge with p1(rev) as 'ancestor'
2378 # perform the graft merge with p1(rev) as 'ancestor'
2379 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
2379 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
2380 with ui.configoverride(overrides, 'graft'):
2380 with ui.configoverride(overrides, 'graft'):
2381 stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'graft'])
2381 stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'graft'])
2382 # report any conflicts
2382 # report any conflicts
2383 if stats.unresolvedcount > 0:
2383 if stats.unresolvedcount > 0:
2384 # write out state for --continue
2384 # write out state for --continue
2385 nodes = [repo[rev].hex() for rev in revs[pos:]]
2385 nodes = [repo[rev].hex() for rev in revs[pos:]]
2386 statedata['nodes'] = nodes
2386 statedata['nodes'] = nodes
2387 stateversion = 1
2387 stateversion = 1
2388 graftstate.save(stateversion, statedata)
2388 graftstate.save(stateversion, statedata)
2389 hint = _("use 'hg resolve' and 'hg graft --continue'")
2389 hint = _("use 'hg resolve' and 'hg graft --continue'")
2390 raise error.Abort(
2390 raise error.Abort(
2391 _("unresolved conflicts, can't continue"),
2391 _("unresolved conflicts, can't continue"),
2392 hint=hint)
2392 hint=hint)
2393 else:
2393 else:
2394 cont = False
2394 cont = False
2395
2395
2396 # commit if --no-commit is false
2396 # commit if --no-commit is false
2397 if not opts.get('no_commit'):
2397 if not opts.get('no_commit'):
2398 node = repo.commit(text=message, user=user, date=date, extra=extra,
2398 node = repo.commit(text=message, user=user, date=date, extra=extra,
2399 editor=editor)
2399 editor=editor)
2400 if node is None:
2400 if node is None:
2401 ui.warn(
2401 ui.warn(
2402 _('note: graft of %d:%s created no changes to commit\n') %
2402 _('note: graft of %d:%s created no changes to commit\n') %
2403 (ctx.rev(), ctx))
2403 (ctx.rev(), ctx))
2404 # checking that newnodes exist because old state files won't have it
2404 # checking that newnodes exist because old state files won't have it
2405 elif statedata.get('newnodes') is not None:
2405 elif statedata.get('newnodes') is not None:
2406 statedata['newnodes'].append(node)
2406 statedata['newnodes'].append(node)
2407
2407
2408 # remove state when we complete successfully
2408 # remove state when we complete successfully
2409 if not opts.get('dry_run'):
2409 if not opts.get('dry_run'):
2410 graftstate.delete()
2410 graftstate.delete()
2411
2411
2412 return 0
2412 return 0
2413
2413
2414 def _abortgraft(ui, repo, graftstate):
2414 def _abortgraft(ui, repo, graftstate):
2415 """abort the interrupted graft and rollbacks to the state before interrupted
2415 """abort the interrupted graft and rollbacks to the state before interrupted
2416 graft"""
2416 graft"""
2417 if not graftstate.exists():
2417 if not graftstate.exists():
2418 raise error.Abort(_("no interrupted graft to abort"))
2418 raise error.Abort(_("no interrupted graft to abort"))
2419 statedata = _readgraftstate(repo, graftstate)
2419 statedata = _readgraftstate(repo, graftstate)
2420 newnodes = statedata.get('newnodes')
2420 newnodes = statedata.get('newnodes')
2421 if newnodes is None:
2421 if newnodes is None:
2422 # and old graft state which does not have all the data required to abort
2422 # and old graft state which does not have all the data required to abort
2423 # the graft
2423 # the graft
2424 raise error.Abort(_("cannot abort using an old graftstate"))
2424 raise error.Abort(_("cannot abort using an old graftstate"))
2425
2425
2426 # changeset from which graft operation was started
2426 # changeset from which graft operation was started
2427 startctx = None
2427 startctx = None
2428 if len(newnodes) > 0:
2428 if len(newnodes) > 0:
2429 startctx = repo[newnodes[0]].p1()
2429 startctx = repo[newnodes[0]].p1()
2430 else:
2430 else:
2431 startctx = repo['.']
2431 startctx = repo['.']
2432 # whether to strip or not
2432 # whether to strip or not
2433 cleanup = False
2433 cleanup = False
2434 if newnodes:
2434 if newnodes:
2435 newnodes = [repo[r].rev() for r in newnodes]
2435 newnodes = [repo[r].rev() for r in newnodes]
2436 cleanup = True
2436 cleanup = True
2437 # checking that none of the newnodes turned public or is public
2437 # checking that none of the newnodes turned public or is public
2438 immutable = [c for c in newnodes if not repo[c].mutable()]
2438 immutable = [c for c in newnodes if not repo[c].mutable()]
2439 if immutable:
2439 if immutable:
2440 repo.ui.warn(_("cannot clean up public changesets %s\n")
2440 repo.ui.warn(_("cannot clean up public changesets %s\n")
2441 % ', '.join(bytes(repo[r]) for r in immutable),
2441 % ', '.join(bytes(repo[r]) for r in immutable),
2442 hint=_("see 'hg help phases' for details"))
2442 hint=_("see 'hg help phases' for details"))
2443 cleanup = False
2443 cleanup = False
2444
2444
2445 # checking that no new nodes are created on top of grafted revs
2445 # checking that no new nodes are created on top of grafted revs
2446 desc = set(repo.changelog.descendants(newnodes))
2446 desc = set(repo.changelog.descendants(newnodes))
2447 if desc - set(newnodes):
2447 if desc - set(newnodes):
2448 repo.ui.warn(_("new changesets detected on destination "
2448 repo.ui.warn(_("new changesets detected on destination "
2449 "branch, can't strip\n"))
2449 "branch, can't strip\n"))
2450 cleanup = False
2450 cleanup = False
2451
2451
2452 if cleanup:
2452 if cleanup:
2453 with repo.wlock(), repo.lock():
2453 with repo.wlock(), repo.lock():
2454 hg.updaterepo(repo, startctx.node(), True)
2454 hg.updaterepo(repo, startctx.node(), overwrite=True)
2455 # stripping the new nodes created
2455 # stripping the new nodes created
2456 strippoints = [c.node() for c in repo.set("roots(%ld)",
2456 strippoints = [c.node() for c in repo.set("roots(%ld)",
2457 newnodes)]
2457 newnodes)]
2458 repair.strip(repo.ui, repo, strippoints, backup=False)
2458 repair.strip(repo.ui, repo, strippoints, backup=False)
2459
2459
2460 if not cleanup:
2460 if not cleanup:
2461 # we don't update to the startnode if we can't strip
2461 # we don't update to the startnode if we can't strip
2462 startctx = repo['.']
2462 startctx = repo['.']
2463 hg.updaterepo(repo, startctx.node(), True)
2463 hg.updaterepo(repo, startctx.node(), overwrite=True)
2464
2464
2465 ui.status(_("graft aborted\n"))
2465 ui.status(_("graft aborted\n"))
2466 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12])
2466 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12])
2467 graftstate.delete()
2467 graftstate.delete()
2468 return 0
2468 return 0
2469
2469
2470 def _readgraftstate(repo, graftstate):
2470 def _readgraftstate(repo, graftstate):
2471 """read the graft state file and return a dict of the data stored in it"""
2471 """read the graft state file and return a dict of the data stored in it"""
2472 try:
2472 try:
2473 return graftstate.read()
2473 return graftstate.read()
2474 except error.CorruptedState:
2474 except error.CorruptedState:
2475 nodes = repo.vfs.read('graftstate').splitlines()
2475 nodes = repo.vfs.read('graftstate').splitlines()
2476 return {'nodes': nodes}
2476 return {'nodes': nodes}
2477
2477
2478 def _stopgraft(ui, repo, graftstate):
2478 def _stopgraft(ui, repo, graftstate):
2479 """stop the interrupted graft"""
2479 """stop the interrupted graft"""
2480 if not graftstate.exists():
2480 if not graftstate.exists():
2481 raise error.Abort(_("no interrupted graft found"))
2481 raise error.Abort(_("no interrupted graft found"))
2482 pctx = repo['.']
2482 pctx = repo['.']
2483 hg.updaterepo(repo, pctx.node(), True)
2483 hg.updaterepo(repo, pctx.node(), overwrite=True)
2484 graftstate.delete()
2484 graftstate.delete()
2485 ui.status(_("stopped the interrupted graft\n"))
2485 ui.status(_("stopped the interrupted graft\n"))
2486 ui.status(_("working directory is now at %s\n") % pctx.hex()[:12])
2486 ui.status(_("working directory is now at %s\n") % pctx.hex()[:12])
2487 return 0
2487 return 0
2488
2488
2489 @command('grep',
2489 @command('grep',
2490 [('0', 'print0', None, _('end fields with NUL')),
2490 [('0', 'print0', None, _('end fields with NUL')),
2491 ('', 'all', None, _('print all revisions that match (DEPRECATED) ')),
2491 ('', 'all', None, _('print all revisions that match (DEPRECATED) ')),
2492 ('', 'diff', None, _('print all revisions when the term was introduced '
2492 ('', 'diff', None, _('print all revisions when the term was introduced '
2493 'or removed')),
2493 'or removed')),
2494 ('a', 'text', None, _('treat all files as text')),
2494 ('a', 'text', None, _('treat all files as text')),
2495 ('f', 'follow', None,
2495 ('f', 'follow', None,
2496 _('follow changeset history,'
2496 _('follow changeset history,'
2497 ' or file history across copies and renames')),
2497 ' or file history across copies and renames')),
2498 ('i', 'ignore-case', None, _('ignore case when matching')),
2498 ('i', 'ignore-case', None, _('ignore case when matching')),
2499 ('l', 'files-with-matches', None,
2499 ('l', 'files-with-matches', None,
2500 _('print only filenames and revisions that match')),
2500 _('print only filenames and revisions that match')),
2501 ('n', 'line-number', None, _('print matching line numbers')),
2501 ('n', 'line-number', None, _('print matching line numbers')),
2502 ('r', 'rev', [],
2502 ('r', 'rev', [],
2503 _('only search files changed within revision range'), _('REV')),
2503 _('only search files changed within revision range'), _('REV')),
2504 ('', 'allfiles', False,
2504 ('', 'allfiles', False,
2505 _('include all files in the changeset while grepping (EXPERIMENTAL)')),
2505 _('include all files in the changeset while grepping (EXPERIMENTAL)')),
2506 ('u', 'user', None, _('list the author (long with -v)')),
2506 ('u', 'user', None, _('list the author (long with -v)')),
2507 ('d', 'date', None, _('list the date (short with -q)')),
2507 ('d', 'date', None, _('list the date (short with -q)')),
2508 ] + formatteropts + walkopts,
2508 ] + formatteropts + walkopts,
2509 _('[OPTION]... PATTERN [FILE]...'),
2509 _('[OPTION]... PATTERN [FILE]...'),
2510 inferrepo=True,
2510 inferrepo=True,
2511 intents={INTENT_READONLY})
2511 intents={INTENT_READONLY})
2512 def grep(ui, repo, pattern, *pats, **opts):
2512 def grep(ui, repo, pattern, *pats, **opts):
2513 """search revision history for a pattern in specified files
2513 """search revision history for a pattern in specified files
2514
2514
2515 Search revision history for a regular expression in the specified
2515 Search revision history for a regular expression in the specified
2516 files or the entire project.
2516 files or the entire project.
2517
2517
2518 By default, grep prints the most recent revision number for each
2518 By default, grep prints the most recent revision number for each
2519 file in which it finds a match. To get it to print every revision
2519 file in which it finds a match. To get it to print every revision
2520 that contains a change in match status ("-" for a match that becomes
2520 that contains a change in match status ("-" for a match that becomes
2521 a non-match, or "+" for a non-match that becomes a match), use the
2521 a non-match, or "+" for a non-match that becomes a match), use the
2522 --diff flag.
2522 --diff flag.
2523
2523
2524 PATTERN can be any Python (roughly Perl-compatible) regular
2524 PATTERN can be any Python (roughly Perl-compatible) regular
2525 expression.
2525 expression.
2526
2526
2527 If no FILEs are specified (and -f/--follow isn't set), all files in
2527 If no FILEs are specified (and -f/--follow isn't set), all files in
2528 the repository are searched, including those that don't exist in the
2528 the repository are searched, including those that don't exist in the
2529 current branch or have been deleted in a prior changeset.
2529 current branch or have been deleted in a prior changeset.
2530
2530
2531 Returns 0 if a match is found, 1 otherwise.
2531 Returns 0 if a match is found, 1 otherwise.
2532 """
2532 """
2533 opts = pycompat.byteskwargs(opts)
2533 opts = pycompat.byteskwargs(opts)
2534 diff = opts.get('all') or opts.get('diff')
2534 diff = opts.get('all') or opts.get('diff')
2535 reflags = re.M
2535 reflags = re.M
2536 if opts.get('ignore_case'):
2536 if opts.get('ignore_case'):
2537 reflags |= re.I
2537 reflags |= re.I
2538 try:
2538 try:
2539 regexp = util.re.compile(pattern, reflags)
2539 regexp = util.re.compile(pattern, reflags)
2540 except re.error as inst:
2540 except re.error as inst:
2541 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2541 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2542 return 1
2542 return 1
2543 sep, eol = ':', '\n'
2543 sep, eol = ':', '\n'
2544 if opts.get('print0'):
2544 if opts.get('print0'):
2545 sep = eol = '\0'
2545 sep = eol = '\0'
2546
2546
2547 getfile = util.lrucachefunc(repo.file)
2547 getfile = util.lrucachefunc(repo.file)
2548
2548
2549 def matchlines(body):
2549 def matchlines(body):
2550 begin = 0
2550 begin = 0
2551 linenum = 0
2551 linenum = 0
2552 while begin < len(body):
2552 while begin < len(body):
2553 match = regexp.search(body, begin)
2553 match = regexp.search(body, begin)
2554 if not match:
2554 if not match:
2555 break
2555 break
2556 mstart, mend = match.span()
2556 mstart, mend = match.span()
2557 linenum += body.count('\n', begin, mstart) + 1
2557 linenum += body.count('\n', begin, mstart) + 1
2558 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2558 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2559 begin = body.find('\n', mend) + 1 or len(body) + 1
2559 begin = body.find('\n', mend) + 1 or len(body) + 1
2560 lend = begin - 1
2560 lend = begin - 1
2561 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2561 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2562
2562
2563 class linestate(object):
2563 class linestate(object):
2564 def __init__(self, line, linenum, colstart, colend):
2564 def __init__(self, line, linenum, colstart, colend):
2565 self.line = line
2565 self.line = line
2566 self.linenum = linenum
2566 self.linenum = linenum
2567 self.colstart = colstart
2567 self.colstart = colstart
2568 self.colend = colend
2568 self.colend = colend
2569
2569
2570 def __hash__(self):
2570 def __hash__(self):
2571 return hash((self.linenum, self.line))
2571 return hash((self.linenum, self.line))
2572
2572
2573 def __eq__(self, other):
2573 def __eq__(self, other):
2574 return self.line == other.line
2574 return self.line == other.line
2575
2575
2576 def findpos(self):
2576 def findpos(self):
2577 """Iterate all (start, end) indices of matches"""
2577 """Iterate all (start, end) indices of matches"""
2578 yield self.colstart, self.colend
2578 yield self.colstart, self.colend
2579 p = self.colend
2579 p = self.colend
2580 while p < len(self.line):
2580 while p < len(self.line):
2581 m = regexp.search(self.line, p)
2581 m = regexp.search(self.line, p)
2582 if not m:
2582 if not m:
2583 break
2583 break
2584 yield m.span()
2584 yield m.span()
2585 p = m.end()
2585 p = m.end()
2586
2586
2587 matches = {}
2587 matches = {}
2588 copies = {}
2588 copies = {}
2589 def grepbody(fn, rev, body):
2589 def grepbody(fn, rev, body):
2590 matches[rev].setdefault(fn, [])
2590 matches[rev].setdefault(fn, [])
2591 m = matches[rev][fn]
2591 m = matches[rev][fn]
2592 for lnum, cstart, cend, line in matchlines(body):
2592 for lnum, cstart, cend, line in matchlines(body):
2593 s = linestate(line, lnum, cstart, cend)
2593 s = linestate(line, lnum, cstart, cend)
2594 m.append(s)
2594 m.append(s)
2595
2595
2596 def difflinestates(a, b):
2596 def difflinestates(a, b):
2597 sm = difflib.SequenceMatcher(None, a, b)
2597 sm = difflib.SequenceMatcher(None, a, b)
2598 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2598 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2599 if tag == 'insert':
2599 if tag == 'insert':
2600 for i in xrange(blo, bhi):
2600 for i in xrange(blo, bhi):
2601 yield ('+', b[i])
2601 yield ('+', b[i])
2602 elif tag == 'delete':
2602 elif tag == 'delete':
2603 for i in xrange(alo, ahi):
2603 for i in xrange(alo, ahi):
2604 yield ('-', a[i])
2604 yield ('-', a[i])
2605 elif tag == 'replace':
2605 elif tag == 'replace':
2606 for i in xrange(alo, ahi):
2606 for i in xrange(alo, ahi):
2607 yield ('-', a[i])
2607 yield ('-', a[i])
2608 for i in xrange(blo, bhi):
2608 for i in xrange(blo, bhi):
2609 yield ('+', b[i])
2609 yield ('+', b[i])
2610
2610
2611 def display(fm, fn, ctx, pstates, states):
2611 def display(fm, fn, ctx, pstates, states):
2612 rev = scmutil.intrev(ctx)
2612 rev = scmutil.intrev(ctx)
2613 if fm.isplain():
2613 if fm.isplain():
2614 formatuser = ui.shortuser
2614 formatuser = ui.shortuser
2615 else:
2615 else:
2616 formatuser = str
2616 formatuser = str
2617 if ui.quiet:
2617 if ui.quiet:
2618 datefmt = '%Y-%m-%d'
2618 datefmt = '%Y-%m-%d'
2619 else:
2619 else:
2620 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2620 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2621 found = False
2621 found = False
2622 @util.cachefunc
2622 @util.cachefunc
2623 def binary():
2623 def binary():
2624 flog = getfile(fn)
2624 flog = getfile(fn)
2625 try:
2625 try:
2626 return stringutil.binary(flog.read(ctx.filenode(fn)))
2626 return stringutil.binary(flog.read(ctx.filenode(fn)))
2627 except error.WdirUnsupported:
2627 except error.WdirUnsupported:
2628 return ctx[fn].isbinary()
2628 return ctx[fn].isbinary()
2629
2629
2630 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2630 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2631 if diff:
2631 if diff:
2632 iter = difflinestates(pstates, states)
2632 iter = difflinestates(pstates, states)
2633 else:
2633 else:
2634 iter = [('', l) for l in states]
2634 iter = [('', l) for l in states]
2635 for change, l in iter:
2635 for change, l in iter:
2636 fm.startitem()
2636 fm.startitem()
2637 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)))
2637 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)))
2638
2638
2639 cols = [
2639 cols = [
2640 ('filename', fn, True),
2640 ('filename', fn, True),
2641 ('rev', rev, True),
2641 ('rev', rev, True),
2642 ('linenumber', l.linenum, opts.get('line_number')),
2642 ('linenumber', l.linenum, opts.get('line_number')),
2643 ]
2643 ]
2644 if diff:
2644 if diff:
2645 cols.append(('change', change, True))
2645 cols.append(('change', change, True))
2646 cols.extend([
2646 cols.extend([
2647 ('user', formatuser(ctx.user()), opts.get('user')),
2647 ('user', formatuser(ctx.user()), opts.get('user')),
2648 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2648 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2649 ])
2649 ])
2650 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2650 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2651 for name, data, cond in cols:
2651 for name, data, cond in cols:
2652 field = fieldnamemap.get(name, name)
2652 field = fieldnamemap.get(name, name)
2653 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2653 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2654 if cond and name != lastcol:
2654 if cond and name != lastcol:
2655 fm.plain(sep, label='grep.sep')
2655 fm.plain(sep, label='grep.sep')
2656 if not opts.get('files_with_matches'):
2656 if not opts.get('files_with_matches'):
2657 fm.plain(sep, label='grep.sep')
2657 fm.plain(sep, label='grep.sep')
2658 if not opts.get('text') and binary():
2658 if not opts.get('text') and binary():
2659 fm.plain(_(" Binary file matches"))
2659 fm.plain(_(" Binary file matches"))
2660 else:
2660 else:
2661 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2661 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2662 fm.plain(eol)
2662 fm.plain(eol)
2663 found = True
2663 found = True
2664 if opts.get('files_with_matches'):
2664 if opts.get('files_with_matches'):
2665 break
2665 break
2666 return found
2666 return found
2667
2667
2668 def displaymatches(fm, l):
2668 def displaymatches(fm, l):
2669 p = 0
2669 p = 0
2670 for s, e in l.findpos():
2670 for s, e in l.findpos():
2671 if p < s:
2671 if p < s:
2672 fm.startitem()
2672 fm.startitem()
2673 fm.write('text', '%s', l.line[p:s])
2673 fm.write('text', '%s', l.line[p:s])
2674 fm.data(matched=False)
2674 fm.data(matched=False)
2675 fm.startitem()
2675 fm.startitem()
2676 fm.write('text', '%s', l.line[s:e], label='grep.match')
2676 fm.write('text', '%s', l.line[s:e], label='grep.match')
2677 fm.data(matched=True)
2677 fm.data(matched=True)
2678 p = e
2678 p = e
2679 if p < len(l.line):
2679 if p < len(l.line):
2680 fm.startitem()
2680 fm.startitem()
2681 fm.write('text', '%s', l.line[p:])
2681 fm.write('text', '%s', l.line[p:])
2682 fm.data(matched=False)
2682 fm.data(matched=False)
2683 fm.end()
2683 fm.end()
2684
2684
2685 skip = {}
2685 skip = {}
2686 revfiles = {}
2686 revfiles = {}
2687 match = scmutil.match(repo[None], pats, opts)
2687 match = scmutil.match(repo[None], pats, opts)
2688 found = False
2688 found = False
2689 follow = opts.get('follow')
2689 follow = opts.get('follow')
2690
2690
2691 def prep(ctx, fns):
2691 def prep(ctx, fns):
2692 rev = ctx.rev()
2692 rev = ctx.rev()
2693 pctx = ctx.p1()
2693 pctx = ctx.p1()
2694 parent = pctx.rev()
2694 parent = pctx.rev()
2695 matches.setdefault(rev, {})
2695 matches.setdefault(rev, {})
2696 matches.setdefault(parent, {})
2696 matches.setdefault(parent, {})
2697 files = revfiles.setdefault(rev, [])
2697 files = revfiles.setdefault(rev, [])
2698 for fn in fns:
2698 for fn in fns:
2699 flog = getfile(fn)
2699 flog = getfile(fn)
2700 try:
2700 try:
2701 fnode = ctx.filenode(fn)
2701 fnode = ctx.filenode(fn)
2702 except error.LookupError:
2702 except error.LookupError:
2703 continue
2703 continue
2704 try:
2704 try:
2705 copied = flog.renamed(fnode)
2705 copied = flog.renamed(fnode)
2706 except error.WdirUnsupported:
2706 except error.WdirUnsupported:
2707 copied = ctx[fn].renamed()
2707 copied = ctx[fn].renamed()
2708 copy = follow and copied and copied[0]
2708 copy = follow and copied and copied[0]
2709 if copy:
2709 if copy:
2710 copies.setdefault(rev, {})[fn] = copy
2710 copies.setdefault(rev, {})[fn] = copy
2711 if fn in skip:
2711 if fn in skip:
2712 if copy:
2712 if copy:
2713 skip[copy] = True
2713 skip[copy] = True
2714 continue
2714 continue
2715 files.append(fn)
2715 files.append(fn)
2716
2716
2717 if fn not in matches[rev]:
2717 if fn not in matches[rev]:
2718 try:
2718 try:
2719 content = flog.read(fnode)
2719 content = flog.read(fnode)
2720 except error.WdirUnsupported:
2720 except error.WdirUnsupported:
2721 content = ctx[fn].data()
2721 content = ctx[fn].data()
2722 grepbody(fn, rev, content)
2722 grepbody(fn, rev, content)
2723
2723
2724 pfn = copy or fn
2724 pfn = copy or fn
2725 if pfn not in matches[parent]:
2725 if pfn not in matches[parent]:
2726 try:
2726 try:
2727 fnode = pctx.filenode(pfn)
2727 fnode = pctx.filenode(pfn)
2728 grepbody(pfn, parent, flog.read(fnode))
2728 grepbody(pfn, parent, flog.read(fnode))
2729 except error.LookupError:
2729 except error.LookupError:
2730 pass
2730 pass
2731
2731
2732 ui.pager('grep')
2732 ui.pager('grep')
2733 fm = ui.formatter('grep', opts)
2733 fm = ui.formatter('grep', opts)
2734 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2734 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2735 rev = ctx.rev()
2735 rev = ctx.rev()
2736 parent = ctx.p1().rev()
2736 parent = ctx.p1().rev()
2737 for fn in sorted(revfiles.get(rev, [])):
2737 for fn in sorted(revfiles.get(rev, [])):
2738 states = matches[rev][fn]
2738 states = matches[rev][fn]
2739 copy = copies.get(rev, {}).get(fn)
2739 copy = copies.get(rev, {}).get(fn)
2740 if fn in skip:
2740 if fn in skip:
2741 if copy:
2741 if copy:
2742 skip[copy] = True
2742 skip[copy] = True
2743 continue
2743 continue
2744 pstates = matches.get(parent, {}).get(copy or fn, [])
2744 pstates = matches.get(parent, {}).get(copy or fn, [])
2745 if pstates or states:
2745 if pstates or states:
2746 r = display(fm, fn, ctx, pstates, states)
2746 r = display(fm, fn, ctx, pstates, states)
2747 found = found or r
2747 found = found or r
2748 if r and not diff:
2748 if r and not diff:
2749 skip[fn] = True
2749 skip[fn] = True
2750 if copy:
2750 if copy:
2751 skip[copy] = True
2751 skip[copy] = True
2752 del revfiles[rev]
2752 del revfiles[rev]
2753 # We will keep the matches dict for the duration of the window
2753 # We will keep the matches dict for the duration of the window
2754 # clear the matches dict once the window is over
2754 # clear the matches dict once the window is over
2755 if not revfiles:
2755 if not revfiles:
2756 matches.clear()
2756 matches.clear()
2757 fm.end()
2757 fm.end()
2758
2758
2759 return not found
2759 return not found
2760
2760
2761 @command('heads',
2761 @command('heads',
2762 [('r', 'rev', '',
2762 [('r', 'rev', '',
2763 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2763 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2764 ('t', 'topo', False, _('show topological heads only')),
2764 ('t', 'topo', False, _('show topological heads only')),
2765 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2765 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2766 ('c', 'closed', False, _('show normal and closed branch heads')),
2766 ('c', 'closed', False, _('show normal and closed branch heads')),
2767 ] + templateopts,
2767 ] + templateopts,
2768 _('[-ct] [-r STARTREV] [REV]...'),
2768 _('[-ct] [-r STARTREV] [REV]...'),
2769 intents={INTENT_READONLY})
2769 intents={INTENT_READONLY})
2770 def heads(ui, repo, *branchrevs, **opts):
2770 def heads(ui, repo, *branchrevs, **opts):
2771 """show branch heads
2771 """show branch heads
2772
2772
2773 With no arguments, show all open branch heads in the repository.
2773 With no arguments, show all open branch heads in the repository.
2774 Branch heads are changesets that have no descendants on the
2774 Branch heads are changesets that have no descendants on the
2775 same branch. They are where development generally takes place and
2775 same branch. They are where development generally takes place and
2776 are the usual targets for update and merge operations.
2776 are the usual targets for update and merge operations.
2777
2777
2778 If one or more REVs are given, only open branch heads on the
2778 If one or more REVs are given, only open branch heads on the
2779 branches associated with the specified changesets are shown. This
2779 branches associated with the specified changesets are shown. This
2780 means that you can use :hg:`heads .` to see the heads on the
2780 means that you can use :hg:`heads .` to see the heads on the
2781 currently checked-out branch.
2781 currently checked-out branch.
2782
2782
2783 If -c/--closed is specified, also show branch heads marked closed
2783 If -c/--closed is specified, also show branch heads marked closed
2784 (see :hg:`commit --close-branch`).
2784 (see :hg:`commit --close-branch`).
2785
2785
2786 If STARTREV is specified, only those heads that are descendants of
2786 If STARTREV is specified, only those heads that are descendants of
2787 STARTREV will be displayed.
2787 STARTREV will be displayed.
2788
2788
2789 If -t/--topo is specified, named branch mechanics will be ignored and only
2789 If -t/--topo is specified, named branch mechanics will be ignored and only
2790 topological heads (changesets with no children) will be shown.
2790 topological heads (changesets with no children) will be shown.
2791
2791
2792 Returns 0 if matching heads are found, 1 if not.
2792 Returns 0 if matching heads are found, 1 if not.
2793 """
2793 """
2794
2794
2795 opts = pycompat.byteskwargs(opts)
2795 opts = pycompat.byteskwargs(opts)
2796 start = None
2796 start = None
2797 rev = opts.get('rev')
2797 rev = opts.get('rev')
2798 if rev:
2798 if rev:
2799 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2799 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2800 start = scmutil.revsingle(repo, rev, None).node()
2800 start = scmutil.revsingle(repo, rev, None).node()
2801
2801
2802 if opts.get('topo'):
2802 if opts.get('topo'):
2803 heads = [repo[h] for h in repo.heads(start)]
2803 heads = [repo[h] for h in repo.heads(start)]
2804 else:
2804 else:
2805 heads = []
2805 heads = []
2806 for branch in repo.branchmap():
2806 for branch in repo.branchmap():
2807 heads += repo.branchheads(branch, start, opts.get('closed'))
2807 heads += repo.branchheads(branch, start, opts.get('closed'))
2808 heads = [repo[h] for h in heads]
2808 heads = [repo[h] for h in heads]
2809
2809
2810 if branchrevs:
2810 if branchrevs:
2811 branches = set(repo[r].branch()
2811 branches = set(repo[r].branch()
2812 for r in scmutil.revrange(repo, branchrevs))
2812 for r in scmutil.revrange(repo, branchrevs))
2813 heads = [h for h in heads if h.branch() in branches]
2813 heads = [h for h in heads if h.branch() in branches]
2814
2814
2815 if opts.get('active') and branchrevs:
2815 if opts.get('active') and branchrevs:
2816 dagheads = repo.heads(start)
2816 dagheads = repo.heads(start)
2817 heads = [h for h in heads if h.node() in dagheads]
2817 heads = [h for h in heads if h.node() in dagheads]
2818
2818
2819 if branchrevs:
2819 if branchrevs:
2820 haveheads = set(h.branch() for h in heads)
2820 haveheads = set(h.branch() for h in heads)
2821 if branches - haveheads:
2821 if branches - haveheads:
2822 headless = ', '.join(b for b in branches - haveheads)
2822 headless = ', '.join(b for b in branches - haveheads)
2823 msg = _('no open branch heads found on branches %s')
2823 msg = _('no open branch heads found on branches %s')
2824 if opts.get('rev'):
2824 if opts.get('rev'):
2825 msg += _(' (started at %s)') % opts['rev']
2825 msg += _(' (started at %s)') % opts['rev']
2826 ui.warn((msg + '\n') % headless)
2826 ui.warn((msg + '\n') % headless)
2827
2827
2828 if not heads:
2828 if not heads:
2829 return 1
2829 return 1
2830
2830
2831 ui.pager('heads')
2831 ui.pager('heads')
2832 heads = sorted(heads, key=lambda x: -x.rev())
2832 heads = sorted(heads, key=lambda x: -x.rev())
2833 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
2833 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
2834 for ctx in heads:
2834 for ctx in heads:
2835 displayer.show(ctx)
2835 displayer.show(ctx)
2836 displayer.close()
2836 displayer.close()
2837
2837
2838 @command('help',
2838 @command('help',
2839 [('e', 'extension', None, _('show only help for extensions')),
2839 [('e', 'extension', None, _('show only help for extensions')),
2840 ('c', 'command', None, _('show only help for commands')),
2840 ('c', 'command', None, _('show only help for commands')),
2841 ('k', 'keyword', None, _('show topics matching keyword')),
2841 ('k', 'keyword', None, _('show topics matching keyword')),
2842 ('s', 'system', [], _('show help for specific platform(s)')),
2842 ('s', 'system', [], _('show help for specific platform(s)')),
2843 ],
2843 ],
2844 _('[-ecks] [TOPIC]'),
2844 _('[-ecks] [TOPIC]'),
2845 norepo=True,
2845 norepo=True,
2846 intents={INTENT_READONLY})
2846 intents={INTENT_READONLY})
2847 def help_(ui, name=None, **opts):
2847 def help_(ui, name=None, **opts):
2848 """show help for a given topic or a help overview
2848 """show help for a given topic or a help overview
2849
2849
2850 With no arguments, print a list of commands with short help messages.
2850 With no arguments, print a list of commands with short help messages.
2851
2851
2852 Given a topic, extension, or command name, print help for that
2852 Given a topic, extension, or command name, print help for that
2853 topic.
2853 topic.
2854
2854
2855 Returns 0 if successful.
2855 Returns 0 if successful.
2856 """
2856 """
2857
2857
2858 keep = opts.get(r'system') or []
2858 keep = opts.get(r'system') or []
2859 if len(keep) == 0:
2859 if len(keep) == 0:
2860 if pycompat.sysplatform.startswith('win'):
2860 if pycompat.sysplatform.startswith('win'):
2861 keep.append('windows')
2861 keep.append('windows')
2862 elif pycompat.sysplatform == 'OpenVMS':
2862 elif pycompat.sysplatform == 'OpenVMS':
2863 keep.append('vms')
2863 keep.append('vms')
2864 elif pycompat.sysplatform == 'plan9':
2864 elif pycompat.sysplatform == 'plan9':
2865 keep.append('plan9')
2865 keep.append('plan9')
2866 else:
2866 else:
2867 keep.append('unix')
2867 keep.append('unix')
2868 keep.append(pycompat.sysplatform.lower())
2868 keep.append(pycompat.sysplatform.lower())
2869 if ui.verbose:
2869 if ui.verbose:
2870 keep.append('verbose')
2870 keep.append('verbose')
2871
2871
2872 commands = sys.modules[__name__]
2872 commands = sys.modules[__name__]
2873 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
2873 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
2874 ui.pager('help')
2874 ui.pager('help')
2875 ui.write(formatted)
2875 ui.write(formatted)
2876
2876
2877
2877
2878 @command('identify|id',
2878 @command('identify|id',
2879 [('r', 'rev', '',
2879 [('r', 'rev', '',
2880 _('identify the specified revision'), _('REV')),
2880 _('identify the specified revision'), _('REV')),
2881 ('n', 'num', None, _('show local revision number')),
2881 ('n', 'num', None, _('show local revision number')),
2882 ('i', 'id', None, _('show global revision id')),
2882 ('i', 'id', None, _('show global revision id')),
2883 ('b', 'branch', None, _('show branch')),
2883 ('b', 'branch', None, _('show branch')),
2884 ('t', 'tags', None, _('show tags')),
2884 ('t', 'tags', None, _('show tags')),
2885 ('B', 'bookmarks', None, _('show bookmarks')),
2885 ('B', 'bookmarks', None, _('show bookmarks')),
2886 ] + remoteopts + formatteropts,
2886 ] + remoteopts + formatteropts,
2887 _('[-nibtB] [-r REV] [SOURCE]'),
2887 _('[-nibtB] [-r REV] [SOURCE]'),
2888 optionalrepo=True,
2888 optionalrepo=True,
2889 intents={INTENT_READONLY})
2889 intents={INTENT_READONLY})
2890 def identify(ui, repo, source=None, rev=None,
2890 def identify(ui, repo, source=None, rev=None,
2891 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2891 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2892 """identify the working directory or specified revision
2892 """identify the working directory or specified revision
2893
2893
2894 Print a summary identifying the repository state at REV using one or
2894 Print a summary identifying the repository state at REV using one or
2895 two parent hash identifiers, followed by a "+" if the working
2895 two parent hash identifiers, followed by a "+" if the working
2896 directory has uncommitted changes, the branch name (if not default),
2896 directory has uncommitted changes, the branch name (if not default),
2897 a list of tags, and a list of bookmarks.
2897 a list of tags, and a list of bookmarks.
2898
2898
2899 When REV is not given, print a summary of the current state of the
2899 When REV is not given, print a summary of the current state of the
2900 repository including the working directory. Specify -r. to get information
2900 repository including the working directory. Specify -r. to get information
2901 of the working directory parent without scanning uncommitted changes.
2901 of the working directory parent without scanning uncommitted changes.
2902
2902
2903 Specifying a path to a repository root or Mercurial bundle will
2903 Specifying a path to a repository root or Mercurial bundle will
2904 cause lookup to operate on that repository/bundle.
2904 cause lookup to operate on that repository/bundle.
2905
2905
2906 .. container:: verbose
2906 .. container:: verbose
2907
2907
2908 Examples:
2908 Examples:
2909
2909
2910 - generate a build identifier for the working directory::
2910 - generate a build identifier for the working directory::
2911
2911
2912 hg id --id > build-id.dat
2912 hg id --id > build-id.dat
2913
2913
2914 - find the revision corresponding to a tag::
2914 - find the revision corresponding to a tag::
2915
2915
2916 hg id -n -r 1.3
2916 hg id -n -r 1.3
2917
2917
2918 - check the most recent revision of a remote repository::
2918 - check the most recent revision of a remote repository::
2919
2919
2920 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2920 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2921
2921
2922 See :hg:`log` for generating more information about specific revisions,
2922 See :hg:`log` for generating more information about specific revisions,
2923 including full hash identifiers.
2923 including full hash identifiers.
2924
2924
2925 Returns 0 if successful.
2925 Returns 0 if successful.
2926 """
2926 """
2927
2927
2928 opts = pycompat.byteskwargs(opts)
2928 opts = pycompat.byteskwargs(opts)
2929 if not repo and not source:
2929 if not repo and not source:
2930 raise error.Abort(_("there is no Mercurial repository here "
2930 raise error.Abort(_("there is no Mercurial repository here "
2931 "(.hg not found)"))
2931 "(.hg not found)"))
2932
2932
2933 if ui.debugflag:
2933 if ui.debugflag:
2934 hexfunc = hex
2934 hexfunc = hex
2935 else:
2935 else:
2936 hexfunc = short
2936 hexfunc = short
2937 default = not (num or id or branch or tags or bookmarks)
2937 default = not (num or id or branch or tags or bookmarks)
2938 output = []
2938 output = []
2939 revs = []
2939 revs = []
2940
2940
2941 if source:
2941 if source:
2942 source, branches = hg.parseurl(ui.expandpath(source))
2942 source, branches = hg.parseurl(ui.expandpath(source))
2943 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2943 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2944 repo = peer.local()
2944 repo = peer.local()
2945 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2945 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2946
2946
2947 fm = ui.formatter('identify', opts)
2947 fm = ui.formatter('identify', opts)
2948 fm.startitem()
2948 fm.startitem()
2949
2949
2950 if not repo:
2950 if not repo:
2951 if num or branch or tags:
2951 if num or branch or tags:
2952 raise error.Abort(
2952 raise error.Abort(
2953 _("can't query remote revision number, branch, or tags"))
2953 _("can't query remote revision number, branch, or tags"))
2954 if not rev and revs:
2954 if not rev and revs:
2955 rev = revs[0]
2955 rev = revs[0]
2956 if not rev:
2956 if not rev:
2957 rev = "tip"
2957 rev = "tip"
2958
2958
2959 remoterev = peer.lookup(rev)
2959 remoterev = peer.lookup(rev)
2960 hexrev = hexfunc(remoterev)
2960 hexrev = hexfunc(remoterev)
2961 if default or id:
2961 if default or id:
2962 output = [hexrev]
2962 output = [hexrev]
2963 fm.data(id=hexrev)
2963 fm.data(id=hexrev)
2964
2964
2965 def getbms():
2965 def getbms():
2966 bms = []
2966 bms = []
2967
2967
2968 if 'bookmarks' in peer.listkeys('namespaces'):
2968 if 'bookmarks' in peer.listkeys('namespaces'):
2969 hexremoterev = hex(remoterev)
2969 hexremoterev = hex(remoterev)
2970 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2970 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2971 if bmr == hexremoterev]
2971 if bmr == hexremoterev]
2972
2972
2973 return sorted(bms)
2973 return sorted(bms)
2974
2974
2975 bms = getbms()
2975 bms = getbms()
2976 if bookmarks:
2976 if bookmarks:
2977 output.extend(bms)
2977 output.extend(bms)
2978 elif default and not ui.quiet:
2978 elif default and not ui.quiet:
2979 # multiple bookmarks for a single parent separated by '/'
2979 # multiple bookmarks for a single parent separated by '/'
2980 bm = '/'.join(bms)
2980 bm = '/'.join(bms)
2981 if bm:
2981 if bm:
2982 output.append(bm)
2982 output.append(bm)
2983
2983
2984 fm.data(node=hex(remoterev))
2984 fm.data(node=hex(remoterev))
2985 fm.data(bookmarks=fm.formatlist(bms, name='bookmark'))
2985 fm.data(bookmarks=fm.formatlist(bms, name='bookmark'))
2986 else:
2986 else:
2987 if rev:
2987 if rev:
2988 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2988 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2989 ctx = scmutil.revsingle(repo, rev, None)
2989 ctx = scmutil.revsingle(repo, rev, None)
2990
2990
2991 if ctx.rev() is None:
2991 if ctx.rev() is None:
2992 ctx = repo[None]
2992 ctx = repo[None]
2993 parents = ctx.parents()
2993 parents = ctx.parents()
2994 taglist = []
2994 taglist = []
2995 for p in parents:
2995 for p in parents:
2996 taglist.extend(p.tags())
2996 taglist.extend(p.tags())
2997
2997
2998 dirty = ""
2998 dirty = ""
2999 if ctx.dirty(missing=True, merge=False, branch=False):
2999 if ctx.dirty(missing=True, merge=False, branch=False):
3000 dirty = '+'
3000 dirty = '+'
3001 fm.data(dirty=dirty)
3001 fm.data(dirty=dirty)
3002
3002
3003 hexoutput = [hexfunc(p.node()) for p in parents]
3003 hexoutput = [hexfunc(p.node()) for p in parents]
3004 if default or id:
3004 if default or id:
3005 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
3005 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
3006 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
3006 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
3007
3007
3008 if num:
3008 if num:
3009 numoutput = ["%d" % p.rev() for p in parents]
3009 numoutput = ["%d" % p.rev() for p in parents]
3010 output.append("%s%s" % ('+'.join(numoutput), dirty))
3010 output.append("%s%s" % ('+'.join(numoutput), dirty))
3011
3011
3012 fn = fm.nested('parents', tmpl='{rev}:{node|formatnode}', sep=' ')
3012 fn = fm.nested('parents', tmpl='{rev}:{node|formatnode}', sep=' ')
3013 for p in parents:
3013 for p in parents:
3014 fn.startitem()
3014 fn.startitem()
3015 fn.data(rev=p.rev())
3015 fn.data(rev=p.rev())
3016 fn.data(node=p.hex())
3016 fn.data(node=p.hex())
3017 fn.context(ctx=p)
3017 fn.context(ctx=p)
3018 fn.end()
3018 fn.end()
3019 else:
3019 else:
3020 hexoutput = hexfunc(ctx.node())
3020 hexoutput = hexfunc(ctx.node())
3021 if default or id:
3021 if default or id:
3022 output = [hexoutput]
3022 output = [hexoutput]
3023 fm.data(id=hexoutput)
3023 fm.data(id=hexoutput)
3024
3024
3025 if num:
3025 if num:
3026 output.append(pycompat.bytestr(ctx.rev()))
3026 output.append(pycompat.bytestr(ctx.rev()))
3027 taglist = ctx.tags()
3027 taglist = ctx.tags()
3028
3028
3029 if default and not ui.quiet:
3029 if default and not ui.quiet:
3030 b = ctx.branch()
3030 b = ctx.branch()
3031 if b != 'default':
3031 if b != 'default':
3032 output.append("(%s)" % b)
3032 output.append("(%s)" % b)
3033
3033
3034 # multiple tags for a single parent separated by '/'
3034 # multiple tags for a single parent separated by '/'
3035 t = '/'.join(taglist)
3035 t = '/'.join(taglist)
3036 if t:
3036 if t:
3037 output.append(t)
3037 output.append(t)
3038
3038
3039 # multiple bookmarks for a single parent separated by '/'
3039 # multiple bookmarks for a single parent separated by '/'
3040 bm = '/'.join(ctx.bookmarks())
3040 bm = '/'.join(ctx.bookmarks())
3041 if bm:
3041 if bm:
3042 output.append(bm)
3042 output.append(bm)
3043 else:
3043 else:
3044 if branch:
3044 if branch:
3045 output.append(ctx.branch())
3045 output.append(ctx.branch())
3046
3046
3047 if tags:
3047 if tags:
3048 output.extend(taglist)
3048 output.extend(taglist)
3049
3049
3050 if bookmarks:
3050 if bookmarks:
3051 output.extend(ctx.bookmarks())
3051 output.extend(ctx.bookmarks())
3052
3052
3053 fm.data(node=ctx.hex())
3053 fm.data(node=ctx.hex())
3054 fm.data(branch=ctx.branch())
3054 fm.data(branch=ctx.branch())
3055 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
3055 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
3056 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
3056 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
3057 fm.context(ctx=ctx)
3057 fm.context(ctx=ctx)
3058
3058
3059 fm.plain("%s\n" % ' '.join(output))
3059 fm.plain("%s\n" % ' '.join(output))
3060 fm.end()
3060 fm.end()
3061
3061
3062 @command('import|patch',
3062 @command('import|patch',
3063 [('p', 'strip', 1,
3063 [('p', 'strip', 1,
3064 _('directory strip option for patch. This has the same '
3064 _('directory strip option for patch. This has the same '
3065 'meaning as the corresponding patch option'), _('NUM')),
3065 'meaning as the corresponding patch option'), _('NUM')),
3066 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3066 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3067 ('e', 'edit', False, _('invoke editor on commit messages')),
3067 ('e', 'edit', False, _('invoke editor on commit messages')),
3068 ('f', 'force', None,
3068 ('f', 'force', None,
3069 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3069 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3070 ('', 'no-commit', None,
3070 ('', 'no-commit', None,
3071 _("don't commit, just update the working directory")),
3071 _("don't commit, just update the working directory")),
3072 ('', 'bypass', None,
3072 ('', 'bypass', None,
3073 _("apply patch without touching the working directory")),
3073 _("apply patch without touching the working directory")),
3074 ('', 'partial', None,
3074 ('', 'partial', None,
3075 _('commit even if some hunks fail')),
3075 _('commit even if some hunks fail')),
3076 ('', 'exact', None,
3076 ('', 'exact', None,
3077 _('abort if patch would apply lossily')),
3077 _('abort if patch would apply lossily')),
3078 ('', 'prefix', '',
3078 ('', 'prefix', '',
3079 _('apply patch to subdirectory'), _('DIR')),
3079 _('apply patch to subdirectory'), _('DIR')),
3080 ('', 'import-branch', None,
3080 ('', 'import-branch', None,
3081 _('use any branch information in patch (implied by --exact)'))] +
3081 _('use any branch information in patch (implied by --exact)'))] +
3082 commitopts + commitopts2 + similarityopts,
3082 commitopts + commitopts2 + similarityopts,
3083 _('[OPTION]... PATCH...'))
3083 _('[OPTION]... PATCH...'))
3084 def import_(ui, repo, patch1=None, *patches, **opts):
3084 def import_(ui, repo, patch1=None, *patches, **opts):
3085 """import an ordered set of patches
3085 """import an ordered set of patches
3086
3086
3087 Import a list of patches and commit them individually (unless
3087 Import a list of patches and commit them individually (unless
3088 --no-commit is specified).
3088 --no-commit is specified).
3089
3089
3090 To read a patch from standard input (stdin), use "-" as the patch
3090 To read a patch from standard input (stdin), use "-" as the patch
3091 name. If a URL is specified, the patch will be downloaded from
3091 name. If a URL is specified, the patch will be downloaded from
3092 there.
3092 there.
3093
3093
3094 Import first applies changes to the working directory (unless
3094 Import first applies changes to the working directory (unless
3095 --bypass is specified), import will abort if there are outstanding
3095 --bypass is specified), import will abort if there are outstanding
3096 changes.
3096 changes.
3097
3097
3098 Use --bypass to apply and commit patches directly to the
3098 Use --bypass to apply and commit patches directly to the
3099 repository, without affecting the working directory. Without
3099 repository, without affecting the working directory. Without
3100 --exact, patches will be applied on top of the working directory
3100 --exact, patches will be applied on top of the working directory
3101 parent revision.
3101 parent revision.
3102
3102
3103 You can import a patch straight from a mail message. Even patches
3103 You can import a patch straight from a mail message. Even patches
3104 as attachments work (to use the body part, it must have type
3104 as attachments work (to use the body part, it must have type
3105 text/plain or text/x-patch). From and Subject headers of email
3105 text/plain or text/x-patch). From and Subject headers of email
3106 message are used as default committer and commit message. All
3106 message are used as default committer and commit message. All
3107 text/plain body parts before first diff are added to the commit
3107 text/plain body parts before first diff are added to the commit
3108 message.
3108 message.
3109
3109
3110 If the imported patch was generated by :hg:`export`, user and
3110 If the imported patch was generated by :hg:`export`, user and
3111 description from patch override values from message headers and
3111 description from patch override values from message headers and
3112 body. Values given on command line with -m/--message and -u/--user
3112 body. Values given on command line with -m/--message and -u/--user
3113 override these.
3113 override these.
3114
3114
3115 If --exact is specified, import will set the working directory to
3115 If --exact is specified, import will set the working directory to
3116 the parent of each patch before applying it, and will abort if the
3116 the parent of each patch before applying it, and will abort if the
3117 resulting changeset has a different ID than the one recorded in
3117 resulting changeset has a different ID than the one recorded in
3118 the patch. This will guard against various ways that portable
3118 the patch. This will guard against various ways that portable
3119 patch formats and mail systems might fail to transfer Mercurial
3119 patch formats and mail systems might fail to transfer Mercurial
3120 data or metadata. See :hg:`bundle` for lossless transmission.
3120 data or metadata. See :hg:`bundle` for lossless transmission.
3121
3121
3122 Use --partial to ensure a changeset will be created from the patch
3122 Use --partial to ensure a changeset will be created from the patch
3123 even if some hunks fail to apply. Hunks that fail to apply will be
3123 even if some hunks fail to apply. Hunks that fail to apply will be
3124 written to a <target-file>.rej file. Conflicts can then be resolved
3124 written to a <target-file>.rej file. Conflicts can then be resolved
3125 by hand before :hg:`commit --amend` is run to update the created
3125 by hand before :hg:`commit --amend` is run to update the created
3126 changeset. This flag exists to let people import patches that
3126 changeset. This flag exists to let people import patches that
3127 partially apply without losing the associated metadata (author,
3127 partially apply without losing the associated metadata (author,
3128 date, description, ...).
3128 date, description, ...).
3129
3129
3130 .. note::
3130 .. note::
3131
3131
3132 When no hunks apply cleanly, :hg:`import --partial` will create
3132 When no hunks apply cleanly, :hg:`import --partial` will create
3133 an empty changeset, importing only the patch metadata.
3133 an empty changeset, importing only the patch metadata.
3134
3134
3135 With -s/--similarity, hg will attempt to discover renames and
3135 With -s/--similarity, hg will attempt to discover renames and
3136 copies in the patch in the same way as :hg:`addremove`.
3136 copies in the patch in the same way as :hg:`addremove`.
3137
3137
3138 It is possible to use external patch programs to perform the patch
3138 It is possible to use external patch programs to perform the patch
3139 by setting the ``ui.patch`` configuration option. For the default
3139 by setting the ``ui.patch`` configuration option. For the default
3140 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3140 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3141 See :hg:`help config` for more information about configuration
3141 See :hg:`help config` for more information about configuration
3142 files and how to use these options.
3142 files and how to use these options.
3143
3143
3144 See :hg:`help dates` for a list of formats valid for -d/--date.
3144 See :hg:`help dates` for a list of formats valid for -d/--date.
3145
3145
3146 .. container:: verbose
3146 .. container:: verbose
3147
3147
3148 Examples:
3148 Examples:
3149
3149
3150 - import a traditional patch from a website and detect renames::
3150 - import a traditional patch from a website and detect renames::
3151
3151
3152 hg import -s 80 http://example.com/bugfix.patch
3152 hg import -s 80 http://example.com/bugfix.patch
3153
3153
3154 - import a changeset from an hgweb server::
3154 - import a changeset from an hgweb server::
3155
3155
3156 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3156 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3157
3157
3158 - import all the patches in an Unix-style mbox::
3158 - import all the patches in an Unix-style mbox::
3159
3159
3160 hg import incoming-patches.mbox
3160 hg import incoming-patches.mbox
3161
3161
3162 - import patches from stdin::
3162 - import patches from stdin::
3163
3163
3164 hg import -
3164 hg import -
3165
3165
3166 - attempt to exactly restore an exported changeset (not always
3166 - attempt to exactly restore an exported changeset (not always
3167 possible)::
3167 possible)::
3168
3168
3169 hg import --exact proposed-fix.patch
3169 hg import --exact proposed-fix.patch
3170
3170
3171 - use an external tool to apply a patch which is too fuzzy for
3171 - use an external tool to apply a patch which is too fuzzy for
3172 the default internal tool.
3172 the default internal tool.
3173
3173
3174 hg import --config ui.patch="patch --merge" fuzzy.patch
3174 hg import --config ui.patch="patch --merge" fuzzy.patch
3175
3175
3176 - change the default fuzzing from 2 to a less strict 7
3176 - change the default fuzzing from 2 to a less strict 7
3177
3177
3178 hg import --config ui.fuzz=7 fuzz.patch
3178 hg import --config ui.fuzz=7 fuzz.patch
3179
3179
3180 Returns 0 on success, 1 on partial success (see --partial).
3180 Returns 0 on success, 1 on partial success (see --partial).
3181 """
3181 """
3182
3182
3183 opts = pycompat.byteskwargs(opts)
3183 opts = pycompat.byteskwargs(opts)
3184 if not patch1:
3184 if not patch1:
3185 raise error.Abort(_('need at least one patch to import'))
3185 raise error.Abort(_('need at least one patch to import'))
3186
3186
3187 patches = (patch1,) + patches
3187 patches = (patch1,) + patches
3188
3188
3189 date = opts.get('date')
3189 date = opts.get('date')
3190 if date:
3190 if date:
3191 opts['date'] = dateutil.parsedate(date)
3191 opts['date'] = dateutil.parsedate(date)
3192
3192
3193 exact = opts.get('exact')
3193 exact = opts.get('exact')
3194 update = not opts.get('bypass')
3194 update = not opts.get('bypass')
3195 if not update and opts.get('no_commit'):
3195 if not update and opts.get('no_commit'):
3196 raise error.Abort(_('cannot use --no-commit with --bypass'))
3196 raise error.Abort(_('cannot use --no-commit with --bypass'))
3197 try:
3197 try:
3198 sim = float(opts.get('similarity') or 0)
3198 sim = float(opts.get('similarity') or 0)
3199 except ValueError:
3199 except ValueError:
3200 raise error.Abort(_('similarity must be a number'))
3200 raise error.Abort(_('similarity must be a number'))
3201 if sim < 0 or sim > 100:
3201 if sim < 0 or sim > 100:
3202 raise error.Abort(_('similarity must be between 0 and 100'))
3202 raise error.Abort(_('similarity must be between 0 and 100'))
3203 if sim and not update:
3203 if sim and not update:
3204 raise error.Abort(_('cannot use --similarity with --bypass'))
3204 raise error.Abort(_('cannot use --similarity with --bypass'))
3205 if exact:
3205 if exact:
3206 if opts.get('edit'):
3206 if opts.get('edit'):
3207 raise error.Abort(_('cannot use --exact with --edit'))
3207 raise error.Abort(_('cannot use --exact with --edit'))
3208 if opts.get('prefix'):
3208 if opts.get('prefix'):
3209 raise error.Abort(_('cannot use --exact with --prefix'))
3209 raise error.Abort(_('cannot use --exact with --prefix'))
3210
3210
3211 base = opts["base"]
3211 base = opts["base"]
3212 msgs = []
3212 msgs = []
3213 ret = 0
3213 ret = 0
3214
3214
3215 with repo.wlock():
3215 with repo.wlock():
3216 if update:
3216 if update:
3217 cmdutil.checkunfinished(repo)
3217 cmdutil.checkunfinished(repo)
3218 if (exact or not opts.get('force')):
3218 if (exact or not opts.get('force')):
3219 cmdutil.bailifchanged(repo)
3219 cmdutil.bailifchanged(repo)
3220
3220
3221 if not opts.get('no_commit'):
3221 if not opts.get('no_commit'):
3222 lock = repo.lock
3222 lock = repo.lock
3223 tr = lambda: repo.transaction('import')
3223 tr = lambda: repo.transaction('import')
3224 dsguard = util.nullcontextmanager
3224 dsguard = util.nullcontextmanager
3225 else:
3225 else:
3226 lock = util.nullcontextmanager
3226 lock = util.nullcontextmanager
3227 tr = util.nullcontextmanager
3227 tr = util.nullcontextmanager
3228 dsguard = lambda: dirstateguard.dirstateguard(repo, 'import')
3228 dsguard = lambda: dirstateguard.dirstateguard(repo, 'import')
3229 with lock(), tr(), dsguard():
3229 with lock(), tr(), dsguard():
3230 parents = repo[None].parents()
3230 parents = repo[None].parents()
3231 for patchurl in patches:
3231 for patchurl in patches:
3232 if patchurl == '-':
3232 if patchurl == '-':
3233 ui.status(_('applying patch from stdin\n'))
3233 ui.status(_('applying patch from stdin\n'))
3234 patchfile = ui.fin
3234 patchfile = ui.fin
3235 patchurl = 'stdin' # for error message
3235 patchurl = 'stdin' # for error message
3236 else:
3236 else:
3237 patchurl = os.path.join(base, patchurl)
3237 patchurl = os.path.join(base, patchurl)
3238 ui.status(_('applying %s\n') % patchurl)
3238 ui.status(_('applying %s\n') % patchurl)
3239 patchfile = hg.openpath(ui, patchurl)
3239 patchfile = hg.openpath(ui, patchurl)
3240
3240
3241 haspatch = False
3241 haspatch = False
3242 for hunk in patch.split(patchfile):
3242 for hunk in patch.split(patchfile):
3243 with patch.extract(ui, hunk) as patchdata:
3243 with patch.extract(ui, hunk) as patchdata:
3244 msg, node, rej = cmdutil.tryimportone(ui, repo,
3244 msg, node, rej = cmdutil.tryimportone(ui, repo,
3245 patchdata,
3245 patchdata,
3246 parents, opts,
3246 parents, opts,
3247 msgs, hg.clean)
3247 msgs, hg.clean)
3248 if msg:
3248 if msg:
3249 haspatch = True
3249 haspatch = True
3250 ui.note(msg + '\n')
3250 ui.note(msg + '\n')
3251 if update or exact:
3251 if update or exact:
3252 parents = repo[None].parents()
3252 parents = repo[None].parents()
3253 else:
3253 else:
3254 parents = [repo[node]]
3254 parents = [repo[node]]
3255 if rej:
3255 if rej:
3256 ui.write_err(_("patch applied partially\n"))
3256 ui.write_err(_("patch applied partially\n"))
3257 ui.write_err(_("(fix the .rej files and run "
3257 ui.write_err(_("(fix the .rej files and run "
3258 "`hg commit --amend`)\n"))
3258 "`hg commit --amend`)\n"))
3259 ret = 1
3259 ret = 1
3260 break
3260 break
3261
3261
3262 if not haspatch:
3262 if not haspatch:
3263 raise error.Abort(_('%s: no diffs found') % patchurl)
3263 raise error.Abort(_('%s: no diffs found') % patchurl)
3264
3264
3265 if msgs:
3265 if msgs:
3266 repo.savecommitmessage('\n* * *\n'.join(msgs))
3266 repo.savecommitmessage('\n* * *\n'.join(msgs))
3267 return ret
3267 return ret
3268
3268
3269 @command('incoming|in',
3269 @command('incoming|in',
3270 [('f', 'force', None,
3270 [('f', 'force', None,
3271 _('run even if remote repository is unrelated')),
3271 _('run even if remote repository is unrelated')),
3272 ('n', 'newest-first', None, _('show newest record first')),
3272 ('n', 'newest-first', None, _('show newest record first')),
3273 ('', 'bundle', '',
3273 ('', 'bundle', '',
3274 _('file to store the bundles into'), _('FILE')),
3274 _('file to store the bundles into'), _('FILE')),
3275 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3275 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3276 ('B', 'bookmarks', False, _("compare bookmarks")),
3276 ('B', 'bookmarks', False, _("compare bookmarks")),
3277 ('b', 'branch', [],
3277 ('b', 'branch', [],
3278 _('a specific branch you would like to pull'), _('BRANCH')),
3278 _('a specific branch you would like to pull'), _('BRANCH')),
3279 ] + logopts + remoteopts + subrepoopts,
3279 ] + logopts + remoteopts + subrepoopts,
3280 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3280 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3281 def incoming(ui, repo, source="default", **opts):
3281 def incoming(ui, repo, source="default", **opts):
3282 """show new changesets found in source
3282 """show new changesets found in source
3283
3283
3284 Show new changesets found in the specified path/URL or the default
3284 Show new changesets found in the specified path/URL or the default
3285 pull location. These are the changesets that would have been pulled
3285 pull location. These are the changesets that would have been pulled
3286 by :hg:`pull` at the time you issued this command.
3286 by :hg:`pull` at the time you issued this command.
3287
3287
3288 See pull for valid source format details.
3288 See pull for valid source format details.
3289
3289
3290 .. container:: verbose
3290 .. container:: verbose
3291
3291
3292 With -B/--bookmarks, the result of bookmark comparison between
3292 With -B/--bookmarks, the result of bookmark comparison between
3293 local and remote repositories is displayed. With -v/--verbose,
3293 local and remote repositories is displayed. With -v/--verbose,
3294 status is also displayed for each bookmark like below::
3294 status is also displayed for each bookmark like below::
3295
3295
3296 BM1 01234567890a added
3296 BM1 01234567890a added
3297 BM2 1234567890ab advanced
3297 BM2 1234567890ab advanced
3298 BM3 234567890abc diverged
3298 BM3 234567890abc diverged
3299 BM4 34567890abcd changed
3299 BM4 34567890abcd changed
3300
3300
3301 The action taken locally when pulling depends on the
3301 The action taken locally when pulling depends on the
3302 status of each bookmark:
3302 status of each bookmark:
3303
3303
3304 :``added``: pull will create it
3304 :``added``: pull will create it
3305 :``advanced``: pull will update it
3305 :``advanced``: pull will update it
3306 :``diverged``: pull will create a divergent bookmark
3306 :``diverged``: pull will create a divergent bookmark
3307 :``changed``: result depends on remote changesets
3307 :``changed``: result depends on remote changesets
3308
3308
3309 From the point of view of pulling behavior, bookmark
3309 From the point of view of pulling behavior, bookmark
3310 existing only in the remote repository are treated as ``added``,
3310 existing only in the remote repository are treated as ``added``,
3311 even if it is in fact locally deleted.
3311 even if it is in fact locally deleted.
3312
3312
3313 .. container:: verbose
3313 .. container:: verbose
3314
3314
3315 For remote repository, using --bundle avoids downloading the
3315 For remote repository, using --bundle avoids downloading the
3316 changesets twice if the incoming is followed by a pull.
3316 changesets twice if the incoming is followed by a pull.
3317
3317
3318 Examples:
3318 Examples:
3319
3319
3320 - show incoming changes with patches and full description::
3320 - show incoming changes with patches and full description::
3321
3321
3322 hg incoming -vp
3322 hg incoming -vp
3323
3323
3324 - show incoming changes excluding merges, store a bundle::
3324 - show incoming changes excluding merges, store a bundle::
3325
3325
3326 hg in -vpM --bundle incoming.hg
3326 hg in -vpM --bundle incoming.hg
3327 hg pull incoming.hg
3327 hg pull incoming.hg
3328
3328
3329 - briefly list changes inside a bundle::
3329 - briefly list changes inside a bundle::
3330
3330
3331 hg in changes.hg -T "{desc|firstline}\\n"
3331 hg in changes.hg -T "{desc|firstline}\\n"
3332
3332
3333 Returns 0 if there are incoming changes, 1 otherwise.
3333 Returns 0 if there are incoming changes, 1 otherwise.
3334 """
3334 """
3335 opts = pycompat.byteskwargs(opts)
3335 opts = pycompat.byteskwargs(opts)
3336 if opts.get('graph'):
3336 if opts.get('graph'):
3337 logcmdutil.checkunsupportedgraphflags([], opts)
3337 logcmdutil.checkunsupportedgraphflags([], opts)
3338 def display(other, chlist, displayer):
3338 def display(other, chlist, displayer):
3339 revdag = logcmdutil.graphrevs(other, chlist, opts)
3339 revdag = logcmdutil.graphrevs(other, chlist, opts)
3340 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3340 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3341 graphmod.asciiedges)
3341 graphmod.asciiedges)
3342
3342
3343 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3343 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3344 return 0
3344 return 0
3345
3345
3346 if opts.get('bundle') and opts.get('subrepos'):
3346 if opts.get('bundle') and opts.get('subrepos'):
3347 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3347 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3348
3348
3349 if opts.get('bookmarks'):
3349 if opts.get('bookmarks'):
3350 source, branches = hg.parseurl(ui.expandpath(source),
3350 source, branches = hg.parseurl(ui.expandpath(source),
3351 opts.get('branch'))
3351 opts.get('branch'))
3352 other = hg.peer(repo, opts, source)
3352 other = hg.peer(repo, opts, source)
3353 if 'bookmarks' not in other.listkeys('namespaces'):
3353 if 'bookmarks' not in other.listkeys('namespaces'):
3354 ui.warn(_("remote doesn't support bookmarks\n"))
3354 ui.warn(_("remote doesn't support bookmarks\n"))
3355 return 0
3355 return 0
3356 ui.pager('incoming')
3356 ui.pager('incoming')
3357 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3357 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3358 return bookmarks.incoming(ui, repo, other)
3358 return bookmarks.incoming(ui, repo, other)
3359
3359
3360 repo._subtoppath = ui.expandpath(source)
3360 repo._subtoppath = ui.expandpath(source)
3361 try:
3361 try:
3362 return hg.incoming(ui, repo, source, opts)
3362 return hg.incoming(ui, repo, source, opts)
3363 finally:
3363 finally:
3364 del repo._subtoppath
3364 del repo._subtoppath
3365
3365
3366
3366
3367 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3367 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3368 norepo=True)
3368 norepo=True)
3369 def init(ui, dest=".", **opts):
3369 def init(ui, dest=".", **opts):
3370 """create a new repository in the given directory
3370 """create a new repository in the given directory
3371
3371
3372 Initialize a new repository in the given directory. If the given
3372 Initialize a new repository in the given directory. If the given
3373 directory does not exist, it will be created.
3373 directory does not exist, it will be created.
3374
3374
3375 If no directory is given, the current directory is used.
3375 If no directory is given, the current directory is used.
3376
3376
3377 It is possible to specify an ``ssh://`` URL as the destination.
3377 It is possible to specify an ``ssh://`` URL as the destination.
3378 See :hg:`help urls` for more information.
3378 See :hg:`help urls` for more information.
3379
3379
3380 Returns 0 on success.
3380 Returns 0 on success.
3381 """
3381 """
3382 opts = pycompat.byteskwargs(opts)
3382 opts = pycompat.byteskwargs(opts)
3383 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3383 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3384
3384
3385 @command('locate',
3385 @command('locate',
3386 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3386 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3387 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3387 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3388 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3388 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3389 ] + walkopts,
3389 ] + walkopts,
3390 _('[OPTION]... [PATTERN]...'))
3390 _('[OPTION]... [PATTERN]...'))
3391 def locate(ui, repo, *pats, **opts):
3391 def locate(ui, repo, *pats, **opts):
3392 """locate files matching specific patterns (DEPRECATED)
3392 """locate files matching specific patterns (DEPRECATED)
3393
3393
3394 Print files under Mercurial control in the working directory whose
3394 Print files under Mercurial control in the working directory whose
3395 names match the given patterns.
3395 names match the given patterns.
3396
3396
3397 By default, this command searches all directories in the working
3397 By default, this command searches all directories in the working
3398 directory. To search just the current directory and its
3398 directory. To search just the current directory and its
3399 subdirectories, use "--include .".
3399 subdirectories, use "--include .".
3400
3400
3401 If no patterns are given to match, this command prints the names
3401 If no patterns are given to match, this command prints the names
3402 of all files under Mercurial control in the working directory.
3402 of all files under Mercurial control in the working directory.
3403
3403
3404 If you want to feed the output of this command into the "xargs"
3404 If you want to feed the output of this command into the "xargs"
3405 command, use the -0 option to both this command and "xargs". This
3405 command, use the -0 option to both this command and "xargs". This
3406 will avoid the problem of "xargs" treating single filenames that
3406 will avoid the problem of "xargs" treating single filenames that
3407 contain whitespace as multiple filenames.
3407 contain whitespace as multiple filenames.
3408
3408
3409 See :hg:`help files` for a more versatile command.
3409 See :hg:`help files` for a more versatile command.
3410
3410
3411 Returns 0 if a match is found, 1 otherwise.
3411 Returns 0 if a match is found, 1 otherwise.
3412 """
3412 """
3413 opts = pycompat.byteskwargs(opts)
3413 opts = pycompat.byteskwargs(opts)
3414 if opts.get('print0'):
3414 if opts.get('print0'):
3415 end = '\0'
3415 end = '\0'
3416 else:
3416 else:
3417 end = '\n'
3417 end = '\n'
3418 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3418 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3419
3419
3420 ret = 1
3420 ret = 1
3421 m = scmutil.match(ctx, pats, opts, default='relglob',
3421 m = scmutil.match(ctx, pats, opts, default='relglob',
3422 badfn=lambda x, y: False)
3422 badfn=lambda x, y: False)
3423
3423
3424 ui.pager('locate')
3424 ui.pager('locate')
3425 if ctx.rev() is None:
3425 if ctx.rev() is None:
3426 # When run on the working copy, "locate" includes removed files, so
3426 # When run on the working copy, "locate" includes removed files, so
3427 # we get the list of files from the dirstate.
3427 # we get the list of files from the dirstate.
3428 filesgen = sorted(repo.dirstate.matches(m))
3428 filesgen = sorted(repo.dirstate.matches(m))
3429 else:
3429 else:
3430 filesgen = ctx.matches(m)
3430 filesgen = ctx.matches(m)
3431 for abs in filesgen:
3431 for abs in filesgen:
3432 if opts.get('fullpath'):
3432 if opts.get('fullpath'):
3433 ui.write(repo.wjoin(abs), end)
3433 ui.write(repo.wjoin(abs), end)
3434 else:
3434 else:
3435 ui.write(((pats and m.rel(abs)) or abs), end)
3435 ui.write(((pats and m.rel(abs)) or abs), end)
3436 ret = 0
3436 ret = 0
3437
3437
3438 return ret
3438 return ret
3439
3439
3440 @command('^log|history',
3440 @command('^log|history',
3441 [('f', 'follow', None,
3441 [('f', 'follow', None,
3442 _('follow changeset history, or file history across copies and renames')),
3442 _('follow changeset history, or file history across copies and renames')),
3443 ('', 'follow-first', None,
3443 ('', 'follow-first', None,
3444 _('only follow the first parent of merge changesets (DEPRECATED)')),
3444 _('only follow the first parent of merge changesets (DEPRECATED)')),
3445 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3445 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3446 ('C', 'copies', None, _('show copied files')),
3446 ('C', 'copies', None, _('show copied files')),
3447 ('k', 'keyword', [],
3447 ('k', 'keyword', [],
3448 _('do case-insensitive search for a given text'), _('TEXT')),
3448 _('do case-insensitive search for a given text'), _('TEXT')),
3449 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3449 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3450 ('L', 'line-range', [],
3450 ('L', 'line-range', [],
3451 _('follow line range of specified file (EXPERIMENTAL)'),
3451 _('follow line range of specified file (EXPERIMENTAL)'),
3452 _('FILE,RANGE')),
3452 _('FILE,RANGE')),
3453 ('', 'removed', None, _('include revisions where files were removed')),
3453 ('', 'removed', None, _('include revisions where files were removed')),
3454 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3454 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3455 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3455 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3456 ('', 'only-branch', [],
3456 ('', 'only-branch', [],
3457 _('show only changesets within the given named branch (DEPRECATED)'),
3457 _('show only changesets within the given named branch (DEPRECATED)'),
3458 _('BRANCH')),
3458 _('BRANCH')),
3459 ('b', 'branch', [],
3459 ('b', 'branch', [],
3460 _('show changesets within the given named branch'), _('BRANCH')),
3460 _('show changesets within the given named branch'), _('BRANCH')),
3461 ('P', 'prune', [],
3461 ('P', 'prune', [],
3462 _('do not display revision or any of its ancestors'), _('REV')),
3462 _('do not display revision or any of its ancestors'), _('REV')),
3463 ] + logopts + walkopts,
3463 ] + logopts + walkopts,
3464 _('[OPTION]... [FILE]'),
3464 _('[OPTION]... [FILE]'),
3465 inferrepo=True,
3465 inferrepo=True,
3466 intents={INTENT_READONLY})
3466 intents={INTENT_READONLY})
3467 def log(ui, repo, *pats, **opts):
3467 def log(ui, repo, *pats, **opts):
3468 """show revision history of entire repository or files
3468 """show revision history of entire repository or files
3469
3469
3470 Print the revision history of the specified files or the entire
3470 Print the revision history of the specified files or the entire
3471 project.
3471 project.
3472
3472
3473 If no revision range is specified, the default is ``tip:0`` unless
3473 If no revision range is specified, the default is ``tip:0`` unless
3474 --follow is set, in which case the working directory parent is
3474 --follow is set, in which case the working directory parent is
3475 used as the starting revision.
3475 used as the starting revision.
3476
3476
3477 File history is shown without following rename or copy history of
3477 File history is shown without following rename or copy history of
3478 files. Use -f/--follow with a filename to follow history across
3478 files. Use -f/--follow with a filename to follow history across
3479 renames and copies. --follow without a filename will only show
3479 renames and copies. --follow without a filename will only show
3480 ancestors of the starting revision.
3480 ancestors of the starting revision.
3481
3481
3482 By default this command prints revision number and changeset id,
3482 By default this command prints revision number and changeset id,
3483 tags, non-trivial parents, user, date and time, and a summary for
3483 tags, non-trivial parents, user, date and time, and a summary for
3484 each commit. When the -v/--verbose switch is used, the list of
3484 each commit. When the -v/--verbose switch is used, the list of
3485 changed files and full commit message are shown.
3485 changed files and full commit message are shown.
3486
3486
3487 With --graph the revisions are shown as an ASCII art DAG with the most
3487 With --graph the revisions are shown as an ASCII art DAG with the most
3488 recent changeset at the top.
3488 recent changeset at the top.
3489 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3489 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3490 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3490 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3491 changeset from the lines below is a parent of the 'o' merge on the same
3491 changeset from the lines below is a parent of the 'o' merge on the same
3492 line.
3492 line.
3493 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3493 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3494 of a '|' indicates one or more revisions in a path are omitted.
3494 of a '|' indicates one or more revisions in a path are omitted.
3495
3495
3496 .. container:: verbose
3496 .. container:: verbose
3497
3497
3498 Use -L/--line-range FILE,M:N options to follow the history of lines
3498 Use -L/--line-range FILE,M:N options to follow the history of lines
3499 from M to N in FILE. With -p/--patch only diff hunks affecting
3499 from M to N in FILE. With -p/--patch only diff hunks affecting
3500 specified line range will be shown. This option requires --follow;
3500 specified line range will be shown. This option requires --follow;
3501 it can be specified multiple times. Currently, this option is not
3501 it can be specified multiple times. Currently, this option is not
3502 compatible with --graph. This option is experimental.
3502 compatible with --graph. This option is experimental.
3503
3503
3504 .. note::
3504 .. note::
3505
3505
3506 :hg:`log --patch` may generate unexpected diff output for merge
3506 :hg:`log --patch` may generate unexpected diff output for merge
3507 changesets, as it will only compare the merge changeset against
3507 changesets, as it will only compare the merge changeset against
3508 its first parent. Also, only files different from BOTH parents
3508 its first parent. Also, only files different from BOTH parents
3509 will appear in files:.
3509 will appear in files:.
3510
3510
3511 .. note::
3511 .. note::
3512
3512
3513 For performance reasons, :hg:`log FILE` may omit duplicate changes
3513 For performance reasons, :hg:`log FILE` may omit duplicate changes
3514 made on branches and will not show removals or mode changes. To
3514 made on branches and will not show removals or mode changes. To
3515 see all such changes, use the --removed switch.
3515 see all such changes, use the --removed switch.
3516
3516
3517 .. container:: verbose
3517 .. container:: verbose
3518
3518
3519 .. note::
3519 .. note::
3520
3520
3521 The history resulting from -L/--line-range options depends on diff
3521 The history resulting from -L/--line-range options depends on diff
3522 options; for instance if white-spaces are ignored, respective changes
3522 options; for instance if white-spaces are ignored, respective changes
3523 with only white-spaces in specified line range will not be listed.
3523 with only white-spaces in specified line range will not be listed.
3524
3524
3525 .. container:: verbose
3525 .. container:: verbose
3526
3526
3527 Some examples:
3527 Some examples:
3528
3528
3529 - changesets with full descriptions and file lists::
3529 - changesets with full descriptions and file lists::
3530
3530
3531 hg log -v
3531 hg log -v
3532
3532
3533 - changesets ancestral to the working directory::
3533 - changesets ancestral to the working directory::
3534
3534
3535 hg log -f
3535 hg log -f
3536
3536
3537 - last 10 commits on the current branch::
3537 - last 10 commits on the current branch::
3538
3538
3539 hg log -l 10 -b .
3539 hg log -l 10 -b .
3540
3540
3541 - changesets showing all modifications of a file, including removals::
3541 - changesets showing all modifications of a file, including removals::
3542
3542
3543 hg log --removed file.c
3543 hg log --removed file.c
3544
3544
3545 - all changesets that touch a directory, with diffs, excluding merges::
3545 - all changesets that touch a directory, with diffs, excluding merges::
3546
3546
3547 hg log -Mp lib/
3547 hg log -Mp lib/
3548
3548
3549 - all revision numbers that match a keyword::
3549 - all revision numbers that match a keyword::
3550
3550
3551 hg log -k bug --template "{rev}\\n"
3551 hg log -k bug --template "{rev}\\n"
3552
3552
3553 - the full hash identifier of the working directory parent::
3553 - the full hash identifier of the working directory parent::
3554
3554
3555 hg log -r . --template "{node}\\n"
3555 hg log -r . --template "{node}\\n"
3556
3556
3557 - list available log templates::
3557 - list available log templates::
3558
3558
3559 hg log -T list
3559 hg log -T list
3560
3560
3561 - check if a given changeset is included in a tagged release::
3561 - check if a given changeset is included in a tagged release::
3562
3562
3563 hg log -r "a21ccf and ancestor(1.9)"
3563 hg log -r "a21ccf and ancestor(1.9)"
3564
3564
3565 - find all changesets by some user in a date range::
3565 - find all changesets by some user in a date range::
3566
3566
3567 hg log -k alice -d "may 2008 to jul 2008"
3567 hg log -k alice -d "may 2008 to jul 2008"
3568
3568
3569 - summary of all changesets after the last tag::
3569 - summary of all changesets after the last tag::
3570
3570
3571 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3571 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3572
3572
3573 - changesets touching lines 13 to 23 for file.c::
3573 - changesets touching lines 13 to 23 for file.c::
3574
3574
3575 hg log -L file.c,13:23
3575 hg log -L file.c,13:23
3576
3576
3577 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3577 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3578 main.c with patch::
3578 main.c with patch::
3579
3579
3580 hg log -L file.c,13:23 -L main.c,2:6 -p
3580 hg log -L file.c,13:23 -L main.c,2:6 -p
3581
3581
3582 See :hg:`help dates` for a list of formats valid for -d/--date.
3582 See :hg:`help dates` for a list of formats valid for -d/--date.
3583
3583
3584 See :hg:`help revisions` for more about specifying and ordering
3584 See :hg:`help revisions` for more about specifying and ordering
3585 revisions.
3585 revisions.
3586
3586
3587 See :hg:`help templates` for more about pre-packaged styles and
3587 See :hg:`help templates` for more about pre-packaged styles and
3588 specifying custom templates. The default template used by the log
3588 specifying custom templates. The default template used by the log
3589 command can be customized via the ``ui.logtemplate`` configuration
3589 command can be customized via the ``ui.logtemplate`` configuration
3590 setting.
3590 setting.
3591
3591
3592 Returns 0 on success.
3592 Returns 0 on success.
3593
3593
3594 """
3594 """
3595 opts = pycompat.byteskwargs(opts)
3595 opts = pycompat.byteskwargs(opts)
3596 linerange = opts.get('line_range')
3596 linerange = opts.get('line_range')
3597
3597
3598 if linerange and not opts.get('follow'):
3598 if linerange and not opts.get('follow'):
3599 raise error.Abort(_('--line-range requires --follow'))
3599 raise error.Abort(_('--line-range requires --follow'))
3600
3600
3601 if linerange and pats:
3601 if linerange and pats:
3602 # TODO: take pats as patterns with no line-range filter
3602 # TODO: take pats as patterns with no line-range filter
3603 raise error.Abort(
3603 raise error.Abort(
3604 _('FILE arguments are not compatible with --line-range option')
3604 _('FILE arguments are not compatible with --line-range option')
3605 )
3605 )
3606
3606
3607 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3607 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3608 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3608 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3609 if linerange:
3609 if linerange:
3610 # TODO: should follow file history from logcmdutil._initialrevs(),
3610 # TODO: should follow file history from logcmdutil._initialrevs(),
3611 # then filter the result by logcmdutil._makerevset() and --limit
3611 # then filter the result by logcmdutil._makerevset() and --limit
3612 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3612 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3613
3613
3614 getrenamed = None
3614 getrenamed = None
3615 if opts.get('copies'):
3615 if opts.get('copies'):
3616 endrev = None
3616 endrev = None
3617 if revs:
3617 if revs:
3618 endrev = revs.max() + 1
3618 endrev = revs.max() + 1
3619 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3619 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3620
3620
3621 ui.pager('log')
3621 ui.pager('log')
3622 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3622 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3623 buffered=True)
3623 buffered=True)
3624 if opts.get('graph'):
3624 if opts.get('graph'):
3625 displayfn = logcmdutil.displaygraphrevs
3625 displayfn = logcmdutil.displaygraphrevs
3626 else:
3626 else:
3627 displayfn = logcmdutil.displayrevs
3627 displayfn = logcmdutil.displayrevs
3628 displayfn(ui, repo, revs, displayer, getrenamed)
3628 displayfn(ui, repo, revs, displayer, getrenamed)
3629
3629
3630 @command('manifest',
3630 @command('manifest',
3631 [('r', 'rev', '', _('revision to display'), _('REV')),
3631 [('r', 'rev', '', _('revision to display'), _('REV')),
3632 ('', 'all', False, _("list files from all revisions"))]
3632 ('', 'all', False, _("list files from all revisions"))]
3633 + formatteropts,
3633 + formatteropts,
3634 _('[-r REV]'),
3634 _('[-r REV]'),
3635 intents={INTENT_READONLY})
3635 intents={INTENT_READONLY})
3636 def manifest(ui, repo, node=None, rev=None, **opts):
3636 def manifest(ui, repo, node=None, rev=None, **opts):
3637 """output the current or given revision of the project manifest
3637 """output the current or given revision of the project manifest
3638
3638
3639 Print a list of version controlled files for the given revision.
3639 Print a list of version controlled files for the given revision.
3640 If no revision is given, the first parent of the working directory
3640 If no revision is given, the first parent of the working directory
3641 is used, or the null revision if no revision is checked out.
3641 is used, or the null revision if no revision is checked out.
3642
3642
3643 With -v, print file permissions, symlink and executable bits.
3643 With -v, print file permissions, symlink and executable bits.
3644 With --debug, print file revision hashes.
3644 With --debug, print file revision hashes.
3645
3645
3646 If option --all is specified, the list of all files from all revisions
3646 If option --all is specified, the list of all files from all revisions
3647 is printed. This includes deleted and renamed files.
3647 is printed. This includes deleted and renamed files.
3648
3648
3649 Returns 0 on success.
3649 Returns 0 on success.
3650 """
3650 """
3651 opts = pycompat.byteskwargs(opts)
3651 opts = pycompat.byteskwargs(opts)
3652 fm = ui.formatter('manifest', opts)
3652 fm = ui.formatter('manifest', opts)
3653
3653
3654 if opts.get('all'):
3654 if opts.get('all'):
3655 if rev or node:
3655 if rev or node:
3656 raise error.Abort(_("can't specify a revision with --all"))
3656 raise error.Abort(_("can't specify a revision with --all"))
3657
3657
3658 res = set()
3658 res = set()
3659 for rev in repo:
3659 for rev in repo:
3660 ctx = repo[rev]
3660 ctx = repo[rev]
3661 res |= set(ctx.files())
3661 res |= set(ctx.files())
3662
3662
3663 ui.pager('manifest')
3663 ui.pager('manifest')
3664 for f in sorted(res):
3664 for f in sorted(res):
3665 fm.startitem()
3665 fm.startitem()
3666 fm.write("path", '%s\n', f)
3666 fm.write("path", '%s\n', f)
3667 fm.end()
3667 fm.end()
3668 return
3668 return
3669
3669
3670 if rev and node:
3670 if rev and node:
3671 raise error.Abort(_("please specify just one revision"))
3671 raise error.Abort(_("please specify just one revision"))
3672
3672
3673 if not node:
3673 if not node:
3674 node = rev
3674 node = rev
3675
3675
3676 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3676 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3677 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3677 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3678 if node:
3678 if node:
3679 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3679 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3680 ctx = scmutil.revsingle(repo, node)
3680 ctx = scmutil.revsingle(repo, node)
3681 mf = ctx.manifest()
3681 mf = ctx.manifest()
3682 ui.pager('manifest')
3682 ui.pager('manifest')
3683 for f in ctx:
3683 for f in ctx:
3684 fm.startitem()
3684 fm.startitem()
3685 fl = ctx[f].flags()
3685 fl = ctx[f].flags()
3686 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3686 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3687 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3687 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3688 fm.write('path', '%s\n', f)
3688 fm.write('path', '%s\n', f)
3689 fm.end()
3689 fm.end()
3690
3690
3691 @command('^merge',
3691 @command('^merge',
3692 [('f', 'force', None,
3692 [('f', 'force', None,
3693 _('force a merge including outstanding changes (DEPRECATED)')),
3693 _('force a merge including outstanding changes (DEPRECATED)')),
3694 ('r', 'rev', '', _('revision to merge'), _('REV')),
3694 ('r', 'rev', '', _('revision to merge'), _('REV')),
3695 ('P', 'preview', None,
3695 ('P', 'preview', None,
3696 _('review revisions to merge (no merge is performed)')),
3696 _('review revisions to merge (no merge is performed)')),
3697 ('', 'abort', None, _('abort the ongoing merge')),
3697 ('', 'abort', None, _('abort the ongoing merge')),
3698 ] + mergetoolopts,
3698 ] + mergetoolopts,
3699 _('[-P] [[-r] REV]'))
3699 _('[-P] [[-r] REV]'))
3700 def merge(ui, repo, node=None, **opts):
3700 def merge(ui, repo, node=None, **opts):
3701 """merge another revision into working directory
3701 """merge another revision into working directory
3702
3702
3703 The current working directory is updated with all changes made in
3703 The current working directory is updated with all changes made in
3704 the requested revision since the last common predecessor revision.
3704 the requested revision since the last common predecessor revision.
3705
3705
3706 Files that changed between either parent are marked as changed for
3706 Files that changed between either parent are marked as changed for
3707 the next commit and a commit must be performed before any further
3707 the next commit and a commit must be performed before any further
3708 updates to the repository are allowed. The next commit will have
3708 updates to the repository are allowed. The next commit will have
3709 two parents.
3709 two parents.
3710
3710
3711 ``--tool`` can be used to specify the merge tool used for file
3711 ``--tool`` can be used to specify the merge tool used for file
3712 merges. It overrides the HGMERGE environment variable and your
3712 merges. It overrides the HGMERGE environment variable and your
3713 configuration files. See :hg:`help merge-tools` for options.
3713 configuration files. See :hg:`help merge-tools` for options.
3714
3714
3715 If no revision is specified, the working directory's parent is a
3715 If no revision is specified, the working directory's parent is a
3716 head revision, and the current branch contains exactly one other
3716 head revision, and the current branch contains exactly one other
3717 head, the other head is merged with by default. Otherwise, an
3717 head, the other head is merged with by default. Otherwise, an
3718 explicit revision with which to merge with must be provided.
3718 explicit revision with which to merge with must be provided.
3719
3719
3720 See :hg:`help resolve` for information on handling file conflicts.
3720 See :hg:`help resolve` for information on handling file conflicts.
3721
3721
3722 To undo an uncommitted merge, use :hg:`merge --abort` which
3722 To undo an uncommitted merge, use :hg:`merge --abort` which
3723 will check out a clean copy of the original merge parent, losing
3723 will check out a clean copy of the original merge parent, losing
3724 all changes.
3724 all changes.
3725
3725
3726 Returns 0 on success, 1 if there are unresolved files.
3726 Returns 0 on success, 1 if there are unresolved files.
3727 """
3727 """
3728
3728
3729 opts = pycompat.byteskwargs(opts)
3729 opts = pycompat.byteskwargs(opts)
3730 abort = opts.get('abort')
3730 abort = opts.get('abort')
3731 if abort and repo.dirstate.p2() == nullid:
3731 if abort and repo.dirstate.p2() == nullid:
3732 cmdutil.wrongtooltocontinue(repo, _('merge'))
3732 cmdutil.wrongtooltocontinue(repo, _('merge'))
3733 if abort:
3733 if abort:
3734 if node:
3734 if node:
3735 raise error.Abort(_("cannot specify a node with --abort"))
3735 raise error.Abort(_("cannot specify a node with --abort"))
3736 if opts.get('rev'):
3736 if opts.get('rev'):
3737 raise error.Abort(_("cannot specify both --rev and --abort"))
3737 raise error.Abort(_("cannot specify both --rev and --abort"))
3738 if opts.get('preview'):
3738 if opts.get('preview'):
3739 raise error.Abort(_("cannot specify --preview with --abort"))
3739 raise error.Abort(_("cannot specify --preview with --abort"))
3740 if opts.get('rev') and node:
3740 if opts.get('rev') and node:
3741 raise error.Abort(_("please specify just one revision"))
3741 raise error.Abort(_("please specify just one revision"))
3742 if not node:
3742 if not node:
3743 node = opts.get('rev')
3743 node = opts.get('rev')
3744
3744
3745 if node:
3745 if node:
3746 node = scmutil.revsingle(repo, node).node()
3746 node = scmutil.revsingle(repo, node).node()
3747
3747
3748 if not node and not abort:
3748 if not node and not abort:
3749 node = repo[destutil.destmerge(repo)].node()
3749 node = repo[destutil.destmerge(repo)].node()
3750
3750
3751 if opts.get('preview'):
3751 if opts.get('preview'):
3752 # find nodes that are ancestors of p2 but not of p1
3752 # find nodes that are ancestors of p2 but not of p1
3753 p1 = repo.lookup('.')
3753 p1 = repo.lookup('.')
3754 p2 = node
3754 p2 = node
3755 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3755 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3756
3756
3757 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3757 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3758 for node in nodes:
3758 for node in nodes:
3759 displayer.show(repo[node])
3759 displayer.show(repo[node])
3760 displayer.close()
3760 displayer.close()
3761 return 0
3761 return 0
3762
3762
3763 # ui.forcemerge is an internal variable, do not document
3763 # ui.forcemerge is an internal variable, do not document
3764 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
3764 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
3765 with ui.configoverride(overrides, 'merge'):
3765 with ui.configoverride(overrides, 'merge'):
3766 force = opts.get('force')
3766 force = opts.get('force')
3767 labels = ['working copy', 'merge rev']
3767 labels = ['working copy', 'merge rev']
3768 return hg.merge(repo, node, force=force, mergeforce=force,
3768 return hg.merge(repo, node, force=force, mergeforce=force,
3769 labels=labels, abort=abort)
3769 labels=labels, abort=abort)
3770
3770
3771 @command('outgoing|out',
3771 @command('outgoing|out',
3772 [('f', 'force', None, _('run even when the destination is unrelated')),
3772 [('f', 'force', None, _('run even when the destination is unrelated')),
3773 ('r', 'rev', [],
3773 ('r', 'rev', [],
3774 _('a changeset intended to be included in the destination'), _('REV')),
3774 _('a changeset intended to be included in the destination'), _('REV')),
3775 ('n', 'newest-first', None, _('show newest record first')),
3775 ('n', 'newest-first', None, _('show newest record first')),
3776 ('B', 'bookmarks', False, _('compare bookmarks')),
3776 ('B', 'bookmarks', False, _('compare bookmarks')),
3777 ('b', 'branch', [], _('a specific branch you would like to push'),
3777 ('b', 'branch', [], _('a specific branch you would like to push'),
3778 _('BRANCH')),
3778 _('BRANCH')),
3779 ] + logopts + remoteopts + subrepoopts,
3779 ] + logopts + remoteopts + subrepoopts,
3780 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3780 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3781 def outgoing(ui, repo, dest=None, **opts):
3781 def outgoing(ui, repo, dest=None, **opts):
3782 """show changesets not found in the destination
3782 """show changesets not found in the destination
3783
3783
3784 Show changesets not found in the specified destination repository
3784 Show changesets not found in the specified destination repository
3785 or the default push location. These are the changesets that would
3785 or the default push location. These are the changesets that would
3786 be pushed if a push was requested.
3786 be pushed if a push was requested.
3787
3787
3788 See pull for details of valid destination formats.
3788 See pull for details of valid destination formats.
3789
3789
3790 .. container:: verbose
3790 .. container:: verbose
3791
3791
3792 With -B/--bookmarks, the result of bookmark comparison between
3792 With -B/--bookmarks, the result of bookmark comparison between
3793 local and remote repositories is displayed. With -v/--verbose,
3793 local and remote repositories is displayed. With -v/--verbose,
3794 status is also displayed for each bookmark like below::
3794 status is also displayed for each bookmark like below::
3795
3795
3796 BM1 01234567890a added
3796 BM1 01234567890a added
3797 BM2 deleted
3797 BM2 deleted
3798 BM3 234567890abc advanced
3798 BM3 234567890abc advanced
3799 BM4 34567890abcd diverged
3799 BM4 34567890abcd diverged
3800 BM5 4567890abcde changed
3800 BM5 4567890abcde changed
3801
3801
3802 The action taken when pushing depends on the
3802 The action taken when pushing depends on the
3803 status of each bookmark:
3803 status of each bookmark:
3804
3804
3805 :``added``: push with ``-B`` will create it
3805 :``added``: push with ``-B`` will create it
3806 :``deleted``: push with ``-B`` will delete it
3806 :``deleted``: push with ``-B`` will delete it
3807 :``advanced``: push will update it
3807 :``advanced``: push will update it
3808 :``diverged``: push with ``-B`` will update it
3808 :``diverged``: push with ``-B`` will update it
3809 :``changed``: push with ``-B`` will update it
3809 :``changed``: push with ``-B`` will update it
3810
3810
3811 From the point of view of pushing behavior, bookmarks
3811 From the point of view of pushing behavior, bookmarks
3812 existing only in the remote repository are treated as
3812 existing only in the remote repository are treated as
3813 ``deleted``, even if it is in fact added remotely.
3813 ``deleted``, even if it is in fact added remotely.
3814
3814
3815 Returns 0 if there are outgoing changes, 1 otherwise.
3815 Returns 0 if there are outgoing changes, 1 otherwise.
3816 """
3816 """
3817 # hg._outgoing() needs to re-resolve the path in order to handle #branch
3817 # hg._outgoing() needs to re-resolve the path in order to handle #branch
3818 # style URLs, so don't overwrite dest.
3818 # style URLs, so don't overwrite dest.
3819 path = ui.paths.getpath(dest, default=('default-push', 'default'))
3819 path = ui.paths.getpath(dest, default=('default-push', 'default'))
3820 if not path:
3820 if not path:
3821 raise error.Abort(_('default repository not configured!'),
3821 raise error.Abort(_('default repository not configured!'),
3822 hint=_("see 'hg help config.paths'"))
3822 hint=_("see 'hg help config.paths'"))
3823
3823
3824 opts = pycompat.byteskwargs(opts)
3824 opts = pycompat.byteskwargs(opts)
3825 if opts.get('graph'):
3825 if opts.get('graph'):
3826 logcmdutil.checkunsupportedgraphflags([], opts)
3826 logcmdutil.checkunsupportedgraphflags([], opts)
3827 o, other = hg._outgoing(ui, repo, dest, opts)
3827 o, other = hg._outgoing(ui, repo, dest, opts)
3828 if not o:
3828 if not o:
3829 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3829 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3830 return
3830 return
3831
3831
3832 revdag = logcmdutil.graphrevs(repo, o, opts)
3832 revdag = logcmdutil.graphrevs(repo, o, opts)
3833 ui.pager('outgoing')
3833 ui.pager('outgoing')
3834 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
3834 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
3835 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3835 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3836 graphmod.asciiedges)
3836 graphmod.asciiedges)
3837 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3837 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3838 return 0
3838 return 0
3839
3839
3840 if opts.get('bookmarks'):
3840 if opts.get('bookmarks'):
3841 dest = path.pushloc or path.loc
3841 dest = path.pushloc or path.loc
3842 other = hg.peer(repo, opts, dest)
3842 other = hg.peer(repo, opts, dest)
3843 if 'bookmarks' not in other.listkeys('namespaces'):
3843 if 'bookmarks' not in other.listkeys('namespaces'):
3844 ui.warn(_("remote doesn't support bookmarks\n"))
3844 ui.warn(_("remote doesn't support bookmarks\n"))
3845 return 0
3845 return 0
3846 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3846 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3847 ui.pager('outgoing')
3847 ui.pager('outgoing')
3848 return bookmarks.outgoing(ui, repo, other)
3848 return bookmarks.outgoing(ui, repo, other)
3849
3849
3850 repo._subtoppath = path.pushloc or path.loc
3850 repo._subtoppath = path.pushloc or path.loc
3851 try:
3851 try:
3852 return hg.outgoing(ui, repo, dest, opts)
3852 return hg.outgoing(ui, repo, dest, opts)
3853 finally:
3853 finally:
3854 del repo._subtoppath
3854 del repo._subtoppath
3855
3855
3856 @command('parents',
3856 @command('parents',
3857 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3857 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3858 ] + templateopts,
3858 ] + templateopts,
3859 _('[-r REV] [FILE]'),
3859 _('[-r REV] [FILE]'),
3860 inferrepo=True)
3860 inferrepo=True)
3861 def parents(ui, repo, file_=None, **opts):
3861 def parents(ui, repo, file_=None, **opts):
3862 """show the parents of the working directory or revision (DEPRECATED)
3862 """show the parents of the working directory or revision (DEPRECATED)
3863
3863
3864 Print the working directory's parent revisions. If a revision is
3864 Print the working directory's parent revisions. If a revision is
3865 given via -r/--rev, the parent of that revision will be printed.
3865 given via -r/--rev, the parent of that revision will be printed.
3866 If a file argument is given, the revision in which the file was
3866 If a file argument is given, the revision in which the file was
3867 last changed (before the working directory revision or the
3867 last changed (before the working directory revision or the
3868 argument to --rev if given) is printed.
3868 argument to --rev if given) is printed.
3869
3869
3870 This command is equivalent to::
3870 This command is equivalent to::
3871
3871
3872 hg log -r "p1()+p2()" or
3872 hg log -r "p1()+p2()" or
3873 hg log -r "p1(REV)+p2(REV)" or
3873 hg log -r "p1(REV)+p2(REV)" or
3874 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3874 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3875 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3875 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3876
3876
3877 See :hg:`summary` and :hg:`help revsets` for related information.
3877 See :hg:`summary` and :hg:`help revsets` for related information.
3878
3878
3879 Returns 0 on success.
3879 Returns 0 on success.
3880 """
3880 """
3881
3881
3882 opts = pycompat.byteskwargs(opts)
3882 opts = pycompat.byteskwargs(opts)
3883 rev = opts.get('rev')
3883 rev = opts.get('rev')
3884 if rev:
3884 if rev:
3885 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3885 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3886 ctx = scmutil.revsingle(repo, rev, None)
3886 ctx = scmutil.revsingle(repo, rev, None)
3887
3887
3888 if file_:
3888 if file_:
3889 m = scmutil.match(ctx, (file_,), opts)
3889 m = scmutil.match(ctx, (file_,), opts)
3890 if m.anypats() or len(m.files()) != 1:
3890 if m.anypats() or len(m.files()) != 1:
3891 raise error.Abort(_('can only specify an explicit filename'))
3891 raise error.Abort(_('can only specify an explicit filename'))
3892 file_ = m.files()[0]
3892 file_ = m.files()[0]
3893 filenodes = []
3893 filenodes = []
3894 for cp in ctx.parents():
3894 for cp in ctx.parents():
3895 if not cp:
3895 if not cp:
3896 continue
3896 continue
3897 try:
3897 try:
3898 filenodes.append(cp.filenode(file_))
3898 filenodes.append(cp.filenode(file_))
3899 except error.LookupError:
3899 except error.LookupError:
3900 pass
3900 pass
3901 if not filenodes:
3901 if not filenodes:
3902 raise error.Abort(_("'%s' not found in manifest!") % file_)
3902 raise error.Abort(_("'%s' not found in manifest!") % file_)
3903 p = []
3903 p = []
3904 for fn in filenodes:
3904 for fn in filenodes:
3905 fctx = repo.filectx(file_, fileid=fn)
3905 fctx = repo.filectx(file_, fileid=fn)
3906 p.append(fctx.node())
3906 p.append(fctx.node())
3907 else:
3907 else:
3908 p = [cp.node() for cp in ctx.parents()]
3908 p = [cp.node() for cp in ctx.parents()]
3909
3909
3910 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3910 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3911 for n in p:
3911 for n in p:
3912 if n != nullid:
3912 if n != nullid:
3913 displayer.show(repo[n])
3913 displayer.show(repo[n])
3914 displayer.close()
3914 displayer.close()
3915
3915
3916 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True,
3916 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True,
3917 intents={INTENT_READONLY})
3917 intents={INTENT_READONLY})
3918 def paths(ui, repo, search=None, **opts):
3918 def paths(ui, repo, search=None, **opts):
3919 """show aliases for remote repositories
3919 """show aliases for remote repositories
3920
3920
3921 Show definition of symbolic path name NAME. If no name is given,
3921 Show definition of symbolic path name NAME. If no name is given,
3922 show definition of all available names.
3922 show definition of all available names.
3923
3923
3924 Option -q/--quiet suppresses all output when searching for NAME
3924 Option -q/--quiet suppresses all output when searching for NAME
3925 and shows only the path names when listing all definitions.
3925 and shows only the path names when listing all definitions.
3926
3926
3927 Path names are defined in the [paths] section of your
3927 Path names are defined in the [paths] section of your
3928 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3928 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3929 repository, ``.hg/hgrc`` is used, too.
3929 repository, ``.hg/hgrc`` is used, too.
3930
3930
3931 The path names ``default`` and ``default-push`` have a special
3931 The path names ``default`` and ``default-push`` have a special
3932 meaning. When performing a push or pull operation, they are used
3932 meaning. When performing a push or pull operation, they are used
3933 as fallbacks if no location is specified on the command-line.
3933 as fallbacks if no location is specified on the command-line.
3934 When ``default-push`` is set, it will be used for push and
3934 When ``default-push`` is set, it will be used for push and
3935 ``default`` will be used for pull; otherwise ``default`` is used
3935 ``default`` will be used for pull; otherwise ``default`` is used
3936 as the fallback for both. When cloning a repository, the clone
3936 as the fallback for both. When cloning a repository, the clone
3937 source is written as ``default`` in ``.hg/hgrc``.
3937 source is written as ``default`` in ``.hg/hgrc``.
3938
3938
3939 .. note::
3939 .. note::
3940
3940
3941 ``default`` and ``default-push`` apply to all inbound (e.g.
3941 ``default`` and ``default-push`` apply to all inbound (e.g.
3942 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3942 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3943 and :hg:`bundle`) operations.
3943 and :hg:`bundle`) operations.
3944
3944
3945 See :hg:`help urls` for more information.
3945 See :hg:`help urls` for more information.
3946
3946
3947 Returns 0 on success.
3947 Returns 0 on success.
3948 """
3948 """
3949
3949
3950 opts = pycompat.byteskwargs(opts)
3950 opts = pycompat.byteskwargs(opts)
3951 ui.pager('paths')
3951 ui.pager('paths')
3952 if search:
3952 if search:
3953 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3953 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3954 if name == search]
3954 if name == search]
3955 else:
3955 else:
3956 pathitems = sorted(ui.paths.iteritems())
3956 pathitems = sorted(ui.paths.iteritems())
3957
3957
3958 fm = ui.formatter('paths', opts)
3958 fm = ui.formatter('paths', opts)
3959 if fm.isplain():
3959 if fm.isplain():
3960 hidepassword = util.hidepassword
3960 hidepassword = util.hidepassword
3961 else:
3961 else:
3962 hidepassword = bytes
3962 hidepassword = bytes
3963 if ui.quiet:
3963 if ui.quiet:
3964 namefmt = '%s\n'
3964 namefmt = '%s\n'
3965 else:
3965 else:
3966 namefmt = '%s = '
3966 namefmt = '%s = '
3967 showsubopts = not search and not ui.quiet
3967 showsubopts = not search and not ui.quiet
3968
3968
3969 for name, path in pathitems:
3969 for name, path in pathitems:
3970 fm.startitem()
3970 fm.startitem()
3971 fm.condwrite(not search, 'name', namefmt, name)
3971 fm.condwrite(not search, 'name', namefmt, name)
3972 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3972 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3973 for subopt, value in sorted(path.suboptions.items()):
3973 for subopt, value in sorted(path.suboptions.items()):
3974 assert subopt not in ('name', 'url')
3974 assert subopt not in ('name', 'url')
3975 if showsubopts:
3975 if showsubopts:
3976 fm.plain('%s:%s = ' % (name, subopt))
3976 fm.plain('%s:%s = ' % (name, subopt))
3977 fm.condwrite(showsubopts, subopt, '%s\n', value)
3977 fm.condwrite(showsubopts, subopt, '%s\n', value)
3978
3978
3979 fm.end()
3979 fm.end()
3980
3980
3981 if search and not pathitems:
3981 if search and not pathitems:
3982 if not ui.quiet:
3982 if not ui.quiet:
3983 ui.warn(_("not found!\n"))
3983 ui.warn(_("not found!\n"))
3984 return 1
3984 return 1
3985 else:
3985 else:
3986 return 0
3986 return 0
3987
3987
3988 @command('phase',
3988 @command('phase',
3989 [('p', 'public', False, _('set changeset phase to public')),
3989 [('p', 'public', False, _('set changeset phase to public')),
3990 ('d', 'draft', False, _('set changeset phase to draft')),
3990 ('d', 'draft', False, _('set changeset phase to draft')),
3991 ('s', 'secret', False, _('set changeset phase to secret')),
3991 ('s', 'secret', False, _('set changeset phase to secret')),
3992 ('f', 'force', False, _('allow to move boundary backward')),
3992 ('f', 'force', False, _('allow to move boundary backward')),
3993 ('r', 'rev', [], _('target revision'), _('REV')),
3993 ('r', 'rev', [], _('target revision'), _('REV')),
3994 ],
3994 ],
3995 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3995 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3996 def phase(ui, repo, *revs, **opts):
3996 def phase(ui, repo, *revs, **opts):
3997 """set or show the current phase name
3997 """set or show the current phase name
3998
3998
3999 With no argument, show the phase name of the current revision(s).
3999 With no argument, show the phase name of the current revision(s).
4000
4000
4001 With one of -p/--public, -d/--draft or -s/--secret, change the
4001 With one of -p/--public, -d/--draft or -s/--secret, change the
4002 phase value of the specified revisions.
4002 phase value of the specified revisions.
4003
4003
4004 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
4004 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
4005 lower phase to a higher phase. Phases are ordered as follows::
4005 lower phase to a higher phase. Phases are ordered as follows::
4006
4006
4007 public < draft < secret
4007 public < draft < secret
4008
4008
4009 Returns 0 on success, 1 if some phases could not be changed.
4009 Returns 0 on success, 1 if some phases could not be changed.
4010
4010
4011 (For more information about the phases concept, see :hg:`help phases`.)
4011 (For more information about the phases concept, see :hg:`help phases`.)
4012 """
4012 """
4013 opts = pycompat.byteskwargs(opts)
4013 opts = pycompat.byteskwargs(opts)
4014 # search for a unique phase argument
4014 # search for a unique phase argument
4015 targetphase = None
4015 targetphase = None
4016 for idx, name in enumerate(phases.phasenames):
4016 for idx, name in enumerate(phases.phasenames):
4017 if opts[name]:
4017 if opts[name]:
4018 if targetphase is not None:
4018 if targetphase is not None:
4019 raise error.Abort(_('only one phase can be specified'))
4019 raise error.Abort(_('only one phase can be specified'))
4020 targetphase = idx
4020 targetphase = idx
4021
4021
4022 # look for specified revision
4022 # look for specified revision
4023 revs = list(revs)
4023 revs = list(revs)
4024 revs.extend(opts['rev'])
4024 revs.extend(opts['rev'])
4025 if not revs:
4025 if not revs:
4026 # display both parents as the second parent phase can influence
4026 # display both parents as the second parent phase can influence
4027 # the phase of a merge commit
4027 # the phase of a merge commit
4028 revs = [c.rev() for c in repo[None].parents()]
4028 revs = [c.rev() for c in repo[None].parents()]
4029
4029
4030 revs = scmutil.revrange(repo, revs)
4030 revs = scmutil.revrange(repo, revs)
4031
4031
4032 ret = 0
4032 ret = 0
4033 if targetphase is None:
4033 if targetphase is None:
4034 # display
4034 # display
4035 for r in revs:
4035 for r in revs:
4036 ctx = repo[r]
4036 ctx = repo[r]
4037 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4037 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4038 else:
4038 else:
4039 with repo.lock(), repo.transaction("phase") as tr:
4039 with repo.lock(), repo.transaction("phase") as tr:
4040 # set phase
4040 # set phase
4041 if not revs:
4041 if not revs:
4042 raise error.Abort(_('empty revision set'))
4042 raise error.Abort(_('empty revision set'))
4043 nodes = [repo[r].node() for r in revs]
4043 nodes = [repo[r].node() for r in revs]
4044 # moving revision from public to draft may hide them
4044 # moving revision from public to draft may hide them
4045 # We have to check result on an unfiltered repository
4045 # We have to check result on an unfiltered repository
4046 unfi = repo.unfiltered()
4046 unfi = repo.unfiltered()
4047 getphase = unfi._phasecache.phase
4047 getphase = unfi._phasecache.phase
4048 olddata = [getphase(unfi, r) for r in unfi]
4048 olddata = [getphase(unfi, r) for r in unfi]
4049 phases.advanceboundary(repo, tr, targetphase, nodes)
4049 phases.advanceboundary(repo, tr, targetphase, nodes)
4050 if opts['force']:
4050 if opts['force']:
4051 phases.retractboundary(repo, tr, targetphase, nodes)
4051 phases.retractboundary(repo, tr, targetphase, nodes)
4052 getphase = unfi._phasecache.phase
4052 getphase = unfi._phasecache.phase
4053 newdata = [getphase(unfi, r) for r in unfi]
4053 newdata = [getphase(unfi, r) for r in unfi]
4054 changes = sum(newdata[r] != olddata[r] for r in unfi)
4054 changes = sum(newdata[r] != olddata[r] for r in unfi)
4055 cl = unfi.changelog
4055 cl = unfi.changelog
4056 rejected = [n for n in nodes
4056 rejected = [n for n in nodes
4057 if newdata[cl.rev(n)] < targetphase]
4057 if newdata[cl.rev(n)] < targetphase]
4058 if rejected:
4058 if rejected:
4059 ui.warn(_('cannot move %i changesets to a higher '
4059 ui.warn(_('cannot move %i changesets to a higher '
4060 'phase, use --force\n') % len(rejected))
4060 'phase, use --force\n') % len(rejected))
4061 ret = 1
4061 ret = 1
4062 if changes:
4062 if changes:
4063 msg = _('phase changed for %i changesets\n') % changes
4063 msg = _('phase changed for %i changesets\n') % changes
4064 if ret:
4064 if ret:
4065 ui.status(msg)
4065 ui.status(msg)
4066 else:
4066 else:
4067 ui.note(msg)
4067 ui.note(msg)
4068 else:
4068 else:
4069 ui.warn(_('no phases changed\n'))
4069 ui.warn(_('no phases changed\n'))
4070 return ret
4070 return ret
4071
4071
4072 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
4072 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
4073 """Run after a changegroup has been added via pull/unbundle
4073 """Run after a changegroup has been added via pull/unbundle
4074
4074
4075 This takes arguments below:
4075 This takes arguments below:
4076
4076
4077 :modheads: change of heads by pull/unbundle
4077 :modheads: change of heads by pull/unbundle
4078 :optupdate: updating working directory is needed or not
4078 :optupdate: updating working directory is needed or not
4079 :checkout: update destination revision (or None to default destination)
4079 :checkout: update destination revision (or None to default destination)
4080 :brev: a name, which might be a bookmark to be activated after updating
4080 :brev: a name, which might be a bookmark to be activated after updating
4081 """
4081 """
4082 if modheads == 0:
4082 if modheads == 0:
4083 return
4083 return
4084 if optupdate:
4084 if optupdate:
4085 try:
4085 try:
4086 return hg.updatetotally(ui, repo, checkout, brev)
4086 return hg.updatetotally(ui, repo, checkout, brev)
4087 except error.UpdateAbort as inst:
4087 except error.UpdateAbort as inst:
4088 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
4088 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
4089 hint = inst.hint
4089 hint = inst.hint
4090 raise error.UpdateAbort(msg, hint=hint)
4090 raise error.UpdateAbort(msg, hint=hint)
4091 if modheads > 1:
4091 if modheads > 1:
4092 currentbranchheads = len(repo.branchheads())
4092 currentbranchheads = len(repo.branchheads())
4093 if currentbranchheads == modheads:
4093 if currentbranchheads == modheads:
4094 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4094 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4095 elif currentbranchheads > 1:
4095 elif currentbranchheads > 1:
4096 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4096 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4097 "merge)\n"))
4097 "merge)\n"))
4098 else:
4098 else:
4099 ui.status(_("(run 'hg heads' to see heads)\n"))
4099 ui.status(_("(run 'hg heads' to see heads)\n"))
4100 elif not ui.configbool('commands', 'update.requiredest'):
4100 elif not ui.configbool('commands', 'update.requiredest'):
4101 ui.status(_("(run 'hg update' to get a working copy)\n"))
4101 ui.status(_("(run 'hg update' to get a working copy)\n"))
4102
4102
4103 @command('^pull',
4103 @command('^pull',
4104 [('u', 'update', None,
4104 [('u', 'update', None,
4105 _('update to new branch head if new descendants were pulled')),
4105 _('update to new branch head if new descendants were pulled')),
4106 ('f', 'force', None, _('run even when remote repository is unrelated')),
4106 ('f', 'force', None, _('run even when remote repository is unrelated')),
4107 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4107 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4108 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4108 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4109 ('b', 'branch', [], _('a specific branch you would like to pull'),
4109 ('b', 'branch', [], _('a specific branch you would like to pull'),
4110 _('BRANCH')),
4110 _('BRANCH')),
4111 ] + remoteopts,
4111 ] + remoteopts,
4112 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4112 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4113 def pull(ui, repo, source="default", **opts):
4113 def pull(ui, repo, source="default", **opts):
4114 """pull changes from the specified source
4114 """pull changes from the specified source
4115
4115
4116 Pull changes from a remote repository to a local one.
4116 Pull changes from a remote repository to a local one.
4117
4117
4118 This finds all changes from the repository at the specified path
4118 This finds all changes from the repository at the specified path
4119 or URL and adds them to a local repository (the current one unless
4119 or URL and adds them to a local repository (the current one unless
4120 -R is specified). By default, this does not update the copy of the
4120 -R is specified). By default, this does not update the copy of the
4121 project in the working directory.
4121 project in the working directory.
4122
4122
4123 When cloning from servers that support it, Mercurial may fetch
4123 When cloning from servers that support it, Mercurial may fetch
4124 pre-generated data. When this is done, hooks operating on incoming
4124 pre-generated data. When this is done, hooks operating on incoming
4125 changesets and changegroups may fire more than once, once for each
4125 changesets and changegroups may fire more than once, once for each
4126 pre-generated bundle and as well as for any additional remaining
4126 pre-generated bundle and as well as for any additional remaining
4127 data. See :hg:`help -e clonebundles` for more.
4127 data. See :hg:`help -e clonebundles` for more.
4128
4128
4129 Use :hg:`incoming` if you want to see what would have been added
4129 Use :hg:`incoming` if you want to see what would have been added
4130 by a pull at the time you issued this command. If you then decide
4130 by a pull at the time you issued this command. If you then decide
4131 to add those changes to the repository, you should use :hg:`pull
4131 to add those changes to the repository, you should use :hg:`pull
4132 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4132 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4133
4133
4134 If SOURCE is omitted, the 'default' path will be used.
4134 If SOURCE is omitted, the 'default' path will be used.
4135 See :hg:`help urls` for more information.
4135 See :hg:`help urls` for more information.
4136
4136
4137 Specifying bookmark as ``.`` is equivalent to specifying the active
4137 Specifying bookmark as ``.`` is equivalent to specifying the active
4138 bookmark's name.
4138 bookmark's name.
4139
4139
4140 Returns 0 on success, 1 if an update had unresolved files.
4140 Returns 0 on success, 1 if an update had unresolved files.
4141 """
4141 """
4142
4142
4143 opts = pycompat.byteskwargs(opts)
4143 opts = pycompat.byteskwargs(opts)
4144 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
4144 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
4145 msg = _('update destination required by configuration')
4145 msg = _('update destination required by configuration')
4146 hint = _('use hg pull followed by hg update DEST')
4146 hint = _('use hg pull followed by hg update DEST')
4147 raise error.Abort(msg, hint=hint)
4147 raise error.Abort(msg, hint=hint)
4148
4148
4149 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4149 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4150 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4150 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4151 other = hg.peer(repo, opts, source)
4151 other = hg.peer(repo, opts, source)
4152 try:
4152 try:
4153 revs, checkout = hg.addbranchrevs(repo, other, branches,
4153 revs, checkout = hg.addbranchrevs(repo, other, branches,
4154 opts.get('rev'))
4154 opts.get('rev'))
4155
4155
4156
4156
4157 pullopargs = {}
4157 pullopargs = {}
4158 if opts.get('bookmark'):
4158 if opts.get('bookmark'):
4159 if not revs:
4159 if not revs:
4160 revs = []
4160 revs = []
4161 # The list of bookmark used here is not the one used to actually
4161 # The list of bookmark used here is not the one used to actually
4162 # update the bookmark name. This can result in the revision pulled
4162 # update the bookmark name. This can result in the revision pulled
4163 # not ending up with the name of the bookmark because of a race
4163 # not ending up with the name of the bookmark because of a race
4164 # condition on the server. (See issue 4689 for details)
4164 # condition on the server. (See issue 4689 for details)
4165 remotebookmarks = other.listkeys('bookmarks')
4165 remotebookmarks = other.listkeys('bookmarks')
4166 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4166 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4167 pullopargs['remotebookmarks'] = remotebookmarks
4167 pullopargs['remotebookmarks'] = remotebookmarks
4168 for b in opts['bookmark']:
4168 for b in opts['bookmark']:
4169 b = repo._bookmarks.expandname(b)
4169 b = repo._bookmarks.expandname(b)
4170 if b not in remotebookmarks:
4170 if b not in remotebookmarks:
4171 raise error.Abort(_('remote bookmark %s not found!') % b)
4171 raise error.Abort(_('remote bookmark %s not found!') % b)
4172 revs.append(hex(remotebookmarks[b]))
4172 revs.append(hex(remotebookmarks[b]))
4173
4173
4174 if revs:
4174 if revs:
4175 try:
4175 try:
4176 # When 'rev' is a bookmark name, we cannot guarantee that it
4176 # When 'rev' is a bookmark name, we cannot guarantee that it
4177 # will be updated with that name because of a race condition
4177 # will be updated with that name because of a race condition
4178 # server side. (See issue 4689 for details)
4178 # server side. (See issue 4689 for details)
4179 oldrevs = revs
4179 oldrevs = revs
4180 revs = [] # actually, nodes
4180 revs = [] # actually, nodes
4181 for r in oldrevs:
4181 for r in oldrevs:
4182 with other.commandexecutor() as e:
4182 with other.commandexecutor() as e:
4183 node = e.callcommand('lookup', {'key': r}).result()
4183 node = e.callcommand('lookup', {'key': r}).result()
4184
4184
4185 revs.append(node)
4185 revs.append(node)
4186 if r == checkout:
4186 if r == checkout:
4187 checkout = node
4187 checkout = node
4188 except error.CapabilityError:
4188 except error.CapabilityError:
4189 err = _("other repository doesn't support revision lookup, "
4189 err = _("other repository doesn't support revision lookup, "
4190 "so a rev cannot be specified.")
4190 "so a rev cannot be specified.")
4191 raise error.Abort(err)
4191 raise error.Abort(err)
4192
4192
4193 wlock = util.nullcontextmanager()
4193 wlock = util.nullcontextmanager()
4194 if opts.get('update'):
4194 if opts.get('update'):
4195 wlock = repo.wlock()
4195 wlock = repo.wlock()
4196 with wlock:
4196 with wlock:
4197 pullopargs.update(opts.get('opargs', {}))
4197 pullopargs.update(opts.get('opargs', {}))
4198 modheads = exchange.pull(repo, other, heads=revs,
4198 modheads = exchange.pull(repo, other, heads=revs,
4199 force=opts.get('force'),
4199 force=opts.get('force'),
4200 bookmarks=opts.get('bookmark', ()),
4200 bookmarks=opts.get('bookmark', ()),
4201 opargs=pullopargs).cgresult
4201 opargs=pullopargs).cgresult
4202
4202
4203 # brev is a name, which might be a bookmark to be activated at
4203 # brev is a name, which might be a bookmark to be activated at
4204 # the end of the update. In other words, it is an explicit
4204 # the end of the update. In other words, it is an explicit
4205 # destination of the update
4205 # destination of the update
4206 brev = None
4206 brev = None
4207
4207
4208 if checkout:
4208 if checkout:
4209 checkout = repo.changelog.rev(checkout)
4209 checkout = repo.changelog.rev(checkout)
4210
4210
4211 # order below depends on implementation of
4211 # order below depends on implementation of
4212 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4212 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4213 # because 'checkout' is determined without it.
4213 # because 'checkout' is determined without it.
4214 if opts.get('rev'):
4214 if opts.get('rev'):
4215 brev = opts['rev'][0]
4215 brev = opts['rev'][0]
4216 elif opts.get('branch'):
4216 elif opts.get('branch'):
4217 brev = opts['branch'][0]
4217 brev = opts['branch'][0]
4218 else:
4218 else:
4219 brev = branches[0]
4219 brev = branches[0]
4220 repo._subtoppath = source
4220 repo._subtoppath = source
4221 try:
4221 try:
4222 ret = postincoming(ui, repo, modheads, opts.get('update'),
4222 ret = postincoming(ui, repo, modheads, opts.get('update'),
4223 checkout, brev)
4223 checkout, brev)
4224
4224
4225 finally:
4225 finally:
4226 del repo._subtoppath
4226 del repo._subtoppath
4227
4227
4228 finally:
4228 finally:
4229 other.close()
4229 other.close()
4230 return ret
4230 return ret
4231
4231
4232 @command('^push',
4232 @command('^push',
4233 [('f', 'force', None, _('force push')),
4233 [('f', 'force', None, _('force push')),
4234 ('r', 'rev', [],
4234 ('r', 'rev', [],
4235 _('a changeset intended to be included in the destination'),
4235 _('a changeset intended to be included in the destination'),
4236 _('REV')),
4236 _('REV')),
4237 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4237 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4238 ('b', 'branch', [],
4238 ('b', 'branch', [],
4239 _('a specific branch you would like to push'), _('BRANCH')),
4239 _('a specific branch you would like to push'), _('BRANCH')),
4240 ('', 'new-branch', False, _('allow pushing a new branch')),
4240 ('', 'new-branch', False, _('allow pushing a new branch')),
4241 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4241 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4242 ] + remoteopts,
4242 ] + remoteopts,
4243 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4243 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4244 def push(ui, repo, dest=None, **opts):
4244 def push(ui, repo, dest=None, **opts):
4245 """push changes to the specified destination
4245 """push changes to the specified destination
4246
4246
4247 Push changesets from the local repository to the specified
4247 Push changesets from the local repository to the specified
4248 destination.
4248 destination.
4249
4249
4250 This operation is symmetrical to pull: it is identical to a pull
4250 This operation is symmetrical to pull: it is identical to a pull
4251 in the destination repository from the current one.
4251 in the destination repository from the current one.
4252
4252
4253 By default, push will not allow creation of new heads at the
4253 By default, push will not allow creation of new heads at the
4254 destination, since multiple heads would make it unclear which head
4254 destination, since multiple heads would make it unclear which head
4255 to use. In this situation, it is recommended to pull and merge
4255 to use. In this situation, it is recommended to pull and merge
4256 before pushing.
4256 before pushing.
4257
4257
4258 Use --new-branch if you want to allow push to create a new named
4258 Use --new-branch if you want to allow push to create a new named
4259 branch that is not present at the destination. This allows you to
4259 branch that is not present at the destination. This allows you to
4260 only create a new branch without forcing other changes.
4260 only create a new branch without forcing other changes.
4261
4261
4262 .. note::
4262 .. note::
4263
4263
4264 Extra care should be taken with the -f/--force option,
4264 Extra care should be taken with the -f/--force option,
4265 which will push all new heads on all branches, an action which will
4265 which will push all new heads on all branches, an action which will
4266 almost always cause confusion for collaborators.
4266 almost always cause confusion for collaborators.
4267
4267
4268 If -r/--rev is used, the specified revision and all its ancestors
4268 If -r/--rev is used, the specified revision and all its ancestors
4269 will be pushed to the remote repository.
4269 will be pushed to the remote repository.
4270
4270
4271 If -B/--bookmark is used, the specified bookmarked revision, its
4271 If -B/--bookmark is used, the specified bookmarked revision, its
4272 ancestors, and the bookmark will be pushed to the remote
4272 ancestors, and the bookmark will be pushed to the remote
4273 repository. Specifying ``.`` is equivalent to specifying the active
4273 repository. Specifying ``.`` is equivalent to specifying the active
4274 bookmark's name.
4274 bookmark's name.
4275
4275
4276 Please see :hg:`help urls` for important details about ``ssh://``
4276 Please see :hg:`help urls` for important details about ``ssh://``
4277 URLs. If DESTINATION is omitted, a default path will be used.
4277 URLs. If DESTINATION is omitted, a default path will be used.
4278
4278
4279 .. container:: verbose
4279 .. container:: verbose
4280
4280
4281 The --pushvars option sends strings to the server that become
4281 The --pushvars option sends strings to the server that become
4282 environment variables prepended with ``HG_USERVAR_``. For example,
4282 environment variables prepended with ``HG_USERVAR_``. For example,
4283 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4283 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4284 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4284 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4285
4285
4286 pushvars can provide for user-overridable hooks as well as set debug
4286 pushvars can provide for user-overridable hooks as well as set debug
4287 levels. One example is having a hook that blocks commits containing
4287 levels. One example is having a hook that blocks commits containing
4288 conflict markers, but enables the user to override the hook if the file
4288 conflict markers, but enables the user to override the hook if the file
4289 is using conflict markers for testing purposes or the file format has
4289 is using conflict markers for testing purposes or the file format has
4290 strings that look like conflict markers.
4290 strings that look like conflict markers.
4291
4291
4292 By default, servers will ignore `--pushvars`. To enable it add the
4292 By default, servers will ignore `--pushvars`. To enable it add the
4293 following to your configuration file::
4293 following to your configuration file::
4294
4294
4295 [push]
4295 [push]
4296 pushvars.server = true
4296 pushvars.server = true
4297
4297
4298 Returns 0 if push was successful, 1 if nothing to push.
4298 Returns 0 if push was successful, 1 if nothing to push.
4299 """
4299 """
4300
4300
4301 opts = pycompat.byteskwargs(opts)
4301 opts = pycompat.byteskwargs(opts)
4302 if opts.get('bookmark'):
4302 if opts.get('bookmark'):
4303 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4303 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4304 for b in opts['bookmark']:
4304 for b in opts['bookmark']:
4305 # translate -B options to -r so changesets get pushed
4305 # translate -B options to -r so changesets get pushed
4306 b = repo._bookmarks.expandname(b)
4306 b = repo._bookmarks.expandname(b)
4307 if b in repo._bookmarks:
4307 if b in repo._bookmarks:
4308 opts.setdefault('rev', []).append(b)
4308 opts.setdefault('rev', []).append(b)
4309 else:
4309 else:
4310 # if we try to push a deleted bookmark, translate it to null
4310 # if we try to push a deleted bookmark, translate it to null
4311 # this lets simultaneous -r, -b options continue working
4311 # this lets simultaneous -r, -b options continue working
4312 opts.setdefault('rev', []).append("null")
4312 opts.setdefault('rev', []).append("null")
4313
4313
4314 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4314 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4315 if not path:
4315 if not path:
4316 raise error.Abort(_('default repository not configured!'),
4316 raise error.Abort(_('default repository not configured!'),
4317 hint=_("see 'hg help config.paths'"))
4317 hint=_("see 'hg help config.paths'"))
4318 dest = path.pushloc or path.loc
4318 dest = path.pushloc or path.loc
4319 branches = (path.branch, opts.get('branch') or [])
4319 branches = (path.branch, opts.get('branch') or [])
4320 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4320 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4321 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4321 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4322 other = hg.peer(repo, opts, dest)
4322 other = hg.peer(repo, opts, dest)
4323
4323
4324 if revs:
4324 if revs:
4325 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4325 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4326 if not revs:
4326 if not revs:
4327 raise error.Abort(_("specified revisions evaluate to an empty set"),
4327 raise error.Abort(_("specified revisions evaluate to an empty set"),
4328 hint=_("use different revision arguments"))
4328 hint=_("use different revision arguments"))
4329 elif path.pushrev:
4329 elif path.pushrev:
4330 # It doesn't make any sense to specify ancestor revisions. So limit
4330 # It doesn't make any sense to specify ancestor revisions. So limit
4331 # to DAG heads to make discovery simpler.
4331 # to DAG heads to make discovery simpler.
4332 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4332 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4333 revs = scmutil.revrange(repo, [expr])
4333 revs = scmutil.revrange(repo, [expr])
4334 revs = [repo[rev].node() for rev in revs]
4334 revs = [repo[rev].node() for rev in revs]
4335 if not revs:
4335 if not revs:
4336 raise error.Abort(_('default push revset for path evaluates to an '
4336 raise error.Abort(_('default push revset for path evaluates to an '
4337 'empty set'))
4337 'empty set'))
4338
4338
4339 repo._subtoppath = dest
4339 repo._subtoppath = dest
4340 try:
4340 try:
4341 # push subrepos depth-first for coherent ordering
4341 # push subrepos depth-first for coherent ordering
4342 c = repo['.']
4342 c = repo['.']
4343 subs = c.substate # only repos that are committed
4343 subs = c.substate # only repos that are committed
4344 for s in sorted(subs):
4344 for s in sorted(subs):
4345 result = c.sub(s).push(opts)
4345 result = c.sub(s).push(opts)
4346 if result == 0:
4346 if result == 0:
4347 return not result
4347 return not result
4348 finally:
4348 finally:
4349 del repo._subtoppath
4349 del repo._subtoppath
4350
4350
4351 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4351 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4352 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4352 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4353
4353
4354 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4354 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4355 newbranch=opts.get('new_branch'),
4355 newbranch=opts.get('new_branch'),
4356 bookmarks=opts.get('bookmark', ()),
4356 bookmarks=opts.get('bookmark', ()),
4357 opargs=opargs)
4357 opargs=opargs)
4358
4358
4359 result = not pushop.cgresult
4359 result = not pushop.cgresult
4360
4360
4361 if pushop.bkresult is not None:
4361 if pushop.bkresult is not None:
4362 if pushop.bkresult == 2:
4362 if pushop.bkresult == 2:
4363 result = 2
4363 result = 2
4364 elif not result and pushop.bkresult:
4364 elif not result and pushop.bkresult:
4365 result = 2
4365 result = 2
4366
4366
4367 return result
4367 return result
4368
4368
4369 @command('recover', [])
4369 @command('recover', [])
4370 def recover(ui, repo):
4370 def recover(ui, repo):
4371 """roll back an interrupted transaction
4371 """roll back an interrupted transaction
4372
4372
4373 Recover from an interrupted commit or pull.
4373 Recover from an interrupted commit or pull.
4374
4374
4375 This command tries to fix the repository status after an
4375 This command tries to fix the repository status after an
4376 interrupted operation. It should only be necessary when Mercurial
4376 interrupted operation. It should only be necessary when Mercurial
4377 suggests it.
4377 suggests it.
4378
4378
4379 Returns 0 if successful, 1 if nothing to recover or verify fails.
4379 Returns 0 if successful, 1 if nothing to recover or verify fails.
4380 """
4380 """
4381 if repo.recover():
4381 if repo.recover():
4382 return hg.verify(repo)
4382 return hg.verify(repo)
4383 return 1
4383 return 1
4384
4384
4385 @command('^remove|rm',
4385 @command('^remove|rm',
4386 [('A', 'after', None, _('record delete for missing files')),
4386 [('A', 'after', None, _('record delete for missing files')),
4387 ('f', 'force', None,
4387 ('f', 'force', None,
4388 _('forget added files, delete modified files')),
4388 _('forget added files, delete modified files')),
4389 ] + subrepoopts + walkopts + dryrunopts,
4389 ] + subrepoopts + walkopts + dryrunopts,
4390 _('[OPTION]... FILE...'),
4390 _('[OPTION]... FILE...'),
4391 inferrepo=True)
4391 inferrepo=True)
4392 def remove(ui, repo, *pats, **opts):
4392 def remove(ui, repo, *pats, **opts):
4393 """remove the specified files on the next commit
4393 """remove the specified files on the next commit
4394
4394
4395 Schedule the indicated files for removal from the current branch.
4395 Schedule the indicated files for removal from the current branch.
4396
4396
4397 This command schedules the files to be removed at the next commit.
4397 This command schedules the files to be removed at the next commit.
4398 To undo a remove before that, see :hg:`revert`. To undo added
4398 To undo a remove before that, see :hg:`revert`. To undo added
4399 files, see :hg:`forget`.
4399 files, see :hg:`forget`.
4400
4400
4401 .. container:: verbose
4401 .. container:: verbose
4402
4402
4403 -A/--after can be used to remove only files that have already
4403 -A/--after can be used to remove only files that have already
4404 been deleted, -f/--force can be used to force deletion, and -Af
4404 been deleted, -f/--force can be used to force deletion, and -Af
4405 can be used to remove files from the next revision without
4405 can be used to remove files from the next revision without
4406 deleting them from the working directory.
4406 deleting them from the working directory.
4407
4407
4408 The following table details the behavior of remove for different
4408 The following table details the behavior of remove for different
4409 file states (columns) and option combinations (rows). The file
4409 file states (columns) and option combinations (rows). The file
4410 states are Added [A], Clean [C], Modified [M] and Missing [!]
4410 states are Added [A], Clean [C], Modified [M] and Missing [!]
4411 (as reported by :hg:`status`). The actions are Warn, Remove
4411 (as reported by :hg:`status`). The actions are Warn, Remove
4412 (from branch) and Delete (from disk):
4412 (from branch) and Delete (from disk):
4413
4413
4414 ========= == == == ==
4414 ========= == == == ==
4415 opt/state A C M !
4415 opt/state A C M !
4416 ========= == == == ==
4416 ========= == == == ==
4417 none W RD W R
4417 none W RD W R
4418 -f R RD RD R
4418 -f R RD RD R
4419 -A W W W R
4419 -A W W W R
4420 -Af R R R R
4420 -Af R R R R
4421 ========= == == == ==
4421 ========= == == == ==
4422
4422
4423 .. note::
4423 .. note::
4424
4424
4425 :hg:`remove` never deletes files in Added [A] state from the
4425 :hg:`remove` never deletes files in Added [A] state from the
4426 working directory, not even if ``--force`` is specified.
4426 working directory, not even if ``--force`` is specified.
4427
4427
4428 Returns 0 on success, 1 if any warnings encountered.
4428 Returns 0 on success, 1 if any warnings encountered.
4429 """
4429 """
4430
4430
4431 opts = pycompat.byteskwargs(opts)
4431 opts = pycompat.byteskwargs(opts)
4432 after, force = opts.get('after'), opts.get('force')
4432 after, force = opts.get('after'), opts.get('force')
4433 dryrun = opts.get('dry_run')
4433 dryrun = opts.get('dry_run')
4434 if not pats and not after:
4434 if not pats and not after:
4435 raise error.Abort(_('no files specified'))
4435 raise error.Abort(_('no files specified'))
4436
4436
4437 m = scmutil.match(repo[None], pats, opts)
4437 m = scmutil.match(repo[None], pats, opts)
4438 subrepos = opts.get('subrepos')
4438 subrepos = opts.get('subrepos')
4439 return cmdutil.remove(ui, repo, m, "", after, force, subrepos,
4439 return cmdutil.remove(ui, repo, m, "", after, force, subrepos,
4440 dryrun=dryrun)
4440 dryrun=dryrun)
4441
4441
4442 @command('rename|move|mv',
4442 @command('rename|move|mv',
4443 [('A', 'after', None, _('record a rename that has already occurred')),
4443 [('A', 'after', None, _('record a rename that has already occurred')),
4444 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4444 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4445 ] + walkopts + dryrunopts,
4445 ] + walkopts + dryrunopts,
4446 _('[OPTION]... SOURCE... DEST'))
4446 _('[OPTION]... SOURCE... DEST'))
4447 def rename(ui, repo, *pats, **opts):
4447 def rename(ui, repo, *pats, **opts):
4448 """rename files; equivalent of copy + remove
4448 """rename files; equivalent of copy + remove
4449
4449
4450 Mark dest as copies of sources; mark sources for deletion. If dest
4450 Mark dest as copies of sources; mark sources for deletion. If dest
4451 is a directory, copies are put in that directory. If dest is a
4451 is a directory, copies are put in that directory. If dest is a
4452 file, there can only be one source.
4452 file, there can only be one source.
4453
4453
4454 By default, this command copies the contents of files as they
4454 By default, this command copies the contents of files as they
4455 exist in the working directory. If invoked with -A/--after, the
4455 exist in the working directory. If invoked with -A/--after, the
4456 operation is recorded, but no copying is performed.
4456 operation is recorded, but no copying is performed.
4457
4457
4458 This command takes effect at the next commit. To undo a rename
4458 This command takes effect at the next commit. To undo a rename
4459 before that, see :hg:`revert`.
4459 before that, see :hg:`revert`.
4460
4460
4461 Returns 0 on success, 1 if errors are encountered.
4461 Returns 0 on success, 1 if errors are encountered.
4462 """
4462 """
4463 opts = pycompat.byteskwargs(opts)
4463 opts = pycompat.byteskwargs(opts)
4464 with repo.wlock(False):
4464 with repo.wlock(False):
4465 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4465 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4466
4466
4467 @command('resolve',
4467 @command('resolve',
4468 [('a', 'all', None, _('select all unresolved files')),
4468 [('a', 'all', None, _('select all unresolved files')),
4469 ('l', 'list', None, _('list state of files needing merge')),
4469 ('l', 'list', None, _('list state of files needing merge')),
4470 ('m', 'mark', None, _('mark files as resolved')),
4470 ('m', 'mark', None, _('mark files as resolved')),
4471 ('u', 'unmark', None, _('mark files as unresolved')),
4471 ('u', 'unmark', None, _('mark files as unresolved')),
4472 ('n', 'no-status', None, _('hide status prefix'))]
4472 ('n', 'no-status', None, _('hide status prefix'))]
4473 + mergetoolopts + walkopts + formatteropts,
4473 + mergetoolopts + walkopts + formatteropts,
4474 _('[OPTION]... [FILE]...'),
4474 _('[OPTION]... [FILE]...'),
4475 inferrepo=True)
4475 inferrepo=True)
4476 def resolve(ui, repo, *pats, **opts):
4476 def resolve(ui, repo, *pats, **opts):
4477 """redo merges or set/view the merge status of files
4477 """redo merges or set/view the merge status of files
4478
4478
4479 Merges with unresolved conflicts are often the result of
4479 Merges with unresolved conflicts are often the result of
4480 non-interactive merging using the ``internal:merge`` configuration
4480 non-interactive merging using the ``internal:merge`` configuration
4481 setting, or a command-line merge tool like ``diff3``. The resolve
4481 setting, or a command-line merge tool like ``diff3``. The resolve
4482 command is used to manage the files involved in a merge, after
4482 command is used to manage the files involved in a merge, after
4483 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4483 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4484 working directory must have two parents). See :hg:`help
4484 working directory must have two parents). See :hg:`help
4485 merge-tools` for information on configuring merge tools.
4485 merge-tools` for information on configuring merge tools.
4486
4486
4487 The resolve command can be used in the following ways:
4487 The resolve command can be used in the following ways:
4488
4488
4489 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4489 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4490 files, discarding any previous merge attempts. Re-merging is not
4490 files, discarding any previous merge attempts. Re-merging is not
4491 performed for files already marked as resolved. Use ``--all/-a``
4491 performed for files already marked as resolved. Use ``--all/-a``
4492 to select all unresolved files. ``--tool`` can be used to specify
4492 to select all unresolved files. ``--tool`` can be used to specify
4493 the merge tool used for the given files. It overrides the HGMERGE
4493 the merge tool used for the given files. It overrides the HGMERGE
4494 environment variable and your configuration files. Previous file
4494 environment variable and your configuration files. Previous file
4495 contents are saved with a ``.orig`` suffix.
4495 contents are saved with a ``.orig`` suffix.
4496
4496
4497 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4497 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4498 (e.g. after having manually fixed-up the files). The default is
4498 (e.g. after having manually fixed-up the files). The default is
4499 to mark all unresolved files.
4499 to mark all unresolved files.
4500
4500
4501 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4501 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4502 default is to mark all resolved files.
4502 default is to mark all resolved files.
4503
4503
4504 - :hg:`resolve -l`: list files which had or still have conflicts.
4504 - :hg:`resolve -l`: list files which had or still have conflicts.
4505 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4505 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4506 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4506 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4507 the list. See :hg:`help filesets` for details.
4507 the list. See :hg:`help filesets` for details.
4508
4508
4509 .. note::
4509 .. note::
4510
4510
4511 Mercurial will not let you commit files with unresolved merge
4511 Mercurial will not let you commit files with unresolved merge
4512 conflicts. You must use :hg:`resolve -m ...` before you can
4512 conflicts. You must use :hg:`resolve -m ...` before you can
4513 commit after a conflicting merge.
4513 commit after a conflicting merge.
4514
4514
4515 Returns 0 on success, 1 if any files fail a resolve attempt.
4515 Returns 0 on success, 1 if any files fail a resolve attempt.
4516 """
4516 """
4517
4517
4518 opts = pycompat.byteskwargs(opts)
4518 opts = pycompat.byteskwargs(opts)
4519 flaglist = 'all mark unmark list no_status'.split()
4519 flaglist = 'all mark unmark list no_status'.split()
4520 all, mark, unmark, show, nostatus = \
4520 all, mark, unmark, show, nostatus = \
4521 [opts.get(o) for o in flaglist]
4521 [opts.get(o) for o in flaglist]
4522
4522
4523 if (show and (mark or unmark)) or (mark and unmark):
4523 if (show and (mark or unmark)) or (mark and unmark):
4524 raise error.Abort(_("too many options specified"))
4524 raise error.Abort(_("too many options specified"))
4525 if pats and all:
4525 if pats and all:
4526 raise error.Abort(_("can't specify --all and patterns"))
4526 raise error.Abort(_("can't specify --all and patterns"))
4527 if not (all or pats or show or mark or unmark):
4527 if not (all or pats or show or mark or unmark):
4528 raise error.Abort(_('no files or directories specified'),
4528 raise error.Abort(_('no files or directories specified'),
4529 hint=('use --all to re-merge all unresolved files'))
4529 hint=('use --all to re-merge all unresolved files'))
4530
4530
4531 if show:
4531 if show:
4532 ui.pager('resolve')
4532 ui.pager('resolve')
4533 fm = ui.formatter('resolve', opts)
4533 fm = ui.formatter('resolve', opts)
4534 ms = mergemod.mergestate.read(repo)
4534 ms = mergemod.mergestate.read(repo)
4535 m = scmutil.match(repo[None], pats, opts)
4535 m = scmutil.match(repo[None], pats, opts)
4536
4536
4537 # Labels and keys based on merge state. Unresolved path conflicts show
4537 # Labels and keys based on merge state. Unresolved path conflicts show
4538 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4538 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4539 # resolved conflicts.
4539 # resolved conflicts.
4540 mergestateinfo = {
4540 mergestateinfo = {
4541 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4541 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4542 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4542 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4543 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4543 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4544 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4544 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4545 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4545 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4546 'D'),
4546 'D'),
4547 }
4547 }
4548
4548
4549 for f in ms:
4549 for f in ms:
4550 if not m(f):
4550 if not m(f):
4551 continue
4551 continue
4552
4552
4553 label, key = mergestateinfo[ms[f]]
4553 label, key = mergestateinfo[ms[f]]
4554 fm.startitem()
4554 fm.startitem()
4555 fm.condwrite(not nostatus, 'status', '%s ', key, label=label)
4555 fm.condwrite(not nostatus, 'status', '%s ', key, label=label)
4556 fm.write('path', '%s\n', f, label=label)
4556 fm.write('path', '%s\n', f, label=label)
4557 fm.end()
4557 fm.end()
4558 return 0
4558 return 0
4559
4559
4560 with repo.wlock():
4560 with repo.wlock():
4561 ms = mergemod.mergestate.read(repo)
4561 ms = mergemod.mergestate.read(repo)
4562
4562
4563 if not (ms.active() or repo.dirstate.p2() != nullid):
4563 if not (ms.active() or repo.dirstate.p2() != nullid):
4564 raise error.Abort(
4564 raise error.Abort(
4565 _('resolve command not applicable when not merging'))
4565 _('resolve command not applicable when not merging'))
4566
4566
4567 wctx = repo[None]
4567 wctx = repo[None]
4568
4568
4569 if (ms.mergedriver
4569 if (ms.mergedriver
4570 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4570 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4571 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4571 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4572 ms.commit()
4572 ms.commit()
4573 # allow mark and unmark to go through
4573 # allow mark and unmark to go through
4574 if not mark and not unmark and not proceed:
4574 if not mark and not unmark and not proceed:
4575 return 1
4575 return 1
4576
4576
4577 m = scmutil.match(wctx, pats, opts)
4577 m = scmutil.match(wctx, pats, opts)
4578 ret = 0
4578 ret = 0
4579 didwork = False
4579 didwork = False
4580 runconclude = False
4580 runconclude = False
4581
4581
4582 tocomplete = []
4582 tocomplete = []
4583 for f in ms:
4583 for f in ms:
4584 if not m(f):
4584 if not m(f):
4585 continue
4585 continue
4586
4586
4587 didwork = True
4587 didwork = True
4588
4588
4589 # don't let driver-resolved files be marked, and run the conclude
4589 # don't let driver-resolved files be marked, and run the conclude
4590 # step if asked to resolve
4590 # step if asked to resolve
4591 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4591 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4592 exact = m.exact(f)
4592 exact = m.exact(f)
4593 if mark:
4593 if mark:
4594 if exact:
4594 if exact:
4595 ui.warn(_('not marking %s as it is driver-resolved\n')
4595 ui.warn(_('not marking %s as it is driver-resolved\n')
4596 % f)
4596 % f)
4597 elif unmark:
4597 elif unmark:
4598 if exact:
4598 if exact:
4599 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4599 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4600 % f)
4600 % f)
4601 else:
4601 else:
4602 runconclude = True
4602 runconclude = True
4603 continue
4603 continue
4604
4604
4605 # path conflicts must be resolved manually
4605 # path conflicts must be resolved manually
4606 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4606 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4607 mergemod.MERGE_RECORD_RESOLVED_PATH):
4607 mergemod.MERGE_RECORD_RESOLVED_PATH):
4608 if mark:
4608 if mark:
4609 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4609 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4610 elif unmark:
4610 elif unmark:
4611 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4611 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4612 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4612 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4613 ui.warn(_('%s: path conflict must be resolved manually\n')
4613 ui.warn(_('%s: path conflict must be resolved manually\n')
4614 % f)
4614 % f)
4615 continue
4615 continue
4616
4616
4617 if mark:
4617 if mark:
4618 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4618 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4619 elif unmark:
4619 elif unmark:
4620 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4620 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4621 else:
4621 else:
4622 # backup pre-resolve (merge uses .orig for its own purposes)
4622 # backup pre-resolve (merge uses .orig for its own purposes)
4623 a = repo.wjoin(f)
4623 a = repo.wjoin(f)
4624 try:
4624 try:
4625 util.copyfile(a, a + ".resolve")
4625 util.copyfile(a, a + ".resolve")
4626 except (IOError, OSError) as inst:
4626 except (IOError, OSError) as inst:
4627 if inst.errno != errno.ENOENT:
4627 if inst.errno != errno.ENOENT:
4628 raise
4628 raise
4629
4629
4630 try:
4630 try:
4631 # preresolve file
4631 # preresolve file
4632 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4632 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4633 with ui.configoverride(overrides, 'resolve'):
4633 with ui.configoverride(overrides, 'resolve'):
4634 complete, r = ms.preresolve(f, wctx)
4634 complete, r = ms.preresolve(f, wctx)
4635 if not complete:
4635 if not complete:
4636 tocomplete.append(f)
4636 tocomplete.append(f)
4637 elif r:
4637 elif r:
4638 ret = 1
4638 ret = 1
4639 finally:
4639 finally:
4640 ms.commit()
4640 ms.commit()
4641
4641
4642 # replace filemerge's .orig file with our resolve file, but only
4642 # replace filemerge's .orig file with our resolve file, but only
4643 # for merges that are complete
4643 # for merges that are complete
4644 if complete:
4644 if complete:
4645 try:
4645 try:
4646 util.rename(a + ".resolve",
4646 util.rename(a + ".resolve",
4647 scmutil.origpath(ui, repo, a))
4647 scmutil.origpath(ui, repo, a))
4648 except OSError as inst:
4648 except OSError as inst:
4649 if inst.errno != errno.ENOENT:
4649 if inst.errno != errno.ENOENT:
4650 raise
4650 raise
4651
4651
4652 for f in tocomplete:
4652 for f in tocomplete:
4653 try:
4653 try:
4654 # resolve file
4654 # resolve file
4655 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4655 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4656 with ui.configoverride(overrides, 'resolve'):
4656 with ui.configoverride(overrides, 'resolve'):
4657 r = ms.resolve(f, wctx)
4657 r = ms.resolve(f, wctx)
4658 if r:
4658 if r:
4659 ret = 1
4659 ret = 1
4660 finally:
4660 finally:
4661 ms.commit()
4661 ms.commit()
4662
4662
4663 # replace filemerge's .orig file with our resolve file
4663 # replace filemerge's .orig file with our resolve file
4664 a = repo.wjoin(f)
4664 a = repo.wjoin(f)
4665 try:
4665 try:
4666 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4666 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4667 except OSError as inst:
4667 except OSError as inst:
4668 if inst.errno != errno.ENOENT:
4668 if inst.errno != errno.ENOENT:
4669 raise
4669 raise
4670
4670
4671 ms.commit()
4671 ms.commit()
4672 ms.recordactions()
4672 ms.recordactions()
4673
4673
4674 if not didwork and pats:
4674 if not didwork and pats:
4675 hint = None
4675 hint = None
4676 if not any([p for p in pats if p.find(':') >= 0]):
4676 if not any([p for p in pats if p.find(':') >= 0]):
4677 pats = ['path:%s' % p for p in pats]
4677 pats = ['path:%s' % p for p in pats]
4678 m = scmutil.match(wctx, pats, opts)
4678 m = scmutil.match(wctx, pats, opts)
4679 for f in ms:
4679 for f in ms:
4680 if not m(f):
4680 if not m(f):
4681 continue
4681 continue
4682 flags = ''.join(['-%s ' % o[0:1] for o in flaglist
4682 flags = ''.join(['-%s ' % o[0:1] for o in flaglist
4683 if opts.get(o)])
4683 if opts.get(o)])
4684 hint = _("(try: hg resolve %s%s)\n") % (
4684 hint = _("(try: hg resolve %s%s)\n") % (
4685 flags,
4685 flags,
4686 ' '.join(pats))
4686 ' '.join(pats))
4687 break
4687 break
4688 ui.warn(_("arguments do not match paths that need resolving\n"))
4688 ui.warn(_("arguments do not match paths that need resolving\n"))
4689 if hint:
4689 if hint:
4690 ui.warn(hint)
4690 ui.warn(hint)
4691 elif ms.mergedriver and ms.mdstate() != 's':
4691 elif ms.mergedriver and ms.mdstate() != 's':
4692 # run conclude step when either a driver-resolved file is requested
4692 # run conclude step when either a driver-resolved file is requested
4693 # or there are no driver-resolved files
4693 # or there are no driver-resolved files
4694 # we can't use 'ret' to determine whether any files are unresolved
4694 # we can't use 'ret' to determine whether any files are unresolved
4695 # because we might not have tried to resolve some
4695 # because we might not have tried to resolve some
4696 if ((runconclude or not list(ms.driverresolved()))
4696 if ((runconclude or not list(ms.driverresolved()))
4697 and not list(ms.unresolved())):
4697 and not list(ms.unresolved())):
4698 proceed = mergemod.driverconclude(repo, ms, wctx)
4698 proceed = mergemod.driverconclude(repo, ms, wctx)
4699 ms.commit()
4699 ms.commit()
4700 if not proceed:
4700 if not proceed:
4701 return 1
4701 return 1
4702
4702
4703 # Nudge users into finishing an unfinished operation
4703 # Nudge users into finishing an unfinished operation
4704 unresolvedf = list(ms.unresolved())
4704 unresolvedf = list(ms.unresolved())
4705 driverresolvedf = list(ms.driverresolved())
4705 driverresolvedf = list(ms.driverresolved())
4706 if not unresolvedf and not driverresolvedf:
4706 if not unresolvedf and not driverresolvedf:
4707 ui.status(_('(no more unresolved files)\n'))
4707 ui.status(_('(no more unresolved files)\n'))
4708 cmdutil.checkafterresolved(repo)
4708 cmdutil.checkafterresolved(repo)
4709 elif not unresolvedf:
4709 elif not unresolvedf:
4710 ui.status(_('(no more unresolved files -- '
4710 ui.status(_('(no more unresolved files -- '
4711 'run "hg resolve --all" to conclude)\n'))
4711 'run "hg resolve --all" to conclude)\n'))
4712
4712
4713 return ret
4713 return ret
4714
4714
4715 @command('revert',
4715 @command('revert',
4716 [('a', 'all', None, _('revert all changes when no arguments given')),
4716 [('a', 'all', None, _('revert all changes when no arguments given')),
4717 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4717 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4718 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4718 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4719 ('C', 'no-backup', None, _('do not save backup copies of files')),
4719 ('C', 'no-backup', None, _('do not save backup copies of files')),
4720 ('i', 'interactive', None, _('interactively select the changes')),
4720 ('i', 'interactive', None, _('interactively select the changes')),
4721 ] + walkopts + dryrunopts,
4721 ] + walkopts + dryrunopts,
4722 _('[OPTION]... [-r REV] [NAME]...'))
4722 _('[OPTION]... [-r REV] [NAME]...'))
4723 def revert(ui, repo, *pats, **opts):
4723 def revert(ui, repo, *pats, **opts):
4724 """restore files to their checkout state
4724 """restore files to their checkout state
4725
4725
4726 .. note::
4726 .. note::
4727
4727
4728 To check out earlier revisions, you should use :hg:`update REV`.
4728 To check out earlier revisions, you should use :hg:`update REV`.
4729 To cancel an uncommitted merge (and lose your changes),
4729 To cancel an uncommitted merge (and lose your changes),
4730 use :hg:`merge --abort`.
4730 use :hg:`merge --abort`.
4731
4731
4732 With no revision specified, revert the specified files or directories
4732 With no revision specified, revert the specified files or directories
4733 to the contents they had in the parent of the working directory.
4733 to the contents they had in the parent of the working directory.
4734 This restores the contents of files to an unmodified
4734 This restores the contents of files to an unmodified
4735 state and unschedules adds, removes, copies, and renames. If the
4735 state and unschedules adds, removes, copies, and renames. If the
4736 working directory has two parents, you must explicitly specify a
4736 working directory has two parents, you must explicitly specify a
4737 revision.
4737 revision.
4738
4738
4739 Using the -r/--rev or -d/--date options, revert the given files or
4739 Using the -r/--rev or -d/--date options, revert the given files or
4740 directories to their states as of a specific revision. Because
4740 directories to their states as of a specific revision. Because
4741 revert does not change the working directory parents, this will
4741 revert does not change the working directory parents, this will
4742 cause these files to appear modified. This can be helpful to "back
4742 cause these files to appear modified. This can be helpful to "back
4743 out" some or all of an earlier change. See :hg:`backout` for a
4743 out" some or all of an earlier change. See :hg:`backout` for a
4744 related method.
4744 related method.
4745
4745
4746 Modified files are saved with a .orig suffix before reverting.
4746 Modified files are saved with a .orig suffix before reverting.
4747 To disable these backups, use --no-backup. It is possible to store
4747 To disable these backups, use --no-backup. It is possible to store
4748 the backup files in a custom directory relative to the root of the
4748 the backup files in a custom directory relative to the root of the
4749 repository by setting the ``ui.origbackuppath`` configuration
4749 repository by setting the ``ui.origbackuppath`` configuration
4750 option.
4750 option.
4751
4751
4752 See :hg:`help dates` for a list of formats valid for -d/--date.
4752 See :hg:`help dates` for a list of formats valid for -d/--date.
4753
4753
4754 See :hg:`help backout` for a way to reverse the effect of an
4754 See :hg:`help backout` for a way to reverse the effect of an
4755 earlier changeset.
4755 earlier changeset.
4756
4756
4757 Returns 0 on success.
4757 Returns 0 on success.
4758 """
4758 """
4759
4759
4760 opts = pycompat.byteskwargs(opts)
4760 opts = pycompat.byteskwargs(opts)
4761 if opts.get("date"):
4761 if opts.get("date"):
4762 if opts.get("rev"):
4762 if opts.get("rev"):
4763 raise error.Abort(_("you can't specify a revision and a date"))
4763 raise error.Abort(_("you can't specify a revision and a date"))
4764 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4764 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4765
4765
4766 parent, p2 = repo.dirstate.parents()
4766 parent, p2 = repo.dirstate.parents()
4767 if not opts.get('rev') and p2 != nullid:
4767 if not opts.get('rev') and p2 != nullid:
4768 # revert after merge is a trap for new users (issue2915)
4768 # revert after merge is a trap for new users (issue2915)
4769 raise error.Abort(_('uncommitted merge with no revision specified'),
4769 raise error.Abort(_('uncommitted merge with no revision specified'),
4770 hint=_("use 'hg update' or see 'hg help revert'"))
4770 hint=_("use 'hg update' or see 'hg help revert'"))
4771
4771
4772 rev = opts.get('rev')
4772 rev = opts.get('rev')
4773 if rev:
4773 if rev:
4774 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4774 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4775 ctx = scmutil.revsingle(repo, rev)
4775 ctx = scmutil.revsingle(repo, rev)
4776
4776
4777 if (not (pats or opts.get('include') or opts.get('exclude') or
4777 if (not (pats or opts.get('include') or opts.get('exclude') or
4778 opts.get('all') or opts.get('interactive'))):
4778 opts.get('all') or opts.get('interactive'))):
4779 msg = _("no files or directories specified")
4779 msg = _("no files or directories specified")
4780 if p2 != nullid:
4780 if p2 != nullid:
4781 hint = _("uncommitted merge, use --all to discard all changes,"
4781 hint = _("uncommitted merge, use --all to discard all changes,"
4782 " or 'hg update -C .' to abort the merge")
4782 " or 'hg update -C .' to abort the merge")
4783 raise error.Abort(msg, hint=hint)
4783 raise error.Abort(msg, hint=hint)
4784 dirty = any(repo.status())
4784 dirty = any(repo.status())
4785 node = ctx.node()
4785 node = ctx.node()
4786 if node != parent:
4786 if node != parent:
4787 if dirty:
4787 if dirty:
4788 hint = _("uncommitted changes, use --all to discard all"
4788 hint = _("uncommitted changes, use --all to discard all"
4789 " changes, or 'hg update %s' to update") % ctx.rev()
4789 " changes, or 'hg update %s' to update") % ctx.rev()
4790 else:
4790 else:
4791 hint = _("use --all to revert all files,"
4791 hint = _("use --all to revert all files,"
4792 " or 'hg update %s' to update") % ctx.rev()
4792 " or 'hg update %s' to update") % ctx.rev()
4793 elif dirty:
4793 elif dirty:
4794 hint = _("uncommitted changes, use --all to discard all changes")
4794 hint = _("uncommitted changes, use --all to discard all changes")
4795 else:
4795 else:
4796 hint = _("use --all to revert all files")
4796 hint = _("use --all to revert all files")
4797 raise error.Abort(msg, hint=hint)
4797 raise error.Abort(msg, hint=hint)
4798
4798
4799 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
4799 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
4800 **pycompat.strkwargs(opts))
4800 **pycompat.strkwargs(opts))
4801
4801
4802 @command('rollback', dryrunopts +
4802 @command('rollback', dryrunopts +
4803 [('f', 'force', False, _('ignore safety measures'))])
4803 [('f', 'force', False, _('ignore safety measures'))])
4804 def rollback(ui, repo, **opts):
4804 def rollback(ui, repo, **opts):
4805 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4805 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4806
4806
4807 Please use :hg:`commit --amend` instead of rollback to correct
4807 Please use :hg:`commit --amend` instead of rollback to correct
4808 mistakes in the last commit.
4808 mistakes in the last commit.
4809
4809
4810 This command should be used with care. There is only one level of
4810 This command should be used with care. There is only one level of
4811 rollback, and there is no way to undo a rollback. It will also
4811 rollback, and there is no way to undo a rollback. It will also
4812 restore the dirstate at the time of the last transaction, losing
4812 restore the dirstate at the time of the last transaction, losing
4813 any dirstate changes since that time. This command does not alter
4813 any dirstate changes since that time. This command does not alter
4814 the working directory.
4814 the working directory.
4815
4815
4816 Transactions are used to encapsulate the effects of all commands
4816 Transactions are used to encapsulate the effects of all commands
4817 that create new changesets or propagate existing changesets into a
4817 that create new changesets or propagate existing changesets into a
4818 repository.
4818 repository.
4819
4819
4820 .. container:: verbose
4820 .. container:: verbose
4821
4821
4822 For example, the following commands are transactional, and their
4822 For example, the following commands are transactional, and their
4823 effects can be rolled back:
4823 effects can be rolled back:
4824
4824
4825 - commit
4825 - commit
4826 - import
4826 - import
4827 - pull
4827 - pull
4828 - push (with this repository as the destination)
4828 - push (with this repository as the destination)
4829 - unbundle
4829 - unbundle
4830
4830
4831 To avoid permanent data loss, rollback will refuse to rollback a
4831 To avoid permanent data loss, rollback will refuse to rollback a
4832 commit transaction if it isn't checked out. Use --force to
4832 commit transaction if it isn't checked out. Use --force to
4833 override this protection.
4833 override this protection.
4834
4834
4835 The rollback command can be entirely disabled by setting the
4835 The rollback command can be entirely disabled by setting the
4836 ``ui.rollback`` configuration setting to false. If you're here
4836 ``ui.rollback`` configuration setting to false. If you're here
4837 because you want to use rollback and it's disabled, you can
4837 because you want to use rollback and it's disabled, you can
4838 re-enable the command by setting ``ui.rollback`` to true.
4838 re-enable the command by setting ``ui.rollback`` to true.
4839
4839
4840 This command is not intended for use on public repositories. Once
4840 This command is not intended for use on public repositories. Once
4841 changes are visible for pull by other users, rolling a transaction
4841 changes are visible for pull by other users, rolling a transaction
4842 back locally is ineffective (someone else may already have pulled
4842 back locally is ineffective (someone else may already have pulled
4843 the changes). Furthermore, a race is possible with readers of the
4843 the changes). Furthermore, a race is possible with readers of the
4844 repository; for example an in-progress pull from the repository
4844 repository; for example an in-progress pull from the repository
4845 may fail if a rollback is performed.
4845 may fail if a rollback is performed.
4846
4846
4847 Returns 0 on success, 1 if no rollback data is available.
4847 Returns 0 on success, 1 if no rollback data is available.
4848 """
4848 """
4849 if not ui.configbool('ui', 'rollback'):
4849 if not ui.configbool('ui', 'rollback'):
4850 raise error.Abort(_('rollback is disabled because it is unsafe'),
4850 raise error.Abort(_('rollback is disabled because it is unsafe'),
4851 hint=('see `hg help -v rollback` for information'))
4851 hint=('see `hg help -v rollback` for information'))
4852 return repo.rollback(dryrun=opts.get(r'dry_run'),
4852 return repo.rollback(dryrun=opts.get(r'dry_run'),
4853 force=opts.get(r'force'))
4853 force=opts.get(r'force'))
4854
4854
4855 @command('root', [], intents={INTENT_READONLY})
4855 @command('root', [], intents={INTENT_READONLY})
4856 def root(ui, repo):
4856 def root(ui, repo):
4857 """print the root (top) of the current working directory
4857 """print the root (top) of the current working directory
4858
4858
4859 Print the root directory of the current repository.
4859 Print the root directory of the current repository.
4860
4860
4861 Returns 0 on success.
4861 Returns 0 on success.
4862 """
4862 """
4863 ui.write(repo.root + "\n")
4863 ui.write(repo.root + "\n")
4864
4864
4865 @command('^serve',
4865 @command('^serve',
4866 [('A', 'accesslog', '', _('name of access log file to write to'),
4866 [('A', 'accesslog', '', _('name of access log file to write to'),
4867 _('FILE')),
4867 _('FILE')),
4868 ('d', 'daemon', None, _('run server in background')),
4868 ('d', 'daemon', None, _('run server in background')),
4869 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4869 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4870 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4870 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4871 # use string type, then we can check if something was passed
4871 # use string type, then we can check if something was passed
4872 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4872 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4873 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4873 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4874 _('ADDR')),
4874 _('ADDR')),
4875 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4875 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4876 _('PREFIX')),
4876 _('PREFIX')),
4877 ('n', 'name', '',
4877 ('n', 'name', '',
4878 _('name to show in web pages (default: working directory)'), _('NAME')),
4878 _('name to show in web pages (default: working directory)'), _('NAME')),
4879 ('', 'web-conf', '',
4879 ('', 'web-conf', '',
4880 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4880 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4881 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4881 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4882 _('FILE')),
4882 _('FILE')),
4883 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4883 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4884 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4884 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4885 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4885 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4886 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4886 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4887 ('', 'style', '', _('template style to use'), _('STYLE')),
4887 ('', 'style', '', _('template style to use'), _('STYLE')),
4888 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4888 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4889 ('', 'certificate', '', _('SSL certificate file'), _('FILE')),
4889 ('', 'certificate', '', _('SSL certificate file'), _('FILE')),
4890 ('', 'print-url', None, _('start and print only the URL'))]
4890 ('', 'print-url', None, _('start and print only the URL'))]
4891 + subrepoopts,
4891 + subrepoopts,
4892 _('[OPTION]...'),
4892 _('[OPTION]...'),
4893 optionalrepo=True)
4893 optionalrepo=True)
4894 def serve(ui, repo, **opts):
4894 def serve(ui, repo, **opts):
4895 """start stand-alone webserver
4895 """start stand-alone webserver
4896
4896
4897 Start a local HTTP repository browser and pull server. You can use
4897 Start a local HTTP repository browser and pull server. You can use
4898 this for ad-hoc sharing and browsing of repositories. It is
4898 this for ad-hoc sharing and browsing of repositories. It is
4899 recommended to use a real web server to serve a repository for
4899 recommended to use a real web server to serve a repository for
4900 longer periods of time.
4900 longer periods of time.
4901
4901
4902 Please note that the server does not implement access control.
4902 Please note that the server does not implement access control.
4903 This means that, by default, anybody can read from the server and
4903 This means that, by default, anybody can read from the server and
4904 nobody can write to it by default. Set the ``web.allow-push``
4904 nobody can write to it by default. Set the ``web.allow-push``
4905 option to ``*`` to allow everybody to push to the server. You
4905 option to ``*`` to allow everybody to push to the server. You
4906 should use a real web server if you need to authenticate users.
4906 should use a real web server if you need to authenticate users.
4907
4907
4908 By default, the server logs accesses to stdout and errors to
4908 By default, the server logs accesses to stdout and errors to
4909 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4909 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4910 files.
4910 files.
4911
4911
4912 To have the server choose a free port number to listen on, specify
4912 To have the server choose a free port number to listen on, specify
4913 a port number of 0; in this case, the server will print the port
4913 a port number of 0; in this case, the server will print the port
4914 number it uses.
4914 number it uses.
4915
4915
4916 Returns 0 on success.
4916 Returns 0 on success.
4917 """
4917 """
4918
4918
4919 opts = pycompat.byteskwargs(opts)
4919 opts = pycompat.byteskwargs(opts)
4920 if opts["stdio"] and opts["cmdserver"]:
4920 if opts["stdio"] and opts["cmdserver"]:
4921 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4921 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4922 if opts["print_url"] and ui.verbose:
4922 if opts["print_url"] and ui.verbose:
4923 raise error.Abort(_("cannot use --print-url with --verbose"))
4923 raise error.Abort(_("cannot use --print-url with --verbose"))
4924
4924
4925 if opts["stdio"]:
4925 if opts["stdio"]:
4926 if repo is None:
4926 if repo is None:
4927 raise error.RepoError(_("there is no Mercurial repository here"
4927 raise error.RepoError(_("there is no Mercurial repository here"
4928 " (.hg not found)"))
4928 " (.hg not found)"))
4929 s = wireprotoserver.sshserver(ui, repo)
4929 s = wireprotoserver.sshserver(ui, repo)
4930 s.serve_forever()
4930 s.serve_forever()
4931
4931
4932 service = server.createservice(ui, repo, opts)
4932 service = server.createservice(ui, repo, opts)
4933 return server.runservice(opts, initfn=service.init, runfn=service.run)
4933 return server.runservice(opts, initfn=service.init, runfn=service.run)
4934
4934
4935 _NOTTERSE = 'nothing'
4935 _NOTTERSE = 'nothing'
4936
4936
4937 @command('^status|st',
4937 @command('^status|st',
4938 [('A', 'all', None, _('show status of all files')),
4938 [('A', 'all', None, _('show status of all files')),
4939 ('m', 'modified', None, _('show only modified files')),
4939 ('m', 'modified', None, _('show only modified files')),
4940 ('a', 'added', None, _('show only added files')),
4940 ('a', 'added', None, _('show only added files')),
4941 ('r', 'removed', None, _('show only removed files')),
4941 ('r', 'removed', None, _('show only removed files')),
4942 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4942 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4943 ('c', 'clean', None, _('show only files without changes')),
4943 ('c', 'clean', None, _('show only files without changes')),
4944 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4944 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4945 ('i', 'ignored', None, _('show only ignored files')),
4945 ('i', 'ignored', None, _('show only ignored files')),
4946 ('n', 'no-status', None, _('hide status prefix')),
4946 ('n', 'no-status', None, _('hide status prefix')),
4947 ('t', 'terse', _NOTTERSE, _('show the terse output (EXPERIMENTAL)')),
4947 ('t', 'terse', _NOTTERSE, _('show the terse output (EXPERIMENTAL)')),
4948 ('C', 'copies', None, _('show source of copied files')),
4948 ('C', 'copies', None, _('show source of copied files')),
4949 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4949 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4950 ('', 'rev', [], _('show difference from revision'), _('REV')),
4950 ('', 'rev', [], _('show difference from revision'), _('REV')),
4951 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4951 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4952 ] + walkopts + subrepoopts + formatteropts,
4952 ] + walkopts + subrepoopts + formatteropts,
4953 _('[OPTION]... [FILE]...'),
4953 _('[OPTION]... [FILE]...'),
4954 inferrepo=True,
4954 inferrepo=True,
4955 intents={INTENT_READONLY})
4955 intents={INTENT_READONLY})
4956 def status(ui, repo, *pats, **opts):
4956 def status(ui, repo, *pats, **opts):
4957 """show changed files in the working directory
4957 """show changed files in the working directory
4958
4958
4959 Show status of files in the repository. If names are given, only
4959 Show status of files in the repository. If names are given, only
4960 files that match are shown. Files that are clean or ignored or
4960 files that match are shown. Files that are clean or ignored or
4961 the source of a copy/move operation, are not listed unless
4961 the source of a copy/move operation, are not listed unless
4962 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4962 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4963 Unless options described with "show only ..." are given, the
4963 Unless options described with "show only ..." are given, the
4964 options -mardu are used.
4964 options -mardu are used.
4965
4965
4966 Option -q/--quiet hides untracked (unknown and ignored) files
4966 Option -q/--quiet hides untracked (unknown and ignored) files
4967 unless explicitly requested with -u/--unknown or -i/--ignored.
4967 unless explicitly requested with -u/--unknown or -i/--ignored.
4968
4968
4969 .. note::
4969 .. note::
4970
4970
4971 :hg:`status` may appear to disagree with diff if permissions have
4971 :hg:`status` may appear to disagree with diff if permissions have
4972 changed or a merge has occurred. The standard diff format does
4972 changed or a merge has occurred. The standard diff format does
4973 not report permission changes and diff only reports changes
4973 not report permission changes and diff only reports changes
4974 relative to one merge parent.
4974 relative to one merge parent.
4975
4975
4976 If one revision is given, it is used as the base revision.
4976 If one revision is given, it is used as the base revision.
4977 If two revisions are given, the differences between them are
4977 If two revisions are given, the differences between them are
4978 shown. The --change option can also be used as a shortcut to list
4978 shown. The --change option can also be used as a shortcut to list
4979 the changed files of a revision from its first parent.
4979 the changed files of a revision from its first parent.
4980
4980
4981 The codes used to show the status of files are::
4981 The codes used to show the status of files are::
4982
4982
4983 M = modified
4983 M = modified
4984 A = added
4984 A = added
4985 R = removed
4985 R = removed
4986 C = clean
4986 C = clean
4987 ! = missing (deleted by non-hg command, but still tracked)
4987 ! = missing (deleted by non-hg command, but still tracked)
4988 ? = not tracked
4988 ? = not tracked
4989 I = ignored
4989 I = ignored
4990 = origin of the previous file (with --copies)
4990 = origin of the previous file (with --copies)
4991
4991
4992 .. container:: verbose
4992 .. container:: verbose
4993
4993
4994 The -t/--terse option abbreviates the output by showing only the directory
4994 The -t/--terse option abbreviates the output by showing only the directory
4995 name if all the files in it share the same status. The option takes an
4995 name if all the files in it share the same status. The option takes an
4996 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
4996 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
4997 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
4997 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
4998 for 'ignored' and 'c' for clean.
4998 for 'ignored' and 'c' for clean.
4999
4999
5000 It abbreviates only those statuses which are passed. Note that clean and
5000 It abbreviates only those statuses which are passed. Note that clean and
5001 ignored files are not displayed with '--terse ic' unless the -c/--clean
5001 ignored files are not displayed with '--terse ic' unless the -c/--clean
5002 and -i/--ignored options are also used.
5002 and -i/--ignored options are also used.
5003
5003
5004 The -v/--verbose option shows information when the repository is in an
5004 The -v/--verbose option shows information when the repository is in an
5005 unfinished merge, shelve, rebase state etc. You can have this behavior
5005 unfinished merge, shelve, rebase state etc. You can have this behavior
5006 turned on by default by enabling the ``commands.status.verbose`` option.
5006 turned on by default by enabling the ``commands.status.verbose`` option.
5007
5007
5008 You can skip displaying some of these states by setting
5008 You can skip displaying some of these states by setting
5009 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
5009 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
5010 'histedit', 'merge', 'rebase', or 'unshelve'.
5010 'histedit', 'merge', 'rebase', or 'unshelve'.
5011
5011
5012 Examples:
5012 Examples:
5013
5013
5014 - show changes in the working directory relative to a
5014 - show changes in the working directory relative to a
5015 changeset::
5015 changeset::
5016
5016
5017 hg status --rev 9353
5017 hg status --rev 9353
5018
5018
5019 - show changes in the working directory relative to the
5019 - show changes in the working directory relative to the
5020 current directory (see :hg:`help patterns` for more information)::
5020 current directory (see :hg:`help patterns` for more information)::
5021
5021
5022 hg status re:
5022 hg status re:
5023
5023
5024 - show all changes including copies in an existing changeset::
5024 - show all changes including copies in an existing changeset::
5025
5025
5026 hg status --copies --change 9353
5026 hg status --copies --change 9353
5027
5027
5028 - get a NUL separated list of added files, suitable for xargs::
5028 - get a NUL separated list of added files, suitable for xargs::
5029
5029
5030 hg status -an0
5030 hg status -an0
5031
5031
5032 - show more information about the repository status, abbreviating
5032 - show more information about the repository status, abbreviating
5033 added, removed, modified, deleted, and untracked paths::
5033 added, removed, modified, deleted, and untracked paths::
5034
5034
5035 hg status -v -t mardu
5035 hg status -v -t mardu
5036
5036
5037 Returns 0 on success.
5037 Returns 0 on success.
5038
5038
5039 """
5039 """
5040
5040
5041 opts = pycompat.byteskwargs(opts)
5041 opts = pycompat.byteskwargs(opts)
5042 revs = opts.get('rev')
5042 revs = opts.get('rev')
5043 change = opts.get('change')
5043 change = opts.get('change')
5044 terse = opts.get('terse')
5044 terse = opts.get('terse')
5045 if terse is _NOTTERSE:
5045 if terse is _NOTTERSE:
5046 if revs:
5046 if revs:
5047 terse = ''
5047 terse = ''
5048 else:
5048 else:
5049 terse = ui.config('commands', 'status.terse')
5049 terse = ui.config('commands', 'status.terse')
5050
5050
5051 if revs and change:
5051 if revs and change:
5052 msg = _('cannot specify --rev and --change at the same time')
5052 msg = _('cannot specify --rev and --change at the same time')
5053 raise error.Abort(msg)
5053 raise error.Abort(msg)
5054 elif revs and terse:
5054 elif revs and terse:
5055 msg = _('cannot use --terse with --rev')
5055 msg = _('cannot use --terse with --rev')
5056 raise error.Abort(msg)
5056 raise error.Abort(msg)
5057 elif change:
5057 elif change:
5058 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
5058 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
5059 ctx2 = scmutil.revsingle(repo, change, None)
5059 ctx2 = scmutil.revsingle(repo, change, None)
5060 ctx1 = ctx2.p1()
5060 ctx1 = ctx2.p1()
5061 else:
5061 else:
5062 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
5062 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
5063 ctx1, ctx2 = scmutil.revpair(repo, revs)
5063 ctx1, ctx2 = scmutil.revpair(repo, revs)
5064
5064
5065 if pats or ui.configbool('commands', 'status.relative'):
5065 if pats or ui.configbool('commands', 'status.relative'):
5066 cwd = repo.getcwd()
5066 cwd = repo.getcwd()
5067 else:
5067 else:
5068 cwd = ''
5068 cwd = ''
5069
5069
5070 if opts.get('print0'):
5070 if opts.get('print0'):
5071 end = '\0'
5071 end = '\0'
5072 else:
5072 else:
5073 end = '\n'
5073 end = '\n'
5074 copy = {}
5074 copy = {}
5075 states = 'modified added removed deleted unknown ignored clean'.split()
5075 states = 'modified added removed deleted unknown ignored clean'.split()
5076 show = [k for k in states if opts.get(k)]
5076 show = [k for k in states if opts.get(k)]
5077 if opts.get('all'):
5077 if opts.get('all'):
5078 show += ui.quiet and (states[:4] + ['clean']) or states
5078 show += ui.quiet and (states[:4] + ['clean']) or states
5079
5079
5080 if not show:
5080 if not show:
5081 if ui.quiet:
5081 if ui.quiet:
5082 show = states[:4]
5082 show = states[:4]
5083 else:
5083 else:
5084 show = states[:5]
5084 show = states[:5]
5085
5085
5086 m = scmutil.match(ctx2, pats, opts)
5086 m = scmutil.match(ctx2, pats, opts)
5087 if terse:
5087 if terse:
5088 # we need to compute clean and unknown to terse
5088 # we need to compute clean and unknown to terse
5089 stat = repo.status(ctx1.node(), ctx2.node(), m,
5089 stat = repo.status(ctx1.node(), ctx2.node(), m,
5090 'ignored' in show or 'i' in terse,
5090 'ignored' in show or 'i' in terse,
5091 clean=True, unknown=True,
5091 clean=True, unknown=True,
5092 listsubrepos=opts.get('subrepos'))
5092 listsubrepos=opts.get('subrepos'))
5093
5093
5094 stat = cmdutil.tersedir(stat, terse)
5094 stat = cmdutil.tersedir(stat, terse)
5095 else:
5095 else:
5096 stat = repo.status(ctx1.node(), ctx2.node(), m,
5096 stat = repo.status(ctx1.node(), ctx2.node(), m,
5097 'ignored' in show, 'clean' in show,
5097 'ignored' in show, 'clean' in show,
5098 'unknown' in show, opts.get('subrepos'))
5098 'unknown' in show, opts.get('subrepos'))
5099
5099
5100 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
5100 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
5101
5101
5102 if (opts.get('all') or opts.get('copies')
5102 if (opts.get('all') or opts.get('copies')
5103 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
5103 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
5104 copy = copies.pathcopies(ctx1, ctx2, m)
5104 copy = copies.pathcopies(ctx1, ctx2, m)
5105
5105
5106 ui.pager('status')
5106 ui.pager('status')
5107 fm = ui.formatter('status', opts)
5107 fm = ui.formatter('status', opts)
5108 fmt = '%s' + end
5108 fmt = '%s' + end
5109 showchar = not opts.get('no_status')
5109 showchar = not opts.get('no_status')
5110
5110
5111 for state, char, files in changestates:
5111 for state, char, files in changestates:
5112 if state in show:
5112 if state in show:
5113 label = 'status.' + state
5113 label = 'status.' + state
5114 for f in files:
5114 for f in files:
5115 fm.startitem()
5115 fm.startitem()
5116 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5116 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5117 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
5117 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
5118 if f in copy:
5118 if f in copy:
5119 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5119 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5120 label='status.copied')
5120 label='status.copied')
5121
5121
5122 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
5122 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
5123 and not ui.plain()):
5123 and not ui.plain()):
5124 cmdutil.morestatus(repo, fm)
5124 cmdutil.morestatus(repo, fm)
5125 fm.end()
5125 fm.end()
5126
5126
5127 @command('^summary|sum',
5127 @command('^summary|sum',
5128 [('', 'remote', None, _('check for push and pull'))],
5128 [('', 'remote', None, _('check for push and pull'))],
5129 '[--remote]',
5129 '[--remote]',
5130 intents={INTENT_READONLY})
5130 intents={INTENT_READONLY})
5131 def summary(ui, repo, **opts):
5131 def summary(ui, repo, **opts):
5132 """summarize working directory state
5132 """summarize working directory state
5133
5133
5134 This generates a brief summary of the working directory state,
5134 This generates a brief summary of the working directory state,
5135 including parents, branch, commit status, phase and available updates.
5135 including parents, branch, commit status, phase and available updates.
5136
5136
5137 With the --remote option, this will check the default paths for
5137 With the --remote option, this will check the default paths for
5138 incoming and outgoing changes. This can be time-consuming.
5138 incoming and outgoing changes. This can be time-consuming.
5139
5139
5140 Returns 0 on success.
5140 Returns 0 on success.
5141 """
5141 """
5142
5142
5143 opts = pycompat.byteskwargs(opts)
5143 opts = pycompat.byteskwargs(opts)
5144 ui.pager('summary')
5144 ui.pager('summary')
5145 ctx = repo[None]
5145 ctx = repo[None]
5146 parents = ctx.parents()
5146 parents = ctx.parents()
5147 pnode = parents[0].node()
5147 pnode = parents[0].node()
5148 marks = []
5148 marks = []
5149
5149
5150 ms = None
5150 ms = None
5151 try:
5151 try:
5152 ms = mergemod.mergestate.read(repo)
5152 ms = mergemod.mergestate.read(repo)
5153 except error.UnsupportedMergeRecords as e:
5153 except error.UnsupportedMergeRecords as e:
5154 s = ' '.join(e.recordtypes)
5154 s = ' '.join(e.recordtypes)
5155 ui.warn(
5155 ui.warn(
5156 _('warning: merge state has unsupported record types: %s\n') % s)
5156 _('warning: merge state has unsupported record types: %s\n') % s)
5157 unresolved = []
5157 unresolved = []
5158 else:
5158 else:
5159 unresolved = list(ms.unresolved())
5159 unresolved = list(ms.unresolved())
5160
5160
5161 for p in parents:
5161 for p in parents:
5162 # label with log.changeset (instead of log.parent) since this
5162 # label with log.changeset (instead of log.parent) since this
5163 # shows a working directory parent *changeset*:
5163 # shows a working directory parent *changeset*:
5164 # i18n: column positioning for "hg summary"
5164 # i18n: column positioning for "hg summary"
5165 ui.write(_('parent: %d:%s ') % (p.rev(), p),
5165 ui.write(_('parent: %d:%s ') % (p.rev(), p),
5166 label=logcmdutil.changesetlabels(p))
5166 label=logcmdutil.changesetlabels(p))
5167 ui.write(' '.join(p.tags()), label='log.tag')
5167 ui.write(' '.join(p.tags()), label='log.tag')
5168 if p.bookmarks():
5168 if p.bookmarks():
5169 marks.extend(p.bookmarks())
5169 marks.extend(p.bookmarks())
5170 if p.rev() == -1:
5170 if p.rev() == -1:
5171 if not len(repo):
5171 if not len(repo):
5172 ui.write(_(' (empty repository)'))
5172 ui.write(_(' (empty repository)'))
5173 else:
5173 else:
5174 ui.write(_(' (no revision checked out)'))
5174 ui.write(_(' (no revision checked out)'))
5175 if p.obsolete():
5175 if p.obsolete():
5176 ui.write(_(' (obsolete)'))
5176 ui.write(_(' (obsolete)'))
5177 if p.isunstable():
5177 if p.isunstable():
5178 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5178 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5179 for instability in p.instabilities())
5179 for instability in p.instabilities())
5180 ui.write(' ('
5180 ui.write(' ('
5181 + ', '.join(instabilities)
5181 + ', '.join(instabilities)
5182 + ')')
5182 + ')')
5183 ui.write('\n')
5183 ui.write('\n')
5184 if p.description():
5184 if p.description():
5185 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5185 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5186 label='log.summary')
5186 label='log.summary')
5187
5187
5188 branch = ctx.branch()
5188 branch = ctx.branch()
5189 bheads = repo.branchheads(branch)
5189 bheads = repo.branchheads(branch)
5190 # i18n: column positioning for "hg summary"
5190 # i18n: column positioning for "hg summary"
5191 m = _('branch: %s\n') % branch
5191 m = _('branch: %s\n') % branch
5192 if branch != 'default':
5192 if branch != 'default':
5193 ui.write(m, label='log.branch')
5193 ui.write(m, label='log.branch')
5194 else:
5194 else:
5195 ui.status(m, label='log.branch')
5195 ui.status(m, label='log.branch')
5196
5196
5197 if marks:
5197 if marks:
5198 active = repo._activebookmark
5198 active = repo._activebookmark
5199 # i18n: column positioning for "hg summary"
5199 # i18n: column positioning for "hg summary"
5200 ui.write(_('bookmarks:'), label='log.bookmark')
5200 ui.write(_('bookmarks:'), label='log.bookmark')
5201 if active is not None:
5201 if active is not None:
5202 if active in marks:
5202 if active in marks:
5203 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5203 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5204 marks.remove(active)
5204 marks.remove(active)
5205 else:
5205 else:
5206 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5206 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5207 for m in marks:
5207 for m in marks:
5208 ui.write(' ' + m, label='log.bookmark')
5208 ui.write(' ' + m, label='log.bookmark')
5209 ui.write('\n', label='log.bookmark')
5209 ui.write('\n', label='log.bookmark')
5210
5210
5211 status = repo.status(unknown=True)
5211 status = repo.status(unknown=True)
5212
5212
5213 c = repo.dirstate.copies()
5213 c = repo.dirstate.copies()
5214 copied, renamed = [], []
5214 copied, renamed = [], []
5215 for d, s in c.iteritems():
5215 for d, s in c.iteritems():
5216 if s in status.removed:
5216 if s in status.removed:
5217 status.removed.remove(s)
5217 status.removed.remove(s)
5218 renamed.append(d)
5218 renamed.append(d)
5219 else:
5219 else:
5220 copied.append(d)
5220 copied.append(d)
5221 if d in status.added:
5221 if d in status.added:
5222 status.added.remove(d)
5222 status.added.remove(d)
5223
5223
5224 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5224 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5225
5225
5226 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5226 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5227 (ui.label(_('%d added'), 'status.added'), status.added),
5227 (ui.label(_('%d added'), 'status.added'), status.added),
5228 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5228 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5229 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5229 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5230 (ui.label(_('%d copied'), 'status.copied'), copied),
5230 (ui.label(_('%d copied'), 'status.copied'), copied),
5231 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5231 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5232 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5232 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5233 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5233 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5234 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5234 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5235 t = []
5235 t = []
5236 for l, s in labels:
5236 for l, s in labels:
5237 if s:
5237 if s:
5238 t.append(l % len(s))
5238 t.append(l % len(s))
5239
5239
5240 t = ', '.join(t)
5240 t = ', '.join(t)
5241 cleanworkdir = False
5241 cleanworkdir = False
5242
5242
5243 if repo.vfs.exists('graftstate'):
5243 if repo.vfs.exists('graftstate'):
5244 t += _(' (graft in progress)')
5244 t += _(' (graft in progress)')
5245 if repo.vfs.exists('updatestate'):
5245 if repo.vfs.exists('updatestate'):
5246 t += _(' (interrupted update)')
5246 t += _(' (interrupted update)')
5247 elif len(parents) > 1:
5247 elif len(parents) > 1:
5248 t += _(' (merge)')
5248 t += _(' (merge)')
5249 elif branch != parents[0].branch():
5249 elif branch != parents[0].branch():
5250 t += _(' (new branch)')
5250 t += _(' (new branch)')
5251 elif (parents[0].closesbranch() and
5251 elif (parents[0].closesbranch() and
5252 pnode in repo.branchheads(branch, closed=True)):
5252 pnode in repo.branchheads(branch, closed=True)):
5253 t += _(' (head closed)')
5253 t += _(' (head closed)')
5254 elif not (status.modified or status.added or status.removed or renamed or
5254 elif not (status.modified or status.added or status.removed or renamed or
5255 copied or subs):
5255 copied or subs):
5256 t += _(' (clean)')
5256 t += _(' (clean)')
5257 cleanworkdir = True
5257 cleanworkdir = True
5258 elif pnode not in bheads:
5258 elif pnode not in bheads:
5259 t += _(' (new branch head)')
5259 t += _(' (new branch head)')
5260
5260
5261 if parents:
5261 if parents:
5262 pendingphase = max(p.phase() for p in parents)
5262 pendingphase = max(p.phase() for p in parents)
5263 else:
5263 else:
5264 pendingphase = phases.public
5264 pendingphase = phases.public
5265
5265
5266 if pendingphase > phases.newcommitphase(ui):
5266 if pendingphase > phases.newcommitphase(ui):
5267 t += ' (%s)' % phases.phasenames[pendingphase]
5267 t += ' (%s)' % phases.phasenames[pendingphase]
5268
5268
5269 if cleanworkdir:
5269 if cleanworkdir:
5270 # i18n: column positioning for "hg summary"
5270 # i18n: column positioning for "hg summary"
5271 ui.status(_('commit: %s\n') % t.strip())
5271 ui.status(_('commit: %s\n') % t.strip())
5272 else:
5272 else:
5273 # i18n: column positioning for "hg summary"
5273 # i18n: column positioning for "hg summary"
5274 ui.write(_('commit: %s\n') % t.strip())
5274 ui.write(_('commit: %s\n') % t.strip())
5275
5275
5276 # all ancestors of branch heads - all ancestors of parent = new csets
5276 # all ancestors of branch heads - all ancestors of parent = new csets
5277 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5277 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5278 bheads))
5278 bheads))
5279
5279
5280 if new == 0:
5280 if new == 0:
5281 # i18n: column positioning for "hg summary"
5281 # i18n: column positioning for "hg summary"
5282 ui.status(_('update: (current)\n'))
5282 ui.status(_('update: (current)\n'))
5283 elif pnode not in bheads:
5283 elif pnode not in bheads:
5284 # i18n: column positioning for "hg summary"
5284 # i18n: column positioning for "hg summary"
5285 ui.write(_('update: %d new changesets (update)\n') % new)
5285 ui.write(_('update: %d new changesets (update)\n') % new)
5286 else:
5286 else:
5287 # i18n: column positioning for "hg summary"
5287 # i18n: column positioning for "hg summary"
5288 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5288 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5289 (new, len(bheads)))
5289 (new, len(bheads)))
5290
5290
5291 t = []
5291 t = []
5292 draft = len(repo.revs('draft()'))
5292 draft = len(repo.revs('draft()'))
5293 if draft:
5293 if draft:
5294 t.append(_('%d draft') % draft)
5294 t.append(_('%d draft') % draft)
5295 secret = len(repo.revs('secret()'))
5295 secret = len(repo.revs('secret()'))
5296 if secret:
5296 if secret:
5297 t.append(_('%d secret') % secret)
5297 t.append(_('%d secret') % secret)
5298
5298
5299 if draft or secret:
5299 if draft or secret:
5300 ui.status(_('phases: %s\n') % ', '.join(t))
5300 ui.status(_('phases: %s\n') % ', '.join(t))
5301
5301
5302 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5302 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5303 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5303 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5304 numtrouble = len(repo.revs(trouble + "()"))
5304 numtrouble = len(repo.revs(trouble + "()"))
5305 # We write all the possibilities to ease translation
5305 # We write all the possibilities to ease translation
5306 troublemsg = {
5306 troublemsg = {
5307 "orphan": _("orphan: %d changesets"),
5307 "orphan": _("orphan: %d changesets"),
5308 "contentdivergent": _("content-divergent: %d changesets"),
5308 "contentdivergent": _("content-divergent: %d changesets"),
5309 "phasedivergent": _("phase-divergent: %d changesets"),
5309 "phasedivergent": _("phase-divergent: %d changesets"),
5310 }
5310 }
5311 if numtrouble > 0:
5311 if numtrouble > 0:
5312 ui.status(troublemsg[trouble] % numtrouble + "\n")
5312 ui.status(troublemsg[trouble] % numtrouble + "\n")
5313
5313
5314 cmdutil.summaryhooks(ui, repo)
5314 cmdutil.summaryhooks(ui, repo)
5315
5315
5316 if opts.get('remote'):
5316 if opts.get('remote'):
5317 needsincoming, needsoutgoing = True, True
5317 needsincoming, needsoutgoing = True, True
5318 else:
5318 else:
5319 needsincoming, needsoutgoing = False, False
5319 needsincoming, needsoutgoing = False, False
5320 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5320 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5321 if i:
5321 if i:
5322 needsincoming = True
5322 needsincoming = True
5323 if o:
5323 if o:
5324 needsoutgoing = True
5324 needsoutgoing = True
5325 if not needsincoming and not needsoutgoing:
5325 if not needsincoming and not needsoutgoing:
5326 return
5326 return
5327
5327
5328 def getincoming():
5328 def getincoming():
5329 source, branches = hg.parseurl(ui.expandpath('default'))
5329 source, branches = hg.parseurl(ui.expandpath('default'))
5330 sbranch = branches[0]
5330 sbranch = branches[0]
5331 try:
5331 try:
5332 other = hg.peer(repo, {}, source)
5332 other = hg.peer(repo, {}, source)
5333 except error.RepoError:
5333 except error.RepoError:
5334 if opts.get('remote'):
5334 if opts.get('remote'):
5335 raise
5335 raise
5336 return source, sbranch, None, None, None
5336 return source, sbranch, None, None, None
5337 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5337 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5338 if revs:
5338 if revs:
5339 revs = [other.lookup(rev) for rev in revs]
5339 revs = [other.lookup(rev) for rev in revs]
5340 ui.debug('comparing with %s\n' % util.hidepassword(source))
5340 ui.debug('comparing with %s\n' % util.hidepassword(source))
5341 repo.ui.pushbuffer()
5341 repo.ui.pushbuffer()
5342 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5342 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5343 repo.ui.popbuffer()
5343 repo.ui.popbuffer()
5344 return source, sbranch, other, commoninc, commoninc[1]
5344 return source, sbranch, other, commoninc, commoninc[1]
5345
5345
5346 if needsincoming:
5346 if needsincoming:
5347 source, sbranch, sother, commoninc, incoming = getincoming()
5347 source, sbranch, sother, commoninc, incoming = getincoming()
5348 else:
5348 else:
5349 source = sbranch = sother = commoninc = incoming = None
5349 source = sbranch = sother = commoninc = incoming = None
5350
5350
5351 def getoutgoing():
5351 def getoutgoing():
5352 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5352 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5353 dbranch = branches[0]
5353 dbranch = branches[0]
5354 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5354 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5355 if source != dest:
5355 if source != dest:
5356 try:
5356 try:
5357 dother = hg.peer(repo, {}, dest)
5357 dother = hg.peer(repo, {}, dest)
5358 except error.RepoError:
5358 except error.RepoError:
5359 if opts.get('remote'):
5359 if opts.get('remote'):
5360 raise
5360 raise
5361 return dest, dbranch, None, None
5361 return dest, dbranch, None, None
5362 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5362 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5363 elif sother is None:
5363 elif sother is None:
5364 # there is no explicit destination peer, but source one is invalid
5364 # there is no explicit destination peer, but source one is invalid
5365 return dest, dbranch, None, None
5365 return dest, dbranch, None, None
5366 else:
5366 else:
5367 dother = sother
5367 dother = sother
5368 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5368 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5369 common = None
5369 common = None
5370 else:
5370 else:
5371 common = commoninc
5371 common = commoninc
5372 if revs:
5372 if revs:
5373 revs = [repo.lookup(rev) for rev in revs]
5373 revs = [repo.lookup(rev) for rev in revs]
5374 repo.ui.pushbuffer()
5374 repo.ui.pushbuffer()
5375 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5375 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5376 commoninc=common)
5376 commoninc=common)
5377 repo.ui.popbuffer()
5377 repo.ui.popbuffer()
5378 return dest, dbranch, dother, outgoing
5378 return dest, dbranch, dother, outgoing
5379
5379
5380 if needsoutgoing:
5380 if needsoutgoing:
5381 dest, dbranch, dother, outgoing = getoutgoing()
5381 dest, dbranch, dother, outgoing = getoutgoing()
5382 else:
5382 else:
5383 dest = dbranch = dother = outgoing = None
5383 dest = dbranch = dother = outgoing = None
5384
5384
5385 if opts.get('remote'):
5385 if opts.get('remote'):
5386 t = []
5386 t = []
5387 if incoming:
5387 if incoming:
5388 t.append(_('1 or more incoming'))
5388 t.append(_('1 or more incoming'))
5389 o = outgoing.missing
5389 o = outgoing.missing
5390 if o:
5390 if o:
5391 t.append(_('%d outgoing') % len(o))
5391 t.append(_('%d outgoing') % len(o))
5392 other = dother or sother
5392 other = dother or sother
5393 if 'bookmarks' in other.listkeys('namespaces'):
5393 if 'bookmarks' in other.listkeys('namespaces'):
5394 counts = bookmarks.summary(repo, other)
5394 counts = bookmarks.summary(repo, other)
5395 if counts[0] > 0:
5395 if counts[0] > 0:
5396 t.append(_('%d incoming bookmarks') % counts[0])
5396 t.append(_('%d incoming bookmarks') % counts[0])
5397 if counts[1] > 0:
5397 if counts[1] > 0:
5398 t.append(_('%d outgoing bookmarks') % counts[1])
5398 t.append(_('%d outgoing bookmarks') % counts[1])
5399
5399
5400 if t:
5400 if t:
5401 # i18n: column positioning for "hg summary"
5401 # i18n: column positioning for "hg summary"
5402 ui.write(_('remote: %s\n') % (', '.join(t)))
5402 ui.write(_('remote: %s\n') % (', '.join(t)))
5403 else:
5403 else:
5404 # i18n: column positioning for "hg summary"
5404 # i18n: column positioning for "hg summary"
5405 ui.status(_('remote: (synced)\n'))
5405 ui.status(_('remote: (synced)\n'))
5406
5406
5407 cmdutil.summaryremotehooks(ui, repo, opts,
5407 cmdutil.summaryremotehooks(ui, repo, opts,
5408 ((source, sbranch, sother, commoninc),
5408 ((source, sbranch, sother, commoninc),
5409 (dest, dbranch, dother, outgoing)))
5409 (dest, dbranch, dother, outgoing)))
5410
5410
5411 @command('tag',
5411 @command('tag',
5412 [('f', 'force', None, _('force tag')),
5412 [('f', 'force', None, _('force tag')),
5413 ('l', 'local', None, _('make the tag local')),
5413 ('l', 'local', None, _('make the tag local')),
5414 ('r', 'rev', '', _('revision to tag'), _('REV')),
5414 ('r', 'rev', '', _('revision to tag'), _('REV')),
5415 ('', 'remove', None, _('remove a tag')),
5415 ('', 'remove', None, _('remove a tag')),
5416 # -l/--local is already there, commitopts cannot be used
5416 # -l/--local is already there, commitopts cannot be used
5417 ('e', 'edit', None, _('invoke editor on commit messages')),
5417 ('e', 'edit', None, _('invoke editor on commit messages')),
5418 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5418 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5419 ] + commitopts2,
5419 ] + commitopts2,
5420 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5420 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5421 def tag(ui, repo, name1, *names, **opts):
5421 def tag(ui, repo, name1, *names, **opts):
5422 """add one or more tags for the current or given revision
5422 """add one or more tags for the current or given revision
5423
5423
5424 Name a particular revision using <name>.
5424 Name a particular revision using <name>.
5425
5425
5426 Tags are used to name particular revisions of the repository and are
5426 Tags are used to name particular revisions of the repository and are
5427 very useful to compare different revisions, to go back to significant
5427 very useful to compare different revisions, to go back to significant
5428 earlier versions or to mark branch points as releases, etc. Changing
5428 earlier versions or to mark branch points as releases, etc. Changing
5429 an existing tag is normally disallowed; use -f/--force to override.
5429 an existing tag is normally disallowed; use -f/--force to override.
5430
5430
5431 If no revision is given, the parent of the working directory is
5431 If no revision is given, the parent of the working directory is
5432 used.
5432 used.
5433
5433
5434 To facilitate version control, distribution, and merging of tags,
5434 To facilitate version control, distribution, and merging of tags,
5435 they are stored as a file named ".hgtags" which is managed similarly
5435 they are stored as a file named ".hgtags" which is managed similarly
5436 to other project files and can be hand-edited if necessary. This
5436 to other project files and can be hand-edited if necessary. This
5437 also means that tagging creates a new commit. The file
5437 also means that tagging creates a new commit. The file
5438 ".hg/localtags" is used for local tags (not shared among
5438 ".hg/localtags" is used for local tags (not shared among
5439 repositories).
5439 repositories).
5440
5440
5441 Tag commits are usually made at the head of a branch. If the parent
5441 Tag commits are usually made at the head of a branch. If the parent
5442 of the working directory is not a branch head, :hg:`tag` aborts; use
5442 of the working directory is not a branch head, :hg:`tag` aborts; use
5443 -f/--force to force the tag commit to be based on a non-head
5443 -f/--force to force the tag commit to be based on a non-head
5444 changeset.
5444 changeset.
5445
5445
5446 See :hg:`help dates` for a list of formats valid for -d/--date.
5446 See :hg:`help dates` for a list of formats valid for -d/--date.
5447
5447
5448 Since tag names have priority over branch names during revision
5448 Since tag names have priority over branch names during revision
5449 lookup, using an existing branch name as a tag name is discouraged.
5449 lookup, using an existing branch name as a tag name is discouraged.
5450
5450
5451 Returns 0 on success.
5451 Returns 0 on success.
5452 """
5452 """
5453 opts = pycompat.byteskwargs(opts)
5453 opts = pycompat.byteskwargs(opts)
5454 with repo.wlock(), repo.lock():
5454 with repo.wlock(), repo.lock():
5455 rev_ = "."
5455 rev_ = "."
5456 names = [t.strip() for t in (name1,) + names]
5456 names = [t.strip() for t in (name1,) + names]
5457 if len(names) != len(set(names)):
5457 if len(names) != len(set(names)):
5458 raise error.Abort(_('tag names must be unique'))
5458 raise error.Abort(_('tag names must be unique'))
5459 for n in names:
5459 for n in names:
5460 scmutil.checknewlabel(repo, n, 'tag')
5460 scmutil.checknewlabel(repo, n, 'tag')
5461 if not n:
5461 if not n:
5462 raise error.Abort(_('tag names cannot consist entirely of '
5462 raise error.Abort(_('tag names cannot consist entirely of '
5463 'whitespace'))
5463 'whitespace'))
5464 if opts.get('rev') and opts.get('remove'):
5464 if opts.get('rev') and opts.get('remove'):
5465 raise error.Abort(_("--rev and --remove are incompatible"))
5465 raise error.Abort(_("--rev and --remove are incompatible"))
5466 if opts.get('rev'):
5466 if opts.get('rev'):
5467 rev_ = opts['rev']
5467 rev_ = opts['rev']
5468 message = opts.get('message')
5468 message = opts.get('message')
5469 if opts.get('remove'):
5469 if opts.get('remove'):
5470 if opts.get('local'):
5470 if opts.get('local'):
5471 expectedtype = 'local'
5471 expectedtype = 'local'
5472 else:
5472 else:
5473 expectedtype = 'global'
5473 expectedtype = 'global'
5474
5474
5475 for n in names:
5475 for n in names:
5476 if not repo.tagtype(n):
5476 if not repo.tagtype(n):
5477 raise error.Abort(_("tag '%s' does not exist") % n)
5477 raise error.Abort(_("tag '%s' does not exist") % n)
5478 if repo.tagtype(n) != expectedtype:
5478 if repo.tagtype(n) != expectedtype:
5479 if expectedtype == 'global':
5479 if expectedtype == 'global':
5480 raise error.Abort(_("tag '%s' is not a global tag") % n)
5480 raise error.Abort(_("tag '%s' is not a global tag") % n)
5481 else:
5481 else:
5482 raise error.Abort(_("tag '%s' is not a local tag") % n)
5482 raise error.Abort(_("tag '%s' is not a local tag") % n)
5483 rev_ = 'null'
5483 rev_ = 'null'
5484 if not message:
5484 if not message:
5485 # we don't translate commit messages
5485 # we don't translate commit messages
5486 message = 'Removed tag %s' % ', '.join(names)
5486 message = 'Removed tag %s' % ', '.join(names)
5487 elif not opts.get('force'):
5487 elif not opts.get('force'):
5488 for n in names:
5488 for n in names:
5489 if n in repo.tags():
5489 if n in repo.tags():
5490 raise error.Abort(_("tag '%s' already exists "
5490 raise error.Abort(_("tag '%s' already exists "
5491 "(use -f to force)") % n)
5491 "(use -f to force)") % n)
5492 if not opts.get('local'):
5492 if not opts.get('local'):
5493 p1, p2 = repo.dirstate.parents()
5493 p1, p2 = repo.dirstate.parents()
5494 if p2 != nullid:
5494 if p2 != nullid:
5495 raise error.Abort(_('uncommitted merge'))
5495 raise error.Abort(_('uncommitted merge'))
5496 bheads = repo.branchheads()
5496 bheads = repo.branchheads()
5497 if not opts.get('force') and bheads and p1 not in bheads:
5497 if not opts.get('force') and bheads and p1 not in bheads:
5498 raise error.Abort(_('working directory is not at a branch head '
5498 raise error.Abort(_('working directory is not at a branch head '
5499 '(use -f to force)'))
5499 '(use -f to force)'))
5500 node = scmutil.revsingle(repo, rev_).node()
5500 node = scmutil.revsingle(repo, rev_).node()
5501
5501
5502 if not message:
5502 if not message:
5503 # we don't translate commit messages
5503 # we don't translate commit messages
5504 message = ('Added tag %s for changeset %s' %
5504 message = ('Added tag %s for changeset %s' %
5505 (', '.join(names), short(node)))
5505 (', '.join(names), short(node)))
5506
5506
5507 date = opts.get('date')
5507 date = opts.get('date')
5508 if date:
5508 if date:
5509 date = dateutil.parsedate(date)
5509 date = dateutil.parsedate(date)
5510
5510
5511 if opts.get('remove'):
5511 if opts.get('remove'):
5512 editform = 'tag.remove'
5512 editform = 'tag.remove'
5513 else:
5513 else:
5514 editform = 'tag.add'
5514 editform = 'tag.add'
5515 editor = cmdutil.getcommiteditor(editform=editform,
5515 editor = cmdutil.getcommiteditor(editform=editform,
5516 **pycompat.strkwargs(opts))
5516 **pycompat.strkwargs(opts))
5517
5517
5518 # don't allow tagging the null rev
5518 # don't allow tagging the null rev
5519 if (not opts.get('remove') and
5519 if (not opts.get('remove') and
5520 scmutil.revsingle(repo, rev_).rev() == nullrev):
5520 scmutil.revsingle(repo, rev_).rev() == nullrev):
5521 raise error.Abort(_("cannot tag null revision"))
5521 raise error.Abort(_("cannot tag null revision"))
5522
5522
5523 tagsmod.tag(repo, names, node, message, opts.get('local'),
5523 tagsmod.tag(repo, names, node, message, opts.get('local'),
5524 opts.get('user'), date, editor=editor)
5524 opts.get('user'), date, editor=editor)
5525
5525
5526 @command('tags', formatteropts, '', intents={INTENT_READONLY})
5526 @command('tags', formatteropts, '', intents={INTENT_READONLY})
5527 def tags(ui, repo, **opts):
5527 def tags(ui, repo, **opts):
5528 """list repository tags
5528 """list repository tags
5529
5529
5530 This lists both regular and local tags. When the -v/--verbose
5530 This lists both regular and local tags. When the -v/--verbose
5531 switch is used, a third column "local" is printed for local tags.
5531 switch is used, a third column "local" is printed for local tags.
5532 When the -q/--quiet switch is used, only the tag name is printed.
5532 When the -q/--quiet switch is used, only the tag name is printed.
5533
5533
5534 Returns 0 on success.
5534 Returns 0 on success.
5535 """
5535 """
5536
5536
5537 opts = pycompat.byteskwargs(opts)
5537 opts = pycompat.byteskwargs(opts)
5538 ui.pager('tags')
5538 ui.pager('tags')
5539 fm = ui.formatter('tags', opts)
5539 fm = ui.formatter('tags', opts)
5540 contexthint = fm.contexthint('tag rev node type')
5540 contexthint = fm.contexthint('tag rev node type')
5541 hexfunc = fm.hexfunc
5541 hexfunc = fm.hexfunc
5542 tagtype = ""
5542 tagtype = ""
5543
5543
5544 for t, n in reversed(repo.tagslist()):
5544 for t, n in reversed(repo.tagslist()):
5545 hn = hexfunc(n)
5545 hn = hexfunc(n)
5546 label = 'tags.normal'
5546 label = 'tags.normal'
5547 tagtype = ''
5547 tagtype = ''
5548 if repo.tagtype(t) == 'local':
5548 if repo.tagtype(t) == 'local':
5549 label = 'tags.local'
5549 label = 'tags.local'
5550 tagtype = 'local'
5550 tagtype = 'local'
5551
5551
5552 fm.startitem()
5552 fm.startitem()
5553 if 'ctx' in contexthint:
5553 if 'ctx' in contexthint:
5554 fm.context(ctx=repo[n])
5554 fm.context(ctx=repo[n])
5555 fm.write('tag', '%s', t, label=label)
5555 fm.write('tag', '%s', t, label=label)
5556 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5556 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5557 fm.condwrite(not ui.quiet, 'rev node', fmt,
5557 fm.condwrite(not ui.quiet, 'rev node', fmt,
5558 repo.changelog.rev(n), hn, label=label)
5558 repo.changelog.rev(n), hn, label=label)
5559 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5559 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5560 tagtype, label=label)
5560 tagtype, label=label)
5561 fm.plain('\n')
5561 fm.plain('\n')
5562 fm.end()
5562 fm.end()
5563
5563
5564 @command('tip',
5564 @command('tip',
5565 [('p', 'patch', None, _('show patch')),
5565 [('p', 'patch', None, _('show patch')),
5566 ('g', 'git', None, _('use git extended diff format')),
5566 ('g', 'git', None, _('use git extended diff format')),
5567 ] + templateopts,
5567 ] + templateopts,
5568 _('[-p] [-g]'))
5568 _('[-p] [-g]'))
5569 def tip(ui, repo, **opts):
5569 def tip(ui, repo, **opts):
5570 """show the tip revision (DEPRECATED)
5570 """show the tip revision (DEPRECATED)
5571
5571
5572 The tip revision (usually just called the tip) is the changeset
5572 The tip revision (usually just called the tip) is the changeset
5573 most recently added to the repository (and therefore the most
5573 most recently added to the repository (and therefore the most
5574 recently changed head).
5574 recently changed head).
5575
5575
5576 If you have just made a commit, that commit will be the tip. If
5576 If you have just made a commit, that commit will be the tip. If
5577 you have just pulled changes from another repository, the tip of
5577 you have just pulled changes from another repository, the tip of
5578 that repository becomes the current tip. The "tip" tag is special
5578 that repository becomes the current tip. The "tip" tag is special
5579 and cannot be renamed or assigned to a different changeset.
5579 and cannot be renamed or assigned to a different changeset.
5580
5580
5581 This command is deprecated, please use :hg:`heads` instead.
5581 This command is deprecated, please use :hg:`heads` instead.
5582
5582
5583 Returns 0 on success.
5583 Returns 0 on success.
5584 """
5584 """
5585 opts = pycompat.byteskwargs(opts)
5585 opts = pycompat.byteskwargs(opts)
5586 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5586 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5587 displayer.show(repo['tip'])
5587 displayer.show(repo['tip'])
5588 displayer.close()
5588 displayer.close()
5589
5589
5590 @command('unbundle',
5590 @command('unbundle',
5591 [('u', 'update', None,
5591 [('u', 'update', None,
5592 _('update to new branch head if changesets were unbundled'))],
5592 _('update to new branch head if changesets were unbundled'))],
5593 _('[-u] FILE...'))
5593 _('[-u] FILE...'))
5594 def unbundle(ui, repo, fname1, *fnames, **opts):
5594 def unbundle(ui, repo, fname1, *fnames, **opts):
5595 """apply one or more bundle files
5595 """apply one or more bundle files
5596
5596
5597 Apply one or more bundle files generated by :hg:`bundle`.
5597 Apply one or more bundle files generated by :hg:`bundle`.
5598
5598
5599 Returns 0 on success, 1 if an update has unresolved files.
5599 Returns 0 on success, 1 if an update has unresolved files.
5600 """
5600 """
5601 fnames = (fname1,) + fnames
5601 fnames = (fname1,) + fnames
5602
5602
5603 with repo.lock():
5603 with repo.lock():
5604 for fname in fnames:
5604 for fname in fnames:
5605 f = hg.openpath(ui, fname)
5605 f = hg.openpath(ui, fname)
5606 gen = exchange.readbundle(ui, f, fname)
5606 gen = exchange.readbundle(ui, f, fname)
5607 if isinstance(gen, streamclone.streamcloneapplier):
5607 if isinstance(gen, streamclone.streamcloneapplier):
5608 raise error.Abort(
5608 raise error.Abort(
5609 _('packed bundles cannot be applied with '
5609 _('packed bundles cannot be applied with '
5610 '"hg unbundle"'),
5610 '"hg unbundle"'),
5611 hint=_('use "hg debugapplystreamclonebundle"'))
5611 hint=_('use "hg debugapplystreamclonebundle"'))
5612 url = 'bundle:' + fname
5612 url = 'bundle:' + fname
5613 try:
5613 try:
5614 txnname = 'unbundle'
5614 txnname = 'unbundle'
5615 if not isinstance(gen, bundle2.unbundle20):
5615 if not isinstance(gen, bundle2.unbundle20):
5616 txnname = 'unbundle\n%s' % util.hidepassword(url)
5616 txnname = 'unbundle\n%s' % util.hidepassword(url)
5617 with repo.transaction(txnname) as tr:
5617 with repo.transaction(txnname) as tr:
5618 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5618 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5619 url=url)
5619 url=url)
5620 except error.BundleUnknownFeatureError as exc:
5620 except error.BundleUnknownFeatureError as exc:
5621 raise error.Abort(
5621 raise error.Abort(
5622 _('%s: unknown bundle feature, %s') % (fname, exc),
5622 _('%s: unknown bundle feature, %s') % (fname, exc),
5623 hint=_("see https://mercurial-scm.org/"
5623 hint=_("see https://mercurial-scm.org/"
5624 "wiki/BundleFeature for more "
5624 "wiki/BundleFeature for more "
5625 "information"))
5625 "information"))
5626 modheads = bundle2.combinechangegroupresults(op)
5626 modheads = bundle2.combinechangegroupresults(op)
5627
5627
5628 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5628 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5629
5629
5630 @command('^update|up|checkout|co',
5630 @command('^update|up|checkout|co',
5631 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5631 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5632 ('c', 'check', None, _('require clean working directory')),
5632 ('c', 'check', None, _('require clean working directory')),
5633 ('m', 'merge', None, _('merge uncommitted changes')),
5633 ('m', 'merge', None, _('merge uncommitted changes')),
5634 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5634 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5635 ('r', 'rev', '', _('revision'), _('REV'))
5635 ('r', 'rev', '', _('revision'), _('REV'))
5636 ] + mergetoolopts,
5636 ] + mergetoolopts,
5637 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5637 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5638 def update(ui, repo, node=None, **opts):
5638 def update(ui, repo, node=None, **opts):
5639 """update working directory (or switch revisions)
5639 """update working directory (or switch revisions)
5640
5640
5641 Update the repository's working directory to the specified
5641 Update the repository's working directory to the specified
5642 changeset. If no changeset is specified, update to the tip of the
5642 changeset. If no changeset is specified, update to the tip of the
5643 current named branch and move the active bookmark (see :hg:`help
5643 current named branch and move the active bookmark (see :hg:`help
5644 bookmarks`).
5644 bookmarks`).
5645
5645
5646 Update sets the working directory's parent revision to the specified
5646 Update sets the working directory's parent revision to the specified
5647 changeset (see :hg:`help parents`).
5647 changeset (see :hg:`help parents`).
5648
5648
5649 If the changeset is not a descendant or ancestor of the working
5649 If the changeset is not a descendant or ancestor of the working
5650 directory's parent and there are uncommitted changes, the update is
5650 directory's parent and there are uncommitted changes, the update is
5651 aborted. With the -c/--check option, the working directory is checked
5651 aborted. With the -c/--check option, the working directory is checked
5652 for uncommitted changes; if none are found, the working directory is
5652 for uncommitted changes; if none are found, the working directory is
5653 updated to the specified changeset.
5653 updated to the specified changeset.
5654
5654
5655 .. container:: verbose
5655 .. container:: verbose
5656
5656
5657 The -C/--clean, -c/--check, and -m/--merge options control what
5657 The -C/--clean, -c/--check, and -m/--merge options control what
5658 happens if the working directory contains uncommitted changes.
5658 happens if the working directory contains uncommitted changes.
5659 At most of one of them can be specified.
5659 At most of one of them can be specified.
5660
5660
5661 1. If no option is specified, and if
5661 1. If no option is specified, and if
5662 the requested changeset is an ancestor or descendant of
5662 the requested changeset is an ancestor or descendant of
5663 the working directory's parent, the uncommitted changes
5663 the working directory's parent, the uncommitted changes
5664 are merged into the requested changeset and the merged
5664 are merged into the requested changeset and the merged
5665 result is left uncommitted. If the requested changeset is
5665 result is left uncommitted. If the requested changeset is
5666 not an ancestor or descendant (that is, it is on another
5666 not an ancestor or descendant (that is, it is on another
5667 branch), the update is aborted and the uncommitted changes
5667 branch), the update is aborted and the uncommitted changes
5668 are preserved.
5668 are preserved.
5669
5669
5670 2. With the -m/--merge option, the update is allowed even if the
5670 2. With the -m/--merge option, the update is allowed even if the
5671 requested changeset is not an ancestor or descendant of
5671 requested changeset is not an ancestor or descendant of
5672 the working directory's parent.
5672 the working directory's parent.
5673
5673
5674 3. With the -c/--check option, the update is aborted and the
5674 3. With the -c/--check option, the update is aborted and the
5675 uncommitted changes are preserved.
5675 uncommitted changes are preserved.
5676
5676
5677 4. With the -C/--clean option, uncommitted changes are discarded and
5677 4. With the -C/--clean option, uncommitted changes are discarded and
5678 the working directory is updated to the requested changeset.
5678 the working directory is updated to the requested changeset.
5679
5679
5680 To cancel an uncommitted merge (and lose your changes), use
5680 To cancel an uncommitted merge (and lose your changes), use
5681 :hg:`merge --abort`.
5681 :hg:`merge --abort`.
5682
5682
5683 Use null as the changeset to remove the working directory (like
5683 Use null as the changeset to remove the working directory (like
5684 :hg:`clone -U`).
5684 :hg:`clone -U`).
5685
5685
5686 If you want to revert just one file to an older revision, use
5686 If you want to revert just one file to an older revision, use
5687 :hg:`revert [-r REV] NAME`.
5687 :hg:`revert [-r REV] NAME`.
5688
5688
5689 See :hg:`help dates` for a list of formats valid for -d/--date.
5689 See :hg:`help dates` for a list of formats valid for -d/--date.
5690
5690
5691 Returns 0 on success, 1 if there are unresolved files.
5691 Returns 0 on success, 1 if there are unresolved files.
5692 """
5692 """
5693 rev = opts.get(r'rev')
5693 rev = opts.get(r'rev')
5694 date = opts.get(r'date')
5694 date = opts.get(r'date')
5695 clean = opts.get(r'clean')
5695 clean = opts.get(r'clean')
5696 check = opts.get(r'check')
5696 check = opts.get(r'check')
5697 merge = opts.get(r'merge')
5697 merge = opts.get(r'merge')
5698 if rev and node:
5698 if rev and node:
5699 raise error.Abort(_("please specify just one revision"))
5699 raise error.Abort(_("please specify just one revision"))
5700
5700
5701 if ui.configbool('commands', 'update.requiredest'):
5701 if ui.configbool('commands', 'update.requiredest'):
5702 if not node and not rev and not date:
5702 if not node and not rev and not date:
5703 raise error.Abort(_('you must specify a destination'),
5703 raise error.Abort(_('you must specify a destination'),
5704 hint=_('for example: hg update ".::"'))
5704 hint=_('for example: hg update ".::"'))
5705
5705
5706 if rev is None or rev == '':
5706 if rev is None or rev == '':
5707 rev = node
5707 rev = node
5708
5708
5709 if date and rev is not None:
5709 if date and rev is not None:
5710 raise error.Abort(_("you can't specify a revision and a date"))
5710 raise error.Abort(_("you can't specify a revision and a date"))
5711
5711
5712 if len([x for x in (clean, check, merge) if x]) > 1:
5712 if len([x for x in (clean, check, merge) if x]) > 1:
5713 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5713 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5714 "or -m/--merge"))
5714 "or -m/--merge"))
5715
5715
5716 updatecheck = None
5716 updatecheck = None
5717 if check:
5717 if check:
5718 updatecheck = 'abort'
5718 updatecheck = 'abort'
5719 elif merge:
5719 elif merge:
5720 updatecheck = 'none'
5720 updatecheck = 'none'
5721
5721
5722 with repo.wlock():
5722 with repo.wlock():
5723 cmdutil.clearunfinished(repo)
5723 cmdutil.clearunfinished(repo)
5724
5724
5725 if date:
5725 if date:
5726 rev = cmdutil.finddate(ui, repo, date)
5726 rev = cmdutil.finddate(ui, repo, date)
5727
5727
5728 # if we defined a bookmark, we have to remember the original name
5728 # if we defined a bookmark, we have to remember the original name
5729 brev = rev
5729 brev = rev
5730 if rev:
5730 if rev:
5731 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5731 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5732 ctx = scmutil.revsingle(repo, rev, rev)
5732 ctx = scmutil.revsingle(repo, rev, rev)
5733 rev = ctx.rev()
5733 rev = ctx.rev()
5734 hidden = ctx.hidden()
5734 hidden = ctx.hidden()
5735 overrides = {('ui', 'forcemerge'): opts.get(r'tool', '')}
5735 overrides = {('ui', 'forcemerge'): opts.get(r'tool', '')}
5736 with ui.configoverride(overrides, 'update'):
5736 with ui.configoverride(overrides, 'update'):
5737 ret = hg.updatetotally(ui, repo, rev, brev, clean=clean,
5737 ret = hg.updatetotally(ui, repo, rev, brev, clean=clean,
5738 updatecheck=updatecheck)
5738 updatecheck=updatecheck)
5739 if hidden:
5739 if hidden:
5740 ctxstr = ctx.hex()[:12]
5740 ctxstr = ctx.hex()[:12]
5741 ui.warn(_("updated to hidden changeset %s\n") % ctxstr)
5741 ui.warn(_("updated to hidden changeset %s\n") % ctxstr)
5742
5742
5743 if ctx.obsolete():
5743 if ctx.obsolete():
5744 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
5744 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
5745 ui.warn("(%s)\n" % obsfatemsg)
5745 ui.warn("(%s)\n" % obsfatemsg)
5746 return ret
5746 return ret
5747
5747
5748 @command('verify', [])
5748 @command('verify', [])
5749 def verify(ui, repo):
5749 def verify(ui, repo):
5750 """verify the integrity of the repository
5750 """verify the integrity of the repository
5751
5751
5752 Verify the integrity of the current repository.
5752 Verify the integrity of the current repository.
5753
5753
5754 This will perform an extensive check of the repository's
5754 This will perform an extensive check of the repository's
5755 integrity, validating the hashes and checksums of each entry in
5755 integrity, validating the hashes and checksums of each entry in
5756 the changelog, manifest, and tracked files, as well as the
5756 the changelog, manifest, and tracked files, as well as the
5757 integrity of their crosslinks and indices.
5757 integrity of their crosslinks and indices.
5758
5758
5759 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5759 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5760 for more information about recovery from corruption of the
5760 for more information about recovery from corruption of the
5761 repository.
5761 repository.
5762
5762
5763 Returns 0 on success, 1 if errors are encountered.
5763 Returns 0 on success, 1 if errors are encountered.
5764 """
5764 """
5765 return hg.verify(repo)
5765 return hg.verify(repo)
5766
5766
5767 @command('version', [] + formatteropts, norepo=True,
5767 @command('version', [] + formatteropts, norepo=True,
5768 intents={INTENT_READONLY})
5768 intents={INTENT_READONLY})
5769 def version_(ui, **opts):
5769 def version_(ui, **opts):
5770 """output version and copyright information"""
5770 """output version and copyright information"""
5771 opts = pycompat.byteskwargs(opts)
5771 opts = pycompat.byteskwargs(opts)
5772 if ui.verbose:
5772 if ui.verbose:
5773 ui.pager('version')
5773 ui.pager('version')
5774 fm = ui.formatter("version", opts)
5774 fm = ui.formatter("version", opts)
5775 fm.startitem()
5775 fm.startitem()
5776 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5776 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5777 util.version())
5777 util.version())
5778 license = _(
5778 license = _(
5779 "(see https://mercurial-scm.org for more information)\n"
5779 "(see https://mercurial-scm.org for more information)\n"
5780 "\nCopyright (C) 2005-2018 Matt Mackall and others\n"
5780 "\nCopyright (C) 2005-2018 Matt Mackall and others\n"
5781 "This is free software; see the source for copying conditions. "
5781 "This is free software; see the source for copying conditions. "
5782 "There is NO\nwarranty; "
5782 "There is NO\nwarranty; "
5783 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5783 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5784 )
5784 )
5785 if not ui.quiet:
5785 if not ui.quiet:
5786 fm.plain(license)
5786 fm.plain(license)
5787
5787
5788 if ui.verbose:
5788 if ui.verbose:
5789 fm.plain(_("\nEnabled extensions:\n\n"))
5789 fm.plain(_("\nEnabled extensions:\n\n"))
5790 # format names and versions into columns
5790 # format names and versions into columns
5791 names = []
5791 names = []
5792 vers = []
5792 vers = []
5793 isinternals = []
5793 isinternals = []
5794 for name, module in extensions.extensions():
5794 for name, module in extensions.extensions():
5795 names.append(name)
5795 names.append(name)
5796 vers.append(extensions.moduleversion(module) or None)
5796 vers.append(extensions.moduleversion(module) or None)
5797 isinternals.append(extensions.ismoduleinternal(module))
5797 isinternals.append(extensions.ismoduleinternal(module))
5798 fn = fm.nested("extensions", tmpl='{name}\n')
5798 fn = fm.nested("extensions", tmpl='{name}\n')
5799 if names:
5799 if names:
5800 namefmt = " %%-%ds " % max(len(n) for n in names)
5800 namefmt = " %%-%ds " % max(len(n) for n in names)
5801 places = [_("external"), _("internal")]
5801 places = [_("external"), _("internal")]
5802 for n, v, p in zip(names, vers, isinternals):
5802 for n, v, p in zip(names, vers, isinternals):
5803 fn.startitem()
5803 fn.startitem()
5804 fn.condwrite(ui.verbose, "name", namefmt, n)
5804 fn.condwrite(ui.verbose, "name", namefmt, n)
5805 if ui.verbose:
5805 if ui.verbose:
5806 fn.plain("%s " % places[p])
5806 fn.plain("%s " % places[p])
5807 fn.data(bundled=p)
5807 fn.data(bundled=p)
5808 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5808 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5809 if ui.verbose:
5809 if ui.verbose:
5810 fn.plain("\n")
5810 fn.plain("\n")
5811 fn.end()
5811 fn.end()
5812 fm.end()
5812 fm.end()
5813
5813
5814 def loadcmdtable(ui, name, cmdtable):
5814 def loadcmdtable(ui, name, cmdtable):
5815 """Load command functions from specified cmdtable
5815 """Load command functions from specified cmdtable
5816 """
5816 """
5817 overrides = [cmd for cmd in cmdtable if cmd in table]
5817 overrides = [cmd for cmd in cmdtable if cmd in table]
5818 if overrides:
5818 if overrides:
5819 ui.warn(_("extension '%s' overrides commands: %s\n")
5819 ui.warn(_("extension '%s' overrides commands: %s\n")
5820 % (name, " ".join(overrides)))
5820 % (name, " ".join(overrides)))
5821 table.update(cmdtable)
5821 table.update(cmdtable)
General Comments 0
You need to be logged in to leave comments. Login now