##// END OF EJS Templates
cleanup: rename "matchfn" to "match" where obviously a matcher...
Martin von Zweigbergk -
r34085:08346a8f default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,579 +1,578 b''
1 # Copyright 2009-2010 Gregory P. Ward
1 # Copyright 2009-2010 Gregory P. Ward
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 # Copyright 2010-2011 Fog Creek Software
3 # Copyright 2010-2011 Fog Creek Software
4 # Copyright 2010-2011 Unity Technologies
4 # Copyright 2010-2011 Unity Technologies
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''High-level command function for lfconvert, plus the cmdtable.'''
9 '''High-level command function for lfconvert, plus the cmdtable.'''
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import errno
12 import errno
13 import hashlib
13 import hashlib
14 import os
14 import os
15 import shutil
15 import shutil
16
16
17 from mercurial.i18n import _
17 from mercurial.i18n import _
18
18
19 from mercurial import (
19 from mercurial import (
20 cmdutil,
20 cmdutil,
21 context,
21 context,
22 error,
22 error,
23 hg,
23 hg,
24 lock,
24 lock,
25 match as matchmod,
25 match as matchmod,
26 node,
26 node,
27 registrar,
27 registrar,
28 scmutil,
28 scmutil,
29 util,
29 util,
30 )
30 )
31
31
32 from ..convert import (
32 from ..convert import (
33 convcmd,
33 convcmd,
34 filemap,
34 filemap,
35 )
35 )
36
36
37 from . import (
37 from . import (
38 lfutil,
38 lfutil,
39 storefactory
39 storefactory
40 )
40 )
41
41
42 release = lock.release
42 release = lock.release
43
43
44 # -- Commands ----------------------------------------------------------
44 # -- Commands ----------------------------------------------------------
45
45
46 cmdtable = {}
46 cmdtable = {}
47 command = registrar.command(cmdtable)
47 command = registrar.command(cmdtable)
48
48
49 @command('lfconvert',
49 @command('lfconvert',
50 [('s', 'size', '',
50 [('s', 'size', '',
51 _('minimum size (MB) for files to be converted as largefiles'), 'SIZE'),
51 _('minimum size (MB) for files to be converted as largefiles'), 'SIZE'),
52 ('', 'to-normal', False,
52 ('', 'to-normal', False,
53 _('convert from a largefiles repo to a normal repo')),
53 _('convert from a largefiles repo to a normal repo')),
54 ],
54 ],
55 _('hg lfconvert SOURCE DEST [FILE ...]'),
55 _('hg lfconvert SOURCE DEST [FILE ...]'),
56 norepo=True,
56 norepo=True,
57 inferrepo=True)
57 inferrepo=True)
58 def lfconvert(ui, src, dest, *pats, **opts):
58 def lfconvert(ui, src, dest, *pats, **opts):
59 '''convert a normal repository to a largefiles repository
59 '''convert a normal repository to a largefiles repository
60
60
61 Convert repository SOURCE to a new repository DEST, identical to
61 Convert repository SOURCE to a new repository DEST, identical to
62 SOURCE except that certain files will be converted as largefiles:
62 SOURCE except that certain files will be converted as largefiles:
63 specifically, any file that matches any PATTERN *or* whose size is
63 specifically, any file that matches any PATTERN *or* whose size is
64 above the minimum size threshold is converted as a largefile. The
64 above the minimum size threshold is converted as a largefile. The
65 size used to determine whether or not to track a file as a
65 size used to determine whether or not to track a file as a
66 largefile is the size of the first version of the file. The
66 largefile is the size of the first version of the file. The
67 minimum size can be specified either with --size or in
67 minimum size can be specified either with --size or in
68 configuration as ``largefiles.size``.
68 configuration as ``largefiles.size``.
69
69
70 After running this command you will need to make sure that
70 After running this command you will need to make sure that
71 largefiles is enabled anywhere you intend to push the new
71 largefiles is enabled anywhere you intend to push the new
72 repository.
72 repository.
73
73
74 Use --to-normal to convert largefiles back to normal files; after
74 Use --to-normal to convert largefiles back to normal files; after
75 this, the DEST repository can be used without largefiles at all.'''
75 this, the DEST repository can be used without largefiles at all.'''
76
76
77 if opts['to_normal']:
77 if opts['to_normal']:
78 tolfile = False
78 tolfile = False
79 else:
79 else:
80 tolfile = True
80 tolfile = True
81 size = lfutil.getminsize(ui, True, opts.get('size'), default=None)
81 size = lfutil.getminsize(ui, True, opts.get('size'), default=None)
82
82
83 if not hg.islocal(src):
83 if not hg.islocal(src):
84 raise error.Abort(_('%s is not a local Mercurial repo') % src)
84 raise error.Abort(_('%s is not a local Mercurial repo') % src)
85 if not hg.islocal(dest):
85 if not hg.islocal(dest):
86 raise error.Abort(_('%s is not a local Mercurial repo') % dest)
86 raise error.Abort(_('%s is not a local Mercurial repo') % dest)
87
87
88 rsrc = hg.repository(ui, src)
88 rsrc = hg.repository(ui, src)
89 ui.status(_('initializing destination %s\n') % dest)
89 ui.status(_('initializing destination %s\n') % dest)
90 rdst = hg.repository(ui, dest, create=True)
90 rdst = hg.repository(ui, dest, create=True)
91
91
92 success = False
92 success = False
93 dstwlock = dstlock = None
93 dstwlock = dstlock = None
94 try:
94 try:
95 # Get a list of all changesets in the source. The easy way to do this
95 # Get a list of all changesets in the source. The easy way to do this
96 # is to simply walk the changelog, using changelog.nodesbetween().
96 # is to simply walk the changelog, using changelog.nodesbetween().
97 # Take a look at mercurial/revlog.py:639 for more details.
97 # Take a look at mercurial/revlog.py:639 for more details.
98 # Use a generator instead of a list to decrease memory usage
98 # Use a generator instead of a list to decrease memory usage
99 ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None,
99 ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None,
100 rsrc.heads())[0])
100 rsrc.heads())[0])
101 revmap = {node.nullid: node.nullid}
101 revmap = {node.nullid: node.nullid}
102 if tolfile:
102 if tolfile:
103 # Lock destination to prevent modification while it is converted to.
103 # Lock destination to prevent modification while it is converted to.
104 # Don't need to lock src because we are just reading from its
104 # Don't need to lock src because we are just reading from its
105 # history which can't change.
105 # history which can't change.
106 dstwlock = rdst.wlock()
106 dstwlock = rdst.wlock()
107 dstlock = rdst.lock()
107 dstlock = rdst.lock()
108
108
109 lfiles = set()
109 lfiles = set()
110 normalfiles = set()
110 normalfiles = set()
111 if not pats:
111 if not pats:
112 pats = ui.configlist(lfutil.longname, 'patterns', default=[])
112 pats = ui.configlist(lfutil.longname, 'patterns', default=[])
113 if pats:
113 if pats:
114 matcher = matchmod.match(rsrc.root, '', list(pats))
114 matcher = matchmod.match(rsrc.root, '', list(pats))
115 else:
115 else:
116 matcher = None
116 matcher = None
117
117
118 lfiletohash = {}
118 lfiletohash = {}
119 for ctx in ctxs:
119 for ctx in ctxs:
120 ui.progress(_('converting revisions'), ctx.rev(),
120 ui.progress(_('converting revisions'), ctx.rev(),
121 unit=_('revisions'), total=rsrc['tip'].rev())
121 unit=_('revisions'), total=rsrc['tip'].rev())
122 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
122 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
123 lfiles, normalfiles, matcher, size, lfiletohash)
123 lfiles, normalfiles, matcher, size, lfiletohash)
124 ui.progress(_('converting revisions'), None)
124 ui.progress(_('converting revisions'), None)
125
125
126 if rdst.wvfs.exists(lfutil.shortname):
126 if rdst.wvfs.exists(lfutil.shortname):
127 rdst.wvfs.rmtree(lfutil.shortname)
127 rdst.wvfs.rmtree(lfutil.shortname)
128
128
129 for f in lfiletohash.keys():
129 for f in lfiletohash.keys():
130 if rdst.wvfs.isfile(f):
130 if rdst.wvfs.isfile(f):
131 rdst.wvfs.unlink(f)
131 rdst.wvfs.unlink(f)
132 try:
132 try:
133 rdst.wvfs.removedirs(rdst.wvfs.dirname(f))
133 rdst.wvfs.removedirs(rdst.wvfs.dirname(f))
134 except OSError:
134 except OSError:
135 pass
135 pass
136
136
137 # If there were any files converted to largefiles, add largefiles
137 # If there were any files converted to largefiles, add largefiles
138 # to the destination repository's requirements.
138 # to the destination repository's requirements.
139 if lfiles:
139 if lfiles:
140 rdst.requirements.add('largefiles')
140 rdst.requirements.add('largefiles')
141 rdst._writerequirements()
141 rdst._writerequirements()
142 else:
142 else:
143 class lfsource(filemap.filemap_source):
143 class lfsource(filemap.filemap_source):
144 def __init__(self, ui, source):
144 def __init__(self, ui, source):
145 super(lfsource, self).__init__(ui, source, None)
145 super(lfsource, self).__init__(ui, source, None)
146 self.filemapper.rename[lfutil.shortname] = '.'
146 self.filemapper.rename[lfutil.shortname] = '.'
147
147
148 def getfile(self, name, rev):
148 def getfile(self, name, rev):
149 realname, realrev = rev
149 realname, realrev = rev
150 f = super(lfsource, self).getfile(name, rev)
150 f = super(lfsource, self).getfile(name, rev)
151
151
152 if (not realname.startswith(lfutil.shortnameslash)
152 if (not realname.startswith(lfutil.shortnameslash)
153 or f[0] is None):
153 or f[0] is None):
154 return f
154 return f
155
155
156 # Substitute in the largefile data for the hash
156 # Substitute in the largefile data for the hash
157 hash = f[0].strip()
157 hash = f[0].strip()
158 path = lfutil.findfile(rsrc, hash)
158 path = lfutil.findfile(rsrc, hash)
159
159
160 if path is None:
160 if path is None:
161 raise error.Abort(_("missing largefile for '%s' in %s")
161 raise error.Abort(_("missing largefile for '%s' in %s")
162 % (realname, realrev))
162 % (realname, realrev))
163 return util.readfile(path), f[1]
163 return util.readfile(path), f[1]
164
164
165 class converter(convcmd.converter):
165 class converter(convcmd.converter):
166 def __init__(self, ui, source, dest, revmapfile, opts):
166 def __init__(self, ui, source, dest, revmapfile, opts):
167 src = lfsource(ui, source)
167 src = lfsource(ui, source)
168
168
169 super(converter, self).__init__(ui, src, dest, revmapfile,
169 super(converter, self).__init__(ui, src, dest, revmapfile,
170 opts)
170 opts)
171
171
172 found, missing = downloadlfiles(ui, rsrc)
172 found, missing = downloadlfiles(ui, rsrc)
173 if missing != 0:
173 if missing != 0:
174 raise error.Abort(_("all largefiles must be present locally"))
174 raise error.Abort(_("all largefiles must be present locally"))
175
175
176 orig = convcmd.converter
176 orig = convcmd.converter
177 convcmd.converter = converter
177 convcmd.converter = converter
178
178
179 try:
179 try:
180 convcmd.convert(ui, src, dest)
180 convcmd.convert(ui, src, dest)
181 finally:
181 finally:
182 convcmd.converter = orig
182 convcmd.converter = orig
183 success = True
183 success = True
184 finally:
184 finally:
185 if tolfile:
185 if tolfile:
186 rdst.dirstate.clear()
186 rdst.dirstate.clear()
187 release(dstlock, dstwlock)
187 release(dstlock, dstwlock)
188 if not success:
188 if not success:
189 # we failed, remove the new directory
189 # we failed, remove the new directory
190 shutil.rmtree(rdst.root)
190 shutil.rmtree(rdst.root)
191
191
192 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles,
192 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles,
193 matcher, size, lfiletohash):
193 matcher, size, lfiletohash):
194 # Convert src parents to dst parents
194 # Convert src parents to dst parents
195 parents = _convertparents(ctx, revmap)
195 parents = _convertparents(ctx, revmap)
196
196
197 # Generate list of changed files
197 # Generate list of changed files
198 files = _getchangedfiles(ctx, parents)
198 files = _getchangedfiles(ctx, parents)
199
199
200 dstfiles = []
200 dstfiles = []
201 for f in files:
201 for f in files:
202 if f not in lfiles and f not in normalfiles:
202 if f not in lfiles and f not in normalfiles:
203 islfile = _islfile(f, ctx, matcher, size)
203 islfile = _islfile(f, ctx, matcher, size)
204 # If this file was renamed or copied then copy
204 # If this file was renamed or copied then copy
205 # the largefile-ness of its predecessor
205 # the largefile-ness of its predecessor
206 if f in ctx.manifest():
206 if f in ctx.manifest():
207 fctx = ctx.filectx(f)
207 fctx = ctx.filectx(f)
208 renamed = fctx.renamed()
208 renamed = fctx.renamed()
209 renamedlfile = renamed and renamed[0] in lfiles
209 renamedlfile = renamed and renamed[0] in lfiles
210 islfile |= renamedlfile
210 islfile |= renamedlfile
211 if 'l' in fctx.flags():
211 if 'l' in fctx.flags():
212 if renamedlfile:
212 if renamedlfile:
213 raise error.Abort(
213 raise error.Abort(
214 _('renamed/copied largefile %s becomes symlink')
214 _('renamed/copied largefile %s becomes symlink')
215 % f)
215 % f)
216 islfile = False
216 islfile = False
217 if islfile:
217 if islfile:
218 lfiles.add(f)
218 lfiles.add(f)
219 else:
219 else:
220 normalfiles.add(f)
220 normalfiles.add(f)
221
221
222 if f in lfiles:
222 if f in lfiles:
223 fstandin = lfutil.standin(f)
223 fstandin = lfutil.standin(f)
224 dstfiles.append(fstandin)
224 dstfiles.append(fstandin)
225 # largefile in manifest if it has not been removed/renamed
225 # largefile in manifest if it has not been removed/renamed
226 if f in ctx.manifest():
226 if f in ctx.manifest():
227 fctx = ctx.filectx(f)
227 fctx = ctx.filectx(f)
228 if 'l' in fctx.flags():
228 if 'l' in fctx.flags():
229 renamed = fctx.renamed()
229 renamed = fctx.renamed()
230 if renamed and renamed[0] in lfiles:
230 if renamed and renamed[0] in lfiles:
231 raise error.Abort(_('largefile %s becomes symlink') % f)
231 raise error.Abort(_('largefile %s becomes symlink') % f)
232
232
233 # largefile was modified, update standins
233 # largefile was modified, update standins
234 m = hashlib.sha1('')
234 m = hashlib.sha1('')
235 m.update(ctx[f].data())
235 m.update(ctx[f].data())
236 hash = m.hexdigest()
236 hash = m.hexdigest()
237 if f not in lfiletohash or lfiletohash[f] != hash:
237 if f not in lfiletohash or lfiletohash[f] != hash:
238 rdst.wwrite(f, ctx[f].data(), ctx[f].flags())
238 rdst.wwrite(f, ctx[f].data(), ctx[f].flags())
239 executable = 'x' in ctx[f].flags()
239 executable = 'x' in ctx[f].flags()
240 lfutil.writestandin(rdst, fstandin, hash,
240 lfutil.writestandin(rdst, fstandin, hash,
241 executable)
241 executable)
242 lfiletohash[f] = hash
242 lfiletohash[f] = hash
243 else:
243 else:
244 # normal file
244 # normal file
245 dstfiles.append(f)
245 dstfiles.append(f)
246
246
247 def getfilectx(repo, memctx, f):
247 def getfilectx(repo, memctx, f):
248 srcfname = lfutil.splitstandin(f)
248 srcfname = lfutil.splitstandin(f)
249 if srcfname is not None:
249 if srcfname is not None:
250 # if the file isn't in the manifest then it was removed
250 # if the file isn't in the manifest then it was removed
251 # or renamed, return None to indicate this
251 # or renamed, return None to indicate this
252 try:
252 try:
253 fctx = ctx.filectx(srcfname)
253 fctx = ctx.filectx(srcfname)
254 except error.LookupError:
254 except error.LookupError:
255 return None
255 return None
256 renamed = fctx.renamed()
256 renamed = fctx.renamed()
257 if renamed:
257 if renamed:
258 # standin is always a largefile because largefile-ness
258 # standin is always a largefile because largefile-ness
259 # doesn't change after rename or copy
259 # doesn't change after rename or copy
260 renamed = lfutil.standin(renamed[0])
260 renamed = lfutil.standin(renamed[0])
261
261
262 return context.memfilectx(repo, f, lfiletohash[srcfname] + '\n',
262 return context.memfilectx(repo, f, lfiletohash[srcfname] + '\n',
263 'l' in fctx.flags(), 'x' in fctx.flags(),
263 'l' in fctx.flags(), 'x' in fctx.flags(),
264 renamed)
264 renamed)
265 else:
265 else:
266 return _getnormalcontext(repo, ctx, f, revmap)
266 return _getnormalcontext(repo, ctx, f, revmap)
267
267
268 # Commit
268 # Commit
269 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
269 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
270
270
271 def _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap):
271 def _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap):
272 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
272 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
273 getfilectx, ctx.user(), ctx.date(), ctx.extra())
273 getfilectx, ctx.user(), ctx.date(), ctx.extra())
274 ret = rdst.commitctx(mctx)
274 ret = rdst.commitctx(mctx)
275 lfutil.copyalltostore(rdst, ret)
275 lfutil.copyalltostore(rdst, ret)
276 rdst.setparents(ret)
276 rdst.setparents(ret)
277 revmap[ctx.node()] = rdst.changelog.tip()
277 revmap[ctx.node()] = rdst.changelog.tip()
278
278
279 # Generate list of changed files
279 # Generate list of changed files
280 def _getchangedfiles(ctx, parents):
280 def _getchangedfiles(ctx, parents):
281 files = set(ctx.files())
281 files = set(ctx.files())
282 if node.nullid not in parents:
282 if node.nullid not in parents:
283 mc = ctx.manifest()
283 mc = ctx.manifest()
284 mp1 = ctx.parents()[0].manifest()
284 mp1 = ctx.parents()[0].manifest()
285 mp2 = ctx.parents()[1].manifest()
285 mp2 = ctx.parents()[1].manifest()
286 files |= (set(mp1) | set(mp2)) - set(mc)
286 files |= (set(mp1) | set(mp2)) - set(mc)
287 for f in mc:
287 for f in mc:
288 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
288 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
289 files.add(f)
289 files.add(f)
290 return files
290 return files
291
291
292 # Convert src parents to dst parents
292 # Convert src parents to dst parents
293 def _convertparents(ctx, revmap):
293 def _convertparents(ctx, revmap):
294 parents = []
294 parents = []
295 for p in ctx.parents():
295 for p in ctx.parents():
296 parents.append(revmap[p.node()])
296 parents.append(revmap[p.node()])
297 while len(parents) < 2:
297 while len(parents) < 2:
298 parents.append(node.nullid)
298 parents.append(node.nullid)
299 return parents
299 return parents
300
300
301 # Get memfilectx for a normal file
301 # Get memfilectx for a normal file
302 def _getnormalcontext(repo, ctx, f, revmap):
302 def _getnormalcontext(repo, ctx, f, revmap):
303 try:
303 try:
304 fctx = ctx.filectx(f)
304 fctx = ctx.filectx(f)
305 except error.LookupError:
305 except error.LookupError:
306 return None
306 return None
307 renamed = fctx.renamed()
307 renamed = fctx.renamed()
308 if renamed:
308 if renamed:
309 renamed = renamed[0]
309 renamed = renamed[0]
310
310
311 data = fctx.data()
311 data = fctx.data()
312 if f == '.hgtags':
312 if f == '.hgtags':
313 data = _converttags (repo.ui, revmap, data)
313 data = _converttags (repo.ui, revmap, data)
314 return context.memfilectx(repo, f, data, 'l' in fctx.flags(),
314 return context.memfilectx(repo, f, data, 'l' in fctx.flags(),
315 'x' in fctx.flags(), renamed)
315 'x' in fctx.flags(), renamed)
316
316
317 # Remap tag data using a revision map
317 # Remap tag data using a revision map
318 def _converttags(ui, revmap, data):
318 def _converttags(ui, revmap, data):
319 newdata = []
319 newdata = []
320 for line in data.splitlines():
320 for line in data.splitlines():
321 try:
321 try:
322 id, name = line.split(' ', 1)
322 id, name = line.split(' ', 1)
323 except ValueError:
323 except ValueError:
324 ui.warn(_('skipping incorrectly formatted tag %s\n')
324 ui.warn(_('skipping incorrectly formatted tag %s\n')
325 % line)
325 % line)
326 continue
326 continue
327 try:
327 try:
328 newid = node.bin(id)
328 newid = node.bin(id)
329 except TypeError:
329 except TypeError:
330 ui.warn(_('skipping incorrectly formatted id %s\n')
330 ui.warn(_('skipping incorrectly formatted id %s\n')
331 % id)
331 % id)
332 continue
332 continue
333 try:
333 try:
334 newdata.append('%s %s\n' % (node.hex(revmap[newid]),
334 newdata.append('%s %s\n' % (node.hex(revmap[newid]),
335 name))
335 name))
336 except KeyError:
336 except KeyError:
337 ui.warn(_('no mapping for id %s\n') % id)
337 ui.warn(_('no mapping for id %s\n') % id)
338 continue
338 continue
339 return ''.join(newdata)
339 return ''.join(newdata)
340
340
341 def _islfile(file, ctx, matcher, size):
341 def _islfile(file, ctx, matcher, size):
342 '''Return true if file should be considered a largefile, i.e.
342 '''Return true if file should be considered a largefile, i.e.
343 matcher matches it or it is larger than size.'''
343 matcher matches it or it is larger than size.'''
344 # never store special .hg* files as largefiles
344 # never store special .hg* files as largefiles
345 if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs':
345 if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs':
346 return False
346 return False
347 if matcher and matcher(file):
347 if matcher and matcher(file):
348 return True
348 return True
349 try:
349 try:
350 return ctx.filectx(file).size() >= size * 1024 * 1024
350 return ctx.filectx(file).size() >= size * 1024 * 1024
351 except error.LookupError:
351 except error.LookupError:
352 return False
352 return False
353
353
354 def uploadlfiles(ui, rsrc, rdst, files):
354 def uploadlfiles(ui, rsrc, rdst, files):
355 '''upload largefiles to the central store'''
355 '''upload largefiles to the central store'''
356
356
357 if not files:
357 if not files:
358 return
358 return
359
359
360 store = storefactory.openstore(rsrc, rdst, put=True)
360 store = storefactory.openstore(rsrc, rdst, put=True)
361
361
362 at = 0
362 at = 0
363 ui.debug("sending statlfile command for %d largefiles\n" % len(files))
363 ui.debug("sending statlfile command for %d largefiles\n" % len(files))
364 retval = store.exists(files)
364 retval = store.exists(files)
365 files = filter(lambda h: not retval[h], files)
365 files = filter(lambda h: not retval[h], files)
366 ui.debug("%d largefiles need to be uploaded\n" % len(files))
366 ui.debug("%d largefiles need to be uploaded\n" % len(files))
367
367
368 for hash in files:
368 for hash in files:
369 ui.progress(_('uploading largefiles'), at, unit=_('files'),
369 ui.progress(_('uploading largefiles'), at, unit=_('files'),
370 total=len(files))
370 total=len(files))
371 source = lfutil.findfile(rsrc, hash)
371 source = lfutil.findfile(rsrc, hash)
372 if not source:
372 if not source:
373 raise error.Abort(_('largefile %s missing from store'
373 raise error.Abort(_('largefile %s missing from store'
374 ' (needs to be uploaded)') % hash)
374 ' (needs to be uploaded)') % hash)
375 # XXX check for errors here
375 # XXX check for errors here
376 store.put(source, hash)
376 store.put(source, hash)
377 at += 1
377 at += 1
378 ui.progress(_('uploading largefiles'), None)
378 ui.progress(_('uploading largefiles'), None)
379
379
380 def verifylfiles(ui, repo, all=False, contents=False):
380 def verifylfiles(ui, repo, all=False, contents=False):
381 '''Verify that every largefile revision in the current changeset
381 '''Verify that every largefile revision in the current changeset
382 exists in the central store. With --contents, also verify that
382 exists in the central store. With --contents, also verify that
383 the contents of each local largefile file revision are correct (SHA-1 hash
383 the contents of each local largefile file revision are correct (SHA-1 hash
384 matches the revision ID). With --all, check every changeset in
384 matches the revision ID). With --all, check every changeset in
385 this repository.'''
385 this repository.'''
386 if all:
386 if all:
387 revs = repo.revs('all()')
387 revs = repo.revs('all()')
388 else:
388 else:
389 revs = ['.']
389 revs = ['.']
390
390
391 store = storefactory.openstore(repo)
391 store = storefactory.openstore(repo)
392 return store.verify(revs, contents=contents)
392 return store.verify(revs, contents=contents)
393
393
394 def cachelfiles(ui, repo, node, filelist=None):
394 def cachelfiles(ui, repo, node, filelist=None):
395 '''cachelfiles ensures that all largefiles needed by the specified revision
395 '''cachelfiles ensures that all largefiles needed by the specified revision
396 are present in the repository's largefile cache.
396 are present in the repository's largefile cache.
397
397
398 returns a tuple (cached, missing). cached is the list of files downloaded
398 returns a tuple (cached, missing). cached is the list of files downloaded
399 by this operation; missing is the list of files that were needed but could
399 by this operation; missing is the list of files that were needed but could
400 not be found.'''
400 not be found.'''
401 lfiles = lfutil.listlfiles(repo, node)
401 lfiles = lfutil.listlfiles(repo, node)
402 if filelist:
402 if filelist:
403 lfiles = set(lfiles) & set(filelist)
403 lfiles = set(lfiles) & set(filelist)
404 toget = []
404 toget = []
405
405
406 ctx = repo[node]
406 ctx = repo[node]
407 for lfile in lfiles:
407 for lfile in lfiles:
408 try:
408 try:
409 expectedhash = lfutil.readasstandin(ctx[lfutil.standin(lfile)])
409 expectedhash = lfutil.readasstandin(ctx[lfutil.standin(lfile)])
410 except IOError as err:
410 except IOError as err:
411 if err.errno == errno.ENOENT:
411 if err.errno == errno.ENOENT:
412 continue # node must be None and standin wasn't found in wctx
412 continue # node must be None and standin wasn't found in wctx
413 raise
413 raise
414 if not lfutil.findfile(repo, expectedhash):
414 if not lfutil.findfile(repo, expectedhash):
415 toget.append((lfile, expectedhash))
415 toget.append((lfile, expectedhash))
416
416
417 if toget:
417 if toget:
418 store = storefactory.openstore(repo)
418 store = storefactory.openstore(repo)
419 ret = store.get(toget)
419 ret = store.get(toget)
420 return ret
420 return ret
421
421
422 return ([], [])
422 return ([], [])
423
423
424 def downloadlfiles(ui, repo, rev=None):
424 def downloadlfiles(ui, repo, rev=None):
425 matchfn = scmutil.match(repo[None],
425 match = scmutil.match(repo[None], [repo.wjoin(lfutil.shortname)], {})
426 [repo.wjoin(lfutil.shortname)], {})
427 def prepare(ctx, fns):
426 def prepare(ctx, fns):
428 pass
427 pass
429 totalsuccess = 0
428 totalsuccess = 0
430 totalmissing = 0
429 totalmissing = 0
431 if rev != []: # walkchangerevs on empty list would return all revs
430 if rev != []: # walkchangerevs on empty list would return all revs
432 for ctx in cmdutil.walkchangerevs(repo, matchfn, {'rev' : rev},
431 for ctx in cmdutil.walkchangerevs(repo, match, {'rev' : rev},
433 prepare):
432 prepare):
434 success, missing = cachelfiles(ui, repo, ctx.node())
433 success, missing = cachelfiles(ui, repo, ctx.node())
435 totalsuccess += len(success)
434 totalsuccess += len(success)
436 totalmissing += len(missing)
435 totalmissing += len(missing)
437 ui.status(_("%d additional largefiles cached\n") % totalsuccess)
436 ui.status(_("%d additional largefiles cached\n") % totalsuccess)
438 if totalmissing > 0:
437 if totalmissing > 0:
439 ui.status(_("%d largefiles failed to download\n") % totalmissing)
438 ui.status(_("%d largefiles failed to download\n") % totalmissing)
440 return totalsuccess, totalmissing
439 return totalsuccess, totalmissing
441
440
442 def updatelfiles(ui, repo, filelist=None, printmessage=None,
441 def updatelfiles(ui, repo, filelist=None, printmessage=None,
443 normallookup=False):
442 normallookup=False):
444 '''Update largefiles according to standins in the working directory
443 '''Update largefiles according to standins in the working directory
445
444
446 If ``printmessage`` is other than ``None``, it means "print (or
445 If ``printmessage`` is other than ``None``, it means "print (or
447 ignore, for false) message forcibly".
446 ignore, for false) message forcibly".
448 '''
447 '''
449 statuswriter = lfutil.getstatuswriter(ui, repo, printmessage)
448 statuswriter = lfutil.getstatuswriter(ui, repo, printmessage)
450 with repo.wlock():
449 with repo.wlock():
451 lfdirstate = lfutil.openlfdirstate(ui, repo)
450 lfdirstate = lfutil.openlfdirstate(ui, repo)
452 lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate)
451 lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate)
453
452
454 if filelist is not None:
453 if filelist is not None:
455 filelist = set(filelist)
454 filelist = set(filelist)
456 lfiles = [f for f in lfiles if f in filelist]
455 lfiles = [f for f in lfiles if f in filelist]
457
456
458 update = {}
457 update = {}
459 updated, removed = 0, 0
458 updated, removed = 0, 0
460 wvfs = repo.wvfs
459 wvfs = repo.wvfs
461 wctx = repo[None]
460 wctx = repo[None]
462 for lfile in lfiles:
461 for lfile in lfiles:
463 rellfile = lfile
462 rellfile = lfile
464 rellfileorig = os.path.relpath(
463 rellfileorig = os.path.relpath(
465 scmutil.origpath(ui, repo, wvfs.join(rellfile)),
464 scmutil.origpath(ui, repo, wvfs.join(rellfile)),
466 start=repo.root)
465 start=repo.root)
467 relstandin = lfutil.standin(lfile)
466 relstandin = lfutil.standin(lfile)
468 relstandinorig = os.path.relpath(
467 relstandinorig = os.path.relpath(
469 scmutil.origpath(ui, repo, wvfs.join(relstandin)),
468 scmutil.origpath(ui, repo, wvfs.join(relstandin)),
470 start=repo.root)
469 start=repo.root)
471 if wvfs.exists(relstandin):
470 if wvfs.exists(relstandin):
472 if (wvfs.exists(relstandinorig) and
471 if (wvfs.exists(relstandinorig) and
473 wvfs.exists(rellfile)):
472 wvfs.exists(rellfile)):
474 shutil.copyfile(wvfs.join(rellfile),
473 shutil.copyfile(wvfs.join(rellfile),
475 wvfs.join(rellfileorig))
474 wvfs.join(rellfileorig))
476 wvfs.unlinkpath(relstandinorig)
475 wvfs.unlinkpath(relstandinorig)
477 expecthash = lfutil.readasstandin(wctx[relstandin])
476 expecthash = lfutil.readasstandin(wctx[relstandin])
478 if expecthash != '':
477 if expecthash != '':
479 if lfile not in wctx: # not switched to normal file
478 if lfile not in wctx: # not switched to normal file
480 wvfs.unlinkpath(rellfile, ignoremissing=True)
479 wvfs.unlinkpath(rellfile, ignoremissing=True)
481 # use normallookup() to allocate an entry in largefiles
480 # use normallookup() to allocate an entry in largefiles
482 # dirstate to prevent lfilesrepo.status() from reporting
481 # dirstate to prevent lfilesrepo.status() from reporting
483 # missing files as removed.
482 # missing files as removed.
484 lfdirstate.normallookup(lfile)
483 lfdirstate.normallookup(lfile)
485 update[lfile] = expecthash
484 update[lfile] = expecthash
486 else:
485 else:
487 # Remove lfiles for which the standin is deleted, unless the
486 # Remove lfiles for which the standin is deleted, unless the
488 # lfile is added to the repository again. This happens when a
487 # lfile is added to the repository again. This happens when a
489 # largefile is converted back to a normal file: the standin
488 # largefile is converted back to a normal file: the standin
490 # disappears, but a new (normal) file appears as the lfile.
489 # disappears, but a new (normal) file appears as the lfile.
491 if (wvfs.exists(rellfile) and
490 if (wvfs.exists(rellfile) and
492 repo.dirstate.normalize(lfile) not in wctx):
491 repo.dirstate.normalize(lfile) not in wctx):
493 wvfs.unlinkpath(rellfile)
492 wvfs.unlinkpath(rellfile)
494 removed += 1
493 removed += 1
495
494
496 # largefile processing might be slow and be interrupted - be prepared
495 # largefile processing might be slow and be interrupted - be prepared
497 lfdirstate.write()
496 lfdirstate.write()
498
497
499 if lfiles:
498 if lfiles:
500 statuswriter(_('getting changed largefiles\n'))
499 statuswriter(_('getting changed largefiles\n'))
501 cachelfiles(ui, repo, None, lfiles)
500 cachelfiles(ui, repo, None, lfiles)
502
501
503 for lfile in lfiles:
502 for lfile in lfiles:
504 update1 = 0
503 update1 = 0
505
504
506 expecthash = update.get(lfile)
505 expecthash = update.get(lfile)
507 if expecthash:
506 if expecthash:
508 if not lfutil.copyfromcache(repo, expecthash, lfile):
507 if not lfutil.copyfromcache(repo, expecthash, lfile):
509 # failed ... but already removed and set to normallookup
508 # failed ... but already removed and set to normallookup
510 continue
509 continue
511 # Synchronize largefile dirstate to the last modified
510 # Synchronize largefile dirstate to the last modified
512 # time of the file
511 # time of the file
513 lfdirstate.normal(lfile)
512 lfdirstate.normal(lfile)
514 update1 = 1
513 update1 = 1
515
514
516 # copy the exec mode of largefile standin from the repository's
515 # copy the exec mode of largefile standin from the repository's
517 # dirstate to its state in the lfdirstate.
516 # dirstate to its state in the lfdirstate.
518 rellfile = lfile
517 rellfile = lfile
519 relstandin = lfutil.standin(lfile)
518 relstandin = lfutil.standin(lfile)
520 if wvfs.exists(relstandin):
519 if wvfs.exists(relstandin):
521 # exec is decided by the users permissions using mask 0o100
520 # exec is decided by the users permissions using mask 0o100
522 standinexec = wvfs.stat(relstandin).st_mode & 0o100
521 standinexec = wvfs.stat(relstandin).st_mode & 0o100
523 st = wvfs.stat(rellfile)
522 st = wvfs.stat(rellfile)
524 mode = st.st_mode
523 mode = st.st_mode
525 if standinexec != mode & 0o100:
524 if standinexec != mode & 0o100:
526 # first remove all X bits, then shift all R bits to X
525 # first remove all X bits, then shift all R bits to X
527 mode &= ~0o111
526 mode &= ~0o111
528 if standinexec:
527 if standinexec:
529 mode |= (mode >> 2) & 0o111 & ~util.umask
528 mode |= (mode >> 2) & 0o111 & ~util.umask
530 wvfs.chmod(rellfile, mode)
529 wvfs.chmod(rellfile, mode)
531 update1 = 1
530 update1 = 1
532
531
533 updated += update1
532 updated += update1
534
533
535 lfutil.synclfdirstate(repo, lfdirstate, lfile, normallookup)
534 lfutil.synclfdirstate(repo, lfdirstate, lfile, normallookup)
536
535
537 lfdirstate.write()
536 lfdirstate.write()
538 if lfiles:
537 if lfiles:
539 statuswriter(_('%d largefiles updated, %d removed\n') % (updated,
538 statuswriter(_('%d largefiles updated, %d removed\n') % (updated,
540 removed))
539 removed))
541
540
542 @command('lfpull',
541 @command('lfpull',
543 [('r', 'rev', [], _('pull largefiles for these revisions'))
542 [('r', 'rev', [], _('pull largefiles for these revisions'))
544 ] + cmdutil.remoteopts,
543 ] + cmdutil.remoteopts,
545 _('-r REV... [-e CMD] [--remotecmd CMD] [SOURCE]'))
544 _('-r REV... [-e CMD] [--remotecmd CMD] [SOURCE]'))
546 def lfpull(ui, repo, source="default", **opts):
545 def lfpull(ui, repo, source="default", **opts):
547 """pull largefiles for the specified revisions from the specified source
546 """pull largefiles for the specified revisions from the specified source
548
547
549 Pull largefiles that are referenced from local changesets but missing
548 Pull largefiles that are referenced from local changesets but missing
550 locally, pulling from a remote repository to the local cache.
549 locally, pulling from a remote repository to the local cache.
551
550
552 If SOURCE is omitted, the 'default' path will be used.
551 If SOURCE is omitted, the 'default' path will be used.
553 See :hg:`help urls` for more information.
552 See :hg:`help urls` for more information.
554
553
555 .. container:: verbose
554 .. container:: verbose
556
555
557 Some examples:
556 Some examples:
558
557
559 - pull largefiles for all branch heads::
558 - pull largefiles for all branch heads::
560
559
561 hg lfpull -r "head() and not closed()"
560 hg lfpull -r "head() and not closed()"
562
561
563 - pull largefiles on the default branch::
562 - pull largefiles on the default branch::
564
563
565 hg lfpull -r "branch(default)"
564 hg lfpull -r "branch(default)"
566 """
565 """
567 repo.lfpullsource = source
566 repo.lfpullsource = source
568
567
569 revs = opts.get('rev', [])
568 revs = opts.get('rev', [])
570 if not revs:
569 if not revs:
571 raise error.Abort(_('no revisions specified'))
570 raise error.Abort(_('no revisions specified'))
572 revs = scmutil.revrange(repo, revs)
571 revs = scmutil.revrange(repo, revs)
573
572
574 numcached = 0
573 numcached = 0
575 for rev in revs:
574 for rev in revs:
576 ui.note(_('pulling largefiles for revision %s\n') % rev)
575 ui.note(_('pulling largefiles for revision %s\n') % rev)
577 (cached, missing) = cachelfiles(ui, repo, rev)
576 (cached, missing) = cachelfiles(ui, repo, rev)
578 numcached += len(cached)
577 numcached += len(cached)
579 ui.status(_("%d largefiles cached\n") % numcached)
578 ui.status(_("%d largefiles cached\n") % numcached)
@@ -1,3611 +1,3611 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 dirstateguard,
82 dirstateguard,
83 encoding,
83 encoding,
84 error,
84 error,
85 extensions,
85 extensions,
86 hg,
86 hg,
87 localrepo,
87 localrepo,
88 lock as lockmod,
88 lock as lockmod,
89 patch as patchmod,
89 patch as patchmod,
90 phases,
90 phases,
91 pycompat,
91 pycompat,
92 registrar,
92 registrar,
93 revsetlang,
93 revsetlang,
94 scmutil,
94 scmutil,
95 smartset,
95 smartset,
96 subrepo,
96 subrepo,
97 util,
97 util,
98 vfs as vfsmod,
98 vfs as vfsmod,
99 )
99 )
100
100
101 release = lockmod.release
101 release = lockmod.release
102 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
102 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
103
103
104 cmdtable = {}
104 cmdtable = {}
105 command = registrar.command(cmdtable)
105 command = registrar.command(cmdtable)
106 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
106 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
107 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
107 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
108 # be specifying the version(s) of Mercurial they are tested with, or
108 # be specifying the version(s) of Mercurial they are tested with, or
109 # leave the attribute unspecified.
109 # leave the attribute unspecified.
110 testedwith = 'ships-with-hg-core'
110 testedwith = 'ships-with-hg-core'
111
111
112 # force load strip extension formerly included in mq and import some utility
112 # force load strip extension formerly included in mq and import some utility
113 try:
113 try:
114 stripext = extensions.find('strip')
114 stripext = extensions.find('strip')
115 except KeyError:
115 except KeyError:
116 # note: load is lazy so we could avoid the try-except,
116 # note: load is lazy so we could avoid the try-except,
117 # but I (marmoute) prefer this explicit code.
117 # but I (marmoute) prefer this explicit code.
118 class dummyui(object):
118 class dummyui(object):
119 def debug(self, msg):
119 def debug(self, msg):
120 pass
120 pass
121 stripext = extensions.load(dummyui(), 'strip', '')
121 stripext = extensions.load(dummyui(), 'strip', '')
122
122
123 strip = stripext.strip
123 strip = stripext.strip
124 checksubstate = stripext.checksubstate
124 checksubstate = stripext.checksubstate
125 checklocalchanges = stripext.checklocalchanges
125 checklocalchanges = stripext.checklocalchanges
126
126
127
127
128 # Patch names looks like unix-file names.
128 # Patch names looks like unix-file names.
129 # They must be joinable with queue directory and result in the patch path.
129 # They must be joinable with queue directory and result in the patch path.
130 normname = util.normpath
130 normname = util.normpath
131
131
132 class statusentry(object):
132 class statusentry(object):
133 def __init__(self, node, name):
133 def __init__(self, node, name):
134 self.node, self.name = node, name
134 self.node, self.name = node, name
135 def __repr__(self):
135 def __repr__(self):
136 return hex(self.node) + ':' + self.name
136 return hex(self.node) + ':' + self.name
137
137
138 # The order of the headers in 'hg export' HG patches:
138 # The order of the headers in 'hg export' HG patches:
139 HGHEADERS = [
139 HGHEADERS = [
140 # '# HG changeset patch',
140 # '# HG changeset patch',
141 '# User ',
141 '# User ',
142 '# Date ',
142 '# Date ',
143 '# ',
143 '# ',
144 '# Branch ',
144 '# Branch ',
145 '# Node ID ',
145 '# Node ID ',
146 '# Parent ', # can occur twice for merges - but that is not relevant for mq
146 '# Parent ', # can occur twice for merges - but that is not relevant for mq
147 ]
147 ]
148 # The order of headers in plain 'mail style' patches:
148 # The order of headers in plain 'mail style' patches:
149 PLAINHEADERS = {
149 PLAINHEADERS = {
150 'from': 0,
150 'from': 0,
151 'date': 1,
151 'date': 1,
152 'subject': 2,
152 'subject': 2,
153 }
153 }
154
154
155 def inserthgheader(lines, header, value):
155 def inserthgheader(lines, header, value):
156 """Assuming lines contains a HG patch header, add a header line with value.
156 """Assuming lines contains a HG patch header, add a header line with value.
157 >>> try: inserthgheader([], '# Date ', 'z')
157 >>> try: inserthgheader([], '# Date ', 'z')
158 ... except ValueError, inst: print "oops"
158 ... except ValueError, inst: print "oops"
159 oops
159 oops
160 >>> inserthgheader(['# HG changeset patch'], '# Date ', 'z')
160 >>> inserthgheader(['# HG changeset patch'], '# Date ', 'z')
161 ['# HG changeset patch', '# Date z']
161 ['# HG changeset patch', '# Date z']
162 >>> inserthgheader(['# HG changeset patch', ''], '# Date ', 'z')
162 >>> inserthgheader(['# HG changeset patch', ''], '# Date ', 'z')
163 ['# HG changeset patch', '# Date z', '']
163 ['# HG changeset patch', '# Date z', '']
164 >>> inserthgheader(['# HG changeset patch', '# User y'], '# Date ', 'z')
164 >>> inserthgheader(['# HG changeset patch', '# User y'], '# Date ', 'z')
165 ['# HG changeset patch', '# User y', '# Date z']
165 ['# HG changeset patch', '# User y', '# Date z']
166 >>> inserthgheader(['# HG changeset patch', '# Date x', '# User y'],
166 >>> inserthgheader(['# HG changeset patch', '# Date x', '# User y'],
167 ... '# User ', 'z')
167 ... '# User ', 'z')
168 ['# HG changeset patch', '# Date x', '# User z']
168 ['# HG changeset patch', '# Date x', '# User z']
169 >>> inserthgheader(['# HG changeset patch', '# Date y'], '# Date ', 'z')
169 >>> inserthgheader(['# HG changeset patch', '# Date y'], '# Date ', 'z')
170 ['# HG changeset patch', '# Date z']
170 ['# HG changeset patch', '# Date z']
171 >>> inserthgheader(['# HG changeset patch', '', '# Date y'], '# Date ', 'z')
171 >>> inserthgheader(['# HG changeset patch', '', '# Date y'], '# Date ', 'z')
172 ['# HG changeset patch', '# Date z', '', '# Date y']
172 ['# HG changeset patch', '# Date z', '', '# Date y']
173 >>> inserthgheader(['# HG changeset patch', '# Parent y'], '# Date ', 'z')
173 >>> inserthgheader(['# HG changeset patch', '# Parent y'], '# Date ', 'z')
174 ['# HG changeset patch', '# Date z', '# Parent y']
174 ['# HG changeset patch', '# Date z', '# Parent y']
175 """
175 """
176 start = lines.index('# HG changeset patch') + 1
176 start = lines.index('# HG changeset patch') + 1
177 newindex = HGHEADERS.index(header)
177 newindex = HGHEADERS.index(header)
178 bestpos = len(lines)
178 bestpos = len(lines)
179 for i in range(start, len(lines)):
179 for i in range(start, len(lines)):
180 line = lines[i]
180 line = lines[i]
181 if not line.startswith('# '):
181 if not line.startswith('# '):
182 bestpos = min(bestpos, i)
182 bestpos = min(bestpos, i)
183 break
183 break
184 for lineindex, h in enumerate(HGHEADERS):
184 for lineindex, h in enumerate(HGHEADERS):
185 if line.startswith(h):
185 if line.startswith(h):
186 if lineindex == newindex:
186 if lineindex == newindex:
187 lines[i] = header + value
187 lines[i] = header + value
188 return lines
188 return lines
189 if lineindex > newindex:
189 if lineindex > newindex:
190 bestpos = min(bestpos, i)
190 bestpos = min(bestpos, i)
191 break # next line
191 break # next line
192 lines.insert(bestpos, header + value)
192 lines.insert(bestpos, header + value)
193 return lines
193 return lines
194
194
195 def insertplainheader(lines, header, value):
195 def insertplainheader(lines, header, value):
196 """For lines containing a plain patch header, add a header line with value.
196 """For lines containing a plain patch header, add a header line with value.
197 >>> insertplainheader([], 'Date', 'z')
197 >>> insertplainheader([], 'Date', 'z')
198 ['Date: z']
198 ['Date: z']
199 >>> insertplainheader([''], 'Date', 'z')
199 >>> insertplainheader([''], 'Date', 'z')
200 ['Date: z', '']
200 ['Date: z', '']
201 >>> insertplainheader(['x'], 'Date', 'z')
201 >>> insertplainheader(['x'], 'Date', 'z')
202 ['Date: z', '', 'x']
202 ['Date: z', '', 'x']
203 >>> insertplainheader(['From: y', 'x'], 'Date', 'z')
203 >>> insertplainheader(['From: y', 'x'], 'Date', 'z')
204 ['From: y', 'Date: z', '', 'x']
204 ['From: y', 'Date: z', '', 'x']
205 >>> insertplainheader([' date : x', ' from : y', ''], 'From', 'z')
205 >>> insertplainheader([' date : x', ' from : y', ''], 'From', 'z')
206 [' date : x', 'From: z', '']
206 [' date : x', 'From: z', '']
207 >>> insertplainheader(['', 'Date: y'], 'Date', 'z')
207 >>> insertplainheader(['', 'Date: y'], 'Date', 'z')
208 ['Date: z', '', 'Date: y']
208 ['Date: z', '', 'Date: y']
209 >>> insertplainheader(['foo: bar', 'DATE: z', 'x'], 'From', 'y')
209 >>> insertplainheader(['foo: bar', 'DATE: z', 'x'], 'From', 'y')
210 ['From: y', 'foo: bar', 'DATE: z', '', 'x']
210 ['From: y', 'foo: bar', 'DATE: z', '', 'x']
211 """
211 """
212 newprio = PLAINHEADERS[header.lower()]
212 newprio = PLAINHEADERS[header.lower()]
213 bestpos = len(lines)
213 bestpos = len(lines)
214 for i, line in enumerate(lines):
214 for i, line in enumerate(lines):
215 if ':' in line:
215 if ':' in line:
216 lheader = line.split(':', 1)[0].strip().lower()
216 lheader = line.split(':', 1)[0].strip().lower()
217 lprio = PLAINHEADERS.get(lheader, newprio + 1)
217 lprio = PLAINHEADERS.get(lheader, newprio + 1)
218 if lprio == newprio:
218 if lprio == newprio:
219 lines[i] = '%s: %s' % (header, value)
219 lines[i] = '%s: %s' % (header, value)
220 return lines
220 return lines
221 if lprio > newprio and i < bestpos:
221 if lprio > newprio and i < bestpos:
222 bestpos = i
222 bestpos = i
223 else:
223 else:
224 if line:
224 if line:
225 lines.insert(i, '')
225 lines.insert(i, '')
226 if i < bestpos:
226 if i < bestpos:
227 bestpos = i
227 bestpos = i
228 break
228 break
229 lines.insert(bestpos, '%s: %s' % (header, value))
229 lines.insert(bestpos, '%s: %s' % (header, value))
230 return lines
230 return lines
231
231
232 class patchheader(object):
232 class patchheader(object):
233 def __init__(self, pf, plainmode=False):
233 def __init__(self, pf, plainmode=False):
234 def eatdiff(lines):
234 def eatdiff(lines):
235 while lines:
235 while lines:
236 l = lines[-1]
236 l = lines[-1]
237 if (l.startswith("diff -") or
237 if (l.startswith("diff -") or
238 l.startswith("Index:") or
238 l.startswith("Index:") or
239 l.startswith("===========")):
239 l.startswith("===========")):
240 del lines[-1]
240 del lines[-1]
241 else:
241 else:
242 break
242 break
243 def eatempty(lines):
243 def eatempty(lines):
244 while lines:
244 while lines:
245 if not lines[-1].strip():
245 if not lines[-1].strip():
246 del lines[-1]
246 del lines[-1]
247 else:
247 else:
248 break
248 break
249
249
250 message = []
250 message = []
251 comments = []
251 comments = []
252 user = None
252 user = None
253 date = None
253 date = None
254 parent = None
254 parent = None
255 format = None
255 format = None
256 subject = None
256 subject = None
257 branch = None
257 branch = None
258 nodeid = None
258 nodeid = None
259 diffstart = 0
259 diffstart = 0
260
260
261 for line in file(pf):
261 for line in file(pf):
262 line = line.rstrip()
262 line = line.rstrip()
263 if (line.startswith('diff --git')
263 if (line.startswith('diff --git')
264 or (diffstart and line.startswith('+++ '))):
264 or (diffstart and line.startswith('+++ '))):
265 diffstart = 2
265 diffstart = 2
266 break
266 break
267 diffstart = 0 # reset
267 diffstart = 0 # reset
268 if line.startswith("--- "):
268 if line.startswith("--- "):
269 diffstart = 1
269 diffstart = 1
270 continue
270 continue
271 elif format == "hgpatch":
271 elif format == "hgpatch":
272 # parse values when importing the result of an hg export
272 # parse values when importing the result of an hg export
273 if line.startswith("# User "):
273 if line.startswith("# User "):
274 user = line[7:]
274 user = line[7:]
275 elif line.startswith("# Date "):
275 elif line.startswith("# Date "):
276 date = line[7:]
276 date = line[7:]
277 elif line.startswith("# Parent "):
277 elif line.startswith("# Parent "):
278 parent = line[9:].lstrip() # handle double trailing space
278 parent = line[9:].lstrip() # handle double trailing space
279 elif line.startswith("# Branch "):
279 elif line.startswith("# Branch "):
280 branch = line[9:]
280 branch = line[9:]
281 elif line.startswith("# Node ID "):
281 elif line.startswith("# Node ID "):
282 nodeid = line[10:]
282 nodeid = line[10:]
283 elif not line.startswith("# ") and line:
283 elif not line.startswith("# ") and line:
284 message.append(line)
284 message.append(line)
285 format = None
285 format = None
286 elif line == '# HG changeset patch':
286 elif line == '# HG changeset patch':
287 message = []
287 message = []
288 format = "hgpatch"
288 format = "hgpatch"
289 elif (format != "tagdone" and (line.startswith("Subject: ") or
289 elif (format != "tagdone" and (line.startswith("Subject: ") or
290 line.startswith("subject: "))):
290 line.startswith("subject: "))):
291 subject = line[9:]
291 subject = line[9:]
292 format = "tag"
292 format = "tag"
293 elif (format != "tagdone" and (line.startswith("From: ") or
293 elif (format != "tagdone" and (line.startswith("From: ") or
294 line.startswith("from: "))):
294 line.startswith("from: "))):
295 user = line[6:]
295 user = line[6:]
296 format = "tag"
296 format = "tag"
297 elif (format != "tagdone" and (line.startswith("Date: ") or
297 elif (format != "tagdone" and (line.startswith("Date: ") or
298 line.startswith("date: "))):
298 line.startswith("date: "))):
299 date = line[6:]
299 date = line[6:]
300 format = "tag"
300 format = "tag"
301 elif format == "tag" and line == "":
301 elif format == "tag" and line == "":
302 # when looking for tags (subject: from: etc) they
302 # when looking for tags (subject: from: etc) they
303 # end once you find a blank line in the source
303 # end once you find a blank line in the source
304 format = "tagdone"
304 format = "tagdone"
305 elif message or line:
305 elif message or line:
306 message.append(line)
306 message.append(line)
307 comments.append(line)
307 comments.append(line)
308
308
309 eatdiff(message)
309 eatdiff(message)
310 eatdiff(comments)
310 eatdiff(comments)
311 # Remember the exact starting line of the patch diffs before consuming
311 # Remember the exact starting line of the patch diffs before consuming
312 # empty lines, for external use by TortoiseHg and others
312 # empty lines, for external use by TortoiseHg and others
313 self.diffstartline = len(comments)
313 self.diffstartline = len(comments)
314 eatempty(message)
314 eatempty(message)
315 eatempty(comments)
315 eatempty(comments)
316
316
317 # make sure message isn't empty
317 # make sure message isn't empty
318 if format and format.startswith("tag") and subject:
318 if format and format.startswith("tag") and subject:
319 message.insert(0, subject)
319 message.insert(0, subject)
320
320
321 self.message = message
321 self.message = message
322 self.comments = comments
322 self.comments = comments
323 self.user = user
323 self.user = user
324 self.date = date
324 self.date = date
325 self.parent = parent
325 self.parent = parent
326 # nodeid and branch are for external use by TortoiseHg and others
326 # nodeid and branch are for external use by TortoiseHg and others
327 self.nodeid = nodeid
327 self.nodeid = nodeid
328 self.branch = branch
328 self.branch = branch
329 self.haspatch = diffstart > 1
329 self.haspatch = diffstart > 1
330 self.plainmode = (plainmode or
330 self.plainmode = (plainmode or
331 '# HG changeset patch' not in self.comments and
331 '# HG changeset patch' not in self.comments and
332 any(c.startswith('Date: ') or
332 any(c.startswith('Date: ') or
333 c.startswith('From: ')
333 c.startswith('From: ')
334 for c in self.comments))
334 for c in self.comments))
335
335
336 def setuser(self, user):
336 def setuser(self, user):
337 try:
337 try:
338 inserthgheader(self.comments, '# User ', user)
338 inserthgheader(self.comments, '# User ', user)
339 except ValueError:
339 except ValueError:
340 if self.plainmode:
340 if self.plainmode:
341 insertplainheader(self.comments, 'From', user)
341 insertplainheader(self.comments, 'From', user)
342 else:
342 else:
343 tmp = ['# HG changeset patch', '# User ' + user]
343 tmp = ['# HG changeset patch', '# User ' + user]
344 self.comments = tmp + self.comments
344 self.comments = tmp + self.comments
345 self.user = user
345 self.user = user
346
346
347 def setdate(self, date):
347 def setdate(self, date):
348 try:
348 try:
349 inserthgheader(self.comments, '# Date ', date)
349 inserthgheader(self.comments, '# Date ', date)
350 except ValueError:
350 except ValueError:
351 if self.plainmode:
351 if self.plainmode:
352 insertplainheader(self.comments, 'Date', date)
352 insertplainheader(self.comments, 'Date', date)
353 else:
353 else:
354 tmp = ['# HG changeset patch', '# Date ' + date]
354 tmp = ['# HG changeset patch', '# Date ' + date]
355 self.comments = tmp + self.comments
355 self.comments = tmp + self.comments
356 self.date = date
356 self.date = date
357
357
358 def setparent(self, parent):
358 def setparent(self, parent):
359 try:
359 try:
360 inserthgheader(self.comments, '# Parent ', parent)
360 inserthgheader(self.comments, '# Parent ', parent)
361 except ValueError:
361 except ValueError:
362 if not self.plainmode:
362 if not self.plainmode:
363 tmp = ['# HG changeset patch', '# Parent ' + parent]
363 tmp = ['# HG changeset patch', '# Parent ' + parent]
364 self.comments = tmp + self.comments
364 self.comments = tmp + self.comments
365 self.parent = parent
365 self.parent = parent
366
366
367 def setmessage(self, message):
367 def setmessage(self, message):
368 if self.comments:
368 if self.comments:
369 self._delmsg()
369 self._delmsg()
370 self.message = [message]
370 self.message = [message]
371 if message:
371 if message:
372 if self.plainmode and self.comments and self.comments[-1]:
372 if self.plainmode and self.comments and self.comments[-1]:
373 self.comments.append('')
373 self.comments.append('')
374 self.comments.append(message)
374 self.comments.append(message)
375
375
376 def __str__(self):
376 def __str__(self):
377 s = '\n'.join(self.comments).rstrip()
377 s = '\n'.join(self.comments).rstrip()
378 if not s:
378 if not s:
379 return ''
379 return ''
380 return s + '\n\n'
380 return s + '\n\n'
381
381
382 def _delmsg(self):
382 def _delmsg(self):
383 '''Remove existing message, keeping the rest of the comments fields.
383 '''Remove existing message, keeping the rest of the comments fields.
384 If comments contains 'subject: ', message will prepend
384 If comments contains 'subject: ', message will prepend
385 the field and a blank line.'''
385 the field and a blank line.'''
386 if self.message:
386 if self.message:
387 subj = 'subject: ' + self.message[0].lower()
387 subj = 'subject: ' + self.message[0].lower()
388 for i in xrange(len(self.comments)):
388 for i in xrange(len(self.comments)):
389 if subj == self.comments[i].lower():
389 if subj == self.comments[i].lower():
390 del self.comments[i]
390 del self.comments[i]
391 self.message = self.message[2:]
391 self.message = self.message[2:]
392 break
392 break
393 ci = 0
393 ci = 0
394 for mi in self.message:
394 for mi in self.message:
395 while mi != self.comments[ci]:
395 while mi != self.comments[ci]:
396 ci += 1
396 ci += 1
397 del self.comments[ci]
397 del self.comments[ci]
398
398
399 def newcommit(repo, phase, *args, **kwargs):
399 def newcommit(repo, phase, *args, **kwargs):
400 """helper dedicated to ensure a commit respect mq.secret setting
400 """helper dedicated to ensure a commit respect mq.secret setting
401
401
402 It should be used instead of repo.commit inside the mq source for operation
402 It should be used instead of repo.commit inside the mq source for operation
403 creating new changeset.
403 creating new changeset.
404 """
404 """
405 repo = repo.unfiltered()
405 repo = repo.unfiltered()
406 if phase is None:
406 if phase is None:
407 if repo.ui.configbool('mq', 'secret', False):
407 if repo.ui.configbool('mq', 'secret', False):
408 phase = phases.secret
408 phase = phases.secret
409 overrides = {('ui', 'allowemptycommit'): True}
409 overrides = {('ui', 'allowemptycommit'): True}
410 if phase is not None:
410 if phase is not None:
411 overrides[('phases', 'new-commit')] = phase
411 overrides[('phases', 'new-commit')] = phase
412 with repo.ui.configoverride(overrides, 'mq'):
412 with repo.ui.configoverride(overrides, 'mq'):
413 repo.ui.setconfig('ui', 'allowemptycommit', True)
413 repo.ui.setconfig('ui', 'allowemptycommit', True)
414 return repo.commit(*args, **kwargs)
414 return repo.commit(*args, **kwargs)
415
415
416 class AbortNoCleanup(error.Abort):
416 class AbortNoCleanup(error.Abort):
417 pass
417 pass
418
418
419 class queue(object):
419 class queue(object):
420 def __init__(self, ui, baseui, path, patchdir=None):
420 def __init__(self, ui, baseui, path, patchdir=None):
421 self.basepath = path
421 self.basepath = path
422 try:
422 try:
423 fh = open(os.path.join(path, 'patches.queue'))
423 fh = open(os.path.join(path, 'patches.queue'))
424 cur = fh.read().rstrip()
424 cur = fh.read().rstrip()
425 fh.close()
425 fh.close()
426 if not cur:
426 if not cur:
427 curpath = os.path.join(path, 'patches')
427 curpath = os.path.join(path, 'patches')
428 else:
428 else:
429 curpath = os.path.join(path, 'patches-' + cur)
429 curpath = os.path.join(path, 'patches-' + cur)
430 except IOError:
430 except IOError:
431 curpath = os.path.join(path, 'patches')
431 curpath = os.path.join(path, 'patches')
432 self.path = patchdir or curpath
432 self.path = patchdir or curpath
433 self.opener = vfsmod.vfs(self.path)
433 self.opener = vfsmod.vfs(self.path)
434 self.ui = ui
434 self.ui = ui
435 self.baseui = baseui
435 self.baseui = baseui
436 self.applieddirty = False
436 self.applieddirty = False
437 self.seriesdirty = False
437 self.seriesdirty = False
438 self.added = []
438 self.added = []
439 self.seriespath = "series"
439 self.seriespath = "series"
440 self.statuspath = "status"
440 self.statuspath = "status"
441 self.guardspath = "guards"
441 self.guardspath = "guards"
442 self.activeguards = None
442 self.activeguards = None
443 self.guardsdirty = False
443 self.guardsdirty = False
444 # Handle mq.git as a bool with extended values
444 # Handle mq.git as a bool with extended values
445 try:
445 try:
446 gitmode = ui.configbool('mq', 'git', None)
446 gitmode = ui.configbool('mq', 'git', None)
447 if gitmode is None:
447 if gitmode is None:
448 raise error.ConfigError
448 raise error.ConfigError
449 if gitmode:
449 if gitmode:
450 self.gitmode = 'yes'
450 self.gitmode = 'yes'
451 else:
451 else:
452 self.gitmode = 'no'
452 self.gitmode = 'no'
453 except error.ConfigError:
453 except error.ConfigError:
454 # let's have check-config ignore the type mismatch
454 # let's have check-config ignore the type mismatch
455 self.gitmode = ui.config(r'mq', 'git', 'auto').lower()
455 self.gitmode = ui.config(r'mq', 'git', 'auto').lower()
456 # deprecated config: mq.plain
456 # deprecated config: mq.plain
457 self.plainmode = ui.configbool('mq', 'plain', False)
457 self.plainmode = ui.configbool('mq', 'plain', False)
458 self.checkapplied = True
458 self.checkapplied = True
459
459
460 @util.propertycache
460 @util.propertycache
461 def applied(self):
461 def applied(self):
462 def parselines(lines):
462 def parselines(lines):
463 for l in lines:
463 for l in lines:
464 entry = l.split(':', 1)
464 entry = l.split(':', 1)
465 if len(entry) > 1:
465 if len(entry) > 1:
466 n, name = entry
466 n, name = entry
467 yield statusentry(bin(n), name)
467 yield statusentry(bin(n), name)
468 elif l.strip():
468 elif l.strip():
469 self.ui.warn(_('malformated mq status line: %s\n') % entry)
469 self.ui.warn(_('malformated mq status line: %s\n') % entry)
470 # else we ignore empty lines
470 # else we ignore empty lines
471 try:
471 try:
472 lines = self.opener.read(self.statuspath).splitlines()
472 lines = self.opener.read(self.statuspath).splitlines()
473 return list(parselines(lines))
473 return list(parselines(lines))
474 except IOError as e:
474 except IOError as e:
475 if e.errno == errno.ENOENT:
475 if e.errno == errno.ENOENT:
476 return []
476 return []
477 raise
477 raise
478
478
479 @util.propertycache
479 @util.propertycache
480 def fullseries(self):
480 def fullseries(self):
481 try:
481 try:
482 return self.opener.read(self.seriespath).splitlines()
482 return self.opener.read(self.seriespath).splitlines()
483 except IOError as e:
483 except IOError as e:
484 if e.errno == errno.ENOENT:
484 if e.errno == errno.ENOENT:
485 return []
485 return []
486 raise
486 raise
487
487
488 @util.propertycache
488 @util.propertycache
489 def series(self):
489 def series(self):
490 self.parseseries()
490 self.parseseries()
491 return self.series
491 return self.series
492
492
493 @util.propertycache
493 @util.propertycache
494 def seriesguards(self):
494 def seriesguards(self):
495 self.parseseries()
495 self.parseseries()
496 return self.seriesguards
496 return self.seriesguards
497
497
498 def invalidate(self):
498 def invalidate(self):
499 for a in 'applied fullseries series seriesguards'.split():
499 for a in 'applied fullseries series seriesguards'.split():
500 if a in self.__dict__:
500 if a in self.__dict__:
501 delattr(self, a)
501 delattr(self, a)
502 self.applieddirty = False
502 self.applieddirty = False
503 self.seriesdirty = False
503 self.seriesdirty = False
504 self.guardsdirty = False
504 self.guardsdirty = False
505 self.activeguards = None
505 self.activeguards = None
506
506
507 def diffopts(self, opts=None, patchfn=None):
507 def diffopts(self, opts=None, patchfn=None):
508 diffopts = patchmod.diffopts(self.ui, opts)
508 diffopts = patchmod.diffopts(self.ui, opts)
509 if self.gitmode == 'auto':
509 if self.gitmode == 'auto':
510 diffopts.upgrade = True
510 diffopts.upgrade = True
511 elif self.gitmode == 'keep':
511 elif self.gitmode == 'keep':
512 pass
512 pass
513 elif self.gitmode in ('yes', 'no'):
513 elif self.gitmode in ('yes', 'no'):
514 diffopts.git = self.gitmode == 'yes'
514 diffopts.git = self.gitmode == 'yes'
515 else:
515 else:
516 raise error.Abort(_('mq.git option can be auto/keep/yes/no'
516 raise error.Abort(_('mq.git option can be auto/keep/yes/no'
517 ' got %s') % self.gitmode)
517 ' got %s') % self.gitmode)
518 if patchfn:
518 if patchfn:
519 diffopts = self.patchopts(diffopts, patchfn)
519 diffopts = self.patchopts(diffopts, patchfn)
520 return diffopts
520 return diffopts
521
521
522 def patchopts(self, diffopts, *patches):
522 def patchopts(self, diffopts, *patches):
523 """Return a copy of input diff options with git set to true if
523 """Return a copy of input diff options with git set to true if
524 referenced patch is a git patch and should be preserved as such.
524 referenced patch is a git patch and should be preserved as such.
525 """
525 """
526 diffopts = diffopts.copy()
526 diffopts = diffopts.copy()
527 if not diffopts.git and self.gitmode == 'keep':
527 if not diffopts.git and self.gitmode == 'keep':
528 for patchfn in patches:
528 for patchfn in patches:
529 patchf = self.opener(patchfn, 'r')
529 patchf = self.opener(patchfn, 'r')
530 # if the patch was a git patch, refresh it as a git patch
530 # if the patch was a git patch, refresh it as a git patch
531 for line in patchf:
531 for line in patchf:
532 if line.startswith('diff --git'):
532 if line.startswith('diff --git'):
533 diffopts.git = True
533 diffopts.git = True
534 break
534 break
535 patchf.close()
535 patchf.close()
536 return diffopts
536 return diffopts
537
537
538 def join(self, *p):
538 def join(self, *p):
539 return os.path.join(self.path, *p)
539 return os.path.join(self.path, *p)
540
540
541 def findseries(self, patch):
541 def findseries(self, patch):
542 def matchpatch(l):
542 def matchpatch(l):
543 l = l.split('#', 1)[0]
543 l = l.split('#', 1)[0]
544 return l.strip() == patch
544 return l.strip() == patch
545 for index, l in enumerate(self.fullseries):
545 for index, l in enumerate(self.fullseries):
546 if matchpatch(l):
546 if matchpatch(l):
547 return index
547 return index
548 return None
548 return None
549
549
550 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
550 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
551
551
552 def parseseries(self):
552 def parseseries(self):
553 self.series = []
553 self.series = []
554 self.seriesguards = []
554 self.seriesguards = []
555 for l in self.fullseries:
555 for l in self.fullseries:
556 h = l.find('#')
556 h = l.find('#')
557 if h == -1:
557 if h == -1:
558 patch = l
558 patch = l
559 comment = ''
559 comment = ''
560 elif h == 0:
560 elif h == 0:
561 continue
561 continue
562 else:
562 else:
563 patch = l[:h]
563 patch = l[:h]
564 comment = l[h:]
564 comment = l[h:]
565 patch = patch.strip()
565 patch = patch.strip()
566 if patch:
566 if patch:
567 if patch in self.series:
567 if patch in self.series:
568 raise error.Abort(_('%s appears more than once in %s') %
568 raise error.Abort(_('%s appears more than once in %s') %
569 (patch, self.join(self.seriespath)))
569 (patch, self.join(self.seriespath)))
570 self.series.append(patch)
570 self.series.append(patch)
571 self.seriesguards.append(self.guard_re.findall(comment))
571 self.seriesguards.append(self.guard_re.findall(comment))
572
572
573 def checkguard(self, guard):
573 def checkguard(self, guard):
574 if not guard:
574 if not guard:
575 return _('guard cannot be an empty string')
575 return _('guard cannot be an empty string')
576 bad_chars = '# \t\r\n\f'
576 bad_chars = '# \t\r\n\f'
577 first = guard[0]
577 first = guard[0]
578 if first in '-+':
578 if first in '-+':
579 return (_('guard %r starts with invalid character: %r') %
579 return (_('guard %r starts with invalid character: %r') %
580 (guard, first))
580 (guard, first))
581 for c in bad_chars:
581 for c in bad_chars:
582 if c in guard:
582 if c in guard:
583 return _('invalid character in guard %r: %r') % (guard, c)
583 return _('invalid character in guard %r: %r') % (guard, c)
584
584
585 def setactive(self, guards):
585 def setactive(self, guards):
586 for guard in guards:
586 for guard in guards:
587 bad = self.checkguard(guard)
587 bad = self.checkguard(guard)
588 if bad:
588 if bad:
589 raise error.Abort(bad)
589 raise error.Abort(bad)
590 guards = sorted(set(guards))
590 guards = sorted(set(guards))
591 self.ui.debug('active guards: %s\n' % ' '.join(guards))
591 self.ui.debug('active guards: %s\n' % ' '.join(guards))
592 self.activeguards = guards
592 self.activeguards = guards
593 self.guardsdirty = True
593 self.guardsdirty = True
594
594
595 def active(self):
595 def active(self):
596 if self.activeguards is None:
596 if self.activeguards is None:
597 self.activeguards = []
597 self.activeguards = []
598 try:
598 try:
599 guards = self.opener.read(self.guardspath).split()
599 guards = self.opener.read(self.guardspath).split()
600 except IOError as err:
600 except IOError as err:
601 if err.errno != errno.ENOENT:
601 if err.errno != errno.ENOENT:
602 raise
602 raise
603 guards = []
603 guards = []
604 for i, guard in enumerate(guards):
604 for i, guard in enumerate(guards):
605 bad = self.checkguard(guard)
605 bad = self.checkguard(guard)
606 if bad:
606 if bad:
607 self.ui.warn('%s:%d: %s\n' %
607 self.ui.warn('%s:%d: %s\n' %
608 (self.join(self.guardspath), i + 1, bad))
608 (self.join(self.guardspath), i + 1, bad))
609 else:
609 else:
610 self.activeguards.append(guard)
610 self.activeguards.append(guard)
611 return self.activeguards
611 return self.activeguards
612
612
613 def setguards(self, idx, guards):
613 def setguards(self, idx, guards):
614 for g in guards:
614 for g in guards:
615 if len(g) < 2:
615 if len(g) < 2:
616 raise error.Abort(_('guard %r too short') % g)
616 raise error.Abort(_('guard %r too short') % g)
617 if g[0] not in '-+':
617 if g[0] not in '-+':
618 raise error.Abort(_('guard %r starts with invalid char') % g)
618 raise error.Abort(_('guard %r starts with invalid char') % g)
619 bad = self.checkguard(g[1:])
619 bad = self.checkguard(g[1:])
620 if bad:
620 if bad:
621 raise error.Abort(bad)
621 raise error.Abort(bad)
622 drop = self.guard_re.sub('', self.fullseries[idx])
622 drop = self.guard_re.sub('', self.fullseries[idx])
623 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
623 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
624 self.parseseries()
624 self.parseseries()
625 self.seriesdirty = True
625 self.seriesdirty = True
626
626
627 def pushable(self, idx):
627 def pushable(self, idx):
628 if isinstance(idx, str):
628 if isinstance(idx, str):
629 idx = self.series.index(idx)
629 idx = self.series.index(idx)
630 patchguards = self.seriesguards[idx]
630 patchguards = self.seriesguards[idx]
631 if not patchguards:
631 if not patchguards:
632 return True, None
632 return True, None
633 guards = self.active()
633 guards = self.active()
634 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
634 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
635 if exactneg:
635 if exactneg:
636 return False, repr(exactneg[0])
636 return False, repr(exactneg[0])
637 pos = [g for g in patchguards if g[0] == '+']
637 pos = [g for g in patchguards if g[0] == '+']
638 exactpos = [g for g in pos if g[1:] in guards]
638 exactpos = [g for g in pos if g[1:] in guards]
639 if pos:
639 if pos:
640 if exactpos:
640 if exactpos:
641 return True, repr(exactpos[0])
641 return True, repr(exactpos[0])
642 return False, ' '.join(map(repr, pos))
642 return False, ' '.join(map(repr, pos))
643 return True, ''
643 return True, ''
644
644
645 def explainpushable(self, idx, all_patches=False):
645 def explainpushable(self, idx, all_patches=False):
646 if all_patches:
646 if all_patches:
647 write = self.ui.write
647 write = self.ui.write
648 else:
648 else:
649 write = self.ui.warn
649 write = self.ui.warn
650
650
651 if all_patches or self.ui.verbose:
651 if all_patches or self.ui.verbose:
652 if isinstance(idx, str):
652 if isinstance(idx, str):
653 idx = self.series.index(idx)
653 idx = self.series.index(idx)
654 pushable, why = self.pushable(idx)
654 pushable, why = self.pushable(idx)
655 if all_patches and pushable:
655 if all_patches and pushable:
656 if why is None:
656 if why is None:
657 write(_('allowing %s - no guards in effect\n') %
657 write(_('allowing %s - no guards in effect\n') %
658 self.series[idx])
658 self.series[idx])
659 else:
659 else:
660 if not why:
660 if not why:
661 write(_('allowing %s - no matching negative guards\n') %
661 write(_('allowing %s - no matching negative guards\n') %
662 self.series[idx])
662 self.series[idx])
663 else:
663 else:
664 write(_('allowing %s - guarded by %s\n') %
664 write(_('allowing %s - guarded by %s\n') %
665 (self.series[idx], why))
665 (self.series[idx], why))
666 if not pushable:
666 if not pushable:
667 if why:
667 if why:
668 write(_('skipping %s - guarded by %s\n') %
668 write(_('skipping %s - guarded by %s\n') %
669 (self.series[idx], why))
669 (self.series[idx], why))
670 else:
670 else:
671 write(_('skipping %s - no matching guards\n') %
671 write(_('skipping %s - no matching guards\n') %
672 self.series[idx])
672 self.series[idx])
673
673
674 def savedirty(self):
674 def savedirty(self):
675 def writelist(items, path):
675 def writelist(items, path):
676 fp = self.opener(path, 'w')
676 fp = self.opener(path, 'w')
677 for i in items:
677 for i in items:
678 fp.write("%s\n" % i)
678 fp.write("%s\n" % i)
679 fp.close()
679 fp.close()
680 if self.applieddirty:
680 if self.applieddirty:
681 writelist(map(str, self.applied), self.statuspath)
681 writelist(map(str, self.applied), self.statuspath)
682 self.applieddirty = False
682 self.applieddirty = False
683 if self.seriesdirty:
683 if self.seriesdirty:
684 writelist(self.fullseries, self.seriespath)
684 writelist(self.fullseries, self.seriespath)
685 self.seriesdirty = False
685 self.seriesdirty = False
686 if self.guardsdirty:
686 if self.guardsdirty:
687 writelist(self.activeguards, self.guardspath)
687 writelist(self.activeguards, self.guardspath)
688 self.guardsdirty = False
688 self.guardsdirty = False
689 if self.added:
689 if self.added:
690 qrepo = self.qrepo()
690 qrepo = self.qrepo()
691 if qrepo:
691 if qrepo:
692 qrepo[None].add(f for f in self.added if f not in qrepo[None])
692 qrepo[None].add(f for f in self.added if f not in qrepo[None])
693 self.added = []
693 self.added = []
694
694
695 def removeundo(self, repo):
695 def removeundo(self, repo):
696 undo = repo.sjoin('undo')
696 undo = repo.sjoin('undo')
697 if not os.path.exists(undo):
697 if not os.path.exists(undo):
698 return
698 return
699 try:
699 try:
700 os.unlink(undo)
700 os.unlink(undo)
701 except OSError as inst:
701 except OSError as inst:
702 self.ui.warn(_('error removing undo: %s\n') % str(inst))
702 self.ui.warn(_('error removing undo: %s\n') % str(inst))
703
703
704 def backup(self, repo, files, copy=False):
704 def backup(self, repo, files, copy=False):
705 # backup local changes in --force case
705 # backup local changes in --force case
706 for f in sorted(files):
706 for f in sorted(files):
707 absf = repo.wjoin(f)
707 absf = repo.wjoin(f)
708 if os.path.lexists(absf):
708 if os.path.lexists(absf):
709 self.ui.note(_('saving current version of %s as %s\n') %
709 self.ui.note(_('saving current version of %s as %s\n') %
710 (f, scmutil.origpath(self.ui, repo, f)))
710 (f, scmutil.origpath(self.ui, repo, f)))
711
711
712 absorig = scmutil.origpath(self.ui, repo, absf)
712 absorig = scmutil.origpath(self.ui, repo, absf)
713 if copy:
713 if copy:
714 util.copyfile(absf, absorig)
714 util.copyfile(absf, absorig)
715 else:
715 else:
716 util.rename(absf, absorig)
716 util.rename(absf, absorig)
717
717
718 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
718 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
719 fp=None, changes=None, opts=None):
719 fp=None, changes=None, opts=None):
720 if opts is None:
720 if opts is None:
721 opts = {}
721 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 with repo.dirstate.parentchange():
936 with repo.dirstate.parentchange():
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
943
944 if all_files and '.hgsubstate' in all_files:
944 if all_files and '.hgsubstate' in all_files:
945 wctx = repo[None]
945 wctx = repo[None]
946 pctx = repo['.']
946 pctx = repo['.']
947 overwrite = False
947 overwrite = False
948 mergedsubstate = subrepo.submerge(repo, pctx, wctx, wctx,
948 mergedsubstate = subrepo.submerge(repo, pctx, wctx, wctx,
949 overwrite)
949 overwrite)
950 files += mergedsubstate.keys()
950 files += mergedsubstate.keys()
951
951
952 match = scmutil.matchfiles(repo, files or [])
952 match = scmutil.matchfiles(repo, files or [])
953 oldtip = repo['tip']
953 oldtip = repo['tip']
954 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
954 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
955 force=True)
955 force=True)
956 if repo['tip'] == oldtip:
956 if repo['tip'] == oldtip:
957 raise error.Abort(_("qpush exactly duplicates child changeset"))
957 raise error.Abort(_("qpush exactly duplicates child changeset"))
958 if n is None:
958 if n is None:
959 raise error.Abort(_("repository commit failed"))
959 raise error.Abort(_("repository commit failed"))
960
960
961 if update_status:
961 if update_status:
962 self.applied.append(statusentry(n, patchname))
962 self.applied.append(statusentry(n, patchname))
963
963
964 if patcherr:
964 if patcherr:
965 self.ui.warn(_("patch failed, rejects left in working "
965 self.ui.warn(_("patch failed, rejects left in working "
966 "directory\n"))
966 "directory\n"))
967 err = 2
967 err = 2
968 break
968 break
969
969
970 if fuzz and strict:
970 if fuzz and strict:
971 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
971 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
972 err = 3
972 err = 3
973 break
973 break
974 return (err, n)
974 return (err, n)
975
975
976 def _cleanup(self, patches, numrevs, keep=False):
976 def _cleanup(self, patches, numrevs, keep=False):
977 if not keep:
977 if not keep:
978 r = self.qrepo()
978 r = self.qrepo()
979 if r:
979 if r:
980 r[None].forget(patches)
980 r[None].forget(patches)
981 for p in patches:
981 for p in patches:
982 try:
982 try:
983 os.unlink(self.join(p))
983 os.unlink(self.join(p))
984 except OSError as inst:
984 except OSError as inst:
985 if inst.errno != errno.ENOENT:
985 if inst.errno != errno.ENOENT:
986 raise
986 raise
987
987
988 qfinished = []
988 qfinished = []
989 if numrevs:
989 if numrevs:
990 qfinished = self.applied[:numrevs]
990 qfinished = self.applied[:numrevs]
991 del self.applied[:numrevs]
991 del self.applied[:numrevs]
992 self.applieddirty = True
992 self.applieddirty = True
993
993
994 unknown = []
994 unknown = []
995
995
996 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
996 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
997 reverse=True):
997 reverse=True):
998 if i is not None:
998 if i is not None:
999 del self.fullseries[i]
999 del self.fullseries[i]
1000 else:
1000 else:
1001 unknown.append(p)
1001 unknown.append(p)
1002
1002
1003 if unknown:
1003 if unknown:
1004 if numrevs:
1004 if numrevs:
1005 rev = dict((entry.name, entry.node) for entry in qfinished)
1005 rev = dict((entry.name, entry.node) for entry in qfinished)
1006 for p in unknown:
1006 for p in unknown:
1007 msg = _('revision %s refers to unknown patches: %s\n')
1007 msg = _('revision %s refers to unknown patches: %s\n')
1008 self.ui.warn(msg % (short(rev[p]), p))
1008 self.ui.warn(msg % (short(rev[p]), p))
1009 else:
1009 else:
1010 msg = _('unknown patches: %s\n')
1010 msg = _('unknown patches: %s\n')
1011 raise error.Abort(''.join(msg % p for p in unknown))
1011 raise error.Abort(''.join(msg % p for p in unknown))
1012
1012
1013 self.parseseries()
1013 self.parseseries()
1014 self.seriesdirty = True
1014 self.seriesdirty = True
1015 return [entry.node for entry in qfinished]
1015 return [entry.node for entry in qfinished]
1016
1016
1017 def _revpatches(self, repo, revs):
1017 def _revpatches(self, repo, revs):
1018 firstrev = repo[self.applied[0].node].rev()
1018 firstrev = repo[self.applied[0].node].rev()
1019 patches = []
1019 patches = []
1020 for i, rev in enumerate(revs):
1020 for i, rev in enumerate(revs):
1021
1021
1022 if rev < firstrev:
1022 if rev < firstrev:
1023 raise error.Abort(_('revision %d is not managed') % rev)
1023 raise error.Abort(_('revision %d is not managed') % rev)
1024
1024
1025 ctx = repo[rev]
1025 ctx = repo[rev]
1026 base = self.applied[i].node
1026 base = self.applied[i].node
1027 if ctx.node() != base:
1027 if ctx.node() != base:
1028 msg = _('cannot delete revision %d above applied patches')
1028 msg = _('cannot delete revision %d above applied patches')
1029 raise error.Abort(msg % rev)
1029 raise error.Abort(msg % rev)
1030
1030
1031 patch = self.applied[i].name
1031 patch = self.applied[i].name
1032 for fmt in ('[mq]: %s', 'imported patch %s'):
1032 for fmt in ('[mq]: %s', 'imported patch %s'):
1033 if ctx.description() == fmt % patch:
1033 if ctx.description() == fmt % patch:
1034 msg = _('patch %s finalized without changeset message\n')
1034 msg = _('patch %s finalized without changeset message\n')
1035 repo.ui.status(msg % patch)
1035 repo.ui.status(msg % patch)
1036 break
1036 break
1037
1037
1038 patches.append(patch)
1038 patches.append(patch)
1039 return patches
1039 return patches
1040
1040
1041 def finish(self, repo, revs):
1041 def finish(self, repo, revs):
1042 # Manually trigger phase computation to ensure phasedefaults is
1042 # Manually trigger phase computation to ensure phasedefaults is
1043 # executed before we remove the patches.
1043 # executed before we remove the patches.
1044 repo._phasecache
1044 repo._phasecache
1045 patches = self._revpatches(repo, sorted(revs))
1045 patches = self._revpatches(repo, sorted(revs))
1046 qfinished = self._cleanup(patches, len(patches))
1046 qfinished = self._cleanup(patches, len(patches))
1047 if qfinished and repo.ui.configbool('mq', 'secret', False):
1047 if qfinished and repo.ui.configbool('mq', 'secret', False):
1048 # only use this logic when the secret option is added
1048 # only use this logic when the secret option is added
1049 oldqbase = repo[qfinished[0]]
1049 oldqbase = repo[qfinished[0]]
1050 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
1050 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
1051 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
1051 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
1052 with repo.transaction('qfinish') as tr:
1052 with repo.transaction('qfinish') as tr:
1053 phases.advanceboundary(repo, tr, tphase, qfinished)
1053 phases.advanceboundary(repo, tr, tphase, qfinished)
1054
1054
1055 def delete(self, repo, patches, opts):
1055 def delete(self, repo, patches, opts):
1056 if not patches and not opts.get('rev'):
1056 if not patches and not opts.get('rev'):
1057 raise error.Abort(_('qdelete requires at least one revision or '
1057 raise error.Abort(_('qdelete requires at least one revision or '
1058 'patch name'))
1058 'patch name'))
1059
1059
1060 realpatches = []
1060 realpatches = []
1061 for patch in patches:
1061 for patch in patches:
1062 patch = self.lookup(patch, strict=True)
1062 patch = self.lookup(patch, strict=True)
1063 info = self.isapplied(patch)
1063 info = self.isapplied(patch)
1064 if info:
1064 if info:
1065 raise error.Abort(_("cannot delete applied patch %s") % patch)
1065 raise error.Abort(_("cannot delete applied patch %s") % patch)
1066 if patch not in self.series:
1066 if patch not in self.series:
1067 raise error.Abort(_("patch %s not in series file") % patch)
1067 raise error.Abort(_("patch %s not in series file") % patch)
1068 if patch not in realpatches:
1068 if patch not in realpatches:
1069 realpatches.append(patch)
1069 realpatches.append(patch)
1070
1070
1071 numrevs = 0
1071 numrevs = 0
1072 if opts.get('rev'):
1072 if opts.get('rev'):
1073 if not self.applied:
1073 if not self.applied:
1074 raise error.Abort(_('no patches applied'))
1074 raise error.Abort(_('no patches applied'))
1075 revs = scmutil.revrange(repo, opts.get('rev'))
1075 revs = scmutil.revrange(repo, opts.get('rev'))
1076 revs.sort()
1076 revs.sort()
1077 revpatches = self._revpatches(repo, revs)
1077 revpatches = self._revpatches(repo, revs)
1078 realpatches += revpatches
1078 realpatches += revpatches
1079 numrevs = len(revpatches)
1079 numrevs = len(revpatches)
1080
1080
1081 self._cleanup(realpatches, numrevs, opts.get('keep'))
1081 self._cleanup(realpatches, numrevs, opts.get('keep'))
1082
1082
1083 def checktoppatch(self, repo):
1083 def checktoppatch(self, repo):
1084 '''check that working directory is at qtip'''
1084 '''check that working directory is at qtip'''
1085 if self.applied:
1085 if self.applied:
1086 top = self.applied[-1].node
1086 top = self.applied[-1].node
1087 patch = self.applied[-1].name
1087 patch = self.applied[-1].name
1088 if repo.dirstate.p1() != top:
1088 if repo.dirstate.p1() != top:
1089 raise error.Abort(_("working directory revision is not qtip"))
1089 raise error.Abort(_("working directory revision is not qtip"))
1090 return top, patch
1090 return top, patch
1091 return None, None
1091 return None, None
1092
1092
1093 def putsubstate2changes(self, substatestate, changes):
1093 def putsubstate2changes(self, substatestate, changes):
1094 for files in changes[:3]:
1094 for files in changes[:3]:
1095 if '.hgsubstate' in files:
1095 if '.hgsubstate' in files:
1096 return # already listed up
1096 return # already listed up
1097 # not yet listed up
1097 # not yet listed up
1098 if substatestate in 'a?':
1098 if substatestate in 'a?':
1099 changes[1].append('.hgsubstate')
1099 changes[1].append('.hgsubstate')
1100 elif substatestate in 'r':
1100 elif substatestate in 'r':
1101 changes[2].append('.hgsubstate')
1101 changes[2].append('.hgsubstate')
1102 else: # modified
1102 else: # modified
1103 changes[0].append('.hgsubstate')
1103 changes[0].append('.hgsubstate')
1104
1104
1105 def checklocalchanges(self, repo, force=False, refresh=True):
1105 def checklocalchanges(self, repo, force=False, refresh=True):
1106 excsuffix = ''
1106 excsuffix = ''
1107 if refresh:
1107 if refresh:
1108 excsuffix = ', qrefresh first'
1108 excsuffix = ', qrefresh first'
1109 # plain versions for i18n tool to detect them
1109 # plain versions for i18n tool to detect them
1110 _("local changes found, qrefresh first")
1110 _("local changes found, qrefresh first")
1111 _("local changed subrepos found, qrefresh first")
1111 _("local changed subrepos found, qrefresh first")
1112 return checklocalchanges(repo, force, excsuffix)
1112 return checklocalchanges(repo, force, excsuffix)
1113
1113
1114 _reserved = ('series', 'status', 'guards', '.', '..')
1114 _reserved = ('series', 'status', 'guards', '.', '..')
1115 def checkreservedname(self, name):
1115 def checkreservedname(self, name):
1116 if name in self._reserved:
1116 if name in self._reserved:
1117 raise error.Abort(_('"%s" cannot be used as the name of a patch')
1117 raise error.Abort(_('"%s" cannot be used as the name of a patch')
1118 % name)
1118 % name)
1119 if name != name.strip():
1119 if name != name.strip():
1120 # whitespace is stripped by parseseries()
1120 # whitespace is stripped by parseseries()
1121 raise error.Abort(_('patch name cannot begin or end with '
1121 raise error.Abort(_('patch name cannot begin or end with '
1122 'whitespace'))
1122 'whitespace'))
1123 for prefix in ('.hg', '.mq'):
1123 for prefix in ('.hg', '.mq'):
1124 if name.startswith(prefix):
1124 if name.startswith(prefix):
1125 raise error.Abort(_('patch name cannot begin with "%s"')
1125 raise error.Abort(_('patch name cannot begin with "%s"')
1126 % prefix)
1126 % prefix)
1127 for c in ('#', ':', '\r', '\n'):
1127 for c in ('#', ':', '\r', '\n'):
1128 if c in name:
1128 if c in name:
1129 raise error.Abort(_('%r cannot be used in the name of a patch')
1129 raise error.Abort(_('%r cannot be used in the name of a patch')
1130 % c)
1130 % c)
1131
1131
1132 def checkpatchname(self, name, force=False):
1132 def checkpatchname(self, name, force=False):
1133 self.checkreservedname(name)
1133 self.checkreservedname(name)
1134 if not force and os.path.exists(self.join(name)):
1134 if not force and os.path.exists(self.join(name)):
1135 if os.path.isdir(self.join(name)):
1135 if os.path.isdir(self.join(name)):
1136 raise error.Abort(_('"%s" already exists as a directory')
1136 raise error.Abort(_('"%s" already exists as a directory')
1137 % name)
1137 % name)
1138 else:
1138 else:
1139 raise error.Abort(_('patch "%s" already exists') % name)
1139 raise error.Abort(_('patch "%s" already exists') % name)
1140
1140
1141 def makepatchname(self, title, fallbackname):
1141 def makepatchname(self, title, fallbackname):
1142 """Return a suitable filename for title, adding a suffix to make
1142 """Return a suitable filename for title, adding a suffix to make
1143 it unique in the existing list"""
1143 it unique in the existing list"""
1144 namebase = re.sub('[\s\W_]+', '_', title.lower()).strip('_')
1144 namebase = re.sub('[\s\W_]+', '_', title.lower()).strip('_')
1145 namebase = namebase[:75] # avoid too long name (issue5117)
1145 namebase = namebase[:75] # avoid too long name (issue5117)
1146 if namebase:
1146 if namebase:
1147 try:
1147 try:
1148 self.checkreservedname(namebase)
1148 self.checkreservedname(namebase)
1149 except error.Abort:
1149 except error.Abort:
1150 namebase = fallbackname
1150 namebase = fallbackname
1151 else:
1151 else:
1152 namebase = fallbackname
1152 namebase = fallbackname
1153 name = namebase
1153 name = namebase
1154 i = 0
1154 i = 0
1155 while True:
1155 while True:
1156 if name not in self.fullseries:
1156 if name not in self.fullseries:
1157 try:
1157 try:
1158 self.checkpatchname(name)
1158 self.checkpatchname(name)
1159 break
1159 break
1160 except error.Abort:
1160 except error.Abort:
1161 pass
1161 pass
1162 i += 1
1162 i += 1
1163 name = '%s__%s' % (namebase, i)
1163 name = '%s__%s' % (namebase, i)
1164 return name
1164 return name
1165
1165
1166 def checkkeepchanges(self, keepchanges, force):
1166 def checkkeepchanges(self, keepchanges, force):
1167 if force and keepchanges:
1167 if force and keepchanges:
1168 raise error.Abort(_('cannot use both --force and --keep-changes'))
1168 raise error.Abort(_('cannot use both --force and --keep-changes'))
1169
1169
1170 def new(self, repo, patchfn, *pats, **opts):
1170 def new(self, repo, patchfn, *pats, **opts):
1171 """options:
1171 """options:
1172 msg: a string or a no-argument function returning a string
1172 msg: a string or a no-argument function returning a string
1173 """
1173 """
1174 msg = opts.get('msg')
1174 msg = opts.get('msg')
1175 edit = opts.get('edit')
1175 edit = opts.get('edit')
1176 editform = opts.get('editform', 'mq.qnew')
1176 editform = opts.get('editform', 'mq.qnew')
1177 user = opts.get('user')
1177 user = opts.get('user')
1178 date = opts.get('date')
1178 date = opts.get('date')
1179 if date:
1179 if date:
1180 date = util.parsedate(date)
1180 date = util.parsedate(date)
1181 diffopts = self.diffopts({'git': opts.get('git')})
1181 diffopts = self.diffopts({'git': opts.get('git')})
1182 if opts.get('checkname', True):
1182 if opts.get('checkname', True):
1183 self.checkpatchname(patchfn)
1183 self.checkpatchname(patchfn)
1184 inclsubs = checksubstate(repo)
1184 inclsubs = checksubstate(repo)
1185 if inclsubs:
1185 if inclsubs:
1186 substatestate = repo.dirstate['.hgsubstate']
1186 substatestate = repo.dirstate['.hgsubstate']
1187 if opts.get('include') or opts.get('exclude') or pats:
1187 if opts.get('include') or opts.get('exclude') or pats:
1188 # detect missing files in pats
1188 # detect missing files in pats
1189 def badfn(f, msg):
1189 def badfn(f, msg):
1190 if f != '.hgsubstate': # .hgsubstate is auto-created
1190 if f != '.hgsubstate': # .hgsubstate is auto-created
1191 raise error.Abort('%s: %s' % (f, msg))
1191 raise error.Abort('%s: %s' % (f, msg))
1192 match = scmutil.match(repo[None], pats, opts, badfn=badfn)
1192 match = scmutil.match(repo[None], pats, opts, badfn=badfn)
1193 changes = repo.status(match=match)
1193 changes = repo.status(match=match)
1194 else:
1194 else:
1195 changes = self.checklocalchanges(repo, force=True)
1195 changes = self.checklocalchanges(repo, force=True)
1196 commitfiles = list(inclsubs)
1196 commitfiles = list(inclsubs)
1197 for files in changes[:3]:
1197 for files in changes[:3]:
1198 commitfiles.extend(files)
1198 commitfiles.extend(files)
1199 match = scmutil.matchfiles(repo, commitfiles)
1199 match = scmutil.matchfiles(repo, commitfiles)
1200 if len(repo[None].parents()) > 1:
1200 if len(repo[None].parents()) > 1:
1201 raise error.Abort(_('cannot manage merge changesets'))
1201 raise error.Abort(_('cannot manage merge changesets'))
1202 self.checktoppatch(repo)
1202 self.checktoppatch(repo)
1203 insert = self.fullseriesend()
1203 insert = self.fullseriesend()
1204 with repo.wlock():
1204 with repo.wlock():
1205 try:
1205 try:
1206 # if patch file write fails, abort early
1206 # if patch file write fails, abort early
1207 p = self.opener(patchfn, "w")
1207 p = self.opener(patchfn, "w")
1208 except IOError as e:
1208 except IOError as e:
1209 raise error.Abort(_('cannot write patch "%s": %s')
1209 raise error.Abort(_('cannot write patch "%s": %s')
1210 % (patchfn, encoding.strtolocal(e.strerror)))
1210 % (patchfn, encoding.strtolocal(e.strerror)))
1211 try:
1211 try:
1212 defaultmsg = "[mq]: %s" % patchfn
1212 defaultmsg = "[mq]: %s" % patchfn
1213 editor = cmdutil.getcommiteditor(editform=editform)
1213 editor = cmdutil.getcommiteditor(editform=editform)
1214 if edit:
1214 if edit:
1215 def finishdesc(desc):
1215 def finishdesc(desc):
1216 if desc.rstrip():
1216 if desc.rstrip():
1217 return desc
1217 return desc
1218 else:
1218 else:
1219 return defaultmsg
1219 return defaultmsg
1220 # i18n: this message is shown in editor with "HG: " prefix
1220 # i18n: this message is shown in editor with "HG: " prefix
1221 extramsg = _('Leave message empty to use default message.')
1221 extramsg = _('Leave message empty to use default message.')
1222 editor = cmdutil.getcommiteditor(finishdesc=finishdesc,
1222 editor = cmdutil.getcommiteditor(finishdesc=finishdesc,
1223 extramsg=extramsg,
1223 extramsg=extramsg,
1224 editform=editform)
1224 editform=editform)
1225 commitmsg = msg
1225 commitmsg = msg
1226 else:
1226 else:
1227 commitmsg = msg or defaultmsg
1227 commitmsg = msg or defaultmsg
1228
1228
1229 n = newcommit(repo, None, commitmsg, user, date, match=match,
1229 n = newcommit(repo, None, commitmsg, user, date, match=match,
1230 force=True, editor=editor)
1230 force=True, editor=editor)
1231 if n is None:
1231 if n is None:
1232 raise error.Abort(_("repo commit failed"))
1232 raise error.Abort(_("repo commit failed"))
1233 try:
1233 try:
1234 self.fullseries[insert:insert] = [patchfn]
1234 self.fullseries[insert:insert] = [patchfn]
1235 self.applied.append(statusentry(n, patchfn))
1235 self.applied.append(statusentry(n, patchfn))
1236 self.parseseries()
1236 self.parseseries()
1237 self.seriesdirty = True
1237 self.seriesdirty = True
1238 self.applieddirty = True
1238 self.applieddirty = True
1239 nctx = repo[n]
1239 nctx = repo[n]
1240 ph = patchheader(self.join(patchfn), self.plainmode)
1240 ph = patchheader(self.join(patchfn), self.plainmode)
1241 if user:
1241 if user:
1242 ph.setuser(user)
1242 ph.setuser(user)
1243 if date:
1243 if date:
1244 ph.setdate('%s %s' % date)
1244 ph.setdate('%s %s' % date)
1245 ph.setparent(hex(nctx.p1().node()))
1245 ph.setparent(hex(nctx.p1().node()))
1246 msg = nctx.description().strip()
1246 msg = nctx.description().strip()
1247 if msg == defaultmsg.strip():
1247 if msg == defaultmsg.strip():
1248 msg = ''
1248 msg = ''
1249 ph.setmessage(msg)
1249 ph.setmessage(msg)
1250 p.write(str(ph))
1250 p.write(str(ph))
1251 if commitfiles:
1251 if commitfiles:
1252 parent = self.qparents(repo, n)
1252 parent = self.qparents(repo, n)
1253 if inclsubs:
1253 if inclsubs:
1254 self.putsubstate2changes(substatestate, changes)
1254 self.putsubstate2changes(substatestate, changes)
1255 chunks = patchmod.diff(repo, node1=parent, node2=n,
1255 chunks = patchmod.diff(repo, node1=parent, node2=n,
1256 changes=changes, opts=diffopts)
1256 changes=changes, opts=diffopts)
1257 for chunk in chunks:
1257 for chunk in chunks:
1258 p.write(chunk)
1258 p.write(chunk)
1259 p.close()
1259 p.close()
1260 r = self.qrepo()
1260 r = self.qrepo()
1261 if r:
1261 if r:
1262 r[None].add([patchfn])
1262 r[None].add([patchfn])
1263 except: # re-raises
1263 except: # re-raises
1264 repo.rollback()
1264 repo.rollback()
1265 raise
1265 raise
1266 except Exception:
1266 except Exception:
1267 patchpath = self.join(patchfn)
1267 patchpath = self.join(patchfn)
1268 try:
1268 try:
1269 os.unlink(patchpath)
1269 os.unlink(patchpath)
1270 except OSError:
1270 except OSError:
1271 self.ui.warn(_('error unlinking %s\n') % patchpath)
1271 self.ui.warn(_('error unlinking %s\n') % patchpath)
1272 raise
1272 raise
1273 self.removeundo(repo)
1273 self.removeundo(repo)
1274
1274
1275 def isapplied(self, patch):
1275 def isapplied(self, patch):
1276 """returns (index, rev, patch)"""
1276 """returns (index, rev, patch)"""
1277 for i, a in enumerate(self.applied):
1277 for i, a in enumerate(self.applied):
1278 if a.name == patch:
1278 if a.name == patch:
1279 return (i, a.node, a.name)
1279 return (i, a.node, a.name)
1280 return None
1280 return None
1281
1281
1282 # if the exact patch name does not exist, we try a few
1282 # if the exact patch name does not exist, we try a few
1283 # variations. If strict is passed, we try only #1
1283 # variations. If strict is passed, we try only #1
1284 #
1284 #
1285 # 1) a number (as string) to indicate an offset in the series file
1285 # 1) a number (as string) to indicate an offset in the series file
1286 # 2) a unique substring of the patch name was given
1286 # 2) a unique substring of the patch name was given
1287 # 3) patchname[-+]num to indicate an offset in the series file
1287 # 3) patchname[-+]num to indicate an offset in the series file
1288 def lookup(self, patch, strict=False):
1288 def lookup(self, patch, strict=False):
1289 def partialname(s):
1289 def partialname(s):
1290 if s in self.series:
1290 if s in self.series:
1291 return s
1291 return s
1292 matches = [x for x in self.series if s in x]
1292 matches = [x for x in self.series if s in x]
1293 if len(matches) > 1:
1293 if len(matches) > 1:
1294 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1294 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1295 for m in matches:
1295 for m in matches:
1296 self.ui.warn(' %s\n' % m)
1296 self.ui.warn(' %s\n' % m)
1297 return None
1297 return None
1298 if matches:
1298 if matches:
1299 return matches[0]
1299 return matches[0]
1300 if self.series and self.applied:
1300 if self.series and self.applied:
1301 if s == 'qtip':
1301 if s == 'qtip':
1302 return self.series[self.seriesend(True) - 1]
1302 return self.series[self.seriesend(True) - 1]
1303 if s == 'qbase':
1303 if s == 'qbase':
1304 return self.series[0]
1304 return self.series[0]
1305 return None
1305 return None
1306
1306
1307 if patch in self.series:
1307 if patch in self.series:
1308 return patch
1308 return patch
1309
1309
1310 if not os.path.isfile(self.join(patch)):
1310 if not os.path.isfile(self.join(patch)):
1311 try:
1311 try:
1312 sno = int(patch)
1312 sno = int(patch)
1313 except (ValueError, OverflowError):
1313 except (ValueError, OverflowError):
1314 pass
1314 pass
1315 else:
1315 else:
1316 if -len(self.series) <= sno < len(self.series):
1316 if -len(self.series) <= sno < len(self.series):
1317 return self.series[sno]
1317 return self.series[sno]
1318
1318
1319 if not strict:
1319 if not strict:
1320 res = partialname(patch)
1320 res = partialname(patch)
1321 if res:
1321 if res:
1322 return res
1322 return res
1323 minus = patch.rfind('-')
1323 minus = patch.rfind('-')
1324 if minus >= 0:
1324 if minus >= 0:
1325 res = partialname(patch[:minus])
1325 res = partialname(patch[:minus])
1326 if res:
1326 if res:
1327 i = self.series.index(res)
1327 i = self.series.index(res)
1328 try:
1328 try:
1329 off = int(patch[minus + 1:] or 1)
1329 off = int(patch[minus + 1:] or 1)
1330 except (ValueError, OverflowError):
1330 except (ValueError, OverflowError):
1331 pass
1331 pass
1332 else:
1332 else:
1333 if i - off >= 0:
1333 if i - off >= 0:
1334 return self.series[i - off]
1334 return self.series[i - off]
1335 plus = patch.rfind('+')
1335 plus = patch.rfind('+')
1336 if plus >= 0:
1336 if plus >= 0:
1337 res = partialname(patch[:plus])
1337 res = partialname(patch[:plus])
1338 if res:
1338 if res:
1339 i = self.series.index(res)
1339 i = self.series.index(res)
1340 try:
1340 try:
1341 off = int(patch[plus + 1:] or 1)
1341 off = int(patch[plus + 1:] or 1)
1342 except (ValueError, OverflowError):
1342 except (ValueError, OverflowError):
1343 pass
1343 pass
1344 else:
1344 else:
1345 if i + off < len(self.series):
1345 if i + off < len(self.series):
1346 return self.series[i + off]
1346 return self.series[i + off]
1347 raise error.Abort(_("patch %s not in series") % patch)
1347 raise error.Abort(_("patch %s not in series") % patch)
1348
1348
1349 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1349 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1350 all=False, move=False, exact=False, nobackup=False,
1350 all=False, move=False, exact=False, nobackup=False,
1351 keepchanges=False):
1351 keepchanges=False):
1352 self.checkkeepchanges(keepchanges, force)
1352 self.checkkeepchanges(keepchanges, force)
1353 diffopts = self.diffopts()
1353 diffopts = self.diffopts()
1354 with repo.wlock():
1354 with repo.wlock():
1355 heads = []
1355 heads = []
1356 for hs in repo.branchmap().itervalues():
1356 for hs in repo.branchmap().itervalues():
1357 heads.extend(hs)
1357 heads.extend(hs)
1358 if not heads:
1358 if not heads:
1359 heads = [nullid]
1359 heads = [nullid]
1360 if repo.dirstate.p1() not in heads and not exact:
1360 if repo.dirstate.p1() not in heads and not exact:
1361 self.ui.status(_("(working directory not at a head)\n"))
1361 self.ui.status(_("(working directory not at a head)\n"))
1362
1362
1363 if not self.series:
1363 if not self.series:
1364 self.ui.warn(_('no patches in series\n'))
1364 self.ui.warn(_('no patches in series\n'))
1365 return 0
1365 return 0
1366
1366
1367 # Suppose our series file is: A B C and the current 'top'
1367 # Suppose our series file is: A B C and the current 'top'
1368 # patch is B. qpush C should be performed (moving forward)
1368 # patch is B. qpush C should be performed (moving forward)
1369 # qpush B is a NOP (no change) qpush A is an error (can't
1369 # qpush B is a NOP (no change) qpush A is an error (can't
1370 # go backwards with qpush)
1370 # go backwards with qpush)
1371 if patch:
1371 if patch:
1372 patch = self.lookup(patch)
1372 patch = self.lookup(patch)
1373 info = self.isapplied(patch)
1373 info = self.isapplied(patch)
1374 if info and info[0] >= len(self.applied) - 1:
1374 if info and info[0] >= len(self.applied) - 1:
1375 self.ui.warn(
1375 self.ui.warn(
1376 _('qpush: %s is already at the top\n') % patch)
1376 _('qpush: %s is already at the top\n') % patch)
1377 return 0
1377 return 0
1378
1378
1379 pushable, reason = self.pushable(patch)
1379 pushable, reason = self.pushable(patch)
1380 if pushable:
1380 if pushable:
1381 if self.series.index(patch) < self.seriesend():
1381 if self.series.index(patch) < self.seriesend():
1382 raise error.Abort(
1382 raise error.Abort(
1383 _("cannot push to a previous patch: %s") % patch)
1383 _("cannot push to a previous patch: %s") % patch)
1384 else:
1384 else:
1385 if reason:
1385 if reason:
1386 reason = _('guarded by %s') % reason
1386 reason = _('guarded by %s') % reason
1387 else:
1387 else:
1388 reason = _('no matching guards')
1388 reason = _('no matching guards')
1389 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1389 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1390 return 1
1390 return 1
1391 elif all:
1391 elif all:
1392 patch = self.series[-1]
1392 patch = self.series[-1]
1393 if self.isapplied(patch):
1393 if self.isapplied(patch):
1394 self.ui.warn(_('all patches are currently applied\n'))
1394 self.ui.warn(_('all patches are currently applied\n'))
1395 return 0
1395 return 0
1396
1396
1397 # Following the above example, starting at 'top' of B:
1397 # Following the above example, starting at 'top' of B:
1398 # qpush should be performed (pushes C), but a subsequent
1398 # qpush should be performed (pushes C), but a subsequent
1399 # qpush without an argument is an error (nothing to
1399 # qpush without an argument is an error (nothing to
1400 # apply). This allows a loop of "...while hg qpush..." to
1400 # apply). This allows a loop of "...while hg qpush..." to
1401 # work as it detects an error when done
1401 # work as it detects an error when done
1402 start = self.seriesend()
1402 start = self.seriesend()
1403 if start == len(self.series):
1403 if start == len(self.series):
1404 self.ui.warn(_('patch series already fully applied\n'))
1404 self.ui.warn(_('patch series already fully applied\n'))
1405 return 1
1405 return 1
1406 if not force and not keepchanges:
1406 if not force and not keepchanges:
1407 self.checklocalchanges(repo, refresh=self.applied)
1407 self.checklocalchanges(repo, refresh=self.applied)
1408
1408
1409 if exact:
1409 if exact:
1410 if keepchanges:
1410 if keepchanges:
1411 raise error.Abort(
1411 raise error.Abort(
1412 _("cannot use --exact and --keep-changes together"))
1412 _("cannot use --exact and --keep-changes together"))
1413 if move:
1413 if move:
1414 raise error.Abort(_('cannot use --exact and --move '
1414 raise error.Abort(_('cannot use --exact and --move '
1415 'together'))
1415 'together'))
1416 if self.applied:
1416 if self.applied:
1417 raise error.Abort(_('cannot push --exact with applied '
1417 raise error.Abort(_('cannot push --exact with applied '
1418 'patches'))
1418 'patches'))
1419 root = self.series[start]
1419 root = self.series[start]
1420 target = patchheader(self.join(root), self.plainmode).parent
1420 target = patchheader(self.join(root), self.plainmode).parent
1421 if not target:
1421 if not target:
1422 raise error.Abort(
1422 raise error.Abort(
1423 _("%s does not have a parent recorded") % root)
1423 _("%s does not have a parent recorded") % root)
1424 if not repo[target] == repo['.']:
1424 if not repo[target] == repo['.']:
1425 hg.update(repo, target)
1425 hg.update(repo, target)
1426
1426
1427 if move:
1427 if move:
1428 if not patch:
1428 if not patch:
1429 raise error.Abort(_("please specify the patch to move"))
1429 raise error.Abort(_("please specify the patch to move"))
1430 for fullstart, rpn in enumerate(self.fullseries):
1430 for fullstart, rpn in enumerate(self.fullseries):
1431 # strip markers for patch guards
1431 # strip markers for patch guards
1432 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1432 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1433 break
1433 break
1434 for i, rpn in enumerate(self.fullseries[fullstart:]):
1434 for i, rpn in enumerate(self.fullseries[fullstart:]):
1435 # strip markers for patch guards
1435 # strip markers for patch guards
1436 if self.guard_re.split(rpn, 1)[0] == patch:
1436 if self.guard_re.split(rpn, 1)[0] == patch:
1437 break
1437 break
1438 index = fullstart + i
1438 index = fullstart + i
1439 assert index < len(self.fullseries)
1439 assert index < len(self.fullseries)
1440 fullpatch = self.fullseries[index]
1440 fullpatch = self.fullseries[index]
1441 del self.fullseries[index]
1441 del self.fullseries[index]
1442 self.fullseries.insert(fullstart, fullpatch)
1442 self.fullseries.insert(fullstart, fullpatch)
1443 self.parseseries()
1443 self.parseseries()
1444 self.seriesdirty = True
1444 self.seriesdirty = True
1445
1445
1446 self.applieddirty = True
1446 self.applieddirty = True
1447 if start > 0:
1447 if start > 0:
1448 self.checktoppatch(repo)
1448 self.checktoppatch(repo)
1449 if not patch:
1449 if not patch:
1450 patch = self.series[start]
1450 patch = self.series[start]
1451 end = start + 1
1451 end = start + 1
1452 else:
1452 else:
1453 end = self.series.index(patch, start) + 1
1453 end = self.series.index(patch, start) + 1
1454
1454
1455 tobackup = set()
1455 tobackup = set()
1456 if (not nobackup and force) or keepchanges:
1456 if (not nobackup and force) or keepchanges:
1457 status = self.checklocalchanges(repo, force=True)
1457 status = self.checklocalchanges(repo, force=True)
1458 if keepchanges:
1458 if keepchanges:
1459 tobackup.update(status.modified + status.added +
1459 tobackup.update(status.modified + status.added +
1460 status.removed + status.deleted)
1460 status.removed + status.deleted)
1461 else:
1461 else:
1462 tobackup.update(status.modified + status.added)
1462 tobackup.update(status.modified + status.added)
1463
1463
1464 s = self.series[start:end]
1464 s = self.series[start:end]
1465 all_files = set()
1465 all_files = set()
1466 try:
1466 try:
1467 if mergeq:
1467 if mergeq:
1468 ret = self.mergepatch(repo, mergeq, s, diffopts)
1468 ret = self.mergepatch(repo, mergeq, s, diffopts)
1469 else:
1469 else:
1470 ret = self.apply(repo, s, list, all_files=all_files,
1470 ret = self.apply(repo, s, list, all_files=all_files,
1471 tobackup=tobackup, keepchanges=keepchanges)
1471 tobackup=tobackup, keepchanges=keepchanges)
1472 except AbortNoCleanup:
1472 except AbortNoCleanup:
1473 raise
1473 raise
1474 except: # re-raises
1474 except: # re-raises
1475 self.ui.warn(_('cleaning up working directory...\n'))
1475 self.ui.warn(_('cleaning up working directory...\n'))
1476 cmdutil.revert(self.ui, repo, repo['.'],
1476 cmdutil.revert(self.ui, repo, repo['.'],
1477 repo.dirstate.parents(), no_backup=True)
1477 repo.dirstate.parents(), no_backup=True)
1478 # only remove unknown files that we know we touched or
1478 # only remove unknown files that we know we touched or
1479 # created while patching
1479 # created while patching
1480 for f in all_files:
1480 for f in all_files:
1481 if f not in repo.dirstate:
1481 if f not in repo.dirstate:
1482 repo.wvfs.unlinkpath(f, ignoremissing=True)
1482 repo.wvfs.unlinkpath(f, ignoremissing=True)
1483 self.ui.warn(_('done\n'))
1483 self.ui.warn(_('done\n'))
1484 raise
1484 raise
1485
1485
1486 if not self.applied:
1486 if not self.applied:
1487 return ret[0]
1487 return ret[0]
1488 top = self.applied[-1].name
1488 top = self.applied[-1].name
1489 if ret[0] and ret[0] > 1:
1489 if ret[0] and ret[0] > 1:
1490 msg = _("errors during apply, please fix and qrefresh %s\n")
1490 msg = _("errors during apply, please fix and qrefresh %s\n")
1491 self.ui.write(msg % top)
1491 self.ui.write(msg % top)
1492 else:
1492 else:
1493 self.ui.write(_("now at: %s\n") % top)
1493 self.ui.write(_("now at: %s\n") % top)
1494 return ret[0]
1494 return ret[0]
1495
1495
1496 def pop(self, repo, patch=None, force=False, update=True, all=False,
1496 def pop(self, repo, patch=None, force=False, update=True, all=False,
1497 nobackup=False, keepchanges=False):
1497 nobackup=False, keepchanges=False):
1498 self.checkkeepchanges(keepchanges, force)
1498 self.checkkeepchanges(keepchanges, force)
1499 with repo.wlock():
1499 with repo.wlock():
1500 if patch:
1500 if patch:
1501 # index, rev, patch
1501 # index, rev, patch
1502 info = self.isapplied(patch)
1502 info = self.isapplied(patch)
1503 if not info:
1503 if not info:
1504 patch = self.lookup(patch)
1504 patch = self.lookup(patch)
1505 info = self.isapplied(patch)
1505 info = self.isapplied(patch)
1506 if not info:
1506 if not info:
1507 raise error.Abort(_("patch %s is not applied") % patch)
1507 raise error.Abort(_("patch %s is not applied") % patch)
1508
1508
1509 if not self.applied:
1509 if not self.applied:
1510 # Allow qpop -a to work repeatedly,
1510 # Allow qpop -a to work repeatedly,
1511 # but not qpop without an argument
1511 # but not qpop without an argument
1512 self.ui.warn(_("no patches applied\n"))
1512 self.ui.warn(_("no patches applied\n"))
1513 return not all
1513 return not all
1514
1514
1515 if all:
1515 if all:
1516 start = 0
1516 start = 0
1517 elif patch:
1517 elif patch:
1518 start = info[0] + 1
1518 start = info[0] + 1
1519 else:
1519 else:
1520 start = len(self.applied) - 1
1520 start = len(self.applied) - 1
1521
1521
1522 if start >= len(self.applied):
1522 if start >= len(self.applied):
1523 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1523 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1524 return
1524 return
1525
1525
1526 if not update:
1526 if not update:
1527 parents = repo.dirstate.parents()
1527 parents = repo.dirstate.parents()
1528 rr = [x.node for x in self.applied]
1528 rr = [x.node for x in self.applied]
1529 for p in parents:
1529 for p in parents:
1530 if p in rr:
1530 if p in rr:
1531 self.ui.warn(_("qpop: forcing dirstate update\n"))
1531 self.ui.warn(_("qpop: forcing dirstate update\n"))
1532 update = True
1532 update = True
1533 else:
1533 else:
1534 parents = [p.node() for p in repo[None].parents()]
1534 parents = [p.node() for p in repo[None].parents()]
1535 needupdate = False
1535 needupdate = False
1536 for entry in self.applied[start:]:
1536 for entry in self.applied[start:]:
1537 if entry.node in parents:
1537 if entry.node in parents:
1538 needupdate = True
1538 needupdate = True
1539 break
1539 break
1540 update = needupdate
1540 update = needupdate
1541
1541
1542 tobackup = set()
1542 tobackup = set()
1543 if update:
1543 if update:
1544 s = self.checklocalchanges(repo, force=force or keepchanges)
1544 s = self.checklocalchanges(repo, force=force or keepchanges)
1545 if force:
1545 if force:
1546 if not nobackup:
1546 if not nobackup:
1547 tobackup.update(s.modified + s.added)
1547 tobackup.update(s.modified + s.added)
1548 elif keepchanges:
1548 elif keepchanges:
1549 tobackup.update(s.modified + s.added +
1549 tobackup.update(s.modified + s.added +
1550 s.removed + s.deleted)
1550 s.removed + s.deleted)
1551
1551
1552 self.applieddirty = True
1552 self.applieddirty = True
1553 end = len(self.applied)
1553 end = len(self.applied)
1554 rev = self.applied[start].node
1554 rev = self.applied[start].node
1555
1555
1556 try:
1556 try:
1557 heads = repo.changelog.heads(rev)
1557 heads = repo.changelog.heads(rev)
1558 except error.LookupError:
1558 except error.LookupError:
1559 node = short(rev)
1559 node = short(rev)
1560 raise error.Abort(_('trying to pop unknown node %s') % node)
1560 raise error.Abort(_('trying to pop unknown node %s') % node)
1561
1561
1562 if heads != [self.applied[-1].node]:
1562 if heads != [self.applied[-1].node]:
1563 raise error.Abort(_("popping would remove a revision not "
1563 raise error.Abort(_("popping would remove a revision not "
1564 "managed by this patch queue"))
1564 "managed by this patch queue"))
1565 if not repo[self.applied[-1].node].mutable():
1565 if not repo[self.applied[-1].node].mutable():
1566 raise error.Abort(
1566 raise error.Abort(
1567 _("popping would remove a public revision"),
1567 _("popping would remove a public revision"),
1568 hint=_("see 'hg help phases' for details"))
1568 hint=_("see 'hg help phases' for details"))
1569
1569
1570 # we know there are no local changes, so we can make a simplified
1570 # we know there are no local changes, so we can make a simplified
1571 # form of hg.update.
1571 # form of hg.update.
1572 if update:
1572 if update:
1573 qp = self.qparents(repo, rev)
1573 qp = self.qparents(repo, rev)
1574 ctx = repo[qp]
1574 ctx = repo[qp]
1575 m, a, r, d = repo.status(qp, '.')[:4]
1575 m, a, r, d = repo.status(qp, '.')[:4]
1576 if d:
1576 if d:
1577 raise error.Abort(_("deletions found between repo revs"))
1577 raise error.Abort(_("deletions found between repo revs"))
1578
1578
1579 tobackup = set(a + m + r) & tobackup
1579 tobackup = set(a + m + r) & tobackup
1580 if keepchanges and tobackup:
1580 if keepchanges and tobackup:
1581 raise error.Abort(_("local changes found, qrefresh first"))
1581 raise error.Abort(_("local changes found, qrefresh first"))
1582 self.backup(repo, tobackup)
1582 self.backup(repo, tobackup)
1583 with repo.dirstate.parentchange():
1583 with repo.dirstate.parentchange():
1584 for f in a:
1584 for f in a:
1585 repo.wvfs.unlinkpath(f, ignoremissing=True)
1585 repo.wvfs.unlinkpath(f, ignoremissing=True)
1586 repo.dirstate.drop(f)
1586 repo.dirstate.drop(f)
1587 for f in m + r:
1587 for f in m + r:
1588 fctx = ctx[f]
1588 fctx = ctx[f]
1589 repo.wwrite(f, fctx.data(), fctx.flags())
1589 repo.wwrite(f, fctx.data(), fctx.flags())
1590 repo.dirstate.normal(f)
1590 repo.dirstate.normal(f)
1591 repo.setparents(qp, nullid)
1591 repo.setparents(qp, nullid)
1592 for patch in reversed(self.applied[start:end]):
1592 for patch in reversed(self.applied[start:end]):
1593 self.ui.status(_("popping %s\n") % patch.name)
1593 self.ui.status(_("popping %s\n") % patch.name)
1594 del self.applied[start:end]
1594 del self.applied[start:end]
1595 strip(self.ui, repo, [rev], update=False, backup=False)
1595 strip(self.ui, repo, [rev], update=False, backup=False)
1596 for s, state in repo['.'].substate.items():
1596 for s, state in repo['.'].substate.items():
1597 repo['.'].sub(s).get(state)
1597 repo['.'].sub(s).get(state)
1598 if self.applied:
1598 if self.applied:
1599 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1599 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1600 else:
1600 else:
1601 self.ui.write(_("patch queue now empty\n"))
1601 self.ui.write(_("patch queue now empty\n"))
1602
1602
1603 def diff(self, repo, pats, opts):
1603 def diff(self, repo, pats, opts):
1604 top, patch = self.checktoppatch(repo)
1604 top, patch = self.checktoppatch(repo)
1605 if not top:
1605 if not top:
1606 self.ui.write(_("no patches applied\n"))
1606 self.ui.write(_("no patches applied\n"))
1607 return
1607 return
1608 qp = self.qparents(repo, top)
1608 qp = self.qparents(repo, top)
1609 if opts.get('reverse'):
1609 if opts.get('reverse'):
1610 node1, node2 = None, qp
1610 node1, node2 = None, qp
1611 else:
1611 else:
1612 node1, node2 = qp, None
1612 node1, node2 = qp, None
1613 diffopts = self.diffopts(opts, patch)
1613 diffopts = self.diffopts(opts, patch)
1614 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1614 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1615
1615
1616 def refresh(self, repo, pats=None, **opts):
1616 def refresh(self, repo, pats=None, **opts):
1617 if not self.applied:
1617 if not self.applied:
1618 self.ui.write(_("no patches applied\n"))
1618 self.ui.write(_("no patches applied\n"))
1619 return 1
1619 return 1
1620 msg = opts.get('msg', '').rstrip()
1620 msg = opts.get('msg', '').rstrip()
1621 edit = opts.get('edit')
1621 edit = opts.get('edit')
1622 editform = opts.get('editform', 'mq.qrefresh')
1622 editform = opts.get('editform', 'mq.qrefresh')
1623 newuser = opts.get('user')
1623 newuser = opts.get('user')
1624 newdate = opts.get('date')
1624 newdate = opts.get('date')
1625 if newdate:
1625 if newdate:
1626 newdate = '%d %d' % util.parsedate(newdate)
1626 newdate = '%d %d' % util.parsedate(newdate)
1627 wlock = repo.wlock()
1627 wlock = repo.wlock()
1628
1628
1629 try:
1629 try:
1630 self.checktoppatch(repo)
1630 self.checktoppatch(repo)
1631 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1631 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1632 if repo.changelog.heads(top) != [top]:
1632 if repo.changelog.heads(top) != [top]:
1633 raise error.Abort(_("cannot qrefresh a revision with children"))
1633 raise error.Abort(_("cannot qrefresh a revision with children"))
1634 if not repo[top].mutable():
1634 if not repo[top].mutable():
1635 raise error.Abort(_("cannot qrefresh public revision"),
1635 raise error.Abort(_("cannot qrefresh public revision"),
1636 hint=_("see 'hg help phases' for details"))
1636 hint=_("see 'hg help phases' for details"))
1637
1637
1638 cparents = repo.changelog.parents(top)
1638 cparents = repo.changelog.parents(top)
1639 patchparent = self.qparents(repo, top)
1639 patchparent = self.qparents(repo, top)
1640
1640
1641 inclsubs = checksubstate(repo, hex(patchparent))
1641 inclsubs = checksubstate(repo, hex(patchparent))
1642 if inclsubs:
1642 if inclsubs:
1643 substatestate = repo.dirstate['.hgsubstate']
1643 substatestate = repo.dirstate['.hgsubstate']
1644
1644
1645 ph = patchheader(self.join(patchfn), self.plainmode)
1645 ph = patchheader(self.join(patchfn), self.plainmode)
1646 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1646 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1647 if newuser:
1647 if newuser:
1648 ph.setuser(newuser)
1648 ph.setuser(newuser)
1649 if newdate:
1649 if newdate:
1650 ph.setdate(newdate)
1650 ph.setdate(newdate)
1651 ph.setparent(hex(patchparent))
1651 ph.setparent(hex(patchparent))
1652
1652
1653 # only commit new patch when write is complete
1653 # only commit new patch when write is complete
1654 patchf = self.opener(patchfn, 'w', atomictemp=True)
1654 patchf = self.opener(patchfn, 'w', atomictemp=True)
1655
1655
1656 # update the dirstate in place, strip off the qtip commit
1656 # update the dirstate in place, strip off the qtip commit
1657 # and then commit.
1657 # and then commit.
1658 #
1658 #
1659 # this should really read:
1659 # this should really read:
1660 # mm, dd, aa = repo.status(top, patchparent)[:3]
1660 # mm, dd, aa = repo.status(top, patchparent)[:3]
1661 # but we do it backwards to take advantage of manifest/changelog
1661 # but we do it backwards to take advantage of manifest/changelog
1662 # caching against the next repo.status call
1662 # caching against the next repo.status call
1663 mm, aa, dd = repo.status(patchparent, top)[:3]
1663 mm, aa, dd = repo.status(patchparent, top)[:3]
1664 changes = repo.changelog.read(top)
1664 changes = repo.changelog.read(top)
1665 man = repo.manifestlog[changes[0]].read()
1665 man = repo.manifestlog[changes[0]].read()
1666 aaa = aa[:]
1666 aaa = aa[:]
1667 matchfn = scmutil.match(repo[None], pats, opts)
1667 match1 = scmutil.match(repo[None], pats, opts)
1668 # in short mode, we only diff the files included in the
1668 # in short mode, we only diff the files included in the
1669 # patch already plus specified files
1669 # patch already plus specified files
1670 if opts.get('short'):
1670 if opts.get('short'):
1671 # if amending a patch, we start with existing
1671 # if amending a patch, we start with existing
1672 # files plus specified files - unfiltered
1672 # files plus specified files - unfiltered
1673 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1673 match = scmutil.matchfiles(repo, mm + aa + dd + match1.files())
1674 # filter with include/exclude options
1674 # filter with include/exclude options
1675 matchfn = scmutil.match(repo[None], opts=opts)
1675 match1 = scmutil.match(repo[None], opts=opts)
1676 else:
1676 else:
1677 match = scmutil.matchall(repo)
1677 match = scmutil.matchall(repo)
1678 m, a, r, d = repo.status(match=match)[:4]
1678 m, a, r, d = repo.status(match=match)[:4]
1679 mm = set(mm)
1679 mm = set(mm)
1680 aa = set(aa)
1680 aa = set(aa)
1681 dd = set(dd)
1681 dd = set(dd)
1682
1682
1683 # we might end up with files that were added between
1683 # we might end up with files that were added between
1684 # qtip and the dirstate parent, but then changed in the
1684 # qtip and the dirstate parent, but then changed in the
1685 # local dirstate. in this case, we want them to only
1685 # local dirstate. in this case, we want them to only
1686 # show up in the added section
1686 # show up in the added section
1687 for x in m:
1687 for x in m:
1688 if x not in aa:
1688 if x not in aa:
1689 mm.add(x)
1689 mm.add(x)
1690 # we might end up with files added by the local dirstate that
1690 # we might end up with files added by the local dirstate that
1691 # were deleted by the patch. In this case, they should only
1691 # were deleted by the patch. In this case, they should only
1692 # show up in the changed section.
1692 # show up in the changed section.
1693 for x in a:
1693 for x in a:
1694 if x in dd:
1694 if x in dd:
1695 dd.remove(x)
1695 dd.remove(x)
1696 mm.add(x)
1696 mm.add(x)
1697 else:
1697 else:
1698 aa.add(x)
1698 aa.add(x)
1699 # make sure any files deleted in the local dirstate
1699 # make sure any files deleted in the local dirstate
1700 # are not in the add or change column of the patch
1700 # are not in the add or change column of the patch
1701 forget = []
1701 forget = []
1702 for x in d + r:
1702 for x in d + r:
1703 if x in aa:
1703 if x in aa:
1704 aa.remove(x)
1704 aa.remove(x)
1705 forget.append(x)
1705 forget.append(x)
1706 continue
1706 continue
1707 else:
1707 else:
1708 mm.discard(x)
1708 mm.discard(x)
1709 dd.add(x)
1709 dd.add(x)
1710
1710
1711 m = list(mm)
1711 m = list(mm)
1712 r = list(dd)
1712 r = list(dd)
1713 a = list(aa)
1713 a = list(aa)
1714
1714
1715 # create 'match' that includes the files to be recommitted.
1715 # create 'match' that includes the files to be recommitted.
1716 # apply matchfn via repo.status to ensure correct case handling.
1716 # apply match1 via repo.status to ensure correct case handling.
1717 cm, ca, cr, cd = repo.status(patchparent, match=matchfn)[:4]
1717 cm, ca, cr, cd = repo.status(patchparent, match=match1)[:4]
1718 allmatches = set(cm + ca + cr + cd)
1718 allmatches = set(cm + ca + cr + cd)
1719 refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
1719 refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
1720
1720
1721 files = set(inclsubs)
1721 files = set(inclsubs)
1722 for x in refreshchanges:
1722 for x in refreshchanges:
1723 files.update(x)
1723 files.update(x)
1724 match = scmutil.matchfiles(repo, files)
1724 match = scmutil.matchfiles(repo, files)
1725
1725
1726 bmlist = repo[top].bookmarks()
1726 bmlist = repo[top].bookmarks()
1727
1727
1728 dsguard = None
1728 dsguard = None
1729 try:
1729 try:
1730 dsguard = dirstateguard.dirstateguard(repo, 'mq.refresh')
1730 dsguard = dirstateguard.dirstateguard(repo, 'mq.refresh')
1731 if diffopts.git or diffopts.upgrade:
1731 if diffopts.git or diffopts.upgrade:
1732 copies = {}
1732 copies = {}
1733 for dst in a:
1733 for dst in a:
1734 src = repo.dirstate.copied(dst)
1734 src = repo.dirstate.copied(dst)
1735 # during qfold, the source file for copies may
1735 # during qfold, the source file for copies may
1736 # be removed. Treat this as a simple add.
1736 # be removed. Treat this as a simple add.
1737 if src is not None and src in repo.dirstate:
1737 if src is not None and src in repo.dirstate:
1738 copies.setdefault(src, []).append(dst)
1738 copies.setdefault(src, []).append(dst)
1739 repo.dirstate.add(dst)
1739 repo.dirstate.add(dst)
1740 # remember the copies between patchparent and qtip
1740 # remember the copies between patchparent and qtip
1741 for dst in aaa:
1741 for dst in aaa:
1742 f = repo.file(dst)
1742 f = repo.file(dst)
1743 src = f.renamed(man[dst])
1743 src = f.renamed(man[dst])
1744 if src:
1744 if src:
1745 copies.setdefault(src[0], []).extend(
1745 copies.setdefault(src[0], []).extend(
1746 copies.get(dst, []))
1746 copies.get(dst, []))
1747 if dst in a:
1747 if dst in a:
1748 copies[src[0]].append(dst)
1748 copies[src[0]].append(dst)
1749 # we can't copy a file created by the patch itself
1749 # we can't copy a file created by the patch itself
1750 if dst in copies:
1750 if dst in copies:
1751 del copies[dst]
1751 del copies[dst]
1752 for src, dsts in copies.iteritems():
1752 for src, dsts in copies.iteritems():
1753 for dst in dsts:
1753 for dst in dsts:
1754 repo.dirstate.copy(src, dst)
1754 repo.dirstate.copy(src, dst)
1755 else:
1755 else:
1756 for dst in a:
1756 for dst in a:
1757 repo.dirstate.add(dst)
1757 repo.dirstate.add(dst)
1758 # Drop useless copy information
1758 # Drop useless copy information
1759 for f in list(repo.dirstate.copies()):
1759 for f in list(repo.dirstate.copies()):
1760 repo.dirstate.copy(None, f)
1760 repo.dirstate.copy(None, f)
1761 for f in r:
1761 for f in r:
1762 repo.dirstate.remove(f)
1762 repo.dirstate.remove(f)
1763 # if the patch excludes a modified file, mark that
1763 # if the patch excludes a modified file, mark that
1764 # file with mtime=0 so status can see it.
1764 # file with mtime=0 so status can see it.
1765 mm = []
1765 mm = []
1766 for i in xrange(len(m) - 1, -1, -1):
1766 for i in xrange(len(m) - 1, -1, -1):
1767 if not matchfn(m[i]):
1767 if not match1(m[i]):
1768 mm.append(m[i])
1768 mm.append(m[i])
1769 del m[i]
1769 del m[i]
1770 for f in m:
1770 for f in m:
1771 repo.dirstate.normal(f)
1771 repo.dirstate.normal(f)
1772 for f in mm:
1772 for f in mm:
1773 repo.dirstate.normallookup(f)
1773 repo.dirstate.normallookup(f)
1774 for f in forget:
1774 for f in forget:
1775 repo.dirstate.drop(f)
1775 repo.dirstate.drop(f)
1776
1776
1777 user = ph.user or changes[1]
1777 user = ph.user or changes[1]
1778
1778
1779 oldphase = repo[top].phase()
1779 oldphase = repo[top].phase()
1780
1780
1781 # assumes strip can roll itself back if interrupted
1781 # assumes strip can roll itself back if interrupted
1782 repo.setparents(*cparents)
1782 repo.setparents(*cparents)
1783 self.applied.pop()
1783 self.applied.pop()
1784 self.applieddirty = True
1784 self.applieddirty = True
1785 strip(self.ui, repo, [top], update=False, backup=False)
1785 strip(self.ui, repo, [top], update=False, backup=False)
1786 dsguard.close()
1786 dsguard.close()
1787 finally:
1787 finally:
1788 release(dsguard)
1788 release(dsguard)
1789
1789
1790 try:
1790 try:
1791 # might be nice to attempt to roll back strip after this
1791 # might be nice to attempt to roll back strip after this
1792
1792
1793 defaultmsg = "[mq]: %s" % patchfn
1793 defaultmsg = "[mq]: %s" % patchfn
1794 editor = cmdutil.getcommiteditor(editform=editform)
1794 editor = cmdutil.getcommiteditor(editform=editform)
1795 if edit:
1795 if edit:
1796 def finishdesc(desc):
1796 def finishdesc(desc):
1797 if desc.rstrip():
1797 if desc.rstrip():
1798 ph.setmessage(desc)
1798 ph.setmessage(desc)
1799 return desc
1799 return desc
1800 return defaultmsg
1800 return defaultmsg
1801 # i18n: this message is shown in editor with "HG: " prefix
1801 # i18n: this message is shown in editor with "HG: " prefix
1802 extramsg = _('Leave message empty to use default message.')
1802 extramsg = _('Leave message empty to use default message.')
1803 editor = cmdutil.getcommiteditor(finishdesc=finishdesc,
1803 editor = cmdutil.getcommiteditor(finishdesc=finishdesc,
1804 extramsg=extramsg,
1804 extramsg=extramsg,
1805 editform=editform)
1805 editform=editform)
1806 message = msg or "\n".join(ph.message)
1806 message = msg or "\n".join(ph.message)
1807 elif not msg:
1807 elif not msg:
1808 if not ph.message:
1808 if not ph.message:
1809 message = defaultmsg
1809 message = defaultmsg
1810 else:
1810 else:
1811 message = "\n".join(ph.message)
1811 message = "\n".join(ph.message)
1812 else:
1812 else:
1813 message = msg
1813 message = msg
1814 ph.setmessage(msg)
1814 ph.setmessage(msg)
1815
1815
1816 # Ensure we create a new changeset in the same phase than
1816 # Ensure we create a new changeset in the same phase than
1817 # the old one.
1817 # the old one.
1818 lock = tr = None
1818 lock = tr = None
1819 try:
1819 try:
1820 lock = repo.lock()
1820 lock = repo.lock()
1821 tr = repo.transaction('mq')
1821 tr = repo.transaction('mq')
1822 n = newcommit(repo, oldphase, message, user, ph.date,
1822 n = newcommit(repo, oldphase, message, user, ph.date,
1823 match=match, force=True, editor=editor)
1823 match=match, force=True, editor=editor)
1824 # only write patch after a successful commit
1824 # only write patch after a successful commit
1825 c = [list(x) for x in refreshchanges]
1825 c = [list(x) for x in refreshchanges]
1826 if inclsubs:
1826 if inclsubs:
1827 self.putsubstate2changes(substatestate, c)
1827 self.putsubstate2changes(substatestate, c)
1828 chunks = patchmod.diff(repo, patchparent,
1828 chunks = patchmod.diff(repo, patchparent,
1829 changes=c, opts=diffopts)
1829 changes=c, opts=diffopts)
1830 comments = str(ph)
1830 comments = str(ph)
1831 if comments:
1831 if comments:
1832 patchf.write(comments)
1832 patchf.write(comments)
1833 for chunk in chunks:
1833 for chunk in chunks:
1834 patchf.write(chunk)
1834 patchf.write(chunk)
1835 patchf.close()
1835 patchf.close()
1836
1836
1837 marks = repo._bookmarks
1837 marks = repo._bookmarks
1838 marks.applychanges(repo, tr, [(bm, n) for bm in bmlist])
1838 marks.applychanges(repo, tr, [(bm, n) for bm in bmlist])
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 ] + cmdutil.remoteopts,
2409 ] + cmdutil.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 ] + cmdutil.walkopts + cmdutil.commitopts,
2577 ] + cmdutil.walkopts + cmdutil.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 ] + cmdutil.walkopts + cmdutil.commitopts,
2626 ] + cmdutil.walkopts + cmdutil.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 cmdutil.diffopts + cmdutil.diffopts2 + cmdutil.walkopts,
2659 cmdutil.diffopts + cmdutil.diffopts2 + cmdutil.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 ui.pager('qdiff')
2677 ui.pager('qdiff')
2678 repo.mq.diff(repo, pats, opts)
2678 repo.mq.diff(repo, pats, opts)
2679 return 0
2679 return 0
2680
2680
2681 @command('qfold',
2681 @command('qfold',
2682 [('e', 'edit', None, _('invoke editor on commit messages')),
2682 [('e', 'edit', None, _('invoke editor on commit messages')),
2683 ('k', 'keep', None, _('keep folded patch files')),
2683 ('k', 'keep', None, _('keep folded patch files')),
2684 ] + cmdutil.commitopts,
2684 ] + cmdutil.commitopts,
2685 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2685 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2686 def fold(ui, repo, *files, **opts):
2686 def fold(ui, repo, *files, **opts):
2687 """fold the named patches into the current patch
2687 """fold the named patches into the current patch
2688
2688
2689 Patches must not yet be applied. Each patch will be successively
2689 Patches must not yet be applied. Each patch will be successively
2690 applied to the current patch in the order given. If all the
2690 applied to the current patch in the order given. If all the
2691 patches apply successfully, the current patch will be refreshed
2691 patches apply successfully, the current patch will be refreshed
2692 with the new cumulative patch, and the folded patches will be
2692 with the new cumulative patch, and the folded patches will be
2693 deleted. With -k/--keep, the folded patch files will not be
2693 deleted. With -k/--keep, the folded patch files will not be
2694 removed afterwards.
2694 removed afterwards.
2695
2695
2696 The header for each folded patch will be concatenated with the
2696 The header for each folded patch will be concatenated with the
2697 current patch header, separated by a line of ``* * *``.
2697 current patch header, separated by a line of ``* * *``.
2698
2698
2699 Returns 0 on success."""
2699 Returns 0 on success."""
2700 q = repo.mq
2700 q = repo.mq
2701 if not files:
2701 if not files:
2702 raise error.Abort(_('qfold requires at least one patch name'))
2702 raise error.Abort(_('qfold requires at least one patch name'))
2703 if not q.checktoppatch(repo)[0]:
2703 if not q.checktoppatch(repo)[0]:
2704 raise error.Abort(_('no patches applied'))
2704 raise error.Abort(_('no patches applied'))
2705 q.checklocalchanges(repo)
2705 q.checklocalchanges(repo)
2706
2706
2707 message = cmdutil.logmessage(ui, opts)
2707 message = cmdutil.logmessage(ui, opts)
2708
2708
2709 parent = q.lookup('qtip')
2709 parent = q.lookup('qtip')
2710 patches = []
2710 patches = []
2711 messages = []
2711 messages = []
2712 for f in files:
2712 for f in files:
2713 p = q.lookup(f)
2713 p = q.lookup(f)
2714 if p in patches or p == parent:
2714 if p in patches or p == parent:
2715 ui.warn(_('skipping already folded patch %s\n') % p)
2715 ui.warn(_('skipping already folded patch %s\n') % p)
2716 if q.isapplied(p):
2716 if q.isapplied(p):
2717 raise error.Abort(_('qfold cannot fold already applied patch %s')
2717 raise error.Abort(_('qfold cannot fold already applied patch %s')
2718 % p)
2718 % p)
2719 patches.append(p)
2719 patches.append(p)
2720
2720
2721 for p in patches:
2721 for p in patches:
2722 if not message:
2722 if not message:
2723 ph = patchheader(q.join(p), q.plainmode)
2723 ph = patchheader(q.join(p), q.plainmode)
2724 if ph.message:
2724 if ph.message:
2725 messages.append(ph.message)
2725 messages.append(ph.message)
2726 pf = q.join(p)
2726 pf = q.join(p)
2727 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2727 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2728 if not patchsuccess:
2728 if not patchsuccess:
2729 raise error.Abort(_('error folding patch %s') % p)
2729 raise error.Abort(_('error folding patch %s') % p)
2730
2730
2731 if not message:
2731 if not message:
2732 ph = patchheader(q.join(parent), q.plainmode)
2732 ph = patchheader(q.join(parent), q.plainmode)
2733 message = ph.message
2733 message = ph.message
2734 for msg in messages:
2734 for msg in messages:
2735 if msg:
2735 if msg:
2736 if message:
2736 if message:
2737 message.append('* * *')
2737 message.append('* * *')
2738 message.extend(msg)
2738 message.extend(msg)
2739 message = '\n'.join(message)
2739 message = '\n'.join(message)
2740
2740
2741 diffopts = q.patchopts(q.diffopts(), *patches)
2741 diffopts = q.patchopts(q.diffopts(), *patches)
2742 with repo.wlock():
2742 with repo.wlock():
2743 q.refresh(repo, msg=message, git=diffopts.git, edit=opts.get('edit'),
2743 q.refresh(repo, msg=message, git=diffopts.git, edit=opts.get('edit'),
2744 editform='mq.qfold')
2744 editform='mq.qfold')
2745 q.delete(repo, patches, opts)
2745 q.delete(repo, patches, opts)
2746 q.savedirty()
2746 q.savedirty()
2747
2747
2748 @command("qgoto",
2748 @command("qgoto",
2749 [('', 'keep-changes', None,
2749 [('', 'keep-changes', None,
2750 _('tolerate non-conflicting local changes')),
2750 _('tolerate non-conflicting local changes')),
2751 ('f', 'force', None, _('overwrite any local changes')),
2751 ('f', 'force', None, _('overwrite any local changes')),
2752 ('', 'no-backup', None, _('do not save backup copies of files'))],
2752 ('', 'no-backup', None, _('do not save backup copies of files'))],
2753 _('hg qgoto [OPTION]... PATCH'))
2753 _('hg qgoto [OPTION]... PATCH'))
2754 def goto(ui, repo, patch, **opts):
2754 def goto(ui, repo, patch, **opts):
2755 '''push or pop patches until named patch is at top of stack
2755 '''push or pop patches until named patch is at top of stack
2756
2756
2757 Returns 0 on success.'''
2757 Returns 0 on success.'''
2758 opts = fixkeepchangesopts(ui, opts)
2758 opts = fixkeepchangesopts(ui, opts)
2759 q = repo.mq
2759 q = repo.mq
2760 patch = q.lookup(patch)
2760 patch = q.lookup(patch)
2761 nobackup = opts.get('no_backup')
2761 nobackup = opts.get('no_backup')
2762 keepchanges = opts.get('keep_changes')
2762 keepchanges = opts.get('keep_changes')
2763 if q.isapplied(patch):
2763 if q.isapplied(patch):
2764 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2764 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2765 keepchanges=keepchanges)
2765 keepchanges=keepchanges)
2766 else:
2766 else:
2767 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2767 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2768 keepchanges=keepchanges)
2768 keepchanges=keepchanges)
2769 q.savedirty()
2769 q.savedirty()
2770 return ret
2770 return ret
2771
2771
2772 @command("qguard",
2772 @command("qguard",
2773 [('l', 'list', None, _('list all patches and guards')),
2773 [('l', 'list', None, _('list all patches and guards')),
2774 ('n', 'none', None, _('drop all guards'))],
2774 ('n', 'none', None, _('drop all guards'))],
2775 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2775 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2776 def guard(ui, repo, *args, **opts):
2776 def guard(ui, repo, *args, **opts):
2777 '''set or print guards for a patch
2777 '''set or print guards for a patch
2778
2778
2779 Guards control whether a patch can be pushed. A patch with no
2779 Guards control whether a patch can be pushed. A patch with no
2780 guards is always pushed. A patch with a positive guard ("+foo") is
2780 guards is always pushed. A patch with a positive guard ("+foo") is
2781 pushed only if the :hg:`qselect` command has activated it. A patch with
2781 pushed only if the :hg:`qselect` command has activated it. A patch with
2782 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2782 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2783 has activated it.
2783 has activated it.
2784
2784
2785 With no arguments, print the currently active guards.
2785 With no arguments, print the currently active guards.
2786 With arguments, set guards for the named patch.
2786 With arguments, set guards for the named patch.
2787
2787
2788 .. note::
2788 .. note::
2789
2789
2790 Specifying negative guards now requires '--'.
2790 Specifying negative guards now requires '--'.
2791
2791
2792 To set guards on another patch::
2792 To set guards on another patch::
2793
2793
2794 hg qguard other.patch -- +2.6.17 -stable
2794 hg qguard other.patch -- +2.6.17 -stable
2795
2795
2796 Returns 0 on success.
2796 Returns 0 on success.
2797 '''
2797 '''
2798 def status(idx):
2798 def status(idx):
2799 guards = q.seriesguards[idx] or ['unguarded']
2799 guards = q.seriesguards[idx] or ['unguarded']
2800 if q.series[idx] in applied:
2800 if q.series[idx] in applied:
2801 state = 'applied'
2801 state = 'applied'
2802 elif q.pushable(idx)[0]:
2802 elif q.pushable(idx)[0]:
2803 state = 'unapplied'
2803 state = 'unapplied'
2804 else:
2804 else:
2805 state = 'guarded'
2805 state = 'guarded'
2806 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2806 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2807 ui.write('%s: ' % ui.label(q.series[idx], label))
2807 ui.write('%s: ' % ui.label(q.series[idx], label))
2808
2808
2809 for i, guard in enumerate(guards):
2809 for i, guard in enumerate(guards):
2810 if guard.startswith('+'):
2810 if guard.startswith('+'):
2811 ui.write(guard, label='qguard.positive')
2811 ui.write(guard, label='qguard.positive')
2812 elif guard.startswith('-'):
2812 elif guard.startswith('-'):
2813 ui.write(guard, label='qguard.negative')
2813 ui.write(guard, label='qguard.negative')
2814 else:
2814 else:
2815 ui.write(guard, label='qguard.unguarded')
2815 ui.write(guard, label='qguard.unguarded')
2816 if i != len(guards) - 1:
2816 if i != len(guards) - 1:
2817 ui.write(' ')
2817 ui.write(' ')
2818 ui.write('\n')
2818 ui.write('\n')
2819 q = repo.mq
2819 q = repo.mq
2820 applied = set(p.name for p in q.applied)
2820 applied = set(p.name for p in q.applied)
2821 patch = None
2821 patch = None
2822 args = list(args)
2822 args = list(args)
2823 if opts.get('list'):
2823 if opts.get('list'):
2824 if args or opts.get('none'):
2824 if args or opts.get('none'):
2825 raise error.Abort(_('cannot mix -l/--list with options or '
2825 raise error.Abort(_('cannot mix -l/--list with options or '
2826 'arguments'))
2826 'arguments'))
2827 for i in xrange(len(q.series)):
2827 for i in xrange(len(q.series)):
2828 status(i)
2828 status(i)
2829 return
2829 return
2830 if not args or args[0][0:1] in '-+':
2830 if not args or args[0][0:1] in '-+':
2831 if not q.applied:
2831 if not q.applied:
2832 raise error.Abort(_('no patches applied'))
2832 raise error.Abort(_('no patches applied'))
2833 patch = q.applied[-1].name
2833 patch = q.applied[-1].name
2834 if patch is None and args[0][0:1] not in '-+':
2834 if patch is None and args[0][0:1] not in '-+':
2835 patch = args.pop(0)
2835 patch = args.pop(0)
2836 if patch is None:
2836 if patch is None:
2837 raise error.Abort(_('no patch to work with'))
2837 raise error.Abort(_('no patch to work with'))
2838 if args or opts.get('none'):
2838 if args or opts.get('none'):
2839 idx = q.findseries(patch)
2839 idx = q.findseries(patch)
2840 if idx is None:
2840 if idx is None:
2841 raise error.Abort(_('no patch named %s') % patch)
2841 raise error.Abort(_('no patch named %s') % patch)
2842 q.setguards(idx, args)
2842 q.setguards(idx, args)
2843 q.savedirty()
2843 q.savedirty()
2844 else:
2844 else:
2845 status(q.series.index(q.lookup(patch)))
2845 status(q.series.index(q.lookup(patch)))
2846
2846
2847 @command("qheader", [], _('hg qheader [PATCH]'))
2847 @command("qheader", [], _('hg qheader [PATCH]'))
2848 def header(ui, repo, patch=None):
2848 def header(ui, repo, patch=None):
2849 """print the header of the topmost or specified patch
2849 """print the header of the topmost or specified patch
2850
2850
2851 Returns 0 on success."""
2851 Returns 0 on success."""
2852 q = repo.mq
2852 q = repo.mq
2853
2853
2854 if patch:
2854 if patch:
2855 patch = q.lookup(patch)
2855 patch = q.lookup(patch)
2856 else:
2856 else:
2857 if not q.applied:
2857 if not q.applied:
2858 ui.write(_('no patches applied\n'))
2858 ui.write(_('no patches applied\n'))
2859 return 1
2859 return 1
2860 patch = q.lookup('qtip')
2860 patch = q.lookup('qtip')
2861 ph = patchheader(q.join(patch), q.plainmode)
2861 ph = patchheader(q.join(patch), q.plainmode)
2862
2862
2863 ui.write('\n'.join(ph.message) + '\n')
2863 ui.write('\n'.join(ph.message) + '\n')
2864
2864
2865 def lastsavename(path):
2865 def lastsavename(path):
2866 (directory, base) = os.path.split(path)
2866 (directory, base) = os.path.split(path)
2867 names = os.listdir(directory)
2867 names = os.listdir(directory)
2868 namere = re.compile("%s.([0-9]+)" % base)
2868 namere = re.compile("%s.([0-9]+)" % base)
2869 maxindex = None
2869 maxindex = None
2870 maxname = None
2870 maxname = None
2871 for f in names:
2871 for f in names:
2872 m = namere.match(f)
2872 m = namere.match(f)
2873 if m:
2873 if m:
2874 index = int(m.group(1))
2874 index = int(m.group(1))
2875 if maxindex is None or index > maxindex:
2875 if maxindex is None or index > maxindex:
2876 maxindex = index
2876 maxindex = index
2877 maxname = f
2877 maxname = f
2878 if maxname:
2878 if maxname:
2879 return (os.path.join(directory, maxname), maxindex)
2879 return (os.path.join(directory, maxname), maxindex)
2880 return (None, None)
2880 return (None, None)
2881
2881
2882 def savename(path):
2882 def savename(path):
2883 (last, index) = lastsavename(path)
2883 (last, index) = lastsavename(path)
2884 if last is None:
2884 if last is None:
2885 index = 0
2885 index = 0
2886 newpath = path + ".%d" % (index + 1)
2886 newpath = path + ".%d" % (index + 1)
2887 return newpath
2887 return newpath
2888
2888
2889 @command("^qpush",
2889 @command("^qpush",
2890 [('', 'keep-changes', None,
2890 [('', 'keep-changes', None,
2891 _('tolerate non-conflicting local changes')),
2891 _('tolerate non-conflicting local changes')),
2892 ('f', 'force', None, _('apply on top of local changes')),
2892 ('f', 'force', None, _('apply on top of local changes')),
2893 ('e', 'exact', None,
2893 ('e', 'exact', None,
2894 _('apply the target patch to its recorded parent')),
2894 _('apply the target patch to its recorded parent')),
2895 ('l', 'list', None, _('list patch name in commit text')),
2895 ('l', 'list', None, _('list patch name in commit text')),
2896 ('a', 'all', None, _('apply all patches')),
2896 ('a', 'all', None, _('apply all patches')),
2897 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2897 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2898 ('n', 'name', '',
2898 ('n', 'name', '',
2899 _('merge queue name (DEPRECATED)'), _('NAME')),
2899 _('merge queue name (DEPRECATED)'), _('NAME')),
2900 ('', 'move', None,
2900 ('', 'move', None,
2901 _('reorder patch series and apply only the patch')),
2901 _('reorder patch series and apply only the patch')),
2902 ('', 'no-backup', None, _('do not save backup copies of files'))],
2902 ('', 'no-backup', None, _('do not save backup copies of files'))],
2903 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2903 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2904 def push(ui, repo, patch=None, **opts):
2904 def push(ui, repo, patch=None, **opts):
2905 """push the next patch onto the stack
2905 """push the next patch onto the stack
2906
2906
2907 By default, abort if the working directory contains uncommitted
2907 By default, abort if the working directory contains uncommitted
2908 changes. With --keep-changes, abort only if the uncommitted files
2908 changes. With --keep-changes, abort only if the uncommitted files
2909 overlap with patched files. With -f/--force, backup and patch over
2909 overlap with patched files. With -f/--force, backup and patch over
2910 uncommitted changes.
2910 uncommitted changes.
2911
2911
2912 Return 0 on success.
2912 Return 0 on success.
2913 """
2913 """
2914 q = repo.mq
2914 q = repo.mq
2915 mergeq = None
2915 mergeq = None
2916
2916
2917 opts = fixkeepchangesopts(ui, opts)
2917 opts = fixkeepchangesopts(ui, opts)
2918 if opts.get('merge'):
2918 if opts.get('merge'):
2919 if opts.get('name'):
2919 if opts.get('name'):
2920 newpath = repo.vfs.join(opts.get('name'))
2920 newpath = repo.vfs.join(opts.get('name'))
2921 else:
2921 else:
2922 newpath, i = lastsavename(q.path)
2922 newpath, i = lastsavename(q.path)
2923 if not newpath:
2923 if not newpath:
2924 ui.warn(_("no saved queues found, please use -n\n"))
2924 ui.warn(_("no saved queues found, please use -n\n"))
2925 return 1
2925 return 1
2926 mergeq = queue(ui, repo.baseui, repo.path, newpath)
2926 mergeq = queue(ui, repo.baseui, repo.path, newpath)
2927 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2927 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2928 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2928 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2929 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2929 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2930 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2930 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2931 keepchanges=opts.get('keep_changes'))
2931 keepchanges=opts.get('keep_changes'))
2932 return ret
2932 return ret
2933
2933
2934 @command("^qpop",
2934 @command("^qpop",
2935 [('a', 'all', None, _('pop all patches')),
2935 [('a', 'all', None, _('pop all patches')),
2936 ('n', 'name', '',
2936 ('n', 'name', '',
2937 _('queue name to pop (DEPRECATED)'), _('NAME')),
2937 _('queue name to pop (DEPRECATED)'), _('NAME')),
2938 ('', 'keep-changes', None,
2938 ('', 'keep-changes', None,
2939 _('tolerate non-conflicting local changes')),
2939 _('tolerate non-conflicting local changes')),
2940 ('f', 'force', None, _('forget any local changes to patched files')),
2940 ('f', 'force', None, _('forget any local changes to patched files')),
2941 ('', 'no-backup', None, _('do not save backup copies of files'))],
2941 ('', 'no-backup', None, _('do not save backup copies of files'))],
2942 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2942 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2943 def pop(ui, repo, patch=None, **opts):
2943 def pop(ui, repo, patch=None, **opts):
2944 """pop the current patch off the stack
2944 """pop the current patch off the stack
2945
2945
2946 Without argument, pops off the top of the patch stack. If given a
2946 Without argument, pops off the top of the patch stack. If given a
2947 patch name, keeps popping off patches until the named patch is at
2947 patch name, keeps popping off patches until the named patch is at
2948 the top of the stack.
2948 the top of the stack.
2949
2949
2950 By default, abort if the working directory contains uncommitted
2950 By default, abort if the working directory contains uncommitted
2951 changes. With --keep-changes, abort only if the uncommitted files
2951 changes. With --keep-changes, abort only if the uncommitted files
2952 overlap with patched files. With -f/--force, backup and discard
2952 overlap with patched files. With -f/--force, backup and discard
2953 changes made to such files.
2953 changes made to such files.
2954
2954
2955 Return 0 on success.
2955 Return 0 on success.
2956 """
2956 """
2957 opts = fixkeepchangesopts(ui, opts)
2957 opts = fixkeepchangesopts(ui, opts)
2958 localupdate = True
2958 localupdate = True
2959 if opts.get('name'):
2959 if opts.get('name'):
2960 q = queue(ui, repo.baseui, repo.path, repo.vfs.join(opts.get('name')))
2960 q = queue(ui, repo.baseui, repo.path, repo.vfs.join(opts.get('name')))
2961 ui.warn(_('using patch queue: %s\n') % q.path)
2961 ui.warn(_('using patch queue: %s\n') % q.path)
2962 localupdate = False
2962 localupdate = False
2963 else:
2963 else:
2964 q = repo.mq
2964 q = repo.mq
2965 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2965 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2966 all=opts.get('all'), nobackup=opts.get('no_backup'),
2966 all=opts.get('all'), nobackup=opts.get('no_backup'),
2967 keepchanges=opts.get('keep_changes'))
2967 keepchanges=opts.get('keep_changes'))
2968 q.savedirty()
2968 q.savedirty()
2969 return ret
2969 return ret
2970
2970
2971 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2971 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2972 def rename(ui, repo, patch, name=None, **opts):
2972 def rename(ui, repo, patch, name=None, **opts):
2973 """rename a patch
2973 """rename a patch
2974
2974
2975 With one argument, renames the current patch to PATCH1.
2975 With one argument, renames the current patch to PATCH1.
2976 With two arguments, renames PATCH1 to PATCH2.
2976 With two arguments, renames PATCH1 to PATCH2.
2977
2977
2978 Returns 0 on success."""
2978 Returns 0 on success."""
2979 q = repo.mq
2979 q = repo.mq
2980 if not name:
2980 if not name:
2981 name = patch
2981 name = patch
2982 patch = None
2982 patch = None
2983
2983
2984 if patch:
2984 if patch:
2985 patch = q.lookup(patch)
2985 patch = q.lookup(patch)
2986 else:
2986 else:
2987 if not q.applied:
2987 if not q.applied:
2988 ui.write(_('no patches applied\n'))
2988 ui.write(_('no patches applied\n'))
2989 return
2989 return
2990 patch = q.lookup('qtip')
2990 patch = q.lookup('qtip')
2991 absdest = q.join(name)
2991 absdest = q.join(name)
2992 if os.path.isdir(absdest):
2992 if os.path.isdir(absdest):
2993 name = normname(os.path.join(name, os.path.basename(patch)))
2993 name = normname(os.path.join(name, os.path.basename(patch)))
2994 absdest = q.join(name)
2994 absdest = q.join(name)
2995 q.checkpatchname(name)
2995 q.checkpatchname(name)
2996
2996
2997 ui.note(_('renaming %s to %s\n') % (patch, name))
2997 ui.note(_('renaming %s to %s\n') % (patch, name))
2998 i = q.findseries(patch)
2998 i = q.findseries(patch)
2999 guards = q.guard_re.findall(q.fullseries[i])
2999 guards = q.guard_re.findall(q.fullseries[i])
3000 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
3000 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
3001 q.parseseries()
3001 q.parseseries()
3002 q.seriesdirty = True
3002 q.seriesdirty = True
3003
3003
3004 info = q.isapplied(patch)
3004 info = q.isapplied(patch)
3005 if info:
3005 if info:
3006 q.applied[info[0]] = statusentry(info[1], name)
3006 q.applied[info[0]] = statusentry(info[1], name)
3007 q.applieddirty = True
3007 q.applieddirty = True
3008
3008
3009 destdir = os.path.dirname(absdest)
3009 destdir = os.path.dirname(absdest)
3010 if not os.path.isdir(destdir):
3010 if not os.path.isdir(destdir):
3011 os.makedirs(destdir)
3011 os.makedirs(destdir)
3012 util.rename(q.join(patch), absdest)
3012 util.rename(q.join(patch), absdest)
3013 r = q.qrepo()
3013 r = q.qrepo()
3014 if r and patch in r.dirstate:
3014 if r and patch in r.dirstate:
3015 wctx = r[None]
3015 wctx = r[None]
3016 with r.wlock():
3016 with r.wlock():
3017 if r.dirstate[patch] == 'a':
3017 if r.dirstate[patch] == 'a':
3018 r.dirstate.drop(patch)
3018 r.dirstate.drop(patch)
3019 r.dirstate.add(name)
3019 r.dirstate.add(name)
3020 else:
3020 else:
3021 wctx.copy(patch, name)
3021 wctx.copy(patch, name)
3022 wctx.forget([patch])
3022 wctx.forget([patch])
3023
3023
3024 q.savedirty()
3024 q.savedirty()
3025
3025
3026 @command("qrestore",
3026 @command("qrestore",
3027 [('d', 'delete', None, _('delete save entry')),
3027 [('d', 'delete', None, _('delete save entry')),
3028 ('u', 'update', None, _('update queue working directory'))],
3028 ('u', 'update', None, _('update queue working directory'))],
3029 _('hg qrestore [-d] [-u] REV'))
3029 _('hg qrestore [-d] [-u] REV'))
3030 def restore(ui, repo, rev, **opts):
3030 def restore(ui, repo, rev, **opts):
3031 """restore the queue state saved by a revision (DEPRECATED)
3031 """restore the queue state saved by a revision (DEPRECATED)
3032
3032
3033 This command is deprecated, use :hg:`rebase` instead."""
3033 This command is deprecated, use :hg:`rebase` instead."""
3034 rev = repo.lookup(rev)
3034 rev = repo.lookup(rev)
3035 q = repo.mq
3035 q = repo.mq
3036 q.restore(repo, rev, delete=opts.get('delete'),
3036 q.restore(repo, rev, delete=opts.get('delete'),
3037 qupdate=opts.get('update'))
3037 qupdate=opts.get('update'))
3038 q.savedirty()
3038 q.savedirty()
3039 return 0
3039 return 0
3040
3040
3041 @command("qsave",
3041 @command("qsave",
3042 [('c', 'copy', None, _('copy patch directory')),
3042 [('c', 'copy', None, _('copy patch directory')),
3043 ('n', 'name', '',
3043 ('n', 'name', '',
3044 _('copy directory name'), _('NAME')),
3044 _('copy directory name'), _('NAME')),
3045 ('e', 'empty', None, _('clear queue status file')),
3045 ('e', 'empty', None, _('clear queue status file')),
3046 ('f', 'force', None, _('force copy'))] + cmdutil.commitopts,
3046 ('f', 'force', None, _('force copy'))] + cmdutil.commitopts,
3047 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
3047 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
3048 def save(ui, repo, **opts):
3048 def save(ui, repo, **opts):
3049 """save current queue state (DEPRECATED)
3049 """save current queue state (DEPRECATED)
3050
3050
3051 This command is deprecated, use :hg:`rebase` instead."""
3051 This command is deprecated, use :hg:`rebase` instead."""
3052 q = repo.mq
3052 q = repo.mq
3053 message = cmdutil.logmessage(ui, opts)
3053 message = cmdutil.logmessage(ui, opts)
3054 ret = q.save(repo, msg=message)
3054 ret = q.save(repo, msg=message)
3055 if ret:
3055 if ret:
3056 return ret
3056 return ret
3057 q.savedirty() # save to .hg/patches before copying
3057 q.savedirty() # save to .hg/patches before copying
3058 if opts.get('copy'):
3058 if opts.get('copy'):
3059 path = q.path
3059 path = q.path
3060 if opts.get('name'):
3060 if opts.get('name'):
3061 newpath = os.path.join(q.basepath, opts.get('name'))
3061 newpath = os.path.join(q.basepath, opts.get('name'))
3062 if os.path.exists(newpath):
3062 if os.path.exists(newpath):
3063 if not os.path.isdir(newpath):
3063 if not os.path.isdir(newpath):
3064 raise error.Abort(_('destination %s exists and is not '
3064 raise error.Abort(_('destination %s exists and is not '
3065 'a directory') % newpath)
3065 'a directory') % newpath)
3066 if not opts.get('force'):
3066 if not opts.get('force'):
3067 raise error.Abort(_('destination %s exists, '
3067 raise error.Abort(_('destination %s exists, '
3068 'use -f to force') % newpath)
3068 'use -f to force') % newpath)
3069 else:
3069 else:
3070 newpath = savename(path)
3070 newpath = savename(path)
3071 ui.warn(_("copy %s to %s\n") % (path, newpath))
3071 ui.warn(_("copy %s to %s\n") % (path, newpath))
3072 util.copyfiles(path, newpath)
3072 util.copyfiles(path, newpath)
3073 if opts.get('empty'):
3073 if opts.get('empty'):
3074 del q.applied[:]
3074 del q.applied[:]
3075 q.applieddirty = True
3075 q.applieddirty = True
3076 q.savedirty()
3076 q.savedirty()
3077 return 0
3077 return 0
3078
3078
3079
3079
3080 @command("qselect",
3080 @command("qselect",
3081 [('n', 'none', None, _('disable all guards')),
3081 [('n', 'none', None, _('disable all guards')),
3082 ('s', 'series', None, _('list all guards in series file')),
3082 ('s', 'series', None, _('list all guards in series file')),
3083 ('', 'pop', None, _('pop to before first guarded applied patch')),
3083 ('', 'pop', None, _('pop to before first guarded applied patch')),
3084 ('', 'reapply', None, _('pop, then reapply patches'))],
3084 ('', 'reapply', None, _('pop, then reapply patches'))],
3085 _('hg qselect [OPTION]... [GUARD]...'))
3085 _('hg qselect [OPTION]... [GUARD]...'))
3086 def select(ui, repo, *args, **opts):
3086 def select(ui, repo, *args, **opts):
3087 '''set or print guarded patches to push
3087 '''set or print guarded patches to push
3088
3088
3089 Use the :hg:`qguard` command to set or print guards on patch, then use
3089 Use the :hg:`qguard` command to set or print guards on patch, then use
3090 qselect to tell mq which guards to use. A patch will be pushed if
3090 qselect to tell mq which guards to use. A patch will be pushed if
3091 it has no guards or any positive guards match the currently
3091 it has no guards or any positive guards match the currently
3092 selected guard, but will not be pushed if any negative guards
3092 selected guard, but will not be pushed if any negative guards
3093 match the current guard. For example::
3093 match the current guard. For example::
3094
3094
3095 qguard foo.patch -- -stable (negative guard)
3095 qguard foo.patch -- -stable (negative guard)
3096 qguard bar.patch +stable (positive guard)
3096 qguard bar.patch +stable (positive guard)
3097 qselect stable
3097 qselect stable
3098
3098
3099 This activates the "stable" guard. mq will skip foo.patch (because
3099 This activates the "stable" guard. mq will skip foo.patch (because
3100 it has a negative match) but push bar.patch (because it has a
3100 it has a negative match) but push bar.patch (because it has a
3101 positive match).
3101 positive match).
3102
3102
3103 With no arguments, prints the currently active guards.
3103 With no arguments, prints the currently active guards.
3104 With one argument, sets the active guard.
3104 With one argument, sets the active guard.
3105
3105
3106 Use -n/--none to deactivate guards (no other arguments needed).
3106 Use -n/--none to deactivate guards (no other arguments needed).
3107 When no guards are active, patches with positive guards are
3107 When no guards are active, patches with positive guards are
3108 skipped and patches with negative guards are pushed.
3108 skipped and patches with negative guards are pushed.
3109
3109
3110 qselect can change the guards on applied patches. It does not pop
3110 qselect can change the guards on applied patches. It does not pop
3111 guarded patches by default. Use --pop to pop back to the last
3111 guarded patches by default. Use --pop to pop back to the last
3112 applied patch that is not guarded. Use --reapply (which implies
3112 applied patch that is not guarded. Use --reapply (which implies
3113 --pop) to push back to the current patch afterwards, but skip
3113 --pop) to push back to the current patch afterwards, but skip
3114 guarded patches.
3114 guarded patches.
3115
3115
3116 Use -s/--series to print a list of all guards in the series file
3116 Use -s/--series to print a list of all guards in the series file
3117 (no other arguments needed). Use -v for more information.
3117 (no other arguments needed). Use -v for more information.
3118
3118
3119 Returns 0 on success.'''
3119 Returns 0 on success.'''
3120
3120
3121 q = repo.mq
3121 q = repo.mq
3122 guards = q.active()
3122 guards = q.active()
3123 pushable = lambda i: q.pushable(q.applied[i].name)[0]
3123 pushable = lambda i: q.pushable(q.applied[i].name)[0]
3124 if args or opts.get('none'):
3124 if args or opts.get('none'):
3125 old_unapplied = q.unapplied(repo)
3125 old_unapplied = q.unapplied(repo)
3126 old_guarded = [i for i in xrange(len(q.applied)) if not pushable(i)]
3126 old_guarded = [i for i in xrange(len(q.applied)) if not pushable(i)]
3127 q.setactive(args)
3127 q.setactive(args)
3128 q.savedirty()
3128 q.savedirty()
3129 if not args:
3129 if not args:
3130 ui.status(_('guards deactivated\n'))
3130 ui.status(_('guards deactivated\n'))
3131 if not opts.get('pop') and not opts.get('reapply'):
3131 if not opts.get('pop') and not opts.get('reapply'):
3132 unapplied = q.unapplied(repo)
3132 unapplied = q.unapplied(repo)
3133 guarded = [i for i in xrange(len(q.applied)) if not pushable(i)]
3133 guarded = [i for i in xrange(len(q.applied)) if not pushable(i)]
3134 if len(unapplied) != len(old_unapplied):
3134 if len(unapplied) != len(old_unapplied):
3135 ui.status(_('number of unguarded, unapplied patches has '
3135 ui.status(_('number of unguarded, unapplied patches has '
3136 'changed from %d to %d\n') %
3136 'changed from %d to %d\n') %
3137 (len(old_unapplied), len(unapplied)))
3137 (len(old_unapplied), len(unapplied)))
3138 if len(guarded) != len(old_guarded):
3138 if len(guarded) != len(old_guarded):
3139 ui.status(_('number of guarded, applied patches has changed '
3139 ui.status(_('number of guarded, applied patches has changed '
3140 'from %d to %d\n') %
3140 'from %d to %d\n') %
3141 (len(old_guarded), len(guarded)))
3141 (len(old_guarded), len(guarded)))
3142 elif opts.get('series'):
3142 elif opts.get('series'):
3143 guards = {}
3143 guards = {}
3144 noguards = 0
3144 noguards = 0
3145 for gs in q.seriesguards:
3145 for gs in q.seriesguards:
3146 if not gs:
3146 if not gs:
3147 noguards += 1
3147 noguards += 1
3148 for g in gs:
3148 for g in gs:
3149 guards.setdefault(g, 0)
3149 guards.setdefault(g, 0)
3150 guards[g] += 1
3150 guards[g] += 1
3151 if ui.verbose:
3151 if ui.verbose:
3152 guards['NONE'] = noguards
3152 guards['NONE'] = noguards
3153 guards = guards.items()
3153 guards = guards.items()
3154 guards.sort(key=lambda x: x[0][1:])
3154 guards.sort(key=lambda x: x[0][1:])
3155 if guards:
3155 if guards:
3156 ui.note(_('guards in series file:\n'))
3156 ui.note(_('guards in series file:\n'))
3157 for guard, count in guards:
3157 for guard, count in guards:
3158 ui.note('%2d ' % count)
3158 ui.note('%2d ' % count)
3159 ui.write(guard, '\n')
3159 ui.write(guard, '\n')
3160 else:
3160 else:
3161 ui.note(_('no guards in series file\n'))
3161 ui.note(_('no guards in series file\n'))
3162 else:
3162 else:
3163 if guards:
3163 if guards:
3164 ui.note(_('active guards:\n'))
3164 ui.note(_('active guards:\n'))
3165 for g in guards:
3165 for g in guards:
3166 ui.write(g, '\n')
3166 ui.write(g, '\n')
3167 else:
3167 else:
3168 ui.write(_('no active guards\n'))
3168 ui.write(_('no active guards\n'))
3169 reapply = opts.get('reapply') and q.applied and q.applied[-1].name
3169 reapply = opts.get('reapply') and q.applied and q.applied[-1].name
3170 popped = False
3170 popped = False
3171 if opts.get('pop') or opts.get('reapply'):
3171 if opts.get('pop') or opts.get('reapply'):
3172 for i in xrange(len(q.applied)):
3172 for i in xrange(len(q.applied)):
3173 if not pushable(i):
3173 if not pushable(i):
3174 ui.status(_('popping guarded patches\n'))
3174 ui.status(_('popping guarded patches\n'))
3175 popped = True
3175 popped = True
3176 if i == 0:
3176 if i == 0:
3177 q.pop(repo, all=True)
3177 q.pop(repo, all=True)
3178 else:
3178 else:
3179 q.pop(repo, q.applied[i - 1].name)
3179 q.pop(repo, q.applied[i - 1].name)
3180 break
3180 break
3181 if popped:
3181 if popped:
3182 try:
3182 try:
3183 if reapply:
3183 if reapply:
3184 ui.status(_('reapplying unguarded patches\n'))
3184 ui.status(_('reapplying unguarded patches\n'))
3185 q.push(repo, reapply)
3185 q.push(repo, reapply)
3186 finally:
3186 finally:
3187 q.savedirty()
3187 q.savedirty()
3188
3188
3189 @command("qfinish",
3189 @command("qfinish",
3190 [('a', 'applied', None, _('finish all applied changesets'))],
3190 [('a', 'applied', None, _('finish all applied changesets'))],
3191 _('hg qfinish [-a] [REV]...'))
3191 _('hg qfinish [-a] [REV]...'))
3192 def finish(ui, repo, *revrange, **opts):
3192 def finish(ui, repo, *revrange, **opts):
3193 """move applied patches into repository history
3193 """move applied patches into repository history
3194
3194
3195 Finishes the specified revisions (corresponding to applied
3195 Finishes the specified revisions (corresponding to applied
3196 patches) by moving them out of mq control into regular repository
3196 patches) by moving them out of mq control into regular repository
3197 history.
3197 history.
3198
3198
3199 Accepts a revision range or the -a/--applied option. If --applied
3199 Accepts a revision range or the -a/--applied option. If --applied
3200 is specified, all applied mq revisions are removed from mq
3200 is specified, all applied mq revisions are removed from mq
3201 control. Otherwise, the given revisions must be at the base of the
3201 control. Otherwise, the given revisions must be at the base of the
3202 stack of applied patches.
3202 stack of applied patches.
3203
3203
3204 This can be especially useful if your changes have been applied to
3204 This can be especially useful if your changes have been applied to
3205 an upstream repository, or if you are about to push your changes
3205 an upstream repository, or if you are about to push your changes
3206 to upstream.
3206 to upstream.
3207
3207
3208 Returns 0 on success.
3208 Returns 0 on success.
3209 """
3209 """
3210 if not opts.get('applied') and not revrange:
3210 if not opts.get('applied') and not revrange:
3211 raise error.Abort(_('no revisions specified'))
3211 raise error.Abort(_('no revisions specified'))
3212 elif opts.get('applied'):
3212 elif opts.get('applied'):
3213 revrange = ('qbase::qtip',) + revrange
3213 revrange = ('qbase::qtip',) + revrange
3214
3214
3215 q = repo.mq
3215 q = repo.mq
3216 if not q.applied:
3216 if not q.applied:
3217 ui.status(_('no patches applied\n'))
3217 ui.status(_('no patches applied\n'))
3218 return 0
3218 return 0
3219
3219
3220 revs = scmutil.revrange(repo, revrange)
3220 revs = scmutil.revrange(repo, revrange)
3221 if repo['.'].rev() in revs and repo[None].files():
3221 if repo['.'].rev() in revs and repo[None].files():
3222 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3222 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3223 # queue.finish may changes phases but leave the responsibility to lock the
3223 # queue.finish may changes phases but leave the responsibility to lock the
3224 # repo to the caller to avoid deadlock with wlock. This command code is
3224 # repo to the caller to avoid deadlock with wlock. This command code is
3225 # responsibility for this locking.
3225 # responsibility for this locking.
3226 with repo.lock():
3226 with repo.lock():
3227 q.finish(repo, revs)
3227 q.finish(repo, revs)
3228 q.savedirty()
3228 q.savedirty()
3229 return 0
3229 return 0
3230
3230
3231 @command("qqueue",
3231 @command("qqueue",
3232 [('l', 'list', False, _('list all available queues')),
3232 [('l', 'list', False, _('list all available queues')),
3233 ('', 'active', False, _('print name of active queue')),
3233 ('', 'active', False, _('print name of active queue')),
3234 ('c', 'create', False, _('create new queue')),
3234 ('c', 'create', False, _('create new queue')),
3235 ('', 'rename', False, _('rename active queue')),
3235 ('', 'rename', False, _('rename active queue')),
3236 ('', 'delete', False, _('delete reference to queue')),
3236 ('', 'delete', False, _('delete reference to queue')),
3237 ('', 'purge', False, _('delete queue, and remove patch dir')),
3237 ('', 'purge', False, _('delete queue, and remove patch dir')),
3238 ],
3238 ],
3239 _('[OPTION] [QUEUE]'))
3239 _('[OPTION] [QUEUE]'))
3240 def qqueue(ui, repo, name=None, **opts):
3240 def qqueue(ui, repo, name=None, **opts):
3241 '''manage multiple patch queues
3241 '''manage multiple patch queues
3242
3242
3243 Supports switching between different patch queues, as well as creating
3243 Supports switching between different patch queues, as well as creating
3244 new patch queues and deleting existing ones.
3244 new patch queues and deleting existing ones.
3245
3245
3246 Omitting a queue name or specifying -l/--list will show you the registered
3246 Omitting a queue name or specifying -l/--list will show you the registered
3247 queues - by default the "normal" patches queue is registered. The currently
3247 queues - by default the "normal" patches queue is registered. The currently
3248 active queue will be marked with "(active)". Specifying --active will print
3248 active queue will be marked with "(active)". Specifying --active will print
3249 only the name of the active queue.
3249 only the name of the active queue.
3250
3250
3251 To create a new queue, use -c/--create. The queue is automatically made
3251 To create a new queue, use -c/--create. The queue is automatically made
3252 active, except in the case where there are applied patches from the
3252 active, except in the case where there are applied patches from the
3253 currently active queue in the repository. Then the queue will only be
3253 currently active queue in the repository. Then the queue will only be
3254 created and switching will fail.
3254 created and switching will fail.
3255
3255
3256 To delete an existing queue, use --delete. You cannot delete the currently
3256 To delete an existing queue, use --delete. You cannot delete the currently
3257 active queue.
3257 active queue.
3258
3258
3259 Returns 0 on success.
3259 Returns 0 on success.
3260 '''
3260 '''
3261 q = repo.mq
3261 q = repo.mq
3262 _defaultqueue = 'patches'
3262 _defaultqueue = 'patches'
3263 _allqueues = 'patches.queues'
3263 _allqueues = 'patches.queues'
3264 _activequeue = 'patches.queue'
3264 _activequeue = 'patches.queue'
3265
3265
3266 def _getcurrent():
3266 def _getcurrent():
3267 cur = os.path.basename(q.path)
3267 cur = os.path.basename(q.path)
3268 if cur.startswith('patches-'):
3268 if cur.startswith('patches-'):
3269 cur = cur[8:]
3269 cur = cur[8:]
3270 return cur
3270 return cur
3271
3271
3272 def _noqueues():
3272 def _noqueues():
3273 try:
3273 try:
3274 fh = repo.vfs(_allqueues, 'r')
3274 fh = repo.vfs(_allqueues, 'r')
3275 fh.close()
3275 fh.close()
3276 except IOError:
3276 except IOError:
3277 return True
3277 return True
3278
3278
3279 return False
3279 return False
3280
3280
3281 def _getqueues():
3281 def _getqueues():
3282 current = _getcurrent()
3282 current = _getcurrent()
3283
3283
3284 try:
3284 try:
3285 fh = repo.vfs(_allqueues, 'r')
3285 fh = repo.vfs(_allqueues, 'r')
3286 queues = [queue.strip() for queue in fh if queue.strip()]
3286 queues = [queue.strip() for queue in fh if queue.strip()]
3287 fh.close()
3287 fh.close()
3288 if current not in queues:
3288 if current not in queues:
3289 queues.append(current)
3289 queues.append(current)
3290 except IOError:
3290 except IOError:
3291 queues = [_defaultqueue]
3291 queues = [_defaultqueue]
3292
3292
3293 return sorted(queues)
3293 return sorted(queues)
3294
3294
3295 def _setactive(name):
3295 def _setactive(name):
3296 if q.applied:
3296 if q.applied:
3297 raise error.Abort(_('new queue created, but cannot make active '
3297 raise error.Abort(_('new queue created, but cannot make active '
3298 'as patches are applied'))
3298 'as patches are applied'))
3299 _setactivenocheck(name)
3299 _setactivenocheck(name)
3300
3300
3301 def _setactivenocheck(name):
3301 def _setactivenocheck(name):
3302 fh = repo.vfs(_activequeue, 'w')
3302 fh = repo.vfs(_activequeue, 'w')
3303 if name != 'patches':
3303 if name != 'patches':
3304 fh.write(name)
3304 fh.write(name)
3305 fh.close()
3305 fh.close()
3306
3306
3307 def _addqueue(name):
3307 def _addqueue(name):
3308 fh = repo.vfs(_allqueues, 'a')
3308 fh = repo.vfs(_allqueues, 'a')
3309 fh.write('%s\n' % (name,))
3309 fh.write('%s\n' % (name,))
3310 fh.close()
3310 fh.close()
3311
3311
3312 def _queuedir(name):
3312 def _queuedir(name):
3313 if name == 'patches':
3313 if name == 'patches':
3314 return repo.vfs.join('patches')
3314 return repo.vfs.join('patches')
3315 else:
3315 else:
3316 return repo.vfs.join('patches-' + name)
3316 return repo.vfs.join('patches-' + name)
3317
3317
3318 def _validname(name):
3318 def _validname(name):
3319 for n in name:
3319 for n in name:
3320 if n in ':\\/.':
3320 if n in ':\\/.':
3321 return False
3321 return False
3322 return True
3322 return True
3323
3323
3324 def _delete(name):
3324 def _delete(name):
3325 if name not in existing:
3325 if name not in existing:
3326 raise error.Abort(_('cannot delete queue that does not exist'))
3326 raise error.Abort(_('cannot delete queue that does not exist'))
3327
3327
3328 current = _getcurrent()
3328 current = _getcurrent()
3329
3329
3330 if name == current:
3330 if name == current:
3331 raise error.Abort(_('cannot delete currently active queue'))
3331 raise error.Abort(_('cannot delete currently active queue'))
3332
3332
3333 fh = repo.vfs('patches.queues.new', 'w')
3333 fh = repo.vfs('patches.queues.new', 'w')
3334 for queue in existing:
3334 for queue in existing:
3335 if queue == name:
3335 if queue == name:
3336 continue
3336 continue
3337 fh.write('%s\n' % (queue,))
3337 fh.write('%s\n' % (queue,))
3338 fh.close()
3338 fh.close()
3339 repo.vfs.rename('patches.queues.new', _allqueues)
3339 repo.vfs.rename('patches.queues.new', _allqueues)
3340
3340
3341 if not name or opts.get('list') or opts.get('active'):
3341 if not name or opts.get('list') or opts.get('active'):
3342 current = _getcurrent()
3342 current = _getcurrent()
3343 if opts.get('active'):
3343 if opts.get('active'):
3344 ui.write('%s\n' % (current,))
3344 ui.write('%s\n' % (current,))
3345 return
3345 return
3346 for queue in _getqueues():
3346 for queue in _getqueues():
3347 ui.write('%s' % (queue,))
3347 ui.write('%s' % (queue,))
3348 if queue == current and not ui.quiet:
3348 if queue == current and not ui.quiet:
3349 ui.write(_(' (active)\n'))
3349 ui.write(_(' (active)\n'))
3350 else:
3350 else:
3351 ui.write('\n')
3351 ui.write('\n')
3352 return
3352 return
3353
3353
3354 if not _validname(name):
3354 if not _validname(name):
3355 raise error.Abort(
3355 raise error.Abort(
3356 _('invalid queue name, may not contain the characters ":\\/."'))
3356 _('invalid queue name, may not contain the characters ":\\/."'))
3357
3357
3358 with repo.wlock():
3358 with repo.wlock():
3359 existing = _getqueues()
3359 existing = _getqueues()
3360
3360
3361 if opts.get('create'):
3361 if opts.get('create'):
3362 if name in existing:
3362 if name in existing:
3363 raise error.Abort(_('queue "%s" already exists') % name)
3363 raise error.Abort(_('queue "%s" already exists') % name)
3364 if _noqueues():
3364 if _noqueues():
3365 _addqueue(_defaultqueue)
3365 _addqueue(_defaultqueue)
3366 _addqueue(name)
3366 _addqueue(name)
3367 _setactive(name)
3367 _setactive(name)
3368 elif opts.get('rename'):
3368 elif opts.get('rename'):
3369 current = _getcurrent()
3369 current = _getcurrent()
3370 if name == current:
3370 if name == current:
3371 raise error.Abort(_('can\'t rename "%s" to its current name')
3371 raise error.Abort(_('can\'t rename "%s" to its current name')
3372 % name)
3372 % name)
3373 if name in existing:
3373 if name in existing:
3374 raise error.Abort(_('queue "%s" already exists') % name)
3374 raise error.Abort(_('queue "%s" already exists') % name)
3375
3375
3376 olddir = _queuedir(current)
3376 olddir = _queuedir(current)
3377 newdir = _queuedir(name)
3377 newdir = _queuedir(name)
3378
3378
3379 if os.path.exists(newdir):
3379 if os.path.exists(newdir):
3380 raise error.Abort(_('non-queue directory "%s" already exists') %
3380 raise error.Abort(_('non-queue directory "%s" already exists') %
3381 newdir)
3381 newdir)
3382
3382
3383 fh = repo.vfs('patches.queues.new', 'w')
3383 fh = repo.vfs('patches.queues.new', 'w')
3384 for queue in existing:
3384 for queue in existing:
3385 if queue == current:
3385 if queue == current:
3386 fh.write('%s\n' % (name,))
3386 fh.write('%s\n' % (name,))
3387 if os.path.exists(olddir):
3387 if os.path.exists(olddir):
3388 util.rename(olddir, newdir)
3388 util.rename(olddir, newdir)
3389 else:
3389 else:
3390 fh.write('%s\n' % (queue,))
3390 fh.write('%s\n' % (queue,))
3391 fh.close()
3391 fh.close()
3392 repo.vfs.rename('patches.queues.new', _allqueues)
3392 repo.vfs.rename('patches.queues.new', _allqueues)
3393 _setactivenocheck(name)
3393 _setactivenocheck(name)
3394 elif opts.get('delete'):
3394 elif opts.get('delete'):
3395 _delete(name)
3395 _delete(name)
3396 elif opts.get('purge'):
3396 elif opts.get('purge'):
3397 if name in existing:
3397 if name in existing:
3398 _delete(name)
3398 _delete(name)
3399 qdir = _queuedir(name)
3399 qdir = _queuedir(name)
3400 if os.path.exists(qdir):
3400 if os.path.exists(qdir):
3401 shutil.rmtree(qdir)
3401 shutil.rmtree(qdir)
3402 else:
3402 else:
3403 if name not in existing:
3403 if name not in existing:
3404 raise error.Abort(_('use --create to create a new queue'))
3404 raise error.Abort(_('use --create to create a new queue'))
3405 _setactive(name)
3405 _setactive(name)
3406
3406
3407 def mqphasedefaults(repo, roots):
3407 def mqphasedefaults(repo, roots):
3408 """callback used to set mq changeset as secret when no phase data exists"""
3408 """callback used to set mq changeset as secret when no phase data exists"""
3409 if repo.mq.applied:
3409 if repo.mq.applied:
3410 if repo.ui.configbool('mq', 'secret', False):
3410 if repo.ui.configbool('mq', 'secret', False):
3411 mqphase = phases.secret
3411 mqphase = phases.secret
3412 else:
3412 else:
3413 mqphase = phases.draft
3413 mqphase = phases.draft
3414 qbase = repo[repo.mq.applied[0].node]
3414 qbase = repo[repo.mq.applied[0].node]
3415 roots[mqphase].add(qbase.node())
3415 roots[mqphase].add(qbase.node())
3416 return roots
3416 return roots
3417
3417
3418 def reposetup(ui, repo):
3418 def reposetup(ui, repo):
3419 class mqrepo(repo.__class__):
3419 class mqrepo(repo.__class__):
3420 @localrepo.unfilteredpropertycache
3420 @localrepo.unfilteredpropertycache
3421 def mq(self):
3421 def mq(self):
3422 return queue(self.ui, self.baseui, self.path)
3422 return queue(self.ui, self.baseui, self.path)
3423
3423
3424 def invalidateall(self):
3424 def invalidateall(self):
3425 super(mqrepo, self).invalidateall()
3425 super(mqrepo, self).invalidateall()
3426 if localrepo.hasunfilteredcache(self, 'mq'):
3426 if localrepo.hasunfilteredcache(self, 'mq'):
3427 # recreate mq in case queue path was changed
3427 # recreate mq in case queue path was changed
3428 delattr(self.unfiltered(), 'mq')
3428 delattr(self.unfiltered(), 'mq')
3429
3429
3430 def abortifwdirpatched(self, errmsg, force=False):
3430 def abortifwdirpatched(self, errmsg, force=False):
3431 if self.mq.applied and self.mq.checkapplied and not force:
3431 if self.mq.applied and self.mq.checkapplied and not force:
3432 parents = self.dirstate.parents()
3432 parents = self.dirstate.parents()
3433 patches = [s.node for s in self.mq.applied]
3433 patches = [s.node for s in self.mq.applied]
3434 if parents[0] in patches or parents[1] in patches:
3434 if parents[0] in patches or parents[1] in patches:
3435 raise error.Abort(errmsg)
3435 raise error.Abort(errmsg)
3436
3436
3437 def commit(self, text="", user=None, date=None, match=None,
3437 def commit(self, text="", user=None, date=None, match=None,
3438 force=False, editor=False, extra=None):
3438 force=False, editor=False, extra=None):
3439 if extra is None:
3439 if extra is None:
3440 extra = {}
3440 extra = {}
3441 self.abortifwdirpatched(
3441 self.abortifwdirpatched(
3442 _('cannot commit over an applied mq patch'),
3442 _('cannot commit over an applied mq patch'),
3443 force)
3443 force)
3444
3444
3445 return super(mqrepo, self).commit(text, user, date, match, force,
3445 return super(mqrepo, self).commit(text, user, date, match, force,
3446 editor, extra)
3446 editor, extra)
3447
3447
3448 def checkpush(self, pushop):
3448 def checkpush(self, pushop):
3449 if self.mq.applied and self.mq.checkapplied and not pushop.force:
3449 if self.mq.applied and self.mq.checkapplied and not pushop.force:
3450 outapplied = [e.node for e in self.mq.applied]
3450 outapplied = [e.node for e in self.mq.applied]
3451 if pushop.revs:
3451 if pushop.revs:
3452 # Assume applied patches have no non-patch descendants and
3452 # Assume applied patches have no non-patch descendants and
3453 # are not on remote already. Filtering any changeset not
3453 # are not on remote already. Filtering any changeset not
3454 # pushed.
3454 # pushed.
3455 heads = set(pushop.revs)
3455 heads = set(pushop.revs)
3456 for node in reversed(outapplied):
3456 for node in reversed(outapplied):
3457 if node in heads:
3457 if node in heads:
3458 break
3458 break
3459 else:
3459 else:
3460 outapplied.pop()
3460 outapplied.pop()
3461 # looking for pushed and shared changeset
3461 # looking for pushed and shared changeset
3462 for node in outapplied:
3462 for node in outapplied:
3463 if self[node].phase() < phases.secret:
3463 if self[node].phase() < phases.secret:
3464 raise error.Abort(_('source has mq patches applied'))
3464 raise error.Abort(_('source has mq patches applied'))
3465 # no non-secret patches pushed
3465 # no non-secret patches pushed
3466 super(mqrepo, self).checkpush(pushop)
3466 super(mqrepo, self).checkpush(pushop)
3467
3467
3468 def _findtags(self):
3468 def _findtags(self):
3469 '''augment tags from base class with patch tags'''
3469 '''augment tags from base class with patch tags'''
3470 result = super(mqrepo, self)._findtags()
3470 result = super(mqrepo, self)._findtags()
3471
3471
3472 q = self.mq
3472 q = self.mq
3473 if not q.applied:
3473 if not q.applied:
3474 return result
3474 return result
3475
3475
3476 mqtags = [(patch.node, patch.name) for patch in q.applied]
3476 mqtags = [(patch.node, patch.name) for patch in q.applied]
3477
3477
3478 try:
3478 try:
3479 # for now ignore filtering business
3479 # for now ignore filtering business
3480 self.unfiltered().changelog.rev(mqtags[-1][0])
3480 self.unfiltered().changelog.rev(mqtags[-1][0])
3481 except error.LookupError:
3481 except error.LookupError:
3482 self.ui.warn(_('mq status file refers to unknown node %s\n')
3482 self.ui.warn(_('mq status file refers to unknown node %s\n')
3483 % short(mqtags[-1][0]))
3483 % short(mqtags[-1][0]))
3484 return result
3484 return result
3485
3485
3486 # do not add fake tags for filtered revisions
3486 # do not add fake tags for filtered revisions
3487 included = self.changelog.hasnode
3487 included = self.changelog.hasnode
3488 mqtags = [mqt for mqt in mqtags if included(mqt[0])]
3488 mqtags = [mqt for mqt in mqtags if included(mqt[0])]
3489 if not mqtags:
3489 if not mqtags:
3490 return result
3490 return result
3491
3491
3492 mqtags.append((mqtags[-1][0], 'qtip'))
3492 mqtags.append((mqtags[-1][0], 'qtip'))
3493 mqtags.append((mqtags[0][0], 'qbase'))
3493 mqtags.append((mqtags[0][0], 'qbase'))
3494 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3494 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3495 tags = result[0]
3495 tags = result[0]
3496 for patch in mqtags:
3496 for patch in mqtags:
3497 if patch[1] in tags:
3497 if patch[1] in tags:
3498 self.ui.warn(_('tag %s overrides mq patch of the same '
3498 self.ui.warn(_('tag %s overrides mq patch of the same '
3499 'name\n') % patch[1])
3499 'name\n') % patch[1])
3500 else:
3500 else:
3501 tags[patch[1]] = patch[0]
3501 tags[patch[1]] = patch[0]
3502
3502
3503 return result
3503 return result
3504
3504
3505 if repo.local():
3505 if repo.local():
3506 repo.__class__ = mqrepo
3506 repo.__class__ = mqrepo
3507
3507
3508 repo._phasedefaults.append(mqphasedefaults)
3508 repo._phasedefaults.append(mqphasedefaults)
3509
3509
3510 def mqimport(orig, ui, repo, *args, **kwargs):
3510 def mqimport(orig, ui, repo, *args, **kwargs):
3511 if (util.safehasattr(repo, 'abortifwdirpatched')
3511 if (util.safehasattr(repo, 'abortifwdirpatched')
3512 and not kwargs.get('no_commit', False)):
3512 and not kwargs.get('no_commit', False)):
3513 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3513 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3514 kwargs.get('force'))
3514 kwargs.get('force'))
3515 return orig(ui, repo, *args, **kwargs)
3515 return orig(ui, repo, *args, **kwargs)
3516
3516
3517 def mqinit(orig, ui, *args, **kwargs):
3517 def mqinit(orig, ui, *args, **kwargs):
3518 mq = kwargs.pop('mq', None)
3518 mq = kwargs.pop('mq', None)
3519
3519
3520 if not mq:
3520 if not mq:
3521 return orig(ui, *args, **kwargs)
3521 return orig(ui, *args, **kwargs)
3522
3522
3523 if args:
3523 if args:
3524 repopath = args[0]
3524 repopath = args[0]
3525 if not hg.islocal(repopath):
3525 if not hg.islocal(repopath):
3526 raise error.Abort(_('only a local queue repository '
3526 raise error.Abort(_('only a local queue repository '
3527 'may be initialized'))
3527 'may be initialized'))
3528 else:
3528 else:
3529 repopath = cmdutil.findrepo(pycompat.getcwd())
3529 repopath = cmdutil.findrepo(pycompat.getcwd())
3530 if not repopath:
3530 if not repopath:
3531 raise error.Abort(_('there is no Mercurial repository here '
3531 raise error.Abort(_('there is no Mercurial repository here '
3532 '(.hg not found)'))
3532 '(.hg not found)'))
3533 repo = hg.repository(ui, repopath)
3533 repo = hg.repository(ui, repopath)
3534 return qinit(ui, repo, True)
3534 return qinit(ui, repo, True)
3535
3535
3536 def mqcommand(orig, ui, repo, *args, **kwargs):
3536 def mqcommand(orig, ui, repo, *args, **kwargs):
3537 """Add --mq option to operate on patch repository instead of main"""
3537 """Add --mq option to operate on patch repository instead of main"""
3538
3538
3539 # some commands do not like getting unknown options
3539 # some commands do not like getting unknown options
3540 mq = kwargs.pop(r'mq', None)
3540 mq = kwargs.pop(r'mq', None)
3541
3541
3542 if not mq:
3542 if not mq:
3543 return orig(ui, repo, *args, **kwargs)
3543 return orig(ui, repo, *args, **kwargs)
3544
3544
3545 q = repo.mq
3545 q = repo.mq
3546 r = q.qrepo()
3546 r = q.qrepo()
3547 if not r:
3547 if not r:
3548 raise error.Abort(_('no queue repository'))
3548 raise error.Abort(_('no queue repository'))
3549 return orig(r.ui, r, *args, **kwargs)
3549 return orig(r.ui, r, *args, **kwargs)
3550
3550
3551 def summaryhook(ui, repo):
3551 def summaryhook(ui, repo):
3552 q = repo.mq
3552 q = repo.mq
3553 m = []
3553 m = []
3554 a, u = len(q.applied), len(q.unapplied(repo))
3554 a, u = len(q.applied), len(q.unapplied(repo))
3555 if a:
3555 if a:
3556 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3556 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3557 if u:
3557 if u:
3558 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3558 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3559 if m:
3559 if m:
3560 # i18n: column positioning for "hg summary"
3560 # i18n: column positioning for "hg summary"
3561 ui.write(_("mq: %s\n") % ', '.join(m))
3561 ui.write(_("mq: %s\n") % ', '.join(m))
3562 else:
3562 else:
3563 # i18n: column positioning for "hg summary"
3563 # i18n: column positioning for "hg summary"
3564 ui.note(_("mq: (empty queue)\n"))
3564 ui.note(_("mq: (empty queue)\n"))
3565
3565
3566 revsetpredicate = registrar.revsetpredicate()
3566 revsetpredicate = registrar.revsetpredicate()
3567
3567
3568 @revsetpredicate('mq()')
3568 @revsetpredicate('mq()')
3569 def revsetmq(repo, subset, x):
3569 def revsetmq(repo, subset, x):
3570 """Changesets managed by MQ.
3570 """Changesets managed by MQ.
3571 """
3571 """
3572 revsetlang.getargs(x, 0, 0, _("mq takes no arguments"))
3572 revsetlang.getargs(x, 0, 0, _("mq takes no arguments"))
3573 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3573 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3574 return smartset.baseset([r for r in subset if r in applied])
3574 return smartset.baseset([r for r in subset if r in applied])
3575
3575
3576 # tell hggettext to extract docstrings from these functions:
3576 # tell hggettext to extract docstrings from these functions:
3577 i18nfunctions = [revsetmq]
3577 i18nfunctions = [revsetmq]
3578
3578
3579 def extsetup(ui):
3579 def extsetup(ui):
3580 # Ensure mq wrappers are called first, regardless of extension load order by
3580 # Ensure mq wrappers are called first, regardless of extension load order by
3581 # NOT wrapping in uisetup() and instead deferring to init stage two here.
3581 # NOT wrapping in uisetup() and instead deferring to init stage two here.
3582 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3582 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3583
3583
3584 extensions.wrapcommand(commands.table, 'import', mqimport)
3584 extensions.wrapcommand(commands.table, 'import', mqimport)
3585 cmdutil.summaryhooks.add('mq', summaryhook)
3585 cmdutil.summaryhooks.add('mq', summaryhook)
3586
3586
3587 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3587 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3588 entry[1].extend(mqopt)
3588 entry[1].extend(mqopt)
3589
3589
3590 def dotable(cmdtable):
3590 def dotable(cmdtable):
3591 for cmd, entry in cmdtable.iteritems():
3591 for cmd, entry in cmdtable.iteritems():
3592 cmd = cmdutil.parsealiases(cmd)[0]
3592 cmd = cmdutil.parsealiases(cmd)[0]
3593 func = entry[0]
3593 func = entry[0]
3594 if func.norepo:
3594 if func.norepo:
3595 continue
3595 continue
3596 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3596 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3597 entry[1].extend(mqopt)
3597 entry[1].extend(mqopt)
3598
3598
3599 dotable(commands.table)
3599 dotable(commands.table)
3600
3600
3601 for extname, extmodule in extensions.extensions():
3601 for extname, extmodule in extensions.extensions():
3602 if extmodule.__file__ != __file__:
3602 if extmodule.__file__ != __file__:
3603 dotable(getattr(extmodule, 'cmdtable', {}))
3603 dotable(getattr(extmodule, 'cmdtable', {}))
3604
3604
3605 colortable = {'qguard.negative': 'red',
3605 colortable = {'qguard.negative': 'red',
3606 'qguard.positive': 'yellow',
3606 'qguard.positive': 'yellow',
3607 'qguard.unguarded': 'green',
3607 'qguard.unguarded': 'green',
3608 'qseries.applied': 'blue bold underline',
3608 'qseries.applied': 'blue bold underline',
3609 'qseries.guarded': 'black bold',
3609 'qseries.guarded': 'black bold',
3610 'qseries.missing': 'red bold',
3610 'qseries.missing': 'red bold',
3611 'qseries.unapplied': 'black bold'}
3611 'qseries.unapplied': 'black bold'}
@@ -1,3865 +1,3865 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in 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 errno
10 import errno
11 import itertools
11 import itertools
12 import os
12 import os
13 import re
13 import re
14 import tempfile
14 import tempfile
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 hex,
18 hex,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 short,
21 short,
22 )
22 )
23
23
24 from . import (
24 from . import (
25 bookmarks,
25 bookmarks,
26 changelog,
26 changelog,
27 copies,
27 copies,
28 crecord as crecordmod,
28 crecord as crecordmod,
29 dirstateguard,
29 dirstateguard,
30 encoding,
30 encoding,
31 error,
31 error,
32 formatter,
32 formatter,
33 graphmod,
33 graphmod,
34 match as matchmod,
34 match as matchmod,
35 obsolete,
35 obsolete,
36 patch,
36 patch,
37 pathutil,
37 pathutil,
38 pycompat,
38 pycompat,
39 registrar,
39 registrar,
40 revlog,
40 revlog,
41 revset,
41 revset,
42 scmutil,
42 scmutil,
43 smartset,
43 smartset,
44 templatekw,
44 templatekw,
45 templater,
45 templater,
46 util,
46 util,
47 vfs as vfsmod,
47 vfs as vfsmod,
48 )
48 )
49 stringio = util.stringio
49 stringio = util.stringio
50
50
51 # templates of common command options
51 # templates of common command options
52
52
53 dryrunopts = [
53 dryrunopts = [
54 ('n', 'dry-run', None,
54 ('n', 'dry-run', None,
55 _('do not perform actions, just print output')),
55 _('do not perform actions, just print output')),
56 ]
56 ]
57
57
58 remoteopts = [
58 remoteopts = [
59 ('e', 'ssh', '',
59 ('e', 'ssh', '',
60 _('specify ssh command to use'), _('CMD')),
60 _('specify ssh command to use'), _('CMD')),
61 ('', 'remotecmd', '',
61 ('', 'remotecmd', '',
62 _('specify hg command to run on the remote side'), _('CMD')),
62 _('specify hg command to run on the remote side'), _('CMD')),
63 ('', 'insecure', None,
63 ('', 'insecure', None,
64 _('do not verify server certificate (ignoring web.cacerts config)')),
64 _('do not verify server certificate (ignoring web.cacerts config)')),
65 ]
65 ]
66
66
67 walkopts = [
67 walkopts = [
68 ('I', 'include', [],
68 ('I', 'include', [],
69 _('include names matching the given patterns'), _('PATTERN')),
69 _('include names matching the given patterns'), _('PATTERN')),
70 ('X', 'exclude', [],
70 ('X', 'exclude', [],
71 _('exclude names matching the given patterns'), _('PATTERN')),
71 _('exclude names matching the given patterns'), _('PATTERN')),
72 ]
72 ]
73
73
74 commitopts = [
74 commitopts = [
75 ('m', 'message', '',
75 ('m', 'message', '',
76 _('use text as commit message'), _('TEXT')),
76 _('use text as commit message'), _('TEXT')),
77 ('l', 'logfile', '',
77 ('l', 'logfile', '',
78 _('read commit message from file'), _('FILE')),
78 _('read commit message from file'), _('FILE')),
79 ]
79 ]
80
80
81 commitopts2 = [
81 commitopts2 = [
82 ('d', 'date', '',
82 ('d', 'date', '',
83 _('record the specified date as commit date'), _('DATE')),
83 _('record the specified date as commit date'), _('DATE')),
84 ('u', 'user', '',
84 ('u', 'user', '',
85 _('record the specified user as committer'), _('USER')),
85 _('record the specified user as committer'), _('USER')),
86 ]
86 ]
87
87
88 # hidden for now
88 # hidden for now
89 formatteropts = [
89 formatteropts = [
90 ('T', 'template', '',
90 ('T', 'template', '',
91 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
91 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
92 ]
92 ]
93
93
94 templateopts = [
94 templateopts = [
95 ('', 'style', '',
95 ('', 'style', '',
96 _('display using template map file (DEPRECATED)'), _('STYLE')),
96 _('display using template map file (DEPRECATED)'), _('STYLE')),
97 ('T', 'template', '',
97 ('T', 'template', '',
98 _('display with template'), _('TEMPLATE')),
98 _('display with template'), _('TEMPLATE')),
99 ]
99 ]
100
100
101 logopts = [
101 logopts = [
102 ('p', 'patch', None, _('show patch')),
102 ('p', 'patch', None, _('show patch')),
103 ('g', 'git', None, _('use git extended diff format')),
103 ('g', 'git', None, _('use git extended diff format')),
104 ('l', 'limit', '',
104 ('l', 'limit', '',
105 _('limit number of changes displayed'), _('NUM')),
105 _('limit number of changes displayed'), _('NUM')),
106 ('M', 'no-merges', None, _('do not show merges')),
106 ('M', 'no-merges', None, _('do not show merges')),
107 ('', 'stat', None, _('output diffstat-style summary of changes')),
107 ('', 'stat', None, _('output diffstat-style summary of changes')),
108 ('G', 'graph', None, _("show the revision DAG")),
108 ('G', 'graph', None, _("show the revision DAG")),
109 ] + templateopts
109 ] + templateopts
110
110
111 diffopts = [
111 diffopts = [
112 ('a', 'text', None, _('treat all files as text')),
112 ('a', 'text', None, _('treat all files as text')),
113 ('g', 'git', None, _('use git extended diff format')),
113 ('g', 'git', None, _('use git extended diff format')),
114 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
114 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
115 ('', 'nodates', None, _('omit dates from diff headers'))
115 ('', 'nodates', None, _('omit dates from diff headers'))
116 ]
116 ]
117
117
118 diffwsopts = [
118 diffwsopts = [
119 ('w', 'ignore-all-space', None,
119 ('w', 'ignore-all-space', None,
120 _('ignore white space when comparing lines')),
120 _('ignore white space when comparing lines')),
121 ('b', 'ignore-space-change', None,
121 ('b', 'ignore-space-change', None,
122 _('ignore changes in the amount of white space')),
122 _('ignore changes in the amount of white space')),
123 ('B', 'ignore-blank-lines', None,
123 ('B', 'ignore-blank-lines', None,
124 _('ignore changes whose lines are all blank')),
124 _('ignore changes whose lines are all blank')),
125 ('Z', 'ignore-space-at-eol', None,
125 ('Z', 'ignore-space-at-eol', None,
126 _('ignore changes in whitespace at EOL')),
126 _('ignore changes in whitespace at EOL')),
127 ]
127 ]
128
128
129 diffopts2 = [
129 diffopts2 = [
130 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
130 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
131 ('p', 'show-function', None, _('show which function each change is in')),
131 ('p', 'show-function', None, _('show which function each change is in')),
132 ('', 'reverse', None, _('produce a diff that undoes the changes')),
132 ('', 'reverse', None, _('produce a diff that undoes the changes')),
133 ] + diffwsopts + [
133 ] + diffwsopts + [
134 ('U', 'unified', '',
134 ('U', 'unified', '',
135 _('number of lines of context to show'), _('NUM')),
135 _('number of lines of context to show'), _('NUM')),
136 ('', 'stat', None, _('output diffstat-style summary of changes')),
136 ('', 'stat', None, _('output diffstat-style summary of changes')),
137 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
137 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
138 ]
138 ]
139
139
140 mergetoolopts = [
140 mergetoolopts = [
141 ('t', 'tool', '', _('specify merge tool')),
141 ('t', 'tool', '', _('specify merge tool')),
142 ]
142 ]
143
143
144 similarityopts = [
144 similarityopts = [
145 ('s', 'similarity', '',
145 ('s', 'similarity', '',
146 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
146 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
147 ]
147 ]
148
148
149 subrepoopts = [
149 subrepoopts = [
150 ('S', 'subrepos', None,
150 ('S', 'subrepos', None,
151 _('recurse into subrepositories'))
151 _('recurse into subrepositories'))
152 ]
152 ]
153
153
154 debugrevlogopts = [
154 debugrevlogopts = [
155 ('c', 'changelog', False, _('open changelog')),
155 ('c', 'changelog', False, _('open changelog')),
156 ('m', 'manifest', False, _('open manifest')),
156 ('m', 'manifest', False, _('open manifest')),
157 ('', 'dir', '', _('open directory manifest')),
157 ('', 'dir', '', _('open directory manifest')),
158 ]
158 ]
159
159
160 # special string such that everything below this line will be ingored in the
160 # special string such that everything below this line will be ingored in the
161 # editor text
161 # editor text
162 _linebelow = "^HG: ------------------------ >8 ------------------------$"
162 _linebelow = "^HG: ------------------------ >8 ------------------------$"
163
163
164 def ishunk(x):
164 def ishunk(x):
165 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
165 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
166 return isinstance(x, hunkclasses)
166 return isinstance(x, hunkclasses)
167
167
168 def newandmodified(chunks, originalchunks):
168 def newandmodified(chunks, originalchunks):
169 newlyaddedandmodifiedfiles = set()
169 newlyaddedandmodifiedfiles = set()
170 for chunk in chunks:
170 for chunk in chunks:
171 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
171 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
172 originalchunks:
172 originalchunks:
173 newlyaddedandmodifiedfiles.add(chunk.header.filename())
173 newlyaddedandmodifiedfiles.add(chunk.header.filename())
174 return newlyaddedandmodifiedfiles
174 return newlyaddedandmodifiedfiles
175
175
176 def parsealiases(cmd):
176 def parsealiases(cmd):
177 return cmd.lstrip("^").split("|")
177 return cmd.lstrip("^").split("|")
178
178
179 def setupwrapcolorwrite(ui):
179 def setupwrapcolorwrite(ui):
180 # wrap ui.write so diff output can be labeled/colorized
180 # wrap ui.write so diff output can be labeled/colorized
181 def wrapwrite(orig, *args, **kw):
181 def wrapwrite(orig, *args, **kw):
182 label = kw.pop('label', '')
182 label = kw.pop('label', '')
183 for chunk, l in patch.difflabel(lambda: args):
183 for chunk, l in patch.difflabel(lambda: args):
184 orig(chunk, label=label + l)
184 orig(chunk, label=label + l)
185
185
186 oldwrite = ui.write
186 oldwrite = ui.write
187 def wrap(*args, **kwargs):
187 def wrap(*args, **kwargs):
188 return wrapwrite(oldwrite, *args, **kwargs)
188 return wrapwrite(oldwrite, *args, **kwargs)
189 setattr(ui, 'write', wrap)
189 setattr(ui, 'write', wrap)
190 return oldwrite
190 return oldwrite
191
191
192 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
192 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
193 if usecurses:
193 if usecurses:
194 if testfile:
194 if testfile:
195 recordfn = crecordmod.testdecorator(testfile,
195 recordfn = crecordmod.testdecorator(testfile,
196 crecordmod.testchunkselector)
196 crecordmod.testchunkselector)
197 else:
197 else:
198 recordfn = crecordmod.chunkselector
198 recordfn = crecordmod.chunkselector
199
199
200 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
200 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
201
201
202 else:
202 else:
203 return patch.filterpatch(ui, originalhunks, operation)
203 return patch.filterpatch(ui, originalhunks, operation)
204
204
205 def recordfilter(ui, originalhunks, operation=None):
205 def recordfilter(ui, originalhunks, operation=None):
206 """ Prompts the user to filter the originalhunks and return a list of
206 """ Prompts the user to filter the originalhunks and return a list of
207 selected hunks.
207 selected hunks.
208 *operation* is used for to build ui messages to indicate the user what
208 *operation* is used for to build ui messages to indicate the user what
209 kind of filtering they are doing: reverting, committing, shelving, etc.
209 kind of filtering they are doing: reverting, committing, shelving, etc.
210 (see patch.filterpatch).
210 (see patch.filterpatch).
211 """
211 """
212 usecurses = crecordmod.checkcurses(ui)
212 usecurses = crecordmod.checkcurses(ui)
213 testfile = ui.config('experimental', 'crecordtest')
213 testfile = ui.config('experimental', 'crecordtest')
214 oldwrite = setupwrapcolorwrite(ui)
214 oldwrite = setupwrapcolorwrite(ui)
215 try:
215 try:
216 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
216 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
217 testfile, operation)
217 testfile, operation)
218 finally:
218 finally:
219 ui.write = oldwrite
219 ui.write = oldwrite
220 return newchunks, newopts
220 return newchunks, newopts
221
221
222 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
222 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
223 filterfn, *pats, **opts):
223 filterfn, *pats, **opts):
224 from . import merge as mergemod
224 from . import merge as mergemod
225 opts = pycompat.byteskwargs(opts)
225 opts = pycompat.byteskwargs(opts)
226 if not ui.interactive():
226 if not ui.interactive():
227 if cmdsuggest:
227 if cmdsuggest:
228 msg = _('running non-interactively, use %s instead') % cmdsuggest
228 msg = _('running non-interactively, use %s instead') % cmdsuggest
229 else:
229 else:
230 msg = _('running non-interactively')
230 msg = _('running non-interactively')
231 raise error.Abort(msg)
231 raise error.Abort(msg)
232
232
233 # make sure username is set before going interactive
233 # make sure username is set before going interactive
234 if not opts.get('user'):
234 if not opts.get('user'):
235 ui.username() # raise exception, username not provided
235 ui.username() # raise exception, username not provided
236
236
237 def recordfunc(ui, repo, message, match, opts):
237 def recordfunc(ui, repo, message, match, opts):
238 """This is generic record driver.
238 """This is generic record driver.
239
239
240 Its job is to interactively filter local changes, and
240 Its job is to interactively filter local changes, and
241 accordingly prepare working directory into a state in which the
241 accordingly prepare working directory into a state in which the
242 job can be delegated to a non-interactive commit command such as
242 job can be delegated to a non-interactive commit command such as
243 'commit' or 'qrefresh'.
243 'commit' or 'qrefresh'.
244
244
245 After the actual job is done by non-interactive command, the
245 After the actual job is done by non-interactive command, the
246 working directory is restored to its original state.
246 working directory is restored to its original state.
247
247
248 In the end we'll record interesting changes, and everything else
248 In the end we'll record interesting changes, and everything else
249 will be left in place, so the user can continue working.
249 will be left in place, so the user can continue working.
250 """
250 """
251
251
252 checkunfinished(repo, commit=True)
252 checkunfinished(repo, commit=True)
253 wctx = repo[None]
253 wctx = repo[None]
254 merge = len(wctx.parents()) > 1
254 merge = len(wctx.parents()) > 1
255 if merge:
255 if merge:
256 raise error.Abort(_('cannot partially commit a merge '
256 raise error.Abort(_('cannot partially commit a merge '
257 '(use "hg commit" instead)'))
257 '(use "hg commit" instead)'))
258
258
259 def fail(f, msg):
259 def fail(f, msg):
260 raise error.Abort('%s: %s' % (f, msg))
260 raise error.Abort('%s: %s' % (f, msg))
261
261
262 force = opts.get('force')
262 force = opts.get('force')
263 if not force:
263 if not force:
264 vdirs = []
264 vdirs = []
265 match.explicitdir = vdirs.append
265 match.explicitdir = vdirs.append
266 match.bad = fail
266 match.bad = fail
267
267
268 status = repo.status(match=match)
268 status = repo.status(match=match)
269 if not force:
269 if not force:
270 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
270 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
271 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
271 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
272 diffopts.nodates = True
272 diffopts.nodates = True
273 diffopts.git = True
273 diffopts.git = True
274 diffopts.showfunc = True
274 diffopts.showfunc = True
275 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
275 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
276 originalchunks = patch.parsepatch(originaldiff)
276 originalchunks = patch.parsepatch(originaldiff)
277
277
278 # 1. filter patch, since we are intending to apply subset of it
278 # 1. filter patch, since we are intending to apply subset of it
279 try:
279 try:
280 chunks, newopts = filterfn(ui, originalchunks)
280 chunks, newopts = filterfn(ui, originalchunks)
281 except patch.PatchError as err:
281 except patch.PatchError as err:
282 raise error.Abort(_('error parsing patch: %s') % err)
282 raise error.Abort(_('error parsing patch: %s') % err)
283 opts.update(newopts)
283 opts.update(newopts)
284
284
285 # We need to keep a backup of files that have been newly added and
285 # We need to keep a backup of files that have been newly added and
286 # modified during the recording process because there is a previous
286 # modified during the recording process because there is a previous
287 # version without the edit in the workdir
287 # version without the edit in the workdir
288 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
288 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
289 contenders = set()
289 contenders = set()
290 for h in chunks:
290 for h in chunks:
291 try:
291 try:
292 contenders.update(set(h.files()))
292 contenders.update(set(h.files()))
293 except AttributeError:
293 except AttributeError:
294 pass
294 pass
295
295
296 changed = status.modified + status.added + status.removed
296 changed = status.modified + status.added + status.removed
297 newfiles = [f for f in changed if f in contenders]
297 newfiles = [f for f in changed if f in contenders]
298 if not newfiles:
298 if not newfiles:
299 ui.status(_('no changes to record\n'))
299 ui.status(_('no changes to record\n'))
300 return 0
300 return 0
301
301
302 modified = set(status.modified)
302 modified = set(status.modified)
303
303
304 # 2. backup changed files, so we can restore them in the end
304 # 2. backup changed files, so we can restore them in the end
305
305
306 if backupall:
306 if backupall:
307 tobackup = changed
307 tobackup = changed
308 else:
308 else:
309 tobackup = [f for f in newfiles if f in modified or f in \
309 tobackup = [f for f in newfiles if f in modified or f in \
310 newlyaddedandmodifiedfiles]
310 newlyaddedandmodifiedfiles]
311 backups = {}
311 backups = {}
312 if tobackup:
312 if tobackup:
313 backupdir = repo.vfs.join('record-backups')
313 backupdir = repo.vfs.join('record-backups')
314 try:
314 try:
315 os.mkdir(backupdir)
315 os.mkdir(backupdir)
316 except OSError as err:
316 except OSError as err:
317 if err.errno != errno.EEXIST:
317 if err.errno != errno.EEXIST:
318 raise
318 raise
319 try:
319 try:
320 # backup continues
320 # backup continues
321 for f in tobackup:
321 for f in tobackup:
322 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
322 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
323 dir=backupdir)
323 dir=backupdir)
324 os.close(fd)
324 os.close(fd)
325 ui.debug('backup %r as %r\n' % (f, tmpname))
325 ui.debug('backup %r as %r\n' % (f, tmpname))
326 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
326 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
327 backups[f] = tmpname
327 backups[f] = tmpname
328
328
329 fp = stringio()
329 fp = stringio()
330 for c in chunks:
330 for c in chunks:
331 fname = c.filename()
331 fname = c.filename()
332 if fname in backups:
332 if fname in backups:
333 c.write(fp)
333 c.write(fp)
334 dopatch = fp.tell()
334 dopatch = fp.tell()
335 fp.seek(0)
335 fp.seek(0)
336
336
337 # 2.5 optionally review / modify patch in text editor
337 # 2.5 optionally review / modify patch in text editor
338 if opts.get('review', False):
338 if opts.get('review', False):
339 patchtext = (crecordmod.diffhelptext
339 patchtext = (crecordmod.diffhelptext
340 + crecordmod.patchhelptext
340 + crecordmod.patchhelptext
341 + fp.read())
341 + fp.read())
342 reviewedpatch = ui.edit(patchtext, "",
342 reviewedpatch = ui.edit(patchtext, "",
343 action="diff",
343 action="diff",
344 repopath=repo.path)
344 repopath=repo.path)
345 fp.truncate(0)
345 fp.truncate(0)
346 fp.write(reviewedpatch)
346 fp.write(reviewedpatch)
347 fp.seek(0)
347 fp.seek(0)
348
348
349 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
349 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
350 # 3a. apply filtered patch to clean repo (clean)
350 # 3a. apply filtered patch to clean repo (clean)
351 if backups:
351 if backups:
352 # Equivalent to hg.revert
352 # Equivalent to hg.revert
353 m = scmutil.matchfiles(repo, backups.keys())
353 m = scmutil.matchfiles(repo, backups.keys())
354 mergemod.update(repo, repo.dirstate.p1(),
354 mergemod.update(repo, repo.dirstate.p1(),
355 False, True, matcher=m)
355 False, True, matcher=m)
356
356
357 # 3b. (apply)
357 # 3b. (apply)
358 if dopatch:
358 if dopatch:
359 try:
359 try:
360 ui.debug('applying patch\n')
360 ui.debug('applying patch\n')
361 ui.debug(fp.getvalue())
361 ui.debug(fp.getvalue())
362 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
362 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
363 except patch.PatchError as err:
363 except patch.PatchError as err:
364 raise error.Abort(str(err))
364 raise error.Abort(str(err))
365 del fp
365 del fp
366
366
367 # 4. We prepared working directory according to filtered
367 # 4. We prepared working directory according to filtered
368 # patch. Now is the time to delegate the job to
368 # patch. Now is the time to delegate the job to
369 # commit/qrefresh or the like!
369 # commit/qrefresh or the like!
370
370
371 # Make all of the pathnames absolute.
371 # Make all of the pathnames absolute.
372 newfiles = [repo.wjoin(nf) for nf in newfiles]
372 newfiles = [repo.wjoin(nf) for nf in newfiles]
373 return commitfunc(ui, repo, *newfiles, **opts)
373 return commitfunc(ui, repo, *newfiles, **opts)
374 finally:
374 finally:
375 # 5. finally restore backed-up files
375 # 5. finally restore backed-up files
376 try:
376 try:
377 dirstate = repo.dirstate
377 dirstate = repo.dirstate
378 for realname, tmpname in backups.iteritems():
378 for realname, tmpname in backups.iteritems():
379 ui.debug('restoring %r to %r\n' % (tmpname, realname))
379 ui.debug('restoring %r to %r\n' % (tmpname, realname))
380
380
381 if dirstate[realname] == 'n':
381 if dirstate[realname] == 'n':
382 # without normallookup, restoring timestamp
382 # without normallookup, restoring timestamp
383 # may cause partially committed files
383 # may cause partially committed files
384 # to be treated as unmodified
384 # to be treated as unmodified
385 dirstate.normallookup(realname)
385 dirstate.normallookup(realname)
386
386
387 # copystat=True here and above are a hack to trick any
387 # copystat=True here and above are a hack to trick any
388 # editors that have f open that we haven't modified them.
388 # editors that have f open that we haven't modified them.
389 #
389 #
390 # Also note that this racy as an editor could notice the
390 # Also note that this racy as an editor could notice the
391 # file's mtime before we've finished writing it.
391 # file's mtime before we've finished writing it.
392 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
392 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
393 os.unlink(tmpname)
393 os.unlink(tmpname)
394 if tobackup:
394 if tobackup:
395 os.rmdir(backupdir)
395 os.rmdir(backupdir)
396 except OSError:
396 except OSError:
397 pass
397 pass
398
398
399 def recordinwlock(ui, repo, message, match, opts):
399 def recordinwlock(ui, repo, message, match, opts):
400 with repo.wlock():
400 with repo.wlock():
401 return recordfunc(ui, repo, message, match, opts)
401 return recordfunc(ui, repo, message, match, opts)
402
402
403 return commit(ui, repo, recordinwlock, pats, opts)
403 return commit(ui, repo, recordinwlock, pats, opts)
404
404
405 def tersestatus(root, statlist, status, ignorefn, ignore):
405 def tersestatus(root, statlist, status, ignorefn, ignore):
406 """
406 """
407 Returns a list of statuses with directory collapsed if all the files in the
407 Returns a list of statuses with directory collapsed if all the files in the
408 directory has the same status.
408 directory has the same status.
409 """
409 """
410
410
411 def numfiles(dirname):
411 def numfiles(dirname):
412 """
412 """
413 Calculates the number of tracked files in a given directory which also
413 Calculates the number of tracked files in a given directory which also
414 includes files which were removed or deleted. Considers ignored files
414 includes files which were removed or deleted. Considers ignored files
415 if ignore argument is True or 'i' is present in status argument.
415 if ignore argument is True or 'i' is present in status argument.
416 """
416 """
417 if lencache.get(dirname):
417 if lencache.get(dirname):
418 return lencache[dirname]
418 return lencache[dirname]
419 if 'i' in status or ignore:
419 if 'i' in status or ignore:
420 def match(localpath):
420 def match(localpath):
421 absolutepath = os.path.join(root, localpath)
421 absolutepath = os.path.join(root, localpath)
422 if os.path.isdir(absolutepath) and isemptydir(absolutepath):
422 if os.path.isdir(absolutepath) and isemptydir(absolutepath):
423 return True
423 return True
424 return False
424 return False
425 else:
425 else:
426 def match(localpath):
426 def match(localpath):
427 # there can be directory whose all the files are ignored and
427 # there can be directory whose all the files are ignored and
428 # hence the drectory should also be ignored while counting
428 # hence the drectory should also be ignored while counting
429 # number of files or subdirs in it's parent directory. This
429 # number of files or subdirs in it's parent directory. This
430 # checks the same.
430 # checks the same.
431 # XXX: We need a better logic here.
431 # XXX: We need a better logic here.
432 if os.path.isdir(os.path.join(root, localpath)):
432 if os.path.isdir(os.path.join(root, localpath)):
433 return isignoreddir(localpath)
433 return isignoreddir(localpath)
434 else:
434 else:
435 # XXX: there can be files which have the ignored pattern but
435 # XXX: there can be files which have the ignored pattern but
436 # are not ignored. That leads to bug in counting number of
436 # are not ignored. That leads to bug in counting number of
437 # tracked files in the directory.
437 # tracked files in the directory.
438 return ignorefn(localpath)
438 return ignorefn(localpath)
439 lendir = 0
439 lendir = 0
440 abspath = os.path.join(root, dirname)
440 abspath = os.path.join(root, dirname)
441 # There might be cases when a directory does not exists as the whole
441 # There might be cases when a directory does not exists as the whole
442 # directory can be removed and/or deleted.
442 # directory can be removed and/or deleted.
443 try:
443 try:
444 for f in os.listdir(abspath):
444 for f in os.listdir(abspath):
445 localpath = os.path.join(dirname, f)
445 localpath = os.path.join(dirname, f)
446 if not match(localpath):
446 if not match(localpath):
447 lendir += 1
447 lendir += 1
448 except OSError:
448 except OSError:
449 pass
449 pass
450 lendir += len(absentdir.get(dirname, []))
450 lendir += len(absentdir.get(dirname, []))
451 lencache[dirname] = lendir
451 lencache[dirname] = lendir
452 return lendir
452 return lendir
453
453
454 def isemptydir(abspath):
454 def isemptydir(abspath):
455 """
455 """
456 Check whether a directory is empty or not, i.e. there is no files in the
456 Check whether a directory is empty or not, i.e. there is no files in the
457 directory and all its subdirectories.
457 directory and all its subdirectories.
458 """
458 """
459 for f in os.listdir(abspath):
459 for f in os.listdir(abspath):
460 fullpath = os.path.join(abspath, f)
460 fullpath = os.path.join(abspath, f)
461 if os.path.isdir(fullpath):
461 if os.path.isdir(fullpath):
462 # recursion here
462 # recursion here
463 ret = isemptydir(fullpath)
463 ret = isemptydir(fullpath)
464 if not ret:
464 if not ret:
465 return False
465 return False
466 else:
466 else:
467 return False
467 return False
468 return True
468 return True
469
469
470 def isignoreddir(localpath):
470 def isignoreddir(localpath):
471 """Return True if `localpath` directory is ignored or contains only
471 """Return True if `localpath` directory is ignored or contains only
472 ignored files and should hence be considered ignored.
472 ignored files and should hence be considered ignored.
473 """
473 """
474 dirpath = os.path.join(root, localpath)
474 dirpath = os.path.join(root, localpath)
475 if ignorefn(dirpath):
475 if ignorefn(dirpath):
476 return True
476 return True
477 for f in os.listdir(dirpath):
477 for f in os.listdir(dirpath):
478 filepath = os.path.join(dirpath, f)
478 filepath = os.path.join(dirpath, f)
479 if os.path.isdir(filepath):
479 if os.path.isdir(filepath):
480 # recursion here
480 # recursion here
481 ret = isignoreddir(os.path.join(localpath, f))
481 ret = isignoreddir(os.path.join(localpath, f))
482 if not ret:
482 if not ret:
483 return False
483 return False
484 else:
484 else:
485 if not ignorefn(os.path.join(localpath, f)):
485 if not ignorefn(os.path.join(localpath, f)):
486 return False
486 return False
487 return True
487 return True
488
488
489 def absentones(removedfiles, missingfiles):
489 def absentones(removedfiles, missingfiles):
490 """
490 """
491 Returns a dictionary of directories with files in it which are either
491 Returns a dictionary of directories with files in it which are either
492 removed or missing (deleted) in them.
492 removed or missing (deleted) in them.
493 """
493 """
494 absentdir = {}
494 absentdir = {}
495 absentfiles = removedfiles + missingfiles
495 absentfiles = removedfiles + missingfiles
496 while absentfiles:
496 while absentfiles:
497 f = absentfiles.pop()
497 f = absentfiles.pop()
498 par = os.path.dirname(f)
498 par = os.path.dirname(f)
499 if par == '':
499 if par == '':
500 continue
500 continue
501 # we need to store files rather than number of files as some files
501 # we need to store files rather than number of files as some files
502 # or subdirectories in a directory can be counted twice. This is
502 # or subdirectories in a directory can be counted twice. This is
503 # also we have used sets here.
503 # also we have used sets here.
504 try:
504 try:
505 absentdir[par].add(f)
505 absentdir[par].add(f)
506 except KeyError:
506 except KeyError:
507 absentdir[par] = set([f])
507 absentdir[par] = set([f])
508 absentfiles.append(par)
508 absentfiles.append(par)
509 return absentdir
509 return absentdir
510
510
511 indexes = {'m': 0, 'a': 1, 'r': 2, 'd': 3, 'u': 4, 'i': 5, 'c': 6}
511 indexes = {'m': 0, 'a': 1, 'r': 2, 'd': 3, 'u': 4, 'i': 5, 'c': 6}
512 # get a dictonary of directories and files which are missing as os.listdir()
512 # get a dictonary of directories and files which are missing as os.listdir()
513 # won't be able to list them.
513 # won't be able to list them.
514 absentdir = absentones(statlist[2], statlist[3])
514 absentdir = absentones(statlist[2], statlist[3])
515 finalrs = [[]] * len(indexes)
515 finalrs = [[]] * len(indexes)
516 didsomethingchanged = False
516 didsomethingchanged = False
517 # dictionary to store number of files and subdir in a directory so that we
517 # dictionary to store number of files and subdir in a directory so that we
518 # don't compute that again.
518 # don't compute that again.
519 lencache = {}
519 lencache = {}
520
520
521 for st in pycompat.bytestr(status):
521 for st in pycompat.bytestr(status):
522
522
523 try:
523 try:
524 ind = indexes[st]
524 ind = indexes[st]
525 except KeyError:
525 except KeyError:
526 # TODO: Need a better error message here
526 # TODO: Need a better error message here
527 raise error.Abort("'%s' not recognized" % st)
527 raise error.Abort("'%s' not recognized" % st)
528
528
529 sfiles = statlist[ind]
529 sfiles = statlist[ind]
530 if not sfiles:
530 if not sfiles:
531 continue
531 continue
532 pardict = {}
532 pardict = {}
533 for a in sfiles:
533 for a in sfiles:
534 par = os.path.dirname(a)
534 par = os.path.dirname(a)
535 pardict.setdefault(par, []).append(a)
535 pardict.setdefault(par, []).append(a)
536
536
537 rs = []
537 rs = []
538 newls = []
538 newls = []
539 for par, files in pardict.iteritems():
539 for par, files in pardict.iteritems():
540 lenpar = numfiles(par)
540 lenpar = numfiles(par)
541 if lenpar == len(files):
541 if lenpar == len(files):
542 newls.append(par)
542 newls.append(par)
543
543
544 if not newls:
544 if not newls:
545 continue
545 continue
546
546
547 while newls:
547 while newls:
548 newel = newls.pop()
548 newel = newls.pop()
549 if newel == '':
549 if newel == '':
550 continue
550 continue
551 parn = os.path.dirname(newel)
551 parn = os.path.dirname(newel)
552 pardict[newel] = []
552 pardict[newel] = []
553 # Adding pycompat.ossep as newel is a directory.
553 # Adding pycompat.ossep as newel is a directory.
554 pardict.setdefault(parn, []).append(newel + pycompat.ossep)
554 pardict.setdefault(parn, []).append(newel + pycompat.ossep)
555 lenpar = numfiles(parn)
555 lenpar = numfiles(parn)
556 if lenpar == len(pardict[parn]):
556 if lenpar == len(pardict[parn]):
557 newls.append(parn)
557 newls.append(parn)
558
558
559 # dict.values() for Py3 compatibility
559 # dict.values() for Py3 compatibility
560 for files in pardict.values():
560 for files in pardict.values():
561 rs.extend(files)
561 rs.extend(files)
562
562
563 rs.sort()
563 rs.sort()
564 finalrs[ind] = rs
564 finalrs[ind] = rs
565 didsomethingchanged = True
565 didsomethingchanged = True
566
566
567 # If nothing is changed, make sure the order of files is preserved.
567 # If nothing is changed, make sure the order of files is preserved.
568 if not didsomethingchanged:
568 if not didsomethingchanged:
569 return statlist
569 return statlist
570
570
571 for x in xrange(len(indexes)):
571 for x in xrange(len(indexes)):
572 if not finalrs[x]:
572 if not finalrs[x]:
573 finalrs[x] = statlist[x]
573 finalrs[x] = statlist[x]
574
574
575 return finalrs
575 return finalrs
576
576
577 def _commentlines(raw):
577 def _commentlines(raw):
578 '''Surround lineswith a comment char and a new line'''
578 '''Surround lineswith a comment char and a new line'''
579 lines = raw.splitlines()
579 lines = raw.splitlines()
580 commentedlines = ['# %s' % line for line in lines]
580 commentedlines = ['# %s' % line for line in lines]
581 return '\n'.join(commentedlines) + '\n'
581 return '\n'.join(commentedlines) + '\n'
582
582
583 def _conflictsmsg(repo):
583 def _conflictsmsg(repo):
584 # avoid merge cycle
584 # avoid merge cycle
585 from . import merge as mergemod
585 from . import merge as mergemod
586 mergestate = mergemod.mergestate.read(repo)
586 mergestate = mergemod.mergestate.read(repo)
587 if not mergestate.active():
587 if not mergestate.active():
588 return
588 return
589
589
590 m = scmutil.match(repo[None])
590 m = scmutil.match(repo[None])
591 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
591 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
592 if unresolvedlist:
592 if unresolvedlist:
593 mergeliststr = '\n'.join(
593 mergeliststr = '\n'.join(
594 [' %s' % os.path.relpath(
594 [' %s' % os.path.relpath(
595 os.path.join(repo.root, path),
595 os.path.join(repo.root, path),
596 pycompat.getcwd()) for path in unresolvedlist])
596 pycompat.getcwd()) for path in unresolvedlist])
597 msg = _('''Unresolved merge conflicts:
597 msg = _('''Unresolved merge conflicts:
598
598
599 %s
599 %s
600
600
601 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
601 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
602 else:
602 else:
603 msg = _('No unresolved merge conflicts.')
603 msg = _('No unresolved merge conflicts.')
604
604
605 return _commentlines(msg)
605 return _commentlines(msg)
606
606
607 def _helpmessage(continuecmd, abortcmd):
607 def _helpmessage(continuecmd, abortcmd):
608 msg = _('To continue: %s\n'
608 msg = _('To continue: %s\n'
609 'To abort: %s') % (continuecmd, abortcmd)
609 'To abort: %s') % (continuecmd, abortcmd)
610 return _commentlines(msg)
610 return _commentlines(msg)
611
611
612 def _rebasemsg():
612 def _rebasemsg():
613 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
613 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
614
614
615 def _histeditmsg():
615 def _histeditmsg():
616 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
616 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
617
617
618 def _unshelvemsg():
618 def _unshelvemsg():
619 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
619 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
620
620
621 def _updatecleanmsg(dest=None):
621 def _updatecleanmsg(dest=None):
622 warning = _('warning: this will discard uncommitted changes')
622 warning = _('warning: this will discard uncommitted changes')
623 return 'hg update --clean %s (%s)' % (dest or '.', warning)
623 return 'hg update --clean %s (%s)' % (dest or '.', warning)
624
624
625 def _graftmsg():
625 def _graftmsg():
626 # tweakdefaults requires `update` to have a rev hence the `.`
626 # tweakdefaults requires `update` to have a rev hence the `.`
627 return _helpmessage('hg graft --continue', _updatecleanmsg())
627 return _helpmessage('hg graft --continue', _updatecleanmsg())
628
628
629 def _mergemsg():
629 def _mergemsg():
630 # tweakdefaults requires `update` to have a rev hence the `.`
630 # tweakdefaults requires `update` to have a rev hence the `.`
631 return _helpmessage('hg commit', _updatecleanmsg())
631 return _helpmessage('hg commit', _updatecleanmsg())
632
632
633 def _bisectmsg():
633 def _bisectmsg():
634 msg = _('To mark the changeset good: hg bisect --good\n'
634 msg = _('To mark the changeset good: hg bisect --good\n'
635 'To mark the changeset bad: hg bisect --bad\n'
635 'To mark the changeset bad: hg bisect --bad\n'
636 'To abort: hg bisect --reset\n')
636 'To abort: hg bisect --reset\n')
637 return _commentlines(msg)
637 return _commentlines(msg)
638
638
639 def fileexistspredicate(filename):
639 def fileexistspredicate(filename):
640 return lambda repo: repo.vfs.exists(filename)
640 return lambda repo: repo.vfs.exists(filename)
641
641
642 def _mergepredicate(repo):
642 def _mergepredicate(repo):
643 return len(repo[None].parents()) > 1
643 return len(repo[None].parents()) > 1
644
644
645 STATES = (
645 STATES = (
646 # (state, predicate to detect states, helpful message function)
646 # (state, predicate to detect states, helpful message function)
647 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
647 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
648 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
648 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
649 ('graft', fileexistspredicate('graftstate'), _graftmsg),
649 ('graft', fileexistspredicate('graftstate'), _graftmsg),
650 ('unshelve', fileexistspredicate('unshelverebasestate'), _unshelvemsg),
650 ('unshelve', fileexistspredicate('unshelverebasestate'), _unshelvemsg),
651 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
651 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
652 # The merge state is part of a list that will be iterated over.
652 # The merge state is part of a list that will be iterated over.
653 # They need to be last because some of the other unfinished states may also
653 # They need to be last because some of the other unfinished states may also
654 # be in a merge or update state (eg. rebase, histedit, graft, etc).
654 # be in a merge or update state (eg. rebase, histedit, graft, etc).
655 # We want those to have priority.
655 # We want those to have priority.
656 ('merge', _mergepredicate, _mergemsg),
656 ('merge', _mergepredicate, _mergemsg),
657 )
657 )
658
658
659 def _getrepostate(repo):
659 def _getrepostate(repo):
660 # experimental config: commands.status.skipstates
660 # experimental config: commands.status.skipstates
661 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
661 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
662 for state, statedetectionpredicate, msgfn in STATES:
662 for state, statedetectionpredicate, msgfn in STATES:
663 if state in skip:
663 if state in skip:
664 continue
664 continue
665 if statedetectionpredicate(repo):
665 if statedetectionpredicate(repo):
666 return (state, statedetectionpredicate, msgfn)
666 return (state, statedetectionpredicate, msgfn)
667
667
668 def morestatus(repo, fm):
668 def morestatus(repo, fm):
669 statetuple = _getrepostate(repo)
669 statetuple = _getrepostate(repo)
670 label = 'status.morestatus'
670 label = 'status.morestatus'
671 if statetuple:
671 if statetuple:
672 fm.startitem()
672 fm.startitem()
673 state, statedetectionpredicate, helpfulmsg = statetuple
673 state, statedetectionpredicate, helpfulmsg = statetuple
674 statemsg = _('The repository is in an unfinished *%s* state.') % state
674 statemsg = _('The repository is in an unfinished *%s* state.') % state
675 fm.write('statemsg', '%s\n', _commentlines(statemsg), label=label)
675 fm.write('statemsg', '%s\n', _commentlines(statemsg), label=label)
676 conmsg = _conflictsmsg(repo)
676 conmsg = _conflictsmsg(repo)
677 if conmsg:
677 if conmsg:
678 fm.write('conflictsmsg', '%s\n', conmsg, label=label)
678 fm.write('conflictsmsg', '%s\n', conmsg, label=label)
679 if helpfulmsg:
679 if helpfulmsg:
680 helpmsg = helpfulmsg()
680 helpmsg = helpfulmsg()
681 fm.write('helpmsg', '%s\n', helpmsg, label=label)
681 fm.write('helpmsg', '%s\n', helpmsg, label=label)
682
682
683 def findpossible(cmd, table, strict=False):
683 def findpossible(cmd, table, strict=False):
684 """
684 """
685 Return cmd -> (aliases, command table entry)
685 Return cmd -> (aliases, command table entry)
686 for each matching command.
686 for each matching command.
687 Return debug commands (or their aliases) only if no normal command matches.
687 Return debug commands (or their aliases) only if no normal command matches.
688 """
688 """
689 choice = {}
689 choice = {}
690 debugchoice = {}
690 debugchoice = {}
691
691
692 if cmd in table:
692 if cmd in table:
693 # short-circuit exact matches, "log" alias beats "^log|history"
693 # short-circuit exact matches, "log" alias beats "^log|history"
694 keys = [cmd]
694 keys = [cmd]
695 else:
695 else:
696 keys = table.keys()
696 keys = table.keys()
697
697
698 allcmds = []
698 allcmds = []
699 for e in keys:
699 for e in keys:
700 aliases = parsealiases(e)
700 aliases = parsealiases(e)
701 allcmds.extend(aliases)
701 allcmds.extend(aliases)
702 found = None
702 found = None
703 if cmd in aliases:
703 if cmd in aliases:
704 found = cmd
704 found = cmd
705 elif not strict:
705 elif not strict:
706 for a in aliases:
706 for a in aliases:
707 if a.startswith(cmd):
707 if a.startswith(cmd):
708 found = a
708 found = a
709 break
709 break
710 if found is not None:
710 if found is not None:
711 if aliases[0].startswith("debug") or found.startswith("debug"):
711 if aliases[0].startswith("debug") or found.startswith("debug"):
712 debugchoice[found] = (aliases, table[e])
712 debugchoice[found] = (aliases, table[e])
713 else:
713 else:
714 choice[found] = (aliases, table[e])
714 choice[found] = (aliases, table[e])
715
715
716 if not choice and debugchoice:
716 if not choice and debugchoice:
717 choice = debugchoice
717 choice = debugchoice
718
718
719 return choice, allcmds
719 return choice, allcmds
720
720
721 def findcmd(cmd, table, strict=True):
721 def findcmd(cmd, table, strict=True):
722 """Return (aliases, command table entry) for command string."""
722 """Return (aliases, command table entry) for command string."""
723 choice, allcmds = findpossible(cmd, table, strict)
723 choice, allcmds = findpossible(cmd, table, strict)
724
724
725 if cmd in choice:
725 if cmd in choice:
726 return choice[cmd]
726 return choice[cmd]
727
727
728 if len(choice) > 1:
728 if len(choice) > 1:
729 clist = sorted(choice)
729 clist = sorted(choice)
730 raise error.AmbiguousCommand(cmd, clist)
730 raise error.AmbiguousCommand(cmd, clist)
731
731
732 if choice:
732 if choice:
733 return list(choice.values())[0]
733 return list(choice.values())[0]
734
734
735 raise error.UnknownCommand(cmd, allcmds)
735 raise error.UnknownCommand(cmd, allcmds)
736
736
737 def findrepo(p):
737 def findrepo(p):
738 while not os.path.isdir(os.path.join(p, ".hg")):
738 while not os.path.isdir(os.path.join(p, ".hg")):
739 oldp, p = p, os.path.dirname(p)
739 oldp, p = p, os.path.dirname(p)
740 if p == oldp:
740 if p == oldp:
741 return None
741 return None
742
742
743 return p
743 return p
744
744
745 def bailifchanged(repo, merge=True, hint=None):
745 def bailifchanged(repo, merge=True, hint=None):
746 """ enforce the precondition that working directory must be clean.
746 """ enforce the precondition that working directory must be clean.
747
747
748 'merge' can be set to false if a pending uncommitted merge should be
748 'merge' can be set to false if a pending uncommitted merge should be
749 ignored (such as when 'update --check' runs).
749 ignored (such as when 'update --check' runs).
750
750
751 'hint' is the usual hint given to Abort exception.
751 'hint' is the usual hint given to Abort exception.
752 """
752 """
753
753
754 if merge and repo.dirstate.p2() != nullid:
754 if merge and repo.dirstate.p2() != nullid:
755 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
755 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
756 modified, added, removed, deleted = repo.status()[:4]
756 modified, added, removed, deleted = repo.status()[:4]
757 if modified or added or removed or deleted:
757 if modified or added or removed or deleted:
758 raise error.Abort(_('uncommitted changes'), hint=hint)
758 raise error.Abort(_('uncommitted changes'), hint=hint)
759 ctx = repo[None]
759 ctx = repo[None]
760 for s in sorted(ctx.substate):
760 for s in sorted(ctx.substate):
761 ctx.sub(s).bailifchanged(hint=hint)
761 ctx.sub(s).bailifchanged(hint=hint)
762
762
763 def logmessage(ui, opts):
763 def logmessage(ui, opts):
764 """ get the log message according to -m and -l option """
764 """ get the log message according to -m and -l option """
765 message = opts.get('message')
765 message = opts.get('message')
766 logfile = opts.get('logfile')
766 logfile = opts.get('logfile')
767
767
768 if message and logfile:
768 if message and logfile:
769 raise error.Abort(_('options --message and --logfile are mutually '
769 raise error.Abort(_('options --message and --logfile are mutually '
770 'exclusive'))
770 'exclusive'))
771 if not message and logfile:
771 if not message and logfile:
772 try:
772 try:
773 if isstdiofilename(logfile):
773 if isstdiofilename(logfile):
774 message = ui.fin.read()
774 message = ui.fin.read()
775 else:
775 else:
776 message = '\n'.join(util.readfile(logfile).splitlines())
776 message = '\n'.join(util.readfile(logfile).splitlines())
777 except IOError as inst:
777 except IOError as inst:
778 raise error.Abort(_("can't read commit message '%s': %s") %
778 raise error.Abort(_("can't read commit message '%s': %s") %
779 (logfile, encoding.strtolocal(inst.strerror)))
779 (logfile, encoding.strtolocal(inst.strerror)))
780 return message
780 return message
781
781
782 def mergeeditform(ctxorbool, baseformname):
782 def mergeeditform(ctxorbool, baseformname):
783 """return appropriate editform name (referencing a committemplate)
783 """return appropriate editform name (referencing a committemplate)
784
784
785 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
785 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
786 merging is committed.
786 merging is committed.
787
787
788 This returns baseformname with '.merge' appended if it is a merge,
788 This returns baseformname with '.merge' appended if it is a merge,
789 otherwise '.normal' is appended.
789 otherwise '.normal' is appended.
790 """
790 """
791 if isinstance(ctxorbool, bool):
791 if isinstance(ctxorbool, bool):
792 if ctxorbool:
792 if ctxorbool:
793 return baseformname + ".merge"
793 return baseformname + ".merge"
794 elif 1 < len(ctxorbool.parents()):
794 elif 1 < len(ctxorbool.parents()):
795 return baseformname + ".merge"
795 return baseformname + ".merge"
796
796
797 return baseformname + ".normal"
797 return baseformname + ".normal"
798
798
799 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
799 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
800 editform='', **opts):
800 editform='', **opts):
801 """get appropriate commit message editor according to '--edit' option
801 """get appropriate commit message editor according to '--edit' option
802
802
803 'finishdesc' is a function to be called with edited commit message
803 'finishdesc' is a function to be called with edited commit message
804 (= 'description' of the new changeset) just after editing, but
804 (= 'description' of the new changeset) just after editing, but
805 before checking empty-ness. It should return actual text to be
805 before checking empty-ness. It should return actual text to be
806 stored into history. This allows to change description before
806 stored into history. This allows to change description before
807 storing.
807 storing.
808
808
809 'extramsg' is a extra message to be shown in the editor instead of
809 'extramsg' is a extra message to be shown in the editor instead of
810 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
810 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
811 is automatically added.
811 is automatically added.
812
812
813 'editform' is a dot-separated list of names, to distinguish
813 'editform' is a dot-separated list of names, to distinguish
814 the purpose of commit text editing.
814 the purpose of commit text editing.
815
815
816 'getcommiteditor' returns 'commitforceeditor' regardless of
816 'getcommiteditor' returns 'commitforceeditor' regardless of
817 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
817 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
818 they are specific for usage in MQ.
818 they are specific for usage in MQ.
819 """
819 """
820 if edit or finishdesc or extramsg:
820 if edit or finishdesc or extramsg:
821 return lambda r, c, s: commitforceeditor(r, c, s,
821 return lambda r, c, s: commitforceeditor(r, c, s,
822 finishdesc=finishdesc,
822 finishdesc=finishdesc,
823 extramsg=extramsg,
823 extramsg=extramsg,
824 editform=editform)
824 editform=editform)
825 elif editform:
825 elif editform:
826 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
826 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
827 else:
827 else:
828 return commiteditor
828 return commiteditor
829
829
830 def loglimit(opts):
830 def loglimit(opts):
831 """get the log limit according to option -l/--limit"""
831 """get the log limit according to option -l/--limit"""
832 limit = opts.get('limit')
832 limit = opts.get('limit')
833 if limit:
833 if limit:
834 try:
834 try:
835 limit = int(limit)
835 limit = int(limit)
836 except ValueError:
836 except ValueError:
837 raise error.Abort(_('limit must be a positive integer'))
837 raise error.Abort(_('limit must be a positive integer'))
838 if limit <= 0:
838 if limit <= 0:
839 raise error.Abort(_('limit must be positive'))
839 raise error.Abort(_('limit must be positive'))
840 else:
840 else:
841 limit = None
841 limit = None
842 return limit
842 return limit
843
843
844 def makefilename(repo, pat, node, desc=None,
844 def makefilename(repo, pat, node, desc=None,
845 total=None, seqno=None, revwidth=None, pathname=None):
845 total=None, seqno=None, revwidth=None, pathname=None):
846 node_expander = {
846 node_expander = {
847 'H': lambda: hex(node),
847 'H': lambda: hex(node),
848 'R': lambda: str(repo.changelog.rev(node)),
848 'R': lambda: str(repo.changelog.rev(node)),
849 'h': lambda: short(node),
849 'h': lambda: short(node),
850 'm': lambda: re.sub('[^\w]', '_', str(desc))
850 'm': lambda: re.sub('[^\w]', '_', str(desc))
851 }
851 }
852 expander = {
852 expander = {
853 '%': lambda: '%',
853 '%': lambda: '%',
854 'b': lambda: os.path.basename(repo.root),
854 'b': lambda: os.path.basename(repo.root),
855 }
855 }
856
856
857 try:
857 try:
858 if node:
858 if node:
859 expander.update(node_expander)
859 expander.update(node_expander)
860 if node:
860 if node:
861 expander['r'] = (lambda:
861 expander['r'] = (lambda:
862 str(repo.changelog.rev(node)).zfill(revwidth or 0))
862 str(repo.changelog.rev(node)).zfill(revwidth or 0))
863 if total is not None:
863 if total is not None:
864 expander['N'] = lambda: str(total)
864 expander['N'] = lambda: str(total)
865 if seqno is not None:
865 if seqno is not None:
866 expander['n'] = lambda: str(seqno)
866 expander['n'] = lambda: str(seqno)
867 if total is not None and seqno is not None:
867 if total is not None and seqno is not None:
868 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
868 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
869 if pathname is not None:
869 if pathname is not None:
870 expander['s'] = lambda: os.path.basename(pathname)
870 expander['s'] = lambda: os.path.basename(pathname)
871 expander['d'] = lambda: os.path.dirname(pathname) or '.'
871 expander['d'] = lambda: os.path.dirname(pathname) or '.'
872 expander['p'] = lambda: pathname
872 expander['p'] = lambda: pathname
873
873
874 newname = []
874 newname = []
875 patlen = len(pat)
875 patlen = len(pat)
876 i = 0
876 i = 0
877 while i < patlen:
877 while i < patlen:
878 c = pat[i:i + 1]
878 c = pat[i:i + 1]
879 if c == '%':
879 if c == '%':
880 i += 1
880 i += 1
881 c = pat[i:i + 1]
881 c = pat[i:i + 1]
882 c = expander[c]()
882 c = expander[c]()
883 newname.append(c)
883 newname.append(c)
884 i += 1
884 i += 1
885 return ''.join(newname)
885 return ''.join(newname)
886 except KeyError as inst:
886 except KeyError as inst:
887 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
887 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
888 inst.args[0])
888 inst.args[0])
889
889
890 def isstdiofilename(pat):
890 def isstdiofilename(pat):
891 """True if the given pat looks like a filename denoting stdin/stdout"""
891 """True if the given pat looks like a filename denoting stdin/stdout"""
892 return not pat or pat == '-'
892 return not pat or pat == '-'
893
893
894 class _unclosablefile(object):
894 class _unclosablefile(object):
895 def __init__(self, fp):
895 def __init__(self, fp):
896 self._fp = fp
896 self._fp = fp
897
897
898 def close(self):
898 def close(self):
899 pass
899 pass
900
900
901 def __iter__(self):
901 def __iter__(self):
902 return iter(self._fp)
902 return iter(self._fp)
903
903
904 def __getattr__(self, attr):
904 def __getattr__(self, attr):
905 return getattr(self._fp, attr)
905 return getattr(self._fp, attr)
906
906
907 def __enter__(self):
907 def __enter__(self):
908 return self
908 return self
909
909
910 def __exit__(self, exc_type, exc_value, exc_tb):
910 def __exit__(self, exc_type, exc_value, exc_tb):
911 pass
911 pass
912
912
913 def makefileobj(repo, pat, node=None, desc=None, total=None,
913 def makefileobj(repo, pat, node=None, desc=None, total=None,
914 seqno=None, revwidth=None, mode='wb', modemap=None,
914 seqno=None, revwidth=None, mode='wb', modemap=None,
915 pathname=None):
915 pathname=None):
916
916
917 writable = mode not in ('r', 'rb')
917 writable = mode not in ('r', 'rb')
918
918
919 if isstdiofilename(pat):
919 if isstdiofilename(pat):
920 if writable:
920 if writable:
921 fp = repo.ui.fout
921 fp = repo.ui.fout
922 else:
922 else:
923 fp = repo.ui.fin
923 fp = repo.ui.fin
924 return _unclosablefile(fp)
924 return _unclosablefile(fp)
925 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
925 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
926 if modemap is not None:
926 if modemap is not None:
927 mode = modemap.get(fn, mode)
927 mode = modemap.get(fn, mode)
928 if mode == 'wb':
928 if mode == 'wb':
929 modemap[fn] = 'ab'
929 modemap[fn] = 'ab'
930 return open(fn, mode)
930 return open(fn, mode)
931
931
932 def openrevlog(repo, cmd, file_, opts):
932 def openrevlog(repo, cmd, file_, opts):
933 """opens the changelog, manifest, a filelog or a given revlog"""
933 """opens the changelog, manifest, a filelog or a given revlog"""
934 cl = opts['changelog']
934 cl = opts['changelog']
935 mf = opts['manifest']
935 mf = opts['manifest']
936 dir = opts['dir']
936 dir = opts['dir']
937 msg = None
937 msg = None
938 if cl and mf:
938 if cl and mf:
939 msg = _('cannot specify --changelog and --manifest at the same time')
939 msg = _('cannot specify --changelog and --manifest at the same time')
940 elif cl and dir:
940 elif cl and dir:
941 msg = _('cannot specify --changelog and --dir at the same time')
941 msg = _('cannot specify --changelog and --dir at the same time')
942 elif cl or mf or dir:
942 elif cl or mf or dir:
943 if file_:
943 if file_:
944 msg = _('cannot specify filename with --changelog or --manifest')
944 msg = _('cannot specify filename with --changelog or --manifest')
945 elif not repo:
945 elif not repo:
946 msg = _('cannot specify --changelog or --manifest or --dir '
946 msg = _('cannot specify --changelog or --manifest or --dir '
947 'without a repository')
947 'without a repository')
948 if msg:
948 if msg:
949 raise error.Abort(msg)
949 raise error.Abort(msg)
950
950
951 r = None
951 r = None
952 if repo:
952 if repo:
953 if cl:
953 if cl:
954 r = repo.unfiltered().changelog
954 r = repo.unfiltered().changelog
955 elif dir:
955 elif dir:
956 if 'treemanifest' not in repo.requirements:
956 if 'treemanifest' not in repo.requirements:
957 raise error.Abort(_("--dir can only be used on repos with "
957 raise error.Abort(_("--dir can only be used on repos with "
958 "treemanifest enabled"))
958 "treemanifest enabled"))
959 dirlog = repo.manifestlog._revlog.dirlog(dir)
959 dirlog = repo.manifestlog._revlog.dirlog(dir)
960 if len(dirlog):
960 if len(dirlog):
961 r = dirlog
961 r = dirlog
962 elif mf:
962 elif mf:
963 r = repo.manifestlog._revlog
963 r = repo.manifestlog._revlog
964 elif file_:
964 elif file_:
965 filelog = repo.file(file_)
965 filelog = repo.file(file_)
966 if len(filelog):
966 if len(filelog):
967 r = filelog
967 r = filelog
968 if not r:
968 if not r:
969 if not file_:
969 if not file_:
970 raise error.CommandError(cmd, _('invalid arguments'))
970 raise error.CommandError(cmd, _('invalid arguments'))
971 if not os.path.isfile(file_):
971 if not os.path.isfile(file_):
972 raise error.Abort(_("revlog '%s' not found") % file_)
972 raise error.Abort(_("revlog '%s' not found") % file_)
973 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
973 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
974 file_[:-2] + ".i")
974 file_[:-2] + ".i")
975 return r
975 return r
976
976
977 def copy(ui, repo, pats, opts, rename=False):
977 def copy(ui, repo, pats, opts, rename=False):
978 # called with the repo lock held
978 # called with the repo lock held
979 #
979 #
980 # hgsep => pathname that uses "/" to separate directories
980 # hgsep => pathname that uses "/" to separate directories
981 # ossep => pathname that uses os.sep to separate directories
981 # ossep => pathname that uses os.sep to separate directories
982 cwd = repo.getcwd()
982 cwd = repo.getcwd()
983 targets = {}
983 targets = {}
984 after = opts.get("after")
984 after = opts.get("after")
985 dryrun = opts.get("dry_run")
985 dryrun = opts.get("dry_run")
986 wctx = repo[None]
986 wctx = repo[None]
987
987
988 def walkpat(pat):
988 def walkpat(pat):
989 srcs = []
989 srcs = []
990 if after:
990 if after:
991 badstates = '?'
991 badstates = '?'
992 else:
992 else:
993 badstates = '?r'
993 badstates = '?r'
994 m = scmutil.match(wctx, [pat], opts, globbed=True)
994 m = scmutil.match(wctx, [pat], opts, globbed=True)
995 for abs in wctx.walk(m):
995 for abs in wctx.walk(m):
996 state = repo.dirstate[abs]
996 state = repo.dirstate[abs]
997 rel = m.rel(abs)
997 rel = m.rel(abs)
998 exact = m.exact(abs)
998 exact = m.exact(abs)
999 if state in badstates:
999 if state in badstates:
1000 if exact and state == '?':
1000 if exact and state == '?':
1001 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1001 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1002 if exact and state == 'r':
1002 if exact and state == 'r':
1003 ui.warn(_('%s: not copying - file has been marked for'
1003 ui.warn(_('%s: not copying - file has been marked for'
1004 ' remove\n') % rel)
1004 ' remove\n') % rel)
1005 continue
1005 continue
1006 # abs: hgsep
1006 # abs: hgsep
1007 # rel: ossep
1007 # rel: ossep
1008 srcs.append((abs, rel, exact))
1008 srcs.append((abs, rel, exact))
1009 return srcs
1009 return srcs
1010
1010
1011 # abssrc: hgsep
1011 # abssrc: hgsep
1012 # relsrc: ossep
1012 # relsrc: ossep
1013 # otarget: ossep
1013 # otarget: ossep
1014 def copyfile(abssrc, relsrc, otarget, exact):
1014 def copyfile(abssrc, relsrc, otarget, exact):
1015 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1015 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1016 if '/' in abstarget:
1016 if '/' in abstarget:
1017 # We cannot normalize abstarget itself, this would prevent
1017 # We cannot normalize abstarget itself, this would prevent
1018 # case only renames, like a => A.
1018 # case only renames, like a => A.
1019 abspath, absname = abstarget.rsplit('/', 1)
1019 abspath, absname = abstarget.rsplit('/', 1)
1020 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1020 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1021 reltarget = repo.pathto(abstarget, cwd)
1021 reltarget = repo.pathto(abstarget, cwd)
1022 target = repo.wjoin(abstarget)
1022 target = repo.wjoin(abstarget)
1023 src = repo.wjoin(abssrc)
1023 src = repo.wjoin(abssrc)
1024 state = repo.dirstate[abstarget]
1024 state = repo.dirstate[abstarget]
1025
1025
1026 scmutil.checkportable(ui, abstarget)
1026 scmutil.checkportable(ui, abstarget)
1027
1027
1028 # check for collisions
1028 # check for collisions
1029 prevsrc = targets.get(abstarget)
1029 prevsrc = targets.get(abstarget)
1030 if prevsrc is not None:
1030 if prevsrc is not None:
1031 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1031 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1032 (reltarget, repo.pathto(abssrc, cwd),
1032 (reltarget, repo.pathto(abssrc, cwd),
1033 repo.pathto(prevsrc, cwd)))
1033 repo.pathto(prevsrc, cwd)))
1034 return
1034 return
1035
1035
1036 # check for overwrites
1036 # check for overwrites
1037 exists = os.path.lexists(target)
1037 exists = os.path.lexists(target)
1038 samefile = False
1038 samefile = False
1039 if exists and abssrc != abstarget:
1039 if exists and abssrc != abstarget:
1040 if (repo.dirstate.normalize(abssrc) ==
1040 if (repo.dirstate.normalize(abssrc) ==
1041 repo.dirstate.normalize(abstarget)):
1041 repo.dirstate.normalize(abstarget)):
1042 if not rename:
1042 if not rename:
1043 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1043 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1044 return
1044 return
1045 exists = False
1045 exists = False
1046 samefile = True
1046 samefile = True
1047
1047
1048 if not after and exists or after and state in 'mn':
1048 if not after and exists or after and state in 'mn':
1049 if not opts['force']:
1049 if not opts['force']:
1050 if state in 'mn':
1050 if state in 'mn':
1051 msg = _('%s: not overwriting - file already committed\n')
1051 msg = _('%s: not overwriting - file already committed\n')
1052 if after:
1052 if after:
1053 flags = '--after --force'
1053 flags = '--after --force'
1054 else:
1054 else:
1055 flags = '--force'
1055 flags = '--force'
1056 if rename:
1056 if rename:
1057 hint = _('(hg rename %s to replace the file by '
1057 hint = _('(hg rename %s to replace the file by '
1058 'recording a rename)\n') % flags
1058 'recording a rename)\n') % flags
1059 else:
1059 else:
1060 hint = _('(hg copy %s to replace the file by '
1060 hint = _('(hg copy %s to replace the file by '
1061 'recording a copy)\n') % flags
1061 'recording a copy)\n') % flags
1062 else:
1062 else:
1063 msg = _('%s: not overwriting - file exists\n')
1063 msg = _('%s: not overwriting - file exists\n')
1064 if rename:
1064 if rename:
1065 hint = _('(hg rename --after to record the rename)\n')
1065 hint = _('(hg rename --after to record the rename)\n')
1066 else:
1066 else:
1067 hint = _('(hg copy --after to record the copy)\n')
1067 hint = _('(hg copy --after to record the copy)\n')
1068 ui.warn(msg % reltarget)
1068 ui.warn(msg % reltarget)
1069 ui.warn(hint)
1069 ui.warn(hint)
1070 return
1070 return
1071
1071
1072 if after:
1072 if after:
1073 if not exists:
1073 if not exists:
1074 if rename:
1074 if rename:
1075 ui.warn(_('%s: not recording move - %s does not exist\n') %
1075 ui.warn(_('%s: not recording move - %s does not exist\n') %
1076 (relsrc, reltarget))
1076 (relsrc, reltarget))
1077 else:
1077 else:
1078 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1078 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1079 (relsrc, reltarget))
1079 (relsrc, reltarget))
1080 return
1080 return
1081 elif not dryrun:
1081 elif not dryrun:
1082 try:
1082 try:
1083 if exists:
1083 if exists:
1084 os.unlink(target)
1084 os.unlink(target)
1085 targetdir = os.path.dirname(target) or '.'
1085 targetdir = os.path.dirname(target) or '.'
1086 if not os.path.isdir(targetdir):
1086 if not os.path.isdir(targetdir):
1087 os.makedirs(targetdir)
1087 os.makedirs(targetdir)
1088 if samefile:
1088 if samefile:
1089 tmp = target + "~hgrename"
1089 tmp = target + "~hgrename"
1090 os.rename(src, tmp)
1090 os.rename(src, tmp)
1091 os.rename(tmp, target)
1091 os.rename(tmp, target)
1092 else:
1092 else:
1093 util.copyfile(src, target)
1093 util.copyfile(src, target)
1094 srcexists = True
1094 srcexists = True
1095 except IOError as inst:
1095 except IOError as inst:
1096 if inst.errno == errno.ENOENT:
1096 if inst.errno == errno.ENOENT:
1097 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1097 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1098 srcexists = False
1098 srcexists = False
1099 else:
1099 else:
1100 ui.warn(_('%s: cannot copy - %s\n') %
1100 ui.warn(_('%s: cannot copy - %s\n') %
1101 (relsrc, encoding.strtolocal(inst.strerror)))
1101 (relsrc, encoding.strtolocal(inst.strerror)))
1102 return True # report a failure
1102 return True # report a failure
1103
1103
1104 if ui.verbose or not exact:
1104 if ui.verbose or not exact:
1105 if rename:
1105 if rename:
1106 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1106 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1107 else:
1107 else:
1108 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1108 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1109
1109
1110 targets[abstarget] = abssrc
1110 targets[abstarget] = abssrc
1111
1111
1112 # fix up dirstate
1112 # fix up dirstate
1113 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1113 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1114 dryrun=dryrun, cwd=cwd)
1114 dryrun=dryrun, cwd=cwd)
1115 if rename and not dryrun:
1115 if rename and not dryrun:
1116 if not after and srcexists and not samefile:
1116 if not after and srcexists and not samefile:
1117 repo.wvfs.unlinkpath(abssrc)
1117 repo.wvfs.unlinkpath(abssrc)
1118 wctx.forget([abssrc])
1118 wctx.forget([abssrc])
1119
1119
1120 # pat: ossep
1120 # pat: ossep
1121 # dest ossep
1121 # dest ossep
1122 # srcs: list of (hgsep, hgsep, ossep, bool)
1122 # srcs: list of (hgsep, hgsep, ossep, bool)
1123 # return: function that takes hgsep and returns ossep
1123 # return: function that takes hgsep and returns ossep
1124 def targetpathfn(pat, dest, srcs):
1124 def targetpathfn(pat, dest, srcs):
1125 if os.path.isdir(pat):
1125 if os.path.isdir(pat):
1126 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1126 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1127 abspfx = util.localpath(abspfx)
1127 abspfx = util.localpath(abspfx)
1128 if destdirexists:
1128 if destdirexists:
1129 striplen = len(os.path.split(abspfx)[0])
1129 striplen = len(os.path.split(abspfx)[0])
1130 else:
1130 else:
1131 striplen = len(abspfx)
1131 striplen = len(abspfx)
1132 if striplen:
1132 if striplen:
1133 striplen += len(pycompat.ossep)
1133 striplen += len(pycompat.ossep)
1134 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1134 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1135 elif destdirexists:
1135 elif destdirexists:
1136 res = lambda p: os.path.join(dest,
1136 res = lambda p: os.path.join(dest,
1137 os.path.basename(util.localpath(p)))
1137 os.path.basename(util.localpath(p)))
1138 else:
1138 else:
1139 res = lambda p: dest
1139 res = lambda p: dest
1140 return res
1140 return res
1141
1141
1142 # pat: ossep
1142 # pat: ossep
1143 # dest ossep
1143 # dest ossep
1144 # srcs: list of (hgsep, hgsep, ossep, bool)
1144 # srcs: list of (hgsep, hgsep, ossep, bool)
1145 # return: function that takes hgsep and returns ossep
1145 # return: function that takes hgsep and returns ossep
1146 def targetpathafterfn(pat, dest, srcs):
1146 def targetpathafterfn(pat, dest, srcs):
1147 if matchmod.patkind(pat):
1147 if matchmod.patkind(pat):
1148 # a mercurial pattern
1148 # a mercurial pattern
1149 res = lambda p: os.path.join(dest,
1149 res = lambda p: os.path.join(dest,
1150 os.path.basename(util.localpath(p)))
1150 os.path.basename(util.localpath(p)))
1151 else:
1151 else:
1152 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1152 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1153 if len(abspfx) < len(srcs[0][0]):
1153 if len(abspfx) < len(srcs[0][0]):
1154 # A directory. Either the target path contains the last
1154 # A directory. Either the target path contains the last
1155 # component of the source path or it does not.
1155 # component of the source path or it does not.
1156 def evalpath(striplen):
1156 def evalpath(striplen):
1157 score = 0
1157 score = 0
1158 for s in srcs:
1158 for s in srcs:
1159 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1159 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1160 if os.path.lexists(t):
1160 if os.path.lexists(t):
1161 score += 1
1161 score += 1
1162 return score
1162 return score
1163
1163
1164 abspfx = util.localpath(abspfx)
1164 abspfx = util.localpath(abspfx)
1165 striplen = len(abspfx)
1165 striplen = len(abspfx)
1166 if striplen:
1166 if striplen:
1167 striplen += len(pycompat.ossep)
1167 striplen += len(pycompat.ossep)
1168 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1168 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1169 score = evalpath(striplen)
1169 score = evalpath(striplen)
1170 striplen1 = len(os.path.split(abspfx)[0])
1170 striplen1 = len(os.path.split(abspfx)[0])
1171 if striplen1:
1171 if striplen1:
1172 striplen1 += len(pycompat.ossep)
1172 striplen1 += len(pycompat.ossep)
1173 if evalpath(striplen1) > score:
1173 if evalpath(striplen1) > score:
1174 striplen = striplen1
1174 striplen = striplen1
1175 res = lambda p: os.path.join(dest,
1175 res = lambda p: os.path.join(dest,
1176 util.localpath(p)[striplen:])
1176 util.localpath(p)[striplen:])
1177 else:
1177 else:
1178 # a file
1178 # a file
1179 if destdirexists:
1179 if destdirexists:
1180 res = lambda p: os.path.join(dest,
1180 res = lambda p: os.path.join(dest,
1181 os.path.basename(util.localpath(p)))
1181 os.path.basename(util.localpath(p)))
1182 else:
1182 else:
1183 res = lambda p: dest
1183 res = lambda p: dest
1184 return res
1184 return res
1185
1185
1186 pats = scmutil.expandpats(pats)
1186 pats = scmutil.expandpats(pats)
1187 if not pats:
1187 if not pats:
1188 raise error.Abort(_('no source or destination specified'))
1188 raise error.Abort(_('no source or destination specified'))
1189 if len(pats) == 1:
1189 if len(pats) == 1:
1190 raise error.Abort(_('no destination specified'))
1190 raise error.Abort(_('no destination specified'))
1191 dest = pats.pop()
1191 dest = pats.pop()
1192 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1192 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1193 if not destdirexists:
1193 if not destdirexists:
1194 if len(pats) > 1 or matchmod.patkind(pats[0]):
1194 if len(pats) > 1 or matchmod.patkind(pats[0]):
1195 raise error.Abort(_('with multiple sources, destination must be an '
1195 raise error.Abort(_('with multiple sources, destination must be an '
1196 'existing directory'))
1196 'existing directory'))
1197 if util.endswithsep(dest):
1197 if util.endswithsep(dest):
1198 raise error.Abort(_('destination %s is not a directory') % dest)
1198 raise error.Abort(_('destination %s is not a directory') % dest)
1199
1199
1200 tfn = targetpathfn
1200 tfn = targetpathfn
1201 if after:
1201 if after:
1202 tfn = targetpathafterfn
1202 tfn = targetpathafterfn
1203 copylist = []
1203 copylist = []
1204 for pat in pats:
1204 for pat in pats:
1205 srcs = walkpat(pat)
1205 srcs = walkpat(pat)
1206 if not srcs:
1206 if not srcs:
1207 continue
1207 continue
1208 copylist.append((tfn(pat, dest, srcs), srcs))
1208 copylist.append((tfn(pat, dest, srcs), srcs))
1209 if not copylist:
1209 if not copylist:
1210 raise error.Abort(_('no files to copy'))
1210 raise error.Abort(_('no files to copy'))
1211
1211
1212 errors = 0
1212 errors = 0
1213 for targetpath, srcs in copylist:
1213 for targetpath, srcs in copylist:
1214 for abssrc, relsrc, exact in srcs:
1214 for abssrc, relsrc, exact in srcs:
1215 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1215 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1216 errors += 1
1216 errors += 1
1217
1217
1218 if errors:
1218 if errors:
1219 ui.warn(_('(consider using --after)\n'))
1219 ui.warn(_('(consider using --after)\n'))
1220
1220
1221 return errors != 0
1221 return errors != 0
1222
1222
1223 ## facility to let extension process additional data into an import patch
1223 ## facility to let extension process additional data into an import patch
1224 # list of identifier to be executed in order
1224 # list of identifier to be executed in order
1225 extrapreimport = [] # run before commit
1225 extrapreimport = [] # run before commit
1226 extrapostimport = [] # run after commit
1226 extrapostimport = [] # run after commit
1227 # mapping from identifier to actual import function
1227 # mapping from identifier to actual import function
1228 #
1228 #
1229 # 'preimport' are run before the commit is made and are provided the following
1229 # 'preimport' are run before the commit is made and are provided the following
1230 # arguments:
1230 # arguments:
1231 # - repo: the localrepository instance,
1231 # - repo: the localrepository instance,
1232 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1232 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1233 # - extra: the future extra dictionary of the changeset, please mutate it,
1233 # - extra: the future extra dictionary of the changeset, please mutate it,
1234 # - opts: the import options.
1234 # - opts: the import options.
1235 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1235 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1236 # mutation of in memory commit and more. Feel free to rework the code to get
1236 # mutation of in memory commit and more. Feel free to rework the code to get
1237 # there.
1237 # there.
1238 extrapreimportmap = {}
1238 extrapreimportmap = {}
1239 # 'postimport' are run after the commit is made and are provided the following
1239 # 'postimport' are run after the commit is made and are provided the following
1240 # argument:
1240 # argument:
1241 # - ctx: the changectx created by import.
1241 # - ctx: the changectx created by import.
1242 extrapostimportmap = {}
1242 extrapostimportmap = {}
1243
1243
1244 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
1244 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
1245 """Utility function used by commands.import to import a single patch
1245 """Utility function used by commands.import to import a single patch
1246
1246
1247 This function is explicitly defined here to help the evolve extension to
1247 This function is explicitly defined here to help the evolve extension to
1248 wrap this part of the import logic.
1248 wrap this part of the import logic.
1249
1249
1250 The API is currently a bit ugly because it a simple code translation from
1250 The API is currently a bit ugly because it a simple code translation from
1251 the import command. Feel free to make it better.
1251 the import command. Feel free to make it better.
1252
1252
1253 :hunk: a patch (as a binary string)
1253 :hunk: a patch (as a binary string)
1254 :parents: nodes that will be parent of the created commit
1254 :parents: nodes that will be parent of the created commit
1255 :opts: the full dict of option passed to the import command
1255 :opts: the full dict of option passed to the import command
1256 :msgs: list to save commit message to.
1256 :msgs: list to save commit message to.
1257 (used in case we need to save it when failing)
1257 (used in case we need to save it when failing)
1258 :updatefunc: a function that update a repo to a given node
1258 :updatefunc: a function that update a repo to a given node
1259 updatefunc(<repo>, <node>)
1259 updatefunc(<repo>, <node>)
1260 """
1260 """
1261 # avoid cycle context -> subrepo -> cmdutil
1261 # avoid cycle context -> subrepo -> cmdutil
1262 from . import context
1262 from . import context
1263 extractdata = patch.extract(ui, hunk)
1263 extractdata = patch.extract(ui, hunk)
1264 tmpname = extractdata.get('filename')
1264 tmpname = extractdata.get('filename')
1265 message = extractdata.get('message')
1265 message = extractdata.get('message')
1266 user = opts.get('user') or extractdata.get('user')
1266 user = opts.get('user') or extractdata.get('user')
1267 date = opts.get('date') or extractdata.get('date')
1267 date = opts.get('date') or extractdata.get('date')
1268 branch = extractdata.get('branch')
1268 branch = extractdata.get('branch')
1269 nodeid = extractdata.get('nodeid')
1269 nodeid = extractdata.get('nodeid')
1270 p1 = extractdata.get('p1')
1270 p1 = extractdata.get('p1')
1271 p2 = extractdata.get('p2')
1271 p2 = extractdata.get('p2')
1272
1272
1273 nocommit = opts.get('no_commit')
1273 nocommit = opts.get('no_commit')
1274 importbranch = opts.get('import_branch')
1274 importbranch = opts.get('import_branch')
1275 update = not opts.get('bypass')
1275 update = not opts.get('bypass')
1276 strip = opts["strip"]
1276 strip = opts["strip"]
1277 prefix = opts["prefix"]
1277 prefix = opts["prefix"]
1278 sim = float(opts.get('similarity') or 0)
1278 sim = float(opts.get('similarity') or 0)
1279 if not tmpname:
1279 if not tmpname:
1280 return (None, None, False)
1280 return (None, None, False)
1281
1281
1282 rejects = False
1282 rejects = False
1283
1283
1284 try:
1284 try:
1285 cmdline_message = logmessage(ui, opts)
1285 cmdline_message = logmessage(ui, opts)
1286 if cmdline_message:
1286 if cmdline_message:
1287 # pickup the cmdline msg
1287 # pickup the cmdline msg
1288 message = cmdline_message
1288 message = cmdline_message
1289 elif message:
1289 elif message:
1290 # pickup the patch msg
1290 # pickup the patch msg
1291 message = message.strip()
1291 message = message.strip()
1292 else:
1292 else:
1293 # launch the editor
1293 # launch the editor
1294 message = None
1294 message = None
1295 ui.debug('message:\n%s\n' % message)
1295 ui.debug('message:\n%s\n' % message)
1296
1296
1297 if len(parents) == 1:
1297 if len(parents) == 1:
1298 parents.append(repo[nullid])
1298 parents.append(repo[nullid])
1299 if opts.get('exact'):
1299 if opts.get('exact'):
1300 if not nodeid or not p1:
1300 if not nodeid or not p1:
1301 raise error.Abort(_('not a Mercurial patch'))
1301 raise error.Abort(_('not a Mercurial patch'))
1302 p1 = repo[p1]
1302 p1 = repo[p1]
1303 p2 = repo[p2 or nullid]
1303 p2 = repo[p2 or nullid]
1304 elif p2:
1304 elif p2:
1305 try:
1305 try:
1306 p1 = repo[p1]
1306 p1 = repo[p1]
1307 p2 = repo[p2]
1307 p2 = repo[p2]
1308 # Without any options, consider p2 only if the
1308 # Without any options, consider p2 only if the
1309 # patch is being applied on top of the recorded
1309 # patch is being applied on top of the recorded
1310 # first parent.
1310 # first parent.
1311 if p1 != parents[0]:
1311 if p1 != parents[0]:
1312 p1 = parents[0]
1312 p1 = parents[0]
1313 p2 = repo[nullid]
1313 p2 = repo[nullid]
1314 except error.RepoError:
1314 except error.RepoError:
1315 p1, p2 = parents
1315 p1, p2 = parents
1316 if p2.node() == nullid:
1316 if p2.node() == nullid:
1317 ui.warn(_("warning: import the patch as a normal revision\n"
1317 ui.warn(_("warning: import the patch as a normal revision\n"
1318 "(use --exact to import the patch as a merge)\n"))
1318 "(use --exact to import the patch as a merge)\n"))
1319 else:
1319 else:
1320 p1, p2 = parents
1320 p1, p2 = parents
1321
1321
1322 n = None
1322 n = None
1323 if update:
1323 if update:
1324 if p1 != parents[0]:
1324 if p1 != parents[0]:
1325 updatefunc(repo, p1.node())
1325 updatefunc(repo, p1.node())
1326 if p2 != parents[1]:
1326 if p2 != parents[1]:
1327 repo.setparents(p1.node(), p2.node())
1327 repo.setparents(p1.node(), p2.node())
1328
1328
1329 if opts.get('exact') or importbranch:
1329 if opts.get('exact') or importbranch:
1330 repo.dirstate.setbranch(branch or 'default')
1330 repo.dirstate.setbranch(branch or 'default')
1331
1331
1332 partial = opts.get('partial', False)
1332 partial = opts.get('partial', False)
1333 files = set()
1333 files = set()
1334 try:
1334 try:
1335 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1335 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1336 files=files, eolmode=None, similarity=sim / 100.0)
1336 files=files, eolmode=None, similarity=sim / 100.0)
1337 except patch.PatchError as e:
1337 except patch.PatchError as e:
1338 if not partial:
1338 if not partial:
1339 raise error.Abort(str(e))
1339 raise error.Abort(str(e))
1340 if partial:
1340 if partial:
1341 rejects = True
1341 rejects = True
1342
1342
1343 files = list(files)
1343 files = list(files)
1344 if nocommit:
1344 if nocommit:
1345 if message:
1345 if message:
1346 msgs.append(message)
1346 msgs.append(message)
1347 else:
1347 else:
1348 if opts.get('exact') or p2:
1348 if opts.get('exact') or p2:
1349 # If you got here, you either use --force and know what
1349 # If you got here, you either use --force and know what
1350 # you are doing or used --exact or a merge patch while
1350 # you are doing or used --exact or a merge patch while
1351 # being updated to its first parent.
1351 # being updated to its first parent.
1352 m = None
1352 m = None
1353 else:
1353 else:
1354 m = scmutil.matchfiles(repo, files or [])
1354 m = scmutil.matchfiles(repo, files or [])
1355 editform = mergeeditform(repo[None], 'import.normal')
1355 editform = mergeeditform(repo[None], 'import.normal')
1356 if opts.get('exact'):
1356 if opts.get('exact'):
1357 editor = None
1357 editor = None
1358 else:
1358 else:
1359 editor = getcommiteditor(editform=editform, **opts)
1359 editor = getcommiteditor(editform=editform, **opts)
1360 extra = {}
1360 extra = {}
1361 for idfunc in extrapreimport:
1361 for idfunc in extrapreimport:
1362 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1362 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1363 overrides = {}
1363 overrides = {}
1364 if partial:
1364 if partial:
1365 overrides[('ui', 'allowemptycommit')] = True
1365 overrides[('ui', 'allowemptycommit')] = True
1366 with repo.ui.configoverride(overrides, 'import'):
1366 with repo.ui.configoverride(overrides, 'import'):
1367 n = repo.commit(message, user,
1367 n = repo.commit(message, user,
1368 date, match=m,
1368 date, match=m,
1369 editor=editor, extra=extra)
1369 editor=editor, extra=extra)
1370 for idfunc in extrapostimport:
1370 for idfunc in extrapostimport:
1371 extrapostimportmap[idfunc](repo[n])
1371 extrapostimportmap[idfunc](repo[n])
1372 else:
1372 else:
1373 if opts.get('exact') or importbranch:
1373 if opts.get('exact') or importbranch:
1374 branch = branch or 'default'
1374 branch = branch or 'default'
1375 else:
1375 else:
1376 branch = p1.branch()
1376 branch = p1.branch()
1377 store = patch.filestore()
1377 store = patch.filestore()
1378 try:
1378 try:
1379 files = set()
1379 files = set()
1380 try:
1380 try:
1381 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1381 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1382 files, eolmode=None)
1382 files, eolmode=None)
1383 except patch.PatchError as e:
1383 except patch.PatchError as e:
1384 raise error.Abort(str(e))
1384 raise error.Abort(str(e))
1385 if opts.get('exact'):
1385 if opts.get('exact'):
1386 editor = None
1386 editor = None
1387 else:
1387 else:
1388 editor = getcommiteditor(editform='import.bypass')
1388 editor = getcommiteditor(editform='import.bypass')
1389 memctx = context.memctx(repo, (p1.node(), p2.node()),
1389 memctx = context.memctx(repo, (p1.node(), p2.node()),
1390 message,
1390 message,
1391 files=files,
1391 files=files,
1392 filectxfn=store,
1392 filectxfn=store,
1393 user=user,
1393 user=user,
1394 date=date,
1394 date=date,
1395 branch=branch,
1395 branch=branch,
1396 editor=editor)
1396 editor=editor)
1397 n = memctx.commit()
1397 n = memctx.commit()
1398 finally:
1398 finally:
1399 store.close()
1399 store.close()
1400 if opts.get('exact') and nocommit:
1400 if opts.get('exact') and nocommit:
1401 # --exact with --no-commit is still useful in that it does merge
1401 # --exact with --no-commit is still useful in that it does merge
1402 # and branch bits
1402 # and branch bits
1403 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1403 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1404 elif opts.get('exact') and hex(n) != nodeid:
1404 elif opts.get('exact') and hex(n) != nodeid:
1405 raise error.Abort(_('patch is damaged or loses information'))
1405 raise error.Abort(_('patch is damaged or loses information'))
1406 msg = _('applied to working directory')
1406 msg = _('applied to working directory')
1407 if n:
1407 if n:
1408 # i18n: refers to a short changeset id
1408 # i18n: refers to a short changeset id
1409 msg = _('created %s') % short(n)
1409 msg = _('created %s') % short(n)
1410 return (msg, n, rejects)
1410 return (msg, n, rejects)
1411 finally:
1411 finally:
1412 os.unlink(tmpname)
1412 os.unlink(tmpname)
1413
1413
1414 # facility to let extensions include additional data in an exported patch
1414 # facility to let extensions include additional data in an exported patch
1415 # list of identifiers to be executed in order
1415 # list of identifiers to be executed in order
1416 extraexport = []
1416 extraexport = []
1417 # mapping from identifier to actual export function
1417 # mapping from identifier to actual export function
1418 # function as to return a string to be added to the header or None
1418 # function as to return a string to be added to the header or None
1419 # it is given two arguments (sequencenumber, changectx)
1419 # it is given two arguments (sequencenumber, changectx)
1420 extraexportmap = {}
1420 extraexportmap = {}
1421
1421
1422 def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
1422 def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
1423 node = scmutil.binnode(ctx)
1423 node = scmutil.binnode(ctx)
1424 parents = [p.node() for p in ctx.parents() if p]
1424 parents = [p.node() for p in ctx.parents() if p]
1425 branch = ctx.branch()
1425 branch = ctx.branch()
1426 if switch_parent:
1426 if switch_parent:
1427 parents.reverse()
1427 parents.reverse()
1428
1428
1429 if parents:
1429 if parents:
1430 prev = parents[0]
1430 prev = parents[0]
1431 else:
1431 else:
1432 prev = nullid
1432 prev = nullid
1433
1433
1434 write("# HG changeset patch\n")
1434 write("# HG changeset patch\n")
1435 write("# User %s\n" % ctx.user())
1435 write("# User %s\n" % ctx.user())
1436 write("# Date %d %d\n" % ctx.date())
1436 write("# Date %d %d\n" % ctx.date())
1437 write("# %s\n" % util.datestr(ctx.date()))
1437 write("# %s\n" % util.datestr(ctx.date()))
1438 if branch and branch != 'default':
1438 if branch and branch != 'default':
1439 write("# Branch %s\n" % branch)
1439 write("# Branch %s\n" % branch)
1440 write("# Node ID %s\n" % hex(node))
1440 write("# Node ID %s\n" % hex(node))
1441 write("# Parent %s\n" % hex(prev))
1441 write("# Parent %s\n" % hex(prev))
1442 if len(parents) > 1:
1442 if len(parents) > 1:
1443 write("# Parent %s\n" % hex(parents[1]))
1443 write("# Parent %s\n" % hex(parents[1]))
1444
1444
1445 for headerid in extraexport:
1445 for headerid in extraexport:
1446 header = extraexportmap[headerid](seqno, ctx)
1446 header = extraexportmap[headerid](seqno, ctx)
1447 if header is not None:
1447 if header is not None:
1448 write('# %s\n' % header)
1448 write('# %s\n' % header)
1449 write(ctx.description().rstrip())
1449 write(ctx.description().rstrip())
1450 write("\n\n")
1450 write("\n\n")
1451
1451
1452 for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts):
1452 for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts):
1453 write(chunk, label=label)
1453 write(chunk, label=label)
1454
1454
1455 def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
1455 def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
1456 opts=None, match=None):
1456 opts=None, match=None):
1457 '''export changesets as hg patches
1457 '''export changesets as hg patches
1458
1458
1459 Args:
1459 Args:
1460 repo: The repository from which we're exporting revisions.
1460 repo: The repository from which we're exporting revisions.
1461 revs: A list of revisions to export as revision numbers.
1461 revs: A list of revisions to export as revision numbers.
1462 fntemplate: An optional string to use for generating patch file names.
1462 fntemplate: An optional string to use for generating patch file names.
1463 fp: An optional file-like object to which patches should be written.
1463 fp: An optional file-like object to which patches should be written.
1464 switch_parent: If True, show diffs against second parent when not nullid.
1464 switch_parent: If True, show diffs against second parent when not nullid.
1465 Default is false, which always shows diff against p1.
1465 Default is false, which always shows diff against p1.
1466 opts: diff options to use for generating the patch.
1466 opts: diff options to use for generating the patch.
1467 match: If specified, only export changes to files matching this matcher.
1467 match: If specified, only export changes to files matching this matcher.
1468
1468
1469 Returns:
1469 Returns:
1470 Nothing.
1470 Nothing.
1471
1471
1472 Side Effect:
1472 Side Effect:
1473 "HG Changeset Patch" data is emitted to one of the following
1473 "HG Changeset Patch" data is emitted to one of the following
1474 destinations:
1474 destinations:
1475 fp is specified: All revs are written to the specified
1475 fp is specified: All revs are written to the specified
1476 file-like object.
1476 file-like object.
1477 fntemplate specified: Each rev is written to a unique file named using
1477 fntemplate specified: Each rev is written to a unique file named using
1478 the given template.
1478 the given template.
1479 Neither fp nor template specified: All revs written to repo.ui.write()
1479 Neither fp nor template specified: All revs written to repo.ui.write()
1480 '''
1480 '''
1481
1481
1482 total = len(revs)
1482 total = len(revs)
1483 revwidth = max(len(str(rev)) for rev in revs)
1483 revwidth = max(len(str(rev)) for rev in revs)
1484 filemode = {}
1484 filemode = {}
1485
1485
1486 write = None
1486 write = None
1487 dest = '<unnamed>'
1487 dest = '<unnamed>'
1488 if fp:
1488 if fp:
1489 dest = getattr(fp, 'name', dest)
1489 dest = getattr(fp, 'name', dest)
1490 def write(s, **kw):
1490 def write(s, **kw):
1491 fp.write(s)
1491 fp.write(s)
1492 elif not fntemplate:
1492 elif not fntemplate:
1493 write = repo.ui.write
1493 write = repo.ui.write
1494
1494
1495 for seqno, rev in enumerate(revs, 1):
1495 for seqno, rev in enumerate(revs, 1):
1496 ctx = repo[rev]
1496 ctx = repo[rev]
1497 fo = None
1497 fo = None
1498 if not fp and fntemplate:
1498 if not fp and fntemplate:
1499 desc_lines = ctx.description().rstrip().split('\n')
1499 desc_lines = ctx.description().rstrip().split('\n')
1500 desc = desc_lines[0] #Commit always has a first line.
1500 desc = desc_lines[0] #Commit always has a first line.
1501 fo = makefileobj(repo, fntemplate, ctx.node(), desc=desc,
1501 fo = makefileobj(repo, fntemplate, ctx.node(), desc=desc,
1502 total=total, seqno=seqno, revwidth=revwidth,
1502 total=total, seqno=seqno, revwidth=revwidth,
1503 mode='wb', modemap=filemode)
1503 mode='wb', modemap=filemode)
1504 dest = fo.name
1504 dest = fo.name
1505 def write(s, **kw):
1505 def write(s, **kw):
1506 fo.write(s)
1506 fo.write(s)
1507 if not dest.startswith('<'):
1507 if not dest.startswith('<'):
1508 repo.ui.note("%s\n" % dest)
1508 repo.ui.note("%s\n" % dest)
1509 _exportsingle(
1509 _exportsingle(
1510 repo, ctx, match, switch_parent, rev, seqno, write, opts)
1510 repo, ctx, match, switch_parent, rev, seqno, write, opts)
1511 if fo is not None:
1511 if fo is not None:
1512 fo.close()
1512 fo.close()
1513
1513
1514 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1514 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1515 changes=None, stat=False, fp=None, prefix='',
1515 changes=None, stat=False, fp=None, prefix='',
1516 root='', listsubrepos=False):
1516 root='', listsubrepos=False):
1517 '''show diff or diffstat.'''
1517 '''show diff or diffstat.'''
1518 if fp is None:
1518 if fp is None:
1519 write = ui.write
1519 write = ui.write
1520 else:
1520 else:
1521 def write(s, **kw):
1521 def write(s, **kw):
1522 fp.write(s)
1522 fp.write(s)
1523
1523
1524 if root:
1524 if root:
1525 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1525 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1526 else:
1526 else:
1527 relroot = ''
1527 relroot = ''
1528 if relroot != '':
1528 if relroot != '':
1529 # XXX relative roots currently don't work if the root is within a
1529 # XXX relative roots currently don't work if the root is within a
1530 # subrepo
1530 # subrepo
1531 uirelroot = match.uipath(relroot)
1531 uirelroot = match.uipath(relroot)
1532 relroot += '/'
1532 relroot += '/'
1533 for matchroot in match.files():
1533 for matchroot in match.files():
1534 if not matchroot.startswith(relroot):
1534 if not matchroot.startswith(relroot):
1535 ui.warn(_('warning: %s not inside relative root %s\n') % (
1535 ui.warn(_('warning: %s not inside relative root %s\n') % (
1536 match.uipath(matchroot), uirelroot))
1536 match.uipath(matchroot), uirelroot))
1537
1537
1538 if stat:
1538 if stat:
1539 diffopts = diffopts.copy(context=0)
1539 diffopts = diffopts.copy(context=0)
1540 width = 80
1540 width = 80
1541 if not ui.plain():
1541 if not ui.plain():
1542 width = ui.termwidth()
1542 width = ui.termwidth()
1543 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1543 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1544 prefix=prefix, relroot=relroot)
1544 prefix=prefix, relroot=relroot)
1545 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1545 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1546 width=width):
1546 width=width):
1547 write(chunk, label=label)
1547 write(chunk, label=label)
1548 else:
1548 else:
1549 for chunk, label in patch.diffui(repo, node1, node2, match,
1549 for chunk, label in patch.diffui(repo, node1, node2, match,
1550 changes, diffopts, prefix=prefix,
1550 changes, diffopts, prefix=prefix,
1551 relroot=relroot):
1551 relroot=relroot):
1552 write(chunk, label=label)
1552 write(chunk, label=label)
1553
1553
1554 if listsubrepos:
1554 if listsubrepos:
1555 ctx1 = repo[node1]
1555 ctx1 = repo[node1]
1556 ctx2 = repo[node2]
1556 ctx2 = repo[node2]
1557 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1557 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1558 tempnode2 = node2
1558 tempnode2 = node2
1559 try:
1559 try:
1560 if node2 is not None:
1560 if node2 is not None:
1561 tempnode2 = ctx2.substate[subpath][1]
1561 tempnode2 = ctx2.substate[subpath][1]
1562 except KeyError:
1562 except KeyError:
1563 # A subrepo that existed in node1 was deleted between node1 and
1563 # A subrepo that existed in node1 was deleted between node1 and
1564 # node2 (inclusive). Thus, ctx2's substate won't contain that
1564 # node2 (inclusive). Thus, ctx2's substate won't contain that
1565 # subpath. The best we can do is to ignore it.
1565 # subpath. The best we can do is to ignore it.
1566 tempnode2 = None
1566 tempnode2 = None
1567 submatch = matchmod.subdirmatcher(subpath, match)
1567 submatch = matchmod.subdirmatcher(subpath, match)
1568 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1568 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1569 stat=stat, fp=fp, prefix=prefix)
1569 stat=stat, fp=fp, prefix=prefix)
1570
1570
1571 def _changesetlabels(ctx):
1571 def _changesetlabels(ctx):
1572 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1572 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1573 if ctx.obsolete():
1573 if ctx.obsolete():
1574 labels.append('changeset.obsolete')
1574 labels.append('changeset.obsolete')
1575 if ctx.isunstable():
1575 if ctx.isunstable():
1576 labels.append('changeset.unstable')
1576 labels.append('changeset.unstable')
1577 for instability in ctx.instabilities():
1577 for instability in ctx.instabilities():
1578 labels.append('instability.%s' % instability)
1578 labels.append('instability.%s' % instability)
1579 return ' '.join(labels)
1579 return ' '.join(labels)
1580
1580
1581 class changeset_printer(object):
1581 class changeset_printer(object):
1582 '''show changeset information when templating not requested.'''
1582 '''show changeset information when templating not requested.'''
1583
1583
1584 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1584 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1585 self.ui = ui
1585 self.ui = ui
1586 self.repo = repo
1586 self.repo = repo
1587 self.buffered = buffered
1587 self.buffered = buffered
1588 self.matchfn = matchfn
1588 self.matchfn = matchfn
1589 self.diffopts = diffopts
1589 self.diffopts = diffopts
1590 self.header = {}
1590 self.header = {}
1591 self.hunk = {}
1591 self.hunk = {}
1592 self.lastheader = None
1592 self.lastheader = None
1593 self.footer = None
1593 self.footer = None
1594
1594
1595 def flush(self, ctx):
1595 def flush(self, ctx):
1596 rev = ctx.rev()
1596 rev = ctx.rev()
1597 if rev in self.header:
1597 if rev in self.header:
1598 h = self.header[rev]
1598 h = self.header[rev]
1599 if h != self.lastheader:
1599 if h != self.lastheader:
1600 self.lastheader = h
1600 self.lastheader = h
1601 self.ui.write(h)
1601 self.ui.write(h)
1602 del self.header[rev]
1602 del self.header[rev]
1603 if rev in self.hunk:
1603 if rev in self.hunk:
1604 self.ui.write(self.hunk[rev])
1604 self.ui.write(self.hunk[rev])
1605 del self.hunk[rev]
1605 del self.hunk[rev]
1606 return 1
1606 return 1
1607 return 0
1607 return 0
1608
1608
1609 def close(self):
1609 def close(self):
1610 if self.footer:
1610 if self.footer:
1611 self.ui.write(self.footer)
1611 self.ui.write(self.footer)
1612
1612
1613 def show(self, ctx, copies=None, matchfn=None, **props):
1613 def show(self, ctx, copies=None, matchfn=None, **props):
1614 props = pycompat.byteskwargs(props)
1614 props = pycompat.byteskwargs(props)
1615 if self.buffered:
1615 if self.buffered:
1616 self.ui.pushbuffer(labeled=True)
1616 self.ui.pushbuffer(labeled=True)
1617 self._show(ctx, copies, matchfn, props)
1617 self._show(ctx, copies, matchfn, props)
1618 self.hunk[ctx.rev()] = self.ui.popbuffer()
1618 self.hunk[ctx.rev()] = self.ui.popbuffer()
1619 else:
1619 else:
1620 self._show(ctx, copies, matchfn, props)
1620 self._show(ctx, copies, matchfn, props)
1621
1621
1622 def _show(self, ctx, copies, matchfn, props):
1622 def _show(self, ctx, copies, matchfn, props):
1623 '''show a single changeset or file revision'''
1623 '''show a single changeset or file revision'''
1624 changenode = ctx.node()
1624 changenode = ctx.node()
1625 rev = ctx.rev()
1625 rev = ctx.rev()
1626 if self.ui.debugflag:
1626 if self.ui.debugflag:
1627 hexfunc = hex
1627 hexfunc = hex
1628 else:
1628 else:
1629 hexfunc = short
1629 hexfunc = short
1630 # as of now, wctx.node() and wctx.rev() return None, but we want to
1630 # as of now, wctx.node() and wctx.rev() return None, but we want to
1631 # show the same values as {node} and {rev} templatekw
1631 # show the same values as {node} and {rev} templatekw
1632 revnode = (scmutil.intrev(ctx), hexfunc(scmutil.binnode(ctx)))
1632 revnode = (scmutil.intrev(ctx), hexfunc(scmutil.binnode(ctx)))
1633
1633
1634 if self.ui.quiet:
1634 if self.ui.quiet:
1635 self.ui.write("%d:%s\n" % revnode, label='log.node')
1635 self.ui.write("%d:%s\n" % revnode, label='log.node')
1636 return
1636 return
1637
1637
1638 date = util.datestr(ctx.date())
1638 date = util.datestr(ctx.date())
1639
1639
1640 # i18n: column positioning for "hg log"
1640 # i18n: column positioning for "hg log"
1641 self.ui.write(_("changeset: %d:%s\n") % revnode,
1641 self.ui.write(_("changeset: %d:%s\n") % revnode,
1642 label=_changesetlabels(ctx))
1642 label=_changesetlabels(ctx))
1643
1643
1644 # branches are shown first before any other names due to backwards
1644 # branches are shown first before any other names due to backwards
1645 # compatibility
1645 # compatibility
1646 branch = ctx.branch()
1646 branch = ctx.branch()
1647 # don't show the default branch name
1647 # don't show the default branch name
1648 if branch != 'default':
1648 if branch != 'default':
1649 # i18n: column positioning for "hg log"
1649 # i18n: column positioning for "hg log"
1650 self.ui.write(_("branch: %s\n") % branch,
1650 self.ui.write(_("branch: %s\n") % branch,
1651 label='log.branch')
1651 label='log.branch')
1652
1652
1653 for nsname, ns in self.repo.names.iteritems():
1653 for nsname, ns in self.repo.names.iteritems():
1654 # branches has special logic already handled above, so here we just
1654 # branches has special logic already handled above, so here we just
1655 # skip it
1655 # skip it
1656 if nsname == 'branches':
1656 if nsname == 'branches':
1657 continue
1657 continue
1658 # we will use the templatename as the color name since those two
1658 # we will use the templatename as the color name since those two
1659 # should be the same
1659 # should be the same
1660 for name in ns.names(self.repo, changenode):
1660 for name in ns.names(self.repo, changenode):
1661 self.ui.write(ns.logfmt % name,
1661 self.ui.write(ns.logfmt % name,
1662 label='log.%s' % ns.colorname)
1662 label='log.%s' % ns.colorname)
1663 if self.ui.debugflag:
1663 if self.ui.debugflag:
1664 # i18n: column positioning for "hg log"
1664 # i18n: column positioning for "hg log"
1665 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1665 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1666 label='log.phase')
1666 label='log.phase')
1667 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1667 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1668 label = 'log.parent changeset.%s' % pctx.phasestr()
1668 label = 'log.parent changeset.%s' % pctx.phasestr()
1669 # i18n: column positioning for "hg log"
1669 # i18n: column positioning for "hg log"
1670 self.ui.write(_("parent: %d:%s\n")
1670 self.ui.write(_("parent: %d:%s\n")
1671 % (pctx.rev(), hexfunc(pctx.node())),
1671 % (pctx.rev(), hexfunc(pctx.node())),
1672 label=label)
1672 label=label)
1673
1673
1674 if self.ui.debugflag and rev is not None:
1674 if self.ui.debugflag and rev is not None:
1675 mnode = ctx.manifestnode()
1675 mnode = ctx.manifestnode()
1676 # i18n: column positioning for "hg log"
1676 # i18n: column positioning for "hg log"
1677 self.ui.write(_("manifest: %d:%s\n") %
1677 self.ui.write(_("manifest: %d:%s\n") %
1678 (self.repo.manifestlog._revlog.rev(mnode),
1678 (self.repo.manifestlog._revlog.rev(mnode),
1679 hex(mnode)),
1679 hex(mnode)),
1680 label='ui.debug log.manifest')
1680 label='ui.debug log.manifest')
1681 # i18n: column positioning for "hg log"
1681 # i18n: column positioning for "hg log"
1682 self.ui.write(_("user: %s\n") % ctx.user(),
1682 self.ui.write(_("user: %s\n") % ctx.user(),
1683 label='log.user')
1683 label='log.user')
1684 # i18n: column positioning for "hg log"
1684 # i18n: column positioning for "hg log"
1685 self.ui.write(_("date: %s\n") % date,
1685 self.ui.write(_("date: %s\n") % date,
1686 label='log.date')
1686 label='log.date')
1687
1687
1688 if ctx.isunstable():
1688 if ctx.isunstable():
1689 # i18n: column positioning for "hg log"
1689 # i18n: column positioning for "hg log"
1690 instabilities = ctx.instabilities()
1690 instabilities = ctx.instabilities()
1691 self.ui.write(_("instability: %s\n") % ', '.join(instabilities),
1691 self.ui.write(_("instability: %s\n") % ', '.join(instabilities),
1692 label='log.instability')
1692 label='log.instability')
1693
1693
1694 self._exthook(ctx)
1694 self._exthook(ctx)
1695
1695
1696 if self.ui.debugflag:
1696 if self.ui.debugflag:
1697 files = ctx.p1().status(ctx)[:3]
1697 files = ctx.p1().status(ctx)[:3]
1698 for key, value in zip([# i18n: column positioning for "hg log"
1698 for key, value in zip([# i18n: column positioning for "hg log"
1699 _("files:"),
1699 _("files:"),
1700 # i18n: column positioning for "hg log"
1700 # i18n: column positioning for "hg log"
1701 _("files+:"),
1701 _("files+:"),
1702 # i18n: column positioning for "hg log"
1702 # i18n: column positioning for "hg log"
1703 _("files-:")], files):
1703 _("files-:")], files):
1704 if value:
1704 if value:
1705 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1705 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1706 label='ui.debug log.files')
1706 label='ui.debug log.files')
1707 elif ctx.files() and self.ui.verbose:
1707 elif ctx.files() and self.ui.verbose:
1708 # i18n: column positioning for "hg log"
1708 # i18n: column positioning for "hg log"
1709 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1709 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1710 label='ui.note log.files')
1710 label='ui.note log.files')
1711 if copies and self.ui.verbose:
1711 if copies and self.ui.verbose:
1712 copies = ['%s (%s)' % c for c in copies]
1712 copies = ['%s (%s)' % c for c in copies]
1713 # i18n: column positioning for "hg log"
1713 # i18n: column positioning for "hg log"
1714 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1714 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1715 label='ui.note log.copies')
1715 label='ui.note log.copies')
1716
1716
1717 extra = ctx.extra()
1717 extra = ctx.extra()
1718 if extra and self.ui.debugflag:
1718 if extra and self.ui.debugflag:
1719 for key, value in sorted(extra.items()):
1719 for key, value in sorted(extra.items()):
1720 # i18n: column positioning for "hg log"
1720 # i18n: column positioning for "hg log"
1721 self.ui.write(_("extra: %s=%s\n")
1721 self.ui.write(_("extra: %s=%s\n")
1722 % (key, util.escapestr(value)),
1722 % (key, util.escapestr(value)),
1723 label='ui.debug log.extra')
1723 label='ui.debug log.extra')
1724
1724
1725 description = ctx.description().strip()
1725 description = ctx.description().strip()
1726 if description:
1726 if description:
1727 if self.ui.verbose:
1727 if self.ui.verbose:
1728 self.ui.write(_("description:\n"),
1728 self.ui.write(_("description:\n"),
1729 label='ui.note log.description')
1729 label='ui.note log.description')
1730 self.ui.write(description,
1730 self.ui.write(description,
1731 label='ui.note log.description')
1731 label='ui.note log.description')
1732 self.ui.write("\n\n")
1732 self.ui.write("\n\n")
1733 else:
1733 else:
1734 # i18n: column positioning for "hg log"
1734 # i18n: column positioning for "hg log"
1735 self.ui.write(_("summary: %s\n") %
1735 self.ui.write(_("summary: %s\n") %
1736 description.splitlines()[0],
1736 description.splitlines()[0],
1737 label='log.summary')
1737 label='log.summary')
1738 self.ui.write("\n")
1738 self.ui.write("\n")
1739
1739
1740 self.showpatch(ctx, matchfn)
1740 self.showpatch(ctx, matchfn)
1741
1741
1742 def _exthook(self, ctx):
1742 def _exthook(self, ctx):
1743 '''empty method used by extension as a hook point
1743 '''empty method used by extension as a hook point
1744 '''
1744 '''
1745 pass
1745 pass
1746
1746
1747 def showpatch(self, ctx, matchfn):
1747 def showpatch(self, ctx, matchfn):
1748 if not matchfn:
1748 if not matchfn:
1749 matchfn = self.matchfn
1749 matchfn = self.matchfn
1750 if matchfn:
1750 if matchfn:
1751 stat = self.diffopts.get('stat')
1751 stat = self.diffopts.get('stat')
1752 diff = self.diffopts.get('patch')
1752 diff = self.diffopts.get('patch')
1753 diffopts = patch.diffallopts(self.ui, self.diffopts)
1753 diffopts = patch.diffallopts(self.ui, self.diffopts)
1754 node = ctx.node()
1754 node = ctx.node()
1755 prev = ctx.p1().node()
1755 prev = ctx.p1().node()
1756 if stat:
1756 if stat:
1757 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1757 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1758 match=matchfn, stat=True)
1758 match=matchfn, stat=True)
1759 if diff:
1759 if diff:
1760 if stat:
1760 if stat:
1761 self.ui.write("\n")
1761 self.ui.write("\n")
1762 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1762 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1763 match=matchfn, stat=False)
1763 match=matchfn, stat=False)
1764 self.ui.write("\n")
1764 self.ui.write("\n")
1765
1765
1766 class jsonchangeset(changeset_printer):
1766 class jsonchangeset(changeset_printer):
1767 '''format changeset information.'''
1767 '''format changeset information.'''
1768
1768
1769 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1769 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1770 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1770 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1771 self.cache = {}
1771 self.cache = {}
1772 self._first = True
1772 self._first = True
1773
1773
1774 def close(self):
1774 def close(self):
1775 if not self._first:
1775 if not self._first:
1776 self.ui.write("\n]\n")
1776 self.ui.write("\n]\n")
1777 else:
1777 else:
1778 self.ui.write("[]\n")
1778 self.ui.write("[]\n")
1779
1779
1780 def _show(self, ctx, copies, matchfn, props):
1780 def _show(self, ctx, copies, matchfn, props):
1781 '''show a single changeset or file revision'''
1781 '''show a single changeset or file revision'''
1782 rev = ctx.rev()
1782 rev = ctx.rev()
1783 if rev is None:
1783 if rev is None:
1784 jrev = jnode = 'null'
1784 jrev = jnode = 'null'
1785 else:
1785 else:
1786 jrev = '%d' % rev
1786 jrev = '%d' % rev
1787 jnode = '"%s"' % hex(ctx.node())
1787 jnode = '"%s"' % hex(ctx.node())
1788 j = encoding.jsonescape
1788 j = encoding.jsonescape
1789
1789
1790 if self._first:
1790 if self._first:
1791 self.ui.write("[\n {")
1791 self.ui.write("[\n {")
1792 self._first = False
1792 self._first = False
1793 else:
1793 else:
1794 self.ui.write(",\n {")
1794 self.ui.write(",\n {")
1795
1795
1796 if self.ui.quiet:
1796 if self.ui.quiet:
1797 self.ui.write(('\n "rev": %s') % jrev)
1797 self.ui.write(('\n "rev": %s') % jrev)
1798 self.ui.write((',\n "node": %s') % jnode)
1798 self.ui.write((',\n "node": %s') % jnode)
1799 self.ui.write('\n }')
1799 self.ui.write('\n }')
1800 return
1800 return
1801
1801
1802 self.ui.write(('\n "rev": %s') % jrev)
1802 self.ui.write(('\n "rev": %s') % jrev)
1803 self.ui.write((',\n "node": %s') % jnode)
1803 self.ui.write((',\n "node": %s') % jnode)
1804 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1804 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1805 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1805 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1806 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1806 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1807 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1807 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1808 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1808 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1809
1809
1810 self.ui.write((',\n "bookmarks": [%s]') %
1810 self.ui.write((',\n "bookmarks": [%s]') %
1811 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1811 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1812 self.ui.write((',\n "tags": [%s]') %
1812 self.ui.write((',\n "tags": [%s]') %
1813 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1813 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1814 self.ui.write((',\n "parents": [%s]') %
1814 self.ui.write((',\n "parents": [%s]') %
1815 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1815 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1816
1816
1817 if self.ui.debugflag:
1817 if self.ui.debugflag:
1818 if rev is None:
1818 if rev is None:
1819 jmanifestnode = 'null'
1819 jmanifestnode = 'null'
1820 else:
1820 else:
1821 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1821 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1822 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1822 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1823
1823
1824 self.ui.write((',\n "extra": {%s}') %
1824 self.ui.write((',\n "extra": {%s}') %
1825 ", ".join('"%s": "%s"' % (j(k), j(v))
1825 ", ".join('"%s": "%s"' % (j(k), j(v))
1826 for k, v in ctx.extra().items()))
1826 for k, v in ctx.extra().items()))
1827
1827
1828 files = ctx.p1().status(ctx)
1828 files = ctx.p1().status(ctx)
1829 self.ui.write((',\n "modified": [%s]') %
1829 self.ui.write((',\n "modified": [%s]') %
1830 ", ".join('"%s"' % j(f) for f in files[0]))
1830 ", ".join('"%s"' % j(f) for f in files[0]))
1831 self.ui.write((',\n "added": [%s]') %
1831 self.ui.write((',\n "added": [%s]') %
1832 ", ".join('"%s"' % j(f) for f in files[1]))
1832 ", ".join('"%s"' % j(f) for f in files[1]))
1833 self.ui.write((',\n "removed": [%s]') %
1833 self.ui.write((',\n "removed": [%s]') %
1834 ", ".join('"%s"' % j(f) for f in files[2]))
1834 ", ".join('"%s"' % j(f) for f in files[2]))
1835
1835
1836 elif self.ui.verbose:
1836 elif self.ui.verbose:
1837 self.ui.write((',\n "files": [%s]') %
1837 self.ui.write((',\n "files": [%s]') %
1838 ", ".join('"%s"' % j(f) for f in ctx.files()))
1838 ", ".join('"%s"' % j(f) for f in ctx.files()))
1839
1839
1840 if copies:
1840 if copies:
1841 self.ui.write((',\n "copies": {%s}') %
1841 self.ui.write((',\n "copies": {%s}') %
1842 ", ".join('"%s": "%s"' % (j(k), j(v))
1842 ", ".join('"%s": "%s"' % (j(k), j(v))
1843 for k, v in copies))
1843 for k, v in copies))
1844
1844
1845 matchfn = self.matchfn
1845 matchfn = self.matchfn
1846 if matchfn:
1846 if matchfn:
1847 stat = self.diffopts.get('stat')
1847 stat = self.diffopts.get('stat')
1848 diff = self.diffopts.get('patch')
1848 diff = self.diffopts.get('patch')
1849 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1849 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1850 node, prev = ctx.node(), ctx.p1().node()
1850 node, prev = ctx.node(), ctx.p1().node()
1851 if stat:
1851 if stat:
1852 self.ui.pushbuffer()
1852 self.ui.pushbuffer()
1853 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1853 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1854 match=matchfn, stat=True)
1854 match=matchfn, stat=True)
1855 self.ui.write((',\n "diffstat": "%s"')
1855 self.ui.write((',\n "diffstat": "%s"')
1856 % j(self.ui.popbuffer()))
1856 % j(self.ui.popbuffer()))
1857 if diff:
1857 if diff:
1858 self.ui.pushbuffer()
1858 self.ui.pushbuffer()
1859 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1859 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1860 match=matchfn, stat=False)
1860 match=matchfn, stat=False)
1861 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1861 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1862
1862
1863 self.ui.write("\n }")
1863 self.ui.write("\n }")
1864
1864
1865 class changeset_templater(changeset_printer):
1865 class changeset_templater(changeset_printer):
1866 '''format changeset information.'''
1866 '''format changeset information.'''
1867
1867
1868 # Arguments before "buffered" used to be positional. Consider not
1868 # Arguments before "buffered" used to be positional. Consider not
1869 # adding/removing arguments before "buffered" to not break callers.
1869 # adding/removing arguments before "buffered" to not break callers.
1870 def __init__(self, ui, repo, tmplspec, matchfn=None, diffopts=None,
1870 def __init__(self, ui, repo, tmplspec, matchfn=None, diffopts=None,
1871 buffered=False):
1871 buffered=False):
1872 diffopts = diffopts or {}
1872 diffopts = diffopts or {}
1873
1873
1874 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1874 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1875 self.t = formatter.loadtemplater(ui, tmplspec,
1875 self.t = formatter.loadtemplater(ui, tmplspec,
1876 cache=templatekw.defaulttempl)
1876 cache=templatekw.defaulttempl)
1877 self._counter = itertools.count()
1877 self._counter = itertools.count()
1878 self.cache = {}
1878 self.cache = {}
1879
1879
1880 self._tref = tmplspec.ref
1880 self._tref = tmplspec.ref
1881 self._parts = {'header': '', 'footer': '',
1881 self._parts = {'header': '', 'footer': '',
1882 tmplspec.ref: tmplspec.ref,
1882 tmplspec.ref: tmplspec.ref,
1883 'docheader': '', 'docfooter': '',
1883 'docheader': '', 'docfooter': '',
1884 'separator': ''}
1884 'separator': ''}
1885 if tmplspec.mapfile:
1885 if tmplspec.mapfile:
1886 # find correct templates for current mode, for backward
1886 # find correct templates for current mode, for backward
1887 # compatibility with 'log -v/-q/--debug' using a mapfile
1887 # compatibility with 'log -v/-q/--debug' using a mapfile
1888 tmplmodes = [
1888 tmplmodes = [
1889 (True, ''),
1889 (True, ''),
1890 (self.ui.verbose, '_verbose'),
1890 (self.ui.verbose, '_verbose'),
1891 (self.ui.quiet, '_quiet'),
1891 (self.ui.quiet, '_quiet'),
1892 (self.ui.debugflag, '_debug'),
1892 (self.ui.debugflag, '_debug'),
1893 ]
1893 ]
1894 for mode, postfix in tmplmodes:
1894 for mode, postfix in tmplmodes:
1895 for t in self._parts:
1895 for t in self._parts:
1896 cur = t + postfix
1896 cur = t + postfix
1897 if mode and cur in self.t:
1897 if mode and cur in self.t:
1898 self._parts[t] = cur
1898 self._parts[t] = cur
1899 else:
1899 else:
1900 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
1900 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
1901 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
1901 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
1902 self._parts.update(m)
1902 self._parts.update(m)
1903
1903
1904 if self._parts['docheader']:
1904 if self._parts['docheader']:
1905 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1905 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1906
1906
1907 def close(self):
1907 def close(self):
1908 if self._parts['docfooter']:
1908 if self._parts['docfooter']:
1909 if not self.footer:
1909 if not self.footer:
1910 self.footer = ""
1910 self.footer = ""
1911 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1911 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1912 return super(changeset_templater, self).close()
1912 return super(changeset_templater, self).close()
1913
1913
1914 def _show(self, ctx, copies, matchfn, props):
1914 def _show(self, ctx, copies, matchfn, props):
1915 '''show a single changeset or file revision'''
1915 '''show a single changeset or file revision'''
1916 props = props.copy()
1916 props = props.copy()
1917 props.update(templatekw.keywords)
1917 props.update(templatekw.keywords)
1918 props['templ'] = self.t
1918 props['templ'] = self.t
1919 props['ctx'] = ctx
1919 props['ctx'] = ctx
1920 props['repo'] = self.repo
1920 props['repo'] = self.repo
1921 props['ui'] = self.repo.ui
1921 props['ui'] = self.repo.ui
1922 props['index'] = index = next(self._counter)
1922 props['index'] = index = next(self._counter)
1923 props['revcache'] = {'copies': copies}
1923 props['revcache'] = {'copies': copies}
1924 props['cache'] = self.cache
1924 props['cache'] = self.cache
1925 props = pycompat.strkwargs(props)
1925 props = pycompat.strkwargs(props)
1926
1926
1927 # write separator, which wouldn't work well with the header part below
1927 # write separator, which wouldn't work well with the header part below
1928 # since there's inherently a conflict between header (across items) and
1928 # since there's inherently a conflict between header (across items) and
1929 # separator (per item)
1929 # separator (per item)
1930 if self._parts['separator'] and index > 0:
1930 if self._parts['separator'] and index > 0:
1931 self.ui.write(templater.stringify(self.t(self._parts['separator'])))
1931 self.ui.write(templater.stringify(self.t(self._parts['separator'])))
1932
1932
1933 # write header
1933 # write header
1934 if self._parts['header']:
1934 if self._parts['header']:
1935 h = templater.stringify(self.t(self._parts['header'], **props))
1935 h = templater.stringify(self.t(self._parts['header'], **props))
1936 if self.buffered:
1936 if self.buffered:
1937 self.header[ctx.rev()] = h
1937 self.header[ctx.rev()] = h
1938 else:
1938 else:
1939 if self.lastheader != h:
1939 if self.lastheader != h:
1940 self.lastheader = h
1940 self.lastheader = h
1941 self.ui.write(h)
1941 self.ui.write(h)
1942
1942
1943 # write changeset metadata, then patch if requested
1943 # write changeset metadata, then patch if requested
1944 key = self._parts[self._tref]
1944 key = self._parts[self._tref]
1945 self.ui.write(templater.stringify(self.t(key, **props)))
1945 self.ui.write(templater.stringify(self.t(key, **props)))
1946 self.showpatch(ctx, matchfn)
1946 self.showpatch(ctx, matchfn)
1947
1947
1948 if self._parts['footer']:
1948 if self._parts['footer']:
1949 if not self.footer:
1949 if not self.footer:
1950 self.footer = templater.stringify(
1950 self.footer = templater.stringify(
1951 self.t(self._parts['footer'], **props))
1951 self.t(self._parts['footer'], **props))
1952
1952
1953 def logtemplatespec(tmpl, mapfile):
1953 def logtemplatespec(tmpl, mapfile):
1954 if mapfile:
1954 if mapfile:
1955 return formatter.templatespec('changeset', tmpl, mapfile)
1955 return formatter.templatespec('changeset', tmpl, mapfile)
1956 else:
1956 else:
1957 return formatter.templatespec('', tmpl, None)
1957 return formatter.templatespec('', tmpl, None)
1958
1958
1959 def _lookuplogtemplate(ui, tmpl, style):
1959 def _lookuplogtemplate(ui, tmpl, style):
1960 """Find the template matching the given template spec or style
1960 """Find the template matching the given template spec or style
1961
1961
1962 See formatter.lookuptemplate() for details.
1962 See formatter.lookuptemplate() for details.
1963 """
1963 """
1964
1964
1965 # ui settings
1965 # ui settings
1966 if not tmpl and not style: # template are stronger than style
1966 if not tmpl and not style: # template are stronger than style
1967 tmpl = ui.config('ui', 'logtemplate')
1967 tmpl = ui.config('ui', 'logtemplate')
1968 if tmpl:
1968 if tmpl:
1969 return logtemplatespec(templater.unquotestring(tmpl), None)
1969 return logtemplatespec(templater.unquotestring(tmpl), None)
1970 else:
1970 else:
1971 style = util.expandpath(ui.config('ui', 'style'))
1971 style = util.expandpath(ui.config('ui', 'style'))
1972
1972
1973 if not tmpl and style:
1973 if not tmpl and style:
1974 mapfile = style
1974 mapfile = style
1975 if not os.path.split(mapfile)[0]:
1975 if not os.path.split(mapfile)[0]:
1976 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1976 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1977 or templater.templatepath(mapfile))
1977 or templater.templatepath(mapfile))
1978 if mapname:
1978 if mapname:
1979 mapfile = mapname
1979 mapfile = mapname
1980 return logtemplatespec(None, mapfile)
1980 return logtemplatespec(None, mapfile)
1981
1981
1982 if not tmpl:
1982 if not tmpl:
1983 return logtemplatespec(None, None)
1983 return logtemplatespec(None, None)
1984
1984
1985 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1985 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1986
1986
1987 def makelogtemplater(ui, repo, tmpl, buffered=False):
1987 def makelogtemplater(ui, repo, tmpl, buffered=False):
1988 """Create a changeset_templater from a literal template 'tmpl'"""
1988 """Create a changeset_templater from a literal template 'tmpl'"""
1989 spec = logtemplatespec(tmpl, None)
1989 spec = logtemplatespec(tmpl, None)
1990 return changeset_templater(ui, repo, spec, buffered=buffered)
1990 return changeset_templater(ui, repo, spec, buffered=buffered)
1991
1991
1992 def show_changeset(ui, repo, opts, buffered=False):
1992 def show_changeset(ui, repo, opts, buffered=False):
1993 """show one changeset using template or regular display.
1993 """show one changeset using template or regular display.
1994
1994
1995 Display format will be the first non-empty hit of:
1995 Display format will be the first non-empty hit of:
1996 1. option 'template'
1996 1. option 'template'
1997 2. option 'style'
1997 2. option 'style'
1998 3. [ui] setting 'logtemplate'
1998 3. [ui] setting 'logtemplate'
1999 4. [ui] setting 'style'
1999 4. [ui] setting 'style'
2000 If all of these values are either the unset or the empty string,
2000 If all of these values are either the unset or the empty string,
2001 regular display via changeset_printer() is done.
2001 regular display via changeset_printer() is done.
2002 """
2002 """
2003 # options
2003 # options
2004 matchfn = None
2004 match = None
2005 if opts.get('patch') or opts.get('stat'):
2005 if opts.get('patch') or opts.get('stat'):
2006 matchfn = scmutil.matchall(repo)
2006 match = scmutil.matchall(repo)
2007
2007
2008 if opts.get('template') == 'json':
2008 if opts.get('template') == 'json':
2009 return jsonchangeset(ui, repo, matchfn, opts, buffered)
2009 return jsonchangeset(ui, repo, match, opts, buffered)
2010
2010
2011 spec = _lookuplogtemplate(ui, opts.get('template'), opts.get('style'))
2011 spec = _lookuplogtemplate(ui, opts.get('template'), opts.get('style'))
2012
2012
2013 if not spec.ref and not spec.tmpl and not spec.mapfile:
2013 if not spec.ref and not spec.tmpl and not spec.mapfile:
2014 return changeset_printer(ui, repo, matchfn, opts, buffered)
2014 return changeset_printer(ui, repo, match, opts, buffered)
2015
2015
2016 return changeset_templater(ui, repo, spec, matchfn, opts, buffered)
2016 return changeset_templater(ui, repo, spec, match, opts, buffered)
2017
2017
2018 def showmarker(fm, marker, index=None):
2018 def showmarker(fm, marker, index=None):
2019 """utility function to display obsolescence marker in a readable way
2019 """utility function to display obsolescence marker in a readable way
2020
2020
2021 To be used by debug function."""
2021 To be used by debug function."""
2022 if index is not None:
2022 if index is not None:
2023 fm.write('index', '%i ', index)
2023 fm.write('index', '%i ', index)
2024 fm.write('prednode', '%s ', hex(marker.prednode()))
2024 fm.write('prednode', '%s ', hex(marker.prednode()))
2025 succs = marker.succnodes()
2025 succs = marker.succnodes()
2026 fm.condwrite(succs, 'succnodes', '%s ',
2026 fm.condwrite(succs, 'succnodes', '%s ',
2027 fm.formatlist(map(hex, succs), name='node'))
2027 fm.formatlist(map(hex, succs), name='node'))
2028 fm.write('flag', '%X ', marker.flags())
2028 fm.write('flag', '%X ', marker.flags())
2029 parents = marker.parentnodes()
2029 parents = marker.parentnodes()
2030 if parents is not None:
2030 if parents is not None:
2031 fm.write('parentnodes', '{%s} ',
2031 fm.write('parentnodes', '{%s} ',
2032 fm.formatlist(map(hex, parents), name='node', sep=', '))
2032 fm.formatlist(map(hex, parents), name='node', sep=', '))
2033 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
2033 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
2034 meta = marker.metadata().copy()
2034 meta = marker.metadata().copy()
2035 meta.pop('date', None)
2035 meta.pop('date', None)
2036 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
2036 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
2037 fm.plain('\n')
2037 fm.plain('\n')
2038
2038
2039 def finddate(ui, repo, date):
2039 def finddate(ui, repo, date):
2040 """Find the tipmost changeset that matches the given date spec"""
2040 """Find the tipmost changeset that matches the given date spec"""
2041
2041
2042 df = util.matchdate(date)
2042 df = util.matchdate(date)
2043 m = scmutil.matchall(repo)
2043 m = scmutil.matchall(repo)
2044 results = {}
2044 results = {}
2045
2045
2046 def prep(ctx, fns):
2046 def prep(ctx, fns):
2047 d = ctx.date()
2047 d = ctx.date()
2048 if df(d[0]):
2048 if df(d[0]):
2049 results[ctx.rev()] = d
2049 results[ctx.rev()] = d
2050
2050
2051 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
2051 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
2052 rev = ctx.rev()
2052 rev = ctx.rev()
2053 if rev in results:
2053 if rev in results:
2054 ui.status(_("found revision %s from %s\n") %
2054 ui.status(_("found revision %s from %s\n") %
2055 (rev, util.datestr(results[rev])))
2055 (rev, util.datestr(results[rev])))
2056 return '%d' % rev
2056 return '%d' % rev
2057
2057
2058 raise error.Abort(_("revision matching date not found"))
2058 raise error.Abort(_("revision matching date not found"))
2059
2059
2060 def increasingwindows(windowsize=8, sizelimit=512):
2060 def increasingwindows(windowsize=8, sizelimit=512):
2061 while True:
2061 while True:
2062 yield windowsize
2062 yield windowsize
2063 if windowsize < sizelimit:
2063 if windowsize < sizelimit:
2064 windowsize *= 2
2064 windowsize *= 2
2065
2065
2066 class FileWalkError(Exception):
2066 class FileWalkError(Exception):
2067 pass
2067 pass
2068
2068
2069 def walkfilerevs(repo, match, follow, revs, fncache):
2069 def walkfilerevs(repo, match, follow, revs, fncache):
2070 '''Walks the file history for the matched files.
2070 '''Walks the file history for the matched files.
2071
2071
2072 Returns the changeset revs that are involved in the file history.
2072 Returns the changeset revs that are involved in the file history.
2073
2073
2074 Throws FileWalkError if the file history can't be walked using
2074 Throws FileWalkError if the file history can't be walked using
2075 filelogs alone.
2075 filelogs alone.
2076 '''
2076 '''
2077 wanted = set()
2077 wanted = set()
2078 copies = []
2078 copies = []
2079 minrev, maxrev = min(revs), max(revs)
2079 minrev, maxrev = min(revs), max(revs)
2080 def filerevgen(filelog, last):
2080 def filerevgen(filelog, last):
2081 """
2081 """
2082 Only files, no patterns. Check the history of each file.
2082 Only files, no patterns. Check the history of each file.
2083
2083
2084 Examines filelog entries within minrev, maxrev linkrev range
2084 Examines filelog entries within minrev, maxrev linkrev range
2085 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
2085 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
2086 tuples in backwards order
2086 tuples in backwards order
2087 """
2087 """
2088 cl_count = len(repo)
2088 cl_count = len(repo)
2089 revs = []
2089 revs = []
2090 for j in xrange(0, last + 1):
2090 for j in xrange(0, last + 1):
2091 linkrev = filelog.linkrev(j)
2091 linkrev = filelog.linkrev(j)
2092 if linkrev < minrev:
2092 if linkrev < minrev:
2093 continue
2093 continue
2094 # only yield rev for which we have the changelog, it can
2094 # only yield rev for which we have the changelog, it can
2095 # happen while doing "hg log" during a pull or commit
2095 # happen while doing "hg log" during a pull or commit
2096 if linkrev >= cl_count:
2096 if linkrev >= cl_count:
2097 break
2097 break
2098
2098
2099 parentlinkrevs = []
2099 parentlinkrevs = []
2100 for p in filelog.parentrevs(j):
2100 for p in filelog.parentrevs(j):
2101 if p != nullrev:
2101 if p != nullrev:
2102 parentlinkrevs.append(filelog.linkrev(p))
2102 parentlinkrevs.append(filelog.linkrev(p))
2103 n = filelog.node(j)
2103 n = filelog.node(j)
2104 revs.append((linkrev, parentlinkrevs,
2104 revs.append((linkrev, parentlinkrevs,
2105 follow and filelog.renamed(n)))
2105 follow and filelog.renamed(n)))
2106
2106
2107 return reversed(revs)
2107 return reversed(revs)
2108 def iterfiles():
2108 def iterfiles():
2109 pctx = repo['.']
2109 pctx = repo['.']
2110 for filename in match.files():
2110 for filename in match.files():
2111 if follow:
2111 if follow:
2112 if filename not in pctx:
2112 if filename not in pctx:
2113 raise error.Abort(_('cannot follow file not in parent '
2113 raise error.Abort(_('cannot follow file not in parent '
2114 'revision: "%s"') % filename)
2114 'revision: "%s"') % filename)
2115 yield filename, pctx[filename].filenode()
2115 yield filename, pctx[filename].filenode()
2116 else:
2116 else:
2117 yield filename, None
2117 yield filename, None
2118 for filename_node in copies:
2118 for filename_node in copies:
2119 yield filename_node
2119 yield filename_node
2120
2120
2121 for file_, node in iterfiles():
2121 for file_, node in iterfiles():
2122 filelog = repo.file(file_)
2122 filelog = repo.file(file_)
2123 if not len(filelog):
2123 if not len(filelog):
2124 if node is None:
2124 if node is None:
2125 # A zero count may be a directory or deleted file, so
2125 # A zero count may be a directory or deleted file, so
2126 # try to find matching entries on the slow path.
2126 # try to find matching entries on the slow path.
2127 if follow:
2127 if follow:
2128 raise error.Abort(
2128 raise error.Abort(
2129 _('cannot follow nonexistent file: "%s"') % file_)
2129 _('cannot follow nonexistent file: "%s"') % file_)
2130 raise FileWalkError("Cannot walk via filelog")
2130 raise FileWalkError("Cannot walk via filelog")
2131 else:
2131 else:
2132 continue
2132 continue
2133
2133
2134 if node is None:
2134 if node is None:
2135 last = len(filelog) - 1
2135 last = len(filelog) - 1
2136 else:
2136 else:
2137 last = filelog.rev(node)
2137 last = filelog.rev(node)
2138
2138
2139 # keep track of all ancestors of the file
2139 # keep track of all ancestors of the file
2140 ancestors = {filelog.linkrev(last)}
2140 ancestors = {filelog.linkrev(last)}
2141
2141
2142 # iterate from latest to oldest revision
2142 # iterate from latest to oldest revision
2143 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
2143 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
2144 if not follow:
2144 if not follow:
2145 if rev > maxrev:
2145 if rev > maxrev:
2146 continue
2146 continue
2147 else:
2147 else:
2148 # Note that last might not be the first interesting
2148 # Note that last might not be the first interesting
2149 # rev to us:
2149 # rev to us:
2150 # if the file has been changed after maxrev, we'll
2150 # if the file has been changed after maxrev, we'll
2151 # have linkrev(last) > maxrev, and we still need
2151 # have linkrev(last) > maxrev, and we still need
2152 # to explore the file graph
2152 # to explore the file graph
2153 if rev not in ancestors:
2153 if rev not in ancestors:
2154 continue
2154 continue
2155 # XXX insert 1327 fix here
2155 # XXX insert 1327 fix here
2156 if flparentlinkrevs:
2156 if flparentlinkrevs:
2157 ancestors.update(flparentlinkrevs)
2157 ancestors.update(flparentlinkrevs)
2158
2158
2159 fncache.setdefault(rev, []).append(file_)
2159 fncache.setdefault(rev, []).append(file_)
2160 wanted.add(rev)
2160 wanted.add(rev)
2161 if copied:
2161 if copied:
2162 copies.append(copied)
2162 copies.append(copied)
2163
2163
2164 return wanted
2164 return wanted
2165
2165
2166 class _followfilter(object):
2166 class _followfilter(object):
2167 def __init__(self, repo, onlyfirst=False):
2167 def __init__(self, repo, onlyfirst=False):
2168 self.repo = repo
2168 self.repo = repo
2169 self.startrev = nullrev
2169 self.startrev = nullrev
2170 self.roots = set()
2170 self.roots = set()
2171 self.onlyfirst = onlyfirst
2171 self.onlyfirst = onlyfirst
2172
2172
2173 def match(self, rev):
2173 def match(self, rev):
2174 def realparents(rev):
2174 def realparents(rev):
2175 if self.onlyfirst:
2175 if self.onlyfirst:
2176 return self.repo.changelog.parentrevs(rev)[0:1]
2176 return self.repo.changelog.parentrevs(rev)[0:1]
2177 else:
2177 else:
2178 return filter(lambda x: x != nullrev,
2178 return filter(lambda x: x != nullrev,
2179 self.repo.changelog.parentrevs(rev))
2179 self.repo.changelog.parentrevs(rev))
2180
2180
2181 if self.startrev == nullrev:
2181 if self.startrev == nullrev:
2182 self.startrev = rev
2182 self.startrev = rev
2183 return True
2183 return True
2184
2184
2185 if rev > self.startrev:
2185 if rev > self.startrev:
2186 # forward: all descendants
2186 # forward: all descendants
2187 if not self.roots:
2187 if not self.roots:
2188 self.roots.add(self.startrev)
2188 self.roots.add(self.startrev)
2189 for parent in realparents(rev):
2189 for parent in realparents(rev):
2190 if parent in self.roots:
2190 if parent in self.roots:
2191 self.roots.add(rev)
2191 self.roots.add(rev)
2192 return True
2192 return True
2193 else:
2193 else:
2194 # backwards: all parents
2194 # backwards: all parents
2195 if not self.roots:
2195 if not self.roots:
2196 self.roots.update(realparents(self.startrev))
2196 self.roots.update(realparents(self.startrev))
2197 if rev in self.roots:
2197 if rev in self.roots:
2198 self.roots.remove(rev)
2198 self.roots.remove(rev)
2199 self.roots.update(realparents(rev))
2199 self.roots.update(realparents(rev))
2200 return True
2200 return True
2201
2201
2202 return False
2202 return False
2203
2203
2204 def walkchangerevs(repo, match, opts, prepare):
2204 def walkchangerevs(repo, match, opts, prepare):
2205 '''Iterate over files and the revs in which they changed.
2205 '''Iterate over files and the revs in which they changed.
2206
2206
2207 Callers most commonly need to iterate backwards over the history
2207 Callers most commonly need to iterate backwards over the history
2208 in which they are interested. Doing so has awful (quadratic-looking)
2208 in which they are interested. Doing so has awful (quadratic-looking)
2209 performance, so we use iterators in a "windowed" way.
2209 performance, so we use iterators in a "windowed" way.
2210
2210
2211 We walk a window of revisions in the desired order. Within the
2211 We walk a window of revisions in the desired order. Within the
2212 window, we first walk forwards to gather data, then in the desired
2212 window, we first walk forwards to gather data, then in the desired
2213 order (usually backwards) to display it.
2213 order (usually backwards) to display it.
2214
2214
2215 This function returns an iterator yielding contexts. Before
2215 This function returns an iterator yielding contexts. Before
2216 yielding each context, the iterator will first call the prepare
2216 yielding each context, the iterator will first call the prepare
2217 function on each context in the window in forward order.'''
2217 function on each context in the window in forward order.'''
2218
2218
2219 follow = opts.get('follow') or opts.get('follow_first')
2219 follow = opts.get('follow') or opts.get('follow_first')
2220 revs = _logrevs(repo, opts)
2220 revs = _logrevs(repo, opts)
2221 if not revs:
2221 if not revs:
2222 return []
2222 return []
2223 wanted = set()
2223 wanted = set()
2224 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2224 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2225 opts.get('removed'))
2225 opts.get('removed'))
2226 fncache = {}
2226 fncache = {}
2227 change = repo.changectx
2227 change = repo.changectx
2228
2228
2229 # First step is to fill wanted, the set of revisions that we want to yield.
2229 # First step is to fill wanted, the set of revisions that we want to yield.
2230 # When it does not induce extra cost, we also fill fncache for revisions in
2230 # When it does not induce extra cost, we also fill fncache for revisions in
2231 # wanted: a cache of filenames that were changed (ctx.files()) and that
2231 # wanted: a cache of filenames that were changed (ctx.files()) and that
2232 # match the file filtering conditions.
2232 # match the file filtering conditions.
2233
2233
2234 if match.always():
2234 if match.always():
2235 # No files, no patterns. Display all revs.
2235 # No files, no patterns. Display all revs.
2236 wanted = revs
2236 wanted = revs
2237 elif not slowpath:
2237 elif not slowpath:
2238 # We only have to read through the filelog to find wanted revisions
2238 # We only have to read through the filelog to find wanted revisions
2239
2239
2240 try:
2240 try:
2241 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2241 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2242 except FileWalkError:
2242 except FileWalkError:
2243 slowpath = True
2243 slowpath = True
2244
2244
2245 # We decided to fall back to the slowpath because at least one
2245 # We decided to fall back to the slowpath because at least one
2246 # of the paths was not a file. Check to see if at least one of them
2246 # of the paths was not a file. Check to see if at least one of them
2247 # existed in history, otherwise simply return
2247 # existed in history, otherwise simply return
2248 for path in match.files():
2248 for path in match.files():
2249 if path == '.' or path in repo.store:
2249 if path == '.' or path in repo.store:
2250 break
2250 break
2251 else:
2251 else:
2252 return []
2252 return []
2253
2253
2254 if slowpath:
2254 if slowpath:
2255 # We have to read the changelog to match filenames against
2255 # We have to read the changelog to match filenames against
2256 # changed files
2256 # changed files
2257
2257
2258 if follow:
2258 if follow:
2259 raise error.Abort(_('can only follow copies/renames for explicit '
2259 raise error.Abort(_('can only follow copies/renames for explicit '
2260 'filenames'))
2260 'filenames'))
2261
2261
2262 # The slow path checks files modified in every changeset.
2262 # The slow path checks files modified in every changeset.
2263 # This is really slow on large repos, so compute the set lazily.
2263 # This is really slow on large repos, so compute the set lazily.
2264 class lazywantedset(object):
2264 class lazywantedset(object):
2265 def __init__(self):
2265 def __init__(self):
2266 self.set = set()
2266 self.set = set()
2267 self.revs = set(revs)
2267 self.revs = set(revs)
2268
2268
2269 # No need to worry about locality here because it will be accessed
2269 # No need to worry about locality here because it will be accessed
2270 # in the same order as the increasing window below.
2270 # in the same order as the increasing window below.
2271 def __contains__(self, value):
2271 def __contains__(self, value):
2272 if value in self.set:
2272 if value in self.set:
2273 return True
2273 return True
2274 elif not value in self.revs:
2274 elif not value in self.revs:
2275 return False
2275 return False
2276 else:
2276 else:
2277 self.revs.discard(value)
2277 self.revs.discard(value)
2278 ctx = change(value)
2278 ctx = change(value)
2279 matches = filter(match, ctx.files())
2279 matches = filter(match, ctx.files())
2280 if matches:
2280 if matches:
2281 fncache[value] = matches
2281 fncache[value] = matches
2282 self.set.add(value)
2282 self.set.add(value)
2283 return True
2283 return True
2284 return False
2284 return False
2285
2285
2286 def discard(self, value):
2286 def discard(self, value):
2287 self.revs.discard(value)
2287 self.revs.discard(value)
2288 self.set.discard(value)
2288 self.set.discard(value)
2289
2289
2290 wanted = lazywantedset()
2290 wanted = lazywantedset()
2291
2291
2292 # it might be worthwhile to do this in the iterator if the rev range
2292 # it might be worthwhile to do this in the iterator if the rev range
2293 # is descending and the prune args are all within that range
2293 # is descending and the prune args are all within that range
2294 for rev in opts.get('prune', ()):
2294 for rev in opts.get('prune', ()):
2295 rev = repo[rev].rev()
2295 rev = repo[rev].rev()
2296 ff = _followfilter(repo)
2296 ff = _followfilter(repo)
2297 stop = min(revs[0], revs[-1])
2297 stop = min(revs[0], revs[-1])
2298 for x in xrange(rev, stop - 1, -1):
2298 for x in xrange(rev, stop - 1, -1):
2299 if ff.match(x):
2299 if ff.match(x):
2300 wanted = wanted - [x]
2300 wanted = wanted - [x]
2301
2301
2302 # Now that wanted is correctly initialized, we can iterate over the
2302 # Now that wanted is correctly initialized, we can iterate over the
2303 # revision range, yielding only revisions in wanted.
2303 # revision range, yielding only revisions in wanted.
2304 def iterate():
2304 def iterate():
2305 if follow and match.always():
2305 if follow and match.always():
2306 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
2306 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
2307 def want(rev):
2307 def want(rev):
2308 return ff.match(rev) and rev in wanted
2308 return ff.match(rev) and rev in wanted
2309 else:
2309 else:
2310 def want(rev):
2310 def want(rev):
2311 return rev in wanted
2311 return rev in wanted
2312
2312
2313 it = iter(revs)
2313 it = iter(revs)
2314 stopiteration = False
2314 stopiteration = False
2315 for windowsize in increasingwindows():
2315 for windowsize in increasingwindows():
2316 nrevs = []
2316 nrevs = []
2317 for i in xrange(windowsize):
2317 for i in xrange(windowsize):
2318 rev = next(it, None)
2318 rev = next(it, None)
2319 if rev is None:
2319 if rev is None:
2320 stopiteration = True
2320 stopiteration = True
2321 break
2321 break
2322 elif want(rev):
2322 elif want(rev):
2323 nrevs.append(rev)
2323 nrevs.append(rev)
2324 for rev in sorted(nrevs):
2324 for rev in sorted(nrevs):
2325 fns = fncache.get(rev)
2325 fns = fncache.get(rev)
2326 ctx = change(rev)
2326 ctx = change(rev)
2327 if not fns:
2327 if not fns:
2328 def fns_generator():
2328 def fns_generator():
2329 for f in ctx.files():
2329 for f in ctx.files():
2330 if match(f):
2330 if match(f):
2331 yield f
2331 yield f
2332 fns = fns_generator()
2332 fns = fns_generator()
2333 prepare(ctx, fns)
2333 prepare(ctx, fns)
2334 for rev in nrevs:
2334 for rev in nrevs:
2335 yield change(rev)
2335 yield change(rev)
2336
2336
2337 if stopiteration:
2337 if stopiteration:
2338 break
2338 break
2339
2339
2340 return iterate()
2340 return iterate()
2341
2341
2342 def _makefollowlogfilematcher(repo, files, followfirst):
2342 def _makefollowlogfilematcher(repo, files, followfirst):
2343 # When displaying a revision with --patch --follow FILE, we have
2343 # When displaying a revision with --patch --follow FILE, we have
2344 # to know which file of the revision must be diffed. With
2344 # to know which file of the revision must be diffed. With
2345 # --follow, we want the names of the ancestors of FILE in the
2345 # --follow, we want the names of the ancestors of FILE in the
2346 # revision, stored in "fcache". "fcache" is populated by
2346 # revision, stored in "fcache". "fcache" is populated by
2347 # reproducing the graph traversal already done by --follow revset
2347 # reproducing the graph traversal already done by --follow revset
2348 # and relating revs to file names (which is not "correct" but
2348 # and relating revs to file names (which is not "correct" but
2349 # good enough).
2349 # good enough).
2350 fcache = {}
2350 fcache = {}
2351 fcacheready = [False]
2351 fcacheready = [False]
2352 pctx = repo['.']
2352 pctx = repo['.']
2353
2353
2354 def populate():
2354 def populate():
2355 for fn in files:
2355 for fn in files:
2356 fctx = pctx[fn]
2356 fctx = pctx[fn]
2357 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
2357 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
2358 for c in fctx.ancestors(followfirst=followfirst):
2358 for c in fctx.ancestors(followfirst=followfirst):
2359 fcache.setdefault(c.rev(), set()).add(c.path())
2359 fcache.setdefault(c.rev(), set()).add(c.path())
2360
2360
2361 def filematcher(rev):
2361 def filematcher(rev):
2362 if not fcacheready[0]:
2362 if not fcacheready[0]:
2363 # Lazy initialization
2363 # Lazy initialization
2364 fcacheready[0] = True
2364 fcacheready[0] = True
2365 populate()
2365 populate()
2366 return scmutil.matchfiles(repo, fcache.get(rev, []))
2366 return scmutil.matchfiles(repo, fcache.get(rev, []))
2367
2367
2368 return filematcher
2368 return filematcher
2369
2369
2370 def _makenofollowlogfilematcher(repo, pats, opts):
2370 def _makenofollowlogfilematcher(repo, pats, opts):
2371 '''hook for extensions to override the filematcher for non-follow cases'''
2371 '''hook for extensions to override the filematcher for non-follow cases'''
2372 return None
2372 return None
2373
2373
2374 def _makelogrevset(repo, pats, opts, revs):
2374 def _makelogrevset(repo, pats, opts, revs):
2375 """Return (expr, filematcher) where expr is a revset string built
2375 """Return (expr, filematcher) where expr is a revset string built
2376 from log options and file patterns or None. If --stat or --patch
2376 from log options and file patterns or None. If --stat or --patch
2377 are not passed filematcher is None. Otherwise it is a callable
2377 are not passed filematcher is None. Otherwise it is a callable
2378 taking a revision number and returning a match objects filtering
2378 taking a revision number and returning a match objects filtering
2379 the files to be detailed when displaying the revision.
2379 the files to be detailed when displaying the revision.
2380 """
2380 """
2381 opt2revset = {
2381 opt2revset = {
2382 'no_merges': ('not merge()', None),
2382 'no_merges': ('not merge()', None),
2383 'only_merges': ('merge()', None),
2383 'only_merges': ('merge()', None),
2384 '_ancestors': ('ancestors(%(val)s)', None),
2384 '_ancestors': ('ancestors(%(val)s)', None),
2385 '_fancestors': ('_firstancestors(%(val)s)', None),
2385 '_fancestors': ('_firstancestors(%(val)s)', None),
2386 '_descendants': ('descendants(%(val)s)', None),
2386 '_descendants': ('descendants(%(val)s)', None),
2387 '_fdescendants': ('_firstdescendants(%(val)s)', None),
2387 '_fdescendants': ('_firstdescendants(%(val)s)', None),
2388 '_matchfiles': ('_matchfiles(%(val)s)', None),
2388 '_matchfiles': ('_matchfiles(%(val)s)', None),
2389 'date': ('date(%(val)r)', None),
2389 'date': ('date(%(val)r)', None),
2390 'branch': ('branch(%(val)r)', ' or '),
2390 'branch': ('branch(%(val)r)', ' or '),
2391 '_patslog': ('filelog(%(val)r)', ' or '),
2391 '_patslog': ('filelog(%(val)r)', ' or '),
2392 '_patsfollow': ('follow(%(val)r)', ' or '),
2392 '_patsfollow': ('follow(%(val)r)', ' or '),
2393 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
2393 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
2394 'keyword': ('keyword(%(val)r)', ' or '),
2394 'keyword': ('keyword(%(val)r)', ' or '),
2395 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
2395 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
2396 'user': ('user(%(val)r)', ' or '),
2396 'user': ('user(%(val)r)', ' or '),
2397 }
2397 }
2398
2398
2399 opts = dict(opts)
2399 opts = dict(opts)
2400 # follow or not follow?
2400 # follow or not follow?
2401 follow = opts.get('follow') or opts.get('follow_first')
2401 follow = opts.get('follow') or opts.get('follow_first')
2402 if opts.get('follow_first'):
2402 if opts.get('follow_first'):
2403 followfirst = 1
2403 followfirst = 1
2404 else:
2404 else:
2405 followfirst = 0
2405 followfirst = 0
2406 # --follow with FILE behavior depends on revs...
2406 # --follow with FILE behavior depends on revs...
2407 it = iter(revs)
2407 it = iter(revs)
2408 startrev = next(it)
2408 startrev = next(it)
2409 followdescendants = startrev < next(it, startrev)
2409 followdescendants = startrev < next(it, startrev)
2410
2410
2411 # branch and only_branch are really aliases and must be handled at
2411 # branch and only_branch are really aliases and must be handled at
2412 # the same time
2412 # the same time
2413 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
2413 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
2414 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
2414 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
2415 # pats/include/exclude are passed to match.match() directly in
2415 # pats/include/exclude are passed to match.match() directly in
2416 # _matchfiles() revset but walkchangerevs() builds its matcher with
2416 # _matchfiles() revset but walkchangerevs() builds its matcher with
2417 # scmutil.match(). The difference is input pats are globbed on
2417 # scmutil.match(). The difference is input pats are globbed on
2418 # platforms without shell expansion (windows).
2418 # platforms without shell expansion (windows).
2419 wctx = repo[None]
2419 wctx = repo[None]
2420 match, pats = scmutil.matchandpats(wctx, pats, opts)
2420 match, pats = scmutil.matchandpats(wctx, pats, opts)
2421 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2421 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2422 opts.get('removed'))
2422 opts.get('removed'))
2423 if not slowpath:
2423 if not slowpath:
2424 for f in match.files():
2424 for f in match.files():
2425 if follow and f not in wctx:
2425 if follow and f not in wctx:
2426 # If the file exists, it may be a directory, so let it
2426 # If the file exists, it may be a directory, so let it
2427 # take the slow path.
2427 # take the slow path.
2428 if os.path.exists(repo.wjoin(f)):
2428 if os.path.exists(repo.wjoin(f)):
2429 slowpath = True
2429 slowpath = True
2430 continue
2430 continue
2431 else:
2431 else:
2432 raise error.Abort(_('cannot follow file not in parent '
2432 raise error.Abort(_('cannot follow file not in parent '
2433 'revision: "%s"') % f)
2433 'revision: "%s"') % f)
2434 filelog = repo.file(f)
2434 filelog = repo.file(f)
2435 if not filelog:
2435 if not filelog:
2436 # A zero count may be a directory or deleted file, so
2436 # A zero count may be a directory or deleted file, so
2437 # try to find matching entries on the slow path.
2437 # try to find matching entries on the slow path.
2438 if follow:
2438 if follow:
2439 raise error.Abort(
2439 raise error.Abort(
2440 _('cannot follow nonexistent file: "%s"') % f)
2440 _('cannot follow nonexistent file: "%s"') % f)
2441 slowpath = True
2441 slowpath = True
2442
2442
2443 # We decided to fall back to the slowpath because at least one
2443 # We decided to fall back to the slowpath because at least one
2444 # of the paths was not a file. Check to see if at least one of them
2444 # of the paths was not a file. Check to see if at least one of them
2445 # existed in history - in that case, we'll continue down the
2445 # existed in history - in that case, we'll continue down the
2446 # slowpath; otherwise, we can turn off the slowpath
2446 # slowpath; otherwise, we can turn off the slowpath
2447 if slowpath:
2447 if slowpath:
2448 for path in match.files():
2448 for path in match.files():
2449 if path == '.' or path in repo.store:
2449 if path == '.' or path in repo.store:
2450 break
2450 break
2451 else:
2451 else:
2452 slowpath = False
2452 slowpath = False
2453
2453
2454 fpats = ('_patsfollow', '_patsfollowfirst')
2454 fpats = ('_patsfollow', '_patsfollowfirst')
2455 fnopats = (('_ancestors', '_fancestors'),
2455 fnopats = (('_ancestors', '_fancestors'),
2456 ('_descendants', '_fdescendants'))
2456 ('_descendants', '_fdescendants'))
2457 if slowpath:
2457 if slowpath:
2458 # See walkchangerevs() slow path.
2458 # See walkchangerevs() slow path.
2459 #
2459 #
2460 # pats/include/exclude cannot be represented as separate
2460 # pats/include/exclude cannot be represented as separate
2461 # revset expressions as their filtering logic applies at file
2461 # revset expressions as their filtering logic applies at file
2462 # level. For instance "-I a -X a" matches a revision touching
2462 # level. For instance "-I a -X a" matches a revision touching
2463 # "a" and "b" while "file(a) and not file(b)" does
2463 # "a" and "b" while "file(a) and not file(b)" does
2464 # not. Besides, filesets are evaluated against the working
2464 # not. Besides, filesets are evaluated against the working
2465 # directory.
2465 # directory.
2466 matchargs = ['r:', 'd:relpath']
2466 matchargs = ['r:', 'd:relpath']
2467 for p in pats:
2467 for p in pats:
2468 matchargs.append('p:' + p)
2468 matchargs.append('p:' + p)
2469 for p in opts.get('include', []):
2469 for p in opts.get('include', []):
2470 matchargs.append('i:' + p)
2470 matchargs.append('i:' + p)
2471 for p in opts.get('exclude', []):
2471 for p in opts.get('exclude', []):
2472 matchargs.append('x:' + p)
2472 matchargs.append('x:' + p)
2473 matchargs = ','.join(('%r' % p) for p in matchargs)
2473 matchargs = ','.join(('%r' % p) for p in matchargs)
2474 opts['_matchfiles'] = matchargs
2474 opts['_matchfiles'] = matchargs
2475 if follow:
2475 if follow:
2476 opts[fnopats[0][followfirst]] = '.'
2476 opts[fnopats[0][followfirst]] = '.'
2477 else:
2477 else:
2478 if follow:
2478 if follow:
2479 if pats:
2479 if pats:
2480 # follow() revset interprets its file argument as a
2480 # follow() revset interprets its file argument as a
2481 # manifest entry, so use match.files(), not pats.
2481 # manifest entry, so use match.files(), not pats.
2482 opts[fpats[followfirst]] = list(match.files())
2482 opts[fpats[followfirst]] = list(match.files())
2483 else:
2483 else:
2484 op = fnopats[followdescendants][followfirst]
2484 op = fnopats[followdescendants][followfirst]
2485 opts[op] = 'rev(%d)' % startrev
2485 opts[op] = 'rev(%d)' % startrev
2486 else:
2486 else:
2487 opts['_patslog'] = list(pats)
2487 opts['_patslog'] = list(pats)
2488
2488
2489 filematcher = None
2489 filematcher = None
2490 if opts.get('patch') or opts.get('stat'):
2490 if opts.get('patch') or opts.get('stat'):
2491 # When following files, track renames via a special matcher.
2491 # When following files, track renames via a special matcher.
2492 # If we're forced to take the slowpath it means we're following
2492 # If we're forced to take the slowpath it means we're following
2493 # at least one pattern/directory, so don't bother with rename tracking.
2493 # at least one pattern/directory, so don't bother with rename tracking.
2494 if follow and not match.always() and not slowpath:
2494 if follow and not match.always() and not slowpath:
2495 # _makefollowlogfilematcher expects its files argument to be
2495 # _makefollowlogfilematcher expects its files argument to be
2496 # relative to the repo root, so use match.files(), not pats.
2496 # relative to the repo root, so use match.files(), not pats.
2497 filematcher = _makefollowlogfilematcher(repo, match.files(),
2497 filematcher = _makefollowlogfilematcher(repo, match.files(),
2498 followfirst)
2498 followfirst)
2499 else:
2499 else:
2500 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2500 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2501 if filematcher is None:
2501 if filematcher is None:
2502 filematcher = lambda rev: match
2502 filematcher = lambda rev: match
2503
2503
2504 expr = []
2504 expr = []
2505 for op, val in sorted(opts.iteritems()):
2505 for op, val in sorted(opts.iteritems()):
2506 if not val:
2506 if not val:
2507 continue
2507 continue
2508 if op not in opt2revset:
2508 if op not in opt2revset:
2509 continue
2509 continue
2510 revop, andor = opt2revset[op]
2510 revop, andor = opt2revset[op]
2511 if '%(val)' not in revop:
2511 if '%(val)' not in revop:
2512 expr.append(revop)
2512 expr.append(revop)
2513 else:
2513 else:
2514 if not isinstance(val, list):
2514 if not isinstance(val, list):
2515 e = revop % {'val': val}
2515 e = revop % {'val': val}
2516 else:
2516 else:
2517 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2517 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2518 expr.append(e)
2518 expr.append(e)
2519
2519
2520 if expr:
2520 if expr:
2521 expr = '(' + ' and '.join(expr) + ')'
2521 expr = '(' + ' and '.join(expr) + ')'
2522 else:
2522 else:
2523 expr = None
2523 expr = None
2524 return expr, filematcher
2524 return expr, filematcher
2525
2525
2526 def _logrevs(repo, opts):
2526 def _logrevs(repo, opts):
2527 # Default --rev value depends on --follow but --follow behavior
2527 # Default --rev value depends on --follow but --follow behavior
2528 # depends on revisions resolved from --rev...
2528 # depends on revisions resolved from --rev...
2529 follow = opts.get('follow') or opts.get('follow_first')
2529 follow = opts.get('follow') or opts.get('follow_first')
2530 if opts.get('rev'):
2530 if opts.get('rev'):
2531 revs = scmutil.revrange(repo, opts['rev'])
2531 revs = scmutil.revrange(repo, opts['rev'])
2532 elif follow and repo.dirstate.p1() == nullid:
2532 elif follow and repo.dirstate.p1() == nullid:
2533 revs = smartset.baseset()
2533 revs = smartset.baseset()
2534 elif follow:
2534 elif follow:
2535 revs = repo.revs('reverse(:.)')
2535 revs = repo.revs('reverse(:.)')
2536 else:
2536 else:
2537 revs = smartset.spanset(repo)
2537 revs = smartset.spanset(repo)
2538 revs.reverse()
2538 revs.reverse()
2539 return revs
2539 return revs
2540
2540
2541 def getgraphlogrevs(repo, pats, opts):
2541 def getgraphlogrevs(repo, pats, opts):
2542 """Return (revs, expr, filematcher) where revs is an iterable of
2542 """Return (revs, expr, filematcher) where revs is an iterable of
2543 revision numbers, expr is a revset string built from log options
2543 revision numbers, expr is a revset string built from log options
2544 and file patterns or None, and used to filter 'revs'. If --stat or
2544 and file patterns or None, and used to filter 'revs'. If --stat or
2545 --patch are not passed filematcher is None. Otherwise it is a
2545 --patch are not passed filematcher is None. Otherwise it is a
2546 callable taking a revision number and returning a match objects
2546 callable taking a revision number and returning a match objects
2547 filtering the files to be detailed when displaying the revision.
2547 filtering the files to be detailed when displaying the revision.
2548 """
2548 """
2549 limit = loglimit(opts)
2549 limit = loglimit(opts)
2550 revs = _logrevs(repo, opts)
2550 revs = _logrevs(repo, opts)
2551 if not revs:
2551 if not revs:
2552 return smartset.baseset(), None, None
2552 return smartset.baseset(), None, None
2553 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2553 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2554 if opts.get('rev'):
2554 if opts.get('rev'):
2555 # User-specified revs might be unsorted, but don't sort before
2555 # User-specified revs might be unsorted, but don't sort before
2556 # _makelogrevset because it might depend on the order of revs
2556 # _makelogrevset because it might depend on the order of revs
2557 if not (revs.isdescending() or revs.istopo()):
2557 if not (revs.isdescending() or revs.istopo()):
2558 revs.sort(reverse=True)
2558 revs.sort(reverse=True)
2559 if expr:
2559 if expr:
2560 matcher = revset.match(repo.ui, expr)
2560 matcher = revset.match(repo.ui, expr)
2561 revs = matcher(repo, revs)
2561 revs = matcher(repo, revs)
2562 if limit is not None:
2562 if limit is not None:
2563 limitedrevs = []
2563 limitedrevs = []
2564 for idx, rev in enumerate(revs):
2564 for idx, rev in enumerate(revs):
2565 if idx >= limit:
2565 if idx >= limit:
2566 break
2566 break
2567 limitedrevs.append(rev)
2567 limitedrevs.append(rev)
2568 revs = smartset.baseset(limitedrevs)
2568 revs = smartset.baseset(limitedrevs)
2569
2569
2570 return revs, expr, filematcher
2570 return revs, expr, filematcher
2571
2571
2572 def getlogrevs(repo, pats, opts):
2572 def getlogrevs(repo, pats, opts):
2573 """Return (revs, expr, filematcher) where revs is an iterable of
2573 """Return (revs, expr, filematcher) where revs is an iterable of
2574 revision numbers, expr is a revset string built from log options
2574 revision numbers, expr is a revset string built from log options
2575 and file patterns or None, and used to filter 'revs'. If --stat or
2575 and file patterns or None, and used to filter 'revs'. If --stat or
2576 --patch are not passed filematcher is None. Otherwise it is a
2576 --patch are not passed filematcher is None. Otherwise it is a
2577 callable taking a revision number and returning a match objects
2577 callable taking a revision number and returning a match objects
2578 filtering the files to be detailed when displaying the revision.
2578 filtering the files to be detailed when displaying the revision.
2579 """
2579 """
2580 limit = loglimit(opts)
2580 limit = loglimit(opts)
2581 revs = _logrevs(repo, opts)
2581 revs = _logrevs(repo, opts)
2582 if not revs:
2582 if not revs:
2583 return smartset.baseset([]), None, None
2583 return smartset.baseset([]), None, None
2584 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2584 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2585 if expr:
2585 if expr:
2586 matcher = revset.match(repo.ui, expr)
2586 matcher = revset.match(repo.ui, expr)
2587 revs = matcher(repo, revs)
2587 revs = matcher(repo, revs)
2588 if limit is not None:
2588 if limit is not None:
2589 limitedrevs = []
2589 limitedrevs = []
2590 for idx, r in enumerate(revs):
2590 for idx, r in enumerate(revs):
2591 if limit <= idx:
2591 if limit <= idx:
2592 break
2592 break
2593 limitedrevs.append(r)
2593 limitedrevs.append(r)
2594 revs = smartset.baseset(limitedrevs)
2594 revs = smartset.baseset(limitedrevs)
2595
2595
2596 return revs, expr, filematcher
2596 return revs, expr, filematcher
2597
2597
2598 def _graphnodeformatter(ui, displayer):
2598 def _graphnodeformatter(ui, displayer):
2599 spec = ui.config('ui', 'graphnodetemplate')
2599 spec = ui.config('ui', 'graphnodetemplate')
2600 if not spec:
2600 if not spec:
2601 return templatekw.showgraphnode # fast path for "{graphnode}"
2601 return templatekw.showgraphnode # fast path for "{graphnode}"
2602
2602
2603 spec = templater.unquotestring(spec)
2603 spec = templater.unquotestring(spec)
2604 templ = formatter.maketemplater(ui, spec)
2604 templ = formatter.maketemplater(ui, spec)
2605 cache = {}
2605 cache = {}
2606 if isinstance(displayer, changeset_templater):
2606 if isinstance(displayer, changeset_templater):
2607 cache = displayer.cache # reuse cache of slow templates
2607 cache = displayer.cache # reuse cache of slow templates
2608 props = templatekw.keywords.copy()
2608 props = templatekw.keywords.copy()
2609 props['templ'] = templ
2609 props['templ'] = templ
2610 props['cache'] = cache
2610 props['cache'] = cache
2611 def formatnode(repo, ctx):
2611 def formatnode(repo, ctx):
2612 props['ctx'] = ctx
2612 props['ctx'] = ctx
2613 props['repo'] = repo
2613 props['repo'] = repo
2614 props['ui'] = repo.ui
2614 props['ui'] = repo.ui
2615 props['revcache'] = {}
2615 props['revcache'] = {}
2616 return templ.render(props)
2616 return templ.render(props)
2617 return formatnode
2617 return formatnode
2618
2618
2619 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2619 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2620 filematcher=None):
2620 filematcher=None):
2621 formatnode = _graphnodeformatter(ui, displayer)
2621 formatnode = _graphnodeformatter(ui, displayer)
2622 state = graphmod.asciistate()
2622 state = graphmod.asciistate()
2623 styles = state['styles']
2623 styles = state['styles']
2624
2624
2625 # only set graph styling if HGPLAIN is not set.
2625 # only set graph styling if HGPLAIN is not set.
2626 if ui.plain('graph'):
2626 if ui.plain('graph'):
2627 # set all edge styles to |, the default pre-3.8 behaviour
2627 # set all edge styles to |, the default pre-3.8 behaviour
2628 styles.update(dict.fromkeys(styles, '|'))
2628 styles.update(dict.fromkeys(styles, '|'))
2629 else:
2629 else:
2630 edgetypes = {
2630 edgetypes = {
2631 'parent': graphmod.PARENT,
2631 'parent': graphmod.PARENT,
2632 'grandparent': graphmod.GRANDPARENT,
2632 'grandparent': graphmod.GRANDPARENT,
2633 'missing': graphmod.MISSINGPARENT
2633 'missing': graphmod.MISSINGPARENT
2634 }
2634 }
2635 for name, key in edgetypes.items():
2635 for name, key in edgetypes.items():
2636 # experimental config: experimental.graphstyle.*
2636 # experimental config: experimental.graphstyle.*
2637 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2637 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2638 styles[key])
2638 styles[key])
2639 if not styles[key]:
2639 if not styles[key]:
2640 styles[key] = None
2640 styles[key] = None
2641
2641
2642 # experimental config: experimental.graphshorten
2642 # experimental config: experimental.graphshorten
2643 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2643 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2644
2644
2645 for rev, type, ctx, parents in dag:
2645 for rev, type, ctx, parents in dag:
2646 char = formatnode(repo, ctx)
2646 char = formatnode(repo, ctx)
2647 copies = None
2647 copies = None
2648 if getrenamed and ctx.rev():
2648 if getrenamed and ctx.rev():
2649 copies = []
2649 copies = []
2650 for fn in ctx.files():
2650 for fn in ctx.files():
2651 rename = getrenamed(fn, ctx.rev())
2651 rename = getrenamed(fn, ctx.rev())
2652 if rename:
2652 if rename:
2653 copies.append((fn, rename[0]))
2653 copies.append((fn, rename[0]))
2654 revmatchfn = None
2654 revmatchfn = None
2655 if filematcher is not None:
2655 if filematcher is not None:
2656 revmatchfn = filematcher(ctx.rev())
2656 revmatchfn = filematcher(ctx.rev())
2657 edges = edgefn(type, char, state, rev, parents)
2657 edges = edgefn(type, char, state, rev, parents)
2658 firstedge = next(edges)
2658 firstedge = next(edges)
2659 width = firstedge[2]
2659 width = firstedge[2]
2660 displayer.show(ctx, copies=copies, matchfn=revmatchfn,
2660 displayer.show(ctx, copies=copies, matchfn=revmatchfn,
2661 _graphwidth=width)
2661 _graphwidth=width)
2662 lines = displayer.hunk.pop(rev).split('\n')
2662 lines = displayer.hunk.pop(rev).split('\n')
2663 if not lines[-1]:
2663 if not lines[-1]:
2664 del lines[-1]
2664 del lines[-1]
2665 displayer.flush(ctx)
2665 displayer.flush(ctx)
2666 for type, char, width, coldata in itertools.chain([firstedge], edges):
2666 for type, char, width, coldata in itertools.chain([firstedge], edges):
2667 graphmod.ascii(ui, state, type, char, lines, coldata)
2667 graphmod.ascii(ui, state, type, char, lines, coldata)
2668 lines = []
2668 lines = []
2669 displayer.close()
2669 displayer.close()
2670
2670
2671 def graphlog(ui, repo, pats, opts):
2671 def graphlog(ui, repo, pats, opts):
2672 # Parameters are identical to log command ones
2672 # Parameters are identical to log command ones
2673 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2673 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2674 revdag = graphmod.dagwalker(repo, revs)
2674 revdag = graphmod.dagwalker(repo, revs)
2675
2675
2676 getrenamed = None
2676 getrenamed = None
2677 if opts.get('copies'):
2677 if opts.get('copies'):
2678 endrev = None
2678 endrev = None
2679 if opts.get('rev'):
2679 if opts.get('rev'):
2680 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2680 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2681 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2681 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2682
2682
2683 ui.pager('log')
2683 ui.pager('log')
2684 displayer = show_changeset(ui, repo, opts, buffered=True)
2684 displayer = show_changeset(ui, repo, opts, buffered=True)
2685 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2685 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2686 filematcher)
2686 filematcher)
2687
2687
2688 def checkunsupportedgraphflags(pats, opts):
2688 def checkunsupportedgraphflags(pats, opts):
2689 for op in ["newest_first"]:
2689 for op in ["newest_first"]:
2690 if op in opts and opts[op]:
2690 if op in opts and opts[op]:
2691 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2691 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2692 % op.replace("_", "-"))
2692 % op.replace("_", "-"))
2693
2693
2694 def graphrevs(repo, nodes, opts):
2694 def graphrevs(repo, nodes, opts):
2695 limit = loglimit(opts)
2695 limit = loglimit(opts)
2696 nodes.reverse()
2696 nodes.reverse()
2697 if limit is not None:
2697 if limit is not None:
2698 nodes = nodes[:limit]
2698 nodes = nodes[:limit]
2699 return graphmod.nodes(repo, nodes)
2699 return graphmod.nodes(repo, nodes)
2700
2700
2701 def add(ui, repo, match, prefix, explicitonly, **opts):
2701 def add(ui, repo, match, prefix, explicitonly, **opts):
2702 join = lambda f: os.path.join(prefix, f)
2702 join = lambda f: os.path.join(prefix, f)
2703 bad = []
2703 bad = []
2704
2704
2705 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2705 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2706 names = []
2706 names = []
2707 wctx = repo[None]
2707 wctx = repo[None]
2708 cca = None
2708 cca = None
2709 abort, warn = scmutil.checkportabilityalert(ui)
2709 abort, warn = scmutil.checkportabilityalert(ui)
2710 if abort or warn:
2710 if abort or warn:
2711 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2711 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2712
2712
2713 badmatch = matchmod.badmatch(match, badfn)
2713 badmatch = matchmod.badmatch(match, badfn)
2714 dirstate = repo.dirstate
2714 dirstate = repo.dirstate
2715 # We don't want to just call wctx.walk here, since it would return a lot of
2715 # We don't want to just call wctx.walk here, since it would return a lot of
2716 # clean files, which we aren't interested in and takes time.
2716 # clean files, which we aren't interested in and takes time.
2717 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2717 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2718 True, False, full=False)):
2718 True, False, full=False)):
2719 exact = match.exact(f)
2719 exact = match.exact(f)
2720 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2720 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2721 if cca:
2721 if cca:
2722 cca(f)
2722 cca(f)
2723 names.append(f)
2723 names.append(f)
2724 if ui.verbose or not exact:
2724 if ui.verbose or not exact:
2725 ui.status(_('adding %s\n') % match.rel(f))
2725 ui.status(_('adding %s\n') % match.rel(f))
2726
2726
2727 for subpath in sorted(wctx.substate):
2727 for subpath in sorted(wctx.substate):
2728 sub = wctx.sub(subpath)
2728 sub = wctx.sub(subpath)
2729 try:
2729 try:
2730 submatch = matchmod.subdirmatcher(subpath, match)
2730 submatch = matchmod.subdirmatcher(subpath, match)
2731 if opts.get(r'subrepos'):
2731 if opts.get(r'subrepos'):
2732 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2732 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2733 else:
2733 else:
2734 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2734 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2735 except error.LookupError:
2735 except error.LookupError:
2736 ui.status(_("skipping missing subrepository: %s\n")
2736 ui.status(_("skipping missing subrepository: %s\n")
2737 % join(subpath))
2737 % join(subpath))
2738
2738
2739 if not opts.get(r'dry_run'):
2739 if not opts.get(r'dry_run'):
2740 rejected = wctx.add(names, prefix)
2740 rejected = wctx.add(names, prefix)
2741 bad.extend(f for f in rejected if f in match.files())
2741 bad.extend(f for f in rejected if f in match.files())
2742 return bad
2742 return bad
2743
2743
2744 def addwebdirpath(repo, serverpath, webconf):
2744 def addwebdirpath(repo, serverpath, webconf):
2745 webconf[serverpath] = repo.root
2745 webconf[serverpath] = repo.root
2746 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2746 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2747
2747
2748 for r in repo.revs('filelog("path:.hgsub")'):
2748 for r in repo.revs('filelog("path:.hgsub")'):
2749 ctx = repo[r]
2749 ctx = repo[r]
2750 for subpath in ctx.substate:
2750 for subpath in ctx.substate:
2751 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2751 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2752
2752
2753 def forget(ui, repo, match, prefix, explicitonly):
2753 def forget(ui, repo, match, prefix, explicitonly):
2754 join = lambda f: os.path.join(prefix, f)
2754 join = lambda f: os.path.join(prefix, f)
2755 bad = []
2755 bad = []
2756 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2756 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2757 wctx = repo[None]
2757 wctx = repo[None]
2758 forgot = []
2758 forgot = []
2759
2759
2760 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2760 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2761 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2761 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2762 if explicitonly:
2762 if explicitonly:
2763 forget = [f for f in forget if match.exact(f)]
2763 forget = [f for f in forget if match.exact(f)]
2764
2764
2765 for subpath in sorted(wctx.substate):
2765 for subpath in sorted(wctx.substate):
2766 sub = wctx.sub(subpath)
2766 sub = wctx.sub(subpath)
2767 try:
2767 try:
2768 submatch = matchmod.subdirmatcher(subpath, match)
2768 submatch = matchmod.subdirmatcher(subpath, match)
2769 subbad, subforgot = sub.forget(submatch, prefix)
2769 subbad, subforgot = sub.forget(submatch, prefix)
2770 bad.extend([subpath + '/' + f for f in subbad])
2770 bad.extend([subpath + '/' + f for f in subbad])
2771 forgot.extend([subpath + '/' + f for f in subforgot])
2771 forgot.extend([subpath + '/' + f for f in subforgot])
2772 except error.LookupError:
2772 except error.LookupError:
2773 ui.status(_("skipping missing subrepository: %s\n")
2773 ui.status(_("skipping missing subrepository: %s\n")
2774 % join(subpath))
2774 % join(subpath))
2775
2775
2776 if not explicitonly:
2776 if not explicitonly:
2777 for f in match.files():
2777 for f in match.files():
2778 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2778 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2779 if f not in forgot:
2779 if f not in forgot:
2780 if repo.wvfs.exists(f):
2780 if repo.wvfs.exists(f):
2781 # Don't complain if the exact case match wasn't given.
2781 # Don't complain if the exact case match wasn't given.
2782 # But don't do this until after checking 'forgot', so
2782 # But don't do this until after checking 'forgot', so
2783 # that subrepo files aren't normalized, and this op is
2783 # that subrepo files aren't normalized, and this op is
2784 # purely from data cached by the status walk above.
2784 # purely from data cached by the status walk above.
2785 if repo.dirstate.normalize(f) in repo.dirstate:
2785 if repo.dirstate.normalize(f) in repo.dirstate:
2786 continue
2786 continue
2787 ui.warn(_('not removing %s: '
2787 ui.warn(_('not removing %s: '
2788 'file is already untracked\n')
2788 'file is already untracked\n')
2789 % match.rel(f))
2789 % match.rel(f))
2790 bad.append(f)
2790 bad.append(f)
2791
2791
2792 for f in forget:
2792 for f in forget:
2793 if ui.verbose or not match.exact(f):
2793 if ui.verbose or not match.exact(f):
2794 ui.status(_('removing %s\n') % match.rel(f))
2794 ui.status(_('removing %s\n') % match.rel(f))
2795
2795
2796 rejected = wctx.forget(forget, prefix)
2796 rejected = wctx.forget(forget, prefix)
2797 bad.extend(f for f in rejected if f in match.files())
2797 bad.extend(f for f in rejected if f in match.files())
2798 forgot.extend(f for f in forget if f not in rejected)
2798 forgot.extend(f for f in forget if f not in rejected)
2799 return bad, forgot
2799 return bad, forgot
2800
2800
2801 def files(ui, ctx, m, fm, fmt, subrepos):
2801 def files(ui, ctx, m, fm, fmt, subrepos):
2802 rev = ctx.rev()
2802 rev = ctx.rev()
2803 ret = 1
2803 ret = 1
2804 ds = ctx.repo().dirstate
2804 ds = ctx.repo().dirstate
2805
2805
2806 for f in ctx.matches(m):
2806 for f in ctx.matches(m):
2807 if rev is None and ds[f] == 'r':
2807 if rev is None and ds[f] == 'r':
2808 continue
2808 continue
2809 fm.startitem()
2809 fm.startitem()
2810 if ui.verbose:
2810 if ui.verbose:
2811 fc = ctx[f]
2811 fc = ctx[f]
2812 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2812 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2813 fm.data(abspath=f)
2813 fm.data(abspath=f)
2814 fm.write('path', fmt, m.rel(f))
2814 fm.write('path', fmt, m.rel(f))
2815 ret = 0
2815 ret = 0
2816
2816
2817 for subpath in sorted(ctx.substate):
2817 for subpath in sorted(ctx.substate):
2818 submatch = matchmod.subdirmatcher(subpath, m)
2818 submatch = matchmod.subdirmatcher(subpath, m)
2819 if (subrepos or m.exact(subpath) or any(submatch.files())):
2819 if (subrepos or m.exact(subpath) or any(submatch.files())):
2820 sub = ctx.sub(subpath)
2820 sub = ctx.sub(subpath)
2821 try:
2821 try:
2822 recurse = m.exact(subpath) or subrepos
2822 recurse = m.exact(subpath) or subrepos
2823 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2823 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2824 ret = 0
2824 ret = 0
2825 except error.LookupError:
2825 except error.LookupError:
2826 ui.status(_("skipping missing subrepository: %s\n")
2826 ui.status(_("skipping missing subrepository: %s\n")
2827 % m.abs(subpath))
2827 % m.abs(subpath))
2828
2828
2829 return ret
2829 return ret
2830
2830
2831 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2831 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2832 join = lambda f: os.path.join(prefix, f)
2832 join = lambda f: os.path.join(prefix, f)
2833 ret = 0
2833 ret = 0
2834 s = repo.status(match=m, clean=True)
2834 s = repo.status(match=m, clean=True)
2835 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2835 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2836
2836
2837 wctx = repo[None]
2837 wctx = repo[None]
2838
2838
2839 if warnings is None:
2839 if warnings is None:
2840 warnings = []
2840 warnings = []
2841 warn = True
2841 warn = True
2842 else:
2842 else:
2843 warn = False
2843 warn = False
2844
2844
2845 subs = sorted(wctx.substate)
2845 subs = sorted(wctx.substate)
2846 total = len(subs)
2846 total = len(subs)
2847 count = 0
2847 count = 0
2848 for subpath in subs:
2848 for subpath in subs:
2849 count += 1
2849 count += 1
2850 submatch = matchmod.subdirmatcher(subpath, m)
2850 submatch = matchmod.subdirmatcher(subpath, m)
2851 if subrepos or m.exact(subpath) or any(submatch.files()):
2851 if subrepos or m.exact(subpath) or any(submatch.files()):
2852 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2852 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2853 sub = wctx.sub(subpath)
2853 sub = wctx.sub(subpath)
2854 try:
2854 try:
2855 if sub.removefiles(submatch, prefix, after, force, subrepos,
2855 if sub.removefiles(submatch, prefix, after, force, subrepos,
2856 warnings):
2856 warnings):
2857 ret = 1
2857 ret = 1
2858 except error.LookupError:
2858 except error.LookupError:
2859 warnings.append(_("skipping missing subrepository: %s\n")
2859 warnings.append(_("skipping missing subrepository: %s\n")
2860 % join(subpath))
2860 % join(subpath))
2861 ui.progress(_('searching'), None)
2861 ui.progress(_('searching'), None)
2862
2862
2863 # warn about failure to delete explicit files/dirs
2863 # warn about failure to delete explicit files/dirs
2864 deleteddirs = util.dirs(deleted)
2864 deleteddirs = util.dirs(deleted)
2865 files = m.files()
2865 files = m.files()
2866 total = len(files)
2866 total = len(files)
2867 count = 0
2867 count = 0
2868 for f in files:
2868 for f in files:
2869 def insubrepo():
2869 def insubrepo():
2870 for subpath in wctx.substate:
2870 for subpath in wctx.substate:
2871 if f.startswith(subpath + '/'):
2871 if f.startswith(subpath + '/'):
2872 return True
2872 return True
2873 return False
2873 return False
2874
2874
2875 count += 1
2875 count += 1
2876 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2876 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2877 isdir = f in deleteddirs or wctx.hasdir(f)
2877 isdir = f in deleteddirs or wctx.hasdir(f)
2878 if (f in repo.dirstate or isdir or f == '.'
2878 if (f in repo.dirstate or isdir or f == '.'
2879 or insubrepo() or f in subs):
2879 or insubrepo() or f in subs):
2880 continue
2880 continue
2881
2881
2882 if repo.wvfs.exists(f):
2882 if repo.wvfs.exists(f):
2883 if repo.wvfs.isdir(f):
2883 if repo.wvfs.isdir(f):
2884 warnings.append(_('not removing %s: no tracked files\n')
2884 warnings.append(_('not removing %s: no tracked files\n')
2885 % m.rel(f))
2885 % m.rel(f))
2886 else:
2886 else:
2887 warnings.append(_('not removing %s: file is untracked\n')
2887 warnings.append(_('not removing %s: file is untracked\n')
2888 % m.rel(f))
2888 % m.rel(f))
2889 # missing files will generate a warning elsewhere
2889 # missing files will generate a warning elsewhere
2890 ret = 1
2890 ret = 1
2891 ui.progress(_('deleting'), None)
2891 ui.progress(_('deleting'), None)
2892
2892
2893 if force:
2893 if force:
2894 list = modified + deleted + clean + added
2894 list = modified + deleted + clean + added
2895 elif after:
2895 elif after:
2896 list = deleted
2896 list = deleted
2897 remaining = modified + added + clean
2897 remaining = modified + added + clean
2898 total = len(remaining)
2898 total = len(remaining)
2899 count = 0
2899 count = 0
2900 for f in remaining:
2900 for f in remaining:
2901 count += 1
2901 count += 1
2902 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2902 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2903 warnings.append(_('not removing %s: file still exists\n')
2903 warnings.append(_('not removing %s: file still exists\n')
2904 % m.rel(f))
2904 % m.rel(f))
2905 ret = 1
2905 ret = 1
2906 ui.progress(_('skipping'), None)
2906 ui.progress(_('skipping'), None)
2907 else:
2907 else:
2908 list = deleted + clean
2908 list = deleted + clean
2909 total = len(modified) + len(added)
2909 total = len(modified) + len(added)
2910 count = 0
2910 count = 0
2911 for f in modified:
2911 for f in modified:
2912 count += 1
2912 count += 1
2913 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2913 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2914 warnings.append(_('not removing %s: file is modified (use -f'
2914 warnings.append(_('not removing %s: file is modified (use -f'
2915 ' to force removal)\n') % m.rel(f))
2915 ' to force removal)\n') % m.rel(f))
2916 ret = 1
2916 ret = 1
2917 for f in added:
2917 for f in added:
2918 count += 1
2918 count += 1
2919 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2919 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2920 warnings.append(_("not removing %s: file has been marked for add"
2920 warnings.append(_("not removing %s: file has been marked for add"
2921 " (use 'hg forget' to undo add)\n") % m.rel(f))
2921 " (use 'hg forget' to undo add)\n") % m.rel(f))
2922 ret = 1
2922 ret = 1
2923 ui.progress(_('skipping'), None)
2923 ui.progress(_('skipping'), None)
2924
2924
2925 list = sorted(list)
2925 list = sorted(list)
2926 total = len(list)
2926 total = len(list)
2927 count = 0
2927 count = 0
2928 for f in list:
2928 for f in list:
2929 count += 1
2929 count += 1
2930 if ui.verbose or not m.exact(f):
2930 if ui.verbose or not m.exact(f):
2931 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2931 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2932 ui.status(_('removing %s\n') % m.rel(f))
2932 ui.status(_('removing %s\n') % m.rel(f))
2933 ui.progress(_('deleting'), None)
2933 ui.progress(_('deleting'), None)
2934
2934
2935 with repo.wlock():
2935 with repo.wlock():
2936 if not after:
2936 if not after:
2937 for f in list:
2937 for f in list:
2938 if f in added:
2938 if f in added:
2939 continue # we never unlink added files on remove
2939 continue # we never unlink added files on remove
2940 repo.wvfs.unlinkpath(f, ignoremissing=True)
2940 repo.wvfs.unlinkpath(f, ignoremissing=True)
2941 repo[None].forget(list)
2941 repo[None].forget(list)
2942
2942
2943 if warn:
2943 if warn:
2944 for warning in warnings:
2944 for warning in warnings:
2945 ui.warn(warning)
2945 ui.warn(warning)
2946
2946
2947 return ret
2947 return ret
2948
2948
2949 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2949 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2950 err = 1
2950 err = 1
2951
2951
2952 def write(path):
2952 def write(path):
2953 filename = None
2953 filename = None
2954 if fntemplate:
2954 if fntemplate:
2955 filename = makefilename(repo, fntemplate, ctx.node(),
2955 filename = makefilename(repo, fntemplate, ctx.node(),
2956 pathname=os.path.join(prefix, path))
2956 pathname=os.path.join(prefix, path))
2957 with formatter.maybereopen(basefm, filename, opts) as fm:
2957 with formatter.maybereopen(basefm, filename, opts) as fm:
2958 data = ctx[path].data()
2958 data = ctx[path].data()
2959 if opts.get('decode'):
2959 if opts.get('decode'):
2960 data = repo.wwritedata(path, data)
2960 data = repo.wwritedata(path, data)
2961 fm.startitem()
2961 fm.startitem()
2962 fm.write('data', '%s', data)
2962 fm.write('data', '%s', data)
2963 fm.data(abspath=path, path=matcher.rel(path))
2963 fm.data(abspath=path, path=matcher.rel(path))
2964
2964
2965 # Automation often uses hg cat on single files, so special case it
2965 # Automation often uses hg cat on single files, so special case it
2966 # for performance to avoid the cost of parsing the manifest.
2966 # for performance to avoid the cost of parsing the manifest.
2967 if len(matcher.files()) == 1 and not matcher.anypats():
2967 if len(matcher.files()) == 1 and not matcher.anypats():
2968 file = matcher.files()[0]
2968 file = matcher.files()[0]
2969 mfl = repo.manifestlog
2969 mfl = repo.manifestlog
2970 mfnode = ctx.manifestnode()
2970 mfnode = ctx.manifestnode()
2971 try:
2971 try:
2972 if mfnode and mfl[mfnode].find(file)[0]:
2972 if mfnode and mfl[mfnode].find(file)[0]:
2973 write(file)
2973 write(file)
2974 return 0
2974 return 0
2975 except KeyError:
2975 except KeyError:
2976 pass
2976 pass
2977
2977
2978 for abs in ctx.walk(matcher):
2978 for abs in ctx.walk(matcher):
2979 write(abs)
2979 write(abs)
2980 err = 0
2980 err = 0
2981
2981
2982 for subpath in sorted(ctx.substate):
2982 for subpath in sorted(ctx.substate):
2983 sub = ctx.sub(subpath)
2983 sub = ctx.sub(subpath)
2984 try:
2984 try:
2985 submatch = matchmod.subdirmatcher(subpath, matcher)
2985 submatch = matchmod.subdirmatcher(subpath, matcher)
2986
2986
2987 if not sub.cat(submatch, basefm, fntemplate,
2987 if not sub.cat(submatch, basefm, fntemplate,
2988 os.path.join(prefix, sub._path), **opts):
2988 os.path.join(prefix, sub._path), **opts):
2989 err = 0
2989 err = 0
2990 except error.RepoLookupError:
2990 except error.RepoLookupError:
2991 ui.status(_("skipping missing subrepository: %s\n")
2991 ui.status(_("skipping missing subrepository: %s\n")
2992 % os.path.join(prefix, subpath))
2992 % os.path.join(prefix, subpath))
2993
2993
2994 return err
2994 return err
2995
2995
2996 def commit(ui, repo, commitfunc, pats, opts):
2996 def commit(ui, repo, commitfunc, pats, opts):
2997 '''commit the specified files or all outstanding changes'''
2997 '''commit the specified files or all outstanding changes'''
2998 date = opts.get('date')
2998 date = opts.get('date')
2999 if date:
2999 if date:
3000 opts['date'] = util.parsedate(date)
3000 opts['date'] = util.parsedate(date)
3001 message = logmessage(ui, opts)
3001 message = logmessage(ui, opts)
3002 matcher = scmutil.match(repo[None], pats, opts)
3002 matcher = scmutil.match(repo[None], pats, opts)
3003
3003
3004 dsguard = None
3004 dsguard = None
3005 # extract addremove carefully -- this function can be called from a command
3005 # extract addremove carefully -- this function can be called from a command
3006 # that doesn't support addremove
3006 # that doesn't support addremove
3007 if opts.get('addremove'):
3007 if opts.get('addremove'):
3008 dsguard = dirstateguard.dirstateguard(repo, 'commit')
3008 dsguard = dirstateguard.dirstateguard(repo, 'commit')
3009 with dsguard or util.nullcontextmanager():
3009 with dsguard or util.nullcontextmanager():
3010 if dsguard:
3010 if dsguard:
3011 if scmutil.addremove(repo, matcher, "", opts) != 0:
3011 if scmutil.addremove(repo, matcher, "", opts) != 0:
3012 raise error.Abort(
3012 raise error.Abort(
3013 _("failed to mark all new/missing files as added/removed"))
3013 _("failed to mark all new/missing files as added/removed"))
3014
3014
3015 return commitfunc(ui, repo, message, matcher, opts)
3015 return commitfunc(ui, repo, message, matcher, opts)
3016
3016
3017 def samefile(f, ctx1, ctx2):
3017 def samefile(f, ctx1, ctx2):
3018 if f in ctx1.manifest():
3018 if f in ctx1.manifest():
3019 a = ctx1.filectx(f)
3019 a = ctx1.filectx(f)
3020 if f in ctx2.manifest():
3020 if f in ctx2.manifest():
3021 b = ctx2.filectx(f)
3021 b = ctx2.filectx(f)
3022 return (not a.cmp(b)
3022 return (not a.cmp(b)
3023 and a.flags() == b.flags())
3023 and a.flags() == b.flags())
3024 else:
3024 else:
3025 return False
3025 return False
3026 else:
3026 else:
3027 return f not in ctx2.manifest()
3027 return f not in ctx2.manifest()
3028
3028
3029 def amend(ui, repo, commitfunc, old, extra, pats, opts):
3029 def amend(ui, repo, commitfunc, old, extra, pats, opts):
3030 # avoid cycle context -> subrepo -> cmdutil
3030 # avoid cycle context -> subrepo -> cmdutil
3031 from . import context
3031 from . import context
3032
3032
3033 # amend will reuse the existing user if not specified, but the obsolete
3033 # amend will reuse the existing user if not specified, but the obsolete
3034 # marker creation requires that the current user's name is specified.
3034 # marker creation requires that the current user's name is specified.
3035 if obsolete.isenabled(repo, obsolete.createmarkersopt):
3035 if obsolete.isenabled(repo, obsolete.createmarkersopt):
3036 ui.username() # raise exception if username not set
3036 ui.username() # raise exception if username not set
3037
3037
3038 ui.note(_('amending changeset %s\n') % old)
3038 ui.note(_('amending changeset %s\n') % old)
3039 base = old.p1()
3039 base = old.p1()
3040
3040
3041 with repo.wlock(), repo.lock(), repo.transaction('amend'):
3041 with repo.wlock(), repo.lock(), repo.transaction('amend'):
3042 # See if we got a message from -m or -l, if not, open the editor
3042 # See if we got a message from -m or -l, if not, open the editor
3043 # with the message of the changeset to amend
3043 # with the message of the changeset to amend
3044 message = logmessage(ui, opts)
3044 message = logmessage(ui, opts)
3045 # ensure logfile does not conflict with later enforcement of the
3045 # ensure logfile does not conflict with later enforcement of the
3046 # message. potential logfile content has been processed by
3046 # message. potential logfile content has been processed by
3047 # `logmessage` anyway.
3047 # `logmessage` anyway.
3048 opts.pop('logfile')
3048 opts.pop('logfile')
3049 # First, do a regular commit to record all changes in the working
3049 # First, do a regular commit to record all changes in the working
3050 # directory (if there are any)
3050 # directory (if there are any)
3051 ui.callhooks = False
3051 ui.callhooks = False
3052 activebookmark = repo._bookmarks.active
3052 activebookmark = repo._bookmarks.active
3053 try:
3053 try:
3054 repo._bookmarks.active = None
3054 repo._bookmarks.active = None
3055 opts['message'] = 'temporary amend commit for %s' % old
3055 opts['message'] = 'temporary amend commit for %s' % old
3056 node = commit(ui, repo, commitfunc, pats, opts)
3056 node = commit(ui, repo, commitfunc, pats, opts)
3057 finally:
3057 finally:
3058 repo._bookmarks.active = activebookmark
3058 repo._bookmarks.active = activebookmark
3059 ui.callhooks = True
3059 ui.callhooks = True
3060 ctx = repo[node]
3060 ctx = repo[node]
3061
3061
3062 # Participating changesets:
3062 # Participating changesets:
3063 #
3063 #
3064 # node/ctx o - new (intermediate) commit that contains changes
3064 # node/ctx o - new (intermediate) commit that contains changes
3065 # | from working dir to go into amending commit
3065 # | from working dir to go into amending commit
3066 # | (or a workingctx if there were no changes)
3066 # | (or a workingctx if there were no changes)
3067 # |
3067 # |
3068 # old o - changeset to amend
3068 # old o - changeset to amend
3069 # |
3069 # |
3070 # base o - first parent of the changeset to amend
3070 # base o - first parent of the changeset to amend
3071
3071
3072 # Update extra dict from amended commit (e.g. to preserve graft
3072 # Update extra dict from amended commit (e.g. to preserve graft
3073 # source)
3073 # source)
3074 extra.update(old.extra())
3074 extra.update(old.extra())
3075
3075
3076 # Also update it from the intermediate commit or from the wctx
3076 # Also update it from the intermediate commit or from the wctx
3077 extra.update(ctx.extra())
3077 extra.update(ctx.extra())
3078
3078
3079 if len(old.parents()) > 1:
3079 if len(old.parents()) > 1:
3080 # ctx.files() isn't reliable for merges, so fall back to the
3080 # ctx.files() isn't reliable for merges, so fall back to the
3081 # slower repo.status() method
3081 # slower repo.status() method
3082 files = set([fn for st in repo.status(base, old)[:3]
3082 files = set([fn for st in repo.status(base, old)[:3]
3083 for fn in st])
3083 for fn in st])
3084 else:
3084 else:
3085 files = set(old.files())
3085 files = set(old.files())
3086
3086
3087 # Second, we use either the commit we just did, or if there were no
3087 # Second, we use either the commit we just did, or if there were no
3088 # changes the parent of the working directory as the version of the
3088 # changes the parent of the working directory as the version of the
3089 # files in the final amend commit
3089 # files in the final amend commit
3090 if node:
3090 if node:
3091 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
3091 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
3092
3092
3093 user = ctx.user()
3093 user = ctx.user()
3094 date = ctx.date()
3094 date = ctx.date()
3095 # Recompute copies (avoid recording a -> b -> a)
3095 # Recompute copies (avoid recording a -> b -> a)
3096 copied = copies.pathcopies(base, ctx)
3096 copied = copies.pathcopies(base, ctx)
3097 if old.p2:
3097 if old.p2:
3098 copied.update(copies.pathcopies(old.p2(), ctx))
3098 copied.update(copies.pathcopies(old.p2(), ctx))
3099
3099
3100 # Prune files which were reverted by the updates: if old
3100 # Prune files which were reverted by the updates: if old
3101 # introduced file X and our intermediate commit, node,
3101 # introduced file X and our intermediate commit, node,
3102 # renamed that file, then those two files are the same and
3102 # renamed that file, then those two files are the same and
3103 # we can discard X from our list of files. Likewise if X
3103 # we can discard X from our list of files. Likewise if X
3104 # was deleted, it's no longer relevant
3104 # was deleted, it's no longer relevant
3105 files.update(ctx.files())
3105 files.update(ctx.files())
3106 files = [f for f in files if not samefile(f, ctx, base)]
3106 files = [f for f in files if not samefile(f, ctx, base)]
3107
3107
3108 def filectxfn(repo, ctx_, path):
3108 def filectxfn(repo, ctx_, path):
3109 try:
3109 try:
3110 fctx = ctx[path]
3110 fctx = ctx[path]
3111 flags = fctx.flags()
3111 flags = fctx.flags()
3112 mctx = context.memfilectx(repo,
3112 mctx = context.memfilectx(repo,
3113 fctx.path(), fctx.data(),
3113 fctx.path(), fctx.data(),
3114 islink='l' in flags,
3114 islink='l' in flags,
3115 isexec='x' in flags,
3115 isexec='x' in flags,
3116 copied=copied.get(path))
3116 copied=copied.get(path))
3117 return mctx
3117 return mctx
3118 except KeyError:
3118 except KeyError:
3119 return None
3119 return None
3120 else:
3120 else:
3121 ui.note(_('copying changeset %s to %s\n') % (old, base))
3121 ui.note(_('copying changeset %s to %s\n') % (old, base))
3122
3122
3123 # Use version of files as in the old cset
3123 # Use version of files as in the old cset
3124 def filectxfn(repo, ctx_, path):
3124 def filectxfn(repo, ctx_, path):
3125 try:
3125 try:
3126 return old.filectx(path)
3126 return old.filectx(path)
3127 except KeyError:
3127 except KeyError:
3128 return None
3128 return None
3129
3129
3130 user = opts.get('user') or old.user()
3130 user = opts.get('user') or old.user()
3131 date = opts.get('date') or old.date()
3131 date = opts.get('date') or old.date()
3132 editform = mergeeditform(old, 'commit.amend')
3132 editform = mergeeditform(old, 'commit.amend')
3133 editor = getcommiteditor(editform=editform,
3133 editor = getcommiteditor(editform=editform,
3134 **pycompat.strkwargs(opts))
3134 **pycompat.strkwargs(opts))
3135 if not message:
3135 if not message:
3136 editor = getcommiteditor(edit=True, editform=editform)
3136 editor = getcommiteditor(edit=True, editform=editform)
3137 message = old.description()
3137 message = old.description()
3138
3138
3139 pureextra = extra.copy()
3139 pureextra = extra.copy()
3140 extra['amend_source'] = old.hex()
3140 extra['amend_source'] = old.hex()
3141
3141
3142 new = context.memctx(repo,
3142 new = context.memctx(repo,
3143 parents=[base.node(), old.p2().node()],
3143 parents=[base.node(), old.p2().node()],
3144 text=message,
3144 text=message,
3145 files=files,
3145 files=files,
3146 filectxfn=filectxfn,
3146 filectxfn=filectxfn,
3147 user=user,
3147 user=user,
3148 date=date,
3148 date=date,
3149 extra=extra,
3149 extra=extra,
3150 editor=editor)
3150 editor=editor)
3151
3151
3152 newdesc = changelog.stripdesc(new.description())
3152 newdesc = changelog.stripdesc(new.description())
3153 if ((not node)
3153 if ((not node)
3154 and newdesc == old.description()
3154 and newdesc == old.description()
3155 and user == old.user()
3155 and user == old.user()
3156 and date == old.date()
3156 and date == old.date()
3157 and pureextra == old.extra()):
3157 and pureextra == old.extra()):
3158 # nothing changed. continuing here would create a new node
3158 # nothing changed. continuing here would create a new node
3159 # anyway because of the amend_source noise.
3159 # anyway because of the amend_source noise.
3160 #
3160 #
3161 # This not what we expect from amend.
3161 # This not what we expect from amend.
3162 return old.node()
3162 return old.node()
3163
3163
3164 if opts.get('secret'):
3164 if opts.get('secret'):
3165 commitphase = 'secret'
3165 commitphase = 'secret'
3166 else:
3166 else:
3167 commitphase = old.phase()
3167 commitphase = old.phase()
3168 overrides = {('phases', 'new-commit'): commitphase}
3168 overrides = {('phases', 'new-commit'): commitphase}
3169 with ui.configoverride(overrides, 'amend'):
3169 with ui.configoverride(overrides, 'amend'):
3170 newid = repo.commitctx(new)
3170 newid = repo.commitctx(new)
3171
3171
3172 # Reroute the working copy parent to the new changeset
3172 # Reroute the working copy parent to the new changeset
3173 repo.setparents(newid, nullid)
3173 repo.setparents(newid, nullid)
3174 mapping = {old.node(): (newid,)}
3174 mapping = {old.node(): (newid,)}
3175 if node:
3175 if node:
3176 mapping[node] = ()
3176 mapping[node] = ()
3177 scmutil.cleanupnodes(repo, mapping, 'amend')
3177 scmutil.cleanupnodes(repo, mapping, 'amend')
3178
3178
3179 return newid
3179 return newid
3180
3180
3181 def commiteditor(repo, ctx, subs, editform=''):
3181 def commiteditor(repo, ctx, subs, editform=''):
3182 if ctx.description():
3182 if ctx.description():
3183 return ctx.description()
3183 return ctx.description()
3184 return commitforceeditor(repo, ctx, subs, editform=editform,
3184 return commitforceeditor(repo, ctx, subs, editform=editform,
3185 unchangedmessagedetection=True)
3185 unchangedmessagedetection=True)
3186
3186
3187 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
3187 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
3188 editform='', unchangedmessagedetection=False):
3188 editform='', unchangedmessagedetection=False):
3189 if not extramsg:
3189 if not extramsg:
3190 extramsg = _("Leave message empty to abort commit.")
3190 extramsg = _("Leave message empty to abort commit.")
3191
3191
3192 forms = [e for e in editform.split('.') if e]
3192 forms = [e for e in editform.split('.') if e]
3193 forms.insert(0, 'changeset')
3193 forms.insert(0, 'changeset')
3194 templatetext = None
3194 templatetext = None
3195 while forms:
3195 while forms:
3196 ref = '.'.join(forms)
3196 ref = '.'.join(forms)
3197 if repo.ui.config('committemplate', ref):
3197 if repo.ui.config('committemplate', ref):
3198 templatetext = committext = buildcommittemplate(
3198 templatetext = committext = buildcommittemplate(
3199 repo, ctx, subs, extramsg, ref)
3199 repo, ctx, subs, extramsg, ref)
3200 break
3200 break
3201 forms.pop()
3201 forms.pop()
3202 else:
3202 else:
3203 committext = buildcommittext(repo, ctx, subs, extramsg)
3203 committext = buildcommittext(repo, ctx, subs, extramsg)
3204
3204
3205 # run editor in the repository root
3205 # run editor in the repository root
3206 olddir = pycompat.getcwd()
3206 olddir = pycompat.getcwd()
3207 os.chdir(repo.root)
3207 os.chdir(repo.root)
3208
3208
3209 # make in-memory changes visible to external process
3209 # make in-memory changes visible to external process
3210 tr = repo.currenttransaction()
3210 tr = repo.currenttransaction()
3211 repo.dirstate.write(tr)
3211 repo.dirstate.write(tr)
3212 pending = tr and tr.writepending() and repo.root
3212 pending = tr and tr.writepending() and repo.root
3213
3213
3214 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
3214 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
3215 editform=editform, pending=pending,
3215 editform=editform, pending=pending,
3216 repopath=repo.path, action='commit')
3216 repopath=repo.path, action='commit')
3217 text = editortext
3217 text = editortext
3218
3218
3219 # strip away anything below this special string (used for editors that want
3219 # strip away anything below this special string (used for editors that want
3220 # to display the diff)
3220 # to display the diff)
3221 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3221 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3222 if stripbelow:
3222 if stripbelow:
3223 text = text[:stripbelow.start()]
3223 text = text[:stripbelow.start()]
3224
3224
3225 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
3225 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
3226 os.chdir(olddir)
3226 os.chdir(olddir)
3227
3227
3228 if finishdesc:
3228 if finishdesc:
3229 text = finishdesc(text)
3229 text = finishdesc(text)
3230 if not text.strip():
3230 if not text.strip():
3231 raise error.Abort(_("empty commit message"))
3231 raise error.Abort(_("empty commit message"))
3232 if unchangedmessagedetection and editortext == templatetext:
3232 if unchangedmessagedetection and editortext == templatetext:
3233 raise error.Abort(_("commit message unchanged"))
3233 raise error.Abort(_("commit message unchanged"))
3234
3234
3235 return text
3235 return text
3236
3236
3237 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3237 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3238 ui = repo.ui
3238 ui = repo.ui
3239 spec = formatter.templatespec(ref, None, None)
3239 spec = formatter.templatespec(ref, None, None)
3240 t = changeset_templater(ui, repo, spec, None, {}, False)
3240 t = changeset_templater(ui, repo, spec, None, {}, False)
3241 t.t.cache.update((k, templater.unquotestring(v))
3241 t.t.cache.update((k, templater.unquotestring(v))
3242 for k, v in repo.ui.configitems('committemplate'))
3242 for k, v in repo.ui.configitems('committemplate'))
3243
3243
3244 if not extramsg:
3244 if not extramsg:
3245 extramsg = '' # ensure that extramsg is string
3245 extramsg = '' # ensure that extramsg is string
3246
3246
3247 ui.pushbuffer()
3247 ui.pushbuffer()
3248 t.show(ctx, extramsg=extramsg)
3248 t.show(ctx, extramsg=extramsg)
3249 return ui.popbuffer()
3249 return ui.popbuffer()
3250
3250
3251 def hgprefix(msg):
3251 def hgprefix(msg):
3252 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
3252 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
3253
3253
3254 def buildcommittext(repo, ctx, subs, extramsg):
3254 def buildcommittext(repo, ctx, subs, extramsg):
3255 edittext = []
3255 edittext = []
3256 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3256 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3257 if ctx.description():
3257 if ctx.description():
3258 edittext.append(ctx.description())
3258 edittext.append(ctx.description())
3259 edittext.append("")
3259 edittext.append("")
3260 edittext.append("") # Empty line between message and comments.
3260 edittext.append("") # Empty line between message and comments.
3261 edittext.append(hgprefix(_("Enter commit message."
3261 edittext.append(hgprefix(_("Enter commit message."
3262 " Lines beginning with 'HG:' are removed.")))
3262 " Lines beginning with 'HG:' are removed.")))
3263 edittext.append(hgprefix(extramsg))
3263 edittext.append(hgprefix(extramsg))
3264 edittext.append("HG: --")
3264 edittext.append("HG: --")
3265 edittext.append(hgprefix(_("user: %s") % ctx.user()))
3265 edittext.append(hgprefix(_("user: %s") % ctx.user()))
3266 if ctx.p2():
3266 if ctx.p2():
3267 edittext.append(hgprefix(_("branch merge")))
3267 edittext.append(hgprefix(_("branch merge")))
3268 if ctx.branch():
3268 if ctx.branch():
3269 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
3269 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
3270 if bookmarks.isactivewdirparent(repo):
3270 if bookmarks.isactivewdirparent(repo):
3271 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
3271 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
3272 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
3272 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
3273 edittext.extend([hgprefix(_("added %s") % f) for f in added])
3273 edittext.extend([hgprefix(_("added %s") % f) for f in added])
3274 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
3274 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
3275 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
3275 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
3276 if not added and not modified and not removed:
3276 if not added and not modified and not removed:
3277 edittext.append(hgprefix(_("no files changed")))
3277 edittext.append(hgprefix(_("no files changed")))
3278 edittext.append("")
3278 edittext.append("")
3279
3279
3280 return "\n".join(edittext)
3280 return "\n".join(edittext)
3281
3281
3282 def commitstatus(repo, node, branch, bheads=None, opts=None):
3282 def commitstatus(repo, node, branch, bheads=None, opts=None):
3283 if opts is None:
3283 if opts is None:
3284 opts = {}
3284 opts = {}
3285 ctx = repo[node]
3285 ctx = repo[node]
3286 parents = ctx.parents()
3286 parents = ctx.parents()
3287
3287
3288 if (not opts.get('amend') and bheads and node not in bheads and not
3288 if (not opts.get('amend') and bheads and node not in bheads and not
3289 [x for x in parents if x.node() in bheads and x.branch() == branch]):
3289 [x for x in parents if x.node() in bheads and x.branch() == branch]):
3290 repo.ui.status(_('created new head\n'))
3290 repo.ui.status(_('created new head\n'))
3291 # The message is not printed for initial roots. For the other
3291 # The message is not printed for initial roots. For the other
3292 # changesets, it is printed in the following situations:
3292 # changesets, it is printed in the following situations:
3293 #
3293 #
3294 # Par column: for the 2 parents with ...
3294 # Par column: for the 2 parents with ...
3295 # N: null or no parent
3295 # N: null or no parent
3296 # B: parent is on another named branch
3296 # B: parent is on another named branch
3297 # C: parent is a regular non head changeset
3297 # C: parent is a regular non head changeset
3298 # H: parent was a branch head of the current branch
3298 # H: parent was a branch head of the current branch
3299 # Msg column: whether we print "created new head" message
3299 # Msg column: whether we print "created new head" message
3300 # In the following, it is assumed that there already exists some
3300 # In the following, it is assumed that there already exists some
3301 # initial branch heads of the current branch, otherwise nothing is
3301 # initial branch heads of the current branch, otherwise nothing is
3302 # printed anyway.
3302 # printed anyway.
3303 #
3303 #
3304 # Par Msg Comment
3304 # Par Msg Comment
3305 # N N y additional topo root
3305 # N N y additional topo root
3306 #
3306 #
3307 # B N y additional branch root
3307 # B N y additional branch root
3308 # C N y additional topo head
3308 # C N y additional topo head
3309 # H N n usual case
3309 # H N n usual case
3310 #
3310 #
3311 # B B y weird additional branch root
3311 # B B y weird additional branch root
3312 # C B y branch merge
3312 # C B y branch merge
3313 # H B n merge with named branch
3313 # H B n merge with named branch
3314 #
3314 #
3315 # C C y additional head from merge
3315 # C C y additional head from merge
3316 # C H n merge with a head
3316 # C H n merge with a head
3317 #
3317 #
3318 # H H n head merge: head count decreases
3318 # H H n head merge: head count decreases
3319
3319
3320 if not opts.get('close_branch'):
3320 if not opts.get('close_branch'):
3321 for r in parents:
3321 for r in parents:
3322 if r.closesbranch() and r.branch() == branch:
3322 if r.closesbranch() and r.branch() == branch:
3323 repo.ui.status(_('reopening closed branch head %d\n') % r)
3323 repo.ui.status(_('reopening closed branch head %d\n') % r)
3324
3324
3325 if repo.ui.debugflag:
3325 if repo.ui.debugflag:
3326 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
3326 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
3327 elif repo.ui.verbose:
3327 elif repo.ui.verbose:
3328 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
3328 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
3329
3329
3330 def postcommitstatus(repo, pats, opts):
3330 def postcommitstatus(repo, pats, opts):
3331 return repo.status(match=scmutil.match(repo[None], pats, opts))
3331 return repo.status(match=scmutil.match(repo[None], pats, opts))
3332
3332
3333 def revert(ui, repo, ctx, parents, *pats, **opts):
3333 def revert(ui, repo, ctx, parents, *pats, **opts):
3334 parent, p2 = parents
3334 parent, p2 = parents
3335 node = ctx.node()
3335 node = ctx.node()
3336
3336
3337 mf = ctx.manifest()
3337 mf = ctx.manifest()
3338 if node == p2:
3338 if node == p2:
3339 parent = p2
3339 parent = p2
3340
3340
3341 # need all matching names in dirstate and manifest of target rev,
3341 # need all matching names in dirstate and manifest of target rev,
3342 # so have to walk both. do not print errors if files exist in one
3342 # so have to walk both. do not print errors if files exist in one
3343 # but not other. in both cases, filesets should be evaluated against
3343 # but not other. in both cases, filesets should be evaluated against
3344 # workingctx to get consistent result (issue4497). this means 'set:**'
3344 # workingctx to get consistent result (issue4497). this means 'set:**'
3345 # cannot be used to select missing files from target rev.
3345 # cannot be used to select missing files from target rev.
3346
3346
3347 # `names` is a mapping for all elements in working copy and target revision
3347 # `names` is a mapping for all elements in working copy and target revision
3348 # The mapping is in the form:
3348 # The mapping is in the form:
3349 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3349 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3350 names = {}
3350 names = {}
3351
3351
3352 with repo.wlock():
3352 with repo.wlock():
3353 ## filling of the `names` mapping
3353 ## filling of the `names` mapping
3354 # walk dirstate to fill `names`
3354 # walk dirstate to fill `names`
3355
3355
3356 interactive = opts.get('interactive', False)
3356 interactive = opts.get('interactive', False)
3357 wctx = repo[None]
3357 wctx = repo[None]
3358 m = scmutil.match(wctx, pats, opts)
3358 m = scmutil.match(wctx, pats, opts)
3359
3359
3360 # we'll need this later
3360 # we'll need this later
3361 targetsubs = sorted(s for s in wctx.substate if m(s))
3361 targetsubs = sorted(s for s in wctx.substate if m(s))
3362
3362
3363 if not m.always():
3363 if not m.always():
3364 matcher = matchmod.badmatch(m, lambda x, y: False)
3364 matcher = matchmod.badmatch(m, lambda x, y: False)
3365 for abs in wctx.walk(matcher):
3365 for abs in wctx.walk(matcher):
3366 names[abs] = m.rel(abs), m.exact(abs)
3366 names[abs] = m.rel(abs), m.exact(abs)
3367
3367
3368 # walk target manifest to fill `names`
3368 # walk target manifest to fill `names`
3369
3369
3370 def badfn(path, msg):
3370 def badfn(path, msg):
3371 if path in names:
3371 if path in names:
3372 return
3372 return
3373 if path in ctx.substate:
3373 if path in ctx.substate:
3374 return
3374 return
3375 path_ = path + '/'
3375 path_ = path + '/'
3376 for f in names:
3376 for f in names:
3377 if f.startswith(path_):
3377 if f.startswith(path_):
3378 return
3378 return
3379 ui.warn("%s: %s\n" % (m.rel(path), msg))
3379 ui.warn("%s: %s\n" % (m.rel(path), msg))
3380
3380
3381 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3381 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3382 if abs not in names:
3382 if abs not in names:
3383 names[abs] = m.rel(abs), m.exact(abs)
3383 names[abs] = m.rel(abs), m.exact(abs)
3384
3384
3385 # Find status of all file in `names`.
3385 # Find status of all file in `names`.
3386 m = scmutil.matchfiles(repo, names)
3386 m = scmutil.matchfiles(repo, names)
3387
3387
3388 changes = repo.status(node1=node, match=m,
3388 changes = repo.status(node1=node, match=m,
3389 unknown=True, ignored=True, clean=True)
3389 unknown=True, ignored=True, clean=True)
3390 else:
3390 else:
3391 changes = repo.status(node1=node, match=m)
3391 changes = repo.status(node1=node, match=m)
3392 for kind in changes:
3392 for kind in changes:
3393 for abs in kind:
3393 for abs in kind:
3394 names[abs] = m.rel(abs), m.exact(abs)
3394 names[abs] = m.rel(abs), m.exact(abs)
3395
3395
3396 m = scmutil.matchfiles(repo, names)
3396 m = scmutil.matchfiles(repo, names)
3397
3397
3398 modified = set(changes.modified)
3398 modified = set(changes.modified)
3399 added = set(changes.added)
3399 added = set(changes.added)
3400 removed = set(changes.removed)
3400 removed = set(changes.removed)
3401 _deleted = set(changes.deleted)
3401 _deleted = set(changes.deleted)
3402 unknown = set(changes.unknown)
3402 unknown = set(changes.unknown)
3403 unknown.update(changes.ignored)
3403 unknown.update(changes.ignored)
3404 clean = set(changes.clean)
3404 clean = set(changes.clean)
3405 modadded = set()
3405 modadded = set()
3406
3406
3407 # We need to account for the state of the file in the dirstate,
3407 # We need to account for the state of the file in the dirstate,
3408 # even when we revert against something else than parent. This will
3408 # even when we revert against something else than parent. This will
3409 # slightly alter the behavior of revert (doing back up or not, delete
3409 # slightly alter the behavior of revert (doing back up or not, delete
3410 # or just forget etc).
3410 # or just forget etc).
3411 if parent == node:
3411 if parent == node:
3412 dsmodified = modified
3412 dsmodified = modified
3413 dsadded = added
3413 dsadded = added
3414 dsremoved = removed
3414 dsremoved = removed
3415 # store all local modifications, useful later for rename detection
3415 # store all local modifications, useful later for rename detection
3416 localchanges = dsmodified | dsadded
3416 localchanges = dsmodified | dsadded
3417 modified, added, removed = set(), set(), set()
3417 modified, added, removed = set(), set(), set()
3418 else:
3418 else:
3419 changes = repo.status(node1=parent, match=m)
3419 changes = repo.status(node1=parent, match=m)
3420 dsmodified = set(changes.modified)
3420 dsmodified = set(changes.modified)
3421 dsadded = set(changes.added)
3421 dsadded = set(changes.added)
3422 dsremoved = set(changes.removed)
3422 dsremoved = set(changes.removed)
3423 # store all local modifications, useful later for rename detection
3423 # store all local modifications, useful later for rename detection
3424 localchanges = dsmodified | dsadded
3424 localchanges = dsmodified | dsadded
3425
3425
3426 # only take into account for removes between wc and target
3426 # only take into account for removes between wc and target
3427 clean |= dsremoved - removed
3427 clean |= dsremoved - removed
3428 dsremoved &= removed
3428 dsremoved &= removed
3429 # distinct between dirstate remove and other
3429 # distinct between dirstate remove and other
3430 removed -= dsremoved
3430 removed -= dsremoved
3431
3431
3432 modadded = added & dsmodified
3432 modadded = added & dsmodified
3433 added -= modadded
3433 added -= modadded
3434
3434
3435 # tell newly modified apart.
3435 # tell newly modified apart.
3436 dsmodified &= modified
3436 dsmodified &= modified
3437 dsmodified |= modified & dsadded # dirstate added may need backup
3437 dsmodified |= modified & dsadded # dirstate added may need backup
3438 modified -= dsmodified
3438 modified -= dsmodified
3439
3439
3440 # We need to wait for some post-processing to update this set
3440 # We need to wait for some post-processing to update this set
3441 # before making the distinction. The dirstate will be used for
3441 # before making the distinction. The dirstate will be used for
3442 # that purpose.
3442 # that purpose.
3443 dsadded = added
3443 dsadded = added
3444
3444
3445 # in case of merge, files that are actually added can be reported as
3445 # in case of merge, files that are actually added can be reported as
3446 # modified, we need to post process the result
3446 # modified, we need to post process the result
3447 if p2 != nullid:
3447 if p2 != nullid:
3448 mergeadd = set(dsmodified)
3448 mergeadd = set(dsmodified)
3449 for path in dsmodified:
3449 for path in dsmodified:
3450 if path in mf:
3450 if path in mf:
3451 mergeadd.remove(path)
3451 mergeadd.remove(path)
3452 dsadded |= mergeadd
3452 dsadded |= mergeadd
3453 dsmodified -= mergeadd
3453 dsmodified -= mergeadd
3454
3454
3455 # if f is a rename, update `names` to also revert the source
3455 # if f is a rename, update `names` to also revert the source
3456 cwd = repo.getcwd()
3456 cwd = repo.getcwd()
3457 for f in localchanges:
3457 for f in localchanges:
3458 src = repo.dirstate.copied(f)
3458 src = repo.dirstate.copied(f)
3459 # XXX should we check for rename down to target node?
3459 # XXX should we check for rename down to target node?
3460 if src and src not in names and repo.dirstate[src] == 'r':
3460 if src and src not in names and repo.dirstate[src] == 'r':
3461 dsremoved.add(src)
3461 dsremoved.add(src)
3462 names[src] = (repo.pathto(src, cwd), True)
3462 names[src] = (repo.pathto(src, cwd), True)
3463
3463
3464 # determine the exact nature of the deleted changesets
3464 # determine the exact nature of the deleted changesets
3465 deladded = set(_deleted)
3465 deladded = set(_deleted)
3466 for path in _deleted:
3466 for path in _deleted:
3467 if path in mf:
3467 if path in mf:
3468 deladded.remove(path)
3468 deladded.remove(path)
3469 deleted = _deleted - deladded
3469 deleted = _deleted - deladded
3470
3470
3471 # distinguish between file to forget and the other
3471 # distinguish between file to forget and the other
3472 added = set()
3472 added = set()
3473 for abs in dsadded:
3473 for abs in dsadded:
3474 if repo.dirstate[abs] != 'a':
3474 if repo.dirstate[abs] != 'a':
3475 added.add(abs)
3475 added.add(abs)
3476 dsadded -= added
3476 dsadded -= added
3477
3477
3478 for abs in deladded:
3478 for abs in deladded:
3479 if repo.dirstate[abs] == 'a':
3479 if repo.dirstate[abs] == 'a':
3480 dsadded.add(abs)
3480 dsadded.add(abs)
3481 deladded -= dsadded
3481 deladded -= dsadded
3482
3482
3483 # For files marked as removed, we check if an unknown file is present at
3483 # For files marked as removed, we check if an unknown file is present at
3484 # the same path. If a such file exists it may need to be backed up.
3484 # the same path. If a such file exists it may need to be backed up.
3485 # Making the distinction at this stage helps have simpler backup
3485 # Making the distinction at this stage helps have simpler backup
3486 # logic.
3486 # logic.
3487 removunk = set()
3487 removunk = set()
3488 for abs in removed:
3488 for abs in removed:
3489 target = repo.wjoin(abs)
3489 target = repo.wjoin(abs)
3490 if os.path.lexists(target):
3490 if os.path.lexists(target):
3491 removunk.add(abs)
3491 removunk.add(abs)
3492 removed -= removunk
3492 removed -= removunk
3493
3493
3494 dsremovunk = set()
3494 dsremovunk = set()
3495 for abs in dsremoved:
3495 for abs in dsremoved:
3496 target = repo.wjoin(abs)
3496 target = repo.wjoin(abs)
3497 if os.path.lexists(target):
3497 if os.path.lexists(target):
3498 dsremovunk.add(abs)
3498 dsremovunk.add(abs)
3499 dsremoved -= dsremovunk
3499 dsremoved -= dsremovunk
3500
3500
3501 # action to be actually performed by revert
3501 # action to be actually performed by revert
3502 # (<list of file>, message>) tuple
3502 # (<list of file>, message>) tuple
3503 actions = {'revert': ([], _('reverting %s\n')),
3503 actions = {'revert': ([], _('reverting %s\n')),
3504 'add': ([], _('adding %s\n')),
3504 'add': ([], _('adding %s\n')),
3505 'remove': ([], _('removing %s\n')),
3505 'remove': ([], _('removing %s\n')),
3506 'drop': ([], _('removing %s\n')),
3506 'drop': ([], _('removing %s\n')),
3507 'forget': ([], _('forgetting %s\n')),
3507 'forget': ([], _('forgetting %s\n')),
3508 'undelete': ([], _('undeleting %s\n')),
3508 'undelete': ([], _('undeleting %s\n')),
3509 'noop': (None, _('no changes needed to %s\n')),
3509 'noop': (None, _('no changes needed to %s\n')),
3510 'unknown': (None, _('file not managed: %s\n')),
3510 'unknown': (None, _('file not managed: %s\n')),
3511 }
3511 }
3512
3512
3513 # "constant" that convey the backup strategy.
3513 # "constant" that convey the backup strategy.
3514 # All set to `discard` if `no-backup` is set do avoid checking
3514 # All set to `discard` if `no-backup` is set do avoid checking
3515 # no_backup lower in the code.
3515 # no_backup lower in the code.
3516 # These values are ordered for comparison purposes
3516 # These values are ordered for comparison purposes
3517 backupinteractive = 3 # do backup if interactively modified
3517 backupinteractive = 3 # do backup if interactively modified
3518 backup = 2 # unconditionally do backup
3518 backup = 2 # unconditionally do backup
3519 check = 1 # check if the existing file differs from target
3519 check = 1 # check if the existing file differs from target
3520 discard = 0 # never do backup
3520 discard = 0 # never do backup
3521 if opts.get('no_backup'):
3521 if opts.get('no_backup'):
3522 backupinteractive = backup = check = discard
3522 backupinteractive = backup = check = discard
3523 if interactive:
3523 if interactive:
3524 dsmodifiedbackup = backupinteractive
3524 dsmodifiedbackup = backupinteractive
3525 else:
3525 else:
3526 dsmodifiedbackup = backup
3526 dsmodifiedbackup = backup
3527 tobackup = set()
3527 tobackup = set()
3528
3528
3529 backupanddel = actions['remove']
3529 backupanddel = actions['remove']
3530 if not opts.get('no_backup'):
3530 if not opts.get('no_backup'):
3531 backupanddel = actions['drop']
3531 backupanddel = actions['drop']
3532
3532
3533 disptable = (
3533 disptable = (
3534 # dispatch table:
3534 # dispatch table:
3535 # file state
3535 # file state
3536 # action
3536 # action
3537 # make backup
3537 # make backup
3538
3538
3539 ## Sets that results that will change file on disk
3539 ## Sets that results that will change file on disk
3540 # Modified compared to target, no local change
3540 # Modified compared to target, no local change
3541 (modified, actions['revert'], discard),
3541 (modified, actions['revert'], discard),
3542 # Modified compared to target, but local file is deleted
3542 # Modified compared to target, but local file is deleted
3543 (deleted, actions['revert'], discard),
3543 (deleted, actions['revert'], discard),
3544 # Modified compared to target, local change
3544 # Modified compared to target, local change
3545 (dsmodified, actions['revert'], dsmodifiedbackup),
3545 (dsmodified, actions['revert'], dsmodifiedbackup),
3546 # Added since target
3546 # Added since target
3547 (added, actions['remove'], discard),
3547 (added, actions['remove'], discard),
3548 # Added in working directory
3548 # Added in working directory
3549 (dsadded, actions['forget'], discard),
3549 (dsadded, actions['forget'], discard),
3550 # Added since target, have local modification
3550 # Added since target, have local modification
3551 (modadded, backupanddel, backup),
3551 (modadded, backupanddel, backup),
3552 # Added since target but file is missing in working directory
3552 # Added since target but file is missing in working directory
3553 (deladded, actions['drop'], discard),
3553 (deladded, actions['drop'], discard),
3554 # Removed since target, before working copy parent
3554 # Removed since target, before working copy parent
3555 (removed, actions['add'], discard),
3555 (removed, actions['add'], discard),
3556 # Same as `removed` but an unknown file exists at the same path
3556 # Same as `removed` but an unknown file exists at the same path
3557 (removunk, actions['add'], check),
3557 (removunk, actions['add'], check),
3558 # Removed since targe, marked as such in working copy parent
3558 # Removed since targe, marked as such in working copy parent
3559 (dsremoved, actions['undelete'], discard),
3559 (dsremoved, actions['undelete'], discard),
3560 # Same as `dsremoved` but an unknown file exists at the same path
3560 # Same as `dsremoved` but an unknown file exists at the same path
3561 (dsremovunk, actions['undelete'], check),
3561 (dsremovunk, actions['undelete'], check),
3562 ## the following sets does not result in any file changes
3562 ## the following sets does not result in any file changes
3563 # File with no modification
3563 # File with no modification
3564 (clean, actions['noop'], discard),
3564 (clean, actions['noop'], discard),
3565 # Existing file, not tracked anywhere
3565 # Existing file, not tracked anywhere
3566 (unknown, actions['unknown'], discard),
3566 (unknown, actions['unknown'], discard),
3567 )
3567 )
3568
3568
3569 for abs, (rel, exact) in sorted(names.items()):
3569 for abs, (rel, exact) in sorted(names.items()):
3570 # target file to be touch on disk (relative to cwd)
3570 # target file to be touch on disk (relative to cwd)
3571 target = repo.wjoin(abs)
3571 target = repo.wjoin(abs)
3572 # search the entry in the dispatch table.
3572 # search the entry in the dispatch table.
3573 # if the file is in any of these sets, it was touched in the working
3573 # if the file is in any of these sets, it was touched in the working
3574 # directory parent and we are sure it needs to be reverted.
3574 # directory parent and we are sure it needs to be reverted.
3575 for table, (xlist, msg), dobackup in disptable:
3575 for table, (xlist, msg), dobackup in disptable:
3576 if abs not in table:
3576 if abs not in table:
3577 continue
3577 continue
3578 if xlist is not None:
3578 if xlist is not None:
3579 xlist.append(abs)
3579 xlist.append(abs)
3580 if dobackup:
3580 if dobackup:
3581 # If in interactive mode, don't automatically create
3581 # If in interactive mode, don't automatically create
3582 # .orig files (issue4793)
3582 # .orig files (issue4793)
3583 if dobackup == backupinteractive:
3583 if dobackup == backupinteractive:
3584 tobackup.add(abs)
3584 tobackup.add(abs)
3585 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3585 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3586 bakname = scmutil.origpath(ui, repo, rel)
3586 bakname = scmutil.origpath(ui, repo, rel)
3587 ui.note(_('saving current version of %s as %s\n') %
3587 ui.note(_('saving current version of %s as %s\n') %
3588 (rel, bakname))
3588 (rel, bakname))
3589 if not opts.get('dry_run'):
3589 if not opts.get('dry_run'):
3590 if interactive:
3590 if interactive:
3591 util.copyfile(target, bakname)
3591 util.copyfile(target, bakname)
3592 else:
3592 else:
3593 util.rename(target, bakname)
3593 util.rename(target, bakname)
3594 if ui.verbose or not exact:
3594 if ui.verbose or not exact:
3595 if not isinstance(msg, basestring):
3595 if not isinstance(msg, basestring):
3596 msg = msg(abs)
3596 msg = msg(abs)
3597 ui.status(msg % rel)
3597 ui.status(msg % rel)
3598 elif exact:
3598 elif exact:
3599 ui.warn(msg % rel)
3599 ui.warn(msg % rel)
3600 break
3600 break
3601
3601
3602 if not opts.get('dry_run'):
3602 if not opts.get('dry_run'):
3603 needdata = ('revert', 'add', 'undelete')
3603 needdata = ('revert', 'add', 'undelete')
3604 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3604 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3605 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3605 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3606
3606
3607 if targetsubs:
3607 if targetsubs:
3608 # Revert the subrepos on the revert list
3608 # Revert the subrepos on the revert list
3609 for sub in targetsubs:
3609 for sub in targetsubs:
3610 try:
3610 try:
3611 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3611 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3612 except KeyError:
3612 except KeyError:
3613 raise error.Abort("subrepository '%s' does not exist in %s!"
3613 raise error.Abort("subrepository '%s' does not exist in %s!"
3614 % (sub, short(ctx.node())))
3614 % (sub, short(ctx.node())))
3615
3615
3616 def _revertprefetch(repo, ctx, *files):
3616 def _revertprefetch(repo, ctx, *files):
3617 """Let extension changing the storage layer prefetch content"""
3617 """Let extension changing the storage layer prefetch content"""
3618 pass
3618 pass
3619
3619
3620 def _performrevert(repo, parents, ctx, actions, interactive=False,
3620 def _performrevert(repo, parents, ctx, actions, interactive=False,
3621 tobackup=None):
3621 tobackup=None):
3622 """function that actually perform all the actions computed for revert
3622 """function that actually perform all the actions computed for revert
3623
3623
3624 This is an independent function to let extension to plug in and react to
3624 This is an independent function to let extension to plug in and react to
3625 the imminent revert.
3625 the imminent revert.
3626
3626
3627 Make sure you have the working directory locked when calling this function.
3627 Make sure you have the working directory locked when calling this function.
3628 """
3628 """
3629 parent, p2 = parents
3629 parent, p2 = parents
3630 node = ctx.node()
3630 node = ctx.node()
3631 excluded_files = []
3631 excluded_files = []
3632 matcher_opts = {"exclude": excluded_files}
3632 matcher_opts = {"exclude": excluded_files}
3633
3633
3634 def checkout(f):
3634 def checkout(f):
3635 fc = ctx[f]
3635 fc = ctx[f]
3636 repo.wwrite(f, fc.data(), fc.flags())
3636 repo.wwrite(f, fc.data(), fc.flags())
3637
3637
3638 def doremove(f):
3638 def doremove(f):
3639 try:
3639 try:
3640 repo.wvfs.unlinkpath(f)
3640 repo.wvfs.unlinkpath(f)
3641 except OSError:
3641 except OSError:
3642 pass
3642 pass
3643 repo.dirstate.remove(f)
3643 repo.dirstate.remove(f)
3644
3644
3645 audit_path = pathutil.pathauditor(repo.root, cached=True)
3645 audit_path = pathutil.pathauditor(repo.root, cached=True)
3646 for f in actions['forget'][0]:
3646 for f in actions['forget'][0]:
3647 if interactive:
3647 if interactive:
3648 choice = repo.ui.promptchoice(
3648 choice = repo.ui.promptchoice(
3649 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3649 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3650 if choice == 0:
3650 if choice == 0:
3651 repo.dirstate.drop(f)
3651 repo.dirstate.drop(f)
3652 else:
3652 else:
3653 excluded_files.append(repo.wjoin(f))
3653 excluded_files.append(repo.wjoin(f))
3654 else:
3654 else:
3655 repo.dirstate.drop(f)
3655 repo.dirstate.drop(f)
3656 for f in actions['remove'][0]:
3656 for f in actions['remove'][0]:
3657 audit_path(f)
3657 audit_path(f)
3658 if interactive:
3658 if interactive:
3659 choice = repo.ui.promptchoice(
3659 choice = repo.ui.promptchoice(
3660 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3660 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3661 if choice == 0:
3661 if choice == 0:
3662 doremove(f)
3662 doremove(f)
3663 else:
3663 else:
3664 excluded_files.append(repo.wjoin(f))
3664 excluded_files.append(repo.wjoin(f))
3665 else:
3665 else:
3666 doremove(f)
3666 doremove(f)
3667 for f in actions['drop'][0]:
3667 for f in actions['drop'][0]:
3668 audit_path(f)
3668 audit_path(f)
3669 repo.dirstate.remove(f)
3669 repo.dirstate.remove(f)
3670
3670
3671 normal = None
3671 normal = None
3672 if node == parent:
3672 if node == parent:
3673 # We're reverting to our parent. If possible, we'd like status
3673 # We're reverting to our parent. If possible, we'd like status
3674 # to report the file as clean. We have to use normallookup for
3674 # to report the file as clean. We have to use normallookup for
3675 # merges to avoid losing information about merged/dirty files.
3675 # merges to avoid losing information about merged/dirty files.
3676 if p2 != nullid:
3676 if p2 != nullid:
3677 normal = repo.dirstate.normallookup
3677 normal = repo.dirstate.normallookup
3678 else:
3678 else:
3679 normal = repo.dirstate.normal
3679 normal = repo.dirstate.normal
3680
3680
3681 newlyaddedandmodifiedfiles = set()
3681 newlyaddedandmodifiedfiles = set()
3682 if interactive:
3682 if interactive:
3683 # Prompt the user for changes to revert
3683 # Prompt the user for changes to revert
3684 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3684 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3685 m = scmutil.match(ctx, torevert, matcher_opts)
3685 m = scmutil.match(ctx, torevert, matcher_opts)
3686 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3686 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3687 diffopts.nodates = True
3687 diffopts.nodates = True
3688 diffopts.git = True
3688 diffopts.git = True
3689 operation = 'discard'
3689 operation = 'discard'
3690 reversehunks = True
3690 reversehunks = True
3691 if node != parent:
3691 if node != parent:
3692 operation = 'revert'
3692 operation = 'revert'
3693 reversehunks = repo.ui.configbool('experimental',
3693 reversehunks = repo.ui.configbool('experimental',
3694 'revertalternateinteractivemode')
3694 'revertalternateinteractivemode')
3695 if reversehunks:
3695 if reversehunks:
3696 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3696 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3697 else:
3697 else:
3698 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3698 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3699 originalchunks = patch.parsepatch(diff)
3699 originalchunks = patch.parsepatch(diff)
3700
3700
3701 try:
3701 try:
3702
3702
3703 chunks, opts = recordfilter(repo.ui, originalchunks,
3703 chunks, opts = recordfilter(repo.ui, originalchunks,
3704 operation=operation)
3704 operation=operation)
3705 if reversehunks:
3705 if reversehunks:
3706 chunks = patch.reversehunks(chunks)
3706 chunks = patch.reversehunks(chunks)
3707
3707
3708 except patch.PatchError as err:
3708 except patch.PatchError as err:
3709 raise error.Abort(_('error parsing patch: %s') % err)
3709 raise error.Abort(_('error parsing patch: %s') % err)
3710
3710
3711 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3711 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3712 if tobackup is None:
3712 if tobackup is None:
3713 tobackup = set()
3713 tobackup = set()
3714 # Apply changes
3714 # Apply changes
3715 fp = stringio()
3715 fp = stringio()
3716 for c in chunks:
3716 for c in chunks:
3717 # Create a backup file only if this hunk should be backed up
3717 # Create a backup file only if this hunk should be backed up
3718 if ishunk(c) and c.header.filename() in tobackup:
3718 if ishunk(c) and c.header.filename() in tobackup:
3719 abs = c.header.filename()
3719 abs = c.header.filename()
3720 target = repo.wjoin(abs)
3720 target = repo.wjoin(abs)
3721 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3721 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3722 util.copyfile(target, bakname)
3722 util.copyfile(target, bakname)
3723 tobackup.remove(abs)
3723 tobackup.remove(abs)
3724 c.write(fp)
3724 c.write(fp)
3725 dopatch = fp.tell()
3725 dopatch = fp.tell()
3726 fp.seek(0)
3726 fp.seek(0)
3727 if dopatch:
3727 if dopatch:
3728 try:
3728 try:
3729 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3729 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3730 except patch.PatchError as err:
3730 except patch.PatchError as err:
3731 raise error.Abort(str(err))
3731 raise error.Abort(str(err))
3732 del fp
3732 del fp
3733 else:
3733 else:
3734 for f in actions['revert'][0]:
3734 for f in actions['revert'][0]:
3735 checkout(f)
3735 checkout(f)
3736 if normal:
3736 if normal:
3737 normal(f)
3737 normal(f)
3738
3738
3739 for f in actions['add'][0]:
3739 for f in actions['add'][0]:
3740 # Don't checkout modified files, they are already created by the diff
3740 # Don't checkout modified files, they are already created by the diff
3741 if f not in newlyaddedandmodifiedfiles:
3741 if f not in newlyaddedandmodifiedfiles:
3742 checkout(f)
3742 checkout(f)
3743 repo.dirstate.add(f)
3743 repo.dirstate.add(f)
3744
3744
3745 normal = repo.dirstate.normallookup
3745 normal = repo.dirstate.normallookup
3746 if node == parent and p2 == nullid:
3746 if node == parent and p2 == nullid:
3747 normal = repo.dirstate.normal
3747 normal = repo.dirstate.normal
3748 for f in actions['undelete'][0]:
3748 for f in actions['undelete'][0]:
3749 checkout(f)
3749 checkout(f)
3750 normal(f)
3750 normal(f)
3751
3751
3752 copied = copies.pathcopies(repo[parent], ctx)
3752 copied = copies.pathcopies(repo[parent], ctx)
3753
3753
3754 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3754 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3755 if f in copied:
3755 if f in copied:
3756 repo.dirstate.copy(copied[f], f)
3756 repo.dirstate.copy(copied[f], f)
3757
3757
3758 class command(registrar.command):
3758 class command(registrar.command):
3759 def _doregister(self, func, name, *args, **kwargs):
3759 def _doregister(self, func, name, *args, **kwargs):
3760 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3760 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3761 return super(command, self)._doregister(func, name, *args, **kwargs)
3761 return super(command, self)._doregister(func, name, *args, **kwargs)
3762
3762
3763 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3763 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3764 # commands.outgoing. "missing" is "missing" of the result of
3764 # commands.outgoing. "missing" is "missing" of the result of
3765 # "findcommonoutgoing()"
3765 # "findcommonoutgoing()"
3766 outgoinghooks = util.hooks()
3766 outgoinghooks = util.hooks()
3767
3767
3768 # a list of (ui, repo) functions called by commands.summary
3768 # a list of (ui, repo) functions called by commands.summary
3769 summaryhooks = util.hooks()
3769 summaryhooks = util.hooks()
3770
3770
3771 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3771 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3772 #
3772 #
3773 # functions should return tuple of booleans below, if 'changes' is None:
3773 # functions should return tuple of booleans below, if 'changes' is None:
3774 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3774 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3775 #
3775 #
3776 # otherwise, 'changes' is a tuple of tuples below:
3776 # otherwise, 'changes' is a tuple of tuples below:
3777 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3777 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3778 # - (desturl, destbranch, destpeer, outgoing)
3778 # - (desturl, destbranch, destpeer, outgoing)
3779 summaryremotehooks = util.hooks()
3779 summaryremotehooks = util.hooks()
3780
3780
3781 # A list of state files kept by multistep operations like graft.
3781 # A list of state files kept by multistep operations like graft.
3782 # Since graft cannot be aborted, it is considered 'clearable' by update.
3782 # Since graft cannot be aborted, it is considered 'clearable' by update.
3783 # note: bisect is intentionally excluded
3783 # note: bisect is intentionally excluded
3784 # (state file, clearable, allowcommit, error, hint)
3784 # (state file, clearable, allowcommit, error, hint)
3785 unfinishedstates = [
3785 unfinishedstates = [
3786 ('graftstate', True, False, _('graft in progress'),
3786 ('graftstate', True, False, _('graft in progress'),
3787 _("use 'hg graft --continue' or 'hg update' to abort")),
3787 _("use 'hg graft --continue' or 'hg update' to abort")),
3788 ('updatestate', True, False, _('last update was interrupted'),
3788 ('updatestate', True, False, _('last update was interrupted'),
3789 _("use 'hg update' to get a consistent checkout"))
3789 _("use 'hg update' to get a consistent checkout"))
3790 ]
3790 ]
3791
3791
3792 def checkunfinished(repo, commit=False):
3792 def checkunfinished(repo, commit=False):
3793 '''Look for an unfinished multistep operation, like graft, and abort
3793 '''Look for an unfinished multistep operation, like graft, and abort
3794 if found. It's probably good to check this right before
3794 if found. It's probably good to check this right before
3795 bailifchanged().
3795 bailifchanged().
3796 '''
3796 '''
3797 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3797 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3798 if commit and allowcommit:
3798 if commit and allowcommit:
3799 continue
3799 continue
3800 if repo.vfs.exists(f):
3800 if repo.vfs.exists(f):
3801 raise error.Abort(msg, hint=hint)
3801 raise error.Abort(msg, hint=hint)
3802
3802
3803 def clearunfinished(repo):
3803 def clearunfinished(repo):
3804 '''Check for unfinished operations (as above), and clear the ones
3804 '''Check for unfinished operations (as above), and clear the ones
3805 that are clearable.
3805 that are clearable.
3806 '''
3806 '''
3807 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3807 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3808 if not clearable and repo.vfs.exists(f):
3808 if not clearable and repo.vfs.exists(f):
3809 raise error.Abort(msg, hint=hint)
3809 raise error.Abort(msg, hint=hint)
3810 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3810 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3811 if clearable and repo.vfs.exists(f):
3811 if clearable and repo.vfs.exists(f):
3812 util.unlink(repo.vfs.join(f))
3812 util.unlink(repo.vfs.join(f))
3813
3813
3814 afterresolvedstates = [
3814 afterresolvedstates = [
3815 ('graftstate',
3815 ('graftstate',
3816 _('hg graft --continue')),
3816 _('hg graft --continue')),
3817 ]
3817 ]
3818
3818
3819 def howtocontinue(repo):
3819 def howtocontinue(repo):
3820 '''Check for an unfinished operation and return the command to finish
3820 '''Check for an unfinished operation and return the command to finish
3821 it.
3821 it.
3822
3822
3823 afterresolvedstates tuples define a .hg/{file} and the corresponding
3823 afterresolvedstates tuples define a .hg/{file} and the corresponding
3824 command needed to finish it.
3824 command needed to finish it.
3825
3825
3826 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3826 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3827 a boolean.
3827 a boolean.
3828 '''
3828 '''
3829 contmsg = _("continue: %s")
3829 contmsg = _("continue: %s")
3830 for f, msg in afterresolvedstates:
3830 for f, msg in afterresolvedstates:
3831 if repo.vfs.exists(f):
3831 if repo.vfs.exists(f):
3832 return contmsg % msg, True
3832 return contmsg % msg, True
3833 if repo[None].dirty(missing=True, merge=False, branch=False):
3833 if repo[None].dirty(missing=True, merge=False, branch=False):
3834 return contmsg % _("hg commit"), False
3834 return contmsg % _("hg commit"), False
3835 return None, None
3835 return None, None
3836
3836
3837 def checkafterresolved(repo):
3837 def checkafterresolved(repo):
3838 '''Inform the user about the next action after completing hg resolve
3838 '''Inform the user about the next action after completing hg resolve
3839
3839
3840 If there's a matching afterresolvedstates, howtocontinue will yield
3840 If there's a matching afterresolvedstates, howtocontinue will yield
3841 repo.ui.warn as the reporter.
3841 repo.ui.warn as the reporter.
3842
3842
3843 Otherwise, it will yield repo.ui.note.
3843 Otherwise, it will yield repo.ui.note.
3844 '''
3844 '''
3845 msg, warning = howtocontinue(repo)
3845 msg, warning = howtocontinue(repo)
3846 if msg is not None:
3846 if msg is not None:
3847 if warning:
3847 if warning:
3848 repo.ui.warn("%s\n" % msg)
3848 repo.ui.warn("%s\n" % msg)
3849 else:
3849 else:
3850 repo.ui.note("%s\n" % msg)
3850 repo.ui.note("%s\n" % msg)
3851
3851
3852 def wrongtooltocontinue(repo, task):
3852 def wrongtooltocontinue(repo, task):
3853 '''Raise an abort suggesting how to properly continue if there is an
3853 '''Raise an abort suggesting how to properly continue if there is an
3854 active task.
3854 active task.
3855
3855
3856 Uses howtocontinue() to find the active task.
3856 Uses howtocontinue() to find the active task.
3857
3857
3858 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3858 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3859 a hint.
3859 a hint.
3860 '''
3860 '''
3861 after = howtocontinue(repo)
3861 after = howtocontinue(repo)
3862 hint = None
3862 hint = None
3863 if after[1]:
3863 if after[1]:
3864 hint = after[0]
3864 hint = after[0]
3865 raise error.Abort(_('no %s in progress') % task, hint=hint)
3865 raise error.Abort(_('no %s in progress') % task, hint=hint)
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now