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