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