##// END OF EJS Templates
serve: add chdir command for --daemon-postexec...
Jun Wu -
r28452:0bb01c87 default
parent child Browse files
Show More
@@ -1,3476 +1,3478 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 cStringIO
10 import cStringIO
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14 import sys
14 import sys
15 import tempfile
15 import tempfile
16
16
17 from .i18n import _
17 from .i18n import _
18 from .node import (
18 from .node import (
19 bin,
19 bin,
20 hex,
20 hex,
21 nullid,
21 nullid,
22 nullrev,
22 nullrev,
23 short,
23 short,
24 )
24 )
25
25
26 from . import (
26 from . import (
27 bookmarks,
27 bookmarks,
28 changelog,
28 changelog,
29 copies,
29 copies,
30 crecord as crecordmod,
30 crecord as crecordmod,
31 encoding,
31 encoding,
32 error,
32 error,
33 formatter,
33 formatter,
34 graphmod,
34 graphmod,
35 lock as lockmod,
35 lock as lockmod,
36 match as matchmod,
36 match as matchmod,
37 obsolete,
37 obsolete,
38 patch,
38 patch,
39 pathutil,
39 pathutil,
40 phases,
40 phases,
41 repair,
41 repair,
42 revlog,
42 revlog,
43 revset,
43 revset,
44 scmutil,
44 scmutil,
45 templatekw,
45 templatekw,
46 templater,
46 templater,
47 util,
47 util,
48 )
48 )
49
49
50 def ishunk(x):
50 def ishunk(x):
51 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
51 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
52 return isinstance(x, hunkclasses)
52 return isinstance(x, hunkclasses)
53
53
54 def newandmodified(chunks, originalchunks):
54 def newandmodified(chunks, originalchunks):
55 newlyaddedandmodifiedfiles = set()
55 newlyaddedandmodifiedfiles = set()
56 for chunk in chunks:
56 for chunk in chunks:
57 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
57 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
58 originalchunks:
58 originalchunks:
59 newlyaddedandmodifiedfiles.add(chunk.header.filename())
59 newlyaddedandmodifiedfiles.add(chunk.header.filename())
60 return newlyaddedandmodifiedfiles
60 return newlyaddedandmodifiedfiles
61
61
62 def parsealiases(cmd):
62 def parsealiases(cmd):
63 return cmd.lstrip("^").split("|")
63 return cmd.lstrip("^").split("|")
64
64
65 def setupwrapcolorwrite(ui):
65 def setupwrapcolorwrite(ui):
66 # wrap ui.write so diff output can be labeled/colorized
66 # wrap ui.write so diff output can be labeled/colorized
67 def wrapwrite(orig, *args, **kw):
67 def wrapwrite(orig, *args, **kw):
68 label = kw.pop('label', '')
68 label = kw.pop('label', '')
69 for chunk, l in patch.difflabel(lambda: args):
69 for chunk, l in patch.difflabel(lambda: args):
70 orig(chunk, label=label + l)
70 orig(chunk, label=label + l)
71
71
72 oldwrite = ui.write
72 oldwrite = ui.write
73 def wrap(*args, **kwargs):
73 def wrap(*args, **kwargs):
74 return wrapwrite(oldwrite, *args, **kwargs)
74 return wrapwrite(oldwrite, *args, **kwargs)
75 setattr(ui, 'write', wrap)
75 setattr(ui, 'write', wrap)
76 return oldwrite
76 return oldwrite
77
77
78 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
78 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
79 if usecurses:
79 if usecurses:
80 if testfile:
80 if testfile:
81 recordfn = crecordmod.testdecorator(testfile,
81 recordfn = crecordmod.testdecorator(testfile,
82 crecordmod.testchunkselector)
82 crecordmod.testchunkselector)
83 else:
83 else:
84 recordfn = crecordmod.chunkselector
84 recordfn = crecordmod.chunkselector
85
85
86 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
86 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
87
87
88 else:
88 else:
89 return patch.filterpatch(ui, originalhunks, operation)
89 return patch.filterpatch(ui, originalhunks, operation)
90
90
91 def recordfilter(ui, originalhunks, operation=None):
91 def recordfilter(ui, originalhunks, operation=None):
92 """ Prompts the user to filter the originalhunks and return a list of
92 """ Prompts the user to filter the originalhunks and return a list of
93 selected hunks.
93 selected hunks.
94 *operation* is used for ui purposes to indicate the user
94 *operation* is used for ui purposes to indicate the user
95 what kind of filtering they are doing: reverting, committing, shelving, etc.
95 what kind of filtering they are doing: reverting, committing, shelving, etc.
96 *operation* has to be a translated string.
96 *operation* has to be a translated string.
97 """
97 """
98 usecurses = crecordmod.checkcurses(ui)
98 usecurses = crecordmod.checkcurses(ui)
99 testfile = ui.config('experimental', 'crecordtest', None)
99 testfile = ui.config('experimental', 'crecordtest', None)
100 oldwrite = setupwrapcolorwrite(ui)
100 oldwrite = setupwrapcolorwrite(ui)
101 try:
101 try:
102 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
102 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
103 testfile, operation)
103 testfile, operation)
104 finally:
104 finally:
105 ui.write = oldwrite
105 ui.write = oldwrite
106 return newchunks, newopts
106 return newchunks, newopts
107
107
108 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
108 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
109 filterfn, *pats, **opts):
109 filterfn, *pats, **opts):
110 from . import merge as mergemod
110 from . import merge as mergemod
111 if not ui.interactive():
111 if not ui.interactive():
112 if cmdsuggest:
112 if cmdsuggest:
113 msg = _('running non-interactively, use %s instead') % cmdsuggest
113 msg = _('running non-interactively, use %s instead') % cmdsuggest
114 else:
114 else:
115 msg = _('running non-interactively')
115 msg = _('running non-interactively')
116 raise error.Abort(msg)
116 raise error.Abort(msg)
117
117
118 # make sure username is set before going interactive
118 # make sure username is set before going interactive
119 if not opts.get('user'):
119 if not opts.get('user'):
120 ui.username() # raise exception, username not provided
120 ui.username() # raise exception, username not provided
121
121
122 def recordfunc(ui, repo, message, match, opts):
122 def recordfunc(ui, repo, message, match, opts):
123 """This is generic record driver.
123 """This is generic record driver.
124
124
125 Its job is to interactively filter local changes, and
125 Its job is to interactively filter local changes, and
126 accordingly prepare working directory into a state in which the
126 accordingly prepare working directory into a state in which the
127 job can be delegated to a non-interactive commit command such as
127 job can be delegated to a non-interactive commit command such as
128 'commit' or 'qrefresh'.
128 'commit' or 'qrefresh'.
129
129
130 After the actual job is done by non-interactive command, the
130 After the actual job is done by non-interactive command, the
131 working directory is restored to its original state.
131 working directory is restored to its original state.
132
132
133 In the end we'll record interesting changes, and everything else
133 In the end we'll record interesting changes, and everything else
134 will be left in place, so the user can continue working.
134 will be left in place, so the user can continue working.
135 """
135 """
136
136
137 checkunfinished(repo, commit=True)
137 checkunfinished(repo, commit=True)
138 merge = len(repo[None].parents()) > 1
138 merge = len(repo[None].parents()) > 1
139 if merge:
139 if merge:
140 raise error.Abort(_('cannot partially commit a merge '
140 raise error.Abort(_('cannot partially commit a merge '
141 '(use "hg commit" instead)'))
141 '(use "hg commit" instead)'))
142
142
143 status = repo.status(match=match)
143 status = repo.status(match=match)
144 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
144 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
145 diffopts.nodates = True
145 diffopts.nodates = True
146 diffopts.git = True
146 diffopts.git = True
147 diffopts.showfunc = True
147 diffopts.showfunc = True
148 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
148 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
149 originalchunks = patch.parsepatch(originaldiff)
149 originalchunks = patch.parsepatch(originaldiff)
150
150
151 # 1. filter patch, so we have intending-to apply subset of it
151 # 1. filter patch, so we have intending-to apply subset of it
152 try:
152 try:
153 chunks, newopts = filterfn(ui, originalchunks)
153 chunks, newopts = filterfn(ui, originalchunks)
154 except patch.PatchError as err:
154 except patch.PatchError as err:
155 raise error.Abort(_('error parsing patch: %s') % err)
155 raise error.Abort(_('error parsing patch: %s') % err)
156 opts.update(newopts)
156 opts.update(newopts)
157
157
158 # We need to keep a backup of files that have been newly added and
158 # We need to keep a backup of files that have been newly added and
159 # modified during the recording process because there is a previous
159 # modified during the recording process because there is a previous
160 # version without the edit in the workdir
160 # version without the edit in the workdir
161 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
161 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
162 contenders = set()
162 contenders = set()
163 for h in chunks:
163 for h in chunks:
164 try:
164 try:
165 contenders.update(set(h.files()))
165 contenders.update(set(h.files()))
166 except AttributeError:
166 except AttributeError:
167 pass
167 pass
168
168
169 changed = status.modified + status.added + status.removed
169 changed = status.modified + status.added + status.removed
170 newfiles = [f for f in changed if f in contenders]
170 newfiles = [f for f in changed if f in contenders]
171 if not newfiles:
171 if not newfiles:
172 ui.status(_('no changes to record\n'))
172 ui.status(_('no changes to record\n'))
173 return 0
173 return 0
174
174
175 modified = set(status.modified)
175 modified = set(status.modified)
176
176
177 # 2. backup changed files, so we can restore them in the end
177 # 2. backup changed files, so we can restore them in the end
178
178
179 if backupall:
179 if backupall:
180 tobackup = changed
180 tobackup = changed
181 else:
181 else:
182 tobackup = [f for f in newfiles if f in modified or f in \
182 tobackup = [f for f in newfiles if f in modified or f in \
183 newlyaddedandmodifiedfiles]
183 newlyaddedandmodifiedfiles]
184 backups = {}
184 backups = {}
185 if tobackup:
185 if tobackup:
186 backupdir = repo.join('record-backups')
186 backupdir = repo.join('record-backups')
187 try:
187 try:
188 os.mkdir(backupdir)
188 os.mkdir(backupdir)
189 except OSError as err:
189 except OSError as err:
190 if err.errno != errno.EEXIST:
190 if err.errno != errno.EEXIST:
191 raise
191 raise
192 try:
192 try:
193 # backup continues
193 # backup continues
194 for f in tobackup:
194 for f in tobackup:
195 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
195 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
196 dir=backupdir)
196 dir=backupdir)
197 os.close(fd)
197 os.close(fd)
198 ui.debug('backup %r as %r\n' % (f, tmpname))
198 ui.debug('backup %r as %r\n' % (f, tmpname))
199 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
199 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
200 backups[f] = tmpname
200 backups[f] = tmpname
201
201
202 fp = cStringIO.StringIO()
202 fp = cStringIO.StringIO()
203 for c in chunks:
203 for c in chunks:
204 fname = c.filename()
204 fname = c.filename()
205 if fname in backups:
205 if fname in backups:
206 c.write(fp)
206 c.write(fp)
207 dopatch = fp.tell()
207 dopatch = fp.tell()
208 fp.seek(0)
208 fp.seek(0)
209
209
210 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
210 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
211 # 3a. apply filtered patch to clean repo (clean)
211 # 3a. apply filtered patch to clean repo (clean)
212 if backups:
212 if backups:
213 # Equivalent to hg.revert
213 # Equivalent to hg.revert
214 m = scmutil.matchfiles(repo, backups.keys())
214 m = scmutil.matchfiles(repo, backups.keys())
215 mergemod.update(repo, repo.dirstate.p1(),
215 mergemod.update(repo, repo.dirstate.p1(),
216 False, True, matcher=m)
216 False, True, matcher=m)
217
217
218 # 3b. (apply)
218 # 3b. (apply)
219 if dopatch:
219 if dopatch:
220 try:
220 try:
221 ui.debug('applying patch\n')
221 ui.debug('applying patch\n')
222 ui.debug(fp.getvalue())
222 ui.debug(fp.getvalue())
223 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
223 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
224 except patch.PatchError as err:
224 except patch.PatchError as err:
225 raise error.Abort(str(err))
225 raise error.Abort(str(err))
226 del fp
226 del fp
227
227
228 # 4. We prepared working directory according to filtered
228 # 4. We prepared working directory according to filtered
229 # patch. Now is the time to delegate the job to
229 # patch. Now is the time to delegate the job to
230 # commit/qrefresh or the like!
230 # commit/qrefresh or the like!
231
231
232 # Make all of the pathnames absolute.
232 # Make all of the pathnames absolute.
233 newfiles = [repo.wjoin(nf) for nf in newfiles]
233 newfiles = [repo.wjoin(nf) for nf in newfiles]
234 return commitfunc(ui, repo, *newfiles, **opts)
234 return commitfunc(ui, repo, *newfiles, **opts)
235 finally:
235 finally:
236 # 5. finally restore backed-up files
236 # 5. finally restore backed-up files
237 try:
237 try:
238 dirstate = repo.dirstate
238 dirstate = repo.dirstate
239 for realname, tmpname in backups.iteritems():
239 for realname, tmpname in backups.iteritems():
240 ui.debug('restoring %r to %r\n' % (tmpname, realname))
240 ui.debug('restoring %r to %r\n' % (tmpname, realname))
241
241
242 if dirstate[realname] == 'n':
242 if dirstate[realname] == 'n':
243 # without normallookup, restoring timestamp
243 # without normallookup, restoring timestamp
244 # may cause partially committed files
244 # may cause partially committed files
245 # to be treated as unmodified
245 # to be treated as unmodified
246 dirstate.normallookup(realname)
246 dirstate.normallookup(realname)
247
247
248 # copystat=True here and above are a hack to trick any
248 # copystat=True here and above are a hack to trick any
249 # editors that have f open that we haven't modified them.
249 # editors that have f open that we haven't modified them.
250 #
250 #
251 # Also note that this racy as an editor could notice the
251 # Also note that this racy as an editor could notice the
252 # file's mtime before we've finished writing it.
252 # file's mtime before we've finished writing it.
253 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
253 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
254 os.unlink(tmpname)
254 os.unlink(tmpname)
255 if tobackup:
255 if tobackup:
256 os.rmdir(backupdir)
256 os.rmdir(backupdir)
257 except OSError:
257 except OSError:
258 pass
258 pass
259
259
260 def recordinwlock(ui, repo, message, match, opts):
260 def recordinwlock(ui, repo, message, match, opts):
261 with repo.wlock():
261 with repo.wlock():
262 return recordfunc(ui, repo, message, match, opts)
262 return recordfunc(ui, repo, message, match, opts)
263
263
264 return commit(ui, repo, recordinwlock, pats, opts)
264 return commit(ui, repo, recordinwlock, pats, opts)
265
265
266 def findpossible(cmd, table, strict=False):
266 def findpossible(cmd, table, strict=False):
267 """
267 """
268 Return cmd -> (aliases, command table entry)
268 Return cmd -> (aliases, command table entry)
269 for each matching command.
269 for each matching command.
270 Return debug commands (or their aliases) only if no normal command matches.
270 Return debug commands (or their aliases) only if no normal command matches.
271 """
271 """
272 choice = {}
272 choice = {}
273 debugchoice = {}
273 debugchoice = {}
274
274
275 if cmd in table:
275 if cmd in table:
276 # short-circuit exact matches, "log" alias beats "^log|history"
276 # short-circuit exact matches, "log" alias beats "^log|history"
277 keys = [cmd]
277 keys = [cmd]
278 else:
278 else:
279 keys = table.keys()
279 keys = table.keys()
280
280
281 allcmds = []
281 allcmds = []
282 for e in keys:
282 for e in keys:
283 aliases = parsealiases(e)
283 aliases = parsealiases(e)
284 allcmds.extend(aliases)
284 allcmds.extend(aliases)
285 found = None
285 found = None
286 if cmd in aliases:
286 if cmd in aliases:
287 found = cmd
287 found = cmd
288 elif not strict:
288 elif not strict:
289 for a in aliases:
289 for a in aliases:
290 if a.startswith(cmd):
290 if a.startswith(cmd):
291 found = a
291 found = a
292 break
292 break
293 if found is not None:
293 if found is not None:
294 if aliases[0].startswith("debug") or found.startswith("debug"):
294 if aliases[0].startswith("debug") or found.startswith("debug"):
295 debugchoice[found] = (aliases, table[e])
295 debugchoice[found] = (aliases, table[e])
296 else:
296 else:
297 choice[found] = (aliases, table[e])
297 choice[found] = (aliases, table[e])
298
298
299 if not choice and debugchoice:
299 if not choice and debugchoice:
300 choice = debugchoice
300 choice = debugchoice
301
301
302 return choice, allcmds
302 return choice, allcmds
303
303
304 def findcmd(cmd, table, strict=True):
304 def findcmd(cmd, table, strict=True):
305 """Return (aliases, command table entry) for command string."""
305 """Return (aliases, command table entry) for command string."""
306 choice, allcmds = findpossible(cmd, table, strict)
306 choice, allcmds = findpossible(cmd, table, strict)
307
307
308 if cmd in choice:
308 if cmd in choice:
309 return choice[cmd]
309 return choice[cmd]
310
310
311 if len(choice) > 1:
311 if len(choice) > 1:
312 clist = choice.keys()
312 clist = choice.keys()
313 clist.sort()
313 clist.sort()
314 raise error.AmbiguousCommand(cmd, clist)
314 raise error.AmbiguousCommand(cmd, clist)
315
315
316 if choice:
316 if choice:
317 return choice.values()[0]
317 return choice.values()[0]
318
318
319 raise error.UnknownCommand(cmd, allcmds)
319 raise error.UnknownCommand(cmd, allcmds)
320
320
321 def findrepo(p):
321 def findrepo(p):
322 while not os.path.isdir(os.path.join(p, ".hg")):
322 while not os.path.isdir(os.path.join(p, ".hg")):
323 oldp, p = p, os.path.dirname(p)
323 oldp, p = p, os.path.dirname(p)
324 if p == oldp:
324 if p == oldp:
325 return None
325 return None
326
326
327 return p
327 return p
328
328
329 def bailifchanged(repo, merge=True):
329 def bailifchanged(repo, merge=True):
330 if merge and repo.dirstate.p2() != nullid:
330 if merge and repo.dirstate.p2() != nullid:
331 raise error.Abort(_('outstanding uncommitted merge'))
331 raise error.Abort(_('outstanding uncommitted merge'))
332 modified, added, removed, deleted = repo.status()[:4]
332 modified, added, removed, deleted = repo.status()[:4]
333 if modified or added or removed or deleted:
333 if modified or added or removed or deleted:
334 raise error.Abort(_('uncommitted changes'))
334 raise error.Abort(_('uncommitted changes'))
335 ctx = repo[None]
335 ctx = repo[None]
336 for s in sorted(ctx.substate):
336 for s in sorted(ctx.substate):
337 ctx.sub(s).bailifchanged()
337 ctx.sub(s).bailifchanged()
338
338
339 def logmessage(ui, opts):
339 def logmessage(ui, opts):
340 """ get the log message according to -m and -l option """
340 """ get the log message according to -m and -l option """
341 message = opts.get('message')
341 message = opts.get('message')
342 logfile = opts.get('logfile')
342 logfile = opts.get('logfile')
343
343
344 if message and logfile:
344 if message and logfile:
345 raise error.Abort(_('options --message and --logfile are mutually '
345 raise error.Abort(_('options --message and --logfile are mutually '
346 'exclusive'))
346 'exclusive'))
347 if not message and logfile:
347 if not message and logfile:
348 try:
348 try:
349 if logfile == '-':
349 if logfile == '-':
350 message = ui.fin.read()
350 message = ui.fin.read()
351 else:
351 else:
352 message = '\n'.join(util.readfile(logfile).splitlines())
352 message = '\n'.join(util.readfile(logfile).splitlines())
353 except IOError as inst:
353 except IOError as inst:
354 raise error.Abort(_("can't read commit message '%s': %s") %
354 raise error.Abort(_("can't read commit message '%s': %s") %
355 (logfile, inst.strerror))
355 (logfile, inst.strerror))
356 return message
356 return message
357
357
358 def mergeeditform(ctxorbool, baseformname):
358 def mergeeditform(ctxorbool, baseformname):
359 """return appropriate editform name (referencing a committemplate)
359 """return appropriate editform name (referencing a committemplate)
360
360
361 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
361 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
362 merging is committed.
362 merging is committed.
363
363
364 This returns baseformname with '.merge' appended if it is a merge,
364 This returns baseformname with '.merge' appended if it is a merge,
365 otherwise '.normal' is appended.
365 otherwise '.normal' is appended.
366 """
366 """
367 if isinstance(ctxorbool, bool):
367 if isinstance(ctxorbool, bool):
368 if ctxorbool:
368 if ctxorbool:
369 return baseformname + ".merge"
369 return baseformname + ".merge"
370 elif 1 < len(ctxorbool.parents()):
370 elif 1 < len(ctxorbool.parents()):
371 return baseformname + ".merge"
371 return baseformname + ".merge"
372
372
373 return baseformname + ".normal"
373 return baseformname + ".normal"
374
374
375 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
375 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
376 editform='', **opts):
376 editform='', **opts):
377 """get appropriate commit message editor according to '--edit' option
377 """get appropriate commit message editor according to '--edit' option
378
378
379 'finishdesc' is a function to be called with edited commit message
379 'finishdesc' is a function to be called with edited commit message
380 (= 'description' of the new changeset) just after editing, but
380 (= 'description' of the new changeset) just after editing, but
381 before checking empty-ness. It should return actual text to be
381 before checking empty-ness. It should return actual text to be
382 stored into history. This allows to change description before
382 stored into history. This allows to change description before
383 storing.
383 storing.
384
384
385 'extramsg' is a extra message to be shown in the editor instead of
385 'extramsg' is a extra message to be shown in the editor instead of
386 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
386 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
387 is automatically added.
387 is automatically added.
388
388
389 'editform' is a dot-separated list of names, to distinguish
389 'editform' is a dot-separated list of names, to distinguish
390 the purpose of commit text editing.
390 the purpose of commit text editing.
391
391
392 'getcommiteditor' returns 'commitforceeditor' regardless of
392 'getcommiteditor' returns 'commitforceeditor' regardless of
393 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
393 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
394 they are specific for usage in MQ.
394 they are specific for usage in MQ.
395 """
395 """
396 if edit or finishdesc or extramsg:
396 if edit or finishdesc or extramsg:
397 return lambda r, c, s: commitforceeditor(r, c, s,
397 return lambda r, c, s: commitforceeditor(r, c, s,
398 finishdesc=finishdesc,
398 finishdesc=finishdesc,
399 extramsg=extramsg,
399 extramsg=extramsg,
400 editform=editform)
400 editform=editform)
401 elif editform:
401 elif editform:
402 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
402 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
403 else:
403 else:
404 return commiteditor
404 return commiteditor
405
405
406 def loglimit(opts):
406 def loglimit(opts):
407 """get the log limit according to option -l/--limit"""
407 """get the log limit according to option -l/--limit"""
408 limit = opts.get('limit')
408 limit = opts.get('limit')
409 if limit:
409 if limit:
410 try:
410 try:
411 limit = int(limit)
411 limit = int(limit)
412 except ValueError:
412 except ValueError:
413 raise error.Abort(_('limit must be a positive integer'))
413 raise error.Abort(_('limit must be a positive integer'))
414 if limit <= 0:
414 if limit <= 0:
415 raise error.Abort(_('limit must be positive'))
415 raise error.Abort(_('limit must be positive'))
416 else:
416 else:
417 limit = None
417 limit = None
418 return limit
418 return limit
419
419
420 def makefilename(repo, pat, node, desc=None,
420 def makefilename(repo, pat, node, desc=None,
421 total=None, seqno=None, revwidth=None, pathname=None):
421 total=None, seqno=None, revwidth=None, pathname=None):
422 node_expander = {
422 node_expander = {
423 'H': lambda: hex(node),
423 'H': lambda: hex(node),
424 'R': lambda: str(repo.changelog.rev(node)),
424 'R': lambda: str(repo.changelog.rev(node)),
425 'h': lambda: short(node),
425 'h': lambda: short(node),
426 'm': lambda: re.sub('[^\w]', '_', str(desc))
426 'm': lambda: re.sub('[^\w]', '_', str(desc))
427 }
427 }
428 expander = {
428 expander = {
429 '%': lambda: '%',
429 '%': lambda: '%',
430 'b': lambda: os.path.basename(repo.root),
430 'b': lambda: os.path.basename(repo.root),
431 }
431 }
432
432
433 try:
433 try:
434 if node:
434 if node:
435 expander.update(node_expander)
435 expander.update(node_expander)
436 if node:
436 if node:
437 expander['r'] = (lambda:
437 expander['r'] = (lambda:
438 str(repo.changelog.rev(node)).zfill(revwidth or 0))
438 str(repo.changelog.rev(node)).zfill(revwidth or 0))
439 if total is not None:
439 if total is not None:
440 expander['N'] = lambda: str(total)
440 expander['N'] = lambda: str(total)
441 if seqno is not None:
441 if seqno is not None:
442 expander['n'] = lambda: str(seqno)
442 expander['n'] = lambda: str(seqno)
443 if total is not None and seqno is not None:
443 if total is not None and seqno is not None:
444 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
444 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
445 if pathname is not None:
445 if pathname is not None:
446 expander['s'] = lambda: os.path.basename(pathname)
446 expander['s'] = lambda: os.path.basename(pathname)
447 expander['d'] = lambda: os.path.dirname(pathname) or '.'
447 expander['d'] = lambda: os.path.dirname(pathname) or '.'
448 expander['p'] = lambda: pathname
448 expander['p'] = lambda: pathname
449
449
450 newname = []
450 newname = []
451 patlen = len(pat)
451 patlen = len(pat)
452 i = 0
452 i = 0
453 while i < patlen:
453 while i < patlen:
454 c = pat[i]
454 c = pat[i]
455 if c == '%':
455 if c == '%':
456 i += 1
456 i += 1
457 c = pat[i]
457 c = pat[i]
458 c = expander[c]()
458 c = expander[c]()
459 newname.append(c)
459 newname.append(c)
460 i += 1
460 i += 1
461 return ''.join(newname)
461 return ''.join(newname)
462 except KeyError as inst:
462 except KeyError as inst:
463 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
463 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
464 inst.args[0])
464 inst.args[0])
465
465
466 class _unclosablefile(object):
466 class _unclosablefile(object):
467 def __init__(self, fp):
467 def __init__(self, fp):
468 self._fp = fp
468 self._fp = fp
469
469
470 def close(self):
470 def close(self):
471 pass
471 pass
472
472
473 def __iter__(self):
473 def __iter__(self):
474 return iter(self._fp)
474 return iter(self._fp)
475
475
476 def __getattr__(self, attr):
476 def __getattr__(self, attr):
477 return getattr(self._fp, attr)
477 return getattr(self._fp, attr)
478
478
479 def makefileobj(repo, pat, node=None, desc=None, total=None,
479 def makefileobj(repo, pat, node=None, desc=None, total=None,
480 seqno=None, revwidth=None, mode='wb', modemap=None,
480 seqno=None, revwidth=None, mode='wb', modemap=None,
481 pathname=None):
481 pathname=None):
482
482
483 writable = mode not in ('r', 'rb')
483 writable = mode not in ('r', 'rb')
484
484
485 if not pat or pat == '-':
485 if not pat or pat == '-':
486 if writable:
486 if writable:
487 fp = repo.ui.fout
487 fp = repo.ui.fout
488 else:
488 else:
489 fp = repo.ui.fin
489 fp = repo.ui.fin
490 return _unclosablefile(fp)
490 return _unclosablefile(fp)
491 if util.safehasattr(pat, 'write') and writable:
491 if util.safehasattr(pat, 'write') and writable:
492 return pat
492 return pat
493 if util.safehasattr(pat, 'read') and 'r' in mode:
493 if util.safehasattr(pat, 'read') and 'r' in mode:
494 return pat
494 return pat
495 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
495 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
496 if modemap is not None:
496 if modemap is not None:
497 mode = modemap.get(fn, mode)
497 mode = modemap.get(fn, mode)
498 if mode == 'wb':
498 if mode == 'wb':
499 modemap[fn] = 'ab'
499 modemap[fn] = 'ab'
500 return open(fn, mode)
500 return open(fn, mode)
501
501
502 def openrevlog(repo, cmd, file_, opts):
502 def openrevlog(repo, cmd, file_, opts):
503 """opens the changelog, manifest, a filelog or a given revlog"""
503 """opens the changelog, manifest, a filelog or a given revlog"""
504 cl = opts['changelog']
504 cl = opts['changelog']
505 mf = opts['manifest']
505 mf = opts['manifest']
506 dir = opts['dir']
506 dir = opts['dir']
507 msg = None
507 msg = None
508 if cl and mf:
508 if cl and mf:
509 msg = _('cannot specify --changelog and --manifest at the same time')
509 msg = _('cannot specify --changelog and --manifest at the same time')
510 elif cl and dir:
510 elif cl and dir:
511 msg = _('cannot specify --changelog and --dir at the same time')
511 msg = _('cannot specify --changelog and --dir at the same time')
512 elif cl or mf:
512 elif cl or mf:
513 if file_:
513 if file_:
514 msg = _('cannot specify filename with --changelog or --manifest')
514 msg = _('cannot specify filename with --changelog or --manifest')
515 elif not repo:
515 elif not repo:
516 msg = _('cannot specify --changelog or --manifest or --dir '
516 msg = _('cannot specify --changelog or --manifest or --dir '
517 'without a repository')
517 'without a repository')
518 if msg:
518 if msg:
519 raise error.Abort(msg)
519 raise error.Abort(msg)
520
520
521 r = None
521 r = None
522 if repo:
522 if repo:
523 if cl:
523 if cl:
524 r = repo.unfiltered().changelog
524 r = repo.unfiltered().changelog
525 elif dir:
525 elif dir:
526 if 'treemanifest' not in repo.requirements:
526 if 'treemanifest' not in repo.requirements:
527 raise error.Abort(_("--dir can only be used on repos with "
527 raise error.Abort(_("--dir can only be used on repos with "
528 "treemanifest enabled"))
528 "treemanifest enabled"))
529 dirlog = repo.dirlog(file_)
529 dirlog = repo.dirlog(file_)
530 if len(dirlog):
530 if len(dirlog):
531 r = dirlog
531 r = dirlog
532 elif mf:
532 elif mf:
533 r = repo.manifest
533 r = repo.manifest
534 elif file_:
534 elif file_:
535 filelog = repo.file(file_)
535 filelog = repo.file(file_)
536 if len(filelog):
536 if len(filelog):
537 r = filelog
537 r = filelog
538 if not r:
538 if not r:
539 if not file_:
539 if not file_:
540 raise error.CommandError(cmd, _('invalid arguments'))
540 raise error.CommandError(cmd, _('invalid arguments'))
541 if not os.path.isfile(file_):
541 if not os.path.isfile(file_):
542 raise error.Abort(_("revlog '%s' not found") % file_)
542 raise error.Abort(_("revlog '%s' not found") % file_)
543 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
543 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
544 file_[:-2] + ".i")
544 file_[:-2] + ".i")
545 return r
545 return r
546
546
547 def copy(ui, repo, pats, opts, rename=False):
547 def copy(ui, repo, pats, opts, rename=False):
548 # called with the repo lock held
548 # called with the repo lock held
549 #
549 #
550 # hgsep => pathname that uses "/" to separate directories
550 # hgsep => pathname that uses "/" to separate directories
551 # ossep => pathname that uses os.sep to separate directories
551 # ossep => pathname that uses os.sep to separate directories
552 cwd = repo.getcwd()
552 cwd = repo.getcwd()
553 targets = {}
553 targets = {}
554 after = opts.get("after")
554 after = opts.get("after")
555 dryrun = opts.get("dry_run")
555 dryrun = opts.get("dry_run")
556 wctx = repo[None]
556 wctx = repo[None]
557
557
558 def walkpat(pat):
558 def walkpat(pat):
559 srcs = []
559 srcs = []
560 if after:
560 if after:
561 badstates = '?'
561 badstates = '?'
562 else:
562 else:
563 badstates = '?r'
563 badstates = '?r'
564 m = scmutil.match(repo[None], [pat], opts, globbed=True)
564 m = scmutil.match(repo[None], [pat], opts, globbed=True)
565 for abs in repo.walk(m):
565 for abs in repo.walk(m):
566 state = repo.dirstate[abs]
566 state = repo.dirstate[abs]
567 rel = m.rel(abs)
567 rel = m.rel(abs)
568 exact = m.exact(abs)
568 exact = m.exact(abs)
569 if state in badstates:
569 if state in badstates:
570 if exact and state == '?':
570 if exact and state == '?':
571 ui.warn(_('%s: not copying - file is not managed\n') % rel)
571 ui.warn(_('%s: not copying - file is not managed\n') % rel)
572 if exact and state == 'r':
572 if exact and state == 'r':
573 ui.warn(_('%s: not copying - file has been marked for'
573 ui.warn(_('%s: not copying - file has been marked for'
574 ' remove\n') % rel)
574 ' remove\n') % rel)
575 continue
575 continue
576 # abs: hgsep
576 # abs: hgsep
577 # rel: ossep
577 # rel: ossep
578 srcs.append((abs, rel, exact))
578 srcs.append((abs, rel, exact))
579 return srcs
579 return srcs
580
580
581 # abssrc: hgsep
581 # abssrc: hgsep
582 # relsrc: ossep
582 # relsrc: ossep
583 # otarget: ossep
583 # otarget: ossep
584 def copyfile(abssrc, relsrc, otarget, exact):
584 def copyfile(abssrc, relsrc, otarget, exact):
585 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
585 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
586 if '/' in abstarget:
586 if '/' in abstarget:
587 # We cannot normalize abstarget itself, this would prevent
587 # We cannot normalize abstarget itself, this would prevent
588 # case only renames, like a => A.
588 # case only renames, like a => A.
589 abspath, absname = abstarget.rsplit('/', 1)
589 abspath, absname = abstarget.rsplit('/', 1)
590 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
590 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
591 reltarget = repo.pathto(abstarget, cwd)
591 reltarget = repo.pathto(abstarget, cwd)
592 target = repo.wjoin(abstarget)
592 target = repo.wjoin(abstarget)
593 src = repo.wjoin(abssrc)
593 src = repo.wjoin(abssrc)
594 state = repo.dirstate[abstarget]
594 state = repo.dirstate[abstarget]
595
595
596 scmutil.checkportable(ui, abstarget)
596 scmutil.checkportable(ui, abstarget)
597
597
598 # check for collisions
598 # check for collisions
599 prevsrc = targets.get(abstarget)
599 prevsrc = targets.get(abstarget)
600 if prevsrc is not None:
600 if prevsrc is not None:
601 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
601 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
602 (reltarget, repo.pathto(abssrc, cwd),
602 (reltarget, repo.pathto(abssrc, cwd),
603 repo.pathto(prevsrc, cwd)))
603 repo.pathto(prevsrc, cwd)))
604 return
604 return
605
605
606 # check for overwrites
606 # check for overwrites
607 exists = os.path.lexists(target)
607 exists = os.path.lexists(target)
608 samefile = False
608 samefile = False
609 if exists and abssrc != abstarget:
609 if exists and abssrc != abstarget:
610 if (repo.dirstate.normalize(abssrc) ==
610 if (repo.dirstate.normalize(abssrc) ==
611 repo.dirstate.normalize(abstarget)):
611 repo.dirstate.normalize(abstarget)):
612 if not rename:
612 if not rename:
613 ui.warn(_("%s: can't copy - same file\n") % reltarget)
613 ui.warn(_("%s: can't copy - same file\n") % reltarget)
614 return
614 return
615 exists = False
615 exists = False
616 samefile = True
616 samefile = True
617
617
618 if not after and exists or after and state in 'mn':
618 if not after and exists or after and state in 'mn':
619 if not opts['force']:
619 if not opts['force']:
620 ui.warn(_('%s: not overwriting - file exists\n') %
620 ui.warn(_('%s: not overwriting - file exists\n') %
621 reltarget)
621 reltarget)
622 return
622 return
623
623
624 if after:
624 if after:
625 if not exists:
625 if not exists:
626 if rename:
626 if rename:
627 ui.warn(_('%s: not recording move - %s does not exist\n') %
627 ui.warn(_('%s: not recording move - %s does not exist\n') %
628 (relsrc, reltarget))
628 (relsrc, reltarget))
629 else:
629 else:
630 ui.warn(_('%s: not recording copy - %s does not exist\n') %
630 ui.warn(_('%s: not recording copy - %s does not exist\n') %
631 (relsrc, reltarget))
631 (relsrc, reltarget))
632 return
632 return
633 elif not dryrun:
633 elif not dryrun:
634 try:
634 try:
635 if exists:
635 if exists:
636 os.unlink(target)
636 os.unlink(target)
637 targetdir = os.path.dirname(target) or '.'
637 targetdir = os.path.dirname(target) or '.'
638 if not os.path.isdir(targetdir):
638 if not os.path.isdir(targetdir):
639 os.makedirs(targetdir)
639 os.makedirs(targetdir)
640 if samefile:
640 if samefile:
641 tmp = target + "~hgrename"
641 tmp = target + "~hgrename"
642 os.rename(src, tmp)
642 os.rename(src, tmp)
643 os.rename(tmp, target)
643 os.rename(tmp, target)
644 else:
644 else:
645 util.copyfile(src, target)
645 util.copyfile(src, target)
646 srcexists = True
646 srcexists = True
647 except IOError as inst:
647 except IOError as inst:
648 if inst.errno == errno.ENOENT:
648 if inst.errno == errno.ENOENT:
649 ui.warn(_('%s: deleted in working directory\n') % relsrc)
649 ui.warn(_('%s: deleted in working directory\n') % relsrc)
650 srcexists = False
650 srcexists = False
651 else:
651 else:
652 ui.warn(_('%s: cannot copy - %s\n') %
652 ui.warn(_('%s: cannot copy - %s\n') %
653 (relsrc, inst.strerror))
653 (relsrc, inst.strerror))
654 return True # report a failure
654 return True # report a failure
655
655
656 if ui.verbose or not exact:
656 if ui.verbose or not exact:
657 if rename:
657 if rename:
658 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
658 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
659 else:
659 else:
660 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
660 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
661
661
662 targets[abstarget] = abssrc
662 targets[abstarget] = abssrc
663
663
664 # fix up dirstate
664 # fix up dirstate
665 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
665 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
666 dryrun=dryrun, cwd=cwd)
666 dryrun=dryrun, cwd=cwd)
667 if rename and not dryrun:
667 if rename and not dryrun:
668 if not after and srcexists and not samefile:
668 if not after and srcexists and not samefile:
669 util.unlinkpath(repo.wjoin(abssrc))
669 util.unlinkpath(repo.wjoin(abssrc))
670 wctx.forget([abssrc])
670 wctx.forget([abssrc])
671
671
672 # pat: ossep
672 # pat: ossep
673 # dest ossep
673 # dest ossep
674 # srcs: list of (hgsep, hgsep, ossep, bool)
674 # srcs: list of (hgsep, hgsep, ossep, bool)
675 # return: function that takes hgsep and returns ossep
675 # return: function that takes hgsep and returns ossep
676 def targetpathfn(pat, dest, srcs):
676 def targetpathfn(pat, dest, srcs):
677 if os.path.isdir(pat):
677 if os.path.isdir(pat):
678 abspfx = pathutil.canonpath(repo.root, cwd, pat)
678 abspfx = pathutil.canonpath(repo.root, cwd, pat)
679 abspfx = util.localpath(abspfx)
679 abspfx = util.localpath(abspfx)
680 if destdirexists:
680 if destdirexists:
681 striplen = len(os.path.split(abspfx)[0])
681 striplen = len(os.path.split(abspfx)[0])
682 else:
682 else:
683 striplen = len(abspfx)
683 striplen = len(abspfx)
684 if striplen:
684 if striplen:
685 striplen += len(os.sep)
685 striplen += len(os.sep)
686 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
686 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
687 elif destdirexists:
687 elif destdirexists:
688 res = lambda p: os.path.join(dest,
688 res = lambda p: os.path.join(dest,
689 os.path.basename(util.localpath(p)))
689 os.path.basename(util.localpath(p)))
690 else:
690 else:
691 res = lambda p: dest
691 res = lambda p: dest
692 return res
692 return res
693
693
694 # pat: ossep
694 # pat: ossep
695 # dest ossep
695 # dest ossep
696 # srcs: list of (hgsep, hgsep, ossep, bool)
696 # srcs: list of (hgsep, hgsep, ossep, bool)
697 # return: function that takes hgsep and returns ossep
697 # return: function that takes hgsep and returns ossep
698 def targetpathafterfn(pat, dest, srcs):
698 def targetpathafterfn(pat, dest, srcs):
699 if matchmod.patkind(pat):
699 if matchmod.patkind(pat):
700 # a mercurial pattern
700 # a mercurial pattern
701 res = lambda p: os.path.join(dest,
701 res = lambda p: os.path.join(dest,
702 os.path.basename(util.localpath(p)))
702 os.path.basename(util.localpath(p)))
703 else:
703 else:
704 abspfx = pathutil.canonpath(repo.root, cwd, pat)
704 abspfx = pathutil.canonpath(repo.root, cwd, pat)
705 if len(abspfx) < len(srcs[0][0]):
705 if len(abspfx) < len(srcs[0][0]):
706 # A directory. Either the target path contains the last
706 # A directory. Either the target path contains the last
707 # component of the source path or it does not.
707 # component of the source path or it does not.
708 def evalpath(striplen):
708 def evalpath(striplen):
709 score = 0
709 score = 0
710 for s in srcs:
710 for s in srcs:
711 t = os.path.join(dest, util.localpath(s[0])[striplen:])
711 t = os.path.join(dest, util.localpath(s[0])[striplen:])
712 if os.path.lexists(t):
712 if os.path.lexists(t):
713 score += 1
713 score += 1
714 return score
714 return score
715
715
716 abspfx = util.localpath(abspfx)
716 abspfx = util.localpath(abspfx)
717 striplen = len(abspfx)
717 striplen = len(abspfx)
718 if striplen:
718 if striplen:
719 striplen += len(os.sep)
719 striplen += len(os.sep)
720 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
720 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
721 score = evalpath(striplen)
721 score = evalpath(striplen)
722 striplen1 = len(os.path.split(abspfx)[0])
722 striplen1 = len(os.path.split(abspfx)[0])
723 if striplen1:
723 if striplen1:
724 striplen1 += len(os.sep)
724 striplen1 += len(os.sep)
725 if evalpath(striplen1) > score:
725 if evalpath(striplen1) > score:
726 striplen = striplen1
726 striplen = striplen1
727 res = lambda p: os.path.join(dest,
727 res = lambda p: os.path.join(dest,
728 util.localpath(p)[striplen:])
728 util.localpath(p)[striplen:])
729 else:
729 else:
730 # a file
730 # a file
731 if destdirexists:
731 if destdirexists:
732 res = lambda p: os.path.join(dest,
732 res = lambda p: os.path.join(dest,
733 os.path.basename(util.localpath(p)))
733 os.path.basename(util.localpath(p)))
734 else:
734 else:
735 res = lambda p: dest
735 res = lambda p: dest
736 return res
736 return res
737
737
738 pats = scmutil.expandpats(pats)
738 pats = scmutil.expandpats(pats)
739 if not pats:
739 if not pats:
740 raise error.Abort(_('no source or destination specified'))
740 raise error.Abort(_('no source or destination specified'))
741 if len(pats) == 1:
741 if len(pats) == 1:
742 raise error.Abort(_('no destination specified'))
742 raise error.Abort(_('no destination specified'))
743 dest = pats.pop()
743 dest = pats.pop()
744 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
744 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
745 if not destdirexists:
745 if not destdirexists:
746 if len(pats) > 1 or matchmod.patkind(pats[0]):
746 if len(pats) > 1 or matchmod.patkind(pats[0]):
747 raise error.Abort(_('with multiple sources, destination must be an '
747 raise error.Abort(_('with multiple sources, destination must be an '
748 'existing directory'))
748 'existing directory'))
749 if util.endswithsep(dest):
749 if util.endswithsep(dest):
750 raise error.Abort(_('destination %s is not a directory') % dest)
750 raise error.Abort(_('destination %s is not a directory') % dest)
751
751
752 tfn = targetpathfn
752 tfn = targetpathfn
753 if after:
753 if after:
754 tfn = targetpathafterfn
754 tfn = targetpathafterfn
755 copylist = []
755 copylist = []
756 for pat in pats:
756 for pat in pats:
757 srcs = walkpat(pat)
757 srcs = walkpat(pat)
758 if not srcs:
758 if not srcs:
759 continue
759 continue
760 copylist.append((tfn(pat, dest, srcs), srcs))
760 copylist.append((tfn(pat, dest, srcs), srcs))
761 if not copylist:
761 if not copylist:
762 raise error.Abort(_('no files to copy'))
762 raise error.Abort(_('no files to copy'))
763
763
764 errors = 0
764 errors = 0
765 for targetpath, srcs in copylist:
765 for targetpath, srcs in copylist:
766 for abssrc, relsrc, exact in srcs:
766 for abssrc, relsrc, exact in srcs:
767 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
767 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
768 errors += 1
768 errors += 1
769
769
770 if errors:
770 if errors:
771 ui.warn(_('(consider using --after)\n'))
771 ui.warn(_('(consider using --after)\n'))
772
772
773 return errors != 0
773 return errors != 0
774
774
775 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
775 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
776 runargs=None, appendpid=False):
776 runargs=None, appendpid=False):
777 '''Run a command as a service.'''
777 '''Run a command as a service.'''
778
778
779 def writepid(pid):
779 def writepid(pid):
780 if opts['pid_file']:
780 if opts['pid_file']:
781 if appendpid:
781 if appendpid:
782 mode = 'a'
782 mode = 'a'
783 else:
783 else:
784 mode = 'w'
784 mode = 'w'
785 fp = open(opts['pid_file'], mode)
785 fp = open(opts['pid_file'], mode)
786 fp.write(str(pid) + '\n')
786 fp.write(str(pid) + '\n')
787 fp.close()
787 fp.close()
788
788
789 if opts['daemon'] and not opts['daemon_postexec']:
789 if opts['daemon'] and not opts['daemon_postexec']:
790 # Signal child process startup with file removal
790 # Signal child process startup with file removal
791 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
791 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
792 os.close(lockfd)
792 os.close(lockfd)
793 try:
793 try:
794 if not runargs:
794 if not runargs:
795 runargs = util.hgcmd() + sys.argv[1:]
795 runargs = util.hgcmd() + sys.argv[1:]
796 runargs.append('--daemon-postexec=unlink:%s' % lockpath)
796 runargs.append('--daemon-postexec=unlink:%s' % lockpath)
797 # Don't pass --cwd to the child process, because we've already
797 # Don't pass --cwd to the child process, because we've already
798 # changed directory.
798 # changed directory.
799 for i in xrange(1, len(runargs)):
799 for i in xrange(1, len(runargs)):
800 if runargs[i].startswith('--cwd='):
800 if runargs[i].startswith('--cwd='):
801 del runargs[i]
801 del runargs[i]
802 break
802 break
803 elif runargs[i].startswith('--cwd'):
803 elif runargs[i].startswith('--cwd'):
804 del runargs[i:i + 2]
804 del runargs[i:i + 2]
805 break
805 break
806 def condfn():
806 def condfn():
807 return not os.path.exists(lockpath)
807 return not os.path.exists(lockpath)
808 pid = util.rundetached(runargs, condfn)
808 pid = util.rundetached(runargs, condfn)
809 if pid < 0:
809 if pid < 0:
810 raise error.Abort(_('child process failed to start'))
810 raise error.Abort(_('child process failed to start'))
811 writepid(pid)
811 writepid(pid)
812 finally:
812 finally:
813 try:
813 try:
814 os.unlink(lockpath)
814 os.unlink(lockpath)
815 except OSError as e:
815 except OSError as e:
816 if e.errno != errno.ENOENT:
816 if e.errno != errno.ENOENT:
817 raise
817 raise
818 if parentfn:
818 if parentfn:
819 return parentfn(pid)
819 return parentfn(pid)
820 else:
820 else:
821 return
821 return
822
822
823 if initfn:
823 if initfn:
824 initfn()
824 initfn()
825
825
826 if not opts['daemon']:
826 if not opts['daemon']:
827 writepid(util.getpid())
827 writepid(util.getpid())
828
828
829 if opts['daemon_postexec']:
829 if opts['daemon_postexec']:
830 try:
830 try:
831 os.setsid()
831 os.setsid()
832 except AttributeError:
832 except AttributeError:
833 pass
833 pass
834 for inst in opts['daemon_postexec']:
834 for inst in opts['daemon_postexec']:
835 if inst.startswith('unlink:'):
835 if inst.startswith('unlink:'):
836 lockpath = inst[7:]
836 lockpath = inst[7:]
837 os.unlink(lockpath)
837 os.unlink(lockpath)
838 elif inst.startswith('chdir:'):
839 os.chdir(inst[6:])
838 elif inst != 'none':
840 elif inst != 'none':
839 raise error.Abort(_('invalid value for --daemon-postexec: %s')
841 raise error.Abort(_('invalid value for --daemon-postexec: %s')
840 % inst)
842 % inst)
841 util.hidewindow()
843 util.hidewindow()
842 sys.stdout.flush()
844 sys.stdout.flush()
843 sys.stderr.flush()
845 sys.stderr.flush()
844
846
845 nullfd = os.open(os.devnull, os.O_RDWR)
847 nullfd = os.open(os.devnull, os.O_RDWR)
846 logfilefd = nullfd
848 logfilefd = nullfd
847 if logfile:
849 if logfile:
848 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
850 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
849 os.dup2(nullfd, 0)
851 os.dup2(nullfd, 0)
850 os.dup2(logfilefd, 1)
852 os.dup2(logfilefd, 1)
851 os.dup2(logfilefd, 2)
853 os.dup2(logfilefd, 2)
852 if nullfd not in (0, 1, 2):
854 if nullfd not in (0, 1, 2):
853 os.close(nullfd)
855 os.close(nullfd)
854 if logfile and logfilefd not in (0, 1, 2):
856 if logfile and logfilefd not in (0, 1, 2):
855 os.close(logfilefd)
857 os.close(logfilefd)
856
858
857 if runfn:
859 if runfn:
858 return runfn()
860 return runfn()
859
861
860 ## facility to let extension process additional data into an import patch
862 ## facility to let extension process additional data into an import patch
861 # list of identifier to be executed in order
863 # list of identifier to be executed in order
862 extrapreimport = [] # run before commit
864 extrapreimport = [] # run before commit
863 extrapostimport = [] # run after commit
865 extrapostimport = [] # run after commit
864 # mapping from identifier to actual import function
866 # mapping from identifier to actual import function
865 #
867 #
866 # 'preimport' are run before the commit is made and are provided the following
868 # 'preimport' are run before the commit is made and are provided the following
867 # arguments:
869 # arguments:
868 # - repo: the localrepository instance,
870 # - repo: the localrepository instance,
869 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
871 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
870 # - extra: the future extra dictionary of the changeset, please mutate it,
872 # - extra: the future extra dictionary of the changeset, please mutate it,
871 # - opts: the import options.
873 # - opts: the import options.
872 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
874 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
873 # mutation of in memory commit and more. Feel free to rework the code to get
875 # mutation of in memory commit and more. Feel free to rework the code to get
874 # there.
876 # there.
875 extrapreimportmap = {}
877 extrapreimportmap = {}
876 # 'postimport' are run after the commit is made and are provided the following
878 # 'postimport' are run after the commit is made and are provided the following
877 # argument:
879 # argument:
878 # - ctx: the changectx created by import.
880 # - ctx: the changectx created by import.
879 extrapostimportmap = {}
881 extrapostimportmap = {}
880
882
881 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
883 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
882 """Utility function used by commands.import to import a single patch
884 """Utility function used by commands.import to import a single patch
883
885
884 This function is explicitly defined here to help the evolve extension to
886 This function is explicitly defined here to help the evolve extension to
885 wrap this part of the import logic.
887 wrap this part of the import logic.
886
888
887 The API is currently a bit ugly because it a simple code translation from
889 The API is currently a bit ugly because it a simple code translation from
888 the import command. Feel free to make it better.
890 the import command. Feel free to make it better.
889
891
890 :hunk: a patch (as a binary string)
892 :hunk: a patch (as a binary string)
891 :parents: nodes that will be parent of the created commit
893 :parents: nodes that will be parent of the created commit
892 :opts: the full dict of option passed to the import command
894 :opts: the full dict of option passed to the import command
893 :msgs: list to save commit message to.
895 :msgs: list to save commit message to.
894 (used in case we need to save it when failing)
896 (used in case we need to save it when failing)
895 :updatefunc: a function that update a repo to a given node
897 :updatefunc: a function that update a repo to a given node
896 updatefunc(<repo>, <node>)
898 updatefunc(<repo>, <node>)
897 """
899 """
898 # avoid cycle context -> subrepo -> cmdutil
900 # avoid cycle context -> subrepo -> cmdutil
899 from . import context
901 from . import context
900 extractdata = patch.extract(ui, hunk)
902 extractdata = patch.extract(ui, hunk)
901 tmpname = extractdata.get('filename')
903 tmpname = extractdata.get('filename')
902 message = extractdata.get('message')
904 message = extractdata.get('message')
903 user = opts.get('user') or extractdata.get('user')
905 user = opts.get('user') or extractdata.get('user')
904 date = opts.get('date') or extractdata.get('date')
906 date = opts.get('date') or extractdata.get('date')
905 branch = extractdata.get('branch')
907 branch = extractdata.get('branch')
906 nodeid = extractdata.get('nodeid')
908 nodeid = extractdata.get('nodeid')
907 p1 = extractdata.get('p1')
909 p1 = extractdata.get('p1')
908 p2 = extractdata.get('p2')
910 p2 = extractdata.get('p2')
909
911
910 nocommit = opts.get('no_commit')
912 nocommit = opts.get('no_commit')
911 importbranch = opts.get('import_branch')
913 importbranch = opts.get('import_branch')
912 update = not opts.get('bypass')
914 update = not opts.get('bypass')
913 strip = opts["strip"]
915 strip = opts["strip"]
914 prefix = opts["prefix"]
916 prefix = opts["prefix"]
915 sim = float(opts.get('similarity') or 0)
917 sim = float(opts.get('similarity') or 0)
916 if not tmpname:
918 if not tmpname:
917 return (None, None, False)
919 return (None, None, False)
918
920
919 rejects = False
921 rejects = False
920
922
921 try:
923 try:
922 cmdline_message = logmessage(ui, opts)
924 cmdline_message = logmessage(ui, opts)
923 if cmdline_message:
925 if cmdline_message:
924 # pickup the cmdline msg
926 # pickup the cmdline msg
925 message = cmdline_message
927 message = cmdline_message
926 elif message:
928 elif message:
927 # pickup the patch msg
929 # pickup the patch msg
928 message = message.strip()
930 message = message.strip()
929 else:
931 else:
930 # launch the editor
932 # launch the editor
931 message = None
933 message = None
932 ui.debug('message:\n%s\n' % message)
934 ui.debug('message:\n%s\n' % message)
933
935
934 if len(parents) == 1:
936 if len(parents) == 1:
935 parents.append(repo[nullid])
937 parents.append(repo[nullid])
936 if opts.get('exact'):
938 if opts.get('exact'):
937 if not nodeid or not p1:
939 if not nodeid or not p1:
938 raise error.Abort(_('not a Mercurial patch'))
940 raise error.Abort(_('not a Mercurial patch'))
939 p1 = repo[p1]
941 p1 = repo[p1]
940 p2 = repo[p2 or nullid]
942 p2 = repo[p2 or nullid]
941 elif p2:
943 elif p2:
942 try:
944 try:
943 p1 = repo[p1]
945 p1 = repo[p1]
944 p2 = repo[p2]
946 p2 = repo[p2]
945 # Without any options, consider p2 only if the
947 # Without any options, consider p2 only if the
946 # patch is being applied on top of the recorded
948 # patch is being applied on top of the recorded
947 # first parent.
949 # first parent.
948 if p1 != parents[0]:
950 if p1 != parents[0]:
949 p1 = parents[0]
951 p1 = parents[0]
950 p2 = repo[nullid]
952 p2 = repo[nullid]
951 except error.RepoError:
953 except error.RepoError:
952 p1, p2 = parents
954 p1, p2 = parents
953 if p2.node() == nullid:
955 if p2.node() == nullid:
954 ui.warn(_("warning: import the patch as a normal revision\n"
956 ui.warn(_("warning: import the patch as a normal revision\n"
955 "(use --exact to import the patch as a merge)\n"))
957 "(use --exact to import the patch as a merge)\n"))
956 else:
958 else:
957 p1, p2 = parents
959 p1, p2 = parents
958
960
959 n = None
961 n = None
960 if update:
962 if update:
961 if p1 != parents[0]:
963 if p1 != parents[0]:
962 updatefunc(repo, p1.node())
964 updatefunc(repo, p1.node())
963 if p2 != parents[1]:
965 if p2 != parents[1]:
964 repo.setparents(p1.node(), p2.node())
966 repo.setparents(p1.node(), p2.node())
965
967
966 if opts.get('exact') or importbranch:
968 if opts.get('exact') or importbranch:
967 repo.dirstate.setbranch(branch or 'default')
969 repo.dirstate.setbranch(branch or 'default')
968
970
969 partial = opts.get('partial', False)
971 partial = opts.get('partial', False)
970 files = set()
972 files = set()
971 try:
973 try:
972 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
974 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
973 files=files, eolmode=None, similarity=sim / 100.0)
975 files=files, eolmode=None, similarity=sim / 100.0)
974 except patch.PatchError as e:
976 except patch.PatchError as e:
975 if not partial:
977 if not partial:
976 raise error.Abort(str(e))
978 raise error.Abort(str(e))
977 if partial:
979 if partial:
978 rejects = True
980 rejects = True
979
981
980 files = list(files)
982 files = list(files)
981 if nocommit:
983 if nocommit:
982 if message:
984 if message:
983 msgs.append(message)
985 msgs.append(message)
984 else:
986 else:
985 if opts.get('exact') or p2:
987 if opts.get('exact') or p2:
986 # If you got here, you either use --force and know what
988 # If you got here, you either use --force and know what
987 # you are doing or used --exact or a merge patch while
989 # you are doing or used --exact or a merge patch while
988 # being updated to its first parent.
990 # being updated to its first parent.
989 m = None
991 m = None
990 else:
992 else:
991 m = scmutil.matchfiles(repo, files or [])
993 m = scmutil.matchfiles(repo, files or [])
992 editform = mergeeditform(repo[None], 'import.normal')
994 editform = mergeeditform(repo[None], 'import.normal')
993 if opts.get('exact'):
995 if opts.get('exact'):
994 editor = None
996 editor = None
995 else:
997 else:
996 editor = getcommiteditor(editform=editform, **opts)
998 editor = getcommiteditor(editform=editform, **opts)
997 allowemptyback = repo.ui.backupconfig('ui', 'allowemptycommit')
999 allowemptyback = repo.ui.backupconfig('ui', 'allowemptycommit')
998 extra = {}
1000 extra = {}
999 for idfunc in extrapreimport:
1001 for idfunc in extrapreimport:
1000 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1002 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1001 try:
1003 try:
1002 if partial:
1004 if partial:
1003 repo.ui.setconfig('ui', 'allowemptycommit', True)
1005 repo.ui.setconfig('ui', 'allowemptycommit', True)
1004 n = repo.commit(message, user,
1006 n = repo.commit(message, user,
1005 date, match=m,
1007 date, match=m,
1006 editor=editor, extra=extra)
1008 editor=editor, extra=extra)
1007 for idfunc in extrapostimport:
1009 for idfunc in extrapostimport:
1008 extrapostimportmap[idfunc](repo[n])
1010 extrapostimportmap[idfunc](repo[n])
1009 finally:
1011 finally:
1010 repo.ui.restoreconfig(allowemptyback)
1012 repo.ui.restoreconfig(allowemptyback)
1011 else:
1013 else:
1012 if opts.get('exact') or importbranch:
1014 if opts.get('exact') or importbranch:
1013 branch = branch or 'default'
1015 branch = branch or 'default'
1014 else:
1016 else:
1015 branch = p1.branch()
1017 branch = p1.branch()
1016 store = patch.filestore()
1018 store = patch.filestore()
1017 try:
1019 try:
1018 files = set()
1020 files = set()
1019 try:
1021 try:
1020 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1022 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1021 files, eolmode=None)
1023 files, eolmode=None)
1022 except patch.PatchError as e:
1024 except patch.PatchError as e:
1023 raise error.Abort(str(e))
1025 raise error.Abort(str(e))
1024 if opts.get('exact'):
1026 if opts.get('exact'):
1025 editor = None
1027 editor = None
1026 else:
1028 else:
1027 editor = getcommiteditor(editform='import.bypass')
1029 editor = getcommiteditor(editform='import.bypass')
1028 memctx = context.makememctx(repo, (p1.node(), p2.node()),
1030 memctx = context.makememctx(repo, (p1.node(), p2.node()),
1029 message,
1031 message,
1030 user,
1032 user,
1031 date,
1033 date,
1032 branch, files, store,
1034 branch, files, store,
1033 editor=editor)
1035 editor=editor)
1034 n = memctx.commit()
1036 n = memctx.commit()
1035 finally:
1037 finally:
1036 store.close()
1038 store.close()
1037 if opts.get('exact') and nocommit:
1039 if opts.get('exact') and nocommit:
1038 # --exact with --no-commit is still useful in that it does merge
1040 # --exact with --no-commit is still useful in that it does merge
1039 # and branch bits
1041 # and branch bits
1040 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1042 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1041 elif opts.get('exact') and hex(n) != nodeid:
1043 elif opts.get('exact') and hex(n) != nodeid:
1042 raise error.Abort(_('patch is damaged or loses information'))
1044 raise error.Abort(_('patch is damaged or loses information'))
1043 msg = _('applied to working directory')
1045 msg = _('applied to working directory')
1044 if n:
1046 if n:
1045 # i18n: refers to a short changeset id
1047 # i18n: refers to a short changeset id
1046 msg = _('created %s') % short(n)
1048 msg = _('created %s') % short(n)
1047 return (msg, n, rejects)
1049 return (msg, n, rejects)
1048 finally:
1050 finally:
1049 os.unlink(tmpname)
1051 os.unlink(tmpname)
1050
1052
1051 # facility to let extensions include additional data in an exported patch
1053 # facility to let extensions include additional data in an exported patch
1052 # list of identifiers to be executed in order
1054 # list of identifiers to be executed in order
1053 extraexport = []
1055 extraexport = []
1054 # mapping from identifier to actual export function
1056 # mapping from identifier to actual export function
1055 # function as to return a string to be added to the header or None
1057 # function as to return a string to be added to the header or None
1056 # it is given two arguments (sequencenumber, changectx)
1058 # it is given two arguments (sequencenumber, changectx)
1057 extraexportmap = {}
1059 extraexportmap = {}
1058
1060
1059 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1061 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1060 opts=None, match=None):
1062 opts=None, match=None):
1061 '''export changesets as hg patches.'''
1063 '''export changesets as hg patches.'''
1062
1064
1063 total = len(revs)
1065 total = len(revs)
1064 revwidth = max([len(str(rev)) for rev in revs])
1066 revwidth = max([len(str(rev)) for rev in revs])
1065 filemode = {}
1067 filemode = {}
1066
1068
1067 def single(rev, seqno, fp):
1069 def single(rev, seqno, fp):
1068 ctx = repo[rev]
1070 ctx = repo[rev]
1069 node = ctx.node()
1071 node = ctx.node()
1070 parents = [p.node() for p in ctx.parents() if p]
1072 parents = [p.node() for p in ctx.parents() if p]
1071 branch = ctx.branch()
1073 branch = ctx.branch()
1072 if switch_parent:
1074 if switch_parent:
1073 parents.reverse()
1075 parents.reverse()
1074
1076
1075 if parents:
1077 if parents:
1076 prev = parents[0]
1078 prev = parents[0]
1077 else:
1079 else:
1078 prev = nullid
1080 prev = nullid
1079
1081
1080 shouldclose = False
1082 shouldclose = False
1081 if not fp and len(template) > 0:
1083 if not fp and len(template) > 0:
1082 desc_lines = ctx.description().rstrip().split('\n')
1084 desc_lines = ctx.description().rstrip().split('\n')
1083 desc = desc_lines[0] #Commit always has a first line.
1085 desc = desc_lines[0] #Commit always has a first line.
1084 fp = makefileobj(repo, template, node, desc=desc, total=total,
1086 fp = makefileobj(repo, template, node, desc=desc, total=total,
1085 seqno=seqno, revwidth=revwidth, mode='wb',
1087 seqno=seqno, revwidth=revwidth, mode='wb',
1086 modemap=filemode)
1088 modemap=filemode)
1087 shouldclose = True
1089 shouldclose = True
1088 if fp and not getattr(fp, 'name', '<unnamed>').startswith('<'):
1090 if fp and not getattr(fp, 'name', '<unnamed>').startswith('<'):
1089 repo.ui.note("%s\n" % fp.name)
1091 repo.ui.note("%s\n" % fp.name)
1090
1092
1091 if not fp:
1093 if not fp:
1092 write = repo.ui.write
1094 write = repo.ui.write
1093 else:
1095 else:
1094 def write(s, **kw):
1096 def write(s, **kw):
1095 fp.write(s)
1097 fp.write(s)
1096
1098
1097 write("# HG changeset patch\n")
1099 write("# HG changeset patch\n")
1098 write("# User %s\n" % ctx.user())
1100 write("# User %s\n" % ctx.user())
1099 write("# Date %d %d\n" % ctx.date())
1101 write("# Date %d %d\n" % ctx.date())
1100 write("# %s\n" % util.datestr(ctx.date()))
1102 write("# %s\n" % util.datestr(ctx.date()))
1101 if branch and branch != 'default':
1103 if branch and branch != 'default':
1102 write("# Branch %s\n" % branch)
1104 write("# Branch %s\n" % branch)
1103 write("# Node ID %s\n" % hex(node))
1105 write("# Node ID %s\n" % hex(node))
1104 write("# Parent %s\n" % hex(prev))
1106 write("# Parent %s\n" % hex(prev))
1105 if len(parents) > 1:
1107 if len(parents) > 1:
1106 write("# Parent %s\n" % hex(parents[1]))
1108 write("# Parent %s\n" % hex(parents[1]))
1107
1109
1108 for headerid in extraexport:
1110 for headerid in extraexport:
1109 header = extraexportmap[headerid](seqno, ctx)
1111 header = extraexportmap[headerid](seqno, ctx)
1110 if header is not None:
1112 if header is not None:
1111 write('# %s\n' % header)
1113 write('# %s\n' % header)
1112 write(ctx.description().rstrip())
1114 write(ctx.description().rstrip())
1113 write("\n\n")
1115 write("\n\n")
1114
1116
1115 for chunk, label in patch.diffui(repo, prev, node, match, opts=opts):
1117 for chunk, label in patch.diffui(repo, prev, node, match, opts=opts):
1116 write(chunk, label=label)
1118 write(chunk, label=label)
1117
1119
1118 if shouldclose:
1120 if shouldclose:
1119 fp.close()
1121 fp.close()
1120
1122
1121 for seqno, rev in enumerate(revs):
1123 for seqno, rev in enumerate(revs):
1122 single(rev, seqno + 1, fp)
1124 single(rev, seqno + 1, fp)
1123
1125
1124 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1126 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1125 changes=None, stat=False, fp=None, prefix='',
1127 changes=None, stat=False, fp=None, prefix='',
1126 root='', listsubrepos=False):
1128 root='', listsubrepos=False):
1127 '''show diff or diffstat.'''
1129 '''show diff or diffstat.'''
1128 if fp is None:
1130 if fp is None:
1129 write = ui.write
1131 write = ui.write
1130 else:
1132 else:
1131 def write(s, **kw):
1133 def write(s, **kw):
1132 fp.write(s)
1134 fp.write(s)
1133
1135
1134 if root:
1136 if root:
1135 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1137 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1136 else:
1138 else:
1137 relroot = ''
1139 relroot = ''
1138 if relroot != '':
1140 if relroot != '':
1139 # XXX relative roots currently don't work if the root is within a
1141 # XXX relative roots currently don't work if the root is within a
1140 # subrepo
1142 # subrepo
1141 uirelroot = match.uipath(relroot)
1143 uirelroot = match.uipath(relroot)
1142 relroot += '/'
1144 relroot += '/'
1143 for matchroot in match.files():
1145 for matchroot in match.files():
1144 if not matchroot.startswith(relroot):
1146 if not matchroot.startswith(relroot):
1145 ui.warn(_('warning: %s not inside relative root %s\n') % (
1147 ui.warn(_('warning: %s not inside relative root %s\n') % (
1146 match.uipath(matchroot), uirelroot))
1148 match.uipath(matchroot), uirelroot))
1147
1149
1148 if stat:
1150 if stat:
1149 diffopts = diffopts.copy(context=0)
1151 diffopts = diffopts.copy(context=0)
1150 width = 80
1152 width = 80
1151 if not ui.plain():
1153 if not ui.plain():
1152 width = ui.termwidth()
1154 width = ui.termwidth()
1153 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1155 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1154 prefix=prefix, relroot=relroot)
1156 prefix=prefix, relroot=relroot)
1155 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1157 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1156 width=width,
1158 width=width,
1157 git=diffopts.git):
1159 git=diffopts.git):
1158 write(chunk, label=label)
1160 write(chunk, label=label)
1159 else:
1161 else:
1160 for chunk, label in patch.diffui(repo, node1, node2, match,
1162 for chunk, label in patch.diffui(repo, node1, node2, match,
1161 changes, diffopts, prefix=prefix,
1163 changes, diffopts, prefix=prefix,
1162 relroot=relroot):
1164 relroot=relroot):
1163 write(chunk, label=label)
1165 write(chunk, label=label)
1164
1166
1165 if listsubrepos:
1167 if listsubrepos:
1166 ctx1 = repo[node1]
1168 ctx1 = repo[node1]
1167 ctx2 = repo[node2]
1169 ctx2 = repo[node2]
1168 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1170 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1169 tempnode2 = node2
1171 tempnode2 = node2
1170 try:
1172 try:
1171 if node2 is not None:
1173 if node2 is not None:
1172 tempnode2 = ctx2.substate[subpath][1]
1174 tempnode2 = ctx2.substate[subpath][1]
1173 except KeyError:
1175 except KeyError:
1174 # A subrepo that existed in node1 was deleted between node1 and
1176 # A subrepo that existed in node1 was deleted between node1 and
1175 # node2 (inclusive). Thus, ctx2's substate won't contain that
1177 # node2 (inclusive). Thus, ctx2's substate won't contain that
1176 # subpath. The best we can do is to ignore it.
1178 # subpath. The best we can do is to ignore it.
1177 tempnode2 = None
1179 tempnode2 = None
1178 submatch = matchmod.subdirmatcher(subpath, match)
1180 submatch = matchmod.subdirmatcher(subpath, match)
1179 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1181 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1180 stat=stat, fp=fp, prefix=prefix)
1182 stat=stat, fp=fp, prefix=prefix)
1181
1183
1182 class changeset_printer(object):
1184 class changeset_printer(object):
1183 '''show changeset information when templating not requested.'''
1185 '''show changeset information when templating not requested.'''
1184
1186
1185 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1187 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1186 self.ui = ui
1188 self.ui = ui
1187 self.repo = repo
1189 self.repo = repo
1188 self.buffered = buffered
1190 self.buffered = buffered
1189 self.matchfn = matchfn
1191 self.matchfn = matchfn
1190 self.diffopts = diffopts
1192 self.diffopts = diffopts
1191 self.header = {}
1193 self.header = {}
1192 self.hunk = {}
1194 self.hunk = {}
1193 self.lastheader = None
1195 self.lastheader = None
1194 self.footer = None
1196 self.footer = None
1195
1197
1196 def flush(self, ctx):
1198 def flush(self, ctx):
1197 rev = ctx.rev()
1199 rev = ctx.rev()
1198 if rev in self.header:
1200 if rev in self.header:
1199 h = self.header[rev]
1201 h = self.header[rev]
1200 if h != self.lastheader:
1202 if h != self.lastheader:
1201 self.lastheader = h
1203 self.lastheader = h
1202 self.ui.write(h)
1204 self.ui.write(h)
1203 del self.header[rev]
1205 del self.header[rev]
1204 if rev in self.hunk:
1206 if rev in self.hunk:
1205 self.ui.write(self.hunk[rev])
1207 self.ui.write(self.hunk[rev])
1206 del self.hunk[rev]
1208 del self.hunk[rev]
1207 return 1
1209 return 1
1208 return 0
1210 return 0
1209
1211
1210 def close(self):
1212 def close(self):
1211 if self.footer:
1213 if self.footer:
1212 self.ui.write(self.footer)
1214 self.ui.write(self.footer)
1213
1215
1214 def show(self, ctx, copies=None, matchfn=None, **props):
1216 def show(self, ctx, copies=None, matchfn=None, **props):
1215 if self.buffered:
1217 if self.buffered:
1216 self.ui.pushbuffer(labeled=True)
1218 self.ui.pushbuffer(labeled=True)
1217 self._show(ctx, copies, matchfn, props)
1219 self._show(ctx, copies, matchfn, props)
1218 self.hunk[ctx.rev()] = self.ui.popbuffer()
1220 self.hunk[ctx.rev()] = self.ui.popbuffer()
1219 else:
1221 else:
1220 self._show(ctx, copies, matchfn, props)
1222 self._show(ctx, copies, matchfn, props)
1221
1223
1222 def _show(self, ctx, copies, matchfn, props):
1224 def _show(self, ctx, copies, matchfn, props):
1223 '''show a single changeset or file revision'''
1225 '''show a single changeset or file revision'''
1224 changenode = ctx.node()
1226 changenode = ctx.node()
1225 rev = ctx.rev()
1227 rev = ctx.rev()
1226 if self.ui.debugflag:
1228 if self.ui.debugflag:
1227 hexfunc = hex
1229 hexfunc = hex
1228 else:
1230 else:
1229 hexfunc = short
1231 hexfunc = short
1230 # as of now, wctx.node() and wctx.rev() return None, but we want to
1232 # as of now, wctx.node() and wctx.rev() return None, but we want to
1231 # show the same values as {node} and {rev} templatekw
1233 # show the same values as {node} and {rev} templatekw
1232 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1234 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1233
1235
1234 if self.ui.quiet:
1236 if self.ui.quiet:
1235 self.ui.write("%d:%s\n" % revnode, label='log.node')
1237 self.ui.write("%d:%s\n" % revnode, label='log.node')
1236 return
1238 return
1237
1239
1238 date = util.datestr(ctx.date())
1240 date = util.datestr(ctx.date())
1239
1241
1240 # i18n: column positioning for "hg log"
1242 # i18n: column positioning for "hg log"
1241 self.ui.write(_("changeset: %d:%s\n") % revnode,
1243 self.ui.write(_("changeset: %d:%s\n") % revnode,
1242 label='log.changeset changeset.%s' % ctx.phasestr())
1244 label='log.changeset changeset.%s' % ctx.phasestr())
1243
1245
1244 # branches are shown first before any other names due to backwards
1246 # branches are shown first before any other names due to backwards
1245 # compatibility
1247 # compatibility
1246 branch = ctx.branch()
1248 branch = ctx.branch()
1247 # don't show the default branch name
1249 # don't show the default branch name
1248 if branch != 'default':
1250 if branch != 'default':
1249 # i18n: column positioning for "hg log"
1251 # i18n: column positioning for "hg log"
1250 self.ui.write(_("branch: %s\n") % branch,
1252 self.ui.write(_("branch: %s\n") % branch,
1251 label='log.branch')
1253 label='log.branch')
1252
1254
1253 for name, ns in self.repo.names.iteritems():
1255 for name, ns in self.repo.names.iteritems():
1254 # branches has special logic already handled above, so here we just
1256 # branches has special logic already handled above, so here we just
1255 # skip it
1257 # skip it
1256 if name == 'branches':
1258 if name == 'branches':
1257 continue
1259 continue
1258 # we will use the templatename as the color name since those two
1260 # we will use the templatename as the color name since those two
1259 # should be the same
1261 # should be the same
1260 for name in ns.names(self.repo, changenode):
1262 for name in ns.names(self.repo, changenode):
1261 self.ui.write(ns.logfmt % name,
1263 self.ui.write(ns.logfmt % name,
1262 label='log.%s' % ns.colorname)
1264 label='log.%s' % ns.colorname)
1263 if self.ui.debugflag:
1265 if self.ui.debugflag:
1264 # i18n: column positioning for "hg log"
1266 # i18n: column positioning for "hg log"
1265 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1267 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1266 label='log.phase')
1268 label='log.phase')
1267 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1269 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1268 label = 'log.parent changeset.%s' % pctx.phasestr()
1270 label = 'log.parent changeset.%s' % pctx.phasestr()
1269 # i18n: column positioning for "hg log"
1271 # i18n: column positioning for "hg log"
1270 self.ui.write(_("parent: %d:%s\n")
1272 self.ui.write(_("parent: %d:%s\n")
1271 % (pctx.rev(), hexfunc(pctx.node())),
1273 % (pctx.rev(), hexfunc(pctx.node())),
1272 label=label)
1274 label=label)
1273
1275
1274 if self.ui.debugflag and rev is not None:
1276 if self.ui.debugflag and rev is not None:
1275 mnode = ctx.manifestnode()
1277 mnode = ctx.manifestnode()
1276 # i18n: column positioning for "hg log"
1278 # i18n: column positioning for "hg log"
1277 self.ui.write(_("manifest: %d:%s\n") %
1279 self.ui.write(_("manifest: %d:%s\n") %
1278 (self.repo.manifest.rev(mnode), hex(mnode)),
1280 (self.repo.manifest.rev(mnode), hex(mnode)),
1279 label='ui.debug log.manifest')
1281 label='ui.debug log.manifest')
1280 # i18n: column positioning for "hg log"
1282 # i18n: column positioning for "hg log"
1281 self.ui.write(_("user: %s\n") % ctx.user(),
1283 self.ui.write(_("user: %s\n") % ctx.user(),
1282 label='log.user')
1284 label='log.user')
1283 # i18n: column positioning for "hg log"
1285 # i18n: column positioning for "hg log"
1284 self.ui.write(_("date: %s\n") % date,
1286 self.ui.write(_("date: %s\n") % date,
1285 label='log.date')
1287 label='log.date')
1286
1288
1287 if self.ui.debugflag:
1289 if self.ui.debugflag:
1288 files = ctx.p1().status(ctx)[:3]
1290 files = ctx.p1().status(ctx)[:3]
1289 for key, value in zip([# i18n: column positioning for "hg log"
1291 for key, value in zip([# i18n: column positioning for "hg log"
1290 _("files:"),
1292 _("files:"),
1291 # i18n: column positioning for "hg log"
1293 # i18n: column positioning for "hg log"
1292 _("files+:"),
1294 _("files+:"),
1293 # i18n: column positioning for "hg log"
1295 # i18n: column positioning for "hg log"
1294 _("files-:")], files):
1296 _("files-:")], files):
1295 if value:
1297 if value:
1296 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1298 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1297 label='ui.debug log.files')
1299 label='ui.debug log.files')
1298 elif ctx.files() and self.ui.verbose:
1300 elif ctx.files() and self.ui.verbose:
1299 # i18n: column positioning for "hg log"
1301 # i18n: column positioning for "hg log"
1300 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1302 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1301 label='ui.note log.files')
1303 label='ui.note log.files')
1302 if copies and self.ui.verbose:
1304 if copies and self.ui.verbose:
1303 copies = ['%s (%s)' % c for c in copies]
1305 copies = ['%s (%s)' % c for c in copies]
1304 # i18n: column positioning for "hg log"
1306 # i18n: column positioning for "hg log"
1305 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1307 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1306 label='ui.note log.copies')
1308 label='ui.note log.copies')
1307
1309
1308 extra = ctx.extra()
1310 extra = ctx.extra()
1309 if extra and self.ui.debugflag:
1311 if extra and self.ui.debugflag:
1310 for key, value in sorted(extra.items()):
1312 for key, value in sorted(extra.items()):
1311 # i18n: column positioning for "hg log"
1313 # i18n: column positioning for "hg log"
1312 self.ui.write(_("extra: %s=%s\n")
1314 self.ui.write(_("extra: %s=%s\n")
1313 % (key, value.encode('string_escape')),
1315 % (key, value.encode('string_escape')),
1314 label='ui.debug log.extra')
1316 label='ui.debug log.extra')
1315
1317
1316 description = ctx.description().strip()
1318 description = ctx.description().strip()
1317 if description:
1319 if description:
1318 if self.ui.verbose:
1320 if self.ui.verbose:
1319 self.ui.write(_("description:\n"),
1321 self.ui.write(_("description:\n"),
1320 label='ui.note log.description')
1322 label='ui.note log.description')
1321 self.ui.write(description,
1323 self.ui.write(description,
1322 label='ui.note log.description')
1324 label='ui.note log.description')
1323 self.ui.write("\n\n")
1325 self.ui.write("\n\n")
1324 else:
1326 else:
1325 # i18n: column positioning for "hg log"
1327 # i18n: column positioning for "hg log"
1326 self.ui.write(_("summary: %s\n") %
1328 self.ui.write(_("summary: %s\n") %
1327 description.splitlines()[0],
1329 description.splitlines()[0],
1328 label='log.summary')
1330 label='log.summary')
1329 self.ui.write("\n")
1331 self.ui.write("\n")
1330
1332
1331 self.showpatch(ctx, matchfn)
1333 self.showpatch(ctx, matchfn)
1332
1334
1333 def showpatch(self, ctx, matchfn):
1335 def showpatch(self, ctx, matchfn):
1334 if not matchfn:
1336 if not matchfn:
1335 matchfn = self.matchfn
1337 matchfn = self.matchfn
1336 if matchfn:
1338 if matchfn:
1337 stat = self.diffopts.get('stat')
1339 stat = self.diffopts.get('stat')
1338 diff = self.diffopts.get('patch')
1340 diff = self.diffopts.get('patch')
1339 diffopts = patch.diffallopts(self.ui, self.diffopts)
1341 diffopts = patch.diffallopts(self.ui, self.diffopts)
1340 node = ctx.node()
1342 node = ctx.node()
1341 prev = ctx.p1().node()
1343 prev = ctx.p1().node()
1342 if stat:
1344 if stat:
1343 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1345 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1344 match=matchfn, stat=True)
1346 match=matchfn, stat=True)
1345 if diff:
1347 if diff:
1346 if stat:
1348 if stat:
1347 self.ui.write("\n")
1349 self.ui.write("\n")
1348 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1350 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1349 match=matchfn, stat=False)
1351 match=matchfn, stat=False)
1350 self.ui.write("\n")
1352 self.ui.write("\n")
1351
1353
1352 class jsonchangeset(changeset_printer):
1354 class jsonchangeset(changeset_printer):
1353 '''format changeset information.'''
1355 '''format changeset information.'''
1354
1356
1355 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1357 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1356 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1358 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1357 self.cache = {}
1359 self.cache = {}
1358 self._first = True
1360 self._first = True
1359
1361
1360 def close(self):
1362 def close(self):
1361 if not self._first:
1363 if not self._first:
1362 self.ui.write("\n]\n")
1364 self.ui.write("\n]\n")
1363 else:
1365 else:
1364 self.ui.write("[]\n")
1366 self.ui.write("[]\n")
1365
1367
1366 def _show(self, ctx, copies, matchfn, props):
1368 def _show(self, ctx, copies, matchfn, props):
1367 '''show a single changeset or file revision'''
1369 '''show a single changeset or file revision'''
1368 rev = ctx.rev()
1370 rev = ctx.rev()
1369 if rev is None:
1371 if rev is None:
1370 jrev = jnode = 'null'
1372 jrev = jnode = 'null'
1371 else:
1373 else:
1372 jrev = str(rev)
1374 jrev = str(rev)
1373 jnode = '"%s"' % hex(ctx.node())
1375 jnode = '"%s"' % hex(ctx.node())
1374 j = encoding.jsonescape
1376 j = encoding.jsonescape
1375
1377
1376 if self._first:
1378 if self._first:
1377 self.ui.write("[\n {")
1379 self.ui.write("[\n {")
1378 self._first = False
1380 self._first = False
1379 else:
1381 else:
1380 self.ui.write(",\n {")
1382 self.ui.write(",\n {")
1381
1383
1382 if self.ui.quiet:
1384 if self.ui.quiet:
1383 self.ui.write('\n "rev": %s' % jrev)
1385 self.ui.write('\n "rev": %s' % jrev)
1384 self.ui.write(',\n "node": %s' % jnode)
1386 self.ui.write(',\n "node": %s' % jnode)
1385 self.ui.write('\n }')
1387 self.ui.write('\n }')
1386 return
1388 return
1387
1389
1388 self.ui.write('\n "rev": %s' % jrev)
1390 self.ui.write('\n "rev": %s' % jrev)
1389 self.ui.write(',\n "node": %s' % jnode)
1391 self.ui.write(',\n "node": %s' % jnode)
1390 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1392 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1391 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1393 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1392 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1394 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1393 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1395 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1394 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1396 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1395
1397
1396 self.ui.write(',\n "bookmarks": [%s]' %
1398 self.ui.write(',\n "bookmarks": [%s]' %
1397 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1399 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1398 self.ui.write(',\n "tags": [%s]' %
1400 self.ui.write(',\n "tags": [%s]' %
1399 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1401 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1400 self.ui.write(',\n "parents": [%s]' %
1402 self.ui.write(',\n "parents": [%s]' %
1401 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1403 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1402
1404
1403 if self.ui.debugflag:
1405 if self.ui.debugflag:
1404 if rev is None:
1406 if rev is None:
1405 jmanifestnode = 'null'
1407 jmanifestnode = 'null'
1406 else:
1408 else:
1407 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1409 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1408 self.ui.write(',\n "manifest": %s' % jmanifestnode)
1410 self.ui.write(',\n "manifest": %s' % jmanifestnode)
1409
1411
1410 self.ui.write(',\n "extra": {%s}' %
1412 self.ui.write(',\n "extra": {%s}' %
1411 ", ".join('"%s": "%s"' % (j(k), j(v))
1413 ", ".join('"%s": "%s"' % (j(k), j(v))
1412 for k, v in ctx.extra().items()))
1414 for k, v in ctx.extra().items()))
1413
1415
1414 files = ctx.p1().status(ctx)
1416 files = ctx.p1().status(ctx)
1415 self.ui.write(',\n "modified": [%s]' %
1417 self.ui.write(',\n "modified": [%s]' %
1416 ", ".join('"%s"' % j(f) for f in files[0]))
1418 ", ".join('"%s"' % j(f) for f in files[0]))
1417 self.ui.write(',\n "added": [%s]' %
1419 self.ui.write(',\n "added": [%s]' %
1418 ", ".join('"%s"' % j(f) for f in files[1]))
1420 ", ".join('"%s"' % j(f) for f in files[1]))
1419 self.ui.write(',\n "removed": [%s]' %
1421 self.ui.write(',\n "removed": [%s]' %
1420 ", ".join('"%s"' % j(f) for f in files[2]))
1422 ", ".join('"%s"' % j(f) for f in files[2]))
1421
1423
1422 elif self.ui.verbose:
1424 elif self.ui.verbose:
1423 self.ui.write(',\n "files": [%s]' %
1425 self.ui.write(',\n "files": [%s]' %
1424 ", ".join('"%s"' % j(f) for f in ctx.files()))
1426 ", ".join('"%s"' % j(f) for f in ctx.files()))
1425
1427
1426 if copies:
1428 if copies:
1427 self.ui.write(',\n "copies": {%s}' %
1429 self.ui.write(',\n "copies": {%s}' %
1428 ", ".join('"%s": "%s"' % (j(k), j(v))
1430 ", ".join('"%s": "%s"' % (j(k), j(v))
1429 for k, v in copies))
1431 for k, v in copies))
1430
1432
1431 matchfn = self.matchfn
1433 matchfn = self.matchfn
1432 if matchfn:
1434 if matchfn:
1433 stat = self.diffopts.get('stat')
1435 stat = self.diffopts.get('stat')
1434 diff = self.diffopts.get('patch')
1436 diff = self.diffopts.get('patch')
1435 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1437 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1436 node, prev = ctx.node(), ctx.p1().node()
1438 node, prev = ctx.node(), ctx.p1().node()
1437 if stat:
1439 if stat:
1438 self.ui.pushbuffer()
1440 self.ui.pushbuffer()
1439 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1441 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1440 match=matchfn, stat=True)
1442 match=matchfn, stat=True)
1441 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1443 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1442 if diff:
1444 if diff:
1443 self.ui.pushbuffer()
1445 self.ui.pushbuffer()
1444 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1446 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1445 match=matchfn, stat=False)
1447 match=matchfn, stat=False)
1446 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1448 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1447
1449
1448 self.ui.write("\n }")
1450 self.ui.write("\n }")
1449
1451
1450 class changeset_templater(changeset_printer):
1452 class changeset_templater(changeset_printer):
1451 '''format changeset information.'''
1453 '''format changeset information.'''
1452
1454
1453 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1455 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1454 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1456 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1455 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1457 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1456 defaulttempl = {
1458 defaulttempl = {
1457 'parent': '{rev}:{node|formatnode} ',
1459 'parent': '{rev}:{node|formatnode} ',
1458 'manifest': '{rev}:{node|formatnode}',
1460 'manifest': '{rev}:{node|formatnode}',
1459 'file_copy': '{name} ({source})',
1461 'file_copy': '{name} ({source})',
1460 'extra': '{key}={value|stringescape}'
1462 'extra': '{key}={value|stringescape}'
1461 }
1463 }
1462 # filecopy is preserved for compatibility reasons
1464 # filecopy is preserved for compatibility reasons
1463 defaulttempl['filecopy'] = defaulttempl['file_copy']
1465 defaulttempl['filecopy'] = defaulttempl['file_copy']
1464 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1466 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1465 cache=defaulttempl)
1467 cache=defaulttempl)
1466 if tmpl:
1468 if tmpl:
1467 self.t.cache['changeset'] = tmpl
1469 self.t.cache['changeset'] = tmpl
1468
1470
1469 self.cache = {}
1471 self.cache = {}
1470
1472
1471 # find correct templates for current mode
1473 # find correct templates for current mode
1472 tmplmodes = [
1474 tmplmodes = [
1473 (True, None),
1475 (True, None),
1474 (self.ui.verbose, 'verbose'),
1476 (self.ui.verbose, 'verbose'),
1475 (self.ui.quiet, 'quiet'),
1477 (self.ui.quiet, 'quiet'),
1476 (self.ui.debugflag, 'debug'),
1478 (self.ui.debugflag, 'debug'),
1477 ]
1479 ]
1478
1480
1479 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1481 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1480 'docheader': '', 'docfooter': ''}
1482 'docheader': '', 'docfooter': ''}
1481 for mode, postfix in tmplmodes:
1483 for mode, postfix in tmplmodes:
1482 for t in self._parts:
1484 for t in self._parts:
1483 cur = t
1485 cur = t
1484 if postfix:
1486 if postfix:
1485 cur += "_" + postfix
1487 cur += "_" + postfix
1486 if mode and cur in self.t:
1488 if mode and cur in self.t:
1487 self._parts[t] = cur
1489 self._parts[t] = cur
1488
1490
1489 if self._parts['docheader']:
1491 if self._parts['docheader']:
1490 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1492 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1491
1493
1492 def close(self):
1494 def close(self):
1493 if self._parts['docfooter']:
1495 if self._parts['docfooter']:
1494 if not self.footer:
1496 if not self.footer:
1495 self.footer = ""
1497 self.footer = ""
1496 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1498 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1497 return super(changeset_templater, self).close()
1499 return super(changeset_templater, self).close()
1498
1500
1499 def _show(self, ctx, copies, matchfn, props):
1501 def _show(self, ctx, copies, matchfn, props):
1500 '''show a single changeset or file revision'''
1502 '''show a single changeset or file revision'''
1501 props = props.copy()
1503 props = props.copy()
1502 props.update(templatekw.keywords)
1504 props.update(templatekw.keywords)
1503 props['templ'] = self.t
1505 props['templ'] = self.t
1504 props['ctx'] = ctx
1506 props['ctx'] = ctx
1505 props['repo'] = self.repo
1507 props['repo'] = self.repo
1506 props['ui'] = self.repo.ui
1508 props['ui'] = self.repo.ui
1507 props['revcache'] = {'copies': copies}
1509 props['revcache'] = {'copies': copies}
1508 props['cache'] = self.cache
1510 props['cache'] = self.cache
1509
1511
1510 try:
1512 try:
1511 # write header
1513 # write header
1512 if self._parts['header']:
1514 if self._parts['header']:
1513 h = templater.stringify(self.t(self._parts['header'], **props))
1515 h = templater.stringify(self.t(self._parts['header'], **props))
1514 if self.buffered:
1516 if self.buffered:
1515 self.header[ctx.rev()] = h
1517 self.header[ctx.rev()] = h
1516 else:
1518 else:
1517 if self.lastheader != h:
1519 if self.lastheader != h:
1518 self.lastheader = h
1520 self.lastheader = h
1519 self.ui.write(h)
1521 self.ui.write(h)
1520
1522
1521 # write changeset metadata, then patch if requested
1523 # write changeset metadata, then patch if requested
1522 key = self._parts['changeset']
1524 key = self._parts['changeset']
1523 self.ui.write(templater.stringify(self.t(key, **props)))
1525 self.ui.write(templater.stringify(self.t(key, **props)))
1524 self.showpatch(ctx, matchfn)
1526 self.showpatch(ctx, matchfn)
1525
1527
1526 if self._parts['footer']:
1528 if self._parts['footer']:
1527 if not self.footer:
1529 if not self.footer:
1528 self.footer = templater.stringify(
1530 self.footer = templater.stringify(
1529 self.t(self._parts['footer'], **props))
1531 self.t(self._parts['footer'], **props))
1530 except KeyError as inst:
1532 except KeyError as inst:
1531 msg = _("%s: no key named '%s'")
1533 msg = _("%s: no key named '%s'")
1532 raise error.Abort(msg % (self.t.mapfile, inst.args[0]))
1534 raise error.Abort(msg % (self.t.mapfile, inst.args[0]))
1533 except SyntaxError as inst:
1535 except SyntaxError as inst:
1534 raise error.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1536 raise error.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1535
1537
1536 def gettemplate(ui, tmpl, style):
1538 def gettemplate(ui, tmpl, style):
1537 """
1539 """
1538 Find the template matching the given template spec or style.
1540 Find the template matching the given template spec or style.
1539 """
1541 """
1540
1542
1541 # ui settings
1543 # ui settings
1542 if not tmpl and not style: # template are stronger than style
1544 if not tmpl and not style: # template are stronger than style
1543 tmpl = ui.config('ui', 'logtemplate')
1545 tmpl = ui.config('ui', 'logtemplate')
1544 if tmpl:
1546 if tmpl:
1545 try:
1547 try:
1546 tmpl = templater.unquotestring(tmpl)
1548 tmpl = templater.unquotestring(tmpl)
1547 except SyntaxError:
1549 except SyntaxError:
1548 pass
1550 pass
1549 return tmpl, None
1551 return tmpl, None
1550 else:
1552 else:
1551 style = util.expandpath(ui.config('ui', 'style', ''))
1553 style = util.expandpath(ui.config('ui', 'style', ''))
1552
1554
1553 if not tmpl and style:
1555 if not tmpl and style:
1554 mapfile = style
1556 mapfile = style
1555 if not os.path.split(mapfile)[0]:
1557 if not os.path.split(mapfile)[0]:
1556 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1558 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1557 or templater.templatepath(mapfile))
1559 or templater.templatepath(mapfile))
1558 if mapname:
1560 if mapname:
1559 mapfile = mapname
1561 mapfile = mapname
1560 return None, mapfile
1562 return None, mapfile
1561
1563
1562 if not tmpl:
1564 if not tmpl:
1563 return None, None
1565 return None, None
1564
1566
1565 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1567 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1566
1568
1567 def show_changeset(ui, repo, opts, buffered=False):
1569 def show_changeset(ui, repo, opts, buffered=False):
1568 """show one changeset using template or regular display.
1570 """show one changeset using template or regular display.
1569
1571
1570 Display format will be the first non-empty hit of:
1572 Display format will be the first non-empty hit of:
1571 1. option 'template'
1573 1. option 'template'
1572 2. option 'style'
1574 2. option 'style'
1573 3. [ui] setting 'logtemplate'
1575 3. [ui] setting 'logtemplate'
1574 4. [ui] setting 'style'
1576 4. [ui] setting 'style'
1575 If all of these values are either the unset or the empty string,
1577 If all of these values are either the unset or the empty string,
1576 regular display via changeset_printer() is done.
1578 regular display via changeset_printer() is done.
1577 """
1579 """
1578 # options
1580 # options
1579 matchfn = None
1581 matchfn = None
1580 if opts.get('patch') or opts.get('stat'):
1582 if opts.get('patch') or opts.get('stat'):
1581 matchfn = scmutil.matchall(repo)
1583 matchfn = scmutil.matchall(repo)
1582
1584
1583 if opts.get('template') == 'json':
1585 if opts.get('template') == 'json':
1584 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1586 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1585
1587
1586 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1588 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1587
1589
1588 if not tmpl and not mapfile:
1590 if not tmpl and not mapfile:
1589 return changeset_printer(ui, repo, matchfn, opts, buffered)
1591 return changeset_printer(ui, repo, matchfn, opts, buffered)
1590
1592
1591 try:
1593 try:
1592 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1594 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1593 buffered)
1595 buffered)
1594 except SyntaxError as inst:
1596 except SyntaxError as inst:
1595 raise error.Abort(inst.args[0])
1597 raise error.Abort(inst.args[0])
1596 return t
1598 return t
1597
1599
1598 def showmarker(ui, marker):
1600 def showmarker(ui, marker):
1599 """utility function to display obsolescence marker in a readable way
1601 """utility function to display obsolescence marker in a readable way
1600
1602
1601 To be used by debug function."""
1603 To be used by debug function."""
1602 ui.write(hex(marker.precnode()))
1604 ui.write(hex(marker.precnode()))
1603 for repl in marker.succnodes():
1605 for repl in marker.succnodes():
1604 ui.write(' ')
1606 ui.write(' ')
1605 ui.write(hex(repl))
1607 ui.write(hex(repl))
1606 ui.write(' %X ' % marker.flags())
1608 ui.write(' %X ' % marker.flags())
1607 parents = marker.parentnodes()
1609 parents = marker.parentnodes()
1608 if parents is not None:
1610 if parents is not None:
1609 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1611 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1610 ui.write('(%s) ' % util.datestr(marker.date()))
1612 ui.write('(%s) ' % util.datestr(marker.date()))
1611 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1613 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1612 sorted(marker.metadata().items())
1614 sorted(marker.metadata().items())
1613 if t[0] != 'date')))
1615 if t[0] != 'date')))
1614 ui.write('\n')
1616 ui.write('\n')
1615
1617
1616 def finddate(ui, repo, date):
1618 def finddate(ui, repo, date):
1617 """Find the tipmost changeset that matches the given date spec"""
1619 """Find the tipmost changeset that matches the given date spec"""
1618
1620
1619 df = util.matchdate(date)
1621 df = util.matchdate(date)
1620 m = scmutil.matchall(repo)
1622 m = scmutil.matchall(repo)
1621 results = {}
1623 results = {}
1622
1624
1623 def prep(ctx, fns):
1625 def prep(ctx, fns):
1624 d = ctx.date()
1626 d = ctx.date()
1625 if df(d[0]):
1627 if df(d[0]):
1626 results[ctx.rev()] = d
1628 results[ctx.rev()] = d
1627
1629
1628 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1630 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1629 rev = ctx.rev()
1631 rev = ctx.rev()
1630 if rev in results:
1632 if rev in results:
1631 ui.status(_("found revision %s from %s\n") %
1633 ui.status(_("found revision %s from %s\n") %
1632 (rev, util.datestr(results[rev])))
1634 (rev, util.datestr(results[rev])))
1633 return str(rev)
1635 return str(rev)
1634
1636
1635 raise error.Abort(_("revision matching date not found"))
1637 raise error.Abort(_("revision matching date not found"))
1636
1638
1637 def increasingwindows(windowsize=8, sizelimit=512):
1639 def increasingwindows(windowsize=8, sizelimit=512):
1638 while True:
1640 while True:
1639 yield windowsize
1641 yield windowsize
1640 if windowsize < sizelimit:
1642 if windowsize < sizelimit:
1641 windowsize *= 2
1643 windowsize *= 2
1642
1644
1643 class FileWalkError(Exception):
1645 class FileWalkError(Exception):
1644 pass
1646 pass
1645
1647
1646 def walkfilerevs(repo, match, follow, revs, fncache):
1648 def walkfilerevs(repo, match, follow, revs, fncache):
1647 '''Walks the file history for the matched files.
1649 '''Walks the file history for the matched files.
1648
1650
1649 Returns the changeset revs that are involved in the file history.
1651 Returns the changeset revs that are involved in the file history.
1650
1652
1651 Throws FileWalkError if the file history can't be walked using
1653 Throws FileWalkError if the file history can't be walked using
1652 filelogs alone.
1654 filelogs alone.
1653 '''
1655 '''
1654 wanted = set()
1656 wanted = set()
1655 copies = []
1657 copies = []
1656 minrev, maxrev = min(revs), max(revs)
1658 minrev, maxrev = min(revs), max(revs)
1657 def filerevgen(filelog, last):
1659 def filerevgen(filelog, last):
1658 """
1660 """
1659 Only files, no patterns. Check the history of each file.
1661 Only files, no patterns. Check the history of each file.
1660
1662
1661 Examines filelog entries within minrev, maxrev linkrev range
1663 Examines filelog entries within minrev, maxrev linkrev range
1662 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1664 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1663 tuples in backwards order
1665 tuples in backwards order
1664 """
1666 """
1665 cl_count = len(repo)
1667 cl_count = len(repo)
1666 revs = []
1668 revs = []
1667 for j in xrange(0, last + 1):
1669 for j in xrange(0, last + 1):
1668 linkrev = filelog.linkrev(j)
1670 linkrev = filelog.linkrev(j)
1669 if linkrev < minrev:
1671 if linkrev < minrev:
1670 continue
1672 continue
1671 # only yield rev for which we have the changelog, it can
1673 # only yield rev for which we have the changelog, it can
1672 # happen while doing "hg log" during a pull or commit
1674 # happen while doing "hg log" during a pull or commit
1673 if linkrev >= cl_count:
1675 if linkrev >= cl_count:
1674 break
1676 break
1675
1677
1676 parentlinkrevs = []
1678 parentlinkrevs = []
1677 for p in filelog.parentrevs(j):
1679 for p in filelog.parentrevs(j):
1678 if p != nullrev:
1680 if p != nullrev:
1679 parentlinkrevs.append(filelog.linkrev(p))
1681 parentlinkrevs.append(filelog.linkrev(p))
1680 n = filelog.node(j)
1682 n = filelog.node(j)
1681 revs.append((linkrev, parentlinkrevs,
1683 revs.append((linkrev, parentlinkrevs,
1682 follow and filelog.renamed(n)))
1684 follow and filelog.renamed(n)))
1683
1685
1684 return reversed(revs)
1686 return reversed(revs)
1685 def iterfiles():
1687 def iterfiles():
1686 pctx = repo['.']
1688 pctx = repo['.']
1687 for filename in match.files():
1689 for filename in match.files():
1688 if follow:
1690 if follow:
1689 if filename not in pctx:
1691 if filename not in pctx:
1690 raise error.Abort(_('cannot follow file not in parent '
1692 raise error.Abort(_('cannot follow file not in parent '
1691 'revision: "%s"') % filename)
1693 'revision: "%s"') % filename)
1692 yield filename, pctx[filename].filenode()
1694 yield filename, pctx[filename].filenode()
1693 else:
1695 else:
1694 yield filename, None
1696 yield filename, None
1695 for filename_node in copies:
1697 for filename_node in copies:
1696 yield filename_node
1698 yield filename_node
1697
1699
1698 for file_, node in iterfiles():
1700 for file_, node in iterfiles():
1699 filelog = repo.file(file_)
1701 filelog = repo.file(file_)
1700 if not len(filelog):
1702 if not len(filelog):
1701 if node is None:
1703 if node is None:
1702 # A zero count may be a directory or deleted file, so
1704 # A zero count may be a directory or deleted file, so
1703 # try to find matching entries on the slow path.
1705 # try to find matching entries on the slow path.
1704 if follow:
1706 if follow:
1705 raise error.Abort(
1707 raise error.Abort(
1706 _('cannot follow nonexistent file: "%s"') % file_)
1708 _('cannot follow nonexistent file: "%s"') % file_)
1707 raise FileWalkError("Cannot walk via filelog")
1709 raise FileWalkError("Cannot walk via filelog")
1708 else:
1710 else:
1709 continue
1711 continue
1710
1712
1711 if node is None:
1713 if node is None:
1712 last = len(filelog) - 1
1714 last = len(filelog) - 1
1713 else:
1715 else:
1714 last = filelog.rev(node)
1716 last = filelog.rev(node)
1715
1717
1716 # keep track of all ancestors of the file
1718 # keep track of all ancestors of the file
1717 ancestors = set([filelog.linkrev(last)])
1719 ancestors = set([filelog.linkrev(last)])
1718
1720
1719 # iterate from latest to oldest revision
1721 # iterate from latest to oldest revision
1720 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1722 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1721 if not follow:
1723 if not follow:
1722 if rev > maxrev:
1724 if rev > maxrev:
1723 continue
1725 continue
1724 else:
1726 else:
1725 # Note that last might not be the first interesting
1727 # Note that last might not be the first interesting
1726 # rev to us:
1728 # rev to us:
1727 # if the file has been changed after maxrev, we'll
1729 # if the file has been changed after maxrev, we'll
1728 # have linkrev(last) > maxrev, and we still need
1730 # have linkrev(last) > maxrev, and we still need
1729 # to explore the file graph
1731 # to explore the file graph
1730 if rev not in ancestors:
1732 if rev not in ancestors:
1731 continue
1733 continue
1732 # XXX insert 1327 fix here
1734 # XXX insert 1327 fix here
1733 if flparentlinkrevs:
1735 if flparentlinkrevs:
1734 ancestors.update(flparentlinkrevs)
1736 ancestors.update(flparentlinkrevs)
1735
1737
1736 fncache.setdefault(rev, []).append(file_)
1738 fncache.setdefault(rev, []).append(file_)
1737 wanted.add(rev)
1739 wanted.add(rev)
1738 if copied:
1740 if copied:
1739 copies.append(copied)
1741 copies.append(copied)
1740
1742
1741 return wanted
1743 return wanted
1742
1744
1743 class _followfilter(object):
1745 class _followfilter(object):
1744 def __init__(self, repo, onlyfirst=False):
1746 def __init__(self, repo, onlyfirst=False):
1745 self.repo = repo
1747 self.repo = repo
1746 self.startrev = nullrev
1748 self.startrev = nullrev
1747 self.roots = set()
1749 self.roots = set()
1748 self.onlyfirst = onlyfirst
1750 self.onlyfirst = onlyfirst
1749
1751
1750 def match(self, rev):
1752 def match(self, rev):
1751 def realparents(rev):
1753 def realparents(rev):
1752 if self.onlyfirst:
1754 if self.onlyfirst:
1753 return self.repo.changelog.parentrevs(rev)[0:1]
1755 return self.repo.changelog.parentrevs(rev)[0:1]
1754 else:
1756 else:
1755 return filter(lambda x: x != nullrev,
1757 return filter(lambda x: x != nullrev,
1756 self.repo.changelog.parentrevs(rev))
1758 self.repo.changelog.parentrevs(rev))
1757
1759
1758 if self.startrev == nullrev:
1760 if self.startrev == nullrev:
1759 self.startrev = rev
1761 self.startrev = rev
1760 return True
1762 return True
1761
1763
1762 if rev > self.startrev:
1764 if rev > self.startrev:
1763 # forward: all descendants
1765 # forward: all descendants
1764 if not self.roots:
1766 if not self.roots:
1765 self.roots.add(self.startrev)
1767 self.roots.add(self.startrev)
1766 for parent in realparents(rev):
1768 for parent in realparents(rev):
1767 if parent in self.roots:
1769 if parent in self.roots:
1768 self.roots.add(rev)
1770 self.roots.add(rev)
1769 return True
1771 return True
1770 else:
1772 else:
1771 # backwards: all parents
1773 # backwards: all parents
1772 if not self.roots:
1774 if not self.roots:
1773 self.roots.update(realparents(self.startrev))
1775 self.roots.update(realparents(self.startrev))
1774 if rev in self.roots:
1776 if rev in self.roots:
1775 self.roots.remove(rev)
1777 self.roots.remove(rev)
1776 self.roots.update(realparents(rev))
1778 self.roots.update(realparents(rev))
1777 return True
1779 return True
1778
1780
1779 return False
1781 return False
1780
1782
1781 def walkchangerevs(repo, match, opts, prepare):
1783 def walkchangerevs(repo, match, opts, prepare):
1782 '''Iterate over files and the revs in which they changed.
1784 '''Iterate over files and the revs in which they changed.
1783
1785
1784 Callers most commonly need to iterate backwards over the history
1786 Callers most commonly need to iterate backwards over the history
1785 in which they are interested. Doing so has awful (quadratic-looking)
1787 in which they are interested. Doing so has awful (quadratic-looking)
1786 performance, so we use iterators in a "windowed" way.
1788 performance, so we use iterators in a "windowed" way.
1787
1789
1788 We walk a window of revisions in the desired order. Within the
1790 We walk a window of revisions in the desired order. Within the
1789 window, we first walk forwards to gather data, then in the desired
1791 window, we first walk forwards to gather data, then in the desired
1790 order (usually backwards) to display it.
1792 order (usually backwards) to display it.
1791
1793
1792 This function returns an iterator yielding contexts. Before
1794 This function returns an iterator yielding contexts. Before
1793 yielding each context, the iterator will first call the prepare
1795 yielding each context, the iterator will first call the prepare
1794 function on each context in the window in forward order.'''
1796 function on each context in the window in forward order.'''
1795
1797
1796 follow = opts.get('follow') or opts.get('follow_first')
1798 follow = opts.get('follow') or opts.get('follow_first')
1797 revs = _logrevs(repo, opts)
1799 revs = _logrevs(repo, opts)
1798 if not revs:
1800 if not revs:
1799 return []
1801 return []
1800 wanted = set()
1802 wanted = set()
1801 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1803 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1802 opts.get('removed'))
1804 opts.get('removed'))
1803 fncache = {}
1805 fncache = {}
1804 change = repo.changectx
1806 change = repo.changectx
1805
1807
1806 # First step is to fill wanted, the set of revisions that we want to yield.
1808 # First step is to fill wanted, the set of revisions that we want to yield.
1807 # When it does not induce extra cost, we also fill fncache for revisions in
1809 # When it does not induce extra cost, we also fill fncache for revisions in
1808 # wanted: a cache of filenames that were changed (ctx.files()) and that
1810 # wanted: a cache of filenames that were changed (ctx.files()) and that
1809 # match the file filtering conditions.
1811 # match the file filtering conditions.
1810
1812
1811 if match.always():
1813 if match.always():
1812 # No files, no patterns. Display all revs.
1814 # No files, no patterns. Display all revs.
1813 wanted = revs
1815 wanted = revs
1814 elif not slowpath:
1816 elif not slowpath:
1815 # We only have to read through the filelog to find wanted revisions
1817 # We only have to read through the filelog to find wanted revisions
1816
1818
1817 try:
1819 try:
1818 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1820 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1819 except FileWalkError:
1821 except FileWalkError:
1820 slowpath = True
1822 slowpath = True
1821
1823
1822 # We decided to fall back to the slowpath because at least one
1824 # We decided to fall back to the slowpath because at least one
1823 # of the paths was not a file. Check to see if at least one of them
1825 # of the paths was not a file. Check to see if at least one of them
1824 # existed in history, otherwise simply return
1826 # existed in history, otherwise simply return
1825 for path in match.files():
1827 for path in match.files():
1826 if path == '.' or path in repo.store:
1828 if path == '.' or path in repo.store:
1827 break
1829 break
1828 else:
1830 else:
1829 return []
1831 return []
1830
1832
1831 if slowpath:
1833 if slowpath:
1832 # We have to read the changelog to match filenames against
1834 # We have to read the changelog to match filenames against
1833 # changed files
1835 # changed files
1834
1836
1835 if follow:
1837 if follow:
1836 raise error.Abort(_('can only follow copies/renames for explicit '
1838 raise error.Abort(_('can only follow copies/renames for explicit '
1837 'filenames'))
1839 'filenames'))
1838
1840
1839 # The slow path checks files modified in every changeset.
1841 # The slow path checks files modified in every changeset.
1840 # This is really slow on large repos, so compute the set lazily.
1842 # This is really slow on large repos, so compute the set lazily.
1841 class lazywantedset(object):
1843 class lazywantedset(object):
1842 def __init__(self):
1844 def __init__(self):
1843 self.set = set()
1845 self.set = set()
1844 self.revs = set(revs)
1846 self.revs = set(revs)
1845
1847
1846 # No need to worry about locality here because it will be accessed
1848 # No need to worry about locality here because it will be accessed
1847 # in the same order as the increasing window below.
1849 # in the same order as the increasing window below.
1848 def __contains__(self, value):
1850 def __contains__(self, value):
1849 if value in self.set:
1851 if value in self.set:
1850 return True
1852 return True
1851 elif not value in self.revs:
1853 elif not value in self.revs:
1852 return False
1854 return False
1853 else:
1855 else:
1854 self.revs.discard(value)
1856 self.revs.discard(value)
1855 ctx = change(value)
1857 ctx = change(value)
1856 matches = filter(match, ctx.files())
1858 matches = filter(match, ctx.files())
1857 if matches:
1859 if matches:
1858 fncache[value] = matches
1860 fncache[value] = matches
1859 self.set.add(value)
1861 self.set.add(value)
1860 return True
1862 return True
1861 return False
1863 return False
1862
1864
1863 def discard(self, value):
1865 def discard(self, value):
1864 self.revs.discard(value)
1866 self.revs.discard(value)
1865 self.set.discard(value)
1867 self.set.discard(value)
1866
1868
1867 wanted = lazywantedset()
1869 wanted = lazywantedset()
1868
1870
1869 # it might be worthwhile to do this in the iterator if the rev range
1871 # it might be worthwhile to do this in the iterator if the rev range
1870 # is descending and the prune args are all within that range
1872 # is descending and the prune args are all within that range
1871 for rev in opts.get('prune', ()):
1873 for rev in opts.get('prune', ()):
1872 rev = repo[rev].rev()
1874 rev = repo[rev].rev()
1873 ff = _followfilter(repo)
1875 ff = _followfilter(repo)
1874 stop = min(revs[0], revs[-1])
1876 stop = min(revs[0], revs[-1])
1875 for x in xrange(rev, stop - 1, -1):
1877 for x in xrange(rev, stop - 1, -1):
1876 if ff.match(x):
1878 if ff.match(x):
1877 wanted = wanted - [x]
1879 wanted = wanted - [x]
1878
1880
1879 # Now that wanted is correctly initialized, we can iterate over the
1881 # Now that wanted is correctly initialized, we can iterate over the
1880 # revision range, yielding only revisions in wanted.
1882 # revision range, yielding only revisions in wanted.
1881 def iterate():
1883 def iterate():
1882 if follow and match.always():
1884 if follow and match.always():
1883 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1885 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1884 def want(rev):
1886 def want(rev):
1885 return ff.match(rev) and rev in wanted
1887 return ff.match(rev) and rev in wanted
1886 else:
1888 else:
1887 def want(rev):
1889 def want(rev):
1888 return rev in wanted
1890 return rev in wanted
1889
1891
1890 it = iter(revs)
1892 it = iter(revs)
1891 stopiteration = False
1893 stopiteration = False
1892 for windowsize in increasingwindows():
1894 for windowsize in increasingwindows():
1893 nrevs = []
1895 nrevs = []
1894 for i in xrange(windowsize):
1896 for i in xrange(windowsize):
1895 rev = next(it, None)
1897 rev = next(it, None)
1896 if rev is None:
1898 if rev is None:
1897 stopiteration = True
1899 stopiteration = True
1898 break
1900 break
1899 elif want(rev):
1901 elif want(rev):
1900 nrevs.append(rev)
1902 nrevs.append(rev)
1901 for rev in sorted(nrevs):
1903 for rev in sorted(nrevs):
1902 fns = fncache.get(rev)
1904 fns = fncache.get(rev)
1903 ctx = change(rev)
1905 ctx = change(rev)
1904 if not fns:
1906 if not fns:
1905 def fns_generator():
1907 def fns_generator():
1906 for f in ctx.files():
1908 for f in ctx.files():
1907 if match(f):
1909 if match(f):
1908 yield f
1910 yield f
1909 fns = fns_generator()
1911 fns = fns_generator()
1910 prepare(ctx, fns)
1912 prepare(ctx, fns)
1911 for rev in nrevs:
1913 for rev in nrevs:
1912 yield change(rev)
1914 yield change(rev)
1913
1915
1914 if stopiteration:
1916 if stopiteration:
1915 break
1917 break
1916
1918
1917 return iterate()
1919 return iterate()
1918
1920
1919 def _makefollowlogfilematcher(repo, files, followfirst):
1921 def _makefollowlogfilematcher(repo, files, followfirst):
1920 # When displaying a revision with --patch --follow FILE, we have
1922 # When displaying a revision with --patch --follow FILE, we have
1921 # to know which file of the revision must be diffed. With
1923 # to know which file of the revision must be diffed. With
1922 # --follow, we want the names of the ancestors of FILE in the
1924 # --follow, we want the names of the ancestors of FILE in the
1923 # revision, stored in "fcache". "fcache" is populated by
1925 # revision, stored in "fcache". "fcache" is populated by
1924 # reproducing the graph traversal already done by --follow revset
1926 # reproducing the graph traversal already done by --follow revset
1925 # and relating linkrevs to file names (which is not "correct" but
1927 # and relating linkrevs to file names (which is not "correct" but
1926 # good enough).
1928 # good enough).
1927 fcache = {}
1929 fcache = {}
1928 fcacheready = [False]
1930 fcacheready = [False]
1929 pctx = repo['.']
1931 pctx = repo['.']
1930
1932
1931 def populate():
1933 def populate():
1932 for fn in files:
1934 for fn in files:
1933 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1935 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1934 for c in i:
1936 for c in i:
1935 fcache.setdefault(c.linkrev(), set()).add(c.path())
1937 fcache.setdefault(c.linkrev(), set()).add(c.path())
1936
1938
1937 def filematcher(rev):
1939 def filematcher(rev):
1938 if not fcacheready[0]:
1940 if not fcacheready[0]:
1939 # Lazy initialization
1941 # Lazy initialization
1940 fcacheready[0] = True
1942 fcacheready[0] = True
1941 populate()
1943 populate()
1942 return scmutil.matchfiles(repo, fcache.get(rev, []))
1944 return scmutil.matchfiles(repo, fcache.get(rev, []))
1943
1945
1944 return filematcher
1946 return filematcher
1945
1947
1946 def _makenofollowlogfilematcher(repo, pats, opts):
1948 def _makenofollowlogfilematcher(repo, pats, opts):
1947 '''hook for extensions to override the filematcher for non-follow cases'''
1949 '''hook for extensions to override the filematcher for non-follow cases'''
1948 return None
1950 return None
1949
1951
1950 def _makelogrevset(repo, pats, opts, revs):
1952 def _makelogrevset(repo, pats, opts, revs):
1951 """Return (expr, filematcher) where expr is a revset string built
1953 """Return (expr, filematcher) where expr is a revset string built
1952 from log options and file patterns or None. If --stat or --patch
1954 from log options and file patterns or None. If --stat or --patch
1953 are not passed filematcher is None. Otherwise it is a callable
1955 are not passed filematcher is None. Otherwise it is a callable
1954 taking a revision number and returning a match objects filtering
1956 taking a revision number and returning a match objects filtering
1955 the files to be detailed when displaying the revision.
1957 the files to be detailed when displaying the revision.
1956 """
1958 """
1957 opt2revset = {
1959 opt2revset = {
1958 'no_merges': ('not merge()', None),
1960 'no_merges': ('not merge()', None),
1959 'only_merges': ('merge()', None),
1961 'only_merges': ('merge()', None),
1960 '_ancestors': ('ancestors(%(val)s)', None),
1962 '_ancestors': ('ancestors(%(val)s)', None),
1961 '_fancestors': ('_firstancestors(%(val)s)', None),
1963 '_fancestors': ('_firstancestors(%(val)s)', None),
1962 '_descendants': ('descendants(%(val)s)', None),
1964 '_descendants': ('descendants(%(val)s)', None),
1963 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1965 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1964 '_matchfiles': ('_matchfiles(%(val)s)', None),
1966 '_matchfiles': ('_matchfiles(%(val)s)', None),
1965 'date': ('date(%(val)r)', None),
1967 'date': ('date(%(val)r)', None),
1966 'branch': ('branch(%(val)r)', ' or '),
1968 'branch': ('branch(%(val)r)', ' or '),
1967 '_patslog': ('filelog(%(val)r)', ' or '),
1969 '_patslog': ('filelog(%(val)r)', ' or '),
1968 '_patsfollow': ('follow(%(val)r)', ' or '),
1970 '_patsfollow': ('follow(%(val)r)', ' or '),
1969 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1971 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1970 'keyword': ('keyword(%(val)r)', ' or '),
1972 'keyword': ('keyword(%(val)r)', ' or '),
1971 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1973 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1972 'user': ('user(%(val)r)', ' or '),
1974 'user': ('user(%(val)r)', ' or '),
1973 }
1975 }
1974
1976
1975 opts = dict(opts)
1977 opts = dict(opts)
1976 # follow or not follow?
1978 # follow or not follow?
1977 follow = opts.get('follow') or opts.get('follow_first')
1979 follow = opts.get('follow') or opts.get('follow_first')
1978 if opts.get('follow_first'):
1980 if opts.get('follow_first'):
1979 followfirst = 1
1981 followfirst = 1
1980 else:
1982 else:
1981 followfirst = 0
1983 followfirst = 0
1982 # --follow with FILE behavior depends on revs...
1984 # --follow with FILE behavior depends on revs...
1983 it = iter(revs)
1985 it = iter(revs)
1984 startrev = it.next()
1986 startrev = it.next()
1985 followdescendants = startrev < next(it, startrev)
1987 followdescendants = startrev < next(it, startrev)
1986
1988
1987 # branch and only_branch are really aliases and must be handled at
1989 # branch and only_branch are really aliases and must be handled at
1988 # the same time
1990 # the same time
1989 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1991 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1990 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1992 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1991 # pats/include/exclude are passed to match.match() directly in
1993 # pats/include/exclude are passed to match.match() directly in
1992 # _matchfiles() revset but walkchangerevs() builds its matcher with
1994 # _matchfiles() revset but walkchangerevs() builds its matcher with
1993 # scmutil.match(). The difference is input pats are globbed on
1995 # scmutil.match(). The difference is input pats are globbed on
1994 # platforms without shell expansion (windows).
1996 # platforms without shell expansion (windows).
1995 wctx = repo[None]
1997 wctx = repo[None]
1996 match, pats = scmutil.matchandpats(wctx, pats, opts)
1998 match, pats = scmutil.matchandpats(wctx, pats, opts)
1997 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1999 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1998 opts.get('removed'))
2000 opts.get('removed'))
1999 if not slowpath:
2001 if not slowpath:
2000 for f in match.files():
2002 for f in match.files():
2001 if follow and f not in wctx:
2003 if follow and f not in wctx:
2002 # If the file exists, it may be a directory, so let it
2004 # If the file exists, it may be a directory, so let it
2003 # take the slow path.
2005 # take the slow path.
2004 if os.path.exists(repo.wjoin(f)):
2006 if os.path.exists(repo.wjoin(f)):
2005 slowpath = True
2007 slowpath = True
2006 continue
2008 continue
2007 else:
2009 else:
2008 raise error.Abort(_('cannot follow file not in parent '
2010 raise error.Abort(_('cannot follow file not in parent '
2009 'revision: "%s"') % f)
2011 'revision: "%s"') % f)
2010 filelog = repo.file(f)
2012 filelog = repo.file(f)
2011 if not filelog:
2013 if not filelog:
2012 # A zero count may be a directory or deleted file, so
2014 # A zero count may be a directory or deleted file, so
2013 # try to find matching entries on the slow path.
2015 # try to find matching entries on the slow path.
2014 if follow:
2016 if follow:
2015 raise error.Abort(
2017 raise error.Abort(
2016 _('cannot follow nonexistent file: "%s"') % f)
2018 _('cannot follow nonexistent file: "%s"') % f)
2017 slowpath = True
2019 slowpath = True
2018
2020
2019 # We decided to fall back to the slowpath because at least one
2021 # We decided to fall back to the slowpath because at least one
2020 # of the paths was not a file. Check to see if at least one of them
2022 # of the paths was not a file. Check to see if at least one of them
2021 # existed in history - in that case, we'll continue down the
2023 # existed in history - in that case, we'll continue down the
2022 # slowpath; otherwise, we can turn off the slowpath
2024 # slowpath; otherwise, we can turn off the slowpath
2023 if slowpath:
2025 if slowpath:
2024 for path in match.files():
2026 for path in match.files():
2025 if path == '.' or path in repo.store:
2027 if path == '.' or path in repo.store:
2026 break
2028 break
2027 else:
2029 else:
2028 slowpath = False
2030 slowpath = False
2029
2031
2030 fpats = ('_patsfollow', '_patsfollowfirst')
2032 fpats = ('_patsfollow', '_patsfollowfirst')
2031 fnopats = (('_ancestors', '_fancestors'),
2033 fnopats = (('_ancestors', '_fancestors'),
2032 ('_descendants', '_fdescendants'))
2034 ('_descendants', '_fdescendants'))
2033 if slowpath:
2035 if slowpath:
2034 # See walkchangerevs() slow path.
2036 # See walkchangerevs() slow path.
2035 #
2037 #
2036 # pats/include/exclude cannot be represented as separate
2038 # pats/include/exclude cannot be represented as separate
2037 # revset expressions as their filtering logic applies at file
2039 # revset expressions as their filtering logic applies at file
2038 # level. For instance "-I a -X a" matches a revision touching
2040 # level. For instance "-I a -X a" matches a revision touching
2039 # "a" and "b" while "file(a) and not file(b)" does
2041 # "a" and "b" while "file(a) and not file(b)" does
2040 # not. Besides, filesets are evaluated against the working
2042 # not. Besides, filesets are evaluated against the working
2041 # directory.
2043 # directory.
2042 matchargs = ['r:', 'd:relpath']
2044 matchargs = ['r:', 'd:relpath']
2043 for p in pats:
2045 for p in pats:
2044 matchargs.append('p:' + p)
2046 matchargs.append('p:' + p)
2045 for p in opts.get('include', []):
2047 for p in opts.get('include', []):
2046 matchargs.append('i:' + p)
2048 matchargs.append('i:' + p)
2047 for p in opts.get('exclude', []):
2049 for p in opts.get('exclude', []):
2048 matchargs.append('x:' + p)
2050 matchargs.append('x:' + p)
2049 matchargs = ','.join(('%r' % p) for p in matchargs)
2051 matchargs = ','.join(('%r' % p) for p in matchargs)
2050 opts['_matchfiles'] = matchargs
2052 opts['_matchfiles'] = matchargs
2051 if follow:
2053 if follow:
2052 opts[fnopats[0][followfirst]] = '.'
2054 opts[fnopats[0][followfirst]] = '.'
2053 else:
2055 else:
2054 if follow:
2056 if follow:
2055 if pats:
2057 if pats:
2056 # follow() revset interprets its file argument as a
2058 # follow() revset interprets its file argument as a
2057 # manifest entry, so use match.files(), not pats.
2059 # manifest entry, so use match.files(), not pats.
2058 opts[fpats[followfirst]] = list(match.files())
2060 opts[fpats[followfirst]] = list(match.files())
2059 else:
2061 else:
2060 op = fnopats[followdescendants][followfirst]
2062 op = fnopats[followdescendants][followfirst]
2061 opts[op] = 'rev(%d)' % startrev
2063 opts[op] = 'rev(%d)' % startrev
2062 else:
2064 else:
2063 opts['_patslog'] = list(pats)
2065 opts['_patslog'] = list(pats)
2064
2066
2065 filematcher = None
2067 filematcher = None
2066 if opts.get('patch') or opts.get('stat'):
2068 if opts.get('patch') or opts.get('stat'):
2067 # When following files, track renames via a special matcher.
2069 # When following files, track renames via a special matcher.
2068 # If we're forced to take the slowpath it means we're following
2070 # If we're forced to take the slowpath it means we're following
2069 # at least one pattern/directory, so don't bother with rename tracking.
2071 # at least one pattern/directory, so don't bother with rename tracking.
2070 if follow and not match.always() and not slowpath:
2072 if follow and not match.always() and not slowpath:
2071 # _makefollowlogfilematcher expects its files argument to be
2073 # _makefollowlogfilematcher expects its files argument to be
2072 # relative to the repo root, so use match.files(), not pats.
2074 # relative to the repo root, so use match.files(), not pats.
2073 filematcher = _makefollowlogfilematcher(repo, match.files(),
2075 filematcher = _makefollowlogfilematcher(repo, match.files(),
2074 followfirst)
2076 followfirst)
2075 else:
2077 else:
2076 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2078 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2077 if filematcher is None:
2079 if filematcher is None:
2078 filematcher = lambda rev: match
2080 filematcher = lambda rev: match
2079
2081
2080 expr = []
2082 expr = []
2081 for op, val in sorted(opts.iteritems()):
2083 for op, val in sorted(opts.iteritems()):
2082 if not val:
2084 if not val:
2083 continue
2085 continue
2084 if op not in opt2revset:
2086 if op not in opt2revset:
2085 continue
2087 continue
2086 revop, andor = opt2revset[op]
2088 revop, andor = opt2revset[op]
2087 if '%(val)' not in revop:
2089 if '%(val)' not in revop:
2088 expr.append(revop)
2090 expr.append(revop)
2089 else:
2091 else:
2090 if not isinstance(val, list):
2092 if not isinstance(val, list):
2091 e = revop % {'val': val}
2093 e = revop % {'val': val}
2092 else:
2094 else:
2093 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2095 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2094 expr.append(e)
2096 expr.append(e)
2095
2097
2096 if expr:
2098 if expr:
2097 expr = '(' + ' and '.join(expr) + ')'
2099 expr = '(' + ' and '.join(expr) + ')'
2098 else:
2100 else:
2099 expr = None
2101 expr = None
2100 return expr, filematcher
2102 return expr, filematcher
2101
2103
2102 def _logrevs(repo, opts):
2104 def _logrevs(repo, opts):
2103 # Default --rev value depends on --follow but --follow behavior
2105 # Default --rev value depends on --follow but --follow behavior
2104 # depends on revisions resolved from --rev...
2106 # depends on revisions resolved from --rev...
2105 follow = opts.get('follow') or opts.get('follow_first')
2107 follow = opts.get('follow') or opts.get('follow_first')
2106 if opts.get('rev'):
2108 if opts.get('rev'):
2107 revs = scmutil.revrange(repo, opts['rev'])
2109 revs = scmutil.revrange(repo, opts['rev'])
2108 elif follow and repo.dirstate.p1() == nullid:
2110 elif follow and repo.dirstate.p1() == nullid:
2109 revs = revset.baseset()
2111 revs = revset.baseset()
2110 elif follow:
2112 elif follow:
2111 revs = repo.revs('reverse(:.)')
2113 revs = repo.revs('reverse(:.)')
2112 else:
2114 else:
2113 revs = revset.spanset(repo)
2115 revs = revset.spanset(repo)
2114 revs.reverse()
2116 revs.reverse()
2115 return revs
2117 return revs
2116
2118
2117 def getgraphlogrevs(repo, pats, opts):
2119 def getgraphlogrevs(repo, pats, opts):
2118 """Return (revs, expr, filematcher) where revs is an iterable of
2120 """Return (revs, expr, filematcher) where revs is an iterable of
2119 revision numbers, expr is a revset string built from log options
2121 revision numbers, expr is a revset string built from log options
2120 and file patterns or None, and used to filter 'revs'. If --stat or
2122 and file patterns or None, and used to filter 'revs'. If --stat or
2121 --patch are not passed filematcher is None. Otherwise it is a
2123 --patch are not passed filematcher is None. Otherwise it is a
2122 callable taking a revision number and returning a match objects
2124 callable taking a revision number and returning a match objects
2123 filtering the files to be detailed when displaying the revision.
2125 filtering the files to be detailed when displaying the revision.
2124 """
2126 """
2125 limit = loglimit(opts)
2127 limit = loglimit(opts)
2126 revs = _logrevs(repo, opts)
2128 revs = _logrevs(repo, opts)
2127 if not revs:
2129 if not revs:
2128 return revset.baseset(), None, None
2130 return revset.baseset(), None, None
2129 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2131 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2130 if opts.get('rev'):
2132 if opts.get('rev'):
2131 # User-specified revs might be unsorted, but don't sort before
2133 # User-specified revs might be unsorted, but don't sort before
2132 # _makelogrevset because it might depend on the order of revs
2134 # _makelogrevset because it might depend on the order of revs
2133 revs.sort(reverse=True)
2135 revs.sort(reverse=True)
2134 if expr:
2136 if expr:
2135 # Revset matchers often operate faster on revisions in changelog
2137 # Revset matchers often operate faster on revisions in changelog
2136 # order, because most filters deal with the changelog.
2138 # order, because most filters deal with the changelog.
2137 revs.reverse()
2139 revs.reverse()
2138 matcher = revset.match(repo.ui, expr)
2140 matcher = revset.match(repo.ui, expr)
2139 # Revset matches can reorder revisions. "A or B" typically returns
2141 # Revset matches can reorder revisions. "A or B" typically returns
2140 # returns the revision matching A then the revision matching B. Sort
2142 # returns the revision matching A then the revision matching B. Sort
2141 # again to fix that.
2143 # again to fix that.
2142 revs = matcher(repo, revs)
2144 revs = matcher(repo, revs)
2143 revs.sort(reverse=True)
2145 revs.sort(reverse=True)
2144 if limit is not None:
2146 if limit is not None:
2145 limitedrevs = []
2147 limitedrevs = []
2146 for idx, rev in enumerate(revs):
2148 for idx, rev in enumerate(revs):
2147 if idx >= limit:
2149 if idx >= limit:
2148 break
2150 break
2149 limitedrevs.append(rev)
2151 limitedrevs.append(rev)
2150 revs = revset.baseset(limitedrevs)
2152 revs = revset.baseset(limitedrevs)
2151
2153
2152 return revs, expr, filematcher
2154 return revs, expr, filematcher
2153
2155
2154 def getlogrevs(repo, pats, opts):
2156 def getlogrevs(repo, pats, opts):
2155 """Return (revs, expr, filematcher) where revs is an iterable of
2157 """Return (revs, expr, filematcher) where revs is an iterable of
2156 revision numbers, expr is a revset string built from log options
2158 revision numbers, expr is a revset string built from log options
2157 and file patterns or None, and used to filter 'revs'. If --stat or
2159 and file patterns or None, and used to filter 'revs'. If --stat or
2158 --patch are not passed filematcher is None. Otherwise it is a
2160 --patch are not passed filematcher is None. Otherwise it is a
2159 callable taking a revision number and returning a match objects
2161 callable taking a revision number and returning a match objects
2160 filtering the files to be detailed when displaying the revision.
2162 filtering the files to be detailed when displaying the revision.
2161 """
2163 """
2162 limit = loglimit(opts)
2164 limit = loglimit(opts)
2163 revs = _logrevs(repo, opts)
2165 revs = _logrevs(repo, opts)
2164 if not revs:
2166 if not revs:
2165 return revset.baseset([]), None, None
2167 return revset.baseset([]), None, None
2166 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2168 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2167 if expr:
2169 if expr:
2168 # Revset matchers often operate faster on revisions in changelog
2170 # Revset matchers often operate faster on revisions in changelog
2169 # order, because most filters deal with the changelog.
2171 # order, because most filters deal with the changelog.
2170 if not opts.get('rev'):
2172 if not opts.get('rev'):
2171 revs.reverse()
2173 revs.reverse()
2172 matcher = revset.match(repo.ui, expr)
2174 matcher = revset.match(repo.ui, expr)
2173 # Revset matches can reorder revisions. "A or B" typically returns
2175 # Revset matches can reorder revisions. "A or B" typically returns
2174 # returns the revision matching A then the revision matching B. Sort
2176 # returns the revision matching A then the revision matching B. Sort
2175 # again to fix that.
2177 # again to fix that.
2176 fixopts = ['branch', 'only_branch', 'keyword', 'user']
2178 fixopts = ['branch', 'only_branch', 'keyword', 'user']
2177 oldrevs = revs
2179 oldrevs = revs
2178 revs = matcher(repo, revs)
2180 revs = matcher(repo, revs)
2179 if not opts.get('rev'):
2181 if not opts.get('rev'):
2180 revs.sort(reverse=True)
2182 revs.sort(reverse=True)
2181 elif len(pats) > 1 or any(len(opts.get(op, [])) > 1 for op in fixopts):
2183 elif len(pats) > 1 or any(len(opts.get(op, [])) > 1 for op in fixopts):
2182 # XXX "A or B" is known to change the order; fix it by filtering
2184 # XXX "A or B" is known to change the order; fix it by filtering
2183 # matched set again (issue5100)
2185 # matched set again (issue5100)
2184 revs = oldrevs & revs
2186 revs = oldrevs & revs
2185 if limit is not None:
2187 if limit is not None:
2186 limitedrevs = []
2188 limitedrevs = []
2187 for idx, r in enumerate(revs):
2189 for idx, r in enumerate(revs):
2188 if limit <= idx:
2190 if limit <= idx:
2189 break
2191 break
2190 limitedrevs.append(r)
2192 limitedrevs.append(r)
2191 revs = revset.baseset(limitedrevs)
2193 revs = revset.baseset(limitedrevs)
2192
2194
2193 return revs, expr, filematcher
2195 return revs, expr, filematcher
2194
2196
2195 def _graphnodeformatter(ui, displayer):
2197 def _graphnodeformatter(ui, displayer):
2196 spec = ui.config('ui', 'graphnodetemplate')
2198 spec = ui.config('ui', 'graphnodetemplate')
2197 if not spec:
2199 if not spec:
2198 return templatekw.showgraphnode # fast path for "{graphnode}"
2200 return templatekw.showgraphnode # fast path for "{graphnode}"
2199
2201
2200 templ = formatter.gettemplater(ui, 'graphnode', spec)
2202 templ = formatter.gettemplater(ui, 'graphnode', spec)
2201 cache = {}
2203 cache = {}
2202 if isinstance(displayer, changeset_templater):
2204 if isinstance(displayer, changeset_templater):
2203 cache = displayer.cache # reuse cache of slow templates
2205 cache = displayer.cache # reuse cache of slow templates
2204 props = templatekw.keywords.copy()
2206 props = templatekw.keywords.copy()
2205 props['templ'] = templ
2207 props['templ'] = templ
2206 props['cache'] = cache
2208 props['cache'] = cache
2207 def formatnode(repo, ctx):
2209 def formatnode(repo, ctx):
2208 props['ctx'] = ctx
2210 props['ctx'] = ctx
2209 props['repo'] = repo
2211 props['repo'] = repo
2210 props['ui'] = repo.ui
2212 props['ui'] = repo.ui
2211 props['revcache'] = {}
2213 props['revcache'] = {}
2212 return templater.stringify(templ('graphnode', **props))
2214 return templater.stringify(templ('graphnode', **props))
2213 return formatnode
2215 return formatnode
2214
2216
2215 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2217 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2216 filematcher=None):
2218 filematcher=None):
2217 formatnode = _graphnodeformatter(ui, displayer)
2219 formatnode = _graphnodeformatter(ui, displayer)
2218 state = graphmod.asciistate()
2220 state = graphmod.asciistate()
2219 for rev, type, ctx, parents in dag:
2221 for rev, type, ctx, parents in dag:
2220 char = formatnode(repo, ctx)
2222 char = formatnode(repo, ctx)
2221 copies = None
2223 copies = None
2222 if getrenamed and ctx.rev():
2224 if getrenamed and ctx.rev():
2223 copies = []
2225 copies = []
2224 for fn in ctx.files():
2226 for fn in ctx.files():
2225 rename = getrenamed(fn, ctx.rev())
2227 rename = getrenamed(fn, ctx.rev())
2226 if rename:
2228 if rename:
2227 copies.append((fn, rename[0]))
2229 copies.append((fn, rename[0]))
2228 revmatchfn = None
2230 revmatchfn = None
2229 if filematcher is not None:
2231 if filematcher is not None:
2230 revmatchfn = filematcher(ctx.rev())
2232 revmatchfn = filematcher(ctx.rev())
2231 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2233 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2232 lines = displayer.hunk.pop(rev).split('\n')
2234 lines = displayer.hunk.pop(rev).split('\n')
2233 if not lines[-1]:
2235 if not lines[-1]:
2234 del lines[-1]
2236 del lines[-1]
2235 displayer.flush(ctx)
2237 displayer.flush(ctx)
2236 edges = edgefn(type, char, lines, state, rev, parents)
2238 edges = edgefn(type, char, lines, state, rev, parents)
2237 for type, char, lines, coldata in edges:
2239 for type, char, lines, coldata in edges:
2238 graphmod.ascii(ui, state, type, char, lines, coldata)
2240 graphmod.ascii(ui, state, type, char, lines, coldata)
2239 displayer.close()
2241 displayer.close()
2240
2242
2241 def graphlog(ui, repo, *pats, **opts):
2243 def graphlog(ui, repo, *pats, **opts):
2242 # Parameters are identical to log command ones
2244 # Parameters are identical to log command ones
2243 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2245 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2244 revdag = graphmod.dagwalker(repo, revs)
2246 revdag = graphmod.dagwalker(repo, revs)
2245
2247
2246 getrenamed = None
2248 getrenamed = None
2247 if opts.get('copies'):
2249 if opts.get('copies'):
2248 endrev = None
2250 endrev = None
2249 if opts.get('rev'):
2251 if opts.get('rev'):
2250 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2252 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2251 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2253 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2252 displayer = show_changeset(ui, repo, opts, buffered=True)
2254 displayer = show_changeset(ui, repo, opts, buffered=True)
2253 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2255 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2254 filematcher)
2256 filematcher)
2255
2257
2256 def checkunsupportedgraphflags(pats, opts):
2258 def checkunsupportedgraphflags(pats, opts):
2257 for op in ["newest_first"]:
2259 for op in ["newest_first"]:
2258 if op in opts and opts[op]:
2260 if op in opts and opts[op]:
2259 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2261 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2260 % op.replace("_", "-"))
2262 % op.replace("_", "-"))
2261
2263
2262 def graphrevs(repo, nodes, opts):
2264 def graphrevs(repo, nodes, opts):
2263 limit = loglimit(opts)
2265 limit = loglimit(opts)
2264 nodes.reverse()
2266 nodes.reverse()
2265 if limit is not None:
2267 if limit is not None:
2266 nodes = nodes[:limit]
2268 nodes = nodes[:limit]
2267 return graphmod.nodes(repo, nodes)
2269 return graphmod.nodes(repo, nodes)
2268
2270
2269 def add(ui, repo, match, prefix, explicitonly, **opts):
2271 def add(ui, repo, match, prefix, explicitonly, **opts):
2270 join = lambda f: os.path.join(prefix, f)
2272 join = lambda f: os.path.join(prefix, f)
2271 bad = []
2273 bad = []
2272
2274
2273 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2275 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2274 names = []
2276 names = []
2275 wctx = repo[None]
2277 wctx = repo[None]
2276 cca = None
2278 cca = None
2277 abort, warn = scmutil.checkportabilityalert(ui)
2279 abort, warn = scmutil.checkportabilityalert(ui)
2278 if abort or warn:
2280 if abort or warn:
2279 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2281 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2280
2282
2281 badmatch = matchmod.badmatch(match, badfn)
2283 badmatch = matchmod.badmatch(match, badfn)
2282 dirstate = repo.dirstate
2284 dirstate = repo.dirstate
2283 # We don't want to just call wctx.walk here, since it would return a lot of
2285 # We don't want to just call wctx.walk here, since it would return a lot of
2284 # clean files, which we aren't interested in and takes time.
2286 # clean files, which we aren't interested in and takes time.
2285 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2287 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2286 True, False, full=False)):
2288 True, False, full=False)):
2287 exact = match.exact(f)
2289 exact = match.exact(f)
2288 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2290 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2289 if cca:
2291 if cca:
2290 cca(f)
2292 cca(f)
2291 names.append(f)
2293 names.append(f)
2292 if ui.verbose or not exact:
2294 if ui.verbose or not exact:
2293 ui.status(_('adding %s\n') % match.rel(f))
2295 ui.status(_('adding %s\n') % match.rel(f))
2294
2296
2295 for subpath in sorted(wctx.substate):
2297 for subpath in sorted(wctx.substate):
2296 sub = wctx.sub(subpath)
2298 sub = wctx.sub(subpath)
2297 try:
2299 try:
2298 submatch = matchmod.subdirmatcher(subpath, match)
2300 submatch = matchmod.subdirmatcher(subpath, match)
2299 if opts.get('subrepos'):
2301 if opts.get('subrepos'):
2300 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2302 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2301 else:
2303 else:
2302 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2304 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2303 except error.LookupError:
2305 except error.LookupError:
2304 ui.status(_("skipping missing subrepository: %s\n")
2306 ui.status(_("skipping missing subrepository: %s\n")
2305 % join(subpath))
2307 % join(subpath))
2306
2308
2307 if not opts.get('dry_run'):
2309 if not opts.get('dry_run'):
2308 rejected = wctx.add(names, prefix)
2310 rejected = wctx.add(names, prefix)
2309 bad.extend(f for f in rejected if f in match.files())
2311 bad.extend(f for f in rejected if f in match.files())
2310 return bad
2312 return bad
2311
2313
2312 def forget(ui, repo, match, prefix, explicitonly):
2314 def forget(ui, repo, match, prefix, explicitonly):
2313 join = lambda f: os.path.join(prefix, f)
2315 join = lambda f: os.path.join(prefix, f)
2314 bad = []
2316 bad = []
2315 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2317 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2316 wctx = repo[None]
2318 wctx = repo[None]
2317 forgot = []
2319 forgot = []
2318
2320
2319 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2321 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2320 forget = sorted(s[0] + s[1] + s[3] + s[6])
2322 forget = sorted(s[0] + s[1] + s[3] + s[6])
2321 if explicitonly:
2323 if explicitonly:
2322 forget = [f for f in forget if match.exact(f)]
2324 forget = [f for f in forget if match.exact(f)]
2323
2325
2324 for subpath in sorted(wctx.substate):
2326 for subpath in sorted(wctx.substate):
2325 sub = wctx.sub(subpath)
2327 sub = wctx.sub(subpath)
2326 try:
2328 try:
2327 submatch = matchmod.subdirmatcher(subpath, match)
2329 submatch = matchmod.subdirmatcher(subpath, match)
2328 subbad, subforgot = sub.forget(submatch, prefix)
2330 subbad, subforgot = sub.forget(submatch, prefix)
2329 bad.extend([subpath + '/' + f for f in subbad])
2331 bad.extend([subpath + '/' + f for f in subbad])
2330 forgot.extend([subpath + '/' + f for f in subforgot])
2332 forgot.extend([subpath + '/' + f for f in subforgot])
2331 except error.LookupError:
2333 except error.LookupError:
2332 ui.status(_("skipping missing subrepository: %s\n")
2334 ui.status(_("skipping missing subrepository: %s\n")
2333 % join(subpath))
2335 % join(subpath))
2334
2336
2335 if not explicitonly:
2337 if not explicitonly:
2336 for f in match.files():
2338 for f in match.files():
2337 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2339 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2338 if f not in forgot:
2340 if f not in forgot:
2339 if repo.wvfs.exists(f):
2341 if repo.wvfs.exists(f):
2340 # Don't complain if the exact case match wasn't given.
2342 # Don't complain if the exact case match wasn't given.
2341 # But don't do this until after checking 'forgot', so
2343 # But don't do this until after checking 'forgot', so
2342 # that subrepo files aren't normalized, and this op is
2344 # that subrepo files aren't normalized, and this op is
2343 # purely from data cached by the status walk above.
2345 # purely from data cached by the status walk above.
2344 if repo.dirstate.normalize(f) in repo.dirstate:
2346 if repo.dirstate.normalize(f) in repo.dirstate:
2345 continue
2347 continue
2346 ui.warn(_('not removing %s: '
2348 ui.warn(_('not removing %s: '
2347 'file is already untracked\n')
2349 'file is already untracked\n')
2348 % match.rel(f))
2350 % match.rel(f))
2349 bad.append(f)
2351 bad.append(f)
2350
2352
2351 for f in forget:
2353 for f in forget:
2352 if ui.verbose or not match.exact(f):
2354 if ui.verbose or not match.exact(f):
2353 ui.status(_('removing %s\n') % match.rel(f))
2355 ui.status(_('removing %s\n') % match.rel(f))
2354
2356
2355 rejected = wctx.forget(forget, prefix)
2357 rejected = wctx.forget(forget, prefix)
2356 bad.extend(f for f in rejected if f in match.files())
2358 bad.extend(f for f in rejected if f in match.files())
2357 forgot.extend(f for f in forget if f not in rejected)
2359 forgot.extend(f for f in forget if f not in rejected)
2358 return bad, forgot
2360 return bad, forgot
2359
2361
2360 def files(ui, ctx, m, fm, fmt, subrepos):
2362 def files(ui, ctx, m, fm, fmt, subrepos):
2361 rev = ctx.rev()
2363 rev = ctx.rev()
2362 ret = 1
2364 ret = 1
2363 ds = ctx.repo().dirstate
2365 ds = ctx.repo().dirstate
2364
2366
2365 for f in ctx.matches(m):
2367 for f in ctx.matches(m):
2366 if rev is None and ds[f] == 'r':
2368 if rev is None and ds[f] == 'r':
2367 continue
2369 continue
2368 fm.startitem()
2370 fm.startitem()
2369 if ui.verbose:
2371 if ui.verbose:
2370 fc = ctx[f]
2372 fc = ctx[f]
2371 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2373 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2372 fm.data(abspath=f)
2374 fm.data(abspath=f)
2373 fm.write('path', fmt, m.rel(f))
2375 fm.write('path', fmt, m.rel(f))
2374 ret = 0
2376 ret = 0
2375
2377
2376 for subpath in sorted(ctx.substate):
2378 for subpath in sorted(ctx.substate):
2377 def matchessubrepo(subpath):
2379 def matchessubrepo(subpath):
2378 return (m.exact(subpath)
2380 return (m.exact(subpath)
2379 or any(f.startswith(subpath + '/') for f in m.files()))
2381 or any(f.startswith(subpath + '/') for f in m.files()))
2380
2382
2381 if subrepos or matchessubrepo(subpath):
2383 if subrepos or matchessubrepo(subpath):
2382 sub = ctx.sub(subpath)
2384 sub = ctx.sub(subpath)
2383 try:
2385 try:
2384 submatch = matchmod.subdirmatcher(subpath, m)
2386 submatch = matchmod.subdirmatcher(subpath, m)
2385 recurse = m.exact(subpath) or subrepos
2387 recurse = m.exact(subpath) or subrepos
2386 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2388 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2387 ret = 0
2389 ret = 0
2388 except error.LookupError:
2390 except error.LookupError:
2389 ui.status(_("skipping missing subrepository: %s\n")
2391 ui.status(_("skipping missing subrepository: %s\n")
2390 % m.abs(subpath))
2392 % m.abs(subpath))
2391
2393
2392 return ret
2394 return ret
2393
2395
2394 def remove(ui, repo, m, prefix, after, force, subrepos):
2396 def remove(ui, repo, m, prefix, after, force, subrepos):
2395 join = lambda f: os.path.join(prefix, f)
2397 join = lambda f: os.path.join(prefix, f)
2396 ret = 0
2398 ret = 0
2397 s = repo.status(match=m, clean=True)
2399 s = repo.status(match=m, clean=True)
2398 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2400 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2399
2401
2400 wctx = repo[None]
2402 wctx = repo[None]
2401
2403
2402 for subpath in sorted(wctx.substate):
2404 for subpath in sorted(wctx.substate):
2403 def matchessubrepo(matcher, subpath):
2405 def matchessubrepo(matcher, subpath):
2404 if matcher.exact(subpath):
2406 if matcher.exact(subpath):
2405 return True
2407 return True
2406 for f in matcher.files():
2408 for f in matcher.files():
2407 if f.startswith(subpath):
2409 if f.startswith(subpath):
2408 return True
2410 return True
2409 return False
2411 return False
2410
2412
2411 if subrepos or matchessubrepo(m, subpath):
2413 if subrepos or matchessubrepo(m, subpath):
2412 sub = wctx.sub(subpath)
2414 sub = wctx.sub(subpath)
2413 try:
2415 try:
2414 submatch = matchmod.subdirmatcher(subpath, m)
2416 submatch = matchmod.subdirmatcher(subpath, m)
2415 if sub.removefiles(submatch, prefix, after, force, subrepos):
2417 if sub.removefiles(submatch, prefix, after, force, subrepos):
2416 ret = 1
2418 ret = 1
2417 except error.LookupError:
2419 except error.LookupError:
2418 ui.status(_("skipping missing subrepository: %s\n")
2420 ui.status(_("skipping missing subrepository: %s\n")
2419 % join(subpath))
2421 % join(subpath))
2420
2422
2421 # warn about failure to delete explicit files/dirs
2423 # warn about failure to delete explicit files/dirs
2422 deleteddirs = util.dirs(deleted)
2424 deleteddirs = util.dirs(deleted)
2423 for f in m.files():
2425 for f in m.files():
2424 def insubrepo():
2426 def insubrepo():
2425 for subpath in wctx.substate:
2427 for subpath in wctx.substate:
2426 if f.startswith(subpath):
2428 if f.startswith(subpath):
2427 return True
2429 return True
2428 return False
2430 return False
2429
2431
2430 isdir = f in deleteddirs or wctx.hasdir(f)
2432 isdir = f in deleteddirs or wctx.hasdir(f)
2431 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2433 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2432 continue
2434 continue
2433
2435
2434 if repo.wvfs.exists(f):
2436 if repo.wvfs.exists(f):
2435 if repo.wvfs.isdir(f):
2437 if repo.wvfs.isdir(f):
2436 ui.warn(_('not removing %s: no tracked files\n')
2438 ui.warn(_('not removing %s: no tracked files\n')
2437 % m.rel(f))
2439 % m.rel(f))
2438 else:
2440 else:
2439 ui.warn(_('not removing %s: file is untracked\n')
2441 ui.warn(_('not removing %s: file is untracked\n')
2440 % m.rel(f))
2442 % m.rel(f))
2441 # missing files will generate a warning elsewhere
2443 # missing files will generate a warning elsewhere
2442 ret = 1
2444 ret = 1
2443
2445
2444 if force:
2446 if force:
2445 list = modified + deleted + clean + added
2447 list = modified + deleted + clean + added
2446 elif after:
2448 elif after:
2447 list = deleted
2449 list = deleted
2448 for f in modified + added + clean:
2450 for f in modified + added + clean:
2449 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2451 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2450 ret = 1
2452 ret = 1
2451 else:
2453 else:
2452 list = deleted + clean
2454 list = deleted + clean
2453 for f in modified:
2455 for f in modified:
2454 ui.warn(_('not removing %s: file is modified (use -f'
2456 ui.warn(_('not removing %s: file is modified (use -f'
2455 ' to force removal)\n') % m.rel(f))
2457 ' to force removal)\n') % m.rel(f))
2456 ret = 1
2458 ret = 1
2457 for f in added:
2459 for f in added:
2458 ui.warn(_('not removing %s: file has been marked for add'
2460 ui.warn(_('not removing %s: file has been marked for add'
2459 ' (use forget to undo)\n') % m.rel(f))
2461 ' (use forget to undo)\n') % m.rel(f))
2460 ret = 1
2462 ret = 1
2461
2463
2462 for f in sorted(list):
2464 for f in sorted(list):
2463 if ui.verbose or not m.exact(f):
2465 if ui.verbose or not m.exact(f):
2464 ui.status(_('removing %s\n') % m.rel(f))
2466 ui.status(_('removing %s\n') % m.rel(f))
2465
2467
2466 with repo.wlock():
2468 with repo.wlock():
2467 if not after:
2469 if not after:
2468 for f in list:
2470 for f in list:
2469 if f in added:
2471 if f in added:
2470 continue # we never unlink added files on remove
2472 continue # we never unlink added files on remove
2471 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2473 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2472 repo[None].forget(list)
2474 repo[None].forget(list)
2473
2475
2474 return ret
2476 return ret
2475
2477
2476 def cat(ui, repo, ctx, matcher, prefix, **opts):
2478 def cat(ui, repo, ctx, matcher, prefix, **opts):
2477 err = 1
2479 err = 1
2478
2480
2479 def write(path):
2481 def write(path):
2480 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2482 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2481 pathname=os.path.join(prefix, path))
2483 pathname=os.path.join(prefix, path))
2482 data = ctx[path].data()
2484 data = ctx[path].data()
2483 if opts.get('decode'):
2485 if opts.get('decode'):
2484 data = repo.wwritedata(path, data)
2486 data = repo.wwritedata(path, data)
2485 fp.write(data)
2487 fp.write(data)
2486 fp.close()
2488 fp.close()
2487
2489
2488 # Automation often uses hg cat on single files, so special case it
2490 # Automation often uses hg cat on single files, so special case it
2489 # for performance to avoid the cost of parsing the manifest.
2491 # for performance to avoid the cost of parsing the manifest.
2490 if len(matcher.files()) == 1 and not matcher.anypats():
2492 if len(matcher.files()) == 1 and not matcher.anypats():
2491 file = matcher.files()[0]
2493 file = matcher.files()[0]
2492 mf = repo.manifest
2494 mf = repo.manifest
2493 mfnode = ctx.manifestnode()
2495 mfnode = ctx.manifestnode()
2494 if mfnode and mf.find(mfnode, file)[0]:
2496 if mfnode and mf.find(mfnode, file)[0]:
2495 write(file)
2497 write(file)
2496 return 0
2498 return 0
2497
2499
2498 # Don't warn about "missing" files that are really in subrepos
2500 # Don't warn about "missing" files that are really in subrepos
2499 def badfn(path, msg):
2501 def badfn(path, msg):
2500 for subpath in ctx.substate:
2502 for subpath in ctx.substate:
2501 if path.startswith(subpath):
2503 if path.startswith(subpath):
2502 return
2504 return
2503 matcher.bad(path, msg)
2505 matcher.bad(path, msg)
2504
2506
2505 for abs in ctx.walk(matchmod.badmatch(matcher, badfn)):
2507 for abs in ctx.walk(matchmod.badmatch(matcher, badfn)):
2506 write(abs)
2508 write(abs)
2507 err = 0
2509 err = 0
2508
2510
2509 for subpath in sorted(ctx.substate):
2511 for subpath in sorted(ctx.substate):
2510 sub = ctx.sub(subpath)
2512 sub = ctx.sub(subpath)
2511 try:
2513 try:
2512 submatch = matchmod.subdirmatcher(subpath, matcher)
2514 submatch = matchmod.subdirmatcher(subpath, matcher)
2513
2515
2514 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2516 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2515 **opts):
2517 **opts):
2516 err = 0
2518 err = 0
2517 except error.RepoLookupError:
2519 except error.RepoLookupError:
2518 ui.status(_("skipping missing subrepository: %s\n")
2520 ui.status(_("skipping missing subrepository: %s\n")
2519 % os.path.join(prefix, subpath))
2521 % os.path.join(prefix, subpath))
2520
2522
2521 return err
2523 return err
2522
2524
2523 def commit(ui, repo, commitfunc, pats, opts):
2525 def commit(ui, repo, commitfunc, pats, opts):
2524 '''commit the specified files or all outstanding changes'''
2526 '''commit the specified files or all outstanding changes'''
2525 date = opts.get('date')
2527 date = opts.get('date')
2526 if date:
2528 if date:
2527 opts['date'] = util.parsedate(date)
2529 opts['date'] = util.parsedate(date)
2528 message = logmessage(ui, opts)
2530 message = logmessage(ui, opts)
2529 matcher = scmutil.match(repo[None], pats, opts)
2531 matcher = scmutil.match(repo[None], pats, opts)
2530
2532
2531 # extract addremove carefully -- this function can be called from a command
2533 # extract addremove carefully -- this function can be called from a command
2532 # that doesn't support addremove
2534 # that doesn't support addremove
2533 if opts.get('addremove'):
2535 if opts.get('addremove'):
2534 if scmutil.addremove(repo, matcher, "", opts) != 0:
2536 if scmutil.addremove(repo, matcher, "", opts) != 0:
2535 raise error.Abort(
2537 raise error.Abort(
2536 _("failed to mark all new/missing files as added/removed"))
2538 _("failed to mark all new/missing files as added/removed"))
2537
2539
2538 return commitfunc(ui, repo, message, matcher, opts)
2540 return commitfunc(ui, repo, message, matcher, opts)
2539
2541
2540 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2542 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2541 # avoid cycle context -> subrepo -> cmdutil
2543 # avoid cycle context -> subrepo -> cmdutil
2542 from . import context
2544 from . import context
2543
2545
2544 # amend will reuse the existing user if not specified, but the obsolete
2546 # amend will reuse the existing user if not specified, but the obsolete
2545 # marker creation requires that the current user's name is specified.
2547 # marker creation requires that the current user's name is specified.
2546 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2548 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2547 ui.username() # raise exception if username not set
2549 ui.username() # raise exception if username not set
2548
2550
2549 ui.note(_('amending changeset %s\n') % old)
2551 ui.note(_('amending changeset %s\n') % old)
2550 base = old.p1()
2552 base = old.p1()
2551 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2553 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2552
2554
2553 wlock = lock = newid = None
2555 wlock = lock = newid = None
2554 try:
2556 try:
2555 wlock = repo.wlock()
2557 wlock = repo.wlock()
2556 lock = repo.lock()
2558 lock = repo.lock()
2557 with repo.transaction('amend') as tr:
2559 with repo.transaction('amend') as tr:
2558 # See if we got a message from -m or -l, if not, open the editor
2560 # See if we got a message from -m or -l, if not, open the editor
2559 # with the message of the changeset to amend
2561 # with the message of the changeset to amend
2560 message = logmessage(ui, opts)
2562 message = logmessage(ui, opts)
2561 # ensure logfile does not conflict with later enforcement of the
2563 # ensure logfile does not conflict with later enforcement of the
2562 # message. potential logfile content has been processed by
2564 # message. potential logfile content has been processed by
2563 # `logmessage` anyway.
2565 # `logmessage` anyway.
2564 opts.pop('logfile')
2566 opts.pop('logfile')
2565 # First, do a regular commit to record all changes in the working
2567 # First, do a regular commit to record all changes in the working
2566 # directory (if there are any)
2568 # directory (if there are any)
2567 ui.callhooks = False
2569 ui.callhooks = False
2568 activebookmark = repo._bookmarks.active
2570 activebookmark = repo._bookmarks.active
2569 try:
2571 try:
2570 repo._bookmarks.active = None
2572 repo._bookmarks.active = None
2571 opts['message'] = 'temporary amend commit for %s' % old
2573 opts['message'] = 'temporary amend commit for %s' % old
2572 node = commit(ui, repo, commitfunc, pats, opts)
2574 node = commit(ui, repo, commitfunc, pats, opts)
2573 finally:
2575 finally:
2574 repo._bookmarks.active = activebookmark
2576 repo._bookmarks.active = activebookmark
2575 repo._bookmarks.recordchange(tr)
2577 repo._bookmarks.recordchange(tr)
2576 ui.callhooks = True
2578 ui.callhooks = True
2577 ctx = repo[node]
2579 ctx = repo[node]
2578
2580
2579 # Participating changesets:
2581 # Participating changesets:
2580 #
2582 #
2581 # node/ctx o - new (intermediate) commit that contains changes
2583 # node/ctx o - new (intermediate) commit that contains changes
2582 # | from working dir to go into amending commit
2584 # | from working dir to go into amending commit
2583 # | (or a workingctx if there were no changes)
2585 # | (or a workingctx if there were no changes)
2584 # |
2586 # |
2585 # old o - changeset to amend
2587 # old o - changeset to amend
2586 # |
2588 # |
2587 # base o - parent of amending changeset
2589 # base o - parent of amending changeset
2588
2590
2589 # Update extra dict from amended commit (e.g. to preserve graft
2591 # Update extra dict from amended commit (e.g. to preserve graft
2590 # source)
2592 # source)
2591 extra.update(old.extra())
2593 extra.update(old.extra())
2592
2594
2593 # Also update it from the intermediate commit or from the wctx
2595 # Also update it from the intermediate commit or from the wctx
2594 extra.update(ctx.extra())
2596 extra.update(ctx.extra())
2595
2597
2596 if len(old.parents()) > 1:
2598 if len(old.parents()) > 1:
2597 # ctx.files() isn't reliable for merges, so fall back to the
2599 # ctx.files() isn't reliable for merges, so fall back to the
2598 # slower repo.status() method
2600 # slower repo.status() method
2599 files = set([fn for st in repo.status(base, old)[:3]
2601 files = set([fn for st in repo.status(base, old)[:3]
2600 for fn in st])
2602 for fn in st])
2601 else:
2603 else:
2602 files = set(old.files())
2604 files = set(old.files())
2603
2605
2604 # Second, we use either the commit we just did, or if there were no
2606 # Second, we use either the commit we just did, or if there were no
2605 # changes the parent of the working directory as the version of the
2607 # changes the parent of the working directory as the version of the
2606 # files in the final amend commit
2608 # files in the final amend commit
2607 if node:
2609 if node:
2608 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2610 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2609
2611
2610 user = ctx.user()
2612 user = ctx.user()
2611 date = ctx.date()
2613 date = ctx.date()
2612 # Recompute copies (avoid recording a -> b -> a)
2614 # Recompute copies (avoid recording a -> b -> a)
2613 copied = copies.pathcopies(base, ctx)
2615 copied = copies.pathcopies(base, ctx)
2614 if old.p2:
2616 if old.p2:
2615 copied.update(copies.pathcopies(old.p2(), ctx))
2617 copied.update(copies.pathcopies(old.p2(), ctx))
2616
2618
2617 # Prune files which were reverted by the updates: if old
2619 # Prune files which were reverted by the updates: if old
2618 # introduced file X and our intermediate commit, node,
2620 # introduced file X and our intermediate commit, node,
2619 # renamed that file, then those two files are the same and
2621 # renamed that file, then those two files are the same and
2620 # we can discard X from our list of files. Likewise if X
2622 # we can discard X from our list of files. Likewise if X
2621 # was deleted, it's no longer relevant
2623 # was deleted, it's no longer relevant
2622 files.update(ctx.files())
2624 files.update(ctx.files())
2623
2625
2624 def samefile(f):
2626 def samefile(f):
2625 if f in ctx.manifest():
2627 if f in ctx.manifest():
2626 a = ctx.filectx(f)
2628 a = ctx.filectx(f)
2627 if f in base.manifest():
2629 if f in base.manifest():
2628 b = base.filectx(f)
2630 b = base.filectx(f)
2629 return (not a.cmp(b)
2631 return (not a.cmp(b)
2630 and a.flags() == b.flags())
2632 and a.flags() == b.flags())
2631 else:
2633 else:
2632 return False
2634 return False
2633 else:
2635 else:
2634 return f not in base.manifest()
2636 return f not in base.manifest()
2635 files = [f for f in files if not samefile(f)]
2637 files = [f for f in files if not samefile(f)]
2636
2638
2637 def filectxfn(repo, ctx_, path):
2639 def filectxfn(repo, ctx_, path):
2638 try:
2640 try:
2639 fctx = ctx[path]
2641 fctx = ctx[path]
2640 flags = fctx.flags()
2642 flags = fctx.flags()
2641 mctx = context.memfilectx(repo,
2643 mctx = context.memfilectx(repo,
2642 fctx.path(), fctx.data(),
2644 fctx.path(), fctx.data(),
2643 islink='l' in flags,
2645 islink='l' in flags,
2644 isexec='x' in flags,
2646 isexec='x' in flags,
2645 copied=copied.get(path))
2647 copied=copied.get(path))
2646 return mctx
2648 return mctx
2647 except KeyError:
2649 except KeyError:
2648 return None
2650 return None
2649 else:
2651 else:
2650 ui.note(_('copying changeset %s to %s\n') % (old, base))
2652 ui.note(_('copying changeset %s to %s\n') % (old, base))
2651
2653
2652 # Use version of files as in the old cset
2654 # Use version of files as in the old cset
2653 def filectxfn(repo, ctx_, path):
2655 def filectxfn(repo, ctx_, path):
2654 try:
2656 try:
2655 return old.filectx(path)
2657 return old.filectx(path)
2656 except KeyError:
2658 except KeyError:
2657 return None
2659 return None
2658
2660
2659 user = opts.get('user') or old.user()
2661 user = opts.get('user') or old.user()
2660 date = opts.get('date') or old.date()
2662 date = opts.get('date') or old.date()
2661 editform = mergeeditform(old, 'commit.amend')
2663 editform = mergeeditform(old, 'commit.amend')
2662 editor = getcommiteditor(editform=editform, **opts)
2664 editor = getcommiteditor(editform=editform, **opts)
2663 if not message:
2665 if not message:
2664 editor = getcommiteditor(edit=True, editform=editform)
2666 editor = getcommiteditor(edit=True, editform=editform)
2665 message = old.description()
2667 message = old.description()
2666
2668
2667 pureextra = extra.copy()
2669 pureextra = extra.copy()
2668 extra['amend_source'] = old.hex()
2670 extra['amend_source'] = old.hex()
2669
2671
2670 new = context.memctx(repo,
2672 new = context.memctx(repo,
2671 parents=[base.node(), old.p2().node()],
2673 parents=[base.node(), old.p2().node()],
2672 text=message,
2674 text=message,
2673 files=files,
2675 files=files,
2674 filectxfn=filectxfn,
2676 filectxfn=filectxfn,
2675 user=user,
2677 user=user,
2676 date=date,
2678 date=date,
2677 extra=extra,
2679 extra=extra,
2678 editor=editor)
2680 editor=editor)
2679
2681
2680 newdesc = changelog.stripdesc(new.description())
2682 newdesc = changelog.stripdesc(new.description())
2681 if ((not node)
2683 if ((not node)
2682 and newdesc == old.description()
2684 and newdesc == old.description()
2683 and user == old.user()
2685 and user == old.user()
2684 and date == old.date()
2686 and date == old.date()
2685 and pureextra == old.extra()):
2687 and pureextra == old.extra()):
2686 # nothing changed. continuing here would create a new node
2688 # nothing changed. continuing here would create a new node
2687 # anyway because of the amend_source noise.
2689 # anyway because of the amend_source noise.
2688 #
2690 #
2689 # This not what we expect from amend.
2691 # This not what we expect from amend.
2690 return old.node()
2692 return old.node()
2691
2693
2692 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2694 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2693 try:
2695 try:
2694 if opts.get('secret'):
2696 if opts.get('secret'):
2695 commitphase = 'secret'
2697 commitphase = 'secret'
2696 else:
2698 else:
2697 commitphase = old.phase()
2699 commitphase = old.phase()
2698 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2700 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2699 newid = repo.commitctx(new)
2701 newid = repo.commitctx(new)
2700 finally:
2702 finally:
2701 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2703 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2702 if newid != old.node():
2704 if newid != old.node():
2703 # Reroute the working copy parent to the new changeset
2705 # Reroute the working copy parent to the new changeset
2704 repo.setparents(newid, nullid)
2706 repo.setparents(newid, nullid)
2705
2707
2706 # Move bookmarks from old parent to amend commit
2708 # Move bookmarks from old parent to amend commit
2707 bms = repo.nodebookmarks(old.node())
2709 bms = repo.nodebookmarks(old.node())
2708 if bms:
2710 if bms:
2709 marks = repo._bookmarks
2711 marks = repo._bookmarks
2710 for bm in bms:
2712 for bm in bms:
2711 ui.debug('moving bookmarks %r from %s to %s\n' %
2713 ui.debug('moving bookmarks %r from %s to %s\n' %
2712 (marks, old.hex(), hex(newid)))
2714 (marks, old.hex(), hex(newid)))
2713 marks[bm] = newid
2715 marks[bm] = newid
2714 marks.recordchange(tr)
2716 marks.recordchange(tr)
2715 #commit the whole amend process
2717 #commit the whole amend process
2716 if createmarkers:
2718 if createmarkers:
2717 # mark the new changeset as successor of the rewritten one
2719 # mark the new changeset as successor of the rewritten one
2718 new = repo[newid]
2720 new = repo[newid]
2719 obs = [(old, (new,))]
2721 obs = [(old, (new,))]
2720 if node:
2722 if node:
2721 obs.append((ctx, ()))
2723 obs.append((ctx, ()))
2722
2724
2723 obsolete.createmarkers(repo, obs)
2725 obsolete.createmarkers(repo, obs)
2724 if not createmarkers and newid != old.node():
2726 if not createmarkers and newid != old.node():
2725 # Strip the intermediate commit (if there was one) and the amended
2727 # Strip the intermediate commit (if there was one) and the amended
2726 # commit
2728 # commit
2727 if node:
2729 if node:
2728 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2730 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2729 ui.note(_('stripping amended changeset %s\n') % old)
2731 ui.note(_('stripping amended changeset %s\n') % old)
2730 repair.strip(ui, repo, old.node(), topic='amend-backup')
2732 repair.strip(ui, repo, old.node(), topic='amend-backup')
2731 finally:
2733 finally:
2732 lockmod.release(lock, wlock)
2734 lockmod.release(lock, wlock)
2733 return newid
2735 return newid
2734
2736
2735 def commiteditor(repo, ctx, subs, editform=''):
2737 def commiteditor(repo, ctx, subs, editform=''):
2736 if ctx.description():
2738 if ctx.description():
2737 return ctx.description()
2739 return ctx.description()
2738 return commitforceeditor(repo, ctx, subs, editform=editform,
2740 return commitforceeditor(repo, ctx, subs, editform=editform,
2739 unchangedmessagedetection=True)
2741 unchangedmessagedetection=True)
2740
2742
2741 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2743 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2742 editform='', unchangedmessagedetection=False):
2744 editform='', unchangedmessagedetection=False):
2743 if not extramsg:
2745 if not extramsg:
2744 extramsg = _("Leave message empty to abort commit.")
2746 extramsg = _("Leave message empty to abort commit.")
2745
2747
2746 forms = [e for e in editform.split('.') if e]
2748 forms = [e for e in editform.split('.') if e]
2747 forms.insert(0, 'changeset')
2749 forms.insert(0, 'changeset')
2748 templatetext = None
2750 templatetext = None
2749 while forms:
2751 while forms:
2750 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2752 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2751 if tmpl:
2753 if tmpl:
2752 templatetext = committext = buildcommittemplate(
2754 templatetext = committext = buildcommittemplate(
2753 repo, ctx, subs, extramsg, tmpl)
2755 repo, ctx, subs, extramsg, tmpl)
2754 break
2756 break
2755 forms.pop()
2757 forms.pop()
2756 else:
2758 else:
2757 committext = buildcommittext(repo, ctx, subs, extramsg)
2759 committext = buildcommittext(repo, ctx, subs, extramsg)
2758
2760
2759 # run editor in the repository root
2761 # run editor in the repository root
2760 olddir = os.getcwd()
2762 olddir = os.getcwd()
2761 os.chdir(repo.root)
2763 os.chdir(repo.root)
2762
2764
2763 # make in-memory changes visible to external process
2765 # make in-memory changes visible to external process
2764 tr = repo.currenttransaction()
2766 tr = repo.currenttransaction()
2765 repo.dirstate.write(tr)
2767 repo.dirstate.write(tr)
2766 pending = tr and tr.writepending() and repo.root
2768 pending = tr and tr.writepending() and repo.root
2767
2769
2768 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2770 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2769 editform=editform, pending=pending)
2771 editform=editform, pending=pending)
2770 text = re.sub("(?m)^HG:.*(\n|$)", "", editortext)
2772 text = re.sub("(?m)^HG:.*(\n|$)", "", editortext)
2771 os.chdir(olddir)
2773 os.chdir(olddir)
2772
2774
2773 if finishdesc:
2775 if finishdesc:
2774 text = finishdesc(text)
2776 text = finishdesc(text)
2775 if not text.strip():
2777 if not text.strip():
2776 raise error.Abort(_("empty commit message"))
2778 raise error.Abort(_("empty commit message"))
2777 if unchangedmessagedetection and editortext == templatetext:
2779 if unchangedmessagedetection and editortext == templatetext:
2778 raise error.Abort(_("commit message unchanged"))
2780 raise error.Abort(_("commit message unchanged"))
2779
2781
2780 return text
2782 return text
2781
2783
2782 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2784 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2783 ui = repo.ui
2785 ui = repo.ui
2784 tmpl, mapfile = gettemplate(ui, tmpl, None)
2786 tmpl, mapfile = gettemplate(ui, tmpl, None)
2785
2787
2786 try:
2788 try:
2787 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2789 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2788 except SyntaxError as inst:
2790 except SyntaxError as inst:
2789 raise error.Abort(inst.args[0])
2791 raise error.Abort(inst.args[0])
2790
2792
2791 for k, v in repo.ui.configitems('committemplate'):
2793 for k, v in repo.ui.configitems('committemplate'):
2792 if k != 'changeset':
2794 if k != 'changeset':
2793 t.t.cache[k] = v
2795 t.t.cache[k] = v
2794
2796
2795 if not extramsg:
2797 if not extramsg:
2796 extramsg = '' # ensure that extramsg is string
2798 extramsg = '' # ensure that extramsg is string
2797
2799
2798 ui.pushbuffer()
2800 ui.pushbuffer()
2799 t.show(ctx, extramsg=extramsg)
2801 t.show(ctx, extramsg=extramsg)
2800 return ui.popbuffer()
2802 return ui.popbuffer()
2801
2803
2802 def hgprefix(msg):
2804 def hgprefix(msg):
2803 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2805 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2804
2806
2805 def buildcommittext(repo, ctx, subs, extramsg):
2807 def buildcommittext(repo, ctx, subs, extramsg):
2806 edittext = []
2808 edittext = []
2807 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2809 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2808 if ctx.description():
2810 if ctx.description():
2809 edittext.append(ctx.description())
2811 edittext.append(ctx.description())
2810 edittext.append("")
2812 edittext.append("")
2811 edittext.append("") # Empty line between message and comments.
2813 edittext.append("") # Empty line between message and comments.
2812 edittext.append(hgprefix(_("Enter commit message."
2814 edittext.append(hgprefix(_("Enter commit message."
2813 " Lines beginning with 'HG:' are removed.")))
2815 " Lines beginning with 'HG:' are removed.")))
2814 edittext.append(hgprefix(extramsg))
2816 edittext.append(hgprefix(extramsg))
2815 edittext.append("HG: --")
2817 edittext.append("HG: --")
2816 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2818 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2817 if ctx.p2():
2819 if ctx.p2():
2818 edittext.append(hgprefix(_("branch merge")))
2820 edittext.append(hgprefix(_("branch merge")))
2819 if ctx.branch():
2821 if ctx.branch():
2820 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2822 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2821 if bookmarks.isactivewdirparent(repo):
2823 if bookmarks.isactivewdirparent(repo):
2822 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2824 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2823 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2825 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2824 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2826 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2825 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2827 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2826 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2828 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2827 if not added and not modified and not removed:
2829 if not added and not modified and not removed:
2828 edittext.append(hgprefix(_("no files changed")))
2830 edittext.append(hgprefix(_("no files changed")))
2829 edittext.append("")
2831 edittext.append("")
2830
2832
2831 return "\n".join(edittext)
2833 return "\n".join(edittext)
2832
2834
2833 def commitstatus(repo, node, branch, bheads=None, opts=None):
2835 def commitstatus(repo, node, branch, bheads=None, opts=None):
2834 if opts is None:
2836 if opts is None:
2835 opts = {}
2837 opts = {}
2836 ctx = repo[node]
2838 ctx = repo[node]
2837 parents = ctx.parents()
2839 parents = ctx.parents()
2838
2840
2839 if (not opts.get('amend') and bheads and node not in bheads and not
2841 if (not opts.get('amend') and bheads and node not in bheads and not
2840 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2842 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2841 repo.ui.status(_('created new head\n'))
2843 repo.ui.status(_('created new head\n'))
2842 # The message is not printed for initial roots. For the other
2844 # The message is not printed for initial roots. For the other
2843 # changesets, it is printed in the following situations:
2845 # changesets, it is printed in the following situations:
2844 #
2846 #
2845 # Par column: for the 2 parents with ...
2847 # Par column: for the 2 parents with ...
2846 # N: null or no parent
2848 # N: null or no parent
2847 # B: parent is on another named branch
2849 # B: parent is on another named branch
2848 # C: parent is a regular non head changeset
2850 # C: parent is a regular non head changeset
2849 # H: parent was a branch head of the current branch
2851 # H: parent was a branch head of the current branch
2850 # Msg column: whether we print "created new head" message
2852 # Msg column: whether we print "created new head" message
2851 # In the following, it is assumed that there already exists some
2853 # In the following, it is assumed that there already exists some
2852 # initial branch heads of the current branch, otherwise nothing is
2854 # initial branch heads of the current branch, otherwise nothing is
2853 # printed anyway.
2855 # printed anyway.
2854 #
2856 #
2855 # Par Msg Comment
2857 # Par Msg Comment
2856 # N N y additional topo root
2858 # N N y additional topo root
2857 #
2859 #
2858 # B N y additional branch root
2860 # B N y additional branch root
2859 # C N y additional topo head
2861 # C N y additional topo head
2860 # H N n usual case
2862 # H N n usual case
2861 #
2863 #
2862 # B B y weird additional branch root
2864 # B B y weird additional branch root
2863 # C B y branch merge
2865 # C B y branch merge
2864 # H B n merge with named branch
2866 # H B n merge with named branch
2865 #
2867 #
2866 # C C y additional head from merge
2868 # C C y additional head from merge
2867 # C H n merge with a head
2869 # C H n merge with a head
2868 #
2870 #
2869 # H H n head merge: head count decreases
2871 # H H n head merge: head count decreases
2870
2872
2871 if not opts.get('close_branch'):
2873 if not opts.get('close_branch'):
2872 for r in parents:
2874 for r in parents:
2873 if r.closesbranch() and r.branch() == branch:
2875 if r.closesbranch() and r.branch() == branch:
2874 repo.ui.status(_('reopening closed branch head %d\n') % r)
2876 repo.ui.status(_('reopening closed branch head %d\n') % r)
2875
2877
2876 if repo.ui.debugflag:
2878 if repo.ui.debugflag:
2877 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2879 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2878 elif repo.ui.verbose:
2880 elif repo.ui.verbose:
2879 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2881 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2880
2882
2881 def postcommitstatus(repo, pats, opts):
2883 def postcommitstatus(repo, pats, opts):
2882 return repo.status(match=scmutil.match(repo[None], pats, opts))
2884 return repo.status(match=scmutil.match(repo[None], pats, opts))
2883
2885
2884 def revert(ui, repo, ctx, parents, *pats, **opts):
2886 def revert(ui, repo, ctx, parents, *pats, **opts):
2885 parent, p2 = parents
2887 parent, p2 = parents
2886 node = ctx.node()
2888 node = ctx.node()
2887
2889
2888 mf = ctx.manifest()
2890 mf = ctx.manifest()
2889 if node == p2:
2891 if node == p2:
2890 parent = p2
2892 parent = p2
2891
2893
2892 # need all matching names in dirstate and manifest of target rev,
2894 # need all matching names in dirstate and manifest of target rev,
2893 # so have to walk both. do not print errors if files exist in one
2895 # so have to walk both. do not print errors if files exist in one
2894 # but not other. in both cases, filesets should be evaluated against
2896 # but not other. in both cases, filesets should be evaluated against
2895 # workingctx to get consistent result (issue4497). this means 'set:**'
2897 # workingctx to get consistent result (issue4497). this means 'set:**'
2896 # cannot be used to select missing files from target rev.
2898 # cannot be used to select missing files from target rev.
2897
2899
2898 # `names` is a mapping for all elements in working copy and target revision
2900 # `names` is a mapping for all elements in working copy and target revision
2899 # The mapping is in the form:
2901 # The mapping is in the form:
2900 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2902 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2901 names = {}
2903 names = {}
2902
2904
2903 with repo.wlock():
2905 with repo.wlock():
2904 ## filling of the `names` mapping
2906 ## filling of the `names` mapping
2905 # walk dirstate to fill `names`
2907 # walk dirstate to fill `names`
2906
2908
2907 interactive = opts.get('interactive', False)
2909 interactive = opts.get('interactive', False)
2908 wctx = repo[None]
2910 wctx = repo[None]
2909 m = scmutil.match(wctx, pats, opts)
2911 m = scmutil.match(wctx, pats, opts)
2910
2912
2911 # we'll need this later
2913 # we'll need this later
2912 targetsubs = sorted(s for s in wctx.substate if m(s))
2914 targetsubs = sorted(s for s in wctx.substate if m(s))
2913
2915
2914 if not m.always():
2916 if not m.always():
2915 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2917 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2916 names[abs] = m.rel(abs), m.exact(abs)
2918 names[abs] = m.rel(abs), m.exact(abs)
2917
2919
2918 # walk target manifest to fill `names`
2920 # walk target manifest to fill `names`
2919
2921
2920 def badfn(path, msg):
2922 def badfn(path, msg):
2921 if path in names:
2923 if path in names:
2922 return
2924 return
2923 if path in ctx.substate:
2925 if path in ctx.substate:
2924 return
2926 return
2925 path_ = path + '/'
2927 path_ = path + '/'
2926 for f in names:
2928 for f in names:
2927 if f.startswith(path_):
2929 if f.startswith(path_):
2928 return
2930 return
2929 ui.warn("%s: %s\n" % (m.rel(path), msg))
2931 ui.warn("%s: %s\n" % (m.rel(path), msg))
2930
2932
2931 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2933 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2932 if abs not in names:
2934 if abs not in names:
2933 names[abs] = m.rel(abs), m.exact(abs)
2935 names[abs] = m.rel(abs), m.exact(abs)
2934
2936
2935 # Find status of all file in `names`.
2937 # Find status of all file in `names`.
2936 m = scmutil.matchfiles(repo, names)
2938 m = scmutil.matchfiles(repo, names)
2937
2939
2938 changes = repo.status(node1=node, match=m,
2940 changes = repo.status(node1=node, match=m,
2939 unknown=True, ignored=True, clean=True)
2941 unknown=True, ignored=True, clean=True)
2940 else:
2942 else:
2941 changes = repo.status(node1=node, match=m)
2943 changes = repo.status(node1=node, match=m)
2942 for kind in changes:
2944 for kind in changes:
2943 for abs in kind:
2945 for abs in kind:
2944 names[abs] = m.rel(abs), m.exact(abs)
2946 names[abs] = m.rel(abs), m.exact(abs)
2945
2947
2946 m = scmutil.matchfiles(repo, names)
2948 m = scmutil.matchfiles(repo, names)
2947
2949
2948 modified = set(changes.modified)
2950 modified = set(changes.modified)
2949 added = set(changes.added)
2951 added = set(changes.added)
2950 removed = set(changes.removed)
2952 removed = set(changes.removed)
2951 _deleted = set(changes.deleted)
2953 _deleted = set(changes.deleted)
2952 unknown = set(changes.unknown)
2954 unknown = set(changes.unknown)
2953 unknown.update(changes.ignored)
2955 unknown.update(changes.ignored)
2954 clean = set(changes.clean)
2956 clean = set(changes.clean)
2955 modadded = set()
2957 modadded = set()
2956
2958
2957 # split between files known in target manifest and the others
2959 # split between files known in target manifest and the others
2958 smf = set(mf)
2960 smf = set(mf)
2959
2961
2960 # determine the exact nature of the deleted changesets
2962 # determine the exact nature of the deleted changesets
2961 deladded = _deleted - smf
2963 deladded = _deleted - smf
2962 deleted = _deleted - deladded
2964 deleted = _deleted - deladded
2963
2965
2964 # We need to account for the state of the file in the dirstate,
2966 # We need to account for the state of the file in the dirstate,
2965 # even when we revert against something else than parent. This will
2967 # even when we revert against something else than parent. This will
2966 # slightly alter the behavior of revert (doing back up or not, delete
2968 # slightly alter the behavior of revert (doing back up or not, delete
2967 # or just forget etc).
2969 # or just forget etc).
2968 if parent == node:
2970 if parent == node:
2969 dsmodified = modified
2971 dsmodified = modified
2970 dsadded = added
2972 dsadded = added
2971 dsremoved = removed
2973 dsremoved = removed
2972 # store all local modifications, useful later for rename detection
2974 # store all local modifications, useful later for rename detection
2973 localchanges = dsmodified | dsadded
2975 localchanges = dsmodified | dsadded
2974 modified, added, removed = set(), set(), set()
2976 modified, added, removed = set(), set(), set()
2975 else:
2977 else:
2976 changes = repo.status(node1=parent, match=m)
2978 changes = repo.status(node1=parent, match=m)
2977 dsmodified = set(changes.modified)
2979 dsmodified = set(changes.modified)
2978 dsadded = set(changes.added)
2980 dsadded = set(changes.added)
2979 dsremoved = set(changes.removed)
2981 dsremoved = set(changes.removed)
2980 # store all local modifications, useful later for rename detection
2982 # store all local modifications, useful later for rename detection
2981 localchanges = dsmodified | dsadded
2983 localchanges = dsmodified | dsadded
2982
2984
2983 # only take into account for removes between wc and target
2985 # only take into account for removes between wc and target
2984 clean |= dsremoved - removed
2986 clean |= dsremoved - removed
2985 dsremoved &= removed
2987 dsremoved &= removed
2986 # distinct between dirstate remove and other
2988 # distinct between dirstate remove and other
2987 removed -= dsremoved
2989 removed -= dsremoved
2988
2990
2989 modadded = added & dsmodified
2991 modadded = added & dsmodified
2990 added -= modadded
2992 added -= modadded
2991
2993
2992 # tell newly modified apart.
2994 # tell newly modified apart.
2993 dsmodified &= modified
2995 dsmodified &= modified
2994 dsmodified |= modified & dsadded # dirstate added may needs backup
2996 dsmodified |= modified & dsadded # dirstate added may needs backup
2995 modified -= dsmodified
2997 modified -= dsmodified
2996
2998
2997 # We need to wait for some post-processing to update this set
2999 # We need to wait for some post-processing to update this set
2998 # before making the distinction. The dirstate will be used for
3000 # before making the distinction. The dirstate will be used for
2999 # that purpose.
3001 # that purpose.
3000 dsadded = added
3002 dsadded = added
3001
3003
3002 # in case of merge, files that are actually added can be reported as
3004 # in case of merge, files that are actually added can be reported as
3003 # modified, we need to post process the result
3005 # modified, we need to post process the result
3004 if p2 != nullid:
3006 if p2 != nullid:
3005 mergeadd = dsmodified - smf
3007 mergeadd = dsmodified - smf
3006 dsadded |= mergeadd
3008 dsadded |= mergeadd
3007 dsmodified -= mergeadd
3009 dsmodified -= mergeadd
3008
3010
3009 # if f is a rename, update `names` to also revert the source
3011 # if f is a rename, update `names` to also revert the source
3010 cwd = repo.getcwd()
3012 cwd = repo.getcwd()
3011 for f in localchanges:
3013 for f in localchanges:
3012 src = repo.dirstate.copied(f)
3014 src = repo.dirstate.copied(f)
3013 # XXX should we check for rename down to target node?
3015 # XXX should we check for rename down to target node?
3014 if src and src not in names and repo.dirstate[src] == 'r':
3016 if src and src not in names and repo.dirstate[src] == 'r':
3015 dsremoved.add(src)
3017 dsremoved.add(src)
3016 names[src] = (repo.pathto(src, cwd), True)
3018 names[src] = (repo.pathto(src, cwd), True)
3017
3019
3018 # distinguish between file to forget and the other
3020 # distinguish between file to forget and the other
3019 added = set()
3021 added = set()
3020 for abs in dsadded:
3022 for abs in dsadded:
3021 if repo.dirstate[abs] != 'a':
3023 if repo.dirstate[abs] != 'a':
3022 added.add(abs)
3024 added.add(abs)
3023 dsadded -= added
3025 dsadded -= added
3024
3026
3025 for abs in deladded:
3027 for abs in deladded:
3026 if repo.dirstate[abs] == 'a':
3028 if repo.dirstate[abs] == 'a':
3027 dsadded.add(abs)
3029 dsadded.add(abs)
3028 deladded -= dsadded
3030 deladded -= dsadded
3029
3031
3030 # For files marked as removed, we check if an unknown file is present at
3032 # For files marked as removed, we check if an unknown file is present at
3031 # the same path. If a such file exists it may need to be backed up.
3033 # the same path. If a such file exists it may need to be backed up.
3032 # Making the distinction at this stage helps have simpler backup
3034 # Making the distinction at this stage helps have simpler backup
3033 # logic.
3035 # logic.
3034 removunk = set()
3036 removunk = set()
3035 for abs in removed:
3037 for abs in removed:
3036 target = repo.wjoin(abs)
3038 target = repo.wjoin(abs)
3037 if os.path.lexists(target):
3039 if os.path.lexists(target):
3038 removunk.add(abs)
3040 removunk.add(abs)
3039 removed -= removunk
3041 removed -= removunk
3040
3042
3041 dsremovunk = set()
3043 dsremovunk = set()
3042 for abs in dsremoved:
3044 for abs in dsremoved:
3043 target = repo.wjoin(abs)
3045 target = repo.wjoin(abs)
3044 if os.path.lexists(target):
3046 if os.path.lexists(target):
3045 dsremovunk.add(abs)
3047 dsremovunk.add(abs)
3046 dsremoved -= dsremovunk
3048 dsremoved -= dsremovunk
3047
3049
3048 # action to be actually performed by revert
3050 # action to be actually performed by revert
3049 # (<list of file>, message>) tuple
3051 # (<list of file>, message>) tuple
3050 actions = {'revert': ([], _('reverting %s\n')),
3052 actions = {'revert': ([], _('reverting %s\n')),
3051 'add': ([], _('adding %s\n')),
3053 'add': ([], _('adding %s\n')),
3052 'remove': ([], _('removing %s\n')),
3054 'remove': ([], _('removing %s\n')),
3053 'drop': ([], _('removing %s\n')),
3055 'drop': ([], _('removing %s\n')),
3054 'forget': ([], _('forgetting %s\n')),
3056 'forget': ([], _('forgetting %s\n')),
3055 'undelete': ([], _('undeleting %s\n')),
3057 'undelete': ([], _('undeleting %s\n')),
3056 'noop': (None, _('no changes needed to %s\n')),
3058 'noop': (None, _('no changes needed to %s\n')),
3057 'unknown': (None, _('file not managed: %s\n')),
3059 'unknown': (None, _('file not managed: %s\n')),
3058 }
3060 }
3059
3061
3060 # "constant" that convey the backup strategy.
3062 # "constant" that convey the backup strategy.
3061 # All set to `discard` if `no-backup` is set do avoid checking
3063 # All set to `discard` if `no-backup` is set do avoid checking
3062 # no_backup lower in the code.
3064 # no_backup lower in the code.
3063 # These values are ordered for comparison purposes
3065 # These values are ordered for comparison purposes
3064 backup = 2 # unconditionally do backup
3066 backup = 2 # unconditionally do backup
3065 check = 1 # check if the existing file differs from target
3067 check = 1 # check if the existing file differs from target
3066 discard = 0 # never do backup
3068 discard = 0 # never do backup
3067 if opts.get('no_backup'):
3069 if opts.get('no_backup'):
3068 backup = check = discard
3070 backup = check = discard
3069
3071
3070 backupanddel = actions['remove']
3072 backupanddel = actions['remove']
3071 if not opts.get('no_backup'):
3073 if not opts.get('no_backup'):
3072 backupanddel = actions['drop']
3074 backupanddel = actions['drop']
3073
3075
3074 disptable = (
3076 disptable = (
3075 # dispatch table:
3077 # dispatch table:
3076 # file state
3078 # file state
3077 # action
3079 # action
3078 # make backup
3080 # make backup
3079
3081
3080 ## Sets that results that will change file on disk
3082 ## Sets that results that will change file on disk
3081 # Modified compared to target, no local change
3083 # Modified compared to target, no local change
3082 (modified, actions['revert'], discard),
3084 (modified, actions['revert'], discard),
3083 # Modified compared to target, but local file is deleted
3085 # Modified compared to target, but local file is deleted
3084 (deleted, actions['revert'], discard),
3086 (deleted, actions['revert'], discard),
3085 # Modified compared to target, local change
3087 # Modified compared to target, local change
3086 (dsmodified, actions['revert'], backup),
3088 (dsmodified, actions['revert'], backup),
3087 # Added since target
3089 # Added since target
3088 (added, actions['remove'], discard),
3090 (added, actions['remove'], discard),
3089 # Added in working directory
3091 # Added in working directory
3090 (dsadded, actions['forget'], discard),
3092 (dsadded, actions['forget'], discard),
3091 # Added since target, have local modification
3093 # Added since target, have local modification
3092 (modadded, backupanddel, backup),
3094 (modadded, backupanddel, backup),
3093 # Added since target but file is missing in working directory
3095 # Added since target but file is missing in working directory
3094 (deladded, actions['drop'], discard),
3096 (deladded, actions['drop'], discard),
3095 # Removed since target, before working copy parent
3097 # Removed since target, before working copy parent
3096 (removed, actions['add'], discard),
3098 (removed, actions['add'], discard),
3097 # Same as `removed` but an unknown file exists at the same path
3099 # Same as `removed` but an unknown file exists at the same path
3098 (removunk, actions['add'], check),
3100 (removunk, actions['add'], check),
3099 # Removed since targe, marked as such in working copy parent
3101 # Removed since targe, marked as such in working copy parent
3100 (dsremoved, actions['undelete'], discard),
3102 (dsremoved, actions['undelete'], discard),
3101 # Same as `dsremoved` but an unknown file exists at the same path
3103 # Same as `dsremoved` but an unknown file exists at the same path
3102 (dsremovunk, actions['undelete'], check),
3104 (dsremovunk, actions['undelete'], check),
3103 ## the following sets does not result in any file changes
3105 ## the following sets does not result in any file changes
3104 # File with no modification
3106 # File with no modification
3105 (clean, actions['noop'], discard),
3107 (clean, actions['noop'], discard),
3106 # Existing file, not tracked anywhere
3108 # Existing file, not tracked anywhere
3107 (unknown, actions['unknown'], discard),
3109 (unknown, actions['unknown'], discard),
3108 )
3110 )
3109
3111
3110 for abs, (rel, exact) in sorted(names.items()):
3112 for abs, (rel, exact) in sorted(names.items()):
3111 # target file to be touch on disk (relative to cwd)
3113 # target file to be touch on disk (relative to cwd)
3112 target = repo.wjoin(abs)
3114 target = repo.wjoin(abs)
3113 # search the entry in the dispatch table.
3115 # search the entry in the dispatch table.
3114 # if the file is in any of these sets, it was touched in the working
3116 # if the file is in any of these sets, it was touched in the working
3115 # directory parent and we are sure it needs to be reverted.
3117 # directory parent and we are sure it needs to be reverted.
3116 for table, (xlist, msg), dobackup in disptable:
3118 for table, (xlist, msg), dobackup in disptable:
3117 if abs not in table:
3119 if abs not in table:
3118 continue
3120 continue
3119 if xlist is not None:
3121 if xlist is not None:
3120 xlist.append(abs)
3122 xlist.append(abs)
3121 if dobackup and (backup <= dobackup
3123 if dobackup and (backup <= dobackup
3122 or wctx[abs].cmp(ctx[abs])):
3124 or wctx[abs].cmp(ctx[abs])):
3123 bakname = scmutil.origpath(ui, repo, rel)
3125 bakname = scmutil.origpath(ui, repo, rel)
3124 ui.note(_('saving current version of %s as %s\n') %
3126 ui.note(_('saving current version of %s as %s\n') %
3125 (rel, bakname))
3127 (rel, bakname))
3126 if not opts.get('dry_run'):
3128 if not opts.get('dry_run'):
3127 if interactive:
3129 if interactive:
3128 util.copyfile(target, bakname)
3130 util.copyfile(target, bakname)
3129 else:
3131 else:
3130 util.rename(target, bakname)
3132 util.rename(target, bakname)
3131 if ui.verbose or not exact:
3133 if ui.verbose or not exact:
3132 if not isinstance(msg, basestring):
3134 if not isinstance(msg, basestring):
3133 msg = msg(abs)
3135 msg = msg(abs)
3134 ui.status(msg % rel)
3136 ui.status(msg % rel)
3135 elif exact:
3137 elif exact:
3136 ui.warn(msg % rel)
3138 ui.warn(msg % rel)
3137 break
3139 break
3138
3140
3139 if not opts.get('dry_run'):
3141 if not opts.get('dry_run'):
3140 needdata = ('revert', 'add', 'undelete')
3142 needdata = ('revert', 'add', 'undelete')
3141 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3143 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3142 _performrevert(repo, parents, ctx, actions, interactive)
3144 _performrevert(repo, parents, ctx, actions, interactive)
3143
3145
3144 if targetsubs:
3146 if targetsubs:
3145 # Revert the subrepos on the revert list
3147 # Revert the subrepos on the revert list
3146 for sub in targetsubs:
3148 for sub in targetsubs:
3147 try:
3149 try:
3148 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3150 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3149 except KeyError:
3151 except KeyError:
3150 raise error.Abort("subrepository '%s' does not exist in %s!"
3152 raise error.Abort("subrepository '%s' does not exist in %s!"
3151 % (sub, short(ctx.node())))
3153 % (sub, short(ctx.node())))
3152
3154
3153 def _revertprefetch(repo, ctx, *files):
3155 def _revertprefetch(repo, ctx, *files):
3154 """Let extension changing the storage layer prefetch content"""
3156 """Let extension changing the storage layer prefetch content"""
3155 pass
3157 pass
3156
3158
3157 def _performrevert(repo, parents, ctx, actions, interactive=False):
3159 def _performrevert(repo, parents, ctx, actions, interactive=False):
3158 """function that actually perform all the actions computed for revert
3160 """function that actually perform all the actions computed for revert
3159
3161
3160 This is an independent function to let extension to plug in and react to
3162 This is an independent function to let extension to plug in and react to
3161 the imminent revert.
3163 the imminent revert.
3162
3164
3163 Make sure you have the working directory locked when calling this function.
3165 Make sure you have the working directory locked when calling this function.
3164 """
3166 """
3165 parent, p2 = parents
3167 parent, p2 = parents
3166 node = ctx.node()
3168 node = ctx.node()
3167 excluded_files = []
3169 excluded_files = []
3168 matcher_opts = {"exclude": excluded_files}
3170 matcher_opts = {"exclude": excluded_files}
3169
3171
3170 def checkout(f):
3172 def checkout(f):
3171 fc = ctx[f]
3173 fc = ctx[f]
3172 repo.wwrite(f, fc.data(), fc.flags())
3174 repo.wwrite(f, fc.data(), fc.flags())
3173
3175
3174 audit_path = pathutil.pathauditor(repo.root)
3176 audit_path = pathutil.pathauditor(repo.root)
3175 for f in actions['forget'][0]:
3177 for f in actions['forget'][0]:
3176 if interactive:
3178 if interactive:
3177 choice = \
3179 choice = \
3178 repo.ui.promptchoice(
3180 repo.ui.promptchoice(
3179 _("forget added file %s (yn)?$$ &Yes $$ &No")
3181 _("forget added file %s (yn)?$$ &Yes $$ &No")
3180 % f)
3182 % f)
3181 if choice == 0:
3183 if choice == 0:
3182 repo.dirstate.drop(f)
3184 repo.dirstate.drop(f)
3183 else:
3185 else:
3184 excluded_files.append(repo.wjoin(f))
3186 excluded_files.append(repo.wjoin(f))
3185 else:
3187 else:
3186 repo.dirstate.drop(f)
3188 repo.dirstate.drop(f)
3187 for f in actions['remove'][0]:
3189 for f in actions['remove'][0]:
3188 audit_path(f)
3190 audit_path(f)
3189 try:
3191 try:
3190 util.unlinkpath(repo.wjoin(f))
3192 util.unlinkpath(repo.wjoin(f))
3191 except OSError:
3193 except OSError:
3192 pass
3194 pass
3193 repo.dirstate.remove(f)
3195 repo.dirstate.remove(f)
3194 for f in actions['drop'][0]:
3196 for f in actions['drop'][0]:
3195 audit_path(f)
3197 audit_path(f)
3196 repo.dirstate.remove(f)
3198 repo.dirstate.remove(f)
3197
3199
3198 normal = None
3200 normal = None
3199 if node == parent:
3201 if node == parent:
3200 # We're reverting to our parent. If possible, we'd like status
3202 # We're reverting to our parent. If possible, we'd like status
3201 # to report the file as clean. We have to use normallookup for
3203 # to report the file as clean. We have to use normallookup for
3202 # merges to avoid losing information about merged/dirty files.
3204 # merges to avoid losing information about merged/dirty files.
3203 if p2 != nullid:
3205 if p2 != nullid:
3204 normal = repo.dirstate.normallookup
3206 normal = repo.dirstate.normallookup
3205 else:
3207 else:
3206 normal = repo.dirstate.normal
3208 normal = repo.dirstate.normal
3207
3209
3208 newlyaddedandmodifiedfiles = set()
3210 newlyaddedandmodifiedfiles = set()
3209 if interactive:
3211 if interactive:
3210 # Prompt the user for changes to revert
3212 # Prompt the user for changes to revert
3211 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3213 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3212 m = scmutil.match(ctx, torevert, matcher_opts)
3214 m = scmutil.match(ctx, torevert, matcher_opts)
3213 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3215 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3214 diffopts.nodates = True
3216 diffopts.nodates = True
3215 diffopts.git = True
3217 diffopts.git = True
3216 reversehunks = repo.ui.configbool('experimental',
3218 reversehunks = repo.ui.configbool('experimental',
3217 'revertalternateinteractivemode',
3219 'revertalternateinteractivemode',
3218 True)
3220 True)
3219 if reversehunks:
3221 if reversehunks:
3220 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3222 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3221 else:
3223 else:
3222 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3224 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3223 originalchunks = patch.parsepatch(diff)
3225 originalchunks = patch.parsepatch(diff)
3224
3226
3225 try:
3227 try:
3226
3228
3227 chunks, opts = recordfilter(repo.ui, originalchunks)
3229 chunks, opts = recordfilter(repo.ui, originalchunks)
3228 if reversehunks:
3230 if reversehunks:
3229 chunks = patch.reversehunks(chunks)
3231 chunks = patch.reversehunks(chunks)
3230
3232
3231 except patch.PatchError as err:
3233 except patch.PatchError as err:
3232 raise error.Abort(_('error parsing patch: %s') % err)
3234 raise error.Abort(_('error parsing patch: %s') % err)
3233
3235
3234 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3236 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3235 # Apply changes
3237 # Apply changes
3236 fp = cStringIO.StringIO()
3238 fp = cStringIO.StringIO()
3237 for c in chunks:
3239 for c in chunks:
3238 c.write(fp)
3240 c.write(fp)
3239 dopatch = fp.tell()
3241 dopatch = fp.tell()
3240 fp.seek(0)
3242 fp.seek(0)
3241 if dopatch:
3243 if dopatch:
3242 try:
3244 try:
3243 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3245 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3244 except patch.PatchError as err:
3246 except patch.PatchError as err:
3245 raise error.Abort(str(err))
3247 raise error.Abort(str(err))
3246 del fp
3248 del fp
3247 else:
3249 else:
3248 for f in actions['revert'][0]:
3250 for f in actions['revert'][0]:
3249 checkout(f)
3251 checkout(f)
3250 if normal:
3252 if normal:
3251 normal(f)
3253 normal(f)
3252
3254
3253 for f in actions['add'][0]:
3255 for f in actions['add'][0]:
3254 # Don't checkout modified files, they are already created by the diff
3256 # Don't checkout modified files, they are already created by the diff
3255 if f not in newlyaddedandmodifiedfiles:
3257 if f not in newlyaddedandmodifiedfiles:
3256 checkout(f)
3258 checkout(f)
3257 repo.dirstate.add(f)
3259 repo.dirstate.add(f)
3258
3260
3259 normal = repo.dirstate.normallookup
3261 normal = repo.dirstate.normallookup
3260 if node == parent and p2 == nullid:
3262 if node == parent and p2 == nullid:
3261 normal = repo.dirstate.normal
3263 normal = repo.dirstate.normal
3262 for f in actions['undelete'][0]:
3264 for f in actions['undelete'][0]:
3263 checkout(f)
3265 checkout(f)
3264 normal(f)
3266 normal(f)
3265
3267
3266 copied = copies.pathcopies(repo[parent], ctx)
3268 copied = copies.pathcopies(repo[parent], ctx)
3267
3269
3268 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3270 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3269 if f in copied:
3271 if f in copied:
3270 repo.dirstate.copy(copied[f], f)
3272 repo.dirstate.copy(copied[f], f)
3271
3273
3272 def command(table):
3274 def command(table):
3273 """Returns a function object to be used as a decorator for making commands.
3275 """Returns a function object to be used as a decorator for making commands.
3274
3276
3275 This function receives a command table as its argument. The table should
3277 This function receives a command table as its argument. The table should
3276 be a dict.
3278 be a dict.
3277
3279
3278 The returned function can be used as a decorator for adding commands
3280 The returned function can be used as a decorator for adding commands
3279 to that command table. This function accepts multiple arguments to define
3281 to that command table. This function accepts multiple arguments to define
3280 a command.
3282 a command.
3281
3283
3282 The first argument is the command name.
3284 The first argument is the command name.
3283
3285
3284 The options argument is an iterable of tuples defining command arguments.
3286 The options argument is an iterable of tuples defining command arguments.
3285 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3287 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3286
3288
3287 The synopsis argument defines a short, one line summary of how to use the
3289 The synopsis argument defines a short, one line summary of how to use the
3288 command. This shows up in the help output.
3290 command. This shows up in the help output.
3289
3291
3290 The norepo argument defines whether the command does not require a
3292 The norepo argument defines whether the command does not require a
3291 local repository. Most commands operate against a repository, thus the
3293 local repository. Most commands operate against a repository, thus the
3292 default is False.
3294 default is False.
3293
3295
3294 The optionalrepo argument defines whether the command optionally requires
3296 The optionalrepo argument defines whether the command optionally requires
3295 a local repository.
3297 a local repository.
3296
3298
3297 The inferrepo argument defines whether to try to find a repository from the
3299 The inferrepo argument defines whether to try to find a repository from the
3298 command line arguments. If True, arguments will be examined for potential
3300 command line arguments. If True, arguments will be examined for potential
3299 repository locations. See ``findrepo()``. If a repository is found, it
3301 repository locations. See ``findrepo()``. If a repository is found, it
3300 will be used.
3302 will be used.
3301 """
3303 """
3302 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3304 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3303 inferrepo=False):
3305 inferrepo=False):
3304 def decorator(func):
3306 def decorator(func):
3305 func.norepo = norepo
3307 func.norepo = norepo
3306 func.optionalrepo = optionalrepo
3308 func.optionalrepo = optionalrepo
3307 func.inferrepo = inferrepo
3309 func.inferrepo = inferrepo
3308 if synopsis:
3310 if synopsis:
3309 table[name] = func, list(options), synopsis
3311 table[name] = func, list(options), synopsis
3310 else:
3312 else:
3311 table[name] = func, list(options)
3313 table[name] = func, list(options)
3312 return func
3314 return func
3313 return decorator
3315 return decorator
3314
3316
3315 return cmd
3317 return cmd
3316
3318
3317 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3319 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3318 # commands.outgoing. "missing" is "missing" of the result of
3320 # commands.outgoing. "missing" is "missing" of the result of
3319 # "findcommonoutgoing()"
3321 # "findcommonoutgoing()"
3320 outgoinghooks = util.hooks()
3322 outgoinghooks = util.hooks()
3321
3323
3322 # a list of (ui, repo) functions called by commands.summary
3324 # a list of (ui, repo) functions called by commands.summary
3323 summaryhooks = util.hooks()
3325 summaryhooks = util.hooks()
3324
3326
3325 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3327 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3326 #
3328 #
3327 # functions should return tuple of booleans below, if 'changes' is None:
3329 # functions should return tuple of booleans below, if 'changes' is None:
3328 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3330 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3329 #
3331 #
3330 # otherwise, 'changes' is a tuple of tuples below:
3332 # otherwise, 'changes' is a tuple of tuples below:
3331 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3333 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3332 # - (desturl, destbranch, destpeer, outgoing)
3334 # - (desturl, destbranch, destpeer, outgoing)
3333 summaryremotehooks = util.hooks()
3335 summaryremotehooks = util.hooks()
3334
3336
3335 # A list of state files kept by multistep operations like graft.
3337 # A list of state files kept by multistep operations like graft.
3336 # Since graft cannot be aborted, it is considered 'clearable' by update.
3338 # Since graft cannot be aborted, it is considered 'clearable' by update.
3337 # note: bisect is intentionally excluded
3339 # note: bisect is intentionally excluded
3338 # (state file, clearable, allowcommit, error, hint)
3340 # (state file, clearable, allowcommit, error, hint)
3339 unfinishedstates = [
3341 unfinishedstates = [
3340 ('graftstate', True, False, _('graft in progress'),
3342 ('graftstate', True, False, _('graft in progress'),
3341 _("use 'hg graft --continue' or 'hg update' to abort")),
3343 _("use 'hg graft --continue' or 'hg update' to abort")),
3342 ('updatestate', True, False, _('last update was interrupted'),
3344 ('updatestate', True, False, _('last update was interrupted'),
3343 _("use 'hg update' to get a consistent checkout"))
3345 _("use 'hg update' to get a consistent checkout"))
3344 ]
3346 ]
3345
3347
3346 def checkunfinished(repo, commit=False):
3348 def checkunfinished(repo, commit=False):
3347 '''Look for an unfinished multistep operation, like graft, and abort
3349 '''Look for an unfinished multistep operation, like graft, and abort
3348 if found. It's probably good to check this right before
3350 if found. It's probably good to check this right before
3349 bailifchanged().
3351 bailifchanged().
3350 '''
3352 '''
3351 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3353 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3352 if commit and allowcommit:
3354 if commit and allowcommit:
3353 continue
3355 continue
3354 if repo.vfs.exists(f):
3356 if repo.vfs.exists(f):
3355 raise error.Abort(msg, hint=hint)
3357 raise error.Abort(msg, hint=hint)
3356
3358
3357 def clearunfinished(repo):
3359 def clearunfinished(repo):
3358 '''Check for unfinished operations (as above), and clear the ones
3360 '''Check for unfinished operations (as above), and clear the ones
3359 that are clearable.
3361 that are clearable.
3360 '''
3362 '''
3361 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3363 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3362 if not clearable and repo.vfs.exists(f):
3364 if not clearable and repo.vfs.exists(f):
3363 raise error.Abort(msg, hint=hint)
3365 raise error.Abort(msg, hint=hint)
3364 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3366 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3365 if clearable and repo.vfs.exists(f):
3367 if clearable and repo.vfs.exists(f):
3366 util.unlink(repo.join(f))
3368 util.unlink(repo.join(f))
3367
3369
3368 afterresolvedstates = [
3370 afterresolvedstates = [
3369 ('graftstate',
3371 ('graftstate',
3370 _('hg graft --continue')),
3372 _('hg graft --continue')),
3371 ]
3373 ]
3372
3374
3373 def howtocontinue(repo):
3375 def howtocontinue(repo):
3374 '''Check for an unfinished operation and return the command to finish
3376 '''Check for an unfinished operation and return the command to finish
3375 it.
3377 it.
3376
3378
3377 afterresolvedstates tupples define a .hg/{file} and the corresponding
3379 afterresolvedstates tupples define a .hg/{file} and the corresponding
3378 command needed to finish it.
3380 command needed to finish it.
3379
3381
3380 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3382 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3381 a boolean.
3383 a boolean.
3382 '''
3384 '''
3383 contmsg = _("continue: %s")
3385 contmsg = _("continue: %s")
3384 for f, msg in afterresolvedstates:
3386 for f, msg in afterresolvedstates:
3385 if repo.vfs.exists(f):
3387 if repo.vfs.exists(f):
3386 return contmsg % msg, True
3388 return contmsg % msg, True
3387 workingctx = repo[None]
3389 workingctx = repo[None]
3388 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3390 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3389 for s in workingctx.substate)
3391 for s in workingctx.substate)
3390 if dirty:
3392 if dirty:
3391 return contmsg % _("hg commit"), False
3393 return contmsg % _("hg commit"), False
3392 return None, None
3394 return None, None
3393
3395
3394 def checkafterresolved(repo):
3396 def checkafterresolved(repo):
3395 '''Inform the user about the next action after completing hg resolve
3397 '''Inform the user about the next action after completing hg resolve
3396
3398
3397 If there's a matching afterresolvedstates, howtocontinue will yield
3399 If there's a matching afterresolvedstates, howtocontinue will yield
3398 repo.ui.warn as the reporter.
3400 repo.ui.warn as the reporter.
3399
3401
3400 Otherwise, it will yield repo.ui.note.
3402 Otherwise, it will yield repo.ui.note.
3401 '''
3403 '''
3402 msg, warning = howtocontinue(repo)
3404 msg, warning = howtocontinue(repo)
3403 if msg is not None:
3405 if msg is not None:
3404 if warning:
3406 if warning:
3405 repo.ui.warn("%s\n" % msg)
3407 repo.ui.warn("%s\n" % msg)
3406 else:
3408 else:
3407 repo.ui.note("%s\n" % msg)
3409 repo.ui.note("%s\n" % msg)
3408
3410
3409 def wrongtooltocontinue(repo, task):
3411 def wrongtooltocontinue(repo, task):
3410 '''Raise an abort suggesting how to properly continue if there is an
3412 '''Raise an abort suggesting how to properly continue if there is an
3411 active task.
3413 active task.
3412
3414
3413 Uses howtocontinue() to find the active task.
3415 Uses howtocontinue() to find the active task.
3414
3416
3415 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3417 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3416 a hint.
3418 a hint.
3417 '''
3419 '''
3418 after = howtocontinue(repo)
3420 after = howtocontinue(repo)
3419 hint = None
3421 hint = None
3420 if after[1]:
3422 if after[1]:
3421 hint = after[0]
3423 hint = after[0]
3422 raise error.Abort(_('no %s in progress') % task, hint=hint)
3424 raise error.Abort(_('no %s in progress') % task, hint=hint)
3423
3425
3424 class dirstateguard(object):
3426 class dirstateguard(object):
3425 '''Restore dirstate at unexpected failure.
3427 '''Restore dirstate at unexpected failure.
3426
3428
3427 At the construction, this class does:
3429 At the construction, this class does:
3428
3430
3429 - write current ``repo.dirstate`` out, and
3431 - write current ``repo.dirstate`` out, and
3430 - save ``.hg/dirstate`` into the backup file
3432 - save ``.hg/dirstate`` into the backup file
3431
3433
3432 This restores ``.hg/dirstate`` from backup file, if ``release()``
3434 This restores ``.hg/dirstate`` from backup file, if ``release()``
3433 is invoked before ``close()``.
3435 is invoked before ``close()``.
3434
3436
3435 This just removes the backup file at ``close()`` before ``release()``.
3437 This just removes the backup file at ``close()`` before ``release()``.
3436 '''
3438 '''
3437
3439
3438 def __init__(self, repo, name):
3440 def __init__(self, repo, name):
3439 self._repo = repo
3441 self._repo = repo
3440 self._suffix = '.backup.%s.%d' % (name, id(self))
3442 self._suffix = '.backup.%s.%d' % (name, id(self))
3441 repo.dirstate._savebackup(repo.currenttransaction(), self._suffix)
3443 repo.dirstate._savebackup(repo.currenttransaction(), self._suffix)
3442 self._active = True
3444 self._active = True
3443 self._closed = False
3445 self._closed = False
3444
3446
3445 def __del__(self):
3447 def __del__(self):
3446 if self._active: # still active
3448 if self._active: # still active
3447 # this may occur, even if this class is used correctly:
3449 # this may occur, even if this class is used correctly:
3448 # for example, releasing other resources like transaction
3450 # for example, releasing other resources like transaction
3449 # may raise exception before ``dirstateguard.release`` in
3451 # may raise exception before ``dirstateguard.release`` in
3450 # ``release(tr, ....)``.
3452 # ``release(tr, ....)``.
3451 self._abort()
3453 self._abort()
3452
3454
3453 def close(self):
3455 def close(self):
3454 if not self._active: # already inactivated
3456 if not self._active: # already inactivated
3455 msg = (_("can't close already inactivated backup: dirstate%s")
3457 msg = (_("can't close already inactivated backup: dirstate%s")
3456 % self._suffix)
3458 % self._suffix)
3457 raise error.Abort(msg)
3459 raise error.Abort(msg)
3458
3460
3459 self._repo.dirstate._clearbackup(self._repo.currenttransaction(),
3461 self._repo.dirstate._clearbackup(self._repo.currenttransaction(),
3460 self._suffix)
3462 self._suffix)
3461 self._active = False
3463 self._active = False
3462 self._closed = True
3464 self._closed = True
3463
3465
3464 def _abort(self):
3466 def _abort(self):
3465 self._repo.dirstate._restorebackup(self._repo.currenttransaction(),
3467 self._repo.dirstate._restorebackup(self._repo.currenttransaction(),
3466 self._suffix)
3468 self._suffix)
3467 self._active = False
3469 self._active = False
3468
3470
3469 def release(self):
3471 def release(self):
3470 if not self._closed:
3472 if not self._closed:
3471 if not self._active: # already inactivated
3473 if not self._active: # already inactivated
3472 msg = (_("can't release already inactivated backup:"
3474 msg = (_("can't release already inactivated backup:"
3473 " dirstate%s")
3475 " dirstate%s")
3474 % self._suffix)
3476 % self._suffix)
3475 raise error.Abort(msg)
3477 raise error.Abort(msg)
3476 self._abort()
3478 self._abort()
General Comments 0
You need to be logged in to leave comments. Login now