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