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