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