##// END OF EJS Templates
cat: replace match.bad() monkey patching with match.badmatch()...
Matt Harbison -
r25438:7ce5a323 default
parent child Browse files
Show More
@@ -1,3364 +1,3358 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
2216
2217 badfn = lambda x, y: bad.append(x) or match.bad(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
2224
2225 for f in wctx.walk(matchmod.badmatch(match, badfn)):
2225 for f in wctx.walk(matchmod.badmatch(match, badfn)):
2226 exact = match.exact(f)
2226 exact = match.exact(f)
2227 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):
2228 if cca:
2228 if cca:
2229 cca(f)
2229 cca(f)
2230 names.append(f)
2230 names.append(f)
2231 if ui.verbose or not exact:
2231 if ui.verbose or not exact:
2232 ui.status(_('adding %s\n') % match.rel(f))
2232 ui.status(_('adding %s\n') % match.rel(f))
2233
2233
2234 for subpath in sorted(wctx.substate):
2234 for subpath in sorted(wctx.substate):
2235 sub = wctx.sub(subpath)
2235 sub = wctx.sub(subpath)
2236 try:
2236 try:
2237 submatch = matchmod.narrowmatcher(subpath, match)
2237 submatch = matchmod.narrowmatcher(subpath, match)
2238 if opts.get('subrepos'):
2238 if opts.get('subrepos'):
2239 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2239 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2240 else:
2240 else:
2241 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2241 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2242 except error.LookupError:
2242 except error.LookupError:
2243 ui.status(_("skipping missing subrepository: %s\n")
2243 ui.status(_("skipping missing subrepository: %s\n")
2244 % join(subpath))
2244 % join(subpath))
2245
2245
2246 if not opts.get('dry_run'):
2246 if not opts.get('dry_run'):
2247 rejected = wctx.add(names, prefix)
2247 rejected = wctx.add(names, prefix)
2248 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())
2249 return bad
2249 return bad
2250
2250
2251 def forget(ui, repo, match, prefix, explicitonly):
2251 def forget(ui, repo, match, prefix, explicitonly):
2252 join = lambda f: os.path.join(prefix, f)
2252 join = lambda f: os.path.join(prefix, f)
2253 bad = []
2253 bad = []
2254 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2254 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2255 wctx = repo[None]
2255 wctx = repo[None]
2256 forgot = []
2256 forgot = []
2257
2257
2258 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2258 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2259 forget = sorted(s[0] + s[1] + s[3] + s[6])
2259 forget = sorted(s[0] + s[1] + s[3] + s[6])
2260 if explicitonly:
2260 if explicitonly:
2261 forget = [f for f in forget if match.exact(f)]
2261 forget = [f for f in forget if match.exact(f)]
2262
2262
2263 for subpath in sorted(wctx.substate):
2263 for subpath in sorted(wctx.substate):
2264 sub = wctx.sub(subpath)
2264 sub = wctx.sub(subpath)
2265 try:
2265 try:
2266 submatch = matchmod.narrowmatcher(subpath, match)
2266 submatch = matchmod.narrowmatcher(subpath, match)
2267 subbad, subforgot = sub.forget(submatch, prefix)
2267 subbad, subforgot = sub.forget(submatch, prefix)
2268 bad.extend([subpath + '/' + f for f in subbad])
2268 bad.extend([subpath + '/' + f for f in subbad])
2269 forgot.extend([subpath + '/' + f for f in subforgot])
2269 forgot.extend([subpath + '/' + f for f in subforgot])
2270 except error.LookupError:
2270 except error.LookupError:
2271 ui.status(_("skipping missing subrepository: %s\n")
2271 ui.status(_("skipping missing subrepository: %s\n")
2272 % join(subpath))
2272 % join(subpath))
2273
2273
2274 if not explicitonly:
2274 if not explicitonly:
2275 for f in match.files():
2275 for f in match.files():
2276 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):
2277 if f not in forgot:
2277 if f not in forgot:
2278 if repo.wvfs.exists(f):
2278 if repo.wvfs.exists(f):
2279 # Don't complain if the exact case match wasn't given.
2279 # Don't complain if the exact case match wasn't given.
2280 # But don't do this until after checking 'forgot', so
2280 # But don't do this until after checking 'forgot', so
2281 # that subrepo files aren't normalized, and this op is
2281 # that subrepo files aren't normalized, and this op is
2282 # purely from data cached by the status walk above.
2282 # purely from data cached by the status walk above.
2283 if repo.dirstate.normalize(f) in repo.dirstate:
2283 if repo.dirstate.normalize(f) in repo.dirstate:
2284 continue
2284 continue
2285 ui.warn(_('not removing %s: '
2285 ui.warn(_('not removing %s: '
2286 'file is already untracked\n')
2286 'file is already untracked\n')
2287 % match.rel(f))
2287 % match.rel(f))
2288 bad.append(f)
2288 bad.append(f)
2289
2289
2290 for f in forget:
2290 for f in forget:
2291 if ui.verbose or not match.exact(f):
2291 if ui.verbose or not match.exact(f):
2292 ui.status(_('removing %s\n') % match.rel(f))
2292 ui.status(_('removing %s\n') % match.rel(f))
2293
2293
2294 rejected = wctx.forget(forget, prefix)
2294 rejected = wctx.forget(forget, prefix)
2295 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())
2296 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)
2297 return bad, forgot
2297 return bad, forgot
2298
2298
2299 def files(ui, ctx, m, fm, fmt, subrepos):
2299 def files(ui, ctx, m, fm, fmt, subrepos):
2300 rev = ctx.rev()
2300 rev = ctx.rev()
2301 ret = 1
2301 ret = 1
2302 ds = ctx.repo().dirstate
2302 ds = ctx.repo().dirstate
2303
2303
2304 for f in ctx.matches(m):
2304 for f in ctx.matches(m):
2305 if rev is None and ds[f] == 'r':
2305 if rev is None and ds[f] == 'r':
2306 continue
2306 continue
2307 fm.startitem()
2307 fm.startitem()
2308 if ui.verbose:
2308 if ui.verbose:
2309 fc = ctx[f]
2309 fc = ctx[f]
2310 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2310 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2311 fm.data(abspath=f)
2311 fm.data(abspath=f)
2312 fm.write('path', fmt, m.rel(f))
2312 fm.write('path', fmt, m.rel(f))
2313 ret = 0
2313 ret = 0
2314
2314
2315 for subpath in sorted(ctx.substate):
2315 for subpath in sorted(ctx.substate):
2316 def matchessubrepo(subpath):
2316 def matchessubrepo(subpath):
2317 return (m.always() or m.exact(subpath)
2317 return (m.always() or m.exact(subpath)
2318 or any(f.startswith(subpath + '/') for f in m.files()))
2318 or any(f.startswith(subpath + '/') for f in m.files()))
2319
2319
2320 if subrepos or matchessubrepo(subpath):
2320 if subrepos or matchessubrepo(subpath):
2321 sub = ctx.sub(subpath)
2321 sub = ctx.sub(subpath)
2322 try:
2322 try:
2323 submatch = matchmod.narrowmatcher(subpath, m)
2323 submatch = matchmod.narrowmatcher(subpath, m)
2324 if sub.printfiles(ui, submatch, fm, fmt, subrepos) == 0:
2324 if sub.printfiles(ui, submatch, fm, fmt, subrepos) == 0:
2325 ret = 0
2325 ret = 0
2326 except error.LookupError:
2326 except error.LookupError:
2327 ui.status(_("skipping missing subrepository: %s\n")
2327 ui.status(_("skipping missing subrepository: %s\n")
2328 % m.abs(subpath))
2328 % m.abs(subpath))
2329
2329
2330 return ret
2330 return ret
2331
2331
2332 def remove(ui, repo, m, prefix, after, force, subrepos):
2332 def remove(ui, repo, m, prefix, after, force, subrepos):
2333 join = lambda f: os.path.join(prefix, f)
2333 join = lambda f: os.path.join(prefix, f)
2334 ret = 0
2334 ret = 0
2335 s = repo.status(match=m, clean=True)
2335 s = repo.status(match=m, clean=True)
2336 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]
2337
2337
2338 wctx = repo[None]
2338 wctx = repo[None]
2339
2339
2340 for subpath in sorted(wctx.substate):
2340 for subpath in sorted(wctx.substate):
2341 def matchessubrepo(matcher, subpath):
2341 def matchessubrepo(matcher, subpath):
2342 if matcher.exact(subpath):
2342 if matcher.exact(subpath):
2343 return True
2343 return True
2344 for f in matcher.files():
2344 for f in matcher.files():
2345 if f.startswith(subpath):
2345 if f.startswith(subpath):
2346 return True
2346 return True
2347 return False
2347 return False
2348
2348
2349 if subrepos or matchessubrepo(m, subpath):
2349 if subrepos or matchessubrepo(m, subpath):
2350 sub = wctx.sub(subpath)
2350 sub = wctx.sub(subpath)
2351 try:
2351 try:
2352 submatch = matchmod.narrowmatcher(subpath, m)
2352 submatch = matchmod.narrowmatcher(subpath, m)
2353 if sub.removefiles(submatch, prefix, after, force, subrepos):
2353 if sub.removefiles(submatch, prefix, after, force, subrepos):
2354 ret = 1
2354 ret = 1
2355 except error.LookupError:
2355 except error.LookupError:
2356 ui.status(_("skipping missing subrepository: %s\n")
2356 ui.status(_("skipping missing subrepository: %s\n")
2357 % join(subpath))
2357 % join(subpath))
2358
2358
2359 # warn about failure to delete explicit files/dirs
2359 # warn about failure to delete explicit files/dirs
2360 deleteddirs = util.dirs(deleted)
2360 deleteddirs = util.dirs(deleted)
2361 for f in m.files():
2361 for f in m.files():
2362 def insubrepo():
2362 def insubrepo():
2363 for subpath in wctx.substate:
2363 for subpath in wctx.substate:
2364 if f.startswith(subpath):
2364 if f.startswith(subpath):
2365 return True
2365 return True
2366 return False
2366 return False
2367
2367
2368 isdir = f in deleteddirs or wctx.hasdir(f)
2368 isdir = f in deleteddirs or wctx.hasdir(f)
2369 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2369 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2370 continue
2370 continue
2371
2371
2372 if repo.wvfs.exists(f):
2372 if repo.wvfs.exists(f):
2373 if repo.wvfs.isdir(f):
2373 if repo.wvfs.isdir(f):
2374 ui.warn(_('not removing %s: no tracked files\n')
2374 ui.warn(_('not removing %s: no tracked files\n')
2375 % m.rel(f))
2375 % m.rel(f))
2376 else:
2376 else:
2377 ui.warn(_('not removing %s: file is untracked\n')
2377 ui.warn(_('not removing %s: file is untracked\n')
2378 % m.rel(f))
2378 % m.rel(f))
2379 # missing files will generate a warning elsewhere
2379 # missing files will generate a warning elsewhere
2380 ret = 1
2380 ret = 1
2381
2381
2382 if force:
2382 if force:
2383 list = modified + deleted + clean + added
2383 list = modified + deleted + clean + added
2384 elif after:
2384 elif after:
2385 list = deleted
2385 list = deleted
2386 for f in modified + added + clean:
2386 for f in modified + added + clean:
2387 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))
2388 ret = 1
2388 ret = 1
2389 else:
2389 else:
2390 list = deleted + clean
2390 list = deleted + clean
2391 for f in modified:
2391 for f in modified:
2392 ui.warn(_('not removing %s: file is modified (use -f'
2392 ui.warn(_('not removing %s: file is modified (use -f'
2393 ' to force removal)\n') % m.rel(f))
2393 ' to force removal)\n') % m.rel(f))
2394 ret = 1
2394 ret = 1
2395 for f in added:
2395 for f in added:
2396 ui.warn(_('not removing %s: file has been marked for add'
2396 ui.warn(_('not removing %s: file has been marked for add'
2397 ' (use forget to undo)\n') % m.rel(f))
2397 ' (use forget to undo)\n') % m.rel(f))
2398 ret = 1
2398 ret = 1
2399
2399
2400 for f in sorted(list):
2400 for f in sorted(list):
2401 if ui.verbose or not m.exact(f):
2401 if ui.verbose or not m.exact(f):
2402 ui.status(_('removing %s\n') % m.rel(f))
2402 ui.status(_('removing %s\n') % m.rel(f))
2403
2403
2404 wlock = repo.wlock()
2404 wlock = repo.wlock()
2405 try:
2405 try:
2406 if not after:
2406 if not after:
2407 for f in list:
2407 for f in list:
2408 if f in added:
2408 if f in added:
2409 continue # we never unlink added files on remove
2409 continue # we never unlink added files on remove
2410 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2410 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2411 repo[None].forget(list)
2411 repo[None].forget(list)
2412 finally:
2412 finally:
2413 wlock.release()
2413 wlock.release()
2414
2414
2415 return ret
2415 return ret
2416
2416
2417 def cat(ui, repo, ctx, matcher, prefix, **opts):
2417 def cat(ui, repo, ctx, matcher, prefix, **opts):
2418 err = 1
2418 err = 1
2419
2419
2420 def write(path):
2420 def write(path):
2421 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2421 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2422 pathname=os.path.join(prefix, path))
2422 pathname=os.path.join(prefix, path))
2423 data = ctx[path].data()
2423 data = ctx[path].data()
2424 if opts.get('decode'):
2424 if opts.get('decode'):
2425 data = repo.wwritedata(path, data)
2425 data = repo.wwritedata(path, data)
2426 fp.write(data)
2426 fp.write(data)
2427 fp.close()
2427 fp.close()
2428
2428
2429 # 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
2430 # for performance to avoid the cost of parsing the manifest.
2430 # for performance to avoid the cost of parsing the manifest.
2431 if len(matcher.files()) == 1 and not matcher.anypats():
2431 if len(matcher.files()) == 1 and not matcher.anypats():
2432 file = matcher.files()[0]
2432 file = matcher.files()[0]
2433 mf = repo.manifest
2433 mf = repo.manifest
2434 mfnode = ctx.manifestnode()
2434 mfnode = ctx.manifestnode()
2435 if mfnode and mf.find(mfnode, file)[0]:
2435 if mfnode and mf.find(mfnode, file)[0]:
2436 write(file)
2436 write(file)
2437 return 0
2437 return 0
2438
2438
2439 # Don't warn about "missing" files that are really in subrepos
2439 # Don't warn about "missing" files that are really in subrepos
2440 bad = matcher.bad
2441
2442 def badfn(path, msg):
2440 def badfn(path, msg):
2443 for subpath in ctx.substate:
2441 for subpath in ctx.substate:
2444 if path.startswith(subpath):
2442 if path.startswith(subpath):
2445 return
2443 return
2446 bad(path, msg)
2444 matcher.bad(path, msg)
2447
2445
2448 matcher.bad = badfn
2446 for abs in ctx.walk(matchmod.badmatch(matcher, badfn)):
2449
2450 for abs in ctx.walk(matcher):
2451 write(abs)
2447 write(abs)
2452 err = 0
2448 err = 0
2453
2449
2454 matcher.bad = bad
2455
2456 for subpath in sorted(ctx.substate):
2450 for subpath in sorted(ctx.substate):
2457 sub = ctx.sub(subpath)
2451 sub = ctx.sub(subpath)
2458 try:
2452 try:
2459 submatch = matchmod.narrowmatcher(subpath, matcher)
2453 submatch = matchmod.narrowmatcher(subpath, matcher)
2460
2454
2461 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2455 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2462 **opts):
2456 **opts):
2463 err = 0
2457 err = 0
2464 except error.RepoLookupError:
2458 except error.RepoLookupError:
2465 ui.status(_("skipping missing subrepository: %s\n")
2459 ui.status(_("skipping missing subrepository: %s\n")
2466 % os.path.join(prefix, subpath))
2460 % os.path.join(prefix, subpath))
2467
2461
2468 return err
2462 return err
2469
2463
2470 def commit(ui, repo, commitfunc, pats, opts):
2464 def commit(ui, repo, commitfunc, pats, opts):
2471 '''commit the specified files or all outstanding changes'''
2465 '''commit the specified files or all outstanding changes'''
2472 date = opts.get('date')
2466 date = opts.get('date')
2473 if date:
2467 if date:
2474 opts['date'] = util.parsedate(date)
2468 opts['date'] = util.parsedate(date)
2475 message = logmessage(ui, opts)
2469 message = logmessage(ui, opts)
2476 matcher = scmutil.match(repo[None], pats, opts)
2470 matcher = scmutil.match(repo[None], pats, opts)
2477
2471
2478 # extract addremove carefully -- this function can be called from a command
2472 # extract addremove carefully -- this function can be called from a command
2479 # that doesn't support addremove
2473 # that doesn't support addremove
2480 if opts.get('addremove'):
2474 if opts.get('addremove'):
2481 if scmutil.addremove(repo, matcher, "", opts) != 0:
2475 if scmutil.addremove(repo, matcher, "", opts) != 0:
2482 raise util.Abort(
2476 raise util.Abort(
2483 _("failed to mark all new/missing files as added/removed"))
2477 _("failed to mark all new/missing files as added/removed"))
2484
2478
2485 return commitfunc(ui, repo, message, matcher, opts)
2479 return commitfunc(ui, repo, message, matcher, opts)
2486
2480
2487 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2481 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2488 # amend will reuse the existing user if not specified, but the obsolete
2482 # amend will reuse the existing user if not specified, but the obsolete
2489 # marker creation requires that the current user's name is specified.
2483 # marker creation requires that the current user's name is specified.
2490 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2484 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2491 ui.username() # raise exception if username not set
2485 ui.username() # raise exception if username not set
2492
2486
2493 ui.note(_('amending changeset %s\n') % old)
2487 ui.note(_('amending changeset %s\n') % old)
2494 base = old.p1()
2488 base = old.p1()
2495
2489
2496 wlock = dsguard = lock = newid = None
2490 wlock = dsguard = lock = newid = None
2497 try:
2491 try:
2498 wlock = repo.wlock()
2492 wlock = repo.wlock()
2499 dsguard = dirstateguard(repo, 'amend')
2493 dsguard = dirstateguard(repo, 'amend')
2500 lock = repo.lock()
2494 lock = repo.lock()
2501 tr = repo.transaction('amend')
2495 tr = repo.transaction('amend')
2502 try:
2496 try:
2503 # See if we got a message from -m or -l, if not, open the editor
2497 # See if we got a message from -m or -l, if not, open the editor
2504 # with the message of the changeset to amend
2498 # with the message of the changeset to amend
2505 message = logmessage(ui, opts)
2499 message = logmessage(ui, opts)
2506 # ensure logfile does not conflict with later enforcement of the
2500 # ensure logfile does not conflict with later enforcement of the
2507 # message. potential logfile content has been processed by
2501 # message. potential logfile content has been processed by
2508 # `logmessage` anyway.
2502 # `logmessage` anyway.
2509 opts.pop('logfile')
2503 opts.pop('logfile')
2510 # First, do a regular commit to record all changes in the working
2504 # First, do a regular commit to record all changes in the working
2511 # directory (if there are any)
2505 # directory (if there are any)
2512 ui.callhooks = False
2506 ui.callhooks = False
2513 activebookmark = repo._activebookmark
2507 activebookmark = repo._activebookmark
2514 try:
2508 try:
2515 repo._activebookmark = None
2509 repo._activebookmark = None
2516 opts['message'] = 'temporary amend commit for %s' % old
2510 opts['message'] = 'temporary amend commit for %s' % old
2517 node = commit(ui, repo, commitfunc, pats, opts)
2511 node = commit(ui, repo, commitfunc, pats, opts)
2518 finally:
2512 finally:
2519 repo._activebookmark = activebookmark
2513 repo._activebookmark = activebookmark
2520 ui.callhooks = True
2514 ui.callhooks = True
2521 ctx = repo[node]
2515 ctx = repo[node]
2522
2516
2523 # Participating changesets:
2517 # Participating changesets:
2524 #
2518 #
2525 # node/ctx o - new (intermediate) commit that contains changes
2519 # node/ctx o - new (intermediate) commit that contains changes
2526 # | from working dir to go into amending commit
2520 # | from working dir to go into amending commit
2527 # | (or a workingctx if there were no changes)
2521 # | (or a workingctx if there were no changes)
2528 # |
2522 # |
2529 # old o - changeset to amend
2523 # old o - changeset to amend
2530 # |
2524 # |
2531 # base o - parent of amending changeset
2525 # base o - parent of amending changeset
2532
2526
2533 # Update extra dict from amended commit (e.g. to preserve graft
2527 # Update extra dict from amended commit (e.g. to preserve graft
2534 # source)
2528 # source)
2535 extra.update(old.extra())
2529 extra.update(old.extra())
2536
2530
2537 # Also update it from the intermediate commit or from the wctx
2531 # Also update it from the intermediate commit or from the wctx
2538 extra.update(ctx.extra())
2532 extra.update(ctx.extra())
2539
2533
2540 if len(old.parents()) > 1:
2534 if len(old.parents()) > 1:
2541 # ctx.files() isn't reliable for merges, so fall back to the
2535 # ctx.files() isn't reliable for merges, so fall back to the
2542 # slower repo.status() method
2536 # slower repo.status() method
2543 files = set([fn for st in repo.status(base, old)[:3]
2537 files = set([fn for st in repo.status(base, old)[:3]
2544 for fn in st])
2538 for fn in st])
2545 else:
2539 else:
2546 files = set(old.files())
2540 files = set(old.files())
2547
2541
2548 # Second, we use either the commit we just did, or if there were no
2542 # Second, we use either the commit we just did, or if there were no
2549 # changes the parent of the working directory as the version of the
2543 # changes the parent of the working directory as the version of the
2550 # files in the final amend commit
2544 # files in the final amend commit
2551 if node:
2545 if node:
2552 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2546 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2553
2547
2554 user = ctx.user()
2548 user = ctx.user()
2555 date = ctx.date()
2549 date = ctx.date()
2556 # Recompute copies (avoid recording a -> b -> a)
2550 # Recompute copies (avoid recording a -> b -> a)
2557 copied = copies.pathcopies(base, ctx)
2551 copied = copies.pathcopies(base, ctx)
2558 if old.p2:
2552 if old.p2:
2559 copied.update(copies.pathcopies(old.p2(), ctx))
2553 copied.update(copies.pathcopies(old.p2(), ctx))
2560
2554
2561 # Prune files which were reverted by the updates: if old
2555 # Prune files which were reverted by the updates: if old
2562 # introduced file X and our intermediate commit, node,
2556 # introduced file X and our intermediate commit, node,
2563 # renamed that file, then those two files are the same and
2557 # renamed that file, then those two files are the same and
2564 # we can discard X from our list of files. Likewise if X
2558 # we can discard X from our list of files. Likewise if X
2565 # was deleted, it's no longer relevant
2559 # was deleted, it's no longer relevant
2566 files.update(ctx.files())
2560 files.update(ctx.files())
2567
2561
2568 def samefile(f):
2562 def samefile(f):
2569 if f in ctx.manifest():
2563 if f in ctx.manifest():
2570 a = ctx.filectx(f)
2564 a = ctx.filectx(f)
2571 if f in base.manifest():
2565 if f in base.manifest():
2572 b = base.filectx(f)
2566 b = base.filectx(f)
2573 return (not a.cmp(b)
2567 return (not a.cmp(b)
2574 and a.flags() == b.flags())
2568 and a.flags() == b.flags())
2575 else:
2569 else:
2576 return False
2570 return False
2577 else:
2571 else:
2578 return f not in base.manifest()
2572 return f not in base.manifest()
2579 files = [f for f in files if not samefile(f)]
2573 files = [f for f in files if not samefile(f)]
2580
2574
2581 def filectxfn(repo, ctx_, path):
2575 def filectxfn(repo, ctx_, path):
2582 try:
2576 try:
2583 fctx = ctx[path]
2577 fctx = ctx[path]
2584 flags = fctx.flags()
2578 flags = fctx.flags()
2585 mctx = context.memfilectx(repo,
2579 mctx = context.memfilectx(repo,
2586 fctx.path(), fctx.data(),
2580 fctx.path(), fctx.data(),
2587 islink='l' in flags,
2581 islink='l' in flags,
2588 isexec='x' in flags,
2582 isexec='x' in flags,
2589 copied=copied.get(path))
2583 copied=copied.get(path))
2590 return mctx
2584 return mctx
2591 except KeyError:
2585 except KeyError:
2592 return None
2586 return None
2593 else:
2587 else:
2594 ui.note(_('copying changeset %s to %s\n') % (old, base))
2588 ui.note(_('copying changeset %s to %s\n') % (old, base))
2595
2589
2596 # Use version of files as in the old cset
2590 # Use version of files as in the old cset
2597 def filectxfn(repo, ctx_, path):
2591 def filectxfn(repo, ctx_, path):
2598 try:
2592 try:
2599 return old.filectx(path)
2593 return old.filectx(path)
2600 except KeyError:
2594 except KeyError:
2601 return None
2595 return None
2602
2596
2603 user = opts.get('user') or old.user()
2597 user = opts.get('user') or old.user()
2604 date = opts.get('date') or old.date()
2598 date = opts.get('date') or old.date()
2605 editform = mergeeditform(old, 'commit.amend')
2599 editform = mergeeditform(old, 'commit.amend')
2606 editor = getcommiteditor(editform=editform, **opts)
2600 editor = getcommiteditor(editform=editform, **opts)
2607 if not message:
2601 if not message:
2608 editor = getcommiteditor(edit=True, editform=editform)
2602 editor = getcommiteditor(edit=True, editform=editform)
2609 message = old.description()
2603 message = old.description()
2610
2604
2611 pureextra = extra.copy()
2605 pureextra = extra.copy()
2612 extra['amend_source'] = old.hex()
2606 extra['amend_source'] = old.hex()
2613
2607
2614 new = context.memctx(repo,
2608 new = context.memctx(repo,
2615 parents=[base.node(), old.p2().node()],
2609 parents=[base.node(), old.p2().node()],
2616 text=message,
2610 text=message,
2617 files=files,
2611 files=files,
2618 filectxfn=filectxfn,
2612 filectxfn=filectxfn,
2619 user=user,
2613 user=user,
2620 date=date,
2614 date=date,
2621 extra=extra,
2615 extra=extra,
2622 editor=editor)
2616 editor=editor)
2623
2617
2624 newdesc = changelog.stripdesc(new.description())
2618 newdesc = changelog.stripdesc(new.description())
2625 if ((not node)
2619 if ((not node)
2626 and newdesc == old.description()
2620 and newdesc == old.description()
2627 and user == old.user()
2621 and user == old.user()
2628 and date == old.date()
2622 and date == old.date()
2629 and pureextra == old.extra()):
2623 and pureextra == old.extra()):
2630 # nothing changed. continuing here would create a new node
2624 # nothing changed. continuing here would create a new node
2631 # anyway because of the amend_source noise.
2625 # anyway because of the amend_source noise.
2632 #
2626 #
2633 # This not what we expect from amend.
2627 # This not what we expect from amend.
2634 return old.node()
2628 return old.node()
2635
2629
2636 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2630 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2637 try:
2631 try:
2638 if opts.get('secret'):
2632 if opts.get('secret'):
2639 commitphase = 'secret'
2633 commitphase = 'secret'
2640 else:
2634 else:
2641 commitphase = old.phase()
2635 commitphase = old.phase()
2642 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2636 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2643 newid = repo.commitctx(new)
2637 newid = repo.commitctx(new)
2644 finally:
2638 finally:
2645 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2639 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2646 if newid != old.node():
2640 if newid != old.node():
2647 # Reroute the working copy parent to the new changeset
2641 # Reroute the working copy parent to the new changeset
2648 repo.setparents(newid, nullid)
2642 repo.setparents(newid, nullid)
2649
2643
2650 # Move bookmarks from old parent to amend commit
2644 # Move bookmarks from old parent to amend commit
2651 bms = repo.nodebookmarks(old.node())
2645 bms = repo.nodebookmarks(old.node())
2652 if bms:
2646 if bms:
2653 marks = repo._bookmarks
2647 marks = repo._bookmarks
2654 for bm in bms:
2648 for bm in bms:
2655 marks[bm] = newid
2649 marks[bm] = newid
2656 marks.write()
2650 marks.write()
2657 #commit the whole amend process
2651 #commit the whole amend process
2658 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2652 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2659 if createmarkers and newid != old.node():
2653 if createmarkers and newid != old.node():
2660 # mark the new changeset as successor of the rewritten one
2654 # mark the new changeset as successor of the rewritten one
2661 new = repo[newid]
2655 new = repo[newid]
2662 obs = [(old, (new,))]
2656 obs = [(old, (new,))]
2663 if node:
2657 if node:
2664 obs.append((ctx, ()))
2658 obs.append((ctx, ()))
2665
2659
2666 obsolete.createmarkers(repo, obs)
2660 obsolete.createmarkers(repo, obs)
2667 tr.close()
2661 tr.close()
2668 finally:
2662 finally:
2669 tr.release()
2663 tr.release()
2670 dsguard.close()
2664 dsguard.close()
2671 if not createmarkers and newid != old.node():
2665 if not createmarkers and newid != old.node():
2672 # Strip the intermediate commit (if there was one) and the amended
2666 # Strip the intermediate commit (if there was one) and the amended
2673 # commit
2667 # commit
2674 if node:
2668 if node:
2675 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2669 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2676 ui.note(_('stripping amended changeset %s\n') % old)
2670 ui.note(_('stripping amended changeset %s\n') % old)
2677 repair.strip(ui, repo, old.node(), topic='amend-backup')
2671 repair.strip(ui, repo, old.node(), topic='amend-backup')
2678 finally:
2672 finally:
2679 lockmod.release(lock, dsguard, wlock)
2673 lockmod.release(lock, dsguard, wlock)
2680 return newid
2674 return newid
2681
2675
2682 def commiteditor(repo, ctx, subs, editform=''):
2676 def commiteditor(repo, ctx, subs, editform=''):
2683 if ctx.description():
2677 if ctx.description():
2684 return ctx.description()
2678 return ctx.description()
2685 return commitforceeditor(repo, ctx, subs, editform=editform)
2679 return commitforceeditor(repo, ctx, subs, editform=editform)
2686
2680
2687 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2681 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2688 editform=''):
2682 editform=''):
2689 if not extramsg:
2683 if not extramsg:
2690 extramsg = _("Leave message empty to abort commit.")
2684 extramsg = _("Leave message empty to abort commit.")
2691
2685
2692 forms = [e for e in editform.split('.') if e]
2686 forms = [e for e in editform.split('.') if e]
2693 forms.insert(0, 'changeset')
2687 forms.insert(0, 'changeset')
2694 while forms:
2688 while forms:
2695 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2689 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2696 if tmpl:
2690 if tmpl:
2697 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2691 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2698 break
2692 break
2699 forms.pop()
2693 forms.pop()
2700 else:
2694 else:
2701 committext = buildcommittext(repo, ctx, subs, extramsg)
2695 committext = buildcommittext(repo, ctx, subs, extramsg)
2702
2696
2703 # run editor in the repository root
2697 # run editor in the repository root
2704 olddir = os.getcwd()
2698 olddir = os.getcwd()
2705 os.chdir(repo.root)
2699 os.chdir(repo.root)
2706 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2700 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2707 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2701 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2708 os.chdir(olddir)
2702 os.chdir(olddir)
2709
2703
2710 if finishdesc:
2704 if finishdesc:
2711 text = finishdesc(text)
2705 text = finishdesc(text)
2712 if not text.strip():
2706 if not text.strip():
2713 raise util.Abort(_("empty commit message"))
2707 raise util.Abort(_("empty commit message"))
2714
2708
2715 return text
2709 return text
2716
2710
2717 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2711 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2718 ui = repo.ui
2712 ui = repo.ui
2719 tmpl, mapfile = gettemplate(ui, tmpl, None)
2713 tmpl, mapfile = gettemplate(ui, tmpl, None)
2720
2714
2721 try:
2715 try:
2722 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2716 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2723 except SyntaxError, inst:
2717 except SyntaxError, inst:
2724 raise util.Abort(inst.args[0])
2718 raise util.Abort(inst.args[0])
2725
2719
2726 for k, v in repo.ui.configitems('committemplate'):
2720 for k, v in repo.ui.configitems('committemplate'):
2727 if k != 'changeset':
2721 if k != 'changeset':
2728 t.t.cache[k] = v
2722 t.t.cache[k] = v
2729
2723
2730 if not extramsg:
2724 if not extramsg:
2731 extramsg = '' # ensure that extramsg is string
2725 extramsg = '' # ensure that extramsg is string
2732
2726
2733 ui.pushbuffer()
2727 ui.pushbuffer()
2734 t.show(ctx, extramsg=extramsg)
2728 t.show(ctx, extramsg=extramsg)
2735 return ui.popbuffer()
2729 return ui.popbuffer()
2736
2730
2737 def buildcommittext(repo, ctx, subs, extramsg):
2731 def buildcommittext(repo, ctx, subs, extramsg):
2738 edittext = []
2732 edittext = []
2739 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2733 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2740 if ctx.description():
2734 if ctx.description():
2741 edittext.append(ctx.description())
2735 edittext.append(ctx.description())
2742 edittext.append("")
2736 edittext.append("")
2743 edittext.append("") # Empty line between message and comments.
2737 edittext.append("") # Empty line between message and comments.
2744 edittext.append(_("HG: Enter commit message."
2738 edittext.append(_("HG: Enter commit message."
2745 " Lines beginning with 'HG:' are removed."))
2739 " Lines beginning with 'HG:' are removed."))
2746 edittext.append("HG: %s" % extramsg)
2740 edittext.append("HG: %s" % extramsg)
2747 edittext.append("HG: --")
2741 edittext.append("HG: --")
2748 edittext.append(_("HG: user: %s") % ctx.user())
2742 edittext.append(_("HG: user: %s") % ctx.user())
2749 if ctx.p2():
2743 if ctx.p2():
2750 edittext.append(_("HG: branch merge"))
2744 edittext.append(_("HG: branch merge"))
2751 if ctx.branch():
2745 if ctx.branch():
2752 edittext.append(_("HG: branch '%s'") % ctx.branch())
2746 edittext.append(_("HG: branch '%s'") % ctx.branch())
2753 if bookmarks.isactivewdirparent(repo):
2747 if bookmarks.isactivewdirparent(repo):
2754 edittext.append(_("HG: bookmark '%s'") % repo._activebookmark)
2748 edittext.append(_("HG: bookmark '%s'") % repo._activebookmark)
2755 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2749 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2756 edittext.extend([_("HG: added %s") % f for f in added])
2750 edittext.extend([_("HG: added %s") % f for f in added])
2757 edittext.extend([_("HG: changed %s") % f for f in modified])
2751 edittext.extend([_("HG: changed %s") % f for f in modified])
2758 edittext.extend([_("HG: removed %s") % f for f in removed])
2752 edittext.extend([_("HG: removed %s") % f for f in removed])
2759 if not added and not modified and not removed:
2753 if not added and not modified and not removed:
2760 edittext.append(_("HG: no files changed"))
2754 edittext.append(_("HG: no files changed"))
2761 edittext.append("")
2755 edittext.append("")
2762
2756
2763 return "\n".join(edittext)
2757 return "\n".join(edittext)
2764
2758
2765 def commitstatus(repo, node, branch, bheads=None, opts={}):
2759 def commitstatus(repo, node, branch, bheads=None, opts={}):
2766 ctx = repo[node]
2760 ctx = repo[node]
2767 parents = ctx.parents()
2761 parents = ctx.parents()
2768
2762
2769 if (not opts.get('amend') and bheads and node not in bheads and not
2763 if (not opts.get('amend') and bheads and node not in bheads and not
2770 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2764 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2771 repo.ui.status(_('created new head\n'))
2765 repo.ui.status(_('created new head\n'))
2772 # The message is not printed for initial roots. For the other
2766 # The message is not printed for initial roots. For the other
2773 # changesets, it is printed in the following situations:
2767 # changesets, it is printed in the following situations:
2774 #
2768 #
2775 # Par column: for the 2 parents with ...
2769 # Par column: for the 2 parents with ...
2776 # N: null or no parent
2770 # N: null or no parent
2777 # B: parent is on another named branch
2771 # B: parent is on another named branch
2778 # C: parent is a regular non head changeset
2772 # C: parent is a regular non head changeset
2779 # H: parent was a branch head of the current branch
2773 # H: parent was a branch head of the current branch
2780 # Msg column: whether we print "created new head" message
2774 # Msg column: whether we print "created new head" message
2781 # In the following, it is assumed that there already exists some
2775 # In the following, it is assumed that there already exists some
2782 # initial branch heads of the current branch, otherwise nothing is
2776 # initial branch heads of the current branch, otherwise nothing is
2783 # printed anyway.
2777 # printed anyway.
2784 #
2778 #
2785 # Par Msg Comment
2779 # Par Msg Comment
2786 # N N y additional topo root
2780 # N N y additional topo root
2787 #
2781 #
2788 # B N y additional branch root
2782 # B N y additional branch root
2789 # C N y additional topo head
2783 # C N y additional topo head
2790 # H N n usual case
2784 # H N n usual case
2791 #
2785 #
2792 # B B y weird additional branch root
2786 # B B y weird additional branch root
2793 # C B y branch merge
2787 # C B y branch merge
2794 # H B n merge with named branch
2788 # H B n merge with named branch
2795 #
2789 #
2796 # C C y additional head from merge
2790 # C C y additional head from merge
2797 # C H n merge with a head
2791 # C H n merge with a head
2798 #
2792 #
2799 # H H n head merge: head count decreases
2793 # H H n head merge: head count decreases
2800
2794
2801 if not opts.get('close_branch'):
2795 if not opts.get('close_branch'):
2802 for r in parents:
2796 for r in parents:
2803 if r.closesbranch() and r.branch() == branch:
2797 if r.closesbranch() and r.branch() == branch:
2804 repo.ui.status(_('reopening closed branch head %d\n') % r)
2798 repo.ui.status(_('reopening closed branch head %d\n') % r)
2805
2799
2806 if repo.ui.debugflag:
2800 if repo.ui.debugflag:
2807 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2801 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2808 elif repo.ui.verbose:
2802 elif repo.ui.verbose:
2809 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2803 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2810
2804
2811 def revert(ui, repo, ctx, parents, *pats, **opts):
2805 def revert(ui, repo, ctx, parents, *pats, **opts):
2812 parent, p2 = parents
2806 parent, p2 = parents
2813 node = ctx.node()
2807 node = ctx.node()
2814
2808
2815 mf = ctx.manifest()
2809 mf = ctx.manifest()
2816 if node == p2:
2810 if node == p2:
2817 parent = p2
2811 parent = p2
2818 if node == parent:
2812 if node == parent:
2819 pmf = mf
2813 pmf = mf
2820 else:
2814 else:
2821 pmf = None
2815 pmf = None
2822
2816
2823 # need all matching names in dirstate and manifest of target rev,
2817 # need all matching names in dirstate and manifest of target rev,
2824 # so have to walk both. do not print errors if files exist in one
2818 # so have to walk both. do not print errors if files exist in one
2825 # but not other. in both cases, filesets should be evaluated against
2819 # but not other. in both cases, filesets should be evaluated against
2826 # workingctx to get consistent result (issue4497). this means 'set:**'
2820 # workingctx to get consistent result (issue4497). this means 'set:**'
2827 # cannot be used to select missing files from target rev.
2821 # cannot be used to select missing files from target rev.
2828
2822
2829 # `names` is a mapping for all elements in working copy and target revision
2823 # `names` is a mapping for all elements in working copy and target revision
2830 # The mapping is in the form:
2824 # The mapping is in the form:
2831 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2825 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2832 names = {}
2826 names = {}
2833
2827
2834 wlock = repo.wlock()
2828 wlock = repo.wlock()
2835 try:
2829 try:
2836 ## filling of the `names` mapping
2830 ## filling of the `names` mapping
2837 # walk dirstate to fill `names`
2831 # walk dirstate to fill `names`
2838
2832
2839 interactive = opts.get('interactive', False)
2833 interactive = opts.get('interactive', False)
2840 wctx = repo[None]
2834 wctx = repo[None]
2841 m = scmutil.match(wctx, pats, opts)
2835 m = scmutil.match(wctx, pats, opts)
2842
2836
2843 # we'll need this later
2837 # we'll need this later
2844 targetsubs = sorted(s for s in wctx.substate if m(s))
2838 targetsubs = sorted(s for s in wctx.substate if m(s))
2845
2839
2846 if not m.always():
2840 if not m.always():
2847 m.bad = lambda x, y: False
2841 m.bad = lambda x, y: False
2848 for abs in repo.walk(m):
2842 for abs in repo.walk(m):
2849 names[abs] = m.rel(abs), m.exact(abs)
2843 names[abs] = m.rel(abs), m.exact(abs)
2850
2844
2851 # walk target manifest to fill `names`
2845 # walk target manifest to fill `names`
2852
2846
2853 def badfn(path, msg):
2847 def badfn(path, msg):
2854 if path in names:
2848 if path in names:
2855 return
2849 return
2856 if path in ctx.substate:
2850 if path in ctx.substate:
2857 return
2851 return
2858 path_ = path + '/'
2852 path_ = path + '/'
2859 for f in names:
2853 for f in names:
2860 if f.startswith(path_):
2854 if f.startswith(path_):
2861 return
2855 return
2862 ui.warn("%s: %s\n" % (m.rel(path), msg))
2856 ui.warn("%s: %s\n" % (m.rel(path), msg))
2863
2857
2864 m.bad = badfn
2858 m.bad = badfn
2865 for abs in ctx.walk(m):
2859 for abs in ctx.walk(m):
2866 if abs not in names:
2860 if abs not in names:
2867 names[abs] = m.rel(abs), m.exact(abs)
2861 names[abs] = m.rel(abs), m.exact(abs)
2868
2862
2869 # Find status of all file in `names`.
2863 # Find status of all file in `names`.
2870 m = scmutil.matchfiles(repo, names)
2864 m = scmutil.matchfiles(repo, names)
2871
2865
2872 changes = repo.status(node1=node, match=m,
2866 changes = repo.status(node1=node, match=m,
2873 unknown=True, ignored=True, clean=True)
2867 unknown=True, ignored=True, clean=True)
2874 else:
2868 else:
2875 changes = repo.status(node1=node, match=m)
2869 changes = repo.status(node1=node, match=m)
2876 for kind in changes:
2870 for kind in changes:
2877 for abs in kind:
2871 for abs in kind:
2878 names[abs] = m.rel(abs), m.exact(abs)
2872 names[abs] = m.rel(abs), m.exact(abs)
2879
2873
2880 m = scmutil.matchfiles(repo, names)
2874 m = scmutil.matchfiles(repo, names)
2881
2875
2882 modified = set(changes.modified)
2876 modified = set(changes.modified)
2883 added = set(changes.added)
2877 added = set(changes.added)
2884 removed = set(changes.removed)
2878 removed = set(changes.removed)
2885 _deleted = set(changes.deleted)
2879 _deleted = set(changes.deleted)
2886 unknown = set(changes.unknown)
2880 unknown = set(changes.unknown)
2887 unknown.update(changes.ignored)
2881 unknown.update(changes.ignored)
2888 clean = set(changes.clean)
2882 clean = set(changes.clean)
2889 modadded = set()
2883 modadded = set()
2890
2884
2891 # split between files known in target manifest and the others
2885 # split between files known in target manifest and the others
2892 smf = set(mf)
2886 smf = set(mf)
2893
2887
2894 # determine the exact nature of the deleted changesets
2888 # determine the exact nature of the deleted changesets
2895 deladded = _deleted - smf
2889 deladded = _deleted - smf
2896 deleted = _deleted - deladded
2890 deleted = _deleted - deladded
2897
2891
2898 # We need to account for the state of the file in the dirstate,
2892 # We need to account for the state of the file in the dirstate,
2899 # even when we revert against something else than parent. This will
2893 # even when we revert against something else than parent. This will
2900 # slightly alter the behavior of revert (doing back up or not, delete
2894 # slightly alter the behavior of revert (doing back up or not, delete
2901 # or just forget etc).
2895 # or just forget etc).
2902 if parent == node:
2896 if parent == node:
2903 dsmodified = modified
2897 dsmodified = modified
2904 dsadded = added
2898 dsadded = added
2905 dsremoved = removed
2899 dsremoved = removed
2906 # store all local modifications, useful later for rename detection
2900 # store all local modifications, useful later for rename detection
2907 localchanges = dsmodified | dsadded
2901 localchanges = dsmodified | dsadded
2908 modified, added, removed = set(), set(), set()
2902 modified, added, removed = set(), set(), set()
2909 else:
2903 else:
2910 changes = repo.status(node1=parent, match=m)
2904 changes = repo.status(node1=parent, match=m)
2911 dsmodified = set(changes.modified)
2905 dsmodified = set(changes.modified)
2912 dsadded = set(changes.added)
2906 dsadded = set(changes.added)
2913 dsremoved = set(changes.removed)
2907 dsremoved = set(changes.removed)
2914 # store all local modifications, useful later for rename detection
2908 # store all local modifications, useful later for rename detection
2915 localchanges = dsmodified | dsadded
2909 localchanges = dsmodified | dsadded
2916
2910
2917 # only take into account for removes between wc and target
2911 # only take into account for removes between wc and target
2918 clean |= dsremoved - removed
2912 clean |= dsremoved - removed
2919 dsremoved &= removed
2913 dsremoved &= removed
2920 # distinct between dirstate remove and other
2914 # distinct between dirstate remove and other
2921 removed -= dsremoved
2915 removed -= dsremoved
2922
2916
2923 modadded = added & dsmodified
2917 modadded = added & dsmodified
2924 added -= modadded
2918 added -= modadded
2925
2919
2926 # tell newly modified apart.
2920 # tell newly modified apart.
2927 dsmodified &= modified
2921 dsmodified &= modified
2928 dsmodified |= modified & dsadded # dirstate added may needs backup
2922 dsmodified |= modified & dsadded # dirstate added may needs backup
2929 modified -= dsmodified
2923 modified -= dsmodified
2930
2924
2931 # We need to wait for some post-processing to update this set
2925 # We need to wait for some post-processing to update this set
2932 # before making the distinction. The dirstate will be used for
2926 # before making the distinction. The dirstate will be used for
2933 # that purpose.
2927 # that purpose.
2934 dsadded = added
2928 dsadded = added
2935
2929
2936 # in case of merge, files that are actually added can be reported as
2930 # in case of merge, files that are actually added can be reported as
2937 # modified, we need to post process the result
2931 # modified, we need to post process the result
2938 if p2 != nullid:
2932 if p2 != nullid:
2939 if pmf is None:
2933 if pmf is None:
2940 # only need parent manifest in the merge case,
2934 # only need parent manifest in the merge case,
2941 # so do not read by default
2935 # so do not read by default
2942 pmf = repo[parent].manifest()
2936 pmf = repo[parent].manifest()
2943 mergeadd = dsmodified - set(pmf)
2937 mergeadd = dsmodified - set(pmf)
2944 dsadded |= mergeadd
2938 dsadded |= mergeadd
2945 dsmodified -= mergeadd
2939 dsmodified -= mergeadd
2946
2940
2947 # if f is a rename, update `names` to also revert the source
2941 # if f is a rename, update `names` to also revert the source
2948 cwd = repo.getcwd()
2942 cwd = repo.getcwd()
2949 for f in localchanges:
2943 for f in localchanges:
2950 src = repo.dirstate.copied(f)
2944 src = repo.dirstate.copied(f)
2951 # XXX should we check for rename down to target node?
2945 # XXX should we check for rename down to target node?
2952 if src and src not in names and repo.dirstate[src] == 'r':
2946 if src and src not in names and repo.dirstate[src] == 'r':
2953 dsremoved.add(src)
2947 dsremoved.add(src)
2954 names[src] = (repo.pathto(src, cwd), True)
2948 names[src] = (repo.pathto(src, cwd), True)
2955
2949
2956 # distinguish between file to forget and the other
2950 # distinguish between file to forget and the other
2957 added = set()
2951 added = set()
2958 for abs in dsadded:
2952 for abs in dsadded:
2959 if repo.dirstate[abs] != 'a':
2953 if repo.dirstate[abs] != 'a':
2960 added.add(abs)
2954 added.add(abs)
2961 dsadded -= added
2955 dsadded -= added
2962
2956
2963 for abs in deladded:
2957 for abs in deladded:
2964 if repo.dirstate[abs] == 'a':
2958 if repo.dirstate[abs] == 'a':
2965 dsadded.add(abs)
2959 dsadded.add(abs)
2966 deladded -= dsadded
2960 deladded -= dsadded
2967
2961
2968 # For files marked as removed, we check if an unknown file is present at
2962 # For files marked as removed, we check if an unknown file is present at
2969 # the same path. If a such file exists it may need to be backed up.
2963 # the same path. If a such file exists it may need to be backed up.
2970 # Making the distinction at this stage helps have simpler backup
2964 # Making the distinction at this stage helps have simpler backup
2971 # logic.
2965 # logic.
2972 removunk = set()
2966 removunk = set()
2973 for abs in removed:
2967 for abs in removed:
2974 target = repo.wjoin(abs)
2968 target = repo.wjoin(abs)
2975 if os.path.lexists(target):
2969 if os.path.lexists(target):
2976 removunk.add(abs)
2970 removunk.add(abs)
2977 removed -= removunk
2971 removed -= removunk
2978
2972
2979 dsremovunk = set()
2973 dsremovunk = set()
2980 for abs in dsremoved:
2974 for abs in dsremoved:
2981 target = repo.wjoin(abs)
2975 target = repo.wjoin(abs)
2982 if os.path.lexists(target):
2976 if os.path.lexists(target):
2983 dsremovunk.add(abs)
2977 dsremovunk.add(abs)
2984 dsremoved -= dsremovunk
2978 dsremoved -= dsremovunk
2985
2979
2986 # action to be actually performed by revert
2980 # action to be actually performed by revert
2987 # (<list of file>, message>) tuple
2981 # (<list of file>, message>) tuple
2988 actions = {'revert': ([], _('reverting %s\n')),
2982 actions = {'revert': ([], _('reverting %s\n')),
2989 'add': ([], _('adding %s\n')),
2983 'add': ([], _('adding %s\n')),
2990 'remove': ([], _('removing %s\n')),
2984 'remove': ([], _('removing %s\n')),
2991 'drop': ([], _('removing %s\n')),
2985 'drop': ([], _('removing %s\n')),
2992 'forget': ([], _('forgetting %s\n')),
2986 'forget': ([], _('forgetting %s\n')),
2993 'undelete': ([], _('undeleting %s\n')),
2987 'undelete': ([], _('undeleting %s\n')),
2994 'noop': (None, _('no changes needed to %s\n')),
2988 'noop': (None, _('no changes needed to %s\n')),
2995 'unknown': (None, _('file not managed: %s\n')),
2989 'unknown': (None, _('file not managed: %s\n')),
2996 }
2990 }
2997
2991
2998 # "constant" that convey the backup strategy.
2992 # "constant" that convey the backup strategy.
2999 # All set to `discard` if `no-backup` is set do avoid checking
2993 # All set to `discard` if `no-backup` is set do avoid checking
3000 # no_backup lower in the code.
2994 # no_backup lower in the code.
3001 # These values are ordered for comparison purposes
2995 # These values are ordered for comparison purposes
3002 backup = 2 # unconditionally do backup
2996 backup = 2 # unconditionally do backup
3003 check = 1 # check if the existing file differs from target
2997 check = 1 # check if the existing file differs from target
3004 discard = 0 # never do backup
2998 discard = 0 # never do backup
3005 if opts.get('no_backup'):
2999 if opts.get('no_backup'):
3006 backup = check = discard
3000 backup = check = discard
3007
3001
3008 backupanddel = actions['remove']
3002 backupanddel = actions['remove']
3009 if not opts.get('no_backup'):
3003 if not opts.get('no_backup'):
3010 backupanddel = actions['drop']
3004 backupanddel = actions['drop']
3011
3005
3012 disptable = (
3006 disptable = (
3013 # dispatch table:
3007 # dispatch table:
3014 # file state
3008 # file state
3015 # action
3009 # action
3016 # make backup
3010 # make backup
3017
3011
3018 ## Sets that results that will change file on disk
3012 ## Sets that results that will change file on disk
3019 # Modified compared to target, no local change
3013 # Modified compared to target, no local change
3020 (modified, actions['revert'], discard),
3014 (modified, actions['revert'], discard),
3021 # Modified compared to target, but local file is deleted
3015 # Modified compared to target, but local file is deleted
3022 (deleted, actions['revert'], discard),
3016 (deleted, actions['revert'], discard),
3023 # Modified compared to target, local change
3017 # Modified compared to target, local change
3024 (dsmodified, actions['revert'], backup),
3018 (dsmodified, actions['revert'], backup),
3025 # Added since target
3019 # Added since target
3026 (added, actions['remove'], discard),
3020 (added, actions['remove'], discard),
3027 # Added in working directory
3021 # Added in working directory
3028 (dsadded, actions['forget'], discard),
3022 (dsadded, actions['forget'], discard),
3029 # Added since target, have local modification
3023 # Added since target, have local modification
3030 (modadded, backupanddel, backup),
3024 (modadded, backupanddel, backup),
3031 # Added since target but file is missing in working directory
3025 # Added since target but file is missing in working directory
3032 (deladded, actions['drop'], discard),
3026 (deladded, actions['drop'], discard),
3033 # Removed since target, before working copy parent
3027 # Removed since target, before working copy parent
3034 (removed, actions['add'], discard),
3028 (removed, actions['add'], discard),
3035 # Same as `removed` but an unknown file exists at the same path
3029 # Same as `removed` but an unknown file exists at the same path
3036 (removunk, actions['add'], check),
3030 (removunk, actions['add'], check),
3037 # Removed since targe, marked as such in working copy parent
3031 # Removed since targe, marked as such in working copy parent
3038 (dsremoved, actions['undelete'], discard),
3032 (dsremoved, actions['undelete'], discard),
3039 # Same as `dsremoved` but an unknown file exists at the same path
3033 # Same as `dsremoved` but an unknown file exists at the same path
3040 (dsremovunk, actions['undelete'], check),
3034 (dsremovunk, actions['undelete'], check),
3041 ## the following sets does not result in any file changes
3035 ## the following sets does not result in any file changes
3042 # File with no modification
3036 # File with no modification
3043 (clean, actions['noop'], discard),
3037 (clean, actions['noop'], discard),
3044 # Existing file, not tracked anywhere
3038 # Existing file, not tracked anywhere
3045 (unknown, actions['unknown'], discard),
3039 (unknown, actions['unknown'], discard),
3046 )
3040 )
3047
3041
3048 for abs, (rel, exact) in sorted(names.items()):
3042 for abs, (rel, exact) in sorted(names.items()):
3049 # target file to be touch on disk (relative to cwd)
3043 # target file to be touch on disk (relative to cwd)
3050 target = repo.wjoin(abs)
3044 target = repo.wjoin(abs)
3051 # search the entry in the dispatch table.
3045 # search the entry in the dispatch table.
3052 # if the file is in any of these sets, it was touched in the working
3046 # if the file is in any of these sets, it was touched in the working
3053 # directory parent and we are sure it needs to be reverted.
3047 # directory parent and we are sure it needs to be reverted.
3054 for table, (xlist, msg), dobackup in disptable:
3048 for table, (xlist, msg), dobackup in disptable:
3055 if abs not in table:
3049 if abs not in table:
3056 continue
3050 continue
3057 if xlist is not None:
3051 if xlist is not None:
3058 xlist.append(abs)
3052 xlist.append(abs)
3059 if dobackup and (backup <= dobackup
3053 if dobackup and (backup <= dobackup
3060 or wctx[abs].cmp(ctx[abs])):
3054 or wctx[abs].cmp(ctx[abs])):
3061 bakname = "%s.orig" % rel
3055 bakname = "%s.orig" % rel
3062 ui.note(_('saving current version of %s as %s\n') %
3056 ui.note(_('saving current version of %s as %s\n') %
3063 (rel, bakname))
3057 (rel, bakname))
3064 if not opts.get('dry_run'):
3058 if not opts.get('dry_run'):
3065 if interactive:
3059 if interactive:
3066 util.copyfile(target, bakname)
3060 util.copyfile(target, bakname)
3067 else:
3061 else:
3068 util.rename(target, bakname)
3062 util.rename(target, bakname)
3069 if ui.verbose or not exact:
3063 if ui.verbose or not exact:
3070 if not isinstance(msg, basestring):
3064 if not isinstance(msg, basestring):
3071 msg = msg(abs)
3065 msg = msg(abs)
3072 ui.status(msg % rel)
3066 ui.status(msg % rel)
3073 elif exact:
3067 elif exact:
3074 ui.warn(msg % rel)
3068 ui.warn(msg % rel)
3075 break
3069 break
3076
3070
3077 if not opts.get('dry_run'):
3071 if not opts.get('dry_run'):
3078 needdata = ('revert', 'add', 'undelete')
3072 needdata = ('revert', 'add', 'undelete')
3079 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3073 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3080 _performrevert(repo, parents, ctx, actions, interactive)
3074 _performrevert(repo, parents, ctx, actions, interactive)
3081
3075
3082 if targetsubs:
3076 if targetsubs:
3083 # Revert the subrepos on the revert list
3077 # Revert the subrepos on the revert list
3084 for sub in targetsubs:
3078 for sub in targetsubs:
3085 try:
3079 try:
3086 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3080 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3087 except KeyError:
3081 except KeyError:
3088 raise util.Abort("subrepository '%s' does not exist in %s!"
3082 raise util.Abort("subrepository '%s' does not exist in %s!"
3089 % (sub, short(ctx.node())))
3083 % (sub, short(ctx.node())))
3090 finally:
3084 finally:
3091 wlock.release()
3085 wlock.release()
3092
3086
3093 def _revertprefetch(repo, ctx, *files):
3087 def _revertprefetch(repo, ctx, *files):
3094 """Let extension changing the storage layer prefetch content"""
3088 """Let extension changing the storage layer prefetch content"""
3095 pass
3089 pass
3096
3090
3097 def _performrevert(repo, parents, ctx, actions, interactive=False):
3091 def _performrevert(repo, parents, ctx, actions, interactive=False):
3098 """function that actually perform all the actions computed for revert
3092 """function that actually perform all the actions computed for revert
3099
3093
3100 This is an independent function to let extension to plug in and react to
3094 This is an independent function to let extension to plug in and react to
3101 the imminent revert.
3095 the imminent revert.
3102
3096
3103 Make sure you have the working directory locked when calling this function.
3097 Make sure you have the working directory locked when calling this function.
3104 """
3098 """
3105 parent, p2 = parents
3099 parent, p2 = parents
3106 node = ctx.node()
3100 node = ctx.node()
3107 def checkout(f):
3101 def checkout(f):
3108 fc = ctx[f]
3102 fc = ctx[f]
3109 return repo.wwrite(f, fc.data(), fc.flags())
3103 return repo.wwrite(f, fc.data(), fc.flags())
3110
3104
3111 audit_path = pathutil.pathauditor(repo.root)
3105 audit_path = pathutil.pathauditor(repo.root)
3112 for f in actions['forget'][0]:
3106 for f in actions['forget'][0]:
3113 repo.dirstate.drop(f)
3107 repo.dirstate.drop(f)
3114 for f in actions['remove'][0]:
3108 for f in actions['remove'][0]:
3115 audit_path(f)
3109 audit_path(f)
3116 try:
3110 try:
3117 util.unlinkpath(repo.wjoin(f))
3111 util.unlinkpath(repo.wjoin(f))
3118 except OSError:
3112 except OSError:
3119 pass
3113 pass
3120 repo.dirstate.remove(f)
3114 repo.dirstate.remove(f)
3121 for f in actions['drop'][0]:
3115 for f in actions['drop'][0]:
3122 audit_path(f)
3116 audit_path(f)
3123 repo.dirstate.remove(f)
3117 repo.dirstate.remove(f)
3124
3118
3125 normal = None
3119 normal = None
3126 if node == parent:
3120 if node == parent:
3127 # We're reverting to our parent. If possible, we'd like status
3121 # We're reverting to our parent. If possible, we'd like status
3128 # to report the file as clean. We have to use normallookup for
3122 # to report the file as clean. We have to use normallookup for
3129 # merges to avoid losing information about merged/dirty files.
3123 # merges to avoid losing information about merged/dirty files.
3130 if p2 != nullid:
3124 if p2 != nullid:
3131 normal = repo.dirstate.normallookup
3125 normal = repo.dirstate.normallookup
3132 else:
3126 else:
3133 normal = repo.dirstate.normal
3127 normal = repo.dirstate.normal
3134
3128
3135 newlyaddedandmodifiedfiles = set()
3129 newlyaddedandmodifiedfiles = set()
3136 if interactive:
3130 if interactive:
3137 # Prompt the user for changes to revert
3131 # Prompt the user for changes to revert
3138 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3132 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3139 m = scmutil.match(ctx, torevert, {})
3133 m = scmutil.match(ctx, torevert, {})
3140 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3134 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3141 diffopts.nodates = True
3135 diffopts.nodates = True
3142 diffopts.git = True
3136 diffopts.git = True
3143 reversehunks = repo.ui.configbool('experimental',
3137 reversehunks = repo.ui.configbool('experimental',
3144 'revertalternateinteractivemode',
3138 'revertalternateinteractivemode',
3145 False)
3139 False)
3146 if reversehunks:
3140 if reversehunks:
3147 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3141 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3148 else:
3142 else:
3149 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3143 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3150 originalchunks = patch.parsepatch(diff)
3144 originalchunks = patch.parsepatch(diff)
3151
3145
3152 try:
3146 try:
3153
3147
3154 chunks = recordfilter(repo.ui, originalchunks)
3148 chunks = recordfilter(repo.ui, originalchunks)
3155 if reversehunks:
3149 if reversehunks:
3156 chunks = patch.reversehunks(chunks)
3150 chunks = patch.reversehunks(chunks)
3157
3151
3158 except patch.PatchError, err:
3152 except patch.PatchError, err:
3159 raise util.Abort(_('error parsing patch: %s') % err)
3153 raise util.Abort(_('error parsing patch: %s') % err)
3160
3154
3161 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3155 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3162 # Apply changes
3156 # Apply changes
3163 fp = cStringIO.StringIO()
3157 fp = cStringIO.StringIO()
3164 for c in chunks:
3158 for c in chunks:
3165 c.write(fp)
3159 c.write(fp)
3166 dopatch = fp.tell()
3160 dopatch = fp.tell()
3167 fp.seek(0)
3161 fp.seek(0)
3168 if dopatch:
3162 if dopatch:
3169 try:
3163 try:
3170 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3164 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3171 except patch.PatchError, err:
3165 except patch.PatchError, err:
3172 raise util.Abort(str(err))
3166 raise util.Abort(str(err))
3173 del fp
3167 del fp
3174 else:
3168 else:
3175 for f in actions['revert'][0]:
3169 for f in actions['revert'][0]:
3176 wsize = checkout(f)
3170 wsize = checkout(f)
3177 if normal:
3171 if normal:
3178 normal(f)
3172 normal(f)
3179 elif wsize == repo.dirstate._map[f][2]:
3173 elif wsize == repo.dirstate._map[f][2]:
3180 # changes may be overlooked without normallookup,
3174 # changes may be overlooked without normallookup,
3181 # if size isn't changed at reverting
3175 # if size isn't changed at reverting
3182 repo.dirstate.normallookup(f)
3176 repo.dirstate.normallookup(f)
3183
3177
3184 for f in actions['add'][0]:
3178 for f in actions['add'][0]:
3185 # Don't checkout modified files, they are already created by the diff
3179 # Don't checkout modified files, they are already created by the diff
3186 if f not in newlyaddedandmodifiedfiles:
3180 if f not in newlyaddedandmodifiedfiles:
3187 checkout(f)
3181 checkout(f)
3188 repo.dirstate.add(f)
3182 repo.dirstate.add(f)
3189
3183
3190 normal = repo.dirstate.normallookup
3184 normal = repo.dirstate.normallookup
3191 if node == parent and p2 == nullid:
3185 if node == parent and p2 == nullid:
3192 normal = repo.dirstate.normal
3186 normal = repo.dirstate.normal
3193 for f in actions['undelete'][0]:
3187 for f in actions['undelete'][0]:
3194 checkout(f)
3188 checkout(f)
3195 normal(f)
3189 normal(f)
3196
3190
3197 copied = copies.pathcopies(repo[parent], ctx)
3191 copied = copies.pathcopies(repo[parent], ctx)
3198
3192
3199 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3193 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3200 if f in copied:
3194 if f in copied:
3201 repo.dirstate.copy(copied[f], f)
3195 repo.dirstate.copy(copied[f], f)
3202
3196
3203 def command(table):
3197 def command(table):
3204 """Returns a function object to be used as a decorator for making commands.
3198 """Returns a function object to be used as a decorator for making commands.
3205
3199
3206 This function receives a command table as its argument. The table should
3200 This function receives a command table as its argument. The table should
3207 be a dict.
3201 be a dict.
3208
3202
3209 The returned function can be used as a decorator for adding commands
3203 The returned function can be used as a decorator for adding commands
3210 to that command table. This function accepts multiple arguments to define
3204 to that command table. This function accepts multiple arguments to define
3211 a command.
3205 a command.
3212
3206
3213 The first argument is the command name.
3207 The first argument is the command name.
3214
3208
3215 The options argument is an iterable of tuples defining command arguments.
3209 The options argument is an iterable of tuples defining command arguments.
3216 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3210 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3217
3211
3218 The synopsis argument defines a short, one line summary of how to use the
3212 The synopsis argument defines a short, one line summary of how to use the
3219 command. This shows up in the help output.
3213 command. This shows up in the help output.
3220
3214
3221 The norepo argument defines whether the command does not require a
3215 The norepo argument defines whether the command does not require a
3222 local repository. Most commands operate against a repository, thus the
3216 local repository. Most commands operate against a repository, thus the
3223 default is False.
3217 default is False.
3224
3218
3225 The optionalrepo argument defines whether the command optionally requires
3219 The optionalrepo argument defines whether the command optionally requires
3226 a local repository.
3220 a local repository.
3227
3221
3228 The inferrepo argument defines whether to try to find a repository from the
3222 The inferrepo argument defines whether to try to find a repository from the
3229 command line arguments. If True, arguments will be examined for potential
3223 command line arguments. If True, arguments will be examined for potential
3230 repository locations. See ``findrepo()``. If a repository is found, it
3224 repository locations. See ``findrepo()``. If a repository is found, it
3231 will be used.
3225 will be used.
3232 """
3226 """
3233 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3227 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3234 inferrepo=False):
3228 inferrepo=False):
3235 def decorator(func):
3229 def decorator(func):
3236 if synopsis:
3230 if synopsis:
3237 table[name] = func, list(options), synopsis
3231 table[name] = func, list(options), synopsis
3238 else:
3232 else:
3239 table[name] = func, list(options)
3233 table[name] = func, list(options)
3240
3234
3241 if norepo:
3235 if norepo:
3242 # Avoid import cycle.
3236 # Avoid import cycle.
3243 import commands
3237 import commands
3244 commands.norepo += ' %s' % ' '.join(parsealiases(name))
3238 commands.norepo += ' %s' % ' '.join(parsealiases(name))
3245
3239
3246 if optionalrepo:
3240 if optionalrepo:
3247 import commands
3241 import commands
3248 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
3242 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
3249
3243
3250 if inferrepo:
3244 if inferrepo:
3251 import commands
3245 import commands
3252 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
3246 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
3253
3247
3254 return func
3248 return func
3255 return decorator
3249 return decorator
3256
3250
3257 return cmd
3251 return cmd
3258
3252
3259 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3253 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3260 # commands.outgoing. "missing" is "missing" of the result of
3254 # commands.outgoing. "missing" is "missing" of the result of
3261 # "findcommonoutgoing()"
3255 # "findcommonoutgoing()"
3262 outgoinghooks = util.hooks()
3256 outgoinghooks = util.hooks()
3263
3257
3264 # a list of (ui, repo) functions called by commands.summary
3258 # a list of (ui, repo) functions called by commands.summary
3265 summaryhooks = util.hooks()
3259 summaryhooks = util.hooks()
3266
3260
3267 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3261 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3268 #
3262 #
3269 # functions should return tuple of booleans below, if 'changes' is None:
3263 # functions should return tuple of booleans below, if 'changes' is None:
3270 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3264 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3271 #
3265 #
3272 # otherwise, 'changes' is a tuple of tuples below:
3266 # otherwise, 'changes' is a tuple of tuples below:
3273 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3267 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3274 # - (desturl, destbranch, destpeer, outgoing)
3268 # - (desturl, destbranch, destpeer, outgoing)
3275 summaryremotehooks = util.hooks()
3269 summaryremotehooks = util.hooks()
3276
3270
3277 # A list of state files kept by multistep operations like graft.
3271 # A list of state files kept by multistep operations like graft.
3278 # Since graft cannot be aborted, it is considered 'clearable' by update.
3272 # Since graft cannot be aborted, it is considered 'clearable' by update.
3279 # note: bisect is intentionally excluded
3273 # note: bisect is intentionally excluded
3280 # (state file, clearable, allowcommit, error, hint)
3274 # (state file, clearable, allowcommit, error, hint)
3281 unfinishedstates = [
3275 unfinishedstates = [
3282 ('graftstate', True, False, _('graft in progress'),
3276 ('graftstate', True, False, _('graft in progress'),
3283 _("use 'hg graft --continue' or 'hg update' to abort")),
3277 _("use 'hg graft --continue' or 'hg update' to abort")),
3284 ('updatestate', True, False, _('last update was interrupted'),
3278 ('updatestate', True, False, _('last update was interrupted'),
3285 _("use 'hg update' to get a consistent checkout"))
3279 _("use 'hg update' to get a consistent checkout"))
3286 ]
3280 ]
3287
3281
3288 def checkunfinished(repo, commit=False):
3282 def checkunfinished(repo, commit=False):
3289 '''Look for an unfinished multistep operation, like graft, and abort
3283 '''Look for an unfinished multistep operation, like graft, and abort
3290 if found. It's probably good to check this right before
3284 if found. It's probably good to check this right before
3291 bailifchanged().
3285 bailifchanged().
3292 '''
3286 '''
3293 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3287 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3294 if commit and allowcommit:
3288 if commit and allowcommit:
3295 continue
3289 continue
3296 if repo.vfs.exists(f):
3290 if repo.vfs.exists(f):
3297 raise util.Abort(msg, hint=hint)
3291 raise util.Abort(msg, hint=hint)
3298
3292
3299 def clearunfinished(repo):
3293 def clearunfinished(repo):
3300 '''Check for unfinished operations (as above), and clear the ones
3294 '''Check for unfinished operations (as above), and clear the ones
3301 that are clearable.
3295 that are clearable.
3302 '''
3296 '''
3303 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3297 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3304 if not clearable and repo.vfs.exists(f):
3298 if not clearable and repo.vfs.exists(f):
3305 raise util.Abort(msg, hint=hint)
3299 raise util.Abort(msg, hint=hint)
3306 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3300 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3307 if clearable and repo.vfs.exists(f):
3301 if clearable and repo.vfs.exists(f):
3308 util.unlink(repo.join(f))
3302 util.unlink(repo.join(f))
3309
3303
3310 class dirstateguard(object):
3304 class dirstateguard(object):
3311 '''Restore dirstate at unexpected failure.
3305 '''Restore dirstate at unexpected failure.
3312
3306
3313 At the construction, this class does:
3307 At the construction, this class does:
3314
3308
3315 - write current ``repo.dirstate`` out, and
3309 - write current ``repo.dirstate`` out, and
3316 - save ``.hg/dirstate`` into the backup file
3310 - save ``.hg/dirstate`` into the backup file
3317
3311
3318 This restores ``.hg/dirstate`` from backup file, if ``release()``
3312 This restores ``.hg/dirstate`` from backup file, if ``release()``
3319 is invoked before ``close()``.
3313 is invoked before ``close()``.
3320
3314
3321 This just removes the backup file at ``close()`` before ``release()``.
3315 This just removes the backup file at ``close()`` before ``release()``.
3322 '''
3316 '''
3323
3317
3324 def __init__(self, repo, name):
3318 def __init__(self, repo, name):
3325 repo.dirstate.write()
3319 repo.dirstate.write()
3326 self._repo = repo
3320 self._repo = repo
3327 self._filename = 'dirstate.backup.%s.%d' % (name, id(self))
3321 self._filename = 'dirstate.backup.%s.%d' % (name, id(self))
3328 repo.vfs.write(self._filename, repo.vfs.tryread('dirstate'))
3322 repo.vfs.write(self._filename, repo.vfs.tryread('dirstate'))
3329 self._active = True
3323 self._active = True
3330 self._closed = False
3324 self._closed = False
3331
3325
3332 def __del__(self):
3326 def __del__(self):
3333 if self._active: # still active
3327 if self._active: # still active
3334 # this may occur, even if this class is used correctly:
3328 # this may occur, even if this class is used correctly:
3335 # for example, releasing other resources like transaction
3329 # for example, releasing other resources like transaction
3336 # may raise exception before ``dirstateguard.release`` in
3330 # may raise exception before ``dirstateguard.release`` in
3337 # ``release(tr, ....)``.
3331 # ``release(tr, ....)``.
3338 self._abort()
3332 self._abort()
3339
3333
3340 def close(self):
3334 def close(self):
3341 if not self._active: # already inactivated
3335 if not self._active: # already inactivated
3342 msg = (_("can't close already inactivated backup: %s")
3336 msg = (_("can't close already inactivated backup: %s")
3343 % self._filename)
3337 % self._filename)
3344 raise util.Abort(msg)
3338 raise util.Abort(msg)
3345
3339
3346 self._repo.vfs.unlink(self._filename)
3340 self._repo.vfs.unlink(self._filename)
3347 self._active = False
3341 self._active = False
3348 self._closed = True
3342 self._closed = True
3349
3343
3350 def _abort(self):
3344 def _abort(self):
3351 # this "invalidate()" prevents "wlock.release()" from writing
3345 # this "invalidate()" prevents "wlock.release()" from writing
3352 # changes of dirstate out after restoring to original status
3346 # changes of dirstate out after restoring to original status
3353 self._repo.dirstate.invalidate()
3347 self._repo.dirstate.invalidate()
3354
3348
3355 self._repo.vfs.rename(self._filename, 'dirstate')
3349 self._repo.vfs.rename(self._filename, 'dirstate')
3356 self._active = False
3350 self._active = False
3357
3351
3358 def release(self):
3352 def release(self):
3359 if not self._closed:
3353 if not self._closed:
3360 if not self._active: # already inactivated
3354 if not self._active: # already inactivated
3361 msg = (_("can't release already inactivated backup: %s")
3355 msg = (_("can't release already inactivated backup: %s")
3362 % self._filename)
3356 % self._filename)
3363 raise util.Abort(msg)
3357 raise util.Abort(msg)
3364 self._abort()
3358 self._abort()
General Comments 0
You need to be logged in to leave comments. Login now