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