##// END OF EJS Templates
cmdutil: use context instead of lookup
Matt Mackall -
r16380:84ba30e8 default
parent child Browse files
Show More
@@ -1,1518 +1,1518 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
13 import subrepo
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 reltarget = repo.pathto(abstarget, cwd)
271 reltarget = repo.pathto(abstarget, cwd)
272 target = repo.wjoin(abstarget)
272 target = repo.wjoin(abstarget)
273 src = repo.wjoin(abssrc)
273 src = repo.wjoin(abssrc)
274 state = repo.dirstate[abstarget]
274 state = repo.dirstate[abstarget]
275
275
276 scmutil.checkportable(ui, abstarget)
276 scmutil.checkportable(ui, abstarget)
277
277
278 # check for collisions
278 # check for collisions
279 prevsrc = targets.get(abstarget)
279 prevsrc = targets.get(abstarget)
280 if prevsrc is not None:
280 if prevsrc is not None:
281 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
281 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
282 (reltarget, repo.pathto(abssrc, cwd),
282 (reltarget, repo.pathto(abssrc, cwd),
283 repo.pathto(prevsrc, cwd)))
283 repo.pathto(prevsrc, cwd)))
284 return
284 return
285
285
286 # check for overwrites
286 # check for overwrites
287 exists = os.path.lexists(target)
287 exists = os.path.lexists(target)
288 samefile = False
288 samefile = False
289 if exists and abssrc != abstarget:
289 if exists and abssrc != abstarget:
290 if (repo.dirstate.normalize(abssrc) ==
290 if (repo.dirstate.normalize(abssrc) ==
291 repo.dirstate.normalize(abstarget)):
291 repo.dirstate.normalize(abstarget)):
292 if not rename:
292 if not rename:
293 ui.warn(_("%s: can't copy - same file\n") % reltarget)
293 ui.warn(_("%s: can't copy - same file\n") % reltarget)
294 return
294 return
295 exists = False
295 exists = False
296 samefile = True
296 samefile = True
297
297
298 if not after and exists or after and state in 'mn':
298 if not after and exists or after and state in 'mn':
299 if not opts['force']:
299 if not opts['force']:
300 ui.warn(_('%s: not overwriting - file exists\n') %
300 ui.warn(_('%s: not overwriting - file exists\n') %
301 reltarget)
301 reltarget)
302 return
302 return
303
303
304 if after:
304 if after:
305 if not exists:
305 if not exists:
306 if rename:
306 if rename:
307 ui.warn(_('%s: not recording move - %s does not exist\n') %
307 ui.warn(_('%s: not recording move - %s does not exist\n') %
308 (relsrc, reltarget))
308 (relsrc, reltarget))
309 else:
309 else:
310 ui.warn(_('%s: not recording copy - %s does not exist\n') %
310 ui.warn(_('%s: not recording copy - %s does not exist\n') %
311 (relsrc, reltarget))
311 (relsrc, reltarget))
312 return
312 return
313 elif not dryrun:
313 elif not dryrun:
314 try:
314 try:
315 if exists:
315 if exists:
316 os.unlink(target)
316 os.unlink(target)
317 targetdir = os.path.dirname(target) or '.'
317 targetdir = os.path.dirname(target) or '.'
318 if not os.path.isdir(targetdir):
318 if not os.path.isdir(targetdir):
319 os.makedirs(targetdir)
319 os.makedirs(targetdir)
320 if samefile:
320 if samefile:
321 tmp = target + "~hgrename"
321 tmp = target + "~hgrename"
322 os.rename(src, tmp)
322 os.rename(src, tmp)
323 os.rename(tmp, target)
323 os.rename(tmp, target)
324 else:
324 else:
325 util.copyfile(src, target)
325 util.copyfile(src, target)
326 srcexists = True
326 srcexists = True
327 except IOError, inst:
327 except IOError, inst:
328 if inst.errno == errno.ENOENT:
328 if inst.errno == errno.ENOENT:
329 ui.warn(_('%s: deleted in working copy\n') % relsrc)
329 ui.warn(_('%s: deleted in working copy\n') % relsrc)
330 srcexists = False
330 srcexists = False
331 else:
331 else:
332 ui.warn(_('%s: cannot copy - %s\n') %
332 ui.warn(_('%s: cannot copy - %s\n') %
333 (relsrc, inst.strerror))
333 (relsrc, inst.strerror))
334 return True # report a failure
334 return True # report a failure
335
335
336 if ui.verbose or not exact:
336 if ui.verbose or not exact:
337 if rename:
337 if rename:
338 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
338 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
339 else:
339 else:
340 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
340 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
341
341
342 targets[abstarget] = abssrc
342 targets[abstarget] = abssrc
343
343
344 # fix up dirstate
344 # fix up dirstate
345 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
345 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
346 dryrun=dryrun, cwd=cwd)
346 dryrun=dryrun, cwd=cwd)
347 if rename and not dryrun:
347 if rename and not dryrun:
348 if not after and srcexists and not samefile:
348 if not after and srcexists and not samefile:
349 util.unlinkpath(repo.wjoin(abssrc))
349 util.unlinkpath(repo.wjoin(abssrc))
350 wctx.forget([abssrc])
350 wctx.forget([abssrc])
351
351
352 # pat: ossep
352 # pat: ossep
353 # dest ossep
353 # dest ossep
354 # srcs: list of (hgsep, hgsep, ossep, bool)
354 # srcs: list of (hgsep, hgsep, ossep, bool)
355 # return: function that takes hgsep and returns ossep
355 # return: function that takes hgsep and returns ossep
356 def targetpathfn(pat, dest, srcs):
356 def targetpathfn(pat, dest, srcs):
357 if os.path.isdir(pat):
357 if os.path.isdir(pat):
358 abspfx = scmutil.canonpath(repo.root, cwd, pat)
358 abspfx = scmutil.canonpath(repo.root, cwd, pat)
359 abspfx = util.localpath(abspfx)
359 abspfx = util.localpath(abspfx)
360 if destdirexists:
360 if destdirexists:
361 striplen = len(os.path.split(abspfx)[0])
361 striplen = len(os.path.split(abspfx)[0])
362 else:
362 else:
363 striplen = len(abspfx)
363 striplen = len(abspfx)
364 if striplen:
364 if striplen:
365 striplen += len(os.sep)
365 striplen += len(os.sep)
366 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
366 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
367 elif destdirexists:
367 elif destdirexists:
368 res = lambda p: os.path.join(dest,
368 res = lambda p: os.path.join(dest,
369 os.path.basename(util.localpath(p)))
369 os.path.basename(util.localpath(p)))
370 else:
370 else:
371 res = lambda p: dest
371 res = lambda p: dest
372 return res
372 return res
373
373
374 # pat: ossep
374 # pat: ossep
375 # dest ossep
375 # dest ossep
376 # srcs: list of (hgsep, hgsep, ossep, bool)
376 # srcs: list of (hgsep, hgsep, ossep, bool)
377 # return: function that takes hgsep and returns ossep
377 # return: function that takes hgsep and returns ossep
378 def targetpathafterfn(pat, dest, srcs):
378 def targetpathafterfn(pat, dest, srcs):
379 if matchmod.patkind(pat):
379 if matchmod.patkind(pat):
380 # a mercurial pattern
380 # a mercurial pattern
381 res = lambda p: os.path.join(dest,
381 res = lambda p: os.path.join(dest,
382 os.path.basename(util.localpath(p)))
382 os.path.basename(util.localpath(p)))
383 else:
383 else:
384 abspfx = scmutil.canonpath(repo.root, cwd, pat)
384 abspfx = scmutil.canonpath(repo.root, cwd, pat)
385 if len(abspfx) < len(srcs[0][0]):
385 if len(abspfx) < len(srcs[0][0]):
386 # A directory. Either the target path contains the last
386 # A directory. Either the target path contains the last
387 # component of the source path or it does not.
387 # component of the source path or it does not.
388 def evalpath(striplen):
388 def evalpath(striplen):
389 score = 0
389 score = 0
390 for s in srcs:
390 for s in srcs:
391 t = os.path.join(dest, util.localpath(s[0])[striplen:])
391 t = os.path.join(dest, util.localpath(s[0])[striplen:])
392 if os.path.lexists(t):
392 if os.path.lexists(t):
393 score += 1
393 score += 1
394 return score
394 return score
395
395
396 abspfx = util.localpath(abspfx)
396 abspfx = util.localpath(abspfx)
397 striplen = len(abspfx)
397 striplen = len(abspfx)
398 if striplen:
398 if striplen:
399 striplen += len(os.sep)
399 striplen += len(os.sep)
400 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
400 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
401 score = evalpath(striplen)
401 score = evalpath(striplen)
402 striplen1 = len(os.path.split(abspfx)[0])
402 striplen1 = len(os.path.split(abspfx)[0])
403 if striplen1:
403 if striplen1:
404 striplen1 += len(os.sep)
404 striplen1 += len(os.sep)
405 if evalpath(striplen1) > score:
405 if evalpath(striplen1) > score:
406 striplen = striplen1
406 striplen = striplen1
407 res = lambda p: os.path.join(dest,
407 res = lambda p: os.path.join(dest,
408 util.localpath(p)[striplen:])
408 util.localpath(p)[striplen:])
409 else:
409 else:
410 # a file
410 # a file
411 if destdirexists:
411 if destdirexists:
412 res = lambda p: os.path.join(dest,
412 res = lambda p: os.path.join(dest,
413 os.path.basename(util.localpath(p)))
413 os.path.basename(util.localpath(p)))
414 else:
414 else:
415 res = lambda p: dest
415 res = lambda p: dest
416 return res
416 return res
417
417
418
418
419 pats = scmutil.expandpats(pats)
419 pats = scmutil.expandpats(pats)
420 if not pats:
420 if not pats:
421 raise util.Abort(_('no source or destination specified'))
421 raise util.Abort(_('no source or destination specified'))
422 if len(pats) == 1:
422 if len(pats) == 1:
423 raise util.Abort(_('no destination specified'))
423 raise util.Abort(_('no destination specified'))
424 dest = pats.pop()
424 dest = pats.pop()
425 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
425 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
426 if not destdirexists:
426 if not destdirexists:
427 if len(pats) > 1 or matchmod.patkind(pats[0]):
427 if len(pats) > 1 or matchmod.patkind(pats[0]):
428 raise util.Abort(_('with multiple sources, destination must be an '
428 raise util.Abort(_('with multiple sources, destination must be an '
429 'existing directory'))
429 'existing directory'))
430 if util.endswithsep(dest):
430 if util.endswithsep(dest):
431 raise util.Abort(_('destination %s is not a directory') % dest)
431 raise util.Abort(_('destination %s is not a directory') % dest)
432
432
433 tfn = targetpathfn
433 tfn = targetpathfn
434 if after:
434 if after:
435 tfn = targetpathafterfn
435 tfn = targetpathafterfn
436 copylist = []
436 copylist = []
437 for pat in pats:
437 for pat in pats:
438 srcs = walkpat(pat)
438 srcs = walkpat(pat)
439 if not srcs:
439 if not srcs:
440 continue
440 continue
441 copylist.append((tfn(pat, dest, srcs), srcs))
441 copylist.append((tfn(pat, dest, srcs), srcs))
442 if not copylist:
442 if not copylist:
443 raise util.Abort(_('no files to copy'))
443 raise util.Abort(_('no files to copy'))
444
444
445 errors = 0
445 errors = 0
446 for targetpath, srcs in copylist:
446 for targetpath, srcs in copylist:
447 for abssrc, relsrc, exact in srcs:
447 for abssrc, relsrc, exact in srcs:
448 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
448 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
449 errors += 1
449 errors += 1
450
450
451 if errors:
451 if errors:
452 ui.warn(_('(consider using --after)\n'))
452 ui.warn(_('(consider using --after)\n'))
453
453
454 return errors != 0
454 return errors != 0
455
455
456 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
456 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
457 runargs=None, appendpid=False):
457 runargs=None, appendpid=False):
458 '''Run a command as a service.'''
458 '''Run a command as a service.'''
459
459
460 if opts['daemon'] and not opts['daemon_pipefds']:
460 if opts['daemon'] and not opts['daemon_pipefds']:
461 # Signal child process startup with file removal
461 # Signal child process startup with file removal
462 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
462 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
463 os.close(lockfd)
463 os.close(lockfd)
464 try:
464 try:
465 if not runargs:
465 if not runargs:
466 runargs = util.hgcmd() + sys.argv[1:]
466 runargs = util.hgcmd() + sys.argv[1:]
467 runargs.append('--daemon-pipefds=%s' % lockpath)
467 runargs.append('--daemon-pipefds=%s' % lockpath)
468 # Don't pass --cwd to the child process, because we've already
468 # Don't pass --cwd to the child process, because we've already
469 # changed directory.
469 # changed directory.
470 for i in xrange(1, len(runargs)):
470 for i in xrange(1, len(runargs)):
471 if runargs[i].startswith('--cwd='):
471 if runargs[i].startswith('--cwd='):
472 del runargs[i]
472 del runargs[i]
473 break
473 break
474 elif runargs[i].startswith('--cwd'):
474 elif runargs[i].startswith('--cwd'):
475 del runargs[i:i + 2]
475 del runargs[i:i + 2]
476 break
476 break
477 def condfn():
477 def condfn():
478 return not os.path.exists(lockpath)
478 return not os.path.exists(lockpath)
479 pid = util.rundetached(runargs, condfn)
479 pid = util.rundetached(runargs, condfn)
480 if pid < 0:
480 if pid < 0:
481 raise util.Abort(_('child process failed to start'))
481 raise util.Abort(_('child process failed to start'))
482 finally:
482 finally:
483 try:
483 try:
484 os.unlink(lockpath)
484 os.unlink(lockpath)
485 except OSError, e:
485 except OSError, e:
486 if e.errno != errno.ENOENT:
486 if e.errno != errno.ENOENT:
487 raise
487 raise
488 if parentfn:
488 if parentfn:
489 return parentfn(pid)
489 return parentfn(pid)
490 else:
490 else:
491 return
491 return
492
492
493 if initfn:
493 if initfn:
494 initfn()
494 initfn()
495
495
496 if opts['pid_file']:
496 if opts['pid_file']:
497 mode = appendpid and 'a' or 'w'
497 mode = appendpid and 'a' or 'w'
498 fp = open(opts['pid_file'], mode)
498 fp = open(opts['pid_file'], mode)
499 fp.write(str(os.getpid()) + '\n')
499 fp.write(str(os.getpid()) + '\n')
500 fp.close()
500 fp.close()
501
501
502 if opts['daemon_pipefds']:
502 if opts['daemon_pipefds']:
503 lockpath = opts['daemon_pipefds']
503 lockpath = opts['daemon_pipefds']
504 try:
504 try:
505 os.setsid()
505 os.setsid()
506 except AttributeError:
506 except AttributeError:
507 pass
507 pass
508 os.unlink(lockpath)
508 os.unlink(lockpath)
509 util.hidewindow()
509 util.hidewindow()
510 sys.stdout.flush()
510 sys.stdout.flush()
511 sys.stderr.flush()
511 sys.stderr.flush()
512
512
513 nullfd = os.open(util.nulldev, os.O_RDWR)
513 nullfd = os.open(util.nulldev, os.O_RDWR)
514 logfilefd = nullfd
514 logfilefd = nullfd
515 if logfile:
515 if logfile:
516 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
516 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
517 os.dup2(nullfd, 0)
517 os.dup2(nullfd, 0)
518 os.dup2(logfilefd, 1)
518 os.dup2(logfilefd, 1)
519 os.dup2(logfilefd, 2)
519 os.dup2(logfilefd, 2)
520 if nullfd not in (0, 1, 2):
520 if nullfd not in (0, 1, 2):
521 os.close(nullfd)
521 os.close(nullfd)
522 if logfile and logfilefd not in (0, 1, 2):
522 if logfile and logfilefd not in (0, 1, 2):
523 os.close(logfilefd)
523 os.close(logfilefd)
524
524
525 if runfn:
525 if runfn:
526 return runfn()
526 return runfn()
527
527
528 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
528 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
529 opts=None):
529 opts=None):
530 '''export changesets as hg patches.'''
530 '''export changesets as hg patches.'''
531
531
532 total = len(revs)
532 total = len(revs)
533 revwidth = max([len(str(rev)) for rev in revs])
533 revwidth = max([len(str(rev)) for rev in revs])
534
534
535 def single(rev, seqno, fp):
535 def single(rev, seqno, fp):
536 ctx = repo[rev]
536 ctx = repo[rev]
537 node = ctx.node()
537 node = ctx.node()
538 parents = [p.node() for p in ctx.parents() if p]
538 parents = [p.node() for p in ctx.parents() if p]
539 branch = ctx.branch()
539 branch = ctx.branch()
540 if switch_parent:
540 if switch_parent:
541 parents.reverse()
541 parents.reverse()
542 prev = (parents and parents[0]) or nullid
542 prev = (parents and parents[0]) or nullid
543
543
544 shouldclose = False
544 shouldclose = False
545 if not fp:
545 if not fp:
546 desc_lines = ctx.description().rstrip().split('\n')
546 desc_lines = ctx.description().rstrip().split('\n')
547 desc = desc_lines[0] #Commit always has a first line.
547 desc = desc_lines[0] #Commit always has a first line.
548 fp = makefileobj(repo, template, node, desc=desc, total=total,
548 fp = makefileobj(repo, template, node, desc=desc, total=total,
549 seqno=seqno, revwidth=revwidth, mode='ab')
549 seqno=seqno, revwidth=revwidth, mode='ab')
550 if fp != template:
550 if fp != template:
551 shouldclose = True
551 shouldclose = True
552 if fp != sys.stdout and util.safehasattr(fp, 'name'):
552 if fp != sys.stdout and util.safehasattr(fp, 'name'):
553 repo.ui.note("%s\n" % fp.name)
553 repo.ui.note("%s\n" % fp.name)
554
554
555 fp.write("# HG changeset patch\n")
555 fp.write("# HG changeset patch\n")
556 fp.write("# User %s\n" % ctx.user())
556 fp.write("# User %s\n" % ctx.user())
557 fp.write("# Date %d %d\n" % ctx.date())
557 fp.write("# Date %d %d\n" % ctx.date())
558 if branch and branch != 'default':
558 if branch and branch != 'default':
559 fp.write("# Branch %s\n" % branch)
559 fp.write("# Branch %s\n" % branch)
560 fp.write("# Node ID %s\n" % hex(node))
560 fp.write("# Node ID %s\n" % hex(node))
561 fp.write("# Parent %s\n" % hex(prev))
561 fp.write("# Parent %s\n" % hex(prev))
562 if len(parents) > 1:
562 if len(parents) > 1:
563 fp.write("# Parent %s\n" % hex(parents[1]))
563 fp.write("# Parent %s\n" % hex(parents[1]))
564 fp.write(ctx.description().rstrip())
564 fp.write(ctx.description().rstrip())
565 fp.write("\n\n")
565 fp.write("\n\n")
566
566
567 for chunk in patch.diff(repo, prev, node, opts=opts):
567 for chunk in patch.diff(repo, prev, node, opts=opts):
568 fp.write(chunk)
568 fp.write(chunk)
569
569
570 if shouldclose:
570 if shouldclose:
571 fp.close()
571 fp.close()
572
572
573 for seqno, rev in enumerate(revs):
573 for seqno, rev in enumerate(revs):
574 single(rev, seqno + 1, fp)
574 single(rev, seqno + 1, fp)
575
575
576 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
576 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
577 changes=None, stat=False, fp=None, prefix='',
577 changes=None, stat=False, fp=None, prefix='',
578 listsubrepos=False):
578 listsubrepos=False):
579 '''show diff or diffstat.'''
579 '''show diff or diffstat.'''
580 if fp is None:
580 if fp is None:
581 write = ui.write
581 write = ui.write
582 else:
582 else:
583 def write(s, **kw):
583 def write(s, **kw):
584 fp.write(s)
584 fp.write(s)
585
585
586 if stat:
586 if stat:
587 diffopts = diffopts.copy(context=0)
587 diffopts = diffopts.copy(context=0)
588 width = 80
588 width = 80
589 if not ui.plain():
589 if not ui.plain():
590 width = ui.termwidth()
590 width = ui.termwidth()
591 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
591 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
592 prefix=prefix)
592 prefix=prefix)
593 for chunk, label in patch.diffstatui(util.iterlines(chunks),
593 for chunk, label in patch.diffstatui(util.iterlines(chunks),
594 width=width,
594 width=width,
595 git=diffopts.git):
595 git=diffopts.git):
596 write(chunk, label=label)
596 write(chunk, label=label)
597 else:
597 else:
598 for chunk, label in patch.diffui(repo, node1, node2, match,
598 for chunk, label in patch.diffui(repo, node1, node2, match,
599 changes, diffopts, prefix=prefix):
599 changes, diffopts, prefix=prefix):
600 write(chunk, label=label)
600 write(chunk, label=label)
601
601
602 if listsubrepos:
602 if listsubrepos:
603 ctx1 = repo[node1]
603 ctx1 = repo[node1]
604 ctx2 = repo[node2]
604 ctx2 = repo[node2]
605 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
605 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
606 tempnode2 = node2
606 tempnode2 = node2
607 try:
607 try:
608 if node2 is not None:
608 if node2 is not None:
609 tempnode2 = ctx2.substate[subpath][1]
609 tempnode2 = ctx2.substate[subpath][1]
610 except KeyError:
610 except KeyError:
611 # A subrepo that existed in node1 was deleted between node1 and
611 # A subrepo that existed in node1 was deleted between node1 and
612 # node2 (inclusive). Thus, ctx2's substate won't contain that
612 # node2 (inclusive). Thus, ctx2's substate won't contain that
613 # subpath. The best we can do is to ignore it.
613 # subpath. The best we can do is to ignore it.
614 tempnode2 = None
614 tempnode2 = None
615 submatch = matchmod.narrowmatcher(subpath, match)
615 submatch = matchmod.narrowmatcher(subpath, match)
616 sub.diff(diffopts, tempnode2, submatch, changes=changes,
616 sub.diff(diffopts, tempnode2, submatch, changes=changes,
617 stat=stat, fp=fp, prefix=prefix)
617 stat=stat, fp=fp, prefix=prefix)
618
618
619 class changeset_printer(object):
619 class changeset_printer(object):
620 '''show changeset information when templating not requested.'''
620 '''show changeset information when templating not requested.'''
621
621
622 def __init__(self, ui, repo, patch, diffopts, buffered):
622 def __init__(self, ui, repo, patch, diffopts, buffered):
623 self.ui = ui
623 self.ui = ui
624 self.repo = repo
624 self.repo = repo
625 self.buffered = buffered
625 self.buffered = buffered
626 self.patch = patch
626 self.patch = patch
627 self.diffopts = diffopts
627 self.diffopts = diffopts
628 self.header = {}
628 self.header = {}
629 self.hunk = {}
629 self.hunk = {}
630 self.lastheader = None
630 self.lastheader = None
631 self.footer = None
631 self.footer = None
632
632
633 def flush(self, rev):
633 def flush(self, rev):
634 if rev in self.header:
634 if rev in self.header:
635 h = self.header[rev]
635 h = self.header[rev]
636 if h != self.lastheader:
636 if h != self.lastheader:
637 self.lastheader = h
637 self.lastheader = h
638 self.ui.write(h)
638 self.ui.write(h)
639 del self.header[rev]
639 del self.header[rev]
640 if rev in self.hunk:
640 if rev in self.hunk:
641 self.ui.write(self.hunk[rev])
641 self.ui.write(self.hunk[rev])
642 del self.hunk[rev]
642 del self.hunk[rev]
643 return 1
643 return 1
644 return 0
644 return 0
645
645
646 def close(self):
646 def close(self):
647 if self.footer:
647 if self.footer:
648 self.ui.write(self.footer)
648 self.ui.write(self.footer)
649
649
650 def show(self, ctx, copies=None, matchfn=None, **props):
650 def show(self, ctx, copies=None, matchfn=None, **props):
651 if self.buffered:
651 if self.buffered:
652 self.ui.pushbuffer()
652 self.ui.pushbuffer()
653 self._show(ctx, copies, matchfn, props)
653 self._show(ctx, copies, matchfn, props)
654 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
654 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
655 else:
655 else:
656 self._show(ctx, copies, matchfn, props)
656 self._show(ctx, copies, matchfn, props)
657
657
658 def _show(self, ctx, copies, matchfn, props):
658 def _show(self, ctx, copies, matchfn, props):
659 '''show a single changeset or file revision'''
659 '''show a single changeset or file revision'''
660 changenode = ctx.node()
660 changenode = ctx.node()
661 rev = ctx.rev()
661 rev = ctx.rev()
662
662
663 if self.ui.quiet:
663 if self.ui.quiet:
664 self.ui.write("%d:%s\n" % (rev, short(changenode)),
664 self.ui.write("%d:%s\n" % (rev, short(changenode)),
665 label='log.node')
665 label='log.node')
666 return
666 return
667
667
668 log = self.repo.changelog
668 log = self.repo.changelog
669 date = util.datestr(ctx.date())
669 date = util.datestr(ctx.date())
670
670
671 hexfunc = self.ui.debugflag and hex or short
671 hexfunc = self.ui.debugflag and hex or short
672
672
673 parents = [(p, hexfunc(log.node(p)))
673 parents = [(p, hexfunc(log.node(p)))
674 for p in self._meaningful_parentrevs(log, rev)]
674 for p in self._meaningful_parentrevs(log, rev)]
675
675
676 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
676 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
677 label='log.changeset')
677 label='log.changeset')
678
678
679 branch = ctx.branch()
679 branch = ctx.branch()
680 # don't show the default branch name
680 # don't show the default branch name
681 if branch != 'default':
681 if branch != 'default':
682 self.ui.write(_("branch: %s\n") % branch,
682 self.ui.write(_("branch: %s\n") % branch,
683 label='log.branch')
683 label='log.branch')
684 for bookmark in self.repo.nodebookmarks(changenode):
684 for bookmark in self.repo.nodebookmarks(changenode):
685 self.ui.write(_("bookmark: %s\n") % bookmark,
685 self.ui.write(_("bookmark: %s\n") % bookmark,
686 label='log.bookmark')
686 label='log.bookmark')
687 for tag in self.repo.nodetags(changenode):
687 for tag in self.repo.nodetags(changenode):
688 self.ui.write(_("tag: %s\n") % tag,
688 self.ui.write(_("tag: %s\n") % tag,
689 label='log.tag')
689 label='log.tag')
690 if self.ui.debugflag and ctx.phase():
690 if self.ui.debugflag and ctx.phase():
691 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
691 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
692 label='log.phase')
692 label='log.phase')
693 for parent in parents:
693 for parent in parents:
694 self.ui.write(_("parent: %d:%s\n") % parent,
694 self.ui.write(_("parent: %d:%s\n") % parent,
695 label='log.parent')
695 label='log.parent')
696
696
697 if self.ui.debugflag:
697 if self.ui.debugflag:
698 mnode = ctx.manifestnode()
698 mnode = ctx.manifestnode()
699 self.ui.write(_("manifest: %d:%s\n") %
699 self.ui.write(_("manifest: %d:%s\n") %
700 (self.repo.manifest.rev(mnode), hex(mnode)),
700 (self.repo.manifest.rev(mnode), hex(mnode)),
701 label='ui.debug log.manifest')
701 label='ui.debug log.manifest')
702 self.ui.write(_("user: %s\n") % ctx.user(),
702 self.ui.write(_("user: %s\n") % ctx.user(),
703 label='log.user')
703 label='log.user')
704 self.ui.write(_("date: %s\n") % date,
704 self.ui.write(_("date: %s\n") % date,
705 label='log.date')
705 label='log.date')
706
706
707 if self.ui.debugflag:
707 if self.ui.debugflag:
708 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
708 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
709 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
709 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
710 files):
710 files):
711 if value:
711 if value:
712 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
712 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
713 label='ui.debug log.files')
713 label='ui.debug log.files')
714 elif ctx.files() and self.ui.verbose:
714 elif ctx.files() and self.ui.verbose:
715 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
715 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
716 label='ui.note log.files')
716 label='ui.note log.files')
717 if copies and self.ui.verbose:
717 if copies and self.ui.verbose:
718 copies = ['%s (%s)' % c for c in copies]
718 copies = ['%s (%s)' % c for c in copies]
719 self.ui.write(_("copies: %s\n") % ' '.join(copies),
719 self.ui.write(_("copies: %s\n") % ' '.join(copies),
720 label='ui.note log.copies')
720 label='ui.note log.copies')
721
721
722 extra = ctx.extra()
722 extra = ctx.extra()
723 if extra and self.ui.debugflag:
723 if extra and self.ui.debugflag:
724 for key, value in sorted(extra.items()):
724 for key, value in sorted(extra.items()):
725 self.ui.write(_("extra: %s=%s\n")
725 self.ui.write(_("extra: %s=%s\n")
726 % (key, value.encode('string_escape')),
726 % (key, value.encode('string_escape')),
727 label='ui.debug log.extra')
727 label='ui.debug log.extra')
728
728
729 description = ctx.description().strip()
729 description = ctx.description().strip()
730 if description:
730 if description:
731 if self.ui.verbose:
731 if self.ui.verbose:
732 self.ui.write(_("description:\n"),
732 self.ui.write(_("description:\n"),
733 label='ui.note log.description')
733 label='ui.note log.description')
734 self.ui.write(description,
734 self.ui.write(description,
735 label='ui.note log.description')
735 label='ui.note log.description')
736 self.ui.write("\n\n")
736 self.ui.write("\n\n")
737 else:
737 else:
738 self.ui.write(_("summary: %s\n") %
738 self.ui.write(_("summary: %s\n") %
739 description.splitlines()[0],
739 description.splitlines()[0],
740 label='log.summary')
740 label='log.summary')
741 self.ui.write("\n")
741 self.ui.write("\n")
742
742
743 self.showpatch(changenode, matchfn)
743 self.showpatch(changenode, matchfn)
744
744
745 def showpatch(self, node, matchfn):
745 def showpatch(self, node, matchfn):
746 if not matchfn:
746 if not matchfn:
747 matchfn = self.patch
747 matchfn = self.patch
748 if matchfn:
748 if matchfn:
749 stat = self.diffopts.get('stat')
749 stat = self.diffopts.get('stat')
750 diff = self.diffopts.get('patch')
750 diff = self.diffopts.get('patch')
751 diffopts = patch.diffopts(self.ui, self.diffopts)
751 diffopts = patch.diffopts(self.ui, self.diffopts)
752 prev = self.repo.changelog.parents(node)[0]
752 prev = self.repo.changelog.parents(node)[0]
753 if stat:
753 if stat:
754 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
754 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
755 match=matchfn, stat=True)
755 match=matchfn, stat=True)
756 if diff:
756 if diff:
757 if stat:
757 if stat:
758 self.ui.write("\n")
758 self.ui.write("\n")
759 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
759 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
760 match=matchfn, stat=False)
760 match=matchfn, stat=False)
761 self.ui.write("\n")
761 self.ui.write("\n")
762
762
763 def _meaningful_parentrevs(self, log, rev):
763 def _meaningful_parentrevs(self, log, rev):
764 """Return list of meaningful (or all if debug) parentrevs for rev.
764 """Return list of meaningful (or all if debug) parentrevs for rev.
765
765
766 For merges (two non-nullrev revisions) both parents are meaningful.
766 For merges (two non-nullrev revisions) both parents are meaningful.
767 Otherwise the first parent revision is considered meaningful if it
767 Otherwise the first parent revision is considered meaningful if it
768 is not the preceding revision.
768 is not the preceding revision.
769 """
769 """
770 parents = log.parentrevs(rev)
770 parents = log.parentrevs(rev)
771 if not self.ui.debugflag and parents[1] == nullrev:
771 if not self.ui.debugflag and parents[1] == nullrev:
772 if parents[0] >= rev - 1:
772 if parents[0] >= rev - 1:
773 parents = []
773 parents = []
774 else:
774 else:
775 parents = [parents[0]]
775 parents = [parents[0]]
776 return parents
776 return parents
777
777
778
778
779 class changeset_templater(changeset_printer):
779 class changeset_templater(changeset_printer):
780 '''format changeset information.'''
780 '''format changeset information.'''
781
781
782 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
782 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
783 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
783 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
784 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
784 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
785 defaulttempl = {
785 defaulttempl = {
786 'parent': '{rev}:{node|formatnode} ',
786 'parent': '{rev}:{node|formatnode} ',
787 'manifest': '{rev}:{node|formatnode}',
787 'manifest': '{rev}:{node|formatnode}',
788 'file_copy': '{name} ({source})',
788 'file_copy': '{name} ({source})',
789 'extra': '{key}={value|stringescape}'
789 'extra': '{key}={value|stringescape}'
790 }
790 }
791 # filecopy is preserved for compatibility reasons
791 # filecopy is preserved for compatibility reasons
792 defaulttempl['filecopy'] = defaulttempl['file_copy']
792 defaulttempl['filecopy'] = defaulttempl['file_copy']
793 self.t = templater.templater(mapfile, {'formatnode': formatnode},
793 self.t = templater.templater(mapfile, {'formatnode': formatnode},
794 cache=defaulttempl)
794 cache=defaulttempl)
795 self.cache = {}
795 self.cache = {}
796
796
797 def use_template(self, t):
797 def use_template(self, t):
798 '''set template string to use'''
798 '''set template string to use'''
799 self.t.cache['changeset'] = t
799 self.t.cache['changeset'] = t
800
800
801 def _meaningful_parentrevs(self, ctx):
801 def _meaningful_parentrevs(self, ctx):
802 """Return list of meaningful (or all if debug) parentrevs for rev.
802 """Return list of meaningful (or all if debug) parentrevs for rev.
803 """
803 """
804 parents = ctx.parents()
804 parents = ctx.parents()
805 if len(parents) > 1:
805 if len(parents) > 1:
806 return parents
806 return parents
807 if self.ui.debugflag:
807 if self.ui.debugflag:
808 return [parents[0], self.repo['null']]
808 return [parents[0], self.repo['null']]
809 if parents[0].rev() >= ctx.rev() - 1:
809 if parents[0].rev() >= ctx.rev() - 1:
810 return []
810 return []
811 return parents
811 return parents
812
812
813 def _show(self, ctx, copies, matchfn, props):
813 def _show(self, ctx, copies, matchfn, props):
814 '''show a single changeset or file revision'''
814 '''show a single changeset or file revision'''
815
815
816 showlist = templatekw.showlist
816 showlist = templatekw.showlist
817
817
818 # showparents() behaviour depends on ui trace level which
818 # showparents() behaviour depends on ui trace level which
819 # causes unexpected behaviours at templating level and makes
819 # causes unexpected behaviours at templating level and makes
820 # it harder to extract it in a standalone function. Its
820 # it harder to extract it in a standalone function. Its
821 # behaviour cannot be changed so leave it here for now.
821 # behaviour cannot be changed so leave it here for now.
822 def showparents(**args):
822 def showparents(**args):
823 ctx = args['ctx']
823 ctx = args['ctx']
824 parents = [[('rev', p.rev()), ('node', p.hex())]
824 parents = [[('rev', p.rev()), ('node', p.hex())]
825 for p in self._meaningful_parentrevs(ctx)]
825 for p in self._meaningful_parentrevs(ctx)]
826 return showlist('parent', parents, **args)
826 return showlist('parent', parents, **args)
827
827
828 props = props.copy()
828 props = props.copy()
829 props.update(templatekw.keywords)
829 props.update(templatekw.keywords)
830 props['parents'] = showparents
830 props['parents'] = showparents
831 props['templ'] = self.t
831 props['templ'] = self.t
832 props['ctx'] = ctx
832 props['ctx'] = ctx
833 props['repo'] = self.repo
833 props['repo'] = self.repo
834 props['revcache'] = {'copies': copies}
834 props['revcache'] = {'copies': copies}
835 props['cache'] = self.cache
835 props['cache'] = self.cache
836
836
837 # find correct templates for current mode
837 # find correct templates for current mode
838
838
839 tmplmodes = [
839 tmplmodes = [
840 (True, None),
840 (True, None),
841 (self.ui.verbose, 'verbose'),
841 (self.ui.verbose, 'verbose'),
842 (self.ui.quiet, 'quiet'),
842 (self.ui.quiet, 'quiet'),
843 (self.ui.debugflag, 'debug'),
843 (self.ui.debugflag, 'debug'),
844 ]
844 ]
845
845
846 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
846 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
847 for mode, postfix in tmplmodes:
847 for mode, postfix in tmplmodes:
848 for type in types:
848 for type in types:
849 cur = postfix and ('%s_%s' % (type, postfix)) or type
849 cur = postfix and ('%s_%s' % (type, postfix)) or type
850 if mode and cur in self.t:
850 if mode and cur in self.t:
851 types[type] = cur
851 types[type] = cur
852
852
853 try:
853 try:
854
854
855 # write header
855 # write header
856 if types['header']:
856 if types['header']:
857 h = templater.stringify(self.t(types['header'], **props))
857 h = templater.stringify(self.t(types['header'], **props))
858 if self.buffered:
858 if self.buffered:
859 self.header[ctx.rev()] = h
859 self.header[ctx.rev()] = h
860 else:
860 else:
861 if self.lastheader != h:
861 if self.lastheader != h:
862 self.lastheader = h
862 self.lastheader = h
863 self.ui.write(h)
863 self.ui.write(h)
864
864
865 # write changeset metadata, then patch if requested
865 # write changeset metadata, then patch if requested
866 key = types['changeset']
866 key = types['changeset']
867 self.ui.write(templater.stringify(self.t(key, **props)))
867 self.ui.write(templater.stringify(self.t(key, **props)))
868 self.showpatch(ctx.node(), matchfn)
868 self.showpatch(ctx.node(), matchfn)
869
869
870 if types['footer']:
870 if types['footer']:
871 if not self.footer:
871 if not self.footer:
872 self.footer = templater.stringify(self.t(types['footer'],
872 self.footer = templater.stringify(self.t(types['footer'],
873 **props))
873 **props))
874
874
875 except KeyError, inst:
875 except KeyError, inst:
876 msg = _("%s: no key named '%s'")
876 msg = _("%s: no key named '%s'")
877 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
877 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
878 except SyntaxError, inst:
878 except SyntaxError, inst:
879 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
879 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
880
880
881 def show_changeset(ui, repo, opts, buffered=False):
881 def show_changeset(ui, repo, opts, buffered=False):
882 """show one changeset using template or regular display.
882 """show one changeset using template or regular display.
883
883
884 Display format will be the first non-empty hit of:
884 Display format will be the first non-empty hit of:
885 1. option 'template'
885 1. option 'template'
886 2. option 'style'
886 2. option 'style'
887 3. [ui] setting 'logtemplate'
887 3. [ui] setting 'logtemplate'
888 4. [ui] setting 'style'
888 4. [ui] setting 'style'
889 If all of these values are either the unset or the empty string,
889 If all of these values are either the unset or the empty string,
890 regular display via changeset_printer() is done.
890 regular display via changeset_printer() is done.
891 """
891 """
892 # options
892 # options
893 patch = False
893 patch = False
894 if opts.get('patch') or opts.get('stat'):
894 if opts.get('patch') or opts.get('stat'):
895 patch = scmutil.matchall(repo)
895 patch = scmutil.matchall(repo)
896
896
897 tmpl = opts.get('template')
897 tmpl = opts.get('template')
898 style = None
898 style = None
899 if tmpl:
899 if tmpl:
900 tmpl = templater.parsestring(tmpl, quoted=False)
900 tmpl = templater.parsestring(tmpl, quoted=False)
901 else:
901 else:
902 style = opts.get('style')
902 style = opts.get('style')
903
903
904 # ui settings
904 # ui settings
905 if not (tmpl or style):
905 if not (tmpl or style):
906 tmpl = ui.config('ui', 'logtemplate')
906 tmpl = ui.config('ui', 'logtemplate')
907 if tmpl:
907 if tmpl:
908 tmpl = templater.parsestring(tmpl)
908 tmpl = templater.parsestring(tmpl)
909 else:
909 else:
910 style = util.expandpath(ui.config('ui', 'style', ''))
910 style = util.expandpath(ui.config('ui', 'style', ''))
911
911
912 if not (tmpl or style):
912 if not (tmpl or style):
913 return changeset_printer(ui, repo, patch, opts, buffered)
913 return changeset_printer(ui, repo, patch, opts, buffered)
914
914
915 mapfile = None
915 mapfile = None
916 if style and not tmpl:
916 if style and not tmpl:
917 mapfile = style
917 mapfile = style
918 if not os.path.split(mapfile)[0]:
918 if not os.path.split(mapfile)[0]:
919 mapname = (templater.templatepath('map-cmdline.' + mapfile)
919 mapname = (templater.templatepath('map-cmdline.' + mapfile)
920 or templater.templatepath(mapfile))
920 or templater.templatepath(mapfile))
921 if mapname:
921 if mapname:
922 mapfile = mapname
922 mapfile = mapname
923
923
924 try:
924 try:
925 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
925 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
926 except SyntaxError, inst:
926 except SyntaxError, inst:
927 raise util.Abort(inst.args[0])
927 raise util.Abort(inst.args[0])
928 if tmpl:
928 if tmpl:
929 t.use_template(tmpl)
929 t.use_template(tmpl)
930 return t
930 return t
931
931
932 def finddate(ui, repo, date):
932 def finddate(ui, repo, date):
933 """Find the tipmost changeset that matches the given date spec"""
933 """Find the tipmost changeset that matches the given date spec"""
934
934
935 df = util.matchdate(date)
935 df = util.matchdate(date)
936 m = scmutil.matchall(repo)
936 m = scmutil.matchall(repo)
937 results = {}
937 results = {}
938
938
939 def prep(ctx, fns):
939 def prep(ctx, fns):
940 d = ctx.date()
940 d = ctx.date()
941 if df(d[0]):
941 if df(d[0]):
942 results[ctx.rev()] = d
942 results[ctx.rev()] = d
943
943
944 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
944 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
945 rev = ctx.rev()
945 rev = ctx.rev()
946 if rev in results:
946 if rev in results:
947 ui.status(_("Found revision %s from %s\n") %
947 ui.status(_("Found revision %s from %s\n") %
948 (rev, util.datestr(results[rev])))
948 (rev, util.datestr(results[rev])))
949 return str(rev)
949 return str(rev)
950
950
951 raise util.Abort(_("revision matching date not found"))
951 raise util.Abort(_("revision matching date not found"))
952
952
953 def walkchangerevs(repo, match, opts, prepare):
953 def walkchangerevs(repo, match, opts, prepare):
954 '''Iterate over files and the revs in which they changed.
954 '''Iterate over files and the revs in which they changed.
955
955
956 Callers most commonly need to iterate backwards over the history
956 Callers most commonly need to iterate backwards over the history
957 in which they are interested. Doing so has awful (quadratic-looking)
957 in which they are interested. Doing so has awful (quadratic-looking)
958 performance, so we use iterators in a "windowed" way.
958 performance, so we use iterators in a "windowed" way.
959
959
960 We walk a window of revisions in the desired order. Within the
960 We walk a window of revisions in the desired order. Within the
961 window, we first walk forwards to gather data, then in the desired
961 window, we first walk forwards to gather data, then in the desired
962 order (usually backwards) to display it.
962 order (usually backwards) to display it.
963
963
964 This function returns an iterator yielding contexts. Before
964 This function returns an iterator yielding contexts. Before
965 yielding each context, the iterator will first call the prepare
965 yielding each context, the iterator will first call the prepare
966 function on each context in the window in forward order.'''
966 function on each context in the window in forward order.'''
967
967
968 def increasing_windows(start, end, windowsize=8, sizelimit=512):
968 def increasing_windows(start, end, windowsize=8, sizelimit=512):
969 if start < end:
969 if start < end:
970 while start < end:
970 while start < end:
971 yield start, min(windowsize, end - start)
971 yield start, min(windowsize, end - start)
972 start += windowsize
972 start += windowsize
973 if windowsize < sizelimit:
973 if windowsize < sizelimit:
974 windowsize *= 2
974 windowsize *= 2
975 else:
975 else:
976 while start > end:
976 while start > end:
977 yield start, min(windowsize, start - end - 1)
977 yield start, min(windowsize, start - end - 1)
978 start -= windowsize
978 start -= windowsize
979 if windowsize < sizelimit:
979 if windowsize < sizelimit:
980 windowsize *= 2
980 windowsize *= 2
981
981
982 follow = opts.get('follow') or opts.get('follow_first')
982 follow = opts.get('follow') or opts.get('follow_first')
983
983
984 if not len(repo):
984 if not len(repo):
985 return []
985 return []
986
986
987 if follow:
987 if follow:
988 defrange = '%s:0' % repo['.'].rev()
988 defrange = '%s:0' % repo['.'].rev()
989 else:
989 else:
990 defrange = '-1:0'
990 defrange = '-1:0'
991 revs = scmutil.revrange(repo, opts['rev'] or [defrange])
991 revs = scmutil.revrange(repo, opts['rev'] or [defrange])
992 if not revs:
992 if not revs:
993 return []
993 return []
994 wanted = set()
994 wanted = set()
995 slowpath = match.anypats() or (match.files() and opts.get('removed'))
995 slowpath = match.anypats() or (match.files() and opts.get('removed'))
996 fncache = {}
996 fncache = {}
997 change = repo.changectx
997 change = repo.changectx
998
998
999 # First step is to fill wanted, the set of revisions that we want to yield.
999 # First step is to fill wanted, the set of revisions that we want to yield.
1000 # When it does not induce extra cost, we also fill fncache for revisions in
1000 # When it does not induce extra cost, we also fill fncache for revisions in
1001 # wanted: a cache of filenames that were changed (ctx.files()) and that
1001 # wanted: a cache of filenames that were changed (ctx.files()) and that
1002 # match the file filtering conditions.
1002 # match the file filtering conditions.
1003
1003
1004 if not slowpath and not match.files():
1004 if not slowpath and not match.files():
1005 # No files, no patterns. Display all revs.
1005 # No files, no patterns. Display all revs.
1006 wanted = set(revs)
1006 wanted = set(revs)
1007 copies = []
1007 copies = []
1008
1008
1009 if not slowpath:
1009 if not slowpath:
1010 # We only have to read through the filelog to find wanted revisions
1010 # We only have to read through the filelog to find wanted revisions
1011
1011
1012 minrev, maxrev = min(revs), max(revs)
1012 minrev, maxrev = min(revs), max(revs)
1013 def filerevgen(filelog, last):
1013 def filerevgen(filelog, last):
1014 """
1014 """
1015 Only files, no patterns. Check the history of each file.
1015 Only files, no patterns. Check the history of each file.
1016
1016
1017 Examines filelog entries within minrev, maxrev linkrev range
1017 Examines filelog entries within minrev, maxrev linkrev range
1018 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1018 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1019 tuples in backwards order
1019 tuples in backwards order
1020 """
1020 """
1021 cl_count = len(repo)
1021 cl_count = len(repo)
1022 revs = []
1022 revs = []
1023 for j in xrange(0, last + 1):
1023 for j in xrange(0, last + 1):
1024 linkrev = filelog.linkrev(j)
1024 linkrev = filelog.linkrev(j)
1025 if linkrev < minrev:
1025 if linkrev < minrev:
1026 continue
1026 continue
1027 # only yield rev for which we have the changelog, it can
1027 # only yield rev for which we have the changelog, it can
1028 # happen while doing "hg log" during a pull or commit
1028 # happen while doing "hg log" during a pull or commit
1029 if linkrev >= cl_count:
1029 if linkrev >= cl_count:
1030 break
1030 break
1031
1031
1032 parentlinkrevs = []
1032 parentlinkrevs = []
1033 for p in filelog.parentrevs(j):
1033 for p in filelog.parentrevs(j):
1034 if p != nullrev:
1034 if p != nullrev:
1035 parentlinkrevs.append(filelog.linkrev(p))
1035 parentlinkrevs.append(filelog.linkrev(p))
1036 n = filelog.node(j)
1036 n = filelog.node(j)
1037 revs.append((linkrev, parentlinkrevs,
1037 revs.append((linkrev, parentlinkrevs,
1038 follow and filelog.renamed(n)))
1038 follow and filelog.renamed(n)))
1039
1039
1040 return reversed(revs)
1040 return reversed(revs)
1041 def iterfiles():
1041 def iterfiles():
1042 pctx = repo['.']
1042 pctx = repo['.']
1043 for filename in match.files():
1043 for filename in match.files():
1044 if follow:
1044 if follow:
1045 if filename not in pctx:
1045 if filename not in pctx:
1046 raise util.Abort(_('cannot follow file not in parent '
1046 raise util.Abort(_('cannot follow file not in parent '
1047 'revision: "%s"') % filename)
1047 'revision: "%s"') % filename)
1048 yield filename, pctx[filename].filenode()
1048 yield filename, pctx[filename].filenode()
1049 else:
1049 else:
1050 yield filename, None
1050 yield filename, None
1051 for filename_node in copies:
1051 for filename_node in copies:
1052 yield filename_node
1052 yield filename_node
1053 for file_, node in iterfiles():
1053 for file_, node in iterfiles():
1054 filelog = repo.file(file_)
1054 filelog = repo.file(file_)
1055 if not len(filelog):
1055 if not len(filelog):
1056 if node is None:
1056 if node is None:
1057 # A zero count may be a directory or deleted file, so
1057 # A zero count may be a directory or deleted file, so
1058 # try to find matching entries on the slow path.
1058 # try to find matching entries on the slow path.
1059 if follow:
1059 if follow:
1060 raise util.Abort(
1060 raise util.Abort(
1061 _('cannot follow nonexistent file: "%s"') % file_)
1061 _('cannot follow nonexistent file: "%s"') % file_)
1062 slowpath = True
1062 slowpath = True
1063 break
1063 break
1064 else:
1064 else:
1065 continue
1065 continue
1066
1066
1067 if node is None:
1067 if node is None:
1068 last = len(filelog) - 1
1068 last = len(filelog) - 1
1069 else:
1069 else:
1070 last = filelog.rev(node)
1070 last = filelog.rev(node)
1071
1071
1072
1072
1073 # keep track of all ancestors of the file
1073 # keep track of all ancestors of the file
1074 ancestors = set([filelog.linkrev(last)])
1074 ancestors = set([filelog.linkrev(last)])
1075
1075
1076 # iterate from latest to oldest revision
1076 # iterate from latest to oldest revision
1077 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1077 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1078 if not follow:
1078 if not follow:
1079 if rev > maxrev:
1079 if rev > maxrev:
1080 continue
1080 continue
1081 else:
1081 else:
1082 # Note that last might not be the first interesting
1082 # Note that last might not be the first interesting
1083 # rev to us:
1083 # rev to us:
1084 # if the file has been changed after maxrev, we'll
1084 # if the file has been changed after maxrev, we'll
1085 # have linkrev(last) > maxrev, and we still need
1085 # have linkrev(last) > maxrev, and we still need
1086 # to explore the file graph
1086 # to explore the file graph
1087 if rev not in ancestors:
1087 if rev not in ancestors:
1088 continue
1088 continue
1089 # XXX insert 1327 fix here
1089 # XXX insert 1327 fix here
1090 if flparentlinkrevs:
1090 if flparentlinkrevs:
1091 ancestors.update(flparentlinkrevs)
1091 ancestors.update(flparentlinkrevs)
1092
1092
1093 fncache.setdefault(rev, []).append(file_)
1093 fncache.setdefault(rev, []).append(file_)
1094 wanted.add(rev)
1094 wanted.add(rev)
1095 if copied:
1095 if copied:
1096 copies.append(copied)
1096 copies.append(copied)
1097 if slowpath:
1097 if slowpath:
1098 # We have to read the changelog to match filenames against
1098 # We have to read the changelog to match filenames against
1099 # changed files
1099 # changed files
1100
1100
1101 if follow:
1101 if follow:
1102 raise util.Abort(_('can only follow copies/renames for explicit '
1102 raise util.Abort(_('can only follow copies/renames for explicit '
1103 'filenames'))
1103 'filenames'))
1104
1104
1105 # The slow path checks files modified in every changeset.
1105 # The slow path checks files modified in every changeset.
1106 for i in sorted(revs):
1106 for i in sorted(revs):
1107 ctx = change(i)
1107 ctx = change(i)
1108 matches = filter(match, ctx.files())
1108 matches = filter(match, ctx.files())
1109 if matches:
1109 if matches:
1110 fncache[i] = matches
1110 fncache[i] = matches
1111 wanted.add(i)
1111 wanted.add(i)
1112
1112
1113 class followfilter(object):
1113 class followfilter(object):
1114 def __init__(self, onlyfirst=False):
1114 def __init__(self, onlyfirst=False):
1115 self.startrev = nullrev
1115 self.startrev = nullrev
1116 self.roots = set()
1116 self.roots = set()
1117 self.onlyfirst = onlyfirst
1117 self.onlyfirst = onlyfirst
1118
1118
1119 def match(self, rev):
1119 def match(self, rev):
1120 def realparents(rev):
1120 def realparents(rev):
1121 if self.onlyfirst:
1121 if self.onlyfirst:
1122 return repo.changelog.parentrevs(rev)[0:1]
1122 return repo.changelog.parentrevs(rev)[0:1]
1123 else:
1123 else:
1124 return filter(lambda x: x != nullrev,
1124 return filter(lambda x: x != nullrev,
1125 repo.changelog.parentrevs(rev))
1125 repo.changelog.parentrevs(rev))
1126
1126
1127 if self.startrev == nullrev:
1127 if self.startrev == nullrev:
1128 self.startrev = rev
1128 self.startrev = rev
1129 return True
1129 return True
1130
1130
1131 if rev > self.startrev:
1131 if rev > self.startrev:
1132 # forward: all descendants
1132 # forward: all descendants
1133 if not self.roots:
1133 if not self.roots:
1134 self.roots.add(self.startrev)
1134 self.roots.add(self.startrev)
1135 for parent in realparents(rev):
1135 for parent in realparents(rev):
1136 if parent in self.roots:
1136 if parent in self.roots:
1137 self.roots.add(rev)
1137 self.roots.add(rev)
1138 return True
1138 return True
1139 else:
1139 else:
1140 # backwards: all parents
1140 # backwards: all parents
1141 if not self.roots:
1141 if not self.roots:
1142 self.roots.update(realparents(self.startrev))
1142 self.roots.update(realparents(self.startrev))
1143 if rev in self.roots:
1143 if rev in self.roots:
1144 self.roots.remove(rev)
1144 self.roots.remove(rev)
1145 self.roots.update(realparents(rev))
1145 self.roots.update(realparents(rev))
1146 return True
1146 return True
1147
1147
1148 return False
1148 return False
1149
1149
1150 # it might be worthwhile to do this in the iterator if the rev range
1150 # it might be worthwhile to do this in the iterator if the rev range
1151 # is descending and the prune args are all within that range
1151 # is descending and the prune args are all within that range
1152 for rev in opts.get('prune', ()):
1152 for rev in opts.get('prune', ()):
1153 rev = repo.changelog.rev(repo.lookup(rev))
1153 rev = repo[rev].rev()
1154 ff = followfilter()
1154 ff = followfilter()
1155 stop = min(revs[0], revs[-1])
1155 stop = min(revs[0], revs[-1])
1156 for x in xrange(rev, stop - 1, -1):
1156 for x in xrange(rev, stop - 1, -1):
1157 if ff.match(x):
1157 if ff.match(x):
1158 wanted.discard(x)
1158 wanted.discard(x)
1159
1159
1160 # Now that wanted is correctly initialized, we can iterate over the
1160 # Now that wanted is correctly initialized, we can iterate over the
1161 # revision range, yielding only revisions in wanted.
1161 # revision range, yielding only revisions in wanted.
1162 def iterate():
1162 def iterate():
1163 if follow and not match.files():
1163 if follow and not match.files():
1164 ff = followfilter(onlyfirst=opts.get('follow_first'))
1164 ff = followfilter(onlyfirst=opts.get('follow_first'))
1165 def want(rev):
1165 def want(rev):
1166 return ff.match(rev) and rev in wanted
1166 return ff.match(rev) and rev in wanted
1167 else:
1167 else:
1168 def want(rev):
1168 def want(rev):
1169 return rev in wanted
1169 return rev in wanted
1170
1170
1171 for i, window in increasing_windows(0, len(revs)):
1171 for i, window in increasing_windows(0, len(revs)):
1172 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1172 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1173 for rev in sorted(nrevs):
1173 for rev in sorted(nrevs):
1174 fns = fncache.get(rev)
1174 fns = fncache.get(rev)
1175 ctx = change(rev)
1175 ctx = change(rev)
1176 if not fns:
1176 if not fns:
1177 def fns_generator():
1177 def fns_generator():
1178 for f in ctx.files():
1178 for f in ctx.files():
1179 if match(f):
1179 if match(f):
1180 yield f
1180 yield f
1181 fns = fns_generator()
1181 fns = fns_generator()
1182 prepare(ctx, fns)
1182 prepare(ctx, fns)
1183 for rev in nrevs:
1183 for rev in nrevs:
1184 yield change(rev)
1184 yield change(rev)
1185 return iterate()
1185 return iterate()
1186
1186
1187 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1187 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1188 join = lambda f: os.path.join(prefix, f)
1188 join = lambda f: os.path.join(prefix, f)
1189 bad = []
1189 bad = []
1190 oldbad = match.bad
1190 oldbad = match.bad
1191 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1191 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1192 names = []
1192 names = []
1193 wctx = repo[None]
1193 wctx = repo[None]
1194 cca = None
1194 cca = None
1195 abort, warn = scmutil.checkportabilityalert(ui)
1195 abort, warn = scmutil.checkportabilityalert(ui)
1196 if abort or warn:
1196 if abort or warn:
1197 cca = scmutil.casecollisionauditor(ui, abort, wctx)
1197 cca = scmutil.casecollisionauditor(ui, abort, wctx)
1198 for f in repo.walk(match):
1198 for f in repo.walk(match):
1199 exact = match.exact(f)
1199 exact = match.exact(f)
1200 if exact or not explicitonly and f not in repo.dirstate:
1200 if exact or not explicitonly and f not in repo.dirstate:
1201 if cca:
1201 if cca:
1202 cca(f)
1202 cca(f)
1203 names.append(f)
1203 names.append(f)
1204 if ui.verbose or not exact:
1204 if ui.verbose or not exact:
1205 ui.status(_('adding %s\n') % match.rel(join(f)))
1205 ui.status(_('adding %s\n') % match.rel(join(f)))
1206
1206
1207 for subpath in wctx.substate:
1207 for subpath in wctx.substate:
1208 sub = wctx.sub(subpath)
1208 sub = wctx.sub(subpath)
1209 try:
1209 try:
1210 submatch = matchmod.narrowmatcher(subpath, match)
1210 submatch = matchmod.narrowmatcher(subpath, match)
1211 if listsubrepos:
1211 if listsubrepos:
1212 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1212 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1213 False))
1213 False))
1214 else:
1214 else:
1215 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1215 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1216 True))
1216 True))
1217 except error.LookupError:
1217 except error.LookupError:
1218 ui.status(_("skipping missing subrepository: %s\n")
1218 ui.status(_("skipping missing subrepository: %s\n")
1219 % join(subpath))
1219 % join(subpath))
1220
1220
1221 if not dryrun:
1221 if not dryrun:
1222 rejected = wctx.add(names, prefix)
1222 rejected = wctx.add(names, prefix)
1223 bad.extend(f for f in rejected if f in match.files())
1223 bad.extend(f for f in rejected if f in match.files())
1224 return bad
1224 return bad
1225
1225
1226 def forget(ui, repo, match, prefix, explicitonly):
1226 def forget(ui, repo, match, prefix, explicitonly):
1227 join = lambda f: os.path.join(prefix, f)
1227 join = lambda f: os.path.join(prefix, f)
1228 bad = []
1228 bad = []
1229 oldbad = match.bad
1229 oldbad = match.bad
1230 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1230 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1231 wctx = repo[None]
1231 wctx = repo[None]
1232 forgot = []
1232 forgot = []
1233 s = repo.status(match=match, clean=True)
1233 s = repo.status(match=match, clean=True)
1234 forget = sorted(s[0] + s[1] + s[3] + s[6])
1234 forget = sorted(s[0] + s[1] + s[3] + s[6])
1235 if explicitonly:
1235 if explicitonly:
1236 forget = [f for f in forget if match.exact(f)]
1236 forget = [f for f in forget if match.exact(f)]
1237
1237
1238 for subpath in wctx.substate:
1238 for subpath in wctx.substate:
1239 sub = wctx.sub(subpath)
1239 sub = wctx.sub(subpath)
1240 try:
1240 try:
1241 submatch = matchmod.narrowmatcher(subpath, match)
1241 submatch = matchmod.narrowmatcher(subpath, match)
1242 subbad, subforgot = sub.forget(ui, submatch, prefix)
1242 subbad, subforgot = sub.forget(ui, submatch, prefix)
1243 bad.extend([subpath + '/' + f for f in subbad])
1243 bad.extend([subpath + '/' + f for f in subbad])
1244 forgot.extend([subpath + '/' + f for f in subforgot])
1244 forgot.extend([subpath + '/' + f for f in subforgot])
1245 except error.LookupError:
1245 except error.LookupError:
1246 ui.status(_("skipping missing subrepository: %s\n")
1246 ui.status(_("skipping missing subrepository: %s\n")
1247 % join(subpath))
1247 % join(subpath))
1248
1248
1249 if not explicitonly:
1249 if not explicitonly:
1250 for f in match.files():
1250 for f in match.files():
1251 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1251 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1252 if f not in forgot:
1252 if f not in forgot:
1253 if os.path.exists(match.rel(join(f))):
1253 if os.path.exists(match.rel(join(f))):
1254 ui.warn(_('not removing %s: '
1254 ui.warn(_('not removing %s: '
1255 'file is already untracked\n')
1255 'file is already untracked\n')
1256 % match.rel(join(f)))
1256 % match.rel(join(f)))
1257 bad.append(f)
1257 bad.append(f)
1258
1258
1259 for f in forget:
1259 for f in forget:
1260 if ui.verbose or not match.exact(f):
1260 if ui.verbose or not match.exact(f):
1261 ui.status(_('removing %s\n') % match.rel(join(f)))
1261 ui.status(_('removing %s\n') % match.rel(join(f)))
1262
1262
1263 rejected = wctx.forget(forget, prefix)
1263 rejected = wctx.forget(forget, prefix)
1264 bad.extend(f for f in rejected if f in match.files())
1264 bad.extend(f for f in rejected if f in match.files())
1265 forgot.extend(forget)
1265 forgot.extend(forget)
1266 return bad, forgot
1266 return bad, forgot
1267
1267
1268 def duplicatecopies(repo, rev, p1):
1268 def duplicatecopies(repo, rev, p1):
1269 "Reproduce copies found in the source revision in the dirstate for grafts"
1269 "Reproduce copies found in the source revision in the dirstate for grafts"
1270 for dst, src in copies.pathcopies(repo[p1], repo[rev]).iteritems():
1270 for dst, src in copies.pathcopies(repo[p1], repo[rev]).iteritems():
1271 repo.dirstate.copy(src, dst)
1271 repo.dirstate.copy(src, dst)
1272
1272
1273 def commit(ui, repo, commitfunc, pats, opts):
1273 def commit(ui, repo, commitfunc, pats, opts):
1274 '''commit the specified files or all outstanding changes'''
1274 '''commit the specified files or all outstanding changes'''
1275 date = opts.get('date')
1275 date = opts.get('date')
1276 if date:
1276 if date:
1277 opts['date'] = util.parsedate(date)
1277 opts['date'] = util.parsedate(date)
1278 message = logmessage(ui, opts)
1278 message = logmessage(ui, opts)
1279
1279
1280 # extract addremove carefully -- this function can be called from a command
1280 # extract addremove carefully -- this function can be called from a command
1281 # that doesn't support addremove
1281 # that doesn't support addremove
1282 if opts.get('addremove'):
1282 if opts.get('addremove'):
1283 scmutil.addremove(repo, pats, opts)
1283 scmutil.addremove(repo, pats, opts)
1284
1284
1285 return commitfunc(ui, repo, message,
1285 return commitfunc(ui, repo, message,
1286 scmutil.match(repo[None], pats, opts), opts)
1286 scmutil.match(repo[None], pats, opts), opts)
1287
1287
1288 def commiteditor(repo, ctx, subs):
1288 def commiteditor(repo, ctx, subs):
1289 if ctx.description():
1289 if ctx.description():
1290 return ctx.description()
1290 return ctx.description()
1291 return commitforceeditor(repo, ctx, subs)
1291 return commitforceeditor(repo, ctx, subs)
1292
1292
1293 def commitforceeditor(repo, ctx, subs):
1293 def commitforceeditor(repo, ctx, subs):
1294 edittext = []
1294 edittext = []
1295 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1295 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1296 if ctx.description():
1296 if ctx.description():
1297 edittext.append(ctx.description())
1297 edittext.append(ctx.description())
1298 edittext.append("")
1298 edittext.append("")
1299 edittext.append("") # Empty line between message and comments.
1299 edittext.append("") # Empty line between message and comments.
1300 edittext.append(_("HG: Enter commit message."
1300 edittext.append(_("HG: Enter commit message."
1301 " Lines beginning with 'HG:' are removed."))
1301 " Lines beginning with 'HG:' are removed."))
1302 edittext.append(_("HG: Leave message empty to abort commit."))
1302 edittext.append(_("HG: Leave message empty to abort commit."))
1303 edittext.append("HG: --")
1303 edittext.append("HG: --")
1304 edittext.append(_("HG: user: %s") % ctx.user())
1304 edittext.append(_("HG: user: %s") % ctx.user())
1305 if ctx.p2():
1305 if ctx.p2():
1306 edittext.append(_("HG: branch merge"))
1306 edittext.append(_("HG: branch merge"))
1307 if ctx.branch():
1307 if ctx.branch():
1308 edittext.append(_("HG: branch '%s'") % ctx.branch())
1308 edittext.append(_("HG: branch '%s'") % ctx.branch())
1309 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1309 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1310 edittext.extend([_("HG: added %s") % f for f in added])
1310 edittext.extend([_("HG: added %s") % f for f in added])
1311 edittext.extend([_("HG: changed %s") % f for f in modified])
1311 edittext.extend([_("HG: changed %s") % f for f in modified])
1312 edittext.extend([_("HG: removed %s") % f for f in removed])
1312 edittext.extend([_("HG: removed %s") % f for f in removed])
1313 if not added and not modified and not removed:
1313 if not added and not modified and not removed:
1314 edittext.append(_("HG: no files changed"))
1314 edittext.append(_("HG: no files changed"))
1315 edittext.append("")
1315 edittext.append("")
1316 # run editor in the repository root
1316 # run editor in the repository root
1317 olddir = os.getcwd()
1317 olddir = os.getcwd()
1318 os.chdir(repo.root)
1318 os.chdir(repo.root)
1319 text = repo.ui.edit("\n".join(edittext), ctx.user())
1319 text = repo.ui.edit("\n".join(edittext), ctx.user())
1320 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1320 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1321 os.chdir(olddir)
1321 os.chdir(olddir)
1322
1322
1323 if not text.strip():
1323 if not text.strip():
1324 raise util.Abort(_("empty commit message"))
1324 raise util.Abort(_("empty commit message"))
1325
1325
1326 return text
1326 return text
1327
1327
1328 def revert(ui, repo, ctx, parents, *pats, **opts):
1328 def revert(ui, repo, ctx, parents, *pats, **opts):
1329 parent, p2 = parents
1329 parent, p2 = parents
1330 node = ctx.node()
1330 node = ctx.node()
1331
1331
1332 mf = ctx.manifest()
1332 mf = ctx.manifest()
1333 if node == parent:
1333 if node == parent:
1334 pmf = mf
1334 pmf = mf
1335 else:
1335 else:
1336 pmf = None
1336 pmf = None
1337
1337
1338 # need all matching names in dirstate and manifest of target rev,
1338 # need all matching names in dirstate and manifest of target rev,
1339 # so have to walk both. do not print errors if files exist in one
1339 # so have to walk both. do not print errors if files exist in one
1340 # but not other.
1340 # but not other.
1341
1341
1342 names = {}
1342 names = {}
1343
1343
1344 wlock = repo.wlock()
1344 wlock = repo.wlock()
1345 try:
1345 try:
1346 # walk dirstate.
1346 # walk dirstate.
1347
1347
1348 m = scmutil.match(repo[None], pats, opts)
1348 m = scmutil.match(repo[None], pats, opts)
1349 m.bad = lambda x, y: False
1349 m.bad = lambda x, y: False
1350 for abs in repo.walk(m):
1350 for abs in repo.walk(m):
1351 names[abs] = m.rel(abs), m.exact(abs)
1351 names[abs] = m.rel(abs), m.exact(abs)
1352
1352
1353 # walk target manifest.
1353 # walk target manifest.
1354
1354
1355 def badfn(path, msg):
1355 def badfn(path, msg):
1356 if path in names:
1356 if path in names:
1357 return
1357 return
1358 if path in repo[node].substate:
1358 if path in repo[node].substate:
1359 ui.warn("%s: %s\n" % (m.rel(path),
1359 ui.warn("%s: %s\n" % (m.rel(path),
1360 'reverting subrepos is unsupported'))
1360 'reverting subrepos is unsupported'))
1361 return
1361 return
1362 path_ = path + '/'
1362 path_ = path + '/'
1363 for f in names:
1363 for f in names:
1364 if f.startswith(path_):
1364 if f.startswith(path_):
1365 return
1365 return
1366 ui.warn("%s: %s\n" % (m.rel(path), msg))
1366 ui.warn("%s: %s\n" % (m.rel(path), msg))
1367
1367
1368 m = scmutil.match(repo[node], pats, opts)
1368 m = scmutil.match(repo[node], pats, opts)
1369 m.bad = badfn
1369 m.bad = badfn
1370 for abs in repo[node].walk(m):
1370 for abs in repo[node].walk(m):
1371 if abs not in names:
1371 if abs not in names:
1372 names[abs] = m.rel(abs), m.exact(abs)
1372 names[abs] = m.rel(abs), m.exact(abs)
1373
1373
1374 m = scmutil.matchfiles(repo, names)
1374 m = scmutil.matchfiles(repo, names)
1375 changes = repo.status(match=m)[:4]
1375 changes = repo.status(match=m)[:4]
1376 modified, added, removed, deleted = map(set, changes)
1376 modified, added, removed, deleted = map(set, changes)
1377
1377
1378 # if f is a rename, also revert the source
1378 # if f is a rename, also revert the source
1379 cwd = repo.getcwd()
1379 cwd = repo.getcwd()
1380 for f in added:
1380 for f in added:
1381 src = repo.dirstate.copied(f)
1381 src = repo.dirstate.copied(f)
1382 if src and src not in names and repo.dirstate[src] == 'r':
1382 if src and src not in names and repo.dirstate[src] == 'r':
1383 removed.add(src)
1383 removed.add(src)
1384 names[src] = (repo.pathto(src, cwd), True)
1384 names[src] = (repo.pathto(src, cwd), True)
1385
1385
1386 def removeforget(abs):
1386 def removeforget(abs):
1387 if repo.dirstate[abs] == 'a':
1387 if repo.dirstate[abs] == 'a':
1388 return _('forgetting %s\n')
1388 return _('forgetting %s\n')
1389 return _('removing %s\n')
1389 return _('removing %s\n')
1390
1390
1391 revert = ([], _('reverting %s\n'))
1391 revert = ([], _('reverting %s\n'))
1392 add = ([], _('adding %s\n'))
1392 add = ([], _('adding %s\n'))
1393 remove = ([], removeforget)
1393 remove = ([], removeforget)
1394 undelete = ([], _('undeleting %s\n'))
1394 undelete = ([], _('undeleting %s\n'))
1395
1395
1396 disptable = (
1396 disptable = (
1397 # dispatch table:
1397 # dispatch table:
1398 # file state
1398 # file state
1399 # action if in target manifest
1399 # action if in target manifest
1400 # action if not in target manifest
1400 # action if not in target manifest
1401 # make backup if in target manifest
1401 # make backup if in target manifest
1402 # make backup if not in target manifest
1402 # make backup if not in target manifest
1403 (modified, revert, remove, True, True),
1403 (modified, revert, remove, True, True),
1404 (added, revert, remove, True, False),
1404 (added, revert, remove, True, False),
1405 (removed, undelete, None, False, False),
1405 (removed, undelete, None, False, False),
1406 (deleted, revert, remove, False, False),
1406 (deleted, revert, remove, False, False),
1407 )
1407 )
1408
1408
1409 for abs, (rel, exact) in sorted(names.items()):
1409 for abs, (rel, exact) in sorted(names.items()):
1410 mfentry = mf.get(abs)
1410 mfentry = mf.get(abs)
1411 target = repo.wjoin(abs)
1411 target = repo.wjoin(abs)
1412 def handle(xlist, dobackup):
1412 def handle(xlist, dobackup):
1413 xlist[0].append(abs)
1413 xlist[0].append(abs)
1414 if (dobackup and not opts.get('no_backup') and
1414 if (dobackup and not opts.get('no_backup') and
1415 os.path.lexists(target)):
1415 os.path.lexists(target)):
1416 bakname = "%s.orig" % rel
1416 bakname = "%s.orig" % rel
1417 ui.note(_('saving current version of %s as %s\n') %
1417 ui.note(_('saving current version of %s as %s\n') %
1418 (rel, bakname))
1418 (rel, bakname))
1419 if not opts.get('dry_run'):
1419 if not opts.get('dry_run'):
1420 util.rename(target, bakname)
1420 util.rename(target, bakname)
1421 if ui.verbose or not exact:
1421 if ui.verbose or not exact:
1422 msg = xlist[1]
1422 msg = xlist[1]
1423 if not isinstance(msg, basestring):
1423 if not isinstance(msg, basestring):
1424 msg = msg(abs)
1424 msg = msg(abs)
1425 ui.status(msg % rel)
1425 ui.status(msg % rel)
1426 for table, hitlist, misslist, backuphit, backupmiss in disptable:
1426 for table, hitlist, misslist, backuphit, backupmiss in disptable:
1427 if abs not in table:
1427 if abs not in table:
1428 continue
1428 continue
1429 # file has changed in dirstate
1429 # file has changed in dirstate
1430 if mfentry:
1430 if mfentry:
1431 handle(hitlist, backuphit)
1431 handle(hitlist, backuphit)
1432 elif misslist is not None:
1432 elif misslist is not None:
1433 handle(misslist, backupmiss)
1433 handle(misslist, backupmiss)
1434 break
1434 break
1435 else:
1435 else:
1436 if abs not in repo.dirstate:
1436 if abs not in repo.dirstate:
1437 if mfentry:
1437 if mfentry:
1438 handle(add, True)
1438 handle(add, True)
1439 elif exact:
1439 elif exact:
1440 ui.warn(_('file not managed: %s\n') % rel)
1440 ui.warn(_('file not managed: %s\n') % rel)
1441 continue
1441 continue
1442 # file has not changed in dirstate
1442 # file has not changed in dirstate
1443 if node == parent:
1443 if node == parent:
1444 if exact:
1444 if exact:
1445 ui.warn(_('no changes needed to %s\n') % rel)
1445 ui.warn(_('no changes needed to %s\n') % rel)
1446 continue
1446 continue
1447 if pmf is None:
1447 if pmf is None:
1448 # only need parent manifest in this unlikely case,
1448 # only need parent manifest in this unlikely case,
1449 # so do not read by default
1449 # so do not read by default
1450 pmf = repo[parent].manifest()
1450 pmf = repo[parent].manifest()
1451 if abs in pmf and mfentry:
1451 if abs in pmf and mfentry:
1452 # if version of file is same in parent and target
1452 # if version of file is same in parent and target
1453 # manifests, do nothing
1453 # manifests, do nothing
1454 if (pmf[abs] != mfentry or
1454 if (pmf[abs] != mfentry or
1455 pmf.flags(abs) != mf.flags(abs)):
1455 pmf.flags(abs) != mf.flags(abs)):
1456 handle(revert, False)
1456 handle(revert, False)
1457 else:
1457 else:
1458 handle(remove, False)
1458 handle(remove, False)
1459
1459
1460 if not opts.get('dry_run'):
1460 if not opts.get('dry_run'):
1461 def checkout(f):
1461 def checkout(f):
1462 fc = ctx[f]
1462 fc = ctx[f]
1463 repo.wwrite(f, fc.data(), fc.flags())
1463 repo.wwrite(f, fc.data(), fc.flags())
1464
1464
1465 audit_path = scmutil.pathauditor(repo.root)
1465 audit_path = scmutil.pathauditor(repo.root)
1466 for f in remove[0]:
1466 for f in remove[0]:
1467 if repo.dirstate[f] == 'a':
1467 if repo.dirstate[f] == 'a':
1468 repo.dirstate.drop(f)
1468 repo.dirstate.drop(f)
1469 continue
1469 continue
1470 audit_path(f)
1470 audit_path(f)
1471 try:
1471 try:
1472 util.unlinkpath(repo.wjoin(f))
1472 util.unlinkpath(repo.wjoin(f))
1473 except OSError:
1473 except OSError:
1474 pass
1474 pass
1475 repo.dirstate.remove(f)
1475 repo.dirstate.remove(f)
1476
1476
1477 normal = None
1477 normal = None
1478 if node == parent:
1478 if node == parent:
1479 # We're reverting to our parent. If possible, we'd like status
1479 # We're reverting to our parent. If possible, we'd like status
1480 # to report the file as clean. We have to use normallookup for
1480 # to report the file as clean. We have to use normallookup for
1481 # merges to avoid losing information about merged/dirty files.
1481 # merges to avoid losing information about merged/dirty files.
1482 if p2 != nullid:
1482 if p2 != nullid:
1483 normal = repo.dirstate.normallookup
1483 normal = repo.dirstate.normallookup
1484 else:
1484 else:
1485 normal = repo.dirstate.normal
1485 normal = repo.dirstate.normal
1486 for f in revert[0]:
1486 for f in revert[0]:
1487 checkout(f)
1487 checkout(f)
1488 if normal:
1488 if normal:
1489 normal(f)
1489 normal(f)
1490
1490
1491 for f in add[0]:
1491 for f in add[0]:
1492 checkout(f)
1492 checkout(f)
1493 repo.dirstate.add(f)
1493 repo.dirstate.add(f)
1494
1494
1495 normal = repo.dirstate.normallookup
1495 normal = repo.dirstate.normallookup
1496 if node == parent and p2 == nullid:
1496 if node == parent and p2 == nullid:
1497 normal = repo.dirstate.normal
1497 normal = repo.dirstate.normal
1498 for f in undelete[0]:
1498 for f in undelete[0]:
1499 checkout(f)
1499 checkout(f)
1500 normal(f)
1500 normal(f)
1501
1501
1502 finally:
1502 finally:
1503 wlock.release()
1503 wlock.release()
1504
1504
1505 def command(table):
1505 def command(table):
1506 '''returns a function object bound to table which can be used as
1506 '''returns a function object bound to table which can be used as
1507 a decorator for populating table as a command table'''
1507 a decorator for populating table as a command table'''
1508
1508
1509 def cmd(name, options, synopsis=None):
1509 def cmd(name, options, synopsis=None):
1510 def decorator(func):
1510 def decorator(func):
1511 if synopsis:
1511 if synopsis:
1512 table[name] = func, options[:], synopsis
1512 table[name] = func, options[:], synopsis
1513 else:
1513 else:
1514 table[name] = func, options[:]
1514 table[name] = func, options[:]
1515 return func
1515 return func
1516 return decorator
1516 return decorator
1517
1517
1518 return cmd
1518 return cmd
General Comments 0
You need to be logged in to leave comments. Login now