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