##// END OF EJS Templates
node: make bin() be a wrapper instead of just an alias...
Augie Fackler -
r36256:f574cc00 default
parent child Browse files
Show More
@@ -1,1651 +1,1650 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 binascii
187 import errno
186 import errno
188 import os
187 import os
189
188
190 from mercurial.i18n import _
189 from mercurial.i18n import _
191 from mercurial import (
190 from mercurial import (
192 bundle2,
191 bundle2,
193 cmdutil,
192 cmdutil,
194 context,
193 context,
195 copies,
194 copies,
196 destutil,
195 destutil,
197 discovery,
196 discovery,
198 error,
197 error,
199 exchange,
198 exchange,
200 extensions,
199 extensions,
201 hg,
200 hg,
202 lock,
201 lock,
203 merge as mergemod,
202 merge as mergemod,
204 mergeutil,
203 mergeutil,
205 node,
204 node,
206 obsolete,
205 obsolete,
207 pycompat,
206 pycompat,
208 registrar,
207 registrar,
209 repair,
208 repair,
210 scmutil,
209 scmutil,
211 util,
210 util,
212 )
211 )
213
212
214 pickle = util.pickle
213 pickle = util.pickle
215 release = lock.release
214 release = lock.release
216 cmdtable = {}
215 cmdtable = {}
217 command = registrar.command(cmdtable)
216 command = registrar.command(cmdtable)
218
217
219 configtable = {}
218 configtable = {}
220 configitem = registrar.configitem(configtable)
219 configitem = registrar.configitem(configtable)
221 configitem('experimental', 'histedit.autoverb',
220 configitem('experimental', 'histedit.autoverb',
222 default=False,
221 default=False,
223 )
222 )
224 configitem('histedit', 'defaultrev',
223 configitem('histedit', 'defaultrev',
225 default=configitem.dynamicdefault,
224 default=configitem.dynamicdefault,
226 )
225 )
227 configitem('histedit', 'dropmissing',
226 configitem('histedit', 'dropmissing',
228 default=False,
227 default=False,
229 )
228 )
230 configitem('histedit', 'linelen',
229 configitem('histedit', 'linelen',
231 default=80,
230 default=80,
232 )
231 )
233 configitem('histedit', 'singletransaction',
232 configitem('histedit', 'singletransaction',
234 default=False,
233 default=False,
235 )
234 )
236
235
237 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
236 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
238 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
237 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
239 # be specifying the version(s) of Mercurial they are tested with, or
238 # be specifying the version(s) of Mercurial they are tested with, or
240 # leave the attribute unspecified.
239 # leave the attribute unspecified.
241 testedwith = 'ships-with-hg-core'
240 testedwith = 'ships-with-hg-core'
242
241
243 actiontable = {}
242 actiontable = {}
244 primaryactions = set()
243 primaryactions = set()
245 secondaryactions = set()
244 secondaryactions = set()
246 tertiaryactions = set()
245 tertiaryactions = set()
247 internalactions = set()
246 internalactions = set()
248
247
249 def geteditcomment(ui, first, last):
248 def geteditcomment(ui, first, last):
250 """ construct the editor comment
249 """ construct the editor comment
251 The comment includes::
250 The comment includes::
252 - an intro
251 - an intro
253 - sorted primary commands
252 - sorted primary commands
254 - sorted short commands
253 - sorted short commands
255 - sorted long commands
254 - sorted long commands
256 - additional hints
255 - additional hints
257
256
258 Commands are only included once.
257 Commands are only included once.
259 """
258 """
260 intro = _("""Edit history between %s and %s
259 intro = _("""Edit history between %s and %s
261
260
262 Commits are listed from least to most recent
261 Commits are listed from least to most recent
263
262
264 You can reorder changesets by reordering the lines
263 You can reorder changesets by reordering the lines
265
264
266 Commands:
265 Commands:
267 """)
266 """)
268 actions = []
267 actions = []
269 def addverb(v):
268 def addverb(v):
270 a = actiontable[v]
269 a = actiontable[v]
271 lines = a.message.split("\n")
270 lines = a.message.split("\n")
272 if len(a.verbs):
271 if len(a.verbs):
273 v = ', '.join(sorted(a.verbs, key=lambda v: len(v)))
272 v = ', '.join(sorted(a.verbs, key=lambda v: len(v)))
274 actions.append(" %s = %s" % (v, lines[0]))
273 actions.append(" %s = %s" % (v, lines[0]))
275 actions.extend([' %s' for l in lines[1:]])
274 actions.extend([' %s' for l in lines[1:]])
276
275
277 for v in (
276 for v in (
278 sorted(primaryactions) +
277 sorted(primaryactions) +
279 sorted(secondaryactions) +
278 sorted(secondaryactions) +
280 sorted(tertiaryactions)
279 sorted(tertiaryactions)
281 ):
280 ):
282 addverb(v)
281 addverb(v)
283 actions.append('')
282 actions.append('')
284
283
285 hints = []
284 hints = []
286 if ui.configbool('histedit', 'dropmissing'):
285 if ui.configbool('histedit', 'dropmissing'):
287 hints.append("Deleting a changeset from the list "
286 hints.append("Deleting a changeset from the list "
288 "will DISCARD it from the edited history!")
287 "will DISCARD it from the edited history!")
289
288
290 lines = (intro % (first, last)).split('\n') + actions + hints
289 lines = (intro % (first, last)).split('\n') + actions + hints
291
290
292 return ''.join(['# %s\n' % l if l else '#\n' for l in lines])
291 return ''.join(['# %s\n' % l if l else '#\n' for l in lines])
293
292
294 class histeditstate(object):
293 class histeditstate(object):
295 def __init__(self, repo, parentctxnode=None, actions=None, keep=None,
294 def __init__(self, repo, parentctxnode=None, actions=None, keep=None,
296 topmost=None, replacements=None, lock=None, wlock=None):
295 topmost=None, replacements=None, lock=None, wlock=None):
297 self.repo = repo
296 self.repo = repo
298 self.actions = actions
297 self.actions = actions
299 self.keep = keep
298 self.keep = keep
300 self.topmost = topmost
299 self.topmost = topmost
301 self.parentctxnode = parentctxnode
300 self.parentctxnode = parentctxnode
302 self.lock = lock
301 self.lock = lock
303 self.wlock = wlock
302 self.wlock = wlock
304 self.backupfile = None
303 self.backupfile = None
305 if replacements is None:
304 if replacements is None:
306 self.replacements = []
305 self.replacements = []
307 else:
306 else:
308 self.replacements = replacements
307 self.replacements = replacements
309
308
310 def read(self):
309 def read(self):
311 """Load histedit state from disk and set fields appropriately."""
310 """Load histedit state from disk and set fields appropriately."""
312 try:
311 try:
313 state = self.repo.vfs.read('histedit-state')
312 state = self.repo.vfs.read('histedit-state')
314 except IOError as err:
313 except IOError as err:
315 if err.errno != errno.ENOENT:
314 if err.errno != errno.ENOENT:
316 raise
315 raise
317 cmdutil.wrongtooltocontinue(self.repo, _('histedit'))
316 cmdutil.wrongtooltocontinue(self.repo, _('histedit'))
318
317
319 if state.startswith('v1\n'):
318 if state.startswith('v1\n'):
320 data = self._load()
319 data = self._load()
321 parentctxnode, rules, keep, topmost, replacements, backupfile = data
320 parentctxnode, rules, keep, topmost, replacements, backupfile = data
322 else:
321 else:
323 data = pickle.loads(state)
322 data = pickle.loads(state)
324 parentctxnode, rules, keep, topmost, replacements = data
323 parentctxnode, rules, keep, topmost, replacements = data
325 backupfile = None
324 backupfile = None
326
325
327 self.parentctxnode = parentctxnode
326 self.parentctxnode = parentctxnode
328 rules = "\n".join(["%s %s" % (verb, rest) for [verb, rest] in rules])
327 rules = "\n".join(["%s %s" % (verb, rest) for [verb, rest] in rules])
329 actions = parserules(rules, self)
328 actions = parserules(rules, self)
330 self.actions = actions
329 self.actions = actions
331 self.keep = keep
330 self.keep = keep
332 self.topmost = topmost
331 self.topmost = topmost
333 self.replacements = replacements
332 self.replacements = replacements
334 self.backupfile = backupfile
333 self.backupfile = backupfile
335
334
336 def write(self, tr=None):
335 def write(self, tr=None):
337 if tr:
336 if tr:
338 tr.addfilegenerator('histedit-state', ('histedit-state',),
337 tr.addfilegenerator('histedit-state', ('histedit-state',),
339 self._write, location='plain')
338 self._write, location='plain')
340 else:
339 else:
341 with self.repo.vfs("histedit-state", "w") as f:
340 with self.repo.vfs("histedit-state", "w") as f:
342 self._write(f)
341 self._write(f)
343
342
344 def _write(self, fp):
343 def _write(self, fp):
345 fp.write('v1\n')
344 fp.write('v1\n')
346 fp.write('%s\n' % node.hex(self.parentctxnode))
345 fp.write('%s\n' % node.hex(self.parentctxnode))
347 fp.write('%s\n' % node.hex(self.topmost))
346 fp.write('%s\n' % node.hex(self.topmost))
348 fp.write('%s\n' % ('True' if self.keep else 'False'))
347 fp.write('%s\n' % ('True' if self.keep else 'False'))
349 fp.write('%d\n' % len(self.actions))
348 fp.write('%d\n' % len(self.actions))
350 for action in self.actions:
349 for action in self.actions:
351 fp.write('%s\n' % action.tostate())
350 fp.write('%s\n' % action.tostate())
352 fp.write('%d\n' % len(self.replacements))
351 fp.write('%d\n' % len(self.replacements))
353 for replacement in self.replacements:
352 for replacement in self.replacements:
354 fp.write('%s%s\n' % (node.hex(replacement[0]), ''.join(node.hex(r)
353 fp.write('%s%s\n' % (node.hex(replacement[0]), ''.join(node.hex(r)
355 for r in replacement[1])))
354 for r in replacement[1])))
356 backupfile = self.backupfile
355 backupfile = self.backupfile
357 if not backupfile:
356 if not backupfile:
358 backupfile = ''
357 backupfile = ''
359 fp.write('%s\n' % backupfile)
358 fp.write('%s\n' % backupfile)
360
359
361 def _load(self):
360 def _load(self):
362 fp = self.repo.vfs('histedit-state', 'r')
361 fp = self.repo.vfs('histedit-state', 'r')
363 lines = [l[:-1] for l in fp.readlines()]
362 lines = [l[:-1] for l in fp.readlines()]
364
363
365 index = 0
364 index = 0
366 lines[index] # version number
365 lines[index] # version number
367 index += 1
366 index += 1
368
367
369 parentctxnode = node.bin(lines[index])
368 parentctxnode = node.bin(lines[index])
370 index += 1
369 index += 1
371
370
372 topmost = node.bin(lines[index])
371 topmost = node.bin(lines[index])
373 index += 1
372 index += 1
374
373
375 keep = lines[index] == 'True'
374 keep = lines[index] == 'True'
376 index += 1
375 index += 1
377
376
378 # Rules
377 # Rules
379 rules = []
378 rules = []
380 rulelen = int(lines[index])
379 rulelen = int(lines[index])
381 index += 1
380 index += 1
382 for i in xrange(rulelen):
381 for i in xrange(rulelen):
383 ruleaction = lines[index]
382 ruleaction = lines[index]
384 index += 1
383 index += 1
385 rule = lines[index]
384 rule = lines[index]
386 index += 1
385 index += 1
387 rules.append((ruleaction, rule))
386 rules.append((ruleaction, rule))
388
387
389 # Replacements
388 # Replacements
390 replacements = []
389 replacements = []
391 replacementlen = int(lines[index])
390 replacementlen = int(lines[index])
392 index += 1
391 index += 1
393 for i in xrange(replacementlen):
392 for i in xrange(replacementlen):
394 replacement = lines[index]
393 replacement = lines[index]
395 original = node.bin(replacement[:40])
394 original = node.bin(replacement[:40])
396 succ = [node.bin(replacement[i:i + 40]) for i in
395 succ = [node.bin(replacement[i:i + 40]) for i in
397 range(40, len(replacement), 40)]
396 range(40, len(replacement), 40)]
398 replacements.append((original, succ))
397 replacements.append((original, succ))
399 index += 1
398 index += 1
400
399
401 backupfile = lines[index]
400 backupfile = lines[index]
402 index += 1
401 index += 1
403
402
404 fp.close()
403 fp.close()
405
404
406 return parentctxnode, rules, keep, topmost, replacements, backupfile
405 return parentctxnode, rules, keep, topmost, replacements, backupfile
407
406
408 def clear(self):
407 def clear(self):
409 if self.inprogress():
408 if self.inprogress():
410 self.repo.vfs.unlink('histedit-state')
409 self.repo.vfs.unlink('histedit-state')
411
410
412 def inprogress(self):
411 def inprogress(self):
413 return self.repo.vfs.exists('histedit-state')
412 return self.repo.vfs.exists('histedit-state')
414
413
415
414
416 class histeditaction(object):
415 class histeditaction(object):
417 def __init__(self, state, node):
416 def __init__(self, state, node):
418 self.state = state
417 self.state = state
419 self.repo = state.repo
418 self.repo = state.repo
420 self.node = node
419 self.node = node
421
420
422 @classmethod
421 @classmethod
423 def fromrule(cls, state, rule):
422 def fromrule(cls, state, rule):
424 """Parses the given rule, returning an instance of the histeditaction.
423 """Parses the given rule, returning an instance of the histeditaction.
425 """
424 """
426 rulehash = rule.strip().split(' ', 1)[0]
425 rulehash = rule.strip().split(' ', 1)[0]
427 try:
426 try:
428 rev = node.bin(rulehash)
427 rev = node.bin(rulehash)
429 except (TypeError, binascii.Error):
428 except TypeError:
430 raise error.ParseError("invalid changeset %s" % rulehash)
429 raise error.ParseError("invalid changeset %s" % rulehash)
431 return cls(state, rev)
430 return cls(state, rev)
432
431
433 def verify(self, prev, expected, seen):
432 def verify(self, prev, expected, seen):
434 """ Verifies semantic correctness of the rule"""
433 """ Verifies semantic correctness of the rule"""
435 repo = self.repo
434 repo = self.repo
436 ha = node.hex(self.node)
435 ha = node.hex(self.node)
437 try:
436 try:
438 self.node = repo[ha].node()
437 self.node = repo[ha].node()
439 except error.RepoError:
438 except error.RepoError:
440 raise error.ParseError(_('unknown changeset %s listed')
439 raise error.ParseError(_('unknown changeset %s listed')
441 % ha[:12])
440 % ha[:12])
442 if self.node is not None:
441 if self.node is not None:
443 self._verifynodeconstraints(prev, expected, seen)
442 self._verifynodeconstraints(prev, expected, seen)
444
443
445 def _verifynodeconstraints(self, prev, expected, seen):
444 def _verifynodeconstraints(self, prev, expected, seen):
446 # by default command need a node in the edited list
445 # by default command need a node in the edited list
447 if self.node not in expected:
446 if self.node not in expected:
448 raise error.ParseError(_('%s "%s" changeset was not a candidate')
447 raise error.ParseError(_('%s "%s" changeset was not a candidate')
449 % (self.verb, node.short(self.node)),
448 % (self.verb, node.short(self.node)),
450 hint=_('only use listed changesets'))
449 hint=_('only use listed changesets'))
451 # and only one command per node
450 # and only one command per node
452 if self.node in seen:
451 if self.node in seen:
453 raise error.ParseError(_('duplicated command for changeset %s') %
452 raise error.ParseError(_('duplicated command for changeset %s') %
454 node.short(self.node))
453 node.short(self.node))
455
454
456 def torule(self):
455 def torule(self):
457 """build a histedit rule line for an action
456 """build a histedit rule line for an action
458
457
459 by default lines are in the form:
458 by default lines are in the form:
460 <hash> <rev> <summary>
459 <hash> <rev> <summary>
461 """
460 """
462 ctx = self.repo[self.node]
461 ctx = self.repo[self.node]
463 summary = _getsummary(ctx)
462 summary = _getsummary(ctx)
464 line = '%s %s %d %s' % (self.verb, ctx, ctx.rev(), summary)
463 line = '%s %s %d %s' % (self.verb, ctx, ctx.rev(), summary)
465 # trim to 75 columns by default so it's not stupidly wide in my editor
464 # trim to 75 columns by default so it's not stupidly wide in my editor
466 # (the 5 more are left for verb)
465 # (the 5 more are left for verb)
467 maxlen = self.repo.ui.configint('histedit', 'linelen')
466 maxlen = self.repo.ui.configint('histedit', 'linelen')
468 maxlen = max(maxlen, 22) # avoid truncating hash
467 maxlen = max(maxlen, 22) # avoid truncating hash
469 return util.ellipsis(line, maxlen)
468 return util.ellipsis(line, maxlen)
470
469
471 def tostate(self):
470 def tostate(self):
472 """Print an action in format used by histedit state files
471 """Print an action in format used by histedit state files
473 (the first line is a verb, the remainder is the second)
472 (the first line is a verb, the remainder is the second)
474 """
473 """
475 return "%s\n%s" % (self.verb, node.hex(self.node))
474 return "%s\n%s" % (self.verb, node.hex(self.node))
476
475
477 def run(self):
476 def run(self):
478 """Runs the action. The default behavior is simply apply the action's
477 """Runs the action. The default behavior is simply apply the action's
479 rulectx onto the current parentctx."""
478 rulectx onto the current parentctx."""
480 self.applychange()
479 self.applychange()
481 self.continuedirty()
480 self.continuedirty()
482 return self.continueclean()
481 return self.continueclean()
483
482
484 def applychange(self):
483 def applychange(self):
485 """Applies the changes from this action's rulectx onto the current
484 """Applies the changes from this action's rulectx onto the current
486 parentctx, but does not commit them."""
485 parentctx, but does not commit them."""
487 repo = self.repo
486 repo = self.repo
488 rulectx = repo[self.node]
487 rulectx = repo[self.node]
489 repo.ui.pushbuffer(error=True, labeled=True)
488 repo.ui.pushbuffer(error=True, labeled=True)
490 hg.update(repo, self.state.parentctxnode, quietempty=True)
489 hg.update(repo, self.state.parentctxnode, quietempty=True)
491 stats = applychanges(repo.ui, repo, rulectx, {})
490 stats = applychanges(repo.ui, repo, rulectx, {})
492 repo.dirstate.setbranch(rulectx.branch())
491 repo.dirstate.setbranch(rulectx.branch())
493 if stats and stats[3] > 0:
492 if stats and stats[3] > 0:
494 buf = repo.ui.popbuffer()
493 buf = repo.ui.popbuffer()
495 repo.ui.write(buf)
494 repo.ui.write(buf)
496 raise error.InterventionRequired(
495 raise error.InterventionRequired(
497 _('Fix up the change (%s %s)') %
496 _('Fix up the change (%s %s)') %
498 (self.verb, node.short(self.node)),
497 (self.verb, node.short(self.node)),
499 hint=_('hg histedit --continue to resume'))
498 hint=_('hg histedit --continue to resume'))
500 else:
499 else:
501 repo.ui.popbuffer()
500 repo.ui.popbuffer()
502
501
503 def continuedirty(self):
502 def continuedirty(self):
504 """Continues the action when changes have been applied to the working
503 """Continues the action when changes have been applied to the working
505 copy. The default behavior is to commit the dirty changes."""
504 copy. The default behavior is to commit the dirty changes."""
506 repo = self.repo
505 repo = self.repo
507 rulectx = repo[self.node]
506 rulectx = repo[self.node]
508
507
509 editor = self.commiteditor()
508 editor = self.commiteditor()
510 commit = commitfuncfor(repo, rulectx)
509 commit = commitfuncfor(repo, rulectx)
511
510
512 commit(text=rulectx.description(), user=rulectx.user(),
511 commit(text=rulectx.description(), user=rulectx.user(),
513 date=rulectx.date(), extra=rulectx.extra(), editor=editor)
512 date=rulectx.date(), extra=rulectx.extra(), editor=editor)
514
513
515 def commiteditor(self):
514 def commiteditor(self):
516 """The editor to be used to edit the commit message."""
515 """The editor to be used to edit the commit message."""
517 return False
516 return False
518
517
519 def continueclean(self):
518 def continueclean(self):
520 """Continues the action when the working copy is clean. The default
519 """Continues the action when the working copy is clean. The default
521 behavior is to accept the current commit as the new version of the
520 behavior is to accept the current commit as the new version of the
522 rulectx."""
521 rulectx."""
523 ctx = self.repo['.']
522 ctx = self.repo['.']
524 if ctx.node() == self.state.parentctxnode:
523 if ctx.node() == self.state.parentctxnode:
525 self.repo.ui.warn(_('%s: skipping changeset (no changes)\n') %
524 self.repo.ui.warn(_('%s: skipping changeset (no changes)\n') %
526 node.short(self.node))
525 node.short(self.node))
527 return ctx, [(self.node, tuple())]
526 return ctx, [(self.node, tuple())]
528 if ctx.node() == self.node:
527 if ctx.node() == self.node:
529 # Nothing changed
528 # Nothing changed
530 return ctx, []
529 return ctx, []
531 return ctx, [(self.node, (ctx.node(),))]
530 return ctx, [(self.node, (ctx.node(),))]
532
531
533 def commitfuncfor(repo, src):
532 def commitfuncfor(repo, src):
534 """Build a commit function for the replacement of <src>
533 """Build a commit function for the replacement of <src>
535
534
536 This function ensure we apply the same treatment to all changesets.
535 This function ensure we apply the same treatment to all changesets.
537
536
538 - Add a 'histedit_source' entry in extra.
537 - Add a 'histedit_source' entry in extra.
539
538
540 Note that fold has its own separated logic because its handling is a bit
539 Note that fold has its own separated logic because its handling is a bit
541 different and not easily factored out of the fold method.
540 different and not easily factored out of the fold method.
542 """
541 """
543 phasemin = src.phase()
542 phasemin = src.phase()
544 def commitfunc(**kwargs):
543 def commitfunc(**kwargs):
545 overrides = {('phases', 'new-commit'): phasemin}
544 overrides = {('phases', 'new-commit'): phasemin}
546 with repo.ui.configoverride(overrides, 'histedit'):
545 with repo.ui.configoverride(overrides, 'histedit'):
547 extra = kwargs.get(r'extra', {}).copy()
546 extra = kwargs.get(r'extra', {}).copy()
548 extra['histedit_source'] = src.hex()
547 extra['histedit_source'] = src.hex()
549 kwargs[r'extra'] = extra
548 kwargs[r'extra'] = extra
550 return repo.commit(**kwargs)
549 return repo.commit(**kwargs)
551 return commitfunc
550 return commitfunc
552
551
553 def applychanges(ui, repo, ctx, opts):
552 def applychanges(ui, repo, ctx, opts):
554 """Merge changeset from ctx (only) in the current working directory"""
553 """Merge changeset from ctx (only) in the current working directory"""
555 wcpar = repo.dirstate.parents()[0]
554 wcpar = repo.dirstate.parents()[0]
556 if ctx.p1().node() == wcpar:
555 if ctx.p1().node() == wcpar:
557 # edits are "in place" we do not need to make any merge,
556 # edits are "in place" we do not need to make any merge,
558 # just applies changes on parent for editing
557 # just applies changes on parent for editing
559 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
558 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
560 stats = None
559 stats = None
561 else:
560 else:
562 try:
561 try:
563 # ui.forcemerge is an internal variable, do not document
562 # ui.forcemerge is an internal variable, do not document
564 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
563 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
565 'histedit')
564 'histedit')
566 stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'histedit'])
565 stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'histedit'])
567 finally:
566 finally:
568 repo.ui.setconfig('ui', 'forcemerge', '', 'histedit')
567 repo.ui.setconfig('ui', 'forcemerge', '', 'histedit')
569 return stats
568 return stats
570
569
571 def collapse(repo, first, last, commitopts, skipprompt=False):
570 def collapse(repo, first, last, commitopts, skipprompt=False):
572 """collapse the set of revisions from first to last as new one.
571 """collapse the set of revisions from first to last as new one.
573
572
574 Expected commit options are:
573 Expected commit options are:
575 - message
574 - message
576 - date
575 - date
577 - username
576 - username
578 Commit message is edited in all cases.
577 Commit message is edited in all cases.
579
578
580 This function works in memory."""
579 This function works in memory."""
581 ctxs = list(repo.set('%d::%d', first, last))
580 ctxs = list(repo.set('%d::%d', first, last))
582 if not ctxs:
581 if not ctxs:
583 return None
582 return None
584 for c in ctxs:
583 for c in ctxs:
585 if not c.mutable():
584 if not c.mutable():
586 raise error.ParseError(
585 raise error.ParseError(
587 _("cannot fold into public change %s") % node.short(c.node()))
586 _("cannot fold into public change %s") % node.short(c.node()))
588 base = first.parents()[0]
587 base = first.parents()[0]
589
588
590 # commit a new version of the old changeset, including the update
589 # commit a new version of the old changeset, including the update
591 # collect all files which might be affected
590 # collect all files which might be affected
592 files = set()
591 files = set()
593 for ctx in ctxs:
592 for ctx in ctxs:
594 files.update(ctx.files())
593 files.update(ctx.files())
595
594
596 # Recompute copies (avoid recording a -> b -> a)
595 # Recompute copies (avoid recording a -> b -> a)
597 copied = copies.pathcopies(base, last)
596 copied = copies.pathcopies(base, last)
598
597
599 # prune files which were reverted by the updates
598 # prune files which were reverted by the updates
600 files = [f for f in files if not cmdutil.samefile(f, last, base)]
599 files = [f for f in files if not cmdutil.samefile(f, last, base)]
601 # commit version of these files as defined by head
600 # commit version of these files as defined by head
602 headmf = last.manifest()
601 headmf = last.manifest()
603 def filectxfn(repo, ctx, path):
602 def filectxfn(repo, ctx, path):
604 if path in headmf:
603 if path in headmf:
605 fctx = last[path]
604 fctx = last[path]
606 flags = fctx.flags()
605 flags = fctx.flags()
607 mctx = context.memfilectx(repo, ctx,
606 mctx = context.memfilectx(repo, ctx,
608 fctx.path(), fctx.data(),
607 fctx.path(), fctx.data(),
609 islink='l' in flags,
608 islink='l' in flags,
610 isexec='x' in flags,
609 isexec='x' in flags,
611 copied=copied.get(path))
610 copied=copied.get(path))
612 return mctx
611 return mctx
613 return None
612 return None
614
613
615 if commitopts.get('message'):
614 if commitopts.get('message'):
616 message = commitopts['message']
615 message = commitopts['message']
617 else:
616 else:
618 message = first.description()
617 message = first.description()
619 user = commitopts.get('user')
618 user = commitopts.get('user')
620 date = commitopts.get('date')
619 date = commitopts.get('date')
621 extra = commitopts.get('extra')
620 extra = commitopts.get('extra')
622
621
623 parents = (first.p1().node(), first.p2().node())
622 parents = (first.p1().node(), first.p2().node())
624 editor = None
623 editor = None
625 if not skipprompt:
624 if not skipprompt:
626 editor = cmdutil.getcommiteditor(edit=True, editform='histedit.fold')
625 editor = cmdutil.getcommiteditor(edit=True, editform='histedit.fold')
627 new = context.memctx(repo,
626 new = context.memctx(repo,
628 parents=parents,
627 parents=parents,
629 text=message,
628 text=message,
630 files=files,
629 files=files,
631 filectxfn=filectxfn,
630 filectxfn=filectxfn,
632 user=user,
631 user=user,
633 date=date,
632 date=date,
634 extra=extra,
633 extra=extra,
635 editor=editor)
634 editor=editor)
636 return repo.commitctx(new)
635 return repo.commitctx(new)
637
636
638 def _isdirtywc(repo):
637 def _isdirtywc(repo):
639 return repo[None].dirty(missing=True)
638 return repo[None].dirty(missing=True)
640
639
641 def abortdirty():
640 def abortdirty():
642 raise error.Abort(_('working copy has pending changes'),
641 raise error.Abort(_('working copy has pending changes'),
643 hint=_('amend, commit, or revert them and run histedit '
642 hint=_('amend, commit, or revert them and run histedit '
644 '--continue, or abort with histedit --abort'))
643 '--continue, or abort with histedit --abort'))
645
644
646 def action(verbs, message, priority=False, internal=False):
645 def action(verbs, message, priority=False, internal=False):
647 def wrap(cls):
646 def wrap(cls):
648 assert not priority or not internal
647 assert not priority or not internal
649 verb = verbs[0]
648 verb = verbs[0]
650 if priority:
649 if priority:
651 primaryactions.add(verb)
650 primaryactions.add(verb)
652 elif internal:
651 elif internal:
653 internalactions.add(verb)
652 internalactions.add(verb)
654 elif len(verbs) > 1:
653 elif len(verbs) > 1:
655 secondaryactions.add(verb)
654 secondaryactions.add(verb)
656 else:
655 else:
657 tertiaryactions.add(verb)
656 tertiaryactions.add(verb)
658
657
659 cls.verb = verb
658 cls.verb = verb
660 cls.verbs = verbs
659 cls.verbs = verbs
661 cls.message = message
660 cls.message = message
662 for verb in verbs:
661 for verb in verbs:
663 actiontable[verb] = cls
662 actiontable[verb] = cls
664 return cls
663 return cls
665 return wrap
664 return wrap
666
665
667 @action(['pick', 'p'],
666 @action(['pick', 'p'],
668 _('use commit'),
667 _('use commit'),
669 priority=True)
668 priority=True)
670 class pick(histeditaction):
669 class pick(histeditaction):
671 def run(self):
670 def run(self):
672 rulectx = self.repo[self.node]
671 rulectx = self.repo[self.node]
673 if rulectx.parents()[0].node() == self.state.parentctxnode:
672 if rulectx.parents()[0].node() == self.state.parentctxnode:
674 self.repo.ui.debug('node %s unchanged\n' % node.short(self.node))
673 self.repo.ui.debug('node %s unchanged\n' % node.short(self.node))
675 return rulectx, []
674 return rulectx, []
676
675
677 return super(pick, self).run()
676 return super(pick, self).run()
678
677
679 @action(['edit', 'e'],
678 @action(['edit', 'e'],
680 _('use commit, but stop for amending'),
679 _('use commit, but stop for amending'),
681 priority=True)
680 priority=True)
682 class edit(histeditaction):
681 class edit(histeditaction):
683 def run(self):
682 def run(self):
684 repo = self.repo
683 repo = self.repo
685 rulectx = repo[self.node]
684 rulectx = repo[self.node]
686 hg.update(repo, self.state.parentctxnode, quietempty=True)
685 hg.update(repo, self.state.parentctxnode, quietempty=True)
687 applychanges(repo.ui, repo, rulectx, {})
686 applychanges(repo.ui, repo, rulectx, {})
688 raise error.InterventionRequired(
687 raise error.InterventionRequired(
689 _('Editing (%s), you may commit or record as needed now.')
688 _('Editing (%s), you may commit or record as needed now.')
690 % node.short(self.node),
689 % node.short(self.node),
691 hint=_('hg histedit --continue to resume'))
690 hint=_('hg histedit --continue to resume'))
692
691
693 def commiteditor(self):
692 def commiteditor(self):
694 return cmdutil.getcommiteditor(edit=True, editform='histedit.edit')
693 return cmdutil.getcommiteditor(edit=True, editform='histedit.edit')
695
694
696 @action(['fold', 'f'],
695 @action(['fold', 'f'],
697 _('use commit, but combine it with the one above'))
696 _('use commit, but combine it with the one above'))
698 class fold(histeditaction):
697 class fold(histeditaction):
699 def verify(self, prev, expected, seen):
698 def verify(self, prev, expected, seen):
700 """ Verifies semantic correctness of the fold rule"""
699 """ Verifies semantic correctness of the fold rule"""
701 super(fold, self).verify(prev, expected, seen)
700 super(fold, self).verify(prev, expected, seen)
702 repo = self.repo
701 repo = self.repo
703 if not prev:
702 if not prev:
704 c = repo[self.node].parents()[0]
703 c = repo[self.node].parents()[0]
705 elif not prev.verb in ('pick', 'base'):
704 elif not prev.verb in ('pick', 'base'):
706 return
705 return
707 else:
706 else:
708 c = repo[prev.node]
707 c = repo[prev.node]
709 if not c.mutable():
708 if not c.mutable():
710 raise error.ParseError(
709 raise error.ParseError(
711 _("cannot fold into public change %s") % node.short(c.node()))
710 _("cannot fold into public change %s") % node.short(c.node()))
712
711
713
712
714 def continuedirty(self):
713 def continuedirty(self):
715 repo = self.repo
714 repo = self.repo
716 rulectx = repo[self.node]
715 rulectx = repo[self.node]
717
716
718 commit = commitfuncfor(repo, rulectx)
717 commit = commitfuncfor(repo, rulectx)
719 commit(text='fold-temp-revision %s' % node.short(self.node),
718 commit(text='fold-temp-revision %s' % node.short(self.node),
720 user=rulectx.user(), date=rulectx.date(),
719 user=rulectx.user(), date=rulectx.date(),
721 extra=rulectx.extra())
720 extra=rulectx.extra())
722
721
723 def continueclean(self):
722 def continueclean(self):
724 repo = self.repo
723 repo = self.repo
725 ctx = repo['.']
724 ctx = repo['.']
726 rulectx = repo[self.node]
725 rulectx = repo[self.node]
727 parentctxnode = self.state.parentctxnode
726 parentctxnode = self.state.parentctxnode
728 if ctx.node() == parentctxnode:
727 if ctx.node() == parentctxnode:
729 repo.ui.warn(_('%s: empty changeset\n') %
728 repo.ui.warn(_('%s: empty changeset\n') %
730 node.short(self.node))
729 node.short(self.node))
731 return ctx, [(self.node, (parentctxnode,))]
730 return ctx, [(self.node, (parentctxnode,))]
732
731
733 parentctx = repo[parentctxnode]
732 parentctx = repo[parentctxnode]
734 newcommits = set(c.node() for c in repo.set('(%d::. - %d)', parentctx,
733 newcommits = set(c.node() for c in repo.set('(%d::. - %d)', parentctx,
735 parentctx))
734 parentctx))
736 if not newcommits:
735 if not newcommits:
737 repo.ui.warn(_('%s: cannot fold - working copy is not a '
736 repo.ui.warn(_('%s: cannot fold - working copy is not a '
738 'descendant of previous commit %s\n') %
737 'descendant of previous commit %s\n') %
739 (node.short(self.node), node.short(parentctxnode)))
738 (node.short(self.node), node.short(parentctxnode)))
740 return ctx, [(self.node, (ctx.node(),))]
739 return ctx, [(self.node, (ctx.node(),))]
741
740
742 middlecommits = newcommits.copy()
741 middlecommits = newcommits.copy()
743 middlecommits.discard(ctx.node())
742 middlecommits.discard(ctx.node())
744
743
745 return self.finishfold(repo.ui, repo, parentctx, rulectx, ctx.node(),
744 return self.finishfold(repo.ui, repo, parentctx, rulectx, ctx.node(),
746 middlecommits)
745 middlecommits)
747
746
748 def skipprompt(self):
747 def skipprompt(self):
749 """Returns true if the rule should skip the message editor.
748 """Returns true if the rule should skip the message editor.
750
749
751 For example, 'fold' wants to show an editor, but 'rollup'
750 For example, 'fold' wants to show an editor, but 'rollup'
752 doesn't want to.
751 doesn't want to.
753 """
752 """
754 return False
753 return False
755
754
756 def mergedescs(self):
755 def mergedescs(self):
757 """Returns true if the rule should merge messages of multiple changes.
756 """Returns true if the rule should merge messages of multiple changes.
758
757
759 This exists mainly so that 'rollup' rules can be a subclass of
758 This exists mainly so that 'rollup' rules can be a subclass of
760 'fold'.
759 'fold'.
761 """
760 """
762 return True
761 return True
763
762
764 def firstdate(self):
763 def firstdate(self):
765 """Returns true if the rule should preserve the date of the first
764 """Returns true if the rule should preserve the date of the first
766 change.
765 change.
767
766
768 This exists mainly so that 'rollup' rules can be a subclass of
767 This exists mainly so that 'rollup' rules can be a subclass of
769 'fold'.
768 'fold'.
770 """
769 """
771 return False
770 return False
772
771
773 def finishfold(self, ui, repo, ctx, oldctx, newnode, internalchanges):
772 def finishfold(self, ui, repo, ctx, oldctx, newnode, internalchanges):
774 parent = ctx.parents()[0].node()
773 parent = ctx.parents()[0].node()
775 repo.ui.pushbuffer()
774 repo.ui.pushbuffer()
776 hg.update(repo, parent)
775 hg.update(repo, parent)
777 repo.ui.popbuffer()
776 repo.ui.popbuffer()
778 ### prepare new commit data
777 ### prepare new commit data
779 commitopts = {}
778 commitopts = {}
780 commitopts['user'] = ctx.user()
779 commitopts['user'] = ctx.user()
781 # commit message
780 # commit message
782 if not self.mergedescs():
781 if not self.mergedescs():
783 newmessage = ctx.description()
782 newmessage = ctx.description()
784 else:
783 else:
785 newmessage = '\n***\n'.join(
784 newmessage = '\n***\n'.join(
786 [ctx.description()] +
785 [ctx.description()] +
787 [repo[r].description() for r in internalchanges] +
786 [repo[r].description() for r in internalchanges] +
788 [oldctx.description()]) + '\n'
787 [oldctx.description()]) + '\n'
789 commitopts['message'] = newmessage
788 commitopts['message'] = newmessage
790 # date
789 # date
791 if self.firstdate():
790 if self.firstdate():
792 commitopts['date'] = ctx.date()
791 commitopts['date'] = ctx.date()
793 else:
792 else:
794 commitopts['date'] = max(ctx.date(), oldctx.date())
793 commitopts['date'] = max(ctx.date(), oldctx.date())
795 extra = ctx.extra().copy()
794 extra = ctx.extra().copy()
796 # histedit_source
795 # histedit_source
797 # note: ctx is likely a temporary commit but that the best we can do
796 # note: ctx is likely a temporary commit but that the best we can do
798 # here. This is sufficient to solve issue3681 anyway.
797 # here. This is sufficient to solve issue3681 anyway.
799 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
798 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
800 commitopts['extra'] = extra
799 commitopts['extra'] = extra
801 phasemin = max(ctx.phase(), oldctx.phase())
800 phasemin = max(ctx.phase(), oldctx.phase())
802 overrides = {('phases', 'new-commit'): phasemin}
801 overrides = {('phases', 'new-commit'): phasemin}
803 with repo.ui.configoverride(overrides, 'histedit'):
802 with repo.ui.configoverride(overrides, 'histedit'):
804 n = collapse(repo, ctx, repo[newnode], commitopts,
803 n = collapse(repo, ctx, repo[newnode], commitopts,
805 skipprompt=self.skipprompt())
804 skipprompt=self.skipprompt())
806 if n is None:
805 if n is None:
807 return ctx, []
806 return ctx, []
808 repo.ui.pushbuffer()
807 repo.ui.pushbuffer()
809 hg.update(repo, n)
808 hg.update(repo, n)
810 repo.ui.popbuffer()
809 repo.ui.popbuffer()
811 replacements = [(oldctx.node(), (newnode,)),
810 replacements = [(oldctx.node(), (newnode,)),
812 (ctx.node(), (n,)),
811 (ctx.node(), (n,)),
813 (newnode, (n,)),
812 (newnode, (n,)),
814 ]
813 ]
815 for ich in internalchanges:
814 for ich in internalchanges:
816 replacements.append((ich, (n,)))
815 replacements.append((ich, (n,)))
817 return repo[n], replacements
816 return repo[n], replacements
818
817
819 @action(['base', 'b'],
818 @action(['base', 'b'],
820 _('checkout changeset and apply further changesets from there'))
819 _('checkout changeset and apply further changesets from there'))
821 class base(histeditaction):
820 class base(histeditaction):
822
821
823 def run(self):
822 def run(self):
824 if self.repo['.'].node() != self.node:
823 if self.repo['.'].node() != self.node:
825 mergemod.update(self.repo, self.node, False, True)
824 mergemod.update(self.repo, self.node, False, True)
826 # branchmerge, force)
825 # branchmerge, force)
827 return self.continueclean()
826 return self.continueclean()
828
827
829 def continuedirty(self):
828 def continuedirty(self):
830 abortdirty()
829 abortdirty()
831
830
832 def continueclean(self):
831 def continueclean(self):
833 basectx = self.repo['.']
832 basectx = self.repo['.']
834 return basectx, []
833 return basectx, []
835
834
836 def _verifynodeconstraints(self, prev, expected, seen):
835 def _verifynodeconstraints(self, prev, expected, seen):
837 # base can only be use with a node not in the edited set
836 # base can only be use with a node not in the edited set
838 if self.node in expected:
837 if self.node in expected:
839 msg = _('%s "%s" changeset was an edited list candidate')
838 msg = _('%s "%s" changeset was an edited list candidate')
840 raise error.ParseError(
839 raise error.ParseError(
841 msg % (self.verb, node.short(self.node)),
840 msg % (self.verb, node.short(self.node)),
842 hint=_('base must only use unlisted changesets'))
841 hint=_('base must only use unlisted changesets'))
843
842
844 @action(['_multifold'],
843 @action(['_multifold'],
845 _(
844 _(
846 """fold subclass used for when multiple folds happen in a row
845 """fold subclass used for when multiple folds happen in a row
847
846
848 We only want to fire the editor for the folded message once when
847 We only want to fire the editor for the folded message once when
849 (say) four changes are folded down into a single change. This is
848 (say) four changes are folded down into a single change. This is
850 similar to rollup, but we should preserve both messages so that
849 similar to rollup, but we should preserve both messages so that
851 when the last fold operation runs we can show the user all the
850 when the last fold operation runs we can show the user all the
852 commit messages in their editor.
851 commit messages in their editor.
853 """),
852 """),
854 internal=True)
853 internal=True)
855 class _multifold(fold):
854 class _multifold(fold):
856 def skipprompt(self):
855 def skipprompt(self):
857 return True
856 return True
858
857
859 @action(["roll", "r"],
858 @action(["roll", "r"],
860 _("like fold, but discard this commit's description and date"))
859 _("like fold, but discard this commit's description and date"))
861 class rollup(fold):
860 class rollup(fold):
862 def mergedescs(self):
861 def mergedescs(self):
863 return False
862 return False
864
863
865 def skipprompt(self):
864 def skipprompt(self):
866 return True
865 return True
867
866
868 def firstdate(self):
867 def firstdate(self):
869 return True
868 return True
870
869
871 @action(["drop", "d"],
870 @action(["drop", "d"],
872 _('remove commit from history'))
871 _('remove commit from history'))
873 class drop(histeditaction):
872 class drop(histeditaction):
874 def run(self):
873 def run(self):
875 parentctx = self.repo[self.state.parentctxnode]
874 parentctx = self.repo[self.state.parentctxnode]
876 return parentctx, [(self.node, tuple())]
875 return parentctx, [(self.node, tuple())]
877
876
878 @action(["mess", "m"],
877 @action(["mess", "m"],
879 _('edit commit message without changing commit content'),
878 _('edit commit message without changing commit content'),
880 priority=True)
879 priority=True)
881 class message(histeditaction):
880 class message(histeditaction):
882 def commiteditor(self):
881 def commiteditor(self):
883 return cmdutil.getcommiteditor(edit=True, editform='histedit.mess')
882 return cmdutil.getcommiteditor(edit=True, editform='histedit.mess')
884
883
885 def findoutgoing(ui, repo, remote=None, force=False, opts=None):
884 def findoutgoing(ui, repo, remote=None, force=False, opts=None):
886 """utility function to find the first outgoing changeset
885 """utility function to find the first outgoing changeset
887
886
888 Used by initialization code"""
887 Used by initialization code"""
889 if opts is None:
888 if opts is None:
890 opts = {}
889 opts = {}
891 dest = ui.expandpath(remote or 'default-push', remote or 'default')
890 dest = ui.expandpath(remote or 'default-push', remote or 'default')
892 dest, revs = hg.parseurl(dest, None)[:2]
891 dest, revs = hg.parseurl(dest, None)[:2]
893 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
892 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
894
893
895 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
894 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
896 other = hg.peer(repo, opts, dest)
895 other = hg.peer(repo, opts, dest)
897
896
898 if revs:
897 if revs:
899 revs = [repo.lookup(rev) for rev in revs]
898 revs = [repo.lookup(rev) for rev in revs]
900
899
901 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
900 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
902 if not outgoing.missing:
901 if not outgoing.missing:
903 raise error.Abort(_('no outgoing ancestors'))
902 raise error.Abort(_('no outgoing ancestors'))
904 roots = list(repo.revs("roots(%ln)", outgoing.missing))
903 roots = list(repo.revs("roots(%ln)", outgoing.missing))
905 if 1 < len(roots):
904 if 1 < len(roots):
906 msg = _('there are ambiguous outgoing revisions')
905 msg = _('there are ambiguous outgoing revisions')
907 hint = _("see 'hg help histedit' for more detail")
906 hint = _("see 'hg help histedit' for more detail")
908 raise error.Abort(msg, hint=hint)
907 raise error.Abort(msg, hint=hint)
909 return repo.lookup(roots[0])
908 return repo.lookup(roots[0])
910
909
911 @command('histedit',
910 @command('histedit',
912 [('', 'commands', '',
911 [('', 'commands', '',
913 _('read history edits from the specified file'), _('FILE')),
912 _('read history edits from the specified file'), _('FILE')),
914 ('c', 'continue', False, _('continue an edit already in progress')),
913 ('c', 'continue', False, _('continue an edit already in progress')),
915 ('', 'edit-plan', False, _('edit remaining actions list')),
914 ('', 'edit-plan', False, _('edit remaining actions list')),
916 ('k', 'keep', False,
915 ('k', 'keep', False,
917 _("don't strip old nodes after edit is complete")),
916 _("don't strip old nodes after edit is complete")),
918 ('', 'abort', False, _('abort an edit in progress')),
917 ('', 'abort', False, _('abort an edit in progress')),
919 ('o', 'outgoing', False, _('changesets not found in destination')),
918 ('o', 'outgoing', False, _('changesets not found in destination')),
920 ('f', 'force', False,
919 ('f', 'force', False,
921 _('force outgoing even for unrelated repositories')),
920 _('force outgoing even for unrelated repositories')),
922 ('r', 'rev', [], _('first revision to be edited'), _('REV'))] +
921 ('r', 'rev', [], _('first revision to be edited'), _('REV'))] +
923 cmdutil.formatteropts,
922 cmdutil.formatteropts,
924 _("[OPTIONS] ([ANCESTOR] | --outgoing [URL])"))
923 _("[OPTIONS] ([ANCESTOR] | --outgoing [URL])"))
925 def histedit(ui, repo, *freeargs, **opts):
924 def histedit(ui, repo, *freeargs, **opts):
926 """interactively edit changeset history
925 """interactively edit changeset history
927
926
928 This command lets you edit a linear series of changesets (up to
927 This command lets you edit a linear series of changesets (up to
929 and including the working directory, which should be clean).
928 and including the working directory, which should be clean).
930 You can:
929 You can:
931
930
932 - `pick` to [re]order a changeset
931 - `pick` to [re]order a changeset
933
932
934 - `drop` to omit changeset
933 - `drop` to omit changeset
935
934
936 - `mess` to reword the changeset commit message
935 - `mess` to reword the changeset commit message
937
936
938 - `fold` to combine it with the preceding changeset (using the later date)
937 - `fold` to combine it with the preceding changeset (using the later date)
939
938
940 - `roll` like fold, but discarding this commit's description and date
939 - `roll` like fold, but discarding this commit's description and date
941
940
942 - `edit` to edit this changeset (preserving date)
941 - `edit` to edit this changeset (preserving date)
943
942
944 - `base` to checkout changeset and apply further changesets from there
943 - `base` to checkout changeset and apply further changesets from there
945
944
946 There are a number of ways to select the root changeset:
945 There are a number of ways to select the root changeset:
947
946
948 - Specify ANCESTOR directly
947 - Specify ANCESTOR directly
949
948
950 - Use --outgoing -- it will be the first linear changeset not
949 - Use --outgoing -- it will be the first linear changeset not
951 included in destination. (See :hg:`help config.paths.default-push`)
950 included in destination. (See :hg:`help config.paths.default-push`)
952
951
953 - Otherwise, the value from the "histedit.defaultrev" config option
952 - Otherwise, the value from the "histedit.defaultrev" config option
954 is used as a revset to select the base revision when ANCESTOR is not
953 is used as a revset to select the base revision when ANCESTOR is not
955 specified. The first revision returned by the revset is used. By
954 specified. The first revision returned by the revset is used. By
956 default, this selects the editable history that is unique to the
955 default, this selects the editable history that is unique to the
957 ancestry of the working directory.
956 ancestry of the working directory.
958
957
959 .. container:: verbose
958 .. container:: verbose
960
959
961 If you use --outgoing, this command will abort if there are ambiguous
960 If you use --outgoing, this command will abort if there are ambiguous
962 outgoing revisions. For example, if there are multiple branches
961 outgoing revisions. For example, if there are multiple branches
963 containing outgoing revisions.
962 containing outgoing revisions.
964
963
965 Use "min(outgoing() and ::.)" or similar revset specification
964 Use "min(outgoing() and ::.)" or similar revset specification
966 instead of --outgoing to specify edit target revision exactly in
965 instead of --outgoing to specify edit target revision exactly in
967 such ambiguous situation. See :hg:`help revsets` for detail about
966 such ambiguous situation. See :hg:`help revsets` for detail about
968 selecting revisions.
967 selecting revisions.
969
968
970 .. container:: verbose
969 .. container:: verbose
971
970
972 Examples:
971 Examples:
973
972
974 - A number of changes have been made.
973 - A number of changes have been made.
975 Revision 3 is no longer needed.
974 Revision 3 is no longer needed.
976
975
977 Start history editing from revision 3::
976 Start history editing from revision 3::
978
977
979 hg histedit -r 3
978 hg histedit -r 3
980
979
981 An editor opens, containing the list of revisions,
980 An editor opens, containing the list of revisions,
982 with specific actions specified::
981 with specific actions specified::
983
982
984 pick 5339bf82f0ca 3 Zworgle the foobar
983 pick 5339bf82f0ca 3 Zworgle the foobar
985 pick 8ef592ce7cc4 4 Bedazzle the zerlog
984 pick 8ef592ce7cc4 4 Bedazzle the zerlog
986 pick 0a9639fcda9d 5 Morgify the cromulancy
985 pick 0a9639fcda9d 5 Morgify the cromulancy
987
986
988 Additional information about the possible actions
987 Additional information about the possible actions
989 to take appears below the list of revisions.
988 to take appears below the list of revisions.
990
989
991 To remove revision 3 from the history,
990 To remove revision 3 from the history,
992 its action (at the beginning of the relevant line)
991 its action (at the beginning of the relevant line)
993 is changed to 'drop'::
992 is changed to 'drop'::
994
993
995 drop 5339bf82f0ca 3 Zworgle the foobar
994 drop 5339bf82f0ca 3 Zworgle the foobar
996 pick 8ef592ce7cc4 4 Bedazzle the zerlog
995 pick 8ef592ce7cc4 4 Bedazzle the zerlog
997 pick 0a9639fcda9d 5 Morgify the cromulancy
996 pick 0a9639fcda9d 5 Morgify the cromulancy
998
997
999 - A number of changes have been made.
998 - A number of changes have been made.
1000 Revision 2 and 4 need to be swapped.
999 Revision 2 and 4 need to be swapped.
1001
1000
1002 Start history editing from revision 2::
1001 Start history editing from revision 2::
1003
1002
1004 hg histedit -r 2
1003 hg histedit -r 2
1005
1004
1006 An editor opens, containing the list of revisions,
1005 An editor opens, containing the list of revisions,
1007 with specific actions specified::
1006 with specific actions specified::
1008
1007
1009 pick 252a1af424ad 2 Blorb a morgwazzle
1008 pick 252a1af424ad 2 Blorb a morgwazzle
1010 pick 5339bf82f0ca 3 Zworgle the foobar
1009 pick 5339bf82f0ca 3 Zworgle the foobar
1011 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1010 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1012
1011
1013 To swap revision 2 and 4, its lines are swapped
1012 To swap revision 2 and 4, its lines are swapped
1014 in the editor::
1013 in the editor::
1015
1014
1016 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1015 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1017 pick 5339bf82f0ca 3 Zworgle the foobar
1016 pick 5339bf82f0ca 3 Zworgle the foobar
1018 pick 252a1af424ad 2 Blorb a morgwazzle
1017 pick 252a1af424ad 2 Blorb a morgwazzle
1019
1018
1020 Returns 0 on success, 1 if user intervention is required (not only
1019 Returns 0 on success, 1 if user intervention is required (not only
1021 for intentional "edit" command, but also for resolving unexpected
1020 for intentional "edit" command, but also for resolving unexpected
1022 conflicts).
1021 conflicts).
1023 """
1022 """
1024 state = histeditstate(repo)
1023 state = histeditstate(repo)
1025 try:
1024 try:
1026 state.wlock = repo.wlock()
1025 state.wlock = repo.wlock()
1027 state.lock = repo.lock()
1026 state.lock = repo.lock()
1028 _histedit(ui, repo, state, *freeargs, **opts)
1027 _histedit(ui, repo, state, *freeargs, **opts)
1029 finally:
1028 finally:
1030 release(state.lock, state.wlock)
1029 release(state.lock, state.wlock)
1031
1030
1032 goalcontinue = 'continue'
1031 goalcontinue = 'continue'
1033 goalabort = 'abort'
1032 goalabort = 'abort'
1034 goaleditplan = 'edit-plan'
1033 goaleditplan = 'edit-plan'
1035 goalnew = 'new'
1034 goalnew = 'new'
1036
1035
1037 def _getgoal(opts):
1036 def _getgoal(opts):
1038 if opts.get('continue'):
1037 if opts.get('continue'):
1039 return goalcontinue
1038 return goalcontinue
1040 if opts.get('abort'):
1039 if opts.get('abort'):
1041 return goalabort
1040 return goalabort
1042 if opts.get('edit_plan'):
1041 if opts.get('edit_plan'):
1043 return goaleditplan
1042 return goaleditplan
1044 return goalnew
1043 return goalnew
1045
1044
1046 def _readfile(ui, path):
1045 def _readfile(ui, path):
1047 if path == '-':
1046 if path == '-':
1048 with ui.timeblockedsection('histedit'):
1047 with ui.timeblockedsection('histedit'):
1049 return ui.fin.read()
1048 return ui.fin.read()
1050 else:
1049 else:
1051 with open(path, 'rb') as f:
1050 with open(path, 'rb') as f:
1052 return f.read()
1051 return f.read()
1053
1052
1054 def _validateargs(ui, repo, state, freeargs, opts, goal, rules, revs):
1053 def _validateargs(ui, repo, state, freeargs, opts, goal, rules, revs):
1055 # TODO only abort if we try to histedit mq patches, not just
1054 # TODO only abort if we try to histedit mq patches, not just
1056 # blanket if mq patches are applied somewhere
1055 # blanket if mq patches are applied somewhere
1057 mq = getattr(repo, 'mq', None)
1056 mq = getattr(repo, 'mq', None)
1058 if mq and mq.applied:
1057 if mq and mq.applied:
1059 raise error.Abort(_('source has mq patches applied'))
1058 raise error.Abort(_('source has mq patches applied'))
1060
1059
1061 # basic argument incompatibility processing
1060 # basic argument incompatibility processing
1062 outg = opts.get('outgoing')
1061 outg = opts.get('outgoing')
1063 editplan = opts.get('edit_plan')
1062 editplan = opts.get('edit_plan')
1064 abort = opts.get('abort')
1063 abort = opts.get('abort')
1065 force = opts.get('force')
1064 force = opts.get('force')
1066 if force and not outg:
1065 if force and not outg:
1067 raise error.Abort(_('--force only allowed with --outgoing'))
1066 raise error.Abort(_('--force only allowed with --outgoing'))
1068 if goal == 'continue':
1067 if goal == 'continue':
1069 if any((outg, abort, revs, freeargs, rules, editplan)):
1068 if any((outg, abort, revs, freeargs, rules, editplan)):
1070 raise error.Abort(_('no arguments allowed with --continue'))
1069 raise error.Abort(_('no arguments allowed with --continue'))
1071 elif goal == 'abort':
1070 elif goal == 'abort':
1072 if any((outg, revs, freeargs, rules, editplan)):
1071 if any((outg, revs, freeargs, rules, editplan)):
1073 raise error.Abort(_('no arguments allowed with --abort'))
1072 raise error.Abort(_('no arguments allowed with --abort'))
1074 elif goal == 'edit-plan':
1073 elif goal == 'edit-plan':
1075 if any((outg, revs, freeargs)):
1074 if any((outg, revs, freeargs)):
1076 raise error.Abort(_('only --commands argument allowed with '
1075 raise error.Abort(_('only --commands argument allowed with '
1077 '--edit-plan'))
1076 '--edit-plan'))
1078 else:
1077 else:
1079 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
1078 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
1080 raise error.Abort(_('history edit already in progress, try '
1079 raise error.Abort(_('history edit already in progress, try '
1081 '--continue or --abort'))
1080 '--continue or --abort'))
1082 if outg:
1081 if outg:
1083 if revs:
1082 if revs:
1084 raise error.Abort(_('no revisions allowed with --outgoing'))
1083 raise error.Abort(_('no revisions allowed with --outgoing'))
1085 if len(freeargs) > 1:
1084 if len(freeargs) > 1:
1086 raise error.Abort(
1085 raise error.Abort(
1087 _('only one repo argument allowed with --outgoing'))
1086 _('only one repo argument allowed with --outgoing'))
1088 else:
1087 else:
1089 revs.extend(freeargs)
1088 revs.extend(freeargs)
1090 if len(revs) == 0:
1089 if len(revs) == 0:
1091 defaultrev = destutil.desthistedit(ui, repo)
1090 defaultrev = destutil.desthistedit(ui, repo)
1092 if defaultrev is not None:
1091 if defaultrev is not None:
1093 revs.append(defaultrev)
1092 revs.append(defaultrev)
1094
1093
1095 if len(revs) != 1:
1094 if len(revs) != 1:
1096 raise error.Abort(
1095 raise error.Abort(
1097 _('histedit requires exactly one ancestor revision'))
1096 _('histedit requires exactly one ancestor revision'))
1098
1097
1099 def _histedit(ui, repo, state, *freeargs, **opts):
1098 def _histedit(ui, repo, state, *freeargs, **opts):
1100 opts = pycompat.byteskwargs(opts)
1099 opts = pycompat.byteskwargs(opts)
1101 fm = ui.formatter('histedit', opts)
1100 fm = ui.formatter('histedit', opts)
1102 fm.startitem()
1101 fm.startitem()
1103 goal = _getgoal(opts)
1102 goal = _getgoal(opts)
1104 revs = opts.get('rev', [])
1103 revs = opts.get('rev', [])
1105 rules = opts.get('commands', '')
1104 rules = opts.get('commands', '')
1106 state.keep = opts.get('keep', False)
1105 state.keep = opts.get('keep', False)
1107
1106
1108 _validateargs(ui, repo, state, freeargs, opts, goal, rules, revs)
1107 _validateargs(ui, repo, state, freeargs, opts, goal, rules, revs)
1109
1108
1110 # rebuild state
1109 # rebuild state
1111 if goal == goalcontinue:
1110 if goal == goalcontinue:
1112 state.read()
1111 state.read()
1113 state = bootstrapcontinue(ui, state, opts)
1112 state = bootstrapcontinue(ui, state, opts)
1114 elif goal == goaleditplan:
1113 elif goal == goaleditplan:
1115 _edithisteditplan(ui, repo, state, rules)
1114 _edithisteditplan(ui, repo, state, rules)
1116 return
1115 return
1117 elif goal == goalabort:
1116 elif goal == goalabort:
1118 _aborthistedit(ui, repo, state)
1117 _aborthistedit(ui, repo, state)
1119 return
1118 return
1120 else:
1119 else:
1121 # goal == goalnew
1120 # goal == goalnew
1122 _newhistedit(ui, repo, state, revs, freeargs, opts)
1121 _newhistedit(ui, repo, state, revs, freeargs, opts)
1123
1122
1124 _continuehistedit(ui, repo, state)
1123 _continuehistedit(ui, repo, state)
1125 _finishhistedit(ui, repo, state, fm)
1124 _finishhistedit(ui, repo, state, fm)
1126 fm.end()
1125 fm.end()
1127
1126
1128 def _continuehistedit(ui, repo, state):
1127 def _continuehistedit(ui, repo, state):
1129 """This function runs after either:
1128 """This function runs after either:
1130 - bootstrapcontinue (if the goal is 'continue')
1129 - bootstrapcontinue (if the goal is 'continue')
1131 - _newhistedit (if the goal is 'new')
1130 - _newhistedit (if the goal is 'new')
1132 """
1131 """
1133 # preprocess rules so that we can hide inner folds from the user
1132 # preprocess rules so that we can hide inner folds from the user
1134 # and only show one editor
1133 # and only show one editor
1135 actions = state.actions[:]
1134 actions = state.actions[:]
1136 for idx, (action, nextact) in enumerate(
1135 for idx, (action, nextact) in enumerate(
1137 zip(actions, actions[1:] + [None])):
1136 zip(actions, actions[1:] + [None])):
1138 if action.verb == 'fold' and nextact and nextact.verb == 'fold':
1137 if action.verb == 'fold' and nextact and nextact.verb == 'fold':
1139 state.actions[idx].__class__ = _multifold
1138 state.actions[idx].__class__ = _multifold
1140
1139
1141 # Force an initial state file write, so the user can run --abort/continue
1140 # Force an initial state file write, so the user can run --abort/continue
1142 # even if there's an exception before the first transaction serialize.
1141 # even if there's an exception before the first transaction serialize.
1143 state.write()
1142 state.write()
1144
1143
1145 total = len(state.actions)
1144 total = len(state.actions)
1146 pos = 0
1145 pos = 0
1147 tr = None
1146 tr = None
1148 # Don't use singletransaction by default since it rolls the entire
1147 # Don't use singletransaction by default since it rolls the entire
1149 # transaction back if an unexpected exception happens (like a
1148 # transaction back if an unexpected exception happens (like a
1150 # pretxncommit hook throws, or the user aborts the commit msg editor).
1149 # pretxncommit hook throws, or the user aborts the commit msg editor).
1151 if ui.configbool("histedit", "singletransaction"):
1150 if ui.configbool("histedit", "singletransaction"):
1152 # Don't use a 'with' for the transaction, since actions may close
1151 # Don't use a 'with' for the transaction, since actions may close
1153 # and reopen a transaction. For example, if the action executes an
1152 # and reopen a transaction. For example, if the action executes an
1154 # external process it may choose to commit the transaction first.
1153 # external process it may choose to commit the transaction first.
1155 tr = repo.transaction('histedit')
1154 tr = repo.transaction('histedit')
1156 with util.acceptintervention(tr):
1155 with util.acceptintervention(tr):
1157 while state.actions:
1156 while state.actions:
1158 state.write(tr=tr)
1157 state.write(tr=tr)
1159 actobj = state.actions[0]
1158 actobj = state.actions[0]
1160 pos += 1
1159 pos += 1
1161 ui.progress(_("editing"), pos, actobj.torule(),
1160 ui.progress(_("editing"), pos, actobj.torule(),
1162 _('changes'), total)
1161 _('changes'), total)
1163 ui.debug('histedit: processing %s %s\n' % (actobj.verb,\
1162 ui.debug('histedit: processing %s %s\n' % (actobj.verb,\
1164 actobj.torule()))
1163 actobj.torule()))
1165 parentctx, replacement_ = actobj.run()
1164 parentctx, replacement_ = actobj.run()
1166 state.parentctxnode = parentctx.node()
1165 state.parentctxnode = parentctx.node()
1167 state.replacements.extend(replacement_)
1166 state.replacements.extend(replacement_)
1168 state.actions.pop(0)
1167 state.actions.pop(0)
1169
1168
1170 state.write()
1169 state.write()
1171 ui.progress(_("editing"), None)
1170 ui.progress(_("editing"), None)
1172
1171
1173 def _finishhistedit(ui, repo, state, fm):
1172 def _finishhistedit(ui, repo, state, fm):
1174 """This action runs when histedit is finishing its session"""
1173 """This action runs when histedit is finishing its session"""
1175 repo.ui.pushbuffer()
1174 repo.ui.pushbuffer()
1176 hg.update(repo, state.parentctxnode, quietempty=True)
1175 hg.update(repo, state.parentctxnode, quietempty=True)
1177 repo.ui.popbuffer()
1176 repo.ui.popbuffer()
1178
1177
1179 mapping, tmpnodes, created, ntm = processreplacement(state)
1178 mapping, tmpnodes, created, ntm = processreplacement(state)
1180 if mapping:
1179 if mapping:
1181 for prec, succs in mapping.iteritems():
1180 for prec, succs in mapping.iteritems():
1182 if not succs:
1181 if not succs:
1183 ui.debug('histedit: %s is dropped\n' % node.short(prec))
1182 ui.debug('histedit: %s is dropped\n' % node.short(prec))
1184 else:
1183 else:
1185 ui.debug('histedit: %s is replaced by %s\n' % (
1184 ui.debug('histedit: %s is replaced by %s\n' % (
1186 node.short(prec), node.short(succs[0])))
1185 node.short(prec), node.short(succs[0])))
1187 if len(succs) > 1:
1186 if len(succs) > 1:
1188 m = 'histedit: %s'
1187 m = 'histedit: %s'
1189 for n in succs[1:]:
1188 for n in succs[1:]:
1190 ui.debug(m % node.short(n))
1189 ui.debug(m % node.short(n))
1191
1190
1192 if not state.keep:
1191 if not state.keep:
1193 if mapping:
1192 if mapping:
1194 movetopmostbookmarks(repo, state.topmost, ntm)
1193 movetopmostbookmarks(repo, state.topmost, ntm)
1195 # TODO update mq state
1194 # TODO update mq state
1196 else:
1195 else:
1197 mapping = {}
1196 mapping = {}
1198
1197
1199 for n in tmpnodes:
1198 for n in tmpnodes:
1200 mapping[n] = ()
1199 mapping[n] = ()
1201
1200
1202 # remove entries about unknown nodes
1201 # remove entries about unknown nodes
1203 nodemap = repo.unfiltered().changelog.nodemap
1202 nodemap = repo.unfiltered().changelog.nodemap
1204 mapping = {k: v for k, v in mapping.items()
1203 mapping = {k: v for k, v in mapping.items()
1205 if k in nodemap and all(n in nodemap for n in v)}
1204 if k in nodemap and all(n in nodemap for n in v)}
1206 scmutil.cleanupnodes(repo, mapping, 'histedit')
1205 scmutil.cleanupnodes(repo, mapping, 'histedit')
1207 hf = fm.hexfunc
1206 hf = fm.hexfunc
1208 fl = fm.formatlist
1207 fl = fm.formatlist
1209 fd = fm.formatdict
1208 fd = fm.formatdict
1210 nodechanges = fd({hf(oldn): fl([hf(n) for n in newn], name='node')
1209 nodechanges = fd({hf(oldn): fl([hf(n) for n in newn], name='node')
1211 for oldn, newn in mapping.iteritems()},
1210 for oldn, newn in mapping.iteritems()},
1212 key="oldnode", value="newnodes")
1211 key="oldnode", value="newnodes")
1213 fm.data(nodechanges=nodechanges)
1212 fm.data(nodechanges=nodechanges)
1214
1213
1215 state.clear()
1214 state.clear()
1216 if os.path.exists(repo.sjoin('undo')):
1215 if os.path.exists(repo.sjoin('undo')):
1217 os.unlink(repo.sjoin('undo'))
1216 os.unlink(repo.sjoin('undo'))
1218 if repo.vfs.exists('histedit-last-edit.txt'):
1217 if repo.vfs.exists('histedit-last-edit.txt'):
1219 repo.vfs.unlink('histedit-last-edit.txt')
1218 repo.vfs.unlink('histedit-last-edit.txt')
1220
1219
1221 def _aborthistedit(ui, repo, state):
1220 def _aborthistedit(ui, repo, state):
1222 try:
1221 try:
1223 state.read()
1222 state.read()
1224 __, leafs, tmpnodes, __ = processreplacement(state)
1223 __, leafs, tmpnodes, __ = processreplacement(state)
1225 ui.debug('restore wc to old parent %s\n'
1224 ui.debug('restore wc to old parent %s\n'
1226 % node.short(state.topmost))
1225 % node.short(state.topmost))
1227
1226
1228 # Recover our old commits if necessary
1227 # Recover our old commits if necessary
1229 if not state.topmost in repo and state.backupfile:
1228 if not state.topmost in repo and state.backupfile:
1230 backupfile = repo.vfs.join(state.backupfile)
1229 backupfile = repo.vfs.join(state.backupfile)
1231 f = hg.openpath(ui, backupfile)
1230 f = hg.openpath(ui, backupfile)
1232 gen = exchange.readbundle(ui, f, backupfile)
1231 gen = exchange.readbundle(ui, f, backupfile)
1233 with repo.transaction('histedit.abort') as tr:
1232 with repo.transaction('histedit.abort') as tr:
1234 bundle2.applybundle(repo, gen, tr, source='histedit',
1233 bundle2.applybundle(repo, gen, tr, source='histedit',
1235 url='bundle:' + backupfile)
1234 url='bundle:' + backupfile)
1236
1235
1237 os.remove(backupfile)
1236 os.remove(backupfile)
1238
1237
1239 # check whether we should update away
1238 # check whether we should update away
1240 if repo.unfiltered().revs('parents() and (%n or %ln::)',
1239 if repo.unfiltered().revs('parents() and (%n or %ln::)',
1241 state.parentctxnode, leafs | tmpnodes):
1240 state.parentctxnode, leafs | tmpnodes):
1242 hg.clean(repo, state.topmost, show_stats=True, quietempty=True)
1241 hg.clean(repo, state.topmost, show_stats=True, quietempty=True)
1243 cleanupnode(ui, repo, tmpnodes)
1242 cleanupnode(ui, repo, tmpnodes)
1244 cleanupnode(ui, repo, leafs)
1243 cleanupnode(ui, repo, leafs)
1245 except Exception:
1244 except Exception:
1246 if state.inprogress():
1245 if state.inprogress():
1247 ui.warn(_('warning: encountered an exception during histedit '
1246 ui.warn(_('warning: encountered an exception during histedit '
1248 '--abort; the repository may not have been completely '
1247 '--abort; the repository may not have been completely '
1249 'cleaned up\n'))
1248 'cleaned up\n'))
1250 raise
1249 raise
1251 finally:
1250 finally:
1252 state.clear()
1251 state.clear()
1253
1252
1254 def _edithisteditplan(ui, repo, state, rules):
1253 def _edithisteditplan(ui, repo, state, rules):
1255 state.read()
1254 state.read()
1256 if not rules:
1255 if not rules:
1257 comment = geteditcomment(ui,
1256 comment = geteditcomment(ui,
1258 node.short(state.parentctxnode),
1257 node.short(state.parentctxnode),
1259 node.short(state.topmost))
1258 node.short(state.topmost))
1260 rules = ruleeditor(repo, ui, state.actions, comment)
1259 rules = ruleeditor(repo, ui, state.actions, comment)
1261 else:
1260 else:
1262 rules = _readfile(ui, rules)
1261 rules = _readfile(ui, rules)
1263 actions = parserules(rules, state)
1262 actions = parserules(rules, state)
1264 ctxs = [repo[act.node] \
1263 ctxs = [repo[act.node] \
1265 for act in state.actions if act.node]
1264 for act in state.actions if act.node]
1266 warnverifyactions(ui, repo, actions, state, ctxs)
1265 warnverifyactions(ui, repo, actions, state, ctxs)
1267 state.actions = actions
1266 state.actions = actions
1268 state.write()
1267 state.write()
1269
1268
1270 def _newhistedit(ui, repo, state, revs, freeargs, opts):
1269 def _newhistedit(ui, repo, state, revs, freeargs, opts):
1271 outg = opts.get('outgoing')
1270 outg = opts.get('outgoing')
1272 rules = opts.get('commands', '')
1271 rules = opts.get('commands', '')
1273 force = opts.get('force')
1272 force = opts.get('force')
1274
1273
1275 cmdutil.checkunfinished(repo)
1274 cmdutil.checkunfinished(repo)
1276 cmdutil.bailifchanged(repo)
1275 cmdutil.bailifchanged(repo)
1277
1276
1278 topmost, empty = repo.dirstate.parents()
1277 topmost, empty = repo.dirstate.parents()
1279 if outg:
1278 if outg:
1280 if freeargs:
1279 if freeargs:
1281 remote = freeargs[0]
1280 remote = freeargs[0]
1282 else:
1281 else:
1283 remote = None
1282 remote = None
1284 root = findoutgoing(ui, repo, remote, force, opts)
1283 root = findoutgoing(ui, repo, remote, force, opts)
1285 else:
1284 else:
1286 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs)))
1285 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs)))
1287 if len(rr) != 1:
1286 if len(rr) != 1:
1288 raise error.Abort(_('The specified revisions must have '
1287 raise error.Abort(_('The specified revisions must have '
1289 'exactly one common root'))
1288 'exactly one common root'))
1290 root = rr[0].node()
1289 root = rr[0].node()
1291
1290
1292 revs = between(repo, root, topmost, state.keep)
1291 revs = between(repo, root, topmost, state.keep)
1293 if not revs:
1292 if not revs:
1294 raise error.Abort(_('%s is not an ancestor of working directory') %
1293 raise error.Abort(_('%s is not an ancestor of working directory') %
1295 node.short(root))
1294 node.short(root))
1296
1295
1297 ctxs = [repo[r] for r in revs]
1296 ctxs = [repo[r] for r in revs]
1298 if not rules:
1297 if not rules:
1299 comment = geteditcomment(ui, node.short(root), node.short(topmost))
1298 comment = geteditcomment(ui, node.short(root), node.short(topmost))
1300 actions = [pick(state, r) for r in revs]
1299 actions = [pick(state, r) for r in revs]
1301 rules = ruleeditor(repo, ui, actions, comment)
1300 rules = ruleeditor(repo, ui, actions, comment)
1302 else:
1301 else:
1303 rules = _readfile(ui, rules)
1302 rules = _readfile(ui, rules)
1304 actions = parserules(rules, state)
1303 actions = parserules(rules, state)
1305 warnverifyactions(ui, repo, actions, state, ctxs)
1304 warnverifyactions(ui, repo, actions, state, ctxs)
1306
1305
1307 parentctxnode = repo[root].parents()[0].node()
1306 parentctxnode = repo[root].parents()[0].node()
1308
1307
1309 state.parentctxnode = parentctxnode
1308 state.parentctxnode = parentctxnode
1310 state.actions = actions
1309 state.actions = actions
1311 state.topmost = topmost
1310 state.topmost = topmost
1312 state.replacements = []
1311 state.replacements = []
1313
1312
1314 ui.log("histedit", "%d actions to histedit", len(actions),
1313 ui.log("histedit", "%d actions to histedit", len(actions),
1315 histedit_num_actions=len(actions))
1314 histedit_num_actions=len(actions))
1316
1315
1317 # Create a backup so we can always abort completely.
1316 # Create a backup so we can always abort completely.
1318 backupfile = None
1317 backupfile = None
1319 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1318 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1320 backupfile = repair._bundle(repo, [parentctxnode], [topmost], root,
1319 backupfile = repair._bundle(repo, [parentctxnode], [topmost], root,
1321 'histedit')
1320 'histedit')
1322 state.backupfile = backupfile
1321 state.backupfile = backupfile
1323
1322
1324 def _getsummary(ctx):
1323 def _getsummary(ctx):
1325 # a common pattern is to extract the summary but default to the empty
1324 # a common pattern is to extract the summary but default to the empty
1326 # string
1325 # string
1327 summary = ctx.description() or ''
1326 summary = ctx.description() or ''
1328 if summary:
1327 if summary:
1329 summary = summary.splitlines()[0]
1328 summary = summary.splitlines()[0]
1330 return summary
1329 return summary
1331
1330
1332 def bootstrapcontinue(ui, state, opts):
1331 def bootstrapcontinue(ui, state, opts):
1333 repo = state.repo
1332 repo = state.repo
1334
1333
1335 ms = mergemod.mergestate.read(repo)
1334 ms = mergemod.mergestate.read(repo)
1336 mergeutil.checkunresolved(ms)
1335 mergeutil.checkunresolved(ms)
1337
1336
1338 if state.actions:
1337 if state.actions:
1339 actobj = state.actions.pop(0)
1338 actobj = state.actions.pop(0)
1340
1339
1341 if _isdirtywc(repo):
1340 if _isdirtywc(repo):
1342 actobj.continuedirty()
1341 actobj.continuedirty()
1343 if _isdirtywc(repo):
1342 if _isdirtywc(repo):
1344 abortdirty()
1343 abortdirty()
1345
1344
1346 parentctx, replacements = actobj.continueclean()
1345 parentctx, replacements = actobj.continueclean()
1347
1346
1348 state.parentctxnode = parentctx.node()
1347 state.parentctxnode = parentctx.node()
1349 state.replacements.extend(replacements)
1348 state.replacements.extend(replacements)
1350
1349
1351 return state
1350 return state
1352
1351
1353 def between(repo, old, new, keep):
1352 def between(repo, old, new, keep):
1354 """select and validate the set of revision to edit
1353 """select and validate the set of revision to edit
1355
1354
1356 When keep is false, the specified set can't have children."""
1355 When keep is false, the specified set can't have children."""
1357 ctxs = list(repo.set('%n::%n', old, new))
1356 ctxs = list(repo.set('%n::%n', old, new))
1358 if ctxs and not keep:
1357 if ctxs and not keep:
1359 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
1358 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
1360 repo.revs('(%ld::) - (%ld)', ctxs, ctxs)):
1359 repo.revs('(%ld::) - (%ld)', ctxs, ctxs)):
1361 raise error.Abort(_('can only histedit a changeset together '
1360 raise error.Abort(_('can only histedit a changeset together '
1362 'with all its descendants'))
1361 'with all its descendants'))
1363 if repo.revs('(%ld) and merge()', ctxs):
1362 if repo.revs('(%ld) and merge()', ctxs):
1364 raise error.Abort(_('cannot edit history that contains merges'))
1363 raise error.Abort(_('cannot edit history that contains merges'))
1365 root = ctxs[0] # list is already sorted by repo.set
1364 root = ctxs[0] # list is already sorted by repo.set
1366 if not root.mutable():
1365 if not root.mutable():
1367 raise error.Abort(_('cannot edit public changeset: %s') % root,
1366 raise error.Abort(_('cannot edit public changeset: %s') % root,
1368 hint=_("see 'hg help phases' for details"))
1367 hint=_("see 'hg help phases' for details"))
1369 return [c.node() for c in ctxs]
1368 return [c.node() for c in ctxs]
1370
1369
1371 def ruleeditor(repo, ui, actions, editcomment=""):
1370 def ruleeditor(repo, ui, actions, editcomment=""):
1372 """open an editor to edit rules
1371 """open an editor to edit rules
1373
1372
1374 rules are in the format [ [act, ctx], ...] like in state.rules
1373 rules are in the format [ [act, ctx], ...] like in state.rules
1375 """
1374 """
1376 if repo.ui.configbool("experimental", "histedit.autoverb"):
1375 if repo.ui.configbool("experimental", "histedit.autoverb"):
1377 newact = util.sortdict()
1376 newact = util.sortdict()
1378 for act in actions:
1377 for act in actions:
1379 ctx = repo[act.node]
1378 ctx = repo[act.node]
1380 summary = _getsummary(ctx)
1379 summary = _getsummary(ctx)
1381 fword = summary.split(' ', 1)[0].lower()
1380 fword = summary.split(' ', 1)[0].lower()
1382 added = False
1381 added = False
1383
1382
1384 # if it doesn't end with the special character '!' just skip this
1383 # if it doesn't end with the special character '!' just skip this
1385 if fword.endswith('!'):
1384 if fword.endswith('!'):
1386 fword = fword[:-1]
1385 fword = fword[:-1]
1387 if fword in primaryactions | secondaryactions | tertiaryactions:
1386 if fword in primaryactions | secondaryactions | tertiaryactions:
1388 act.verb = fword
1387 act.verb = fword
1389 # get the target summary
1388 # get the target summary
1390 tsum = summary[len(fword) + 1:].lstrip()
1389 tsum = summary[len(fword) + 1:].lstrip()
1391 # safe but slow: reverse iterate over the actions so we
1390 # safe but slow: reverse iterate over the actions so we
1392 # don't clash on two commits having the same summary
1391 # don't clash on two commits having the same summary
1393 for na, l in reversed(list(newact.iteritems())):
1392 for na, l in reversed(list(newact.iteritems())):
1394 actx = repo[na.node]
1393 actx = repo[na.node]
1395 asum = _getsummary(actx)
1394 asum = _getsummary(actx)
1396 if asum == tsum:
1395 if asum == tsum:
1397 added = True
1396 added = True
1398 l.append(act)
1397 l.append(act)
1399 break
1398 break
1400
1399
1401 if not added:
1400 if not added:
1402 newact[act] = []
1401 newact[act] = []
1403
1402
1404 # copy over and flatten the new list
1403 # copy over and flatten the new list
1405 actions = []
1404 actions = []
1406 for na, l in newact.iteritems():
1405 for na, l in newact.iteritems():
1407 actions.append(na)
1406 actions.append(na)
1408 actions += l
1407 actions += l
1409
1408
1410 rules = '\n'.join([act.torule() for act in actions])
1409 rules = '\n'.join([act.torule() for act in actions])
1411 rules += '\n\n'
1410 rules += '\n\n'
1412 rules += editcomment
1411 rules += editcomment
1413 rules = ui.edit(rules, ui.username(), {'prefix': 'histedit'},
1412 rules = ui.edit(rules, ui.username(), {'prefix': 'histedit'},
1414 repopath=repo.path, action='histedit')
1413 repopath=repo.path, action='histedit')
1415
1414
1416 # Save edit rules in .hg/histedit-last-edit.txt in case
1415 # Save edit rules in .hg/histedit-last-edit.txt in case
1417 # the user needs to ask for help after something
1416 # the user needs to ask for help after something
1418 # surprising happens.
1417 # surprising happens.
1419 with repo.vfs('histedit-last-edit.txt', 'wb') as f:
1418 with repo.vfs('histedit-last-edit.txt', 'wb') as f:
1420 f.write(rules)
1419 f.write(rules)
1421
1420
1422 return rules
1421 return rules
1423
1422
1424 def parserules(rules, state):
1423 def parserules(rules, state):
1425 """Read the histedit rules string and return list of action objects """
1424 """Read the histedit rules string and return list of action objects """
1426 rules = [l for l in (r.strip() for r in rules.splitlines())
1425 rules = [l for l in (r.strip() for r in rules.splitlines())
1427 if l and not l.startswith('#')]
1426 if l and not l.startswith('#')]
1428 actions = []
1427 actions = []
1429 for r in rules:
1428 for r in rules:
1430 if ' ' not in r:
1429 if ' ' not in r:
1431 raise error.ParseError(_('malformed line "%s"') % r)
1430 raise error.ParseError(_('malformed line "%s"') % r)
1432 verb, rest = r.split(' ', 1)
1431 verb, rest = r.split(' ', 1)
1433
1432
1434 if verb not in actiontable:
1433 if verb not in actiontable:
1435 raise error.ParseError(_('unknown action "%s"') % verb)
1434 raise error.ParseError(_('unknown action "%s"') % verb)
1436
1435
1437 action = actiontable[verb].fromrule(state, rest)
1436 action = actiontable[verb].fromrule(state, rest)
1438 actions.append(action)
1437 actions.append(action)
1439 return actions
1438 return actions
1440
1439
1441 def warnverifyactions(ui, repo, actions, state, ctxs):
1440 def warnverifyactions(ui, repo, actions, state, ctxs):
1442 try:
1441 try:
1443 verifyactions(actions, state, ctxs)
1442 verifyactions(actions, state, ctxs)
1444 except error.ParseError:
1443 except error.ParseError:
1445 if repo.vfs.exists('histedit-last-edit.txt'):
1444 if repo.vfs.exists('histedit-last-edit.txt'):
1446 ui.warn(_('warning: histedit rules saved '
1445 ui.warn(_('warning: histedit rules saved '
1447 'to: .hg/histedit-last-edit.txt\n'))
1446 'to: .hg/histedit-last-edit.txt\n'))
1448 raise
1447 raise
1449
1448
1450 def verifyactions(actions, state, ctxs):
1449 def verifyactions(actions, state, ctxs):
1451 """Verify that there exists exactly one action per given changeset and
1450 """Verify that there exists exactly one action per given changeset and
1452 other constraints.
1451 other constraints.
1453
1452
1454 Will abort if there are to many or too few rules, a malformed rule,
1453 Will abort if there are to many or too few rules, a malformed rule,
1455 or a rule on a changeset outside of the user-given range.
1454 or a rule on a changeset outside of the user-given range.
1456 """
1455 """
1457 expected = set(c.node() for c in ctxs)
1456 expected = set(c.node() for c in ctxs)
1458 seen = set()
1457 seen = set()
1459 prev = None
1458 prev = None
1460
1459
1461 if actions and actions[0].verb in ['roll', 'fold']:
1460 if actions and actions[0].verb in ['roll', 'fold']:
1462 raise error.ParseError(_('first changeset cannot use verb "%s"') %
1461 raise error.ParseError(_('first changeset cannot use verb "%s"') %
1463 actions[0].verb)
1462 actions[0].verb)
1464
1463
1465 for action in actions:
1464 for action in actions:
1466 action.verify(prev, expected, seen)
1465 action.verify(prev, expected, seen)
1467 prev = action
1466 prev = action
1468 if action.node is not None:
1467 if action.node is not None:
1469 seen.add(action.node)
1468 seen.add(action.node)
1470 missing = sorted(expected - seen) # sort to stabilize output
1469 missing = sorted(expected - seen) # sort to stabilize output
1471
1470
1472 if state.repo.ui.configbool('histedit', 'dropmissing'):
1471 if state.repo.ui.configbool('histedit', 'dropmissing'):
1473 if len(actions) == 0:
1472 if len(actions) == 0:
1474 raise error.ParseError(_('no rules provided'),
1473 raise error.ParseError(_('no rules provided'),
1475 hint=_('use strip extension to remove commits'))
1474 hint=_('use strip extension to remove commits'))
1476
1475
1477 drops = [drop(state, n) for n in missing]
1476 drops = [drop(state, n) for n in missing]
1478 # put the in the beginning so they execute immediately and
1477 # put the in the beginning so they execute immediately and
1479 # don't show in the edit-plan in the future
1478 # don't show in the edit-plan in the future
1480 actions[:0] = drops
1479 actions[:0] = drops
1481 elif missing:
1480 elif missing:
1482 raise error.ParseError(_('missing rules for changeset %s') %
1481 raise error.ParseError(_('missing rules for changeset %s') %
1483 node.short(missing[0]),
1482 node.short(missing[0]),
1484 hint=_('use "drop %s" to discard, see also: '
1483 hint=_('use "drop %s" to discard, see also: '
1485 "'hg help -e histedit.config'")
1484 "'hg help -e histedit.config'")
1486 % node.short(missing[0]))
1485 % node.short(missing[0]))
1487
1486
1488 def adjustreplacementsfrommarkers(repo, oldreplacements):
1487 def adjustreplacementsfrommarkers(repo, oldreplacements):
1489 """Adjust replacements from obsolescence markers
1488 """Adjust replacements from obsolescence markers
1490
1489
1491 Replacements structure is originally generated based on
1490 Replacements structure is originally generated based on
1492 histedit's state and does not account for changes that are
1491 histedit's state and does not account for changes that are
1493 not recorded there. This function fixes that by adding
1492 not recorded there. This function fixes that by adding
1494 data read from obsolescence markers"""
1493 data read from obsolescence markers"""
1495 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1494 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1496 return oldreplacements
1495 return oldreplacements
1497
1496
1498 unfi = repo.unfiltered()
1497 unfi = repo.unfiltered()
1499 nm = unfi.changelog.nodemap
1498 nm = unfi.changelog.nodemap
1500 obsstore = repo.obsstore
1499 obsstore = repo.obsstore
1501 newreplacements = list(oldreplacements)
1500 newreplacements = list(oldreplacements)
1502 oldsuccs = [r[1] for r in oldreplacements]
1501 oldsuccs = [r[1] for r in oldreplacements]
1503 # successors that have already been added to succstocheck once
1502 # successors that have already been added to succstocheck once
1504 seensuccs = set().union(*oldsuccs) # create a set from an iterable of tuples
1503 seensuccs = set().union(*oldsuccs) # create a set from an iterable of tuples
1505 succstocheck = list(seensuccs)
1504 succstocheck = list(seensuccs)
1506 while succstocheck:
1505 while succstocheck:
1507 n = succstocheck.pop()
1506 n = succstocheck.pop()
1508 missing = nm.get(n) is None
1507 missing = nm.get(n) is None
1509 markers = obsstore.successors.get(n, ())
1508 markers = obsstore.successors.get(n, ())
1510 if missing and not markers:
1509 if missing and not markers:
1511 # dead end, mark it as such
1510 # dead end, mark it as such
1512 newreplacements.append((n, ()))
1511 newreplacements.append((n, ()))
1513 for marker in markers:
1512 for marker in markers:
1514 nsuccs = marker[1]
1513 nsuccs = marker[1]
1515 newreplacements.append((n, nsuccs))
1514 newreplacements.append((n, nsuccs))
1516 for nsucc in nsuccs:
1515 for nsucc in nsuccs:
1517 if nsucc not in seensuccs:
1516 if nsucc not in seensuccs:
1518 seensuccs.add(nsucc)
1517 seensuccs.add(nsucc)
1519 succstocheck.append(nsucc)
1518 succstocheck.append(nsucc)
1520
1519
1521 return newreplacements
1520 return newreplacements
1522
1521
1523 def processreplacement(state):
1522 def processreplacement(state):
1524 """process the list of replacements to return
1523 """process the list of replacements to return
1525
1524
1526 1) the final mapping between original and created nodes
1525 1) the final mapping between original and created nodes
1527 2) the list of temporary node created by histedit
1526 2) the list of temporary node created by histedit
1528 3) the list of new commit created by histedit"""
1527 3) the list of new commit created by histedit"""
1529 replacements = adjustreplacementsfrommarkers(state.repo, state.replacements)
1528 replacements = adjustreplacementsfrommarkers(state.repo, state.replacements)
1530 allsuccs = set()
1529 allsuccs = set()
1531 replaced = set()
1530 replaced = set()
1532 fullmapping = {}
1531 fullmapping = {}
1533 # initialize basic set
1532 # initialize basic set
1534 # fullmapping records all operations recorded in replacement
1533 # fullmapping records all operations recorded in replacement
1535 for rep in replacements:
1534 for rep in replacements:
1536 allsuccs.update(rep[1])
1535 allsuccs.update(rep[1])
1537 replaced.add(rep[0])
1536 replaced.add(rep[0])
1538 fullmapping.setdefault(rep[0], set()).update(rep[1])
1537 fullmapping.setdefault(rep[0], set()).update(rep[1])
1539 new = allsuccs - replaced
1538 new = allsuccs - replaced
1540 tmpnodes = allsuccs & replaced
1539 tmpnodes = allsuccs & replaced
1541 # Reduce content fullmapping into direct relation between original nodes
1540 # Reduce content fullmapping into direct relation between original nodes
1542 # and final node created during history edition
1541 # and final node created during history edition
1543 # Dropped changeset are replaced by an empty list
1542 # Dropped changeset are replaced by an empty list
1544 toproceed = set(fullmapping)
1543 toproceed = set(fullmapping)
1545 final = {}
1544 final = {}
1546 while toproceed:
1545 while toproceed:
1547 for x in list(toproceed):
1546 for x in list(toproceed):
1548 succs = fullmapping[x]
1547 succs = fullmapping[x]
1549 for s in list(succs):
1548 for s in list(succs):
1550 if s in toproceed:
1549 if s in toproceed:
1551 # non final node with unknown closure
1550 # non final node with unknown closure
1552 # We can't process this now
1551 # We can't process this now
1553 break
1552 break
1554 elif s in final:
1553 elif s in final:
1555 # non final node, replace with closure
1554 # non final node, replace with closure
1556 succs.remove(s)
1555 succs.remove(s)
1557 succs.update(final[s])
1556 succs.update(final[s])
1558 else:
1557 else:
1559 final[x] = succs
1558 final[x] = succs
1560 toproceed.remove(x)
1559 toproceed.remove(x)
1561 # remove tmpnodes from final mapping
1560 # remove tmpnodes from final mapping
1562 for n in tmpnodes:
1561 for n in tmpnodes:
1563 del final[n]
1562 del final[n]
1564 # we expect all changes involved in final to exist in the repo
1563 # we expect all changes involved in final to exist in the repo
1565 # turn `final` into list (topologically sorted)
1564 # turn `final` into list (topologically sorted)
1566 nm = state.repo.changelog.nodemap
1565 nm = state.repo.changelog.nodemap
1567 for prec, succs in final.items():
1566 for prec, succs in final.items():
1568 final[prec] = sorted(succs, key=nm.get)
1567 final[prec] = sorted(succs, key=nm.get)
1569
1568
1570 # computed topmost element (necessary for bookmark)
1569 # computed topmost element (necessary for bookmark)
1571 if new:
1570 if new:
1572 newtopmost = sorted(new, key=state.repo.changelog.rev)[-1]
1571 newtopmost = sorted(new, key=state.repo.changelog.rev)[-1]
1573 elif not final:
1572 elif not final:
1574 # Nothing rewritten at all. we won't need `newtopmost`
1573 # Nothing rewritten at all. we won't need `newtopmost`
1575 # It is the same as `oldtopmost` and `processreplacement` know it
1574 # It is the same as `oldtopmost` and `processreplacement` know it
1576 newtopmost = None
1575 newtopmost = None
1577 else:
1576 else:
1578 # every body died. The newtopmost is the parent of the root.
1577 # every body died. The newtopmost is the parent of the root.
1579 r = state.repo.changelog.rev
1578 r = state.repo.changelog.rev
1580 newtopmost = state.repo[sorted(final, key=r)[0]].p1().node()
1579 newtopmost = state.repo[sorted(final, key=r)[0]].p1().node()
1581
1580
1582 return final, tmpnodes, new, newtopmost
1581 return final, tmpnodes, new, newtopmost
1583
1582
1584 def movetopmostbookmarks(repo, oldtopmost, newtopmost):
1583 def movetopmostbookmarks(repo, oldtopmost, newtopmost):
1585 """Move bookmark from oldtopmost to newly created topmost
1584 """Move bookmark from oldtopmost to newly created topmost
1586
1585
1587 This is arguably a feature and we may only want that for the active
1586 This is arguably a feature and we may only want that for the active
1588 bookmark. But the behavior is kept compatible with the old version for now.
1587 bookmark. But the behavior is kept compatible with the old version for now.
1589 """
1588 """
1590 if not oldtopmost or not newtopmost:
1589 if not oldtopmost or not newtopmost:
1591 return
1590 return
1592 oldbmarks = repo.nodebookmarks(oldtopmost)
1591 oldbmarks = repo.nodebookmarks(oldtopmost)
1593 if oldbmarks:
1592 if oldbmarks:
1594 with repo.lock(), repo.transaction('histedit') as tr:
1593 with repo.lock(), repo.transaction('histedit') as tr:
1595 marks = repo._bookmarks
1594 marks = repo._bookmarks
1596 changes = []
1595 changes = []
1597 for name in oldbmarks:
1596 for name in oldbmarks:
1598 changes.append((name, newtopmost))
1597 changes.append((name, newtopmost))
1599 marks.applychanges(repo, tr, changes)
1598 marks.applychanges(repo, tr, changes)
1600
1599
1601 def cleanupnode(ui, repo, nodes):
1600 def cleanupnode(ui, repo, nodes):
1602 """strip a group of nodes from the repository
1601 """strip a group of nodes from the repository
1603
1602
1604 The set of node to strip may contains unknown nodes."""
1603 The set of node to strip may contains unknown nodes."""
1605 with repo.lock():
1604 with repo.lock():
1606 # do not let filtering get in the way of the cleanse
1605 # do not let filtering get in the way of the cleanse
1607 # we should probably get rid of obsolescence marker created during the
1606 # we should probably get rid of obsolescence marker created during the
1608 # histedit, but we currently do not have such information.
1607 # histedit, but we currently do not have such information.
1609 repo = repo.unfiltered()
1608 repo = repo.unfiltered()
1610 # Find all nodes that need to be stripped
1609 # Find all nodes that need to be stripped
1611 # (we use %lr instead of %ln to silently ignore unknown items)
1610 # (we use %lr instead of %ln to silently ignore unknown items)
1612 nm = repo.changelog.nodemap
1611 nm = repo.changelog.nodemap
1613 nodes = sorted(n for n in nodes if n in nm)
1612 nodes = sorted(n for n in nodes if n in nm)
1614 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
1613 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
1615 if roots:
1614 if roots:
1616 repair.strip(ui, repo, roots)
1615 repair.strip(ui, repo, roots)
1617
1616
1618 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
1617 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
1619 if isinstance(nodelist, str):
1618 if isinstance(nodelist, str):
1620 nodelist = [nodelist]
1619 nodelist = [nodelist]
1621 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
1620 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
1622 state = histeditstate(repo)
1621 state = histeditstate(repo)
1623 state.read()
1622 state.read()
1624 histedit_nodes = {action.node for action
1623 histedit_nodes = {action.node for action
1625 in state.actions if action.node}
1624 in state.actions if action.node}
1626 common_nodes = histedit_nodes & set(nodelist)
1625 common_nodes = histedit_nodes & set(nodelist)
1627 if common_nodes:
1626 if common_nodes:
1628 raise error.Abort(_("histedit in progress, can't strip %s")
1627 raise error.Abort(_("histedit in progress, can't strip %s")
1629 % ', '.join(node.short(x) for x in common_nodes))
1628 % ', '.join(node.short(x) for x in common_nodes))
1630 return orig(ui, repo, nodelist, *args, **kwargs)
1629 return orig(ui, repo, nodelist, *args, **kwargs)
1631
1630
1632 extensions.wrapfunction(repair, 'strip', stripwrapper)
1631 extensions.wrapfunction(repair, 'strip', stripwrapper)
1633
1632
1634 def summaryhook(ui, repo):
1633 def summaryhook(ui, repo):
1635 if not os.path.exists(repo.vfs.join('histedit-state')):
1634 if not os.path.exists(repo.vfs.join('histedit-state')):
1636 return
1635 return
1637 state = histeditstate(repo)
1636 state = histeditstate(repo)
1638 state.read()
1637 state.read()
1639 if state.actions:
1638 if state.actions:
1640 # i18n: column positioning for "hg summary"
1639 # i18n: column positioning for "hg summary"
1641 ui.write(_('hist: %s (histedit --continue)\n') %
1640 ui.write(_('hist: %s (histedit --continue)\n') %
1642 (ui.label(_('%d remaining'), 'histedit.remaining') %
1641 (ui.label(_('%d remaining'), 'histedit.remaining') %
1643 len(state.actions)))
1642 len(state.actions)))
1644
1643
1645 def extsetup(ui):
1644 def extsetup(ui):
1646 cmdutil.summaryhooks.add('histedit', summaryhook)
1645 cmdutil.summaryhooks.add('histedit', summaryhook)
1647 cmdutil.unfinishedstates.append(
1646 cmdutil.unfinishedstates.append(
1648 ['histedit-state', False, True, _('histedit in progress'),
1647 ['histedit-state', False, True, _('histedit in progress'),
1649 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
1648 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
1650 cmdutil.afterresolvedstates.append(
1649 cmdutil.afterresolvedstates.append(
1651 ['histedit-state', _('hg histedit --continue')])
1650 ['histedit-state', _('hg histedit --continue')])
@@ -1,35 +1,42 b''
1 # node.py - basic nodeid manipulation for mercurial
1 # node.py - basic nodeid manipulation for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import binascii
10 import binascii
11
11
12 # This ugly style has a noticeable effect in manifest parsing
12 # This ugly style has a noticeable effect in manifest parsing
13 hex = binascii.hexlify
13 hex = binascii.hexlify
14 bin = binascii.unhexlify
14 # Adapt to Python 3 API changes. If this ends up showing up in
15 # profiles, we can use this version only on Python 3, and forward
16 # binascii.unhexlify like we used to on Python 2.
17 def bin(s):
18 try:
19 return binascii.unhexlify(s)
20 except binascii.Error as e:
21 raise TypeError(e)
15
22
16 nullrev = -1
23 nullrev = -1
17 nullid = b"\0" * 20
24 nullid = b"\0" * 20
18 nullhex = hex(nullid)
25 nullhex = hex(nullid)
19
26
20 # Phony node value to stand-in for new files in some uses of
27 # Phony node value to stand-in for new files in some uses of
21 # manifests.
28 # manifests.
22 newnodeid = '!' * 20
29 newnodeid = '!' * 20
23 addednodeid = ('0' * 15) + 'added'
30 addednodeid = ('0' * 15) + 'added'
24 modifiednodeid = ('0' * 12) + 'modified'
31 modifiednodeid = ('0' * 12) + 'modified'
25
32
26 wdirnodes = {newnodeid, addednodeid, modifiednodeid}
33 wdirnodes = {newnodeid, addednodeid, modifiednodeid}
27
34
28 # pseudo identifiers for working directory
35 # pseudo identifiers for working directory
29 # (they are experimental, so don't add too many dependencies on them)
36 # (they are experimental, so don't add too many dependencies on them)
30 wdirrev = 0x7fffffff
37 wdirrev = 0x7fffffff
31 wdirid = b"\xff" * 20
38 wdirid = b"\xff" * 20
32 wdirhex = hex(wdirid)
39 wdirhex = hex(wdirid)
33
40
34 def short(node):
41 def short(node):
35 return hex(node[:6])
42 return hex(node[:6])
@@ -1,2501 +1,2500 b''
1 # revlog.py - storage back-end for mercurial
1 # revlog.py - storage back-end for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Storage back-end for Mercurial.
8 """Storage back-end for Mercurial.
9
9
10 This provides efficient delta storage with O(1) retrieve and append
10 This provides efficient delta storage with O(1) retrieve and append
11 and O(changes) merge between branches.
11 and O(changes) merge between branches.
12 """
12 """
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import binascii
17 import collections
16 import collections
18 import contextlib
17 import contextlib
19 import errno
18 import errno
20 import hashlib
19 import hashlib
21 import heapq
20 import heapq
22 import os
21 import os
23 import struct
22 import struct
24 import zlib
23 import zlib
25
24
26 # import stuff from node for others to import from revlog
25 # import stuff from node for others to import from revlog
27 from .node import (
26 from .node import (
28 bin,
27 bin,
29 hex,
28 hex,
30 nullid,
29 nullid,
31 nullrev,
30 nullrev,
32 wdirhex,
31 wdirhex,
33 wdirid,
32 wdirid,
34 wdirrev,
33 wdirrev,
35 )
34 )
36 from .i18n import _
35 from .i18n import _
37 from .thirdparty import (
36 from .thirdparty import (
38 attr,
37 attr,
39 )
38 )
40 from . import (
39 from . import (
41 ancestor,
40 ancestor,
42 error,
41 error,
43 mdiff,
42 mdiff,
44 policy,
43 policy,
45 pycompat,
44 pycompat,
46 templatefilters,
45 templatefilters,
47 util,
46 util,
48 )
47 )
49
48
50 parsers = policy.importmod(r'parsers')
49 parsers = policy.importmod(r'parsers')
51
50
52 # Aliased for performance.
51 # Aliased for performance.
53 _zlibdecompress = zlib.decompress
52 _zlibdecompress = zlib.decompress
54
53
55 # revlog header flags
54 # revlog header flags
56 REVLOGV0 = 0
55 REVLOGV0 = 0
57 REVLOGV1 = 1
56 REVLOGV1 = 1
58 # Dummy value until file format is finalized.
57 # Dummy value until file format is finalized.
59 # Reminder: change the bounds check in revlog.__init__ when this is changed.
58 # Reminder: change the bounds check in revlog.__init__ when this is changed.
60 REVLOGV2 = 0xDEAD
59 REVLOGV2 = 0xDEAD
61 FLAG_INLINE_DATA = (1 << 16)
60 FLAG_INLINE_DATA = (1 << 16)
62 FLAG_GENERALDELTA = (1 << 17)
61 FLAG_GENERALDELTA = (1 << 17)
63 REVLOG_DEFAULT_FLAGS = FLAG_INLINE_DATA
62 REVLOG_DEFAULT_FLAGS = FLAG_INLINE_DATA
64 REVLOG_DEFAULT_FORMAT = REVLOGV1
63 REVLOG_DEFAULT_FORMAT = REVLOGV1
65 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
64 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
66 REVLOGV1_FLAGS = FLAG_INLINE_DATA | FLAG_GENERALDELTA
65 REVLOGV1_FLAGS = FLAG_INLINE_DATA | FLAG_GENERALDELTA
67 REVLOGV2_FLAGS = REVLOGV1_FLAGS
66 REVLOGV2_FLAGS = REVLOGV1_FLAGS
68
67
69 # revlog index flags
68 # revlog index flags
70 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
69 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
71 REVIDX_ELLIPSIS = (1 << 14) # revision hash does not match data (narrowhg)
70 REVIDX_ELLIPSIS = (1 << 14) # revision hash does not match data (narrowhg)
72 REVIDX_EXTSTORED = (1 << 13) # revision data is stored externally
71 REVIDX_EXTSTORED = (1 << 13) # revision data is stored externally
73 REVIDX_DEFAULT_FLAGS = 0
72 REVIDX_DEFAULT_FLAGS = 0
74 # stable order in which flags need to be processed and their processors applied
73 # stable order in which flags need to be processed and their processors applied
75 REVIDX_FLAGS_ORDER = [
74 REVIDX_FLAGS_ORDER = [
76 REVIDX_ISCENSORED,
75 REVIDX_ISCENSORED,
77 REVIDX_ELLIPSIS,
76 REVIDX_ELLIPSIS,
78 REVIDX_EXTSTORED,
77 REVIDX_EXTSTORED,
79 ]
78 ]
80 REVIDX_KNOWN_FLAGS = util.bitsfrom(REVIDX_FLAGS_ORDER)
79 REVIDX_KNOWN_FLAGS = util.bitsfrom(REVIDX_FLAGS_ORDER)
81
80
82 # max size of revlog with inline data
81 # max size of revlog with inline data
83 _maxinline = 131072
82 _maxinline = 131072
84 _chunksize = 1048576
83 _chunksize = 1048576
85
84
86 RevlogError = error.RevlogError
85 RevlogError = error.RevlogError
87 LookupError = error.LookupError
86 LookupError = error.LookupError
88 CensoredNodeError = error.CensoredNodeError
87 CensoredNodeError = error.CensoredNodeError
89 ProgrammingError = error.ProgrammingError
88 ProgrammingError = error.ProgrammingError
90
89
91 # Store flag processors (cf. 'addflagprocessor()' to register)
90 # Store flag processors (cf. 'addflagprocessor()' to register)
92 _flagprocessors = {
91 _flagprocessors = {
93 REVIDX_ISCENSORED: None,
92 REVIDX_ISCENSORED: None,
94 }
93 }
95
94
96 def addflagprocessor(flag, processor):
95 def addflagprocessor(flag, processor):
97 """Register a flag processor on a revision data flag.
96 """Register a flag processor on a revision data flag.
98
97
99 Invariant:
98 Invariant:
100 - Flags need to be defined in REVIDX_KNOWN_FLAGS and REVIDX_FLAGS_ORDER.
99 - Flags need to be defined in REVIDX_KNOWN_FLAGS and REVIDX_FLAGS_ORDER.
101 - Only one flag processor can be registered on a specific flag.
100 - Only one flag processor can be registered on a specific flag.
102 - flagprocessors must be 3-tuples of functions (read, write, raw) with the
101 - flagprocessors must be 3-tuples of functions (read, write, raw) with the
103 following signatures:
102 following signatures:
104 - (read) f(self, rawtext) -> text, bool
103 - (read) f(self, rawtext) -> text, bool
105 - (write) f(self, text) -> rawtext, bool
104 - (write) f(self, text) -> rawtext, bool
106 - (raw) f(self, rawtext) -> bool
105 - (raw) f(self, rawtext) -> bool
107 "text" is presented to the user. "rawtext" is stored in revlog data, not
106 "text" is presented to the user. "rawtext" is stored in revlog data, not
108 directly visible to the user.
107 directly visible to the user.
109 The boolean returned by these transforms is used to determine whether
108 The boolean returned by these transforms is used to determine whether
110 the returned text can be used for hash integrity checking. For example,
109 the returned text can be used for hash integrity checking. For example,
111 if "write" returns False, then "text" is used to generate hash. If
110 if "write" returns False, then "text" is used to generate hash. If
112 "write" returns True, that basically means "rawtext" returned by "write"
111 "write" returns True, that basically means "rawtext" returned by "write"
113 should be used to generate hash. Usually, "write" and "read" return
112 should be used to generate hash. Usually, "write" and "read" return
114 different booleans. And "raw" returns a same boolean as "write".
113 different booleans. And "raw" returns a same boolean as "write".
115
114
116 Note: The 'raw' transform is used for changegroup generation and in some
115 Note: The 'raw' transform is used for changegroup generation and in some
117 debug commands. In this case the transform only indicates whether the
116 debug commands. In this case the transform only indicates whether the
118 contents can be used for hash integrity checks.
117 contents can be used for hash integrity checks.
119 """
118 """
120 if not flag & REVIDX_KNOWN_FLAGS:
119 if not flag & REVIDX_KNOWN_FLAGS:
121 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
120 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
122 raise ProgrammingError(msg)
121 raise ProgrammingError(msg)
123 if flag not in REVIDX_FLAGS_ORDER:
122 if flag not in REVIDX_FLAGS_ORDER:
124 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
123 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
125 raise ProgrammingError(msg)
124 raise ProgrammingError(msg)
126 if flag in _flagprocessors:
125 if flag in _flagprocessors:
127 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
126 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
128 raise error.Abort(msg)
127 raise error.Abort(msg)
129 _flagprocessors[flag] = processor
128 _flagprocessors[flag] = processor
130
129
131 def getoffset(q):
130 def getoffset(q):
132 return int(q >> 16)
131 return int(q >> 16)
133
132
134 def gettype(q):
133 def gettype(q):
135 return int(q & 0xFFFF)
134 return int(q & 0xFFFF)
136
135
137 def offset_type(offset, type):
136 def offset_type(offset, type):
138 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
137 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
139 raise ValueError('unknown revlog index flags')
138 raise ValueError('unknown revlog index flags')
140 return int(int(offset) << 16 | type)
139 return int(int(offset) << 16 | type)
141
140
142 _nullhash = hashlib.sha1(nullid)
141 _nullhash = hashlib.sha1(nullid)
143
142
144 def hash(text, p1, p2):
143 def hash(text, p1, p2):
145 """generate a hash from the given text and its parent hashes
144 """generate a hash from the given text and its parent hashes
146
145
147 This hash combines both the current file contents and its history
146 This hash combines both the current file contents and its history
148 in a manner that makes it easy to distinguish nodes with the same
147 in a manner that makes it easy to distinguish nodes with the same
149 content in the revision graph.
148 content in the revision graph.
150 """
149 """
151 # As of now, if one of the parent node is null, p2 is null
150 # As of now, if one of the parent node is null, p2 is null
152 if p2 == nullid:
151 if p2 == nullid:
153 # deep copy of a hash is faster than creating one
152 # deep copy of a hash is faster than creating one
154 s = _nullhash.copy()
153 s = _nullhash.copy()
155 s.update(p1)
154 s.update(p1)
156 else:
155 else:
157 # none of the parent nodes are nullid
156 # none of the parent nodes are nullid
158 if p1 < p2:
157 if p1 < p2:
159 a = p1
158 a = p1
160 b = p2
159 b = p2
161 else:
160 else:
162 a = p2
161 a = p2
163 b = p1
162 b = p1
164 s = hashlib.sha1(a)
163 s = hashlib.sha1(a)
165 s.update(b)
164 s.update(b)
166 s.update(text)
165 s.update(text)
167 return s.digest()
166 return s.digest()
168
167
169 def _trimchunk(revlog, revs, startidx, endidx=None):
168 def _trimchunk(revlog, revs, startidx, endidx=None):
170 """returns revs[startidx:endidx] without empty trailing revs
169 """returns revs[startidx:endidx] without empty trailing revs
171 """
170 """
172 length = revlog.length
171 length = revlog.length
173
172
174 if endidx is None:
173 if endidx is None:
175 endidx = len(revs)
174 endidx = len(revs)
176
175
177 # Trim empty revs at the end, but never the very first revision of a chain
176 # Trim empty revs at the end, but never the very first revision of a chain
178 while endidx > 1 and endidx > startidx and length(revs[endidx - 1]) == 0:
177 while endidx > 1 and endidx > startidx and length(revs[endidx - 1]) == 0:
179 endidx -= 1
178 endidx -= 1
180
179
181 return revs[startidx:endidx]
180 return revs[startidx:endidx]
182
181
183 def _slicechunk(revlog, revs):
182 def _slicechunk(revlog, revs):
184 """slice revs to reduce the amount of unrelated data to be read from disk.
183 """slice revs to reduce the amount of unrelated data to be read from disk.
185
184
186 ``revs`` is sliced into groups that should be read in one time.
185 ``revs`` is sliced into groups that should be read in one time.
187 Assume that revs are sorted.
186 Assume that revs are sorted.
188 """
187 """
189 start = revlog.start
188 start = revlog.start
190 length = revlog.length
189 length = revlog.length
191
190
192 if len(revs) <= 1:
191 if len(revs) <= 1:
193 yield revs
192 yield revs
194 return
193 return
195
194
196 startbyte = start(revs[0])
195 startbyte = start(revs[0])
197 endbyte = start(revs[-1]) + length(revs[-1])
196 endbyte = start(revs[-1]) + length(revs[-1])
198 readdata = deltachainspan = endbyte - startbyte
197 readdata = deltachainspan = endbyte - startbyte
199
198
200 chainpayload = sum(length(r) for r in revs)
199 chainpayload = sum(length(r) for r in revs)
201
200
202 if deltachainspan:
201 if deltachainspan:
203 density = chainpayload / float(deltachainspan)
202 density = chainpayload / float(deltachainspan)
204 else:
203 else:
205 density = 1.0
204 density = 1.0
206
205
207 # Store the gaps in a heap to have them sorted by decreasing size
206 # Store the gaps in a heap to have them sorted by decreasing size
208 gapsheap = []
207 gapsheap = []
209 heapq.heapify(gapsheap)
208 heapq.heapify(gapsheap)
210 prevend = None
209 prevend = None
211 for i, rev in enumerate(revs):
210 for i, rev in enumerate(revs):
212 revstart = start(rev)
211 revstart = start(rev)
213 revlen = length(rev)
212 revlen = length(rev)
214
213
215 # Skip empty revisions to form larger holes
214 # Skip empty revisions to form larger holes
216 if revlen == 0:
215 if revlen == 0:
217 continue
216 continue
218
217
219 if prevend is not None:
218 if prevend is not None:
220 gapsize = revstart - prevend
219 gapsize = revstart - prevend
221 # only consider holes that are large enough
220 # only consider holes that are large enough
222 if gapsize > revlog._srmingapsize:
221 if gapsize > revlog._srmingapsize:
223 heapq.heappush(gapsheap, (-gapsize, i))
222 heapq.heappush(gapsheap, (-gapsize, i))
224
223
225 prevend = revstart + revlen
224 prevend = revstart + revlen
226
225
227 # Collect the indices of the largest holes until the density is acceptable
226 # Collect the indices of the largest holes until the density is acceptable
228 indicesheap = []
227 indicesheap = []
229 heapq.heapify(indicesheap)
228 heapq.heapify(indicesheap)
230 while gapsheap and density < revlog._srdensitythreshold:
229 while gapsheap and density < revlog._srdensitythreshold:
231 oppgapsize, gapidx = heapq.heappop(gapsheap)
230 oppgapsize, gapidx = heapq.heappop(gapsheap)
232
231
233 heapq.heappush(indicesheap, gapidx)
232 heapq.heappush(indicesheap, gapidx)
234
233
235 # the gap sizes are stored as negatives to be sorted decreasingly
234 # the gap sizes are stored as negatives to be sorted decreasingly
236 # by the heap
235 # by the heap
237 readdata -= (-oppgapsize)
236 readdata -= (-oppgapsize)
238 if readdata > 0:
237 if readdata > 0:
239 density = chainpayload / float(readdata)
238 density = chainpayload / float(readdata)
240 else:
239 else:
241 density = 1.0
240 density = 1.0
242
241
243 # Cut the revs at collected indices
242 # Cut the revs at collected indices
244 previdx = 0
243 previdx = 0
245 while indicesheap:
244 while indicesheap:
246 idx = heapq.heappop(indicesheap)
245 idx = heapq.heappop(indicesheap)
247
246
248 chunk = _trimchunk(revlog, revs, previdx, idx)
247 chunk = _trimchunk(revlog, revs, previdx, idx)
249 if chunk:
248 if chunk:
250 yield chunk
249 yield chunk
251
250
252 previdx = idx
251 previdx = idx
253
252
254 chunk = _trimchunk(revlog, revs, previdx)
253 chunk = _trimchunk(revlog, revs, previdx)
255 if chunk:
254 if chunk:
256 yield chunk
255 yield chunk
257
256
258 @attr.s(slots=True, frozen=True)
257 @attr.s(slots=True, frozen=True)
259 class _deltainfo(object):
258 class _deltainfo(object):
260 distance = attr.ib()
259 distance = attr.ib()
261 deltalen = attr.ib()
260 deltalen = attr.ib()
262 data = attr.ib()
261 data = attr.ib()
263 base = attr.ib()
262 base = attr.ib()
264 chainbase = attr.ib()
263 chainbase = attr.ib()
265 chainlen = attr.ib()
264 chainlen = attr.ib()
266 compresseddeltalen = attr.ib()
265 compresseddeltalen = attr.ib()
267
266
268 class _deltacomputer(object):
267 class _deltacomputer(object):
269 def __init__(self, revlog):
268 def __init__(self, revlog):
270 self.revlog = revlog
269 self.revlog = revlog
271
270
272 def _getcandidaterevs(self, p1, p2, cachedelta):
271 def _getcandidaterevs(self, p1, p2, cachedelta):
273 """
272 """
274 Provides revisions that present an interest to be diffed against,
273 Provides revisions that present an interest to be diffed against,
275 grouped by level of easiness.
274 grouped by level of easiness.
276 """
275 """
277 revlog = self.revlog
276 revlog = self.revlog
278 curr = len(revlog)
277 curr = len(revlog)
279 prev = curr - 1
278 prev = curr - 1
280 p1r, p2r = revlog.rev(p1), revlog.rev(p2)
279 p1r, p2r = revlog.rev(p1), revlog.rev(p2)
281
280
282 # should we try to build a delta?
281 # should we try to build a delta?
283 if prev != nullrev and revlog.storedeltachains:
282 if prev != nullrev and revlog.storedeltachains:
284 tested = set()
283 tested = set()
285 # This condition is true most of the time when processing
284 # This condition is true most of the time when processing
286 # changegroup data into a generaldelta repo. The only time it
285 # changegroup data into a generaldelta repo. The only time it
287 # isn't true is if this is the first revision in a delta chain
286 # isn't true is if this is the first revision in a delta chain
288 # or if ``format.generaldelta=true`` disabled ``lazydeltabase``.
287 # or if ``format.generaldelta=true`` disabled ``lazydeltabase``.
289 if cachedelta and revlog._generaldelta and revlog._lazydeltabase:
288 if cachedelta and revlog._generaldelta and revlog._lazydeltabase:
290 # Assume what we received from the server is a good choice
289 # Assume what we received from the server is a good choice
291 # build delta will reuse the cache
290 # build delta will reuse the cache
292 yield (cachedelta[0],)
291 yield (cachedelta[0],)
293 tested.add(cachedelta[0])
292 tested.add(cachedelta[0])
294
293
295 if revlog._generaldelta:
294 if revlog._generaldelta:
296 # exclude already lazy tested base if any
295 # exclude already lazy tested base if any
297 parents = [p for p in (p1r, p2r)
296 parents = [p for p in (p1r, p2r)
298 if p != nullrev and p not in tested]
297 if p != nullrev and p not in tested]
299 if parents and not revlog._aggressivemergedeltas:
298 if parents and not revlog._aggressivemergedeltas:
300 # Pick whichever parent is closer to us (to minimize the
299 # Pick whichever parent is closer to us (to minimize the
301 # chance of having to build a fulltext).
300 # chance of having to build a fulltext).
302 parents = [max(parents)]
301 parents = [max(parents)]
303 tested.update(parents)
302 tested.update(parents)
304 yield parents
303 yield parents
305
304
306 if prev not in tested:
305 if prev not in tested:
307 # other approach failed try against prev to hopefully save us a
306 # other approach failed try against prev to hopefully save us a
308 # fulltext.
307 # fulltext.
309 yield (prev,)
308 yield (prev,)
310
309
311 def buildtext(self, revinfo, fh):
310 def buildtext(self, revinfo, fh):
312 """Builds a fulltext version of a revision
311 """Builds a fulltext version of a revision
313
312
314 revinfo: _revisioninfo instance that contains all needed info
313 revinfo: _revisioninfo instance that contains all needed info
315 fh: file handle to either the .i or the .d revlog file,
314 fh: file handle to either the .i or the .d revlog file,
316 depending on whether it is inlined or not
315 depending on whether it is inlined or not
317 """
316 """
318 btext = revinfo.btext
317 btext = revinfo.btext
319 if btext[0] is not None:
318 if btext[0] is not None:
320 return btext[0]
319 return btext[0]
321
320
322 revlog = self.revlog
321 revlog = self.revlog
323 cachedelta = revinfo.cachedelta
322 cachedelta = revinfo.cachedelta
324 flags = revinfo.flags
323 flags = revinfo.flags
325 node = revinfo.node
324 node = revinfo.node
326
325
327 baserev = cachedelta[0]
326 baserev = cachedelta[0]
328 delta = cachedelta[1]
327 delta = cachedelta[1]
329 # special case deltas which replace entire base; no need to decode
328 # special case deltas which replace entire base; no need to decode
330 # base revision. this neatly avoids censored bases, which throw when
329 # base revision. this neatly avoids censored bases, which throw when
331 # they're decoded.
330 # they're decoded.
332 hlen = struct.calcsize(">lll")
331 hlen = struct.calcsize(">lll")
333 if delta[:hlen] == mdiff.replacediffheader(revlog.rawsize(baserev),
332 if delta[:hlen] == mdiff.replacediffheader(revlog.rawsize(baserev),
334 len(delta) - hlen):
333 len(delta) - hlen):
335 btext[0] = delta[hlen:]
334 btext[0] = delta[hlen:]
336 else:
335 else:
337 basetext = revlog.revision(baserev, _df=fh, raw=True)
336 basetext = revlog.revision(baserev, _df=fh, raw=True)
338 btext[0] = mdiff.patch(basetext, delta)
337 btext[0] = mdiff.patch(basetext, delta)
339
338
340 try:
339 try:
341 res = revlog._processflags(btext[0], flags, 'read', raw=True)
340 res = revlog._processflags(btext[0], flags, 'read', raw=True)
342 btext[0], validatehash = res
341 btext[0], validatehash = res
343 if validatehash:
342 if validatehash:
344 revlog.checkhash(btext[0], node, p1=revinfo.p1, p2=revinfo.p2)
343 revlog.checkhash(btext[0], node, p1=revinfo.p1, p2=revinfo.p2)
345 if flags & REVIDX_ISCENSORED:
344 if flags & REVIDX_ISCENSORED:
346 raise RevlogError(_('node %s is not censored') % node)
345 raise RevlogError(_('node %s is not censored') % node)
347 except CensoredNodeError:
346 except CensoredNodeError:
348 # must pass the censored index flag to add censored revisions
347 # must pass the censored index flag to add censored revisions
349 if not flags & REVIDX_ISCENSORED:
348 if not flags & REVIDX_ISCENSORED:
350 raise
349 raise
351 return btext[0]
350 return btext[0]
352
351
353 def _builddeltadiff(self, base, revinfo, fh):
352 def _builddeltadiff(self, base, revinfo, fh):
354 revlog = self.revlog
353 revlog = self.revlog
355 t = self.buildtext(revinfo, fh)
354 t = self.buildtext(revinfo, fh)
356 if revlog.iscensored(base):
355 if revlog.iscensored(base):
357 # deltas based on a censored revision must replace the
356 # deltas based on a censored revision must replace the
358 # full content in one patch, so delta works everywhere
357 # full content in one patch, so delta works everywhere
359 header = mdiff.replacediffheader(revlog.rawsize(base), len(t))
358 header = mdiff.replacediffheader(revlog.rawsize(base), len(t))
360 delta = header + t
359 delta = header + t
361 else:
360 else:
362 ptext = revlog.revision(base, _df=fh, raw=True)
361 ptext = revlog.revision(base, _df=fh, raw=True)
363 delta = mdiff.textdiff(ptext, t)
362 delta = mdiff.textdiff(ptext, t)
364
363
365 return delta
364 return delta
366
365
367 def _builddeltainfo(self, revinfo, base, fh):
366 def _builddeltainfo(self, revinfo, base, fh):
368 # can we use the cached delta?
367 # can we use the cached delta?
369 if revinfo.cachedelta and revinfo.cachedelta[0] == base:
368 if revinfo.cachedelta and revinfo.cachedelta[0] == base:
370 delta = revinfo.cachedelta[1]
369 delta = revinfo.cachedelta[1]
371 else:
370 else:
372 delta = self._builddeltadiff(base, revinfo, fh)
371 delta = self._builddeltadiff(base, revinfo, fh)
373 revlog = self.revlog
372 revlog = self.revlog
374 header, data = revlog.compress(delta)
373 header, data = revlog.compress(delta)
375 deltalen = len(header) + len(data)
374 deltalen = len(header) + len(data)
376 chainbase = revlog.chainbase(base)
375 chainbase = revlog.chainbase(base)
377 offset = revlog.end(len(revlog) - 1)
376 offset = revlog.end(len(revlog) - 1)
378 dist = deltalen + offset - revlog.start(chainbase)
377 dist = deltalen + offset - revlog.start(chainbase)
379 if revlog._generaldelta:
378 if revlog._generaldelta:
380 deltabase = base
379 deltabase = base
381 else:
380 else:
382 deltabase = chainbase
381 deltabase = chainbase
383 chainlen, compresseddeltalen = revlog._chaininfo(base)
382 chainlen, compresseddeltalen = revlog._chaininfo(base)
384 chainlen += 1
383 chainlen += 1
385 compresseddeltalen += deltalen
384 compresseddeltalen += deltalen
386 return _deltainfo(dist, deltalen, (header, data), deltabase,
385 return _deltainfo(dist, deltalen, (header, data), deltabase,
387 chainbase, chainlen, compresseddeltalen)
386 chainbase, chainlen, compresseddeltalen)
388
387
389 def finddeltainfo(self, revinfo, fh):
388 def finddeltainfo(self, revinfo, fh):
390 """Find an acceptable delta against a candidate revision
389 """Find an acceptable delta against a candidate revision
391
390
392 revinfo: information about the revision (instance of _revisioninfo)
391 revinfo: information about the revision (instance of _revisioninfo)
393 fh: file handle to either the .i or the .d revlog file,
392 fh: file handle to either the .i or the .d revlog file,
394 depending on whether it is inlined or not
393 depending on whether it is inlined or not
395
394
396 Returns the first acceptable candidate revision, as ordered by
395 Returns the first acceptable candidate revision, as ordered by
397 _getcandidaterevs
396 _getcandidaterevs
398 """
397 """
399 cachedelta = revinfo.cachedelta
398 cachedelta = revinfo.cachedelta
400 p1 = revinfo.p1
399 p1 = revinfo.p1
401 p2 = revinfo.p2
400 p2 = revinfo.p2
402 revlog = self.revlog
401 revlog = self.revlog
403
402
404 deltainfo = None
403 deltainfo = None
405 for candidaterevs in self._getcandidaterevs(p1, p2, cachedelta):
404 for candidaterevs in self._getcandidaterevs(p1, p2, cachedelta):
406 nominateddeltas = []
405 nominateddeltas = []
407 for candidaterev in candidaterevs:
406 for candidaterev in candidaterevs:
408 candidatedelta = self._builddeltainfo(revinfo, candidaterev, fh)
407 candidatedelta = self._builddeltainfo(revinfo, candidaterev, fh)
409 if revlog._isgooddeltainfo(candidatedelta, revinfo.textlen):
408 if revlog._isgooddeltainfo(candidatedelta, revinfo.textlen):
410 nominateddeltas.append(candidatedelta)
409 nominateddeltas.append(candidatedelta)
411 if nominateddeltas:
410 if nominateddeltas:
412 deltainfo = min(nominateddeltas, key=lambda x: x.deltalen)
411 deltainfo = min(nominateddeltas, key=lambda x: x.deltalen)
413 break
412 break
414
413
415 return deltainfo
414 return deltainfo
416
415
417 @attr.s(slots=True, frozen=True)
416 @attr.s(slots=True, frozen=True)
418 class _revisioninfo(object):
417 class _revisioninfo(object):
419 """Information about a revision that allows building its fulltext
418 """Information about a revision that allows building its fulltext
420 node: expected hash of the revision
419 node: expected hash of the revision
421 p1, p2: parent revs of the revision
420 p1, p2: parent revs of the revision
422 btext: built text cache consisting of a one-element list
421 btext: built text cache consisting of a one-element list
423 cachedelta: (baserev, uncompressed_delta) or None
422 cachedelta: (baserev, uncompressed_delta) or None
424 flags: flags associated to the revision storage
423 flags: flags associated to the revision storage
425
424
426 One of btext[0] or cachedelta must be set.
425 One of btext[0] or cachedelta must be set.
427 """
426 """
428 node = attr.ib()
427 node = attr.ib()
429 p1 = attr.ib()
428 p1 = attr.ib()
430 p2 = attr.ib()
429 p2 = attr.ib()
431 btext = attr.ib()
430 btext = attr.ib()
432 textlen = attr.ib()
431 textlen = attr.ib()
433 cachedelta = attr.ib()
432 cachedelta = attr.ib()
434 flags = attr.ib()
433 flags = attr.ib()
435
434
436 # index v0:
435 # index v0:
437 # 4 bytes: offset
436 # 4 bytes: offset
438 # 4 bytes: compressed length
437 # 4 bytes: compressed length
439 # 4 bytes: base rev
438 # 4 bytes: base rev
440 # 4 bytes: link rev
439 # 4 bytes: link rev
441 # 20 bytes: parent 1 nodeid
440 # 20 bytes: parent 1 nodeid
442 # 20 bytes: parent 2 nodeid
441 # 20 bytes: parent 2 nodeid
443 # 20 bytes: nodeid
442 # 20 bytes: nodeid
444 indexformatv0 = struct.Struct(">4l20s20s20s")
443 indexformatv0 = struct.Struct(">4l20s20s20s")
445 indexformatv0_pack = indexformatv0.pack
444 indexformatv0_pack = indexformatv0.pack
446 indexformatv0_unpack = indexformatv0.unpack
445 indexformatv0_unpack = indexformatv0.unpack
447
446
448 class revlogoldio(object):
447 class revlogoldio(object):
449 def __init__(self):
448 def __init__(self):
450 self.size = indexformatv0.size
449 self.size = indexformatv0.size
451
450
452 def parseindex(self, data, inline):
451 def parseindex(self, data, inline):
453 s = self.size
452 s = self.size
454 index = []
453 index = []
455 nodemap = {nullid: nullrev}
454 nodemap = {nullid: nullrev}
456 n = off = 0
455 n = off = 0
457 l = len(data)
456 l = len(data)
458 while off + s <= l:
457 while off + s <= l:
459 cur = data[off:off + s]
458 cur = data[off:off + s]
460 off += s
459 off += s
461 e = indexformatv0_unpack(cur)
460 e = indexformatv0_unpack(cur)
462 # transform to revlogv1 format
461 # transform to revlogv1 format
463 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
462 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
464 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
463 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
465 index.append(e2)
464 index.append(e2)
466 nodemap[e[6]] = n
465 nodemap[e[6]] = n
467 n += 1
466 n += 1
468
467
469 # add the magic null revision at -1
468 # add the magic null revision at -1
470 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
469 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
471
470
472 return index, nodemap, None
471 return index, nodemap, None
473
472
474 def packentry(self, entry, node, version, rev):
473 def packentry(self, entry, node, version, rev):
475 if gettype(entry[0]):
474 if gettype(entry[0]):
476 raise RevlogError(_('index entry flags need revlog version 1'))
475 raise RevlogError(_('index entry flags need revlog version 1'))
477 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
476 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
478 node(entry[5]), node(entry[6]), entry[7])
477 node(entry[5]), node(entry[6]), entry[7])
479 return indexformatv0_pack(*e2)
478 return indexformatv0_pack(*e2)
480
479
481 # index ng:
480 # index ng:
482 # 6 bytes: offset
481 # 6 bytes: offset
483 # 2 bytes: flags
482 # 2 bytes: flags
484 # 4 bytes: compressed length
483 # 4 bytes: compressed length
485 # 4 bytes: uncompressed length
484 # 4 bytes: uncompressed length
486 # 4 bytes: base rev
485 # 4 bytes: base rev
487 # 4 bytes: link rev
486 # 4 bytes: link rev
488 # 4 bytes: parent 1 rev
487 # 4 bytes: parent 1 rev
489 # 4 bytes: parent 2 rev
488 # 4 bytes: parent 2 rev
490 # 32 bytes: nodeid
489 # 32 bytes: nodeid
491 indexformatng = struct.Struct(">Qiiiiii20s12x")
490 indexformatng = struct.Struct(">Qiiiiii20s12x")
492 indexformatng_pack = indexformatng.pack
491 indexformatng_pack = indexformatng.pack
493 versionformat = struct.Struct(">I")
492 versionformat = struct.Struct(">I")
494 versionformat_pack = versionformat.pack
493 versionformat_pack = versionformat.pack
495 versionformat_unpack = versionformat.unpack
494 versionformat_unpack = versionformat.unpack
496
495
497 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
496 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
498 # signed integer)
497 # signed integer)
499 _maxentrysize = 0x7fffffff
498 _maxentrysize = 0x7fffffff
500
499
501 class revlogio(object):
500 class revlogio(object):
502 def __init__(self):
501 def __init__(self):
503 self.size = indexformatng.size
502 self.size = indexformatng.size
504
503
505 def parseindex(self, data, inline):
504 def parseindex(self, data, inline):
506 # call the C implementation to parse the index data
505 # call the C implementation to parse the index data
507 index, cache = parsers.parse_index2(data, inline)
506 index, cache = parsers.parse_index2(data, inline)
508 return index, getattr(index, 'nodemap', None), cache
507 return index, getattr(index, 'nodemap', None), cache
509
508
510 def packentry(self, entry, node, version, rev):
509 def packentry(self, entry, node, version, rev):
511 p = indexformatng_pack(*entry)
510 p = indexformatng_pack(*entry)
512 if rev == 0:
511 if rev == 0:
513 p = versionformat_pack(version) + p[4:]
512 p = versionformat_pack(version) + p[4:]
514 return p
513 return p
515
514
516 class revlog(object):
515 class revlog(object):
517 """
516 """
518 the underlying revision storage object
517 the underlying revision storage object
519
518
520 A revlog consists of two parts, an index and the revision data.
519 A revlog consists of two parts, an index and the revision data.
521
520
522 The index is a file with a fixed record size containing
521 The index is a file with a fixed record size containing
523 information on each revision, including its nodeid (hash), the
522 information on each revision, including its nodeid (hash), the
524 nodeids of its parents, the position and offset of its data within
523 nodeids of its parents, the position and offset of its data within
525 the data file, and the revision it's based on. Finally, each entry
524 the data file, and the revision it's based on. Finally, each entry
526 contains a linkrev entry that can serve as a pointer to external
525 contains a linkrev entry that can serve as a pointer to external
527 data.
526 data.
528
527
529 The revision data itself is a linear collection of data chunks.
528 The revision data itself is a linear collection of data chunks.
530 Each chunk represents a revision and is usually represented as a
529 Each chunk represents a revision and is usually represented as a
531 delta against the previous chunk. To bound lookup time, runs of
530 delta against the previous chunk. To bound lookup time, runs of
532 deltas are limited to about 2 times the length of the original
531 deltas are limited to about 2 times the length of the original
533 version data. This makes retrieval of a version proportional to
532 version data. This makes retrieval of a version proportional to
534 its size, or O(1) relative to the number of revisions.
533 its size, or O(1) relative to the number of revisions.
535
534
536 Both pieces of the revlog are written to in an append-only
535 Both pieces of the revlog are written to in an append-only
537 fashion, which means we never need to rewrite a file to insert or
536 fashion, which means we never need to rewrite a file to insert or
538 remove data, and can use some simple techniques to avoid the need
537 remove data, and can use some simple techniques to avoid the need
539 for locking while reading.
538 for locking while reading.
540
539
541 If checkambig, indexfile is opened with checkambig=True at
540 If checkambig, indexfile is opened with checkambig=True at
542 writing, to avoid file stat ambiguity.
541 writing, to avoid file stat ambiguity.
543
542
544 If mmaplargeindex is True, and an mmapindexthreshold is set, the
543 If mmaplargeindex is True, and an mmapindexthreshold is set, the
545 index will be mmapped rather than read if it is larger than the
544 index will be mmapped rather than read if it is larger than the
546 configured threshold.
545 configured threshold.
547 """
546 """
548 def __init__(self, opener, indexfile, datafile=None, checkambig=False,
547 def __init__(self, opener, indexfile, datafile=None, checkambig=False,
549 mmaplargeindex=False):
548 mmaplargeindex=False):
550 """
549 """
551 create a revlog object
550 create a revlog object
552
551
553 opener is a function that abstracts the file opening operation
552 opener is a function that abstracts the file opening operation
554 and can be used to implement COW semantics or the like.
553 and can be used to implement COW semantics or the like.
555 """
554 """
556 self.indexfile = indexfile
555 self.indexfile = indexfile
557 self.datafile = datafile or (indexfile[:-2] + ".d")
556 self.datafile = datafile or (indexfile[:-2] + ".d")
558 self.opener = opener
557 self.opener = opener
559 # When True, indexfile is opened with checkambig=True at writing, to
558 # When True, indexfile is opened with checkambig=True at writing, to
560 # avoid file stat ambiguity.
559 # avoid file stat ambiguity.
561 self._checkambig = checkambig
560 self._checkambig = checkambig
562 # 3-tuple of (node, rev, text) for a raw revision.
561 # 3-tuple of (node, rev, text) for a raw revision.
563 self._cache = None
562 self._cache = None
564 # Maps rev to chain base rev.
563 # Maps rev to chain base rev.
565 self._chainbasecache = util.lrucachedict(100)
564 self._chainbasecache = util.lrucachedict(100)
566 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
565 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
567 self._chunkcache = (0, '')
566 self._chunkcache = (0, '')
568 # How much data to read and cache into the raw revlog data cache.
567 # How much data to read and cache into the raw revlog data cache.
569 self._chunkcachesize = 65536
568 self._chunkcachesize = 65536
570 self._maxchainlen = None
569 self._maxchainlen = None
571 self._aggressivemergedeltas = False
570 self._aggressivemergedeltas = False
572 self.index = []
571 self.index = []
573 # Mapping of partial identifiers to full nodes.
572 # Mapping of partial identifiers to full nodes.
574 self._pcache = {}
573 self._pcache = {}
575 # Mapping of revision integer to full node.
574 # Mapping of revision integer to full node.
576 self._nodecache = {nullid: nullrev}
575 self._nodecache = {nullid: nullrev}
577 self._nodepos = None
576 self._nodepos = None
578 self._compengine = 'zlib'
577 self._compengine = 'zlib'
579 self._maxdeltachainspan = -1
578 self._maxdeltachainspan = -1
580 self._withsparseread = False
579 self._withsparseread = False
581 self._srdensitythreshold = 0.25
580 self._srdensitythreshold = 0.25
582 self._srmingapsize = 262144
581 self._srmingapsize = 262144
583
582
584 mmapindexthreshold = None
583 mmapindexthreshold = None
585 v = REVLOG_DEFAULT_VERSION
584 v = REVLOG_DEFAULT_VERSION
586 opts = getattr(opener, 'options', None)
585 opts = getattr(opener, 'options', None)
587 if opts is not None:
586 if opts is not None:
588 if 'revlogv2' in opts:
587 if 'revlogv2' in opts:
589 # version 2 revlogs always use generaldelta.
588 # version 2 revlogs always use generaldelta.
590 v = REVLOGV2 | FLAG_GENERALDELTA | FLAG_INLINE_DATA
589 v = REVLOGV2 | FLAG_GENERALDELTA | FLAG_INLINE_DATA
591 elif 'revlogv1' in opts:
590 elif 'revlogv1' in opts:
592 if 'generaldelta' in opts:
591 if 'generaldelta' in opts:
593 v |= FLAG_GENERALDELTA
592 v |= FLAG_GENERALDELTA
594 else:
593 else:
595 v = 0
594 v = 0
596 if 'chunkcachesize' in opts:
595 if 'chunkcachesize' in opts:
597 self._chunkcachesize = opts['chunkcachesize']
596 self._chunkcachesize = opts['chunkcachesize']
598 if 'maxchainlen' in opts:
597 if 'maxchainlen' in opts:
599 self._maxchainlen = opts['maxchainlen']
598 self._maxchainlen = opts['maxchainlen']
600 if 'aggressivemergedeltas' in opts:
599 if 'aggressivemergedeltas' in opts:
601 self._aggressivemergedeltas = opts['aggressivemergedeltas']
600 self._aggressivemergedeltas = opts['aggressivemergedeltas']
602 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
601 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
603 if 'compengine' in opts:
602 if 'compengine' in opts:
604 self._compengine = opts['compengine']
603 self._compengine = opts['compengine']
605 if 'maxdeltachainspan' in opts:
604 if 'maxdeltachainspan' in opts:
606 self._maxdeltachainspan = opts['maxdeltachainspan']
605 self._maxdeltachainspan = opts['maxdeltachainspan']
607 if mmaplargeindex and 'mmapindexthreshold' in opts:
606 if mmaplargeindex and 'mmapindexthreshold' in opts:
608 mmapindexthreshold = opts['mmapindexthreshold']
607 mmapindexthreshold = opts['mmapindexthreshold']
609 self._withsparseread = bool(opts.get('with-sparse-read', False))
608 self._withsparseread = bool(opts.get('with-sparse-read', False))
610 if 'sparse-read-density-threshold' in opts:
609 if 'sparse-read-density-threshold' in opts:
611 self._srdensitythreshold = opts['sparse-read-density-threshold']
610 self._srdensitythreshold = opts['sparse-read-density-threshold']
612 if 'sparse-read-min-gap-size' in opts:
611 if 'sparse-read-min-gap-size' in opts:
613 self._srmingapsize = opts['sparse-read-min-gap-size']
612 self._srmingapsize = opts['sparse-read-min-gap-size']
614
613
615 if self._chunkcachesize <= 0:
614 if self._chunkcachesize <= 0:
616 raise RevlogError(_('revlog chunk cache size %r is not greater '
615 raise RevlogError(_('revlog chunk cache size %r is not greater '
617 'than 0') % self._chunkcachesize)
616 'than 0') % self._chunkcachesize)
618 elif self._chunkcachesize & (self._chunkcachesize - 1):
617 elif self._chunkcachesize & (self._chunkcachesize - 1):
619 raise RevlogError(_('revlog chunk cache size %r is not a power '
618 raise RevlogError(_('revlog chunk cache size %r is not a power '
620 'of 2') % self._chunkcachesize)
619 'of 2') % self._chunkcachesize)
621
620
622 indexdata = ''
621 indexdata = ''
623 self._initempty = True
622 self._initempty = True
624 try:
623 try:
625 with self._indexfp() as f:
624 with self._indexfp() as f:
626 if (mmapindexthreshold is not None and
625 if (mmapindexthreshold is not None and
627 self.opener.fstat(f).st_size >= mmapindexthreshold):
626 self.opener.fstat(f).st_size >= mmapindexthreshold):
628 indexdata = util.buffer(util.mmapread(f))
627 indexdata = util.buffer(util.mmapread(f))
629 else:
628 else:
630 indexdata = f.read()
629 indexdata = f.read()
631 if len(indexdata) > 0:
630 if len(indexdata) > 0:
632 v = versionformat_unpack(indexdata[:4])[0]
631 v = versionformat_unpack(indexdata[:4])[0]
633 self._initempty = False
632 self._initempty = False
634 except IOError as inst:
633 except IOError as inst:
635 if inst.errno != errno.ENOENT:
634 if inst.errno != errno.ENOENT:
636 raise
635 raise
637
636
638 self.version = v
637 self.version = v
639 self._inline = v & FLAG_INLINE_DATA
638 self._inline = v & FLAG_INLINE_DATA
640 self._generaldelta = v & FLAG_GENERALDELTA
639 self._generaldelta = v & FLAG_GENERALDELTA
641 flags = v & ~0xFFFF
640 flags = v & ~0xFFFF
642 fmt = v & 0xFFFF
641 fmt = v & 0xFFFF
643 if fmt == REVLOGV0:
642 if fmt == REVLOGV0:
644 if flags:
643 if flags:
645 raise RevlogError(_('unknown flags (%#04x) in version %d '
644 raise RevlogError(_('unknown flags (%#04x) in version %d '
646 'revlog %s') %
645 'revlog %s') %
647 (flags >> 16, fmt, self.indexfile))
646 (flags >> 16, fmt, self.indexfile))
648 elif fmt == REVLOGV1:
647 elif fmt == REVLOGV1:
649 if flags & ~REVLOGV1_FLAGS:
648 if flags & ~REVLOGV1_FLAGS:
650 raise RevlogError(_('unknown flags (%#04x) in version %d '
649 raise RevlogError(_('unknown flags (%#04x) in version %d '
651 'revlog %s') %
650 'revlog %s') %
652 (flags >> 16, fmt, self.indexfile))
651 (flags >> 16, fmt, self.indexfile))
653 elif fmt == REVLOGV2:
652 elif fmt == REVLOGV2:
654 if flags & ~REVLOGV2_FLAGS:
653 if flags & ~REVLOGV2_FLAGS:
655 raise RevlogError(_('unknown flags (%#04x) in version %d '
654 raise RevlogError(_('unknown flags (%#04x) in version %d '
656 'revlog %s') %
655 'revlog %s') %
657 (flags >> 16, fmt, self.indexfile))
656 (flags >> 16, fmt, self.indexfile))
658 else:
657 else:
659 raise RevlogError(_('unknown version (%d) in revlog %s') %
658 raise RevlogError(_('unknown version (%d) in revlog %s') %
660 (fmt, self.indexfile))
659 (fmt, self.indexfile))
661
660
662 self.storedeltachains = True
661 self.storedeltachains = True
663
662
664 self._io = revlogio()
663 self._io = revlogio()
665 if self.version == REVLOGV0:
664 if self.version == REVLOGV0:
666 self._io = revlogoldio()
665 self._io = revlogoldio()
667 try:
666 try:
668 d = self._io.parseindex(indexdata, self._inline)
667 d = self._io.parseindex(indexdata, self._inline)
669 except (ValueError, IndexError):
668 except (ValueError, IndexError):
670 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
669 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
671 self.index, nodemap, self._chunkcache = d
670 self.index, nodemap, self._chunkcache = d
672 if nodemap is not None:
671 if nodemap is not None:
673 self.nodemap = self._nodecache = nodemap
672 self.nodemap = self._nodecache = nodemap
674 if not self._chunkcache:
673 if not self._chunkcache:
675 self._chunkclear()
674 self._chunkclear()
676 # revnum -> (chain-length, sum-delta-length)
675 # revnum -> (chain-length, sum-delta-length)
677 self._chaininfocache = {}
676 self._chaininfocache = {}
678 # revlog header -> revlog compressor
677 # revlog header -> revlog compressor
679 self._decompressors = {}
678 self._decompressors = {}
680
679
681 @util.propertycache
680 @util.propertycache
682 def _compressor(self):
681 def _compressor(self):
683 return util.compengines[self._compengine].revlogcompressor()
682 return util.compengines[self._compengine].revlogcompressor()
684
683
685 def _indexfp(self, mode='r'):
684 def _indexfp(self, mode='r'):
686 """file object for the revlog's index file"""
685 """file object for the revlog's index file"""
687 args = {r'mode': mode}
686 args = {r'mode': mode}
688 if mode != 'r':
687 if mode != 'r':
689 args[r'checkambig'] = self._checkambig
688 args[r'checkambig'] = self._checkambig
690 if mode == 'w':
689 if mode == 'w':
691 args[r'atomictemp'] = True
690 args[r'atomictemp'] = True
692 return self.opener(self.indexfile, **args)
691 return self.opener(self.indexfile, **args)
693
692
694 def _datafp(self, mode='r'):
693 def _datafp(self, mode='r'):
695 """file object for the revlog's data file"""
694 """file object for the revlog's data file"""
696 return self.opener(self.datafile, mode=mode)
695 return self.opener(self.datafile, mode=mode)
697
696
698 @contextlib.contextmanager
697 @contextlib.contextmanager
699 def _datareadfp(self, existingfp=None):
698 def _datareadfp(self, existingfp=None):
700 """file object suitable to read data"""
699 """file object suitable to read data"""
701 if existingfp is not None:
700 if existingfp is not None:
702 yield existingfp
701 yield existingfp
703 else:
702 else:
704 if self._inline:
703 if self._inline:
705 func = self._indexfp
704 func = self._indexfp
706 else:
705 else:
707 func = self._datafp
706 func = self._datafp
708 with func() as fp:
707 with func() as fp:
709 yield fp
708 yield fp
710
709
711 def tip(self):
710 def tip(self):
712 return self.node(len(self.index) - 2)
711 return self.node(len(self.index) - 2)
713 def __contains__(self, rev):
712 def __contains__(self, rev):
714 return 0 <= rev < len(self)
713 return 0 <= rev < len(self)
715 def __len__(self):
714 def __len__(self):
716 return len(self.index) - 1
715 return len(self.index) - 1
717 def __iter__(self):
716 def __iter__(self):
718 return iter(xrange(len(self)))
717 return iter(xrange(len(self)))
719 def revs(self, start=0, stop=None):
718 def revs(self, start=0, stop=None):
720 """iterate over all rev in this revlog (from start to stop)"""
719 """iterate over all rev in this revlog (from start to stop)"""
721 step = 1
720 step = 1
722 if stop is not None:
721 if stop is not None:
723 if start > stop:
722 if start > stop:
724 step = -1
723 step = -1
725 stop += step
724 stop += step
726 else:
725 else:
727 stop = len(self)
726 stop = len(self)
728 return xrange(start, stop, step)
727 return xrange(start, stop, step)
729
728
730 @util.propertycache
729 @util.propertycache
731 def nodemap(self):
730 def nodemap(self):
732 self.rev(self.node(0))
731 self.rev(self.node(0))
733 return self._nodecache
732 return self._nodecache
734
733
735 def hasnode(self, node):
734 def hasnode(self, node):
736 try:
735 try:
737 self.rev(node)
736 self.rev(node)
738 return True
737 return True
739 except KeyError:
738 except KeyError:
740 return False
739 return False
741
740
742 def clearcaches(self):
741 def clearcaches(self):
743 self._cache = None
742 self._cache = None
744 self._chainbasecache.clear()
743 self._chainbasecache.clear()
745 self._chunkcache = (0, '')
744 self._chunkcache = (0, '')
746 self._pcache = {}
745 self._pcache = {}
747
746
748 try:
747 try:
749 self._nodecache.clearcaches()
748 self._nodecache.clearcaches()
750 except AttributeError:
749 except AttributeError:
751 self._nodecache = {nullid: nullrev}
750 self._nodecache = {nullid: nullrev}
752 self._nodepos = None
751 self._nodepos = None
753
752
754 def rev(self, node):
753 def rev(self, node):
755 try:
754 try:
756 return self._nodecache[node]
755 return self._nodecache[node]
757 except TypeError:
756 except TypeError:
758 raise
757 raise
759 except RevlogError:
758 except RevlogError:
760 # parsers.c radix tree lookup failed
759 # parsers.c radix tree lookup failed
761 if node == wdirid:
760 if node == wdirid:
762 raise error.WdirUnsupported
761 raise error.WdirUnsupported
763 raise LookupError(node, self.indexfile, _('no node'))
762 raise LookupError(node, self.indexfile, _('no node'))
764 except KeyError:
763 except KeyError:
765 # pure python cache lookup failed
764 # pure python cache lookup failed
766 n = self._nodecache
765 n = self._nodecache
767 i = self.index
766 i = self.index
768 p = self._nodepos
767 p = self._nodepos
769 if p is None:
768 if p is None:
770 p = len(i) - 2
769 p = len(i) - 2
771 for r in xrange(p, -1, -1):
770 for r in xrange(p, -1, -1):
772 v = i[r][7]
771 v = i[r][7]
773 n[v] = r
772 n[v] = r
774 if v == node:
773 if v == node:
775 self._nodepos = r - 1
774 self._nodepos = r - 1
776 return r
775 return r
777 if node == wdirid:
776 if node == wdirid:
778 raise error.WdirUnsupported
777 raise error.WdirUnsupported
779 raise LookupError(node, self.indexfile, _('no node'))
778 raise LookupError(node, self.indexfile, _('no node'))
780
779
781 # Accessors for index entries.
780 # Accessors for index entries.
782
781
783 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
782 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
784 # are flags.
783 # are flags.
785 def start(self, rev):
784 def start(self, rev):
786 return int(self.index[rev][0] >> 16)
785 return int(self.index[rev][0] >> 16)
787
786
788 def flags(self, rev):
787 def flags(self, rev):
789 return self.index[rev][0] & 0xFFFF
788 return self.index[rev][0] & 0xFFFF
790
789
791 def length(self, rev):
790 def length(self, rev):
792 return self.index[rev][1]
791 return self.index[rev][1]
793
792
794 def rawsize(self, rev):
793 def rawsize(self, rev):
795 """return the length of the uncompressed text for a given revision"""
794 """return the length of the uncompressed text for a given revision"""
796 l = self.index[rev][2]
795 l = self.index[rev][2]
797 if l >= 0:
796 if l >= 0:
798 return l
797 return l
799
798
800 t = self.revision(rev, raw=True)
799 t = self.revision(rev, raw=True)
801 return len(t)
800 return len(t)
802
801
803 def size(self, rev):
802 def size(self, rev):
804 """length of non-raw text (processed by a "read" flag processor)"""
803 """length of non-raw text (processed by a "read" flag processor)"""
805 # fast path: if no "read" flag processor could change the content,
804 # fast path: if no "read" flag processor could change the content,
806 # size is rawsize. note: ELLIPSIS is known to not change the content.
805 # size is rawsize. note: ELLIPSIS is known to not change the content.
807 flags = self.flags(rev)
806 flags = self.flags(rev)
808 if flags & (REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
807 if flags & (REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
809 return self.rawsize(rev)
808 return self.rawsize(rev)
810
809
811 return len(self.revision(rev, raw=False))
810 return len(self.revision(rev, raw=False))
812
811
813 def chainbase(self, rev):
812 def chainbase(self, rev):
814 base = self._chainbasecache.get(rev)
813 base = self._chainbasecache.get(rev)
815 if base is not None:
814 if base is not None:
816 return base
815 return base
817
816
818 index = self.index
817 index = self.index
819 base = index[rev][3]
818 base = index[rev][3]
820 while base != rev:
819 while base != rev:
821 rev = base
820 rev = base
822 base = index[rev][3]
821 base = index[rev][3]
823
822
824 self._chainbasecache[rev] = base
823 self._chainbasecache[rev] = base
825 return base
824 return base
826
825
827 def linkrev(self, rev):
826 def linkrev(self, rev):
828 return self.index[rev][4]
827 return self.index[rev][4]
829
828
830 def parentrevs(self, rev):
829 def parentrevs(self, rev):
831 try:
830 try:
832 entry = self.index[rev]
831 entry = self.index[rev]
833 except IndexError:
832 except IndexError:
834 if rev == wdirrev:
833 if rev == wdirrev:
835 raise error.WdirUnsupported
834 raise error.WdirUnsupported
836 raise
835 raise
837
836
838 return entry[5], entry[6]
837 return entry[5], entry[6]
839
838
840 def node(self, rev):
839 def node(self, rev):
841 try:
840 try:
842 return self.index[rev][7]
841 return self.index[rev][7]
843 except IndexError:
842 except IndexError:
844 if rev == wdirrev:
843 if rev == wdirrev:
845 raise error.WdirUnsupported
844 raise error.WdirUnsupported
846 raise
845 raise
847
846
848 # Derived from index values.
847 # Derived from index values.
849
848
850 def end(self, rev):
849 def end(self, rev):
851 return self.start(rev) + self.length(rev)
850 return self.start(rev) + self.length(rev)
852
851
853 def parents(self, node):
852 def parents(self, node):
854 i = self.index
853 i = self.index
855 d = i[self.rev(node)]
854 d = i[self.rev(node)]
856 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
855 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
857
856
858 def chainlen(self, rev):
857 def chainlen(self, rev):
859 return self._chaininfo(rev)[0]
858 return self._chaininfo(rev)[0]
860
859
861 def _chaininfo(self, rev):
860 def _chaininfo(self, rev):
862 chaininfocache = self._chaininfocache
861 chaininfocache = self._chaininfocache
863 if rev in chaininfocache:
862 if rev in chaininfocache:
864 return chaininfocache[rev]
863 return chaininfocache[rev]
865 index = self.index
864 index = self.index
866 generaldelta = self._generaldelta
865 generaldelta = self._generaldelta
867 iterrev = rev
866 iterrev = rev
868 e = index[iterrev]
867 e = index[iterrev]
869 clen = 0
868 clen = 0
870 compresseddeltalen = 0
869 compresseddeltalen = 0
871 while iterrev != e[3]:
870 while iterrev != e[3]:
872 clen += 1
871 clen += 1
873 compresseddeltalen += e[1]
872 compresseddeltalen += e[1]
874 if generaldelta:
873 if generaldelta:
875 iterrev = e[3]
874 iterrev = e[3]
876 else:
875 else:
877 iterrev -= 1
876 iterrev -= 1
878 if iterrev in chaininfocache:
877 if iterrev in chaininfocache:
879 t = chaininfocache[iterrev]
878 t = chaininfocache[iterrev]
880 clen += t[0]
879 clen += t[0]
881 compresseddeltalen += t[1]
880 compresseddeltalen += t[1]
882 break
881 break
883 e = index[iterrev]
882 e = index[iterrev]
884 else:
883 else:
885 # Add text length of base since decompressing that also takes
884 # Add text length of base since decompressing that also takes
886 # work. For cache hits the length is already included.
885 # work. For cache hits the length is already included.
887 compresseddeltalen += e[1]
886 compresseddeltalen += e[1]
888 r = (clen, compresseddeltalen)
887 r = (clen, compresseddeltalen)
889 chaininfocache[rev] = r
888 chaininfocache[rev] = r
890 return r
889 return r
891
890
892 def _deltachain(self, rev, stoprev=None):
891 def _deltachain(self, rev, stoprev=None):
893 """Obtain the delta chain for a revision.
892 """Obtain the delta chain for a revision.
894
893
895 ``stoprev`` specifies a revision to stop at. If not specified, we
894 ``stoprev`` specifies a revision to stop at. If not specified, we
896 stop at the base of the chain.
895 stop at the base of the chain.
897
896
898 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
897 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
899 revs in ascending order and ``stopped`` is a bool indicating whether
898 revs in ascending order and ``stopped`` is a bool indicating whether
900 ``stoprev`` was hit.
899 ``stoprev`` was hit.
901 """
900 """
902 # Try C implementation.
901 # Try C implementation.
903 try:
902 try:
904 return self.index.deltachain(rev, stoprev, self._generaldelta)
903 return self.index.deltachain(rev, stoprev, self._generaldelta)
905 except AttributeError:
904 except AttributeError:
906 pass
905 pass
907
906
908 chain = []
907 chain = []
909
908
910 # Alias to prevent attribute lookup in tight loop.
909 # Alias to prevent attribute lookup in tight loop.
911 index = self.index
910 index = self.index
912 generaldelta = self._generaldelta
911 generaldelta = self._generaldelta
913
912
914 iterrev = rev
913 iterrev = rev
915 e = index[iterrev]
914 e = index[iterrev]
916 while iterrev != e[3] and iterrev != stoprev:
915 while iterrev != e[3] and iterrev != stoprev:
917 chain.append(iterrev)
916 chain.append(iterrev)
918 if generaldelta:
917 if generaldelta:
919 iterrev = e[3]
918 iterrev = e[3]
920 else:
919 else:
921 iterrev -= 1
920 iterrev -= 1
922 e = index[iterrev]
921 e = index[iterrev]
923
922
924 if iterrev == stoprev:
923 if iterrev == stoprev:
925 stopped = True
924 stopped = True
926 else:
925 else:
927 chain.append(iterrev)
926 chain.append(iterrev)
928 stopped = False
927 stopped = False
929
928
930 chain.reverse()
929 chain.reverse()
931 return chain, stopped
930 return chain, stopped
932
931
933 def ancestors(self, revs, stoprev=0, inclusive=False):
932 def ancestors(self, revs, stoprev=0, inclusive=False):
934 """Generate the ancestors of 'revs' in reverse topological order.
933 """Generate the ancestors of 'revs' in reverse topological order.
935 Does not generate revs lower than stoprev.
934 Does not generate revs lower than stoprev.
936
935
937 See the documentation for ancestor.lazyancestors for more details."""
936 See the documentation for ancestor.lazyancestors for more details."""
938
937
939 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
938 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
940 inclusive=inclusive)
939 inclusive=inclusive)
941
940
942 def descendants(self, revs):
941 def descendants(self, revs):
943 """Generate the descendants of 'revs' in revision order.
942 """Generate the descendants of 'revs' in revision order.
944
943
945 Yield a sequence of revision numbers starting with a child of
944 Yield a sequence of revision numbers starting with a child of
946 some rev in revs, i.e., each revision is *not* considered a
945 some rev in revs, i.e., each revision is *not* considered a
947 descendant of itself. Results are ordered by revision number (a
946 descendant of itself. Results are ordered by revision number (a
948 topological sort)."""
947 topological sort)."""
949 first = min(revs)
948 first = min(revs)
950 if first == nullrev:
949 if first == nullrev:
951 for i in self:
950 for i in self:
952 yield i
951 yield i
953 return
952 return
954
953
955 seen = set(revs)
954 seen = set(revs)
956 for i in self.revs(start=first + 1):
955 for i in self.revs(start=first + 1):
957 for x in self.parentrevs(i):
956 for x in self.parentrevs(i):
958 if x != nullrev and x in seen:
957 if x != nullrev and x in seen:
959 seen.add(i)
958 seen.add(i)
960 yield i
959 yield i
961 break
960 break
962
961
963 def findcommonmissing(self, common=None, heads=None):
962 def findcommonmissing(self, common=None, heads=None):
964 """Return a tuple of the ancestors of common and the ancestors of heads
963 """Return a tuple of the ancestors of common and the ancestors of heads
965 that are not ancestors of common. In revset terminology, we return the
964 that are not ancestors of common. In revset terminology, we return the
966 tuple:
965 tuple:
967
966
968 ::common, (::heads) - (::common)
967 ::common, (::heads) - (::common)
969
968
970 The list is sorted by revision number, meaning it is
969 The list is sorted by revision number, meaning it is
971 topologically sorted.
970 topologically sorted.
972
971
973 'heads' and 'common' are both lists of node IDs. If heads is
972 'heads' and 'common' are both lists of node IDs. If heads is
974 not supplied, uses all of the revlog's heads. If common is not
973 not supplied, uses all of the revlog's heads. If common is not
975 supplied, uses nullid."""
974 supplied, uses nullid."""
976 if common is None:
975 if common is None:
977 common = [nullid]
976 common = [nullid]
978 if heads is None:
977 if heads is None:
979 heads = self.heads()
978 heads = self.heads()
980
979
981 common = [self.rev(n) for n in common]
980 common = [self.rev(n) for n in common]
982 heads = [self.rev(n) for n in heads]
981 heads = [self.rev(n) for n in heads]
983
982
984 # we want the ancestors, but inclusive
983 # we want the ancestors, but inclusive
985 class lazyset(object):
984 class lazyset(object):
986 def __init__(self, lazyvalues):
985 def __init__(self, lazyvalues):
987 self.addedvalues = set()
986 self.addedvalues = set()
988 self.lazyvalues = lazyvalues
987 self.lazyvalues = lazyvalues
989
988
990 def __contains__(self, value):
989 def __contains__(self, value):
991 return value in self.addedvalues or value in self.lazyvalues
990 return value in self.addedvalues or value in self.lazyvalues
992
991
993 def __iter__(self):
992 def __iter__(self):
994 added = self.addedvalues
993 added = self.addedvalues
995 for r in added:
994 for r in added:
996 yield r
995 yield r
997 for r in self.lazyvalues:
996 for r in self.lazyvalues:
998 if not r in added:
997 if not r in added:
999 yield r
998 yield r
1000
999
1001 def add(self, value):
1000 def add(self, value):
1002 self.addedvalues.add(value)
1001 self.addedvalues.add(value)
1003
1002
1004 def update(self, values):
1003 def update(self, values):
1005 self.addedvalues.update(values)
1004 self.addedvalues.update(values)
1006
1005
1007 has = lazyset(self.ancestors(common))
1006 has = lazyset(self.ancestors(common))
1008 has.add(nullrev)
1007 has.add(nullrev)
1009 has.update(common)
1008 has.update(common)
1010
1009
1011 # take all ancestors from heads that aren't in has
1010 # take all ancestors from heads that aren't in has
1012 missing = set()
1011 missing = set()
1013 visit = collections.deque(r for r in heads if r not in has)
1012 visit = collections.deque(r for r in heads if r not in has)
1014 while visit:
1013 while visit:
1015 r = visit.popleft()
1014 r = visit.popleft()
1016 if r in missing:
1015 if r in missing:
1017 continue
1016 continue
1018 else:
1017 else:
1019 missing.add(r)
1018 missing.add(r)
1020 for p in self.parentrevs(r):
1019 for p in self.parentrevs(r):
1021 if p not in has:
1020 if p not in has:
1022 visit.append(p)
1021 visit.append(p)
1023 missing = list(missing)
1022 missing = list(missing)
1024 missing.sort()
1023 missing.sort()
1025 return has, [self.node(miss) for miss in missing]
1024 return has, [self.node(miss) for miss in missing]
1026
1025
1027 def incrementalmissingrevs(self, common=None):
1026 def incrementalmissingrevs(self, common=None):
1028 """Return an object that can be used to incrementally compute the
1027 """Return an object that can be used to incrementally compute the
1029 revision numbers of the ancestors of arbitrary sets that are not
1028 revision numbers of the ancestors of arbitrary sets that are not
1030 ancestors of common. This is an ancestor.incrementalmissingancestors
1029 ancestors of common. This is an ancestor.incrementalmissingancestors
1031 object.
1030 object.
1032
1031
1033 'common' is a list of revision numbers. If common is not supplied, uses
1032 'common' is a list of revision numbers. If common is not supplied, uses
1034 nullrev.
1033 nullrev.
1035 """
1034 """
1036 if common is None:
1035 if common is None:
1037 common = [nullrev]
1036 common = [nullrev]
1038
1037
1039 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1038 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1040
1039
1041 def findmissingrevs(self, common=None, heads=None):
1040 def findmissingrevs(self, common=None, heads=None):
1042 """Return the revision numbers of the ancestors of heads that
1041 """Return the revision numbers of the ancestors of heads that
1043 are not ancestors of common.
1042 are not ancestors of common.
1044
1043
1045 More specifically, return a list of revision numbers corresponding to
1044 More specifically, return a list of revision numbers corresponding to
1046 nodes N such that every N satisfies the following constraints:
1045 nodes N such that every N satisfies the following constraints:
1047
1046
1048 1. N is an ancestor of some node in 'heads'
1047 1. N is an ancestor of some node in 'heads'
1049 2. N is not an ancestor of any node in 'common'
1048 2. N is not an ancestor of any node in 'common'
1050
1049
1051 The list is sorted by revision number, meaning it is
1050 The list is sorted by revision number, meaning it is
1052 topologically sorted.
1051 topologically sorted.
1053
1052
1054 'heads' and 'common' are both lists of revision numbers. If heads is
1053 'heads' and 'common' are both lists of revision numbers. If heads is
1055 not supplied, uses all of the revlog's heads. If common is not
1054 not supplied, uses all of the revlog's heads. If common is not
1056 supplied, uses nullid."""
1055 supplied, uses nullid."""
1057 if common is None:
1056 if common is None:
1058 common = [nullrev]
1057 common = [nullrev]
1059 if heads is None:
1058 if heads is None:
1060 heads = self.headrevs()
1059 heads = self.headrevs()
1061
1060
1062 inc = self.incrementalmissingrevs(common=common)
1061 inc = self.incrementalmissingrevs(common=common)
1063 return inc.missingancestors(heads)
1062 return inc.missingancestors(heads)
1064
1063
1065 def findmissing(self, common=None, heads=None):
1064 def findmissing(self, common=None, heads=None):
1066 """Return the ancestors of heads that are not ancestors of common.
1065 """Return the ancestors of heads that are not ancestors of common.
1067
1066
1068 More specifically, return a list of nodes N such that every N
1067 More specifically, return a list of nodes N such that every N
1069 satisfies the following constraints:
1068 satisfies the following constraints:
1070
1069
1071 1. N is an ancestor of some node in 'heads'
1070 1. N is an ancestor of some node in 'heads'
1072 2. N is not an ancestor of any node in 'common'
1071 2. N is not an ancestor of any node in 'common'
1073
1072
1074 The list is sorted by revision number, meaning it is
1073 The list is sorted by revision number, meaning it is
1075 topologically sorted.
1074 topologically sorted.
1076
1075
1077 'heads' and 'common' are both lists of node IDs. If heads is
1076 'heads' and 'common' are both lists of node IDs. If heads is
1078 not supplied, uses all of the revlog's heads. If common is not
1077 not supplied, uses all of the revlog's heads. If common is not
1079 supplied, uses nullid."""
1078 supplied, uses nullid."""
1080 if common is None:
1079 if common is None:
1081 common = [nullid]
1080 common = [nullid]
1082 if heads is None:
1081 if heads is None:
1083 heads = self.heads()
1082 heads = self.heads()
1084
1083
1085 common = [self.rev(n) for n in common]
1084 common = [self.rev(n) for n in common]
1086 heads = [self.rev(n) for n in heads]
1085 heads = [self.rev(n) for n in heads]
1087
1086
1088 inc = self.incrementalmissingrevs(common=common)
1087 inc = self.incrementalmissingrevs(common=common)
1089 return [self.node(r) for r in inc.missingancestors(heads)]
1088 return [self.node(r) for r in inc.missingancestors(heads)]
1090
1089
1091 def nodesbetween(self, roots=None, heads=None):
1090 def nodesbetween(self, roots=None, heads=None):
1092 """Return a topological path from 'roots' to 'heads'.
1091 """Return a topological path from 'roots' to 'heads'.
1093
1092
1094 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1093 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1095 topologically sorted list of all nodes N that satisfy both of
1094 topologically sorted list of all nodes N that satisfy both of
1096 these constraints:
1095 these constraints:
1097
1096
1098 1. N is a descendant of some node in 'roots'
1097 1. N is a descendant of some node in 'roots'
1099 2. N is an ancestor of some node in 'heads'
1098 2. N is an ancestor of some node in 'heads'
1100
1099
1101 Every node is considered to be both a descendant and an ancestor
1100 Every node is considered to be both a descendant and an ancestor
1102 of itself, so every reachable node in 'roots' and 'heads' will be
1101 of itself, so every reachable node in 'roots' and 'heads' will be
1103 included in 'nodes'.
1102 included in 'nodes'.
1104
1103
1105 'outroots' is the list of reachable nodes in 'roots', i.e., the
1104 'outroots' is the list of reachable nodes in 'roots', i.e., the
1106 subset of 'roots' that is returned in 'nodes'. Likewise,
1105 subset of 'roots' that is returned in 'nodes'. Likewise,
1107 'outheads' is the subset of 'heads' that is also in 'nodes'.
1106 'outheads' is the subset of 'heads' that is also in 'nodes'.
1108
1107
1109 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1108 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1110 unspecified, uses nullid as the only root. If 'heads' is
1109 unspecified, uses nullid as the only root. If 'heads' is
1111 unspecified, uses list of all of the revlog's heads."""
1110 unspecified, uses list of all of the revlog's heads."""
1112 nonodes = ([], [], [])
1111 nonodes = ([], [], [])
1113 if roots is not None:
1112 if roots is not None:
1114 roots = list(roots)
1113 roots = list(roots)
1115 if not roots:
1114 if not roots:
1116 return nonodes
1115 return nonodes
1117 lowestrev = min([self.rev(n) for n in roots])
1116 lowestrev = min([self.rev(n) for n in roots])
1118 else:
1117 else:
1119 roots = [nullid] # Everybody's a descendant of nullid
1118 roots = [nullid] # Everybody's a descendant of nullid
1120 lowestrev = nullrev
1119 lowestrev = nullrev
1121 if (lowestrev == nullrev) and (heads is None):
1120 if (lowestrev == nullrev) and (heads is None):
1122 # We want _all_ the nodes!
1121 # We want _all_ the nodes!
1123 return ([self.node(r) for r in self], [nullid], list(self.heads()))
1122 return ([self.node(r) for r in self], [nullid], list(self.heads()))
1124 if heads is None:
1123 if heads is None:
1125 # All nodes are ancestors, so the latest ancestor is the last
1124 # All nodes are ancestors, so the latest ancestor is the last
1126 # node.
1125 # node.
1127 highestrev = len(self) - 1
1126 highestrev = len(self) - 1
1128 # Set ancestors to None to signal that every node is an ancestor.
1127 # Set ancestors to None to signal that every node is an ancestor.
1129 ancestors = None
1128 ancestors = None
1130 # Set heads to an empty dictionary for later discovery of heads
1129 # Set heads to an empty dictionary for later discovery of heads
1131 heads = {}
1130 heads = {}
1132 else:
1131 else:
1133 heads = list(heads)
1132 heads = list(heads)
1134 if not heads:
1133 if not heads:
1135 return nonodes
1134 return nonodes
1136 ancestors = set()
1135 ancestors = set()
1137 # Turn heads into a dictionary so we can remove 'fake' heads.
1136 # Turn heads into a dictionary so we can remove 'fake' heads.
1138 # Also, later we will be using it to filter out the heads we can't
1137 # Also, later we will be using it to filter out the heads we can't
1139 # find from roots.
1138 # find from roots.
1140 heads = dict.fromkeys(heads, False)
1139 heads = dict.fromkeys(heads, False)
1141 # Start at the top and keep marking parents until we're done.
1140 # Start at the top and keep marking parents until we're done.
1142 nodestotag = set(heads)
1141 nodestotag = set(heads)
1143 # Remember where the top was so we can use it as a limit later.
1142 # Remember where the top was so we can use it as a limit later.
1144 highestrev = max([self.rev(n) for n in nodestotag])
1143 highestrev = max([self.rev(n) for n in nodestotag])
1145 while nodestotag:
1144 while nodestotag:
1146 # grab a node to tag
1145 # grab a node to tag
1147 n = nodestotag.pop()
1146 n = nodestotag.pop()
1148 # Never tag nullid
1147 # Never tag nullid
1149 if n == nullid:
1148 if n == nullid:
1150 continue
1149 continue
1151 # A node's revision number represents its place in a
1150 # A node's revision number represents its place in a
1152 # topologically sorted list of nodes.
1151 # topologically sorted list of nodes.
1153 r = self.rev(n)
1152 r = self.rev(n)
1154 if r >= lowestrev:
1153 if r >= lowestrev:
1155 if n not in ancestors:
1154 if n not in ancestors:
1156 # If we are possibly a descendant of one of the roots
1155 # If we are possibly a descendant of one of the roots
1157 # and we haven't already been marked as an ancestor
1156 # and we haven't already been marked as an ancestor
1158 ancestors.add(n) # Mark as ancestor
1157 ancestors.add(n) # Mark as ancestor
1159 # Add non-nullid parents to list of nodes to tag.
1158 # Add non-nullid parents to list of nodes to tag.
1160 nodestotag.update([p for p in self.parents(n) if
1159 nodestotag.update([p for p in self.parents(n) if
1161 p != nullid])
1160 p != nullid])
1162 elif n in heads: # We've seen it before, is it a fake head?
1161 elif n in heads: # We've seen it before, is it a fake head?
1163 # So it is, real heads should not be the ancestors of
1162 # So it is, real heads should not be the ancestors of
1164 # any other heads.
1163 # any other heads.
1165 heads.pop(n)
1164 heads.pop(n)
1166 if not ancestors:
1165 if not ancestors:
1167 return nonodes
1166 return nonodes
1168 # Now that we have our set of ancestors, we want to remove any
1167 # Now that we have our set of ancestors, we want to remove any
1169 # roots that are not ancestors.
1168 # roots that are not ancestors.
1170
1169
1171 # If one of the roots was nullid, everything is included anyway.
1170 # If one of the roots was nullid, everything is included anyway.
1172 if lowestrev > nullrev:
1171 if lowestrev > nullrev:
1173 # But, since we weren't, let's recompute the lowest rev to not
1172 # But, since we weren't, let's recompute the lowest rev to not
1174 # include roots that aren't ancestors.
1173 # include roots that aren't ancestors.
1175
1174
1176 # Filter out roots that aren't ancestors of heads
1175 # Filter out roots that aren't ancestors of heads
1177 roots = [root for root in roots if root in ancestors]
1176 roots = [root for root in roots if root in ancestors]
1178 # Recompute the lowest revision
1177 # Recompute the lowest revision
1179 if roots:
1178 if roots:
1180 lowestrev = min([self.rev(root) for root in roots])
1179 lowestrev = min([self.rev(root) for root in roots])
1181 else:
1180 else:
1182 # No more roots? Return empty list
1181 # No more roots? Return empty list
1183 return nonodes
1182 return nonodes
1184 else:
1183 else:
1185 # We are descending from nullid, and don't need to care about
1184 # We are descending from nullid, and don't need to care about
1186 # any other roots.
1185 # any other roots.
1187 lowestrev = nullrev
1186 lowestrev = nullrev
1188 roots = [nullid]
1187 roots = [nullid]
1189 # Transform our roots list into a set.
1188 # Transform our roots list into a set.
1190 descendants = set(roots)
1189 descendants = set(roots)
1191 # Also, keep the original roots so we can filter out roots that aren't
1190 # Also, keep the original roots so we can filter out roots that aren't
1192 # 'real' roots (i.e. are descended from other roots).
1191 # 'real' roots (i.e. are descended from other roots).
1193 roots = descendants.copy()
1192 roots = descendants.copy()
1194 # Our topologically sorted list of output nodes.
1193 # Our topologically sorted list of output nodes.
1195 orderedout = []
1194 orderedout = []
1196 # Don't start at nullid since we don't want nullid in our output list,
1195 # Don't start at nullid since we don't want nullid in our output list,
1197 # and if nullid shows up in descendants, empty parents will look like
1196 # and if nullid shows up in descendants, empty parents will look like
1198 # they're descendants.
1197 # they're descendants.
1199 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1198 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1200 n = self.node(r)
1199 n = self.node(r)
1201 isdescendant = False
1200 isdescendant = False
1202 if lowestrev == nullrev: # Everybody is a descendant of nullid
1201 if lowestrev == nullrev: # Everybody is a descendant of nullid
1203 isdescendant = True
1202 isdescendant = True
1204 elif n in descendants:
1203 elif n in descendants:
1205 # n is already a descendant
1204 # n is already a descendant
1206 isdescendant = True
1205 isdescendant = True
1207 # This check only needs to be done here because all the roots
1206 # This check only needs to be done here because all the roots
1208 # will start being marked is descendants before the loop.
1207 # will start being marked is descendants before the loop.
1209 if n in roots:
1208 if n in roots:
1210 # If n was a root, check if it's a 'real' root.
1209 # If n was a root, check if it's a 'real' root.
1211 p = tuple(self.parents(n))
1210 p = tuple(self.parents(n))
1212 # If any of its parents are descendants, it's not a root.
1211 # If any of its parents are descendants, it's not a root.
1213 if (p[0] in descendants) or (p[1] in descendants):
1212 if (p[0] in descendants) or (p[1] in descendants):
1214 roots.remove(n)
1213 roots.remove(n)
1215 else:
1214 else:
1216 p = tuple(self.parents(n))
1215 p = tuple(self.parents(n))
1217 # A node is a descendant if either of its parents are
1216 # A node is a descendant if either of its parents are
1218 # descendants. (We seeded the dependents list with the roots
1217 # descendants. (We seeded the dependents list with the roots
1219 # up there, remember?)
1218 # up there, remember?)
1220 if (p[0] in descendants) or (p[1] in descendants):
1219 if (p[0] in descendants) or (p[1] in descendants):
1221 descendants.add(n)
1220 descendants.add(n)
1222 isdescendant = True
1221 isdescendant = True
1223 if isdescendant and ((ancestors is None) or (n in ancestors)):
1222 if isdescendant and ((ancestors is None) or (n in ancestors)):
1224 # Only include nodes that are both descendants and ancestors.
1223 # Only include nodes that are both descendants and ancestors.
1225 orderedout.append(n)
1224 orderedout.append(n)
1226 if (ancestors is not None) and (n in heads):
1225 if (ancestors is not None) and (n in heads):
1227 # We're trying to figure out which heads are reachable
1226 # We're trying to figure out which heads are reachable
1228 # from roots.
1227 # from roots.
1229 # Mark this head as having been reached
1228 # Mark this head as having been reached
1230 heads[n] = True
1229 heads[n] = True
1231 elif ancestors is None:
1230 elif ancestors is None:
1232 # Otherwise, we're trying to discover the heads.
1231 # Otherwise, we're trying to discover the heads.
1233 # Assume this is a head because if it isn't, the next step
1232 # Assume this is a head because if it isn't, the next step
1234 # will eventually remove it.
1233 # will eventually remove it.
1235 heads[n] = True
1234 heads[n] = True
1236 # But, obviously its parents aren't.
1235 # But, obviously its parents aren't.
1237 for p in self.parents(n):
1236 for p in self.parents(n):
1238 heads.pop(p, None)
1237 heads.pop(p, None)
1239 heads = [head for head, flag in heads.iteritems() if flag]
1238 heads = [head for head, flag in heads.iteritems() if flag]
1240 roots = list(roots)
1239 roots = list(roots)
1241 assert orderedout
1240 assert orderedout
1242 assert roots
1241 assert roots
1243 assert heads
1242 assert heads
1244 return (orderedout, roots, heads)
1243 return (orderedout, roots, heads)
1245
1244
1246 def headrevs(self):
1245 def headrevs(self):
1247 try:
1246 try:
1248 return self.index.headrevs()
1247 return self.index.headrevs()
1249 except AttributeError:
1248 except AttributeError:
1250 return self._headrevs()
1249 return self._headrevs()
1251
1250
1252 def computephases(self, roots):
1251 def computephases(self, roots):
1253 return self.index.computephasesmapsets(roots)
1252 return self.index.computephasesmapsets(roots)
1254
1253
1255 def _headrevs(self):
1254 def _headrevs(self):
1256 count = len(self)
1255 count = len(self)
1257 if not count:
1256 if not count:
1258 return [nullrev]
1257 return [nullrev]
1259 # we won't iter over filtered rev so nobody is a head at start
1258 # we won't iter over filtered rev so nobody is a head at start
1260 ishead = [0] * (count + 1)
1259 ishead = [0] * (count + 1)
1261 index = self.index
1260 index = self.index
1262 for r in self:
1261 for r in self:
1263 ishead[r] = 1 # I may be an head
1262 ishead[r] = 1 # I may be an head
1264 e = index[r]
1263 e = index[r]
1265 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1264 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1266 return [r for r, val in enumerate(ishead) if val]
1265 return [r for r, val in enumerate(ishead) if val]
1267
1266
1268 def heads(self, start=None, stop=None):
1267 def heads(self, start=None, stop=None):
1269 """return the list of all nodes that have no children
1268 """return the list of all nodes that have no children
1270
1269
1271 if start is specified, only heads that are descendants of
1270 if start is specified, only heads that are descendants of
1272 start will be returned
1271 start will be returned
1273 if stop is specified, it will consider all the revs from stop
1272 if stop is specified, it will consider all the revs from stop
1274 as if they had no children
1273 as if they had no children
1275 """
1274 """
1276 if start is None and stop is None:
1275 if start is None and stop is None:
1277 if not len(self):
1276 if not len(self):
1278 return [nullid]
1277 return [nullid]
1279 return [self.node(r) for r in self.headrevs()]
1278 return [self.node(r) for r in self.headrevs()]
1280
1279
1281 if start is None:
1280 if start is None:
1282 start = nullid
1281 start = nullid
1283 if stop is None:
1282 if stop is None:
1284 stop = []
1283 stop = []
1285 stoprevs = set([self.rev(n) for n in stop])
1284 stoprevs = set([self.rev(n) for n in stop])
1286 startrev = self.rev(start)
1285 startrev = self.rev(start)
1287 reachable = {startrev}
1286 reachable = {startrev}
1288 heads = {startrev}
1287 heads = {startrev}
1289
1288
1290 parentrevs = self.parentrevs
1289 parentrevs = self.parentrevs
1291 for r in self.revs(start=startrev + 1):
1290 for r in self.revs(start=startrev + 1):
1292 for p in parentrevs(r):
1291 for p in parentrevs(r):
1293 if p in reachable:
1292 if p in reachable:
1294 if r not in stoprevs:
1293 if r not in stoprevs:
1295 reachable.add(r)
1294 reachable.add(r)
1296 heads.add(r)
1295 heads.add(r)
1297 if p in heads and p not in stoprevs:
1296 if p in heads and p not in stoprevs:
1298 heads.remove(p)
1297 heads.remove(p)
1299
1298
1300 return [self.node(r) for r in heads]
1299 return [self.node(r) for r in heads]
1301
1300
1302 def children(self, node):
1301 def children(self, node):
1303 """find the children of a given node"""
1302 """find the children of a given node"""
1304 c = []
1303 c = []
1305 p = self.rev(node)
1304 p = self.rev(node)
1306 for r in self.revs(start=p + 1):
1305 for r in self.revs(start=p + 1):
1307 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1306 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1308 if prevs:
1307 if prevs:
1309 for pr in prevs:
1308 for pr in prevs:
1310 if pr == p:
1309 if pr == p:
1311 c.append(self.node(r))
1310 c.append(self.node(r))
1312 elif p == nullrev:
1311 elif p == nullrev:
1313 c.append(self.node(r))
1312 c.append(self.node(r))
1314 return c
1313 return c
1315
1314
1316 def descendant(self, start, end):
1315 def descendant(self, start, end):
1317 if start == nullrev:
1316 if start == nullrev:
1318 return True
1317 return True
1319 for i in self.descendants([start]):
1318 for i in self.descendants([start]):
1320 if i == end:
1319 if i == end:
1321 return True
1320 return True
1322 elif i > end:
1321 elif i > end:
1323 break
1322 break
1324 return False
1323 return False
1325
1324
1326 def commonancestorsheads(self, a, b):
1325 def commonancestorsheads(self, a, b):
1327 """calculate all the heads of the common ancestors of nodes a and b"""
1326 """calculate all the heads of the common ancestors of nodes a and b"""
1328 a, b = self.rev(a), self.rev(b)
1327 a, b = self.rev(a), self.rev(b)
1329 try:
1328 try:
1330 ancs = self.index.commonancestorsheads(a, b)
1329 ancs = self.index.commonancestorsheads(a, b)
1331 except (AttributeError, OverflowError): # C implementation failed
1330 except (AttributeError, OverflowError): # C implementation failed
1332 ancs = ancestor.commonancestorsheads(self.parentrevs, a, b)
1331 ancs = ancestor.commonancestorsheads(self.parentrevs, a, b)
1333 return pycompat.maplist(self.node, ancs)
1332 return pycompat.maplist(self.node, ancs)
1334
1333
1335 def isancestor(self, a, b):
1334 def isancestor(self, a, b):
1336 """return True if node a is an ancestor of node b
1335 """return True if node a is an ancestor of node b
1337
1336
1338 The implementation of this is trivial but the use of
1337 The implementation of this is trivial but the use of
1339 commonancestorsheads is not."""
1338 commonancestorsheads is not."""
1340 return a in self.commonancestorsheads(a, b)
1339 return a in self.commonancestorsheads(a, b)
1341
1340
1342 def ancestor(self, a, b):
1341 def ancestor(self, a, b):
1343 """calculate the "best" common ancestor of nodes a and b"""
1342 """calculate the "best" common ancestor of nodes a and b"""
1344
1343
1345 a, b = self.rev(a), self.rev(b)
1344 a, b = self.rev(a), self.rev(b)
1346 try:
1345 try:
1347 ancs = self.index.ancestors(a, b)
1346 ancs = self.index.ancestors(a, b)
1348 except (AttributeError, OverflowError):
1347 except (AttributeError, OverflowError):
1349 ancs = ancestor.ancestors(self.parentrevs, a, b)
1348 ancs = ancestor.ancestors(self.parentrevs, a, b)
1350 if ancs:
1349 if ancs:
1351 # choose a consistent winner when there's a tie
1350 # choose a consistent winner when there's a tie
1352 return min(map(self.node, ancs))
1351 return min(map(self.node, ancs))
1353 return nullid
1352 return nullid
1354
1353
1355 def _match(self, id):
1354 def _match(self, id):
1356 if isinstance(id, int):
1355 if isinstance(id, int):
1357 # rev
1356 # rev
1358 return self.node(id)
1357 return self.node(id)
1359 if len(id) == 20:
1358 if len(id) == 20:
1360 # possibly a binary node
1359 # possibly a binary node
1361 # odds of a binary node being all hex in ASCII are 1 in 10**25
1360 # odds of a binary node being all hex in ASCII are 1 in 10**25
1362 try:
1361 try:
1363 node = id
1362 node = id
1364 self.rev(node) # quick search the index
1363 self.rev(node) # quick search the index
1365 return node
1364 return node
1366 except LookupError:
1365 except LookupError:
1367 pass # may be partial hex id
1366 pass # may be partial hex id
1368 try:
1367 try:
1369 # str(rev)
1368 # str(rev)
1370 rev = int(id)
1369 rev = int(id)
1371 if str(rev) != id:
1370 if str(rev) != id:
1372 raise ValueError
1371 raise ValueError
1373 if rev < 0:
1372 if rev < 0:
1374 rev = len(self) + rev
1373 rev = len(self) + rev
1375 if rev < 0 or rev >= len(self):
1374 if rev < 0 or rev >= len(self):
1376 raise ValueError
1375 raise ValueError
1377 return self.node(rev)
1376 return self.node(rev)
1378 except (ValueError, OverflowError):
1377 except (ValueError, OverflowError):
1379 pass
1378 pass
1380 if len(id) == 40:
1379 if len(id) == 40:
1381 try:
1380 try:
1382 # a full hex nodeid?
1381 # a full hex nodeid?
1383 node = bin(id)
1382 node = bin(id)
1384 self.rev(node)
1383 self.rev(node)
1385 return node
1384 return node
1386 except (TypeError, LookupError):
1385 except (TypeError, LookupError):
1387 pass
1386 pass
1388
1387
1389 def _partialmatch(self, id):
1388 def _partialmatch(self, id):
1390 maybewdir = wdirhex.startswith(id)
1389 maybewdir = wdirhex.startswith(id)
1391 try:
1390 try:
1392 partial = self.index.partialmatch(id)
1391 partial = self.index.partialmatch(id)
1393 if partial and self.hasnode(partial):
1392 if partial and self.hasnode(partial):
1394 if maybewdir:
1393 if maybewdir:
1395 # single 'ff...' match in radix tree, ambiguous with wdir
1394 # single 'ff...' match in radix tree, ambiguous with wdir
1396 raise RevlogError
1395 raise RevlogError
1397 return partial
1396 return partial
1398 if maybewdir:
1397 if maybewdir:
1399 # no 'ff...' match in radix tree, wdir identified
1398 # no 'ff...' match in radix tree, wdir identified
1400 raise error.WdirUnsupported
1399 raise error.WdirUnsupported
1401 return None
1400 return None
1402 except RevlogError:
1401 except RevlogError:
1403 # parsers.c radix tree lookup gave multiple matches
1402 # parsers.c radix tree lookup gave multiple matches
1404 # fast path: for unfiltered changelog, radix tree is accurate
1403 # fast path: for unfiltered changelog, radix tree is accurate
1405 if not getattr(self, 'filteredrevs', None):
1404 if not getattr(self, 'filteredrevs', None):
1406 raise LookupError(id, self.indexfile,
1405 raise LookupError(id, self.indexfile,
1407 _('ambiguous identifier'))
1406 _('ambiguous identifier'))
1408 # fall through to slow path that filters hidden revisions
1407 # fall through to slow path that filters hidden revisions
1409 except (AttributeError, ValueError):
1408 except (AttributeError, ValueError):
1410 # we are pure python, or key was too short to search radix tree
1409 # we are pure python, or key was too short to search radix tree
1411 pass
1410 pass
1412
1411
1413 if id in self._pcache:
1412 if id in self._pcache:
1414 return self._pcache[id]
1413 return self._pcache[id]
1415
1414
1416 if len(id) < 40:
1415 if len(id) < 40:
1417 try:
1416 try:
1418 # hex(node)[:...]
1417 # hex(node)[:...]
1419 l = len(id) // 2 # grab an even number of digits
1418 l = len(id) // 2 # grab an even number of digits
1420 prefix = bin(id[:l * 2])
1419 prefix = bin(id[:l * 2])
1421 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1420 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1422 nl = [n for n in nl if hex(n).startswith(id) and
1421 nl = [n for n in nl if hex(n).startswith(id) and
1423 self.hasnode(n)]
1422 self.hasnode(n)]
1424 if len(nl) > 0:
1423 if len(nl) > 0:
1425 if len(nl) == 1 and not maybewdir:
1424 if len(nl) == 1 and not maybewdir:
1426 self._pcache[id] = nl[0]
1425 self._pcache[id] = nl[0]
1427 return nl[0]
1426 return nl[0]
1428 raise LookupError(id, self.indexfile,
1427 raise LookupError(id, self.indexfile,
1429 _('ambiguous identifier'))
1428 _('ambiguous identifier'))
1430 if maybewdir:
1429 if maybewdir:
1431 raise error.WdirUnsupported
1430 raise error.WdirUnsupported
1432 return None
1431 return None
1433 except (TypeError, binascii.Error):
1432 except TypeError:
1434 pass
1433 pass
1435
1434
1436 def lookup(self, id):
1435 def lookup(self, id):
1437 """locate a node based on:
1436 """locate a node based on:
1438 - revision number or str(revision number)
1437 - revision number or str(revision number)
1439 - nodeid or subset of hex nodeid
1438 - nodeid or subset of hex nodeid
1440 """
1439 """
1441 n = self._match(id)
1440 n = self._match(id)
1442 if n is not None:
1441 if n is not None:
1443 return n
1442 return n
1444 n = self._partialmatch(id)
1443 n = self._partialmatch(id)
1445 if n:
1444 if n:
1446 return n
1445 return n
1447
1446
1448 raise LookupError(id, self.indexfile, _('no match found'))
1447 raise LookupError(id, self.indexfile, _('no match found'))
1449
1448
1450 def shortest(self, hexnode, minlength=1):
1449 def shortest(self, hexnode, minlength=1):
1451 """Find the shortest unambiguous prefix that matches hexnode."""
1450 """Find the shortest unambiguous prefix that matches hexnode."""
1452 def isvalid(test):
1451 def isvalid(test):
1453 try:
1452 try:
1454 if self._partialmatch(test) is None:
1453 if self._partialmatch(test) is None:
1455 return False
1454 return False
1456
1455
1457 try:
1456 try:
1458 i = int(test)
1457 i = int(test)
1459 # if we are a pure int, then starting with zero will not be
1458 # if we are a pure int, then starting with zero will not be
1460 # confused as a rev; or, obviously, if the int is larger
1459 # confused as a rev; or, obviously, if the int is larger
1461 # than the value of the tip rev
1460 # than the value of the tip rev
1462 if test[0] == '0' or i > len(self):
1461 if test[0] == '0' or i > len(self):
1463 return True
1462 return True
1464 return False
1463 return False
1465 except ValueError:
1464 except ValueError:
1466 return True
1465 return True
1467 except error.RevlogError:
1466 except error.RevlogError:
1468 return False
1467 return False
1469 except error.WdirUnsupported:
1468 except error.WdirUnsupported:
1470 # single 'ff...' match
1469 # single 'ff...' match
1471 return True
1470 return True
1472
1471
1473 shortest = hexnode
1472 shortest = hexnode
1474 startlength = max(6, minlength)
1473 startlength = max(6, minlength)
1475 length = startlength
1474 length = startlength
1476 while True:
1475 while True:
1477 test = hexnode[:length]
1476 test = hexnode[:length]
1478 if isvalid(test):
1477 if isvalid(test):
1479 shortest = test
1478 shortest = test
1480 if length == minlength or length > startlength:
1479 if length == minlength or length > startlength:
1481 return shortest
1480 return shortest
1482 length -= 1
1481 length -= 1
1483 else:
1482 else:
1484 length += 1
1483 length += 1
1485 if len(shortest) <= length:
1484 if len(shortest) <= length:
1486 return shortest
1485 return shortest
1487
1486
1488 def cmp(self, node, text):
1487 def cmp(self, node, text):
1489 """compare text with a given file revision
1488 """compare text with a given file revision
1490
1489
1491 returns True if text is different than what is stored.
1490 returns True if text is different than what is stored.
1492 """
1491 """
1493 p1, p2 = self.parents(node)
1492 p1, p2 = self.parents(node)
1494 return hash(text, p1, p2) != node
1493 return hash(text, p1, p2) != node
1495
1494
1496 def _cachesegment(self, offset, data):
1495 def _cachesegment(self, offset, data):
1497 """Add a segment to the revlog cache.
1496 """Add a segment to the revlog cache.
1498
1497
1499 Accepts an absolute offset and the data that is at that location.
1498 Accepts an absolute offset and the data that is at that location.
1500 """
1499 """
1501 o, d = self._chunkcache
1500 o, d = self._chunkcache
1502 # try to add to existing cache
1501 # try to add to existing cache
1503 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1502 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1504 self._chunkcache = o, d + data
1503 self._chunkcache = o, d + data
1505 else:
1504 else:
1506 self._chunkcache = offset, data
1505 self._chunkcache = offset, data
1507
1506
1508 def _readsegment(self, offset, length, df=None):
1507 def _readsegment(self, offset, length, df=None):
1509 """Load a segment of raw data from the revlog.
1508 """Load a segment of raw data from the revlog.
1510
1509
1511 Accepts an absolute offset, length to read, and an optional existing
1510 Accepts an absolute offset, length to read, and an optional existing
1512 file handle to read from.
1511 file handle to read from.
1513
1512
1514 If an existing file handle is passed, it will be seeked and the
1513 If an existing file handle is passed, it will be seeked and the
1515 original seek position will NOT be restored.
1514 original seek position will NOT be restored.
1516
1515
1517 Returns a str or buffer of raw byte data.
1516 Returns a str or buffer of raw byte data.
1518 """
1517 """
1519 # Cache data both forward and backward around the requested
1518 # Cache data both forward and backward around the requested
1520 # data, in a fixed size window. This helps speed up operations
1519 # data, in a fixed size window. This helps speed up operations
1521 # involving reading the revlog backwards.
1520 # involving reading the revlog backwards.
1522 cachesize = self._chunkcachesize
1521 cachesize = self._chunkcachesize
1523 realoffset = offset & ~(cachesize - 1)
1522 realoffset = offset & ~(cachesize - 1)
1524 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1523 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1525 - realoffset)
1524 - realoffset)
1526 with self._datareadfp(df) as df:
1525 with self._datareadfp(df) as df:
1527 df.seek(realoffset)
1526 df.seek(realoffset)
1528 d = df.read(reallength)
1527 d = df.read(reallength)
1529 self._cachesegment(realoffset, d)
1528 self._cachesegment(realoffset, d)
1530 if offset != realoffset or reallength != length:
1529 if offset != realoffset or reallength != length:
1531 return util.buffer(d, offset - realoffset, length)
1530 return util.buffer(d, offset - realoffset, length)
1532 return d
1531 return d
1533
1532
1534 def _getsegment(self, offset, length, df=None):
1533 def _getsegment(self, offset, length, df=None):
1535 """Obtain a segment of raw data from the revlog.
1534 """Obtain a segment of raw data from the revlog.
1536
1535
1537 Accepts an absolute offset, length of bytes to obtain, and an
1536 Accepts an absolute offset, length of bytes to obtain, and an
1538 optional file handle to the already-opened revlog. If the file
1537 optional file handle to the already-opened revlog. If the file
1539 handle is used, it's original seek position will not be preserved.
1538 handle is used, it's original seek position will not be preserved.
1540
1539
1541 Requests for data may be returned from a cache.
1540 Requests for data may be returned from a cache.
1542
1541
1543 Returns a str or a buffer instance of raw byte data.
1542 Returns a str or a buffer instance of raw byte data.
1544 """
1543 """
1545 o, d = self._chunkcache
1544 o, d = self._chunkcache
1546 l = len(d)
1545 l = len(d)
1547
1546
1548 # is it in the cache?
1547 # is it in the cache?
1549 cachestart = offset - o
1548 cachestart = offset - o
1550 cacheend = cachestart + length
1549 cacheend = cachestart + length
1551 if cachestart >= 0 and cacheend <= l:
1550 if cachestart >= 0 and cacheend <= l:
1552 if cachestart == 0 and cacheend == l:
1551 if cachestart == 0 and cacheend == l:
1553 return d # avoid a copy
1552 return d # avoid a copy
1554 return util.buffer(d, cachestart, cacheend - cachestart)
1553 return util.buffer(d, cachestart, cacheend - cachestart)
1555
1554
1556 return self._readsegment(offset, length, df=df)
1555 return self._readsegment(offset, length, df=df)
1557
1556
1558 def _getsegmentforrevs(self, startrev, endrev, df=None):
1557 def _getsegmentforrevs(self, startrev, endrev, df=None):
1559 """Obtain a segment of raw data corresponding to a range of revisions.
1558 """Obtain a segment of raw data corresponding to a range of revisions.
1560
1559
1561 Accepts the start and end revisions and an optional already-open
1560 Accepts the start and end revisions and an optional already-open
1562 file handle to be used for reading. If the file handle is read, its
1561 file handle to be used for reading. If the file handle is read, its
1563 seek position will not be preserved.
1562 seek position will not be preserved.
1564
1563
1565 Requests for data may be satisfied by a cache.
1564 Requests for data may be satisfied by a cache.
1566
1565
1567 Returns a 2-tuple of (offset, data) for the requested range of
1566 Returns a 2-tuple of (offset, data) for the requested range of
1568 revisions. Offset is the integer offset from the beginning of the
1567 revisions. Offset is the integer offset from the beginning of the
1569 revlog and data is a str or buffer of the raw byte data.
1568 revlog and data is a str or buffer of the raw byte data.
1570
1569
1571 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1570 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1572 to determine where each revision's data begins and ends.
1571 to determine where each revision's data begins and ends.
1573 """
1572 """
1574 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1573 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1575 # (functions are expensive).
1574 # (functions are expensive).
1576 index = self.index
1575 index = self.index
1577 istart = index[startrev]
1576 istart = index[startrev]
1578 start = int(istart[0] >> 16)
1577 start = int(istart[0] >> 16)
1579 if startrev == endrev:
1578 if startrev == endrev:
1580 end = start + istart[1]
1579 end = start + istart[1]
1581 else:
1580 else:
1582 iend = index[endrev]
1581 iend = index[endrev]
1583 end = int(iend[0] >> 16) + iend[1]
1582 end = int(iend[0] >> 16) + iend[1]
1584
1583
1585 if self._inline:
1584 if self._inline:
1586 start += (startrev + 1) * self._io.size
1585 start += (startrev + 1) * self._io.size
1587 end += (endrev + 1) * self._io.size
1586 end += (endrev + 1) * self._io.size
1588 length = end - start
1587 length = end - start
1589
1588
1590 return start, self._getsegment(start, length, df=df)
1589 return start, self._getsegment(start, length, df=df)
1591
1590
1592 def _chunk(self, rev, df=None):
1591 def _chunk(self, rev, df=None):
1593 """Obtain a single decompressed chunk for a revision.
1592 """Obtain a single decompressed chunk for a revision.
1594
1593
1595 Accepts an integer revision and an optional already-open file handle
1594 Accepts an integer revision and an optional already-open file handle
1596 to be used for reading. If used, the seek position of the file will not
1595 to be used for reading. If used, the seek position of the file will not
1597 be preserved.
1596 be preserved.
1598
1597
1599 Returns a str holding uncompressed data for the requested revision.
1598 Returns a str holding uncompressed data for the requested revision.
1600 """
1599 """
1601 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1600 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1602
1601
1603 def _chunks(self, revs, df=None):
1602 def _chunks(self, revs, df=None):
1604 """Obtain decompressed chunks for the specified revisions.
1603 """Obtain decompressed chunks for the specified revisions.
1605
1604
1606 Accepts an iterable of numeric revisions that are assumed to be in
1605 Accepts an iterable of numeric revisions that are assumed to be in
1607 ascending order. Also accepts an optional already-open file handle
1606 ascending order. Also accepts an optional already-open file handle
1608 to be used for reading. If used, the seek position of the file will
1607 to be used for reading. If used, the seek position of the file will
1609 not be preserved.
1608 not be preserved.
1610
1609
1611 This function is similar to calling ``self._chunk()`` multiple times,
1610 This function is similar to calling ``self._chunk()`` multiple times,
1612 but is faster.
1611 but is faster.
1613
1612
1614 Returns a list with decompressed data for each requested revision.
1613 Returns a list with decompressed data for each requested revision.
1615 """
1614 """
1616 if not revs:
1615 if not revs:
1617 return []
1616 return []
1618 start = self.start
1617 start = self.start
1619 length = self.length
1618 length = self.length
1620 inline = self._inline
1619 inline = self._inline
1621 iosize = self._io.size
1620 iosize = self._io.size
1622 buffer = util.buffer
1621 buffer = util.buffer
1623
1622
1624 l = []
1623 l = []
1625 ladd = l.append
1624 ladd = l.append
1626
1625
1627 if not self._withsparseread:
1626 if not self._withsparseread:
1628 slicedchunks = (revs,)
1627 slicedchunks = (revs,)
1629 else:
1628 else:
1630 slicedchunks = _slicechunk(self, revs)
1629 slicedchunks = _slicechunk(self, revs)
1631
1630
1632 for revschunk in slicedchunks:
1631 for revschunk in slicedchunks:
1633 firstrev = revschunk[0]
1632 firstrev = revschunk[0]
1634 # Skip trailing revisions with empty diff
1633 # Skip trailing revisions with empty diff
1635 for lastrev in revschunk[::-1]:
1634 for lastrev in revschunk[::-1]:
1636 if length(lastrev) != 0:
1635 if length(lastrev) != 0:
1637 break
1636 break
1638
1637
1639 try:
1638 try:
1640 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1639 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1641 except OverflowError:
1640 except OverflowError:
1642 # issue4215 - we can't cache a run of chunks greater than
1641 # issue4215 - we can't cache a run of chunks greater than
1643 # 2G on Windows
1642 # 2G on Windows
1644 return [self._chunk(rev, df=df) for rev in revschunk]
1643 return [self._chunk(rev, df=df) for rev in revschunk]
1645
1644
1646 decomp = self.decompress
1645 decomp = self.decompress
1647 for rev in revschunk:
1646 for rev in revschunk:
1648 chunkstart = start(rev)
1647 chunkstart = start(rev)
1649 if inline:
1648 if inline:
1650 chunkstart += (rev + 1) * iosize
1649 chunkstart += (rev + 1) * iosize
1651 chunklength = length(rev)
1650 chunklength = length(rev)
1652 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1651 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1653
1652
1654 return l
1653 return l
1655
1654
1656 def _chunkclear(self):
1655 def _chunkclear(self):
1657 """Clear the raw chunk cache."""
1656 """Clear the raw chunk cache."""
1658 self._chunkcache = (0, '')
1657 self._chunkcache = (0, '')
1659
1658
1660 def deltaparent(self, rev):
1659 def deltaparent(self, rev):
1661 """return deltaparent of the given revision"""
1660 """return deltaparent of the given revision"""
1662 base = self.index[rev][3]
1661 base = self.index[rev][3]
1663 if base == rev:
1662 if base == rev:
1664 return nullrev
1663 return nullrev
1665 elif self._generaldelta:
1664 elif self._generaldelta:
1666 return base
1665 return base
1667 else:
1666 else:
1668 return rev - 1
1667 return rev - 1
1669
1668
1670 def revdiff(self, rev1, rev2):
1669 def revdiff(self, rev1, rev2):
1671 """return or calculate a delta between two revisions
1670 """return or calculate a delta between two revisions
1672
1671
1673 The delta calculated is in binary form and is intended to be written to
1672 The delta calculated is in binary form and is intended to be written to
1674 revlog data directly. So this function needs raw revision data.
1673 revlog data directly. So this function needs raw revision data.
1675 """
1674 """
1676 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1675 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1677 return bytes(self._chunk(rev2))
1676 return bytes(self._chunk(rev2))
1678
1677
1679 return mdiff.textdiff(self.revision(rev1, raw=True),
1678 return mdiff.textdiff(self.revision(rev1, raw=True),
1680 self.revision(rev2, raw=True))
1679 self.revision(rev2, raw=True))
1681
1680
1682 def revision(self, nodeorrev, _df=None, raw=False):
1681 def revision(self, nodeorrev, _df=None, raw=False):
1683 """return an uncompressed revision of a given node or revision
1682 """return an uncompressed revision of a given node or revision
1684 number.
1683 number.
1685
1684
1686 _df - an existing file handle to read from. (internal-only)
1685 _df - an existing file handle to read from. (internal-only)
1687 raw - an optional argument specifying if the revision data is to be
1686 raw - an optional argument specifying if the revision data is to be
1688 treated as raw data when applying flag transforms. 'raw' should be set
1687 treated as raw data when applying flag transforms. 'raw' should be set
1689 to True when generating changegroups or in debug commands.
1688 to True when generating changegroups or in debug commands.
1690 """
1689 """
1691 if isinstance(nodeorrev, int):
1690 if isinstance(nodeorrev, int):
1692 rev = nodeorrev
1691 rev = nodeorrev
1693 node = self.node(rev)
1692 node = self.node(rev)
1694 else:
1693 else:
1695 node = nodeorrev
1694 node = nodeorrev
1696 rev = None
1695 rev = None
1697
1696
1698 cachedrev = None
1697 cachedrev = None
1699 flags = None
1698 flags = None
1700 rawtext = None
1699 rawtext = None
1701 if node == nullid:
1700 if node == nullid:
1702 return ""
1701 return ""
1703 if self._cache:
1702 if self._cache:
1704 if self._cache[0] == node:
1703 if self._cache[0] == node:
1705 # _cache only stores rawtext
1704 # _cache only stores rawtext
1706 if raw:
1705 if raw:
1707 return self._cache[2]
1706 return self._cache[2]
1708 # duplicated, but good for perf
1707 # duplicated, but good for perf
1709 if rev is None:
1708 if rev is None:
1710 rev = self.rev(node)
1709 rev = self.rev(node)
1711 if flags is None:
1710 if flags is None:
1712 flags = self.flags(rev)
1711 flags = self.flags(rev)
1713 # no extra flags set, no flag processor runs, text = rawtext
1712 # no extra flags set, no flag processor runs, text = rawtext
1714 if flags == REVIDX_DEFAULT_FLAGS:
1713 if flags == REVIDX_DEFAULT_FLAGS:
1715 return self._cache[2]
1714 return self._cache[2]
1716 # rawtext is reusable. need to run flag processor
1715 # rawtext is reusable. need to run flag processor
1717 rawtext = self._cache[2]
1716 rawtext = self._cache[2]
1718
1717
1719 cachedrev = self._cache[1]
1718 cachedrev = self._cache[1]
1720
1719
1721 # look up what we need to read
1720 # look up what we need to read
1722 if rawtext is None:
1721 if rawtext is None:
1723 if rev is None:
1722 if rev is None:
1724 rev = self.rev(node)
1723 rev = self.rev(node)
1725
1724
1726 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1725 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1727 if stopped:
1726 if stopped:
1728 rawtext = self._cache[2]
1727 rawtext = self._cache[2]
1729
1728
1730 # drop cache to save memory
1729 # drop cache to save memory
1731 self._cache = None
1730 self._cache = None
1732
1731
1733 bins = self._chunks(chain, df=_df)
1732 bins = self._chunks(chain, df=_df)
1734 if rawtext is None:
1733 if rawtext is None:
1735 rawtext = bytes(bins[0])
1734 rawtext = bytes(bins[0])
1736 bins = bins[1:]
1735 bins = bins[1:]
1737
1736
1738 rawtext = mdiff.patches(rawtext, bins)
1737 rawtext = mdiff.patches(rawtext, bins)
1739 self._cache = (node, rev, rawtext)
1738 self._cache = (node, rev, rawtext)
1740
1739
1741 if flags is None:
1740 if flags is None:
1742 if rev is None:
1741 if rev is None:
1743 rev = self.rev(node)
1742 rev = self.rev(node)
1744 flags = self.flags(rev)
1743 flags = self.flags(rev)
1745
1744
1746 text, validatehash = self._processflags(rawtext, flags, 'read', raw=raw)
1745 text, validatehash = self._processflags(rawtext, flags, 'read', raw=raw)
1747 if validatehash:
1746 if validatehash:
1748 self.checkhash(text, node, rev=rev)
1747 self.checkhash(text, node, rev=rev)
1749
1748
1750 return text
1749 return text
1751
1750
1752 def hash(self, text, p1, p2):
1751 def hash(self, text, p1, p2):
1753 """Compute a node hash.
1752 """Compute a node hash.
1754
1753
1755 Available as a function so that subclasses can replace the hash
1754 Available as a function so that subclasses can replace the hash
1756 as needed.
1755 as needed.
1757 """
1756 """
1758 return hash(text, p1, p2)
1757 return hash(text, p1, p2)
1759
1758
1760 def _processflags(self, text, flags, operation, raw=False):
1759 def _processflags(self, text, flags, operation, raw=False):
1761 """Inspect revision data flags and applies transforms defined by
1760 """Inspect revision data flags and applies transforms defined by
1762 registered flag processors.
1761 registered flag processors.
1763
1762
1764 ``text`` - the revision data to process
1763 ``text`` - the revision data to process
1765 ``flags`` - the revision flags
1764 ``flags`` - the revision flags
1766 ``operation`` - the operation being performed (read or write)
1765 ``operation`` - the operation being performed (read or write)
1767 ``raw`` - an optional argument describing if the raw transform should be
1766 ``raw`` - an optional argument describing if the raw transform should be
1768 applied.
1767 applied.
1769
1768
1770 This method processes the flags in the order (or reverse order if
1769 This method processes the flags in the order (or reverse order if
1771 ``operation`` is 'write') defined by REVIDX_FLAGS_ORDER, applying the
1770 ``operation`` is 'write') defined by REVIDX_FLAGS_ORDER, applying the
1772 flag processors registered for present flags. The order of flags defined
1771 flag processors registered for present flags. The order of flags defined
1773 in REVIDX_FLAGS_ORDER needs to be stable to allow non-commutativity.
1772 in REVIDX_FLAGS_ORDER needs to be stable to allow non-commutativity.
1774
1773
1775 Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
1774 Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
1776 processed text and ``validatehash`` is a bool indicating whether the
1775 processed text and ``validatehash`` is a bool indicating whether the
1777 returned text should be checked for hash integrity.
1776 returned text should be checked for hash integrity.
1778
1777
1779 Note: If the ``raw`` argument is set, it has precedence over the
1778 Note: If the ``raw`` argument is set, it has precedence over the
1780 operation and will only update the value of ``validatehash``.
1779 operation and will only update the value of ``validatehash``.
1781 """
1780 """
1782 # fast path: no flag processors will run
1781 # fast path: no flag processors will run
1783 if flags == 0:
1782 if flags == 0:
1784 return text, True
1783 return text, True
1785 if not operation in ('read', 'write'):
1784 if not operation in ('read', 'write'):
1786 raise ProgrammingError(_("invalid '%s' operation ") % (operation))
1785 raise ProgrammingError(_("invalid '%s' operation ") % (operation))
1787 # Check all flags are known.
1786 # Check all flags are known.
1788 if flags & ~REVIDX_KNOWN_FLAGS:
1787 if flags & ~REVIDX_KNOWN_FLAGS:
1789 raise RevlogError(_("incompatible revision flag '%#x'") %
1788 raise RevlogError(_("incompatible revision flag '%#x'") %
1790 (flags & ~REVIDX_KNOWN_FLAGS))
1789 (flags & ~REVIDX_KNOWN_FLAGS))
1791 validatehash = True
1790 validatehash = True
1792 # Depending on the operation (read or write), the order might be
1791 # Depending on the operation (read or write), the order might be
1793 # reversed due to non-commutative transforms.
1792 # reversed due to non-commutative transforms.
1794 orderedflags = REVIDX_FLAGS_ORDER
1793 orderedflags = REVIDX_FLAGS_ORDER
1795 if operation == 'write':
1794 if operation == 'write':
1796 orderedflags = reversed(orderedflags)
1795 orderedflags = reversed(orderedflags)
1797
1796
1798 for flag in orderedflags:
1797 for flag in orderedflags:
1799 # If a flagprocessor has been registered for a known flag, apply the
1798 # If a flagprocessor has been registered for a known flag, apply the
1800 # related operation transform and update result tuple.
1799 # related operation transform and update result tuple.
1801 if flag & flags:
1800 if flag & flags:
1802 vhash = True
1801 vhash = True
1803
1802
1804 if flag not in _flagprocessors:
1803 if flag not in _flagprocessors:
1805 message = _("missing processor for flag '%#x'") % (flag)
1804 message = _("missing processor for flag '%#x'") % (flag)
1806 raise RevlogError(message)
1805 raise RevlogError(message)
1807
1806
1808 processor = _flagprocessors[flag]
1807 processor = _flagprocessors[flag]
1809 if processor is not None:
1808 if processor is not None:
1810 readtransform, writetransform, rawtransform = processor
1809 readtransform, writetransform, rawtransform = processor
1811
1810
1812 if raw:
1811 if raw:
1813 vhash = rawtransform(self, text)
1812 vhash = rawtransform(self, text)
1814 elif operation == 'read':
1813 elif operation == 'read':
1815 text, vhash = readtransform(self, text)
1814 text, vhash = readtransform(self, text)
1816 else: # write operation
1815 else: # write operation
1817 text, vhash = writetransform(self, text)
1816 text, vhash = writetransform(self, text)
1818 validatehash = validatehash and vhash
1817 validatehash = validatehash and vhash
1819
1818
1820 return text, validatehash
1819 return text, validatehash
1821
1820
1822 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1821 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1823 """Check node hash integrity.
1822 """Check node hash integrity.
1824
1823
1825 Available as a function so that subclasses can extend hash mismatch
1824 Available as a function so that subclasses can extend hash mismatch
1826 behaviors as needed.
1825 behaviors as needed.
1827 """
1826 """
1828 if p1 is None and p2 is None:
1827 if p1 is None and p2 is None:
1829 p1, p2 = self.parents(node)
1828 p1, p2 = self.parents(node)
1830 if node != self.hash(text, p1, p2):
1829 if node != self.hash(text, p1, p2):
1831 revornode = rev
1830 revornode = rev
1832 if revornode is None:
1831 if revornode is None:
1833 revornode = templatefilters.short(hex(node))
1832 revornode = templatefilters.short(hex(node))
1834 raise RevlogError(_("integrity check failed on %s:%s")
1833 raise RevlogError(_("integrity check failed on %s:%s")
1835 % (self.indexfile, pycompat.bytestr(revornode)))
1834 % (self.indexfile, pycompat.bytestr(revornode)))
1836
1835
1837 def _enforceinlinesize(self, tr, fp=None):
1836 def _enforceinlinesize(self, tr, fp=None):
1838 """Check if the revlog is too big for inline and convert if so.
1837 """Check if the revlog is too big for inline and convert if so.
1839
1838
1840 This should be called after revisions are added to the revlog. If the
1839 This should be called after revisions are added to the revlog. If the
1841 revlog has grown too large to be an inline revlog, it will convert it
1840 revlog has grown too large to be an inline revlog, it will convert it
1842 to use multiple index and data files.
1841 to use multiple index and data files.
1843 """
1842 """
1844 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1843 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1845 return
1844 return
1846
1845
1847 trinfo = tr.find(self.indexfile)
1846 trinfo = tr.find(self.indexfile)
1848 if trinfo is None:
1847 if trinfo is None:
1849 raise RevlogError(_("%s not found in the transaction")
1848 raise RevlogError(_("%s not found in the transaction")
1850 % self.indexfile)
1849 % self.indexfile)
1851
1850
1852 trindex = trinfo[2]
1851 trindex = trinfo[2]
1853 if trindex is not None:
1852 if trindex is not None:
1854 dataoff = self.start(trindex)
1853 dataoff = self.start(trindex)
1855 else:
1854 else:
1856 # revlog was stripped at start of transaction, use all leftover data
1855 # revlog was stripped at start of transaction, use all leftover data
1857 trindex = len(self) - 1
1856 trindex = len(self) - 1
1858 dataoff = self.end(-2)
1857 dataoff = self.end(-2)
1859
1858
1860 tr.add(self.datafile, dataoff)
1859 tr.add(self.datafile, dataoff)
1861
1860
1862 if fp:
1861 if fp:
1863 fp.flush()
1862 fp.flush()
1864 fp.close()
1863 fp.close()
1865
1864
1866 with self._datafp('w') as df:
1865 with self._datafp('w') as df:
1867 for r in self:
1866 for r in self:
1868 df.write(self._getsegmentforrevs(r, r)[1])
1867 df.write(self._getsegmentforrevs(r, r)[1])
1869
1868
1870 with self._indexfp('w') as fp:
1869 with self._indexfp('w') as fp:
1871 self.version &= ~FLAG_INLINE_DATA
1870 self.version &= ~FLAG_INLINE_DATA
1872 self._inline = False
1871 self._inline = False
1873 io = self._io
1872 io = self._io
1874 for i in self:
1873 for i in self:
1875 e = io.packentry(self.index[i], self.node, self.version, i)
1874 e = io.packentry(self.index[i], self.node, self.version, i)
1876 fp.write(e)
1875 fp.write(e)
1877
1876
1878 # the temp file replace the real index when we exit the context
1877 # the temp file replace the real index when we exit the context
1879 # manager
1878 # manager
1880
1879
1881 tr.replace(self.indexfile, trindex * self._io.size)
1880 tr.replace(self.indexfile, trindex * self._io.size)
1882 self._chunkclear()
1881 self._chunkclear()
1883
1882
1884 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1883 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1885 node=None, flags=REVIDX_DEFAULT_FLAGS, deltacomputer=None):
1884 node=None, flags=REVIDX_DEFAULT_FLAGS, deltacomputer=None):
1886 """add a revision to the log
1885 """add a revision to the log
1887
1886
1888 text - the revision data to add
1887 text - the revision data to add
1889 transaction - the transaction object used for rollback
1888 transaction - the transaction object used for rollback
1890 link - the linkrev data to add
1889 link - the linkrev data to add
1891 p1, p2 - the parent nodeids of the revision
1890 p1, p2 - the parent nodeids of the revision
1892 cachedelta - an optional precomputed delta
1891 cachedelta - an optional precomputed delta
1893 node - nodeid of revision; typically node is not specified, and it is
1892 node - nodeid of revision; typically node is not specified, and it is
1894 computed by default as hash(text, p1, p2), however subclasses might
1893 computed by default as hash(text, p1, p2), however subclasses might
1895 use different hashing method (and override checkhash() in such case)
1894 use different hashing method (and override checkhash() in such case)
1896 flags - the known flags to set on the revision
1895 flags - the known flags to set on the revision
1897 deltacomputer - an optional _deltacomputer instance shared between
1896 deltacomputer - an optional _deltacomputer instance shared between
1898 multiple calls
1897 multiple calls
1899 """
1898 """
1900 if link == nullrev:
1899 if link == nullrev:
1901 raise RevlogError(_("attempted to add linkrev -1 to %s")
1900 raise RevlogError(_("attempted to add linkrev -1 to %s")
1902 % self.indexfile)
1901 % self.indexfile)
1903
1902
1904 if flags:
1903 if flags:
1905 node = node or self.hash(text, p1, p2)
1904 node = node or self.hash(text, p1, p2)
1906
1905
1907 rawtext, validatehash = self._processflags(text, flags, 'write')
1906 rawtext, validatehash = self._processflags(text, flags, 'write')
1908
1907
1909 # If the flag processor modifies the revision data, ignore any provided
1908 # If the flag processor modifies the revision data, ignore any provided
1910 # cachedelta.
1909 # cachedelta.
1911 if rawtext != text:
1910 if rawtext != text:
1912 cachedelta = None
1911 cachedelta = None
1913
1912
1914 if len(rawtext) > _maxentrysize:
1913 if len(rawtext) > _maxentrysize:
1915 raise RevlogError(
1914 raise RevlogError(
1916 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1915 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1917 % (self.indexfile, len(rawtext)))
1916 % (self.indexfile, len(rawtext)))
1918
1917
1919 node = node or self.hash(rawtext, p1, p2)
1918 node = node or self.hash(rawtext, p1, p2)
1920 if node in self.nodemap:
1919 if node in self.nodemap:
1921 return node
1920 return node
1922
1921
1923 if validatehash:
1922 if validatehash:
1924 self.checkhash(rawtext, node, p1=p1, p2=p2)
1923 self.checkhash(rawtext, node, p1=p1, p2=p2)
1925
1924
1926 return self.addrawrevision(rawtext, transaction, link, p1, p2, node,
1925 return self.addrawrevision(rawtext, transaction, link, p1, p2, node,
1927 flags, cachedelta=cachedelta,
1926 flags, cachedelta=cachedelta,
1928 deltacomputer=deltacomputer)
1927 deltacomputer=deltacomputer)
1929
1928
1930 def addrawrevision(self, rawtext, transaction, link, p1, p2, node, flags,
1929 def addrawrevision(self, rawtext, transaction, link, p1, p2, node, flags,
1931 cachedelta=None, deltacomputer=None):
1930 cachedelta=None, deltacomputer=None):
1932 """add a raw revision with known flags, node and parents
1931 """add a raw revision with known flags, node and parents
1933 useful when reusing a revision not stored in this revlog (ex: received
1932 useful when reusing a revision not stored in this revlog (ex: received
1934 over wire, or read from an external bundle).
1933 over wire, or read from an external bundle).
1935 """
1934 """
1936 dfh = None
1935 dfh = None
1937 if not self._inline:
1936 if not self._inline:
1938 dfh = self._datafp("a+")
1937 dfh = self._datafp("a+")
1939 ifh = self._indexfp("a+")
1938 ifh = self._indexfp("a+")
1940 try:
1939 try:
1941 return self._addrevision(node, rawtext, transaction, link, p1, p2,
1940 return self._addrevision(node, rawtext, transaction, link, p1, p2,
1942 flags, cachedelta, ifh, dfh,
1941 flags, cachedelta, ifh, dfh,
1943 deltacomputer=deltacomputer)
1942 deltacomputer=deltacomputer)
1944 finally:
1943 finally:
1945 if dfh:
1944 if dfh:
1946 dfh.close()
1945 dfh.close()
1947 ifh.close()
1946 ifh.close()
1948
1947
1949 def compress(self, data):
1948 def compress(self, data):
1950 """Generate a possibly-compressed representation of data."""
1949 """Generate a possibly-compressed representation of data."""
1951 if not data:
1950 if not data:
1952 return '', data
1951 return '', data
1953
1952
1954 compressed = self._compressor.compress(data)
1953 compressed = self._compressor.compress(data)
1955
1954
1956 if compressed:
1955 if compressed:
1957 # The revlog compressor added the header in the returned data.
1956 # The revlog compressor added the header in the returned data.
1958 return '', compressed
1957 return '', compressed
1959
1958
1960 if data[0:1] == '\0':
1959 if data[0:1] == '\0':
1961 return '', data
1960 return '', data
1962 return 'u', data
1961 return 'u', data
1963
1962
1964 def decompress(self, data):
1963 def decompress(self, data):
1965 """Decompress a revlog chunk.
1964 """Decompress a revlog chunk.
1966
1965
1967 The chunk is expected to begin with a header identifying the
1966 The chunk is expected to begin with a header identifying the
1968 format type so it can be routed to an appropriate decompressor.
1967 format type so it can be routed to an appropriate decompressor.
1969 """
1968 """
1970 if not data:
1969 if not data:
1971 return data
1970 return data
1972
1971
1973 # Revlogs are read much more frequently than they are written and many
1972 # Revlogs are read much more frequently than they are written and many
1974 # chunks only take microseconds to decompress, so performance is
1973 # chunks only take microseconds to decompress, so performance is
1975 # important here.
1974 # important here.
1976 #
1975 #
1977 # We can make a few assumptions about revlogs:
1976 # We can make a few assumptions about revlogs:
1978 #
1977 #
1979 # 1) the majority of chunks will be compressed (as opposed to inline
1978 # 1) the majority of chunks will be compressed (as opposed to inline
1980 # raw data).
1979 # raw data).
1981 # 2) decompressing *any* data will likely by at least 10x slower than
1980 # 2) decompressing *any* data will likely by at least 10x slower than
1982 # returning raw inline data.
1981 # returning raw inline data.
1983 # 3) we want to prioritize common and officially supported compression
1982 # 3) we want to prioritize common and officially supported compression
1984 # engines
1983 # engines
1985 #
1984 #
1986 # It follows that we want to optimize for "decompress compressed data
1985 # It follows that we want to optimize for "decompress compressed data
1987 # when encoded with common and officially supported compression engines"
1986 # when encoded with common and officially supported compression engines"
1988 # case over "raw data" and "data encoded by less common or non-official
1987 # case over "raw data" and "data encoded by less common or non-official
1989 # compression engines." That is why we have the inline lookup first
1988 # compression engines." That is why we have the inline lookup first
1990 # followed by the compengines lookup.
1989 # followed by the compengines lookup.
1991 #
1990 #
1992 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
1991 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
1993 # compressed chunks. And this matters for changelog and manifest reads.
1992 # compressed chunks. And this matters for changelog and manifest reads.
1994 t = data[0:1]
1993 t = data[0:1]
1995
1994
1996 if t == 'x':
1995 if t == 'x':
1997 try:
1996 try:
1998 return _zlibdecompress(data)
1997 return _zlibdecompress(data)
1999 except zlib.error as e:
1998 except zlib.error as e:
2000 raise RevlogError(_('revlog decompress error: %s') % str(e))
1999 raise RevlogError(_('revlog decompress error: %s') % str(e))
2001 # '\0' is more common than 'u' so it goes first.
2000 # '\0' is more common than 'u' so it goes first.
2002 elif t == '\0':
2001 elif t == '\0':
2003 return data
2002 return data
2004 elif t == 'u':
2003 elif t == 'u':
2005 return util.buffer(data, 1)
2004 return util.buffer(data, 1)
2006
2005
2007 try:
2006 try:
2008 compressor = self._decompressors[t]
2007 compressor = self._decompressors[t]
2009 except KeyError:
2008 except KeyError:
2010 try:
2009 try:
2011 engine = util.compengines.forrevlogheader(t)
2010 engine = util.compengines.forrevlogheader(t)
2012 compressor = engine.revlogcompressor()
2011 compressor = engine.revlogcompressor()
2013 self._decompressors[t] = compressor
2012 self._decompressors[t] = compressor
2014 except KeyError:
2013 except KeyError:
2015 raise RevlogError(_('unknown compression type %r') % t)
2014 raise RevlogError(_('unknown compression type %r') % t)
2016
2015
2017 return compressor.decompress(data)
2016 return compressor.decompress(data)
2018
2017
2019 def _isgooddeltainfo(self, d, textlen):
2018 def _isgooddeltainfo(self, d, textlen):
2020 """Returns True if the given delta is good. Good means that it is within
2019 """Returns True if the given delta is good. Good means that it is within
2021 the disk span, disk size, and chain length bounds that we know to be
2020 the disk span, disk size, and chain length bounds that we know to be
2022 performant."""
2021 performant."""
2023 if d is None:
2022 if d is None:
2024 return False
2023 return False
2025
2024
2026 # - 'd.distance' is the distance from the base revision -- bounding it
2025 # - 'd.distance' is the distance from the base revision -- bounding it
2027 # limits the amount of I/O we need to do.
2026 # limits the amount of I/O we need to do.
2028 # - 'd.compresseddeltalen' is the sum of the total size of deltas we
2027 # - 'd.compresseddeltalen' is the sum of the total size of deltas we
2029 # need to apply -- bounding it limits the amount of CPU we consume.
2028 # need to apply -- bounding it limits the amount of CPU we consume.
2030
2029
2031 defaultmax = textlen * 4
2030 defaultmax = textlen * 4
2032 maxdist = self._maxdeltachainspan
2031 maxdist = self._maxdeltachainspan
2033 if not maxdist:
2032 if not maxdist:
2034 maxdist = d.distance # ensure the conditional pass
2033 maxdist = d.distance # ensure the conditional pass
2035 maxdist = max(maxdist, defaultmax)
2034 maxdist = max(maxdist, defaultmax)
2036 if (d.distance > maxdist or d.deltalen > textlen or
2035 if (d.distance > maxdist or d.deltalen > textlen or
2037 d.compresseddeltalen > textlen * 2 or
2036 d.compresseddeltalen > textlen * 2 or
2038 (self._maxchainlen and d.chainlen > self._maxchainlen)):
2037 (self._maxchainlen and d.chainlen > self._maxchainlen)):
2039 return False
2038 return False
2040
2039
2041 return True
2040 return True
2042
2041
2043 def _addrevision(self, node, rawtext, transaction, link, p1, p2, flags,
2042 def _addrevision(self, node, rawtext, transaction, link, p1, p2, flags,
2044 cachedelta, ifh, dfh, alwayscache=False,
2043 cachedelta, ifh, dfh, alwayscache=False,
2045 deltacomputer=None):
2044 deltacomputer=None):
2046 """internal function to add revisions to the log
2045 """internal function to add revisions to the log
2047
2046
2048 see addrevision for argument descriptions.
2047 see addrevision for argument descriptions.
2049
2048
2050 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2049 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2051
2050
2052 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2051 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2053 be used.
2052 be used.
2054
2053
2055 invariants:
2054 invariants:
2056 - rawtext is optional (can be None); if not set, cachedelta must be set.
2055 - rawtext is optional (can be None); if not set, cachedelta must be set.
2057 if both are set, they must correspond to each other.
2056 if both are set, they must correspond to each other.
2058 """
2057 """
2059 if node == nullid:
2058 if node == nullid:
2060 raise RevlogError(_("%s: attempt to add null revision") %
2059 raise RevlogError(_("%s: attempt to add null revision") %
2061 (self.indexfile))
2060 (self.indexfile))
2062 if node == wdirid:
2061 if node == wdirid:
2063 raise RevlogError(_("%s: attempt to add wdir revision") %
2062 raise RevlogError(_("%s: attempt to add wdir revision") %
2064 (self.indexfile))
2063 (self.indexfile))
2065
2064
2066 if self._inline:
2065 if self._inline:
2067 fh = ifh
2066 fh = ifh
2068 else:
2067 else:
2069 fh = dfh
2068 fh = dfh
2070
2069
2071 btext = [rawtext]
2070 btext = [rawtext]
2072
2071
2073 curr = len(self)
2072 curr = len(self)
2074 prev = curr - 1
2073 prev = curr - 1
2075 offset = self.end(prev)
2074 offset = self.end(prev)
2076 p1r, p2r = self.rev(p1), self.rev(p2)
2075 p1r, p2r = self.rev(p1), self.rev(p2)
2077
2076
2078 # full versions are inserted when the needed deltas
2077 # full versions are inserted when the needed deltas
2079 # become comparable to the uncompressed text
2078 # become comparable to the uncompressed text
2080 if rawtext is None:
2079 if rawtext is None:
2081 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
2080 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
2082 cachedelta[1])
2081 cachedelta[1])
2083 else:
2082 else:
2084 textlen = len(rawtext)
2083 textlen = len(rawtext)
2085
2084
2086 if deltacomputer is None:
2085 if deltacomputer is None:
2087 deltacomputer = _deltacomputer(self)
2086 deltacomputer = _deltacomputer(self)
2088
2087
2089 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2088 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2090 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2089 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2091
2090
2092 if deltainfo is not None:
2091 if deltainfo is not None:
2093 base = deltainfo.base
2092 base = deltainfo.base
2094 chainbase = deltainfo.chainbase
2093 chainbase = deltainfo.chainbase
2095 data = deltainfo.data
2094 data = deltainfo.data
2096 l = deltainfo.deltalen
2095 l = deltainfo.deltalen
2097 else:
2096 else:
2098 rawtext = deltacomputer.buildtext(revinfo, fh)
2097 rawtext = deltacomputer.buildtext(revinfo, fh)
2099 data = self.compress(rawtext)
2098 data = self.compress(rawtext)
2100 l = len(data[1]) + len(data[0])
2099 l = len(data[1]) + len(data[0])
2101 base = chainbase = curr
2100 base = chainbase = curr
2102
2101
2103 e = (offset_type(offset, flags), l, textlen,
2102 e = (offset_type(offset, flags), l, textlen,
2104 base, link, p1r, p2r, node)
2103 base, link, p1r, p2r, node)
2105 self.index.insert(-1, e)
2104 self.index.insert(-1, e)
2106 self.nodemap[node] = curr
2105 self.nodemap[node] = curr
2107
2106
2108 entry = self._io.packentry(e, self.node, self.version, curr)
2107 entry = self._io.packentry(e, self.node, self.version, curr)
2109 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
2108 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
2110
2109
2111 if alwayscache and rawtext is None:
2110 if alwayscache and rawtext is None:
2112 rawtext = deltacomputer._buildtext(revinfo, fh)
2111 rawtext = deltacomputer._buildtext(revinfo, fh)
2113
2112
2114 if type(rawtext) == bytes: # only accept immutable objects
2113 if type(rawtext) == bytes: # only accept immutable objects
2115 self._cache = (node, curr, rawtext)
2114 self._cache = (node, curr, rawtext)
2116 self._chainbasecache[curr] = chainbase
2115 self._chainbasecache[curr] = chainbase
2117 return node
2116 return node
2118
2117
2119 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2118 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2120 # Files opened in a+ mode have inconsistent behavior on various
2119 # Files opened in a+ mode have inconsistent behavior on various
2121 # platforms. Windows requires that a file positioning call be made
2120 # platforms. Windows requires that a file positioning call be made
2122 # when the file handle transitions between reads and writes. See
2121 # when the file handle transitions between reads and writes. See
2123 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2122 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2124 # platforms, Python or the platform itself can be buggy. Some versions
2123 # platforms, Python or the platform itself can be buggy. Some versions
2125 # of Solaris have been observed to not append at the end of the file
2124 # of Solaris have been observed to not append at the end of the file
2126 # if the file was seeked to before the end. See issue4943 for more.
2125 # if the file was seeked to before the end. See issue4943 for more.
2127 #
2126 #
2128 # We work around this issue by inserting a seek() before writing.
2127 # We work around this issue by inserting a seek() before writing.
2129 # Note: This is likely not necessary on Python 3.
2128 # Note: This is likely not necessary on Python 3.
2130 ifh.seek(0, os.SEEK_END)
2129 ifh.seek(0, os.SEEK_END)
2131 if dfh:
2130 if dfh:
2132 dfh.seek(0, os.SEEK_END)
2131 dfh.seek(0, os.SEEK_END)
2133
2132
2134 curr = len(self) - 1
2133 curr = len(self) - 1
2135 if not self._inline:
2134 if not self._inline:
2136 transaction.add(self.datafile, offset)
2135 transaction.add(self.datafile, offset)
2137 transaction.add(self.indexfile, curr * len(entry))
2136 transaction.add(self.indexfile, curr * len(entry))
2138 if data[0]:
2137 if data[0]:
2139 dfh.write(data[0])
2138 dfh.write(data[0])
2140 dfh.write(data[1])
2139 dfh.write(data[1])
2141 ifh.write(entry)
2140 ifh.write(entry)
2142 else:
2141 else:
2143 offset += curr * self._io.size
2142 offset += curr * self._io.size
2144 transaction.add(self.indexfile, offset, curr)
2143 transaction.add(self.indexfile, offset, curr)
2145 ifh.write(entry)
2144 ifh.write(entry)
2146 ifh.write(data[0])
2145 ifh.write(data[0])
2147 ifh.write(data[1])
2146 ifh.write(data[1])
2148 self._enforceinlinesize(transaction, ifh)
2147 self._enforceinlinesize(transaction, ifh)
2149
2148
2150 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
2149 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
2151 """
2150 """
2152 add a delta group
2151 add a delta group
2153
2152
2154 given a set of deltas, add them to the revision log. the
2153 given a set of deltas, add them to the revision log. the
2155 first delta is against its parent, which should be in our
2154 first delta is against its parent, which should be in our
2156 log, the rest are against the previous delta.
2155 log, the rest are against the previous delta.
2157
2156
2158 If ``addrevisioncb`` is defined, it will be called with arguments of
2157 If ``addrevisioncb`` is defined, it will be called with arguments of
2159 this revlog and the node that was added.
2158 this revlog and the node that was added.
2160 """
2159 """
2161
2160
2162 nodes = []
2161 nodes = []
2163
2162
2164 r = len(self)
2163 r = len(self)
2165 end = 0
2164 end = 0
2166 if r:
2165 if r:
2167 end = self.end(r - 1)
2166 end = self.end(r - 1)
2168 ifh = self._indexfp("a+")
2167 ifh = self._indexfp("a+")
2169 isize = r * self._io.size
2168 isize = r * self._io.size
2170 if self._inline:
2169 if self._inline:
2171 transaction.add(self.indexfile, end + isize, r)
2170 transaction.add(self.indexfile, end + isize, r)
2172 dfh = None
2171 dfh = None
2173 else:
2172 else:
2174 transaction.add(self.indexfile, isize, r)
2173 transaction.add(self.indexfile, isize, r)
2175 transaction.add(self.datafile, end)
2174 transaction.add(self.datafile, end)
2176 dfh = self._datafp("a+")
2175 dfh = self._datafp("a+")
2177 def flush():
2176 def flush():
2178 if dfh:
2177 if dfh:
2179 dfh.flush()
2178 dfh.flush()
2180 ifh.flush()
2179 ifh.flush()
2181 try:
2180 try:
2182 deltacomputer = _deltacomputer(self)
2181 deltacomputer = _deltacomputer(self)
2183 # loop through our set of deltas
2182 # loop through our set of deltas
2184 for data in deltas:
2183 for data in deltas:
2185 node, p1, p2, linknode, deltabase, delta, flags = data
2184 node, p1, p2, linknode, deltabase, delta, flags = data
2186 link = linkmapper(linknode)
2185 link = linkmapper(linknode)
2187 flags = flags or REVIDX_DEFAULT_FLAGS
2186 flags = flags or REVIDX_DEFAULT_FLAGS
2188
2187
2189 nodes.append(node)
2188 nodes.append(node)
2190
2189
2191 if node in self.nodemap:
2190 if node in self.nodemap:
2192 # this can happen if two branches make the same change
2191 # this can happen if two branches make the same change
2193 continue
2192 continue
2194
2193
2195 for p in (p1, p2):
2194 for p in (p1, p2):
2196 if p not in self.nodemap:
2195 if p not in self.nodemap:
2197 raise LookupError(p, self.indexfile,
2196 raise LookupError(p, self.indexfile,
2198 _('unknown parent'))
2197 _('unknown parent'))
2199
2198
2200 if deltabase not in self.nodemap:
2199 if deltabase not in self.nodemap:
2201 raise LookupError(deltabase, self.indexfile,
2200 raise LookupError(deltabase, self.indexfile,
2202 _('unknown delta base'))
2201 _('unknown delta base'))
2203
2202
2204 baserev = self.rev(deltabase)
2203 baserev = self.rev(deltabase)
2205
2204
2206 if baserev != nullrev and self.iscensored(baserev):
2205 if baserev != nullrev and self.iscensored(baserev):
2207 # if base is censored, delta must be full replacement in a
2206 # if base is censored, delta must be full replacement in a
2208 # single patch operation
2207 # single patch operation
2209 hlen = struct.calcsize(">lll")
2208 hlen = struct.calcsize(">lll")
2210 oldlen = self.rawsize(baserev)
2209 oldlen = self.rawsize(baserev)
2211 newlen = len(delta) - hlen
2210 newlen = len(delta) - hlen
2212 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2211 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2213 raise error.CensoredBaseError(self.indexfile,
2212 raise error.CensoredBaseError(self.indexfile,
2214 self.node(baserev))
2213 self.node(baserev))
2215
2214
2216 if not flags and self._peek_iscensored(baserev, delta, flush):
2215 if not flags and self._peek_iscensored(baserev, delta, flush):
2217 flags |= REVIDX_ISCENSORED
2216 flags |= REVIDX_ISCENSORED
2218
2217
2219 # We assume consumers of addrevisioncb will want to retrieve
2218 # We assume consumers of addrevisioncb will want to retrieve
2220 # the added revision, which will require a call to
2219 # the added revision, which will require a call to
2221 # revision(). revision() will fast path if there is a cache
2220 # revision(). revision() will fast path if there is a cache
2222 # hit. So, we tell _addrevision() to always cache in this case.
2221 # hit. So, we tell _addrevision() to always cache in this case.
2223 # We're only using addgroup() in the context of changegroup
2222 # We're only using addgroup() in the context of changegroup
2224 # generation so the revision data can always be handled as raw
2223 # generation so the revision data can always be handled as raw
2225 # by the flagprocessor.
2224 # by the flagprocessor.
2226 self._addrevision(node, None, transaction, link,
2225 self._addrevision(node, None, transaction, link,
2227 p1, p2, flags, (baserev, delta),
2226 p1, p2, flags, (baserev, delta),
2228 ifh, dfh,
2227 ifh, dfh,
2229 alwayscache=bool(addrevisioncb),
2228 alwayscache=bool(addrevisioncb),
2230 deltacomputer=deltacomputer)
2229 deltacomputer=deltacomputer)
2231
2230
2232 if addrevisioncb:
2231 if addrevisioncb:
2233 addrevisioncb(self, node)
2232 addrevisioncb(self, node)
2234
2233
2235 if not dfh and not self._inline:
2234 if not dfh and not self._inline:
2236 # addrevision switched from inline to conventional
2235 # addrevision switched from inline to conventional
2237 # reopen the index
2236 # reopen the index
2238 ifh.close()
2237 ifh.close()
2239 dfh = self._datafp("a+")
2238 dfh = self._datafp("a+")
2240 ifh = self._indexfp("a+")
2239 ifh = self._indexfp("a+")
2241 finally:
2240 finally:
2242 if dfh:
2241 if dfh:
2243 dfh.close()
2242 dfh.close()
2244 ifh.close()
2243 ifh.close()
2245
2244
2246 return nodes
2245 return nodes
2247
2246
2248 def iscensored(self, rev):
2247 def iscensored(self, rev):
2249 """Check if a file revision is censored."""
2248 """Check if a file revision is censored."""
2250 return False
2249 return False
2251
2250
2252 def _peek_iscensored(self, baserev, delta, flush):
2251 def _peek_iscensored(self, baserev, delta, flush):
2253 """Quickly check if a delta produces a censored revision."""
2252 """Quickly check if a delta produces a censored revision."""
2254 return False
2253 return False
2255
2254
2256 def getstrippoint(self, minlink):
2255 def getstrippoint(self, minlink):
2257 """find the minimum rev that must be stripped to strip the linkrev
2256 """find the minimum rev that must be stripped to strip the linkrev
2258
2257
2259 Returns a tuple containing the minimum rev and a set of all revs that
2258 Returns a tuple containing the minimum rev and a set of all revs that
2260 have linkrevs that will be broken by this strip.
2259 have linkrevs that will be broken by this strip.
2261 """
2260 """
2262 brokenrevs = set()
2261 brokenrevs = set()
2263 strippoint = len(self)
2262 strippoint = len(self)
2264
2263
2265 heads = {}
2264 heads = {}
2266 futurelargelinkrevs = set()
2265 futurelargelinkrevs = set()
2267 for head in self.headrevs():
2266 for head in self.headrevs():
2268 headlinkrev = self.linkrev(head)
2267 headlinkrev = self.linkrev(head)
2269 heads[head] = headlinkrev
2268 heads[head] = headlinkrev
2270 if headlinkrev >= minlink:
2269 if headlinkrev >= minlink:
2271 futurelargelinkrevs.add(headlinkrev)
2270 futurelargelinkrevs.add(headlinkrev)
2272
2271
2273 # This algorithm involves walking down the rev graph, starting at the
2272 # This algorithm involves walking down the rev graph, starting at the
2274 # heads. Since the revs are topologically sorted according to linkrev,
2273 # heads. Since the revs are topologically sorted according to linkrev,
2275 # once all head linkrevs are below the minlink, we know there are
2274 # once all head linkrevs are below the minlink, we know there are
2276 # no more revs that could have a linkrev greater than minlink.
2275 # no more revs that could have a linkrev greater than minlink.
2277 # So we can stop walking.
2276 # So we can stop walking.
2278 while futurelargelinkrevs:
2277 while futurelargelinkrevs:
2279 strippoint -= 1
2278 strippoint -= 1
2280 linkrev = heads.pop(strippoint)
2279 linkrev = heads.pop(strippoint)
2281
2280
2282 if linkrev < minlink:
2281 if linkrev < minlink:
2283 brokenrevs.add(strippoint)
2282 brokenrevs.add(strippoint)
2284 else:
2283 else:
2285 futurelargelinkrevs.remove(linkrev)
2284 futurelargelinkrevs.remove(linkrev)
2286
2285
2287 for p in self.parentrevs(strippoint):
2286 for p in self.parentrevs(strippoint):
2288 if p != nullrev:
2287 if p != nullrev:
2289 plinkrev = self.linkrev(p)
2288 plinkrev = self.linkrev(p)
2290 heads[p] = plinkrev
2289 heads[p] = plinkrev
2291 if plinkrev >= minlink:
2290 if plinkrev >= minlink:
2292 futurelargelinkrevs.add(plinkrev)
2291 futurelargelinkrevs.add(plinkrev)
2293
2292
2294 return strippoint, brokenrevs
2293 return strippoint, brokenrevs
2295
2294
2296 def strip(self, minlink, transaction):
2295 def strip(self, minlink, transaction):
2297 """truncate the revlog on the first revision with a linkrev >= minlink
2296 """truncate the revlog on the first revision with a linkrev >= minlink
2298
2297
2299 This function is called when we're stripping revision minlink and
2298 This function is called when we're stripping revision minlink and
2300 its descendants from the repository.
2299 its descendants from the repository.
2301
2300
2302 We have to remove all revisions with linkrev >= minlink, because
2301 We have to remove all revisions with linkrev >= minlink, because
2303 the equivalent changelog revisions will be renumbered after the
2302 the equivalent changelog revisions will be renumbered after the
2304 strip.
2303 strip.
2305
2304
2306 So we truncate the revlog on the first of these revisions, and
2305 So we truncate the revlog on the first of these revisions, and
2307 trust that the caller has saved the revisions that shouldn't be
2306 trust that the caller has saved the revisions that shouldn't be
2308 removed and that it'll re-add them after this truncation.
2307 removed and that it'll re-add them after this truncation.
2309 """
2308 """
2310 if len(self) == 0:
2309 if len(self) == 0:
2311 return
2310 return
2312
2311
2313 rev, _ = self.getstrippoint(minlink)
2312 rev, _ = self.getstrippoint(minlink)
2314 if rev == len(self):
2313 if rev == len(self):
2315 return
2314 return
2316
2315
2317 # first truncate the files on disk
2316 # first truncate the files on disk
2318 end = self.start(rev)
2317 end = self.start(rev)
2319 if not self._inline:
2318 if not self._inline:
2320 transaction.add(self.datafile, end)
2319 transaction.add(self.datafile, end)
2321 end = rev * self._io.size
2320 end = rev * self._io.size
2322 else:
2321 else:
2323 end += rev * self._io.size
2322 end += rev * self._io.size
2324
2323
2325 transaction.add(self.indexfile, end)
2324 transaction.add(self.indexfile, end)
2326
2325
2327 # then reset internal state in memory to forget those revisions
2326 # then reset internal state in memory to forget those revisions
2328 self._cache = None
2327 self._cache = None
2329 self._chaininfocache = {}
2328 self._chaininfocache = {}
2330 self._chunkclear()
2329 self._chunkclear()
2331 for x in xrange(rev, len(self)):
2330 for x in xrange(rev, len(self)):
2332 del self.nodemap[self.node(x)]
2331 del self.nodemap[self.node(x)]
2333
2332
2334 del self.index[rev:-1]
2333 del self.index[rev:-1]
2335
2334
2336 def checksize(self):
2335 def checksize(self):
2337 expected = 0
2336 expected = 0
2338 if len(self):
2337 if len(self):
2339 expected = max(0, self.end(len(self) - 1))
2338 expected = max(0, self.end(len(self) - 1))
2340
2339
2341 try:
2340 try:
2342 with self._datafp() as f:
2341 with self._datafp() as f:
2343 f.seek(0, 2)
2342 f.seek(0, 2)
2344 actual = f.tell()
2343 actual = f.tell()
2345 dd = actual - expected
2344 dd = actual - expected
2346 except IOError as inst:
2345 except IOError as inst:
2347 if inst.errno != errno.ENOENT:
2346 if inst.errno != errno.ENOENT:
2348 raise
2347 raise
2349 dd = 0
2348 dd = 0
2350
2349
2351 try:
2350 try:
2352 f = self.opener(self.indexfile)
2351 f = self.opener(self.indexfile)
2353 f.seek(0, 2)
2352 f.seek(0, 2)
2354 actual = f.tell()
2353 actual = f.tell()
2355 f.close()
2354 f.close()
2356 s = self._io.size
2355 s = self._io.size
2357 i = max(0, actual // s)
2356 i = max(0, actual // s)
2358 di = actual - (i * s)
2357 di = actual - (i * s)
2359 if self._inline:
2358 if self._inline:
2360 databytes = 0
2359 databytes = 0
2361 for r in self:
2360 for r in self:
2362 databytes += max(0, self.length(r))
2361 databytes += max(0, self.length(r))
2363 dd = 0
2362 dd = 0
2364 di = actual - len(self) * s - databytes
2363 di = actual - len(self) * s - databytes
2365 except IOError as inst:
2364 except IOError as inst:
2366 if inst.errno != errno.ENOENT:
2365 if inst.errno != errno.ENOENT:
2367 raise
2366 raise
2368 di = 0
2367 di = 0
2369
2368
2370 return (dd, di)
2369 return (dd, di)
2371
2370
2372 def files(self):
2371 def files(self):
2373 res = [self.indexfile]
2372 res = [self.indexfile]
2374 if not self._inline:
2373 if not self._inline:
2375 res.append(self.datafile)
2374 res.append(self.datafile)
2376 return res
2375 return res
2377
2376
2378 DELTAREUSEALWAYS = 'always'
2377 DELTAREUSEALWAYS = 'always'
2379 DELTAREUSESAMEREVS = 'samerevs'
2378 DELTAREUSESAMEREVS = 'samerevs'
2380 DELTAREUSENEVER = 'never'
2379 DELTAREUSENEVER = 'never'
2381
2380
2382 DELTAREUSEFULLADD = 'fulladd'
2381 DELTAREUSEFULLADD = 'fulladd'
2383
2382
2384 DELTAREUSEALL = {'always', 'samerevs', 'never', 'fulladd'}
2383 DELTAREUSEALL = {'always', 'samerevs', 'never', 'fulladd'}
2385
2384
2386 def clone(self, tr, destrevlog, addrevisioncb=None,
2385 def clone(self, tr, destrevlog, addrevisioncb=None,
2387 deltareuse=DELTAREUSESAMEREVS, aggressivemergedeltas=None):
2386 deltareuse=DELTAREUSESAMEREVS, aggressivemergedeltas=None):
2388 """Copy this revlog to another, possibly with format changes.
2387 """Copy this revlog to another, possibly with format changes.
2389
2388
2390 The destination revlog will contain the same revisions and nodes.
2389 The destination revlog will contain the same revisions and nodes.
2391 However, it may not be bit-for-bit identical due to e.g. delta encoding
2390 However, it may not be bit-for-bit identical due to e.g. delta encoding
2392 differences.
2391 differences.
2393
2392
2394 The ``deltareuse`` argument control how deltas from the existing revlog
2393 The ``deltareuse`` argument control how deltas from the existing revlog
2395 are preserved in the destination revlog. The argument can have the
2394 are preserved in the destination revlog. The argument can have the
2396 following values:
2395 following values:
2397
2396
2398 DELTAREUSEALWAYS
2397 DELTAREUSEALWAYS
2399 Deltas will always be reused (if possible), even if the destination
2398 Deltas will always be reused (if possible), even if the destination
2400 revlog would not select the same revisions for the delta. This is the
2399 revlog would not select the same revisions for the delta. This is the
2401 fastest mode of operation.
2400 fastest mode of operation.
2402 DELTAREUSESAMEREVS
2401 DELTAREUSESAMEREVS
2403 Deltas will be reused if the destination revlog would pick the same
2402 Deltas will be reused if the destination revlog would pick the same
2404 revisions for the delta. This mode strikes a balance between speed
2403 revisions for the delta. This mode strikes a balance between speed
2405 and optimization.
2404 and optimization.
2406 DELTAREUSENEVER
2405 DELTAREUSENEVER
2407 Deltas will never be reused. This is the slowest mode of execution.
2406 Deltas will never be reused. This is the slowest mode of execution.
2408 This mode can be used to recompute deltas (e.g. if the diff/delta
2407 This mode can be used to recompute deltas (e.g. if the diff/delta
2409 algorithm changes).
2408 algorithm changes).
2410
2409
2411 Delta computation can be slow, so the choice of delta reuse policy can
2410 Delta computation can be slow, so the choice of delta reuse policy can
2412 significantly affect run time.
2411 significantly affect run time.
2413
2412
2414 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2413 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2415 two extremes. Deltas will be reused if they are appropriate. But if the
2414 two extremes. Deltas will be reused if they are appropriate. But if the
2416 delta could choose a better revision, it will do so. This means if you
2415 delta could choose a better revision, it will do so. This means if you
2417 are converting a non-generaldelta revlog to a generaldelta revlog,
2416 are converting a non-generaldelta revlog to a generaldelta revlog,
2418 deltas will be recomputed if the delta's parent isn't a parent of the
2417 deltas will be recomputed if the delta's parent isn't a parent of the
2419 revision.
2418 revision.
2420
2419
2421 In addition to the delta policy, the ``aggressivemergedeltas`` argument
2420 In addition to the delta policy, the ``aggressivemergedeltas`` argument
2422 controls whether to compute deltas against both parents for merges.
2421 controls whether to compute deltas against both parents for merges.
2423 By default, the current default is used.
2422 By default, the current default is used.
2424 """
2423 """
2425 if deltareuse not in self.DELTAREUSEALL:
2424 if deltareuse not in self.DELTAREUSEALL:
2426 raise ValueError(_('value for deltareuse invalid: %s') % deltareuse)
2425 raise ValueError(_('value for deltareuse invalid: %s') % deltareuse)
2427
2426
2428 if len(destrevlog):
2427 if len(destrevlog):
2429 raise ValueError(_('destination revlog is not empty'))
2428 raise ValueError(_('destination revlog is not empty'))
2430
2429
2431 if getattr(self, 'filteredrevs', None):
2430 if getattr(self, 'filteredrevs', None):
2432 raise ValueError(_('source revlog has filtered revisions'))
2431 raise ValueError(_('source revlog has filtered revisions'))
2433 if getattr(destrevlog, 'filteredrevs', None):
2432 if getattr(destrevlog, 'filteredrevs', None):
2434 raise ValueError(_('destination revlog has filtered revisions'))
2433 raise ValueError(_('destination revlog has filtered revisions'))
2435
2434
2436 # lazydeltabase controls whether to reuse a cached delta, if possible.
2435 # lazydeltabase controls whether to reuse a cached delta, if possible.
2437 oldlazydeltabase = destrevlog._lazydeltabase
2436 oldlazydeltabase = destrevlog._lazydeltabase
2438 oldamd = destrevlog._aggressivemergedeltas
2437 oldamd = destrevlog._aggressivemergedeltas
2439
2438
2440 try:
2439 try:
2441 if deltareuse == self.DELTAREUSEALWAYS:
2440 if deltareuse == self.DELTAREUSEALWAYS:
2442 destrevlog._lazydeltabase = True
2441 destrevlog._lazydeltabase = True
2443 elif deltareuse == self.DELTAREUSESAMEREVS:
2442 elif deltareuse == self.DELTAREUSESAMEREVS:
2444 destrevlog._lazydeltabase = False
2443 destrevlog._lazydeltabase = False
2445
2444
2446 destrevlog._aggressivemergedeltas = aggressivemergedeltas or oldamd
2445 destrevlog._aggressivemergedeltas = aggressivemergedeltas or oldamd
2447
2446
2448 populatecachedelta = deltareuse in (self.DELTAREUSEALWAYS,
2447 populatecachedelta = deltareuse in (self.DELTAREUSEALWAYS,
2449 self.DELTAREUSESAMEREVS)
2448 self.DELTAREUSESAMEREVS)
2450
2449
2451 deltacomputer = _deltacomputer(destrevlog)
2450 deltacomputer = _deltacomputer(destrevlog)
2452 index = self.index
2451 index = self.index
2453 for rev in self:
2452 for rev in self:
2454 entry = index[rev]
2453 entry = index[rev]
2455
2454
2456 # Some classes override linkrev to take filtered revs into
2455 # Some classes override linkrev to take filtered revs into
2457 # account. Use raw entry from index.
2456 # account. Use raw entry from index.
2458 flags = entry[0] & 0xffff
2457 flags = entry[0] & 0xffff
2459 linkrev = entry[4]
2458 linkrev = entry[4]
2460 p1 = index[entry[5]][7]
2459 p1 = index[entry[5]][7]
2461 p2 = index[entry[6]][7]
2460 p2 = index[entry[6]][7]
2462 node = entry[7]
2461 node = entry[7]
2463
2462
2464 # (Possibly) reuse the delta from the revlog if allowed and
2463 # (Possibly) reuse the delta from the revlog if allowed and
2465 # the revlog chunk is a delta.
2464 # the revlog chunk is a delta.
2466 cachedelta = None
2465 cachedelta = None
2467 rawtext = None
2466 rawtext = None
2468 if populatecachedelta:
2467 if populatecachedelta:
2469 dp = self.deltaparent(rev)
2468 dp = self.deltaparent(rev)
2470 if dp != nullrev:
2469 if dp != nullrev:
2471 cachedelta = (dp, str(self._chunk(rev)))
2470 cachedelta = (dp, str(self._chunk(rev)))
2472
2471
2473 if not cachedelta:
2472 if not cachedelta:
2474 rawtext = self.revision(rev, raw=True)
2473 rawtext = self.revision(rev, raw=True)
2475
2474
2476
2475
2477 if deltareuse == self.DELTAREUSEFULLADD:
2476 if deltareuse == self.DELTAREUSEFULLADD:
2478 destrevlog.addrevision(rawtext, tr, linkrev, p1, p2,
2477 destrevlog.addrevision(rawtext, tr, linkrev, p1, p2,
2479 cachedelta=cachedelta,
2478 cachedelta=cachedelta,
2480 node=node, flags=flags,
2479 node=node, flags=flags,
2481 deltacomputer=deltacomputer)
2480 deltacomputer=deltacomputer)
2482 else:
2481 else:
2483 ifh = destrevlog.opener(destrevlog.indexfile, 'a+',
2482 ifh = destrevlog.opener(destrevlog.indexfile, 'a+',
2484 checkambig=False)
2483 checkambig=False)
2485 dfh = None
2484 dfh = None
2486 if not destrevlog._inline:
2485 if not destrevlog._inline:
2487 dfh = destrevlog.opener(destrevlog.datafile, 'a+')
2486 dfh = destrevlog.opener(destrevlog.datafile, 'a+')
2488 try:
2487 try:
2489 destrevlog._addrevision(node, rawtext, tr, linkrev, p1,
2488 destrevlog._addrevision(node, rawtext, tr, linkrev, p1,
2490 p2, flags, cachedelta, ifh, dfh,
2489 p2, flags, cachedelta, ifh, dfh,
2491 deltacomputer=deltacomputer)
2490 deltacomputer=deltacomputer)
2492 finally:
2491 finally:
2493 if dfh:
2492 if dfh:
2494 dfh.close()
2493 dfh.close()
2495 ifh.close()
2494 ifh.close()
2496
2495
2497 if addrevisioncb:
2496 if addrevisioncb:
2498 addrevisioncb(self, rev, node)
2497 addrevisioncb(self, rev, node)
2499 finally:
2498 finally:
2500 destrevlog._lazydeltabase = oldlazydeltabase
2499 destrevlog._lazydeltabase = oldlazydeltabase
2501 destrevlog._aggressivemergedeltas = oldamd
2500 destrevlog._aggressivemergedeltas = oldamd
General Comments 0
You need to be logged in to leave comments. Login now