##// END OF EJS Templates
color: enabled color support for export command (issue1507)...
Ankur Dahiya -
r17460:a306837f default
parent child Browse files
Show More
@@ -1,1931 +1,1938 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, tempfile
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 subrepo, context, repair, bookmarks, graphmod, revset
13 import subrepo, context, repair, bookmarks, graphmod, revset
14
14
15 def parsealiases(cmd):
15 def parsealiases(cmd):
16 return cmd.lstrip("^").split("|")
16 return cmd.lstrip("^").split("|")
17
17
18 def findpossible(cmd, table, strict=False):
18 def findpossible(cmd, table, strict=False):
19 """
19 """
20 Return cmd -> (aliases, command table entry)
20 Return cmd -> (aliases, command table entry)
21 for each matching command.
21 for each matching command.
22 Return debug commands (or their aliases) only if no normal command matches.
22 Return debug commands (or their aliases) only if no normal command matches.
23 """
23 """
24 choice = {}
24 choice = {}
25 debugchoice = {}
25 debugchoice = {}
26
26
27 if cmd in table:
27 if cmd in table:
28 # short-circuit exact matches, "log" alias beats "^log|history"
28 # short-circuit exact matches, "log" alias beats "^log|history"
29 keys = [cmd]
29 keys = [cmd]
30 else:
30 else:
31 keys = table.keys()
31 keys = table.keys()
32
32
33 for e in keys:
33 for e in keys:
34 aliases = parsealiases(e)
34 aliases = parsealiases(e)
35 found = None
35 found = None
36 if cmd in aliases:
36 if cmd in aliases:
37 found = cmd
37 found = cmd
38 elif not strict:
38 elif not strict:
39 for a in aliases:
39 for a in aliases:
40 if a.startswith(cmd):
40 if a.startswith(cmd):
41 found = a
41 found = a
42 break
42 break
43 if found is not None:
43 if found is not None:
44 if aliases[0].startswith("debug") or found.startswith("debug"):
44 if aliases[0].startswith("debug") or found.startswith("debug"):
45 debugchoice[found] = (aliases, table[e])
45 debugchoice[found] = (aliases, table[e])
46 else:
46 else:
47 choice[found] = (aliases, table[e])
47 choice[found] = (aliases, table[e])
48
48
49 if not choice and debugchoice:
49 if not choice and debugchoice:
50 choice = debugchoice
50 choice = debugchoice
51
51
52 return choice
52 return choice
53
53
54 def findcmd(cmd, table, strict=True):
54 def findcmd(cmd, table, strict=True):
55 """Return (aliases, command table entry) for command string."""
55 """Return (aliases, command table entry) for command string."""
56 choice = findpossible(cmd, table, strict)
56 choice = findpossible(cmd, table, strict)
57
57
58 if cmd in choice:
58 if cmd in choice:
59 return choice[cmd]
59 return choice[cmd]
60
60
61 if len(choice) > 1:
61 if len(choice) > 1:
62 clist = choice.keys()
62 clist = choice.keys()
63 clist.sort()
63 clist.sort()
64 raise error.AmbiguousCommand(cmd, clist)
64 raise error.AmbiguousCommand(cmd, clist)
65
65
66 if choice:
66 if choice:
67 return choice.values()[0]
67 return choice.values()[0]
68
68
69 raise error.UnknownCommand(cmd)
69 raise error.UnknownCommand(cmd)
70
70
71 def findrepo(p):
71 def findrepo(p):
72 while not os.path.isdir(os.path.join(p, ".hg")):
72 while not os.path.isdir(os.path.join(p, ".hg")):
73 oldp, p = p, os.path.dirname(p)
73 oldp, p = p, os.path.dirname(p)
74 if p == oldp:
74 if p == oldp:
75 return None
75 return None
76
76
77 return p
77 return p
78
78
79 def bailifchanged(repo):
79 def bailifchanged(repo):
80 if repo.dirstate.p2() != nullid:
80 if repo.dirstate.p2() != nullid:
81 raise util.Abort(_('outstanding uncommitted merge'))
81 raise util.Abort(_('outstanding uncommitted merge'))
82 modified, added, removed, deleted = repo.status()[:4]
82 modified, added, removed, deleted = repo.status()[:4]
83 if modified or added or removed or deleted:
83 if modified or added or removed or deleted:
84 raise util.Abort(_("outstanding uncommitted changes"))
84 raise util.Abort(_("outstanding uncommitted changes"))
85 ctx = repo[None]
85 ctx = repo[None]
86 for s in ctx.substate:
86 for s in ctx.substate:
87 if ctx.sub(s).dirty():
87 if ctx.sub(s).dirty():
88 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
88 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
89
89
90 def logmessage(ui, opts):
90 def logmessage(ui, opts):
91 """ get the log message according to -m and -l option """
91 """ get the log message according to -m and -l option """
92 message = opts.get('message')
92 message = opts.get('message')
93 logfile = opts.get('logfile')
93 logfile = opts.get('logfile')
94
94
95 if message and logfile:
95 if message and logfile:
96 raise util.Abort(_('options --message and --logfile are mutually '
96 raise util.Abort(_('options --message and --logfile are mutually '
97 'exclusive'))
97 'exclusive'))
98 if not message and logfile:
98 if not message and logfile:
99 try:
99 try:
100 if logfile == '-':
100 if logfile == '-':
101 message = ui.fin.read()
101 message = ui.fin.read()
102 else:
102 else:
103 message = '\n'.join(util.readfile(logfile).splitlines())
103 message = '\n'.join(util.readfile(logfile).splitlines())
104 except IOError, inst:
104 except IOError, inst:
105 raise util.Abort(_("can't read commit message '%s': %s") %
105 raise util.Abort(_("can't read commit message '%s': %s") %
106 (logfile, inst.strerror))
106 (logfile, inst.strerror))
107 return message
107 return message
108
108
109 def loglimit(opts):
109 def loglimit(opts):
110 """get the log limit according to option -l/--limit"""
110 """get the log limit according to option -l/--limit"""
111 limit = opts.get('limit')
111 limit = opts.get('limit')
112 if limit:
112 if limit:
113 try:
113 try:
114 limit = int(limit)
114 limit = int(limit)
115 except ValueError:
115 except ValueError:
116 raise util.Abort(_('limit must be a positive integer'))
116 raise util.Abort(_('limit must be a positive integer'))
117 if limit <= 0:
117 if limit <= 0:
118 raise util.Abort(_('limit must be positive'))
118 raise util.Abort(_('limit must be positive'))
119 else:
119 else:
120 limit = None
120 limit = None
121 return limit
121 return limit
122
122
123 def makefilename(repo, pat, node, desc=None,
123 def makefilename(repo, pat, node, desc=None,
124 total=None, seqno=None, revwidth=None, pathname=None):
124 total=None, seqno=None, revwidth=None, pathname=None):
125 node_expander = {
125 node_expander = {
126 'H': lambda: hex(node),
126 'H': lambda: hex(node),
127 'R': lambda: str(repo.changelog.rev(node)),
127 'R': lambda: str(repo.changelog.rev(node)),
128 'h': lambda: short(node),
128 'h': lambda: short(node),
129 'm': lambda: re.sub('[^\w]', '_', str(desc))
129 'm': lambda: re.sub('[^\w]', '_', str(desc))
130 }
130 }
131 expander = {
131 expander = {
132 '%': lambda: '%',
132 '%': lambda: '%',
133 'b': lambda: os.path.basename(repo.root),
133 'b': lambda: os.path.basename(repo.root),
134 }
134 }
135
135
136 try:
136 try:
137 if node:
137 if node:
138 expander.update(node_expander)
138 expander.update(node_expander)
139 if node:
139 if node:
140 expander['r'] = (lambda:
140 expander['r'] = (lambda:
141 str(repo.changelog.rev(node)).zfill(revwidth or 0))
141 str(repo.changelog.rev(node)).zfill(revwidth or 0))
142 if total is not None:
142 if total is not None:
143 expander['N'] = lambda: str(total)
143 expander['N'] = lambda: str(total)
144 if seqno is not None:
144 if seqno is not None:
145 expander['n'] = lambda: str(seqno)
145 expander['n'] = lambda: str(seqno)
146 if total is not None and seqno is not None:
146 if total is not None and seqno is not None:
147 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
147 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
148 if pathname is not None:
148 if pathname is not None:
149 expander['s'] = lambda: os.path.basename(pathname)
149 expander['s'] = lambda: os.path.basename(pathname)
150 expander['d'] = lambda: os.path.dirname(pathname) or '.'
150 expander['d'] = lambda: os.path.dirname(pathname) or '.'
151 expander['p'] = lambda: pathname
151 expander['p'] = lambda: pathname
152
152
153 newname = []
153 newname = []
154 patlen = len(pat)
154 patlen = len(pat)
155 i = 0
155 i = 0
156 while i < patlen:
156 while i < patlen:
157 c = pat[i]
157 c = pat[i]
158 if c == '%':
158 if c == '%':
159 i += 1
159 i += 1
160 c = pat[i]
160 c = pat[i]
161 c = expander[c]()
161 c = expander[c]()
162 newname.append(c)
162 newname.append(c)
163 i += 1
163 i += 1
164 return ''.join(newname)
164 return ''.join(newname)
165 except KeyError, inst:
165 except KeyError, inst:
166 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
166 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
167 inst.args[0])
167 inst.args[0])
168
168
169 def makefileobj(repo, pat, node=None, desc=None, total=None,
169 def makefileobj(repo, pat, node=None, desc=None, total=None,
170 seqno=None, revwidth=None, mode='wb', pathname=None):
170 seqno=None, revwidth=None, mode='wb', pathname=None):
171
171
172 writable = mode not in ('r', 'rb')
172 writable = mode not in ('r', 'rb')
173
173
174 if not pat or pat == '-':
174 if not pat or pat == '-':
175 fp = writable and repo.ui.fout or repo.ui.fin
175 fp = writable and repo.ui.fout or repo.ui.fin
176 if util.safehasattr(fp, 'fileno'):
176 if util.safehasattr(fp, 'fileno'):
177 return os.fdopen(os.dup(fp.fileno()), mode)
177 return os.fdopen(os.dup(fp.fileno()), mode)
178 else:
178 else:
179 # if this fp can't be duped properly, return
179 # if this fp can't be duped properly, return
180 # a dummy object that can be closed
180 # a dummy object that can be closed
181 class wrappedfileobj(object):
181 class wrappedfileobj(object):
182 noop = lambda x: None
182 noop = lambda x: None
183 def __init__(self, f):
183 def __init__(self, f):
184 self.f = f
184 self.f = f
185 def __getattr__(self, attr):
185 def __getattr__(self, attr):
186 if attr == 'close':
186 if attr == 'close':
187 return self.noop
187 return self.noop
188 else:
188 else:
189 return getattr(self.f, attr)
189 return getattr(self.f, attr)
190
190
191 return wrappedfileobj(fp)
191 return wrappedfileobj(fp)
192 if util.safehasattr(pat, 'write') and writable:
192 if util.safehasattr(pat, 'write') and writable:
193 return pat
193 return pat
194 if util.safehasattr(pat, 'read') and 'r' in mode:
194 if util.safehasattr(pat, 'read') and 'r' in mode:
195 return pat
195 return pat
196 return open(makefilename(repo, pat, node, desc, total, seqno, revwidth,
196 return open(makefilename(repo, pat, node, desc, total, seqno, revwidth,
197 pathname),
197 pathname),
198 mode)
198 mode)
199
199
200 def openrevlog(repo, cmd, file_, opts):
200 def openrevlog(repo, cmd, file_, opts):
201 """opens the changelog, manifest, a filelog or a given revlog"""
201 """opens the changelog, manifest, a filelog or a given revlog"""
202 cl = opts['changelog']
202 cl = opts['changelog']
203 mf = opts['manifest']
203 mf = opts['manifest']
204 msg = None
204 msg = None
205 if cl and mf:
205 if cl and mf:
206 msg = _('cannot specify --changelog and --manifest at the same time')
206 msg = _('cannot specify --changelog and --manifest at the same time')
207 elif cl or mf:
207 elif cl or mf:
208 if file_:
208 if file_:
209 msg = _('cannot specify filename with --changelog or --manifest')
209 msg = _('cannot specify filename with --changelog or --manifest')
210 elif not repo:
210 elif not repo:
211 msg = _('cannot specify --changelog or --manifest '
211 msg = _('cannot specify --changelog or --manifest '
212 'without a repository')
212 'without a repository')
213 if msg:
213 if msg:
214 raise util.Abort(msg)
214 raise util.Abort(msg)
215
215
216 r = None
216 r = None
217 if repo:
217 if repo:
218 if cl:
218 if cl:
219 r = repo.changelog
219 r = repo.changelog
220 elif mf:
220 elif mf:
221 r = repo.manifest
221 r = repo.manifest
222 elif file_:
222 elif file_:
223 filelog = repo.file(file_)
223 filelog = repo.file(file_)
224 if len(filelog):
224 if len(filelog):
225 r = filelog
225 r = filelog
226 if not r:
226 if not r:
227 if not file_:
227 if not file_:
228 raise error.CommandError(cmd, _('invalid arguments'))
228 raise error.CommandError(cmd, _('invalid arguments'))
229 if not os.path.isfile(file_):
229 if not os.path.isfile(file_):
230 raise util.Abort(_("revlog '%s' not found") % file_)
230 raise util.Abort(_("revlog '%s' not found") % file_)
231 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
231 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
232 file_[:-2] + ".i")
232 file_[:-2] + ".i")
233 return r
233 return r
234
234
235 def copy(ui, repo, pats, opts, rename=False):
235 def copy(ui, repo, pats, opts, rename=False):
236 # called with the repo lock held
236 # called with the repo lock held
237 #
237 #
238 # hgsep => pathname that uses "/" to separate directories
238 # hgsep => pathname that uses "/" to separate directories
239 # ossep => pathname that uses os.sep to separate directories
239 # ossep => pathname that uses os.sep to separate directories
240 cwd = repo.getcwd()
240 cwd = repo.getcwd()
241 targets = {}
241 targets = {}
242 after = opts.get("after")
242 after = opts.get("after")
243 dryrun = opts.get("dry_run")
243 dryrun = opts.get("dry_run")
244 wctx = repo[None]
244 wctx = repo[None]
245
245
246 def walkpat(pat):
246 def walkpat(pat):
247 srcs = []
247 srcs = []
248 badstates = after and '?' or '?r'
248 badstates = after and '?' or '?r'
249 m = scmutil.match(repo[None], [pat], opts, globbed=True)
249 m = scmutil.match(repo[None], [pat], opts, globbed=True)
250 for abs in repo.walk(m):
250 for abs in repo.walk(m):
251 state = repo.dirstate[abs]
251 state = repo.dirstate[abs]
252 rel = m.rel(abs)
252 rel = m.rel(abs)
253 exact = m.exact(abs)
253 exact = m.exact(abs)
254 if state in badstates:
254 if state in badstates:
255 if exact and state == '?':
255 if exact and state == '?':
256 ui.warn(_('%s: not copying - file is not managed\n') % rel)
256 ui.warn(_('%s: not copying - file is not managed\n') % rel)
257 if exact and state == 'r':
257 if exact and state == 'r':
258 ui.warn(_('%s: not copying - file has been marked for'
258 ui.warn(_('%s: not copying - file has been marked for'
259 ' remove\n') % rel)
259 ' remove\n') % rel)
260 continue
260 continue
261 # abs: hgsep
261 # abs: hgsep
262 # rel: ossep
262 # rel: ossep
263 srcs.append((abs, rel, exact))
263 srcs.append((abs, rel, exact))
264 return srcs
264 return srcs
265
265
266 # abssrc: hgsep
266 # abssrc: hgsep
267 # relsrc: ossep
267 # relsrc: ossep
268 # otarget: ossep
268 # otarget: ossep
269 def copyfile(abssrc, relsrc, otarget, exact):
269 def copyfile(abssrc, relsrc, otarget, exact):
270 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
270 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
271 if '/' in abstarget:
271 if '/' in abstarget:
272 # We cannot normalize abstarget itself, this would prevent
272 # We cannot normalize abstarget itself, this would prevent
273 # case only renames, like a => A.
273 # case only renames, like a => A.
274 abspath, absname = abstarget.rsplit('/', 1)
274 abspath, absname = abstarget.rsplit('/', 1)
275 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
275 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
276 reltarget = repo.pathto(abstarget, cwd)
276 reltarget = repo.pathto(abstarget, cwd)
277 target = repo.wjoin(abstarget)
277 target = repo.wjoin(abstarget)
278 src = repo.wjoin(abssrc)
278 src = repo.wjoin(abssrc)
279 state = repo.dirstate[abstarget]
279 state = repo.dirstate[abstarget]
280
280
281 scmutil.checkportable(ui, abstarget)
281 scmutil.checkportable(ui, abstarget)
282
282
283 # check for collisions
283 # check for collisions
284 prevsrc = targets.get(abstarget)
284 prevsrc = targets.get(abstarget)
285 if prevsrc is not None:
285 if prevsrc is not None:
286 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
286 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
287 (reltarget, repo.pathto(abssrc, cwd),
287 (reltarget, repo.pathto(abssrc, cwd),
288 repo.pathto(prevsrc, cwd)))
288 repo.pathto(prevsrc, cwd)))
289 return
289 return
290
290
291 # check for overwrites
291 # check for overwrites
292 exists = os.path.lexists(target)
292 exists = os.path.lexists(target)
293 samefile = False
293 samefile = False
294 if exists and abssrc != abstarget:
294 if exists and abssrc != abstarget:
295 if (repo.dirstate.normalize(abssrc) ==
295 if (repo.dirstate.normalize(abssrc) ==
296 repo.dirstate.normalize(abstarget)):
296 repo.dirstate.normalize(abstarget)):
297 if not rename:
297 if not rename:
298 ui.warn(_("%s: can't copy - same file\n") % reltarget)
298 ui.warn(_("%s: can't copy - same file\n") % reltarget)
299 return
299 return
300 exists = False
300 exists = False
301 samefile = True
301 samefile = True
302
302
303 if not after and exists or after and state in 'mn':
303 if not after and exists or after and state in 'mn':
304 if not opts['force']:
304 if not opts['force']:
305 ui.warn(_('%s: not overwriting - file exists\n') %
305 ui.warn(_('%s: not overwriting - file exists\n') %
306 reltarget)
306 reltarget)
307 return
307 return
308
308
309 if after:
309 if after:
310 if not exists:
310 if not exists:
311 if rename:
311 if rename:
312 ui.warn(_('%s: not recording move - %s does not exist\n') %
312 ui.warn(_('%s: not recording move - %s does not exist\n') %
313 (relsrc, reltarget))
313 (relsrc, reltarget))
314 else:
314 else:
315 ui.warn(_('%s: not recording copy - %s does not exist\n') %
315 ui.warn(_('%s: not recording copy - %s does not exist\n') %
316 (relsrc, reltarget))
316 (relsrc, reltarget))
317 return
317 return
318 elif not dryrun:
318 elif not dryrun:
319 try:
319 try:
320 if exists:
320 if exists:
321 os.unlink(target)
321 os.unlink(target)
322 targetdir = os.path.dirname(target) or '.'
322 targetdir = os.path.dirname(target) or '.'
323 if not os.path.isdir(targetdir):
323 if not os.path.isdir(targetdir):
324 os.makedirs(targetdir)
324 os.makedirs(targetdir)
325 if samefile:
325 if samefile:
326 tmp = target + "~hgrename"
326 tmp = target + "~hgrename"
327 os.rename(src, tmp)
327 os.rename(src, tmp)
328 os.rename(tmp, target)
328 os.rename(tmp, target)
329 else:
329 else:
330 util.copyfile(src, target)
330 util.copyfile(src, target)
331 srcexists = True
331 srcexists = True
332 except IOError, inst:
332 except IOError, inst:
333 if inst.errno == errno.ENOENT:
333 if inst.errno == errno.ENOENT:
334 ui.warn(_('%s: deleted in working copy\n') % relsrc)
334 ui.warn(_('%s: deleted in working copy\n') % relsrc)
335 srcexists = False
335 srcexists = False
336 else:
336 else:
337 ui.warn(_('%s: cannot copy - %s\n') %
337 ui.warn(_('%s: cannot copy - %s\n') %
338 (relsrc, inst.strerror))
338 (relsrc, inst.strerror))
339 return True # report a failure
339 return True # report a failure
340
340
341 if ui.verbose or not exact:
341 if ui.verbose or not exact:
342 if rename:
342 if rename:
343 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
343 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
344 else:
344 else:
345 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
345 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
346
346
347 targets[abstarget] = abssrc
347 targets[abstarget] = abssrc
348
348
349 # fix up dirstate
349 # fix up dirstate
350 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
350 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
351 dryrun=dryrun, cwd=cwd)
351 dryrun=dryrun, cwd=cwd)
352 if rename and not dryrun:
352 if rename and not dryrun:
353 if not after and srcexists and not samefile:
353 if not after and srcexists and not samefile:
354 util.unlinkpath(repo.wjoin(abssrc))
354 util.unlinkpath(repo.wjoin(abssrc))
355 wctx.forget([abssrc])
355 wctx.forget([abssrc])
356
356
357 # pat: ossep
357 # pat: ossep
358 # dest ossep
358 # dest ossep
359 # srcs: list of (hgsep, hgsep, ossep, bool)
359 # srcs: list of (hgsep, hgsep, ossep, bool)
360 # return: function that takes hgsep and returns ossep
360 # return: function that takes hgsep and returns ossep
361 def targetpathfn(pat, dest, srcs):
361 def targetpathfn(pat, dest, srcs):
362 if os.path.isdir(pat):
362 if os.path.isdir(pat):
363 abspfx = scmutil.canonpath(repo.root, cwd, pat)
363 abspfx = scmutil.canonpath(repo.root, cwd, pat)
364 abspfx = util.localpath(abspfx)
364 abspfx = util.localpath(abspfx)
365 if destdirexists:
365 if destdirexists:
366 striplen = len(os.path.split(abspfx)[0])
366 striplen = len(os.path.split(abspfx)[0])
367 else:
367 else:
368 striplen = len(abspfx)
368 striplen = len(abspfx)
369 if striplen:
369 if striplen:
370 striplen += len(os.sep)
370 striplen += len(os.sep)
371 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
371 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
372 elif destdirexists:
372 elif destdirexists:
373 res = lambda p: os.path.join(dest,
373 res = lambda p: os.path.join(dest,
374 os.path.basename(util.localpath(p)))
374 os.path.basename(util.localpath(p)))
375 else:
375 else:
376 res = lambda p: dest
376 res = lambda p: dest
377 return res
377 return res
378
378
379 # pat: ossep
379 # pat: ossep
380 # dest ossep
380 # dest ossep
381 # srcs: list of (hgsep, hgsep, ossep, bool)
381 # srcs: list of (hgsep, hgsep, ossep, bool)
382 # return: function that takes hgsep and returns ossep
382 # return: function that takes hgsep and returns ossep
383 def targetpathafterfn(pat, dest, srcs):
383 def targetpathafterfn(pat, dest, srcs):
384 if matchmod.patkind(pat):
384 if matchmod.patkind(pat):
385 # a mercurial pattern
385 # a mercurial pattern
386 res = lambda p: os.path.join(dest,
386 res = lambda p: os.path.join(dest,
387 os.path.basename(util.localpath(p)))
387 os.path.basename(util.localpath(p)))
388 else:
388 else:
389 abspfx = scmutil.canonpath(repo.root, cwd, pat)
389 abspfx = scmutil.canonpath(repo.root, cwd, pat)
390 if len(abspfx) < len(srcs[0][0]):
390 if len(abspfx) < len(srcs[0][0]):
391 # A directory. Either the target path contains the last
391 # A directory. Either the target path contains the last
392 # component of the source path or it does not.
392 # component of the source path or it does not.
393 def evalpath(striplen):
393 def evalpath(striplen):
394 score = 0
394 score = 0
395 for s in srcs:
395 for s in srcs:
396 t = os.path.join(dest, util.localpath(s[0])[striplen:])
396 t = os.path.join(dest, util.localpath(s[0])[striplen:])
397 if os.path.lexists(t):
397 if os.path.lexists(t):
398 score += 1
398 score += 1
399 return score
399 return score
400
400
401 abspfx = util.localpath(abspfx)
401 abspfx = util.localpath(abspfx)
402 striplen = len(abspfx)
402 striplen = len(abspfx)
403 if striplen:
403 if striplen:
404 striplen += len(os.sep)
404 striplen += len(os.sep)
405 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
405 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
406 score = evalpath(striplen)
406 score = evalpath(striplen)
407 striplen1 = len(os.path.split(abspfx)[0])
407 striplen1 = len(os.path.split(abspfx)[0])
408 if striplen1:
408 if striplen1:
409 striplen1 += len(os.sep)
409 striplen1 += len(os.sep)
410 if evalpath(striplen1) > score:
410 if evalpath(striplen1) > score:
411 striplen = striplen1
411 striplen = striplen1
412 res = lambda p: os.path.join(dest,
412 res = lambda p: os.path.join(dest,
413 util.localpath(p)[striplen:])
413 util.localpath(p)[striplen:])
414 else:
414 else:
415 # a file
415 # a file
416 if destdirexists:
416 if destdirexists:
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 res = lambda p: dest
420 res = lambda p: dest
421 return res
421 return res
422
422
423
423
424 pats = scmutil.expandpats(pats)
424 pats = scmutil.expandpats(pats)
425 if not pats:
425 if not pats:
426 raise util.Abort(_('no source or destination specified'))
426 raise util.Abort(_('no source or destination specified'))
427 if len(pats) == 1:
427 if len(pats) == 1:
428 raise util.Abort(_('no destination specified'))
428 raise util.Abort(_('no destination specified'))
429 dest = pats.pop()
429 dest = pats.pop()
430 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
430 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
431 if not destdirexists:
431 if not destdirexists:
432 if len(pats) > 1 or matchmod.patkind(pats[0]):
432 if len(pats) > 1 or matchmod.patkind(pats[0]):
433 raise util.Abort(_('with multiple sources, destination must be an '
433 raise util.Abort(_('with multiple sources, destination must be an '
434 'existing directory'))
434 'existing directory'))
435 if util.endswithsep(dest):
435 if util.endswithsep(dest):
436 raise util.Abort(_('destination %s is not a directory') % dest)
436 raise util.Abort(_('destination %s is not a directory') % dest)
437
437
438 tfn = targetpathfn
438 tfn = targetpathfn
439 if after:
439 if after:
440 tfn = targetpathafterfn
440 tfn = targetpathafterfn
441 copylist = []
441 copylist = []
442 for pat in pats:
442 for pat in pats:
443 srcs = walkpat(pat)
443 srcs = walkpat(pat)
444 if not srcs:
444 if not srcs:
445 continue
445 continue
446 copylist.append((tfn(pat, dest, srcs), srcs))
446 copylist.append((tfn(pat, dest, srcs), srcs))
447 if not copylist:
447 if not copylist:
448 raise util.Abort(_('no files to copy'))
448 raise util.Abort(_('no files to copy'))
449
449
450 errors = 0
450 errors = 0
451 for targetpath, srcs in copylist:
451 for targetpath, srcs in copylist:
452 for abssrc, relsrc, exact in srcs:
452 for abssrc, relsrc, exact in srcs:
453 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
453 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
454 errors += 1
454 errors += 1
455
455
456 if errors:
456 if errors:
457 ui.warn(_('(consider using --after)\n'))
457 ui.warn(_('(consider using --after)\n'))
458
458
459 return errors != 0
459 return errors != 0
460
460
461 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
461 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
462 runargs=None, appendpid=False):
462 runargs=None, appendpid=False):
463 '''Run a command as a service.'''
463 '''Run a command as a service.'''
464
464
465 if opts['daemon'] and not opts['daemon_pipefds']:
465 if opts['daemon'] and not opts['daemon_pipefds']:
466 # Signal child process startup with file removal
466 # Signal child process startup with file removal
467 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
467 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
468 os.close(lockfd)
468 os.close(lockfd)
469 try:
469 try:
470 if not runargs:
470 if not runargs:
471 runargs = util.hgcmd() + sys.argv[1:]
471 runargs = util.hgcmd() + sys.argv[1:]
472 runargs.append('--daemon-pipefds=%s' % lockpath)
472 runargs.append('--daemon-pipefds=%s' % lockpath)
473 # Don't pass --cwd to the child process, because we've already
473 # Don't pass --cwd to the child process, because we've already
474 # changed directory.
474 # changed directory.
475 for i in xrange(1, len(runargs)):
475 for i in xrange(1, len(runargs)):
476 if runargs[i].startswith('--cwd='):
476 if runargs[i].startswith('--cwd='):
477 del runargs[i]
477 del runargs[i]
478 break
478 break
479 elif runargs[i].startswith('--cwd'):
479 elif runargs[i].startswith('--cwd'):
480 del runargs[i:i + 2]
480 del runargs[i:i + 2]
481 break
481 break
482 def condfn():
482 def condfn():
483 return not os.path.exists(lockpath)
483 return not os.path.exists(lockpath)
484 pid = util.rundetached(runargs, condfn)
484 pid = util.rundetached(runargs, condfn)
485 if pid < 0:
485 if pid < 0:
486 raise util.Abort(_('child process failed to start'))
486 raise util.Abort(_('child process failed to start'))
487 finally:
487 finally:
488 try:
488 try:
489 os.unlink(lockpath)
489 os.unlink(lockpath)
490 except OSError, e:
490 except OSError, e:
491 if e.errno != errno.ENOENT:
491 if e.errno != errno.ENOENT:
492 raise
492 raise
493 if parentfn:
493 if parentfn:
494 return parentfn(pid)
494 return parentfn(pid)
495 else:
495 else:
496 return
496 return
497
497
498 if initfn:
498 if initfn:
499 initfn()
499 initfn()
500
500
501 if opts['pid_file']:
501 if opts['pid_file']:
502 mode = appendpid and 'a' or 'w'
502 mode = appendpid and 'a' or 'w'
503 fp = open(opts['pid_file'], mode)
503 fp = open(opts['pid_file'], mode)
504 fp.write(str(os.getpid()) + '\n')
504 fp.write(str(os.getpid()) + '\n')
505 fp.close()
505 fp.close()
506
506
507 if opts['daemon_pipefds']:
507 if opts['daemon_pipefds']:
508 lockpath = opts['daemon_pipefds']
508 lockpath = opts['daemon_pipefds']
509 try:
509 try:
510 os.setsid()
510 os.setsid()
511 except AttributeError:
511 except AttributeError:
512 pass
512 pass
513 os.unlink(lockpath)
513 os.unlink(lockpath)
514 util.hidewindow()
514 util.hidewindow()
515 sys.stdout.flush()
515 sys.stdout.flush()
516 sys.stderr.flush()
516 sys.stderr.flush()
517
517
518 nullfd = os.open(os.devnull, os.O_RDWR)
518 nullfd = os.open(os.devnull, os.O_RDWR)
519 logfilefd = nullfd
519 logfilefd = nullfd
520 if logfile:
520 if logfile:
521 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
521 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
522 os.dup2(nullfd, 0)
522 os.dup2(nullfd, 0)
523 os.dup2(logfilefd, 1)
523 os.dup2(logfilefd, 1)
524 os.dup2(logfilefd, 2)
524 os.dup2(logfilefd, 2)
525 if nullfd not in (0, 1, 2):
525 if nullfd not in (0, 1, 2):
526 os.close(nullfd)
526 os.close(nullfd)
527 if logfile and logfilefd not in (0, 1, 2):
527 if logfile and logfilefd not in (0, 1, 2):
528 os.close(logfilefd)
528 os.close(logfilefd)
529
529
530 if runfn:
530 if runfn:
531 return runfn()
531 return runfn()
532
532
533 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
533 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
534 opts=None):
534 opts=None):
535 '''export changesets as hg patches.'''
535 '''export changesets as hg patches.'''
536
536
537 total = len(revs)
537 total = len(revs)
538 revwidth = max([len(str(rev)) for rev in revs])
538 revwidth = max([len(str(rev)) for rev in revs])
539
539
540 def single(rev, seqno, fp):
540 def single(rev, seqno, fp):
541 ctx = repo[rev]
541 ctx = repo[rev]
542 node = ctx.node()
542 node = ctx.node()
543 parents = [p.node() for p in ctx.parents() if p]
543 parents = [p.node() for p in ctx.parents() if p]
544 branch = ctx.branch()
544 branch = ctx.branch()
545 if switch_parent:
545 if switch_parent:
546 parents.reverse()
546 parents.reverse()
547 prev = (parents and parents[0]) or nullid
547 prev = (parents and parents[0]) or nullid
548
548
549 shouldclose = False
549 shouldclose = False
550 if not fp:
550 if not fp and len(template) > 0:
551 desc_lines = ctx.description().rstrip().split('\n')
551 desc_lines = ctx.description().rstrip().split('\n')
552 desc = desc_lines[0] #Commit always has a first line.
552 desc = desc_lines[0] #Commit always has a first line.
553 fp = makefileobj(repo, template, node, desc=desc, total=total,
553 fp = makefileobj(repo, template, node, desc=desc, total=total,
554 seqno=seqno, revwidth=revwidth, mode='ab')
554 seqno=seqno, revwidth=revwidth, mode='ab')
555 if fp != template:
555 if fp != template:
556 shouldclose = True
556 shouldclose = True
557 if fp != sys.stdout and util.safehasattr(fp, 'name'):
557 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
558 repo.ui.note("%s\n" % fp.name)
558 repo.ui.note("%s\n" % fp.name)
559
559
560 fp.write("# HG changeset patch\n")
560 if not fp:
561 fp.write("# User %s\n" % ctx.user())
561 write = repo.ui.write
562 fp.write("# Date %d %d\n" % ctx.date())
562 else:
563 def write(s, **kw):
564 fp.write(s)
565
566
567 write("# HG changeset patch\n")
568 write("# User %s\n" % ctx.user())
569 write("# Date %d %d\n" % ctx.date())
563 if branch and branch != 'default':
570 if branch and branch != 'default':
564 fp.write("# Branch %s\n" % branch)
571 write("# Branch %s\n" % branch)
565 fp.write("# Node ID %s\n" % hex(node))
572 write("# Node ID %s\n" % hex(node))
566 fp.write("# Parent %s\n" % hex(prev))
573 write("# Parent %s\n" % hex(prev))
567 if len(parents) > 1:
574 if len(parents) > 1:
568 fp.write("# Parent %s\n" % hex(parents[1]))
575 write("# Parent %s\n" % hex(parents[1]))
569 fp.write(ctx.description().rstrip())
576 write(ctx.description().rstrip())
570 fp.write("\n\n")
577 write("\n\n")
571
578
572 for chunk in patch.diff(repo, prev, node, opts=opts):
579 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
573 fp.write(chunk)
580 write(chunk, label=label)
574
581
575 if shouldclose:
582 if shouldclose:
576 fp.close()
583 fp.close()
577
584
578 for seqno, rev in enumerate(revs):
585 for seqno, rev in enumerate(revs):
579 single(rev, seqno + 1, fp)
586 single(rev, seqno + 1, fp)
580
587
581 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
588 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
582 changes=None, stat=False, fp=None, prefix='',
589 changes=None, stat=False, fp=None, prefix='',
583 listsubrepos=False):
590 listsubrepos=False):
584 '''show diff or diffstat.'''
591 '''show diff or diffstat.'''
585 if fp is None:
592 if fp is None:
586 write = ui.write
593 write = ui.write
587 else:
594 else:
588 def write(s, **kw):
595 def write(s, **kw):
589 fp.write(s)
596 fp.write(s)
590
597
591 if stat:
598 if stat:
592 diffopts = diffopts.copy(context=0)
599 diffopts = diffopts.copy(context=0)
593 width = 80
600 width = 80
594 if not ui.plain():
601 if not ui.plain():
595 width = ui.termwidth()
602 width = ui.termwidth()
596 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
603 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
597 prefix=prefix)
604 prefix=prefix)
598 for chunk, label in patch.diffstatui(util.iterlines(chunks),
605 for chunk, label in patch.diffstatui(util.iterlines(chunks),
599 width=width,
606 width=width,
600 git=diffopts.git):
607 git=diffopts.git):
601 write(chunk, label=label)
608 write(chunk, label=label)
602 else:
609 else:
603 for chunk, label in patch.diffui(repo, node1, node2, match,
610 for chunk, label in patch.diffui(repo, node1, node2, match,
604 changes, diffopts, prefix=prefix):
611 changes, diffopts, prefix=prefix):
605 write(chunk, label=label)
612 write(chunk, label=label)
606
613
607 if listsubrepos:
614 if listsubrepos:
608 ctx1 = repo[node1]
615 ctx1 = repo[node1]
609 ctx2 = repo[node2]
616 ctx2 = repo[node2]
610 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
617 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
611 tempnode2 = node2
618 tempnode2 = node2
612 try:
619 try:
613 if node2 is not None:
620 if node2 is not None:
614 tempnode2 = ctx2.substate[subpath][1]
621 tempnode2 = ctx2.substate[subpath][1]
615 except KeyError:
622 except KeyError:
616 # A subrepo that existed in node1 was deleted between node1 and
623 # A subrepo that existed in node1 was deleted between node1 and
617 # node2 (inclusive). Thus, ctx2's substate won't contain that
624 # node2 (inclusive). Thus, ctx2's substate won't contain that
618 # subpath. The best we can do is to ignore it.
625 # subpath. The best we can do is to ignore it.
619 tempnode2 = None
626 tempnode2 = None
620 submatch = matchmod.narrowmatcher(subpath, match)
627 submatch = matchmod.narrowmatcher(subpath, match)
621 sub.diff(diffopts, tempnode2, submatch, changes=changes,
628 sub.diff(diffopts, tempnode2, submatch, changes=changes,
622 stat=stat, fp=fp, prefix=prefix)
629 stat=stat, fp=fp, prefix=prefix)
623
630
624 class changeset_printer(object):
631 class changeset_printer(object):
625 '''show changeset information when templating not requested.'''
632 '''show changeset information when templating not requested.'''
626
633
627 def __init__(self, ui, repo, patch, diffopts, buffered):
634 def __init__(self, ui, repo, patch, diffopts, buffered):
628 self.ui = ui
635 self.ui = ui
629 self.repo = repo
636 self.repo = repo
630 self.buffered = buffered
637 self.buffered = buffered
631 self.patch = patch
638 self.patch = patch
632 self.diffopts = diffopts
639 self.diffopts = diffopts
633 self.header = {}
640 self.header = {}
634 self.hunk = {}
641 self.hunk = {}
635 self.lastheader = None
642 self.lastheader = None
636 self.footer = None
643 self.footer = None
637
644
638 def flush(self, rev):
645 def flush(self, rev):
639 if rev in self.header:
646 if rev in self.header:
640 h = self.header[rev]
647 h = self.header[rev]
641 if h != self.lastheader:
648 if h != self.lastheader:
642 self.lastheader = h
649 self.lastheader = h
643 self.ui.write(h)
650 self.ui.write(h)
644 del self.header[rev]
651 del self.header[rev]
645 if rev in self.hunk:
652 if rev in self.hunk:
646 self.ui.write(self.hunk[rev])
653 self.ui.write(self.hunk[rev])
647 del self.hunk[rev]
654 del self.hunk[rev]
648 return 1
655 return 1
649 return 0
656 return 0
650
657
651 def close(self):
658 def close(self):
652 if self.footer:
659 if self.footer:
653 self.ui.write(self.footer)
660 self.ui.write(self.footer)
654
661
655 def show(self, ctx, copies=None, matchfn=None, **props):
662 def show(self, ctx, copies=None, matchfn=None, **props):
656 if self.buffered:
663 if self.buffered:
657 self.ui.pushbuffer()
664 self.ui.pushbuffer()
658 self._show(ctx, copies, matchfn, props)
665 self._show(ctx, copies, matchfn, props)
659 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
666 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
660 else:
667 else:
661 self._show(ctx, copies, matchfn, props)
668 self._show(ctx, copies, matchfn, props)
662
669
663 def _show(self, ctx, copies, matchfn, props):
670 def _show(self, ctx, copies, matchfn, props):
664 '''show a single changeset or file revision'''
671 '''show a single changeset or file revision'''
665 changenode = ctx.node()
672 changenode = ctx.node()
666 rev = ctx.rev()
673 rev = ctx.rev()
667
674
668 if self.ui.quiet:
675 if self.ui.quiet:
669 self.ui.write("%d:%s\n" % (rev, short(changenode)),
676 self.ui.write("%d:%s\n" % (rev, short(changenode)),
670 label='log.node')
677 label='log.node')
671 return
678 return
672
679
673 log = self.repo.changelog
680 log = self.repo.changelog
674 date = util.datestr(ctx.date())
681 date = util.datestr(ctx.date())
675
682
676 hexfunc = self.ui.debugflag and hex or short
683 hexfunc = self.ui.debugflag and hex or short
677
684
678 parents = [(p, hexfunc(log.node(p)))
685 parents = [(p, hexfunc(log.node(p)))
679 for p in self._meaningful_parentrevs(log, rev)]
686 for p in self._meaningful_parentrevs(log, rev)]
680
687
681 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
688 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
682 label='log.changeset')
689 label='log.changeset')
683
690
684 branch = ctx.branch()
691 branch = ctx.branch()
685 # don't show the default branch name
692 # don't show the default branch name
686 if branch != 'default':
693 if branch != 'default':
687 self.ui.write(_("branch: %s\n") % branch,
694 self.ui.write(_("branch: %s\n") % branch,
688 label='log.branch')
695 label='log.branch')
689 for bookmark in self.repo.nodebookmarks(changenode):
696 for bookmark in self.repo.nodebookmarks(changenode):
690 self.ui.write(_("bookmark: %s\n") % bookmark,
697 self.ui.write(_("bookmark: %s\n") % bookmark,
691 label='log.bookmark')
698 label='log.bookmark')
692 for tag in self.repo.nodetags(changenode):
699 for tag in self.repo.nodetags(changenode):
693 self.ui.write(_("tag: %s\n") % tag,
700 self.ui.write(_("tag: %s\n") % tag,
694 label='log.tag')
701 label='log.tag')
695 if self.ui.debugflag and ctx.phase():
702 if self.ui.debugflag and ctx.phase():
696 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
703 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
697 label='log.phase')
704 label='log.phase')
698 for parent in parents:
705 for parent in parents:
699 self.ui.write(_("parent: %d:%s\n") % parent,
706 self.ui.write(_("parent: %d:%s\n") % parent,
700 label='log.parent')
707 label='log.parent')
701
708
702 if self.ui.debugflag:
709 if self.ui.debugflag:
703 mnode = ctx.manifestnode()
710 mnode = ctx.manifestnode()
704 self.ui.write(_("manifest: %d:%s\n") %
711 self.ui.write(_("manifest: %d:%s\n") %
705 (self.repo.manifest.rev(mnode), hex(mnode)),
712 (self.repo.manifest.rev(mnode), hex(mnode)),
706 label='ui.debug log.manifest')
713 label='ui.debug log.manifest')
707 self.ui.write(_("user: %s\n") % ctx.user(),
714 self.ui.write(_("user: %s\n") % ctx.user(),
708 label='log.user')
715 label='log.user')
709 self.ui.write(_("date: %s\n") % date,
716 self.ui.write(_("date: %s\n") % date,
710 label='log.date')
717 label='log.date')
711
718
712 if self.ui.debugflag:
719 if self.ui.debugflag:
713 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
720 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
714 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
721 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
715 files):
722 files):
716 if value:
723 if value:
717 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
724 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
718 label='ui.debug log.files')
725 label='ui.debug log.files')
719 elif ctx.files() and self.ui.verbose:
726 elif ctx.files() and self.ui.verbose:
720 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
727 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
721 label='ui.note log.files')
728 label='ui.note log.files')
722 if copies and self.ui.verbose:
729 if copies and self.ui.verbose:
723 copies = ['%s (%s)' % c for c in copies]
730 copies = ['%s (%s)' % c for c in copies]
724 self.ui.write(_("copies: %s\n") % ' '.join(copies),
731 self.ui.write(_("copies: %s\n") % ' '.join(copies),
725 label='ui.note log.copies')
732 label='ui.note log.copies')
726
733
727 extra = ctx.extra()
734 extra = ctx.extra()
728 if extra and self.ui.debugflag:
735 if extra and self.ui.debugflag:
729 for key, value in sorted(extra.items()):
736 for key, value in sorted(extra.items()):
730 self.ui.write(_("extra: %s=%s\n")
737 self.ui.write(_("extra: %s=%s\n")
731 % (key, value.encode('string_escape')),
738 % (key, value.encode('string_escape')),
732 label='ui.debug log.extra')
739 label='ui.debug log.extra')
733
740
734 description = ctx.description().strip()
741 description = ctx.description().strip()
735 if description:
742 if description:
736 if self.ui.verbose:
743 if self.ui.verbose:
737 self.ui.write(_("description:\n"),
744 self.ui.write(_("description:\n"),
738 label='ui.note log.description')
745 label='ui.note log.description')
739 self.ui.write(description,
746 self.ui.write(description,
740 label='ui.note log.description')
747 label='ui.note log.description')
741 self.ui.write("\n\n")
748 self.ui.write("\n\n")
742 else:
749 else:
743 self.ui.write(_("summary: %s\n") %
750 self.ui.write(_("summary: %s\n") %
744 description.splitlines()[0],
751 description.splitlines()[0],
745 label='log.summary')
752 label='log.summary')
746 self.ui.write("\n")
753 self.ui.write("\n")
747
754
748 self.showpatch(changenode, matchfn)
755 self.showpatch(changenode, matchfn)
749
756
750 def showpatch(self, node, matchfn):
757 def showpatch(self, node, matchfn):
751 if not matchfn:
758 if not matchfn:
752 matchfn = self.patch
759 matchfn = self.patch
753 if matchfn:
760 if matchfn:
754 stat = self.diffopts.get('stat')
761 stat = self.diffopts.get('stat')
755 diff = self.diffopts.get('patch')
762 diff = self.diffopts.get('patch')
756 diffopts = patch.diffopts(self.ui, self.diffopts)
763 diffopts = patch.diffopts(self.ui, self.diffopts)
757 prev = self.repo.changelog.parents(node)[0]
764 prev = self.repo.changelog.parents(node)[0]
758 if stat:
765 if stat:
759 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
766 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
760 match=matchfn, stat=True)
767 match=matchfn, stat=True)
761 if diff:
768 if diff:
762 if stat:
769 if stat:
763 self.ui.write("\n")
770 self.ui.write("\n")
764 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
771 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
765 match=matchfn, stat=False)
772 match=matchfn, stat=False)
766 self.ui.write("\n")
773 self.ui.write("\n")
767
774
768 def _meaningful_parentrevs(self, log, rev):
775 def _meaningful_parentrevs(self, log, rev):
769 """Return list of meaningful (or all if debug) parentrevs for rev.
776 """Return list of meaningful (or all if debug) parentrevs for rev.
770
777
771 For merges (two non-nullrev revisions) both parents are meaningful.
778 For merges (two non-nullrev revisions) both parents are meaningful.
772 Otherwise the first parent revision is considered meaningful if it
779 Otherwise the first parent revision is considered meaningful if it
773 is not the preceding revision.
780 is not the preceding revision.
774 """
781 """
775 parents = log.parentrevs(rev)
782 parents = log.parentrevs(rev)
776 if not self.ui.debugflag and parents[1] == nullrev:
783 if not self.ui.debugflag and parents[1] == nullrev:
777 if parents[0] >= rev - 1:
784 if parents[0] >= rev - 1:
778 parents = []
785 parents = []
779 else:
786 else:
780 parents = [parents[0]]
787 parents = [parents[0]]
781 return parents
788 return parents
782
789
783
790
784 class changeset_templater(changeset_printer):
791 class changeset_templater(changeset_printer):
785 '''format changeset information.'''
792 '''format changeset information.'''
786
793
787 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
794 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
788 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
795 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
789 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
796 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
790 defaulttempl = {
797 defaulttempl = {
791 'parent': '{rev}:{node|formatnode} ',
798 'parent': '{rev}:{node|formatnode} ',
792 'manifest': '{rev}:{node|formatnode}',
799 'manifest': '{rev}:{node|formatnode}',
793 'file_copy': '{name} ({source})',
800 'file_copy': '{name} ({source})',
794 'extra': '{key}={value|stringescape}'
801 'extra': '{key}={value|stringescape}'
795 }
802 }
796 # filecopy is preserved for compatibility reasons
803 # filecopy is preserved for compatibility reasons
797 defaulttempl['filecopy'] = defaulttempl['file_copy']
804 defaulttempl['filecopy'] = defaulttempl['file_copy']
798 self.t = templater.templater(mapfile, {'formatnode': formatnode},
805 self.t = templater.templater(mapfile, {'formatnode': formatnode},
799 cache=defaulttempl)
806 cache=defaulttempl)
800 self.cache = {}
807 self.cache = {}
801
808
802 def use_template(self, t):
809 def use_template(self, t):
803 '''set template string to use'''
810 '''set template string to use'''
804 self.t.cache['changeset'] = t
811 self.t.cache['changeset'] = t
805
812
806 def _meaningful_parentrevs(self, ctx):
813 def _meaningful_parentrevs(self, ctx):
807 """Return list of meaningful (or all if debug) parentrevs for rev.
814 """Return list of meaningful (or all if debug) parentrevs for rev.
808 """
815 """
809 parents = ctx.parents()
816 parents = ctx.parents()
810 if len(parents) > 1:
817 if len(parents) > 1:
811 return parents
818 return parents
812 if self.ui.debugflag:
819 if self.ui.debugflag:
813 return [parents[0], self.repo['null']]
820 return [parents[0], self.repo['null']]
814 if parents[0].rev() >= ctx.rev() - 1:
821 if parents[0].rev() >= ctx.rev() - 1:
815 return []
822 return []
816 return parents
823 return parents
817
824
818 def _show(self, ctx, copies, matchfn, props):
825 def _show(self, ctx, copies, matchfn, props):
819 '''show a single changeset or file revision'''
826 '''show a single changeset or file revision'''
820
827
821 showlist = templatekw.showlist
828 showlist = templatekw.showlist
822
829
823 # showparents() behaviour depends on ui trace level which
830 # showparents() behaviour depends on ui trace level which
824 # causes unexpected behaviours at templating level and makes
831 # causes unexpected behaviours at templating level and makes
825 # it harder to extract it in a standalone function. Its
832 # it harder to extract it in a standalone function. Its
826 # behaviour cannot be changed so leave it here for now.
833 # behaviour cannot be changed so leave it here for now.
827 def showparents(**args):
834 def showparents(**args):
828 ctx = args['ctx']
835 ctx = args['ctx']
829 parents = [[('rev', p.rev()), ('node', p.hex())]
836 parents = [[('rev', p.rev()), ('node', p.hex())]
830 for p in self._meaningful_parentrevs(ctx)]
837 for p in self._meaningful_parentrevs(ctx)]
831 return showlist('parent', parents, **args)
838 return showlist('parent', parents, **args)
832
839
833 props = props.copy()
840 props = props.copy()
834 props.update(templatekw.keywords)
841 props.update(templatekw.keywords)
835 props['parents'] = showparents
842 props['parents'] = showparents
836 props['templ'] = self.t
843 props['templ'] = self.t
837 props['ctx'] = ctx
844 props['ctx'] = ctx
838 props['repo'] = self.repo
845 props['repo'] = self.repo
839 props['revcache'] = {'copies': copies}
846 props['revcache'] = {'copies': copies}
840 props['cache'] = self.cache
847 props['cache'] = self.cache
841
848
842 # find correct templates for current mode
849 # find correct templates for current mode
843
850
844 tmplmodes = [
851 tmplmodes = [
845 (True, None),
852 (True, None),
846 (self.ui.verbose, 'verbose'),
853 (self.ui.verbose, 'verbose'),
847 (self.ui.quiet, 'quiet'),
854 (self.ui.quiet, 'quiet'),
848 (self.ui.debugflag, 'debug'),
855 (self.ui.debugflag, 'debug'),
849 ]
856 ]
850
857
851 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
858 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
852 for mode, postfix in tmplmodes:
859 for mode, postfix in tmplmodes:
853 for type in types:
860 for type in types:
854 cur = postfix and ('%s_%s' % (type, postfix)) or type
861 cur = postfix and ('%s_%s' % (type, postfix)) or type
855 if mode and cur in self.t:
862 if mode and cur in self.t:
856 types[type] = cur
863 types[type] = cur
857
864
858 try:
865 try:
859
866
860 # write header
867 # write header
861 if types['header']:
868 if types['header']:
862 h = templater.stringify(self.t(types['header'], **props))
869 h = templater.stringify(self.t(types['header'], **props))
863 if self.buffered:
870 if self.buffered:
864 self.header[ctx.rev()] = h
871 self.header[ctx.rev()] = h
865 else:
872 else:
866 if self.lastheader != h:
873 if self.lastheader != h:
867 self.lastheader = h
874 self.lastheader = h
868 self.ui.write(h)
875 self.ui.write(h)
869
876
870 # write changeset metadata, then patch if requested
877 # write changeset metadata, then patch if requested
871 key = types['changeset']
878 key = types['changeset']
872 self.ui.write(templater.stringify(self.t(key, **props)))
879 self.ui.write(templater.stringify(self.t(key, **props)))
873 self.showpatch(ctx.node(), matchfn)
880 self.showpatch(ctx.node(), matchfn)
874
881
875 if types['footer']:
882 if types['footer']:
876 if not self.footer:
883 if not self.footer:
877 self.footer = templater.stringify(self.t(types['footer'],
884 self.footer = templater.stringify(self.t(types['footer'],
878 **props))
885 **props))
879
886
880 except KeyError, inst:
887 except KeyError, inst:
881 msg = _("%s: no key named '%s'")
888 msg = _("%s: no key named '%s'")
882 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
889 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
883 except SyntaxError, inst:
890 except SyntaxError, inst:
884 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
891 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
885
892
886 def show_changeset(ui, repo, opts, buffered=False):
893 def show_changeset(ui, repo, opts, buffered=False):
887 """show one changeset using template or regular display.
894 """show one changeset using template or regular display.
888
895
889 Display format will be the first non-empty hit of:
896 Display format will be the first non-empty hit of:
890 1. option 'template'
897 1. option 'template'
891 2. option 'style'
898 2. option 'style'
892 3. [ui] setting 'logtemplate'
899 3. [ui] setting 'logtemplate'
893 4. [ui] setting 'style'
900 4. [ui] setting 'style'
894 If all of these values are either the unset or the empty string,
901 If all of these values are either the unset or the empty string,
895 regular display via changeset_printer() is done.
902 regular display via changeset_printer() is done.
896 """
903 """
897 # options
904 # options
898 patch = False
905 patch = False
899 if opts.get('patch') or opts.get('stat'):
906 if opts.get('patch') or opts.get('stat'):
900 patch = scmutil.matchall(repo)
907 patch = scmutil.matchall(repo)
901
908
902 tmpl = opts.get('template')
909 tmpl = opts.get('template')
903 style = None
910 style = None
904 if tmpl:
911 if tmpl:
905 tmpl = templater.parsestring(tmpl, quoted=False)
912 tmpl = templater.parsestring(tmpl, quoted=False)
906 else:
913 else:
907 style = opts.get('style')
914 style = opts.get('style')
908
915
909 # ui settings
916 # ui settings
910 if not (tmpl or style):
917 if not (tmpl or style):
911 tmpl = ui.config('ui', 'logtemplate')
918 tmpl = ui.config('ui', 'logtemplate')
912 if tmpl:
919 if tmpl:
913 try:
920 try:
914 tmpl = templater.parsestring(tmpl)
921 tmpl = templater.parsestring(tmpl)
915 except SyntaxError:
922 except SyntaxError:
916 tmpl = templater.parsestring(tmpl, quoted=False)
923 tmpl = templater.parsestring(tmpl, quoted=False)
917 else:
924 else:
918 style = util.expandpath(ui.config('ui', 'style', ''))
925 style = util.expandpath(ui.config('ui', 'style', ''))
919
926
920 if not (tmpl or style):
927 if not (tmpl or style):
921 return changeset_printer(ui, repo, patch, opts, buffered)
928 return changeset_printer(ui, repo, patch, opts, buffered)
922
929
923 mapfile = None
930 mapfile = None
924 if style and not tmpl:
931 if style and not tmpl:
925 mapfile = style
932 mapfile = style
926 if not os.path.split(mapfile)[0]:
933 if not os.path.split(mapfile)[0]:
927 mapname = (templater.templatepath('map-cmdline.' + mapfile)
934 mapname = (templater.templatepath('map-cmdline.' + mapfile)
928 or templater.templatepath(mapfile))
935 or templater.templatepath(mapfile))
929 if mapname:
936 if mapname:
930 mapfile = mapname
937 mapfile = mapname
931
938
932 try:
939 try:
933 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
940 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
934 except SyntaxError, inst:
941 except SyntaxError, inst:
935 raise util.Abort(inst.args[0])
942 raise util.Abort(inst.args[0])
936 if tmpl:
943 if tmpl:
937 t.use_template(tmpl)
944 t.use_template(tmpl)
938 return t
945 return t
939
946
940 def finddate(ui, repo, date):
947 def finddate(ui, repo, date):
941 """Find the tipmost changeset that matches the given date spec"""
948 """Find the tipmost changeset that matches the given date spec"""
942
949
943 df = util.matchdate(date)
950 df = util.matchdate(date)
944 m = scmutil.matchall(repo)
951 m = scmutil.matchall(repo)
945 results = {}
952 results = {}
946
953
947 def prep(ctx, fns):
954 def prep(ctx, fns):
948 d = ctx.date()
955 d = ctx.date()
949 if df(d[0]):
956 if df(d[0]):
950 results[ctx.rev()] = d
957 results[ctx.rev()] = d
951
958
952 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
959 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
953 rev = ctx.rev()
960 rev = ctx.rev()
954 if rev in results:
961 if rev in results:
955 ui.status(_("found revision %s from %s\n") %
962 ui.status(_("found revision %s from %s\n") %
956 (rev, util.datestr(results[rev])))
963 (rev, util.datestr(results[rev])))
957 return str(rev)
964 return str(rev)
958
965
959 raise util.Abort(_("revision matching date not found"))
966 raise util.Abort(_("revision matching date not found"))
960
967
961 def increasingwindows(start, end, windowsize=8, sizelimit=512):
968 def increasingwindows(start, end, windowsize=8, sizelimit=512):
962 if start < end:
969 if start < end:
963 while start < end:
970 while start < end:
964 yield start, min(windowsize, end - start)
971 yield start, min(windowsize, end - start)
965 start += windowsize
972 start += windowsize
966 if windowsize < sizelimit:
973 if windowsize < sizelimit:
967 windowsize *= 2
974 windowsize *= 2
968 else:
975 else:
969 while start > end:
976 while start > end:
970 yield start, min(windowsize, start - end - 1)
977 yield start, min(windowsize, start - end - 1)
971 start -= windowsize
978 start -= windowsize
972 if windowsize < sizelimit:
979 if windowsize < sizelimit:
973 windowsize *= 2
980 windowsize *= 2
974
981
975 def walkchangerevs(repo, match, opts, prepare):
982 def walkchangerevs(repo, match, opts, prepare):
976 '''Iterate over files and the revs in which they changed.
983 '''Iterate over files and the revs in which they changed.
977
984
978 Callers most commonly need to iterate backwards over the history
985 Callers most commonly need to iterate backwards over the history
979 in which they are interested. Doing so has awful (quadratic-looking)
986 in which they are interested. Doing so has awful (quadratic-looking)
980 performance, so we use iterators in a "windowed" way.
987 performance, so we use iterators in a "windowed" way.
981
988
982 We walk a window of revisions in the desired order. Within the
989 We walk a window of revisions in the desired order. Within the
983 window, we first walk forwards to gather data, then in the desired
990 window, we first walk forwards to gather data, then in the desired
984 order (usually backwards) to display it.
991 order (usually backwards) to display it.
985
992
986 This function returns an iterator yielding contexts. Before
993 This function returns an iterator yielding contexts. Before
987 yielding each context, the iterator will first call the prepare
994 yielding each context, the iterator will first call the prepare
988 function on each context in the window in forward order.'''
995 function on each context in the window in forward order.'''
989
996
990 follow = opts.get('follow') or opts.get('follow_first')
997 follow = opts.get('follow') or opts.get('follow_first')
991
998
992 if not len(repo):
999 if not len(repo):
993 return []
1000 return []
994
1001
995 if follow:
1002 if follow:
996 defrange = '%s:0' % repo['.'].rev()
1003 defrange = '%s:0' % repo['.'].rev()
997 else:
1004 else:
998 defrange = '-1:0'
1005 defrange = '-1:0'
999 revs = scmutil.revrange(repo, opts.get('rev') or [defrange])
1006 revs = scmutil.revrange(repo, opts.get('rev') or [defrange])
1000 if not revs:
1007 if not revs:
1001 return []
1008 return []
1002 wanted = set()
1009 wanted = set()
1003 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1010 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1004 fncache = {}
1011 fncache = {}
1005 change = repo.changectx
1012 change = repo.changectx
1006
1013
1007 # First step is to fill wanted, the set of revisions that we want to yield.
1014 # First step is to fill wanted, the set of revisions that we want to yield.
1008 # When it does not induce extra cost, we also fill fncache for revisions in
1015 # When it does not induce extra cost, we also fill fncache for revisions in
1009 # wanted: a cache of filenames that were changed (ctx.files()) and that
1016 # wanted: a cache of filenames that were changed (ctx.files()) and that
1010 # match the file filtering conditions.
1017 # match the file filtering conditions.
1011
1018
1012 if not slowpath and not match.files():
1019 if not slowpath and not match.files():
1013 # No files, no patterns. Display all revs.
1020 # No files, no patterns. Display all revs.
1014 wanted = set(revs)
1021 wanted = set(revs)
1015 copies = []
1022 copies = []
1016
1023
1017 if not slowpath and match.files():
1024 if not slowpath and match.files():
1018 # We only have to read through the filelog to find wanted revisions
1025 # We only have to read through the filelog to find wanted revisions
1019
1026
1020 minrev, maxrev = min(revs), max(revs)
1027 minrev, maxrev = min(revs), max(revs)
1021 def filerevgen(filelog, last):
1028 def filerevgen(filelog, last):
1022 """
1029 """
1023 Only files, no patterns. Check the history of each file.
1030 Only files, no patterns. Check the history of each file.
1024
1031
1025 Examines filelog entries within minrev, maxrev linkrev range
1032 Examines filelog entries within minrev, maxrev linkrev range
1026 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1033 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1027 tuples in backwards order
1034 tuples in backwards order
1028 """
1035 """
1029 cl_count = len(repo)
1036 cl_count = len(repo)
1030 revs = []
1037 revs = []
1031 for j in xrange(0, last + 1):
1038 for j in xrange(0, last + 1):
1032 linkrev = filelog.linkrev(j)
1039 linkrev = filelog.linkrev(j)
1033 if linkrev < minrev:
1040 if linkrev < minrev:
1034 continue
1041 continue
1035 # only yield rev for which we have the changelog, it can
1042 # only yield rev for which we have the changelog, it can
1036 # happen while doing "hg log" during a pull or commit
1043 # happen while doing "hg log" during a pull or commit
1037 if linkrev >= cl_count:
1044 if linkrev >= cl_count:
1038 break
1045 break
1039
1046
1040 parentlinkrevs = []
1047 parentlinkrevs = []
1041 for p in filelog.parentrevs(j):
1048 for p in filelog.parentrevs(j):
1042 if p != nullrev:
1049 if p != nullrev:
1043 parentlinkrevs.append(filelog.linkrev(p))
1050 parentlinkrevs.append(filelog.linkrev(p))
1044 n = filelog.node(j)
1051 n = filelog.node(j)
1045 revs.append((linkrev, parentlinkrevs,
1052 revs.append((linkrev, parentlinkrevs,
1046 follow and filelog.renamed(n)))
1053 follow and filelog.renamed(n)))
1047
1054
1048 return reversed(revs)
1055 return reversed(revs)
1049 def iterfiles():
1056 def iterfiles():
1050 pctx = repo['.']
1057 pctx = repo['.']
1051 for filename in match.files():
1058 for filename in match.files():
1052 if follow:
1059 if follow:
1053 if filename not in pctx:
1060 if filename not in pctx:
1054 raise util.Abort(_('cannot follow file not in parent '
1061 raise util.Abort(_('cannot follow file not in parent '
1055 'revision: "%s"') % filename)
1062 'revision: "%s"') % filename)
1056 yield filename, pctx[filename].filenode()
1063 yield filename, pctx[filename].filenode()
1057 else:
1064 else:
1058 yield filename, None
1065 yield filename, None
1059 for filename_node in copies:
1066 for filename_node in copies:
1060 yield filename_node
1067 yield filename_node
1061 for file_, node in iterfiles():
1068 for file_, node in iterfiles():
1062 filelog = repo.file(file_)
1069 filelog = repo.file(file_)
1063 if not len(filelog):
1070 if not len(filelog):
1064 if node is None:
1071 if node is None:
1065 # A zero count may be a directory or deleted file, so
1072 # A zero count may be a directory or deleted file, so
1066 # try to find matching entries on the slow path.
1073 # try to find matching entries on the slow path.
1067 if follow:
1074 if follow:
1068 raise util.Abort(
1075 raise util.Abort(
1069 _('cannot follow nonexistent file: "%s"') % file_)
1076 _('cannot follow nonexistent file: "%s"') % file_)
1070 slowpath = True
1077 slowpath = True
1071 break
1078 break
1072 else:
1079 else:
1073 continue
1080 continue
1074
1081
1075 if node is None:
1082 if node is None:
1076 last = len(filelog) - 1
1083 last = len(filelog) - 1
1077 else:
1084 else:
1078 last = filelog.rev(node)
1085 last = filelog.rev(node)
1079
1086
1080
1087
1081 # keep track of all ancestors of the file
1088 # keep track of all ancestors of the file
1082 ancestors = set([filelog.linkrev(last)])
1089 ancestors = set([filelog.linkrev(last)])
1083
1090
1084 # iterate from latest to oldest revision
1091 # iterate from latest to oldest revision
1085 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1092 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1086 if not follow:
1093 if not follow:
1087 if rev > maxrev:
1094 if rev > maxrev:
1088 continue
1095 continue
1089 else:
1096 else:
1090 # Note that last might not be the first interesting
1097 # Note that last might not be the first interesting
1091 # rev to us:
1098 # rev to us:
1092 # if the file has been changed after maxrev, we'll
1099 # if the file has been changed after maxrev, we'll
1093 # have linkrev(last) > maxrev, and we still need
1100 # have linkrev(last) > maxrev, and we still need
1094 # to explore the file graph
1101 # to explore the file graph
1095 if rev not in ancestors:
1102 if rev not in ancestors:
1096 continue
1103 continue
1097 # XXX insert 1327 fix here
1104 # XXX insert 1327 fix here
1098 if flparentlinkrevs:
1105 if flparentlinkrevs:
1099 ancestors.update(flparentlinkrevs)
1106 ancestors.update(flparentlinkrevs)
1100
1107
1101 fncache.setdefault(rev, []).append(file_)
1108 fncache.setdefault(rev, []).append(file_)
1102 wanted.add(rev)
1109 wanted.add(rev)
1103 if copied:
1110 if copied:
1104 copies.append(copied)
1111 copies.append(copied)
1105 if slowpath:
1112 if slowpath:
1106 # We have to read the changelog to match filenames against
1113 # We have to read the changelog to match filenames against
1107 # changed files
1114 # changed files
1108
1115
1109 if follow:
1116 if follow:
1110 raise util.Abort(_('can only follow copies/renames for explicit '
1117 raise util.Abort(_('can only follow copies/renames for explicit '
1111 'filenames'))
1118 'filenames'))
1112
1119
1113 # The slow path checks files modified in every changeset.
1120 # The slow path checks files modified in every changeset.
1114 for i in sorted(revs):
1121 for i in sorted(revs):
1115 ctx = change(i)
1122 ctx = change(i)
1116 matches = filter(match, ctx.files())
1123 matches = filter(match, ctx.files())
1117 if matches:
1124 if matches:
1118 fncache[i] = matches
1125 fncache[i] = matches
1119 wanted.add(i)
1126 wanted.add(i)
1120
1127
1121 class followfilter(object):
1128 class followfilter(object):
1122 def __init__(self, onlyfirst=False):
1129 def __init__(self, onlyfirst=False):
1123 self.startrev = nullrev
1130 self.startrev = nullrev
1124 self.roots = set()
1131 self.roots = set()
1125 self.onlyfirst = onlyfirst
1132 self.onlyfirst = onlyfirst
1126
1133
1127 def match(self, rev):
1134 def match(self, rev):
1128 def realparents(rev):
1135 def realparents(rev):
1129 if self.onlyfirst:
1136 if self.onlyfirst:
1130 return repo.changelog.parentrevs(rev)[0:1]
1137 return repo.changelog.parentrevs(rev)[0:1]
1131 else:
1138 else:
1132 return filter(lambda x: x != nullrev,
1139 return filter(lambda x: x != nullrev,
1133 repo.changelog.parentrevs(rev))
1140 repo.changelog.parentrevs(rev))
1134
1141
1135 if self.startrev == nullrev:
1142 if self.startrev == nullrev:
1136 self.startrev = rev
1143 self.startrev = rev
1137 return True
1144 return True
1138
1145
1139 if rev > self.startrev:
1146 if rev > self.startrev:
1140 # forward: all descendants
1147 # forward: all descendants
1141 if not self.roots:
1148 if not self.roots:
1142 self.roots.add(self.startrev)
1149 self.roots.add(self.startrev)
1143 for parent in realparents(rev):
1150 for parent in realparents(rev):
1144 if parent in self.roots:
1151 if parent in self.roots:
1145 self.roots.add(rev)
1152 self.roots.add(rev)
1146 return True
1153 return True
1147 else:
1154 else:
1148 # backwards: all parents
1155 # backwards: all parents
1149 if not self.roots:
1156 if not self.roots:
1150 self.roots.update(realparents(self.startrev))
1157 self.roots.update(realparents(self.startrev))
1151 if rev in self.roots:
1158 if rev in self.roots:
1152 self.roots.remove(rev)
1159 self.roots.remove(rev)
1153 self.roots.update(realparents(rev))
1160 self.roots.update(realparents(rev))
1154 return True
1161 return True
1155
1162
1156 return False
1163 return False
1157
1164
1158 # it might be worthwhile to do this in the iterator if the rev range
1165 # it might be worthwhile to do this in the iterator if the rev range
1159 # is descending and the prune args are all within that range
1166 # is descending and the prune args are all within that range
1160 for rev in opts.get('prune', ()):
1167 for rev in opts.get('prune', ()):
1161 rev = repo[rev].rev()
1168 rev = repo[rev].rev()
1162 ff = followfilter()
1169 ff = followfilter()
1163 stop = min(revs[0], revs[-1])
1170 stop = min(revs[0], revs[-1])
1164 for x in xrange(rev, stop - 1, -1):
1171 for x in xrange(rev, stop - 1, -1):
1165 if ff.match(x):
1172 if ff.match(x):
1166 wanted.discard(x)
1173 wanted.discard(x)
1167
1174
1168 # Now that wanted is correctly initialized, we can iterate over the
1175 # Now that wanted is correctly initialized, we can iterate over the
1169 # revision range, yielding only revisions in wanted.
1176 # revision range, yielding only revisions in wanted.
1170 def iterate():
1177 def iterate():
1171 if follow and not match.files():
1178 if follow and not match.files():
1172 ff = followfilter(onlyfirst=opts.get('follow_first'))
1179 ff = followfilter(onlyfirst=opts.get('follow_first'))
1173 def want(rev):
1180 def want(rev):
1174 return ff.match(rev) and rev in wanted
1181 return ff.match(rev) and rev in wanted
1175 else:
1182 else:
1176 def want(rev):
1183 def want(rev):
1177 return rev in wanted
1184 return rev in wanted
1178
1185
1179 for i, window in increasingwindows(0, len(revs)):
1186 for i, window in increasingwindows(0, len(revs)):
1180 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1187 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1181 for rev in sorted(nrevs):
1188 for rev in sorted(nrevs):
1182 fns = fncache.get(rev)
1189 fns = fncache.get(rev)
1183 ctx = change(rev)
1190 ctx = change(rev)
1184 if not fns:
1191 if not fns:
1185 def fns_generator():
1192 def fns_generator():
1186 for f in ctx.files():
1193 for f in ctx.files():
1187 if match(f):
1194 if match(f):
1188 yield f
1195 yield f
1189 fns = fns_generator()
1196 fns = fns_generator()
1190 prepare(ctx, fns)
1197 prepare(ctx, fns)
1191 for rev in nrevs:
1198 for rev in nrevs:
1192 yield change(rev)
1199 yield change(rev)
1193 return iterate()
1200 return iterate()
1194
1201
1195 def _makegraphfilematcher(repo, pats, followfirst):
1202 def _makegraphfilematcher(repo, pats, followfirst):
1196 # When displaying a revision with --patch --follow FILE, we have
1203 # When displaying a revision with --patch --follow FILE, we have
1197 # to know which file of the revision must be diffed. With
1204 # to know which file of the revision must be diffed. With
1198 # --follow, we want the names of the ancestors of FILE in the
1205 # --follow, we want the names of the ancestors of FILE in the
1199 # revision, stored in "fcache". "fcache" is populated by
1206 # revision, stored in "fcache". "fcache" is populated by
1200 # reproducing the graph traversal already done by --follow revset
1207 # reproducing the graph traversal already done by --follow revset
1201 # and relating linkrevs to file names (which is not "correct" but
1208 # and relating linkrevs to file names (which is not "correct" but
1202 # good enough).
1209 # good enough).
1203 fcache = {}
1210 fcache = {}
1204 fcacheready = [False]
1211 fcacheready = [False]
1205 pctx = repo['.']
1212 pctx = repo['.']
1206 wctx = repo[None]
1213 wctx = repo[None]
1207
1214
1208 def populate():
1215 def populate():
1209 for fn in pats:
1216 for fn in pats:
1210 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1217 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1211 for c in i:
1218 for c in i:
1212 fcache.setdefault(c.linkrev(), set()).add(c.path())
1219 fcache.setdefault(c.linkrev(), set()).add(c.path())
1213
1220
1214 def filematcher(rev):
1221 def filematcher(rev):
1215 if not fcacheready[0]:
1222 if not fcacheready[0]:
1216 # Lazy initialization
1223 # Lazy initialization
1217 fcacheready[0] = True
1224 fcacheready[0] = True
1218 populate()
1225 populate()
1219 return scmutil.match(wctx, fcache.get(rev, []), default='path')
1226 return scmutil.match(wctx, fcache.get(rev, []), default='path')
1220
1227
1221 return filematcher
1228 return filematcher
1222
1229
1223 def _makegraphlogrevset(repo, pats, opts, revs):
1230 def _makegraphlogrevset(repo, pats, opts, revs):
1224 """Return (expr, filematcher) where expr is a revset string built
1231 """Return (expr, filematcher) where expr is a revset string built
1225 from log options and file patterns or None. If --stat or --patch
1232 from log options and file patterns or None. If --stat or --patch
1226 are not passed filematcher is None. Otherwise it is a callable
1233 are not passed filematcher is None. Otherwise it is a callable
1227 taking a revision number and returning a match objects filtering
1234 taking a revision number and returning a match objects filtering
1228 the files to be detailed when displaying the revision.
1235 the files to be detailed when displaying the revision.
1229 """
1236 """
1230 opt2revset = {
1237 opt2revset = {
1231 'no_merges': ('not merge()', None),
1238 'no_merges': ('not merge()', None),
1232 'only_merges': ('merge()', None),
1239 'only_merges': ('merge()', None),
1233 '_ancestors': ('ancestors(%(val)s)', None),
1240 '_ancestors': ('ancestors(%(val)s)', None),
1234 '_fancestors': ('_firstancestors(%(val)s)', None),
1241 '_fancestors': ('_firstancestors(%(val)s)', None),
1235 '_descendants': ('descendants(%(val)s)', None),
1242 '_descendants': ('descendants(%(val)s)', None),
1236 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1243 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1237 '_matchfiles': ('_matchfiles(%(val)s)', None),
1244 '_matchfiles': ('_matchfiles(%(val)s)', None),
1238 'date': ('date(%(val)r)', None),
1245 'date': ('date(%(val)r)', None),
1239 'branch': ('branch(%(val)r)', ' or '),
1246 'branch': ('branch(%(val)r)', ' or '),
1240 '_patslog': ('filelog(%(val)r)', ' or '),
1247 '_patslog': ('filelog(%(val)r)', ' or '),
1241 '_patsfollow': ('follow(%(val)r)', ' or '),
1248 '_patsfollow': ('follow(%(val)r)', ' or '),
1242 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1249 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1243 'keyword': ('keyword(%(val)r)', ' or '),
1250 'keyword': ('keyword(%(val)r)', ' or '),
1244 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1251 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1245 'user': ('user(%(val)r)', ' or '),
1252 'user': ('user(%(val)r)', ' or '),
1246 }
1253 }
1247
1254
1248 opts = dict(opts)
1255 opts = dict(opts)
1249 # follow or not follow?
1256 # follow or not follow?
1250 follow = opts.get('follow') or opts.get('follow_first')
1257 follow = opts.get('follow') or opts.get('follow_first')
1251 followfirst = opts.get('follow_first') and 1 or 0
1258 followfirst = opts.get('follow_first') and 1 or 0
1252 # --follow with FILE behaviour depends on revs...
1259 # --follow with FILE behaviour depends on revs...
1253 startrev = revs[0]
1260 startrev = revs[0]
1254 followdescendants = (len(revs) > 1 and revs[0] < revs[1]) and 1 or 0
1261 followdescendants = (len(revs) > 1 and revs[0] < revs[1]) and 1 or 0
1255
1262
1256 # branch and only_branch are really aliases and must be handled at
1263 # branch and only_branch are really aliases and must be handled at
1257 # the same time
1264 # the same time
1258 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1265 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1259 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1266 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1260 # pats/include/exclude are passed to match.match() directly in
1267 # pats/include/exclude are passed to match.match() directly in
1261 # _matchfiles() revset but walkchangerevs() builds its matcher with
1268 # _matchfiles() revset but walkchangerevs() builds its matcher with
1262 # scmutil.match(). The difference is input pats are globbed on
1269 # scmutil.match(). The difference is input pats are globbed on
1263 # platforms without shell expansion (windows).
1270 # platforms without shell expansion (windows).
1264 pctx = repo[None]
1271 pctx = repo[None]
1265 match, pats = scmutil.matchandpats(pctx, pats, opts)
1272 match, pats = scmutil.matchandpats(pctx, pats, opts)
1266 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1273 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1267 if not slowpath:
1274 if not slowpath:
1268 for f in match.files():
1275 for f in match.files():
1269 if follow and f not in pctx:
1276 if follow and f not in pctx:
1270 raise util.Abort(_('cannot follow file not in parent '
1277 raise util.Abort(_('cannot follow file not in parent '
1271 'revision: "%s"') % f)
1278 'revision: "%s"') % f)
1272 filelog = repo.file(f)
1279 filelog = repo.file(f)
1273 if not len(filelog):
1280 if not len(filelog):
1274 # A zero count may be a directory or deleted file, so
1281 # A zero count may be a directory or deleted file, so
1275 # try to find matching entries on the slow path.
1282 # try to find matching entries on the slow path.
1276 if follow:
1283 if follow:
1277 raise util.Abort(
1284 raise util.Abort(
1278 _('cannot follow nonexistent file: "%s"') % f)
1285 _('cannot follow nonexistent file: "%s"') % f)
1279 slowpath = True
1286 slowpath = True
1280 if slowpath:
1287 if slowpath:
1281 # See walkchangerevs() slow path.
1288 # See walkchangerevs() slow path.
1282 #
1289 #
1283 if follow:
1290 if follow:
1284 raise util.Abort(_('can only follow copies/renames for explicit '
1291 raise util.Abort(_('can only follow copies/renames for explicit '
1285 'filenames'))
1292 'filenames'))
1286 # pats/include/exclude cannot be represented as separate
1293 # pats/include/exclude cannot be represented as separate
1287 # revset expressions as their filtering logic applies at file
1294 # revset expressions as their filtering logic applies at file
1288 # level. For instance "-I a -X a" matches a revision touching
1295 # level. For instance "-I a -X a" matches a revision touching
1289 # "a" and "b" while "file(a) and not file(b)" does
1296 # "a" and "b" while "file(a) and not file(b)" does
1290 # not. Besides, filesets are evaluated against the working
1297 # not. Besides, filesets are evaluated against the working
1291 # directory.
1298 # directory.
1292 matchargs = ['r:', 'd:relpath']
1299 matchargs = ['r:', 'd:relpath']
1293 for p in pats:
1300 for p in pats:
1294 matchargs.append('p:' + p)
1301 matchargs.append('p:' + p)
1295 for p in opts.get('include', []):
1302 for p in opts.get('include', []):
1296 matchargs.append('i:' + p)
1303 matchargs.append('i:' + p)
1297 for p in opts.get('exclude', []):
1304 for p in opts.get('exclude', []):
1298 matchargs.append('x:' + p)
1305 matchargs.append('x:' + p)
1299 matchargs = ','.join(('%r' % p) for p in matchargs)
1306 matchargs = ','.join(('%r' % p) for p in matchargs)
1300 opts['_matchfiles'] = matchargs
1307 opts['_matchfiles'] = matchargs
1301 else:
1308 else:
1302 if follow:
1309 if follow:
1303 fpats = ('_patsfollow', '_patsfollowfirst')
1310 fpats = ('_patsfollow', '_patsfollowfirst')
1304 fnopats = (('_ancestors', '_fancestors'),
1311 fnopats = (('_ancestors', '_fancestors'),
1305 ('_descendants', '_fdescendants'))
1312 ('_descendants', '_fdescendants'))
1306 if pats:
1313 if pats:
1307 # follow() revset interprets its file argument as a
1314 # follow() revset interprets its file argument as a
1308 # manifest entry, so use match.files(), not pats.
1315 # manifest entry, so use match.files(), not pats.
1309 opts[fpats[followfirst]] = list(match.files())
1316 opts[fpats[followfirst]] = list(match.files())
1310 else:
1317 else:
1311 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1318 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1312 else:
1319 else:
1313 opts['_patslog'] = list(pats)
1320 opts['_patslog'] = list(pats)
1314
1321
1315 filematcher = None
1322 filematcher = None
1316 if opts.get('patch') or opts.get('stat'):
1323 if opts.get('patch') or opts.get('stat'):
1317 if follow:
1324 if follow:
1318 filematcher = _makegraphfilematcher(repo, pats, followfirst)
1325 filematcher = _makegraphfilematcher(repo, pats, followfirst)
1319 else:
1326 else:
1320 filematcher = lambda rev: match
1327 filematcher = lambda rev: match
1321
1328
1322 expr = []
1329 expr = []
1323 for op, val in opts.iteritems():
1330 for op, val in opts.iteritems():
1324 if not val:
1331 if not val:
1325 continue
1332 continue
1326 if op not in opt2revset:
1333 if op not in opt2revset:
1327 continue
1334 continue
1328 revop, andor = opt2revset[op]
1335 revop, andor = opt2revset[op]
1329 if '%(val)' not in revop:
1336 if '%(val)' not in revop:
1330 expr.append(revop)
1337 expr.append(revop)
1331 else:
1338 else:
1332 if not isinstance(val, list):
1339 if not isinstance(val, list):
1333 e = revop % {'val': val}
1340 e = revop % {'val': val}
1334 else:
1341 else:
1335 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1342 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1336 expr.append(e)
1343 expr.append(e)
1337
1344
1338 if expr:
1345 if expr:
1339 expr = '(' + ' and '.join(expr) + ')'
1346 expr = '(' + ' and '.join(expr) + ')'
1340 else:
1347 else:
1341 expr = None
1348 expr = None
1342 return expr, filematcher
1349 return expr, filematcher
1343
1350
1344 def getgraphlogrevs(repo, pats, opts):
1351 def getgraphlogrevs(repo, pats, opts):
1345 """Return (revs, expr, filematcher) where revs is an iterable of
1352 """Return (revs, expr, filematcher) where revs is an iterable of
1346 revision numbers, expr is a revset string built from log options
1353 revision numbers, expr is a revset string built from log options
1347 and file patterns or None, and used to filter 'revs'. If --stat or
1354 and file patterns or None, and used to filter 'revs'. If --stat or
1348 --patch are not passed filematcher is None. Otherwise it is a
1355 --patch are not passed filematcher is None. Otherwise it is a
1349 callable taking a revision number and returning a match objects
1356 callable taking a revision number and returning a match objects
1350 filtering the files to be detailed when displaying the revision.
1357 filtering the files to be detailed when displaying the revision.
1351 """
1358 """
1352 def increasingrevs(repo, revs, matcher):
1359 def increasingrevs(repo, revs, matcher):
1353 # The sorted input rev sequence is chopped in sub-sequences
1360 # The sorted input rev sequence is chopped in sub-sequences
1354 # which are sorted in ascending order and passed to the
1361 # which are sorted in ascending order and passed to the
1355 # matcher. The filtered revs are sorted again as they were in
1362 # matcher. The filtered revs are sorted again as they were in
1356 # the original sub-sequence. This achieve several things:
1363 # the original sub-sequence. This achieve several things:
1357 #
1364 #
1358 # - getlogrevs() now returns a generator which behaviour is
1365 # - getlogrevs() now returns a generator which behaviour is
1359 # adapted to log need. First results come fast, last ones
1366 # adapted to log need. First results come fast, last ones
1360 # are batched for performances.
1367 # are batched for performances.
1361 #
1368 #
1362 # - revset matchers often operate faster on revision in
1369 # - revset matchers often operate faster on revision in
1363 # changelog order, because most filters deal with the
1370 # changelog order, because most filters deal with the
1364 # changelog.
1371 # changelog.
1365 #
1372 #
1366 # - revset matchers can reorder revisions. "A or B" typically
1373 # - revset matchers can reorder revisions. "A or B" typically
1367 # returns returns the revision matching A then the revision
1374 # returns returns the revision matching A then the revision
1368 # matching B. We want to hide this internal implementation
1375 # matching B. We want to hide this internal implementation
1369 # detail from the caller, and sorting the filtered revision
1376 # detail from the caller, and sorting the filtered revision
1370 # again achieves this.
1377 # again achieves this.
1371 for i, window in increasingwindows(0, len(revs), windowsize=1):
1378 for i, window in increasingwindows(0, len(revs), windowsize=1):
1372 orevs = revs[i:i + window]
1379 orevs = revs[i:i + window]
1373 nrevs = set(matcher(repo, sorted(orevs)))
1380 nrevs = set(matcher(repo, sorted(orevs)))
1374 for rev in orevs:
1381 for rev in orevs:
1375 if rev in nrevs:
1382 if rev in nrevs:
1376 yield rev
1383 yield rev
1377
1384
1378 if not len(repo):
1385 if not len(repo):
1379 return iter([]), None, None
1386 return iter([]), None, None
1380 # Default --rev value depends on --follow but --follow behaviour
1387 # Default --rev value depends on --follow but --follow behaviour
1381 # depends on revisions resolved from --rev...
1388 # depends on revisions resolved from --rev...
1382 follow = opts.get('follow') or opts.get('follow_first')
1389 follow = opts.get('follow') or opts.get('follow_first')
1383 if opts.get('rev'):
1390 if opts.get('rev'):
1384 revs = scmutil.revrange(repo, opts['rev'])
1391 revs = scmutil.revrange(repo, opts['rev'])
1385 else:
1392 else:
1386 if follow and len(repo) > 0:
1393 if follow and len(repo) > 0:
1387 revs = scmutil.revrange(repo, ['.:0'])
1394 revs = scmutil.revrange(repo, ['.:0'])
1388 else:
1395 else:
1389 revs = range(len(repo) - 1, -1, -1)
1396 revs = range(len(repo) - 1, -1, -1)
1390 if not revs:
1397 if not revs:
1391 return iter([]), None, None
1398 return iter([]), None, None
1392 expr, filematcher = _makegraphlogrevset(repo, pats, opts, revs)
1399 expr, filematcher = _makegraphlogrevset(repo, pats, opts, revs)
1393 if expr:
1400 if expr:
1394 matcher = revset.match(repo.ui, expr)
1401 matcher = revset.match(repo.ui, expr)
1395 revs = increasingrevs(repo, revs, matcher)
1402 revs = increasingrevs(repo, revs, matcher)
1396 if not opts.get('hidden'):
1403 if not opts.get('hidden'):
1397 # --hidden is still experimental and not worth a dedicated revset
1404 # --hidden is still experimental and not worth a dedicated revset
1398 # yet. Fortunately, filtering revision number is fast.
1405 # yet. Fortunately, filtering revision number is fast.
1399 revs = (r for r in revs if r not in repo.hiddenrevs)
1406 revs = (r for r in revs if r not in repo.hiddenrevs)
1400 else:
1407 else:
1401 revs = iter(revs)
1408 revs = iter(revs)
1402 return revs, expr, filematcher
1409 return revs, expr, filematcher
1403
1410
1404 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1411 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1405 filematcher=None):
1412 filematcher=None):
1406 seen, state = [], graphmod.asciistate()
1413 seen, state = [], graphmod.asciistate()
1407 for rev, type, ctx, parents in dag:
1414 for rev, type, ctx, parents in dag:
1408 char = 'o'
1415 char = 'o'
1409 if ctx.node() in showparents:
1416 if ctx.node() in showparents:
1410 char = '@'
1417 char = '@'
1411 elif ctx.obsolete():
1418 elif ctx.obsolete():
1412 char = 'x'
1419 char = 'x'
1413 copies = None
1420 copies = None
1414 if getrenamed and ctx.rev():
1421 if getrenamed and ctx.rev():
1415 copies = []
1422 copies = []
1416 for fn in ctx.files():
1423 for fn in ctx.files():
1417 rename = getrenamed(fn, ctx.rev())
1424 rename = getrenamed(fn, ctx.rev())
1418 if rename:
1425 if rename:
1419 copies.append((fn, rename[0]))
1426 copies.append((fn, rename[0]))
1420 revmatchfn = None
1427 revmatchfn = None
1421 if filematcher is not None:
1428 if filematcher is not None:
1422 revmatchfn = filematcher(ctx.rev())
1429 revmatchfn = filematcher(ctx.rev())
1423 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1430 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1424 lines = displayer.hunk.pop(rev).split('\n')
1431 lines = displayer.hunk.pop(rev).split('\n')
1425 if not lines[-1]:
1432 if not lines[-1]:
1426 del lines[-1]
1433 del lines[-1]
1427 displayer.flush(rev)
1434 displayer.flush(rev)
1428 edges = edgefn(type, char, lines, seen, rev, parents)
1435 edges = edgefn(type, char, lines, seen, rev, parents)
1429 for type, char, lines, coldata in edges:
1436 for type, char, lines, coldata in edges:
1430 graphmod.ascii(ui, state, type, char, lines, coldata)
1437 graphmod.ascii(ui, state, type, char, lines, coldata)
1431 displayer.close()
1438 displayer.close()
1432
1439
1433 def graphlog(ui, repo, *pats, **opts):
1440 def graphlog(ui, repo, *pats, **opts):
1434 # Parameters are identical to log command ones
1441 # Parameters are identical to log command ones
1435 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1442 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1436 revs = sorted(revs, reverse=1)
1443 revs = sorted(revs, reverse=1)
1437 limit = loglimit(opts)
1444 limit = loglimit(opts)
1438 if limit is not None:
1445 if limit is not None:
1439 revs = revs[:limit]
1446 revs = revs[:limit]
1440 revdag = graphmod.dagwalker(repo, revs)
1447 revdag = graphmod.dagwalker(repo, revs)
1441
1448
1442 getrenamed = None
1449 getrenamed = None
1443 if opts.get('copies'):
1450 if opts.get('copies'):
1444 endrev = None
1451 endrev = None
1445 if opts.get('rev'):
1452 if opts.get('rev'):
1446 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
1453 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
1447 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1454 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1448 displayer = show_changeset(ui, repo, opts, buffered=True)
1455 displayer = show_changeset(ui, repo, opts, buffered=True)
1449 showparents = [ctx.node() for ctx in repo[None].parents()]
1456 showparents = [ctx.node() for ctx in repo[None].parents()]
1450 displaygraph(ui, revdag, displayer, showparents,
1457 displaygraph(ui, revdag, displayer, showparents,
1451 graphmod.asciiedges, getrenamed, filematcher)
1458 graphmod.asciiedges, getrenamed, filematcher)
1452
1459
1453 def checkunsupportedgraphflags(pats, opts):
1460 def checkunsupportedgraphflags(pats, opts):
1454 for op in ["newest_first"]:
1461 for op in ["newest_first"]:
1455 if op in opts and opts[op]:
1462 if op in opts and opts[op]:
1456 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1463 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1457 % op.replace("_", "-"))
1464 % op.replace("_", "-"))
1458
1465
1459 def graphrevs(repo, nodes, opts):
1466 def graphrevs(repo, nodes, opts):
1460 limit = loglimit(opts)
1467 limit = loglimit(opts)
1461 nodes.reverse()
1468 nodes.reverse()
1462 if limit is not None:
1469 if limit is not None:
1463 nodes = nodes[:limit]
1470 nodes = nodes[:limit]
1464 return graphmod.nodes(repo, nodes)
1471 return graphmod.nodes(repo, nodes)
1465
1472
1466 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1473 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1467 join = lambda f: os.path.join(prefix, f)
1474 join = lambda f: os.path.join(prefix, f)
1468 bad = []
1475 bad = []
1469 oldbad = match.bad
1476 oldbad = match.bad
1470 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1477 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1471 names = []
1478 names = []
1472 wctx = repo[None]
1479 wctx = repo[None]
1473 cca = None
1480 cca = None
1474 abort, warn = scmutil.checkportabilityalert(ui)
1481 abort, warn = scmutil.checkportabilityalert(ui)
1475 if abort or warn:
1482 if abort or warn:
1476 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1483 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1477 for f in repo.walk(match):
1484 for f in repo.walk(match):
1478 exact = match.exact(f)
1485 exact = match.exact(f)
1479 if exact or not explicitonly and f not in repo.dirstate:
1486 if exact or not explicitonly and f not in repo.dirstate:
1480 if cca:
1487 if cca:
1481 cca(f)
1488 cca(f)
1482 names.append(f)
1489 names.append(f)
1483 if ui.verbose or not exact:
1490 if ui.verbose or not exact:
1484 ui.status(_('adding %s\n') % match.rel(join(f)))
1491 ui.status(_('adding %s\n') % match.rel(join(f)))
1485
1492
1486 for subpath in wctx.substate:
1493 for subpath in wctx.substate:
1487 sub = wctx.sub(subpath)
1494 sub = wctx.sub(subpath)
1488 try:
1495 try:
1489 submatch = matchmod.narrowmatcher(subpath, match)
1496 submatch = matchmod.narrowmatcher(subpath, match)
1490 if listsubrepos:
1497 if listsubrepos:
1491 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1498 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1492 False))
1499 False))
1493 else:
1500 else:
1494 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1501 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1495 True))
1502 True))
1496 except error.LookupError:
1503 except error.LookupError:
1497 ui.status(_("skipping missing subrepository: %s\n")
1504 ui.status(_("skipping missing subrepository: %s\n")
1498 % join(subpath))
1505 % join(subpath))
1499
1506
1500 if not dryrun:
1507 if not dryrun:
1501 rejected = wctx.add(names, prefix)
1508 rejected = wctx.add(names, prefix)
1502 bad.extend(f for f in rejected if f in match.files())
1509 bad.extend(f for f in rejected if f in match.files())
1503 return bad
1510 return bad
1504
1511
1505 def forget(ui, repo, match, prefix, explicitonly):
1512 def forget(ui, repo, match, prefix, explicitonly):
1506 join = lambda f: os.path.join(prefix, f)
1513 join = lambda f: os.path.join(prefix, f)
1507 bad = []
1514 bad = []
1508 oldbad = match.bad
1515 oldbad = match.bad
1509 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1516 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1510 wctx = repo[None]
1517 wctx = repo[None]
1511 forgot = []
1518 forgot = []
1512 s = repo.status(match=match, clean=True)
1519 s = repo.status(match=match, clean=True)
1513 forget = sorted(s[0] + s[1] + s[3] + s[6])
1520 forget = sorted(s[0] + s[1] + s[3] + s[6])
1514 if explicitonly:
1521 if explicitonly:
1515 forget = [f for f in forget if match.exact(f)]
1522 forget = [f for f in forget if match.exact(f)]
1516
1523
1517 for subpath in wctx.substate:
1524 for subpath in wctx.substate:
1518 sub = wctx.sub(subpath)
1525 sub = wctx.sub(subpath)
1519 try:
1526 try:
1520 submatch = matchmod.narrowmatcher(subpath, match)
1527 submatch = matchmod.narrowmatcher(subpath, match)
1521 subbad, subforgot = sub.forget(ui, submatch, prefix)
1528 subbad, subforgot = sub.forget(ui, submatch, prefix)
1522 bad.extend([subpath + '/' + f for f in subbad])
1529 bad.extend([subpath + '/' + f for f in subbad])
1523 forgot.extend([subpath + '/' + f for f in subforgot])
1530 forgot.extend([subpath + '/' + f for f in subforgot])
1524 except error.LookupError:
1531 except error.LookupError:
1525 ui.status(_("skipping missing subrepository: %s\n")
1532 ui.status(_("skipping missing subrepository: %s\n")
1526 % join(subpath))
1533 % join(subpath))
1527
1534
1528 if not explicitonly:
1535 if not explicitonly:
1529 for f in match.files():
1536 for f in match.files():
1530 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1537 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1531 if f not in forgot:
1538 if f not in forgot:
1532 if os.path.exists(match.rel(join(f))):
1539 if os.path.exists(match.rel(join(f))):
1533 ui.warn(_('not removing %s: '
1540 ui.warn(_('not removing %s: '
1534 'file is already untracked\n')
1541 'file is already untracked\n')
1535 % match.rel(join(f)))
1542 % match.rel(join(f)))
1536 bad.append(f)
1543 bad.append(f)
1537
1544
1538 for f in forget:
1545 for f in forget:
1539 if ui.verbose or not match.exact(f):
1546 if ui.verbose or not match.exact(f):
1540 ui.status(_('removing %s\n') % match.rel(join(f)))
1547 ui.status(_('removing %s\n') % match.rel(join(f)))
1541
1548
1542 rejected = wctx.forget(forget, prefix)
1549 rejected = wctx.forget(forget, prefix)
1543 bad.extend(f for f in rejected if f in match.files())
1550 bad.extend(f for f in rejected if f in match.files())
1544 forgot.extend(forget)
1551 forgot.extend(forget)
1545 return bad, forgot
1552 return bad, forgot
1546
1553
1547 def duplicatecopies(repo, rev, p1):
1554 def duplicatecopies(repo, rev, p1):
1548 "Reproduce copies found in the source revision in the dirstate for grafts"
1555 "Reproduce copies found in the source revision in the dirstate for grafts"
1549 for dst, src in copies.pathcopies(repo[p1], repo[rev]).iteritems():
1556 for dst, src in copies.pathcopies(repo[p1], repo[rev]).iteritems():
1550 repo.dirstate.copy(src, dst)
1557 repo.dirstate.copy(src, dst)
1551
1558
1552 def commit(ui, repo, commitfunc, pats, opts):
1559 def commit(ui, repo, commitfunc, pats, opts):
1553 '''commit the specified files or all outstanding changes'''
1560 '''commit the specified files or all outstanding changes'''
1554 date = opts.get('date')
1561 date = opts.get('date')
1555 if date:
1562 if date:
1556 opts['date'] = util.parsedate(date)
1563 opts['date'] = util.parsedate(date)
1557 message = logmessage(ui, opts)
1564 message = logmessage(ui, opts)
1558
1565
1559 # extract addremove carefully -- this function can be called from a command
1566 # extract addremove carefully -- this function can be called from a command
1560 # that doesn't support addremove
1567 # that doesn't support addremove
1561 if opts.get('addremove'):
1568 if opts.get('addremove'):
1562 scmutil.addremove(repo, pats, opts)
1569 scmutil.addremove(repo, pats, opts)
1563
1570
1564 return commitfunc(ui, repo, message,
1571 return commitfunc(ui, repo, message,
1565 scmutil.match(repo[None], pats, opts), opts)
1572 scmutil.match(repo[None], pats, opts), opts)
1566
1573
1567 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1574 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1568 ui.note(_('amending changeset %s\n') % old)
1575 ui.note(_('amending changeset %s\n') % old)
1569 base = old.p1()
1576 base = old.p1()
1570
1577
1571 wlock = repo.wlock()
1578 wlock = repo.wlock()
1572 try:
1579 try:
1573 # First, do a regular commit to record all changes in the working
1580 # First, do a regular commit to record all changes in the working
1574 # directory (if there are any)
1581 # directory (if there are any)
1575 ui.callhooks = False
1582 ui.callhooks = False
1576 try:
1583 try:
1577 node = commit(ui, repo, commitfunc, pats, opts)
1584 node = commit(ui, repo, commitfunc, pats, opts)
1578 finally:
1585 finally:
1579 ui.callhooks = True
1586 ui.callhooks = True
1580 ctx = repo[node]
1587 ctx = repo[node]
1581
1588
1582 # Participating changesets:
1589 # Participating changesets:
1583 #
1590 #
1584 # node/ctx o - new (intermediate) commit that contains changes from
1591 # node/ctx o - new (intermediate) commit that contains changes from
1585 # | working dir to go into amending commit (or a workingctx
1592 # | working dir to go into amending commit (or a workingctx
1586 # | if there were no changes)
1593 # | if there were no changes)
1587 # |
1594 # |
1588 # old o - changeset to amend
1595 # old o - changeset to amend
1589 # |
1596 # |
1590 # base o - parent of amending changeset
1597 # base o - parent of amending changeset
1591
1598
1592 # Update extra dict from amended commit (e.g. to preserve graft source)
1599 # Update extra dict from amended commit (e.g. to preserve graft source)
1593 extra.update(old.extra())
1600 extra.update(old.extra())
1594
1601
1595 # Also update it from the intermediate commit or from the wctx
1602 # Also update it from the intermediate commit or from the wctx
1596 extra.update(ctx.extra())
1603 extra.update(ctx.extra())
1597
1604
1598 files = set(old.files())
1605 files = set(old.files())
1599
1606
1600 # Second, we use either the commit we just did, or if there were no
1607 # Second, we use either the commit we just did, or if there were no
1601 # changes the parent of the working directory as the version of the
1608 # changes the parent of the working directory as the version of the
1602 # files in the final amend commit
1609 # files in the final amend commit
1603 if node:
1610 if node:
1604 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
1611 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
1605
1612
1606 user = ctx.user()
1613 user = ctx.user()
1607 date = ctx.date()
1614 date = ctx.date()
1608 message = ctx.description()
1615 message = ctx.description()
1609 # Recompute copies (avoid recording a -> b -> a)
1616 # Recompute copies (avoid recording a -> b -> a)
1610 copied = copies.pathcopies(base, ctx)
1617 copied = copies.pathcopies(base, ctx)
1611
1618
1612 # Prune files which were reverted by the updates: if old introduced
1619 # Prune files which were reverted by the updates: if old introduced
1613 # file X and our intermediate commit, node, renamed that file, then
1620 # file X and our intermediate commit, node, renamed that file, then
1614 # those two files are the same and we can discard X from our list
1621 # those two files are the same and we can discard X from our list
1615 # of files. Likewise if X was deleted, it's no longer relevant
1622 # of files. Likewise if X was deleted, it's no longer relevant
1616 files.update(ctx.files())
1623 files.update(ctx.files())
1617
1624
1618 def samefile(f):
1625 def samefile(f):
1619 if f in ctx.manifest():
1626 if f in ctx.manifest():
1620 a = ctx.filectx(f)
1627 a = ctx.filectx(f)
1621 if f in base.manifest():
1628 if f in base.manifest():
1622 b = base.filectx(f)
1629 b = base.filectx(f)
1623 return (not a.cmp(b)
1630 return (not a.cmp(b)
1624 and a.flags() == b.flags())
1631 and a.flags() == b.flags())
1625 else:
1632 else:
1626 return False
1633 return False
1627 else:
1634 else:
1628 return f not in base.manifest()
1635 return f not in base.manifest()
1629 files = [f for f in files if not samefile(f)]
1636 files = [f for f in files if not samefile(f)]
1630
1637
1631 def filectxfn(repo, ctx_, path):
1638 def filectxfn(repo, ctx_, path):
1632 try:
1639 try:
1633 fctx = ctx[path]
1640 fctx = ctx[path]
1634 flags = fctx.flags()
1641 flags = fctx.flags()
1635 mctx = context.memfilectx(fctx.path(), fctx.data(),
1642 mctx = context.memfilectx(fctx.path(), fctx.data(),
1636 islink='l' in flags,
1643 islink='l' in flags,
1637 isexec='x' in flags,
1644 isexec='x' in flags,
1638 copied=copied.get(path))
1645 copied=copied.get(path))
1639 return mctx
1646 return mctx
1640 except KeyError:
1647 except KeyError:
1641 raise IOError
1648 raise IOError
1642 else:
1649 else:
1643 ui.note(_('copying changeset %s to %s\n') % (old, base))
1650 ui.note(_('copying changeset %s to %s\n') % (old, base))
1644
1651
1645 # Use version of files as in the old cset
1652 # Use version of files as in the old cset
1646 def filectxfn(repo, ctx_, path):
1653 def filectxfn(repo, ctx_, path):
1647 try:
1654 try:
1648 return old.filectx(path)
1655 return old.filectx(path)
1649 except KeyError:
1656 except KeyError:
1650 raise IOError
1657 raise IOError
1651
1658
1652 # See if we got a message from -m or -l, if not, open the editor
1659 # See if we got a message from -m or -l, if not, open the editor
1653 # with the message of the changeset to amend
1660 # with the message of the changeset to amend
1654 user = opts.get('user') or old.user()
1661 user = opts.get('user') or old.user()
1655 date = opts.get('date') or old.date()
1662 date = opts.get('date') or old.date()
1656 message = logmessage(ui, opts)
1663 message = logmessage(ui, opts)
1657 if not message:
1664 if not message:
1658 cctx = context.workingctx(repo, old.description(), user, date,
1665 cctx = context.workingctx(repo, old.description(), user, date,
1659 extra,
1666 extra,
1660 repo.status(base.node(), old.node()))
1667 repo.status(base.node(), old.node()))
1661 message = commitforceeditor(repo, cctx, [])
1668 message = commitforceeditor(repo, cctx, [])
1662
1669
1663 new = context.memctx(repo,
1670 new = context.memctx(repo,
1664 parents=[base.node(), nullid],
1671 parents=[base.node(), nullid],
1665 text=message,
1672 text=message,
1666 files=files,
1673 files=files,
1667 filectxfn=filectxfn,
1674 filectxfn=filectxfn,
1668 user=user,
1675 user=user,
1669 date=date,
1676 date=date,
1670 extra=extra)
1677 extra=extra)
1671 newid = repo.commitctx(new)
1678 newid = repo.commitctx(new)
1672 if newid != old.node():
1679 if newid != old.node():
1673 # Reroute the working copy parent to the new changeset
1680 # Reroute the working copy parent to the new changeset
1674 repo.setparents(newid, nullid)
1681 repo.setparents(newid, nullid)
1675
1682
1676 # Move bookmarks from old parent to amend commit
1683 # Move bookmarks from old parent to amend commit
1677 bms = repo.nodebookmarks(old.node())
1684 bms = repo.nodebookmarks(old.node())
1678 if bms:
1685 if bms:
1679 for bm in bms:
1686 for bm in bms:
1680 repo._bookmarks[bm] = newid
1687 repo._bookmarks[bm] = newid
1681 bookmarks.write(repo)
1688 bookmarks.write(repo)
1682
1689
1683 # Strip the intermediate commit (if there was one) and the amended
1690 # Strip the intermediate commit (if there was one) and the amended
1684 # commit
1691 # commit
1685 lock = repo.lock()
1692 lock = repo.lock()
1686 try:
1693 try:
1687 if node:
1694 if node:
1688 ui.note(_('stripping intermediate changeset %s\n') % ctx)
1695 ui.note(_('stripping intermediate changeset %s\n') % ctx)
1689 ui.note(_('stripping amended changeset %s\n') % old)
1696 ui.note(_('stripping amended changeset %s\n') % old)
1690 repair.strip(ui, repo, old.node(), topic='amend-backup')
1697 repair.strip(ui, repo, old.node(), topic='amend-backup')
1691 finally:
1698 finally:
1692 lock.release()
1699 lock.release()
1693 finally:
1700 finally:
1694 wlock.release()
1701 wlock.release()
1695 return newid
1702 return newid
1696
1703
1697 def commiteditor(repo, ctx, subs):
1704 def commiteditor(repo, ctx, subs):
1698 if ctx.description():
1705 if ctx.description():
1699 return ctx.description()
1706 return ctx.description()
1700 return commitforceeditor(repo, ctx, subs)
1707 return commitforceeditor(repo, ctx, subs)
1701
1708
1702 def commitforceeditor(repo, ctx, subs):
1709 def commitforceeditor(repo, ctx, subs):
1703 edittext = []
1710 edittext = []
1704 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1711 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1705 if ctx.description():
1712 if ctx.description():
1706 edittext.append(ctx.description())
1713 edittext.append(ctx.description())
1707 edittext.append("")
1714 edittext.append("")
1708 edittext.append("") # Empty line between message and comments.
1715 edittext.append("") # Empty line between message and comments.
1709 edittext.append(_("HG: Enter commit message."
1716 edittext.append(_("HG: Enter commit message."
1710 " Lines beginning with 'HG:' are removed."))
1717 " Lines beginning with 'HG:' are removed."))
1711 edittext.append(_("HG: Leave message empty to abort commit."))
1718 edittext.append(_("HG: Leave message empty to abort commit."))
1712 edittext.append("HG: --")
1719 edittext.append("HG: --")
1713 edittext.append(_("HG: user: %s") % ctx.user())
1720 edittext.append(_("HG: user: %s") % ctx.user())
1714 if ctx.p2():
1721 if ctx.p2():
1715 edittext.append(_("HG: branch merge"))
1722 edittext.append(_("HG: branch merge"))
1716 if ctx.branch():
1723 if ctx.branch():
1717 edittext.append(_("HG: branch '%s'") % ctx.branch())
1724 edittext.append(_("HG: branch '%s'") % ctx.branch())
1718 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1725 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1719 edittext.extend([_("HG: added %s") % f for f in added])
1726 edittext.extend([_("HG: added %s") % f for f in added])
1720 edittext.extend([_("HG: changed %s") % f for f in modified])
1727 edittext.extend([_("HG: changed %s") % f for f in modified])
1721 edittext.extend([_("HG: removed %s") % f for f in removed])
1728 edittext.extend([_("HG: removed %s") % f for f in removed])
1722 if not added and not modified and not removed:
1729 if not added and not modified and not removed:
1723 edittext.append(_("HG: no files changed"))
1730 edittext.append(_("HG: no files changed"))
1724 edittext.append("")
1731 edittext.append("")
1725 # run editor in the repository root
1732 # run editor in the repository root
1726 olddir = os.getcwd()
1733 olddir = os.getcwd()
1727 os.chdir(repo.root)
1734 os.chdir(repo.root)
1728 text = repo.ui.edit("\n".join(edittext), ctx.user())
1735 text = repo.ui.edit("\n".join(edittext), ctx.user())
1729 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1736 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1730 os.chdir(olddir)
1737 os.chdir(olddir)
1731
1738
1732 if not text.strip():
1739 if not text.strip():
1733 raise util.Abort(_("empty commit message"))
1740 raise util.Abort(_("empty commit message"))
1734
1741
1735 return text
1742 return text
1736
1743
1737 def revert(ui, repo, ctx, parents, *pats, **opts):
1744 def revert(ui, repo, ctx, parents, *pats, **opts):
1738 parent, p2 = parents
1745 parent, p2 = parents
1739 node = ctx.node()
1746 node = ctx.node()
1740
1747
1741 mf = ctx.manifest()
1748 mf = ctx.manifest()
1742 if node == parent:
1749 if node == parent:
1743 pmf = mf
1750 pmf = mf
1744 else:
1751 else:
1745 pmf = None
1752 pmf = None
1746
1753
1747 # need all matching names in dirstate and manifest of target rev,
1754 # need all matching names in dirstate and manifest of target rev,
1748 # so have to walk both. do not print errors if files exist in one
1755 # so have to walk both. do not print errors if files exist in one
1749 # but not other.
1756 # but not other.
1750
1757
1751 names = {}
1758 names = {}
1752
1759
1753 wlock = repo.wlock()
1760 wlock = repo.wlock()
1754 try:
1761 try:
1755 # walk dirstate.
1762 # walk dirstate.
1756
1763
1757 m = scmutil.match(repo[None], pats, opts)
1764 m = scmutil.match(repo[None], pats, opts)
1758 m.bad = lambda x, y: False
1765 m.bad = lambda x, y: False
1759 for abs in repo.walk(m):
1766 for abs in repo.walk(m):
1760 names[abs] = m.rel(abs), m.exact(abs)
1767 names[abs] = m.rel(abs), m.exact(abs)
1761
1768
1762 # walk target manifest.
1769 # walk target manifest.
1763
1770
1764 def badfn(path, msg):
1771 def badfn(path, msg):
1765 if path in names:
1772 if path in names:
1766 return
1773 return
1767 if path in ctx.substate:
1774 if path in ctx.substate:
1768 return
1775 return
1769 path_ = path + '/'
1776 path_ = path + '/'
1770 for f in names:
1777 for f in names:
1771 if f.startswith(path_):
1778 if f.startswith(path_):
1772 return
1779 return
1773 ui.warn("%s: %s\n" % (m.rel(path), msg))
1780 ui.warn("%s: %s\n" % (m.rel(path), msg))
1774
1781
1775 m = scmutil.match(ctx, pats, opts)
1782 m = scmutil.match(ctx, pats, opts)
1776 m.bad = badfn
1783 m.bad = badfn
1777 for abs in ctx.walk(m):
1784 for abs in ctx.walk(m):
1778 if abs not in names:
1785 if abs not in names:
1779 names[abs] = m.rel(abs), m.exact(abs)
1786 names[abs] = m.rel(abs), m.exact(abs)
1780
1787
1781 # get the list of subrepos that must be reverted
1788 # get the list of subrepos that must be reverted
1782 targetsubs = [s for s in ctx.substate if m(s)]
1789 targetsubs = [s for s in ctx.substate if m(s)]
1783 m = scmutil.matchfiles(repo, names)
1790 m = scmutil.matchfiles(repo, names)
1784 changes = repo.status(match=m)[:4]
1791 changes = repo.status(match=m)[:4]
1785 modified, added, removed, deleted = map(set, changes)
1792 modified, added, removed, deleted = map(set, changes)
1786
1793
1787 # if f is a rename, also revert the source
1794 # if f is a rename, also revert the source
1788 cwd = repo.getcwd()
1795 cwd = repo.getcwd()
1789 for f in added:
1796 for f in added:
1790 src = repo.dirstate.copied(f)
1797 src = repo.dirstate.copied(f)
1791 if src and src not in names and repo.dirstate[src] == 'r':
1798 if src and src not in names and repo.dirstate[src] == 'r':
1792 removed.add(src)
1799 removed.add(src)
1793 names[src] = (repo.pathto(src, cwd), True)
1800 names[src] = (repo.pathto(src, cwd), True)
1794
1801
1795 def removeforget(abs):
1802 def removeforget(abs):
1796 if repo.dirstate[abs] == 'a':
1803 if repo.dirstate[abs] == 'a':
1797 return _('forgetting %s\n')
1804 return _('forgetting %s\n')
1798 return _('removing %s\n')
1805 return _('removing %s\n')
1799
1806
1800 revert = ([], _('reverting %s\n'))
1807 revert = ([], _('reverting %s\n'))
1801 add = ([], _('adding %s\n'))
1808 add = ([], _('adding %s\n'))
1802 remove = ([], removeforget)
1809 remove = ([], removeforget)
1803 undelete = ([], _('undeleting %s\n'))
1810 undelete = ([], _('undeleting %s\n'))
1804
1811
1805 disptable = (
1812 disptable = (
1806 # dispatch table:
1813 # dispatch table:
1807 # file state
1814 # file state
1808 # action if in target manifest
1815 # action if in target manifest
1809 # action if not in target manifest
1816 # action if not in target manifest
1810 # make backup if in target manifest
1817 # make backup if in target manifest
1811 # make backup if not in target manifest
1818 # make backup if not in target manifest
1812 (modified, revert, remove, True, True),
1819 (modified, revert, remove, True, True),
1813 (added, revert, remove, True, False),
1820 (added, revert, remove, True, False),
1814 (removed, undelete, None, False, False),
1821 (removed, undelete, None, False, False),
1815 (deleted, revert, remove, False, False),
1822 (deleted, revert, remove, False, False),
1816 )
1823 )
1817
1824
1818 for abs, (rel, exact) in sorted(names.items()):
1825 for abs, (rel, exact) in sorted(names.items()):
1819 mfentry = mf.get(abs)
1826 mfentry = mf.get(abs)
1820 target = repo.wjoin(abs)
1827 target = repo.wjoin(abs)
1821 def handle(xlist, dobackup):
1828 def handle(xlist, dobackup):
1822 xlist[0].append(abs)
1829 xlist[0].append(abs)
1823 if (dobackup and not opts.get('no_backup') and
1830 if (dobackup and not opts.get('no_backup') and
1824 os.path.lexists(target)):
1831 os.path.lexists(target)):
1825 bakname = "%s.orig" % rel
1832 bakname = "%s.orig" % rel
1826 ui.note(_('saving current version of %s as %s\n') %
1833 ui.note(_('saving current version of %s as %s\n') %
1827 (rel, bakname))
1834 (rel, bakname))
1828 if not opts.get('dry_run'):
1835 if not opts.get('dry_run'):
1829 util.rename(target, bakname)
1836 util.rename(target, bakname)
1830 if ui.verbose or not exact:
1837 if ui.verbose or not exact:
1831 msg = xlist[1]
1838 msg = xlist[1]
1832 if not isinstance(msg, basestring):
1839 if not isinstance(msg, basestring):
1833 msg = msg(abs)
1840 msg = msg(abs)
1834 ui.status(msg % rel)
1841 ui.status(msg % rel)
1835 for table, hitlist, misslist, backuphit, backupmiss in disptable:
1842 for table, hitlist, misslist, backuphit, backupmiss in disptable:
1836 if abs not in table:
1843 if abs not in table:
1837 continue
1844 continue
1838 # file has changed in dirstate
1845 # file has changed in dirstate
1839 if mfentry:
1846 if mfentry:
1840 handle(hitlist, backuphit)
1847 handle(hitlist, backuphit)
1841 elif misslist is not None:
1848 elif misslist is not None:
1842 handle(misslist, backupmiss)
1849 handle(misslist, backupmiss)
1843 break
1850 break
1844 else:
1851 else:
1845 if abs not in repo.dirstate:
1852 if abs not in repo.dirstate:
1846 if mfentry:
1853 if mfentry:
1847 handle(add, True)
1854 handle(add, True)
1848 elif exact:
1855 elif exact:
1849 ui.warn(_('file not managed: %s\n') % rel)
1856 ui.warn(_('file not managed: %s\n') % rel)
1850 continue
1857 continue
1851 # file has not changed in dirstate
1858 # file has not changed in dirstate
1852 if node == parent:
1859 if node == parent:
1853 if exact:
1860 if exact:
1854 ui.warn(_('no changes needed to %s\n') % rel)
1861 ui.warn(_('no changes needed to %s\n') % rel)
1855 continue
1862 continue
1856 if pmf is None:
1863 if pmf is None:
1857 # only need parent manifest in this unlikely case,
1864 # only need parent manifest in this unlikely case,
1858 # so do not read by default
1865 # so do not read by default
1859 pmf = repo[parent].manifest()
1866 pmf = repo[parent].manifest()
1860 if abs in pmf and mfentry:
1867 if abs in pmf and mfentry:
1861 # if version of file is same in parent and target
1868 # if version of file is same in parent and target
1862 # manifests, do nothing
1869 # manifests, do nothing
1863 if (pmf[abs] != mfentry or
1870 if (pmf[abs] != mfentry or
1864 pmf.flags(abs) != mf.flags(abs)):
1871 pmf.flags(abs) != mf.flags(abs)):
1865 handle(revert, False)
1872 handle(revert, False)
1866 else:
1873 else:
1867 handle(remove, False)
1874 handle(remove, False)
1868
1875
1869 if not opts.get('dry_run'):
1876 if not opts.get('dry_run'):
1870 def checkout(f):
1877 def checkout(f):
1871 fc = ctx[f]
1878 fc = ctx[f]
1872 repo.wwrite(f, fc.data(), fc.flags())
1879 repo.wwrite(f, fc.data(), fc.flags())
1873
1880
1874 audit_path = scmutil.pathauditor(repo.root)
1881 audit_path = scmutil.pathauditor(repo.root)
1875 for f in remove[0]:
1882 for f in remove[0]:
1876 if repo.dirstate[f] == 'a':
1883 if repo.dirstate[f] == 'a':
1877 repo.dirstate.drop(f)
1884 repo.dirstate.drop(f)
1878 continue
1885 continue
1879 audit_path(f)
1886 audit_path(f)
1880 try:
1887 try:
1881 util.unlinkpath(repo.wjoin(f))
1888 util.unlinkpath(repo.wjoin(f))
1882 except OSError:
1889 except OSError:
1883 pass
1890 pass
1884 repo.dirstate.remove(f)
1891 repo.dirstate.remove(f)
1885
1892
1886 normal = None
1893 normal = None
1887 if node == parent:
1894 if node == parent:
1888 # We're reverting to our parent. If possible, we'd like status
1895 # We're reverting to our parent. If possible, we'd like status
1889 # to report the file as clean. We have to use normallookup for
1896 # to report the file as clean. We have to use normallookup for
1890 # merges to avoid losing information about merged/dirty files.
1897 # merges to avoid losing information about merged/dirty files.
1891 if p2 != nullid:
1898 if p2 != nullid:
1892 normal = repo.dirstate.normallookup
1899 normal = repo.dirstate.normallookup
1893 else:
1900 else:
1894 normal = repo.dirstate.normal
1901 normal = repo.dirstate.normal
1895 for f in revert[0]:
1902 for f in revert[0]:
1896 checkout(f)
1903 checkout(f)
1897 if normal:
1904 if normal:
1898 normal(f)
1905 normal(f)
1899
1906
1900 for f in add[0]:
1907 for f in add[0]:
1901 checkout(f)
1908 checkout(f)
1902 repo.dirstate.add(f)
1909 repo.dirstate.add(f)
1903
1910
1904 normal = repo.dirstate.normallookup
1911 normal = repo.dirstate.normallookup
1905 if node == parent and p2 == nullid:
1912 if node == parent and p2 == nullid:
1906 normal = repo.dirstate.normal
1913 normal = repo.dirstate.normal
1907 for f in undelete[0]:
1914 for f in undelete[0]:
1908 checkout(f)
1915 checkout(f)
1909 normal(f)
1916 normal(f)
1910
1917
1911 if targetsubs:
1918 if targetsubs:
1912 # Revert the subrepos on the revert list
1919 # Revert the subrepos on the revert list
1913 for sub in targetsubs:
1920 for sub in targetsubs:
1914 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
1921 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
1915 finally:
1922 finally:
1916 wlock.release()
1923 wlock.release()
1917
1924
1918 def command(table):
1925 def command(table):
1919 '''returns a function object bound to table which can be used as
1926 '''returns a function object bound to table which can be used as
1920 a decorator for populating table as a command table'''
1927 a decorator for populating table as a command table'''
1921
1928
1922 def cmd(name, options, synopsis=None):
1929 def cmd(name, options, synopsis=None):
1923 def decorator(func):
1930 def decorator(func):
1924 if synopsis:
1931 if synopsis:
1925 table[name] = func, options[:], synopsis
1932 table[name] = func, options[:], synopsis
1926 else:
1933 else:
1927 table[name] = func, options[:]
1934 table[name] = func, options[:]
1928 return func
1935 return func
1929 return decorator
1936 return decorator
1930
1937
1931 return cmd
1938 return cmd
@@ -1,147 +1,171 b''
1 $ hg init repo
1 $ hg init repo
2 $ cd repo
2 $ cd repo
3 $ touch foo
3 $ touch foo
4 $ hg add foo
4 $ hg add foo
5 $ for i in 0 1 2 3 4 5 6 7 8 9 10 11; do
5 $ for i in 0 1 2 3 4 5 6 7 8 9 10 11; do
6 > echo "foo-$i" >> foo
6 > echo "foo-$i" >> foo
7 > hg ci -m "foo-$i"
7 > hg ci -m "foo-$i"
8 > done
8 > done
9
9
10 $ for out in "%nof%N" "%%%H" "%b-%R" "%h" "%r" "%m"; do
10 $ for out in "%nof%N" "%%%H" "%b-%R" "%h" "%r" "%m"; do
11 > echo
11 > echo
12 > echo "# foo-$out.patch"
12 > echo "# foo-$out.patch"
13 > hg export -v -o "foo-$out.patch" 2:tip
13 > hg export -v -o "foo-$out.patch" 2:tip
14 > done
14 > done
15
15
16 # foo-%nof%N.patch
16 # foo-%nof%N.patch
17 exporting patches:
17 exporting patches:
18 foo-01of10.patch
18 foo-01of10.patch
19 foo-02of10.patch
19 foo-02of10.patch
20 foo-03of10.patch
20 foo-03of10.patch
21 foo-04of10.patch
21 foo-04of10.patch
22 foo-05of10.patch
22 foo-05of10.patch
23 foo-06of10.patch
23 foo-06of10.patch
24 foo-07of10.patch
24 foo-07of10.patch
25 foo-08of10.patch
25 foo-08of10.patch
26 foo-09of10.patch
26 foo-09of10.patch
27 foo-10of10.patch
27 foo-10of10.patch
28
28
29 # foo-%%%H.patch
29 # foo-%%%H.patch
30 exporting patches:
30 exporting patches:
31 foo-%617188a1c80f869a7b66c85134da88a6fb145f67.patch
31 foo-%617188a1c80f869a7b66c85134da88a6fb145f67.patch
32 foo-%dd41a5ff707a5225204105611ba49cc5c229d55f.patch
32 foo-%dd41a5ff707a5225204105611ba49cc5c229d55f.patch
33 foo-%f95a5410f8664b6e1490a4af654e4b7d41a7b321.patch
33 foo-%f95a5410f8664b6e1490a4af654e4b7d41a7b321.patch
34 foo-%4346bcfde53b4d9042489078bcfa9c3e28201db2.patch
34 foo-%4346bcfde53b4d9042489078bcfa9c3e28201db2.patch
35 foo-%afda8c3a009cc99449a05ad8aa4655648c4ecd34.patch
35 foo-%afda8c3a009cc99449a05ad8aa4655648c4ecd34.patch
36 foo-%35284ce2b6b99c9d2ac66268fe99e68e1974e1aa.patch
36 foo-%35284ce2b6b99c9d2ac66268fe99e68e1974e1aa.patch
37 foo-%9688c41894e6931305fa7165a37f6568050b4e9b.patch
37 foo-%9688c41894e6931305fa7165a37f6568050b4e9b.patch
38 foo-%747d3c68f8ec44bb35816bfcd59aeb50b9654c2f.patch
38 foo-%747d3c68f8ec44bb35816bfcd59aeb50b9654c2f.patch
39 foo-%5f17a83f5fbd9414006a5e563eab4c8a00729efd.patch
39 foo-%5f17a83f5fbd9414006a5e563eab4c8a00729efd.patch
40 foo-%f3acbafac161ec68f1598af38f794f28847ca5d3.patch
40 foo-%f3acbafac161ec68f1598af38f794f28847ca5d3.patch
41
41
42 # foo-%b-%R.patch
42 # foo-%b-%R.patch
43 exporting patches:
43 exporting patches:
44 foo-repo-2.patch
44 foo-repo-2.patch
45 foo-repo-3.patch
45 foo-repo-3.patch
46 foo-repo-4.patch
46 foo-repo-4.patch
47 foo-repo-5.patch
47 foo-repo-5.patch
48 foo-repo-6.patch
48 foo-repo-6.patch
49 foo-repo-7.patch
49 foo-repo-7.patch
50 foo-repo-8.patch
50 foo-repo-8.patch
51 foo-repo-9.patch
51 foo-repo-9.patch
52 foo-repo-10.patch
52 foo-repo-10.patch
53 foo-repo-11.patch
53 foo-repo-11.patch
54
54
55 # foo-%h.patch
55 # foo-%h.patch
56 exporting patches:
56 exporting patches:
57 foo-617188a1c80f.patch
57 foo-617188a1c80f.patch
58 foo-dd41a5ff707a.patch
58 foo-dd41a5ff707a.patch
59 foo-f95a5410f866.patch
59 foo-f95a5410f866.patch
60 foo-4346bcfde53b.patch
60 foo-4346bcfde53b.patch
61 foo-afda8c3a009c.patch
61 foo-afda8c3a009c.patch
62 foo-35284ce2b6b9.patch
62 foo-35284ce2b6b9.patch
63 foo-9688c41894e6.patch
63 foo-9688c41894e6.patch
64 foo-747d3c68f8ec.patch
64 foo-747d3c68f8ec.patch
65 foo-5f17a83f5fbd.patch
65 foo-5f17a83f5fbd.patch
66 foo-f3acbafac161.patch
66 foo-f3acbafac161.patch
67
67
68 # foo-%r.patch
68 # foo-%r.patch
69 exporting patches:
69 exporting patches:
70 foo-02.patch
70 foo-02.patch
71 foo-03.patch
71 foo-03.patch
72 foo-04.patch
72 foo-04.patch
73 foo-05.patch
73 foo-05.patch
74 foo-06.patch
74 foo-06.patch
75 foo-07.patch
75 foo-07.patch
76 foo-08.patch
76 foo-08.patch
77 foo-09.patch
77 foo-09.patch
78 foo-10.patch
78 foo-10.patch
79 foo-11.patch
79 foo-11.patch
80
80
81 # foo-%m.patch
81 # foo-%m.patch
82 exporting patches:
82 exporting patches:
83 foo-foo_2.patch
83 foo-foo_2.patch
84 foo-foo_3.patch
84 foo-foo_3.patch
85 foo-foo_4.patch
85 foo-foo_4.patch
86 foo-foo_5.patch
86 foo-foo_5.patch
87 foo-foo_6.patch
87 foo-foo_6.patch
88 foo-foo_7.patch
88 foo-foo_7.patch
89 foo-foo_8.patch
89 foo-foo_8.patch
90 foo-foo_9.patch
90 foo-foo_9.patch
91 foo-foo_10.patch
91 foo-foo_10.patch
92 foo-foo_11.patch
92 foo-foo_11.patch
93
93
94 Exporting 4 changesets to a file:
94 Exporting 4 changesets to a file:
95
95
96 $ hg export -o export_internal 1 2 3 4
96 $ hg export -o export_internal 1 2 3 4
97 $ grep HG export_internal | wc -l
97 $ grep HG export_internal | wc -l
98 \s*4 (re)
98 \s*4 (re)
99
99
100 Exporting 4 changesets to a file:
100 Exporting 4 changesets to a file:
101
101
102 $ hg export 1 2 3 4 | grep HG | wc -l
102 $ hg export 1 2 3 4 | grep HG | wc -l
103 \s*4 (re)
103 \s*4 (re)
104
104
105 Exporting revision -2 to a file:
105 Exporting revision -2 to a file:
106
106
107 $ hg export -- -2
107 $ hg export -- -2
108 # HG changeset patch
108 # HG changeset patch
109 # User test
109 # User test
110 # Date 0 0
110 # Date 0 0
111 # Node ID 5f17a83f5fbd9414006a5e563eab4c8a00729efd
111 # Node ID 5f17a83f5fbd9414006a5e563eab4c8a00729efd
112 # Parent 747d3c68f8ec44bb35816bfcd59aeb50b9654c2f
112 # Parent 747d3c68f8ec44bb35816bfcd59aeb50b9654c2f
113 foo-10
113 foo-10
114
114
115 diff -r 747d3c68f8ec -r 5f17a83f5fbd foo
115 diff -r 747d3c68f8ec -r 5f17a83f5fbd foo
116 --- a/foo Thu Jan 01 00:00:00 1970 +0000
116 --- a/foo Thu Jan 01 00:00:00 1970 +0000
117 +++ b/foo Thu Jan 01 00:00:00 1970 +0000
117 +++ b/foo Thu Jan 01 00:00:00 1970 +0000
118 @@ -8,3 +8,4 @@
118 @@ -8,3 +8,4 @@
119 foo-7
119 foo-7
120 foo-8
120 foo-8
121 foo-9
121 foo-9
122 +foo-10
122 +foo-10
123
123
124 Checking if only alphanumeric characters are used in the file name (%m option):
124 Checking if only alphanumeric characters are used in the file name (%m option):
125
125
126 $ echo "line" >> foo
126 $ echo "line" >> foo
127 $ hg commit -m " !\"#$%&(,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\`abcdefghijklmnopqrstuvwxyz{|}~"
127 $ hg commit -m " !\"#$%&(,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\`abcdefghijklmnopqrstuvwxyz{|}~"
128 $ hg export -v -o %m.patch tip
128 $ hg export -v -o %m.patch tip
129 exporting patch:
129 exporting patch:
130 ____________0123456789_______ABCDEFGHIJKLMNOPQRSTUVWXYZ______abcdefghijklmnopqrstuvwxyz____.patch
130 ____________0123456789_______ABCDEFGHIJKLMNOPQRSTUVWXYZ______abcdefghijklmnopqrstuvwxyz____.patch
131
131
132 Catch exporting unknown revisions (especially empty revsets, see issue3353)
132 Catch exporting unknown revisions (especially empty revsets, see issue3353)
133
133
134 $ hg export
134 $ hg export
135 abort: export requires at least one changeset
135 abort: export requires at least one changeset
136 [255]
136 [255]
137 $ hg export ""
137 $ hg export ""
138 hg: parse error: empty query
138 hg: parse error: empty query
139 [255]
139 [255]
140 $ hg export 999
140 $ hg export 999
141 abort: unknown revision '999'!
141 abort: unknown revision '999'!
142 [255]
142 [255]
143 $ hg export "not all()"
143 $ hg export "not all()"
144 abort: export requires at least one changeset
144 abort: export requires at least one changeset
145 [255]
145 [255]
146
146
147 Check for color output
148 $ echo "[color]" >> $HGRCPATH
149 $ echo "mode = ansi" >> $HGRCPATH
150 $ echo "[extensions]" >> $HGRCPATH
151 $ echo "color=" >> $HGRCPATH
152
153 $ hg export --color always --nodates tip
154 # HG changeset patch
155 # User test
156 # Date 0 0
157 # Node ID * (glob)
158 # Parent * (glob)
159 !"#$%&(,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
160
161 \x1b[0;1mdiff -r f3acbafac161 -r 197ecd81a57f foo\x1b[0m (esc)
162 \x1b[0;31;1m--- a/foo\x1b[0m (esc)
163 \x1b[0;32;1m+++ b/foo\x1b[0m (esc)
164 \x1b[0;35m@@ -10,3 +10,4 @@\x1b[0m (esc)
165 foo-9
166 foo-10
167 foo-11
168 \x1b[0;32m+line\x1b[0m (esc)
169
170
147 $ cd ..
171 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now