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