##// END OF EJS Templates
manifest: remove usages of manifest.read...
Durham Goode -
r30369:d79c141f default
parent child Browse files
Show More
@@ -1,1162 +1,1162 b''
1 # perf.py - performance test routines
1 # perf.py - performance test routines
2 '''helper extension to measure performance'''
2 '''helper extension to measure performance'''
3
3
4 # "historical portability" policy of perf.py:
4 # "historical portability" policy of perf.py:
5 #
5 #
6 # We have to do:
6 # We have to do:
7 # - make perf.py "loadable" with as wide Mercurial version as possible
7 # - make perf.py "loadable" with as wide Mercurial version as possible
8 # This doesn't mean that perf commands work correctly with that Mercurial.
8 # This doesn't mean that perf commands work correctly with that Mercurial.
9 # BTW, perf.py itself has been available since 1.1 (or eb240755386d).
9 # BTW, perf.py itself has been available since 1.1 (or eb240755386d).
10 # - make historical perf command work correctly with as wide Mercurial
10 # - make historical perf command work correctly with as wide Mercurial
11 # version as possible
11 # version as possible
12 #
12 #
13 # We have to do, if possible with reasonable cost:
13 # We have to do, if possible with reasonable cost:
14 # - make recent perf command for historical feature work correctly
14 # - make recent perf command for historical feature work correctly
15 # with early Mercurial
15 # with early Mercurial
16 #
16 #
17 # We don't have to do:
17 # We don't have to do:
18 # - make perf command for recent feature work correctly with early
18 # - make perf command for recent feature work correctly with early
19 # Mercurial
19 # Mercurial
20
20
21 from __future__ import absolute_import
21 from __future__ import absolute_import
22 import functools
22 import functools
23 import os
23 import os
24 import random
24 import random
25 import sys
25 import sys
26 import time
26 import time
27 from mercurial import (
27 from mercurial import (
28 bdiff,
28 bdiff,
29 changegroup,
29 changegroup,
30 cmdutil,
30 cmdutil,
31 commands,
31 commands,
32 copies,
32 copies,
33 error,
33 error,
34 extensions,
34 extensions,
35 mdiff,
35 mdiff,
36 merge,
36 merge,
37 revlog,
37 revlog,
38 util,
38 util,
39 )
39 )
40
40
41 # for "historical portability":
41 # for "historical portability":
42 # try to import modules separately (in dict order), and ignore
42 # try to import modules separately (in dict order), and ignore
43 # failure, because these aren't available with early Mercurial
43 # failure, because these aren't available with early Mercurial
44 try:
44 try:
45 from mercurial import branchmap # since 2.5 (or bcee63733aad)
45 from mercurial import branchmap # since 2.5 (or bcee63733aad)
46 except ImportError:
46 except ImportError:
47 pass
47 pass
48 try:
48 try:
49 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
49 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
50 except ImportError:
50 except ImportError:
51 pass
51 pass
52 try:
52 try:
53 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
53 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
54 except ImportError:
54 except ImportError:
55 pass
55 pass
56 try:
56 try:
57 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
57 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
58 except ImportError:
58 except ImportError:
59 pass
59 pass
60
60
61 # for "historical portability":
61 # for "historical portability":
62 # define util.safehasattr forcibly, because util.safehasattr has been
62 # define util.safehasattr forcibly, because util.safehasattr has been
63 # available since 1.9.3 (or 94b200a11cf7)
63 # available since 1.9.3 (or 94b200a11cf7)
64 _undefined = object()
64 _undefined = object()
65 def safehasattr(thing, attr):
65 def safehasattr(thing, attr):
66 return getattr(thing, attr, _undefined) is not _undefined
66 return getattr(thing, attr, _undefined) is not _undefined
67 setattr(util, 'safehasattr', safehasattr)
67 setattr(util, 'safehasattr', safehasattr)
68
68
69 # for "historical portability":
69 # for "historical portability":
70 # use locally defined empty option list, if formatteropts isn't
70 # use locally defined empty option list, if formatteropts isn't
71 # available, because commands.formatteropts has been available since
71 # available, because commands.formatteropts has been available since
72 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
72 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
73 # available since 2.2 (or ae5f92e154d3)
73 # available since 2.2 (or ae5f92e154d3)
74 formatteropts = getattr(commands, "formatteropts", [])
74 formatteropts = getattr(commands, "formatteropts", [])
75
75
76 # for "historical portability":
76 # for "historical portability":
77 # use locally defined option list, if debugrevlogopts isn't available,
77 # use locally defined option list, if debugrevlogopts isn't available,
78 # because commands.debugrevlogopts has been available since 3.7 (or
78 # because commands.debugrevlogopts has been available since 3.7 (or
79 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
79 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
80 # since 1.9 (or a79fea6b3e77).
80 # since 1.9 (or a79fea6b3e77).
81 revlogopts = getattr(commands, "debugrevlogopts", [
81 revlogopts = getattr(commands, "debugrevlogopts", [
82 ('c', 'changelog', False, ('open changelog')),
82 ('c', 'changelog', False, ('open changelog')),
83 ('m', 'manifest', False, ('open manifest')),
83 ('m', 'manifest', False, ('open manifest')),
84 ('', 'dir', False, ('open directory manifest')),
84 ('', 'dir', False, ('open directory manifest')),
85 ])
85 ])
86
86
87 cmdtable = {}
87 cmdtable = {}
88
88
89 # for "historical portability":
89 # for "historical portability":
90 # define parsealiases locally, because cmdutil.parsealiases has been
90 # define parsealiases locally, because cmdutil.parsealiases has been
91 # available since 1.5 (or 6252852b4332)
91 # available since 1.5 (or 6252852b4332)
92 def parsealiases(cmd):
92 def parsealiases(cmd):
93 return cmd.lstrip("^").split("|")
93 return cmd.lstrip("^").split("|")
94
94
95 if safehasattr(cmdutil, 'command'):
95 if safehasattr(cmdutil, 'command'):
96 import inspect
96 import inspect
97 command = cmdutil.command(cmdtable)
97 command = cmdutil.command(cmdtable)
98 if 'norepo' not in inspect.getargspec(command)[0]:
98 if 'norepo' not in inspect.getargspec(command)[0]:
99 # for "historical portability":
99 # for "historical portability":
100 # wrap original cmdutil.command, because "norepo" option has
100 # wrap original cmdutil.command, because "norepo" option has
101 # been available since 3.1 (or 75a96326cecb)
101 # been available since 3.1 (or 75a96326cecb)
102 _command = command
102 _command = command
103 def command(name, options=(), synopsis=None, norepo=False):
103 def command(name, options=(), synopsis=None, norepo=False):
104 if norepo:
104 if norepo:
105 commands.norepo += ' %s' % ' '.join(parsealiases(name))
105 commands.norepo += ' %s' % ' '.join(parsealiases(name))
106 return _command(name, list(options), synopsis)
106 return _command(name, list(options), synopsis)
107 else:
107 else:
108 # for "historical portability":
108 # for "historical portability":
109 # define "@command" annotation locally, because cmdutil.command
109 # define "@command" annotation locally, because cmdutil.command
110 # has been available since 1.9 (or 2daa5179e73f)
110 # has been available since 1.9 (or 2daa5179e73f)
111 def command(name, options=(), synopsis=None, norepo=False):
111 def command(name, options=(), synopsis=None, norepo=False):
112 def decorator(func):
112 def decorator(func):
113 if synopsis:
113 if synopsis:
114 cmdtable[name] = func, list(options), synopsis
114 cmdtable[name] = func, list(options), synopsis
115 else:
115 else:
116 cmdtable[name] = func, list(options)
116 cmdtable[name] = func, list(options)
117 if norepo:
117 if norepo:
118 commands.norepo += ' %s' % ' '.join(parsealiases(name))
118 commands.norepo += ' %s' % ' '.join(parsealiases(name))
119 return func
119 return func
120 return decorator
120 return decorator
121
121
122 def getlen(ui):
122 def getlen(ui):
123 if ui.configbool("perf", "stub"):
123 if ui.configbool("perf", "stub"):
124 return lambda x: 1
124 return lambda x: 1
125 return len
125 return len
126
126
127 def gettimer(ui, opts=None):
127 def gettimer(ui, opts=None):
128 """return a timer function and formatter: (timer, formatter)
128 """return a timer function and formatter: (timer, formatter)
129
129
130 This function exists to gather the creation of formatter in a single
130 This function exists to gather the creation of formatter in a single
131 place instead of duplicating it in all performance commands."""
131 place instead of duplicating it in all performance commands."""
132
132
133 # enforce an idle period before execution to counteract power management
133 # enforce an idle period before execution to counteract power management
134 # experimental config: perf.presleep
134 # experimental config: perf.presleep
135 time.sleep(getint(ui, "perf", "presleep", 1))
135 time.sleep(getint(ui, "perf", "presleep", 1))
136
136
137 if opts is None:
137 if opts is None:
138 opts = {}
138 opts = {}
139 # redirect all to stderr
139 # redirect all to stderr
140 ui = ui.copy()
140 ui = ui.copy()
141 uifout = safeattrsetter(ui, 'fout', ignoremissing=True)
141 uifout = safeattrsetter(ui, 'fout', ignoremissing=True)
142 if uifout:
142 if uifout:
143 # for "historical portability":
143 # for "historical portability":
144 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
144 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
145 uifout.set(ui.ferr)
145 uifout.set(ui.ferr)
146
146
147 # get a formatter
147 # get a formatter
148 uiformatter = getattr(ui, 'formatter', None)
148 uiformatter = getattr(ui, 'formatter', None)
149 if uiformatter:
149 if uiformatter:
150 fm = uiformatter('perf', opts)
150 fm = uiformatter('perf', opts)
151 else:
151 else:
152 # for "historical portability":
152 # for "historical portability":
153 # define formatter locally, because ui.formatter has been
153 # define formatter locally, because ui.formatter has been
154 # available since 2.2 (or ae5f92e154d3)
154 # available since 2.2 (or ae5f92e154d3)
155 from mercurial import node
155 from mercurial import node
156 class defaultformatter(object):
156 class defaultformatter(object):
157 """Minimized composition of baseformatter and plainformatter
157 """Minimized composition of baseformatter and plainformatter
158 """
158 """
159 def __init__(self, ui, topic, opts):
159 def __init__(self, ui, topic, opts):
160 self._ui = ui
160 self._ui = ui
161 if ui.debugflag:
161 if ui.debugflag:
162 self.hexfunc = node.hex
162 self.hexfunc = node.hex
163 else:
163 else:
164 self.hexfunc = node.short
164 self.hexfunc = node.short
165 def __nonzero__(self):
165 def __nonzero__(self):
166 return False
166 return False
167 def startitem(self):
167 def startitem(self):
168 pass
168 pass
169 def data(self, **data):
169 def data(self, **data):
170 pass
170 pass
171 def write(self, fields, deftext, *fielddata, **opts):
171 def write(self, fields, deftext, *fielddata, **opts):
172 self._ui.write(deftext % fielddata, **opts)
172 self._ui.write(deftext % fielddata, **opts)
173 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
173 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
174 if cond:
174 if cond:
175 self._ui.write(deftext % fielddata, **opts)
175 self._ui.write(deftext % fielddata, **opts)
176 def plain(self, text, **opts):
176 def plain(self, text, **opts):
177 self._ui.write(text, **opts)
177 self._ui.write(text, **opts)
178 def end(self):
178 def end(self):
179 pass
179 pass
180 fm = defaultformatter(ui, 'perf', opts)
180 fm = defaultformatter(ui, 'perf', opts)
181
181
182 # stub function, runs code only once instead of in a loop
182 # stub function, runs code only once instead of in a loop
183 # experimental config: perf.stub
183 # experimental config: perf.stub
184 if ui.configbool("perf", "stub"):
184 if ui.configbool("perf", "stub"):
185 return functools.partial(stub_timer, fm), fm
185 return functools.partial(stub_timer, fm), fm
186 return functools.partial(_timer, fm), fm
186 return functools.partial(_timer, fm), fm
187
187
188 def stub_timer(fm, func, title=None):
188 def stub_timer(fm, func, title=None):
189 func()
189 func()
190
190
191 def _timer(fm, func, title=None):
191 def _timer(fm, func, title=None):
192 results = []
192 results = []
193 begin = time.time()
193 begin = time.time()
194 count = 0
194 count = 0
195 while True:
195 while True:
196 ostart = os.times()
196 ostart = os.times()
197 cstart = time.time()
197 cstart = time.time()
198 r = func()
198 r = func()
199 cstop = time.time()
199 cstop = time.time()
200 ostop = os.times()
200 ostop = os.times()
201 count += 1
201 count += 1
202 a, b = ostart, ostop
202 a, b = ostart, ostop
203 results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
203 results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
204 if cstop - begin > 3 and count >= 100:
204 if cstop - begin > 3 and count >= 100:
205 break
205 break
206 if cstop - begin > 10 and count >= 3:
206 if cstop - begin > 10 and count >= 3:
207 break
207 break
208
208
209 fm.startitem()
209 fm.startitem()
210
210
211 if title:
211 if title:
212 fm.write('title', '! %s\n', title)
212 fm.write('title', '! %s\n', title)
213 if r:
213 if r:
214 fm.write('result', '! result: %s\n', r)
214 fm.write('result', '! result: %s\n', r)
215 m = min(results)
215 m = min(results)
216 fm.plain('!')
216 fm.plain('!')
217 fm.write('wall', ' wall %f', m[0])
217 fm.write('wall', ' wall %f', m[0])
218 fm.write('comb', ' comb %f', m[1] + m[2])
218 fm.write('comb', ' comb %f', m[1] + m[2])
219 fm.write('user', ' user %f', m[1])
219 fm.write('user', ' user %f', m[1])
220 fm.write('sys', ' sys %f', m[2])
220 fm.write('sys', ' sys %f', m[2])
221 fm.write('count', ' (best of %d)', count)
221 fm.write('count', ' (best of %d)', count)
222 fm.plain('\n')
222 fm.plain('\n')
223
223
224 # utilities for historical portability
224 # utilities for historical portability
225
225
226 def getint(ui, section, name, default):
226 def getint(ui, section, name, default):
227 # for "historical portability":
227 # for "historical portability":
228 # ui.configint has been available since 1.9 (or fa2b596db182)
228 # ui.configint has been available since 1.9 (or fa2b596db182)
229 v = ui.config(section, name, None)
229 v = ui.config(section, name, None)
230 if v is None:
230 if v is None:
231 return default
231 return default
232 try:
232 try:
233 return int(v)
233 return int(v)
234 except ValueError:
234 except ValueError:
235 raise error.ConfigError(("%s.%s is not an integer ('%s')")
235 raise error.ConfigError(("%s.%s is not an integer ('%s')")
236 % (section, name, v))
236 % (section, name, v))
237
237
238 def safeattrsetter(obj, name, ignoremissing=False):
238 def safeattrsetter(obj, name, ignoremissing=False):
239 """Ensure that 'obj' has 'name' attribute before subsequent setattr
239 """Ensure that 'obj' has 'name' attribute before subsequent setattr
240
240
241 This function is aborted, if 'obj' doesn't have 'name' attribute
241 This function is aborted, if 'obj' doesn't have 'name' attribute
242 at runtime. This avoids overlooking removal of an attribute, which
242 at runtime. This avoids overlooking removal of an attribute, which
243 breaks assumption of performance measurement, in the future.
243 breaks assumption of performance measurement, in the future.
244
244
245 This function returns the object to (1) assign a new value, and
245 This function returns the object to (1) assign a new value, and
246 (2) restore an original value to the attribute.
246 (2) restore an original value to the attribute.
247
247
248 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
248 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
249 abortion, and this function returns None. This is useful to
249 abortion, and this function returns None. This is useful to
250 examine an attribute, which isn't ensured in all Mercurial
250 examine an attribute, which isn't ensured in all Mercurial
251 versions.
251 versions.
252 """
252 """
253 if not util.safehasattr(obj, name):
253 if not util.safehasattr(obj, name):
254 if ignoremissing:
254 if ignoremissing:
255 return None
255 return None
256 raise error.Abort(("missing attribute %s of %s might break assumption"
256 raise error.Abort(("missing attribute %s of %s might break assumption"
257 " of performance measurement") % (name, obj))
257 " of performance measurement") % (name, obj))
258
258
259 origvalue = getattr(obj, name)
259 origvalue = getattr(obj, name)
260 class attrutil(object):
260 class attrutil(object):
261 def set(self, newvalue):
261 def set(self, newvalue):
262 setattr(obj, name, newvalue)
262 setattr(obj, name, newvalue)
263 def restore(self):
263 def restore(self):
264 setattr(obj, name, origvalue)
264 setattr(obj, name, origvalue)
265
265
266 return attrutil()
266 return attrutil()
267
267
268 # utilities to examine each internal API changes
268 # utilities to examine each internal API changes
269
269
270 def getbranchmapsubsettable():
270 def getbranchmapsubsettable():
271 # for "historical portability":
271 # for "historical portability":
272 # subsettable is defined in:
272 # subsettable is defined in:
273 # - branchmap since 2.9 (or 175c6fd8cacc)
273 # - branchmap since 2.9 (or 175c6fd8cacc)
274 # - repoview since 2.5 (or 59a9f18d4587)
274 # - repoview since 2.5 (or 59a9f18d4587)
275 for mod in (branchmap, repoview):
275 for mod in (branchmap, repoview):
276 subsettable = getattr(mod, 'subsettable', None)
276 subsettable = getattr(mod, 'subsettable', None)
277 if subsettable:
277 if subsettable:
278 return subsettable
278 return subsettable
279
279
280 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
280 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
281 # branchmap and repoview modules exist, but subsettable attribute
281 # branchmap and repoview modules exist, but subsettable attribute
282 # doesn't)
282 # doesn't)
283 raise error.Abort(("perfbranchmap not available with this Mercurial"),
283 raise error.Abort(("perfbranchmap not available with this Mercurial"),
284 hint="use 2.5 or later")
284 hint="use 2.5 or later")
285
285
286 def getsvfs(repo):
286 def getsvfs(repo):
287 """Return appropriate object to access files under .hg/store
287 """Return appropriate object to access files under .hg/store
288 """
288 """
289 # for "historical portability":
289 # for "historical portability":
290 # repo.svfs has been available since 2.3 (or 7034365089bf)
290 # repo.svfs has been available since 2.3 (or 7034365089bf)
291 svfs = getattr(repo, 'svfs', None)
291 svfs = getattr(repo, 'svfs', None)
292 if svfs:
292 if svfs:
293 return svfs
293 return svfs
294 else:
294 else:
295 return getattr(repo, 'sopener')
295 return getattr(repo, 'sopener')
296
296
297 def getvfs(repo):
297 def getvfs(repo):
298 """Return appropriate object to access files under .hg
298 """Return appropriate object to access files under .hg
299 """
299 """
300 # for "historical portability":
300 # for "historical portability":
301 # repo.vfs has been available since 2.3 (or 7034365089bf)
301 # repo.vfs has been available since 2.3 (or 7034365089bf)
302 vfs = getattr(repo, 'vfs', None)
302 vfs = getattr(repo, 'vfs', None)
303 if vfs:
303 if vfs:
304 return vfs
304 return vfs
305 else:
305 else:
306 return getattr(repo, 'opener')
306 return getattr(repo, 'opener')
307
307
308 def repocleartagscachefunc(repo):
308 def repocleartagscachefunc(repo):
309 """Return the function to clear tags cache according to repo internal API
309 """Return the function to clear tags cache according to repo internal API
310 """
310 """
311 if util.safehasattr(repo, '_tagscache'): # since 2.0 (or 9dca7653b525)
311 if util.safehasattr(repo, '_tagscache'): # since 2.0 (or 9dca7653b525)
312 # in this case, setattr(repo, '_tagscache', None) or so isn't
312 # in this case, setattr(repo, '_tagscache', None) or so isn't
313 # correct way to clear tags cache, because existing code paths
313 # correct way to clear tags cache, because existing code paths
314 # expect _tagscache to be a structured object.
314 # expect _tagscache to be a structured object.
315 def clearcache():
315 def clearcache():
316 # _tagscache has been filteredpropertycache since 2.5 (or
316 # _tagscache has been filteredpropertycache since 2.5 (or
317 # 98c867ac1330), and delattr() can't work in such case
317 # 98c867ac1330), and delattr() can't work in such case
318 if '_tagscache' in vars(repo):
318 if '_tagscache' in vars(repo):
319 del repo.__dict__['_tagscache']
319 del repo.__dict__['_tagscache']
320 return clearcache
320 return clearcache
321
321
322 repotags = safeattrsetter(repo, '_tags', ignoremissing=True)
322 repotags = safeattrsetter(repo, '_tags', ignoremissing=True)
323 if repotags: # since 1.4 (or 5614a628d173)
323 if repotags: # since 1.4 (or 5614a628d173)
324 return lambda : repotags.set(None)
324 return lambda : repotags.set(None)
325
325
326 repotagscache = safeattrsetter(repo, 'tagscache', ignoremissing=True)
326 repotagscache = safeattrsetter(repo, 'tagscache', ignoremissing=True)
327 if repotagscache: # since 0.6 (or d7df759d0e97)
327 if repotagscache: # since 0.6 (or d7df759d0e97)
328 return lambda : repotagscache.set(None)
328 return lambda : repotagscache.set(None)
329
329
330 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
330 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
331 # this point, but it isn't so problematic, because:
331 # this point, but it isn't so problematic, because:
332 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
332 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
333 # in perftags() causes failure soon
333 # in perftags() causes failure soon
334 # - perf.py itself has been available since 1.1 (or eb240755386d)
334 # - perf.py itself has been available since 1.1 (or eb240755386d)
335 raise error.Abort(("tags API of this hg command is unknown"))
335 raise error.Abort(("tags API of this hg command is unknown"))
336
336
337 # perf commands
337 # perf commands
338
338
339 @command('perfwalk', formatteropts)
339 @command('perfwalk', formatteropts)
340 def perfwalk(ui, repo, *pats, **opts):
340 def perfwalk(ui, repo, *pats, **opts):
341 timer, fm = gettimer(ui, opts)
341 timer, fm = gettimer(ui, opts)
342 try:
342 try:
343 m = scmutil.match(repo[None], pats, {})
343 m = scmutil.match(repo[None], pats, {})
344 timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
344 timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
345 except Exception:
345 except Exception:
346 try:
346 try:
347 m = scmutil.match(repo[None], pats, {})
347 m = scmutil.match(repo[None], pats, {})
348 timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)]))
348 timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)]))
349 except Exception:
349 except Exception:
350 timer(lambda: len(list(cmdutil.walk(repo, pats, {}))))
350 timer(lambda: len(list(cmdutil.walk(repo, pats, {}))))
351 fm.end()
351 fm.end()
352
352
353 @command('perfannotate', formatteropts)
353 @command('perfannotate', formatteropts)
354 def perfannotate(ui, repo, f, **opts):
354 def perfannotate(ui, repo, f, **opts):
355 timer, fm = gettimer(ui, opts)
355 timer, fm = gettimer(ui, opts)
356 fc = repo['.'][f]
356 fc = repo['.'][f]
357 timer(lambda: len(fc.annotate(True)))
357 timer(lambda: len(fc.annotate(True)))
358 fm.end()
358 fm.end()
359
359
360 @command('perfstatus',
360 @command('perfstatus',
361 [('u', 'unknown', False,
361 [('u', 'unknown', False,
362 'ask status to look for unknown files')] + formatteropts)
362 'ask status to look for unknown files')] + formatteropts)
363 def perfstatus(ui, repo, **opts):
363 def perfstatus(ui, repo, **opts):
364 #m = match.always(repo.root, repo.getcwd())
364 #m = match.always(repo.root, repo.getcwd())
365 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
365 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
366 # False))))
366 # False))))
367 timer, fm = gettimer(ui, opts)
367 timer, fm = gettimer(ui, opts)
368 timer(lambda: sum(map(len, repo.status(unknown=opts['unknown']))))
368 timer(lambda: sum(map(len, repo.status(unknown=opts['unknown']))))
369 fm.end()
369 fm.end()
370
370
371 @command('perfaddremove', formatteropts)
371 @command('perfaddremove', formatteropts)
372 def perfaddremove(ui, repo, **opts):
372 def perfaddremove(ui, repo, **opts):
373 timer, fm = gettimer(ui, opts)
373 timer, fm = gettimer(ui, opts)
374 try:
374 try:
375 oldquiet = repo.ui.quiet
375 oldquiet = repo.ui.quiet
376 repo.ui.quiet = True
376 repo.ui.quiet = True
377 matcher = scmutil.match(repo[None])
377 matcher = scmutil.match(repo[None])
378 timer(lambda: scmutil.addremove(repo, matcher, "", dry_run=True))
378 timer(lambda: scmutil.addremove(repo, matcher, "", dry_run=True))
379 finally:
379 finally:
380 repo.ui.quiet = oldquiet
380 repo.ui.quiet = oldquiet
381 fm.end()
381 fm.end()
382
382
383 def clearcaches(cl):
383 def clearcaches(cl):
384 # behave somewhat consistently across internal API changes
384 # behave somewhat consistently across internal API changes
385 if util.safehasattr(cl, 'clearcaches'):
385 if util.safehasattr(cl, 'clearcaches'):
386 cl.clearcaches()
386 cl.clearcaches()
387 elif util.safehasattr(cl, '_nodecache'):
387 elif util.safehasattr(cl, '_nodecache'):
388 from mercurial.node import nullid, nullrev
388 from mercurial.node import nullid, nullrev
389 cl._nodecache = {nullid: nullrev}
389 cl._nodecache = {nullid: nullrev}
390 cl._nodepos = None
390 cl._nodepos = None
391
391
392 @command('perfheads', formatteropts)
392 @command('perfheads', formatteropts)
393 def perfheads(ui, repo, **opts):
393 def perfheads(ui, repo, **opts):
394 timer, fm = gettimer(ui, opts)
394 timer, fm = gettimer(ui, opts)
395 cl = repo.changelog
395 cl = repo.changelog
396 def d():
396 def d():
397 len(cl.headrevs())
397 len(cl.headrevs())
398 clearcaches(cl)
398 clearcaches(cl)
399 timer(d)
399 timer(d)
400 fm.end()
400 fm.end()
401
401
402 @command('perftags', formatteropts)
402 @command('perftags', formatteropts)
403 def perftags(ui, repo, **opts):
403 def perftags(ui, repo, **opts):
404 import mercurial.changelog
404 import mercurial.changelog
405 import mercurial.manifest
405 import mercurial.manifest
406 timer, fm = gettimer(ui, opts)
406 timer, fm = gettimer(ui, opts)
407 svfs = getsvfs(repo)
407 svfs = getsvfs(repo)
408 repocleartagscache = repocleartagscachefunc(repo)
408 repocleartagscache = repocleartagscachefunc(repo)
409 def t():
409 def t():
410 repo.changelog = mercurial.changelog.changelog(svfs)
410 repo.changelog = mercurial.changelog.changelog(svfs)
411 repo.manifestlog = mercurial.manifest.manifestlog(svfs, repo)
411 repo.manifestlog = mercurial.manifest.manifestlog(svfs, repo)
412 repocleartagscache()
412 repocleartagscache()
413 return len(repo.tags())
413 return len(repo.tags())
414 timer(t)
414 timer(t)
415 fm.end()
415 fm.end()
416
416
417 @command('perfancestors', formatteropts)
417 @command('perfancestors', formatteropts)
418 def perfancestors(ui, repo, **opts):
418 def perfancestors(ui, repo, **opts):
419 timer, fm = gettimer(ui, opts)
419 timer, fm = gettimer(ui, opts)
420 heads = repo.changelog.headrevs()
420 heads = repo.changelog.headrevs()
421 def d():
421 def d():
422 for a in repo.changelog.ancestors(heads):
422 for a in repo.changelog.ancestors(heads):
423 pass
423 pass
424 timer(d)
424 timer(d)
425 fm.end()
425 fm.end()
426
426
427 @command('perfancestorset', formatteropts)
427 @command('perfancestorset', formatteropts)
428 def perfancestorset(ui, repo, revset, **opts):
428 def perfancestorset(ui, repo, revset, **opts):
429 timer, fm = gettimer(ui, opts)
429 timer, fm = gettimer(ui, opts)
430 revs = repo.revs(revset)
430 revs = repo.revs(revset)
431 heads = repo.changelog.headrevs()
431 heads = repo.changelog.headrevs()
432 def d():
432 def d():
433 s = repo.changelog.ancestors(heads)
433 s = repo.changelog.ancestors(heads)
434 for rev in revs:
434 for rev in revs:
435 rev in s
435 rev in s
436 timer(d)
436 timer(d)
437 fm.end()
437 fm.end()
438
438
439 @command('perfchangegroupchangelog', formatteropts +
439 @command('perfchangegroupchangelog', formatteropts +
440 [('', 'version', '02', 'changegroup version'),
440 [('', 'version', '02', 'changegroup version'),
441 ('r', 'rev', '', 'revisions to add to changegroup')])
441 ('r', 'rev', '', 'revisions to add to changegroup')])
442 def perfchangegroupchangelog(ui, repo, version='02', rev=None, **opts):
442 def perfchangegroupchangelog(ui, repo, version='02', rev=None, **opts):
443 """Benchmark producing a changelog group for a changegroup.
443 """Benchmark producing a changelog group for a changegroup.
444
444
445 This measures the time spent processing the changelog during a
445 This measures the time spent processing the changelog during a
446 bundle operation. This occurs during `hg bundle` and on a server
446 bundle operation. This occurs during `hg bundle` and on a server
447 processing a `getbundle` wire protocol request (handles clones
447 processing a `getbundle` wire protocol request (handles clones
448 and pull requests).
448 and pull requests).
449
449
450 By default, all revisions are added to the changegroup.
450 By default, all revisions are added to the changegroup.
451 """
451 """
452 cl = repo.changelog
452 cl = repo.changelog
453 revs = [cl.lookup(r) for r in repo.revs(rev or 'all()')]
453 revs = [cl.lookup(r) for r in repo.revs(rev or 'all()')]
454 bundler = changegroup.getbundler(version, repo)
454 bundler = changegroup.getbundler(version, repo)
455
455
456 def lookup(node):
456 def lookup(node):
457 # The real bundler reads the revision in order to access the
457 # The real bundler reads the revision in order to access the
458 # manifest node and files list. Do that here.
458 # manifest node and files list. Do that here.
459 cl.read(node)
459 cl.read(node)
460 return node
460 return node
461
461
462 def d():
462 def d():
463 for chunk in bundler.group(revs, cl, lookup):
463 for chunk in bundler.group(revs, cl, lookup):
464 pass
464 pass
465
465
466 timer, fm = gettimer(ui, opts)
466 timer, fm = gettimer(ui, opts)
467 timer(d)
467 timer(d)
468 fm.end()
468 fm.end()
469
469
470 @command('perfdirs', formatteropts)
470 @command('perfdirs', formatteropts)
471 def perfdirs(ui, repo, **opts):
471 def perfdirs(ui, repo, **opts):
472 timer, fm = gettimer(ui, opts)
472 timer, fm = gettimer(ui, opts)
473 dirstate = repo.dirstate
473 dirstate = repo.dirstate
474 'a' in dirstate
474 'a' in dirstate
475 def d():
475 def d():
476 dirstate.dirs()
476 dirstate.dirs()
477 del dirstate._dirs
477 del dirstate._dirs
478 timer(d)
478 timer(d)
479 fm.end()
479 fm.end()
480
480
481 @command('perfdirstate', formatteropts)
481 @command('perfdirstate', formatteropts)
482 def perfdirstate(ui, repo, **opts):
482 def perfdirstate(ui, repo, **opts):
483 timer, fm = gettimer(ui, opts)
483 timer, fm = gettimer(ui, opts)
484 "a" in repo.dirstate
484 "a" in repo.dirstate
485 def d():
485 def d():
486 repo.dirstate.invalidate()
486 repo.dirstate.invalidate()
487 "a" in repo.dirstate
487 "a" in repo.dirstate
488 timer(d)
488 timer(d)
489 fm.end()
489 fm.end()
490
490
491 @command('perfdirstatedirs', formatteropts)
491 @command('perfdirstatedirs', formatteropts)
492 def perfdirstatedirs(ui, repo, **opts):
492 def perfdirstatedirs(ui, repo, **opts):
493 timer, fm = gettimer(ui, opts)
493 timer, fm = gettimer(ui, opts)
494 "a" in repo.dirstate
494 "a" in repo.dirstate
495 def d():
495 def d():
496 "a" in repo.dirstate._dirs
496 "a" in repo.dirstate._dirs
497 del repo.dirstate._dirs
497 del repo.dirstate._dirs
498 timer(d)
498 timer(d)
499 fm.end()
499 fm.end()
500
500
501 @command('perfdirstatefoldmap', formatteropts)
501 @command('perfdirstatefoldmap', formatteropts)
502 def perfdirstatefoldmap(ui, repo, **opts):
502 def perfdirstatefoldmap(ui, repo, **opts):
503 timer, fm = gettimer(ui, opts)
503 timer, fm = gettimer(ui, opts)
504 dirstate = repo.dirstate
504 dirstate = repo.dirstate
505 'a' in dirstate
505 'a' in dirstate
506 def d():
506 def d():
507 dirstate._filefoldmap.get('a')
507 dirstate._filefoldmap.get('a')
508 del dirstate._filefoldmap
508 del dirstate._filefoldmap
509 timer(d)
509 timer(d)
510 fm.end()
510 fm.end()
511
511
512 @command('perfdirfoldmap', formatteropts)
512 @command('perfdirfoldmap', formatteropts)
513 def perfdirfoldmap(ui, repo, **opts):
513 def perfdirfoldmap(ui, repo, **opts):
514 timer, fm = gettimer(ui, opts)
514 timer, fm = gettimer(ui, opts)
515 dirstate = repo.dirstate
515 dirstate = repo.dirstate
516 'a' in dirstate
516 'a' in dirstate
517 def d():
517 def d():
518 dirstate._dirfoldmap.get('a')
518 dirstate._dirfoldmap.get('a')
519 del dirstate._dirfoldmap
519 del dirstate._dirfoldmap
520 del dirstate._dirs
520 del dirstate._dirs
521 timer(d)
521 timer(d)
522 fm.end()
522 fm.end()
523
523
524 @command('perfdirstatewrite', formatteropts)
524 @command('perfdirstatewrite', formatteropts)
525 def perfdirstatewrite(ui, repo, **opts):
525 def perfdirstatewrite(ui, repo, **opts):
526 timer, fm = gettimer(ui, opts)
526 timer, fm = gettimer(ui, opts)
527 ds = repo.dirstate
527 ds = repo.dirstate
528 "a" in ds
528 "a" in ds
529 def d():
529 def d():
530 ds._dirty = True
530 ds._dirty = True
531 ds.write(repo.currenttransaction())
531 ds.write(repo.currenttransaction())
532 timer(d)
532 timer(d)
533 fm.end()
533 fm.end()
534
534
535 @command('perfmergecalculate',
535 @command('perfmergecalculate',
536 [('r', 'rev', '.', 'rev to merge against')] + formatteropts)
536 [('r', 'rev', '.', 'rev to merge against')] + formatteropts)
537 def perfmergecalculate(ui, repo, rev, **opts):
537 def perfmergecalculate(ui, repo, rev, **opts):
538 timer, fm = gettimer(ui, opts)
538 timer, fm = gettimer(ui, opts)
539 wctx = repo[None]
539 wctx = repo[None]
540 rctx = scmutil.revsingle(repo, rev, rev)
540 rctx = scmutil.revsingle(repo, rev, rev)
541 ancestor = wctx.ancestor(rctx)
541 ancestor = wctx.ancestor(rctx)
542 # we don't want working dir files to be stat'd in the benchmark, so prime
542 # we don't want working dir files to be stat'd in the benchmark, so prime
543 # that cache
543 # that cache
544 wctx.dirty()
544 wctx.dirty()
545 def d():
545 def d():
546 # acceptremote is True because we don't want prompts in the middle of
546 # acceptremote is True because we don't want prompts in the middle of
547 # our benchmark
547 # our benchmark
548 merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
548 merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
549 acceptremote=True, followcopies=True)
549 acceptremote=True, followcopies=True)
550 timer(d)
550 timer(d)
551 fm.end()
551 fm.end()
552
552
553 @command('perfpathcopies', [], "REV REV")
553 @command('perfpathcopies', [], "REV REV")
554 def perfpathcopies(ui, repo, rev1, rev2, **opts):
554 def perfpathcopies(ui, repo, rev1, rev2, **opts):
555 timer, fm = gettimer(ui, opts)
555 timer, fm = gettimer(ui, opts)
556 ctx1 = scmutil.revsingle(repo, rev1, rev1)
556 ctx1 = scmutil.revsingle(repo, rev1, rev1)
557 ctx2 = scmutil.revsingle(repo, rev2, rev2)
557 ctx2 = scmutil.revsingle(repo, rev2, rev2)
558 def d():
558 def d():
559 copies.pathcopies(ctx1, ctx2)
559 copies.pathcopies(ctx1, ctx2)
560 timer(d)
560 timer(d)
561 fm.end()
561 fm.end()
562
562
563 @command('perfmanifest', [], 'REV')
563 @command('perfmanifest', [], 'REV')
564 def perfmanifest(ui, repo, rev, **opts):
564 def perfmanifest(ui, repo, rev, **opts):
565 timer, fm = gettimer(ui, opts)
565 timer, fm = gettimer(ui, opts)
566 ctx = scmutil.revsingle(repo, rev, rev)
566 ctx = scmutil.revsingle(repo, rev, rev)
567 t = ctx.manifestnode()
567 t = ctx.manifestnode()
568 def d():
568 def d():
569 repo.manifest.clearcaches()
569 repo.manifest.clearcaches()
570 repo.manifest.read(t)
570 repo.manifestlog[t].read()
571 timer(d)
571 timer(d)
572 fm.end()
572 fm.end()
573
573
574 @command('perfchangeset', formatteropts)
574 @command('perfchangeset', formatteropts)
575 def perfchangeset(ui, repo, rev, **opts):
575 def perfchangeset(ui, repo, rev, **opts):
576 timer, fm = gettimer(ui, opts)
576 timer, fm = gettimer(ui, opts)
577 n = repo[rev].node()
577 n = repo[rev].node()
578 def d():
578 def d():
579 repo.changelog.read(n)
579 repo.changelog.read(n)
580 #repo.changelog._cache = None
580 #repo.changelog._cache = None
581 timer(d)
581 timer(d)
582 fm.end()
582 fm.end()
583
583
584 @command('perfindex', formatteropts)
584 @command('perfindex', formatteropts)
585 def perfindex(ui, repo, **opts):
585 def perfindex(ui, repo, **opts):
586 import mercurial.revlog
586 import mercurial.revlog
587 timer, fm = gettimer(ui, opts)
587 timer, fm = gettimer(ui, opts)
588 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
588 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
589 n = repo["tip"].node()
589 n = repo["tip"].node()
590 svfs = getsvfs(repo)
590 svfs = getsvfs(repo)
591 def d():
591 def d():
592 cl = mercurial.revlog.revlog(svfs, "00changelog.i")
592 cl = mercurial.revlog.revlog(svfs, "00changelog.i")
593 cl.rev(n)
593 cl.rev(n)
594 timer(d)
594 timer(d)
595 fm.end()
595 fm.end()
596
596
597 @command('perfstartup', formatteropts)
597 @command('perfstartup', formatteropts)
598 def perfstartup(ui, repo, **opts):
598 def perfstartup(ui, repo, **opts):
599 timer, fm = gettimer(ui, opts)
599 timer, fm = gettimer(ui, opts)
600 cmd = sys.argv[0]
600 cmd = sys.argv[0]
601 def d():
601 def d():
602 if os.name != 'nt':
602 if os.name != 'nt':
603 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
603 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
604 else:
604 else:
605 os.environ['HGRCPATH'] = ''
605 os.environ['HGRCPATH'] = ''
606 os.system("%s version -q > NUL" % cmd)
606 os.system("%s version -q > NUL" % cmd)
607 timer(d)
607 timer(d)
608 fm.end()
608 fm.end()
609
609
610 @command('perfparents', formatteropts)
610 @command('perfparents', formatteropts)
611 def perfparents(ui, repo, **opts):
611 def perfparents(ui, repo, **opts):
612 timer, fm = gettimer(ui, opts)
612 timer, fm = gettimer(ui, opts)
613 # control the number of commits perfparents iterates over
613 # control the number of commits perfparents iterates over
614 # experimental config: perf.parentscount
614 # experimental config: perf.parentscount
615 count = getint(ui, "perf", "parentscount", 1000)
615 count = getint(ui, "perf", "parentscount", 1000)
616 if len(repo.changelog) < count:
616 if len(repo.changelog) < count:
617 raise error.Abort("repo needs %d commits for this test" % count)
617 raise error.Abort("repo needs %d commits for this test" % count)
618 repo = repo.unfiltered()
618 repo = repo.unfiltered()
619 nl = [repo.changelog.node(i) for i in xrange(count)]
619 nl = [repo.changelog.node(i) for i in xrange(count)]
620 def d():
620 def d():
621 for n in nl:
621 for n in nl:
622 repo.changelog.parents(n)
622 repo.changelog.parents(n)
623 timer(d)
623 timer(d)
624 fm.end()
624 fm.end()
625
625
626 @command('perfctxfiles', formatteropts)
626 @command('perfctxfiles', formatteropts)
627 def perfctxfiles(ui, repo, x, **opts):
627 def perfctxfiles(ui, repo, x, **opts):
628 x = int(x)
628 x = int(x)
629 timer, fm = gettimer(ui, opts)
629 timer, fm = gettimer(ui, opts)
630 def d():
630 def d():
631 len(repo[x].files())
631 len(repo[x].files())
632 timer(d)
632 timer(d)
633 fm.end()
633 fm.end()
634
634
635 @command('perfrawfiles', formatteropts)
635 @command('perfrawfiles', formatteropts)
636 def perfrawfiles(ui, repo, x, **opts):
636 def perfrawfiles(ui, repo, x, **opts):
637 x = int(x)
637 x = int(x)
638 timer, fm = gettimer(ui, opts)
638 timer, fm = gettimer(ui, opts)
639 cl = repo.changelog
639 cl = repo.changelog
640 def d():
640 def d():
641 len(cl.read(x)[3])
641 len(cl.read(x)[3])
642 timer(d)
642 timer(d)
643 fm.end()
643 fm.end()
644
644
645 @command('perflookup', formatteropts)
645 @command('perflookup', formatteropts)
646 def perflookup(ui, repo, rev, **opts):
646 def perflookup(ui, repo, rev, **opts):
647 timer, fm = gettimer(ui, opts)
647 timer, fm = gettimer(ui, opts)
648 timer(lambda: len(repo.lookup(rev)))
648 timer(lambda: len(repo.lookup(rev)))
649 fm.end()
649 fm.end()
650
650
651 @command('perfrevrange', formatteropts)
651 @command('perfrevrange', formatteropts)
652 def perfrevrange(ui, repo, *specs, **opts):
652 def perfrevrange(ui, repo, *specs, **opts):
653 timer, fm = gettimer(ui, opts)
653 timer, fm = gettimer(ui, opts)
654 revrange = scmutil.revrange
654 revrange = scmutil.revrange
655 timer(lambda: len(revrange(repo, specs)))
655 timer(lambda: len(revrange(repo, specs)))
656 fm.end()
656 fm.end()
657
657
658 @command('perfnodelookup', formatteropts)
658 @command('perfnodelookup', formatteropts)
659 def perfnodelookup(ui, repo, rev, **opts):
659 def perfnodelookup(ui, repo, rev, **opts):
660 timer, fm = gettimer(ui, opts)
660 timer, fm = gettimer(ui, opts)
661 import mercurial.revlog
661 import mercurial.revlog
662 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
662 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
663 n = repo[rev].node()
663 n = repo[rev].node()
664 cl = mercurial.revlog.revlog(getsvfs(repo), "00changelog.i")
664 cl = mercurial.revlog.revlog(getsvfs(repo), "00changelog.i")
665 def d():
665 def d():
666 cl.rev(n)
666 cl.rev(n)
667 clearcaches(cl)
667 clearcaches(cl)
668 timer(d)
668 timer(d)
669 fm.end()
669 fm.end()
670
670
671 @command('perflog',
671 @command('perflog',
672 [('', 'rename', False, 'ask log to follow renames')] + formatteropts)
672 [('', 'rename', False, 'ask log to follow renames')] + formatteropts)
673 def perflog(ui, repo, rev=None, **opts):
673 def perflog(ui, repo, rev=None, **opts):
674 if rev is None:
674 if rev is None:
675 rev=[]
675 rev=[]
676 timer, fm = gettimer(ui, opts)
676 timer, fm = gettimer(ui, opts)
677 ui.pushbuffer()
677 ui.pushbuffer()
678 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
678 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
679 copies=opts.get('rename')))
679 copies=opts.get('rename')))
680 ui.popbuffer()
680 ui.popbuffer()
681 fm.end()
681 fm.end()
682
682
683 @command('perfmoonwalk', formatteropts)
683 @command('perfmoonwalk', formatteropts)
684 def perfmoonwalk(ui, repo, **opts):
684 def perfmoonwalk(ui, repo, **opts):
685 """benchmark walking the changelog backwards
685 """benchmark walking the changelog backwards
686
686
687 This also loads the changelog data for each revision in the changelog.
687 This also loads the changelog data for each revision in the changelog.
688 """
688 """
689 timer, fm = gettimer(ui, opts)
689 timer, fm = gettimer(ui, opts)
690 def moonwalk():
690 def moonwalk():
691 for i in xrange(len(repo), -1, -1):
691 for i in xrange(len(repo), -1, -1):
692 ctx = repo[i]
692 ctx = repo[i]
693 ctx.branch() # read changelog data (in addition to the index)
693 ctx.branch() # read changelog data (in addition to the index)
694 timer(moonwalk)
694 timer(moonwalk)
695 fm.end()
695 fm.end()
696
696
697 @command('perftemplating', formatteropts)
697 @command('perftemplating', formatteropts)
698 def perftemplating(ui, repo, rev=None, **opts):
698 def perftemplating(ui, repo, rev=None, **opts):
699 if rev is None:
699 if rev is None:
700 rev=[]
700 rev=[]
701 timer, fm = gettimer(ui, opts)
701 timer, fm = gettimer(ui, opts)
702 ui.pushbuffer()
702 ui.pushbuffer()
703 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
703 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
704 template='{date|shortdate} [{rev}:{node|short}]'
704 template='{date|shortdate} [{rev}:{node|short}]'
705 ' {author|person}: {desc|firstline}\n'))
705 ' {author|person}: {desc|firstline}\n'))
706 ui.popbuffer()
706 ui.popbuffer()
707 fm.end()
707 fm.end()
708
708
709 @command('perfcca', formatteropts)
709 @command('perfcca', formatteropts)
710 def perfcca(ui, repo, **opts):
710 def perfcca(ui, repo, **opts):
711 timer, fm = gettimer(ui, opts)
711 timer, fm = gettimer(ui, opts)
712 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
712 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
713 fm.end()
713 fm.end()
714
714
715 @command('perffncacheload', formatteropts)
715 @command('perffncacheload', formatteropts)
716 def perffncacheload(ui, repo, **opts):
716 def perffncacheload(ui, repo, **opts):
717 timer, fm = gettimer(ui, opts)
717 timer, fm = gettimer(ui, opts)
718 s = repo.store
718 s = repo.store
719 def d():
719 def d():
720 s.fncache._load()
720 s.fncache._load()
721 timer(d)
721 timer(d)
722 fm.end()
722 fm.end()
723
723
724 @command('perffncachewrite', formatteropts)
724 @command('perffncachewrite', formatteropts)
725 def perffncachewrite(ui, repo, **opts):
725 def perffncachewrite(ui, repo, **opts):
726 timer, fm = gettimer(ui, opts)
726 timer, fm = gettimer(ui, opts)
727 s = repo.store
727 s = repo.store
728 s.fncache._load()
728 s.fncache._load()
729 lock = repo.lock()
729 lock = repo.lock()
730 tr = repo.transaction('perffncachewrite')
730 tr = repo.transaction('perffncachewrite')
731 def d():
731 def d():
732 s.fncache._dirty = True
732 s.fncache._dirty = True
733 s.fncache.write(tr)
733 s.fncache.write(tr)
734 timer(d)
734 timer(d)
735 tr.close()
735 tr.close()
736 lock.release()
736 lock.release()
737 fm.end()
737 fm.end()
738
738
739 @command('perffncacheencode', formatteropts)
739 @command('perffncacheencode', formatteropts)
740 def perffncacheencode(ui, repo, **opts):
740 def perffncacheencode(ui, repo, **opts):
741 timer, fm = gettimer(ui, opts)
741 timer, fm = gettimer(ui, opts)
742 s = repo.store
742 s = repo.store
743 s.fncache._load()
743 s.fncache._load()
744 def d():
744 def d():
745 for p in s.fncache.entries:
745 for p in s.fncache.entries:
746 s.encode(p)
746 s.encode(p)
747 timer(d)
747 timer(d)
748 fm.end()
748 fm.end()
749
749
750 @command('perfbdiff', revlogopts + formatteropts + [
750 @command('perfbdiff', revlogopts + formatteropts + [
751 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
751 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
752 ('', 'alldata', False, 'test bdiffs for all associated revisions')],
752 ('', 'alldata', False, 'test bdiffs for all associated revisions')],
753 '-c|-m|FILE REV')
753 '-c|-m|FILE REV')
754 def perfbdiff(ui, repo, file_, rev=None, count=None, **opts):
754 def perfbdiff(ui, repo, file_, rev=None, count=None, **opts):
755 """benchmark a bdiff between revisions
755 """benchmark a bdiff between revisions
756
756
757 By default, benchmark a bdiff between its delta parent and itself.
757 By default, benchmark a bdiff between its delta parent and itself.
758
758
759 With ``--count``, benchmark bdiffs between delta parents and self for N
759 With ``--count``, benchmark bdiffs between delta parents and self for N
760 revisions starting at the specified revision.
760 revisions starting at the specified revision.
761
761
762 With ``--alldata``, assume the requested revision is a changeset and
762 With ``--alldata``, assume the requested revision is a changeset and
763 measure bdiffs for all changes related to that changeset (manifest
763 measure bdiffs for all changes related to that changeset (manifest
764 and filelogs).
764 and filelogs).
765 """
765 """
766 if opts['alldata']:
766 if opts['alldata']:
767 opts['changelog'] = True
767 opts['changelog'] = True
768
768
769 if opts.get('changelog') or opts.get('manifest'):
769 if opts.get('changelog') or opts.get('manifest'):
770 file_, rev = None, file_
770 file_, rev = None, file_
771 elif rev is None:
771 elif rev is None:
772 raise error.CommandError('perfbdiff', 'invalid arguments')
772 raise error.CommandError('perfbdiff', 'invalid arguments')
773
773
774 textpairs = []
774 textpairs = []
775
775
776 r = cmdutil.openrevlog(repo, 'perfbdiff', file_, opts)
776 r = cmdutil.openrevlog(repo, 'perfbdiff', file_, opts)
777
777
778 startrev = r.rev(r.lookup(rev))
778 startrev = r.rev(r.lookup(rev))
779 for rev in range(startrev, min(startrev + count, len(r) - 1)):
779 for rev in range(startrev, min(startrev + count, len(r) - 1)):
780 if opts['alldata']:
780 if opts['alldata']:
781 # Load revisions associated with changeset.
781 # Load revisions associated with changeset.
782 ctx = repo[rev]
782 ctx = repo[rev]
783 mtext = repo.manifest.revision(ctx.manifestnode())
783 mtext = repo.manifest.revision(ctx.manifestnode())
784 for pctx in ctx.parents():
784 for pctx in ctx.parents():
785 pman = repo.manifest.revision(pctx.manifestnode())
785 pman = repo.manifest.revision(pctx.manifestnode())
786 textpairs.append((pman, mtext))
786 textpairs.append((pman, mtext))
787
787
788 # Load filelog revisions by iterating manifest delta.
788 # Load filelog revisions by iterating manifest delta.
789 man = ctx.manifest()
789 man = ctx.manifest()
790 pman = ctx.p1().manifest()
790 pman = ctx.p1().manifest()
791 for filename, change in pman.diff(man).items():
791 for filename, change in pman.diff(man).items():
792 fctx = repo.file(filename)
792 fctx = repo.file(filename)
793 f1 = fctx.revision(change[0][0] or -1)
793 f1 = fctx.revision(change[0][0] or -1)
794 f2 = fctx.revision(change[1][0] or -1)
794 f2 = fctx.revision(change[1][0] or -1)
795 textpairs.append((f1, f2))
795 textpairs.append((f1, f2))
796 else:
796 else:
797 dp = r.deltaparent(rev)
797 dp = r.deltaparent(rev)
798 textpairs.append((r.revision(dp), r.revision(rev)))
798 textpairs.append((r.revision(dp), r.revision(rev)))
799
799
800 def d():
800 def d():
801 for pair in textpairs:
801 for pair in textpairs:
802 bdiff.bdiff(*pair)
802 bdiff.bdiff(*pair)
803
803
804 timer, fm = gettimer(ui, opts)
804 timer, fm = gettimer(ui, opts)
805 timer(d)
805 timer(d)
806 fm.end()
806 fm.end()
807
807
808 @command('perfdiffwd', formatteropts)
808 @command('perfdiffwd', formatteropts)
809 def perfdiffwd(ui, repo, **opts):
809 def perfdiffwd(ui, repo, **opts):
810 """Profile diff of working directory changes"""
810 """Profile diff of working directory changes"""
811 timer, fm = gettimer(ui, opts)
811 timer, fm = gettimer(ui, opts)
812 options = {
812 options = {
813 'w': 'ignore_all_space',
813 'w': 'ignore_all_space',
814 'b': 'ignore_space_change',
814 'b': 'ignore_space_change',
815 'B': 'ignore_blank_lines',
815 'B': 'ignore_blank_lines',
816 }
816 }
817
817
818 for diffopt in ('', 'w', 'b', 'B', 'wB'):
818 for diffopt in ('', 'w', 'b', 'B', 'wB'):
819 opts = dict((options[c], '1') for c in diffopt)
819 opts = dict((options[c], '1') for c in diffopt)
820 def d():
820 def d():
821 ui.pushbuffer()
821 ui.pushbuffer()
822 commands.diff(ui, repo, **opts)
822 commands.diff(ui, repo, **opts)
823 ui.popbuffer()
823 ui.popbuffer()
824 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
824 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
825 timer(d, title)
825 timer(d, title)
826 fm.end()
826 fm.end()
827
827
828 @command('perfrevlog', revlogopts + formatteropts +
828 @command('perfrevlog', revlogopts + formatteropts +
829 [('d', 'dist', 100, 'distance between the revisions'),
829 [('d', 'dist', 100, 'distance between the revisions'),
830 ('s', 'startrev', 0, 'revision to start reading at'),
830 ('s', 'startrev', 0, 'revision to start reading at'),
831 ('', 'reverse', False, 'read in reverse')],
831 ('', 'reverse', False, 'read in reverse')],
832 '-c|-m|FILE')
832 '-c|-m|FILE')
833 def perfrevlog(ui, repo, file_=None, startrev=0, reverse=False, **opts):
833 def perfrevlog(ui, repo, file_=None, startrev=0, reverse=False, **opts):
834 """Benchmark reading a series of revisions from a revlog.
834 """Benchmark reading a series of revisions from a revlog.
835
835
836 By default, we read every ``-d/--dist`` revision from 0 to tip of
836 By default, we read every ``-d/--dist`` revision from 0 to tip of
837 the specified revlog.
837 the specified revlog.
838
838
839 The start revision can be defined via ``-s/--startrev``.
839 The start revision can be defined via ``-s/--startrev``.
840 """
840 """
841 timer, fm = gettimer(ui, opts)
841 timer, fm = gettimer(ui, opts)
842 _len = getlen(ui)
842 _len = getlen(ui)
843
843
844 def d():
844 def d():
845 r = cmdutil.openrevlog(repo, 'perfrevlog', file_, opts)
845 r = cmdutil.openrevlog(repo, 'perfrevlog', file_, opts)
846
846
847 startrev = 0
847 startrev = 0
848 endrev = _len(r)
848 endrev = _len(r)
849 dist = opts['dist']
849 dist = opts['dist']
850
850
851 if reverse:
851 if reverse:
852 startrev, endrev = endrev, startrev
852 startrev, endrev = endrev, startrev
853 dist = -1 * dist
853 dist = -1 * dist
854
854
855 for x in xrange(startrev, endrev, dist):
855 for x in xrange(startrev, endrev, dist):
856 r.revision(r.node(x))
856 r.revision(r.node(x))
857
857
858 timer(d)
858 timer(d)
859 fm.end()
859 fm.end()
860
860
861 @command('perfrevlogrevision', revlogopts + formatteropts +
861 @command('perfrevlogrevision', revlogopts + formatteropts +
862 [('', 'cache', False, 'use caches instead of clearing')],
862 [('', 'cache', False, 'use caches instead of clearing')],
863 '-c|-m|FILE REV')
863 '-c|-m|FILE REV')
864 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
864 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
865 """Benchmark obtaining a revlog revision.
865 """Benchmark obtaining a revlog revision.
866
866
867 Obtaining a revlog revision consists of roughly the following steps:
867 Obtaining a revlog revision consists of roughly the following steps:
868
868
869 1. Compute the delta chain
869 1. Compute the delta chain
870 2. Obtain the raw chunks for that delta chain
870 2. Obtain the raw chunks for that delta chain
871 3. Decompress each raw chunk
871 3. Decompress each raw chunk
872 4. Apply binary patches to obtain fulltext
872 4. Apply binary patches to obtain fulltext
873 5. Verify hash of fulltext
873 5. Verify hash of fulltext
874
874
875 This command measures the time spent in each of these phases.
875 This command measures the time spent in each of these phases.
876 """
876 """
877 if opts.get('changelog') or opts.get('manifest'):
877 if opts.get('changelog') or opts.get('manifest'):
878 file_, rev = None, file_
878 file_, rev = None, file_
879 elif rev is None:
879 elif rev is None:
880 raise error.CommandError('perfrevlogrevision', 'invalid arguments')
880 raise error.CommandError('perfrevlogrevision', 'invalid arguments')
881
881
882 r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
882 r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
883 node = r.lookup(rev)
883 node = r.lookup(rev)
884 rev = r.rev(node)
884 rev = r.rev(node)
885
885
886 def dodeltachain(rev):
886 def dodeltachain(rev):
887 if not cache:
887 if not cache:
888 r.clearcaches()
888 r.clearcaches()
889 r._deltachain(rev)
889 r._deltachain(rev)
890
890
891 def doread(chain):
891 def doread(chain):
892 if not cache:
892 if not cache:
893 r.clearcaches()
893 r.clearcaches()
894 r._chunkraw(chain[0], chain[-1])
894 r._chunkraw(chain[0], chain[-1])
895
895
896 def dodecompress(data, chain):
896 def dodecompress(data, chain):
897 if not cache:
897 if not cache:
898 r.clearcaches()
898 r.clearcaches()
899
899
900 start = r.start
900 start = r.start
901 length = r.length
901 length = r.length
902 inline = r._inline
902 inline = r._inline
903 iosize = r._io.size
903 iosize = r._io.size
904 buffer = util.buffer
904 buffer = util.buffer
905 offset = start(chain[0])
905 offset = start(chain[0])
906
906
907 for rev in chain:
907 for rev in chain:
908 chunkstart = start(rev)
908 chunkstart = start(rev)
909 if inline:
909 if inline:
910 chunkstart += (rev + 1) * iosize
910 chunkstart += (rev + 1) * iosize
911 chunklength = length(rev)
911 chunklength = length(rev)
912 b = buffer(data, chunkstart - offset, chunklength)
912 b = buffer(data, chunkstart - offset, chunklength)
913 revlog.decompress(b)
913 revlog.decompress(b)
914
914
915 def dopatch(text, bins):
915 def dopatch(text, bins):
916 if not cache:
916 if not cache:
917 r.clearcaches()
917 r.clearcaches()
918 mdiff.patches(text, bins)
918 mdiff.patches(text, bins)
919
919
920 def dohash(text):
920 def dohash(text):
921 if not cache:
921 if not cache:
922 r.clearcaches()
922 r.clearcaches()
923 r._checkhash(text, node, rev)
923 r._checkhash(text, node, rev)
924
924
925 def dorevision():
925 def dorevision():
926 if not cache:
926 if not cache:
927 r.clearcaches()
927 r.clearcaches()
928 r.revision(node)
928 r.revision(node)
929
929
930 chain = r._deltachain(rev)[0]
930 chain = r._deltachain(rev)[0]
931 data = r._chunkraw(chain[0], chain[-1])[1]
931 data = r._chunkraw(chain[0], chain[-1])[1]
932 bins = r._chunks(chain)
932 bins = r._chunks(chain)
933 text = str(bins[0])
933 text = str(bins[0])
934 bins = bins[1:]
934 bins = bins[1:]
935 text = mdiff.patches(text, bins)
935 text = mdiff.patches(text, bins)
936
936
937 benches = [
937 benches = [
938 (lambda: dorevision(), 'full'),
938 (lambda: dorevision(), 'full'),
939 (lambda: dodeltachain(rev), 'deltachain'),
939 (lambda: dodeltachain(rev), 'deltachain'),
940 (lambda: doread(chain), 'read'),
940 (lambda: doread(chain), 'read'),
941 (lambda: dodecompress(data, chain), 'decompress'),
941 (lambda: dodecompress(data, chain), 'decompress'),
942 (lambda: dopatch(text, bins), 'patch'),
942 (lambda: dopatch(text, bins), 'patch'),
943 (lambda: dohash(text), 'hash'),
943 (lambda: dohash(text), 'hash'),
944 ]
944 ]
945
945
946 for fn, title in benches:
946 for fn, title in benches:
947 timer, fm = gettimer(ui, opts)
947 timer, fm = gettimer(ui, opts)
948 timer(fn, title=title)
948 timer(fn, title=title)
949 fm.end()
949 fm.end()
950
950
951 @command('perfrevset',
951 @command('perfrevset',
952 [('C', 'clear', False, 'clear volatile cache between each call.'),
952 [('C', 'clear', False, 'clear volatile cache between each call.'),
953 ('', 'contexts', False, 'obtain changectx for each revision')]
953 ('', 'contexts', False, 'obtain changectx for each revision')]
954 + formatteropts, "REVSET")
954 + formatteropts, "REVSET")
955 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
955 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
956 """benchmark the execution time of a revset
956 """benchmark the execution time of a revset
957
957
958 Use the --clean option if need to evaluate the impact of build volatile
958 Use the --clean option if need to evaluate the impact of build volatile
959 revisions set cache on the revset execution. Volatile cache hold filtered
959 revisions set cache on the revset execution. Volatile cache hold filtered
960 and obsolete related cache."""
960 and obsolete related cache."""
961 timer, fm = gettimer(ui, opts)
961 timer, fm = gettimer(ui, opts)
962 def d():
962 def d():
963 if clear:
963 if clear:
964 repo.invalidatevolatilesets()
964 repo.invalidatevolatilesets()
965 if contexts:
965 if contexts:
966 for ctx in repo.set(expr): pass
966 for ctx in repo.set(expr): pass
967 else:
967 else:
968 for r in repo.revs(expr): pass
968 for r in repo.revs(expr): pass
969 timer(d)
969 timer(d)
970 fm.end()
970 fm.end()
971
971
972 @command('perfvolatilesets', formatteropts)
972 @command('perfvolatilesets', formatteropts)
973 def perfvolatilesets(ui, repo, *names, **opts):
973 def perfvolatilesets(ui, repo, *names, **opts):
974 """benchmark the computation of various volatile set
974 """benchmark the computation of various volatile set
975
975
976 Volatile set computes element related to filtering and obsolescence."""
976 Volatile set computes element related to filtering and obsolescence."""
977 timer, fm = gettimer(ui, opts)
977 timer, fm = gettimer(ui, opts)
978 repo = repo.unfiltered()
978 repo = repo.unfiltered()
979
979
980 def getobs(name):
980 def getobs(name):
981 def d():
981 def d():
982 repo.invalidatevolatilesets()
982 repo.invalidatevolatilesets()
983 obsolete.getrevs(repo, name)
983 obsolete.getrevs(repo, name)
984 return d
984 return d
985
985
986 allobs = sorted(obsolete.cachefuncs)
986 allobs = sorted(obsolete.cachefuncs)
987 if names:
987 if names:
988 allobs = [n for n in allobs if n in names]
988 allobs = [n for n in allobs if n in names]
989
989
990 for name in allobs:
990 for name in allobs:
991 timer(getobs(name), title=name)
991 timer(getobs(name), title=name)
992
992
993 def getfiltered(name):
993 def getfiltered(name):
994 def d():
994 def d():
995 repo.invalidatevolatilesets()
995 repo.invalidatevolatilesets()
996 repoview.filterrevs(repo, name)
996 repoview.filterrevs(repo, name)
997 return d
997 return d
998
998
999 allfilter = sorted(repoview.filtertable)
999 allfilter = sorted(repoview.filtertable)
1000 if names:
1000 if names:
1001 allfilter = [n for n in allfilter if n in names]
1001 allfilter = [n for n in allfilter if n in names]
1002
1002
1003 for name in allfilter:
1003 for name in allfilter:
1004 timer(getfiltered(name), title=name)
1004 timer(getfiltered(name), title=name)
1005 fm.end()
1005 fm.end()
1006
1006
1007 @command('perfbranchmap',
1007 @command('perfbranchmap',
1008 [('f', 'full', False,
1008 [('f', 'full', False,
1009 'Includes build time of subset'),
1009 'Includes build time of subset'),
1010 ] + formatteropts)
1010 ] + formatteropts)
1011 def perfbranchmap(ui, repo, full=False, **opts):
1011 def perfbranchmap(ui, repo, full=False, **opts):
1012 """benchmark the update of a branchmap
1012 """benchmark the update of a branchmap
1013
1013
1014 This benchmarks the full repo.branchmap() call with read and write disabled
1014 This benchmarks the full repo.branchmap() call with read and write disabled
1015 """
1015 """
1016 timer, fm = gettimer(ui, opts)
1016 timer, fm = gettimer(ui, opts)
1017 def getbranchmap(filtername):
1017 def getbranchmap(filtername):
1018 """generate a benchmark function for the filtername"""
1018 """generate a benchmark function for the filtername"""
1019 if filtername is None:
1019 if filtername is None:
1020 view = repo
1020 view = repo
1021 else:
1021 else:
1022 view = repo.filtered(filtername)
1022 view = repo.filtered(filtername)
1023 def d():
1023 def d():
1024 if full:
1024 if full:
1025 view._branchcaches.clear()
1025 view._branchcaches.clear()
1026 else:
1026 else:
1027 view._branchcaches.pop(filtername, None)
1027 view._branchcaches.pop(filtername, None)
1028 view.branchmap()
1028 view.branchmap()
1029 return d
1029 return d
1030 # add filter in smaller subset to bigger subset
1030 # add filter in smaller subset to bigger subset
1031 possiblefilters = set(repoview.filtertable)
1031 possiblefilters = set(repoview.filtertable)
1032 subsettable = getbranchmapsubsettable()
1032 subsettable = getbranchmapsubsettable()
1033 allfilters = []
1033 allfilters = []
1034 while possiblefilters:
1034 while possiblefilters:
1035 for name in possiblefilters:
1035 for name in possiblefilters:
1036 subset = subsettable.get(name)
1036 subset = subsettable.get(name)
1037 if subset not in possiblefilters:
1037 if subset not in possiblefilters:
1038 break
1038 break
1039 else:
1039 else:
1040 assert False, 'subset cycle %s!' % possiblefilters
1040 assert False, 'subset cycle %s!' % possiblefilters
1041 allfilters.append(name)
1041 allfilters.append(name)
1042 possiblefilters.remove(name)
1042 possiblefilters.remove(name)
1043
1043
1044 # warm the cache
1044 # warm the cache
1045 if not full:
1045 if not full:
1046 for name in allfilters:
1046 for name in allfilters:
1047 repo.filtered(name).branchmap()
1047 repo.filtered(name).branchmap()
1048 # add unfiltered
1048 # add unfiltered
1049 allfilters.append(None)
1049 allfilters.append(None)
1050
1050
1051 branchcacheread = safeattrsetter(branchmap, 'read')
1051 branchcacheread = safeattrsetter(branchmap, 'read')
1052 branchcachewrite = safeattrsetter(branchmap.branchcache, 'write')
1052 branchcachewrite = safeattrsetter(branchmap.branchcache, 'write')
1053 branchcacheread.set(lambda repo: None)
1053 branchcacheread.set(lambda repo: None)
1054 branchcachewrite.set(lambda bc, repo: None)
1054 branchcachewrite.set(lambda bc, repo: None)
1055 try:
1055 try:
1056 for name in allfilters:
1056 for name in allfilters:
1057 timer(getbranchmap(name), title=str(name))
1057 timer(getbranchmap(name), title=str(name))
1058 finally:
1058 finally:
1059 branchcacheread.restore()
1059 branchcacheread.restore()
1060 branchcachewrite.restore()
1060 branchcachewrite.restore()
1061 fm.end()
1061 fm.end()
1062
1062
1063 @command('perfloadmarkers')
1063 @command('perfloadmarkers')
1064 def perfloadmarkers(ui, repo):
1064 def perfloadmarkers(ui, repo):
1065 """benchmark the time to parse the on-disk markers for a repo
1065 """benchmark the time to parse the on-disk markers for a repo
1066
1066
1067 Result is the number of markers in the repo."""
1067 Result is the number of markers in the repo."""
1068 timer, fm = gettimer(ui)
1068 timer, fm = gettimer(ui)
1069 svfs = getsvfs(repo)
1069 svfs = getsvfs(repo)
1070 timer(lambda: len(obsolete.obsstore(svfs)))
1070 timer(lambda: len(obsolete.obsstore(svfs)))
1071 fm.end()
1071 fm.end()
1072
1072
1073 @command('perflrucachedict', formatteropts +
1073 @command('perflrucachedict', formatteropts +
1074 [('', 'size', 4, 'size of cache'),
1074 [('', 'size', 4, 'size of cache'),
1075 ('', 'gets', 10000, 'number of key lookups'),
1075 ('', 'gets', 10000, 'number of key lookups'),
1076 ('', 'sets', 10000, 'number of key sets'),
1076 ('', 'sets', 10000, 'number of key sets'),
1077 ('', 'mixed', 10000, 'number of mixed mode operations'),
1077 ('', 'mixed', 10000, 'number of mixed mode operations'),
1078 ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')],
1078 ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')],
1079 norepo=True)
1079 norepo=True)
1080 def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000,
1080 def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000,
1081 mixedgetfreq=50, **opts):
1081 mixedgetfreq=50, **opts):
1082 def doinit():
1082 def doinit():
1083 for i in xrange(10000):
1083 for i in xrange(10000):
1084 util.lrucachedict(size)
1084 util.lrucachedict(size)
1085
1085
1086 values = []
1086 values = []
1087 for i in xrange(size):
1087 for i in xrange(size):
1088 values.append(random.randint(0, sys.maxint))
1088 values.append(random.randint(0, sys.maxint))
1089
1089
1090 # Get mode fills the cache and tests raw lookup performance with no
1090 # Get mode fills the cache and tests raw lookup performance with no
1091 # eviction.
1091 # eviction.
1092 getseq = []
1092 getseq = []
1093 for i in xrange(gets):
1093 for i in xrange(gets):
1094 getseq.append(random.choice(values))
1094 getseq.append(random.choice(values))
1095
1095
1096 def dogets():
1096 def dogets():
1097 d = util.lrucachedict(size)
1097 d = util.lrucachedict(size)
1098 for v in values:
1098 for v in values:
1099 d[v] = v
1099 d[v] = v
1100 for key in getseq:
1100 for key in getseq:
1101 value = d[key]
1101 value = d[key]
1102 value # silence pyflakes warning
1102 value # silence pyflakes warning
1103
1103
1104 # Set mode tests insertion speed with cache eviction.
1104 # Set mode tests insertion speed with cache eviction.
1105 setseq = []
1105 setseq = []
1106 for i in xrange(sets):
1106 for i in xrange(sets):
1107 setseq.append(random.randint(0, sys.maxint))
1107 setseq.append(random.randint(0, sys.maxint))
1108
1108
1109 def dosets():
1109 def dosets():
1110 d = util.lrucachedict(size)
1110 d = util.lrucachedict(size)
1111 for v in setseq:
1111 for v in setseq:
1112 d[v] = v
1112 d[v] = v
1113
1113
1114 # Mixed mode randomly performs gets and sets with eviction.
1114 # Mixed mode randomly performs gets and sets with eviction.
1115 mixedops = []
1115 mixedops = []
1116 for i in xrange(mixed):
1116 for i in xrange(mixed):
1117 r = random.randint(0, 100)
1117 r = random.randint(0, 100)
1118 if r < mixedgetfreq:
1118 if r < mixedgetfreq:
1119 op = 0
1119 op = 0
1120 else:
1120 else:
1121 op = 1
1121 op = 1
1122
1122
1123 mixedops.append((op, random.randint(0, size * 2)))
1123 mixedops.append((op, random.randint(0, size * 2)))
1124
1124
1125 def domixed():
1125 def domixed():
1126 d = util.lrucachedict(size)
1126 d = util.lrucachedict(size)
1127
1127
1128 for op, v in mixedops:
1128 for op, v in mixedops:
1129 if op == 0:
1129 if op == 0:
1130 try:
1130 try:
1131 d[v]
1131 d[v]
1132 except KeyError:
1132 except KeyError:
1133 pass
1133 pass
1134 else:
1134 else:
1135 d[v] = v
1135 d[v] = v
1136
1136
1137 benches = [
1137 benches = [
1138 (doinit, 'init'),
1138 (doinit, 'init'),
1139 (dogets, 'gets'),
1139 (dogets, 'gets'),
1140 (dosets, 'sets'),
1140 (dosets, 'sets'),
1141 (domixed, 'mixed')
1141 (domixed, 'mixed')
1142 ]
1142 ]
1143
1143
1144 for fn, title in benches:
1144 for fn, title in benches:
1145 timer, fm = gettimer(ui, opts)
1145 timer, fm = gettimer(ui, opts)
1146 timer(fn, title=title)
1146 timer(fn, title=title)
1147 fm.end()
1147 fm.end()
1148
1148
1149 def uisetup(ui):
1149 def uisetup(ui):
1150 if (util.safehasattr(cmdutil, 'openrevlog') and
1150 if (util.safehasattr(cmdutil, 'openrevlog') and
1151 not util.safehasattr(commands, 'debugrevlogopts')):
1151 not util.safehasattr(commands, 'debugrevlogopts')):
1152 # for "historical portability":
1152 # for "historical portability":
1153 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
1153 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
1154 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
1154 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
1155 # openrevlog() should cause failure, because it has been
1155 # openrevlog() should cause failure, because it has been
1156 # available since 3.5 (or 49c583ca48c4).
1156 # available since 3.5 (or 49c583ca48c4).
1157 def openrevlog(orig, repo, cmd, file_, opts):
1157 def openrevlog(orig, repo, cmd, file_, opts):
1158 if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
1158 if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
1159 raise error.Abort("This version doesn't support --dir option",
1159 raise error.Abort("This version doesn't support --dir option",
1160 hint="use 3.5 or later")
1160 hint="use 3.5 or later")
1161 return orig(repo, cmd, file_, opts)
1161 return orig(repo, cmd, file_, opts)
1162 extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
1162 extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
@@ -1,3608 +1,3608 b''
1 # mq.py - patch queues for mercurial
1 # mq.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.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 '''manage a stack of patches
8 '''manage a stack of patches
9
9
10 This extension lets you work with a stack of patches in a Mercurial
10 This extension lets you work with a stack of patches in a Mercurial
11 repository. It manages two stacks of patches - all known patches, and
11 repository. It manages two stacks of patches - all known patches, and
12 applied patches (subset of known patches).
12 applied patches (subset of known patches).
13
13
14 Known patches are represented as patch files in the .hg/patches
14 Known patches are represented as patch files in the .hg/patches
15 directory. Applied patches are both patch files and changesets.
15 directory. Applied patches are both patch files and changesets.
16
16
17 Common tasks (use :hg:`help command` for more details)::
17 Common tasks (use :hg:`help command` for more details)::
18
18
19 create new patch qnew
19 create new patch qnew
20 import existing patch qimport
20 import existing patch qimport
21
21
22 print patch series qseries
22 print patch series qseries
23 print applied patches qapplied
23 print applied patches qapplied
24
24
25 add known patch to applied stack qpush
25 add known patch to applied stack qpush
26 remove patch from applied stack qpop
26 remove patch from applied stack qpop
27 refresh contents of top applied patch qrefresh
27 refresh contents of top applied patch qrefresh
28
28
29 By default, mq will automatically use git patches when required to
29 By default, mq will automatically use git patches when required to
30 avoid losing file mode changes, copy records, binary files or empty
30 avoid losing file mode changes, copy records, binary files or empty
31 files creations or deletions. This behavior can be configured with::
31 files creations or deletions. This behavior can be configured with::
32
32
33 [mq]
33 [mq]
34 git = auto/keep/yes/no
34 git = auto/keep/yes/no
35
35
36 If set to 'keep', mq will obey the [diff] section configuration while
36 If set to 'keep', mq will obey the [diff] section configuration while
37 preserving existing git patches upon qrefresh. If set to 'yes' or
37 preserving existing git patches upon qrefresh. If set to 'yes' or
38 'no', mq will override the [diff] section and always generate git or
38 'no', mq will override the [diff] section and always generate git or
39 regular patches, possibly losing data in the second case.
39 regular patches, possibly losing data in the second case.
40
40
41 It may be desirable for mq changesets to be kept in the secret phase (see
41 It may be desirable for mq changesets to be kept in the secret phase (see
42 :hg:`help phases`), which can be enabled with the following setting::
42 :hg:`help phases`), which can be enabled with the following setting::
43
43
44 [mq]
44 [mq]
45 secret = True
45 secret = True
46
46
47 You will by default be managing a patch queue named "patches". You can
47 You will by default be managing a patch queue named "patches". You can
48 create other, independent patch queues with the :hg:`qqueue` command.
48 create other, independent patch queues with the :hg:`qqueue` command.
49
49
50 If the working directory contains uncommitted files, qpush, qpop and
50 If the working directory contains uncommitted files, qpush, qpop and
51 qgoto abort immediately. If -f/--force is used, the changes are
51 qgoto abort immediately. If -f/--force is used, the changes are
52 discarded. Setting::
52 discarded. Setting::
53
53
54 [mq]
54 [mq]
55 keepchanges = True
55 keepchanges = True
56
56
57 make them behave as if --keep-changes were passed, and non-conflicting
57 make them behave as if --keep-changes were passed, and non-conflicting
58 local changes will be tolerated and preserved. If incompatible options
58 local changes will be tolerated and preserved. If incompatible options
59 such as -f/--force or --exact are passed, this setting is ignored.
59 such as -f/--force or --exact are passed, this setting is ignored.
60
60
61 This extension used to provide a strip command. This command now lives
61 This extension used to provide a strip command. This command now lives
62 in the strip extension.
62 in the strip extension.
63 '''
63 '''
64
64
65 from __future__ import absolute_import
65 from __future__ import absolute_import
66
66
67 import errno
67 import errno
68 import os
68 import os
69 import re
69 import re
70 import shutil
70 import shutil
71 from mercurial.i18n import _
71 from mercurial.i18n import _
72 from mercurial.node import (
72 from mercurial.node import (
73 bin,
73 bin,
74 hex,
74 hex,
75 nullid,
75 nullid,
76 nullrev,
76 nullrev,
77 short,
77 short,
78 )
78 )
79 from mercurial import (
79 from mercurial import (
80 cmdutil,
80 cmdutil,
81 commands,
81 commands,
82 dispatch,
82 dispatch,
83 error,
83 error,
84 extensions,
84 extensions,
85 hg,
85 hg,
86 localrepo,
86 localrepo,
87 lock as lockmod,
87 lock as lockmod,
88 patch as patchmod,
88 patch as patchmod,
89 phases,
89 phases,
90 registrar,
90 registrar,
91 revset,
91 revset,
92 scmutil,
92 scmutil,
93 subrepo,
93 subrepo,
94 util,
94 util,
95 )
95 )
96
96
97 release = lockmod.release
97 release = lockmod.release
98 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
98 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
99
99
100 cmdtable = {}
100 cmdtable = {}
101 command = cmdutil.command(cmdtable)
101 command = cmdutil.command(cmdtable)
102 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
102 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
103 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
103 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
104 # be specifying the version(s) of Mercurial they are tested with, or
104 # be specifying the version(s) of Mercurial they are tested with, or
105 # leave the attribute unspecified.
105 # leave the attribute unspecified.
106 testedwith = 'ships-with-hg-core'
106 testedwith = 'ships-with-hg-core'
107
107
108 # force load strip extension formerly included in mq and import some utility
108 # force load strip extension formerly included in mq and import some utility
109 try:
109 try:
110 stripext = extensions.find('strip')
110 stripext = extensions.find('strip')
111 except KeyError:
111 except KeyError:
112 # note: load is lazy so we could avoid the try-except,
112 # note: load is lazy so we could avoid the try-except,
113 # but I (marmoute) prefer this explicit code.
113 # but I (marmoute) prefer this explicit code.
114 class dummyui(object):
114 class dummyui(object):
115 def debug(self, msg):
115 def debug(self, msg):
116 pass
116 pass
117 stripext = extensions.load(dummyui(), 'strip', '')
117 stripext = extensions.load(dummyui(), 'strip', '')
118
118
119 strip = stripext.strip
119 strip = stripext.strip
120 checksubstate = stripext.checksubstate
120 checksubstate = stripext.checksubstate
121 checklocalchanges = stripext.checklocalchanges
121 checklocalchanges = stripext.checklocalchanges
122
122
123
123
124 # Patch names looks like unix-file names.
124 # Patch names looks like unix-file names.
125 # They must be joinable with queue directory and result in the patch path.
125 # They must be joinable with queue directory and result in the patch path.
126 normname = util.normpath
126 normname = util.normpath
127
127
128 class statusentry(object):
128 class statusentry(object):
129 def __init__(self, node, name):
129 def __init__(self, node, name):
130 self.node, self.name = node, name
130 self.node, self.name = node, name
131 def __repr__(self):
131 def __repr__(self):
132 return hex(self.node) + ':' + self.name
132 return hex(self.node) + ':' + self.name
133
133
134 # The order of the headers in 'hg export' HG patches:
134 # The order of the headers in 'hg export' HG patches:
135 HGHEADERS = [
135 HGHEADERS = [
136 # '# HG changeset patch',
136 # '# HG changeset patch',
137 '# User ',
137 '# User ',
138 '# Date ',
138 '# Date ',
139 '# ',
139 '# ',
140 '# Branch ',
140 '# Branch ',
141 '# Node ID ',
141 '# Node ID ',
142 '# Parent ', # can occur twice for merges - but that is not relevant for mq
142 '# Parent ', # can occur twice for merges - but that is not relevant for mq
143 ]
143 ]
144 # The order of headers in plain 'mail style' patches:
144 # The order of headers in plain 'mail style' patches:
145 PLAINHEADERS = {
145 PLAINHEADERS = {
146 'from': 0,
146 'from': 0,
147 'date': 1,
147 'date': 1,
148 'subject': 2,
148 'subject': 2,
149 }
149 }
150
150
151 def inserthgheader(lines, header, value):
151 def inserthgheader(lines, header, value):
152 """Assuming lines contains a HG patch header, add a header line with value.
152 """Assuming lines contains a HG patch header, add a header line with value.
153 >>> try: inserthgheader([], '# Date ', 'z')
153 >>> try: inserthgheader([], '# Date ', 'z')
154 ... except ValueError, inst: print "oops"
154 ... except ValueError, inst: print "oops"
155 oops
155 oops
156 >>> inserthgheader(['# HG changeset patch'], '# Date ', 'z')
156 >>> inserthgheader(['# HG changeset patch'], '# Date ', 'z')
157 ['# HG changeset patch', '# Date z']
157 ['# HG changeset patch', '# Date z']
158 >>> inserthgheader(['# HG changeset patch', ''], '# Date ', 'z')
158 >>> inserthgheader(['# HG changeset patch', ''], '# Date ', 'z')
159 ['# HG changeset patch', '# Date z', '']
159 ['# HG changeset patch', '# Date z', '']
160 >>> inserthgheader(['# HG changeset patch', '# User y'], '# Date ', 'z')
160 >>> inserthgheader(['# HG changeset patch', '# User y'], '# Date ', 'z')
161 ['# HG changeset patch', '# User y', '# Date z']
161 ['# HG changeset patch', '# User y', '# Date z']
162 >>> inserthgheader(['# HG changeset patch', '# Date x', '# User y'],
162 >>> inserthgheader(['# HG changeset patch', '# Date x', '# User y'],
163 ... '# User ', 'z')
163 ... '# User ', 'z')
164 ['# HG changeset patch', '# Date x', '# User z']
164 ['# HG changeset patch', '# Date x', '# User z']
165 >>> inserthgheader(['# HG changeset patch', '# Date y'], '# Date ', 'z')
165 >>> inserthgheader(['# HG changeset patch', '# Date y'], '# Date ', 'z')
166 ['# HG changeset patch', '# Date z']
166 ['# HG changeset patch', '# Date z']
167 >>> inserthgheader(['# HG changeset patch', '', '# Date y'], '# Date ', 'z')
167 >>> inserthgheader(['# HG changeset patch', '', '# Date y'], '# Date ', 'z')
168 ['# HG changeset patch', '# Date z', '', '# Date y']
168 ['# HG changeset patch', '# Date z', '', '# Date y']
169 >>> inserthgheader(['# HG changeset patch', '# Parent y'], '# Date ', 'z')
169 >>> inserthgheader(['# HG changeset patch', '# Parent y'], '# Date ', 'z')
170 ['# HG changeset patch', '# Date z', '# Parent y']
170 ['# HG changeset patch', '# Date z', '# Parent y']
171 """
171 """
172 start = lines.index('# HG changeset patch') + 1
172 start = lines.index('# HG changeset patch') + 1
173 newindex = HGHEADERS.index(header)
173 newindex = HGHEADERS.index(header)
174 bestpos = len(lines)
174 bestpos = len(lines)
175 for i in range(start, len(lines)):
175 for i in range(start, len(lines)):
176 line = lines[i]
176 line = lines[i]
177 if not line.startswith('# '):
177 if not line.startswith('# '):
178 bestpos = min(bestpos, i)
178 bestpos = min(bestpos, i)
179 break
179 break
180 for lineindex, h in enumerate(HGHEADERS):
180 for lineindex, h in enumerate(HGHEADERS):
181 if line.startswith(h):
181 if line.startswith(h):
182 if lineindex == newindex:
182 if lineindex == newindex:
183 lines[i] = header + value
183 lines[i] = header + value
184 return lines
184 return lines
185 if lineindex > newindex:
185 if lineindex > newindex:
186 bestpos = min(bestpos, i)
186 bestpos = min(bestpos, i)
187 break # next line
187 break # next line
188 lines.insert(bestpos, header + value)
188 lines.insert(bestpos, header + value)
189 return lines
189 return lines
190
190
191 def insertplainheader(lines, header, value):
191 def insertplainheader(lines, header, value):
192 """For lines containing a plain patch header, add a header line with value.
192 """For lines containing a plain patch header, add a header line with value.
193 >>> insertplainheader([], 'Date', 'z')
193 >>> insertplainheader([], 'Date', 'z')
194 ['Date: z']
194 ['Date: z']
195 >>> insertplainheader([''], 'Date', 'z')
195 >>> insertplainheader([''], 'Date', 'z')
196 ['Date: z', '']
196 ['Date: z', '']
197 >>> insertplainheader(['x'], 'Date', 'z')
197 >>> insertplainheader(['x'], 'Date', 'z')
198 ['Date: z', '', 'x']
198 ['Date: z', '', 'x']
199 >>> insertplainheader(['From: y', 'x'], 'Date', 'z')
199 >>> insertplainheader(['From: y', 'x'], 'Date', 'z')
200 ['From: y', 'Date: z', '', 'x']
200 ['From: y', 'Date: z', '', 'x']
201 >>> insertplainheader([' date : x', ' from : y', ''], 'From', 'z')
201 >>> insertplainheader([' date : x', ' from : y', ''], 'From', 'z')
202 [' date : x', 'From: z', '']
202 [' date : x', 'From: z', '']
203 >>> insertplainheader(['', 'Date: y'], 'Date', 'z')
203 >>> insertplainheader(['', 'Date: y'], 'Date', 'z')
204 ['Date: z', '', 'Date: y']
204 ['Date: z', '', 'Date: y']
205 >>> insertplainheader(['foo: bar', 'DATE: z', 'x'], 'From', 'y')
205 >>> insertplainheader(['foo: bar', 'DATE: z', 'x'], 'From', 'y')
206 ['From: y', 'foo: bar', 'DATE: z', '', 'x']
206 ['From: y', 'foo: bar', 'DATE: z', '', 'x']
207 """
207 """
208 newprio = PLAINHEADERS[header.lower()]
208 newprio = PLAINHEADERS[header.lower()]
209 bestpos = len(lines)
209 bestpos = len(lines)
210 for i, line in enumerate(lines):
210 for i, line in enumerate(lines):
211 if ':' in line:
211 if ':' in line:
212 lheader = line.split(':', 1)[0].strip().lower()
212 lheader = line.split(':', 1)[0].strip().lower()
213 lprio = PLAINHEADERS.get(lheader, newprio + 1)
213 lprio = PLAINHEADERS.get(lheader, newprio + 1)
214 if lprio == newprio:
214 if lprio == newprio:
215 lines[i] = '%s: %s' % (header, value)
215 lines[i] = '%s: %s' % (header, value)
216 return lines
216 return lines
217 if lprio > newprio and i < bestpos:
217 if lprio > newprio and i < bestpos:
218 bestpos = i
218 bestpos = i
219 else:
219 else:
220 if line:
220 if line:
221 lines.insert(i, '')
221 lines.insert(i, '')
222 if i < bestpos:
222 if i < bestpos:
223 bestpos = i
223 bestpos = i
224 break
224 break
225 lines.insert(bestpos, '%s: %s' % (header, value))
225 lines.insert(bestpos, '%s: %s' % (header, value))
226 return lines
226 return lines
227
227
228 class patchheader(object):
228 class patchheader(object):
229 def __init__(self, pf, plainmode=False):
229 def __init__(self, pf, plainmode=False):
230 def eatdiff(lines):
230 def eatdiff(lines):
231 while lines:
231 while lines:
232 l = lines[-1]
232 l = lines[-1]
233 if (l.startswith("diff -") or
233 if (l.startswith("diff -") or
234 l.startswith("Index:") or
234 l.startswith("Index:") or
235 l.startswith("===========")):
235 l.startswith("===========")):
236 del lines[-1]
236 del lines[-1]
237 else:
237 else:
238 break
238 break
239 def eatempty(lines):
239 def eatempty(lines):
240 while lines:
240 while lines:
241 if not lines[-1].strip():
241 if not lines[-1].strip():
242 del lines[-1]
242 del lines[-1]
243 else:
243 else:
244 break
244 break
245
245
246 message = []
246 message = []
247 comments = []
247 comments = []
248 user = None
248 user = None
249 date = None
249 date = None
250 parent = None
250 parent = None
251 format = None
251 format = None
252 subject = None
252 subject = None
253 branch = None
253 branch = None
254 nodeid = None
254 nodeid = None
255 diffstart = 0
255 diffstart = 0
256
256
257 for line in file(pf):
257 for line in file(pf):
258 line = line.rstrip()
258 line = line.rstrip()
259 if (line.startswith('diff --git')
259 if (line.startswith('diff --git')
260 or (diffstart and line.startswith('+++ '))):
260 or (diffstart and line.startswith('+++ '))):
261 diffstart = 2
261 diffstart = 2
262 break
262 break
263 diffstart = 0 # reset
263 diffstart = 0 # reset
264 if line.startswith("--- "):
264 if line.startswith("--- "):
265 diffstart = 1
265 diffstart = 1
266 continue
266 continue
267 elif format == "hgpatch":
267 elif format == "hgpatch":
268 # parse values when importing the result of an hg export
268 # parse values when importing the result of an hg export
269 if line.startswith("# User "):
269 if line.startswith("# User "):
270 user = line[7:]
270 user = line[7:]
271 elif line.startswith("# Date "):
271 elif line.startswith("# Date "):
272 date = line[7:]
272 date = line[7:]
273 elif line.startswith("# Parent "):
273 elif line.startswith("# Parent "):
274 parent = line[9:].lstrip() # handle double trailing space
274 parent = line[9:].lstrip() # handle double trailing space
275 elif line.startswith("# Branch "):
275 elif line.startswith("# Branch "):
276 branch = line[9:]
276 branch = line[9:]
277 elif line.startswith("# Node ID "):
277 elif line.startswith("# Node ID "):
278 nodeid = line[10:]
278 nodeid = line[10:]
279 elif not line.startswith("# ") and line:
279 elif not line.startswith("# ") and line:
280 message.append(line)
280 message.append(line)
281 format = None
281 format = None
282 elif line == '# HG changeset patch':
282 elif line == '# HG changeset patch':
283 message = []
283 message = []
284 format = "hgpatch"
284 format = "hgpatch"
285 elif (format != "tagdone" and (line.startswith("Subject: ") or
285 elif (format != "tagdone" and (line.startswith("Subject: ") or
286 line.startswith("subject: "))):
286 line.startswith("subject: "))):
287 subject = line[9:]
287 subject = line[9:]
288 format = "tag"
288 format = "tag"
289 elif (format != "tagdone" and (line.startswith("From: ") or
289 elif (format != "tagdone" and (line.startswith("From: ") or
290 line.startswith("from: "))):
290 line.startswith("from: "))):
291 user = line[6:]
291 user = line[6:]
292 format = "tag"
292 format = "tag"
293 elif (format != "tagdone" and (line.startswith("Date: ") or
293 elif (format != "tagdone" and (line.startswith("Date: ") or
294 line.startswith("date: "))):
294 line.startswith("date: "))):
295 date = line[6:]
295 date = line[6:]
296 format = "tag"
296 format = "tag"
297 elif format == "tag" and line == "":
297 elif format == "tag" and line == "":
298 # when looking for tags (subject: from: etc) they
298 # when looking for tags (subject: from: etc) they
299 # end once you find a blank line in the source
299 # end once you find a blank line in the source
300 format = "tagdone"
300 format = "tagdone"
301 elif message or line:
301 elif message or line:
302 message.append(line)
302 message.append(line)
303 comments.append(line)
303 comments.append(line)
304
304
305 eatdiff(message)
305 eatdiff(message)
306 eatdiff(comments)
306 eatdiff(comments)
307 # Remember the exact starting line of the patch diffs before consuming
307 # Remember the exact starting line of the patch diffs before consuming
308 # empty lines, for external use by TortoiseHg and others
308 # empty lines, for external use by TortoiseHg and others
309 self.diffstartline = len(comments)
309 self.diffstartline = len(comments)
310 eatempty(message)
310 eatempty(message)
311 eatempty(comments)
311 eatempty(comments)
312
312
313 # make sure message isn't empty
313 # make sure message isn't empty
314 if format and format.startswith("tag") and subject:
314 if format and format.startswith("tag") and subject:
315 message.insert(0, subject)
315 message.insert(0, subject)
316
316
317 self.message = message
317 self.message = message
318 self.comments = comments
318 self.comments = comments
319 self.user = user
319 self.user = user
320 self.date = date
320 self.date = date
321 self.parent = parent
321 self.parent = parent
322 # nodeid and branch are for external use by TortoiseHg and others
322 # nodeid and branch are for external use by TortoiseHg and others
323 self.nodeid = nodeid
323 self.nodeid = nodeid
324 self.branch = branch
324 self.branch = branch
325 self.haspatch = diffstart > 1
325 self.haspatch = diffstart > 1
326 self.plainmode = (plainmode or
326 self.plainmode = (plainmode or
327 '# HG changeset patch' not in self.comments and
327 '# HG changeset patch' not in self.comments and
328 any(c.startswith('Date: ') or
328 any(c.startswith('Date: ') or
329 c.startswith('From: ')
329 c.startswith('From: ')
330 for c in self.comments))
330 for c in self.comments))
331
331
332 def setuser(self, user):
332 def setuser(self, user):
333 try:
333 try:
334 inserthgheader(self.comments, '# User ', user)
334 inserthgheader(self.comments, '# User ', user)
335 except ValueError:
335 except ValueError:
336 if self.plainmode:
336 if self.plainmode:
337 insertplainheader(self.comments, 'From', user)
337 insertplainheader(self.comments, 'From', user)
338 else:
338 else:
339 tmp = ['# HG changeset patch', '# User ' + user]
339 tmp = ['# HG changeset patch', '# User ' + user]
340 self.comments = tmp + self.comments
340 self.comments = tmp + self.comments
341 self.user = user
341 self.user = user
342
342
343 def setdate(self, date):
343 def setdate(self, date):
344 try:
344 try:
345 inserthgheader(self.comments, '# Date ', date)
345 inserthgheader(self.comments, '# Date ', date)
346 except ValueError:
346 except ValueError:
347 if self.plainmode:
347 if self.plainmode:
348 insertplainheader(self.comments, 'Date', date)
348 insertplainheader(self.comments, 'Date', date)
349 else:
349 else:
350 tmp = ['# HG changeset patch', '# Date ' + date]
350 tmp = ['# HG changeset patch', '# Date ' + date]
351 self.comments = tmp + self.comments
351 self.comments = tmp + self.comments
352 self.date = date
352 self.date = date
353
353
354 def setparent(self, parent):
354 def setparent(self, parent):
355 try:
355 try:
356 inserthgheader(self.comments, '# Parent ', parent)
356 inserthgheader(self.comments, '# Parent ', parent)
357 except ValueError:
357 except ValueError:
358 if not self.plainmode:
358 if not self.plainmode:
359 tmp = ['# HG changeset patch', '# Parent ' + parent]
359 tmp = ['# HG changeset patch', '# Parent ' + parent]
360 self.comments = tmp + self.comments
360 self.comments = tmp + self.comments
361 self.parent = parent
361 self.parent = parent
362
362
363 def setmessage(self, message):
363 def setmessage(self, message):
364 if self.comments:
364 if self.comments:
365 self._delmsg()
365 self._delmsg()
366 self.message = [message]
366 self.message = [message]
367 if message:
367 if message:
368 if self.plainmode and self.comments and self.comments[-1]:
368 if self.plainmode and self.comments and self.comments[-1]:
369 self.comments.append('')
369 self.comments.append('')
370 self.comments.append(message)
370 self.comments.append(message)
371
371
372 def __str__(self):
372 def __str__(self):
373 s = '\n'.join(self.comments).rstrip()
373 s = '\n'.join(self.comments).rstrip()
374 if not s:
374 if not s:
375 return ''
375 return ''
376 return s + '\n\n'
376 return s + '\n\n'
377
377
378 def _delmsg(self):
378 def _delmsg(self):
379 '''Remove existing message, keeping the rest of the comments fields.
379 '''Remove existing message, keeping the rest of the comments fields.
380 If comments contains 'subject: ', message will prepend
380 If comments contains 'subject: ', message will prepend
381 the field and a blank line.'''
381 the field and a blank line.'''
382 if self.message:
382 if self.message:
383 subj = 'subject: ' + self.message[0].lower()
383 subj = 'subject: ' + self.message[0].lower()
384 for i in xrange(len(self.comments)):
384 for i in xrange(len(self.comments)):
385 if subj == self.comments[i].lower():
385 if subj == self.comments[i].lower():
386 del self.comments[i]
386 del self.comments[i]
387 self.message = self.message[2:]
387 self.message = self.message[2:]
388 break
388 break
389 ci = 0
389 ci = 0
390 for mi in self.message:
390 for mi in self.message:
391 while mi != self.comments[ci]:
391 while mi != self.comments[ci]:
392 ci += 1
392 ci += 1
393 del self.comments[ci]
393 del self.comments[ci]
394
394
395 def newcommit(repo, phase, *args, **kwargs):
395 def newcommit(repo, phase, *args, **kwargs):
396 """helper dedicated to ensure a commit respect mq.secret setting
396 """helper dedicated to ensure a commit respect mq.secret setting
397
397
398 It should be used instead of repo.commit inside the mq source for operation
398 It should be used instead of repo.commit inside the mq source for operation
399 creating new changeset.
399 creating new changeset.
400 """
400 """
401 repo = repo.unfiltered()
401 repo = repo.unfiltered()
402 if phase is None:
402 if phase is None:
403 if repo.ui.configbool('mq', 'secret', False):
403 if repo.ui.configbool('mq', 'secret', False):
404 phase = phases.secret
404 phase = phases.secret
405 if phase is not None:
405 if phase is not None:
406 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
406 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
407 allowemptybackup = repo.ui.backupconfig('ui', 'allowemptycommit')
407 allowemptybackup = repo.ui.backupconfig('ui', 'allowemptycommit')
408 try:
408 try:
409 if phase is not None:
409 if phase is not None:
410 repo.ui.setconfig('phases', 'new-commit', phase, 'mq')
410 repo.ui.setconfig('phases', 'new-commit', phase, 'mq')
411 repo.ui.setconfig('ui', 'allowemptycommit', True)
411 repo.ui.setconfig('ui', 'allowemptycommit', True)
412 return repo.commit(*args, **kwargs)
412 return repo.commit(*args, **kwargs)
413 finally:
413 finally:
414 repo.ui.restoreconfig(allowemptybackup)
414 repo.ui.restoreconfig(allowemptybackup)
415 if phase is not None:
415 if phase is not None:
416 repo.ui.restoreconfig(phasebackup)
416 repo.ui.restoreconfig(phasebackup)
417
417
418 class AbortNoCleanup(error.Abort):
418 class AbortNoCleanup(error.Abort):
419 pass
419 pass
420
420
421 class queue(object):
421 class queue(object):
422 def __init__(self, ui, baseui, path, patchdir=None):
422 def __init__(self, ui, baseui, path, patchdir=None):
423 self.basepath = path
423 self.basepath = path
424 try:
424 try:
425 fh = open(os.path.join(path, 'patches.queue'))
425 fh = open(os.path.join(path, 'patches.queue'))
426 cur = fh.read().rstrip()
426 cur = fh.read().rstrip()
427 fh.close()
427 fh.close()
428 if not cur:
428 if not cur:
429 curpath = os.path.join(path, 'patches')
429 curpath = os.path.join(path, 'patches')
430 else:
430 else:
431 curpath = os.path.join(path, 'patches-' + cur)
431 curpath = os.path.join(path, 'patches-' + cur)
432 except IOError:
432 except IOError:
433 curpath = os.path.join(path, 'patches')
433 curpath = os.path.join(path, 'patches')
434 self.path = patchdir or curpath
434 self.path = patchdir or curpath
435 self.opener = scmutil.opener(self.path)
435 self.opener = scmutil.opener(self.path)
436 self.ui = ui
436 self.ui = ui
437 self.baseui = baseui
437 self.baseui = baseui
438 self.applieddirty = False
438 self.applieddirty = False
439 self.seriesdirty = False
439 self.seriesdirty = False
440 self.added = []
440 self.added = []
441 self.seriespath = "series"
441 self.seriespath = "series"
442 self.statuspath = "status"
442 self.statuspath = "status"
443 self.guardspath = "guards"
443 self.guardspath = "guards"
444 self.activeguards = None
444 self.activeguards = None
445 self.guardsdirty = False
445 self.guardsdirty = False
446 # Handle mq.git as a bool with extended values
446 # Handle mq.git as a bool with extended values
447 try:
447 try:
448 gitmode = ui.configbool('mq', 'git', None)
448 gitmode = ui.configbool('mq', 'git', None)
449 if gitmode is None:
449 if gitmode is None:
450 raise error.ConfigError
450 raise error.ConfigError
451 if gitmode:
451 if gitmode:
452 self.gitmode = 'yes'
452 self.gitmode = 'yes'
453 else:
453 else:
454 self.gitmode = 'no'
454 self.gitmode = 'no'
455 except error.ConfigError:
455 except error.ConfigError:
456 # let's have check-config ignore the type mismatch
456 # let's have check-config ignore the type mismatch
457 self.gitmode = ui.config(r'mq', 'git', 'auto').lower()
457 self.gitmode = ui.config(r'mq', 'git', 'auto').lower()
458 # deprecated config: mq.plain
458 # deprecated config: mq.plain
459 self.plainmode = ui.configbool('mq', 'plain', False)
459 self.plainmode = ui.configbool('mq', 'plain', False)
460 self.checkapplied = True
460 self.checkapplied = True
461
461
462 @util.propertycache
462 @util.propertycache
463 def applied(self):
463 def applied(self):
464 def parselines(lines):
464 def parselines(lines):
465 for l in lines:
465 for l in lines:
466 entry = l.split(':', 1)
466 entry = l.split(':', 1)
467 if len(entry) > 1:
467 if len(entry) > 1:
468 n, name = entry
468 n, name = entry
469 yield statusentry(bin(n), name)
469 yield statusentry(bin(n), name)
470 elif l.strip():
470 elif l.strip():
471 self.ui.warn(_('malformated mq status line: %s\n') % entry)
471 self.ui.warn(_('malformated mq status line: %s\n') % entry)
472 # else we ignore empty lines
472 # else we ignore empty lines
473 try:
473 try:
474 lines = self.opener.read(self.statuspath).splitlines()
474 lines = self.opener.read(self.statuspath).splitlines()
475 return list(parselines(lines))
475 return list(parselines(lines))
476 except IOError as e:
476 except IOError as e:
477 if e.errno == errno.ENOENT:
477 if e.errno == errno.ENOENT:
478 return []
478 return []
479 raise
479 raise
480
480
481 @util.propertycache
481 @util.propertycache
482 def fullseries(self):
482 def fullseries(self):
483 try:
483 try:
484 return self.opener.read(self.seriespath).splitlines()
484 return self.opener.read(self.seriespath).splitlines()
485 except IOError as e:
485 except IOError as e:
486 if e.errno == errno.ENOENT:
486 if e.errno == errno.ENOENT:
487 return []
487 return []
488 raise
488 raise
489
489
490 @util.propertycache
490 @util.propertycache
491 def series(self):
491 def series(self):
492 self.parseseries()
492 self.parseseries()
493 return self.series
493 return self.series
494
494
495 @util.propertycache
495 @util.propertycache
496 def seriesguards(self):
496 def seriesguards(self):
497 self.parseseries()
497 self.parseseries()
498 return self.seriesguards
498 return self.seriesguards
499
499
500 def invalidate(self):
500 def invalidate(self):
501 for a in 'applied fullseries series seriesguards'.split():
501 for a in 'applied fullseries series seriesguards'.split():
502 if a in self.__dict__:
502 if a in self.__dict__:
503 delattr(self, a)
503 delattr(self, a)
504 self.applieddirty = False
504 self.applieddirty = False
505 self.seriesdirty = False
505 self.seriesdirty = False
506 self.guardsdirty = False
506 self.guardsdirty = False
507 self.activeguards = None
507 self.activeguards = None
508
508
509 def diffopts(self, opts=None, patchfn=None):
509 def diffopts(self, opts=None, patchfn=None):
510 diffopts = patchmod.diffopts(self.ui, opts)
510 diffopts = patchmod.diffopts(self.ui, opts)
511 if self.gitmode == 'auto':
511 if self.gitmode == 'auto':
512 diffopts.upgrade = True
512 diffopts.upgrade = True
513 elif self.gitmode == 'keep':
513 elif self.gitmode == 'keep':
514 pass
514 pass
515 elif self.gitmode in ('yes', 'no'):
515 elif self.gitmode in ('yes', 'no'):
516 diffopts.git = self.gitmode == 'yes'
516 diffopts.git = self.gitmode == 'yes'
517 else:
517 else:
518 raise error.Abort(_('mq.git option can be auto/keep/yes/no'
518 raise error.Abort(_('mq.git option can be auto/keep/yes/no'
519 ' got %s') % self.gitmode)
519 ' got %s') % self.gitmode)
520 if patchfn:
520 if patchfn:
521 diffopts = self.patchopts(diffopts, patchfn)
521 diffopts = self.patchopts(diffopts, patchfn)
522 return diffopts
522 return diffopts
523
523
524 def patchopts(self, diffopts, *patches):
524 def patchopts(self, diffopts, *patches):
525 """Return a copy of input diff options with git set to true if
525 """Return a copy of input diff options with git set to true if
526 referenced patch is a git patch and should be preserved as such.
526 referenced patch is a git patch and should be preserved as such.
527 """
527 """
528 diffopts = diffopts.copy()
528 diffopts = diffopts.copy()
529 if not diffopts.git and self.gitmode == 'keep':
529 if not diffopts.git and self.gitmode == 'keep':
530 for patchfn in patches:
530 for patchfn in patches:
531 patchf = self.opener(patchfn, 'r')
531 patchf = self.opener(patchfn, 'r')
532 # if the patch was a git patch, refresh it as a git patch
532 # if the patch was a git patch, refresh it as a git patch
533 for line in patchf:
533 for line in patchf:
534 if line.startswith('diff --git'):
534 if line.startswith('diff --git'):
535 diffopts.git = True
535 diffopts.git = True
536 break
536 break
537 patchf.close()
537 patchf.close()
538 return diffopts
538 return diffopts
539
539
540 def join(self, *p):
540 def join(self, *p):
541 return os.path.join(self.path, *p)
541 return os.path.join(self.path, *p)
542
542
543 def findseries(self, patch):
543 def findseries(self, patch):
544 def matchpatch(l):
544 def matchpatch(l):
545 l = l.split('#', 1)[0]
545 l = l.split('#', 1)[0]
546 return l.strip() == patch
546 return l.strip() == patch
547 for index, l in enumerate(self.fullseries):
547 for index, l in enumerate(self.fullseries):
548 if matchpatch(l):
548 if matchpatch(l):
549 return index
549 return index
550 return None
550 return None
551
551
552 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
552 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
553
553
554 def parseseries(self):
554 def parseseries(self):
555 self.series = []
555 self.series = []
556 self.seriesguards = []
556 self.seriesguards = []
557 for l in self.fullseries:
557 for l in self.fullseries:
558 h = l.find('#')
558 h = l.find('#')
559 if h == -1:
559 if h == -1:
560 patch = l
560 patch = l
561 comment = ''
561 comment = ''
562 elif h == 0:
562 elif h == 0:
563 continue
563 continue
564 else:
564 else:
565 patch = l[:h]
565 patch = l[:h]
566 comment = l[h:]
566 comment = l[h:]
567 patch = patch.strip()
567 patch = patch.strip()
568 if patch:
568 if patch:
569 if patch in self.series:
569 if patch in self.series:
570 raise error.Abort(_('%s appears more than once in %s') %
570 raise error.Abort(_('%s appears more than once in %s') %
571 (patch, self.join(self.seriespath)))
571 (patch, self.join(self.seriespath)))
572 self.series.append(patch)
572 self.series.append(patch)
573 self.seriesguards.append(self.guard_re.findall(comment))
573 self.seriesguards.append(self.guard_re.findall(comment))
574
574
575 def checkguard(self, guard):
575 def checkguard(self, guard):
576 if not guard:
576 if not guard:
577 return _('guard cannot be an empty string')
577 return _('guard cannot be an empty string')
578 bad_chars = '# \t\r\n\f'
578 bad_chars = '# \t\r\n\f'
579 first = guard[0]
579 first = guard[0]
580 if first in '-+':
580 if first in '-+':
581 return (_('guard %r starts with invalid character: %r') %
581 return (_('guard %r starts with invalid character: %r') %
582 (guard, first))
582 (guard, first))
583 for c in bad_chars:
583 for c in bad_chars:
584 if c in guard:
584 if c in guard:
585 return _('invalid character in guard %r: %r') % (guard, c)
585 return _('invalid character in guard %r: %r') % (guard, c)
586
586
587 def setactive(self, guards):
587 def setactive(self, guards):
588 for guard in guards:
588 for guard in guards:
589 bad = self.checkguard(guard)
589 bad = self.checkguard(guard)
590 if bad:
590 if bad:
591 raise error.Abort(bad)
591 raise error.Abort(bad)
592 guards = sorted(set(guards))
592 guards = sorted(set(guards))
593 self.ui.debug('active guards: %s\n' % ' '.join(guards))
593 self.ui.debug('active guards: %s\n' % ' '.join(guards))
594 self.activeguards = guards
594 self.activeguards = guards
595 self.guardsdirty = True
595 self.guardsdirty = True
596
596
597 def active(self):
597 def active(self):
598 if self.activeguards is None:
598 if self.activeguards is None:
599 self.activeguards = []
599 self.activeguards = []
600 try:
600 try:
601 guards = self.opener.read(self.guardspath).split()
601 guards = self.opener.read(self.guardspath).split()
602 except IOError as err:
602 except IOError as err:
603 if err.errno != errno.ENOENT:
603 if err.errno != errno.ENOENT:
604 raise
604 raise
605 guards = []
605 guards = []
606 for i, guard in enumerate(guards):
606 for i, guard in enumerate(guards):
607 bad = self.checkguard(guard)
607 bad = self.checkguard(guard)
608 if bad:
608 if bad:
609 self.ui.warn('%s:%d: %s\n' %
609 self.ui.warn('%s:%d: %s\n' %
610 (self.join(self.guardspath), i + 1, bad))
610 (self.join(self.guardspath), i + 1, bad))
611 else:
611 else:
612 self.activeguards.append(guard)
612 self.activeguards.append(guard)
613 return self.activeguards
613 return self.activeguards
614
614
615 def setguards(self, idx, guards):
615 def setguards(self, idx, guards):
616 for g in guards:
616 for g in guards:
617 if len(g) < 2:
617 if len(g) < 2:
618 raise error.Abort(_('guard %r too short') % g)
618 raise error.Abort(_('guard %r too short') % g)
619 if g[0] not in '-+':
619 if g[0] not in '-+':
620 raise error.Abort(_('guard %r starts with invalid char') % g)
620 raise error.Abort(_('guard %r starts with invalid char') % g)
621 bad = self.checkguard(g[1:])
621 bad = self.checkguard(g[1:])
622 if bad:
622 if bad:
623 raise error.Abort(bad)
623 raise error.Abort(bad)
624 drop = self.guard_re.sub('', self.fullseries[idx])
624 drop = self.guard_re.sub('', self.fullseries[idx])
625 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
625 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
626 self.parseseries()
626 self.parseseries()
627 self.seriesdirty = True
627 self.seriesdirty = True
628
628
629 def pushable(self, idx):
629 def pushable(self, idx):
630 if isinstance(idx, str):
630 if isinstance(idx, str):
631 idx = self.series.index(idx)
631 idx = self.series.index(idx)
632 patchguards = self.seriesguards[idx]
632 patchguards = self.seriesguards[idx]
633 if not patchguards:
633 if not patchguards:
634 return True, None
634 return True, None
635 guards = self.active()
635 guards = self.active()
636 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
636 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
637 if exactneg:
637 if exactneg:
638 return False, repr(exactneg[0])
638 return False, repr(exactneg[0])
639 pos = [g for g in patchguards if g[0] == '+']
639 pos = [g for g in patchguards if g[0] == '+']
640 exactpos = [g for g in pos if g[1:] in guards]
640 exactpos = [g for g in pos if g[1:] in guards]
641 if pos:
641 if pos:
642 if exactpos:
642 if exactpos:
643 return True, repr(exactpos[0])
643 return True, repr(exactpos[0])
644 return False, ' '.join(map(repr, pos))
644 return False, ' '.join(map(repr, pos))
645 return True, ''
645 return True, ''
646
646
647 def explainpushable(self, idx, all_patches=False):
647 def explainpushable(self, idx, all_patches=False):
648 if all_patches:
648 if all_patches:
649 write = self.ui.write
649 write = self.ui.write
650 else:
650 else:
651 write = self.ui.warn
651 write = self.ui.warn
652
652
653 if all_patches or self.ui.verbose:
653 if all_patches or self.ui.verbose:
654 if isinstance(idx, str):
654 if isinstance(idx, str):
655 idx = self.series.index(idx)
655 idx = self.series.index(idx)
656 pushable, why = self.pushable(idx)
656 pushable, why = self.pushable(idx)
657 if all_patches and pushable:
657 if all_patches and pushable:
658 if why is None:
658 if why is None:
659 write(_('allowing %s - no guards in effect\n') %
659 write(_('allowing %s - no guards in effect\n') %
660 self.series[idx])
660 self.series[idx])
661 else:
661 else:
662 if not why:
662 if not why:
663 write(_('allowing %s - no matching negative guards\n') %
663 write(_('allowing %s - no matching negative guards\n') %
664 self.series[idx])
664 self.series[idx])
665 else:
665 else:
666 write(_('allowing %s - guarded by %s\n') %
666 write(_('allowing %s - guarded by %s\n') %
667 (self.series[idx], why))
667 (self.series[idx], why))
668 if not pushable:
668 if not pushable:
669 if why:
669 if why:
670 write(_('skipping %s - guarded by %s\n') %
670 write(_('skipping %s - guarded by %s\n') %
671 (self.series[idx], why))
671 (self.series[idx], why))
672 else:
672 else:
673 write(_('skipping %s - no matching guards\n') %
673 write(_('skipping %s - no matching guards\n') %
674 self.series[idx])
674 self.series[idx])
675
675
676 def savedirty(self):
676 def savedirty(self):
677 def writelist(items, path):
677 def writelist(items, path):
678 fp = self.opener(path, 'w')
678 fp = self.opener(path, 'w')
679 for i in items:
679 for i in items:
680 fp.write("%s\n" % i)
680 fp.write("%s\n" % i)
681 fp.close()
681 fp.close()
682 if self.applieddirty:
682 if self.applieddirty:
683 writelist(map(str, self.applied), self.statuspath)
683 writelist(map(str, self.applied), self.statuspath)
684 self.applieddirty = False
684 self.applieddirty = False
685 if self.seriesdirty:
685 if self.seriesdirty:
686 writelist(self.fullseries, self.seriespath)
686 writelist(self.fullseries, self.seriespath)
687 self.seriesdirty = False
687 self.seriesdirty = False
688 if self.guardsdirty:
688 if self.guardsdirty:
689 writelist(self.activeguards, self.guardspath)
689 writelist(self.activeguards, self.guardspath)
690 self.guardsdirty = False
690 self.guardsdirty = False
691 if self.added:
691 if self.added:
692 qrepo = self.qrepo()
692 qrepo = self.qrepo()
693 if qrepo:
693 if qrepo:
694 qrepo[None].add(f for f in self.added if f not in qrepo[None])
694 qrepo[None].add(f for f in self.added if f not in qrepo[None])
695 self.added = []
695 self.added = []
696
696
697 def removeundo(self, repo):
697 def removeundo(self, repo):
698 undo = repo.sjoin('undo')
698 undo = repo.sjoin('undo')
699 if not os.path.exists(undo):
699 if not os.path.exists(undo):
700 return
700 return
701 try:
701 try:
702 os.unlink(undo)
702 os.unlink(undo)
703 except OSError as inst:
703 except OSError as inst:
704 self.ui.warn(_('error removing undo: %s\n') % str(inst))
704 self.ui.warn(_('error removing undo: %s\n') % str(inst))
705
705
706 def backup(self, repo, files, copy=False):
706 def backup(self, repo, files, copy=False):
707 # backup local changes in --force case
707 # backup local changes in --force case
708 for f in sorted(files):
708 for f in sorted(files):
709 absf = repo.wjoin(f)
709 absf = repo.wjoin(f)
710 if os.path.lexists(absf):
710 if os.path.lexists(absf):
711 self.ui.note(_('saving current version of %s as %s\n') %
711 self.ui.note(_('saving current version of %s as %s\n') %
712 (f, scmutil.origpath(self.ui, repo, f)))
712 (f, scmutil.origpath(self.ui, repo, f)))
713
713
714 absorig = scmutil.origpath(self.ui, repo, absf)
714 absorig = scmutil.origpath(self.ui, repo, absf)
715 if copy:
715 if copy:
716 util.copyfile(absf, absorig)
716 util.copyfile(absf, absorig)
717 else:
717 else:
718 util.rename(absf, absorig)
718 util.rename(absf, absorig)
719
719
720 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
720 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
721 fp=None, changes=None, opts={}):
721 fp=None, changes=None, opts={}):
722 stat = opts.get('stat')
722 stat = opts.get('stat')
723 m = scmutil.match(repo[node1], files, opts)
723 m = scmutil.match(repo[node1], files, opts)
724 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
724 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
725 changes, stat, fp)
725 changes, stat, fp)
726
726
727 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
727 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
728 # first try just applying the patch
728 # first try just applying the patch
729 (err, n) = self.apply(repo, [patch], update_status=False,
729 (err, n) = self.apply(repo, [patch], update_status=False,
730 strict=True, merge=rev)
730 strict=True, merge=rev)
731
731
732 if err == 0:
732 if err == 0:
733 return (err, n)
733 return (err, n)
734
734
735 if n is None:
735 if n is None:
736 raise error.Abort(_("apply failed for patch %s") % patch)
736 raise error.Abort(_("apply failed for patch %s") % patch)
737
737
738 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
738 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
739
739
740 # apply failed, strip away that rev and merge.
740 # apply failed, strip away that rev and merge.
741 hg.clean(repo, head)
741 hg.clean(repo, head)
742 strip(self.ui, repo, [n], update=False, backup=False)
742 strip(self.ui, repo, [n], update=False, backup=False)
743
743
744 ctx = repo[rev]
744 ctx = repo[rev]
745 ret = hg.merge(repo, rev)
745 ret = hg.merge(repo, rev)
746 if ret:
746 if ret:
747 raise error.Abort(_("update returned %d") % ret)
747 raise error.Abort(_("update returned %d") % ret)
748 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
748 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
749 if n is None:
749 if n is None:
750 raise error.Abort(_("repo commit failed"))
750 raise error.Abort(_("repo commit failed"))
751 try:
751 try:
752 ph = patchheader(mergeq.join(patch), self.plainmode)
752 ph = patchheader(mergeq.join(patch), self.plainmode)
753 except Exception:
753 except Exception:
754 raise error.Abort(_("unable to read %s") % patch)
754 raise error.Abort(_("unable to read %s") % patch)
755
755
756 diffopts = self.patchopts(diffopts, patch)
756 diffopts = self.patchopts(diffopts, patch)
757 patchf = self.opener(patch, "w")
757 patchf = self.opener(patch, "w")
758 comments = str(ph)
758 comments = str(ph)
759 if comments:
759 if comments:
760 patchf.write(comments)
760 patchf.write(comments)
761 self.printdiff(repo, diffopts, head, n, fp=patchf)
761 self.printdiff(repo, diffopts, head, n, fp=patchf)
762 patchf.close()
762 patchf.close()
763 self.removeundo(repo)
763 self.removeundo(repo)
764 return (0, n)
764 return (0, n)
765
765
766 def qparents(self, repo, rev=None):
766 def qparents(self, repo, rev=None):
767 """return the mq handled parent or p1
767 """return the mq handled parent or p1
768
768
769 In some case where mq get himself in being the parent of a merge the
769 In some case where mq get himself in being the parent of a merge the
770 appropriate parent may be p2.
770 appropriate parent may be p2.
771 (eg: an in progress merge started with mq disabled)
771 (eg: an in progress merge started with mq disabled)
772
772
773 If no parent are managed by mq, p1 is returned.
773 If no parent are managed by mq, p1 is returned.
774 """
774 """
775 if rev is None:
775 if rev is None:
776 (p1, p2) = repo.dirstate.parents()
776 (p1, p2) = repo.dirstate.parents()
777 if p2 == nullid:
777 if p2 == nullid:
778 return p1
778 return p1
779 if not self.applied:
779 if not self.applied:
780 return None
780 return None
781 return self.applied[-1].node
781 return self.applied[-1].node
782 p1, p2 = repo.changelog.parents(rev)
782 p1, p2 = repo.changelog.parents(rev)
783 if p2 != nullid and p2 in [x.node for x in self.applied]:
783 if p2 != nullid and p2 in [x.node for x in self.applied]:
784 return p2
784 return p2
785 return p1
785 return p1
786
786
787 def mergepatch(self, repo, mergeq, series, diffopts):
787 def mergepatch(self, repo, mergeq, series, diffopts):
788 if not self.applied:
788 if not self.applied:
789 # each of the patches merged in will have two parents. This
789 # each of the patches merged in will have two parents. This
790 # can confuse the qrefresh, qdiff, and strip code because it
790 # can confuse the qrefresh, qdiff, and strip code because it
791 # needs to know which parent is actually in the patch queue.
791 # needs to know which parent is actually in the patch queue.
792 # so, we insert a merge marker with only one parent. This way
792 # so, we insert a merge marker with only one parent. This way
793 # the first patch in the queue is never a merge patch
793 # the first patch in the queue is never a merge patch
794 #
794 #
795 pname = ".hg.patches.merge.marker"
795 pname = ".hg.patches.merge.marker"
796 n = newcommit(repo, None, '[mq]: merge marker', force=True)
796 n = newcommit(repo, None, '[mq]: merge marker', force=True)
797 self.removeundo(repo)
797 self.removeundo(repo)
798 self.applied.append(statusentry(n, pname))
798 self.applied.append(statusentry(n, pname))
799 self.applieddirty = True
799 self.applieddirty = True
800
800
801 head = self.qparents(repo)
801 head = self.qparents(repo)
802
802
803 for patch in series:
803 for patch in series:
804 patch = mergeq.lookup(patch, strict=True)
804 patch = mergeq.lookup(patch, strict=True)
805 if not patch:
805 if not patch:
806 self.ui.warn(_("patch %s does not exist\n") % patch)
806 self.ui.warn(_("patch %s does not exist\n") % patch)
807 return (1, None)
807 return (1, None)
808 pushable, reason = self.pushable(patch)
808 pushable, reason = self.pushable(patch)
809 if not pushable:
809 if not pushable:
810 self.explainpushable(patch, all_patches=True)
810 self.explainpushable(patch, all_patches=True)
811 continue
811 continue
812 info = mergeq.isapplied(patch)
812 info = mergeq.isapplied(patch)
813 if not info:
813 if not info:
814 self.ui.warn(_("patch %s is not applied\n") % patch)
814 self.ui.warn(_("patch %s is not applied\n") % patch)
815 return (1, None)
815 return (1, None)
816 rev = info[1]
816 rev = info[1]
817 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
817 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
818 if head:
818 if head:
819 self.applied.append(statusentry(head, patch))
819 self.applied.append(statusentry(head, patch))
820 self.applieddirty = True
820 self.applieddirty = True
821 if err:
821 if err:
822 return (err, head)
822 return (err, head)
823 self.savedirty()
823 self.savedirty()
824 return (0, head)
824 return (0, head)
825
825
826 def patch(self, repo, patchfile):
826 def patch(self, repo, patchfile):
827 '''Apply patchfile to the working directory.
827 '''Apply patchfile to the working directory.
828 patchfile: name of patch file'''
828 patchfile: name of patch file'''
829 files = set()
829 files = set()
830 try:
830 try:
831 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
831 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
832 files=files, eolmode=None)
832 files=files, eolmode=None)
833 return (True, list(files), fuzz)
833 return (True, list(files), fuzz)
834 except Exception as inst:
834 except Exception as inst:
835 self.ui.note(str(inst) + '\n')
835 self.ui.note(str(inst) + '\n')
836 if not self.ui.verbose:
836 if not self.ui.verbose:
837 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
837 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
838 self.ui.traceback()
838 self.ui.traceback()
839 return (False, list(files), False)
839 return (False, list(files), False)
840
840
841 def apply(self, repo, series, list=False, update_status=True,
841 def apply(self, repo, series, list=False, update_status=True,
842 strict=False, patchdir=None, merge=None, all_files=None,
842 strict=False, patchdir=None, merge=None, all_files=None,
843 tobackup=None, keepchanges=False):
843 tobackup=None, keepchanges=False):
844 wlock = lock = tr = None
844 wlock = lock = tr = None
845 try:
845 try:
846 wlock = repo.wlock()
846 wlock = repo.wlock()
847 lock = repo.lock()
847 lock = repo.lock()
848 tr = repo.transaction("qpush")
848 tr = repo.transaction("qpush")
849 try:
849 try:
850 ret = self._apply(repo, series, list, update_status,
850 ret = self._apply(repo, series, list, update_status,
851 strict, patchdir, merge, all_files=all_files,
851 strict, patchdir, merge, all_files=all_files,
852 tobackup=tobackup, keepchanges=keepchanges)
852 tobackup=tobackup, keepchanges=keepchanges)
853 tr.close()
853 tr.close()
854 self.savedirty()
854 self.savedirty()
855 return ret
855 return ret
856 except AbortNoCleanup:
856 except AbortNoCleanup:
857 tr.close()
857 tr.close()
858 self.savedirty()
858 self.savedirty()
859 raise
859 raise
860 except: # re-raises
860 except: # re-raises
861 try:
861 try:
862 tr.abort()
862 tr.abort()
863 finally:
863 finally:
864 self.invalidate()
864 self.invalidate()
865 raise
865 raise
866 finally:
866 finally:
867 release(tr, lock, wlock)
867 release(tr, lock, wlock)
868 self.removeundo(repo)
868 self.removeundo(repo)
869
869
870 def _apply(self, repo, series, list=False, update_status=True,
870 def _apply(self, repo, series, list=False, update_status=True,
871 strict=False, patchdir=None, merge=None, all_files=None,
871 strict=False, patchdir=None, merge=None, all_files=None,
872 tobackup=None, keepchanges=False):
872 tobackup=None, keepchanges=False):
873 """returns (error, hash)
873 """returns (error, hash)
874
874
875 error = 1 for unable to read, 2 for patch failed, 3 for patch
875 error = 1 for unable to read, 2 for patch failed, 3 for patch
876 fuzz. tobackup is None or a set of files to backup before they
876 fuzz. tobackup is None or a set of files to backup before they
877 are modified by a patch.
877 are modified by a patch.
878 """
878 """
879 # TODO unify with commands.py
879 # TODO unify with commands.py
880 if not patchdir:
880 if not patchdir:
881 patchdir = self.path
881 patchdir = self.path
882 err = 0
882 err = 0
883 n = None
883 n = None
884 for patchname in series:
884 for patchname in series:
885 pushable, reason = self.pushable(patchname)
885 pushable, reason = self.pushable(patchname)
886 if not pushable:
886 if not pushable:
887 self.explainpushable(patchname, all_patches=True)
887 self.explainpushable(patchname, all_patches=True)
888 continue
888 continue
889 self.ui.status(_("applying %s\n") % patchname)
889 self.ui.status(_("applying %s\n") % patchname)
890 pf = os.path.join(patchdir, patchname)
890 pf = os.path.join(patchdir, patchname)
891
891
892 try:
892 try:
893 ph = patchheader(self.join(patchname), self.plainmode)
893 ph = patchheader(self.join(patchname), self.plainmode)
894 except IOError:
894 except IOError:
895 self.ui.warn(_("unable to read %s\n") % patchname)
895 self.ui.warn(_("unable to read %s\n") % patchname)
896 err = 1
896 err = 1
897 break
897 break
898
898
899 message = ph.message
899 message = ph.message
900 if not message:
900 if not message:
901 # The commit message should not be translated
901 # The commit message should not be translated
902 message = "imported patch %s\n" % patchname
902 message = "imported patch %s\n" % patchname
903 else:
903 else:
904 if list:
904 if list:
905 # The commit message should not be translated
905 # The commit message should not be translated
906 message.append("\nimported patch %s" % patchname)
906 message.append("\nimported patch %s" % patchname)
907 message = '\n'.join(message)
907 message = '\n'.join(message)
908
908
909 if ph.haspatch:
909 if ph.haspatch:
910 if tobackup:
910 if tobackup:
911 touched = patchmod.changedfiles(self.ui, repo, pf)
911 touched = patchmod.changedfiles(self.ui, repo, pf)
912 touched = set(touched) & tobackup
912 touched = set(touched) & tobackup
913 if touched and keepchanges:
913 if touched and keepchanges:
914 raise AbortNoCleanup(
914 raise AbortNoCleanup(
915 _("conflicting local changes found"),
915 _("conflicting local changes found"),
916 hint=_("did you forget to qrefresh?"))
916 hint=_("did you forget to qrefresh?"))
917 self.backup(repo, touched, copy=True)
917 self.backup(repo, touched, copy=True)
918 tobackup = tobackup - touched
918 tobackup = tobackup - touched
919 (patcherr, files, fuzz) = self.patch(repo, pf)
919 (patcherr, files, fuzz) = self.patch(repo, pf)
920 if all_files is not None:
920 if all_files is not None:
921 all_files.update(files)
921 all_files.update(files)
922 patcherr = not patcherr
922 patcherr = not patcherr
923 else:
923 else:
924 self.ui.warn(_("patch %s is empty\n") % patchname)
924 self.ui.warn(_("patch %s is empty\n") % patchname)
925 patcherr, files, fuzz = 0, [], 0
925 patcherr, files, fuzz = 0, [], 0
926
926
927 if merge and files:
927 if merge and files:
928 # Mark as removed/merged and update dirstate parent info
928 # Mark as removed/merged and update dirstate parent info
929 removed = []
929 removed = []
930 merged = []
930 merged = []
931 for f in files:
931 for f in files:
932 if os.path.lexists(repo.wjoin(f)):
932 if os.path.lexists(repo.wjoin(f)):
933 merged.append(f)
933 merged.append(f)
934 else:
934 else:
935 removed.append(f)
935 removed.append(f)
936 repo.dirstate.beginparentchange()
936 repo.dirstate.beginparentchange()
937 for f in removed:
937 for f in removed:
938 repo.dirstate.remove(f)
938 repo.dirstate.remove(f)
939 for f in merged:
939 for f in merged:
940 repo.dirstate.merge(f)
940 repo.dirstate.merge(f)
941 p1, p2 = repo.dirstate.parents()
941 p1, p2 = repo.dirstate.parents()
942 repo.setparents(p1, merge)
942 repo.setparents(p1, merge)
943 repo.dirstate.endparentchange()
943 repo.dirstate.endparentchange()
944
944
945 if all_files and '.hgsubstate' in all_files:
945 if all_files and '.hgsubstate' in all_files:
946 wctx = repo[None]
946 wctx = repo[None]
947 pctx = repo['.']
947 pctx = repo['.']
948 overwrite = False
948 overwrite = False
949 mergedsubstate = subrepo.submerge(repo, pctx, wctx, wctx,
949 mergedsubstate = subrepo.submerge(repo, pctx, wctx, wctx,
950 overwrite)
950 overwrite)
951 files += mergedsubstate.keys()
951 files += mergedsubstate.keys()
952
952
953 match = scmutil.matchfiles(repo, files or [])
953 match = scmutil.matchfiles(repo, files or [])
954 oldtip = repo['tip']
954 oldtip = repo['tip']
955 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
955 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
956 force=True)
956 force=True)
957 if repo['tip'] == oldtip:
957 if repo['tip'] == oldtip:
958 raise error.Abort(_("qpush exactly duplicates child changeset"))
958 raise error.Abort(_("qpush exactly duplicates child changeset"))
959 if n is None:
959 if n is None:
960 raise error.Abort(_("repository commit failed"))
960 raise error.Abort(_("repository commit failed"))
961
961
962 if update_status:
962 if update_status:
963 self.applied.append(statusentry(n, patchname))
963 self.applied.append(statusentry(n, patchname))
964
964
965 if patcherr:
965 if patcherr:
966 self.ui.warn(_("patch failed, rejects left in working "
966 self.ui.warn(_("patch failed, rejects left in working "
967 "directory\n"))
967 "directory\n"))
968 err = 2
968 err = 2
969 break
969 break
970
970
971 if fuzz and strict:
971 if fuzz and strict:
972 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
972 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
973 err = 3
973 err = 3
974 break
974 break
975 return (err, n)
975 return (err, n)
976
976
977 def _cleanup(self, patches, numrevs, keep=False):
977 def _cleanup(self, patches, numrevs, keep=False):
978 if not keep:
978 if not keep:
979 r = self.qrepo()
979 r = self.qrepo()
980 if r:
980 if r:
981 r[None].forget(patches)
981 r[None].forget(patches)
982 for p in patches:
982 for p in patches:
983 try:
983 try:
984 os.unlink(self.join(p))
984 os.unlink(self.join(p))
985 except OSError as inst:
985 except OSError as inst:
986 if inst.errno != errno.ENOENT:
986 if inst.errno != errno.ENOENT:
987 raise
987 raise
988
988
989 qfinished = []
989 qfinished = []
990 if numrevs:
990 if numrevs:
991 qfinished = self.applied[:numrevs]
991 qfinished = self.applied[:numrevs]
992 del self.applied[:numrevs]
992 del self.applied[:numrevs]
993 self.applieddirty = True
993 self.applieddirty = True
994
994
995 unknown = []
995 unknown = []
996
996
997 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
997 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
998 reverse=True):
998 reverse=True):
999 if i is not None:
999 if i is not None:
1000 del self.fullseries[i]
1000 del self.fullseries[i]
1001 else:
1001 else:
1002 unknown.append(p)
1002 unknown.append(p)
1003
1003
1004 if unknown:
1004 if unknown:
1005 if numrevs:
1005 if numrevs:
1006 rev = dict((entry.name, entry.node) for entry in qfinished)
1006 rev = dict((entry.name, entry.node) for entry in qfinished)
1007 for p in unknown:
1007 for p in unknown:
1008 msg = _('revision %s refers to unknown patches: %s\n')
1008 msg = _('revision %s refers to unknown patches: %s\n')
1009 self.ui.warn(msg % (short(rev[p]), p))
1009 self.ui.warn(msg % (short(rev[p]), p))
1010 else:
1010 else:
1011 msg = _('unknown patches: %s\n')
1011 msg = _('unknown patches: %s\n')
1012 raise error.Abort(''.join(msg % p for p in unknown))
1012 raise error.Abort(''.join(msg % p for p in unknown))
1013
1013
1014 self.parseseries()
1014 self.parseseries()
1015 self.seriesdirty = True
1015 self.seriesdirty = True
1016 return [entry.node for entry in qfinished]
1016 return [entry.node for entry in qfinished]
1017
1017
1018 def _revpatches(self, repo, revs):
1018 def _revpatches(self, repo, revs):
1019 firstrev = repo[self.applied[0].node].rev()
1019 firstrev = repo[self.applied[0].node].rev()
1020 patches = []
1020 patches = []
1021 for i, rev in enumerate(revs):
1021 for i, rev in enumerate(revs):
1022
1022
1023 if rev < firstrev:
1023 if rev < firstrev:
1024 raise error.Abort(_('revision %d is not managed') % rev)
1024 raise error.Abort(_('revision %d is not managed') % rev)
1025
1025
1026 ctx = repo[rev]
1026 ctx = repo[rev]
1027 base = self.applied[i].node
1027 base = self.applied[i].node
1028 if ctx.node() != base:
1028 if ctx.node() != base:
1029 msg = _('cannot delete revision %d above applied patches')
1029 msg = _('cannot delete revision %d above applied patches')
1030 raise error.Abort(msg % rev)
1030 raise error.Abort(msg % rev)
1031
1031
1032 patch = self.applied[i].name
1032 patch = self.applied[i].name
1033 for fmt in ('[mq]: %s', 'imported patch %s'):
1033 for fmt in ('[mq]: %s', 'imported patch %s'):
1034 if ctx.description() == fmt % patch:
1034 if ctx.description() == fmt % patch:
1035 msg = _('patch %s finalized without changeset message\n')
1035 msg = _('patch %s finalized without changeset message\n')
1036 repo.ui.status(msg % patch)
1036 repo.ui.status(msg % patch)
1037 break
1037 break
1038
1038
1039 patches.append(patch)
1039 patches.append(patch)
1040 return patches
1040 return patches
1041
1041
1042 def finish(self, repo, revs):
1042 def finish(self, repo, revs):
1043 # Manually trigger phase computation to ensure phasedefaults is
1043 # Manually trigger phase computation to ensure phasedefaults is
1044 # executed before we remove the patches.
1044 # executed before we remove the patches.
1045 repo._phasecache
1045 repo._phasecache
1046 patches = self._revpatches(repo, sorted(revs))
1046 patches = self._revpatches(repo, sorted(revs))
1047 qfinished = self._cleanup(patches, len(patches))
1047 qfinished = self._cleanup(patches, len(patches))
1048 if qfinished and repo.ui.configbool('mq', 'secret', False):
1048 if qfinished and repo.ui.configbool('mq', 'secret', False):
1049 # only use this logic when the secret option is added
1049 # only use this logic when the secret option is added
1050 oldqbase = repo[qfinished[0]]
1050 oldqbase = repo[qfinished[0]]
1051 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
1051 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
1052 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
1052 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
1053 with repo.transaction('qfinish') as tr:
1053 with repo.transaction('qfinish') as tr:
1054 phases.advanceboundary(repo, tr, tphase, qfinished)
1054 phases.advanceboundary(repo, tr, tphase, qfinished)
1055
1055
1056 def delete(self, repo, patches, opts):
1056 def delete(self, repo, patches, opts):
1057 if not patches and not opts.get('rev'):
1057 if not patches and not opts.get('rev'):
1058 raise error.Abort(_('qdelete requires at least one revision or '
1058 raise error.Abort(_('qdelete requires at least one revision or '
1059 'patch name'))
1059 'patch name'))
1060
1060
1061 realpatches = []
1061 realpatches = []
1062 for patch in patches:
1062 for patch in patches:
1063 patch = self.lookup(patch, strict=True)
1063 patch = self.lookup(patch, strict=True)
1064 info = self.isapplied(patch)
1064 info = self.isapplied(patch)
1065 if info:
1065 if info:
1066 raise error.Abort(_("cannot delete applied patch %s") % patch)
1066 raise error.Abort(_("cannot delete applied patch %s") % patch)
1067 if patch not in self.series:
1067 if patch not in self.series:
1068 raise error.Abort(_("patch %s not in series file") % patch)
1068 raise error.Abort(_("patch %s not in series file") % patch)
1069 if patch not in realpatches:
1069 if patch not in realpatches:
1070 realpatches.append(patch)
1070 realpatches.append(patch)
1071
1071
1072 numrevs = 0
1072 numrevs = 0
1073 if opts.get('rev'):
1073 if opts.get('rev'):
1074 if not self.applied:
1074 if not self.applied:
1075 raise error.Abort(_('no patches applied'))
1075 raise error.Abort(_('no patches applied'))
1076 revs = scmutil.revrange(repo, opts.get('rev'))
1076 revs = scmutil.revrange(repo, opts.get('rev'))
1077 revs.sort()
1077 revs.sort()
1078 revpatches = self._revpatches(repo, revs)
1078 revpatches = self._revpatches(repo, revs)
1079 realpatches += revpatches
1079 realpatches += revpatches
1080 numrevs = len(revpatches)
1080 numrevs = len(revpatches)
1081
1081
1082 self._cleanup(realpatches, numrevs, opts.get('keep'))
1082 self._cleanup(realpatches, numrevs, opts.get('keep'))
1083
1083
1084 def checktoppatch(self, repo):
1084 def checktoppatch(self, repo):
1085 '''check that working directory is at qtip'''
1085 '''check that working directory is at qtip'''
1086 if self.applied:
1086 if self.applied:
1087 top = self.applied[-1].node
1087 top = self.applied[-1].node
1088 patch = self.applied[-1].name
1088 patch = self.applied[-1].name
1089 if repo.dirstate.p1() != top:
1089 if repo.dirstate.p1() != top:
1090 raise error.Abort(_("working directory revision is not qtip"))
1090 raise error.Abort(_("working directory revision is not qtip"))
1091 return top, patch
1091 return top, patch
1092 return None, None
1092 return None, None
1093
1093
1094 def putsubstate2changes(self, substatestate, changes):
1094 def putsubstate2changes(self, substatestate, changes):
1095 for files in changes[:3]:
1095 for files in changes[:3]:
1096 if '.hgsubstate' in files:
1096 if '.hgsubstate' in files:
1097 return # already listed up
1097 return # already listed up
1098 # not yet listed up
1098 # not yet listed up
1099 if substatestate in 'a?':
1099 if substatestate in 'a?':
1100 changes[1].append('.hgsubstate')
1100 changes[1].append('.hgsubstate')
1101 elif substatestate in 'r':
1101 elif substatestate in 'r':
1102 changes[2].append('.hgsubstate')
1102 changes[2].append('.hgsubstate')
1103 else: # modified
1103 else: # modified
1104 changes[0].append('.hgsubstate')
1104 changes[0].append('.hgsubstate')
1105
1105
1106 def checklocalchanges(self, repo, force=False, refresh=True):
1106 def checklocalchanges(self, repo, force=False, refresh=True):
1107 excsuffix = ''
1107 excsuffix = ''
1108 if refresh:
1108 if refresh:
1109 excsuffix = ', qrefresh first'
1109 excsuffix = ', qrefresh first'
1110 # plain versions for i18n tool to detect them
1110 # plain versions for i18n tool to detect them
1111 _("local changes found, qrefresh first")
1111 _("local changes found, qrefresh first")
1112 _("local changed subrepos found, qrefresh first")
1112 _("local changed subrepos found, qrefresh first")
1113 return checklocalchanges(repo, force, excsuffix)
1113 return checklocalchanges(repo, force, excsuffix)
1114
1114
1115 _reserved = ('series', 'status', 'guards', '.', '..')
1115 _reserved = ('series', 'status', 'guards', '.', '..')
1116 def checkreservedname(self, name):
1116 def checkreservedname(self, name):
1117 if name in self._reserved:
1117 if name in self._reserved:
1118 raise error.Abort(_('"%s" cannot be used as the name of a patch')
1118 raise error.Abort(_('"%s" cannot be used as the name of a patch')
1119 % name)
1119 % name)
1120 for prefix in ('.hg', '.mq'):
1120 for prefix in ('.hg', '.mq'):
1121 if name.startswith(prefix):
1121 if name.startswith(prefix):
1122 raise error.Abort(_('patch name cannot begin with "%s"')
1122 raise error.Abort(_('patch name cannot begin with "%s"')
1123 % prefix)
1123 % prefix)
1124 for c in ('#', ':', '\r', '\n'):
1124 for c in ('#', ':', '\r', '\n'):
1125 if c in name:
1125 if c in name:
1126 raise error.Abort(_('%r cannot be used in the name of a patch')
1126 raise error.Abort(_('%r cannot be used in the name of a patch')
1127 % c)
1127 % c)
1128
1128
1129 def checkpatchname(self, name, force=False):
1129 def checkpatchname(self, name, force=False):
1130 self.checkreservedname(name)
1130 self.checkreservedname(name)
1131 if not force and os.path.exists(self.join(name)):
1131 if not force and os.path.exists(self.join(name)):
1132 if os.path.isdir(self.join(name)):
1132 if os.path.isdir(self.join(name)):
1133 raise error.Abort(_('"%s" already exists as a directory')
1133 raise error.Abort(_('"%s" already exists as a directory')
1134 % name)
1134 % name)
1135 else:
1135 else:
1136 raise error.Abort(_('patch "%s" already exists') % name)
1136 raise error.Abort(_('patch "%s" already exists') % name)
1137
1137
1138 def makepatchname(self, title, fallbackname):
1138 def makepatchname(self, title, fallbackname):
1139 """Return a suitable filename for title, adding a suffix to make
1139 """Return a suitable filename for title, adding a suffix to make
1140 it unique in the existing list"""
1140 it unique in the existing list"""
1141 namebase = re.sub('[\s\W_]+', '_', title.lower()).strip('_')
1141 namebase = re.sub('[\s\W_]+', '_', title.lower()).strip('_')
1142 namebase = namebase[:75] # avoid too long name (issue5117)
1142 namebase = namebase[:75] # avoid too long name (issue5117)
1143 if namebase:
1143 if namebase:
1144 try:
1144 try:
1145 self.checkreservedname(namebase)
1145 self.checkreservedname(namebase)
1146 except error.Abort:
1146 except error.Abort:
1147 namebase = fallbackname
1147 namebase = fallbackname
1148 else:
1148 else:
1149 namebase = fallbackname
1149 namebase = fallbackname
1150 name = namebase
1150 name = namebase
1151 i = 0
1151 i = 0
1152 while True:
1152 while True:
1153 if name not in self.fullseries:
1153 if name not in self.fullseries:
1154 try:
1154 try:
1155 self.checkpatchname(name)
1155 self.checkpatchname(name)
1156 break
1156 break
1157 except error.Abort:
1157 except error.Abort:
1158 pass
1158 pass
1159 i += 1
1159 i += 1
1160 name = '%s__%s' % (namebase, i)
1160 name = '%s__%s' % (namebase, i)
1161 return name
1161 return name
1162
1162
1163 def checkkeepchanges(self, keepchanges, force):
1163 def checkkeepchanges(self, keepchanges, force):
1164 if force and keepchanges:
1164 if force and keepchanges:
1165 raise error.Abort(_('cannot use both --force and --keep-changes'))
1165 raise error.Abort(_('cannot use both --force and --keep-changes'))
1166
1166
1167 def new(self, repo, patchfn, *pats, **opts):
1167 def new(self, repo, patchfn, *pats, **opts):
1168 """options:
1168 """options:
1169 msg: a string or a no-argument function returning a string
1169 msg: a string or a no-argument function returning a string
1170 """
1170 """
1171 msg = opts.get('msg')
1171 msg = opts.get('msg')
1172 edit = opts.get('edit')
1172 edit = opts.get('edit')
1173 editform = opts.get('editform', 'mq.qnew')
1173 editform = opts.get('editform', 'mq.qnew')
1174 user = opts.get('user')
1174 user = opts.get('user')
1175 date = opts.get('date')
1175 date = opts.get('date')
1176 if date:
1176 if date:
1177 date = util.parsedate(date)
1177 date = util.parsedate(date)
1178 diffopts = self.diffopts({'git': opts.get('git')})
1178 diffopts = self.diffopts({'git': opts.get('git')})
1179 if opts.get('checkname', True):
1179 if opts.get('checkname', True):
1180 self.checkpatchname(patchfn)
1180 self.checkpatchname(patchfn)
1181 inclsubs = checksubstate(repo)
1181 inclsubs = checksubstate(repo)
1182 if inclsubs:
1182 if inclsubs:
1183 substatestate = repo.dirstate['.hgsubstate']
1183 substatestate = repo.dirstate['.hgsubstate']
1184 if opts.get('include') or opts.get('exclude') or pats:
1184 if opts.get('include') or opts.get('exclude') or pats:
1185 # detect missing files in pats
1185 # detect missing files in pats
1186 def badfn(f, msg):
1186 def badfn(f, msg):
1187 if f != '.hgsubstate': # .hgsubstate is auto-created
1187 if f != '.hgsubstate': # .hgsubstate is auto-created
1188 raise error.Abort('%s: %s' % (f, msg))
1188 raise error.Abort('%s: %s' % (f, msg))
1189 match = scmutil.match(repo[None], pats, opts, badfn=badfn)
1189 match = scmutil.match(repo[None], pats, opts, badfn=badfn)
1190 changes = repo.status(match=match)
1190 changes = repo.status(match=match)
1191 else:
1191 else:
1192 changes = self.checklocalchanges(repo, force=True)
1192 changes = self.checklocalchanges(repo, force=True)
1193 commitfiles = list(inclsubs)
1193 commitfiles = list(inclsubs)
1194 for files in changes[:3]:
1194 for files in changes[:3]:
1195 commitfiles.extend(files)
1195 commitfiles.extend(files)
1196 match = scmutil.matchfiles(repo, commitfiles)
1196 match = scmutil.matchfiles(repo, commitfiles)
1197 if len(repo[None].parents()) > 1:
1197 if len(repo[None].parents()) > 1:
1198 raise error.Abort(_('cannot manage merge changesets'))
1198 raise error.Abort(_('cannot manage merge changesets'))
1199 self.checktoppatch(repo)
1199 self.checktoppatch(repo)
1200 insert = self.fullseriesend()
1200 insert = self.fullseriesend()
1201 with repo.wlock():
1201 with repo.wlock():
1202 try:
1202 try:
1203 # if patch file write fails, abort early
1203 # if patch file write fails, abort early
1204 p = self.opener(patchfn, "w")
1204 p = self.opener(patchfn, "w")
1205 except IOError as e:
1205 except IOError as e:
1206 raise error.Abort(_('cannot write patch "%s": %s')
1206 raise error.Abort(_('cannot write patch "%s": %s')
1207 % (patchfn, e.strerror))
1207 % (patchfn, e.strerror))
1208 try:
1208 try:
1209 defaultmsg = "[mq]: %s" % patchfn
1209 defaultmsg = "[mq]: %s" % patchfn
1210 editor = cmdutil.getcommiteditor(editform=editform)
1210 editor = cmdutil.getcommiteditor(editform=editform)
1211 if edit:
1211 if edit:
1212 def finishdesc(desc):
1212 def finishdesc(desc):
1213 if desc.rstrip():
1213 if desc.rstrip():
1214 return desc
1214 return desc
1215 else:
1215 else:
1216 return defaultmsg
1216 return defaultmsg
1217 # i18n: this message is shown in editor with "HG: " prefix
1217 # i18n: this message is shown in editor with "HG: " prefix
1218 extramsg = _('Leave message empty to use default message.')
1218 extramsg = _('Leave message empty to use default message.')
1219 editor = cmdutil.getcommiteditor(finishdesc=finishdesc,
1219 editor = cmdutil.getcommiteditor(finishdesc=finishdesc,
1220 extramsg=extramsg,
1220 extramsg=extramsg,
1221 editform=editform)
1221 editform=editform)
1222 commitmsg = msg
1222 commitmsg = msg
1223 else:
1223 else:
1224 commitmsg = msg or defaultmsg
1224 commitmsg = msg or defaultmsg
1225
1225
1226 n = newcommit(repo, None, commitmsg, user, date, match=match,
1226 n = newcommit(repo, None, commitmsg, user, date, match=match,
1227 force=True, editor=editor)
1227 force=True, editor=editor)
1228 if n is None:
1228 if n is None:
1229 raise error.Abort(_("repo commit failed"))
1229 raise error.Abort(_("repo commit failed"))
1230 try:
1230 try:
1231 self.fullseries[insert:insert] = [patchfn]
1231 self.fullseries[insert:insert] = [patchfn]
1232 self.applied.append(statusentry(n, patchfn))
1232 self.applied.append(statusentry(n, patchfn))
1233 self.parseseries()
1233 self.parseseries()
1234 self.seriesdirty = True
1234 self.seriesdirty = True
1235 self.applieddirty = True
1235 self.applieddirty = True
1236 nctx = repo[n]
1236 nctx = repo[n]
1237 ph = patchheader(self.join(patchfn), self.plainmode)
1237 ph = patchheader(self.join(patchfn), self.plainmode)
1238 if user:
1238 if user:
1239 ph.setuser(user)
1239 ph.setuser(user)
1240 if date:
1240 if date:
1241 ph.setdate('%s %s' % date)
1241 ph.setdate('%s %s' % date)
1242 ph.setparent(hex(nctx.p1().node()))
1242 ph.setparent(hex(nctx.p1().node()))
1243 msg = nctx.description().strip()
1243 msg = nctx.description().strip()
1244 if msg == defaultmsg.strip():
1244 if msg == defaultmsg.strip():
1245 msg = ''
1245 msg = ''
1246 ph.setmessage(msg)
1246 ph.setmessage(msg)
1247 p.write(str(ph))
1247 p.write(str(ph))
1248 if commitfiles:
1248 if commitfiles:
1249 parent = self.qparents(repo, n)
1249 parent = self.qparents(repo, n)
1250 if inclsubs:
1250 if inclsubs:
1251 self.putsubstate2changes(substatestate, changes)
1251 self.putsubstate2changes(substatestate, changes)
1252 chunks = patchmod.diff(repo, node1=parent, node2=n,
1252 chunks = patchmod.diff(repo, node1=parent, node2=n,
1253 changes=changes, opts=diffopts)
1253 changes=changes, opts=diffopts)
1254 for chunk in chunks:
1254 for chunk in chunks:
1255 p.write(chunk)
1255 p.write(chunk)
1256 p.close()
1256 p.close()
1257 r = self.qrepo()
1257 r = self.qrepo()
1258 if r:
1258 if r:
1259 r[None].add([patchfn])
1259 r[None].add([patchfn])
1260 except: # re-raises
1260 except: # re-raises
1261 repo.rollback()
1261 repo.rollback()
1262 raise
1262 raise
1263 except Exception:
1263 except Exception:
1264 patchpath = self.join(patchfn)
1264 patchpath = self.join(patchfn)
1265 try:
1265 try:
1266 os.unlink(patchpath)
1266 os.unlink(patchpath)
1267 except OSError:
1267 except OSError:
1268 self.ui.warn(_('error unlinking %s\n') % patchpath)
1268 self.ui.warn(_('error unlinking %s\n') % patchpath)
1269 raise
1269 raise
1270 self.removeundo(repo)
1270 self.removeundo(repo)
1271
1271
1272 def isapplied(self, patch):
1272 def isapplied(self, patch):
1273 """returns (index, rev, patch)"""
1273 """returns (index, rev, patch)"""
1274 for i, a in enumerate(self.applied):
1274 for i, a in enumerate(self.applied):
1275 if a.name == patch:
1275 if a.name == patch:
1276 return (i, a.node, a.name)
1276 return (i, a.node, a.name)
1277 return None
1277 return None
1278
1278
1279 # if the exact patch name does not exist, we try a few
1279 # if the exact patch name does not exist, we try a few
1280 # variations. If strict is passed, we try only #1
1280 # variations. If strict is passed, we try only #1
1281 #
1281 #
1282 # 1) a number (as string) to indicate an offset in the series file
1282 # 1) a number (as string) to indicate an offset in the series file
1283 # 2) a unique substring of the patch name was given
1283 # 2) a unique substring of the patch name was given
1284 # 3) patchname[-+]num to indicate an offset in the series file
1284 # 3) patchname[-+]num to indicate an offset in the series file
1285 def lookup(self, patch, strict=False):
1285 def lookup(self, patch, strict=False):
1286 def partialname(s):
1286 def partialname(s):
1287 if s in self.series:
1287 if s in self.series:
1288 return s
1288 return s
1289 matches = [x for x in self.series if s in x]
1289 matches = [x for x in self.series if s in x]
1290 if len(matches) > 1:
1290 if len(matches) > 1:
1291 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1291 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1292 for m in matches:
1292 for m in matches:
1293 self.ui.warn(' %s\n' % m)
1293 self.ui.warn(' %s\n' % m)
1294 return None
1294 return None
1295 if matches:
1295 if matches:
1296 return matches[0]
1296 return matches[0]
1297 if self.series and self.applied:
1297 if self.series and self.applied:
1298 if s == 'qtip':
1298 if s == 'qtip':
1299 return self.series[self.seriesend(True) - 1]
1299 return self.series[self.seriesend(True) - 1]
1300 if s == 'qbase':
1300 if s == 'qbase':
1301 return self.series[0]
1301 return self.series[0]
1302 return None
1302 return None
1303
1303
1304 if patch in self.series:
1304 if patch in self.series:
1305 return patch
1305 return patch
1306
1306
1307 if not os.path.isfile(self.join(patch)):
1307 if not os.path.isfile(self.join(patch)):
1308 try:
1308 try:
1309 sno = int(patch)
1309 sno = int(patch)
1310 except (ValueError, OverflowError):
1310 except (ValueError, OverflowError):
1311 pass
1311 pass
1312 else:
1312 else:
1313 if -len(self.series) <= sno < len(self.series):
1313 if -len(self.series) <= sno < len(self.series):
1314 return self.series[sno]
1314 return self.series[sno]
1315
1315
1316 if not strict:
1316 if not strict:
1317 res = partialname(patch)
1317 res = partialname(patch)
1318 if res:
1318 if res:
1319 return res
1319 return res
1320 minus = patch.rfind('-')
1320 minus = patch.rfind('-')
1321 if minus >= 0:
1321 if minus >= 0:
1322 res = partialname(patch[:minus])
1322 res = partialname(patch[:minus])
1323 if res:
1323 if res:
1324 i = self.series.index(res)
1324 i = self.series.index(res)
1325 try:
1325 try:
1326 off = int(patch[minus + 1:] or 1)
1326 off = int(patch[minus + 1:] or 1)
1327 except (ValueError, OverflowError):
1327 except (ValueError, OverflowError):
1328 pass
1328 pass
1329 else:
1329 else:
1330 if i - off >= 0:
1330 if i - off >= 0:
1331 return self.series[i - off]
1331 return self.series[i - off]
1332 plus = patch.rfind('+')
1332 plus = patch.rfind('+')
1333 if plus >= 0:
1333 if plus >= 0:
1334 res = partialname(patch[:plus])
1334 res = partialname(patch[:plus])
1335 if res:
1335 if res:
1336 i = self.series.index(res)
1336 i = self.series.index(res)
1337 try:
1337 try:
1338 off = int(patch[plus + 1:] or 1)
1338 off = int(patch[plus + 1:] or 1)
1339 except (ValueError, OverflowError):
1339 except (ValueError, OverflowError):
1340 pass
1340 pass
1341 else:
1341 else:
1342 if i + off < len(self.series):
1342 if i + off < len(self.series):
1343 return self.series[i + off]
1343 return self.series[i + off]
1344 raise error.Abort(_("patch %s not in series") % patch)
1344 raise error.Abort(_("patch %s not in series") % patch)
1345
1345
1346 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1346 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1347 all=False, move=False, exact=False, nobackup=False,
1347 all=False, move=False, exact=False, nobackup=False,
1348 keepchanges=False):
1348 keepchanges=False):
1349 self.checkkeepchanges(keepchanges, force)
1349 self.checkkeepchanges(keepchanges, force)
1350 diffopts = self.diffopts()
1350 diffopts = self.diffopts()
1351 with repo.wlock():
1351 with repo.wlock():
1352 heads = []
1352 heads = []
1353 for hs in repo.branchmap().itervalues():
1353 for hs in repo.branchmap().itervalues():
1354 heads.extend(hs)
1354 heads.extend(hs)
1355 if not heads:
1355 if not heads:
1356 heads = [nullid]
1356 heads = [nullid]
1357 if repo.dirstate.p1() not in heads and not exact:
1357 if repo.dirstate.p1() not in heads and not exact:
1358 self.ui.status(_("(working directory not at a head)\n"))
1358 self.ui.status(_("(working directory not at a head)\n"))
1359
1359
1360 if not self.series:
1360 if not self.series:
1361 self.ui.warn(_('no patches in series\n'))
1361 self.ui.warn(_('no patches in series\n'))
1362 return 0
1362 return 0
1363
1363
1364 # Suppose our series file is: A B C and the current 'top'
1364 # Suppose our series file is: A B C and the current 'top'
1365 # patch is B. qpush C should be performed (moving forward)
1365 # patch is B. qpush C should be performed (moving forward)
1366 # qpush B is a NOP (no change) qpush A is an error (can't
1366 # qpush B is a NOP (no change) qpush A is an error (can't
1367 # go backwards with qpush)
1367 # go backwards with qpush)
1368 if patch:
1368 if patch:
1369 patch = self.lookup(patch)
1369 patch = self.lookup(patch)
1370 info = self.isapplied(patch)
1370 info = self.isapplied(patch)
1371 if info and info[0] >= len(self.applied) - 1:
1371 if info and info[0] >= len(self.applied) - 1:
1372 self.ui.warn(
1372 self.ui.warn(
1373 _('qpush: %s is already at the top\n') % patch)
1373 _('qpush: %s is already at the top\n') % patch)
1374 return 0
1374 return 0
1375
1375
1376 pushable, reason = self.pushable(patch)
1376 pushable, reason = self.pushable(patch)
1377 if pushable:
1377 if pushable:
1378 if self.series.index(patch) < self.seriesend():
1378 if self.series.index(patch) < self.seriesend():
1379 raise error.Abort(
1379 raise error.Abort(
1380 _("cannot push to a previous patch: %s") % patch)
1380 _("cannot push to a previous patch: %s") % patch)
1381 else:
1381 else:
1382 if reason:
1382 if reason:
1383 reason = _('guarded by %s') % reason
1383 reason = _('guarded by %s') % reason
1384 else:
1384 else:
1385 reason = _('no matching guards')
1385 reason = _('no matching guards')
1386 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1386 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1387 return 1
1387 return 1
1388 elif all:
1388 elif all:
1389 patch = self.series[-1]
1389 patch = self.series[-1]
1390 if self.isapplied(patch):
1390 if self.isapplied(patch):
1391 self.ui.warn(_('all patches are currently applied\n'))
1391 self.ui.warn(_('all patches are currently applied\n'))
1392 return 0
1392 return 0
1393
1393
1394 # Following the above example, starting at 'top' of B:
1394 # Following the above example, starting at 'top' of B:
1395 # qpush should be performed (pushes C), but a subsequent
1395 # qpush should be performed (pushes C), but a subsequent
1396 # qpush without an argument is an error (nothing to
1396 # qpush without an argument is an error (nothing to
1397 # apply). This allows a loop of "...while hg qpush..." to
1397 # apply). This allows a loop of "...while hg qpush..." to
1398 # work as it detects an error when done
1398 # work as it detects an error when done
1399 start = self.seriesend()
1399 start = self.seriesend()
1400 if start == len(self.series):
1400 if start == len(self.series):
1401 self.ui.warn(_('patch series already fully applied\n'))
1401 self.ui.warn(_('patch series already fully applied\n'))
1402 return 1
1402 return 1
1403 if not force and not keepchanges:
1403 if not force and not keepchanges:
1404 self.checklocalchanges(repo, refresh=self.applied)
1404 self.checklocalchanges(repo, refresh=self.applied)
1405
1405
1406 if exact:
1406 if exact:
1407 if keepchanges:
1407 if keepchanges:
1408 raise error.Abort(
1408 raise error.Abort(
1409 _("cannot use --exact and --keep-changes together"))
1409 _("cannot use --exact and --keep-changes together"))
1410 if move:
1410 if move:
1411 raise error.Abort(_('cannot use --exact and --move '
1411 raise error.Abort(_('cannot use --exact and --move '
1412 'together'))
1412 'together'))
1413 if self.applied:
1413 if self.applied:
1414 raise error.Abort(_('cannot push --exact with applied '
1414 raise error.Abort(_('cannot push --exact with applied '
1415 'patches'))
1415 'patches'))
1416 root = self.series[start]
1416 root = self.series[start]
1417 target = patchheader(self.join(root), self.plainmode).parent
1417 target = patchheader(self.join(root), self.plainmode).parent
1418 if not target:
1418 if not target:
1419 raise error.Abort(
1419 raise error.Abort(
1420 _("%s does not have a parent recorded") % root)
1420 _("%s does not have a parent recorded") % root)
1421 if not repo[target] == repo['.']:
1421 if not repo[target] == repo['.']:
1422 hg.update(repo, target)
1422 hg.update(repo, target)
1423
1423
1424 if move:
1424 if move:
1425 if not patch:
1425 if not patch:
1426 raise error.Abort(_("please specify the patch to move"))
1426 raise error.Abort(_("please specify the patch to move"))
1427 for fullstart, rpn in enumerate(self.fullseries):
1427 for fullstart, rpn in enumerate(self.fullseries):
1428 # strip markers for patch guards
1428 # strip markers for patch guards
1429 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1429 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1430 break
1430 break
1431 for i, rpn in enumerate(self.fullseries[fullstart:]):
1431 for i, rpn in enumerate(self.fullseries[fullstart:]):
1432 # strip markers for patch guards
1432 # strip markers for patch guards
1433 if self.guard_re.split(rpn, 1)[0] == patch:
1433 if self.guard_re.split(rpn, 1)[0] == patch:
1434 break
1434 break
1435 index = fullstart + i
1435 index = fullstart + i
1436 assert index < len(self.fullseries)
1436 assert index < len(self.fullseries)
1437 fullpatch = self.fullseries[index]
1437 fullpatch = self.fullseries[index]
1438 del self.fullseries[index]
1438 del self.fullseries[index]
1439 self.fullseries.insert(fullstart, fullpatch)
1439 self.fullseries.insert(fullstart, fullpatch)
1440 self.parseseries()
1440 self.parseseries()
1441 self.seriesdirty = True
1441 self.seriesdirty = True
1442
1442
1443 self.applieddirty = True
1443 self.applieddirty = True
1444 if start > 0:
1444 if start > 0:
1445 self.checktoppatch(repo)
1445 self.checktoppatch(repo)
1446 if not patch:
1446 if not patch:
1447 patch = self.series[start]
1447 patch = self.series[start]
1448 end = start + 1
1448 end = start + 1
1449 else:
1449 else:
1450 end = self.series.index(patch, start) + 1
1450 end = self.series.index(patch, start) + 1
1451
1451
1452 tobackup = set()
1452 tobackup = set()
1453 if (not nobackup and force) or keepchanges:
1453 if (not nobackup and force) or keepchanges:
1454 status = self.checklocalchanges(repo, force=True)
1454 status = self.checklocalchanges(repo, force=True)
1455 if keepchanges:
1455 if keepchanges:
1456 tobackup.update(status.modified + status.added +
1456 tobackup.update(status.modified + status.added +
1457 status.removed + status.deleted)
1457 status.removed + status.deleted)
1458 else:
1458 else:
1459 tobackup.update(status.modified + status.added)
1459 tobackup.update(status.modified + status.added)
1460
1460
1461 s = self.series[start:end]
1461 s = self.series[start:end]
1462 all_files = set()
1462 all_files = set()
1463 try:
1463 try:
1464 if mergeq:
1464 if mergeq:
1465 ret = self.mergepatch(repo, mergeq, s, diffopts)
1465 ret = self.mergepatch(repo, mergeq, s, diffopts)
1466 else:
1466 else:
1467 ret = self.apply(repo, s, list, all_files=all_files,
1467 ret = self.apply(repo, s, list, all_files=all_files,
1468 tobackup=tobackup, keepchanges=keepchanges)
1468 tobackup=tobackup, keepchanges=keepchanges)
1469 except AbortNoCleanup:
1469 except AbortNoCleanup:
1470 raise
1470 raise
1471 except: # re-raises
1471 except: # re-raises
1472 self.ui.warn(_('cleaning up working directory...\n'))
1472 self.ui.warn(_('cleaning up working directory...\n'))
1473 cmdutil.revert(self.ui, repo, repo['.'],
1473 cmdutil.revert(self.ui, repo, repo['.'],
1474 repo.dirstate.parents(), no_backup=True)
1474 repo.dirstate.parents(), no_backup=True)
1475 # only remove unknown files that we know we touched or
1475 # only remove unknown files that we know we touched or
1476 # created while patching
1476 # created while patching
1477 for f in all_files:
1477 for f in all_files:
1478 if f not in repo.dirstate:
1478 if f not in repo.dirstate:
1479 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1479 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1480 self.ui.warn(_('done\n'))
1480 self.ui.warn(_('done\n'))
1481 raise
1481 raise
1482
1482
1483 if not self.applied:
1483 if not self.applied:
1484 return ret[0]
1484 return ret[0]
1485 top = self.applied[-1].name
1485 top = self.applied[-1].name
1486 if ret[0] and ret[0] > 1:
1486 if ret[0] and ret[0] > 1:
1487 msg = _("errors during apply, please fix and qrefresh %s\n")
1487 msg = _("errors during apply, please fix and qrefresh %s\n")
1488 self.ui.write(msg % top)
1488 self.ui.write(msg % top)
1489 else:
1489 else:
1490 self.ui.write(_("now at: %s\n") % top)
1490 self.ui.write(_("now at: %s\n") % top)
1491 return ret[0]
1491 return ret[0]
1492
1492
1493 def pop(self, repo, patch=None, force=False, update=True, all=False,
1493 def pop(self, repo, patch=None, force=False, update=True, all=False,
1494 nobackup=False, keepchanges=False):
1494 nobackup=False, keepchanges=False):
1495 self.checkkeepchanges(keepchanges, force)
1495 self.checkkeepchanges(keepchanges, force)
1496 with repo.wlock():
1496 with repo.wlock():
1497 if patch:
1497 if patch:
1498 # index, rev, patch
1498 # index, rev, patch
1499 info = self.isapplied(patch)
1499 info = self.isapplied(patch)
1500 if not info:
1500 if not info:
1501 patch = self.lookup(patch)
1501 patch = self.lookup(patch)
1502 info = self.isapplied(patch)
1502 info = self.isapplied(patch)
1503 if not info:
1503 if not info:
1504 raise error.Abort(_("patch %s is not applied") % patch)
1504 raise error.Abort(_("patch %s is not applied") % patch)
1505
1505
1506 if not self.applied:
1506 if not self.applied:
1507 # Allow qpop -a to work repeatedly,
1507 # Allow qpop -a to work repeatedly,
1508 # but not qpop without an argument
1508 # but not qpop without an argument
1509 self.ui.warn(_("no patches applied\n"))
1509 self.ui.warn(_("no patches applied\n"))
1510 return not all
1510 return not all
1511
1511
1512 if all:
1512 if all:
1513 start = 0
1513 start = 0
1514 elif patch:
1514 elif patch:
1515 start = info[0] + 1
1515 start = info[0] + 1
1516 else:
1516 else:
1517 start = len(self.applied) - 1
1517 start = len(self.applied) - 1
1518
1518
1519 if start >= len(self.applied):
1519 if start >= len(self.applied):
1520 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1520 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1521 return
1521 return
1522
1522
1523 if not update:
1523 if not update:
1524 parents = repo.dirstate.parents()
1524 parents = repo.dirstate.parents()
1525 rr = [x.node for x in self.applied]
1525 rr = [x.node for x in self.applied]
1526 for p in parents:
1526 for p in parents:
1527 if p in rr:
1527 if p in rr:
1528 self.ui.warn(_("qpop: forcing dirstate update\n"))
1528 self.ui.warn(_("qpop: forcing dirstate update\n"))
1529 update = True
1529 update = True
1530 else:
1530 else:
1531 parents = [p.node() for p in repo[None].parents()]
1531 parents = [p.node() for p in repo[None].parents()]
1532 needupdate = False
1532 needupdate = False
1533 for entry in self.applied[start:]:
1533 for entry in self.applied[start:]:
1534 if entry.node in parents:
1534 if entry.node in parents:
1535 needupdate = True
1535 needupdate = True
1536 break
1536 break
1537 update = needupdate
1537 update = needupdate
1538
1538
1539 tobackup = set()
1539 tobackup = set()
1540 if update:
1540 if update:
1541 s = self.checklocalchanges(repo, force=force or keepchanges)
1541 s = self.checklocalchanges(repo, force=force or keepchanges)
1542 if force:
1542 if force:
1543 if not nobackup:
1543 if not nobackup:
1544 tobackup.update(s.modified + s.added)
1544 tobackup.update(s.modified + s.added)
1545 elif keepchanges:
1545 elif keepchanges:
1546 tobackup.update(s.modified + s.added +
1546 tobackup.update(s.modified + s.added +
1547 s.removed + s.deleted)
1547 s.removed + s.deleted)
1548
1548
1549 self.applieddirty = True
1549 self.applieddirty = True
1550 end = len(self.applied)
1550 end = len(self.applied)
1551 rev = self.applied[start].node
1551 rev = self.applied[start].node
1552
1552
1553 try:
1553 try:
1554 heads = repo.changelog.heads(rev)
1554 heads = repo.changelog.heads(rev)
1555 except error.LookupError:
1555 except error.LookupError:
1556 node = short(rev)
1556 node = short(rev)
1557 raise error.Abort(_('trying to pop unknown node %s') % node)
1557 raise error.Abort(_('trying to pop unknown node %s') % node)
1558
1558
1559 if heads != [self.applied[-1].node]:
1559 if heads != [self.applied[-1].node]:
1560 raise error.Abort(_("popping would remove a revision not "
1560 raise error.Abort(_("popping would remove a revision not "
1561 "managed by this patch queue"))
1561 "managed by this patch queue"))
1562 if not repo[self.applied[-1].node].mutable():
1562 if not repo[self.applied[-1].node].mutable():
1563 raise error.Abort(
1563 raise error.Abort(
1564 _("popping would remove a public revision"),
1564 _("popping would remove a public revision"),
1565 hint=_("see 'hg help phases' for details"))
1565 hint=_("see 'hg help phases' for details"))
1566
1566
1567 # we know there are no local changes, so we can make a simplified
1567 # we know there are no local changes, so we can make a simplified
1568 # form of hg.update.
1568 # form of hg.update.
1569 if update:
1569 if update:
1570 qp = self.qparents(repo, rev)
1570 qp = self.qparents(repo, rev)
1571 ctx = repo[qp]
1571 ctx = repo[qp]
1572 m, a, r, d = repo.status(qp, '.')[:4]
1572 m, a, r, d = repo.status(qp, '.')[:4]
1573 if d:
1573 if d:
1574 raise error.Abort(_("deletions found between repo revs"))
1574 raise error.Abort(_("deletions found between repo revs"))
1575
1575
1576 tobackup = set(a + m + r) & tobackup
1576 tobackup = set(a + m + r) & tobackup
1577 if keepchanges and tobackup:
1577 if keepchanges and tobackup:
1578 raise error.Abort(_("local changes found, qrefresh first"))
1578 raise error.Abort(_("local changes found, qrefresh first"))
1579 self.backup(repo, tobackup)
1579 self.backup(repo, tobackup)
1580 repo.dirstate.beginparentchange()
1580 repo.dirstate.beginparentchange()
1581 for f in a:
1581 for f in a:
1582 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1582 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1583 repo.dirstate.drop(f)
1583 repo.dirstate.drop(f)
1584 for f in m + r:
1584 for f in m + r:
1585 fctx = ctx[f]
1585 fctx = ctx[f]
1586 repo.wwrite(f, fctx.data(), fctx.flags())
1586 repo.wwrite(f, fctx.data(), fctx.flags())
1587 repo.dirstate.normal(f)
1587 repo.dirstate.normal(f)
1588 repo.setparents(qp, nullid)
1588 repo.setparents(qp, nullid)
1589 repo.dirstate.endparentchange()
1589 repo.dirstate.endparentchange()
1590 for patch in reversed(self.applied[start:end]):
1590 for patch in reversed(self.applied[start:end]):
1591 self.ui.status(_("popping %s\n") % patch.name)
1591 self.ui.status(_("popping %s\n") % patch.name)
1592 del self.applied[start:end]
1592 del self.applied[start:end]
1593 strip(self.ui, repo, [rev], update=False, backup=False)
1593 strip(self.ui, repo, [rev], update=False, backup=False)
1594 for s, state in repo['.'].substate.items():
1594 for s, state in repo['.'].substate.items():
1595 repo['.'].sub(s).get(state)
1595 repo['.'].sub(s).get(state)
1596 if self.applied:
1596 if self.applied:
1597 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1597 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1598 else:
1598 else:
1599 self.ui.write(_("patch queue now empty\n"))
1599 self.ui.write(_("patch queue now empty\n"))
1600
1600
1601 def diff(self, repo, pats, opts):
1601 def diff(self, repo, pats, opts):
1602 top, patch = self.checktoppatch(repo)
1602 top, patch = self.checktoppatch(repo)
1603 if not top:
1603 if not top:
1604 self.ui.write(_("no patches applied\n"))
1604 self.ui.write(_("no patches applied\n"))
1605 return
1605 return
1606 qp = self.qparents(repo, top)
1606 qp = self.qparents(repo, top)
1607 if opts.get('reverse'):
1607 if opts.get('reverse'):
1608 node1, node2 = None, qp
1608 node1, node2 = None, qp
1609 else:
1609 else:
1610 node1, node2 = qp, None
1610 node1, node2 = qp, None
1611 diffopts = self.diffopts(opts, patch)
1611 diffopts = self.diffopts(opts, patch)
1612 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1612 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1613
1613
1614 def refresh(self, repo, pats=None, **opts):
1614 def refresh(self, repo, pats=None, **opts):
1615 if not self.applied:
1615 if not self.applied:
1616 self.ui.write(_("no patches applied\n"))
1616 self.ui.write(_("no patches applied\n"))
1617 return 1
1617 return 1
1618 msg = opts.get('msg', '').rstrip()
1618 msg = opts.get('msg', '').rstrip()
1619 edit = opts.get('edit')
1619 edit = opts.get('edit')
1620 editform = opts.get('editform', 'mq.qrefresh')
1620 editform = opts.get('editform', 'mq.qrefresh')
1621 newuser = opts.get('user')
1621 newuser = opts.get('user')
1622 newdate = opts.get('date')
1622 newdate = opts.get('date')
1623 if newdate:
1623 if newdate:
1624 newdate = '%d %d' % util.parsedate(newdate)
1624 newdate = '%d %d' % util.parsedate(newdate)
1625 wlock = repo.wlock()
1625 wlock = repo.wlock()
1626
1626
1627 try:
1627 try:
1628 self.checktoppatch(repo)
1628 self.checktoppatch(repo)
1629 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1629 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1630 if repo.changelog.heads(top) != [top]:
1630 if repo.changelog.heads(top) != [top]:
1631 raise error.Abort(_("cannot qrefresh a revision with children"))
1631 raise error.Abort(_("cannot qrefresh a revision with children"))
1632 if not repo[top].mutable():
1632 if not repo[top].mutable():
1633 raise error.Abort(_("cannot qrefresh public revision"),
1633 raise error.Abort(_("cannot qrefresh public revision"),
1634 hint=_("see 'hg help phases' for details"))
1634 hint=_("see 'hg help phases' for details"))
1635
1635
1636 cparents = repo.changelog.parents(top)
1636 cparents = repo.changelog.parents(top)
1637 patchparent = self.qparents(repo, top)
1637 patchparent = self.qparents(repo, top)
1638
1638
1639 inclsubs = checksubstate(repo, hex(patchparent))
1639 inclsubs = checksubstate(repo, hex(patchparent))
1640 if inclsubs:
1640 if inclsubs:
1641 substatestate = repo.dirstate['.hgsubstate']
1641 substatestate = repo.dirstate['.hgsubstate']
1642
1642
1643 ph = patchheader(self.join(patchfn), self.plainmode)
1643 ph = patchheader(self.join(patchfn), self.plainmode)
1644 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1644 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1645 if newuser:
1645 if newuser:
1646 ph.setuser(newuser)
1646 ph.setuser(newuser)
1647 if newdate:
1647 if newdate:
1648 ph.setdate(newdate)
1648 ph.setdate(newdate)
1649 ph.setparent(hex(patchparent))
1649 ph.setparent(hex(patchparent))
1650
1650
1651 # only commit new patch when write is complete
1651 # only commit new patch when write is complete
1652 patchf = self.opener(patchfn, 'w', atomictemp=True)
1652 patchf = self.opener(patchfn, 'w', atomictemp=True)
1653
1653
1654 # update the dirstate in place, strip off the qtip commit
1654 # update the dirstate in place, strip off the qtip commit
1655 # and then commit.
1655 # and then commit.
1656 #
1656 #
1657 # this should really read:
1657 # this should really read:
1658 # mm, dd, aa = repo.status(top, patchparent)[:3]
1658 # mm, dd, aa = repo.status(top, patchparent)[:3]
1659 # but we do it backwards to take advantage of manifest/changelog
1659 # but we do it backwards to take advantage of manifest/changelog
1660 # caching against the next repo.status call
1660 # caching against the next repo.status call
1661 mm, aa, dd = repo.status(patchparent, top)[:3]
1661 mm, aa, dd = repo.status(patchparent, top)[:3]
1662 changes = repo.changelog.read(top)
1662 changes = repo.changelog.read(top)
1663 man = repo.manifest.read(changes[0])
1663 man = repo.manifestlog[changes[0]].read()
1664 aaa = aa[:]
1664 aaa = aa[:]
1665 matchfn = scmutil.match(repo[None], pats, opts)
1665 matchfn = scmutil.match(repo[None], pats, opts)
1666 # in short mode, we only diff the files included in the
1666 # in short mode, we only diff the files included in the
1667 # patch already plus specified files
1667 # patch already plus specified files
1668 if opts.get('short'):
1668 if opts.get('short'):
1669 # if amending a patch, we start with existing
1669 # if amending a patch, we start with existing
1670 # files plus specified files - unfiltered
1670 # files plus specified files - unfiltered
1671 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1671 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1672 # filter with include/exclude options
1672 # filter with include/exclude options
1673 matchfn = scmutil.match(repo[None], opts=opts)
1673 matchfn = scmutil.match(repo[None], opts=opts)
1674 else:
1674 else:
1675 match = scmutil.matchall(repo)
1675 match = scmutil.matchall(repo)
1676 m, a, r, d = repo.status(match=match)[:4]
1676 m, a, r, d = repo.status(match=match)[:4]
1677 mm = set(mm)
1677 mm = set(mm)
1678 aa = set(aa)
1678 aa = set(aa)
1679 dd = set(dd)
1679 dd = set(dd)
1680
1680
1681 # we might end up with files that were added between
1681 # we might end up with files that were added between
1682 # qtip and the dirstate parent, but then changed in the
1682 # qtip and the dirstate parent, but then changed in the
1683 # local dirstate. in this case, we want them to only
1683 # local dirstate. in this case, we want them to only
1684 # show up in the added section
1684 # show up in the added section
1685 for x in m:
1685 for x in m:
1686 if x not in aa:
1686 if x not in aa:
1687 mm.add(x)
1687 mm.add(x)
1688 # we might end up with files added by the local dirstate that
1688 # we might end up with files added by the local dirstate that
1689 # were deleted by the patch. In this case, they should only
1689 # were deleted by the patch. In this case, they should only
1690 # show up in the changed section.
1690 # show up in the changed section.
1691 for x in a:
1691 for x in a:
1692 if x in dd:
1692 if x in dd:
1693 dd.remove(x)
1693 dd.remove(x)
1694 mm.add(x)
1694 mm.add(x)
1695 else:
1695 else:
1696 aa.add(x)
1696 aa.add(x)
1697 # make sure any files deleted in the local dirstate
1697 # make sure any files deleted in the local dirstate
1698 # are not in the add or change column of the patch
1698 # are not in the add or change column of the patch
1699 forget = []
1699 forget = []
1700 for x in d + r:
1700 for x in d + r:
1701 if x in aa:
1701 if x in aa:
1702 aa.remove(x)
1702 aa.remove(x)
1703 forget.append(x)
1703 forget.append(x)
1704 continue
1704 continue
1705 else:
1705 else:
1706 mm.discard(x)
1706 mm.discard(x)
1707 dd.add(x)
1707 dd.add(x)
1708
1708
1709 m = list(mm)
1709 m = list(mm)
1710 r = list(dd)
1710 r = list(dd)
1711 a = list(aa)
1711 a = list(aa)
1712
1712
1713 # create 'match' that includes the files to be recommitted.
1713 # create 'match' that includes the files to be recommitted.
1714 # apply matchfn via repo.status to ensure correct case handling.
1714 # apply matchfn via repo.status to ensure correct case handling.
1715 cm, ca, cr, cd = repo.status(patchparent, match=matchfn)[:4]
1715 cm, ca, cr, cd = repo.status(patchparent, match=matchfn)[:4]
1716 allmatches = set(cm + ca + cr + cd)
1716 allmatches = set(cm + ca + cr + cd)
1717 refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
1717 refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
1718
1718
1719 files = set(inclsubs)
1719 files = set(inclsubs)
1720 for x in refreshchanges:
1720 for x in refreshchanges:
1721 files.update(x)
1721 files.update(x)
1722 match = scmutil.matchfiles(repo, files)
1722 match = scmutil.matchfiles(repo, files)
1723
1723
1724 bmlist = repo[top].bookmarks()
1724 bmlist = repo[top].bookmarks()
1725
1725
1726 dsguard = None
1726 dsguard = None
1727 try:
1727 try:
1728 dsguard = cmdutil.dirstateguard(repo, 'mq.refresh')
1728 dsguard = cmdutil.dirstateguard(repo, 'mq.refresh')
1729 if diffopts.git or diffopts.upgrade:
1729 if diffopts.git or diffopts.upgrade:
1730 copies = {}
1730 copies = {}
1731 for dst in a:
1731 for dst in a:
1732 src = repo.dirstate.copied(dst)
1732 src = repo.dirstate.copied(dst)
1733 # during qfold, the source file for copies may
1733 # during qfold, the source file for copies may
1734 # be removed. Treat this as a simple add.
1734 # be removed. Treat this as a simple add.
1735 if src is not None and src in repo.dirstate:
1735 if src is not None and src in repo.dirstate:
1736 copies.setdefault(src, []).append(dst)
1736 copies.setdefault(src, []).append(dst)
1737 repo.dirstate.add(dst)
1737 repo.dirstate.add(dst)
1738 # remember the copies between patchparent and qtip
1738 # remember the copies between patchparent and qtip
1739 for dst in aaa:
1739 for dst in aaa:
1740 f = repo.file(dst)
1740 f = repo.file(dst)
1741 src = f.renamed(man[dst])
1741 src = f.renamed(man[dst])
1742 if src:
1742 if src:
1743 copies.setdefault(src[0], []).extend(
1743 copies.setdefault(src[0], []).extend(
1744 copies.get(dst, []))
1744 copies.get(dst, []))
1745 if dst in a:
1745 if dst in a:
1746 copies[src[0]].append(dst)
1746 copies[src[0]].append(dst)
1747 # we can't copy a file created by the patch itself
1747 # we can't copy a file created by the patch itself
1748 if dst in copies:
1748 if dst in copies:
1749 del copies[dst]
1749 del copies[dst]
1750 for src, dsts in copies.iteritems():
1750 for src, dsts in copies.iteritems():
1751 for dst in dsts:
1751 for dst in dsts:
1752 repo.dirstate.copy(src, dst)
1752 repo.dirstate.copy(src, dst)
1753 else:
1753 else:
1754 for dst in a:
1754 for dst in a:
1755 repo.dirstate.add(dst)
1755 repo.dirstate.add(dst)
1756 # Drop useless copy information
1756 # Drop useless copy information
1757 for f in list(repo.dirstate.copies()):
1757 for f in list(repo.dirstate.copies()):
1758 repo.dirstate.copy(None, f)
1758 repo.dirstate.copy(None, f)
1759 for f in r:
1759 for f in r:
1760 repo.dirstate.remove(f)
1760 repo.dirstate.remove(f)
1761 # if the patch excludes a modified file, mark that
1761 # if the patch excludes a modified file, mark that
1762 # file with mtime=0 so status can see it.
1762 # file with mtime=0 so status can see it.
1763 mm = []
1763 mm = []
1764 for i in xrange(len(m) - 1, -1, -1):
1764 for i in xrange(len(m) - 1, -1, -1):
1765 if not matchfn(m[i]):
1765 if not matchfn(m[i]):
1766 mm.append(m[i])
1766 mm.append(m[i])
1767 del m[i]
1767 del m[i]
1768 for f in m:
1768 for f in m:
1769 repo.dirstate.normal(f)
1769 repo.dirstate.normal(f)
1770 for f in mm:
1770 for f in mm:
1771 repo.dirstate.normallookup(f)
1771 repo.dirstate.normallookup(f)
1772 for f in forget:
1772 for f in forget:
1773 repo.dirstate.drop(f)
1773 repo.dirstate.drop(f)
1774
1774
1775 user = ph.user or changes[1]
1775 user = ph.user or changes[1]
1776
1776
1777 oldphase = repo[top].phase()
1777 oldphase = repo[top].phase()
1778
1778
1779 # assumes strip can roll itself back if interrupted
1779 # assumes strip can roll itself back if interrupted
1780 repo.setparents(*cparents)
1780 repo.setparents(*cparents)
1781 self.applied.pop()
1781 self.applied.pop()
1782 self.applieddirty = True
1782 self.applieddirty = True
1783 strip(self.ui, repo, [top], update=False, backup=False)
1783 strip(self.ui, repo, [top], update=False, backup=False)
1784 dsguard.close()
1784 dsguard.close()
1785 finally:
1785 finally:
1786 release(dsguard)
1786 release(dsguard)
1787
1787
1788 try:
1788 try:
1789 # might be nice to attempt to roll back strip after this
1789 # might be nice to attempt to roll back strip after this
1790
1790
1791 defaultmsg = "[mq]: %s" % patchfn
1791 defaultmsg = "[mq]: %s" % patchfn
1792 editor = cmdutil.getcommiteditor(editform=editform)
1792 editor = cmdutil.getcommiteditor(editform=editform)
1793 if edit:
1793 if edit:
1794 def finishdesc(desc):
1794 def finishdesc(desc):
1795 if desc.rstrip():
1795 if desc.rstrip():
1796 ph.setmessage(desc)
1796 ph.setmessage(desc)
1797 return desc
1797 return desc
1798 return defaultmsg
1798 return defaultmsg
1799 # i18n: this message is shown in editor with "HG: " prefix
1799 # i18n: this message is shown in editor with "HG: " prefix
1800 extramsg = _('Leave message empty to use default message.')
1800 extramsg = _('Leave message empty to use default message.')
1801 editor = cmdutil.getcommiteditor(finishdesc=finishdesc,
1801 editor = cmdutil.getcommiteditor(finishdesc=finishdesc,
1802 extramsg=extramsg,
1802 extramsg=extramsg,
1803 editform=editform)
1803 editform=editform)
1804 message = msg or "\n".join(ph.message)
1804 message = msg or "\n".join(ph.message)
1805 elif not msg:
1805 elif not msg:
1806 if not ph.message:
1806 if not ph.message:
1807 message = defaultmsg
1807 message = defaultmsg
1808 else:
1808 else:
1809 message = "\n".join(ph.message)
1809 message = "\n".join(ph.message)
1810 else:
1810 else:
1811 message = msg
1811 message = msg
1812 ph.setmessage(msg)
1812 ph.setmessage(msg)
1813
1813
1814 # Ensure we create a new changeset in the same phase than
1814 # Ensure we create a new changeset in the same phase than
1815 # the old one.
1815 # the old one.
1816 lock = tr = None
1816 lock = tr = None
1817 try:
1817 try:
1818 lock = repo.lock()
1818 lock = repo.lock()
1819 tr = repo.transaction('mq')
1819 tr = repo.transaction('mq')
1820 n = newcommit(repo, oldphase, message, user, ph.date,
1820 n = newcommit(repo, oldphase, message, user, ph.date,
1821 match=match, force=True, editor=editor)
1821 match=match, force=True, editor=editor)
1822 # only write patch after a successful commit
1822 # only write patch after a successful commit
1823 c = [list(x) for x in refreshchanges]
1823 c = [list(x) for x in refreshchanges]
1824 if inclsubs:
1824 if inclsubs:
1825 self.putsubstate2changes(substatestate, c)
1825 self.putsubstate2changes(substatestate, c)
1826 chunks = patchmod.diff(repo, patchparent,
1826 chunks = patchmod.diff(repo, patchparent,
1827 changes=c, opts=diffopts)
1827 changes=c, opts=diffopts)
1828 comments = str(ph)
1828 comments = str(ph)
1829 if comments:
1829 if comments:
1830 patchf.write(comments)
1830 patchf.write(comments)
1831 for chunk in chunks:
1831 for chunk in chunks:
1832 patchf.write(chunk)
1832 patchf.write(chunk)
1833 patchf.close()
1833 patchf.close()
1834
1834
1835 marks = repo._bookmarks
1835 marks = repo._bookmarks
1836 for bm in bmlist:
1836 for bm in bmlist:
1837 marks[bm] = n
1837 marks[bm] = n
1838 marks.recordchange(tr)
1838 marks.recordchange(tr)
1839 tr.close()
1839 tr.close()
1840
1840
1841 self.applied.append(statusentry(n, patchfn))
1841 self.applied.append(statusentry(n, patchfn))
1842 finally:
1842 finally:
1843 lockmod.release(tr, lock)
1843 lockmod.release(tr, lock)
1844 except: # re-raises
1844 except: # re-raises
1845 ctx = repo[cparents[0]]
1845 ctx = repo[cparents[0]]
1846 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1846 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1847 self.savedirty()
1847 self.savedirty()
1848 self.ui.warn(_('qrefresh interrupted while patch was popped! '
1848 self.ui.warn(_('qrefresh interrupted while patch was popped! '
1849 '(revert --all, qpush to recover)\n'))
1849 '(revert --all, qpush to recover)\n'))
1850 raise
1850 raise
1851 finally:
1851 finally:
1852 wlock.release()
1852 wlock.release()
1853 self.removeundo(repo)
1853 self.removeundo(repo)
1854
1854
1855 def init(self, repo, create=False):
1855 def init(self, repo, create=False):
1856 if not create and os.path.isdir(self.path):
1856 if not create and os.path.isdir(self.path):
1857 raise error.Abort(_("patch queue directory already exists"))
1857 raise error.Abort(_("patch queue directory already exists"))
1858 try:
1858 try:
1859 os.mkdir(self.path)
1859 os.mkdir(self.path)
1860 except OSError as inst:
1860 except OSError as inst:
1861 if inst.errno != errno.EEXIST or not create:
1861 if inst.errno != errno.EEXIST or not create:
1862 raise
1862 raise
1863 if create:
1863 if create:
1864 return self.qrepo(create=True)
1864 return self.qrepo(create=True)
1865
1865
1866 def unapplied(self, repo, patch=None):
1866 def unapplied(self, repo, patch=None):
1867 if patch and patch not in self.series:
1867 if patch and patch not in self.series:
1868 raise error.Abort(_("patch %s is not in series file") % patch)
1868 raise error.Abort(_("patch %s is not in series file") % patch)
1869 if not patch:
1869 if not patch:
1870 start = self.seriesend()
1870 start = self.seriesend()
1871 else:
1871 else:
1872 start = self.series.index(patch) + 1
1872 start = self.series.index(patch) + 1
1873 unapplied = []
1873 unapplied = []
1874 for i in xrange(start, len(self.series)):
1874 for i in xrange(start, len(self.series)):
1875 pushable, reason = self.pushable(i)
1875 pushable, reason = self.pushable(i)
1876 if pushable:
1876 if pushable:
1877 unapplied.append((i, self.series[i]))
1877 unapplied.append((i, self.series[i]))
1878 self.explainpushable(i)
1878 self.explainpushable(i)
1879 return unapplied
1879 return unapplied
1880
1880
1881 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1881 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1882 summary=False):
1882 summary=False):
1883 def displayname(pfx, patchname, state):
1883 def displayname(pfx, patchname, state):
1884 if pfx:
1884 if pfx:
1885 self.ui.write(pfx)
1885 self.ui.write(pfx)
1886 if summary:
1886 if summary:
1887 ph = patchheader(self.join(patchname), self.plainmode)
1887 ph = patchheader(self.join(patchname), self.plainmode)
1888 if ph.message:
1888 if ph.message:
1889 msg = ph.message[0]
1889 msg = ph.message[0]
1890 else:
1890 else:
1891 msg = ''
1891 msg = ''
1892
1892
1893 if self.ui.formatted():
1893 if self.ui.formatted():
1894 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1894 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1895 if width > 0:
1895 if width > 0:
1896 msg = util.ellipsis(msg, width)
1896 msg = util.ellipsis(msg, width)
1897 else:
1897 else:
1898 msg = ''
1898 msg = ''
1899 self.ui.write(patchname, label='qseries.' + state)
1899 self.ui.write(patchname, label='qseries.' + state)
1900 self.ui.write(': ')
1900 self.ui.write(': ')
1901 self.ui.write(msg, label='qseries.message.' + state)
1901 self.ui.write(msg, label='qseries.message.' + state)
1902 else:
1902 else:
1903 self.ui.write(patchname, label='qseries.' + state)
1903 self.ui.write(patchname, label='qseries.' + state)
1904 self.ui.write('\n')
1904 self.ui.write('\n')
1905
1905
1906 applied = set([p.name for p in self.applied])
1906 applied = set([p.name for p in self.applied])
1907 if length is None:
1907 if length is None:
1908 length = len(self.series) - start
1908 length = len(self.series) - start
1909 if not missing:
1909 if not missing:
1910 if self.ui.verbose:
1910 if self.ui.verbose:
1911 idxwidth = len(str(start + length - 1))
1911 idxwidth = len(str(start + length - 1))
1912 for i in xrange(start, start + length):
1912 for i in xrange(start, start + length):
1913 patch = self.series[i]
1913 patch = self.series[i]
1914 if patch in applied:
1914 if patch in applied:
1915 char, state = 'A', 'applied'
1915 char, state = 'A', 'applied'
1916 elif self.pushable(i)[0]:
1916 elif self.pushable(i)[0]:
1917 char, state = 'U', 'unapplied'
1917 char, state = 'U', 'unapplied'
1918 else:
1918 else:
1919 char, state = 'G', 'guarded'
1919 char, state = 'G', 'guarded'
1920 pfx = ''
1920 pfx = ''
1921 if self.ui.verbose:
1921 if self.ui.verbose:
1922 pfx = '%*d %s ' % (idxwidth, i, char)
1922 pfx = '%*d %s ' % (idxwidth, i, char)
1923 elif status and status != char:
1923 elif status and status != char:
1924 continue
1924 continue
1925 displayname(pfx, patch, state)
1925 displayname(pfx, patch, state)
1926 else:
1926 else:
1927 msng_list = []
1927 msng_list = []
1928 for root, dirs, files in os.walk(self.path):
1928 for root, dirs, files in os.walk(self.path):
1929 d = root[len(self.path) + 1:]
1929 d = root[len(self.path) + 1:]
1930 for f in files:
1930 for f in files:
1931 fl = os.path.join(d, f)
1931 fl = os.path.join(d, f)
1932 if (fl not in self.series and
1932 if (fl not in self.series and
1933 fl not in (self.statuspath, self.seriespath,
1933 fl not in (self.statuspath, self.seriespath,
1934 self.guardspath)
1934 self.guardspath)
1935 and not fl.startswith('.')):
1935 and not fl.startswith('.')):
1936 msng_list.append(fl)
1936 msng_list.append(fl)
1937 for x in sorted(msng_list):
1937 for x in sorted(msng_list):
1938 pfx = self.ui.verbose and ('D ') or ''
1938 pfx = self.ui.verbose and ('D ') or ''
1939 displayname(pfx, x, 'missing')
1939 displayname(pfx, x, 'missing')
1940
1940
1941 def issaveline(self, l):
1941 def issaveline(self, l):
1942 if l.name == '.hg.patches.save.line':
1942 if l.name == '.hg.patches.save.line':
1943 return True
1943 return True
1944
1944
1945 def qrepo(self, create=False):
1945 def qrepo(self, create=False):
1946 ui = self.baseui.copy()
1946 ui = self.baseui.copy()
1947 if create or os.path.isdir(self.join(".hg")):
1947 if create or os.path.isdir(self.join(".hg")):
1948 return hg.repository(ui, path=self.path, create=create)
1948 return hg.repository(ui, path=self.path, create=create)
1949
1949
1950 def restore(self, repo, rev, delete=None, qupdate=None):
1950 def restore(self, repo, rev, delete=None, qupdate=None):
1951 desc = repo[rev].description().strip()
1951 desc = repo[rev].description().strip()
1952 lines = desc.splitlines()
1952 lines = desc.splitlines()
1953 i = 0
1953 i = 0
1954 datastart = None
1954 datastart = None
1955 series = []
1955 series = []
1956 applied = []
1956 applied = []
1957 qpp = None
1957 qpp = None
1958 for i, line in enumerate(lines):
1958 for i, line in enumerate(lines):
1959 if line == 'Patch Data:':
1959 if line == 'Patch Data:':
1960 datastart = i + 1
1960 datastart = i + 1
1961 elif line.startswith('Dirstate:'):
1961 elif line.startswith('Dirstate:'):
1962 l = line.rstrip()
1962 l = line.rstrip()
1963 l = l[10:].split(' ')
1963 l = l[10:].split(' ')
1964 qpp = [bin(x) for x in l]
1964 qpp = [bin(x) for x in l]
1965 elif datastart is not None:
1965 elif datastart is not None:
1966 l = line.rstrip()
1966 l = line.rstrip()
1967 n, name = l.split(':', 1)
1967 n, name = l.split(':', 1)
1968 if n:
1968 if n:
1969 applied.append(statusentry(bin(n), name))
1969 applied.append(statusentry(bin(n), name))
1970 else:
1970 else:
1971 series.append(l)
1971 series.append(l)
1972 if datastart is None:
1972 if datastart is None:
1973 self.ui.warn(_("no saved patch data found\n"))
1973 self.ui.warn(_("no saved patch data found\n"))
1974 return 1
1974 return 1
1975 self.ui.warn(_("restoring status: %s\n") % lines[0])
1975 self.ui.warn(_("restoring status: %s\n") % lines[0])
1976 self.fullseries = series
1976 self.fullseries = series
1977 self.applied = applied
1977 self.applied = applied
1978 self.parseseries()
1978 self.parseseries()
1979 self.seriesdirty = True
1979 self.seriesdirty = True
1980 self.applieddirty = True
1980 self.applieddirty = True
1981 heads = repo.changelog.heads()
1981 heads = repo.changelog.heads()
1982 if delete:
1982 if delete:
1983 if rev not in heads:
1983 if rev not in heads:
1984 self.ui.warn(_("save entry has children, leaving it alone\n"))
1984 self.ui.warn(_("save entry has children, leaving it alone\n"))
1985 else:
1985 else:
1986 self.ui.warn(_("removing save entry %s\n") % short(rev))
1986 self.ui.warn(_("removing save entry %s\n") % short(rev))
1987 pp = repo.dirstate.parents()
1987 pp = repo.dirstate.parents()
1988 if rev in pp:
1988 if rev in pp:
1989 update = True
1989 update = True
1990 else:
1990 else:
1991 update = False
1991 update = False
1992 strip(self.ui, repo, [rev], update=update, backup=False)
1992 strip(self.ui, repo, [rev], update=update, backup=False)
1993 if qpp:
1993 if qpp:
1994 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1994 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1995 (short(qpp[0]), short(qpp[1])))
1995 (short(qpp[0]), short(qpp[1])))
1996 if qupdate:
1996 if qupdate:
1997 self.ui.status(_("updating queue directory\n"))
1997 self.ui.status(_("updating queue directory\n"))
1998 r = self.qrepo()
1998 r = self.qrepo()
1999 if not r:
1999 if not r:
2000 self.ui.warn(_("unable to load queue repository\n"))
2000 self.ui.warn(_("unable to load queue repository\n"))
2001 return 1
2001 return 1
2002 hg.clean(r, qpp[0])
2002 hg.clean(r, qpp[0])
2003
2003
2004 def save(self, repo, msg=None):
2004 def save(self, repo, msg=None):
2005 if not self.applied:
2005 if not self.applied:
2006 self.ui.warn(_("save: no patches applied, exiting\n"))
2006 self.ui.warn(_("save: no patches applied, exiting\n"))
2007 return 1
2007 return 1
2008 if self.issaveline(self.applied[-1]):
2008 if self.issaveline(self.applied[-1]):
2009 self.ui.warn(_("status is already saved\n"))
2009 self.ui.warn(_("status is already saved\n"))
2010 return 1
2010 return 1
2011
2011
2012 if not msg:
2012 if not msg:
2013 msg = _("hg patches saved state")
2013 msg = _("hg patches saved state")
2014 else:
2014 else:
2015 msg = "hg patches: " + msg.rstrip('\r\n')
2015 msg = "hg patches: " + msg.rstrip('\r\n')
2016 r = self.qrepo()
2016 r = self.qrepo()
2017 if r:
2017 if r:
2018 pp = r.dirstate.parents()
2018 pp = r.dirstate.parents()
2019 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
2019 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
2020 msg += "\n\nPatch Data:\n"
2020 msg += "\n\nPatch Data:\n"
2021 msg += ''.join('%s\n' % x for x in self.applied)
2021 msg += ''.join('%s\n' % x for x in self.applied)
2022 msg += ''.join(':%s\n' % x for x in self.fullseries)
2022 msg += ''.join(':%s\n' % x for x in self.fullseries)
2023 n = repo.commit(msg, force=True)
2023 n = repo.commit(msg, force=True)
2024 if not n:
2024 if not n:
2025 self.ui.warn(_("repo commit failed\n"))
2025 self.ui.warn(_("repo commit failed\n"))
2026 return 1
2026 return 1
2027 self.applied.append(statusentry(n, '.hg.patches.save.line'))
2027 self.applied.append(statusentry(n, '.hg.patches.save.line'))
2028 self.applieddirty = True
2028 self.applieddirty = True
2029 self.removeundo(repo)
2029 self.removeundo(repo)
2030
2030
2031 def fullseriesend(self):
2031 def fullseriesend(self):
2032 if self.applied:
2032 if self.applied:
2033 p = self.applied[-1].name
2033 p = self.applied[-1].name
2034 end = self.findseries(p)
2034 end = self.findseries(p)
2035 if end is None:
2035 if end is None:
2036 return len(self.fullseries)
2036 return len(self.fullseries)
2037 return end + 1
2037 return end + 1
2038 return 0
2038 return 0
2039
2039
2040 def seriesend(self, all_patches=False):
2040 def seriesend(self, all_patches=False):
2041 """If all_patches is False, return the index of the next pushable patch
2041 """If all_patches is False, return the index of the next pushable patch
2042 in the series, or the series length. If all_patches is True, return the
2042 in the series, or the series length. If all_patches is True, return the
2043 index of the first patch past the last applied one.
2043 index of the first patch past the last applied one.
2044 """
2044 """
2045 end = 0
2045 end = 0
2046 def nextpatch(start):
2046 def nextpatch(start):
2047 if all_patches or start >= len(self.series):
2047 if all_patches or start >= len(self.series):
2048 return start
2048 return start
2049 for i in xrange(start, len(self.series)):
2049 for i in xrange(start, len(self.series)):
2050 p, reason = self.pushable(i)
2050 p, reason = self.pushable(i)
2051 if p:
2051 if p:
2052 return i
2052 return i
2053 self.explainpushable(i)
2053 self.explainpushable(i)
2054 return len(self.series)
2054 return len(self.series)
2055 if self.applied:
2055 if self.applied:
2056 p = self.applied[-1].name
2056 p = self.applied[-1].name
2057 try:
2057 try:
2058 end = self.series.index(p)
2058 end = self.series.index(p)
2059 except ValueError:
2059 except ValueError:
2060 return 0
2060 return 0
2061 return nextpatch(end + 1)
2061 return nextpatch(end + 1)
2062 return nextpatch(end)
2062 return nextpatch(end)
2063
2063
2064 def appliedname(self, index):
2064 def appliedname(self, index):
2065 pname = self.applied[index].name
2065 pname = self.applied[index].name
2066 if not self.ui.verbose:
2066 if not self.ui.verbose:
2067 p = pname
2067 p = pname
2068 else:
2068 else:
2069 p = str(self.series.index(pname)) + " " + pname
2069 p = str(self.series.index(pname)) + " " + pname
2070 return p
2070 return p
2071
2071
2072 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
2072 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
2073 force=None, git=False):
2073 force=None, git=False):
2074 def checkseries(patchname):
2074 def checkseries(patchname):
2075 if patchname in self.series:
2075 if patchname in self.series:
2076 raise error.Abort(_('patch %s is already in the series file')
2076 raise error.Abort(_('patch %s is already in the series file')
2077 % patchname)
2077 % patchname)
2078
2078
2079 if rev:
2079 if rev:
2080 if files:
2080 if files:
2081 raise error.Abort(_('option "-r" not valid when importing '
2081 raise error.Abort(_('option "-r" not valid when importing '
2082 'files'))
2082 'files'))
2083 rev = scmutil.revrange(repo, rev)
2083 rev = scmutil.revrange(repo, rev)
2084 rev.sort(reverse=True)
2084 rev.sort(reverse=True)
2085 elif not files:
2085 elif not files:
2086 raise error.Abort(_('no files or revisions specified'))
2086 raise error.Abort(_('no files or revisions specified'))
2087 if (len(files) > 1 or len(rev) > 1) and patchname:
2087 if (len(files) > 1 or len(rev) > 1) and patchname:
2088 raise error.Abort(_('option "-n" not valid when importing multiple '
2088 raise error.Abort(_('option "-n" not valid when importing multiple '
2089 'patches'))
2089 'patches'))
2090 imported = []
2090 imported = []
2091 if rev:
2091 if rev:
2092 # If mq patches are applied, we can only import revisions
2092 # If mq patches are applied, we can only import revisions
2093 # that form a linear path to qbase.
2093 # that form a linear path to qbase.
2094 # Otherwise, they should form a linear path to a head.
2094 # Otherwise, they should form a linear path to a head.
2095 heads = repo.changelog.heads(repo.changelog.node(rev.first()))
2095 heads = repo.changelog.heads(repo.changelog.node(rev.first()))
2096 if len(heads) > 1:
2096 if len(heads) > 1:
2097 raise error.Abort(_('revision %d is the root of more than one '
2097 raise error.Abort(_('revision %d is the root of more than one '
2098 'branch') % rev.last())
2098 'branch') % rev.last())
2099 if self.applied:
2099 if self.applied:
2100 base = repo.changelog.node(rev.first())
2100 base = repo.changelog.node(rev.first())
2101 if base in [n.node for n in self.applied]:
2101 if base in [n.node for n in self.applied]:
2102 raise error.Abort(_('revision %d is already managed')
2102 raise error.Abort(_('revision %d is already managed')
2103 % rev.first())
2103 % rev.first())
2104 if heads != [self.applied[-1].node]:
2104 if heads != [self.applied[-1].node]:
2105 raise error.Abort(_('revision %d is not the parent of '
2105 raise error.Abort(_('revision %d is not the parent of '
2106 'the queue') % rev.first())
2106 'the queue') % rev.first())
2107 base = repo.changelog.rev(self.applied[0].node)
2107 base = repo.changelog.rev(self.applied[0].node)
2108 lastparent = repo.changelog.parentrevs(base)[0]
2108 lastparent = repo.changelog.parentrevs(base)[0]
2109 else:
2109 else:
2110 if heads != [repo.changelog.node(rev.first())]:
2110 if heads != [repo.changelog.node(rev.first())]:
2111 raise error.Abort(_('revision %d has unmanaged children')
2111 raise error.Abort(_('revision %d has unmanaged children')
2112 % rev.first())
2112 % rev.first())
2113 lastparent = None
2113 lastparent = None
2114
2114
2115 diffopts = self.diffopts({'git': git})
2115 diffopts = self.diffopts({'git': git})
2116 with repo.transaction('qimport') as tr:
2116 with repo.transaction('qimport') as tr:
2117 for r in rev:
2117 for r in rev:
2118 if not repo[r].mutable():
2118 if not repo[r].mutable():
2119 raise error.Abort(_('revision %d is not mutable') % r,
2119 raise error.Abort(_('revision %d is not mutable') % r,
2120 hint=_("see 'hg help phases' "
2120 hint=_("see 'hg help phases' "
2121 'for details'))
2121 'for details'))
2122 p1, p2 = repo.changelog.parentrevs(r)
2122 p1, p2 = repo.changelog.parentrevs(r)
2123 n = repo.changelog.node(r)
2123 n = repo.changelog.node(r)
2124 if p2 != nullrev:
2124 if p2 != nullrev:
2125 raise error.Abort(_('cannot import merge revision %d')
2125 raise error.Abort(_('cannot import merge revision %d')
2126 % r)
2126 % r)
2127 if lastparent and lastparent != r:
2127 if lastparent and lastparent != r:
2128 raise error.Abort(_('revision %d is not the parent of '
2128 raise error.Abort(_('revision %d is not the parent of '
2129 '%d')
2129 '%d')
2130 % (r, lastparent))
2130 % (r, lastparent))
2131 lastparent = p1
2131 lastparent = p1
2132
2132
2133 if not patchname:
2133 if not patchname:
2134 patchname = self.makepatchname(
2134 patchname = self.makepatchname(
2135 repo[r].description().split('\n', 1)[0],
2135 repo[r].description().split('\n', 1)[0],
2136 '%d.diff' % r)
2136 '%d.diff' % r)
2137 checkseries(patchname)
2137 checkseries(patchname)
2138 self.checkpatchname(patchname, force)
2138 self.checkpatchname(patchname, force)
2139 self.fullseries.insert(0, patchname)
2139 self.fullseries.insert(0, patchname)
2140
2140
2141 patchf = self.opener(patchname, "w")
2141 patchf = self.opener(patchname, "w")
2142 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
2142 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
2143 patchf.close()
2143 patchf.close()
2144
2144
2145 se = statusentry(n, patchname)
2145 se = statusentry(n, patchname)
2146 self.applied.insert(0, se)
2146 self.applied.insert(0, se)
2147
2147
2148 self.added.append(patchname)
2148 self.added.append(patchname)
2149 imported.append(patchname)
2149 imported.append(patchname)
2150 patchname = None
2150 patchname = None
2151 if rev and repo.ui.configbool('mq', 'secret', False):
2151 if rev and repo.ui.configbool('mq', 'secret', False):
2152 # if we added anything with --rev, move the secret root
2152 # if we added anything with --rev, move the secret root
2153 phases.retractboundary(repo, tr, phases.secret, [n])
2153 phases.retractboundary(repo, tr, phases.secret, [n])
2154 self.parseseries()
2154 self.parseseries()
2155 self.applieddirty = True
2155 self.applieddirty = True
2156 self.seriesdirty = True
2156 self.seriesdirty = True
2157
2157
2158 for i, filename in enumerate(files):
2158 for i, filename in enumerate(files):
2159 if existing:
2159 if existing:
2160 if filename == '-':
2160 if filename == '-':
2161 raise error.Abort(_('-e is incompatible with import from -')
2161 raise error.Abort(_('-e is incompatible with import from -')
2162 )
2162 )
2163 filename = normname(filename)
2163 filename = normname(filename)
2164 self.checkreservedname(filename)
2164 self.checkreservedname(filename)
2165 if util.url(filename).islocal():
2165 if util.url(filename).islocal():
2166 originpath = self.join(filename)
2166 originpath = self.join(filename)
2167 if not os.path.isfile(originpath):
2167 if not os.path.isfile(originpath):
2168 raise error.Abort(
2168 raise error.Abort(
2169 _("patch %s does not exist") % filename)
2169 _("patch %s does not exist") % filename)
2170
2170
2171 if patchname:
2171 if patchname:
2172 self.checkpatchname(patchname, force)
2172 self.checkpatchname(patchname, force)
2173
2173
2174 self.ui.write(_('renaming %s to %s\n')
2174 self.ui.write(_('renaming %s to %s\n')
2175 % (filename, patchname))
2175 % (filename, patchname))
2176 util.rename(originpath, self.join(patchname))
2176 util.rename(originpath, self.join(patchname))
2177 else:
2177 else:
2178 patchname = filename
2178 patchname = filename
2179
2179
2180 else:
2180 else:
2181 if filename == '-' and not patchname:
2181 if filename == '-' and not patchname:
2182 raise error.Abort(_('need --name to import a patch from -'))
2182 raise error.Abort(_('need --name to import a patch from -'))
2183 elif not patchname:
2183 elif not patchname:
2184 patchname = normname(os.path.basename(filename.rstrip('/')))
2184 patchname = normname(os.path.basename(filename.rstrip('/')))
2185 self.checkpatchname(patchname, force)
2185 self.checkpatchname(patchname, force)
2186 try:
2186 try:
2187 if filename == '-':
2187 if filename == '-':
2188 text = self.ui.fin.read()
2188 text = self.ui.fin.read()
2189 else:
2189 else:
2190 fp = hg.openpath(self.ui, filename)
2190 fp = hg.openpath(self.ui, filename)
2191 text = fp.read()
2191 text = fp.read()
2192 fp.close()
2192 fp.close()
2193 except (OSError, IOError):
2193 except (OSError, IOError):
2194 raise error.Abort(_("unable to read file %s") % filename)
2194 raise error.Abort(_("unable to read file %s") % filename)
2195 patchf = self.opener(patchname, "w")
2195 patchf = self.opener(patchname, "w")
2196 patchf.write(text)
2196 patchf.write(text)
2197 patchf.close()
2197 patchf.close()
2198 if not force:
2198 if not force:
2199 checkseries(patchname)
2199 checkseries(patchname)
2200 if patchname not in self.series:
2200 if patchname not in self.series:
2201 index = self.fullseriesend() + i
2201 index = self.fullseriesend() + i
2202 self.fullseries[index:index] = [patchname]
2202 self.fullseries[index:index] = [patchname]
2203 self.parseseries()
2203 self.parseseries()
2204 self.seriesdirty = True
2204 self.seriesdirty = True
2205 self.ui.warn(_("adding %s to series file\n") % patchname)
2205 self.ui.warn(_("adding %s to series file\n") % patchname)
2206 self.added.append(patchname)
2206 self.added.append(patchname)
2207 imported.append(patchname)
2207 imported.append(patchname)
2208 patchname = None
2208 patchname = None
2209
2209
2210 self.removeundo(repo)
2210 self.removeundo(repo)
2211 return imported
2211 return imported
2212
2212
2213 def fixkeepchangesopts(ui, opts):
2213 def fixkeepchangesopts(ui, opts):
2214 if (not ui.configbool('mq', 'keepchanges') or opts.get('force')
2214 if (not ui.configbool('mq', 'keepchanges') or opts.get('force')
2215 or opts.get('exact')):
2215 or opts.get('exact')):
2216 return opts
2216 return opts
2217 opts = dict(opts)
2217 opts = dict(opts)
2218 opts['keep_changes'] = True
2218 opts['keep_changes'] = True
2219 return opts
2219 return opts
2220
2220
2221 @command("qdelete|qremove|qrm",
2221 @command("qdelete|qremove|qrm",
2222 [('k', 'keep', None, _('keep patch file')),
2222 [('k', 'keep', None, _('keep patch file')),
2223 ('r', 'rev', [],
2223 ('r', 'rev', [],
2224 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2224 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2225 _('hg qdelete [-k] [PATCH]...'))
2225 _('hg qdelete [-k] [PATCH]...'))
2226 def delete(ui, repo, *patches, **opts):
2226 def delete(ui, repo, *patches, **opts):
2227 """remove patches from queue
2227 """remove patches from queue
2228
2228
2229 The patches must not be applied, and at least one patch is required. Exact
2229 The patches must not be applied, and at least one patch is required. Exact
2230 patch identifiers must be given. With -k/--keep, the patch files are
2230 patch identifiers must be given. With -k/--keep, the patch files are
2231 preserved in the patch directory.
2231 preserved in the patch directory.
2232
2232
2233 To stop managing a patch and move it into permanent history,
2233 To stop managing a patch and move it into permanent history,
2234 use the :hg:`qfinish` command."""
2234 use the :hg:`qfinish` command."""
2235 q = repo.mq
2235 q = repo.mq
2236 q.delete(repo, patches, opts)
2236 q.delete(repo, patches, opts)
2237 q.savedirty()
2237 q.savedirty()
2238 return 0
2238 return 0
2239
2239
2240 @command("qapplied",
2240 @command("qapplied",
2241 [('1', 'last', None, _('show only the preceding applied patch'))
2241 [('1', 'last', None, _('show only the preceding applied patch'))
2242 ] + seriesopts,
2242 ] + seriesopts,
2243 _('hg qapplied [-1] [-s] [PATCH]'))
2243 _('hg qapplied [-1] [-s] [PATCH]'))
2244 def applied(ui, repo, patch=None, **opts):
2244 def applied(ui, repo, patch=None, **opts):
2245 """print the patches already applied
2245 """print the patches already applied
2246
2246
2247 Returns 0 on success."""
2247 Returns 0 on success."""
2248
2248
2249 q = repo.mq
2249 q = repo.mq
2250
2250
2251 if patch:
2251 if patch:
2252 if patch not in q.series:
2252 if patch not in q.series:
2253 raise error.Abort(_("patch %s is not in series file") % patch)
2253 raise error.Abort(_("patch %s is not in series file") % patch)
2254 end = q.series.index(patch) + 1
2254 end = q.series.index(patch) + 1
2255 else:
2255 else:
2256 end = q.seriesend(True)
2256 end = q.seriesend(True)
2257
2257
2258 if opts.get('last') and not end:
2258 if opts.get('last') and not end:
2259 ui.write(_("no patches applied\n"))
2259 ui.write(_("no patches applied\n"))
2260 return 1
2260 return 1
2261 elif opts.get('last') and end == 1:
2261 elif opts.get('last') and end == 1:
2262 ui.write(_("only one patch applied\n"))
2262 ui.write(_("only one patch applied\n"))
2263 return 1
2263 return 1
2264 elif opts.get('last'):
2264 elif opts.get('last'):
2265 start = end - 2
2265 start = end - 2
2266 end = 1
2266 end = 1
2267 else:
2267 else:
2268 start = 0
2268 start = 0
2269
2269
2270 q.qseries(repo, length=end, start=start, status='A',
2270 q.qseries(repo, length=end, start=start, status='A',
2271 summary=opts.get('summary'))
2271 summary=opts.get('summary'))
2272
2272
2273
2273
2274 @command("qunapplied",
2274 @command("qunapplied",
2275 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2275 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2276 _('hg qunapplied [-1] [-s] [PATCH]'))
2276 _('hg qunapplied [-1] [-s] [PATCH]'))
2277 def unapplied(ui, repo, patch=None, **opts):
2277 def unapplied(ui, repo, patch=None, **opts):
2278 """print the patches not yet applied
2278 """print the patches not yet applied
2279
2279
2280 Returns 0 on success."""
2280 Returns 0 on success."""
2281
2281
2282 q = repo.mq
2282 q = repo.mq
2283 if patch:
2283 if patch:
2284 if patch not in q.series:
2284 if patch not in q.series:
2285 raise error.Abort(_("patch %s is not in series file") % patch)
2285 raise error.Abort(_("patch %s is not in series file") % patch)
2286 start = q.series.index(patch) + 1
2286 start = q.series.index(patch) + 1
2287 else:
2287 else:
2288 start = q.seriesend(True)
2288 start = q.seriesend(True)
2289
2289
2290 if start == len(q.series) and opts.get('first'):
2290 if start == len(q.series) and opts.get('first'):
2291 ui.write(_("all patches applied\n"))
2291 ui.write(_("all patches applied\n"))
2292 return 1
2292 return 1
2293
2293
2294 if opts.get('first'):
2294 if opts.get('first'):
2295 length = 1
2295 length = 1
2296 else:
2296 else:
2297 length = None
2297 length = None
2298 q.qseries(repo, start=start, length=length, status='U',
2298 q.qseries(repo, start=start, length=length, status='U',
2299 summary=opts.get('summary'))
2299 summary=opts.get('summary'))
2300
2300
2301 @command("qimport",
2301 @command("qimport",
2302 [('e', 'existing', None, _('import file in patch directory')),
2302 [('e', 'existing', None, _('import file in patch directory')),
2303 ('n', 'name', '',
2303 ('n', 'name', '',
2304 _('name of patch file'), _('NAME')),
2304 _('name of patch file'), _('NAME')),
2305 ('f', 'force', None, _('overwrite existing files')),
2305 ('f', 'force', None, _('overwrite existing files')),
2306 ('r', 'rev', [],
2306 ('r', 'rev', [],
2307 _('place existing revisions under mq control'), _('REV')),
2307 _('place existing revisions under mq control'), _('REV')),
2308 ('g', 'git', None, _('use git extended diff format')),
2308 ('g', 'git', None, _('use git extended diff format')),
2309 ('P', 'push', None, _('qpush after importing'))],
2309 ('P', 'push', None, _('qpush after importing'))],
2310 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'))
2310 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'))
2311 def qimport(ui, repo, *filename, **opts):
2311 def qimport(ui, repo, *filename, **opts):
2312 """import a patch or existing changeset
2312 """import a patch or existing changeset
2313
2313
2314 The patch is inserted into the series after the last applied
2314 The patch is inserted into the series after the last applied
2315 patch. If no patches have been applied, qimport prepends the patch
2315 patch. If no patches have been applied, qimport prepends the patch
2316 to the series.
2316 to the series.
2317
2317
2318 The patch will have the same name as its source file unless you
2318 The patch will have the same name as its source file unless you
2319 give it a new one with -n/--name.
2319 give it a new one with -n/--name.
2320
2320
2321 You can register an existing patch inside the patch directory with
2321 You can register an existing patch inside the patch directory with
2322 the -e/--existing flag.
2322 the -e/--existing flag.
2323
2323
2324 With -f/--force, an existing patch of the same name will be
2324 With -f/--force, an existing patch of the same name will be
2325 overwritten.
2325 overwritten.
2326
2326
2327 An existing changeset may be placed under mq control with -r/--rev
2327 An existing changeset may be placed under mq control with -r/--rev
2328 (e.g. qimport --rev . -n patch will place the current revision
2328 (e.g. qimport --rev . -n patch will place the current revision
2329 under mq control). With -g/--git, patches imported with --rev will
2329 under mq control). With -g/--git, patches imported with --rev will
2330 use the git diff format. See the diffs help topic for information
2330 use the git diff format. See the diffs help topic for information
2331 on why this is important for preserving rename/copy information
2331 on why this is important for preserving rename/copy information
2332 and permission changes. Use :hg:`qfinish` to remove changesets
2332 and permission changes. Use :hg:`qfinish` to remove changesets
2333 from mq control.
2333 from mq control.
2334
2334
2335 To import a patch from standard input, pass - as the patch file.
2335 To import a patch from standard input, pass - as the patch file.
2336 When importing from standard input, a patch name must be specified
2336 When importing from standard input, a patch name must be specified
2337 using the --name flag.
2337 using the --name flag.
2338
2338
2339 To import an existing patch while renaming it::
2339 To import an existing patch while renaming it::
2340
2340
2341 hg qimport -e existing-patch -n new-name
2341 hg qimport -e existing-patch -n new-name
2342
2342
2343 Returns 0 if import succeeded.
2343 Returns 0 if import succeeded.
2344 """
2344 """
2345 with repo.lock(): # cause this may move phase
2345 with repo.lock(): # cause this may move phase
2346 q = repo.mq
2346 q = repo.mq
2347 try:
2347 try:
2348 imported = q.qimport(
2348 imported = q.qimport(
2349 repo, filename, patchname=opts.get('name'),
2349 repo, filename, patchname=opts.get('name'),
2350 existing=opts.get('existing'), force=opts.get('force'),
2350 existing=opts.get('existing'), force=opts.get('force'),
2351 rev=opts.get('rev'), git=opts.get('git'))
2351 rev=opts.get('rev'), git=opts.get('git'))
2352 finally:
2352 finally:
2353 q.savedirty()
2353 q.savedirty()
2354
2354
2355 if imported and opts.get('push') and not opts.get('rev'):
2355 if imported and opts.get('push') and not opts.get('rev'):
2356 return q.push(repo, imported[-1])
2356 return q.push(repo, imported[-1])
2357 return 0
2357 return 0
2358
2358
2359 def qinit(ui, repo, create):
2359 def qinit(ui, repo, create):
2360 """initialize a new queue repository
2360 """initialize a new queue repository
2361
2361
2362 This command also creates a series file for ordering patches, and
2362 This command also creates a series file for ordering patches, and
2363 an mq-specific .hgignore file in the queue repository, to exclude
2363 an mq-specific .hgignore file in the queue repository, to exclude
2364 the status and guards files (these contain mostly transient state).
2364 the status and guards files (these contain mostly transient state).
2365
2365
2366 Returns 0 if initialization succeeded."""
2366 Returns 0 if initialization succeeded."""
2367 q = repo.mq
2367 q = repo.mq
2368 r = q.init(repo, create)
2368 r = q.init(repo, create)
2369 q.savedirty()
2369 q.savedirty()
2370 if r:
2370 if r:
2371 if not os.path.exists(r.wjoin('.hgignore')):
2371 if not os.path.exists(r.wjoin('.hgignore')):
2372 fp = r.wvfs('.hgignore', 'w')
2372 fp = r.wvfs('.hgignore', 'w')
2373 fp.write('^\\.hg\n')
2373 fp.write('^\\.hg\n')
2374 fp.write('^\\.mq\n')
2374 fp.write('^\\.mq\n')
2375 fp.write('syntax: glob\n')
2375 fp.write('syntax: glob\n')
2376 fp.write('status\n')
2376 fp.write('status\n')
2377 fp.write('guards\n')
2377 fp.write('guards\n')
2378 fp.close()
2378 fp.close()
2379 if not os.path.exists(r.wjoin('series')):
2379 if not os.path.exists(r.wjoin('series')):
2380 r.wvfs('series', 'w').close()
2380 r.wvfs('series', 'w').close()
2381 r[None].add(['.hgignore', 'series'])
2381 r[None].add(['.hgignore', 'series'])
2382 commands.add(ui, r)
2382 commands.add(ui, r)
2383 return 0
2383 return 0
2384
2384
2385 @command("^qinit",
2385 @command("^qinit",
2386 [('c', 'create-repo', None, _('create queue repository'))],
2386 [('c', 'create-repo', None, _('create queue repository'))],
2387 _('hg qinit [-c]'))
2387 _('hg qinit [-c]'))
2388 def init(ui, repo, **opts):
2388 def init(ui, repo, **opts):
2389 """init a new queue repository (DEPRECATED)
2389 """init a new queue repository (DEPRECATED)
2390
2390
2391 The queue repository is unversioned by default. If
2391 The queue repository is unversioned by default. If
2392 -c/--create-repo is specified, qinit will create a separate nested
2392 -c/--create-repo is specified, qinit will create a separate nested
2393 repository for patches (qinit -c may also be run later to convert
2393 repository for patches (qinit -c may also be run later to convert
2394 an unversioned patch repository into a versioned one). You can use
2394 an unversioned patch repository into a versioned one). You can use
2395 qcommit to commit changes to this queue repository.
2395 qcommit to commit changes to this queue repository.
2396
2396
2397 This command is deprecated. Without -c, it's implied by other relevant
2397 This command is deprecated. Without -c, it's implied by other relevant
2398 commands. With -c, use :hg:`init --mq` instead."""
2398 commands. With -c, use :hg:`init --mq` instead."""
2399 return qinit(ui, repo, create=opts.get('create_repo'))
2399 return qinit(ui, repo, create=opts.get('create_repo'))
2400
2400
2401 @command("qclone",
2401 @command("qclone",
2402 [('', 'pull', None, _('use pull protocol to copy metadata')),
2402 [('', 'pull', None, _('use pull protocol to copy metadata')),
2403 ('U', 'noupdate', None,
2403 ('U', 'noupdate', None,
2404 _('do not update the new working directories')),
2404 _('do not update the new working directories')),
2405 ('', 'uncompressed', None,
2405 ('', 'uncompressed', None,
2406 _('use uncompressed transfer (fast over LAN)')),
2406 _('use uncompressed transfer (fast over LAN)')),
2407 ('p', 'patches', '',
2407 ('p', 'patches', '',
2408 _('location of source patch repository'), _('REPO')),
2408 _('location of source patch repository'), _('REPO')),
2409 ] + commands.remoteopts,
2409 ] + commands.remoteopts,
2410 _('hg qclone [OPTION]... SOURCE [DEST]'),
2410 _('hg qclone [OPTION]... SOURCE [DEST]'),
2411 norepo=True)
2411 norepo=True)
2412 def clone(ui, source, dest=None, **opts):
2412 def clone(ui, source, dest=None, **opts):
2413 '''clone main and patch repository at same time
2413 '''clone main and patch repository at same time
2414
2414
2415 If source is local, destination will have no patches applied. If
2415 If source is local, destination will have no patches applied. If
2416 source is remote, this command can not check if patches are
2416 source is remote, this command can not check if patches are
2417 applied in source, so cannot guarantee that patches are not
2417 applied in source, so cannot guarantee that patches are not
2418 applied in destination. If you clone remote repository, be sure
2418 applied in destination. If you clone remote repository, be sure
2419 before that it has no patches applied.
2419 before that it has no patches applied.
2420
2420
2421 Source patch repository is looked for in <src>/.hg/patches by
2421 Source patch repository is looked for in <src>/.hg/patches by
2422 default. Use -p <url> to change.
2422 default. Use -p <url> to change.
2423
2423
2424 The patch directory must be a nested Mercurial repository, as
2424 The patch directory must be a nested Mercurial repository, as
2425 would be created by :hg:`init --mq`.
2425 would be created by :hg:`init --mq`.
2426
2426
2427 Return 0 on success.
2427 Return 0 on success.
2428 '''
2428 '''
2429 def patchdir(repo):
2429 def patchdir(repo):
2430 """compute a patch repo url from a repo object"""
2430 """compute a patch repo url from a repo object"""
2431 url = repo.url()
2431 url = repo.url()
2432 if url.endswith('/'):
2432 if url.endswith('/'):
2433 url = url[:-1]
2433 url = url[:-1]
2434 return url + '/.hg/patches'
2434 return url + '/.hg/patches'
2435
2435
2436 # main repo (destination and sources)
2436 # main repo (destination and sources)
2437 if dest is None:
2437 if dest is None:
2438 dest = hg.defaultdest(source)
2438 dest = hg.defaultdest(source)
2439 sr = hg.peer(ui, opts, ui.expandpath(source))
2439 sr = hg.peer(ui, opts, ui.expandpath(source))
2440
2440
2441 # patches repo (source only)
2441 # patches repo (source only)
2442 if opts.get('patches'):
2442 if opts.get('patches'):
2443 patchespath = ui.expandpath(opts.get('patches'))
2443 patchespath = ui.expandpath(opts.get('patches'))
2444 else:
2444 else:
2445 patchespath = patchdir(sr)
2445 patchespath = patchdir(sr)
2446 try:
2446 try:
2447 hg.peer(ui, opts, patchespath)
2447 hg.peer(ui, opts, patchespath)
2448 except error.RepoError:
2448 except error.RepoError:
2449 raise error.Abort(_('versioned patch repository not found'
2449 raise error.Abort(_('versioned patch repository not found'
2450 ' (see init --mq)'))
2450 ' (see init --mq)'))
2451 qbase, destrev = None, None
2451 qbase, destrev = None, None
2452 if sr.local():
2452 if sr.local():
2453 repo = sr.local()
2453 repo = sr.local()
2454 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2454 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2455 qbase = repo.mq.applied[0].node
2455 qbase = repo.mq.applied[0].node
2456 if not hg.islocal(dest):
2456 if not hg.islocal(dest):
2457 heads = set(repo.heads())
2457 heads = set(repo.heads())
2458 destrev = list(heads.difference(repo.heads(qbase)))
2458 destrev = list(heads.difference(repo.heads(qbase)))
2459 destrev.append(repo.changelog.parents(qbase)[0])
2459 destrev.append(repo.changelog.parents(qbase)[0])
2460 elif sr.capable('lookup'):
2460 elif sr.capable('lookup'):
2461 try:
2461 try:
2462 qbase = sr.lookup('qbase')
2462 qbase = sr.lookup('qbase')
2463 except error.RepoError:
2463 except error.RepoError:
2464 pass
2464 pass
2465
2465
2466 ui.note(_('cloning main repository\n'))
2466 ui.note(_('cloning main repository\n'))
2467 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2467 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2468 pull=opts.get('pull'),
2468 pull=opts.get('pull'),
2469 rev=destrev,
2469 rev=destrev,
2470 update=False,
2470 update=False,
2471 stream=opts.get('uncompressed'))
2471 stream=opts.get('uncompressed'))
2472
2472
2473 ui.note(_('cloning patch repository\n'))
2473 ui.note(_('cloning patch repository\n'))
2474 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2474 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2475 pull=opts.get('pull'), update=not opts.get('noupdate'),
2475 pull=opts.get('pull'), update=not opts.get('noupdate'),
2476 stream=opts.get('uncompressed'))
2476 stream=opts.get('uncompressed'))
2477
2477
2478 if dr.local():
2478 if dr.local():
2479 repo = dr.local()
2479 repo = dr.local()
2480 if qbase:
2480 if qbase:
2481 ui.note(_('stripping applied patches from destination '
2481 ui.note(_('stripping applied patches from destination '
2482 'repository\n'))
2482 'repository\n'))
2483 strip(ui, repo, [qbase], update=False, backup=None)
2483 strip(ui, repo, [qbase], update=False, backup=None)
2484 if not opts.get('noupdate'):
2484 if not opts.get('noupdate'):
2485 ui.note(_('updating destination repository\n'))
2485 ui.note(_('updating destination repository\n'))
2486 hg.update(repo, repo.changelog.tip())
2486 hg.update(repo, repo.changelog.tip())
2487
2487
2488 @command("qcommit|qci",
2488 @command("qcommit|qci",
2489 commands.table["^commit|ci"][1],
2489 commands.table["^commit|ci"][1],
2490 _('hg qcommit [OPTION]... [FILE]...'),
2490 _('hg qcommit [OPTION]... [FILE]...'),
2491 inferrepo=True)
2491 inferrepo=True)
2492 def commit(ui, repo, *pats, **opts):
2492 def commit(ui, repo, *pats, **opts):
2493 """commit changes in the queue repository (DEPRECATED)
2493 """commit changes in the queue repository (DEPRECATED)
2494
2494
2495 This command is deprecated; use :hg:`commit --mq` instead."""
2495 This command is deprecated; use :hg:`commit --mq` instead."""
2496 q = repo.mq
2496 q = repo.mq
2497 r = q.qrepo()
2497 r = q.qrepo()
2498 if not r:
2498 if not r:
2499 raise error.Abort('no queue repository')
2499 raise error.Abort('no queue repository')
2500 commands.commit(r.ui, r, *pats, **opts)
2500 commands.commit(r.ui, r, *pats, **opts)
2501
2501
2502 @command("qseries",
2502 @command("qseries",
2503 [('m', 'missing', None, _('print patches not in series')),
2503 [('m', 'missing', None, _('print patches not in series')),
2504 ] + seriesopts,
2504 ] + seriesopts,
2505 _('hg qseries [-ms]'))
2505 _('hg qseries [-ms]'))
2506 def series(ui, repo, **opts):
2506 def series(ui, repo, **opts):
2507 """print the entire series file
2507 """print the entire series file
2508
2508
2509 Returns 0 on success."""
2509 Returns 0 on success."""
2510 repo.mq.qseries(repo, missing=opts.get('missing'),
2510 repo.mq.qseries(repo, missing=opts.get('missing'),
2511 summary=opts.get('summary'))
2511 summary=opts.get('summary'))
2512 return 0
2512 return 0
2513
2513
2514 @command("qtop", seriesopts, _('hg qtop [-s]'))
2514 @command("qtop", seriesopts, _('hg qtop [-s]'))
2515 def top(ui, repo, **opts):
2515 def top(ui, repo, **opts):
2516 """print the name of the current patch
2516 """print the name of the current patch
2517
2517
2518 Returns 0 on success."""
2518 Returns 0 on success."""
2519 q = repo.mq
2519 q = repo.mq
2520 if q.applied:
2520 if q.applied:
2521 t = q.seriesend(True)
2521 t = q.seriesend(True)
2522 else:
2522 else:
2523 t = 0
2523 t = 0
2524
2524
2525 if t:
2525 if t:
2526 q.qseries(repo, start=t - 1, length=1, status='A',
2526 q.qseries(repo, start=t - 1, length=1, status='A',
2527 summary=opts.get('summary'))
2527 summary=opts.get('summary'))
2528 else:
2528 else:
2529 ui.write(_("no patches applied\n"))
2529 ui.write(_("no patches applied\n"))
2530 return 1
2530 return 1
2531
2531
2532 @command("qnext", seriesopts, _('hg qnext [-s]'))
2532 @command("qnext", seriesopts, _('hg qnext [-s]'))
2533 def next(ui, repo, **opts):
2533 def next(ui, repo, **opts):
2534 """print the name of the next pushable patch
2534 """print the name of the next pushable patch
2535
2535
2536 Returns 0 on success."""
2536 Returns 0 on success."""
2537 q = repo.mq
2537 q = repo.mq
2538 end = q.seriesend()
2538 end = q.seriesend()
2539 if end == len(q.series):
2539 if end == len(q.series):
2540 ui.write(_("all patches applied\n"))
2540 ui.write(_("all patches applied\n"))
2541 return 1
2541 return 1
2542 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2542 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2543
2543
2544 @command("qprev", seriesopts, _('hg qprev [-s]'))
2544 @command("qprev", seriesopts, _('hg qprev [-s]'))
2545 def prev(ui, repo, **opts):
2545 def prev(ui, repo, **opts):
2546 """print the name of the preceding applied patch
2546 """print the name of the preceding applied patch
2547
2547
2548 Returns 0 on success."""
2548 Returns 0 on success."""
2549 q = repo.mq
2549 q = repo.mq
2550 l = len(q.applied)
2550 l = len(q.applied)
2551 if l == 1:
2551 if l == 1:
2552 ui.write(_("only one patch applied\n"))
2552 ui.write(_("only one patch applied\n"))
2553 return 1
2553 return 1
2554 if not l:
2554 if not l:
2555 ui.write(_("no patches applied\n"))
2555 ui.write(_("no patches applied\n"))
2556 return 1
2556 return 1
2557 idx = q.series.index(q.applied[-2].name)
2557 idx = q.series.index(q.applied[-2].name)
2558 q.qseries(repo, start=idx, length=1, status='A',
2558 q.qseries(repo, start=idx, length=1, status='A',
2559 summary=opts.get('summary'))
2559 summary=opts.get('summary'))
2560
2560
2561 def setupheaderopts(ui, opts):
2561 def setupheaderopts(ui, opts):
2562 if not opts.get('user') and opts.get('currentuser'):
2562 if not opts.get('user') and opts.get('currentuser'):
2563 opts['user'] = ui.username()
2563 opts['user'] = ui.username()
2564 if not opts.get('date') and opts.get('currentdate'):
2564 if not opts.get('date') and opts.get('currentdate'):
2565 opts['date'] = "%d %d" % util.makedate()
2565 opts['date'] = "%d %d" % util.makedate()
2566
2566
2567 @command("^qnew",
2567 @command("^qnew",
2568 [('e', 'edit', None, _('invoke editor on commit messages')),
2568 [('e', 'edit', None, _('invoke editor on commit messages')),
2569 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2569 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2570 ('g', 'git', None, _('use git extended diff format')),
2570 ('g', 'git', None, _('use git extended diff format')),
2571 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2571 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2572 ('u', 'user', '',
2572 ('u', 'user', '',
2573 _('add "From: <USER>" to patch'), _('USER')),
2573 _('add "From: <USER>" to patch'), _('USER')),
2574 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2574 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2575 ('d', 'date', '',
2575 ('d', 'date', '',
2576 _('add "Date: <DATE>" to patch'), _('DATE'))
2576 _('add "Date: <DATE>" to patch'), _('DATE'))
2577 ] + commands.walkopts + commands.commitopts,
2577 ] + commands.walkopts + commands.commitopts,
2578 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'),
2578 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'),
2579 inferrepo=True)
2579 inferrepo=True)
2580 def new(ui, repo, patch, *args, **opts):
2580 def new(ui, repo, patch, *args, **opts):
2581 """create a new patch
2581 """create a new patch
2582
2582
2583 qnew creates a new patch on top of the currently-applied patch (if
2583 qnew creates a new patch on top of the currently-applied patch (if
2584 any). The patch will be initialized with any outstanding changes
2584 any). The patch will be initialized with any outstanding changes
2585 in the working directory. You may also use -I/--include,
2585 in the working directory. You may also use -I/--include,
2586 -X/--exclude, and/or a list of files after the patch name to add
2586 -X/--exclude, and/or a list of files after the patch name to add
2587 only changes to matching files to the new patch, leaving the rest
2587 only changes to matching files to the new patch, leaving the rest
2588 as uncommitted modifications.
2588 as uncommitted modifications.
2589
2589
2590 -u/--user and -d/--date can be used to set the (given) user and
2590 -u/--user and -d/--date can be used to set the (given) user and
2591 date, respectively. -U/--currentuser and -D/--currentdate set user
2591 date, respectively. -U/--currentuser and -D/--currentdate set user
2592 to current user and date to current date.
2592 to current user and date to current date.
2593
2593
2594 -e/--edit, -m/--message or -l/--logfile set the patch header as
2594 -e/--edit, -m/--message or -l/--logfile set the patch header as
2595 well as the commit message. If none is specified, the header is
2595 well as the commit message. If none is specified, the header is
2596 empty and the commit message is '[mq]: PATCH'.
2596 empty and the commit message is '[mq]: PATCH'.
2597
2597
2598 Use the -g/--git option to keep the patch in the git extended diff
2598 Use the -g/--git option to keep the patch in the git extended diff
2599 format. Read the diffs help topic for more information on why this
2599 format. Read the diffs help topic for more information on why this
2600 is important for preserving permission changes and copy/rename
2600 is important for preserving permission changes and copy/rename
2601 information.
2601 information.
2602
2602
2603 Returns 0 on successful creation of a new patch.
2603 Returns 0 on successful creation of a new patch.
2604 """
2604 """
2605 msg = cmdutil.logmessage(ui, opts)
2605 msg = cmdutil.logmessage(ui, opts)
2606 q = repo.mq
2606 q = repo.mq
2607 opts['msg'] = msg
2607 opts['msg'] = msg
2608 setupheaderopts(ui, opts)
2608 setupheaderopts(ui, opts)
2609 q.new(repo, patch, *args, **opts)
2609 q.new(repo, patch, *args, **opts)
2610 q.savedirty()
2610 q.savedirty()
2611 return 0
2611 return 0
2612
2612
2613 @command("^qrefresh",
2613 @command("^qrefresh",
2614 [('e', 'edit', None, _('invoke editor on commit messages')),
2614 [('e', 'edit', None, _('invoke editor on commit messages')),
2615 ('g', 'git', None, _('use git extended diff format')),
2615 ('g', 'git', None, _('use git extended diff format')),
2616 ('s', 'short', None,
2616 ('s', 'short', None,
2617 _('refresh only files already in the patch and specified files')),
2617 _('refresh only files already in the patch and specified files')),
2618 ('U', 'currentuser', None,
2618 ('U', 'currentuser', None,
2619 _('add/update author field in patch with current user')),
2619 _('add/update author field in patch with current user')),
2620 ('u', 'user', '',
2620 ('u', 'user', '',
2621 _('add/update author field in patch with given user'), _('USER')),
2621 _('add/update author field in patch with given user'), _('USER')),
2622 ('D', 'currentdate', None,
2622 ('D', 'currentdate', None,
2623 _('add/update date field in patch with current date')),
2623 _('add/update date field in patch with current date')),
2624 ('d', 'date', '',
2624 ('d', 'date', '',
2625 _('add/update date field in patch with given date'), _('DATE'))
2625 _('add/update date field in patch with given date'), _('DATE'))
2626 ] + commands.walkopts + commands.commitopts,
2626 ] + commands.walkopts + commands.commitopts,
2627 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'),
2627 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'),
2628 inferrepo=True)
2628 inferrepo=True)
2629 def refresh(ui, repo, *pats, **opts):
2629 def refresh(ui, repo, *pats, **opts):
2630 """update the current patch
2630 """update the current patch
2631
2631
2632 If any file patterns are provided, the refreshed patch will
2632 If any file patterns are provided, the refreshed patch will
2633 contain only the modifications that match those patterns; the
2633 contain only the modifications that match those patterns; the
2634 remaining modifications will remain in the working directory.
2634 remaining modifications will remain in the working directory.
2635
2635
2636 If -s/--short is specified, files currently included in the patch
2636 If -s/--short is specified, files currently included in the patch
2637 will be refreshed just like matched files and remain in the patch.
2637 will be refreshed just like matched files and remain in the patch.
2638
2638
2639 If -e/--edit is specified, Mercurial will start your configured editor for
2639 If -e/--edit is specified, Mercurial will start your configured editor for
2640 you to enter a message. In case qrefresh fails, you will find a backup of
2640 you to enter a message. In case qrefresh fails, you will find a backup of
2641 your message in ``.hg/last-message.txt``.
2641 your message in ``.hg/last-message.txt``.
2642
2642
2643 hg add/remove/copy/rename work as usual, though you might want to
2643 hg add/remove/copy/rename work as usual, though you might want to
2644 use git-style patches (-g/--git or [diff] git=1) to track copies
2644 use git-style patches (-g/--git or [diff] git=1) to track copies
2645 and renames. See the diffs help topic for more information on the
2645 and renames. See the diffs help topic for more information on the
2646 git diff format.
2646 git diff format.
2647
2647
2648 Returns 0 on success.
2648 Returns 0 on success.
2649 """
2649 """
2650 q = repo.mq
2650 q = repo.mq
2651 message = cmdutil.logmessage(ui, opts)
2651 message = cmdutil.logmessage(ui, opts)
2652 setupheaderopts(ui, opts)
2652 setupheaderopts(ui, opts)
2653 with repo.wlock():
2653 with repo.wlock():
2654 ret = q.refresh(repo, pats, msg=message, **opts)
2654 ret = q.refresh(repo, pats, msg=message, **opts)
2655 q.savedirty()
2655 q.savedirty()
2656 return ret
2656 return ret
2657
2657
2658 @command("^qdiff",
2658 @command("^qdiff",
2659 commands.diffopts + commands.diffopts2 + commands.walkopts,
2659 commands.diffopts + commands.diffopts2 + commands.walkopts,
2660 _('hg qdiff [OPTION]... [FILE]...'),
2660 _('hg qdiff [OPTION]... [FILE]...'),
2661 inferrepo=True)
2661 inferrepo=True)
2662 def diff(ui, repo, *pats, **opts):
2662 def diff(ui, repo, *pats, **opts):
2663 """diff of the current patch and subsequent modifications
2663 """diff of the current patch and subsequent modifications
2664
2664
2665 Shows a diff which includes the current patch as well as any
2665 Shows a diff which includes the current patch as well as any
2666 changes which have been made in the working directory since the
2666 changes which have been made in the working directory since the
2667 last refresh (thus showing what the current patch would become
2667 last refresh (thus showing what the current patch would become
2668 after a qrefresh).
2668 after a qrefresh).
2669
2669
2670 Use :hg:`diff` if you only want to see the changes made since the
2670 Use :hg:`diff` if you only want to see the changes made since the
2671 last qrefresh, or :hg:`export qtip` if you want to see changes
2671 last qrefresh, or :hg:`export qtip` if you want to see changes
2672 made by the current patch without including changes made since the
2672 made by the current patch without including changes made since the
2673 qrefresh.
2673 qrefresh.
2674
2674
2675 Returns 0 on success.
2675 Returns 0 on success.
2676 """
2676 """
2677 repo.mq.diff(repo, pats, opts)
2677 repo.mq.diff(repo, pats, opts)
2678 return 0
2678 return 0
2679
2679
2680 @command('qfold',
2680 @command('qfold',
2681 [('e', 'edit', None, _('invoke editor on commit messages')),
2681 [('e', 'edit', None, _('invoke editor on commit messages')),
2682 ('k', 'keep', None, _('keep folded patch files')),
2682 ('k', 'keep', None, _('keep folded patch files')),
2683 ] + commands.commitopts,
2683 ] + commands.commitopts,
2684 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2684 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2685 def fold(ui, repo, *files, **opts):
2685 def fold(ui, repo, *files, **opts):
2686 """fold the named patches into the current patch
2686 """fold the named patches into the current patch
2687
2687
2688 Patches must not yet be applied. Each patch will be successively
2688 Patches must not yet be applied. Each patch will be successively
2689 applied to the current patch in the order given. If all the
2689 applied to the current patch in the order given. If all the
2690 patches apply successfully, the current patch will be refreshed
2690 patches apply successfully, the current patch will be refreshed
2691 with the new cumulative patch, and the folded patches will be
2691 with the new cumulative patch, and the folded patches will be
2692 deleted. With -k/--keep, the folded patch files will not be
2692 deleted. With -k/--keep, the folded patch files will not be
2693 removed afterwards.
2693 removed afterwards.
2694
2694
2695 The header for each folded patch will be concatenated with the
2695 The header for each folded patch will be concatenated with the
2696 current patch header, separated by a line of ``* * *``.
2696 current patch header, separated by a line of ``* * *``.
2697
2697
2698 Returns 0 on success."""
2698 Returns 0 on success."""
2699 q = repo.mq
2699 q = repo.mq
2700 if not files:
2700 if not files:
2701 raise error.Abort(_('qfold requires at least one patch name'))
2701 raise error.Abort(_('qfold requires at least one patch name'))
2702 if not q.checktoppatch(repo)[0]:
2702 if not q.checktoppatch(repo)[0]:
2703 raise error.Abort(_('no patches applied'))
2703 raise error.Abort(_('no patches applied'))
2704 q.checklocalchanges(repo)
2704 q.checklocalchanges(repo)
2705
2705
2706 message = cmdutil.logmessage(ui, opts)
2706 message = cmdutil.logmessage(ui, opts)
2707
2707
2708 parent = q.lookup('qtip')
2708 parent = q.lookup('qtip')
2709 patches = []
2709 patches = []
2710 messages = []
2710 messages = []
2711 for f in files:
2711 for f in files:
2712 p = q.lookup(f)
2712 p = q.lookup(f)
2713 if p in patches or p == parent:
2713 if p in patches or p == parent:
2714 ui.warn(_('skipping already folded patch %s\n') % p)
2714 ui.warn(_('skipping already folded patch %s\n') % p)
2715 if q.isapplied(p):
2715 if q.isapplied(p):
2716 raise error.Abort(_('qfold cannot fold already applied patch %s')
2716 raise error.Abort(_('qfold cannot fold already applied patch %s')
2717 % p)
2717 % p)
2718 patches.append(p)
2718 patches.append(p)
2719
2719
2720 for p in patches:
2720 for p in patches:
2721 if not message:
2721 if not message:
2722 ph = patchheader(q.join(p), q.plainmode)
2722 ph = patchheader(q.join(p), q.plainmode)
2723 if ph.message:
2723 if ph.message:
2724 messages.append(ph.message)
2724 messages.append(ph.message)
2725 pf = q.join(p)
2725 pf = q.join(p)
2726 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2726 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2727 if not patchsuccess:
2727 if not patchsuccess:
2728 raise error.Abort(_('error folding patch %s') % p)
2728 raise error.Abort(_('error folding patch %s') % p)
2729
2729
2730 if not message:
2730 if not message:
2731 ph = patchheader(q.join(parent), q.plainmode)
2731 ph = patchheader(q.join(parent), q.plainmode)
2732 message = ph.message
2732 message = ph.message
2733 for msg in messages:
2733 for msg in messages:
2734 if msg:
2734 if msg:
2735 if message:
2735 if message:
2736 message.append('* * *')
2736 message.append('* * *')
2737 message.extend(msg)
2737 message.extend(msg)
2738 message = '\n'.join(message)
2738 message = '\n'.join(message)
2739
2739
2740 diffopts = q.patchopts(q.diffopts(), *patches)
2740 diffopts = q.patchopts(q.diffopts(), *patches)
2741 with repo.wlock():
2741 with repo.wlock():
2742 q.refresh(repo, msg=message, git=diffopts.git, edit=opts.get('edit'),
2742 q.refresh(repo, msg=message, git=diffopts.git, edit=opts.get('edit'),
2743 editform='mq.qfold')
2743 editform='mq.qfold')
2744 q.delete(repo, patches, opts)
2744 q.delete(repo, patches, opts)
2745 q.savedirty()
2745 q.savedirty()
2746
2746
2747 @command("qgoto",
2747 @command("qgoto",
2748 [('', 'keep-changes', None,
2748 [('', 'keep-changes', None,
2749 _('tolerate non-conflicting local changes')),
2749 _('tolerate non-conflicting local changes')),
2750 ('f', 'force', None, _('overwrite any local changes')),
2750 ('f', 'force', None, _('overwrite any local changes')),
2751 ('', 'no-backup', None, _('do not save backup copies of files'))],
2751 ('', 'no-backup', None, _('do not save backup copies of files'))],
2752 _('hg qgoto [OPTION]... PATCH'))
2752 _('hg qgoto [OPTION]... PATCH'))
2753 def goto(ui, repo, patch, **opts):
2753 def goto(ui, repo, patch, **opts):
2754 '''push or pop patches until named patch is at top of stack
2754 '''push or pop patches until named patch is at top of stack
2755
2755
2756 Returns 0 on success.'''
2756 Returns 0 on success.'''
2757 opts = fixkeepchangesopts(ui, opts)
2757 opts = fixkeepchangesopts(ui, opts)
2758 q = repo.mq
2758 q = repo.mq
2759 patch = q.lookup(patch)
2759 patch = q.lookup(patch)
2760 nobackup = opts.get('no_backup')
2760 nobackup = opts.get('no_backup')
2761 keepchanges = opts.get('keep_changes')
2761 keepchanges = opts.get('keep_changes')
2762 if q.isapplied(patch):
2762 if q.isapplied(patch):
2763 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2763 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2764 keepchanges=keepchanges)
2764 keepchanges=keepchanges)
2765 else:
2765 else:
2766 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2766 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2767 keepchanges=keepchanges)
2767 keepchanges=keepchanges)
2768 q.savedirty()
2768 q.savedirty()
2769 return ret
2769 return ret
2770
2770
2771 @command("qguard",
2771 @command("qguard",
2772 [('l', 'list', None, _('list all patches and guards')),
2772 [('l', 'list', None, _('list all patches and guards')),
2773 ('n', 'none', None, _('drop all guards'))],
2773 ('n', 'none', None, _('drop all guards'))],
2774 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2774 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2775 def guard(ui, repo, *args, **opts):
2775 def guard(ui, repo, *args, **opts):
2776 '''set or print guards for a patch
2776 '''set or print guards for a patch
2777
2777
2778 Guards control whether a patch can be pushed. A patch with no
2778 Guards control whether a patch can be pushed. A patch with no
2779 guards is always pushed. A patch with a positive guard ("+foo") is
2779 guards is always pushed. A patch with a positive guard ("+foo") is
2780 pushed only if the :hg:`qselect` command has activated it. A patch with
2780 pushed only if the :hg:`qselect` command has activated it. A patch with
2781 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2781 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2782 has activated it.
2782 has activated it.
2783
2783
2784 With no arguments, print the currently active guards.
2784 With no arguments, print the currently active guards.
2785 With arguments, set guards for the named patch.
2785 With arguments, set guards for the named patch.
2786
2786
2787 .. note::
2787 .. note::
2788
2788
2789 Specifying negative guards now requires '--'.
2789 Specifying negative guards now requires '--'.
2790
2790
2791 To set guards on another patch::
2791 To set guards on another patch::
2792
2792
2793 hg qguard other.patch -- +2.6.17 -stable
2793 hg qguard other.patch -- +2.6.17 -stable
2794
2794
2795 Returns 0 on success.
2795 Returns 0 on success.
2796 '''
2796 '''
2797 def status(idx):
2797 def status(idx):
2798 guards = q.seriesguards[idx] or ['unguarded']
2798 guards = q.seriesguards[idx] or ['unguarded']
2799 if q.series[idx] in applied:
2799 if q.series[idx] in applied:
2800 state = 'applied'
2800 state = 'applied'
2801 elif q.pushable(idx)[0]:
2801 elif q.pushable(idx)[0]:
2802 state = 'unapplied'
2802 state = 'unapplied'
2803 else:
2803 else:
2804 state = 'guarded'
2804 state = 'guarded'
2805 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2805 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2806 ui.write('%s: ' % ui.label(q.series[idx], label))
2806 ui.write('%s: ' % ui.label(q.series[idx], label))
2807
2807
2808 for i, guard in enumerate(guards):
2808 for i, guard in enumerate(guards):
2809 if guard.startswith('+'):
2809 if guard.startswith('+'):
2810 ui.write(guard, label='qguard.positive')
2810 ui.write(guard, label='qguard.positive')
2811 elif guard.startswith('-'):
2811 elif guard.startswith('-'):
2812 ui.write(guard, label='qguard.negative')
2812 ui.write(guard, label='qguard.negative')
2813 else:
2813 else:
2814 ui.write(guard, label='qguard.unguarded')
2814 ui.write(guard, label='qguard.unguarded')
2815 if i != len(guards) - 1:
2815 if i != len(guards) - 1:
2816 ui.write(' ')
2816 ui.write(' ')
2817 ui.write('\n')
2817 ui.write('\n')
2818 q = repo.mq
2818 q = repo.mq
2819 applied = set(p.name for p in q.applied)
2819 applied = set(p.name for p in q.applied)
2820 patch = None
2820 patch = None
2821 args = list(args)
2821 args = list(args)
2822 if opts.get('list'):
2822 if opts.get('list'):
2823 if args or opts.get('none'):
2823 if args or opts.get('none'):
2824 raise error.Abort(_('cannot mix -l/--list with options or '
2824 raise error.Abort(_('cannot mix -l/--list with options or '
2825 'arguments'))
2825 'arguments'))
2826 for i in xrange(len(q.series)):
2826 for i in xrange(len(q.series)):
2827 status(i)
2827 status(i)
2828 return
2828 return
2829 if not args or args[0][0:1] in '-+':
2829 if not args or args[0][0:1] in '-+':
2830 if not q.applied:
2830 if not q.applied:
2831 raise error.Abort(_('no patches applied'))
2831 raise error.Abort(_('no patches applied'))
2832 patch = q.applied[-1].name
2832 patch = q.applied[-1].name
2833 if patch is None and args[0][0:1] not in '-+':
2833 if patch is None and args[0][0:1] not in '-+':
2834 patch = args.pop(0)
2834 patch = args.pop(0)
2835 if patch is None:
2835 if patch is None:
2836 raise error.Abort(_('no patch to work with'))
2836 raise error.Abort(_('no patch to work with'))
2837 if args or opts.get('none'):
2837 if args or opts.get('none'):
2838 idx = q.findseries(patch)
2838 idx = q.findseries(patch)
2839 if idx is None:
2839 if idx is None:
2840 raise error.Abort(_('no patch named %s') % patch)
2840 raise error.Abort(_('no patch named %s') % patch)
2841 q.setguards(idx, args)
2841 q.setguards(idx, args)
2842 q.savedirty()
2842 q.savedirty()
2843 else:
2843 else:
2844 status(q.series.index(q.lookup(patch)))
2844 status(q.series.index(q.lookup(patch)))
2845
2845
2846 @command("qheader", [], _('hg qheader [PATCH]'))
2846 @command("qheader", [], _('hg qheader [PATCH]'))
2847 def header(ui, repo, patch=None):
2847 def header(ui, repo, patch=None):
2848 """print the header of the topmost or specified patch
2848 """print the header of the topmost or specified patch
2849
2849
2850 Returns 0 on success."""
2850 Returns 0 on success."""
2851 q = repo.mq
2851 q = repo.mq
2852
2852
2853 if patch:
2853 if patch:
2854 patch = q.lookup(patch)
2854 patch = q.lookup(patch)
2855 else:
2855 else:
2856 if not q.applied:
2856 if not q.applied:
2857 ui.write(_('no patches applied\n'))
2857 ui.write(_('no patches applied\n'))
2858 return 1
2858 return 1
2859 patch = q.lookup('qtip')
2859 patch = q.lookup('qtip')
2860 ph = patchheader(q.join(patch), q.plainmode)
2860 ph = patchheader(q.join(patch), q.plainmode)
2861
2861
2862 ui.write('\n'.join(ph.message) + '\n')
2862 ui.write('\n'.join(ph.message) + '\n')
2863
2863
2864 def lastsavename(path):
2864 def lastsavename(path):
2865 (directory, base) = os.path.split(path)
2865 (directory, base) = os.path.split(path)
2866 names = os.listdir(directory)
2866 names = os.listdir(directory)
2867 namere = re.compile("%s.([0-9]+)" % base)
2867 namere = re.compile("%s.([0-9]+)" % base)
2868 maxindex = None
2868 maxindex = None
2869 maxname = None
2869 maxname = None
2870 for f in names:
2870 for f in names:
2871 m = namere.match(f)
2871 m = namere.match(f)
2872 if m:
2872 if m:
2873 index = int(m.group(1))
2873 index = int(m.group(1))
2874 if maxindex is None or index > maxindex:
2874 if maxindex is None or index > maxindex:
2875 maxindex = index
2875 maxindex = index
2876 maxname = f
2876 maxname = f
2877 if maxname:
2877 if maxname:
2878 return (os.path.join(directory, maxname), maxindex)
2878 return (os.path.join(directory, maxname), maxindex)
2879 return (None, None)
2879 return (None, None)
2880
2880
2881 def savename(path):
2881 def savename(path):
2882 (last, index) = lastsavename(path)
2882 (last, index) = lastsavename(path)
2883 if last is None:
2883 if last is None:
2884 index = 0
2884 index = 0
2885 newpath = path + ".%d" % (index + 1)
2885 newpath = path + ".%d" % (index + 1)
2886 return newpath
2886 return newpath
2887
2887
2888 @command("^qpush",
2888 @command("^qpush",
2889 [('', 'keep-changes', None,
2889 [('', 'keep-changes', None,
2890 _('tolerate non-conflicting local changes')),
2890 _('tolerate non-conflicting local changes')),
2891 ('f', 'force', None, _('apply on top of local changes')),
2891 ('f', 'force', None, _('apply on top of local changes')),
2892 ('e', 'exact', None,
2892 ('e', 'exact', None,
2893 _('apply the target patch to its recorded parent')),
2893 _('apply the target patch to its recorded parent')),
2894 ('l', 'list', None, _('list patch name in commit text')),
2894 ('l', 'list', None, _('list patch name in commit text')),
2895 ('a', 'all', None, _('apply all patches')),
2895 ('a', 'all', None, _('apply all patches')),
2896 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2896 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2897 ('n', 'name', '',
2897 ('n', 'name', '',
2898 _('merge queue name (DEPRECATED)'), _('NAME')),
2898 _('merge queue name (DEPRECATED)'), _('NAME')),
2899 ('', 'move', None,
2899 ('', 'move', None,
2900 _('reorder patch series and apply only the patch')),
2900 _('reorder patch series and apply only the patch')),
2901 ('', 'no-backup', None, _('do not save backup copies of files'))],
2901 ('', 'no-backup', None, _('do not save backup copies of files'))],
2902 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2902 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2903 def push(ui, repo, patch=None, **opts):
2903 def push(ui, repo, patch=None, **opts):
2904 """push the next patch onto the stack
2904 """push the next patch onto the stack
2905
2905
2906 By default, abort if the working directory contains uncommitted
2906 By default, abort if the working directory contains uncommitted
2907 changes. With --keep-changes, abort only if the uncommitted files
2907 changes. With --keep-changes, abort only if the uncommitted files
2908 overlap with patched files. With -f/--force, backup and patch over
2908 overlap with patched files. With -f/--force, backup and patch over
2909 uncommitted changes.
2909 uncommitted changes.
2910
2910
2911 Return 0 on success.
2911 Return 0 on success.
2912 """
2912 """
2913 q = repo.mq
2913 q = repo.mq
2914 mergeq = None
2914 mergeq = None
2915
2915
2916 opts = fixkeepchangesopts(ui, opts)
2916 opts = fixkeepchangesopts(ui, opts)
2917 if opts.get('merge'):
2917 if opts.get('merge'):
2918 if opts.get('name'):
2918 if opts.get('name'):
2919 newpath = repo.join(opts.get('name'))
2919 newpath = repo.join(opts.get('name'))
2920 else:
2920 else:
2921 newpath, i = lastsavename(q.path)
2921 newpath, i = lastsavename(q.path)
2922 if not newpath:
2922 if not newpath:
2923 ui.warn(_("no saved queues found, please use -n\n"))
2923 ui.warn(_("no saved queues found, please use -n\n"))
2924 return 1
2924 return 1
2925 mergeq = queue(ui, repo.baseui, repo.path, newpath)
2925 mergeq = queue(ui, repo.baseui, repo.path, newpath)
2926 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2926 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2927 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2927 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2928 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2928 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2929 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2929 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2930 keepchanges=opts.get('keep_changes'))
2930 keepchanges=opts.get('keep_changes'))
2931 return ret
2931 return ret
2932
2932
2933 @command("^qpop",
2933 @command("^qpop",
2934 [('a', 'all', None, _('pop all patches')),
2934 [('a', 'all', None, _('pop all patches')),
2935 ('n', 'name', '',
2935 ('n', 'name', '',
2936 _('queue name to pop (DEPRECATED)'), _('NAME')),
2936 _('queue name to pop (DEPRECATED)'), _('NAME')),
2937 ('', 'keep-changes', None,
2937 ('', 'keep-changes', None,
2938 _('tolerate non-conflicting local changes')),
2938 _('tolerate non-conflicting local changes')),
2939 ('f', 'force', None, _('forget any local changes to patched files')),
2939 ('f', 'force', None, _('forget any local changes to patched files')),
2940 ('', 'no-backup', None, _('do not save backup copies of files'))],
2940 ('', 'no-backup', None, _('do not save backup copies of files'))],
2941 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2941 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2942 def pop(ui, repo, patch=None, **opts):
2942 def pop(ui, repo, patch=None, **opts):
2943 """pop the current patch off the stack
2943 """pop the current patch off the stack
2944
2944
2945 Without argument, pops off the top of the patch stack. If given a
2945 Without argument, pops off the top of the patch stack. If given a
2946 patch name, keeps popping off patches until the named patch is at
2946 patch name, keeps popping off patches until the named patch is at
2947 the top of the stack.
2947 the top of the stack.
2948
2948
2949 By default, abort if the working directory contains uncommitted
2949 By default, abort if the working directory contains uncommitted
2950 changes. With --keep-changes, abort only if the uncommitted files
2950 changes. With --keep-changes, abort only if the uncommitted files
2951 overlap with patched files. With -f/--force, backup and discard
2951 overlap with patched files. With -f/--force, backup and discard
2952 changes made to such files.
2952 changes made to such files.
2953
2953
2954 Return 0 on success.
2954 Return 0 on success.
2955 """
2955 """
2956 opts = fixkeepchangesopts(ui, opts)
2956 opts = fixkeepchangesopts(ui, opts)
2957 localupdate = True
2957 localupdate = True
2958 if opts.get('name'):
2958 if opts.get('name'):
2959 q = queue(ui, repo.baseui, repo.path, repo.join(opts.get('name')))
2959 q = queue(ui, repo.baseui, repo.path, repo.join(opts.get('name')))
2960 ui.warn(_('using patch queue: %s\n') % q.path)
2960 ui.warn(_('using patch queue: %s\n') % q.path)
2961 localupdate = False
2961 localupdate = False
2962 else:
2962 else:
2963 q = repo.mq
2963 q = repo.mq
2964 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2964 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2965 all=opts.get('all'), nobackup=opts.get('no_backup'),
2965 all=opts.get('all'), nobackup=opts.get('no_backup'),
2966 keepchanges=opts.get('keep_changes'))
2966 keepchanges=opts.get('keep_changes'))
2967 q.savedirty()
2967 q.savedirty()
2968 return ret
2968 return ret
2969
2969
2970 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2970 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2971 def rename(ui, repo, patch, name=None, **opts):
2971 def rename(ui, repo, patch, name=None, **opts):
2972 """rename a patch
2972 """rename a patch
2973
2973
2974 With one argument, renames the current patch to PATCH1.
2974 With one argument, renames the current patch to PATCH1.
2975 With two arguments, renames PATCH1 to PATCH2.
2975 With two arguments, renames PATCH1 to PATCH2.
2976
2976
2977 Returns 0 on success."""
2977 Returns 0 on success."""
2978 q = repo.mq
2978 q = repo.mq
2979 if not name:
2979 if not name:
2980 name = patch
2980 name = patch
2981 patch = None
2981 patch = None
2982
2982
2983 if patch:
2983 if patch:
2984 patch = q.lookup(patch)
2984 patch = q.lookup(patch)
2985 else:
2985 else:
2986 if not q.applied:
2986 if not q.applied:
2987 ui.write(_('no patches applied\n'))
2987 ui.write(_('no patches applied\n'))
2988 return
2988 return
2989 patch = q.lookup('qtip')
2989 patch = q.lookup('qtip')
2990 absdest = q.join(name)
2990 absdest = q.join(name)
2991 if os.path.isdir(absdest):
2991 if os.path.isdir(absdest):
2992 name = normname(os.path.join(name, os.path.basename(patch)))
2992 name = normname(os.path.join(name, os.path.basename(patch)))
2993 absdest = q.join(name)
2993 absdest = q.join(name)
2994 q.checkpatchname(name)
2994 q.checkpatchname(name)
2995
2995
2996 ui.note(_('renaming %s to %s\n') % (patch, name))
2996 ui.note(_('renaming %s to %s\n') % (patch, name))
2997 i = q.findseries(patch)
2997 i = q.findseries(patch)
2998 guards = q.guard_re.findall(q.fullseries[i])
2998 guards = q.guard_re.findall(q.fullseries[i])
2999 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2999 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
3000 q.parseseries()
3000 q.parseseries()
3001 q.seriesdirty = True
3001 q.seriesdirty = True
3002
3002
3003 info = q.isapplied(patch)
3003 info = q.isapplied(patch)
3004 if info:
3004 if info:
3005 q.applied[info[0]] = statusentry(info[1], name)
3005 q.applied[info[0]] = statusentry(info[1], name)
3006 q.applieddirty = True
3006 q.applieddirty = True
3007
3007
3008 destdir = os.path.dirname(absdest)
3008 destdir = os.path.dirname(absdest)
3009 if not os.path.isdir(destdir):
3009 if not os.path.isdir(destdir):
3010 os.makedirs(destdir)
3010 os.makedirs(destdir)
3011 util.rename(q.join(patch), absdest)
3011 util.rename(q.join(patch), absdest)
3012 r = q.qrepo()
3012 r = q.qrepo()
3013 if r and patch in r.dirstate:
3013 if r and patch in r.dirstate:
3014 wctx = r[None]
3014 wctx = r[None]
3015 with r.wlock():
3015 with r.wlock():
3016 if r.dirstate[patch] == 'a':
3016 if r.dirstate[patch] == 'a':
3017 r.dirstate.drop(patch)
3017 r.dirstate.drop(patch)
3018 r.dirstate.add(name)
3018 r.dirstate.add(name)
3019 else:
3019 else:
3020 wctx.copy(patch, name)
3020 wctx.copy(patch, name)
3021 wctx.forget([patch])
3021 wctx.forget([patch])
3022
3022
3023 q.savedirty()
3023 q.savedirty()
3024
3024
3025 @command("qrestore",
3025 @command("qrestore",
3026 [('d', 'delete', None, _('delete save entry')),
3026 [('d', 'delete', None, _('delete save entry')),
3027 ('u', 'update', None, _('update queue working directory'))],
3027 ('u', 'update', None, _('update queue working directory'))],
3028 _('hg qrestore [-d] [-u] REV'))
3028 _('hg qrestore [-d] [-u] REV'))
3029 def restore(ui, repo, rev, **opts):
3029 def restore(ui, repo, rev, **opts):
3030 """restore the queue state saved by a revision (DEPRECATED)
3030 """restore the queue state saved by a revision (DEPRECATED)
3031
3031
3032 This command is deprecated, use :hg:`rebase` instead."""
3032 This command is deprecated, use :hg:`rebase` instead."""
3033 rev = repo.lookup(rev)
3033 rev = repo.lookup(rev)
3034 q = repo.mq
3034 q = repo.mq
3035 q.restore(repo, rev, delete=opts.get('delete'),
3035 q.restore(repo, rev, delete=opts.get('delete'),
3036 qupdate=opts.get('update'))
3036 qupdate=opts.get('update'))
3037 q.savedirty()
3037 q.savedirty()
3038 return 0
3038 return 0
3039
3039
3040 @command("qsave",
3040 @command("qsave",
3041 [('c', 'copy', None, _('copy patch directory')),
3041 [('c', 'copy', None, _('copy patch directory')),
3042 ('n', 'name', '',
3042 ('n', 'name', '',
3043 _('copy directory name'), _('NAME')),
3043 _('copy directory name'), _('NAME')),
3044 ('e', 'empty', None, _('clear queue status file')),
3044 ('e', 'empty', None, _('clear queue status file')),
3045 ('f', 'force', None, _('force copy'))] + commands.commitopts,
3045 ('f', 'force', None, _('force copy'))] + commands.commitopts,
3046 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
3046 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
3047 def save(ui, repo, **opts):
3047 def save(ui, repo, **opts):
3048 """save current queue state (DEPRECATED)
3048 """save current queue state (DEPRECATED)
3049
3049
3050 This command is deprecated, use :hg:`rebase` instead."""
3050 This command is deprecated, use :hg:`rebase` instead."""
3051 q = repo.mq
3051 q = repo.mq
3052 message = cmdutil.logmessage(ui, opts)
3052 message = cmdutil.logmessage(ui, opts)
3053 ret = q.save(repo, msg=message)
3053 ret = q.save(repo, msg=message)
3054 if ret:
3054 if ret:
3055 return ret
3055 return ret
3056 q.savedirty() # save to .hg/patches before copying
3056 q.savedirty() # save to .hg/patches before copying
3057 if opts.get('copy'):
3057 if opts.get('copy'):
3058 path = q.path
3058 path = q.path
3059 if opts.get('name'):
3059 if opts.get('name'):
3060 newpath = os.path.join(q.basepath, opts.get('name'))
3060 newpath = os.path.join(q.basepath, opts.get('name'))
3061 if os.path.exists(newpath):
3061 if os.path.exists(newpath):
3062 if not os.path.isdir(newpath):
3062 if not os.path.isdir(newpath):
3063 raise error.Abort(_('destination %s exists and is not '
3063 raise error.Abort(_('destination %s exists and is not '
3064 'a directory') % newpath)
3064 'a directory') % newpath)
3065 if not opts.get('force'):
3065 if not opts.get('force'):
3066 raise error.Abort(_('destination %s exists, '
3066 raise error.Abort(_('destination %s exists, '
3067 'use -f to force') % newpath)
3067 'use -f to force') % newpath)
3068 else:
3068 else:
3069 newpath = savename(path)
3069 newpath = savename(path)
3070 ui.warn(_("copy %s to %s\n") % (path, newpath))
3070 ui.warn(_("copy %s to %s\n") % (path, newpath))
3071 util.copyfiles(path, newpath)
3071 util.copyfiles(path, newpath)
3072 if opts.get('empty'):
3072 if opts.get('empty'):
3073 del q.applied[:]
3073 del q.applied[:]
3074 q.applieddirty = True
3074 q.applieddirty = True
3075 q.savedirty()
3075 q.savedirty()
3076 return 0
3076 return 0
3077
3077
3078
3078
3079 @command("qselect",
3079 @command("qselect",
3080 [('n', 'none', None, _('disable all guards')),
3080 [('n', 'none', None, _('disable all guards')),
3081 ('s', 'series', None, _('list all guards in series file')),
3081 ('s', 'series', None, _('list all guards in series file')),
3082 ('', 'pop', None, _('pop to before first guarded applied patch')),
3082 ('', 'pop', None, _('pop to before first guarded applied patch')),
3083 ('', 'reapply', None, _('pop, then reapply patches'))],
3083 ('', 'reapply', None, _('pop, then reapply patches'))],
3084 _('hg qselect [OPTION]... [GUARD]...'))
3084 _('hg qselect [OPTION]... [GUARD]...'))
3085 def select(ui, repo, *args, **opts):
3085 def select(ui, repo, *args, **opts):
3086 '''set or print guarded patches to push
3086 '''set or print guarded patches to push
3087
3087
3088 Use the :hg:`qguard` command to set or print guards on patch, then use
3088 Use the :hg:`qguard` command to set or print guards on patch, then use
3089 qselect to tell mq which guards to use. A patch will be pushed if
3089 qselect to tell mq which guards to use. A patch will be pushed if
3090 it has no guards or any positive guards match the currently
3090 it has no guards or any positive guards match the currently
3091 selected guard, but will not be pushed if any negative guards
3091 selected guard, but will not be pushed if any negative guards
3092 match the current guard. For example::
3092 match the current guard. For example::
3093
3093
3094 qguard foo.patch -- -stable (negative guard)
3094 qguard foo.patch -- -stable (negative guard)
3095 qguard bar.patch +stable (positive guard)
3095 qguard bar.patch +stable (positive guard)
3096 qselect stable
3096 qselect stable
3097
3097
3098 This activates the "stable" guard. mq will skip foo.patch (because
3098 This activates the "stable" guard. mq will skip foo.patch (because
3099 it has a negative match) but push bar.patch (because it has a
3099 it has a negative match) but push bar.patch (because it has a
3100 positive match).
3100 positive match).
3101
3101
3102 With no arguments, prints the currently active guards.
3102 With no arguments, prints the currently active guards.
3103 With one argument, sets the active guard.
3103 With one argument, sets the active guard.
3104
3104
3105 Use -n/--none to deactivate guards (no other arguments needed).
3105 Use -n/--none to deactivate guards (no other arguments needed).
3106 When no guards are active, patches with positive guards are
3106 When no guards are active, patches with positive guards are
3107 skipped and patches with negative guards are pushed.
3107 skipped and patches with negative guards are pushed.
3108
3108
3109 qselect can change the guards on applied patches. It does not pop
3109 qselect can change the guards on applied patches. It does not pop
3110 guarded patches by default. Use --pop to pop back to the last
3110 guarded patches by default. Use --pop to pop back to the last
3111 applied patch that is not guarded. Use --reapply (which implies
3111 applied patch that is not guarded. Use --reapply (which implies
3112 --pop) to push back to the current patch afterwards, but skip
3112 --pop) to push back to the current patch afterwards, but skip
3113 guarded patches.
3113 guarded patches.
3114
3114
3115 Use -s/--series to print a list of all guards in the series file
3115 Use -s/--series to print a list of all guards in the series file
3116 (no other arguments needed). Use -v for more information.
3116 (no other arguments needed). Use -v for more information.
3117
3117
3118 Returns 0 on success.'''
3118 Returns 0 on success.'''
3119
3119
3120 q = repo.mq
3120 q = repo.mq
3121 guards = q.active()
3121 guards = q.active()
3122 pushable = lambda i: q.pushable(q.applied[i].name)[0]
3122 pushable = lambda i: q.pushable(q.applied[i].name)[0]
3123 if args or opts.get('none'):
3123 if args or opts.get('none'):
3124 old_unapplied = q.unapplied(repo)
3124 old_unapplied = q.unapplied(repo)
3125 old_guarded = [i for i in xrange(len(q.applied)) if not pushable(i)]
3125 old_guarded = [i for i in xrange(len(q.applied)) if not pushable(i)]
3126 q.setactive(args)
3126 q.setactive(args)
3127 q.savedirty()
3127 q.savedirty()
3128 if not args:
3128 if not args:
3129 ui.status(_('guards deactivated\n'))
3129 ui.status(_('guards deactivated\n'))
3130 if not opts.get('pop') and not opts.get('reapply'):
3130 if not opts.get('pop') and not opts.get('reapply'):
3131 unapplied = q.unapplied(repo)
3131 unapplied = q.unapplied(repo)
3132 guarded = [i for i in xrange(len(q.applied)) if not pushable(i)]
3132 guarded = [i for i in xrange(len(q.applied)) if not pushable(i)]
3133 if len(unapplied) != len(old_unapplied):
3133 if len(unapplied) != len(old_unapplied):
3134 ui.status(_('number of unguarded, unapplied patches has '
3134 ui.status(_('number of unguarded, unapplied patches has '
3135 'changed from %d to %d\n') %
3135 'changed from %d to %d\n') %
3136 (len(old_unapplied), len(unapplied)))
3136 (len(old_unapplied), len(unapplied)))
3137 if len(guarded) != len(old_guarded):
3137 if len(guarded) != len(old_guarded):
3138 ui.status(_('number of guarded, applied patches has changed '
3138 ui.status(_('number of guarded, applied patches has changed '
3139 'from %d to %d\n') %
3139 'from %d to %d\n') %
3140 (len(old_guarded), len(guarded)))
3140 (len(old_guarded), len(guarded)))
3141 elif opts.get('series'):
3141 elif opts.get('series'):
3142 guards = {}
3142 guards = {}
3143 noguards = 0
3143 noguards = 0
3144 for gs in q.seriesguards:
3144 for gs in q.seriesguards:
3145 if not gs:
3145 if not gs:
3146 noguards += 1
3146 noguards += 1
3147 for g in gs:
3147 for g in gs:
3148 guards.setdefault(g, 0)
3148 guards.setdefault(g, 0)
3149 guards[g] += 1
3149 guards[g] += 1
3150 if ui.verbose:
3150 if ui.verbose:
3151 guards['NONE'] = noguards
3151 guards['NONE'] = noguards
3152 guards = guards.items()
3152 guards = guards.items()
3153 guards.sort(key=lambda x: x[0][1:])
3153 guards.sort(key=lambda x: x[0][1:])
3154 if guards:
3154 if guards:
3155 ui.note(_('guards in series file:\n'))
3155 ui.note(_('guards in series file:\n'))
3156 for guard, count in guards:
3156 for guard, count in guards:
3157 ui.note('%2d ' % count)
3157 ui.note('%2d ' % count)
3158 ui.write(guard, '\n')
3158 ui.write(guard, '\n')
3159 else:
3159 else:
3160 ui.note(_('no guards in series file\n'))
3160 ui.note(_('no guards in series file\n'))
3161 else:
3161 else:
3162 if guards:
3162 if guards:
3163 ui.note(_('active guards:\n'))
3163 ui.note(_('active guards:\n'))
3164 for g in guards:
3164 for g in guards:
3165 ui.write(g, '\n')
3165 ui.write(g, '\n')
3166 else:
3166 else:
3167 ui.write(_('no active guards\n'))
3167 ui.write(_('no active guards\n'))
3168 reapply = opts.get('reapply') and q.applied and q.applied[-1].name
3168 reapply = opts.get('reapply') and q.applied and q.applied[-1].name
3169 popped = False
3169 popped = False
3170 if opts.get('pop') or opts.get('reapply'):
3170 if opts.get('pop') or opts.get('reapply'):
3171 for i in xrange(len(q.applied)):
3171 for i in xrange(len(q.applied)):
3172 if not pushable(i):
3172 if not pushable(i):
3173 ui.status(_('popping guarded patches\n'))
3173 ui.status(_('popping guarded patches\n'))
3174 popped = True
3174 popped = True
3175 if i == 0:
3175 if i == 0:
3176 q.pop(repo, all=True)
3176 q.pop(repo, all=True)
3177 else:
3177 else:
3178 q.pop(repo, q.applied[i - 1].name)
3178 q.pop(repo, q.applied[i - 1].name)
3179 break
3179 break
3180 if popped:
3180 if popped:
3181 try:
3181 try:
3182 if reapply:
3182 if reapply:
3183 ui.status(_('reapplying unguarded patches\n'))
3183 ui.status(_('reapplying unguarded patches\n'))
3184 q.push(repo, reapply)
3184 q.push(repo, reapply)
3185 finally:
3185 finally:
3186 q.savedirty()
3186 q.savedirty()
3187
3187
3188 @command("qfinish",
3188 @command("qfinish",
3189 [('a', 'applied', None, _('finish all applied changesets'))],
3189 [('a', 'applied', None, _('finish all applied changesets'))],
3190 _('hg qfinish [-a] [REV]...'))
3190 _('hg qfinish [-a] [REV]...'))
3191 def finish(ui, repo, *revrange, **opts):
3191 def finish(ui, repo, *revrange, **opts):
3192 """move applied patches into repository history
3192 """move applied patches into repository history
3193
3193
3194 Finishes the specified revisions (corresponding to applied
3194 Finishes the specified revisions (corresponding to applied
3195 patches) by moving them out of mq control into regular repository
3195 patches) by moving them out of mq control into regular repository
3196 history.
3196 history.
3197
3197
3198 Accepts a revision range or the -a/--applied option. If --applied
3198 Accepts a revision range or the -a/--applied option. If --applied
3199 is specified, all applied mq revisions are removed from mq
3199 is specified, all applied mq revisions are removed from mq
3200 control. Otherwise, the given revisions must be at the base of the
3200 control. Otherwise, the given revisions must be at the base of the
3201 stack of applied patches.
3201 stack of applied patches.
3202
3202
3203 This can be especially useful if your changes have been applied to
3203 This can be especially useful if your changes have been applied to
3204 an upstream repository, or if you are about to push your changes
3204 an upstream repository, or if you are about to push your changes
3205 to upstream.
3205 to upstream.
3206
3206
3207 Returns 0 on success.
3207 Returns 0 on success.
3208 """
3208 """
3209 if not opts.get('applied') and not revrange:
3209 if not opts.get('applied') and not revrange:
3210 raise error.Abort(_('no revisions specified'))
3210 raise error.Abort(_('no revisions specified'))
3211 elif opts.get('applied'):
3211 elif opts.get('applied'):
3212 revrange = ('qbase::qtip',) + revrange
3212 revrange = ('qbase::qtip',) + revrange
3213
3213
3214 q = repo.mq
3214 q = repo.mq
3215 if not q.applied:
3215 if not q.applied:
3216 ui.status(_('no patches applied\n'))
3216 ui.status(_('no patches applied\n'))
3217 return 0
3217 return 0
3218
3218
3219 revs = scmutil.revrange(repo, revrange)
3219 revs = scmutil.revrange(repo, revrange)
3220 if repo['.'].rev() in revs and repo[None].files():
3220 if repo['.'].rev() in revs and repo[None].files():
3221 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3221 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3222 # queue.finish may changes phases but leave the responsibility to lock the
3222 # queue.finish may changes phases but leave the responsibility to lock the
3223 # repo to the caller to avoid deadlock with wlock. This command code is
3223 # repo to the caller to avoid deadlock with wlock. This command code is
3224 # responsibility for this locking.
3224 # responsibility for this locking.
3225 with repo.lock():
3225 with repo.lock():
3226 q.finish(repo, revs)
3226 q.finish(repo, revs)
3227 q.savedirty()
3227 q.savedirty()
3228 return 0
3228 return 0
3229
3229
3230 @command("qqueue",
3230 @command("qqueue",
3231 [('l', 'list', False, _('list all available queues')),
3231 [('l', 'list', False, _('list all available queues')),
3232 ('', 'active', False, _('print name of active queue')),
3232 ('', 'active', False, _('print name of active queue')),
3233 ('c', 'create', False, _('create new queue')),
3233 ('c', 'create', False, _('create new queue')),
3234 ('', 'rename', False, _('rename active queue')),
3234 ('', 'rename', False, _('rename active queue')),
3235 ('', 'delete', False, _('delete reference to queue')),
3235 ('', 'delete', False, _('delete reference to queue')),
3236 ('', 'purge', False, _('delete queue, and remove patch dir')),
3236 ('', 'purge', False, _('delete queue, and remove patch dir')),
3237 ],
3237 ],
3238 _('[OPTION] [QUEUE]'))
3238 _('[OPTION] [QUEUE]'))
3239 def qqueue(ui, repo, name=None, **opts):
3239 def qqueue(ui, repo, name=None, **opts):
3240 '''manage multiple patch queues
3240 '''manage multiple patch queues
3241
3241
3242 Supports switching between different patch queues, as well as creating
3242 Supports switching between different patch queues, as well as creating
3243 new patch queues and deleting existing ones.
3243 new patch queues and deleting existing ones.
3244
3244
3245 Omitting a queue name or specifying -l/--list will show you the registered
3245 Omitting a queue name or specifying -l/--list will show you the registered
3246 queues - by default the "normal" patches queue is registered. The currently
3246 queues - by default the "normal" patches queue is registered. The currently
3247 active queue will be marked with "(active)". Specifying --active will print
3247 active queue will be marked with "(active)". Specifying --active will print
3248 only the name of the active queue.
3248 only the name of the active queue.
3249
3249
3250 To create a new queue, use -c/--create. The queue is automatically made
3250 To create a new queue, use -c/--create. The queue is automatically made
3251 active, except in the case where there are applied patches from the
3251 active, except in the case where there are applied patches from the
3252 currently active queue in the repository. Then the queue will only be
3252 currently active queue in the repository. Then the queue will only be
3253 created and switching will fail.
3253 created and switching will fail.
3254
3254
3255 To delete an existing queue, use --delete. You cannot delete the currently
3255 To delete an existing queue, use --delete. You cannot delete the currently
3256 active queue.
3256 active queue.
3257
3257
3258 Returns 0 on success.
3258 Returns 0 on success.
3259 '''
3259 '''
3260 q = repo.mq
3260 q = repo.mq
3261 _defaultqueue = 'patches'
3261 _defaultqueue = 'patches'
3262 _allqueues = 'patches.queues'
3262 _allqueues = 'patches.queues'
3263 _activequeue = 'patches.queue'
3263 _activequeue = 'patches.queue'
3264
3264
3265 def _getcurrent():
3265 def _getcurrent():
3266 cur = os.path.basename(q.path)
3266 cur = os.path.basename(q.path)
3267 if cur.startswith('patches-'):
3267 if cur.startswith('patches-'):
3268 cur = cur[8:]
3268 cur = cur[8:]
3269 return cur
3269 return cur
3270
3270
3271 def _noqueues():
3271 def _noqueues():
3272 try:
3272 try:
3273 fh = repo.vfs(_allqueues, 'r')
3273 fh = repo.vfs(_allqueues, 'r')
3274 fh.close()
3274 fh.close()
3275 except IOError:
3275 except IOError:
3276 return True
3276 return True
3277
3277
3278 return False
3278 return False
3279
3279
3280 def _getqueues():
3280 def _getqueues():
3281 current = _getcurrent()
3281 current = _getcurrent()
3282
3282
3283 try:
3283 try:
3284 fh = repo.vfs(_allqueues, 'r')
3284 fh = repo.vfs(_allqueues, 'r')
3285 queues = [queue.strip() for queue in fh if queue.strip()]
3285 queues = [queue.strip() for queue in fh if queue.strip()]
3286 fh.close()
3286 fh.close()
3287 if current not in queues:
3287 if current not in queues:
3288 queues.append(current)
3288 queues.append(current)
3289 except IOError:
3289 except IOError:
3290 queues = [_defaultqueue]
3290 queues = [_defaultqueue]
3291
3291
3292 return sorted(queues)
3292 return sorted(queues)
3293
3293
3294 def _setactive(name):
3294 def _setactive(name):
3295 if q.applied:
3295 if q.applied:
3296 raise error.Abort(_('new queue created, but cannot make active '
3296 raise error.Abort(_('new queue created, but cannot make active '
3297 'as patches are applied'))
3297 'as patches are applied'))
3298 _setactivenocheck(name)
3298 _setactivenocheck(name)
3299
3299
3300 def _setactivenocheck(name):
3300 def _setactivenocheck(name):
3301 fh = repo.vfs(_activequeue, 'w')
3301 fh = repo.vfs(_activequeue, 'w')
3302 if name != 'patches':
3302 if name != 'patches':
3303 fh.write(name)
3303 fh.write(name)
3304 fh.close()
3304 fh.close()
3305
3305
3306 def _addqueue(name):
3306 def _addqueue(name):
3307 fh = repo.vfs(_allqueues, 'a')
3307 fh = repo.vfs(_allqueues, 'a')
3308 fh.write('%s\n' % (name,))
3308 fh.write('%s\n' % (name,))
3309 fh.close()
3309 fh.close()
3310
3310
3311 def _queuedir(name):
3311 def _queuedir(name):
3312 if name == 'patches':
3312 if name == 'patches':
3313 return repo.join('patches')
3313 return repo.join('patches')
3314 else:
3314 else:
3315 return repo.join('patches-' + name)
3315 return repo.join('patches-' + name)
3316
3316
3317 def _validname(name):
3317 def _validname(name):
3318 for n in name:
3318 for n in name:
3319 if n in ':\\/.':
3319 if n in ':\\/.':
3320 return False
3320 return False
3321 return True
3321 return True
3322
3322
3323 def _delete(name):
3323 def _delete(name):
3324 if name not in existing:
3324 if name not in existing:
3325 raise error.Abort(_('cannot delete queue that does not exist'))
3325 raise error.Abort(_('cannot delete queue that does not exist'))
3326
3326
3327 current = _getcurrent()
3327 current = _getcurrent()
3328
3328
3329 if name == current:
3329 if name == current:
3330 raise error.Abort(_('cannot delete currently active queue'))
3330 raise error.Abort(_('cannot delete currently active queue'))
3331
3331
3332 fh = repo.vfs('patches.queues.new', 'w')
3332 fh = repo.vfs('patches.queues.new', 'w')
3333 for queue in existing:
3333 for queue in existing:
3334 if queue == name:
3334 if queue == name:
3335 continue
3335 continue
3336 fh.write('%s\n' % (queue,))
3336 fh.write('%s\n' % (queue,))
3337 fh.close()
3337 fh.close()
3338 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3338 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3339
3339
3340 if not name or opts.get('list') or opts.get('active'):
3340 if not name or opts.get('list') or opts.get('active'):
3341 current = _getcurrent()
3341 current = _getcurrent()
3342 if opts.get('active'):
3342 if opts.get('active'):
3343 ui.write('%s\n' % (current,))
3343 ui.write('%s\n' % (current,))
3344 return
3344 return
3345 for queue in _getqueues():
3345 for queue in _getqueues():
3346 ui.write('%s' % (queue,))
3346 ui.write('%s' % (queue,))
3347 if queue == current and not ui.quiet:
3347 if queue == current and not ui.quiet:
3348 ui.write(_(' (active)\n'))
3348 ui.write(_(' (active)\n'))
3349 else:
3349 else:
3350 ui.write('\n')
3350 ui.write('\n')
3351 return
3351 return
3352
3352
3353 if not _validname(name):
3353 if not _validname(name):
3354 raise error.Abort(
3354 raise error.Abort(
3355 _('invalid queue name, may not contain the characters ":\\/."'))
3355 _('invalid queue name, may not contain the characters ":\\/."'))
3356
3356
3357 with repo.wlock():
3357 with repo.wlock():
3358 existing = _getqueues()
3358 existing = _getqueues()
3359
3359
3360 if opts.get('create'):
3360 if opts.get('create'):
3361 if name in existing:
3361 if name in existing:
3362 raise error.Abort(_('queue "%s" already exists') % name)
3362 raise error.Abort(_('queue "%s" already exists') % name)
3363 if _noqueues():
3363 if _noqueues():
3364 _addqueue(_defaultqueue)
3364 _addqueue(_defaultqueue)
3365 _addqueue(name)
3365 _addqueue(name)
3366 _setactive(name)
3366 _setactive(name)
3367 elif opts.get('rename'):
3367 elif opts.get('rename'):
3368 current = _getcurrent()
3368 current = _getcurrent()
3369 if name == current:
3369 if name == current:
3370 raise error.Abort(_('can\'t rename "%s" to its current name')
3370 raise error.Abort(_('can\'t rename "%s" to its current name')
3371 % name)
3371 % name)
3372 if name in existing:
3372 if name in existing:
3373 raise error.Abort(_('queue "%s" already exists') % name)
3373 raise error.Abort(_('queue "%s" already exists') % name)
3374
3374
3375 olddir = _queuedir(current)
3375 olddir = _queuedir(current)
3376 newdir = _queuedir(name)
3376 newdir = _queuedir(name)
3377
3377
3378 if os.path.exists(newdir):
3378 if os.path.exists(newdir):
3379 raise error.Abort(_('non-queue directory "%s" already exists') %
3379 raise error.Abort(_('non-queue directory "%s" already exists') %
3380 newdir)
3380 newdir)
3381
3381
3382 fh = repo.vfs('patches.queues.new', 'w')
3382 fh = repo.vfs('patches.queues.new', 'w')
3383 for queue in existing:
3383 for queue in existing:
3384 if queue == current:
3384 if queue == current:
3385 fh.write('%s\n' % (name,))
3385 fh.write('%s\n' % (name,))
3386 if os.path.exists(olddir):
3386 if os.path.exists(olddir):
3387 util.rename(olddir, newdir)
3387 util.rename(olddir, newdir)
3388 else:
3388 else:
3389 fh.write('%s\n' % (queue,))
3389 fh.write('%s\n' % (queue,))
3390 fh.close()
3390 fh.close()
3391 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3391 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3392 _setactivenocheck(name)
3392 _setactivenocheck(name)
3393 elif opts.get('delete'):
3393 elif opts.get('delete'):
3394 _delete(name)
3394 _delete(name)
3395 elif opts.get('purge'):
3395 elif opts.get('purge'):
3396 if name in existing:
3396 if name in existing:
3397 _delete(name)
3397 _delete(name)
3398 qdir = _queuedir(name)
3398 qdir = _queuedir(name)
3399 if os.path.exists(qdir):
3399 if os.path.exists(qdir):
3400 shutil.rmtree(qdir)
3400 shutil.rmtree(qdir)
3401 else:
3401 else:
3402 if name not in existing:
3402 if name not in existing:
3403 raise error.Abort(_('use --create to create a new queue'))
3403 raise error.Abort(_('use --create to create a new queue'))
3404 _setactive(name)
3404 _setactive(name)
3405
3405
3406 def mqphasedefaults(repo, roots):
3406 def mqphasedefaults(repo, roots):
3407 """callback used to set mq changeset as secret when no phase data exists"""
3407 """callback used to set mq changeset as secret when no phase data exists"""
3408 if repo.mq.applied:
3408 if repo.mq.applied:
3409 if repo.ui.configbool('mq', 'secret', False):
3409 if repo.ui.configbool('mq', 'secret', False):
3410 mqphase = phases.secret
3410 mqphase = phases.secret
3411 else:
3411 else:
3412 mqphase = phases.draft
3412 mqphase = phases.draft
3413 qbase = repo[repo.mq.applied[0].node]
3413 qbase = repo[repo.mq.applied[0].node]
3414 roots[mqphase].add(qbase.node())
3414 roots[mqphase].add(qbase.node())
3415 return roots
3415 return roots
3416
3416
3417 def reposetup(ui, repo):
3417 def reposetup(ui, repo):
3418 class mqrepo(repo.__class__):
3418 class mqrepo(repo.__class__):
3419 @localrepo.unfilteredpropertycache
3419 @localrepo.unfilteredpropertycache
3420 def mq(self):
3420 def mq(self):
3421 return queue(self.ui, self.baseui, self.path)
3421 return queue(self.ui, self.baseui, self.path)
3422
3422
3423 def invalidateall(self):
3423 def invalidateall(self):
3424 super(mqrepo, self).invalidateall()
3424 super(mqrepo, self).invalidateall()
3425 if localrepo.hasunfilteredcache(self, 'mq'):
3425 if localrepo.hasunfilteredcache(self, 'mq'):
3426 # recreate mq in case queue path was changed
3426 # recreate mq in case queue path was changed
3427 delattr(self.unfiltered(), 'mq')
3427 delattr(self.unfiltered(), 'mq')
3428
3428
3429 def abortifwdirpatched(self, errmsg, force=False):
3429 def abortifwdirpatched(self, errmsg, force=False):
3430 if self.mq.applied and self.mq.checkapplied and not force:
3430 if self.mq.applied and self.mq.checkapplied and not force:
3431 parents = self.dirstate.parents()
3431 parents = self.dirstate.parents()
3432 patches = [s.node for s in self.mq.applied]
3432 patches = [s.node for s in self.mq.applied]
3433 if parents[0] in patches or parents[1] in patches:
3433 if parents[0] in patches or parents[1] in patches:
3434 raise error.Abort(errmsg)
3434 raise error.Abort(errmsg)
3435
3435
3436 def commit(self, text="", user=None, date=None, match=None,
3436 def commit(self, text="", user=None, date=None, match=None,
3437 force=False, editor=False, extra={}):
3437 force=False, editor=False, extra={}):
3438 self.abortifwdirpatched(
3438 self.abortifwdirpatched(
3439 _('cannot commit over an applied mq patch'),
3439 _('cannot commit over an applied mq patch'),
3440 force)
3440 force)
3441
3441
3442 return super(mqrepo, self).commit(text, user, date, match, force,
3442 return super(mqrepo, self).commit(text, user, date, match, force,
3443 editor, extra)
3443 editor, extra)
3444
3444
3445 def checkpush(self, pushop):
3445 def checkpush(self, pushop):
3446 if self.mq.applied and self.mq.checkapplied and not pushop.force:
3446 if self.mq.applied and self.mq.checkapplied and not pushop.force:
3447 outapplied = [e.node for e in self.mq.applied]
3447 outapplied = [e.node for e in self.mq.applied]
3448 if pushop.revs:
3448 if pushop.revs:
3449 # Assume applied patches have no non-patch descendants and
3449 # Assume applied patches have no non-patch descendants and
3450 # are not on remote already. Filtering any changeset not
3450 # are not on remote already. Filtering any changeset not
3451 # pushed.
3451 # pushed.
3452 heads = set(pushop.revs)
3452 heads = set(pushop.revs)
3453 for node in reversed(outapplied):
3453 for node in reversed(outapplied):
3454 if node in heads:
3454 if node in heads:
3455 break
3455 break
3456 else:
3456 else:
3457 outapplied.pop()
3457 outapplied.pop()
3458 # looking for pushed and shared changeset
3458 # looking for pushed and shared changeset
3459 for node in outapplied:
3459 for node in outapplied:
3460 if self[node].phase() < phases.secret:
3460 if self[node].phase() < phases.secret:
3461 raise error.Abort(_('source has mq patches applied'))
3461 raise error.Abort(_('source has mq patches applied'))
3462 # no non-secret patches pushed
3462 # no non-secret patches pushed
3463 super(mqrepo, self).checkpush(pushop)
3463 super(mqrepo, self).checkpush(pushop)
3464
3464
3465 def _findtags(self):
3465 def _findtags(self):
3466 '''augment tags from base class with patch tags'''
3466 '''augment tags from base class with patch tags'''
3467 result = super(mqrepo, self)._findtags()
3467 result = super(mqrepo, self)._findtags()
3468
3468
3469 q = self.mq
3469 q = self.mq
3470 if not q.applied:
3470 if not q.applied:
3471 return result
3471 return result
3472
3472
3473 mqtags = [(patch.node, patch.name) for patch in q.applied]
3473 mqtags = [(patch.node, patch.name) for patch in q.applied]
3474
3474
3475 try:
3475 try:
3476 # for now ignore filtering business
3476 # for now ignore filtering business
3477 self.unfiltered().changelog.rev(mqtags[-1][0])
3477 self.unfiltered().changelog.rev(mqtags[-1][0])
3478 except error.LookupError:
3478 except error.LookupError:
3479 self.ui.warn(_('mq status file refers to unknown node %s\n')
3479 self.ui.warn(_('mq status file refers to unknown node %s\n')
3480 % short(mqtags[-1][0]))
3480 % short(mqtags[-1][0]))
3481 return result
3481 return result
3482
3482
3483 # do not add fake tags for filtered revisions
3483 # do not add fake tags for filtered revisions
3484 included = self.changelog.hasnode
3484 included = self.changelog.hasnode
3485 mqtags = [mqt for mqt in mqtags if included(mqt[0])]
3485 mqtags = [mqt for mqt in mqtags if included(mqt[0])]
3486 if not mqtags:
3486 if not mqtags:
3487 return result
3487 return result
3488
3488
3489 mqtags.append((mqtags[-1][0], 'qtip'))
3489 mqtags.append((mqtags[-1][0], 'qtip'))
3490 mqtags.append((mqtags[0][0], 'qbase'))
3490 mqtags.append((mqtags[0][0], 'qbase'))
3491 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3491 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3492 tags = result[0]
3492 tags = result[0]
3493 for patch in mqtags:
3493 for patch in mqtags:
3494 if patch[1] in tags:
3494 if patch[1] in tags:
3495 self.ui.warn(_('tag %s overrides mq patch of the same '
3495 self.ui.warn(_('tag %s overrides mq patch of the same '
3496 'name\n') % patch[1])
3496 'name\n') % patch[1])
3497 else:
3497 else:
3498 tags[patch[1]] = patch[0]
3498 tags[patch[1]] = patch[0]
3499
3499
3500 return result
3500 return result
3501
3501
3502 if repo.local():
3502 if repo.local():
3503 repo.__class__ = mqrepo
3503 repo.__class__ = mqrepo
3504
3504
3505 repo._phasedefaults.append(mqphasedefaults)
3505 repo._phasedefaults.append(mqphasedefaults)
3506
3506
3507 def mqimport(orig, ui, repo, *args, **kwargs):
3507 def mqimport(orig, ui, repo, *args, **kwargs):
3508 if (util.safehasattr(repo, 'abortifwdirpatched')
3508 if (util.safehasattr(repo, 'abortifwdirpatched')
3509 and not kwargs.get('no_commit', False)):
3509 and not kwargs.get('no_commit', False)):
3510 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3510 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3511 kwargs.get('force'))
3511 kwargs.get('force'))
3512 return orig(ui, repo, *args, **kwargs)
3512 return orig(ui, repo, *args, **kwargs)
3513
3513
3514 def mqinit(orig, ui, *args, **kwargs):
3514 def mqinit(orig, ui, *args, **kwargs):
3515 mq = kwargs.pop('mq', None)
3515 mq = kwargs.pop('mq', None)
3516
3516
3517 if not mq:
3517 if not mq:
3518 return orig(ui, *args, **kwargs)
3518 return orig(ui, *args, **kwargs)
3519
3519
3520 if args:
3520 if args:
3521 repopath = args[0]
3521 repopath = args[0]
3522 if not hg.islocal(repopath):
3522 if not hg.islocal(repopath):
3523 raise error.Abort(_('only a local queue repository '
3523 raise error.Abort(_('only a local queue repository '
3524 'may be initialized'))
3524 'may be initialized'))
3525 else:
3525 else:
3526 repopath = cmdutil.findrepo(os.getcwd())
3526 repopath = cmdutil.findrepo(os.getcwd())
3527 if not repopath:
3527 if not repopath:
3528 raise error.Abort(_('there is no Mercurial repository here '
3528 raise error.Abort(_('there is no Mercurial repository here '
3529 '(.hg not found)'))
3529 '(.hg not found)'))
3530 repo = hg.repository(ui, repopath)
3530 repo = hg.repository(ui, repopath)
3531 return qinit(ui, repo, True)
3531 return qinit(ui, repo, True)
3532
3532
3533 def mqcommand(orig, ui, repo, *args, **kwargs):
3533 def mqcommand(orig, ui, repo, *args, **kwargs):
3534 """Add --mq option to operate on patch repository instead of main"""
3534 """Add --mq option to operate on patch repository instead of main"""
3535
3535
3536 # some commands do not like getting unknown options
3536 # some commands do not like getting unknown options
3537 mq = kwargs.pop('mq', None)
3537 mq = kwargs.pop('mq', None)
3538
3538
3539 if not mq:
3539 if not mq:
3540 return orig(ui, repo, *args, **kwargs)
3540 return orig(ui, repo, *args, **kwargs)
3541
3541
3542 q = repo.mq
3542 q = repo.mq
3543 r = q.qrepo()
3543 r = q.qrepo()
3544 if not r:
3544 if not r:
3545 raise error.Abort(_('no queue repository'))
3545 raise error.Abort(_('no queue repository'))
3546 return orig(r.ui, r, *args, **kwargs)
3546 return orig(r.ui, r, *args, **kwargs)
3547
3547
3548 def summaryhook(ui, repo):
3548 def summaryhook(ui, repo):
3549 q = repo.mq
3549 q = repo.mq
3550 m = []
3550 m = []
3551 a, u = len(q.applied), len(q.unapplied(repo))
3551 a, u = len(q.applied), len(q.unapplied(repo))
3552 if a:
3552 if a:
3553 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3553 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3554 if u:
3554 if u:
3555 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3555 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3556 if m:
3556 if m:
3557 # i18n: column positioning for "hg summary"
3557 # i18n: column positioning for "hg summary"
3558 ui.write(_("mq: %s\n") % ', '.join(m))
3558 ui.write(_("mq: %s\n") % ', '.join(m))
3559 else:
3559 else:
3560 # i18n: column positioning for "hg summary"
3560 # i18n: column positioning for "hg summary"
3561 ui.note(_("mq: (empty queue)\n"))
3561 ui.note(_("mq: (empty queue)\n"))
3562
3562
3563 revsetpredicate = registrar.revsetpredicate()
3563 revsetpredicate = registrar.revsetpredicate()
3564
3564
3565 @revsetpredicate('mq()')
3565 @revsetpredicate('mq()')
3566 def revsetmq(repo, subset, x):
3566 def revsetmq(repo, subset, x):
3567 """Changesets managed by MQ.
3567 """Changesets managed by MQ.
3568 """
3568 """
3569 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3569 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3570 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3570 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3571 return revset.baseset([r for r in subset if r in applied])
3571 return revset.baseset([r for r in subset if r in applied])
3572
3572
3573 # tell hggettext to extract docstrings from these functions:
3573 # tell hggettext to extract docstrings from these functions:
3574 i18nfunctions = [revsetmq]
3574 i18nfunctions = [revsetmq]
3575
3575
3576 def extsetup(ui):
3576 def extsetup(ui):
3577 # Ensure mq wrappers are called first, regardless of extension load order by
3577 # Ensure mq wrappers are called first, regardless of extension load order by
3578 # NOT wrapping in uisetup() and instead deferring to init stage two here.
3578 # NOT wrapping in uisetup() and instead deferring to init stage two here.
3579 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3579 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3580
3580
3581 extensions.wrapcommand(commands.table, 'import', mqimport)
3581 extensions.wrapcommand(commands.table, 'import', mqimport)
3582 cmdutil.summaryhooks.add('mq', summaryhook)
3582 cmdutil.summaryhooks.add('mq', summaryhook)
3583
3583
3584 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3584 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3585 entry[1].extend(mqopt)
3585 entry[1].extend(mqopt)
3586
3586
3587 def dotable(cmdtable):
3587 def dotable(cmdtable):
3588 for cmd, entry in cmdtable.iteritems():
3588 for cmd, entry in cmdtable.iteritems():
3589 cmd = cmdutil.parsealiases(cmd)[0]
3589 cmd = cmdutil.parsealiases(cmd)[0]
3590 func = entry[0]
3590 func = entry[0]
3591 if dispatch._cmdattr(ui, cmd, func, 'norepo'):
3591 if dispatch._cmdattr(ui, cmd, func, 'norepo'):
3592 continue
3592 continue
3593 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3593 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3594 entry[1].extend(mqopt)
3594 entry[1].extend(mqopt)
3595
3595
3596 dotable(commands.table)
3596 dotable(commands.table)
3597
3597
3598 for extname, extmodule in extensions.extensions():
3598 for extname, extmodule in extensions.extensions():
3599 if extmodule.__file__ != __file__:
3599 if extmodule.__file__ != __file__:
3600 dotable(getattr(extmodule, 'cmdtable', {}))
3600 dotable(getattr(extmodule, 'cmdtable', {}))
3601
3601
3602 colortable = {'qguard.negative': 'red',
3602 colortable = {'qguard.negative': 'red',
3603 'qguard.positive': 'yellow',
3603 'qguard.positive': 'yellow',
3604 'qguard.unguarded': 'green',
3604 'qguard.unguarded': 'green',
3605 'qseries.applied': 'blue bold underline',
3605 'qseries.applied': 'blue bold underline',
3606 'qseries.guarded': 'black bold',
3606 'qseries.guarded': 'black bold',
3607 'qseries.missing': 'red bold',
3607 'qseries.missing': 'red bold',
3608 'qseries.unapplied': 'black bold'}
3608 'qseries.unapplied': 'black bold'}
@@ -1,1626 +1,1599 b''
1 # manifest.py - manifest revision class for mercurial
1 # manifest.py - manifest revision class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import array
10 import array
11 import heapq
11 import heapq
12 import os
12 import os
13 import struct
13 import struct
14
14
15 from .i18n import _
15 from .i18n import _
16 from . import (
16 from . import (
17 error,
17 error,
18 mdiff,
18 mdiff,
19 parsers,
19 parsers,
20 revlog,
20 revlog,
21 util,
21 util,
22 )
22 )
23
23
24 propertycache = util.propertycache
24 propertycache = util.propertycache
25
25
26 def _parsev1(data):
26 def _parsev1(data):
27 # This method does a little bit of excessive-looking
27 # This method does a little bit of excessive-looking
28 # precondition checking. This is so that the behavior of this
28 # precondition checking. This is so that the behavior of this
29 # class exactly matches its C counterpart to try and help
29 # class exactly matches its C counterpart to try and help
30 # prevent surprise breakage for anyone that develops against
30 # prevent surprise breakage for anyone that develops against
31 # the pure version.
31 # the pure version.
32 if data and data[-1] != '\n':
32 if data and data[-1] != '\n':
33 raise ValueError('Manifest did not end in a newline.')
33 raise ValueError('Manifest did not end in a newline.')
34 prev = None
34 prev = None
35 for l in data.splitlines():
35 for l in data.splitlines():
36 if prev is not None and prev > l:
36 if prev is not None and prev > l:
37 raise ValueError('Manifest lines not in sorted order.')
37 raise ValueError('Manifest lines not in sorted order.')
38 prev = l
38 prev = l
39 f, n = l.split('\0')
39 f, n = l.split('\0')
40 if len(n) > 40:
40 if len(n) > 40:
41 yield f, revlog.bin(n[:40]), n[40:]
41 yield f, revlog.bin(n[:40]), n[40:]
42 else:
42 else:
43 yield f, revlog.bin(n), ''
43 yield f, revlog.bin(n), ''
44
44
45 def _parsev2(data):
45 def _parsev2(data):
46 metadataend = data.find('\n')
46 metadataend = data.find('\n')
47 # Just ignore metadata for now
47 # Just ignore metadata for now
48 pos = metadataend + 1
48 pos = metadataend + 1
49 prevf = ''
49 prevf = ''
50 while pos < len(data):
50 while pos < len(data):
51 end = data.find('\n', pos + 1) # +1 to skip stem length byte
51 end = data.find('\n', pos + 1) # +1 to skip stem length byte
52 if end == -1:
52 if end == -1:
53 raise ValueError('Manifest ended with incomplete file entry.')
53 raise ValueError('Manifest ended with incomplete file entry.')
54 stemlen = ord(data[pos])
54 stemlen = ord(data[pos])
55 items = data[pos + 1:end].split('\0')
55 items = data[pos + 1:end].split('\0')
56 f = prevf[:stemlen] + items[0]
56 f = prevf[:stemlen] + items[0]
57 if prevf > f:
57 if prevf > f:
58 raise ValueError('Manifest entries not in sorted order.')
58 raise ValueError('Manifest entries not in sorted order.')
59 fl = items[1]
59 fl = items[1]
60 # Just ignore metadata (items[2:] for now)
60 # Just ignore metadata (items[2:] for now)
61 n = data[end + 1:end + 21]
61 n = data[end + 1:end + 21]
62 yield f, n, fl
62 yield f, n, fl
63 pos = end + 22
63 pos = end + 22
64 prevf = f
64 prevf = f
65
65
66 def _parse(data):
66 def _parse(data):
67 """Generates (path, node, flags) tuples from a manifest text"""
67 """Generates (path, node, flags) tuples from a manifest text"""
68 if data.startswith('\0'):
68 if data.startswith('\0'):
69 return iter(_parsev2(data))
69 return iter(_parsev2(data))
70 else:
70 else:
71 return iter(_parsev1(data))
71 return iter(_parsev1(data))
72
72
73 def _text(it, usemanifestv2):
73 def _text(it, usemanifestv2):
74 """Given an iterator over (path, node, flags) tuples, returns a manifest
74 """Given an iterator over (path, node, flags) tuples, returns a manifest
75 text"""
75 text"""
76 if usemanifestv2:
76 if usemanifestv2:
77 return _textv2(it)
77 return _textv2(it)
78 else:
78 else:
79 return _textv1(it)
79 return _textv1(it)
80
80
81 def _textv1(it):
81 def _textv1(it):
82 files = []
82 files = []
83 lines = []
83 lines = []
84 _hex = revlog.hex
84 _hex = revlog.hex
85 for f, n, fl in it:
85 for f, n, fl in it:
86 files.append(f)
86 files.append(f)
87 # if this is changed to support newlines in filenames,
87 # if this is changed to support newlines in filenames,
88 # be sure to check the templates/ dir again (especially *-raw.tmpl)
88 # be sure to check the templates/ dir again (especially *-raw.tmpl)
89 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
89 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
90
90
91 _checkforbidden(files)
91 _checkforbidden(files)
92 return ''.join(lines)
92 return ''.join(lines)
93
93
94 def _textv2(it):
94 def _textv2(it):
95 files = []
95 files = []
96 lines = ['\0\n']
96 lines = ['\0\n']
97 prevf = ''
97 prevf = ''
98 for f, n, fl in it:
98 for f, n, fl in it:
99 files.append(f)
99 files.append(f)
100 stem = os.path.commonprefix([prevf, f])
100 stem = os.path.commonprefix([prevf, f])
101 stemlen = min(len(stem), 255)
101 stemlen = min(len(stem), 255)
102 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
102 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
103 prevf = f
103 prevf = f
104 _checkforbidden(files)
104 _checkforbidden(files)
105 return ''.join(lines)
105 return ''.join(lines)
106
106
107 class lazymanifestiter(object):
107 class lazymanifestiter(object):
108 def __init__(self, lm):
108 def __init__(self, lm):
109 self.pos = 0
109 self.pos = 0
110 self.lm = lm
110 self.lm = lm
111
111
112 def __iter__(self):
112 def __iter__(self):
113 return self
113 return self
114
114
115 def next(self):
115 def next(self):
116 try:
116 try:
117 data, pos = self.lm._get(self.pos)
117 data, pos = self.lm._get(self.pos)
118 except IndexError:
118 except IndexError:
119 raise StopIteration
119 raise StopIteration
120 if pos == -1:
120 if pos == -1:
121 self.pos += 1
121 self.pos += 1
122 return data[0]
122 return data[0]
123 self.pos += 1
123 self.pos += 1
124 zeropos = data.find('\x00', pos)
124 zeropos = data.find('\x00', pos)
125 return data[pos:zeropos]
125 return data[pos:zeropos]
126
126
127 class lazymanifestiterentries(object):
127 class lazymanifestiterentries(object):
128 def __init__(self, lm):
128 def __init__(self, lm):
129 self.lm = lm
129 self.lm = lm
130 self.pos = 0
130 self.pos = 0
131
131
132 def __iter__(self):
132 def __iter__(self):
133 return self
133 return self
134
134
135 def next(self):
135 def next(self):
136 try:
136 try:
137 data, pos = self.lm._get(self.pos)
137 data, pos = self.lm._get(self.pos)
138 except IndexError:
138 except IndexError:
139 raise StopIteration
139 raise StopIteration
140 if pos == -1:
140 if pos == -1:
141 self.pos += 1
141 self.pos += 1
142 return data
142 return data
143 zeropos = data.find('\x00', pos)
143 zeropos = data.find('\x00', pos)
144 hashval = unhexlify(data, self.lm.extrainfo[self.pos],
144 hashval = unhexlify(data, self.lm.extrainfo[self.pos],
145 zeropos + 1, 40)
145 zeropos + 1, 40)
146 flags = self.lm._getflags(data, self.pos, zeropos)
146 flags = self.lm._getflags(data, self.pos, zeropos)
147 self.pos += 1
147 self.pos += 1
148 return (data[pos:zeropos], hashval, flags)
148 return (data[pos:zeropos], hashval, flags)
149
149
150 def unhexlify(data, extra, pos, length):
150 def unhexlify(data, extra, pos, length):
151 s = data[pos:pos + length].decode('hex')
151 s = data[pos:pos + length].decode('hex')
152 if extra:
152 if extra:
153 s += chr(extra & 0xff)
153 s += chr(extra & 0xff)
154 return s
154 return s
155
155
156 def _cmp(a, b):
156 def _cmp(a, b):
157 return (a > b) - (a < b)
157 return (a > b) - (a < b)
158
158
159 class _lazymanifest(object):
159 class _lazymanifest(object):
160 def __init__(self, data, positions=None, extrainfo=None, extradata=None):
160 def __init__(self, data, positions=None, extrainfo=None, extradata=None):
161 if positions is None:
161 if positions is None:
162 self.positions = self.findlines(data)
162 self.positions = self.findlines(data)
163 self.extrainfo = [0] * len(self.positions)
163 self.extrainfo = [0] * len(self.positions)
164 self.data = data
164 self.data = data
165 self.extradata = []
165 self.extradata = []
166 else:
166 else:
167 self.positions = positions[:]
167 self.positions = positions[:]
168 self.extrainfo = extrainfo[:]
168 self.extrainfo = extrainfo[:]
169 self.extradata = extradata[:]
169 self.extradata = extradata[:]
170 self.data = data
170 self.data = data
171
171
172 def findlines(self, data):
172 def findlines(self, data):
173 if not data:
173 if not data:
174 return []
174 return []
175 pos = data.find("\n")
175 pos = data.find("\n")
176 if pos == -1 or data[-1] != '\n':
176 if pos == -1 or data[-1] != '\n':
177 raise ValueError("Manifest did not end in a newline.")
177 raise ValueError("Manifest did not end in a newline.")
178 positions = [0]
178 positions = [0]
179 prev = data[:data.find('\x00')]
179 prev = data[:data.find('\x00')]
180 while pos < len(data) - 1 and pos != -1:
180 while pos < len(data) - 1 and pos != -1:
181 positions.append(pos + 1)
181 positions.append(pos + 1)
182 nexts = data[pos + 1:data.find('\x00', pos + 1)]
182 nexts = data[pos + 1:data.find('\x00', pos + 1)]
183 if nexts < prev:
183 if nexts < prev:
184 raise ValueError("Manifest lines not in sorted order.")
184 raise ValueError("Manifest lines not in sorted order.")
185 prev = nexts
185 prev = nexts
186 pos = data.find("\n", pos + 1)
186 pos = data.find("\n", pos + 1)
187 return positions
187 return positions
188
188
189 def _get(self, index):
189 def _get(self, index):
190 # get the position encoded in pos:
190 # get the position encoded in pos:
191 # positive number is an index in 'data'
191 # positive number is an index in 'data'
192 # negative number is in extrapieces
192 # negative number is in extrapieces
193 pos = self.positions[index]
193 pos = self.positions[index]
194 if pos >= 0:
194 if pos >= 0:
195 return self.data, pos
195 return self.data, pos
196 return self.extradata[-pos - 1], -1
196 return self.extradata[-pos - 1], -1
197
197
198 def _getkey(self, pos):
198 def _getkey(self, pos):
199 if pos >= 0:
199 if pos >= 0:
200 return self.data[pos:self.data.find('\x00', pos + 1)]
200 return self.data[pos:self.data.find('\x00', pos + 1)]
201 return self.extradata[-pos - 1][0]
201 return self.extradata[-pos - 1][0]
202
202
203 def bsearch(self, key):
203 def bsearch(self, key):
204 first = 0
204 first = 0
205 last = len(self.positions) - 1
205 last = len(self.positions) - 1
206
206
207 while first <= last:
207 while first <= last:
208 midpoint = (first + last)//2
208 midpoint = (first + last)//2
209 nextpos = self.positions[midpoint]
209 nextpos = self.positions[midpoint]
210 candidate = self._getkey(nextpos)
210 candidate = self._getkey(nextpos)
211 r = _cmp(key, candidate)
211 r = _cmp(key, candidate)
212 if r == 0:
212 if r == 0:
213 return midpoint
213 return midpoint
214 else:
214 else:
215 if r < 0:
215 if r < 0:
216 last = midpoint - 1
216 last = midpoint - 1
217 else:
217 else:
218 first = midpoint + 1
218 first = midpoint + 1
219 return -1
219 return -1
220
220
221 def bsearch2(self, key):
221 def bsearch2(self, key):
222 # same as the above, but will always return the position
222 # same as the above, but will always return the position
223 # done for performance reasons
223 # done for performance reasons
224 first = 0
224 first = 0
225 last = len(self.positions) - 1
225 last = len(self.positions) - 1
226
226
227 while first <= last:
227 while first <= last:
228 midpoint = (first + last)//2
228 midpoint = (first + last)//2
229 nextpos = self.positions[midpoint]
229 nextpos = self.positions[midpoint]
230 candidate = self._getkey(nextpos)
230 candidate = self._getkey(nextpos)
231 r = _cmp(key, candidate)
231 r = _cmp(key, candidate)
232 if r == 0:
232 if r == 0:
233 return (midpoint, True)
233 return (midpoint, True)
234 else:
234 else:
235 if r < 0:
235 if r < 0:
236 last = midpoint - 1
236 last = midpoint - 1
237 else:
237 else:
238 first = midpoint + 1
238 first = midpoint + 1
239 return (first, False)
239 return (first, False)
240
240
241 def __contains__(self, key):
241 def __contains__(self, key):
242 return self.bsearch(key) != -1
242 return self.bsearch(key) != -1
243
243
244 def _getflags(self, data, needle, pos):
244 def _getflags(self, data, needle, pos):
245 start = pos + 41
245 start = pos + 41
246 end = data.find("\n", start)
246 end = data.find("\n", start)
247 if end == -1:
247 if end == -1:
248 end = len(data) - 1
248 end = len(data) - 1
249 if start == end:
249 if start == end:
250 return ''
250 return ''
251 return self.data[start:end]
251 return self.data[start:end]
252
252
253 def __getitem__(self, key):
253 def __getitem__(self, key):
254 if not isinstance(key, str):
254 if not isinstance(key, str):
255 raise TypeError("getitem: manifest keys must be a string.")
255 raise TypeError("getitem: manifest keys must be a string.")
256 needle = self.bsearch(key)
256 needle = self.bsearch(key)
257 if needle == -1:
257 if needle == -1:
258 raise KeyError
258 raise KeyError
259 data, pos = self._get(needle)
259 data, pos = self._get(needle)
260 if pos == -1:
260 if pos == -1:
261 return (data[1], data[2])
261 return (data[1], data[2])
262 zeropos = data.find('\x00', pos)
262 zeropos = data.find('\x00', pos)
263 assert 0 <= needle <= len(self.positions)
263 assert 0 <= needle <= len(self.positions)
264 assert len(self.extrainfo) == len(self.positions)
264 assert len(self.extrainfo) == len(self.positions)
265 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
265 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
266 flags = self._getflags(data, needle, zeropos)
266 flags = self._getflags(data, needle, zeropos)
267 return (hashval, flags)
267 return (hashval, flags)
268
268
269 def __delitem__(self, key):
269 def __delitem__(self, key):
270 needle, found = self.bsearch2(key)
270 needle, found = self.bsearch2(key)
271 if not found:
271 if not found:
272 raise KeyError
272 raise KeyError
273 cur = self.positions[needle]
273 cur = self.positions[needle]
274 self.positions = self.positions[:needle] + self.positions[needle + 1:]
274 self.positions = self.positions[:needle] + self.positions[needle + 1:]
275 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1:]
275 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1:]
276 if cur >= 0:
276 if cur >= 0:
277 self.data = self.data[:cur] + '\x00' + self.data[cur + 1:]
277 self.data = self.data[:cur] + '\x00' + self.data[cur + 1:]
278
278
279 def __setitem__(self, key, value):
279 def __setitem__(self, key, value):
280 if not isinstance(key, str):
280 if not isinstance(key, str):
281 raise TypeError("setitem: manifest keys must be a string.")
281 raise TypeError("setitem: manifest keys must be a string.")
282 if not isinstance(value, tuple) or len(value) != 2:
282 if not isinstance(value, tuple) or len(value) != 2:
283 raise TypeError("Manifest values must be a tuple of (node, flags).")
283 raise TypeError("Manifest values must be a tuple of (node, flags).")
284 hashval = value[0]
284 hashval = value[0]
285 if not isinstance(hashval, str) or not 20 <= len(hashval) <= 22:
285 if not isinstance(hashval, str) or not 20 <= len(hashval) <= 22:
286 raise TypeError("node must be a 20-byte string")
286 raise TypeError("node must be a 20-byte string")
287 flags = value[1]
287 flags = value[1]
288 if len(hashval) == 22:
288 if len(hashval) == 22:
289 hashval = hashval[:-1]
289 hashval = hashval[:-1]
290 if not isinstance(flags, str) or len(flags) > 1:
290 if not isinstance(flags, str) or len(flags) > 1:
291 raise TypeError("flags must a 0 or 1 byte string, got %r", flags)
291 raise TypeError("flags must a 0 or 1 byte string, got %r", flags)
292 needle, found = self.bsearch2(key)
292 needle, found = self.bsearch2(key)
293 if found:
293 if found:
294 # put the item
294 # put the item
295 pos = self.positions[needle]
295 pos = self.positions[needle]
296 if pos < 0:
296 if pos < 0:
297 self.extradata[-pos - 1] = (key, hashval, value[1])
297 self.extradata[-pos - 1] = (key, hashval, value[1])
298 else:
298 else:
299 # just don't bother
299 # just don't bother
300 self.extradata.append((key, hashval, value[1]))
300 self.extradata.append((key, hashval, value[1]))
301 self.positions[needle] = -len(self.extradata)
301 self.positions[needle] = -len(self.extradata)
302 else:
302 else:
303 # not found, put it in with extra positions
303 # not found, put it in with extra positions
304 self.extradata.append((key, hashval, value[1]))
304 self.extradata.append((key, hashval, value[1]))
305 self.positions = (self.positions[:needle] + [-len(self.extradata)]
305 self.positions = (self.positions[:needle] + [-len(self.extradata)]
306 + self.positions[needle:])
306 + self.positions[needle:])
307 self.extrainfo = (self.extrainfo[:needle] + [0] +
307 self.extrainfo = (self.extrainfo[:needle] + [0] +
308 self.extrainfo[needle:])
308 self.extrainfo[needle:])
309
309
310 def copy(self):
310 def copy(self):
311 # XXX call _compact like in C?
311 # XXX call _compact like in C?
312 return _lazymanifest(self.data, self.positions, self.extrainfo,
312 return _lazymanifest(self.data, self.positions, self.extrainfo,
313 self.extradata)
313 self.extradata)
314
314
315 def _compact(self):
315 def _compact(self):
316 # hopefully not called TOO often
316 # hopefully not called TOO often
317 if len(self.extradata) == 0:
317 if len(self.extradata) == 0:
318 return
318 return
319 l = []
319 l = []
320 last_cut = 0
320 last_cut = 0
321 i = 0
321 i = 0
322 offset = 0
322 offset = 0
323 self.extrainfo = [0] * len(self.positions)
323 self.extrainfo = [0] * len(self.positions)
324 while i < len(self.positions):
324 while i < len(self.positions):
325 if self.positions[i] >= 0:
325 if self.positions[i] >= 0:
326 cur = self.positions[i]
326 cur = self.positions[i]
327 last_cut = cur
327 last_cut = cur
328 while True:
328 while True:
329 self.positions[i] = offset
329 self.positions[i] = offset
330 i += 1
330 i += 1
331 if i == len(self.positions) or self.positions[i] < 0:
331 if i == len(self.positions) or self.positions[i] < 0:
332 break
332 break
333 offset += self.positions[i] - cur
333 offset += self.positions[i] - cur
334 cur = self.positions[i]
334 cur = self.positions[i]
335 end_cut = self.data.find('\n', cur)
335 end_cut = self.data.find('\n', cur)
336 if end_cut != -1:
336 if end_cut != -1:
337 end_cut += 1
337 end_cut += 1
338 offset += end_cut - cur
338 offset += end_cut - cur
339 l.append(self.data[last_cut:end_cut])
339 l.append(self.data[last_cut:end_cut])
340 else:
340 else:
341 while i < len(self.positions) and self.positions[i] < 0:
341 while i < len(self.positions) and self.positions[i] < 0:
342 cur = self.positions[i]
342 cur = self.positions[i]
343 t = self.extradata[-cur - 1]
343 t = self.extradata[-cur - 1]
344 l.append(self._pack(t))
344 l.append(self._pack(t))
345 self.positions[i] = offset
345 self.positions[i] = offset
346 if len(t[1]) > 20:
346 if len(t[1]) > 20:
347 self.extrainfo[i] = ord(t[1][21])
347 self.extrainfo[i] = ord(t[1][21])
348 offset += len(l[-1])
348 offset += len(l[-1])
349 i += 1
349 i += 1
350 self.data = ''.join(l)
350 self.data = ''.join(l)
351 self.extradata = []
351 self.extradata = []
352
352
353 def _pack(self, d):
353 def _pack(self, d):
354 return d[0] + '\x00' + d[1][:20].encode('hex') + d[2] + '\n'
354 return d[0] + '\x00' + d[1][:20].encode('hex') + d[2] + '\n'
355
355
356 def text(self):
356 def text(self):
357 self._compact()
357 self._compact()
358 return self.data
358 return self.data
359
359
360 def diff(self, m2, clean=False):
360 def diff(self, m2, clean=False):
361 '''Finds changes between the current manifest and m2.'''
361 '''Finds changes between the current manifest and m2.'''
362 # XXX think whether efficiency matters here
362 # XXX think whether efficiency matters here
363 diff = {}
363 diff = {}
364
364
365 for fn, e1, flags in self.iterentries():
365 for fn, e1, flags in self.iterentries():
366 if fn not in m2:
366 if fn not in m2:
367 diff[fn] = (e1, flags), (None, '')
367 diff[fn] = (e1, flags), (None, '')
368 else:
368 else:
369 e2 = m2[fn]
369 e2 = m2[fn]
370 if (e1, flags) != e2:
370 if (e1, flags) != e2:
371 diff[fn] = (e1, flags), e2
371 diff[fn] = (e1, flags), e2
372 elif clean:
372 elif clean:
373 diff[fn] = None
373 diff[fn] = None
374
374
375 for fn, e2, flags in m2.iterentries():
375 for fn, e2, flags in m2.iterentries():
376 if fn not in self:
376 if fn not in self:
377 diff[fn] = (None, ''), (e2, flags)
377 diff[fn] = (None, ''), (e2, flags)
378
378
379 return diff
379 return diff
380
380
381 def iterentries(self):
381 def iterentries(self):
382 return lazymanifestiterentries(self)
382 return lazymanifestiterentries(self)
383
383
384 def iterkeys(self):
384 def iterkeys(self):
385 return lazymanifestiter(self)
385 return lazymanifestiter(self)
386
386
387 def __iter__(self):
387 def __iter__(self):
388 return lazymanifestiter(self)
388 return lazymanifestiter(self)
389
389
390 def __len__(self):
390 def __len__(self):
391 return len(self.positions)
391 return len(self.positions)
392
392
393 def filtercopy(self, filterfn):
393 def filtercopy(self, filterfn):
394 # XXX should be optimized
394 # XXX should be optimized
395 c = _lazymanifest('')
395 c = _lazymanifest('')
396 for f, n, fl in self.iterentries():
396 for f, n, fl in self.iterentries():
397 if filterfn(f):
397 if filterfn(f):
398 c[f] = n, fl
398 c[f] = n, fl
399 return c
399 return c
400
400
401 try:
401 try:
402 _lazymanifest = parsers.lazymanifest
402 _lazymanifest = parsers.lazymanifest
403 except AttributeError:
403 except AttributeError:
404 pass
404 pass
405
405
406 class manifestdict(object):
406 class manifestdict(object):
407 def __init__(self, data=''):
407 def __init__(self, data=''):
408 if data.startswith('\0'):
408 if data.startswith('\0'):
409 #_lazymanifest can not parse v2
409 #_lazymanifest can not parse v2
410 self._lm = _lazymanifest('')
410 self._lm = _lazymanifest('')
411 for f, n, fl in _parsev2(data):
411 for f, n, fl in _parsev2(data):
412 self._lm[f] = n, fl
412 self._lm[f] = n, fl
413 else:
413 else:
414 self._lm = _lazymanifest(data)
414 self._lm = _lazymanifest(data)
415
415
416 def __getitem__(self, key):
416 def __getitem__(self, key):
417 return self._lm[key][0]
417 return self._lm[key][0]
418
418
419 def find(self, key):
419 def find(self, key):
420 return self._lm[key]
420 return self._lm[key]
421
421
422 def __len__(self):
422 def __len__(self):
423 return len(self._lm)
423 return len(self._lm)
424
424
425 def __nonzero__(self):
425 def __nonzero__(self):
426 # nonzero is covered by the __len__ function, but implementing it here
426 # nonzero is covered by the __len__ function, but implementing it here
427 # makes it easier for extensions to override.
427 # makes it easier for extensions to override.
428 return len(self._lm) != 0
428 return len(self._lm) != 0
429
429
430 def __setitem__(self, key, node):
430 def __setitem__(self, key, node):
431 self._lm[key] = node, self.flags(key, '')
431 self._lm[key] = node, self.flags(key, '')
432
432
433 def __contains__(self, key):
433 def __contains__(self, key):
434 return key in self._lm
434 return key in self._lm
435
435
436 def __delitem__(self, key):
436 def __delitem__(self, key):
437 del self._lm[key]
437 del self._lm[key]
438
438
439 def __iter__(self):
439 def __iter__(self):
440 return self._lm.__iter__()
440 return self._lm.__iter__()
441
441
442 def iterkeys(self):
442 def iterkeys(self):
443 return self._lm.iterkeys()
443 return self._lm.iterkeys()
444
444
445 def keys(self):
445 def keys(self):
446 return list(self.iterkeys())
446 return list(self.iterkeys())
447
447
448 def filesnotin(self, m2):
448 def filesnotin(self, m2):
449 '''Set of files in this manifest that are not in the other'''
449 '''Set of files in this manifest that are not in the other'''
450 diff = self.diff(m2)
450 diff = self.diff(m2)
451 files = set(filepath
451 files = set(filepath
452 for filepath, hashflags in diff.iteritems()
452 for filepath, hashflags in diff.iteritems()
453 if hashflags[1][0] is None)
453 if hashflags[1][0] is None)
454 return files
454 return files
455
455
456 @propertycache
456 @propertycache
457 def _dirs(self):
457 def _dirs(self):
458 return util.dirs(self)
458 return util.dirs(self)
459
459
460 def dirs(self):
460 def dirs(self):
461 return self._dirs
461 return self._dirs
462
462
463 def hasdir(self, dir):
463 def hasdir(self, dir):
464 return dir in self._dirs
464 return dir in self._dirs
465
465
466 def _filesfastpath(self, match):
466 def _filesfastpath(self, match):
467 '''Checks whether we can correctly and quickly iterate over matcher
467 '''Checks whether we can correctly and quickly iterate over matcher
468 files instead of over manifest files.'''
468 files instead of over manifest files.'''
469 files = match.files()
469 files = match.files()
470 return (len(files) < 100 and (match.isexact() or
470 return (len(files) < 100 and (match.isexact() or
471 (match.prefix() and all(fn in self for fn in files))))
471 (match.prefix() and all(fn in self for fn in files))))
472
472
473 def walk(self, match):
473 def walk(self, match):
474 '''Generates matching file names.
474 '''Generates matching file names.
475
475
476 Equivalent to manifest.matches(match).iterkeys(), but without creating
476 Equivalent to manifest.matches(match).iterkeys(), but without creating
477 an entirely new manifest.
477 an entirely new manifest.
478
478
479 It also reports nonexistent files by marking them bad with match.bad().
479 It also reports nonexistent files by marking them bad with match.bad().
480 '''
480 '''
481 if match.always():
481 if match.always():
482 for f in iter(self):
482 for f in iter(self):
483 yield f
483 yield f
484 return
484 return
485
485
486 fset = set(match.files())
486 fset = set(match.files())
487
487
488 # avoid the entire walk if we're only looking for specific files
488 # avoid the entire walk if we're only looking for specific files
489 if self._filesfastpath(match):
489 if self._filesfastpath(match):
490 for fn in sorted(fset):
490 for fn in sorted(fset):
491 yield fn
491 yield fn
492 return
492 return
493
493
494 for fn in self:
494 for fn in self:
495 if fn in fset:
495 if fn in fset:
496 # specified pattern is the exact name
496 # specified pattern is the exact name
497 fset.remove(fn)
497 fset.remove(fn)
498 if match(fn):
498 if match(fn):
499 yield fn
499 yield fn
500
500
501 # for dirstate.walk, files=['.'] means "walk the whole tree".
501 # for dirstate.walk, files=['.'] means "walk the whole tree".
502 # follow that here, too
502 # follow that here, too
503 fset.discard('.')
503 fset.discard('.')
504
504
505 for fn in sorted(fset):
505 for fn in sorted(fset):
506 if not self.hasdir(fn):
506 if not self.hasdir(fn):
507 match.bad(fn, None)
507 match.bad(fn, None)
508
508
509 def matches(self, match):
509 def matches(self, match):
510 '''generate a new manifest filtered by the match argument'''
510 '''generate a new manifest filtered by the match argument'''
511 if match.always():
511 if match.always():
512 return self.copy()
512 return self.copy()
513
513
514 if self._filesfastpath(match):
514 if self._filesfastpath(match):
515 m = manifestdict()
515 m = manifestdict()
516 lm = self._lm
516 lm = self._lm
517 for fn in match.files():
517 for fn in match.files():
518 if fn in lm:
518 if fn in lm:
519 m._lm[fn] = lm[fn]
519 m._lm[fn] = lm[fn]
520 return m
520 return m
521
521
522 m = manifestdict()
522 m = manifestdict()
523 m._lm = self._lm.filtercopy(match)
523 m._lm = self._lm.filtercopy(match)
524 return m
524 return m
525
525
526 def diff(self, m2, clean=False):
526 def diff(self, m2, clean=False):
527 '''Finds changes between the current manifest and m2.
527 '''Finds changes between the current manifest and m2.
528
528
529 Args:
529 Args:
530 m2: the manifest to which this manifest should be compared.
530 m2: the manifest to which this manifest should be compared.
531 clean: if true, include files unchanged between these manifests
531 clean: if true, include files unchanged between these manifests
532 with a None value in the returned dictionary.
532 with a None value in the returned dictionary.
533
533
534 The result is returned as a dict with filename as key and
534 The result is returned as a dict with filename as key and
535 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
535 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
536 nodeid in the current/other manifest and fl1/fl2 is the flag
536 nodeid in the current/other manifest and fl1/fl2 is the flag
537 in the current/other manifest. Where the file does not exist,
537 in the current/other manifest. Where the file does not exist,
538 the nodeid will be None and the flags will be the empty
538 the nodeid will be None and the flags will be the empty
539 string.
539 string.
540 '''
540 '''
541 return self._lm.diff(m2._lm, clean)
541 return self._lm.diff(m2._lm, clean)
542
542
543 def setflag(self, key, flag):
543 def setflag(self, key, flag):
544 self._lm[key] = self[key], flag
544 self._lm[key] = self[key], flag
545
545
546 def get(self, key, default=None):
546 def get(self, key, default=None):
547 try:
547 try:
548 return self._lm[key][0]
548 return self._lm[key][0]
549 except KeyError:
549 except KeyError:
550 return default
550 return default
551
551
552 def flags(self, key, default=''):
552 def flags(self, key, default=''):
553 try:
553 try:
554 return self._lm[key][1]
554 return self._lm[key][1]
555 except KeyError:
555 except KeyError:
556 return default
556 return default
557
557
558 def copy(self):
558 def copy(self):
559 c = manifestdict()
559 c = manifestdict()
560 c._lm = self._lm.copy()
560 c._lm = self._lm.copy()
561 return c
561 return c
562
562
563 def iteritems(self):
563 def iteritems(self):
564 return (x[:2] for x in self._lm.iterentries())
564 return (x[:2] for x in self._lm.iterentries())
565
565
566 def iterentries(self):
566 def iterentries(self):
567 return self._lm.iterentries()
567 return self._lm.iterentries()
568
568
569 def text(self, usemanifestv2=False):
569 def text(self, usemanifestv2=False):
570 if usemanifestv2:
570 if usemanifestv2:
571 return _textv2(self._lm.iterentries())
571 return _textv2(self._lm.iterentries())
572 else:
572 else:
573 # use (probably) native version for v1
573 # use (probably) native version for v1
574 return self._lm.text()
574 return self._lm.text()
575
575
576 def fastdelta(self, base, changes):
576 def fastdelta(self, base, changes):
577 """Given a base manifest text as an array.array and a list of changes
577 """Given a base manifest text as an array.array and a list of changes
578 relative to that text, compute a delta that can be used by revlog.
578 relative to that text, compute a delta that can be used by revlog.
579 """
579 """
580 delta = []
580 delta = []
581 dstart = None
581 dstart = None
582 dend = None
582 dend = None
583 dline = [""]
583 dline = [""]
584 start = 0
584 start = 0
585 # zero copy representation of base as a buffer
585 # zero copy representation of base as a buffer
586 addbuf = util.buffer(base)
586 addbuf = util.buffer(base)
587
587
588 changes = list(changes)
588 changes = list(changes)
589 if len(changes) < 1000:
589 if len(changes) < 1000:
590 # start with a readonly loop that finds the offset of
590 # start with a readonly loop that finds the offset of
591 # each line and creates the deltas
591 # each line and creates the deltas
592 for f, todelete in changes:
592 for f, todelete in changes:
593 # bs will either be the index of the item or the insert point
593 # bs will either be the index of the item or the insert point
594 start, end = _msearch(addbuf, f, start)
594 start, end = _msearch(addbuf, f, start)
595 if not todelete:
595 if not todelete:
596 h, fl = self._lm[f]
596 h, fl = self._lm[f]
597 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
597 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
598 else:
598 else:
599 if start == end:
599 if start == end:
600 # item we want to delete was not found, error out
600 # item we want to delete was not found, error out
601 raise AssertionError(
601 raise AssertionError(
602 _("failed to remove %s from manifest") % f)
602 _("failed to remove %s from manifest") % f)
603 l = ""
603 l = ""
604 if dstart is not None and dstart <= start and dend >= start:
604 if dstart is not None and dstart <= start and dend >= start:
605 if dend < end:
605 if dend < end:
606 dend = end
606 dend = end
607 if l:
607 if l:
608 dline.append(l)
608 dline.append(l)
609 else:
609 else:
610 if dstart is not None:
610 if dstart is not None:
611 delta.append([dstart, dend, "".join(dline)])
611 delta.append([dstart, dend, "".join(dline)])
612 dstart = start
612 dstart = start
613 dend = end
613 dend = end
614 dline = [l]
614 dline = [l]
615
615
616 if dstart is not None:
616 if dstart is not None:
617 delta.append([dstart, dend, "".join(dline)])
617 delta.append([dstart, dend, "".join(dline)])
618 # apply the delta to the base, and get a delta for addrevision
618 # apply the delta to the base, and get a delta for addrevision
619 deltatext, arraytext = _addlistdelta(base, delta)
619 deltatext, arraytext = _addlistdelta(base, delta)
620 else:
620 else:
621 # For large changes, it's much cheaper to just build the text and
621 # For large changes, it's much cheaper to just build the text and
622 # diff it.
622 # diff it.
623 arraytext = array.array('c', self.text())
623 arraytext = array.array('c', self.text())
624 deltatext = mdiff.textdiff(base, arraytext)
624 deltatext = mdiff.textdiff(base, arraytext)
625
625
626 return arraytext, deltatext
626 return arraytext, deltatext
627
627
628 def _msearch(m, s, lo=0, hi=None):
628 def _msearch(m, s, lo=0, hi=None):
629 '''return a tuple (start, end) that says where to find s within m.
629 '''return a tuple (start, end) that says where to find s within m.
630
630
631 If the string is found m[start:end] are the line containing
631 If the string is found m[start:end] are the line containing
632 that string. If start == end the string was not found and
632 that string. If start == end the string was not found and
633 they indicate the proper sorted insertion point.
633 they indicate the proper sorted insertion point.
634
634
635 m should be a buffer or a string
635 m should be a buffer or a string
636 s is a string'''
636 s is a string'''
637 def advance(i, c):
637 def advance(i, c):
638 while i < lenm and m[i] != c:
638 while i < lenm and m[i] != c:
639 i += 1
639 i += 1
640 return i
640 return i
641 if not s:
641 if not s:
642 return (lo, lo)
642 return (lo, lo)
643 lenm = len(m)
643 lenm = len(m)
644 if not hi:
644 if not hi:
645 hi = lenm
645 hi = lenm
646 while lo < hi:
646 while lo < hi:
647 mid = (lo + hi) // 2
647 mid = (lo + hi) // 2
648 start = mid
648 start = mid
649 while start > 0 and m[start - 1] != '\n':
649 while start > 0 and m[start - 1] != '\n':
650 start -= 1
650 start -= 1
651 end = advance(start, '\0')
651 end = advance(start, '\0')
652 if m[start:end] < s:
652 if m[start:end] < s:
653 # we know that after the null there are 40 bytes of sha1
653 # we know that after the null there are 40 bytes of sha1
654 # this translates to the bisect lo = mid + 1
654 # this translates to the bisect lo = mid + 1
655 lo = advance(end + 40, '\n') + 1
655 lo = advance(end + 40, '\n') + 1
656 else:
656 else:
657 # this translates to the bisect hi = mid
657 # this translates to the bisect hi = mid
658 hi = start
658 hi = start
659 end = advance(lo, '\0')
659 end = advance(lo, '\0')
660 found = m[lo:end]
660 found = m[lo:end]
661 if s == found:
661 if s == found:
662 # we know that after the null there are 40 bytes of sha1
662 # we know that after the null there are 40 bytes of sha1
663 end = advance(end + 40, '\n')
663 end = advance(end + 40, '\n')
664 return (lo, end + 1)
664 return (lo, end + 1)
665 else:
665 else:
666 return (lo, lo)
666 return (lo, lo)
667
667
668 def _checkforbidden(l):
668 def _checkforbidden(l):
669 """Check filenames for illegal characters."""
669 """Check filenames for illegal characters."""
670 for f in l:
670 for f in l:
671 if '\n' in f or '\r' in f:
671 if '\n' in f or '\r' in f:
672 raise error.RevlogError(
672 raise error.RevlogError(
673 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
673 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
674
674
675
675
676 # apply the changes collected during the bisect loop to our addlist
676 # apply the changes collected during the bisect loop to our addlist
677 # return a delta suitable for addrevision
677 # return a delta suitable for addrevision
678 def _addlistdelta(addlist, x):
678 def _addlistdelta(addlist, x):
679 # for large addlist arrays, building a new array is cheaper
679 # for large addlist arrays, building a new array is cheaper
680 # than repeatedly modifying the existing one
680 # than repeatedly modifying the existing one
681 currentposition = 0
681 currentposition = 0
682 newaddlist = array.array('c')
682 newaddlist = array.array('c')
683
683
684 for start, end, content in x:
684 for start, end, content in x:
685 newaddlist += addlist[currentposition:start]
685 newaddlist += addlist[currentposition:start]
686 if content:
686 if content:
687 newaddlist += array.array('c', content)
687 newaddlist += array.array('c', content)
688
688
689 currentposition = end
689 currentposition = end
690
690
691 newaddlist += addlist[currentposition:]
691 newaddlist += addlist[currentposition:]
692
692
693 deltatext = "".join(struct.pack(">lll", start, end, len(content))
693 deltatext = "".join(struct.pack(">lll", start, end, len(content))
694 + content for start, end, content in x)
694 + content for start, end, content in x)
695 return deltatext, newaddlist
695 return deltatext, newaddlist
696
696
697 def _splittopdir(f):
697 def _splittopdir(f):
698 if '/' in f:
698 if '/' in f:
699 dir, subpath = f.split('/', 1)
699 dir, subpath = f.split('/', 1)
700 return dir + '/', subpath
700 return dir + '/', subpath
701 else:
701 else:
702 return '', f
702 return '', f
703
703
704 _noop = lambda s: None
704 _noop = lambda s: None
705
705
706 class treemanifest(object):
706 class treemanifest(object):
707 def __init__(self, dir='', text=''):
707 def __init__(self, dir='', text=''):
708 self._dir = dir
708 self._dir = dir
709 self._node = revlog.nullid
709 self._node = revlog.nullid
710 self._loadfunc = _noop
710 self._loadfunc = _noop
711 self._copyfunc = _noop
711 self._copyfunc = _noop
712 self._dirty = False
712 self._dirty = False
713 self._dirs = {}
713 self._dirs = {}
714 # Using _lazymanifest here is a little slower than plain old dicts
714 # Using _lazymanifest here is a little slower than plain old dicts
715 self._files = {}
715 self._files = {}
716 self._flags = {}
716 self._flags = {}
717 if text:
717 if text:
718 def readsubtree(subdir, subm):
718 def readsubtree(subdir, subm):
719 raise AssertionError('treemanifest constructor only accepts '
719 raise AssertionError('treemanifest constructor only accepts '
720 'flat manifests')
720 'flat manifests')
721 self.parse(text, readsubtree)
721 self.parse(text, readsubtree)
722 self._dirty = True # Mark flat manifest dirty after parsing
722 self._dirty = True # Mark flat manifest dirty after parsing
723
723
724 def _subpath(self, path):
724 def _subpath(self, path):
725 return self._dir + path
725 return self._dir + path
726
726
727 def __len__(self):
727 def __len__(self):
728 self._load()
728 self._load()
729 size = len(self._files)
729 size = len(self._files)
730 for m in self._dirs.values():
730 for m in self._dirs.values():
731 size += m.__len__()
731 size += m.__len__()
732 return size
732 return size
733
733
734 def _isempty(self):
734 def _isempty(self):
735 self._load() # for consistency; already loaded by all callers
735 self._load() # for consistency; already loaded by all callers
736 return (not self._files and (not self._dirs or
736 return (not self._files and (not self._dirs or
737 all(m._isempty() for m in self._dirs.values())))
737 all(m._isempty() for m in self._dirs.values())))
738
738
739 def __repr__(self):
739 def __repr__(self):
740 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
740 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
741 (self._dir, revlog.hex(self._node),
741 (self._dir, revlog.hex(self._node),
742 bool(self._loadfunc is _noop),
742 bool(self._loadfunc is _noop),
743 self._dirty, id(self)))
743 self._dirty, id(self)))
744
744
745 def dir(self):
745 def dir(self):
746 '''The directory that this tree manifest represents, including a
746 '''The directory that this tree manifest represents, including a
747 trailing '/'. Empty string for the repo root directory.'''
747 trailing '/'. Empty string for the repo root directory.'''
748 return self._dir
748 return self._dir
749
749
750 def node(self):
750 def node(self):
751 '''This node of this instance. nullid for unsaved instances. Should
751 '''This node of this instance. nullid for unsaved instances. Should
752 be updated when the instance is read or written from a revlog.
752 be updated when the instance is read or written from a revlog.
753 '''
753 '''
754 assert not self._dirty
754 assert not self._dirty
755 return self._node
755 return self._node
756
756
757 def setnode(self, node):
757 def setnode(self, node):
758 self._node = node
758 self._node = node
759 self._dirty = False
759 self._dirty = False
760
760
761 def iterentries(self):
761 def iterentries(self):
762 self._load()
762 self._load()
763 for p, n in sorted(self._dirs.items() + self._files.items()):
763 for p, n in sorted(self._dirs.items() + self._files.items()):
764 if p in self._files:
764 if p in self._files:
765 yield self._subpath(p), n, self._flags.get(p, '')
765 yield self._subpath(p), n, self._flags.get(p, '')
766 else:
766 else:
767 for x in n.iterentries():
767 for x in n.iterentries():
768 yield x
768 yield x
769
769
770 def iteritems(self):
770 def iteritems(self):
771 self._load()
771 self._load()
772 for p, n in sorted(self._dirs.items() + self._files.items()):
772 for p, n in sorted(self._dirs.items() + self._files.items()):
773 if p in self._files:
773 if p in self._files:
774 yield self._subpath(p), n
774 yield self._subpath(p), n
775 else:
775 else:
776 for f, sn in n.iteritems():
776 for f, sn in n.iteritems():
777 yield f, sn
777 yield f, sn
778
778
779 def iterkeys(self):
779 def iterkeys(self):
780 self._load()
780 self._load()
781 for p in sorted(self._dirs.keys() + self._files.keys()):
781 for p in sorted(self._dirs.keys() + self._files.keys()):
782 if p in self._files:
782 if p in self._files:
783 yield self._subpath(p)
783 yield self._subpath(p)
784 else:
784 else:
785 for f in self._dirs[p].iterkeys():
785 for f in self._dirs[p].iterkeys():
786 yield f
786 yield f
787
787
788 def keys(self):
788 def keys(self):
789 return list(self.iterkeys())
789 return list(self.iterkeys())
790
790
791 def __iter__(self):
791 def __iter__(self):
792 return self.iterkeys()
792 return self.iterkeys()
793
793
794 def __contains__(self, f):
794 def __contains__(self, f):
795 if f is None:
795 if f is None:
796 return False
796 return False
797 self._load()
797 self._load()
798 dir, subpath = _splittopdir(f)
798 dir, subpath = _splittopdir(f)
799 if dir:
799 if dir:
800 if dir not in self._dirs:
800 if dir not in self._dirs:
801 return False
801 return False
802 return self._dirs[dir].__contains__(subpath)
802 return self._dirs[dir].__contains__(subpath)
803 else:
803 else:
804 return f in self._files
804 return f in self._files
805
805
806 def get(self, f, default=None):
806 def get(self, f, default=None):
807 self._load()
807 self._load()
808 dir, subpath = _splittopdir(f)
808 dir, subpath = _splittopdir(f)
809 if dir:
809 if dir:
810 if dir not in self._dirs:
810 if dir not in self._dirs:
811 return default
811 return default
812 return self._dirs[dir].get(subpath, default)
812 return self._dirs[dir].get(subpath, default)
813 else:
813 else:
814 return self._files.get(f, default)
814 return self._files.get(f, default)
815
815
816 def __getitem__(self, f):
816 def __getitem__(self, f):
817 self._load()
817 self._load()
818 dir, subpath = _splittopdir(f)
818 dir, subpath = _splittopdir(f)
819 if dir:
819 if dir:
820 return self._dirs[dir].__getitem__(subpath)
820 return self._dirs[dir].__getitem__(subpath)
821 else:
821 else:
822 return self._files[f]
822 return self._files[f]
823
823
824 def flags(self, f):
824 def flags(self, f):
825 self._load()
825 self._load()
826 dir, subpath = _splittopdir(f)
826 dir, subpath = _splittopdir(f)
827 if dir:
827 if dir:
828 if dir not in self._dirs:
828 if dir not in self._dirs:
829 return ''
829 return ''
830 return self._dirs[dir].flags(subpath)
830 return self._dirs[dir].flags(subpath)
831 else:
831 else:
832 if f in self._dirs:
832 if f in self._dirs:
833 return ''
833 return ''
834 return self._flags.get(f, '')
834 return self._flags.get(f, '')
835
835
836 def find(self, f):
836 def find(self, f):
837 self._load()
837 self._load()
838 dir, subpath = _splittopdir(f)
838 dir, subpath = _splittopdir(f)
839 if dir:
839 if dir:
840 return self._dirs[dir].find(subpath)
840 return self._dirs[dir].find(subpath)
841 else:
841 else:
842 return self._files[f], self._flags.get(f, '')
842 return self._files[f], self._flags.get(f, '')
843
843
844 def __delitem__(self, f):
844 def __delitem__(self, f):
845 self._load()
845 self._load()
846 dir, subpath = _splittopdir(f)
846 dir, subpath = _splittopdir(f)
847 if dir:
847 if dir:
848 self._dirs[dir].__delitem__(subpath)
848 self._dirs[dir].__delitem__(subpath)
849 # If the directory is now empty, remove it
849 # If the directory is now empty, remove it
850 if self._dirs[dir]._isempty():
850 if self._dirs[dir]._isempty():
851 del self._dirs[dir]
851 del self._dirs[dir]
852 else:
852 else:
853 del self._files[f]
853 del self._files[f]
854 if f in self._flags:
854 if f in self._flags:
855 del self._flags[f]
855 del self._flags[f]
856 self._dirty = True
856 self._dirty = True
857
857
858 def __setitem__(self, f, n):
858 def __setitem__(self, f, n):
859 assert n is not None
859 assert n is not None
860 self._load()
860 self._load()
861 dir, subpath = _splittopdir(f)
861 dir, subpath = _splittopdir(f)
862 if dir:
862 if dir:
863 if dir not in self._dirs:
863 if dir not in self._dirs:
864 self._dirs[dir] = treemanifest(self._subpath(dir))
864 self._dirs[dir] = treemanifest(self._subpath(dir))
865 self._dirs[dir].__setitem__(subpath, n)
865 self._dirs[dir].__setitem__(subpath, n)
866 else:
866 else:
867 self._files[f] = n[:21] # to match manifestdict's behavior
867 self._files[f] = n[:21] # to match manifestdict's behavior
868 self._dirty = True
868 self._dirty = True
869
869
870 def _load(self):
870 def _load(self):
871 if self._loadfunc is not _noop:
871 if self._loadfunc is not _noop:
872 lf, self._loadfunc = self._loadfunc, _noop
872 lf, self._loadfunc = self._loadfunc, _noop
873 lf(self)
873 lf(self)
874 elif self._copyfunc is not _noop:
874 elif self._copyfunc is not _noop:
875 cf, self._copyfunc = self._copyfunc, _noop
875 cf, self._copyfunc = self._copyfunc, _noop
876 cf(self)
876 cf(self)
877
877
878 def setflag(self, f, flags):
878 def setflag(self, f, flags):
879 """Set the flags (symlink, executable) for path f."""
879 """Set the flags (symlink, executable) for path f."""
880 self._load()
880 self._load()
881 dir, subpath = _splittopdir(f)
881 dir, subpath = _splittopdir(f)
882 if dir:
882 if dir:
883 if dir not in self._dirs:
883 if dir not in self._dirs:
884 self._dirs[dir] = treemanifest(self._subpath(dir))
884 self._dirs[dir] = treemanifest(self._subpath(dir))
885 self._dirs[dir].setflag(subpath, flags)
885 self._dirs[dir].setflag(subpath, flags)
886 else:
886 else:
887 self._flags[f] = flags
887 self._flags[f] = flags
888 self._dirty = True
888 self._dirty = True
889
889
890 def copy(self):
890 def copy(self):
891 copy = treemanifest(self._dir)
891 copy = treemanifest(self._dir)
892 copy._node = self._node
892 copy._node = self._node
893 copy._dirty = self._dirty
893 copy._dirty = self._dirty
894 if self._copyfunc is _noop:
894 if self._copyfunc is _noop:
895 def _copyfunc(s):
895 def _copyfunc(s):
896 self._load()
896 self._load()
897 for d in self._dirs:
897 for d in self._dirs:
898 s._dirs[d] = self._dirs[d].copy()
898 s._dirs[d] = self._dirs[d].copy()
899 s._files = dict.copy(self._files)
899 s._files = dict.copy(self._files)
900 s._flags = dict.copy(self._flags)
900 s._flags = dict.copy(self._flags)
901 if self._loadfunc is _noop:
901 if self._loadfunc is _noop:
902 _copyfunc(copy)
902 _copyfunc(copy)
903 else:
903 else:
904 copy._copyfunc = _copyfunc
904 copy._copyfunc = _copyfunc
905 else:
905 else:
906 copy._copyfunc = self._copyfunc
906 copy._copyfunc = self._copyfunc
907 return copy
907 return copy
908
908
909 def filesnotin(self, m2):
909 def filesnotin(self, m2):
910 '''Set of files in this manifest that are not in the other'''
910 '''Set of files in this manifest that are not in the other'''
911 files = set()
911 files = set()
912 def _filesnotin(t1, t2):
912 def _filesnotin(t1, t2):
913 if t1._node == t2._node and not t1._dirty and not t2._dirty:
913 if t1._node == t2._node and not t1._dirty and not t2._dirty:
914 return
914 return
915 t1._load()
915 t1._load()
916 t2._load()
916 t2._load()
917 for d, m1 in t1._dirs.iteritems():
917 for d, m1 in t1._dirs.iteritems():
918 if d in t2._dirs:
918 if d in t2._dirs:
919 m2 = t2._dirs[d]
919 m2 = t2._dirs[d]
920 _filesnotin(m1, m2)
920 _filesnotin(m1, m2)
921 else:
921 else:
922 files.update(m1.iterkeys())
922 files.update(m1.iterkeys())
923
923
924 for fn in t1._files.iterkeys():
924 for fn in t1._files.iterkeys():
925 if fn not in t2._files:
925 if fn not in t2._files:
926 files.add(t1._subpath(fn))
926 files.add(t1._subpath(fn))
927
927
928 _filesnotin(self, m2)
928 _filesnotin(self, m2)
929 return files
929 return files
930
930
931 @propertycache
931 @propertycache
932 def _alldirs(self):
932 def _alldirs(self):
933 return util.dirs(self)
933 return util.dirs(self)
934
934
935 def dirs(self):
935 def dirs(self):
936 return self._alldirs
936 return self._alldirs
937
937
938 def hasdir(self, dir):
938 def hasdir(self, dir):
939 self._load()
939 self._load()
940 topdir, subdir = _splittopdir(dir)
940 topdir, subdir = _splittopdir(dir)
941 if topdir:
941 if topdir:
942 if topdir in self._dirs:
942 if topdir in self._dirs:
943 return self._dirs[topdir].hasdir(subdir)
943 return self._dirs[topdir].hasdir(subdir)
944 return False
944 return False
945 return (dir + '/') in self._dirs
945 return (dir + '/') in self._dirs
946
946
947 def walk(self, match):
947 def walk(self, match):
948 '''Generates matching file names.
948 '''Generates matching file names.
949
949
950 Equivalent to manifest.matches(match).iterkeys(), but without creating
950 Equivalent to manifest.matches(match).iterkeys(), but without creating
951 an entirely new manifest.
951 an entirely new manifest.
952
952
953 It also reports nonexistent files by marking them bad with match.bad().
953 It also reports nonexistent files by marking them bad with match.bad().
954 '''
954 '''
955 if match.always():
955 if match.always():
956 for f in iter(self):
956 for f in iter(self):
957 yield f
957 yield f
958 return
958 return
959
959
960 fset = set(match.files())
960 fset = set(match.files())
961
961
962 for fn in self._walk(match):
962 for fn in self._walk(match):
963 if fn in fset:
963 if fn in fset:
964 # specified pattern is the exact name
964 # specified pattern is the exact name
965 fset.remove(fn)
965 fset.remove(fn)
966 yield fn
966 yield fn
967
967
968 # for dirstate.walk, files=['.'] means "walk the whole tree".
968 # for dirstate.walk, files=['.'] means "walk the whole tree".
969 # follow that here, too
969 # follow that here, too
970 fset.discard('.')
970 fset.discard('.')
971
971
972 for fn in sorted(fset):
972 for fn in sorted(fset):
973 if not self.hasdir(fn):
973 if not self.hasdir(fn):
974 match.bad(fn, None)
974 match.bad(fn, None)
975
975
976 def _walk(self, match):
976 def _walk(self, match):
977 '''Recursively generates matching file names for walk().'''
977 '''Recursively generates matching file names for walk().'''
978 if not match.visitdir(self._dir[:-1] or '.'):
978 if not match.visitdir(self._dir[:-1] or '.'):
979 return
979 return
980
980
981 # yield this dir's files and walk its submanifests
981 # yield this dir's files and walk its submanifests
982 self._load()
982 self._load()
983 for p in sorted(self._dirs.keys() + self._files.keys()):
983 for p in sorted(self._dirs.keys() + self._files.keys()):
984 if p in self._files:
984 if p in self._files:
985 fullp = self._subpath(p)
985 fullp = self._subpath(p)
986 if match(fullp):
986 if match(fullp):
987 yield fullp
987 yield fullp
988 else:
988 else:
989 for f in self._dirs[p]._walk(match):
989 for f in self._dirs[p]._walk(match):
990 yield f
990 yield f
991
991
992 def matches(self, match):
992 def matches(self, match):
993 '''generate a new manifest filtered by the match argument'''
993 '''generate a new manifest filtered by the match argument'''
994 if match.always():
994 if match.always():
995 return self.copy()
995 return self.copy()
996
996
997 return self._matches(match)
997 return self._matches(match)
998
998
999 def _matches(self, match):
999 def _matches(self, match):
1000 '''recursively generate a new manifest filtered by the match argument.
1000 '''recursively generate a new manifest filtered by the match argument.
1001 '''
1001 '''
1002
1002
1003 visit = match.visitdir(self._dir[:-1] or '.')
1003 visit = match.visitdir(self._dir[:-1] or '.')
1004 if visit == 'all':
1004 if visit == 'all':
1005 return self.copy()
1005 return self.copy()
1006 ret = treemanifest(self._dir)
1006 ret = treemanifest(self._dir)
1007 if not visit:
1007 if not visit:
1008 return ret
1008 return ret
1009
1009
1010 self._load()
1010 self._load()
1011 for fn in self._files:
1011 for fn in self._files:
1012 fullp = self._subpath(fn)
1012 fullp = self._subpath(fn)
1013 if not match(fullp):
1013 if not match(fullp):
1014 continue
1014 continue
1015 ret._files[fn] = self._files[fn]
1015 ret._files[fn] = self._files[fn]
1016 if fn in self._flags:
1016 if fn in self._flags:
1017 ret._flags[fn] = self._flags[fn]
1017 ret._flags[fn] = self._flags[fn]
1018
1018
1019 for dir, subm in self._dirs.iteritems():
1019 for dir, subm in self._dirs.iteritems():
1020 m = subm._matches(match)
1020 m = subm._matches(match)
1021 if not m._isempty():
1021 if not m._isempty():
1022 ret._dirs[dir] = m
1022 ret._dirs[dir] = m
1023
1023
1024 if not ret._isempty():
1024 if not ret._isempty():
1025 ret._dirty = True
1025 ret._dirty = True
1026 return ret
1026 return ret
1027
1027
1028 def diff(self, m2, clean=False):
1028 def diff(self, m2, clean=False):
1029 '''Finds changes between the current manifest and m2.
1029 '''Finds changes between the current manifest and m2.
1030
1030
1031 Args:
1031 Args:
1032 m2: the manifest to which this manifest should be compared.
1032 m2: the manifest to which this manifest should be compared.
1033 clean: if true, include files unchanged between these manifests
1033 clean: if true, include files unchanged between these manifests
1034 with a None value in the returned dictionary.
1034 with a None value in the returned dictionary.
1035
1035
1036 The result is returned as a dict with filename as key and
1036 The result is returned as a dict with filename as key and
1037 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1037 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1038 nodeid in the current/other manifest and fl1/fl2 is the flag
1038 nodeid in the current/other manifest and fl1/fl2 is the flag
1039 in the current/other manifest. Where the file does not exist,
1039 in the current/other manifest. Where the file does not exist,
1040 the nodeid will be None and the flags will be the empty
1040 the nodeid will be None and the flags will be the empty
1041 string.
1041 string.
1042 '''
1042 '''
1043 result = {}
1043 result = {}
1044 emptytree = treemanifest()
1044 emptytree = treemanifest()
1045 def _diff(t1, t2):
1045 def _diff(t1, t2):
1046 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1046 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1047 return
1047 return
1048 t1._load()
1048 t1._load()
1049 t2._load()
1049 t2._load()
1050 for d, m1 in t1._dirs.iteritems():
1050 for d, m1 in t1._dirs.iteritems():
1051 m2 = t2._dirs.get(d, emptytree)
1051 m2 = t2._dirs.get(d, emptytree)
1052 _diff(m1, m2)
1052 _diff(m1, m2)
1053
1053
1054 for d, m2 in t2._dirs.iteritems():
1054 for d, m2 in t2._dirs.iteritems():
1055 if d not in t1._dirs:
1055 if d not in t1._dirs:
1056 _diff(emptytree, m2)
1056 _diff(emptytree, m2)
1057
1057
1058 for fn, n1 in t1._files.iteritems():
1058 for fn, n1 in t1._files.iteritems():
1059 fl1 = t1._flags.get(fn, '')
1059 fl1 = t1._flags.get(fn, '')
1060 n2 = t2._files.get(fn, None)
1060 n2 = t2._files.get(fn, None)
1061 fl2 = t2._flags.get(fn, '')
1061 fl2 = t2._flags.get(fn, '')
1062 if n1 != n2 or fl1 != fl2:
1062 if n1 != n2 or fl1 != fl2:
1063 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1063 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1064 elif clean:
1064 elif clean:
1065 result[t1._subpath(fn)] = None
1065 result[t1._subpath(fn)] = None
1066
1066
1067 for fn, n2 in t2._files.iteritems():
1067 for fn, n2 in t2._files.iteritems():
1068 if fn not in t1._files:
1068 if fn not in t1._files:
1069 fl2 = t2._flags.get(fn, '')
1069 fl2 = t2._flags.get(fn, '')
1070 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1070 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1071
1071
1072 _diff(self, m2)
1072 _diff(self, m2)
1073 return result
1073 return result
1074
1074
1075 def unmodifiedsince(self, m2):
1075 def unmodifiedsince(self, m2):
1076 return not self._dirty and not m2._dirty and self._node == m2._node
1076 return not self._dirty and not m2._dirty and self._node == m2._node
1077
1077
1078 def parse(self, text, readsubtree):
1078 def parse(self, text, readsubtree):
1079 for f, n, fl in _parse(text):
1079 for f, n, fl in _parse(text):
1080 if fl == 't':
1080 if fl == 't':
1081 f = f + '/'
1081 f = f + '/'
1082 self._dirs[f] = readsubtree(self._subpath(f), n)
1082 self._dirs[f] = readsubtree(self._subpath(f), n)
1083 elif '/' in f:
1083 elif '/' in f:
1084 # This is a flat manifest, so use __setitem__ and setflag rather
1084 # This is a flat manifest, so use __setitem__ and setflag rather
1085 # than assigning directly to _files and _flags, so we can
1085 # than assigning directly to _files and _flags, so we can
1086 # assign a path in a subdirectory, and to mark dirty (compared
1086 # assign a path in a subdirectory, and to mark dirty (compared
1087 # to nullid).
1087 # to nullid).
1088 self[f] = n
1088 self[f] = n
1089 if fl:
1089 if fl:
1090 self.setflag(f, fl)
1090 self.setflag(f, fl)
1091 else:
1091 else:
1092 # Assigning to _files and _flags avoids marking as dirty,
1092 # Assigning to _files and _flags avoids marking as dirty,
1093 # and should be a little faster.
1093 # and should be a little faster.
1094 self._files[f] = n
1094 self._files[f] = n
1095 if fl:
1095 if fl:
1096 self._flags[f] = fl
1096 self._flags[f] = fl
1097
1097
1098 def text(self, usemanifestv2=False):
1098 def text(self, usemanifestv2=False):
1099 """Get the full data of this manifest as a bytestring."""
1099 """Get the full data of this manifest as a bytestring."""
1100 self._load()
1100 self._load()
1101 return _text(self.iterentries(), usemanifestv2)
1101 return _text(self.iterentries(), usemanifestv2)
1102
1102
1103 def dirtext(self, usemanifestv2=False):
1103 def dirtext(self, usemanifestv2=False):
1104 """Get the full data of this directory as a bytestring. Make sure that
1104 """Get the full data of this directory as a bytestring. Make sure that
1105 any submanifests have been written first, so their nodeids are correct.
1105 any submanifests have been written first, so their nodeids are correct.
1106 """
1106 """
1107 self._load()
1107 self._load()
1108 flags = self.flags
1108 flags = self.flags
1109 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1109 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1110 files = [(f, self._files[f], flags(f)) for f in self._files]
1110 files = [(f, self._files[f], flags(f)) for f in self._files]
1111 return _text(sorted(dirs + files), usemanifestv2)
1111 return _text(sorted(dirs + files), usemanifestv2)
1112
1112
1113 def read(self, gettext, readsubtree):
1113 def read(self, gettext, readsubtree):
1114 def _load_for_read(s):
1114 def _load_for_read(s):
1115 s.parse(gettext(), readsubtree)
1115 s.parse(gettext(), readsubtree)
1116 s._dirty = False
1116 s._dirty = False
1117 self._loadfunc = _load_for_read
1117 self._loadfunc = _load_for_read
1118
1118
1119 def writesubtrees(self, m1, m2, writesubtree):
1119 def writesubtrees(self, m1, m2, writesubtree):
1120 self._load() # for consistency; should never have any effect here
1120 self._load() # for consistency; should never have any effect here
1121 m1._load()
1121 m1._load()
1122 m2._load()
1122 m2._load()
1123 emptytree = treemanifest()
1123 emptytree = treemanifest()
1124 for d, subm in self._dirs.iteritems():
1124 for d, subm in self._dirs.iteritems():
1125 subp1 = m1._dirs.get(d, emptytree)._node
1125 subp1 = m1._dirs.get(d, emptytree)._node
1126 subp2 = m2._dirs.get(d, emptytree)._node
1126 subp2 = m2._dirs.get(d, emptytree)._node
1127 if subp1 == revlog.nullid:
1127 if subp1 == revlog.nullid:
1128 subp1, subp2 = subp2, subp1
1128 subp1, subp2 = subp2, subp1
1129 writesubtree(subm, subp1, subp2)
1129 writesubtree(subm, subp1, subp2)
1130
1130
1131 class manifestrevlog(revlog.revlog):
1131 class manifestrevlog(revlog.revlog):
1132 '''A revlog that stores manifest texts. This is responsible for caching the
1132 '''A revlog that stores manifest texts. This is responsible for caching the
1133 full-text manifest contents.
1133 full-text manifest contents.
1134 '''
1134 '''
1135 def __init__(self, opener, dir='', dirlogcache=None):
1135 def __init__(self, opener, dir='', dirlogcache=None):
1136 # During normal operations, we expect to deal with not more than four
1136 # During normal operations, we expect to deal with not more than four
1137 # revs at a time (such as during commit --amend). When rebasing large
1137 # revs at a time (such as during commit --amend). When rebasing large
1138 # stacks of commits, the number can go up, hence the config knob below.
1138 # stacks of commits, the number can go up, hence the config knob below.
1139 cachesize = 4
1139 cachesize = 4
1140 usetreemanifest = False
1140 usetreemanifest = False
1141 usemanifestv2 = False
1141 usemanifestv2 = False
1142 opts = getattr(opener, 'options', None)
1142 opts = getattr(opener, 'options', None)
1143 if opts is not None:
1143 if opts is not None:
1144 cachesize = opts.get('manifestcachesize', cachesize)
1144 cachesize = opts.get('manifestcachesize', cachesize)
1145 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1145 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1146 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1146 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1147
1147
1148 self._treeondisk = usetreemanifest
1148 self._treeondisk = usetreemanifest
1149 self._usemanifestv2 = usemanifestv2
1149 self._usemanifestv2 = usemanifestv2
1150
1150
1151 self._fulltextcache = util.lrucachedict(cachesize)
1151 self._fulltextcache = util.lrucachedict(cachesize)
1152
1152
1153 indexfile = "00manifest.i"
1153 indexfile = "00manifest.i"
1154 if dir:
1154 if dir:
1155 assert self._treeondisk, 'opts is %r' % opts
1155 assert self._treeondisk, 'opts is %r' % opts
1156 if not dir.endswith('/'):
1156 if not dir.endswith('/'):
1157 dir = dir + '/'
1157 dir = dir + '/'
1158 indexfile = "meta/" + dir + "00manifest.i"
1158 indexfile = "meta/" + dir + "00manifest.i"
1159 self._dir = dir
1159 self._dir = dir
1160 # The dirlogcache is kept on the root manifest log
1160 # The dirlogcache is kept on the root manifest log
1161 if dir:
1161 if dir:
1162 self._dirlogcache = dirlogcache
1162 self._dirlogcache = dirlogcache
1163 else:
1163 else:
1164 self._dirlogcache = {'': self}
1164 self._dirlogcache = {'': self}
1165
1165
1166 super(manifestrevlog, self).__init__(opener, indexfile,
1166 super(manifestrevlog, self).__init__(opener, indexfile,
1167 checkambig=bool(dir))
1167 checkambig=bool(dir))
1168
1168
1169 @property
1169 @property
1170 def fulltextcache(self):
1170 def fulltextcache(self):
1171 return self._fulltextcache
1171 return self._fulltextcache
1172
1172
1173 def clearcaches(self):
1173 def clearcaches(self):
1174 super(manifestrevlog, self).clearcaches()
1174 super(manifestrevlog, self).clearcaches()
1175 self._fulltextcache.clear()
1175 self._fulltextcache.clear()
1176 self._dirlogcache = {'': self}
1176 self._dirlogcache = {'': self}
1177
1177
1178 def dirlog(self, dir):
1178 def dirlog(self, dir):
1179 if dir:
1179 if dir:
1180 assert self._treeondisk
1180 assert self._treeondisk
1181 if dir not in self._dirlogcache:
1181 if dir not in self._dirlogcache:
1182 self._dirlogcache[dir] = manifestrevlog(self.opener, dir,
1182 self._dirlogcache[dir] = manifestrevlog(self.opener, dir,
1183 self._dirlogcache)
1183 self._dirlogcache)
1184 return self._dirlogcache[dir]
1184 return self._dirlogcache[dir]
1185
1185
1186 def add(self, m, transaction, link, p1, p2, added, removed, readtree=None):
1186 def add(self, m, transaction, link, p1, p2, added, removed, readtree=None):
1187 if (p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta')
1187 if (p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta')
1188 and not self._usemanifestv2):
1188 and not self._usemanifestv2):
1189 # If our first parent is in the manifest cache, we can
1189 # If our first parent is in the manifest cache, we can
1190 # compute a delta here using properties we know about the
1190 # compute a delta here using properties we know about the
1191 # manifest up-front, which may save time later for the
1191 # manifest up-front, which may save time later for the
1192 # revlog layer.
1192 # revlog layer.
1193
1193
1194 _checkforbidden(added)
1194 _checkforbidden(added)
1195 # combine the changed lists into one sorted iterator
1195 # combine the changed lists into one sorted iterator
1196 work = heapq.merge([(x, False) for x in added],
1196 work = heapq.merge([(x, False) for x in added],
1197 [(x, True) for x in removed])
1197 [(x, True) for x in removed])
1198
1198
1199 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1199 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1200 cachedelta = self.rev(p1), deltatext
1200 cachedelta = self.rev(p1), deltatext
1201 text = util.buffer(arraytext)
1201 text = util.buffer(arraytext)
1202 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1202 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1203 else:
1203 else:
1204 # The first parent manifest isn't already loaded, so we'll
1204 # The first parent manifest isn't already loaded, so we'll
1205 # just encode a fulltext of the manifest and pass that
1205 # just encode a fulltext of the manifest and pass that
1206 # through to the revlog layer, and let it handle the delta
1206 # through to the revlog layer, and let it handle the delta
1207 # process.
1207 # process.
1208 if self._treeondisk:
1208 if self._treeondisk:
1209 assert readtree, "readtree must be set for treemanifest writes"
1209 assert readtree, "readtree must be set for treemanifest writes"
1210 m1 = readtree(self._dir, p1)
1210 m1 = readtree(self._dir, p1)
1211 m2 = readtree(self._dir, p2)
1211 m2 = readtree(self._dir, p2)
1212 n = self._addtree(m, transaction, link, m1, m2, readtree)
1212 n = self._addtree(m, transaction, link, m1, m2, readtree)
1213 arraytext = None
1213 arraytext = None
1214 else:
1214 else:
1215 text = m.text(self._usemanifestv2)
1215 text = m.text(self._usemanifestv2)
1216 n = self.addrevision(text, transaction, link, p1, p2)
1216 n = self.addrevision(text, transaction, link, p1, p2)
1217 arraytext = array.array('c', text)
1217 arraytext = array.array('c', text)
1218
1218
1219 if arraytext is not None:
1219 if arraytext is not None:
1220 self.fulltextcache[n] = arraytext
1220 self.fulltextcache[n] = arraytext
1221
1221
1222 return n
1222 return n
1223
1223
1224 def _addtree(self, m, transaction, link, m1, m2, readtree):
1224 def _addtree(self, m, transaction, link, m1, m2, readtree):
1225 # If the manifest is unchanged compared to one parent,
1225 # If the manifest is unchanged compared to one parent,
1226 # don't write a new revision
1226 # don't write a new revision
1227 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1227 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1228 return m.node()
1228 return m.node()
1229 def writesubtree(subm, subp1, subp2):
1229 def writesubtree(subm, subp1, subp2):
1230 sublog = self.dirlog(subm.dir())
1230 sublog = self.dirlog(subm.dir())
1231 sublog.add(subm, transaction, link, subp1, subp2, None, None,
1231 sublog.add(subm, transaction, link, subp1, subp2, None, None,
1232 readtree=readtree)
1232 readtree=readtree)
1233 m.writesubtrees(m1, m2, writesubtree)
1233 m.writesubtrees(m1, m2, writesubtree)
1234 text = m.dirtext(self._usemanifestv2)
1234 text = m.dirtext(self._usemanifestv2)
1235 # Double-check whether contents are unchanged to one parent
1235 # Double-check whether contents are unchanged to one parent
1236 if text == m1.dirtext(self._usemanifestv2):
1236 if text == m1.dirtext(self._usemanifestv2):
1237 n = m1.node()
1237 n = m1.node()
1238 elif text == m2.dirtext(self._usemanifestv2):
1238 elif text == m2.dirtext(self._usemanifestv2):
1239 n = m2.node()
1239 n = m2.node()
1240 else:
1240 else:
1241 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1241 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1242 # Save nodeid so parent manifest can calculate its nodeid
1242 # Save nodeid so parent manifest can calculate its nodeid
1243 m.setnode(n)
1243 m.setnode(n)
1244 return n
1244 return n
1245
1245
1246 class manifestlog(object):
1246 class manifestlog(object):
1247 """A collection class representing the collection of manifest snapshots
1247 """A collection class representing the collection of manifest snapshots
1248 referenced by commits in the repository.
1248 referenced by commits in the repository.
1249
1249
1250 In this situation, 'manifest' refers to the abstract concept of a snapshot
1250 In this situation, 'manifest' refers to the abstract concept of a snapshot
1251 of the list of files in the given commit. Consumers of the output of this
1251 of the list of files in the given commit. Consumers of the output of this
1252 class do not care about the implementation details of the actual manifests
1252 class do not care about the implementation details of the actual manifests
1253 they receive (i.e. tree or flat or lazily loaded, etc)."""
1253 they receive (i.e. tree or flat or lazily loaded, etc)."""
1254 def __init__(self, opener, repo):
1254 def __init__(self, opener, repo):
1255 self._repo = repo
1255 self._repo = repo
1256
1256
1257 usetreemanifest = False
1257 usetreemanifest = False
1258
1258
1259 opts = getattr(opener, 'options', None)
1259 opts = getattr(opener, 'options', None)
1260 if opts is not None:
1260 if opts is not None:
1261 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1261 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1262 self._treeinmem = usetreemanifest
1262 self._treeinmem = usetreemanifest
1263
1263
1264 self._oldmanifest = repo._constructmanifest()
1264 self._oldmanifest = repo._constructmanifest()
1265 self._revlog = self._oldmanifest
1265 self._revlog = self._oldmanifest
1266
1266
1267 # A cache of the manifestctx or treemanifestctx for each directory
1267 # A cache of the manifestctx or treemanifestctx for each directory
1268 self._dirmancache = {}
1268 self._dirmancache = {}
1269
1269
1270 # We'll separate this into it's own cache once oldmanifest is no longer
1270 # We'll separate this into it's own cache once oldmanifest is no longer
1271 # used
1271 # used
1272 self._mancache = self._oldmanifest._mancache
1272 self._mancache = self._oldmanifest._mancache
1273 self._dirmancache[''] = self._mancache
1273 self._dirmancache[''] = self._mancache
1274
1274
1275 # A future patch makes this use the same config value as the existing
1275 # A future patch makes this use the same config value as the existing
1276 # mancache
1276 # mancache
1277 self.cachesize = 4
1277 self.cachesize = 4
1278
1278
1279 def __getitem__(self, node):
1279 def __getitem__(self, node):
1280 """Retrieves the manifest instance for the given node. Throws a
1280 """Retrieves the manifest instance for the given node. Throws a
1281 LookupError if not found.
1281 LookupError if not found.
1282 """
1282 """
1283 return self.get('', node)
1283 return self.get('', node)
1284
1284
1285 def get(self, dir, node):
1285 def get(self, dir, node):
1286 """Retrieves the manifest instance for the given node. Throws a
1286 """Retrieves the manifest instance for the given node. Throws a
1287 LookupError if not found.
1287 LookupError if not found.
1288 """
1288 """
1289 if node in self._dirmancache.get(dir, ()):
1289 if node in self._dirmancache.get(dir, ()):
1290 cachemf = self._dirmancache[dir][node]
1290 cachemf = self._dirmancache[dir][node]
1291 # The old manifest may put non-ctx manifests in the cache, so
1291 # The old manifest may put non-ctx manifests in the cache, so
1292 # skip those since they don't implement the full api.
1292 # skip those since they don't implement the full api.
1293 if (isinstance(cachemf, manifestctx) or
1293 if (isinstance(cachemf, manifestctx) or
1294 isinstance(cachemf, treemanifestctx)):
1294 isinstance(cachemf, treemanifestctx)):
1295 return cachemf
1295 return cachemf
1296
1296
1297 if dir:
1297 if dir:
1298 if self._revlog._treeondisk:
1298 if self._revlog._treeondisk:
1299 dirlog = self._revlog.dirlog(dir)
1299 dirlog = self._revlog.dirlog(dir)
1300 if node not in dirlog.nodemap:
1300 if node not in dirlog.nodemap:
1301 raise LookupError(node, dirlog.indexfile,
1301 raise LookupError(node, dirlog.indexfile,
1302 _('no node'))
1302 _('no node'))
1303 m = treemanifestctx(self._repo, dir, node)
1303 m = treemanifestctx(self._repo, dir, node)
1304 else:
1304 else:
1305 raise error.Abort(
1305 raise error.Abort(
1306 _("cannot ask for manifest directory '%s' in a flat "
1306 _("cannot ask for manifest directory '%s' in a flat "
1307 "manifest") % dir)
1307 "manifest") % dir)
1308 else:
1308 else:
1309 if node not in self._revlog.nodemap:
1309 if node not in self._revlog.nodemap:
1310 raise LookupError(node, self._revlog.indexfile,
1310 raise LookupError(node, self._revlog.indexfile,
1311 _('no node'))
1311 _('no node'))
1312 if self._treeinmem:
1312 if self._treeinmem:
1313 m = treemanifestctx(self._repo, '', node)
1313 m = treemanifestctx(self._repo, '', node)
1314 else:
1314 else:
1315 m = manifestctx(self._repo, node)
1315 m = manifestctx(self._repo, node)
1316
1316
1317 if node != revlog.nullid:
1317 if node != revlog.nullid:
1318 mancache = self._dirmancache.get(dir)
1318 mancache = self._dirmancache.get(dir)
1319 if not mancache:
1319 if not mancache:
1320 mancache = util.lrucachedict(self.cachesize)
1320 mancache = util.lrucachedict(self.cachesize)
1321 self._dirmancache[dir] = mancache
1321 self._dirmancache[dir] = mancache
1322 mancache[node] = m
1322 mancache[node] = m
1323 return m
1323 return m
1324
1324
1325 class memmanifestctx(object):
1325 class memmanifestctx(object):
1326 def __init__(self, repo):
1326 def __init__(self, repo):
1327 self._repo = repo
1327 self._repo = repo
1328 self._manifestdict = manifestdict()
1328 self._manifestdict = manifestdict()
1329
1329
1330 def _revlog(self):
1330 def _revlog(self):
1331 return self._repo.manifestlog._revlog
1331 return self._repo.manifestlog._revlog
1332
1332
1333 def new(self):
1333 def new(self):
1334 return memmanifestctx(self._repo)
1334 return memmanifestctx(self._repo)
1335
1335
1336 def copy(self):
1336 def copy(self):
1337 memmf = memmanifestctx(self._repo)
1337 memmf = memmanifestctx(self._repo)
1338 memmf._manifestdict = self.read().copy()
1338 memmf._manifestdict = self.read().copy()
1339 return memmf
1339 return memmf
1340
1340
1341 def read(self):
1341 def read(self):
1342 return self._manifestdict
1342 return self._manifestdict
1343
1343
1344 def write(self, transaction, link, p1, p2, added, removed):
1344 def write(self, transaction, link, p1, p2, added, removed):
1345 return self._revlog().add(self._manifestdict, transaction, link, p1, p2,
1345 return self._revlog().add(self._manifestdict, transaction, link, p1, p2,
1346 added, removed)
1346 added, removed)
1347
1347
1348 class manifestctx(object):
1348 class manifestctx(object):
1349 """A class representing a single revision of a manifest, including its
1349 """A class representing a single revision of a manifest, including its
1350 contents, its parent revs, and its linkrev.
1350 contents, its parent revs, and its linkrev.
1351 """
1351 """
1352 def __init__(self, repo, node):
1352 def __init__(self, repo, node):
1353 self._repo = repo
1353 self._repo = repo
1354 self._data = None
1354 self._data = None
1355
1355
1356 self._node = node
1356 self._node = node
1357
1357
1358 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1358 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1359 # but let's add it later when something needs it and we can load it
1359 # but let's add it later when something needs it and we can load it
1360 # lazily.
1360 # lazily.
1361 #self.p1, self.p2 = revlog.parents(node)
1361 #self.p1, self.p2 = revlog.parents(node)
1362 #rev = revlog.rev(node)
1362 #rev = revlog.rev(node)
1363 #self.linkrev = revlog.linkrev(rev)
1363 #self.linkrev = revlog.linkrev(rev)
1364
1364
1365 def _revlog(self):
1365 def _revlog(self):
1366 return self._repo.manifestlog._revlog
1366 return self._repo.manifestlog._revlog
1367
1367
1368 def node(self):
1368 def node(self):
1369 return self._node
1369 return self._node
1370
1370
1371 def new(self):
1371 def new(self):
1372 return memmanifestctx(self._repo)
1372 return memmanifestctx(self._repo)
1373
1373
1374 def copy(self):
1374 def copy(self):
1375 memmf = memmanifestctx(self._repo)
1375 memmf = memmanifestctx(self._repo)
1376 memmf._manifestdict = self.read().copy()
1376 memmf._manifestdict = self.read().copy()
1377 return memmf
1377 return memmf
1378
1378
1379 def read(self):
1379 def read(self):
1380 if not self._data:
1380 if not self._data:
1381 if self._node == revlog.nullid:
1381 if self._node == revlog.nullid:
1382 self._data = manifestdict()
1382 self._data = manifestdict()
1383 else:
1383 else:
1384 rl = self._revlog()
1384 rl = self._revlog()
1385 text = rl.revision(self._node)
1385 text = rl.revision(self._node)
1386 arraytext = array.array('c', text)
1386 arraytext = array.array('c', text)
1387 rl._fulltextcache[self._node] = arraytext
1387 rl._fulltextcache[self._node] = arraytext
1388 self._data = manifestdict(text)
1388 self._data = manifestdict(text)
1389 return self._data
1389 return self._data
1390
1390
1391 def readfast(self, shallow=False):
1391 def readfast(self, shallow=False):
1392 '''Calls either readdelta or read, based on which would be less work.
1392 '''Calls either readdelta or read, based on which would be less work.
1393 readdelta is called if the delta is against the p1, and therefore can be
1393 readdelta is called if the delta is against the p1, and therefore can be
1394 read quickly.
1394 read quickly.
1395
1395
1396 If `shallow` is True, nothing changes since this is a flat manifest.
1396 If `shallow` is True, nothing changes since this is a flat manifest.
1397 '''
1397 '''
1398 rl = self._revlog()
1398 rl = self._revlog()
1399 r = rl.rev(self._node)
1399 r = rl.rev(self._node)
1400 deltaparent = rl.deltaparent(r)
1400 deltaparent = rl.deltaparent(r)
1401 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1401 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1402 return self.readdelta()
1402 return self.readdelta()
1403 return self.read()
1403 return self.read()
1404
1404
1405 def readdelta(self, shallow=False):
1405 def readdelta(self, shallow=False):
1406 '''Returns a manifest containing just the entries that are present
1406 '''Returns a manifest containing just the entries that are present
1407 in this manifest, but not in its p1 manifest. This is efficient to read
1407 in this manifest, but not in its p1 manifest. This is efficient to read
1408 if the revlog delta is already p1.
1408 if the revlog delta is already p1.
1409
1409
1410 Changing the value of `shallow` has no effect on flat manifests.
1410 Changing the value of `shallow` has no effect on flat manifests.
1411 '''
1411 '''
1412 revlog = self._revlog()
1412 revlog = self._revlog()
1413 if revlog._usemanifestv2:
1413 if revlog._usemanifestv2:
1414 # Need to perform a slow delta
1414 # Need to perform a slow delta
1415 r0 = revlog.deltaparent(revlog.rev(self._node))
1415 r0 = revlog.deltaparent(revlog.rev(self._node))
1416 m0 = manifestctx(self._repo, revlog.node(r0)).read()
1416 m0 = manifestctx(self._repo, revlog.node(r0)).read()
1417 m1 = self.read()
1417 m1 = self.read()
1418 md = manifestdict()
1418 md = manifestdict()
1419 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1419 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1420 if n1:
1420 if n1:
1421 md[f] = n1
1421 md[f] = n1
1422 if fl1:
1422 if fl1:
1423 md.setflag(f, fl1)
1423 md.setflag(f, fl1)
1424 return md
1424 return md
1425
1425
1426 r = revlog.rev(self._node)
1426 r = revlog.rev(self._node)
1427 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1427 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1428 return manifestdict(d)
1428 return manifestdict(d)
1429
1429
1430 def find(self, key):
1430 def find(self, key):
1431 return self.read().find(key)
1431 return self.read().find(key)
1432
1432
1433 class memtreemanifestctx(object):
1433 class memtreemanifestctx(object):
1434 def __init__(self, repo, dir=''):
1434 def __init__(self, repo, dir=''):
1435 self._repo = repo
1435 self._repo = repo
1436 self._dir = dir
1436 self._dir = dir
1437 self._treemanifest = treemanifest()
1437 self._treemanifest = treemanifest()
1438
1438
1439 def _revlog(self):
1439 def _revlog(self):
1440 return self._repo.manifestlog._revlog
1440 return self._repo.manifestlog._revlog
1441
1441
1442 def new(self, dir=''):
1442 def new(self, dir=''):
1443 return memtreemanifestctx(self._repo, dir=dir)
1443 return memtreemanifestctx(self._repo, dir=dir)
1444
1444
1445 def copy(self):
1445 def copy(self):
1446 memmf = memtreemanifestctx(self._repo, dir=self._dir)
1446 memmf = memtreemanifestctx(self._repo, dir=self._dir)
1447 memmf._treemanifest = self._treemanifest.copy()
1447 memmf._treemanifest = self._treemanifest.copy()
1448 return memmf
1448 return memmf
1449
1449
1450 def read(self):
1450 def read(self):
1451 return self._treemanifest
1451 return self._treemanifest
1452
1452
1453 def write(self, transaction, link, p1, p2, added, removed):
1453 def write(self, transaction, link, p1, p2, added, removed):
1454 def readtree(dir, node):
1454 def readtree(dir, node):
1455 return self._repo.manifestlog.get(dir, node).read()
1455 return self._repo.manifestlog.get(dir, node).read()
1456 return self._revlog().add(self._treemanifest, transaction, link, p1, p2,
1456 return self._revlog().add(self._treemanifest, transaction, link, p1, p2,
1457 added, removed, readtree=readtree)
1457 added, removed, readtree=readtree)
1458
1458
1459 class treemanifestctx(object):
1459 class treemanifestctx(object):
1460 def __init__(self, repo, dir, node):
1460 def __init__(self, repo, dir, node):
1461 self._repo = repo
1461 self._repo = repo
1462 self._dir = dir
1462 self._dir = dir
1463 self._data = None
1463 self._data = None
1464
1464
1465 self._node = node
1465 self._node = node
1466
1466
1467 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1467 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1468 # we can instantiate treemanifestctx objects for directories we don't
1468 # we can instantiate treemanifestctx objects for directories we don't
1469 # have on disk.
1469 # have on disk.
1470 #self.p1, self.p2 = revlog.parents(node)
1470 #self.p1, self.p2 = revlog.parents(node)
1471 #rev = revlog.rev(node)
1471 #rev = revlog.rev(node)
1472 #self.linkrev = revlog.linkrev(rev)
1472 #self.linkrev = revlog.linkrev(rev)
1473
1473
1474 def _revlog(self):
1474 def _revlog(self):
1475 return self._repo.manifestlog._revlog.dirlog(self._dir)
1475 return self._repo.manifestlog._revlog.dirlog(self._dir)
1476
1476
1477 def read(self):
1477 def read(self):
1478 if not self._data:
1478 if not self._data:
1479 rl = self._revlog()
1479 rl = self._revlog()
1480 if self._node == revlog.nullid:
1480 if self._node == revlog.nullid:
1481 self._data = treemanifest()
1481 self._data = treemanifest()
1482 elif rl._treeondisk:
1482 elif rl._treeondisk:
1483 m = treemanifest(dir=self._dir)
1483 m = treemanifest(dir=self._dir)
1484 def gettext():
1484 def gettext():
1485 return rl.revision(self._node)
1485 return rl.revision(self._node)
1486 def readsubtree(dir, subm):
1486 def readsubtree(dir, subm):
1487 return treemanifestctx(self._repo, dir, subm).read()
1487 return treemanifestctx(self._repo, dir, subm).read()
1488 m.read(gettext, readsubtree)
1488 m.read(gettext, readsubtree)
1489 m.setnode(self._node)
1489 m.setnode(self._node)
1490 self._data = m
1490 self._data = m
1491 else:
1491 else:
1492 text = rl.revision(self._node)
1492 text = rl.revision(self._node)
1493 arraytext = array.array('c', text)
1493 arraytext = array.array('c', text)
1494 rl.fulltextcache[self._node] = arraytext
1494 rl.fulltextcache[self._node] = arraytext
1495 self._data = treemanifest(dir=self._dir, text=text)
1495 self._data = treemanifest(dir=self._dir, text=text)
1496
1496
1497 return self._data
1497 return self._data
1498
1498
1499 def node(self):
1499 def node(self):
1500 return self._node
1500 return self._node
1501
1501
1502 def new(self, dir=''):
1502 def new(self, dir=''):
1503 return memtreemanifestctx(self._repo, dir=dir)
1503 return memtreemanifestctx(self._repo, dir=dir)
1504
1504
1505 def copy(self):
1505 def copy(self):
1506 memmf = memtreemanifestctx(self._repo, dir=self._dir)
1506 memmf = memtreemanifestctx(self._repo, dir=self._dir)
1507 memmf._treemanifest = self.read().copy()
1507 memmf._treemanifest = self.read().copy()
1508 return memmf
1508 return memmf
1509
1509
1510 def readdelta(self, shallow=False):
1510 def readdelta(self, shallow=False):
1511 '''Returns a manifest containing just the entries that are present
1511 '''Returns a manifest containing just the entries that are present
1512 in this manifest, but not in its p1 manifest. This is efficient to read
1512 in this manifest, but not in its p1 manifest. This is efficient to read
1513 if the revlog delta is already p1.
1513 if the revlog delta is already p1.
1514
1514
1515 If `shallow` is True, this will read the delta for this directory,
1515 If `shallow` is True, this will read the delta for this directory,
1516 without recursively reading subdirectory manifests. Instead, any
1516 without recursively reading subdirectory manifests. Instead, any
1517 subdirectory entry will be reported as it appears in the manifest, i.e.
1517 subdirectory entry will be reported as it appears in the manifest, i.e.
1518 the subdirectory will be reported among files and distinguished only by
1518 the subdirectory will be reported among files and distinguished only by
1519 its 't' flag.
1519 its 't' flag.
1520 '''
1520 '''
1521 revlog = self._revlog()
1521 revlog = self._revlog()
1522 if shallow and not revlog._usemanifestv2:
1522 if shallow and not revlog._usemanifestv2:
1523 r = revlog.rev(self._node)
1523 r = revlog.rev(self._node)
1524 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1524 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1525 return manifestdict(d)
1525 return manifestdict(d)
1526 else:
1526 else:
1527 # Need to perform a slow delta
1527 # Need to perform a slow delta
1528 r0 = revlog.deltaparent(revlog.rev(self._node))
1528 r0 = revlog.deltaparent(revlog.rev(self._node))
1529 m0 = treemanifestctx(self._repo, self._dir, revlog.node(r0)).read()
1529 m0 = treemanifestctx(self._repo, self._dir, revlog.node(r0)).read()
1530 m1 = self.read()
1530 m1 = self.read()
1531 md = treemanifest(dir=self._dir)
1531 md = treemanifest(dir=self._dir)
1532 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1532 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1533 if n1:
1533 if n1:
1534 md[f] = n1
1534 md[f] = n1
1535 if fl1:
1535 if fl1:
1536 md.setflag(f, fl1)
1536 md.setflag(f, fl1)
1537 return md
1537 return md
1538
1538
1539 def readfast(self, shallow=False):
1539 def readfast(self, shallow=False):
1540 '''Calls either readdelta or read, based on which would be less work.
1540 '''Calls either readdelta or read, based on which would be less work.
1541 readdelta is called if the delta is against the p1, and therefore can be
1541 readdelta is called if the delta is against the p1, and therefore can be
1542 read quickly.
1542 read quickly.
1543
1543
1544 If `shallow` is True, it only returns the entries from this manifest,
1544 If `shallow` is True, it only returns the entries from this manifest,
1545 and not any submanifests.
1545 and not any submanifests.
1546 '''
1546 '''
1547 rl = self._revlog()
1547 rl = self._revlog()
1548 r = rl.rev(self._node)
1548 r = rl.rev(self._node)
1549 deltaparent = rl.deltaparent(r)
1549 deltaparent = rl.deltaparent(r)
1550 if (deltaparent != revlog.nullrev and
1550 if (deltaparent != revlog.nullrev and
1551 deltaparent in rl.parentrevs(r)):
1551 deltaparent in rl.parentrevs(r)):
1552 return self.readdelta(shallow=shallow)
1552 return self.readdelta(shallow=shallow)
1553
1553
1554 if shallow:
1554 if shallow:
1555 return manifestdict(rl.revision(self._node))
1555 return manifestdict(rl.revision(self._node))
1556 else:
1556 else:
1557 return self.read()
1557 return self.read()
1558
1558
1559 def find(self, key):
1559 def find(self, key):
1560 return self.read().find(key)
1560 return self.read().find(key)
1561
1561
1562 class manifest(manifestrevlog):
1562 class manifest(manifestrevlog):
1563 def __init__(self, opener, dir='', dirlogcache=None):
1563 def __init__(self, opener, dir='', dirlogcache=None):
1564 '''The 'dir' and 'dirlogcache' arguments are for internal use by
1564 '''The 'dir' and 'dirlogcache' arguments are for internal use by
1565 manifest.manifest only. External users should create a root manifest
1565 manifest.manifest only. External users should create a root manifest
1566 log with manifest.manifest(opener) and call dirlog() on it.
1566 log with manifest.manifest(opener) and call dirlog() on it.
1567 '''
1567 '''
1568 # During normal operations, we expect to deal with not more than four
1568 # During normal operations, we expect to deal with not more than four
1569 # revs at a time (such as during commit --amend). When rebasing large
1569 # revs at a time (such as during commit --amend). When rebasing large
1570 # stacks of commits, the number can go up, hence the config knob below.
1570 # stacks of commits, the number can go up, hence the config knob below.
1571 cachesize = 4
1571 cachesize = 4
1572 usetreemanifest = False
1572 usetreemanifest = False
1573 opts = getattr(opener, 'options', None)
1573 opts = getattr(opener, 'options', None)
1574 if opts is not None:
1574 if opts is not None:
1575 cachesize = opts.get('manifestcachesize', cachesize)
1575 cachesize = opts.get('manifestcachesize', cachesize)
1576 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1576 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1577 self._mancache = util.lrucachedict(cachesize)
1577 self._mancache = util.lrucachedict(cachesize)
1578 self._treeinmem = usetreemanifest
1578 self._treeinmem = usetreemanifest
1579 super(manifest, self).__init__(opener, dir=dir, dirlogcache=dirlogcache)
1579 super(manifest, self).__init__(opener, dir=dir, dirlogcache=dirlogcache)
1580
1580
1581 def _newmanifest(self, data=''):
1581 def _newmanifest(self, data=''):
1582 if self._treeinmem:
1582 if self._treeinmem:
1583 return treemanifest(self._dir, data)
1583 return treemanifest(self._dir, data)
1584 return manifestdict(data)
1584 return manifestdict(data)
1585
1585
1586 def dirlog(self, dir):
1586 def dirlog(self, dir):
1587 """This overrides the base revlog implementation to allow construction
1587 """This overrides the base revlog implementation to allow construction
1588 'manifest' types instead of manifestrevlog types. This is only needed
1588 'manifest' types instead of manifestrevlog types. This is only needed
1589 until we migrate off the 'manifest' type."""
1589 until we migrate off the 'manifest' type."""
1590 if dir:
1590 if dir:
1591 assert self._treeondisk
1591 assert self._treeondisk
1592 if dir not in self._dirlogcache:
1592 if dir not in self._dirlogcache:
1593 self._dirlogcache[dir] = manifest(self.opener, dir,
1593 self._dirlogcache[dir] = manifest(self.opener, dir,
1594 self._dirlogcache)
1594 self._dirlogcache)
1595 return self._dirlogcache[dir]
1595 return self._dirlogcache[dir]
1596
1596
1597 def read(self, node):
1598 if node == revlog.nullid:
1599 return self._newmanifest() # don't upset local cache
1600 if node in self._mancache:
1601 cached = self._mancache[node]
1602 if (isinstance(cached, manifestctx) or
1603 isinstance(cached, treemanifestctx)):
1604 cached = cached.read()
1605 return cached
1606 if self._treeondisk:
1607 def gettext():
1608 return self.revision(node)
1609 def readsubtree(dir, subm):
1610 return self.dirlog(dir).read(subm)
1611 m = self._newmanifest()
1612 m.read(gettext, readsubtree)
1613 m.setnode(node)
1614 arraytext = None
1615 else:
1616 text = self.revision(node)
1617 m = self._newmanifest(text)
1618 arraytext = array.array('c', text)
1619 self._mancache[node] = m
1620 if arraytext is not None:
1621 self.fulltextcache[node] = arraytext
1622 return m
1623
1624 def clearcaches(self):
1597 def clearcaches(self):
1625 super(manifest, self).clearcaches()
1598 super(manifest, self).clearcaches()
1626 self._mancache.clear()
1599 self._mancache.clear()
General Comments 0
You need to be logged in to leave comments. Login now