##// END OF EJS Templates
histedit: add history-editing-backup config option...
Sushil khanchi -
r38756:c2586a6e default
parent child Browse files
Show More
@@ -1,1657 +1,1659 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, overwrite=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, overwrite=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 ('', 'no-backup', False, _('do not save backup copies of files')),
928 ('', 'no-backup', False, _('do not save backup copies of files')),
929 ('f', 'force', False,
929 ('f', 'force', False,
930 _('force outgoing even for unrelated repositories')),
930 _('force outgoing even for unrelated repositories')),
931 ('r', 'rev', [], _('first revision to be edited'), _('REV'))] +
931 ('r', 'rev', [], _('first revision to be edited'), _('REV'))] +
932 cmdutil.formatteropts,
932 cmdutil.formatteropts,
933 _("[OPTIONS] ([ANCESTOR] | --outgoing [URL])"))
933 _("[OPTIONS] ([ANCESTOR] | --outgoing [URL])"))
934 def histedit(ui, repo, *freeargs, **opts):
934 def histedit(ui, repo, *freeargs, **opts):
935 """interactively edit changeset history
935 """interactively edit changeset history
936
936
937 This command lets you edit a linear series of changesets (up to
937 This command lets you edit a linear series of changesets (up to
938 and including the working directory, which should be clean).
938 and including the working directory, which should be clean).
939 You can:
939 You can:
940
940
941 - `pick` to [re]order a changeset
941 - `pick` to [re]order a changeset
942
942
943 - `drop` to omit changeset
943 - `drop` to omit changeset
944
944
945 - `mess` to reword the changeset commit message
945 - `mess` to reword the changeset commit message
946
946
947 - `fold` to combine it with the preceding changeset (using the later date)
947 - `fold` to combine it with the preceding changeset (using the later date)
948
948
949 - `roll` like fold, but discarding this commit's description and date
949 - `roll` like fold, but discarding this commit's description and date
950
950
951 - `edit` to edit this changeset (preserving date)
951 - `edit` to edit this changeset (preserving date)
952
952
953 - `base` to checkout changeset and apply further changesets from there
953 - `base` to checkout changeset and apply further changesets from there
954
954
955 There are a number of ways to select the root changeset:
955 There are a number of ways to select the root changeset:
956
956
957 - Specify ANCESTOR directly
957 - Specify ANCESTOR directly
958
958
959 - Use --outgoing -- it will be the first linear changeset not
959 - Use --outgoing -- it will be the first linear changeset not
960 included in destination. (See :hg:`help config.paths.default-push`)
960 included in destination. (See :hg:`help config.paths.default-push`)
961
961
962 - Otherwise, the value from the "histedit.defaultrev" config option
962 - Otherwise, the value from the "histedit.defaultrev" config option
963 is used as a revset to select the base revision when ANCESTOR is not
963 is used as a revset to select the base revision when ANCESTOR is not
964 specified. The first revision returned by the revset is used. By
964 specified. The first revision returned by the revset is used. By
965 default, this selects the editable history that is unique to the
965 default, this selects the editable history that is unique to the
966 ancestry of the working directory.
966 ancestry of the working directory.
967
967
968 .. container:: verbose
968 .. container:: verbose
969
969
970 If you use --outgoing, this command will abort if there are ambiguous
970 If you use --outgoing, this command will abort if there are ambiguous
971 outgoing revisions. For example, if there are multiple branches
971 outgoing revisions. For example, if there are multiple branches
972 containing outgoing revisions.
972 containing outgoing revisions.
973
973
974 Use "min(outgoing() and ::.)" or similar revset specification
974 Use "min(outgoing() and ::.)" or similar revset specification
975 instead of --outgoing to specify edit target revision exactly in
975 instead of --outgoing to specify edit target revision exactly in
976 such ambiguous situation. See :hg:`help revsets` for detail about
976 such ambiguous situation. See :hg:`help revsets` for detail about
977 selecting revisions.
977 selecting revisions.
978
978
979 .. container:: verbose
979 .. container:: verbose
980
980
981 Examples:
981 Examples:
982
982
983 - A number of changes have been made.
983 - A number of changes have been made.
984 Revision 3 is no longer needed.
984 Revision 3 is no longer needed.
985
985
986 Start history editing from revision 3::
986 Start history editing from revision 3::
987
987
988 hg histedit -r 3
988 hg histedit -r 3
989
989
990 An editor opens, containing the list of revisions,
990 An editor opens, containing the list of revisions,
991 with specific actions specified::
991 with specific actions specified::
992
992
993 pick 5339bf82f0ca 3 Zworgle the foobar
993 pick 5339bf82f0ca 3 Zworgle the foobar
994 pick 8ef592ce7cc4 4 Bedazzle the zerlog
994 pick 8ef592ce7cc4 4 Bedazzle the zerlog
995 pick 0a9639fcda9d 5 Morgify the cromulancy
995 pick 0a9639fcda9d 5 Morgify the cromulancy
996
996
997 Additional information about the possible actions
997 Additional information about the possible actions
998 to take appears below the list of revisions.
998 to take appears below the list of revisions.
999
999
1000 To remove revision 3 from the history,
1000 To remove revision 3 from the history,
1001 its action (at the beginning of the relevant line)
1001 its action (at the beginning of the relevant line)
1002 is changed to 'drop'::
1002 is changed to 'drop'::
1003
1003
1004 drop 5339bf82f0ca 3 Zworgle the foobar
1004 drop 5339bf82f0ca 3 Zworgle the foobar
1005 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1005 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1006 pick 0a9639fcda9d 5 Morgify the cromulancy
1006 pick 0a9639fcda9d 5 Morgify the cromulancy
1007
1007
1008 - A number of changes have been made.
1008 - A number of changes have been made.
1009 Revision 2 and 4 need to be swapped.
1009 Revision 2 and 4 need to be swapped.
1010
1010
1011 Start history editing from revision 2::
1011 Start history editing from revision 2::
1012
1012
1013 hg histedit -r 2
1013 hg histedit -r 2
1014
1014
1015 An editor opens, containing the list of revisions,
1015 An editor opens, containing the list of revisions,
1016 with specific actions specified::
1016 with specific actions specified::
1017
1017
1018 pick 252a1af424ad 2 Blorb a morgwazzle
1018 pick 252a1af424ad 2 Blorb a morgwazzle
1019 pick 5339bf82f0ca 3 Zworgle the foobar
1019 pick 5339bf82f0ca 3 Zworgle the foobar
1020 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1020 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1021
1021
1022 To swap revision 2 and 4, its lines are swapped
1022 To swap revision 2 and 4, its lines are swapped
1023 in the editor::
1023 in the editor::
1024
1024
1025 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1025 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1026 pick 5339bf82f0ca 3 Zworgle the foobar
1026 pick 5339bf82f0ca 3 Zworgle the foobar
1027 pick 252a1af424ad 2 Blorb a morgwazzle
1027 pick 252a1af424ad 2 Blorb a morgwazzle
1028
1028
1029 Returns 0 on success, 1 if user intervention is required (not only
1029 Returns 0 on success, 1 if user intervention is required (not only
1030 for intentional "edit" command, but also for resolving unexpected
1030 for intentional "edit" command, but also for resolving unexpected
1031 conflicts).
1031 conflicts).
1032 """
1032 """
1033 state = histeditstate(repo)
1033 state = histeditstate(repo)
1034 try:
1034 try:
1035 state.wlock = repo.wlock()
1035 state.wlock = repo.wlock()
1036 state.lock = repo.lock()
1036 state.lock = repo.lock()
1037 _histedit(ui, repo, state, *freeargs, **opts)
1037 _histedit(ui, repo, state, *freeargs, **opts)
1038 finally:
1038 finally:
1039 release(state.lock, state.wlock)
1039 release(state.lock, state.wlock)
1040
1040
1041 goalcontinue = 'continue'
1041 goalcontinue = 'continue'
1042 goalabort = 'abort'
1042 goalabort = 'abort'
1043 goaleditplan = 'edit-plan'
1043 goaleditplan = 'edit-plan'
1044 goalnew = 'new'
1044 goalnew = 'new'
1045
1045
1046 def _getgoal(opts):
1046 def _getgoal(opts):
1047 if opts.get('continue'):
1047 if opts.get('continue'):
1048 return goalcontinue
1048 return goalcontinue
1049 if opts.get('abort'):
1049 if opts.get('abort'):
1050 return goalabort
1050 return goalabort
1051 if opts.get('edit_plan'):
1051 if opts.get('edit_plan'):
1052 return goaleditplan
1052 return goaleditplan
1053 return goalnew
1053 return goalnew
1054
1054
1055 def _readfile(ui, path):
1055 def _readfile(ui, path):
1056 if path == '-':
1056 if path == '-':
1057 with ui.timeblockedsection('histedit'):
1057 with ui.timeblockedsection('histedit'):
1058 return ui.fin.read()
1058 return ui.fin.read()
1059 else:
1059 else:
1060 with open(path, 'rb') as f:
1060 with open(path, 'rb') as f:
1061 return f.read()
1061 return f.read()
1062
1062
1063 def _validateargs(ui, repo, state, freeargs, opts, goal, rules, revs):
1063 def _validateargs(ui, repo, state, freeargs, opts, goal, rules, revs):
1064 # TODO only abort if we try to histedit mq patches, not just
1064 # TODO only abort if we try to histedit mq patches, not just
1065 # blanket if mq patches are applied somewhere
1065 # blanket if mq patches are applied somewhere
1066 mq = getattr(repo, 'mq', None)
1066 mq = getattr(repo, 'mq', None)
1067 if mq and mq.applied:
1067 if mq and mq.applied:
1068 raise error.Abort(_('source has mq patches applied'))
1068 raise error.Abort(_('source has mq patches applied'))
1069
1069
1070 # basic argument incompatibility processing
1070 # basic argument incompatibility processing
1071 outg = opts.get('outgoing')
1071 outg = opts.get('outgoing')
1072 editplan = opts.get('edit_plan')
1072 editplan = opts.get('edit_plan')
1073 abort = opts.get('abort')
1073 abort = opts.get('abort')
1074 force = opts.get('force')
1074 force = opts.get('force')
1075 if force and not outg:
1075 if force and not outg:
1076 raise error.Abort(_('--force only allowed with --outgoing'))
1076 raise error.Abort(_('--force only allowed with --outgoing'))
1077 if goal == 'continue':
1077 if goal == 'continue':
1078 if any((outg, abort, revs, freeargs, rules, editplan)):
1078 if any((outg, abort, revs, freeargs, rules, editplan)):
1079 raise error.Abort(_('no arguments allowed with --continue'))
1079 raise error.Abort(_('no arguments allowed with --continue'))
1080 elif goal == 'abort':
1080 elif goal == 'abort':
1081 if any((outg, revs, freeargs, rules, editplan)):
1081 if any((outg, revs, freeargs, rules, editplan)):
1082 raise error.Abort(_('no arguments allowed with --abort'))
1082 raise error.Abort(_('no arguments allowed with --abort'))
1083 elif goal == 'edit-plan':
1083 elif goal == 'edit-plan':
1084 if any((outg, revs, freeargs)):
1084 if any((outg, revs, freeargs)):
1085 raise error.Abort(_('only --commands argument allowed with '
1085 raise error.Abort(_('only --commands argument allowed with '
1086 '--edit-plan'))
1086 '--edit-plan'))
1087 else:
1087 else:
1088 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
1088 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
1089 raise error.Abort(_('history edit already in progress, try '
1089 raise error.Abort(_('history edit already in progress, try '
1090 '--continue or --abort'))
1090 '--continue or --abort'))
1091 if outg:
1091 if outg:
1092 if revs:
1092 if revs:
1093 raise error.Abort(_('no revisions allowed with --outgoing'))
1093 raise error.Abort(_('no revisions allowed with --outgoing'))
1094 if len(freeargs) > 1:
1094 if len(freeargs) > 1:
1095 raise error.Abort(
1095 raise error.Abort(
1096 _('only one repo argument allowed with --outgoing'))
1096 _('only one repo argument allowed with --outgoing'))
1097 else:
1097 else:
1098 revs.extend(freeargs)
1098 revs.extend(freeargs)
1099 if len(revs) == 0:
1099 if len(revs) == 0:
1100 defaultrev = destutil.desthistedit(ui, repo)
1100 defaultrev = destutil.desthistedit(ui, repo)
1101 if defaultrev is not None:
1101 if defaultrev is not None:
1102 revs.append(defaultrev)
1102 revs.append(defaultrev)
1103
1103
1104 if len(revs) != 1:
1104 if len(revs) != 1:
1105 raise error.Abort(
1105 raise error.Abort(
1106 _('histedit requires exactly one ancestor revision'))
1106 _('histedit requires exactly one ancestor revision'))
1107
1107
1108 def _histedit(ui, repo, state, *freeargs, **opts):
1108 def _histedit(ui, repo, state, *freeargs, **opts):
1109 opts = pycompat.byteskwargs(opts)
1109 opts = pycompat.byteskwargs(opts)
1110 fm = ui.formatter('histedit', opts)
1110 fm = ui.formatter('histedit', opts)
1111 fm.startitem()
1111 fm.startitem()
1112 goal = _getgoal(opts)
1112 goal = _getgoal(opts)
1113 revs = opts.get('rev', [])
1113 revs = opts.get('rev', [])
1114 nobackup = opts.get('no_backup')
1114 # experimental config: ui.history-editing-backup
1115 nobackup = (opts.get('no_backup') or
1116 not ui.configbool('ui', 'history-editing-backup'))
1115 rules = opts.get('commands', '')
1117 rules = opts.get('commands', '')
1116 state.keep = opts.get('keep', False)
1118 state.keep = opts.get('keep', False)
1117
1119
1118 _validateargs(ui, repo, state, freeargs, opts, goal, rules, revs)
1120 _validateargs(ui, repo, state, freeargs, opts, goal, rules, revs)
1119
1121
1120 # rebuild state
1122 # rebuild state
1121 if goal == goalcontinue:
1123 if goal == goalcontinue:
1122 state.read()
1124 state.read()
1123 state = bootstrapcontinue(ui, state, opts)
1125 state = bootstrapcontinue(ui, state, opts)
1124 elif goal == goaleditplan:
1126 elif goal == goaleditplan:
1125 _edithisteditplan(ui, repo, state, rules)
1127 _edithisteditplan(ui, repo, state, rules)
1126 return
1128 return
1127 elif goal == goalabort:
1129 elif goal == goalabort:
1128 _aborthistedit(ui, repo, state, nobackup=nobackup)
1130 _aborthistedit(ui, repo, state, nobackup=nobackup)
1129 return
1131 return
1130 else:
1132 else:
1131 # goal == goalnew
1133 # goal == goalnew
1132 _newhistedit(ui, repo, state, revs, freeargs, opts)
1134 _newhistedit(ui, repo, state, revs, freeargs, opts)
1133
1135
1134 _continuehistedit(ui, repo, state)
1136 _continuehistedit(ui, repo, state)
1135 _finishhistedit(ui, repo, state, fm)
1137 _finishhistedit(ui, repo, state, fm)
1136 fm.end()
1138 fm.end()
1137
1139
1138 def _continuehistedit(ui, repo, state):
1140 def _continuehistedit(ui, repo, state):
1139 """This function runs after either:
1141 """This function runs after either:
1140 - bootstrapcontinue (if the goal is 'continue')
1142 - bootstrapcontinue (if the goal is 'continue')
1141 - _newhistedit (if the goal is 'new')
1143 - _newhistedit (if the goal is 'new')
1142 """
1144 """
1143 # preprocess rules so that we can hide inner folds from the user
1145 # preprocess rules so that we can hide inner folds from the user
1144 # and only show one editor
1146 # and only show one editor
1145 actions = state.actions[:]
1147 actions = state.actions[:]
1146 for idx, (action, nextact) in enumerate(
1148 for idx, (action, nextact) in enumerate(
1147 zip(actions, actions[1:] + [None])):
1149 zip(actions, actions[1:] + [None])):
1148 if action.verb == 'fold' and nextact and nextact.verb == 'fold':
1150 if action.verb == 'fold' and nextact and nextact.verb == 'fold':
1149 state.actions[idx].__class__ = _multifold
1151 state.actions[idx].__class__ = _multifold
1150
1152
1151 # Force an initial state file write, so the user can run --abort/continue
1153 # Force an initial state file write, so the user can run --abort/continue
1152 # even if there's an exception before the first transaction serialize.
1154 # even if there's an exception before the first transaction serialize.
1153 state.write()
1155 state.write()
1154
1156
1155 tr = None
1157 tr = None
1156 # Don't use singletransaction by default since it rolls the entire
1158 # Don't use singletransaction by default since it rolls the entire
1157 # transaction back if an unexpected exception happens (like a
1159 # transaction back if an unexpected exception happens (like a
1158 # pretxncommit hook throws, or the user aborts the commit msg editor).
1160 # pretxncommit hook throws, or the user aborts the commit msg editor).
1159 if ui.configbool("histedit", "singletransaction"):
1161 if ui.configbool("histedit", "singletransaction"):
1160 # Don't use a 'with' for the transaction, since actions may close
1162 # Don't use a 'with' for the transaction, since actions may close
1161 # and reopen a transaction. For example, if the action executes an
1163 # and reopen a transaction. For example, if the action executes an
1162 # external process it may choose to commit the transaction first.
1164 # external process it may choose to commit the transaction first.
1163 tr = repo.transaction('histedit')
1165 tr = repo.transaction('histedit')
1164 progress = ui.makeprogress(_("editing"), unit=_('changes'),
1166 progress = ui.makeprogress(_("editing"), unit=_('changes'),
1165 total=len(state.actions))
1167 total=len(state.actions))
1166 with progress, util.acceptintervention(tr):
1168 with progress, util.acceptintervention(tr):
1167 while state.actions:
1169 while state.actions:
1168 state.write(tr=tr)
1170 state.write(tr=tr)
1169 actobj = state.actions[0]
1171 actobj = state.actions[0]
1170 progress.increment(item=actobj.torule())
1172 progress.increment(item=actobj.torule())
1171 ui.debug('histedit: processing %s %s\n' % (actobj.verb,\
1173 ui.debug('histedit: processing %s %s\n' % (actobj.verb,\
1172 actobj.torule()))
1174 actobj.torule()))
1173 parentctx, replacement_ = actobj.run()
1175 parentctx, replacement_ = actobj.run()
1174 state.parentctxnode = parentctx.node()
1176 state.parentctxnode = parentctx.node()
1175 state.replacements.extend(replacement_)
1177 state.replacements.extend(replacement_)
1176 state.actions.pop(0)
1178 state.actions.pop(0)
1177
1179
1178 state.write()
1180 state.write()
1179
1181
1180 def _finishhistedit(ui, repo, state, fm):
1182 def _finishhistedit(ui, repo, state, fm):
1181 """This action runs when histedit is finishing its session"""
1183 """This action runs when histedit is finishing its session"""
1182 hg.updaterepo(repo, state.parentctxnode, overwrite=False)
1184 hg.updaterepo(repo, state.parentctxnode, overwrite=False)
1183
1185
1184 mapping, tmpnodes, created, ntm = processreplacement(state)
1186 mapping, tmpnodes, created, ntm = processreplacement(state)
1185 if mapping:
1187 if mapping:
1186 for prec, succs in mapping.iteritems():
1188 for prec, succs in mapping.iteritems():
1187 if not succs:
1189 if not succs:
1188 ui.debug('histedit: %s is dropped\n' % node.short(prec))
1190 ui.debug('histedit: %s is dropped\n' % node.short(prec))
1189 else:
1191 else:
1190 ui.debug('histedit: %s is replaced by %s\n' % (
1192 ui.debug('histedit: %s is replaced by %s\n' % (
1191 node.short(prec), node.short(succs[0])))
1193 node.short(prec), node.short(succs[0])))
1192 if len(succs) > 1:
1194 if len(succs) > 1:
1193 m = 'histedit: %s'
1195 m = 'histedit: %s'
1194 for n in succs[1:]:
1196 for n in succs[1:]:
1195 ui.debug(m % node.short(n))
1197 ui.debug(m % node.short(n))
1196
1198
1197 if not state.keep:
1199 if not state.keep:
1198 if mapping:
1200 if mapping:
1199 movetopmostbookmarks(repo, state.topmost, ntm)
1201 movetopmostbookmarks(repo, state.topmost, ntm)
1200 # TODO update mq state
1202 # TODO update mq state
1201 else:
1203 else:
1202 mapping = {}
1204 mapping = {}
1203
1205
1204 for n in tmpnodes:
1206 for n in tmpnodes:
1205 mapping[n] = ()
1207 mapping[n] = ()
1206
1208
1207 # remove entries about unknown nodes
1209 # remove entries about unknown nodes
1208 nodemap = repo.unfiltered().changelog.nodemap
1210 nodemap = repo.unfiltered().changelog.nodemap
1209 mapping = {k: v for k, v in mapping.items()
1211 mapping = {k: v for k, v in mapping.items()
1210 if k in nodemap and all(n in nodemap for n in v)}
1212 if k in nodemap and all(n in nodemap for n in v)}
1211 scmutil.cleanupnodes(repo, mapping, 'histedit')
1213 scmutil.cleanupnodes(repo, mapping, 'histedit')
1212 hf = fm.hexfunc
1214 hf = fm.hexfunc
1213 fl = fm.formatlist
1215 fl = fm.formatlist
1214 fd = fm.formatdict
1216 fd = fm.formatdict
1215 nodechanges = fd({hf(oldn): fl([hf(n) for n in newn], name='node')
1217 nodechanges = fd({hf(oldn): fl([hf(n) for n in newn], name='node')
1216 for oldn, newn in mapping.iteritems()},
1218 for oldn, newn in mapping.iteritems()},
1217 key="oldnode", value="newnodes")
1219 key="oldnode", value="newnodes")
1218 fm.data(nodechanges=nodechanges)
1220 fm.data(nodechanges=nodechanges)
1219
1221
1220 state.clear()
1222 state.clear()
1221 if os.path.exists(repo.sjoin('undo')):
1223 if os.path.exists(repo.sjoin('undo')):
1222 os.unlink(repo.sjoin('undo'))
1224 os.unlink(repo.sjoin('undo'))
1223 if repo.vfs.exists('histedit-last-edit.txt'):
1225 if repo.vfs.exists('histedit-last-edit.txt'):
1224 repo.vfs.unlink('histedit-last-edit.txt')
1226 repo.vfs.unlink('histedit-last-edit.txt')
1225
1227
1226 def _aborthistedit(ui, repo, state, nobackup=False):
1228 def _aborthistedit(ui, repo, state, nobackup=False):
1227 try:
1229 try:
1228 state.read()
1230 state.read()
1229 __, leafs, tmpnodes, __ = processreplacement(state)
1231 __, leafs, tmpnodes, __ = processreplacement(state)
1230 ui.debug('restore wc to old parent %s\n'
1232 ui.debug('restore wc to old parent %s\n'
1231 % node.short(state.topmost))
1233 % node.short(state.topmost))
1232
1234
1233 # Recover our old commits if necessary
1235 # Recover our old commits if necessary
1234 if not state.topmost in repo and state.backupfile:
1236 if not state.topmost in repo and state.backupfile:
1235 backupfile = repo.vfs.join(state.backupfile)
1237 backupfile = repo.vfs.join(state.backupfile)
1236 f = hg.openpath(ui, backupfile)
1238 f = hg.openpath(ui, backupfile)
1237 gen = exchange.readbundle(ui, f, backupfile)
1239 gen = exchange.readbundle(ui, f, backupfile)
1238 with repo.transaction('histedit.abort') as tr:
1240 with repo.transaction('histedit.abort') as tr:
1239 bundle2.applybundle(repo, gen, tr, source='histedit',
1241 bundle2.applybundle(repo, gen, tr, source='histedit',
1240 url='bundle:' + backupfile)
1242 url='bundle:' + backupfile)
1241
1243
1242 os.remove(backupfile)
1244 os.remove(backupfile)
1243
1245
1244 # check whether we should update away
1246 # check whether we should update away
1245 if repo.unfiltered().revs('parents() and (%n or %ln::)',
1247 if repo.unfiltered().revs('parents() and (%n or %ln::)',
1246 state.parentctxnode, leafs | tmpnodes):
1248 state.parentctxnode, leafs | tmpnodes):
1247 hg.clean(repo, state.topmost, show_stats=True, quietempty=True)
1249 hg.clean(repo, state.topmost, show_stats=True, quietempty=True)
1248 cleanupnode(ui, repo, tmpnodes, nobackup=nobackup)
1250 cleanupnode(ui, repo, tmpnodes, nobackup=nobackup)
1249 cleanupnode(ui, repo, leafs, nobackup=nobackup)
1251 cleanupnode(ui, repo, leafs, nobackup=nobackup)
1250 except Exception:
1252 except Exception:
1251 if state.inprogress():
1253 if state.inprogress():
1252 ui.warn(_('warning: encountered an exception during histedit '
1254 ui.warn(_('warning: encountered an exception during histedit '
1253 '--abort; the repository may not have been completely '
1255 '--abort; the repository may not have been completely '
1254 'cleaned up\n'))
1256 'cleaned up\n'))
1255 raise
1257 raise
1256 finally:
1258 finally:
1257 state.clear()
1259 state.clear()
1258
1260
1259 def _edithisteditplan(ui, repo, state, rules):
1261 def _edithisteditplan(ui, repo, state, rules):
1260 state.read()
1262 state.read()
1261 if not rules:
1263 if not rules:
1262 comment = geteditcomment(ui,
1264 comment = geteditcomment(ui,
1263 node.short(state.parentctxnode),
1265 node.short(state.parentctxnode),
1264 node.short(state.topmost))
1266 node.short(state.topmost))
1265 rules = ruleeditor(repo, ui, state.actions, comment)
1267 rules = ruleeditor(repo, ui, state.actions, comment)
1266 else:
1268 else:
1267 rules = _readfile(ui, rules)
1269 rules = _readfile(ui, rules)
1268 actions = parserules(rules, state)
1270 actions = parserules(rules, state)
1269 ctxs = [repo[act.node] \
1271 ctxs = [repo[act.node] \
1270 for act in state.actions if act.node]
1272 for act in state.actions if act.node]
1271 warnverifyactions(ui, repo, actions, state, ctxs)
1273 warnverifyactions(ui, repo, actions, state, ctxs)
1272 state.actions = actions
1274 state.actions = actions
1273 state.write()
1275 state.write()
1274
1276
1275 def _newhistedit(ui, repo, state, revs, freeargs, opts):
1277 def _newhistedit(ui, repo, state, revs, freeargs, opts):
1276 outg = opts.get('outgoing')
1278 outg = opts.get('outgoing')
1277 rules = opts.get('commands', '')
1279 rules = opts.get('commands', '')
1278 force = opts.get('force')
1280 force = opts.get('force')
1279
1281
1280 cmdutil.checkunfinished(repo)
1282 cmdutil.checkunfinished(repo)
1281 cmdutil.bailifchanged(repo)
1283 cmdutil.bailifchanged(repo)
1282
1284
1283 topmost, empty = repo.dirstate.parents()
1285 topmost, empty = repo.dirstate.parents()
1284 if outg:
1286 if outg:
1285 if freeargs:
1287 if freeargs:
1286 remote = freeargs[0]
1288 remote = freeargs[0]
1287 else:
1289 else:
1288 remote = None
1290 remote = None
1289 root = findoutgoing(ui, repo, remote, force, opts)
1291 root = findoutgoing(ui, repo, remote, force, opts)
1290 else:
1292 else:
1291 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs)))
1293 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs)))
1292 if len(rr) != 1:
1294 if len(rr) != 1:
1293 raise error.Abort(_('The specified revisions must have '
1295 raise error.Abort(_('The specified revisions must have '
1294 'exactly one common root'))
1296 'exactly one common root'))
1295 root = rr[0].node()
1297 root = rr[0].node()
1296
1298
1297 revs = between(repo, root, topmost, state.keep)
1299 revs = between(repo, root, topmost, state.keep)
1298 if not revs:
1300 if not revs:
1299 raise error.Abort(_('%s is not an ancestor of working directory') %
1301 raise error.Abort(_('%s is not an ancestor of working directory') %
1300 node.short(root))
1302 node.short(root))
1301
1303
1302 ctxs = [repo[r] for r in revs]
1304 ctxs = [repo[r] for r in revs]
1303 if not rules:
1305 if not rules:
1304 comment = geteditcomment(ui, node.short(root), node.short(topmost))
1306 comment = geteditcomment(ui, node.short(root), node.short(topmost))
1305 actions = [pick(state, r) for r in revs]
1307 actions = [pick(state, r) for r in revs]
1306 rules = ruleeditor(repo, ui, actions, comment)
1308 rules = ruleeditor(repo, ui, actions, comment)
1307 else:
1309 else:
1308 rules = _readfile(ui, rules)
1310 rules = _readfile(ui, rules)
1309 actions = parserules(rules, state)
1311 actions = parserules(rules, state)
1310 warnverifyactions(ui, repo, actions, state, ctxs)
1312 warnverifyactions(ui, repo, actions, state, ctxs)
1311
1313
1312 parentctxnode = repo[root].parents()[0].node()
1314 parentctxnode = repo[root].parents()[0].node()
1313
1315
1314 state.parentctxnode = parentctxnode
1316 state.parentctxnode = parentctxnode
1315 state.actions = actions
1317 state.actions = actions
1316 state.topmost = topmost
1318 state.topmost = topmost
1317 state.replacements = []
1319 state.replacements = []
1318
1320
1319 ui.log("histedit", "%d actions to histedit", len(actions),
1321 ui.log("histedit", "%d actions to histedit", len(actions),
1320 histedit_num_actions=len(actions))
1322 histedit_num_actions=len(actions))
1321
1323
1322 # Create a backup so we can always abort completely.
1324 # Create a backup so we can always abort completely.
1323 backupfile = None
1325 backupfile = None
1324 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1326 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1325 backupfile = repair.backupbundle(repo, [parentctxnode],
1327 backupfile = repair.backupbundle(repo, [parentctxnode],
1326 [topmost], root, 'histedit')
1328 [topmost], root, 'histedit')
1327 state.backupfile = backupfile
1329 state.backupfile = backupfile
1328
1330
1329 def _getsummary(ctx):
1331 def _getsummary(ctx):
1330 # a common pattern is to extract the summary but default to the empty
1332 # a common pattern is to extract the summary but default to the empty
1331 # string
1333 # string
1332 summary = ctx.description() or ''
1334 summary = ctx.description() or ''
1333 if summary:
1335 if summary:
1334 summary = summary.splitlines()[0]
1336 summary = summary.splitlines()[0]
1335 return summary
1337 return summary
1336
1338
1337 def bootstrapcontinue(ui, state, opts):
1339 def bootstrapcontinue(ui, state, opts):
1338 repo = state.repo
1340 repo = state.repo
1339
1341
1340 ms = mergemod.mergestate.read(repo)
1342 ms = mergemod.mergestate.read(repo)
1341 mergeutil.checkunresolved(ms)
1343 mergeutil.checkunresolved(ms)
1342
1344
1343 if state.actions:
1345 if state.actions:
1344 actobj = state.actions.pop(0)
1346 actobj = state.actions.pop(0)
1345
1347
1346 if _isdirtywc(repo):
1348 if _isdirtywc(repo):
1347 actobj.continuedirty()
1349 actobj.continuedirty()
1348 if _isdirtywc(repo):
1350 if _isdirtywc(repo):
1349 abortdirty()
1351 abortdirty()
1350
1352
1351 parentctx, replacements = actobj.continueclean()
1353 parentctx, replacements = actobj.continueclean()
1352
1354
1353 state.parentctxnode = parentctx.node()
1355 state.parentctxnode = parentctx.node()
1354 state.replacements.extend(replacements)
1356 state.replacements.extend(replacements)
1355
1357
1356 return state
1358 return state
1357
1359
1358 def between(repo, old, new, keep):
1360 def between(repo, old, new, keep):
1359 """select and validate the set of revision to edit
1361 """select and validate the set of revision to edit
1360
1362
1361 When keep is false, the specified set can't have children."""
1363 When keep is false, the specified set can't have children."""
1362 revs = repo.revs('%n::%n', old, new)
1364 revs = repo.revs('%n::%n', old, new)
1363 if revs and not keep:
1365 if revs and not keep:
1364 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
1366 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
1365 repo.revs('(%ld::) - (%ld)', revs, revs)):
1367 repo.revs('(%ld::) - (%ld)', revs, revs)):
1366 raise error.Abort(_('can only histedit a changeset together '
1368 raise error.Abort(_('can only histedit a changeset together '
1367 'with all its descendants'))
1369 'with all its descendants'))
1368 if repo.revs('(%ld) and merge()', revs):
1370 if repo.revs('(%ld) and merge()', revs):
1369 raise error.Abort(_('cannot edit history that contains merges'))
1371 raise error.Abort(_('cannot edit history that contains merges'))
1370 root = repo[revs.first()] # list is already sorted by repo.revs()
1372 root = repo[revs.first()] # list is already sorted by repo.revs()
1371 if not root.mutable():
1373 if not root.mutable():
1372 raise error.Abort(_('cannot edit public changeset: %s') % root,
1374 raise error.Abort(_('cannot edit public changeset: %s') % root,
1373 hint=_("see 'hg help phases' for details"))
1375 hint=_("see 'hg help phases' for details"))
1374 return pycompat.maplist(repo.changelog.node, revs)
1376 return pycompat.maplist(repo.changelog.node, revs)
1375
1377
1376 def ruleeditor(repo, ui, actions, editcomment=""):
1378 def ruleeditor(repo, ui, actions, editcomment=""):
1377 """open an editor to edit rules
1379 """open an editor to edit rules
1378
1380
1379 rules are in the format [ [act, ctx], ...] like in state.rules
1381 rules are in the format [ [act, ctx], ...] like in state.rules
1380 """
1382 """
1381 if repo.ui.configbool("experimental", "histedit.autoverb"):
1383 if repo.ui.configbool("experimental", "histedit.autoverb"):
1382 newact = util.sortdict()
1384 newact = util.sortdict()
1383 for act in actions:
1385 for act in actions:
1384 ctx = repo[act.node]
1386 ctx = repo[act.node]
1385 summary = _getsummary(ctx)
1387 summary = _getsummary(ctx)
1386 fword = summary.split(' ', 1)[0].lower()
1388 fword = summary.split(' ', 1)[0].lower()
1387 added = False
1389 added = False
1388
1390
1389 # if it doesn't end with the special character '!' just skip this
1391 # if it doesn't end with the special character '!' just skip this
1390 if fword.endswith('!'):
1392 if fword.endswith('!'):
1391 fword = fword[:-1]
1393 fword = fword[:-1]
1392 if fword in primaryactions | secondaryactions | tertiaryactions:
1394 if fword in primaryactions | secondaryactions | tertiaryactions:
1393 act.verb = fword
1395 act.verb = fword
1394 # get the target summary
1396 # get the target summary
1395 tsum = summary[len(fword) + 1:].lstrip()
1397 tsum = summary[len(fword) + 1:].lstrip()
1396 # safe but slow: reverse iterate over the actions so we
1398 # safe but slow: reverse iterate over the actions so we
1397 # don't clash on two commits having the same summary
1399 # don't clash on two commits having the same summary
1398 for na, l in reversed(list(newact.iteritems())):
1400 for na, l in reversed(list(newact.iteritems())):
1399 actx = repo[na.node]
1401 actx = repo[na.node]
1400 asum = _getsummary(actx)
1402 asum = _getsummary(actx)
1401 if asum == tsum:
1403 if asum == tsum:
1402 added = True
1404 added = True
1403 l.append(act)
1405 l.append(act)
1404 break
1406 break
1405
1407
1406 if not added:
1408 if not added:
1407 newact[act] = []
1409 newact[act] = []
1408
1410
1409 # copy over and flatten the new list
1411 # copy over and flatten the new list
1410 actions = []
1412 actions = []
1411 for na, l in newact.iteritems():
1413 for na, l in newact.iteritems():
1412 actions.append(na)
1414 actions.append(na)
1413 actions += l
1415 actions += l
1414
1416
1415 rules = '\n'.join([act.torule() for act in actions])
1417 rules = '\n'.join([act.torule() for act in actions])
1416 rules += '\n\n'
1418 rules += '\n\n'
1417 rules += editcomment
1419 rules += editcomment
1418 rules = ui.edit(rules, ui.username(), {'prefix': 'histedit'},
1420 rules = ui.edit(rules, ui.username(), {'prefix': 'histedit'},
1419 repopath=repo.path, action='histedit')
1421 repopath=repo.path, action='histedit')
1420
1422
1421 # Save edit rules in .hg/histedit-last-edit.txt in case
1423 # Save edit rules in .hg/histedit-last-edit.txt in case
1422 # the user needs to ask for help after something
1424 # the user needs to ask for help after something
1423 # surprising happens.
1425 # surprising happens.
1424 with repo.vfs('histedit-last-edit.txt', 'wb') as f:
1426 with repo.vfs('histedit-last-edit.txt', 'wb') as f:
1425 f.write(rules)
1427 f.write(rules)
1426
1428
1427 return rules
1429 return rules
1428
1430
1429 def parserules(rules, state):
1431 def parserules(rules, state):
1430 """Read the histedit rules string and return list of action objects """
1432 """Read the histedit rules string and return list of action objects """
1431 rules = [l for l in (r.strip() for r in rules.splitlines())
1433 rules = [l for l in (r.strip() for r in rules.splitlines())
1432 if l and not l.startswith('#')]
1434 if l and not l.startswith('#')]
1433 actions = []
1435 actions = []
1434 for r in rules:
1436 for r in rules:
1435 if ' ' not in r:
1437 if ' ' not in r:
1436 raise error.ParseError(_('malformed line "%s"') % r)
1438 raise error.ParseError(_('malformed line "%s"') % r)
1437 verb, rest = r.split(' ', 1)
1439 verb, rest = r.split(' ', 1)
1438
1440
1439 if verb not in actiontable:
1441 if verb not in actiontable:
1440 raise error.ParseError(_('unknown action "%s"') % verb)
1442 raise error.ParseError(_('unknown action "%s"') % verb)
1441
1443
1442 action = actiontable[verb].fromrule(state, rest)
1444 action = actiontable[verb].fromrule(state, rest)
1443 actions.append(action)
1445 actions.append(action)
1444 return actions
1446 return actions
1445
1447
1446 def warnverifyactions(ui, repo, actions, state, ctxs):
1448 def warnverifyactions(ui, repo, actions, state, ctxs):
1447 try:
1449 try:
1448 verifyactions(actions, state, ctxs)
1450 verifyactions(actions, state, ctxs)
1449 except error.ParseError:
1451 except error.ParseError:
1450 if repo.vfs.exists('histedit-last-edit.txt'):
1452 if repo.vfs.exists('histedit-last-edit.txt'):
1451 ui.warn(_('warning: histedit rules saved '
1453 ui.warn(_('warning: histedit rules saved '
1452 'to: .hg/histedit-last-edit.txt\n'))
1454 'to: .hg/histedit-last-edit.txt\n'))
1453 raise
1455 raise
1454
1456
1455 def verifyactions(actions, state, ctxs):
1457 def verifyactions(actions, state, ctxs):
1456 """Verify that there exists exactly one action per given changeset and
1458 """Verify that there exists exactly one action per given changeset and
1457 other constraints.
1459 other constraints.
1458
1460
1459 Will abort if there are to many or too few rules, a malformed rule,
1461 Will abort if there are to many or too few rules, a malformed rule,
1460 or a rule on a changeset outside of the user-given range.
1462 or a rule on a changeset outside of the user-given range.
1461 """
1463 """
1462 expected = set(c.node() for c in ctxs)
1464 expected = set(c.node() for c in ctxs)
1463 seen = set()
1465 seen = set()
1464 prev = None
1466 prev = None
1465
1467
1466 if actions and actions[0].verb in ['roll', 'fold']:
1468 if actions and actions[0].verb in ['roll', 'fold']:
1467 raise error.ParseError(_('first changeset cannot use verb "%s"') %
1469 raise error.ParseError(_('first changeset cannot use verb "%s"') %
1468 actions[0].verb)
1470 actions[0].verb)
1469
1471
1470 for action in actions:
1472 for action in actions:
1471 action.verify(prev, expected, seen)
1473 action.verify(prev, expected, seen)
1472 prev = action
1474 prev = action
1473 if action.node is not None:
1475 if action.node is not None:
1474 seen.add(action.node)
1476 seen.add(action.node)
1475 missing = sorted(expected - seen) # sort to stabilize output
1477 missing = sorted(expected - seen) # sort to stabilize output
1476
1478
1477 if state.repo.ui.configbool('histedit', 'dropmissing'):
1479 if state.repo.ui.configbool('histedit', 'dropmissing'):
1478 if len(actions) == 0:
1480 if len(actions) == 0:
1479 raise error.ParseError(_('no rules provided'),
1481 raise error.ParseError(_('no rules provided'),
1480 hint=_('use strip extension to remove commits'))
1482 hint=_('use strip extension to remove commits'))
1481
1483
1482 drops = [drop(state, n) for n in missing]
1484 drops = [drop(state, n) for n in missing]
1483 # put the in the beginning so they execute immediately and
1485 # put the in the beginning so they execute immediately and
1484 # don't show in the edit-plan in the future
1486 # don't show in the edit-plan in the future
1485 actions[:0] = drops
1487 actions[:0] = drops
1486 elif missing:
1488 elif missing:
1487 raise error.ParseError(_('missing rules for changeset %s') %
1489 raise error.ParseError(_('missing rules for changeset %s') %
1488 node.short(missing[0]),
1490 node.short(missing[0]),
1489 hint=_('use "drop %s" to discard, see also: '
1491 hint=_('use "drop %s" to discard, see also: '
1490 "'hg help -e histedit.config'")
1492 "'hg help -e histedit.config'")
1491 % node.short(missing[0]))
1493 % node.short(missing[0]))
1492
1494
1493 def adjustreplacementsfrommarkers(repo, oldreplacements):
1495 def adjustreplacementsfrommarkers(repo, oldreplacements):
1494 """Adjust replacements from obsolescence markers
1496 """Adjust replacements from obsolescence markers
1495
1497
1496 Replacements structure is originally generated based on
1498 Replacements structure is originally generated based on
1497 histedit's state and does not account for changes that are
1499 histedit's state and does not account for changes that are
1498 not recorded there. This function fixes that by adding
1500 not recorded there. This function fixes that by adding
1499 data read from obsolescence markers"""
1501 data read from obsolescence markers"""
1500 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1502 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1501 return oldreplacements
1503 return oldreplacements
1502
1504
1503 unfi = repo.unfiltered()
1505 unfi = repo.unfiltered()
1504 nm = unfi.changelog.nodemap
1506 nm = unfi.changelog.nodemap
1505 obsstore = repo.obsstore
1507 obsstore = repo.obsstore
1506 newreplacements = list(oldreplacements)
1508 newreplacements = list(oldreplacements)
1507 oldsuccs = [r[1] for r in oldreplacements]
1509 oldsuccs = [r[1] for r in oldreplacements]
1508 # successors that have already been added to succstocheck once
1510 # successors that have already been added to succstocheck once
1509 seensuccs = set().union(*oldsuccs) # create a set from an iterable of tuples
1511 seensuccs = set().union(*oldsuccs) # create a set from an iterable of tuples
1510 succstocheck = list(seensuccs)
1512 succstocheck = list(seensuccs)
1511 while succstocheck:
1513 while succstocheck:
1512 n = succstocheck.pop()
1514 n = succstocheck.pop()
1513 missing = nm.get(n) is None
1515 missing = nm.get(n) is None
1514 markers = obsstore.successors.get(n, ())
1516 markers = obsstore.successors.get(n, ())
1515 if missing and not markers:
1517 if missing and not markers:
1516 # dead end, mark it as such
1518 # dead end, mark it as such
1517 newreplacements.append((n, ()))
1519 newreplacements.append((n, ()))
1518 for marker in markers:
1520 for marker in markers:
1519 nsuccs = marker[1]
1521 nsuccs = marker[1]
1520 newreplacements.append((n, nsuccs))
1522 newreplacements.append((n, nsuccs))
1521 for nsucc in nsuccs:
1523 for nsucc in nsuccs:
1522 if nsucc not in seensuccs:
1524 if nsucc not in seensuccs:
1523 seensuccs.add(nsucc)
1525 seensuccs.add(nsucc)
1524 succstocheck.append(nsucc)
1526 succstocheck.append(nsucc)
1525
1527
1526 return newreplacements
1528 return newreplacements
1527
1529
1528 def processreplacement(state):
1530 def processreplacement(state):
1529 """process the list of replacements to return
1531 """process the list of replacements to return
1530
1532
1531 1) the final mapping between original and created nodes
1533 1) the final mapping between original and created nodes
1532 2) the list of temporary node created by histedit
1534 2) the list of temporary node created by histedit
1533 3) the list of new commit created by histedit"""
1535 3) the list of new commit created by histedit"""
1534 replacements = adjustreplacementsfrommarkers(state.repo, state.replacements)
1536 replacements = adjustreplacementsfrommarkers(state.repo, state.replacements)
1535 allsuccs = set()
1537 allsuccs = set()
1536 replaced = set()
1538 replaced = set()
1537 fullmapping = {}
1539 fullmapping = {}
1538 # initialize basic set
1540 # initialize basic set
1539 # fullmapping records all operations recorded in replacement
1541 # fullmapping records all operations recorded in replacement
1540 for rep in replacements:
1542 for rep in replacements:
1541 allsuccs.update(rep[1])
1543 allsuccs.update(rep[1])
1542 replaced.add(rep[0])
1544 replaced.add(rep[0])
1543 fullmapping.setdefault(rep[0], set()).update(rep[1])
1545 fullmapping.setdefault(rep[0], set()).update(rep[1])
1544 new = allsuccs - replaced
1546 new = allsuccs - replaced
1545 tmpnodes = allsuccs & replaced
1547 tmpnodes = allsuccs & replaced
1546 # Reduce content fullmapping into direct relation between original nodes
1548 # Reduce content fullmapping into direct relation between original nodes
1547 # and final node created during history edition
1549 # and final node created during history edition
1548 # Dropped changeset are replaced by an empty list
1550 # Dropped changeset are replaced by an empty list
1549 toproceed = set(fullmapping)
1551 toproceed = set(fullmapping)
1550 final = {}
1552 final = {}
1551 while toproceed:
1553 while toproceed:
1552 for x in list(toproceed):
1554 for x in list(toproceed):
1553 succs = fullmapping[x]
1555 succs = fullmapping[x]
1554 for s in list(succs):
1556 for s in list(succs):
1555 if s in toproceed:
1557 if s in toproceed:
1556 # non final node with unknown closure
1558 # non final node with unknown closure
1557 # We can't process this now
1559 # We can't process this now
1558 break
1560 break
1559 elif s in final:
1561 elif s in final:
1560 # non final node, replace with closure
1562 # non final node, replace with closure
1561 succs.remove(s)
1563 succs.remove(s)
1562 succs.update(final[s])
1564 succs.update(final[s])
1563 else:
1565 else:
1564 final[x] = succs
1566 final[x] = succs
1565 toproceed.remove(x)
1567 toproceed.remove(x)
1566 # remove tmpnodes from final mapping
1568 # remove tmpnodes from final mapping
1567 for n in tmpnodes:
1569 for n in tmpnodes:
1568 del final[n]
1570 del final[n]
1569 # we expect all changes involved in final to exist in the repo
1571 # we expect all changes involved in final to exist in the repo
1570 # turn `final` into list (topologically sorted)
1572 # turn `final` into list (topologically sorted)
1571 nm = state.repo.changelog.nodemap
1573 nm = state.repo.changelog.nodemap
1572 for prec, succs in final.items():
1574 for prec, succs in final.items():
1573 final[prec] = sorted(succs, key=nm.get)
1575 final[prec] = sorted(succs, key=nm.get)
1574
1576
1575 # computed topmost element (necessary for bookmark)
1577 # computed topmost element (necessary for bookmark)
1576 if new:
1578 if new:
1577 newtopmost = sorted(new, key=state.repo.changelog.rev)[-1]
1579 newtopmost = sorted(new, key=state.repo.changelog.rev)[-1]
1578 elif not final:
1580 elif not final:
1579 # Nothing rewritten at all. we won't need `newtopmost`
1581 # Nothing rewritten at all. we won't need `newtopmost`
1580 # It is the same as `oldtopmost` and `processreplacement` know it
1582 # It is the same as `oldtopmost` and `processreplacement` know it
1581 newtopmost = None
1583 newtopmost = None
1582 else:
1584 else:
1583 # every body died. The newtopmost is the parent of the root.
1585 # every body died. The newtopmost is the parent of the root.
1584 r = state.repo.changelog.rev
1586 r = state.repo.changelog.rev
1585 newtopmost = state.repo[sorted(final, key=r)[0]].p1().node()
1587 newtopmost = state.repo[sorted(final, key=r)[0]].p1().node()
1586
1588
1587 return final, tmpnodes, new, newtopmost
1589 return final, tmpnodes, new, newtopmost
1588
1590
1589 def movetopmostbookmarks(repo, oldtopmost, newtopmost):
1591 def movetopmostbookmarks(repo, oldtopmost, newtopmost):
1590 """Move bookmark from oldtopmost to newly created topmost
1592 """Move bookmark from oldtopmost to newly created topmost
1591
1593
1592 This is arguably a feature and we may only want that for the active
1594 This is arguably a feature and we may only want that for the active
1593 bookmark. But the behavior is kept compatible with the old version for now.
1595 bookmark. But the behavior is kept compatible with the old version for now.
1594 """
1596 """
1595 if not oldtopmost or not newtopmost:
1597 if not oldtopmost or not newtopmost:
1596 return
1598 return
1597 oldbmarks = repo.nodebookmarks(oldtopmost)
1599 oldbmarks = repo.nodebookmarks(oldtopmost)
1598 if oldbmarks:
1600 if oldbmarks:
1599 with repo.lock(), repo.transaction('histedit') as tr:
1601 with repo.lock(), repo.transaction('histedit') as tr:
1600 marks = repo._bookmarks
1602 marks = repo._bookmarks
1601 changes = []
1603 changes = []
1602 for name in oldbmarks:
1604 for name in oldbmarks:
1603 changes.append((name, newtopmost))
1605 changes.append((name, newtopmost))
1604 marks.applychanges(repo, tr, changes)
1606 marks.applychanges(repo, tr, changes)
1605
1607
1606 def cleanupnode(ui, repo, nodes, nobackup=False):
1608 def cleanupnode(ui, repo, nodes, nobackup=False):
1607 """strip a group of nodes from the repository
1609 """strip a group of nodes from the repository
1608
1610
1609 The set of node to strip may contains unknown nodes."""
1611 The set of node to strip may contains unknown nodes."""
1610 with repo.lock():
1612 with repo.lock():
1611 # do not let filtering get in the way of the cleanse
1613 # do not let filtering get in the way of the cleanse
1612 # we should probably get rid of obsolescence marker created during the
1614 # we should probably get rid of obsolescence marker created during the
1613 # histedit, but we currently do not have such information.
1615 # histedit, but we currently do not have such information.
1614 repo = repo.unfiltered()
1616 repo = repo.unfiltered()
1615 # Find all nodes that need to be stripped
1617 # Find all nodes that need to be stripped
1616 # (we use %lr instead of %ln to silently ignore unknown items)
1618 # (we use %lr instead of %ln to silently ignore unknown items)
1617 nm = repo.changelog.nodemap
1619 nm = repo.changelog.nodemap
1618 nodes = sorted(n for n in nodes if n in nm)
1620 nodes = sorted(n for n in nodes if n in nm)
1619 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
1621 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
1620 if roots:
1622 if roots:
1621 backup = not nobackup
1623 backup = not nobackup
1622 repair.strip(ui, repo, roots, backup=backup)
1624 repair.strip(ui, repo, roots, backup=backup)
1623
1625
1624 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
1626 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
1625 if isinstance(nodelist, str):
1627 if isinstance(nodelist, str):
1626 nodelist = [nodelist]
1628 nodelist = [nodelist]
1627 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
1629 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
1628 state = histeditstate(repo)
1630 state = histeditstate(repo)
1629 state.read()
1631 state.read()
1630 histedit_nodes = {action.node for action
1632 histedit_nodes = {action.node for action
1631 in state.actions if action.node}
1633 in state.actions if action.node}
1632 common_nodes = histedit_nodes & set(nodelist)
1634 common_nodes = histedit_nodes & set(nodelist)
1633 if common_nodes:
1635 if common_nodes:
1634 raise error.Abort(_("histedit in progress, can't strip %s")
1636 raise error.Abort(_("histedit in progress, can't strip %s")
1635 % ', '.join(node.short(x) for x in common_nodes))
1637 % ', '.join(node.short(x) for x in common_nodes))
1636 return orig(ui, repo, nodelist, *args, **kwargs)
1638 return orig(ui, repo, nodelist, *args, **kwargs)
1637
1639
1638 extensions.wrapfunction(repair, 'strip', stripwrapper)
1640 extensions.wrapfunction(repair, 'strip', stripwrapper)
1639
1641
1640 def summaryhook(ui, repo):
1642 def summaryhook(ui, repo):
1641 if not os.path.exists(repo.vfs.join('histedit-state')):
1643 if not os.path.exists(repo.vfs.join('histedit-state')):
1642 return
1644 return
1643 state = histeditstate(repo)
1645 state = histeditstate(repo)
1644 state.read()
1646 state.read()
1645 if state.actions:
1647 if state.actions:
1646 # i18n: column positioning for "hg summary"
1648 # i18n: column positioning for "hg summary"
1647 ui.write(_('hist: %s (histedit --continue)\n') %
1649 ui.write(_('hist: %s (histedit --continue)\n') %
1648 (ui.label(_('%d remaining'), 'histedit.remaining') %
1650 (ui.label(_('%d remaining'), 'histedit.remaining') %
1649 len(state.actions)))
1651 len(state.actions)))
1650
1652
1651 def extsetup(ui):
1653 def extsetup(ui):
1652 cmdutil.summaryhooks.add('histedit', summaryhook)
1654 cmdutil.summaryhooks.add('histedit', summaryhook)
1653 cmdutil.unfinishedstates.append(
1655 cmdutil.unfinishedstates.append(
1654 ['histedit-state', False, True, _('histedit in progress'),
1656 ['histedit-state', False, True, _('histedit in progress'),
1655 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
1657 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
1656 cmdutil.afterresolvedstates.append(
1658 cmdutil.afterresolvedstates.append(
1657 ['histedit-state', _('hg histedit --continue')])
1659 ['histedit-state', _('hg histedit --continue')])
@@ -1,1379 +1,1382 b''
1 # configitems.py - centralized declaration of configuration option
1 # configitems.py - centralized declaration of configuration option
2 #
2 #
3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
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 functools
10 import functools
11 import re
11 import re
12
12
13 from . import (
13 from . import (
14 encoding,
14 encoding,
15 error,
15 error,
16 )
16 )
17
17
18 def loadconfigtable(ui, extname, configtable):
18 def loadconfigtable(ui, extname, configtable):
19 """update config item known to the ui with the extension ones"""
19 """update config item known to the ui with the extension ones"""
20 for section, items in sorted(configtable.items()):
20 for section, items in sorted(configtable.items()):
21 knownitems = ui._knownconfig.setdefault(section, itemregister())
21 knownitems = ui._knownconfig.setdefault(section, itemregister())
22 knownkeys = set(knownitems)
22 knownkeys = set(knownitems)
23 newkeys = set(items)
23 newkeys = set(items)
24 for key in sorted(knownkeys & newkeys):
24 for key in sorted(knownkeys & newkeys):
25 msg = "extension '%s' overwrite config item '%s.%s'"
25 msg = "extension '%s' overwrite config item '%s.%s'"
26 msg %= (extname, section, key)
26 msg %= (extname, section, key)
27 ui.develwarn(msg, config='warn-config')
27 ui.develwarn(msg, config='warn-config')
28
28
29 knownitems.update(items)
29 knownitems.update(items)
30
30
31 class configitem(object):
31 class configitem(object):
32 """represent a known config item
32 """represent a known config item
33
33
34 :section: the official config section where to find this item,
34 :section: the official config section where to find this item,
35 :name: the official name within the section,
35 :name: the official name within the section,
36 :default: default value for this item,
36 :default: default value for this item,
37 :alias: optional list of tuples as alternatives,
37 :alias: optional list of tuples as alternatives,
38 :generic: this is a generic definition, match name using regular expression.
38 :generic: this is a generic definition, match name using regular expression.
39 """
39 """
40
40
41 def __init__(self, section, name, default=None, alias=(),
41 def __init__(self, section, name, default=None, alias=(),
42 generic=False, priority=0):
42 generic=False, priority=0):
43 self.section = section
43 self.section = section
44 self.name = name
44 self.name = name
45 self.default = default
45 self.default = default
46 self.alias = list(alias)
46 self.alias = list(alias)
47 self.generic = generic
47 self.generic = generic
48 self.priority = priority
48 self.priority = priority
49 self._re = None
49 self._re = None
50 if generic:
50 if generic:
51 self._re = re.compile(self.name)
51 self._re = re.compile(self.name)
52
52
53 class itemregister(dict):
53 class itemregister(dict):
54 """A specialized dictionary that can handle wild-card selection"""
54 """A specialized dictionary that can handle wild-card selection"""
55
55
56 def __init__(self):
56 def __init__(self):
57 super(itemregister, self).__init__()
57 super(itemregister, self).__init__()
58 self._generics = set()
58 self._generics = set()
59
59
60 def update(self, other):
60 def update(self, other):
61 super(itemregister, self).update(other)
61 super(itemregister, self).update(other)
62 self._generics.update(other._generics)
62 self._generics.update(other._generics)
63
63
64 def __setitem__(self, key, item):
64 def __setitem__(self, key, item):
65 super(itemregister, self).__setitem__(key, item)
65 super(itemregister, self).__setitem__(key, item)
66 if item.generic:
66 if item.generic:
67 self._generics.add(item)
67 self._generics.add(item)
68
68
69 def get(self, key):
69 def get(self, key):
70 baseitem = super(itemregister, self).get(key)
70 baseitem = super(itemregister, self).get(key)
71 if baseitem is not None and not baseitem.generic:
71 if baseitem is not None and not baseitem.generic:
72 return baseitem
72 return baseitem
73
73
74 # search for a matching generic item
74 # search for a matching generic item
75 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
75 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
76 for item in generics:
76 for item in generics:
77 # we use 'match' instead of 'search' to make the matching simpler
77 # we use 'match' instead of 'search' to make the matching simpler
78 # for people unfamiliar with regular expression. Having the match
78 # for people unfamiliar with regular expression. Having the match
79 # rooted to the start of the string will produce less surprising
79 # rooted to the start of the string will produce less surprising
80 # result for user writing simple regex for sub-attribute.
80 # result for user writing simple regex for sub-attribute.
81 #
81 #
82 # For example using "color\..*" match produces an unsurprising
82 # For example using "color\..*" match produces an unsurprising
83 # result, while using search could suddenly match apparently
83 # result, while using search could suddenly match apparently
84 # unrelated configuration that happens to contains "color."
84 # unrelated configuration that happens to contains "color."
85 # anywhere. This is a tradeoff where we favor requiring ".*" on
85 # anywhere. This is a tradeoff where we favor requiring ".*" on
86 # some match to avoid the need to prefix most pattern with "^".
86 # some match to avoid the need to prefix most pattern with "^".
87 # The "^" seems more error prone.
87 # The "^" seems more error prone.
88 if item._re.match(key):
88 if item._re.match(key):
89 return item
89 return item
90
90
91 return None
91 return None
92
92
93 coreitems = {}
93 coreitems = {}
94
94
95 def _register(configtable, *args, **kwargs):
95 def _register(configtable, *args, **kwargs):
96 item = configitem(*args, **kwargs)
96 item = configitem(*args, **kwargs)
97 section = configtable.setdefault(item.section, itemregister())
97 section = configtable.setdefault(item.section, itemregister())
98 if item.name in section:
98 if item.name in section:
99 msg = "duplicated config item registration for '%s.%s'"
99 msg = "duplicated config item registration for '%s.%s'"
100 raise error.ProgrammingError(msg % (item.section, item.name))
100 raise error.ProgrammingError(msg % (item.section, item.name))
101 section[item.name] = item
101 section[item.name] = item
102
102
103 # special value for case where the default is derived from other values
103 # special value for case where the default is derived from other values
104 dynamicdefault = object()
104 dynamicdefault = object()
105
105
106 # Registering actual config items
106 # Registering actual config items
107
107
108 def getitemregister(configtable):
108 def getitemregister(configtable):
109 f = functools.partial(_register, configtable)
109 f = functools.partial(_register, configtable)
110 # export pseudo enum as configitem.*
110 # export pseudo enum as configitem.*
111 f.dynamicdefault = dynamicdefault
111 f.dynamicdefault = dynamicdefault
112 return f
112 return f
113
113
114 coreconfigitem = getitemregister(coreitems)
114 coreconfigitem = getitemregister(coreitems)
115
115
116 coreconfigitem('alias', '.*',
116 coreconfigitem('alias', '.*',
117 default=dynamicdefault,
117 default=dynamicdefault,
118 generic=True,
118 generic=True,
119 )
119 )
120 coreconfigitem('annotate', 'nodates',
120 coreconfigitem('annotate', 'nodates',
121 default=False,
121 default=False,
122 )
122 )
123 coreconfigitem('annotate', 'showfunc',
123 coreconfigitem('annotate', 'showfunc',
124 default=False,
124 default=False,
125 )
125 )
126 coreconfigitem('annotate', 'unified',
126 coreconfigitem('annotate', 'unified',
127 default=None,
127 default=None,
128 )
128 )
129 coreconfigitem('annotate', 'git',
129 coreconfigitem('annotate', 'git',
130 default=False,
130 default=False,
131 )
131 )
132 coreconfigitem('annotate', 'ignorews',
132 coreconfigitem('annotate', 'ignorews',
133 default=False,
133 default=False,
134 )
134 )
135 coreconfigitem('annotate', 'ignorewsamount',
135 coreconfigitem('annotate', 'ignorewsamount',
136 default=False,
136 default=False,
137 )
137 )
138 coreconfigitem('annotate', 'ignoreblanklines',
138 coreconfigitem('annotate', 'ignoreblanklines',
139 default=False,
139 default=False,
140 )
140 )
141 coreconfigitem('annotate', 'ignorewseol',
141 coreconfigitem('annotate', 'ignorewseol',
142 default=False,
142 default=False,
143 )
143 )
144 coreconfigitem('annotate', 'nobinary',
144 coreconfigitem('annotate', 'nobinary',
145 default=False,
145 default=False,
146 )
146 )
147 coreconfigitem('annotate', 'noprefix',
147 coreconfigitem('annotate', 'noprefix',
148 default=False,
148 default=False,
149 )
149 )
150 coreconfigitem('annotate', 'word-diff',
150 coreconfigitem('annotate', 'word-diff',
151 default=False,
151 default=False,
152 )
152 )
153 coreconfigitem('auth', 'cookiefile',
153 coreconfigitem('auth', 'cookiefile',
154 default=None,
154 default=None,
155 )
155 )
156 # bookmarks.pushing: internal hack for discovery
156 # bookmarks.pushing: internal hack for discovery
157 coreconfigitem('bookmarks', 'pushing',
157 coreconfigitem('bookmarks', 'pushing',
158 default=list,
158 default=list,
159 )
159 )
160 # bundle.mainreporoot: internal hack for bundlerepo
160 # bundle.mainreporoot: internal hack for bundlerepo
161 coreconfigitem('bundle', 'mainreporoot',
161 coreconfigitem('bundle', 'mainreporoot',
162 default='',
162 default='',
163 )
163 )
164 # bundle.reorder: experimental config
164 # bundle.reorder: experimental config
165 coreconfigitem('bundle', 'reorder',
165 coreconfigitem('bundle', 'reorder',
166 default='auto',
166 default='auto',
167 )
167 )
168 coreconfigitem('censor', 'policy',
168 coreconfigitem('censor', 'policy',
169 default='abort',
169 default='abort',
170 )
170 )
171 coreconfigitem('chgserver', 'idletimeout',
171 coreconfigitem('chgserver', 'idletimeout',
172 default=3600,
172 default=3600,
173 )
173 )
174 coreconfigitem('chgserver', 'skiphash',
174 coreconfigitem('chgserver', 'skiphash',
175 default=False,
175 default=False,
176 )
176 )
177 coreconfigitem('cmdserver', 'log',
177 coreconfigitem('cmdserver', 'log',
178 default=None,
178 default=None,
179 )
179 )
180 coreconfigitem('color', '.*',
180 coreconfigitem('color', '.*',
181 default=None,
181 default=None,
182 generic=True,
182 generic=True,
183 )
183 )
184 coreconfigitem('color', 'mode',
184 coreconfigitem('color', 'mode',
185 default='auto',
185 default='auto',
186 )
186 )
187 coreconfigitem('color', 'pagermode',
187 coreconfigitem('color', 'pagermode',
188 default=dynamicdefault,
188 default=dynamicdefault,
189 )
189 )
190 coreconfigitem('commands', 'grep.all-files',
190 coreconfigitem('commands', 'grep.all-files',
191 default=False,
191 default=False,
192 )
192 )
193 coreconfigitem('commands', 'show.aliasprefix',
193 coreconfigitem('commands', 'show.aliasprefix',
194 default=list,
194 default=list,
195 )
195 )
196 coreconfigitem('commands', 'status.relative',
196 coreconfigitem('commands', 'status.relative',
197 default=False,
197 default=False,
198 )
198 )
199 coreconfigitem('commands', 'status.skipstates',
199 coreconfigitem('commands', 'status.skipstates',
200 default=[],
200 default=[],
201 )
201 )
202 coreconfigitem('commands', 'status.terse',
202 coreconfigitem('commands', 'status.terse',
203 default='',
203 default='',
204 )
204 )
205 coreconfigitem('commands', 'status.verbose',
205 coreconfigitem('commands', 'status.verbose',
206 default=False,
206 default=False,
207 )
207 )
208 coreconfigitem('commands', 'update.check',
208 coreconfigitem('commands', 'update.check',
209 default=None,
209 default=None,
210 )
210 )
211 coreconfigitem('commands', 'update.requiredest',
211 coreconfigitem('commands', 'update.requiredest',
212 default=False,
212 default=False,
213 )
213 )
214 coreconfigitem('committemplate', '.*',
214 coreconfigitem('committemplate', '.*',
215 default=None,
215 default=None,
216 generic=True,
216 generic=True,
217 )
217 )
218 coreconfigitem('convert', 'bzr.saverev',
218 coreconfigitem('convert', 'bzr.saverev',
219 default=True,
219 default=True,
220 )
220 )
221 coreconfigitem('convert', 'cvsps.cache',
221 coreconfigitem('convert', 'cvsps.cache',
222 default=True,
222 default=True,
223 )
223 )
224 coreconfigitem('convert', 'cvsps.fuzz',
224 coreconfigitem('convert', 'cvsps.fuzz',
225 default=60,
225 default=60,
226 )
226 )
227 coreconfigitem('convert', 'cvsps.logencoding',
227 coreconfigitem('convert', 'cvsps.logencoding',
228 default=None,
228 default=None,
229 )
229 )
230 coreconfigitem('convert', 'cvsps.mergefrom',
230 coreconfigitem('convert', 'cvsps.mergefrom',
231 default=None,
231 default=None,
232 )
232 )
233 coreconfigitem('convert', 'cvsps.mergeto',
233 coreconfigitem('convert', 'cvsps.mergeto',
234 default=None,
234 default=None,
235 )
235 )
236 coreconfigitem('convert', 'git.committeractions',
236 coreconfigitem('convert', 'git.committeractions',
237 default=lambda: ['messagedifferent'],
237 default=lambda: ['messagedifferent'],
238 )
238 )
239 coreconfigitem('convert', 'git.extrakeys',
239 coreconfigitem('convert', 'git.extrakeys',
240 default=list,
240 default=list,
241 )
241 )
242 coreconfigitem('convert', 'git.findcopiesharder',
242 coreconfigitem('convert', 'git.findcopiesharder',
243 default=False,
243 default=False,
244 )
244 )
245 coreconfigitem('convert', 'git.remoteprefix',
245 coreconfigitem('convert', 'git.remoteprefix',
246 default='remote',
246 default='remote',
247 )
247 )
248 coreconfigitem('convert', 'git.renamelimit',
248 coreconfigitem('convert', 'git.renamelimit',
249 default=400,
249 default=400,
250 )
250 )
251 coreconfigitem('convert', 'git.saverev',
251 coreconfigitem('convert', 'git.saverev',
252 default=True,
252 default=True,
253 )
253 )
254 coreconfigitem('convert', 'git.similarity',
254 coreconfigitem('convert', 'git.similarity',
255 default=50,
255 default=50,
256 )
256 )
257 coreconfigitem('convert', 'git.skipsubmodules',
257 coreconfigitem('convert', 'git.skipsubmodules',
258 default=False,
258 default=False,
259 )
259 )
260 coreconfigitem('convert', 'hg.clonebranches',
260 coreconfigitem('convert', 'hg.clonebranches',
261 default=False,
261 default=False,
262 )
262 )
263 coreconfigitem('convert', 'hg.ignoreerrors',
263 coreconfigitem('convert', 'hg.ignoreerrors',
264 default=False,
264 default=False,
265 )
265 )
266 coreconfigitem('convert', 'hg.revs',
266 coreconfigitem('convert', 'hg.revs',
267 default=None,
267 default=None,
268 )
268 )
269 coreconfigitem('convert', 'hg.saverev',
269 coreconfigitem('convert', 'hg.saverev',
270 default=False,
270 default=False,
271 )
271 )
272 coreconfigitem('convert', 'hg.sourcename',
272 coreconfigitem('convert', 'hg.sourcename',
273 default=None,
273 default=None,
274 )
274 )
275 coreconfigitem('convert', 'hg.startrev',
275 coreconfigitem('convert', 'hg.startrev',
276 default=None,
276 default=None,
277 )
277 )
278 coreconfigitem('convert', 'hg.tagsbranch',
278 coreconfigitem('convert', 'hg.tagsbranch',
279 default='default',
279 default='default',
280 )
280 )
281 coreconfigitem('convert', 'hg.usebranchnames',
281 coreconfigitem('convert', 'hg.usebranchnames',
282 default=True,
282 default=True,
283 )
283 )
284 coreconfigitem('convert', 'ignoreancestorcheck',
284 coreconfigitem('convert', 'ignoreancestorcheck',
285 default=False,
285 default=False,
286 )
286 )
287 coreconfigitem('convert', 'localtimezone',
287 coreconfigitem('convert', 'localtimezone',
288 default=False,
288 default=False,
289 )
289 )
290 coreconfigitem('convert', 'p4.encoding',
290 coreconfigitem('convert', 'p4.encoding',
291 default=dynamicdefault,
291 default=dynamicdefault,
292 )
292 )
293 coreconfigitem('convert', 'p4.startrev',
293 coreconfigitem('convert', 'p4.startrev',
294 default=0,
294 default=0,
295 )
295 )
296 coreconfigitem('convert', 'skiptags',
296 coreconfigitem('convert', 'skiptags',
297 default=False,
297 default=False,
298 )
298 )
299 coreconfigitem('convert', 'svn.debugsvnlog',
299 coreconfigitem('convert', 'svn.debugsvnlog',
300 default=True,
300 default=True,
301 )
301 )
302 coreconfigitem('convert', 'svn.trunk',
302 coreconfigitem('convert', 'svn.trunk',
303 default=None,
303 default=None,
304 )
304 )
305 coreconfigitem('convert', 'svn.tags',
305 coreconfigitem('convert', 'svn.tags',
306 default=None,
306 default=None,
307 )
307 )
308 coreconfigitem('convert', 'svn.branches',
308 coreconfigitem('convert', 'svn.branches',
309 default=None,
309 default=None,
310 )
310 )
311 coreconfigitem('convert', 'svn.startrev',
311 coreconfigitem('convert', 'svn.startrev',
312 default=0,
312 default=0,
313 )
313 )
314 coreconfigitem('debug', 'dirstate.delaywrite',
314 coreconfigitem('debug', 'dirstate.delaywrite',
315 default=0,
315 default=0,
316 )
316 )
317 coreconfigitem('defaults', '.*',
317 coreconfigitem('defaults', '.*',
318 default=None,
318 default=None,
319 generic=True,
319 generic=True,
320 )
320 )
321 coreconfigitem('devel', 'all-warnings',
321 coreconfigitem('devel', 'all-warnings',
322 default=False,
322 default=False,
323 )
323 )
324 coreconfigitem('devel', 'bundle2.debug',
324 coreconfigitem('devel', 'bundle2.debug',
325 default=False,
325 default=False,
326 )
326 )
327 coreconfigitem('devel', 'cache-vfs',
327 coreconfigitem('devel', 'cache-vfs',
328 default=None,
328 default=None,
329 )
329 )
330 coreconfigitem('devel', 'check-locks',
330 coreconfigitem('devel', 'check-locks',
331 default=False,
331 default=False,
332 )
332 )
333 coreconfigitem('devel', 'check-relroot',
333 coreconfigitem('devel', 'check-relroot',
334 default=False,
334 default=False,
335 )
335 )
336 coreconfigitem('devel', 'default-date',
336 coreconfigitem('devel', 'default-date',
337 default=None,
337 default=None,
338 )
338 )
339 coreconfigitem('devel', 'deprec-warn',
339 coreconfigitem('devel', 'deprec-warn',
340 default=False,
340 default=False,
341 )
341 )
342 coreconfigitem('devel', 'disableloaddefaultcerts',
342 coreconfigitem('devel', 'disableloaddefaultcerts',
343 default=False,
343 default=False,
344 )
344 )
345 coreconfigitem('devel', 'warn-empty-changegroup',
345 coreconfigitem('devel', 'warn-empty-changegroup',
346 default=False,
346 default=False,
347 )
347 )
348 coreconfigitem('devel', 'legacy.exchange',
348 coreconfigitem('devel', 'legacy.exchange',
349 default=list,
349 default=list,
350 )
350 )
351 coreconfigitem('devel', 'servercafile',
351 coreconfigitem('devel', 'servercafile',
352 default='',
352 default='',
353 )
353 )
354 coreconfigitem('devel', 'serverexactprotocol',
354 coreconfigitem('devel', 'serverexactprotocol',
355 default='',
355 default='',
356 )
356 )
357 coreconfigitem('devel', 'serverrequirecert',
357 coreconfigitem('devel', 'serverrequirecert',
358 default=False,
358 default=False,
359 )
359 )
360 coreconfigitem('devel', 'strip-obsmarkers',
360 coreconfigitem('devel', 'strip-obsmarkers',
361 default=True,
361 default=True,
362 )
362 )
363 coreconfigitem('devel', 'warn-config',
363 coreconfigitem('devel', 'warn-config',
364 default=None,
364 default=None,
365 )
365 )
366 coreconfigitem('devel', 'warn-config-default',
366 coreconfigitem('devel', 'warn-config-default',
367 default=None,
367 default=None,
368 )
368 )
369 coreconfigitem('devel', 'user.obsmarker',
369 coreconfigitem('devel', 'user.obsmarker',
370 default=None,
370 default=None,
371 )
371 )
372 coreconfigitem('devel', 'warn-config-unknown',
372 coreconfigitem('devel', 'warn-config-unknown',
373 default=None,
373 default=None,
374 )
374 )
375 coreconfigitem('devel', 'debug.extensions',
375 coreconfigitem('devel', 'debug.extensions',
376 default=False,
376 default=False,
377 )
377 )
378 coreconfigitem('devel', 'debug.peer-request',
378 coreconfigitem('devel', 'debug.peer-request',
379 default=False,
379 default=False,
380 )
380 )
381 coreconfigitem('diff', 'nodates',
381 coreconfigitem('diff', 'nodates',
382 default=False,
382 default=False,
383 )
383 )
384 coreconfigitem('diff', 'showfunc',
384 coreconfigitem('diff', 'showfunc',
385 default=False,
385 default=False,
386 )
386 )
387 coreconfigitem('diff', 'unified',
387 coreconfigitem('diff', 'unified',
388 default=None,
388 default=None,
389 )
389 )
390 coreconfigitem('diff', 'git',
390 coreconfigitem('diff', 'git',
391 default=False,
391 default=False,
392 )
392 )
393 coreconfigitem('diff', 'ignorews',
393 coreconfigitem('diff', 'ignorews',
394 default=False,
394 default=False,
395 )
395 )
396 coreconfigitem('diff', 'ignorewsamount',
396 coreconfigitem('diff', 'ignorewsamount',
397 default=False,
397 default=False,
398 )
398 )
399 coreconfigitem('diff', 'ignoreblanklines',
399 coreconfigitem('diff', 'ignoreblanklines',
400 default=False,
400 default=False,
401 )
401 )
402 coreconfigitem('diff', 'ignorewseol',
402 coreconfigitem('diff', 'ignorewseol',
403 default=False,
403 default=False,
404 )
404 )
405 coreconfigitem('diff', 'nobinary',
405 coreconfigitem('diff', 'nobinary',
406 default=False,
406 default=False,
407 )
407 )
408 coreconfigitem('diff', 'noprefix',
408 coreconfigitem('diff', 'noprefix',
409 default=False,
409 default=False,
410 )
410 )
411 coreconfigitem('diff', 'word-diff',
411 coreconfigitem('diff', 'word-diff',
412 default=False,
412 default=False,
413 )
413 )
414 coreconfigitem('email', 'bcc',
414 coreconfigitem('email', 'bcc',
415 default=None,
415 default=None,
416 )
416 )
417 coreconfigitem('email', 'cc',
417 coreconfigitem('email', 'cc',
418 default=None,
418 default=None,
419 )
419 )
420 coreconfigitem('email', 'charsets',
420 coreconfigitem('email', 'charsets',
421 default=list,
421 default=list,
422 )
422 )
423 coreconfigitem('email', 'from',
423 coreconfigitem('email', 'from',
424 default=None,
424 default=None,
425 )
425 )
426 coreconfigitem('email', 'method',
426 coreconfigitem('email', 'method',
427 default='smtp',
427 default='smtp',
428 )
428 )
429 coreconfigitem('email', 'reply-to',
429 coreconfigitem('email', 'reply-to',
430 default=None,
430 default=None,
431 )
431 )
432 coreconfigitem('email', 'to',
432 coreconfigitem('email', 'to',
433 default=None,
433 default=None,
434 )
434 )
435 coreconfigitem('experimental', 'archivemetatemplate',
435 coreconfigitem('experimental', 'archivemetatemplate',
436 default=dynamicdefault,
436 default=dynamicdefault,
437 )
437 )
438 coreconfigitem('experimental', 'bundle-phases',
438 coreconfigitem('experimental', 'bundle-phases',
439 default=False,
439 default=False,
440 )
440 )
441 coreconfigitem('experimental', 'bundle2-advertise',
441 coreconfigitem('experimental', 'bundle2-advertise',
442 default=True,
442 default=True,
443 )
443 )
444 coreconfigitem('experimental', 'bundle2-output-capture',
444 coreconfigitem('experimental', 'bundle2-output-capture',
445 default=False,
445 default=False,
446 )
446 )
447 coreconfigitem('experimental', 'bundle2.pushback',
447 coreconfigitem('experimental', 'bundle2.pushback',
448 default=False,
448 default=False,
449 )
449 )
450 coreconfigitem('experimental', 'bundle2.stream',
450 coreconfigitem('experimental', 'bundle2.stream',
451 default=False,
451 default=False,
452 )
452 )
453 coreconfigitem('experimental', 'bundle2lazylocking',
453 coreconfigitem('experimental', 'bundle2lazylocking',
454 default=False,
454 default=False,
455 )
455 )
456 coreconfigitem('experimental', 'bundlecomplevel',
456 coreconfigitem('experimental', 'bundlecomplevel',
457 default=None,
457 default=None,
458 )
458 )
459 coreconfigitem('experimental', 'bundlecomplevel.bzip2',
459 coreconfigitem('experimental', 'bundlecomplevel.bzip2',
460 default=None,
460 default=None,
461 )
461 )
462 coreconfigitem('experimental', 'bundlecomplevel.gzip',
462 coreconfigitem('experimental', 'bundlecomplevel.gzip',
463 default=None,
463 default=None,
464 )
464 )
465 coreconfigitem('experimental', 'bundlecomplevel.none',
465 coreconfigitem('experimental', 'bundlecomplevel.none',
466 default=None,
466 default=None,
467 )
467 )
468 coreconfigitem('experimental', 'bundlecomplevel.zstd',
468 coreconfigitem('experimental', 'bundlecomplevel.zstd',
469 default=None,
469 default=None,
470 )
470 )
471 coreconfigitem('experimental', 'changegroup3',
471 coreconfigitem('experimental', 'changegroup3',
472 default=False,
472 default=False,
473 )
473 )
474 coreconfigitem('experimental', 'clientcompressionengines',
474 coreconfigitem('experimental', 'clientcompressionengines',
475 default=list,
475 default=list,
476 )
476 )
477 coreconfigitem('experimental', 'copytrace',
477 coreconfigitem('experimental', 'copytrace',
478 default='on',
478 default='on',
479 )
479 )
480 coreconfigitem('experimental', 'copytrace.movecandidateslimit',
480 coreconfigitem('experimental', 'copytrace.movecandidateslimit',
481 default=100,
481 default=100,
482 )
482 )
483 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
483 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
484 default=100,
484 default=100,
485 )
485 )
486 coreconfigitem('experimental', 'crecordtest',
486 coreconfigitem('experimental', 'crecordtest',
487 default=None,
487 default=None,
488 )
488 )
489 coreconfigitem('experimental', 'directaccess',
489 coreconfigitem('experimental', 'directaccess',
490 default=False,
490 default=False,
491 )
491 )
492 coreconfigitem('experimental', 'directaccess.revnums',
492 coreconfigitem('experimental', 'directaccess.revnums',
493 default=False,
493 default=False,
494 )
494 )
495 coreconfigitem('experimental', 'editortmpinhg',
495 coreconfigitem('experimental', 'editortmpinhg',
496 default=False,
496 default=False,
497 )
497 )
498 coreconfigitem('experimental', 'evolution',
498 coreconfigitem('experimental', 'evolution',
499 default=list,
499 default=list,
500 )
500 )
501 coreconfigitem('experimental', 'evolution.allowdivergence',
501 coreconfigitem('experimental', 'evolution.allowdivergence',
502 default=False,
502 default=False,
503 alias=[('experimental', 'allowdivergence')]
503 alias=[('experimental', 'allowdivergence')]
504 )
504 )
505 coreconfigitem('experimental', 'evolution.allowunstable',
505 coreconfigitem('experimental', 'evolution.allowunstable',
506 default=None,
506 default=None,
507 )
507 )
508 coreconfigitem('experimental', 'evolution.createmarkers',
508 coreconfigitem('experimental', 'evolution.createmarkers',
509 default=None,
509 default=None,
510 )
510 )
511 coreconfigitem('experimental', 'evolution.effect-flags',
511 coreconfigitem('experimental', 'evolution.effect-flags',
512 default=True,
512 default=True,
513 alias=[('experimental', 'effect-flags')]
513 alias=[('experimental', 'effect-flags')]
514 )
514 )
515 coreconfigitem('experimental', 'evolution.exchange',
515 coreconfigitem('experimental', 'evolution.exchange',
516 default=None,
516 default=None,
517 )
517 )
518 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
518 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
519 default=False,
519 default=False,
520 )
520 )
521 coreconfigitem('experimental', 'evolution.report-instabilities',
521 coreconfigitem('experimental', 'evolution.report-instabilities',
522 default=True,
522 default=True,
523 )
523 )
524 coreconfigitem('experimental', 'evolution.track-operation',
524 coreconfigitem('experimental', 'evolution.track-operation',
525 default=True,
525 default=True,
526 )
526 )
527 coreconfigitem('experimental', 'maxdeltachainspan',
527 coreconfigitem('experimental', 'maxdeltachainspan',
528 default=-1,
528 default=-1,
529 )
529 )
530 coreconfigitem('experimental', 'mergetempdirprefix',
530 coreconfigitem('experimental', 'mergetempdirprefix',
531 default=None,
531 default=None,
532 )
532 )
533 coreconfigitem('experimental', 'mmapindexthreshold',
533 coreconfigitem('experimental', 'mmapindexthreshold',
534 default=None,
534 default=None,
535 )
535 )
536 coreconfigitem('experimental', 'nonnormalparanoidcheck',
536 coreconfigitem('experimental', 'nonnormalparanoidcheck',
537 default=False,
537 default=False,
538 )
538 )
539 coreconfigitem('experimental', 'exportableenviron',
539 coreconfigitem('experimental', 'exportableenviron',
540 default=list,
540 default=list,
541 )
541 )
542 coreconfigitem('experimental', 'extendedheader.index',
542 coreconfigitem('experimental', 'extendedheader.index',
543 default=None,
543 default=None,
544 )
544 )
545 coreconfigitem('experimental', 'extendedheader.similarity',
545 coreconfigitem('experimental', 'extendedheader.similarity',
546 default=False,
546 default=False,
547 )
547 )
548 coreconfigitem('experimental', 'format.compression',
548 coreconfigitem('experimental', 'format.compression',
549 default='zlib',
549 default='zlib',
550 )
550 )
551 coreconfigitem('experimental', 'graphshorten',
551 coreconfigitem('experimental', 'graphshorten',
552 default=False,
552 default=False,
553 )
553 )
554 coreconfigitem('experimental', 'graphstyle.parent',
554 coreconfigitem('experimental', 'graphstyle.parent',
555 default=dynamicdefault,
555 default=dynamicdefault,
556 )
556 )
557 coreconfigitem('experimental', 'graphstyle.missing',
557 coreconfigitem('experimental', 'graphstyle.missing',
558 default=dynamicdefault,
558 default=dynamicdefault,
559 )
559 )
560 coreconfigitem('experimental', 'graphstyle.grandparent',
560 coreconfigitem('experimental', 'graphstyle.grandparent',
561 default=dynamicdefault,
561 default=dynamicdefault,
562 )
562 )
563 coreconfigitem('experimental', 'hook-track-tags',
563 coreconfigitem('experimental', 'hook-track-tags',
564 default=False,
564 default=False,
565 )
565 )
566 coreconfigitem('experimental', 'httppeer.advertise-v2',
566 coreconfigitem('experimental', 'httppeer.advertise-v2',
567 default=False,
567 default=False,
568 )
568 )
569 coreconfigitem('experimental', 'httppostargs',
569 coreconfigitem('experimental', 'httppostargs',
570 default=False,
570 default=False,
571 )
571 )
572 coreconfigitem('experimental', 'mergedriver',
572 coreconfigitem('experimental', 'mergedriver',
573 default=None,
573 default=None,
574 )
574 )
575 coreconfigitem('experimental', 'nointerrupt', default=False)
575 coreconfigitem('experimental', 'nointerrupt', default=False)
576 coreconfigitem('experimental', 'nointerrupt-interactiveonly', default=True)
576 coreconfigitem('experimental', 'nointerrupt-interactiveonly', default=True)
577
577
578 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
578 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
579 default=False,
579 default=False,
580 )
580 )
581 coreconfigitem('experimental', 'remotenames',
581 coreconfigitem('experimental', 'remotenames',
582 default=False,
582 default=False,
583 )
583 )
584 coreconfigitem('experimental', 'removeemptydirs',
584 coreconfigitem('experimental', 'removeemptydirs',
585 default=True,
585 default=True,
586 )
586 )
587 coreconfigitem('experimental', 'revlogv2',
587 coreconfigitem('experimental', 'revlogv2',
588 default=None,
588 default=None,
589 )
589 )
590 coreconfigitem('experimental', 'single-head-per-branch',
590 coreconfigitem('experimental', 'single-head-per-branch',
591 default=False,
591 default=False,
592 )
592 )
593 coreconfigitem('experimental', 'sshserver.support-v2',
593 coreconfigitem('experimental', 'sshserver.support-v2',
594 default=False,
594 default=False,
595 )
595 )
596 coreconfigitem('experimental', 'spacemovesdown',
596 coreconfigitem('experimental', 'spacemovesdown',
597 default=False,
597 default=False,
598 )
598 )
599 coreconfigitem('experimental', 'sparse-read',
599 coreconfigitem('experimental', 'sparse-read',
600 default=False,
600 default=False,
601 )
601 )
602 coreconfigitem('experimental', 'sparse-read.density-threshold',
602 coreconfigitem('experimental', 'sparse-read.density-threshold',
603 default=0.50,
603 default=0.50,
604 )
604 )
605 coreconfigitem('experimental', 'sparse-read.min-gap-size',
605 coreconfigitem('experimental', 'sparse-read.min-gap-size',
606 default='65K',
606 default='65K',
607 )
607 )
608 coreconfigitem('experimental', 'treemanifest',
608 coreconfigitem('experimental', 'treemanifest',
609 default=False,
609 default=False,
610 )
610 )
611 coreconfigitem('experimental', 'update.atomic-file',
611 coreconfigitem('experimental', 'update.atomic-file',
612 default=False,
612 default=False,
613 )
613 )
614 coreconfigitem('experimental', 'sshpeer.advertise-v2',
614 coreconfigitem('experimental', 'sshpeer.advertise-v2',
615 default=False,
615 default=False,
616 )
616 )
617 coreconfigitem('experimental', 'web.apiserver',
617 coreconfigitem('experimental', 'web.apiserver',
618 default=False,
618 default=False,
619 )
619 )
620 coreconfigitem('experimental', 'web.api.http-v2',
620 coreconfigitem('experimental', 'web.api.http-v2',
621 default=False,
621 default=False,
622 )
622 )
623 coreconfigitem('experimental', 'web.api.debugreflect',
623 coreconfigitem('experimental', 'web.api.debugreflect',
624 default=False,
624 default=False,
625 )
625 )
626 coreconfigitem('experimental', 'worker.wdir-get-thread-safe',
626 coreconfigitem('experimental', 'worker.wdir-get-thread-safe',
627 default=False,
627 default=False,
628 )
628 )
629 coreconfigitem('experimental', 'xdiff',
629 coreconfigitem('experimental', 'xdiff',
630 default=False,
630 default=False,
631 )
631 )
632 coreconfigitem('extensions', '.*',
632 coreconfigitem('extensions', '.*',
633 default=None,
633 default=None,
634 generic=True,
634 generic=True,
635 )
635 )
636 coreconfigitem('extdata', '.*',
636 coreconfigitem('extdata', '.*',
637 default=None,
637 default=None,
638 generic=True,
638 generic=True,
639 )
639 )
640 coreconfigitem('format', 'aggressivemergedeltas',
640 coreconfigitem('format', 'aggressivemergedeltas',
641 default=True,
641 default=True,
642 )
642 )
643 coreconfigitem('format', 'chunkcachesize',
643 coreconfigitem('format', 'chunkcachesize',
644 default=None,
644 default=None,
645 )
645 )
646 coreconfigitem('format', 'dotencode',
646 coreconfigitem('format', 'dotencode',
647 default=True,
647 default=True,
648 )
648 )
649 coreconfigitem('format', 'generaldelta',
649 coreconfigitem('format', 'generaldelta',
650 default=False,
650 default=False,
651 )
651 )
652 coreconfigitem('format', 'manifestcachesize',
652 coreconfigitem('format', 'manifestcachesize',
653 default=None,
653 default=None,
654 )
654 )
655 coreconfigitem('format', 'maxchainlen',
655 coreconfigitem('format', 'maxchainlen',
656 default=None,
656 default=None,
657 )
657 )
658 coreconfigitem('format', 'obsstore-version',
658 coreconfigitem('format', 'obsstore-version',
659 default=None,
659 default=None,
660 )
660 )
661 coreconfigitem('format', 'sparse-revlog',
661 coreconfigitem('format', 'sparse-revlog',
662 default=False,
662 default=False,
663 )
663 )
664 coreconfigitem('format', 'usefncache',
664 coreconfigitem('format', 'usefncache',
665 default=True,
665 default=True,
666 )
666 )
667 coreconfigitem('format', 'usegeneraldelta',
667 coreconfigitem('format', 'usegeneraldelta',
668 default=True,
668 default=True,
669 )
669 )
670 coreconfigitem('format', 'usestore',
670 coreconfigitem('format', 'usestore',
671 default=True,
671 default=True,
672 )
672 )
673 coreconfigitem('fsmonitor', 'warn_when_unused',
673 coreconfigitem('fsmonitor', 'warn_when_unused',
674 default=True,
674 default=True,
675 )
675 )
676 coreconfigitem('fsmonitor', 'warn_update_file_count',
676 coreconfigitem('fsmonitor', 'warn_update_file_count',
677 default=50000,
677 default=50000,
678 )
678 )
679 coreconfigitem('hooks', '.*',
679 coreconfigitem('hooks', '.*',
680 default=dynamicdefault,
680 default=dynamicdefault,
681 generic=True,
681 generic=True,
682 )
682 )
683 coreconfigitem('hgweb-paths', '.*',
683 coreconfigitem('hgweb-paths', '.*',
684 default=list,
684 default=list,
685 generic=True,
685 generic=True,
686 )
686 )
687 coreconfigitem('hostfingerprints', '.*',
687 coreconfigitem('hostfingerprints', '.*',
688 default=list,
688 default=list,
689 generic=True,
689 generic=True,
690 )
690 )
691 coreconfigitem('hostsecurity', 'ciphers',
691 coreconfigitem('hostsecurity', 'ciphers',
692 default=None,
692 default=None,
693 )
693 )
694 coreconfigitem('hostsecurity', 'disabletls10warning',
694 coreconfigitem('hostsecurity', 'disabletls10warning',
695 default=False,
695 default=False,
696 )
696 )
697 coreconfigitem('hostsecurity', 'minimumprotocol',
697 coreconfigitem('hostsecurity', 'minimumprotocol',
698 default=dynamicdefault,
698 default=dynamicdefault,
699 )
699 )
700 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
700 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
701 default=dynamicdefault,
701 default=dynamicdefault,
702 generic=True,
702 generic=True,
703 )
703 )
704 coreconfigitem('hostsecurity', '.*:ciphers$',
704 coreconfigitem('hostsecurity', '.*:ciphers$',
705 default=dynamicdefault,
705 default=dynamicdefault,
706 generic=True,
706 generic=True,
707 )
707 )
708 coreconfigitem('hostsecurity', '.*:fingerprints$',
708 coreconfigitem('hostsecurity', '.*:fingerprints$',
709 default=list,
709 default=list,
710 generic=True,
710 generic=True,
711 )
711 )
712 coreconfigitem('hostsecurity', '.*:verifycertsfile$',
712 coreconfigitem('hostsecurity', '.*:verifycertsfile$',
713 default=None,
713 default=None,
714 generic=True,
714 generic=True,
715 )
715 )
716
716
717 coreconfigitem('http_proxy', 'always',
717 coreconfigitem('http_proxy', 'always',
718 default=False,
718 default=False,
719 )
719 )
720 coreconfigitem('http_proxy', 'host',
720 coreconfigitem('http_proxy', 'host',
721 default=None,
721 default=None,
722 )
722 )
723 coreconfigitem('http_proxy', 'no',
723 coreconfigitem('http_proxy', 'no',
724 default=list,
724 default=list,
725 )
725 )
726 coreconfigitem('http_proxy', 'passwd',
726 coreconfigitem('http_proxy', 'passwd',
727 default=None,
727 default=None,
728 )
728 )
729 coreconfigitem('http_proxy', 'user',
729 coreconfigitem('http_proxy', 'user',
730 default=None,
730 default=None,
731 )
731 )
732 coreconfigitem('logtoprocess', 'commandexception',
732 coreconfigitem('logtoprocess', 'commandexception',
733 default=None,
733 default=None,
734 )
734 )
735 coreconfigitem('logtoprocess', 'commandfinish',
735 coreconfigitem('logtoprocess', 'commandfinish',
736 default=None,
736 default=None,
737 )
737 )
738 coreconfigitem('logtoprocess', 'command',
738 coreconfigitem('logtoprocess', 'command',
739 default=None,
739 default=None,
740 )
740 )
741 coreconfigitem('logtoprocess', 'develwarn',
741 coreconfigitem('logtoprocess', 'develwarn',
742 default=None,
742 default=None,
743 )
743 )
744 coreconfigitem('logtoprocess', 'uiblocked',
744 coreconfigitem('logtoprocess', 'uiblocked',
745 default=None,
745 default=None,
746 )
746 )
747 coreconfigitem('merge', 'checkunknown',
747 coreconfigitem('merge', 'checkunknown',
748 default='abort',
748 default='abort',
749 )
749 )
750 coreconfigitem('merge', 'checkignored',
750 coreconfigitem('merge', 'checkignored',
751 default='abort',
751 default='abort',
752 )
752 )
753 coreconfigitem('experimental', 'merge.checkpathconflicts',
753 coreconfigitem('experimental', 'merge.checkpathconflicts',
754 default=False,
754 default=False,
755 )
755 )
756 coreconfigitem('merge', 'followcopies',
756 coreconfigitem('merge', 'followcopies',
757 default=True,
757 default=True,
758 )
758 )
759 coreconfigitem('merge', 'on-failure',
759 coreconfigitem('merge', 'on-failure',
760 default='continue',
760 default='continue',
761 )
761 )
762 coreconfigitem('merge', 'preferancestor',
762 coreconfigitem('merge', 'preferancestor',
763 default=lambda: ['*'],
763 default=lambda: ['*'],
764 )
764 )
765 coreconfigitem('merge-tools', '.*',
765 coreconfigitem('merge-tools', '.*',
766 default=None,
766 default=None,
767 generic=True,
767 generic=True,
768 )
768 )
769 coreconfigitem('merge-tools', br'.*\.args$',
769 coreconfigitem('merge-tools', br'.*\.args$',
770 default="$local $base $other",
770 default="$local $base $other",
771 generic=True,
771 generic=True,
772 priority=-1,
772 priority=-1,
773 )
773 )
774 coreconfigitem('merge-tools', br'.*\.binary$',
774 coreconfigitem('merge-tools', br'.*\.binary$',
775 default=False,
775 default=False,
776 generic=True,
776 generic=True,
777 priority=-1,
777 priority=-1,
778 )
778 )
779 coreconfigitem('merge-tools', br'.*\.check$',
779 coreconfigitem('merge-tools', br'.*\.check$',
780 default=list,
780 default=list,
781 generic=True,
781 generic=True,
782 priority=-1,
782 priority=-1,
783 )
783 )
784 coreconfigitem('merge-tools', br'.*\.checkchanged$',
784 coreconfigitem('merge-tools', br'.*\.checkchanged$',
785 default=False,
785 default=False,
786 generic=True,
786 generic=True,
787 priority=-1,
787 priority=-1,
788 )
788 )
789 coreconfigitem('merge-tools', br'.*\.executable$',
789 coreconfigitem('merge-tools', br'.*\.executable$',
790 default=dynamicdefault,
790 default=dynamicdefault,
791 generic=True,
791 generic=True,
792 priority=-1,
792 priority=-1,
793 )
793 )
794 coreconfigitem('merge-tools', br'.*\.fixeol$',
794 coreconfigitem('merge-tools', br'.*\.fixeol$',
795 default=False,
795 default=False,
796 generic=True,
796 generic=True,
797 priority=-1,
797 priority=-1,
798 )
798 )
799 coreconfigitem('merge-tools', br'.*\.gui$',
799 coreconfigitem('merge-tools', br'.*\.gui$',
800 default=False,
800 default=False,
801 generic=True,
801 generic=True,
802 priority=-1,
802 priority=-1,
803 )
803 )
804 coreconfigitem('merge-tools', br'.*\.mergemarkers$',
804 coreconfigitem('merge-tools', br'.*\.mergemarkers$',
805 default='basic',
805 default='basic',
806 generic=True,
806 generic=True,
807 priority=-1,
807 priority=-1,
808 )
808 )
809 coreconfigitem('merge-tools', br'.*\.mergemarkertemplate$',
809 coreconfigitem('merge-tools', br'.*\.mergemarkertemplate$',
810 default=dynamicdefault, # take from ui.mergemarkertemplate
810 default=dynamicdefault, # take from ui.mergemarkertemplate
811 generic=True,
811 generic=True,
812 priority=-1,
812 priority=-1,
813 )
813 )
814 coreconfigitem('merge-tools', br'.*\.priority$',
814 coreconfigitem('merge-tools', br'.*\.priority$',
815 default=0,
815 default=0,
816 generic=True,
816 generic=True,
817 priority=-1,
817 priority=-1,
818 )
818 )
819 coreconfigitem('merge-tools', br'.*\.premerge$',
819 coreconfigitem('merge-tools', br'.*\.premerge$',
820 default=dynamicdefault,
820 default=dynamicdefault,
821 generic=True,
821 generic=True,
822 priority=-1,
822 priority=-1,
823 )
823 )
824 coreconfigitem('merge-tools', br'.*\.symlink$',
824 coreconfigitem('merge-tools', br'.*\.symlink$',
825 default=False,
825 default=False,
826 generic=True,
826 generic=True,
827 priority=-1,
827 priority=-1,
828 )
828 )
829 coreconfigitem('pager', 'attend-.*',
829 coreconfigitem('pager', 'attend-.*',
830 default=dynamicdefault,
830 default=dynamicdefault,
831 generic=True,
831 generic=True,
832 )
832 )
833 coreconfigitem('pager', 'ignore',
833 coreconfigitem('pager', 'ignore',
834 default=list,
834 default=list,
835 )
835 )
836 coreconfigitem('pager', 'pager',
836 coreconfigitem('pager', 'pager',
837 default=dynamicdefault,
837 default=dynamicdefault,
838 )
838 )
839 coreconfigitem('patch', 'eol',
839 coreconfigitem('patch', 'eol',
840 default='strict',
840 default='strict',
841 )
841 )
842 coreconfigitem('patch', 'fuzz',
842 coreconfigitem('patch', 'fuzz',
843 default=2,
843 default=2,
844 )
844 )
845 coreconfigitem('paths', 'default',
845 coreconfigitem('paths', 'default',
846 default=None,
846 default=None,
847 )
847 )
848 coreconfigitem('paths', 'default-push',
848 coreconfigitem('paths', 'default-push',
849 default=None,
849 default=None,
850 )
850 )
851 coreconfigitem('paths', '.*',
851 coreconfigitem('paths', '.*',
852 default=None,
852 default=None,
853 generic=True,
853 generic=True,
854 )
854 )
855 coreconfigitem('phases', 'checksubrepos',
855 coreconfigitem('phases', 'checksubrepos',
856 default='follow',
856 default='follow',
857 )
857 )
858 coreconfigitem('phases', 'new-commit',
858 coreconfigitem('phases', 'new-commit',
859 default='draft',
859 default='draft',
860 )
860 )
861 coreconfigitem('phases', 'publish',
861 coreconfigitem('phases', 'publish',
862 default=True,
862 default=True,
863 )
863 )
864 coreconfigitem('profiling', 'enabled',
864 coreconfigitem('profiling', 'enabled',
865 default=False,
865 default=False,
866 )
866 )
867 coreconfigitem('profiling', 'format',
867 coreconfigitem('profiling', 'format',
868 default='text',
868 default='text',
869 )
869 )
870 coreconfigitem('profiling', 'freq',
870 coreconfigitem('profiling', 'freq',
871 default=1000,
871 default=1000,
872 )
872 )
873 coreconfigitem('profiling', 'limit',
873 coreconfigitem('profiling', 'limit',
874 default=30,
874 default=30,
875 )
875 )
876 coreconfigitem('profiling', 'nested',
876 coreconfigitem('profiling', 'nested',
877 default=0,
877 default=0,
878 )
878 )
879 coreconfigitem('profiling', 'output',
879 coreconfigitem('profiling', 'output',
880 default=None,
880 default=None,
881 )
881 )
882 coreconfigitem('profiling', 'showmax',
882 coreconfigitem('profiling', 'showmax',
883 default=0.999,
883 default=0.999,
884 )
884 )
885 coreconfigitem('profiling', 'showmin',
885 coreconfigitem('profiling', 'showmin',
886 default=dynamicdefault,
886 default=dynamicdefault,
887 )
887 )
888 coreconfigitem('profiling', 'sort',
888 coreconfigitem('profiling', 'sort',
889 default='inlinetime',
889 default='inlinetime',
890 )
890 )
891 coreconfigitem('profiling', 'statformat',
891 coreconfigitem('profiling', 'statformat',
892 default='hotpath',
892 default='hotpath',
893 )
893 )
894 coreconfigitem('profiling', 'time-track',
894 coreconfigitem('profiling', 'time-track',
895 default='cpu',
895 default='cpu',
896 )
896 )
897 coreconfigitem('profiling', 'type',
897 coreconfigitem('profiling', 'type',
898 default='stat',
898 default='stat',
899 )
899 )
900 coreconfigitem('progress', 'assume-tty',
900 coreconfigitem('progress', 'assume-tty',
901 default=False,
901 default=False,
902 )
902 )
903 coreconfigitem('progress', 'changedelay',
903 coreconfigitem('progress', 'changedelay',
904 default=1,
904 default=1,
905 )
905 )
906 coreconfigitem('progress', 'clear-complete',
906 coreconfigitem('progress', 'clear-complete',
907 default=True,
907 default=True,
908 )
908 )
909 coreconfigitem('progress', 'debug',
909 coreconfigitem('progress', 'debug',
910 default=False,
910 default=False,
911 )
911 )
912 coreconfigitem('progress', 'delay',
912 coreconfigitem('progress', 'delay',
913 default=3,
913 default=3,
914 )
914 )
915 coreconfigitem('progress', 'disable',
915 coreconfigitem('progress', 'disable',
916 default=False,
916 default=False,
917 )
917 )
918 coreconfigitem('progress', 'estimateinterval',
918 coreconfigitem('progress', 'estimateinterval',
919 default=60.0,
919 default=60.0,
920 )
920 )
921 coreconfigitem('progress', 'format',
921 coreconfigitem('progress', 'format',
922 default=lambda: ['topic', 'bar', 'number', 'estimate'],
922 default=lambda: ['topic', 'bar', 'number', 'estimate'],
923 )
923 )
924 coreconfigitem('progress', 'refresh',
924 coreconfigitem('progress', 'refresh',
925 default=0.1,
925 default=0.1,
926 )
926 )
927 coreconfigitem('progress', 'width',
927 coreconfigitem('progress', 'width',
928 default=dynamicdefault,
928 default=dynamicdefault,
929 )
929 )
930 coreconfigitem('push', 'pushvars.server',
930 coreconfigitem('push', 'pushvars.server',
931 default=False,
931 default=False,
932 )
932 )
933 coreconfigitem('server', 'bookmarks-pushkey-compat',
933 coreconfigitem('server', 'bookmarks-pushkey-compat',
934 default=True,
934 default=True,
935 )
935 )
936 coreconfigitem('server', 'bundle1',
936 coreconfigitem('server', 'bundle1',
937 default=True,
937 default=True,
938 )
938 )
939 coreconfigitem('server', 'bundle1gd',
939 coreconfigitem('server', 'bundle1gd',
940 default=None,
940 default=None,
941 )
941 )
942 coreconfigitem('server', 'bundle1.pull',
942 coreconfigitem('server', 'bundle1.pull',
943 default=None,
943 default=None,
944 )
944 )
945 coreconfigitem('server', 'bundle1gd.pull',
945 coreconfigitem('server', 'bundle1gd.pull',
946 default=None,
946 default=None,
947 )
947 )
948 coreconfigitem('server', 'bundle1.push',
948 coreconfigitem('server', 'bundle1.push',
949 default=None,
949 default=None,
950 )
950 )
951 coreconfigitem('server', 'bundle1gd.push',
951 coreconfigitem('server', 'bundle1gd.push',
952 default=None,
952 default=None,
953 )
953 )
954 coreconfigitem('server', 'compressionengines',
954 coreconfigitem('server', 'compressionengines',
955 default=list,
955 default=list,
956 )
956 )
957 coreconfigitem('server', 'concurrent-push-mode',
957 coreconfigitem('server', 'concurrent-push-mode',
958 default='strict',
958 default='strict',
959 )
959 )
960 coreconfigitem('server', 'disablefullbundle',
960 coreconfigitem('server', 'disablefullbundle',
961 default=False,
961 default=False,
962 )
962 )
963 coreconfigitem('server', 'maxhttpheaderlen',
963 coreconfigitem('server', 'maxhttpheaderlen',
964 default=1024,
964 default=1024,
965 )
965 )
966 coreconfigitem('server', 'pullbundle',
966 coreconfigitem('server', 'pullbundle',
967 default=False,
967 default=False,
968 )
968 )
969 coreconfigitem('server', 'preferuncompressed',
969 coreconfigitem('server', 'preferuncompressed',
970 default=False,
970 default=False,
971 )
971 )
972 coreconfigitem('server', 'streamunbundle',
972 coreconfigitem('server', 'streamunbundle',
973 default=False,
973 default=False,
974 )
974 )
975 coreconfigitem('server', 'uncompressed',
975 coreconfigitem('server', 'uncompressed',
976 default=True,
976 default=True,
977 )
977 )
978 coreconfigitem('server', 'uncompressedallowsecret',
978 coreconfigitem('server', 'uncompressedallowsecret',
979 default=False,
979 default=False,
980 )
980 )
981 coreconfigitem('server', 'validate',
981 coreconfigitem('server', 'validate',
982 default=False,
982 default=False,
983 )
983 )
984 coreconfigitem('server', 'zliblevel',
984 coreconfigitem('server', 'zliblevel',
985 default=-1,
985 default=-1,
986 )
986 )
987 coreconfigitem('server', 'zstdlevel',
987 coreconfigitem('server', 'zstdlevel',
988 default=3,
988 default=3,
989 )
989 )
990 coreconfigitem('share', 'pool',
990 coreconfigitem('share', 'pool',
991 default=None,
991 default=None,
992 )
992 )
993 coreconfigitem('share', 'poolnaming',
993 coreconfigitem('share', 'poolnaming',
994 default='identity',
994 default='identity',
995 )
995 )
996 coreconfigitem('smtp', 'host',
996 coreconfigitem('smtp', 'host',
997 default=None,
997 default=None,
998 )
998 )
999 coreconfigitem('smtp', 'local_hostname',
999 coreconfigitem('smtp', 'local_hostname',
1000 default=None,
1000 default=None,
1001 )
1001 )
1002 coreconfigitem('smtp', 'password',
1002 coreconfigitem('smtp', 'password',
1003 default=None,
1003 default=None,
1004 )
1004 )
1005 coreconfigitem('smtp', 'port',
1005 coreconfigitem('smtp', 'port',
1006 default=dynamicdefault,
1006 default=dynamicdefault,
1007 )
1007 )
1008 coreconfigitem('smtp', 'tls',
1008 coreconfigitem('smtp', 'tls',
1009 default='none',
1009 default='none',
1010 )
1010 )
1011 coreconfigitem('smtp', 'username',
1011 coreconfigitem('smtp', 'username',
1012 default=None,
1012 default=None,
1013 )
1013 )
1014 coreconfigitem('sparse', 'missingwarning',
1014 coreconfigitem('sparse', 'missingwarning',
1015 default=True,
1015 default=True,
1016 )
1016 )
1017 coreconfigitem('subrepos', 'allowed',
1017 coreconfigitem('subrepos', 'allowed',
1018 default=dynamicdefault, # to make backporting simpler
1018 default=dynamicdefault, # to make backporting simpler
1019 )
1019 )
1020 coreconfigitem('subrepos', 'hg:allowed',
1020 coreconfigitem('subrepos', 'hg:allowed',
1021 default=dynamicdefault,
1021 default=dynamicdefault,
1022 )
1022 )
1023 coreconfigitem('subrepos', 'git:allowed',
1023 coreconfigitem('subrepos', 'git:allowed',
1024 default=dynamicdefault,
1024 default=dynamicdefault,
1025 )
1025 )
1026 coreconfigitem('subrepos', 'svn:allowed',
1026 coreconfigitem('subrepos', 'svn:allowed',
1027 default=dynamicdefault,
1027 default=dynamicdefault,
1028 )
1028 )
1029 coreconfigitem('templates', '.*',
1029 coreconfigitem('templates', '.*',
1030 default=None,
1030 default=None,
1031 generic=True,
1031 generic=True,
1032 )
1032 )
1033 coreconfigitem('trusted', 'groups',
1033 coreconfigitem('trusted', 'groups',
1034 default=list,
1034 default=list,
1035 )
1035 )
1036 coreconfigitem('trusted', 'users',
1036 coreconfigitem('trusted', 'users',
1037 default=list,
1037 default=list,
1038 )
1038 )
1039 coreconfigitem('ui', '_usedassubrepo',
1039 coreconfigitem('ui', '_usedassubrepo',
1040 default=False,
1040 default=False,
1041 )
1041 )
1042 coreconfigitem('ui', 'allowemptycommit',
1042 coreconfigitem('ui', 'allowemptycommit',
1043 default=False,
1043 default=False,
1044 )
1044 )
1045 coreconfigitem('ui', 'archivemeta',
1045 coreconfigitem('ui', 'archivemeta',
1046 default=True,
1046 default=True,
1047 )
1047 )
1048 coreconfigitem('ui', 'askusername',
1048 coreconfigitem('ui', 'askusername',
1049 default=False,
1049 default=False,
1050 )
1050 )
1051 coreconfigitem('ui', 'clonebundlefallback',
1051 coreconfigitem('ui', 'clonebundlefallback',
1052 default=False,
1052 default=False,
1053 )
1053 )
1054 coreconfigitem('ui', 'clonebundleprefers',
1054 coreconfigitem('ui', 'clonebundleprefers',
1055 default=list,
1055 default=list,
1056 )
1056 )
1057 coreconfigitem('ui', 'clonebundles',
1057 coreconfigitem('ui', 'clonebundles',
1058 default=True,
1058 default=True,
1059 )
1059 )
1060 coreconfigitem('ui', 'color',
1060 coreconfigitem('ui', 'color',
1061 default='auto',
1061 default='auto',
1062 )
1062 )
1063 coreconfigitem('ui', 'commitsubrepos',
1063 coreconfigitem('ui', 'commitsubrepos',
1064 default=False,
1064 default=False,
1065 )
1065 )
1066 coreconfigitem('ui', 'debug',
1066 coreconfigitem('ui', 'debug',
1067 default=False,
1067 default=False,
1068 )
1068 )
1069 coreconfigitem('ui', 'debugger',
1069 coreconfigitem('ui', 'debugger',
1070 default=None,
1070 default=None,
1071 )
1071 )
1072 coreconfigitem('ui', 'editor',
1072 coreconfigitem('ui', 'editor',
1073 default=dynamicdefault,
1073 default=dynamicdefault,
1074 )
1074 )
1075 coreconfigitem('ui', 'fallbackencoding',
1075 coreconfigitem('ui', 'fallbackencoding',
1076 default=None,
1076 default=None,
1077 )
1077 )
1078 coreconfigitem('ui', 'forcecwd',
1078 coreconfigitem('ui', 'forcecwd',
1079 default=None,
1079 default=None,
1080 )
1080 )
1081 coreconfigitem('ui', 'forcemerge',
1081 coreconfigitem('ui', 'forcemerge',
1082 default=None,
1082 default=None,
1083 )
1083 )
1084 coreconfigitem('ui', 'formatdebug',
1084 coreconfigitem('ui', 'formatdebug',
1085 default=False,
1085 default=False,
1086 )
1086 )
1087 coreconfigitem('ui', 'formatjson',
1087 coreconfigitem('ui', 'formatjson',
1088 default=False,
1088 default=False,
1089 )
1089 )
1090 coreconfigitem('ui', 'formatted',
1090 coreconfigitem('ui', 'formatted',
1091 default=None,
1091 default=None,
1092 )
1092 )
1093 coreconfigitem('ui', 'graphnodetemplate',
1093 coreconfigitem('ui', 'graphnodetemplate',
1094 default=None,
1094 default=None,
1095 )
1095 )
1096 coreconfigitem('ui', 'history-editing-backup',
1097 default=True,
1098 )
1096 coreconfigitem('ui', 'interactive',
1099 coreconfigitem('ui', 'interactive',
1097 default=None,
1100 default=None,
1098 )
1101 )
1099 coreconfigitem('ui', 'interface',
1102 coreconfigitem('ui', 'interface',
1100 default=None,
1103 default=None,
1101 )
1104 )
1102 coreconfigitem('ui', 'interface.chunkselector',
1105 coreconfigitem('ui', 'interface.chunkselector',
1103 default=None,
1106 default=None,
1104 )
1107 )
1105 coreconfigitem('ui', 'large-file-limit',
1108 coreconfigitem('ui', 'large-file-limit',
1106 default=10000000,
1109 default=10000000,
1107 )
1110 )
1108 coreconfigitem('ui', 'logblockedtimes',
1111 coreconfigitem('ui', 'logblockedtimes',
1109 default=False,
1112 default=False,
1110 )
1113 )
1111 coreconfigitem('ui', 'logtemplate',
1114 coreconfigitem('ui', 'logtemplate',
1112 default=None,
1115 default=None,
1113 )
1116 )
1114 coreconfigitem('ui', 'merge',
1117 coreconfigitem('ui', 'merge',
1115 default=None,
1118 default=None,
1116 )
1119 )
1117 coreconfigitem('ui', 'mergemarkers',
1120 coreconfigitem('ui', 'mergemarkers',
1118 default='basic',
1121 default='basic',
1119 )
1122 )
1120 coreconfigitem('ui', 'mergemarkertemplate',
1123 coreconfigitem('ui', 'mergemarkertemplate',
1121 default=('{node|short} '
1124 default=('{node|short} '
1122 '{ifeq(tags, "tip", "", '
1125 '{ifeq(tags, "tip", "", '
1123 'ifeq(tags, "", "", "{tags} "))}'
1126 'ifeq(tags, "", "", "{tags} "))}'
1124 '{if(bookmarks, "{bookmarks} ")}'
1127 '{if(bookmarks, "{bookmarks} ")}'
1125 '{ifeq(branch, "default", "", "{branch} ")}'
1128 '{ifeq(branch, "default", "", "{branch} ")}'
1126 '- {author|user}: {desc|firstline}')
1129 '- {author|user}: {desc|firstline}')
1127 )
1130 )
1128 coreconfigitem('ui', 'nontty',
1131 coreconfigitem('ui', 'nontty',
1129 default=False,
1132 default=False,
1130 )
1133 )
1131 coreconfigitem('ui', 'origbackuppath',
1134 coreconfigitem('ui', 'origbackuppath',
1132 default=None,
1135 default=None,
1133 )
1136 )
1134 coreconfigitem('ui', 'paginate',
1137 coreconfigitem('ui', 'paginate',
1135 default=True,
1138 default=True,
1136 )
1139 )
1137 coreconfigitem('ui', 'patch',
1140 coreconfigitem('ui', 'patch',
1138 default=None,
1141 default=None,
1139 )
1142 )
1140 coreconfigitem('ui', 'portablefilenames',
1143 coreconfigitem('ui', 'portablefilenames',
1141 default='warn',
1144 default='warn',
1142 )
1145 )
1143 coreconfigitem('ui', 'promptecho',
1146 coreconfigitem('ui', 'promptecho',
1144 default=False,
1147 default=False,
1145 )
1148 )
1146 coreconfigitem('ui', 'quiet',
1149 coreconfigitem('ui', 'quiet',
1147 default=False,
1150 default=False,
1148 )
1151 )
1149 coreconfigitem('ui', 'quietbookmarkmove',
1152 coreconfigitem('ui', 'quietbookmarkmove',
1150 default=False,
1153 default=False,
1151 )
1154 )
1152 coreconfigitem('ui', 'remotecmd',
1155 coreconfigitem('ui', 'remotecmd',
1153 default='hg',
1156 default='hg',
1154 )
1157 )
1155 coreconfigitem('ui', 'report_untrusted',
1158 coreconfigitem('ui', 'report_untrusted',
1156 default=True,
1159 default=True,
1157 )
1160 )
1158 coreconfigitem('ui', 'rollback',
1161 coreconfigitem('ui', 'rollback',
1159 default=True,
1162 default=True,
1160 )
1163 )
1161 coreconfigitem('ui', 'signal-safe-lock',
1164 coreconfigitem('ui', 'signal-safe-lock',
1162 default=True,
1165 default=True,
1163 )
1166 )
1164 coreconfigitem('ui', 'slash',
1167 coreconfigitem('ui', 'slash',
1165 default=False,
1168 default=False,
1166 )
1169 )
1167 coreconfigitem('ui', 'ssh',
1170 coreconfigitem('ui', 'ssh',
1168 default='ssh',
1171 default='ssh',
1169 )
1172 )
1170 coreconfigitem('ui', 'ssherrorhint',
1173 coreconfigitem('ui', 'ssherrorhint',
1171 default=None,
1174 default=None,
1172 )
1175 )
1173 coreconfigitem('ui', 'statuscopies',
1176 coreconfigitem('ui', 'statuscopies',
1174 default=False,
1177 default=False,
1175 )
1178 )
1176 coreconfigitem('ui', 'strict',
1179 coreconfigitem('ui', 'strict',
1177 default=False,
1180 default=False,
1178 )
1181 )
1179 coreconfigitem('ui', 'style',
1182 coreconfigitem('ui', 'style',
1180 default='',
1183 default='',
1181 )
1184 )
1182 coreconfigitem('ui', 'supportcontact',
1185 coreconfigitem('ui', 'supportcontact',
1183 default=None,
1186 default=None,
1184 )
1187 )
1185 coreconfigitem('ui', 'textwidth',
1188 coreconfigitem('ui', 'textwidth',
1186 default=78,
1189 default=78,
1187 )
1190 )
1188 coreconfigitem('ui', 'timeout',
1191 coreconfigitem('ui', 'timeout',
1189 default='600',
1192 default='600',
1190 )
1193 )
1191 coreconfigitem('ui', 'timeout.warn',
1194 coreconfigitem('ui', 'timeout.warn',
1192 default=0,
1195 default=0,
1193 )
1196 )
1194 coreconfigitem('ui', 'traceback',
1197 coreconfigitem('ui', 'traceback',
1195 default=False,
1198 default=False,
1196 )
1199 )
1197 coreconfigitem('ui', 'tweakdefaults',
1200 coreconfigitem('ui', 'tweakdefaults',
1198 default=False,
1201 default=False,
1199 )
1202 )
1200 coreconfigitem('ui', 'username',
1203 coreconfigitem('ui', 'username',
1201 alias=[('ui', 'user')]
1204 alias=[('ui', 'user')]
1202 )
1205 )
1203 coreconfigitem('ui', 'verbose',
1206 coreconfigitem('ui', 'verbose',
1204 default=False,
1207 default=False,
1205 )
1208 )
1206 coreconfigitem('verify', 'skipflags',
1209 coreconfigitem('verify', 'skipflags',
1207 default=None,
1210 default=None,
1208 )
1211 )
1209 coreconfigitem('web', 'allowbz2',
1212 coreconfigitem('web', 'allowbz2',
1210 default=False,
1213 default=False,
1211 )
1214 )
1212 coreconfigitem('web', 'allowgz',
1215 coreconfigitem('web', 'allowgz',
1213 default=False,
1216 default=False,
1214 )
1217 )
1215 coreconfigitem('web', 'allow-pull',
1218 coreconfigitem('web', 'allow-pull',
1216 alias=[('web', 'allowpull')],
1219 alias=[('web', 'allowpull')],
1217 default=True,
1220 default=True,
1218 )
1221 )
1219 coreconfigitem('web', 'allow-push',
1222 coreconfigitem('web', 'allow-push',
1220 alias=[('web', 'allow_push')],
1223 alias=[('web', 'allow_push')],
1221 default=list,
1224 default=list,
1222 )
1225 )
1223 coreconfigitem('web', 'allowzip',
1226 coreconfigitem('web', 'allowzip',
1224 default=False,
1227 default=False,
1225 )
1228 )
1226 coreconfigitem('web', 'archivesubrepos',
1229 coreconfigitem('web', 'archivesubrepos',
1227 default=False,
1230 default=False,
1228 )
1231 )
1229 coreconfigitem('web', 'cache',
1232 coreconfigitem('web', 'cache',
1230 default=True,
1233 default=True,
1231 )
1234 )
1232 coreconfigitem('web', 'contact',
1235 coreconfigitem('web', 'contact',
1233 default=None,
1236 default=None,
1234 )
1237 )
1235 coreconfigitem('web', 'deny_push',
1238 coreconfigitem('web', 'deny_push',
1236 default=list,
1239 default=list,
1237 )
1240 )
1238 coreconfigitem('web', 'guessmime',
1241 coreconfigitem('web', 'guessmime',
1239 default=False,
1242 default=False,
1240 )
1243 )
1241 coreconfigitem('web', 'hidden',
1244 coreconfigitem('web', 'hidden',
1242 default=False,
1245 default=False,
1243 )
1246 )
1244 coreconfigitem('web', 'labels',
1247 coreconfigitem('web', 'labels',
1245 default=list,
1248 default=list,
1246 )
1249 )
1247 coreconfigitem('web', 'logoimg',
1250 coreconfigitem('web', 'logoimg',
1248 default='hglogo.png',
1251 default='hglogo.png',
1249 )
1252 )
1250 coreconfigitem('web', 'logourl',
1253 coreconfigitem('web', 'logourl',
1251 default='https://mercurial-scm.org/',
1254 default='https://mercurial-scm.org/',
1252 )
1255 )
1253 coreconfigitem('web', 'accesslog',
1256 coreconfigitem('web', 'accesslog',
1254 default='-',
1257 default='-',
1255 )
1258 )
1256 coreconfigitem('web', 'address',
1259 coreconfigitem('web', 'address',
1257 default='',
1260 default='',
1258 )
1261 )
1259 coreconfigitem('web', 'allow-archive',
1262 coreconfigitem('web', 'allow-archive',
1260 alias=[('web', 'allow_archive')],
1263 alias=[('web', 'allow_archive')],
1261 default=list,
1264 default=list,
1262 )
1265 )
1263 coreconfigitem('web', 'allow_read',
1266 coreconfigitem('web', 'allow_read',
1264 default=list,
1267 default=list,
1265 )
1268 )
1266 coreconfigitem('web', 'baseurl',
1269 coreconfigitem('web', 'baseurl',
1267 default=None,
1270 default=None,
1268 )
1271 )
1269 coreconfigitem('web', 'cacerts',
1272 coreconfigitem('web', 'cacerts',
1270 default=None,
1273 default=None,
1271 )
1274 )
1272 coreconfigitem('web', 'certificate',
1275 coreconfigitem('web', 'certificate',
1273 default=None,
1276 default=None,
1274 )
1277 )
1275 coreconfigitem('web', 'collapse',
1278 coreconfigitem('web', 'collapse',
1276 default=False,
1279 default=False,
1277 )
1280 )
1278 coreconfigitem('web', 'csp',
1281 coreconfigitem('web', 'csp',
1279 default=None,
1282 default=None,
1280 )
1283 )
1281 coreconfigitem('web', 'deny_read',
1284 coreconfigitem('web', 'deny_read',
1282 default=list,
1285 default=list,
1283 )
1286 )
1284 coreconfigitem('web', 'descend',
1287 coreconfigitem('web', 'descend',
1285 default=True,
1288 default=True,
1286 )
1289 )
1287 coreconfigitem('web', 'description',
1290 coreconfigitem('web', 'description',
1288 default="",
1291 default="",
1289 )
1292 )
1290 coreconfigitem('web', 'encoding',
1293 coreconfigitem('web', 'encoding',
1291 default=lambda: encoding.encoding,
1294 default=lambda: encoding.encoding,
1292 )
1295 )
1293 coreconfigitem('web', 'errorlog',
1296 coreconfigitem('web', 'errorlog',
1294 default='-',
1297 default='-',
1295 )
1298 )
1296 coreconfigitem('web', 'ipv6',
1299 coreconfigitem('web', 'ipv6',
1297 default=False,
1300 default=False,
1298 )
1301 )
1299 coreconfigitem('web', 'maxchanges',
1302 coreconfigitem('web', 'maxchanges',
1300 default=10,
1303 default=10,
1301 )
1304 )
1302 coreconfigitem('web', 'maxfiles',
1305 coreconfigitem('web', 'maxfiles',
1303 default=10,
1306 default=10,
1304 )
1307 )
1305 coreconfigitem('web', 'maxshortchanges',
1308 coreconfigitem('web', 'maxshortchanges',
1306 default=60,
1309 default=60,
1307 )
1310 )
1308 coreconfigitem('web', 'motd',
1311 coreconfigitem('web', 'motd',
1309 default='',
1312 default='',
1310 )
1313 )
1311 coreconfigitem('web', 'name',
1314 coreconfigitem('web', 'name',
1312 default=dynamicdefault,
1315 default=dynamicdefault,
1313 )
1316 )
1314 coreconfigitem('web', 'port',
1317 coreconfigitem('web', 'port',
1315 default=8000,
1318 default=8000,
1316 )
1319 )
1317 coreconfigitem('web', 'prefix',
1320 coreconfigitem('web', 'prefix',
1318 default='',
1321 default='',
1319 )
1322 )
1320 coreconfigitem('web', 'push_ssl',
1323 coreconfigitem('web', 'push_ssl',
1321 default=True,
1324 default=True,
1322 )
1325 )
1323 coreconfigitem('web', 'refreshinterval',
1326 coreconfigitem('web', 'refreshinterval',
1324 default=20,
1327 default=20,
1325 )
1328 )
1326 coreconfigitem('web', 'server-header',
1329 coreconfigitem('web', 'server-header',
1327 default=None,
1330 default=None,
1328 )
1331 )
1329 coreconfigitem('web', 'staticurl',
1332 coreconfigitem('web', 'staticurl',
1330 default=None,
1333 default=None,
1331 )
1334 )
1332 coreconfigitem('web', 'stripes',
1335 coreconfigitem('web', 'stripes',
1333 default=1,
1336 default=1,
1334 )
1337 )
1335 coreconfigitem('web', 'style',
1338 coreconfigitem('web', 'style',
1336 default='paper',
1339 default='paper',
1337 )
1340 )
1338 coreconfigitem('web', 'templates',
1341 coreconfigitem('web', 'templates',
1339 default=None,
1342 default=None,
1340 )
1343 )
1341 coreconfigitem('web', 'view',
1344 coreconfigitem('web', 'view',
1342 default='served',
1345 default='served',
1343 )
1346 )
1344 coreconfigitem('worker', 'backgroundclose',
1347 coreconfigitem('worker', 'backgroundclose',
1345 default=dynamicdefault,
1348 default=dynamicdefault,
1346 )
1349 )
1347 # Windows defaults to a limit of 512 open files. A buffer of 128
1350 # Windows defaults to a limit of 512 open files. A buffer of 128
1348 # should give us enough headway.
1351 # should give us enough headway.
1349 coreconfigitem('worker', 'backgroundclosemaxqueue',
1352 coreconfigitem('worker', 'backgroundclosemaxqueue',
1350 default=384,
1353 default=384,
1351 )
1354 )
1352 coreconfigitem('worker', 'backgroundcloseminfilecount',
1355 coreconfigitem('worker', 'backgroundcloseminfilecount',
1353 default=2048,
1356 default=2048,
1354 )
1357 )
1355 coreconfigitem('worker', 'backgroundclosethreadcount',
1358 coreconfigitem('worker', 'backgroundclosethreadcount',
1356 default=4,
1359 default=4,
1357 )
1360 )
1358 coreconfigitem('worker', 'enabled',
1361 coreconfigitem('worker', 'enabled',
1359 default=True,
1362 default=True,
1360 )
1363 )
1361 coreconfigitem('worker', 'numcpus',
1364 coreconfigitem('worker', 'numcpus',
1362 default=None,
1365 default=None,
1363 )
1366 )
1364
1367
1365 # Rebase related configuration moved to core because other extension are doing
1368 # Rebase related configuration moved to core because other extension are doing
1366 # strange things. For example, shelve import the extensions to reuse some bit
1369 # strange things. For example, shelve import the extensions to reuse some bit
1367 # without formally loading it.
1370 # without formally loading it.
1368 coreconfigitem('commands', 'rebase.requiredest',
1371 coreconfigitem('commands', 'rebase.requiredest',
1369 default=False,
1372 default=False,
1370 )
1373 )
1371 coreconfigitem('experimental', 'rebaseskipobsolete',
1374 coreconfigitem('experimental', 'rebaseskipobsolete',
1372 default=True,
1375 default=True,
1373 )
1376 )
1374 coreconfigitem('rebase', 'singletransaction',
1377 coreconfigitem('rebase', 'singletransaction',
1375 default=False,
1378 default=False,
1376 )
1379 )
1377 coreconfigitem('rebase', 'experimental.inmemory',
1380 coreconfigitem('rebase', 'experimental.inmemory',
1378 default=False,
1381 default=False,
1379 )
1382 )
@@ -1,95 +1,133 b''
1 $ . "$TESTDIR/histedit-helpers.sh"
1 $ . "$TESTDIR/histedit-helpers.sh"
2
2
3 Enable extension used by this test
3 Enable extension used by this test
4 $ cat >>$HGRCPATH <<EOF
4 $ cat >>$HGRCPATH <<EOF
5 > [extensions]
5 > [extensions]
6 > histedit=
6 > histedit=
7 > EOF
7 > EOF
8
8
9 Repo setup:
9 Repo setup:
10 $ hg init foo
10 $ hg init foo
11 $ cd foo
11 $ cd foo
12 $ echo first>file
12 $ echo first>file
13 $ hg ci -qAm one
13 $ hg ci -qAm one
14 $ echo second>>file
14 $ echo second>>file
15 $ hg ci -m two
15 $ hg ci -m two
16 $ echo third>>file
16 $ echo third>>file
17 $ hg ci -m three
17 $ hg ci -m three
18 $ echo forth>>file
18 $ echo forth>>file
19 $ hg ci -m four
19 $ hg ci -m four
20 $ hg log -G --style compact
20 $ hg log -G --style compact
21 @ 3[tip] 7d5187087c79 1970-01-01 00:00 +0000 test
21 @ 3[tip] 7d5187087c79 1970-01-01 00:00 +0000 test
22 | four
22 | four
23 |
23 |
24 o 2 80d23dfa866d 1970-01-01 00:00 +0000 test
24 o 2 80d23dfa866d 1970-01-01 00:00 +0000 test
25 | three
25 | three
26 |
26 |
27 o 1 6153eb23e623 1970-01-01 00:00 +0000 test
27 o 1 6153eb23e623 1970-01-01 00:00 +0000 test
28 | two
28 | two
29 |
29 |
30 o 0 36b4bdd91f5b 1970-01-01 00:00 +0000 test
30 o 0 36b4bdd91f5b 1970-01-01 00:00 +0000 test
31 one
31 one
32
32
33 Check when --no-backup is not passed
33 Check when --no-backup is not passed
34 $ hg histedit -r '36b4bdd91f5b' --commands - << EOF
34 $ hg histedit -r '36b4bdd91f5b' --commands - << EOF
35 > pick 36b4bdd91f5b 0 one
35 > pick 36b4bdd91f5b 0 one
36 > pick 6153eb23e623 1 two
36 > pick 6153eb23e623 1 two
37 > roll 80d23dfa866d 2 three
37 > roll 80d23dfa866d 2 three
38 > edit 7d5187087c79 3 four
38 > edit 7d5187087c79 3 four
39 > EOF
39 > EOF
40 merging file
40 merging file
41 Editing (7d5187087c79), you may commit or record as needed now.
41 Editing (7d5187087c79), you may commit or record as needed now.
42 (hg histedit --continue to resume)
42 (hg histedit --continue to resume)
43 [1]
43 [1]
44
44
45 $ hg histedit --abort
45 $ hg histedit --abort
46 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
46 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
47 saved backup bundle to $TESTTMP/foo/.hg/strip-backup/1d8f701c7b35-cf7be322-backup.hg
47 saved backup bundle to $TESTTMP/foo/.hg/strip-backup/1d8f701c7b35-cf7be322-backup.hg
48 saved backup bundle to $TESTTMP/foo/.hg/strip-backup/5c0056670bce-b54b65d0-backup.hg
48 saved backup bundle to $TESTTMP/foo/.hg/strip-backup/5c0056670bce-b54b65d0-backup.hg
49
49
50 $ hg st
50 $ hg st
51 $ hg diff
51 $ hg diff
52 $ hg log -G --style compact
52 $ hg log -G --style compact
53 @ 3[tip] 7d5187087c79 1970-01-01 00:00 +0000 test
53 @ 3[tip] 7d5187087c79 1970-01-01 00:00 +0000 test
54 | four
54 | four
55 |
55 |
56 o 2 80d23dfa866d 1970-01-01 00:00 +0000 test
56 o 2 80d23dfa866d 1970-01-01 00:00 +0000 test
57 | three
57 | three
58 |
58 |
59 o 1 6153eb23e623 1970-01-01 00:00 +0000 test
59 o 1 6153eb23e623 1970-01-01 00:00 +0000 test
60 | two
60 | two
61 |
61 |
62 o 0 36b4bdd91f5b 1970-01-01 00:00 +0000 test
62 o 0 36b4bdd91f5b 1970-01-01 00:00 +0000 test
63 one
63 one
64
64
65
65
66 Check when --no-backup is passed
66 Check when --no-backup is passed
67 $ hg histedit -r '36b4bdd91f5b' --commands - << EOF
67 $ hg histedit -r '36b4bdd91f5b' --commands - << EOF
68 > pick 36b4bdd91f5b 0 one
68 > pick 36b4bdd91f5b 0 one
69 > pick 6153eb23e623 1 two
69 > pick 6153eb23e623 1 two
70 > roll 80d23dfa866d 2 three
70 > roll 80d23dfa866d 2 three
71 > edit 7d5187087c79 3 four
71 > edit 7d5187087c79 3 four
72 > EOF
72 > EOF
73 merging file
73 merging file
74 Editing (7d5187087c79), you may commit or record as needed now.
74 Editing (7d5187087c79), you may commit or record as needed now.
75 (hg histedit --continue to resume)
75 (hg histedit --continue to resume)
76 [1]
76 [1]
77
77
78 $ hg histedit --abort --no-backup
78 $ hg histedit --abort --no-backup
79 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
79 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
80
80
81 $ hg st
81 $ hg st
82 $ hg diff
82 $ hg diff
83 $ hg log -G --style compact
83 $ hg log -G --style compact
84 @ 3[tip] 7d5187087c79 1970-01-01 00:00 +0000 test
84 @ 3[tip] 7d5187087c79 1970-01-01 00:00 +0000 test
85 | four
85 | four
86 |
86 |
87 o 2 80d23dfa866d 1970-01-01 00:00 +0000 test
87 o 2 80d23dfa866d 1970-01-01 00:00 +0000 test
88 | three
88 | three
89 |
89 |
90 o 1 6153eb23e623 1970-01-01 00:00 +0000 test
90 o 1 6153eb23e623 1970-01-01 00:00 +0000 test
91 | two
91 | two
92 |
92 |
93 o 0 36b4bdd91f5b 1970-01-01 00:00 +0000 test
93 o 0 36b4bdd91f5b 1970-01-01 00:00 +0000 test
94 one
94 one
95
95
96 ==========================================
97 Test history-editing-backup config option|
98 ==========================================
99 Test when `history-editing-backup` config option is enabled:
100 $ hg histedit -r '36b4bdd91f5b' --commands - << EOF
101 > pick 36b4bdd91f5b 0 one
102 > pick 6153eb23e623 1 two
103 > roll 80d23dfa866d 2 three
104 > edit 7d5187087c79 3 four
105 > EOF
106 merging file
107 Editing (7d5187087c79), you may commit or record as needed now.
108 (hg histedit --continue to resume)
109 [1]
110 $ hg histedit --abort
111 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
112 saved backup bundle to $TESTTMP/foo/.hg/strip-backup/1d8f701c7b35-cf7be322-backup.hg
113 saved backup bundle to $TESTTMP/foo/.hg/strip-backup/5c0056670bce-b54b65d0-backup.hg
114
115 Test when `history-editing-backup` config option is not enabled
116 Enable config option:
117 $ cat >>$HGRCPATH <<EOF
118 > [ui]
119 > history-editing-backup=False
120 > EOF
121
122 $ hg histedit -r '36b4bdd91f5b' --commands - << EOF
123 > pick 36b4bdd91f5b 0 one
124 > pick 6153eb23e623 1 two
125 > roll 80d23dfa866d 2 three
126 > edit 7d5187087c79 3 four
127 > EOF
128 merging file
129 Editing (7d5187087c79), you may commit or record as needed now.
130 (hg histedit --continue to resume)
131 [1]
132 $ hg histedit --abort
133 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
General Comments 0
You need to be logged in to leave comments. Login now