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