##// END OF EJS Templates
workingctx: eliminate remove function...
Adrian Buehlmann -
r14518:a67e866f default
parent child Browse files
Show More
@@ -1,1224 +1,1228
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
11 import util, scmutil, templater, patch, error, templatekw, revlog
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 for e in table.keys():
26 for e in table.keys():
27 aliases = parsealiases(e)
27 aliases = parsealiases(e)
28 found = None
28 found = None
29 if cmd in aliases:
29 if cmd in aliases:
30 found = cmd
30 found = cmd
31 elif not strict:
31 elif not strict:
32 for a in aliases:
32 for a in aliases:
33 if a.startswith(cmd):
33 if a.startswith(cmd):
34 found = a
34 found = a
35 break
35 break
36 if found is not None:
36 if found is not None:
37 if aliases[0].startswith("debug") or found.startswith("debug"):
37 if aliases[0].startswith("debug") or found.startswith("debug"):
38 debugchoice[found] = (aliases, table[e])
38 debugchoice[found] = (aliases, table[e])
39 else:
39 else:
40 choice[found] = (aliases, table[e])
40 choice[found] = (aliases, table[e])
41
41
42 if not choice and debugchoice:
42 if not choice and debugchoice:
43 choice = debugchoice
43 choice = debugchoice
44
44
45 return choice
45 return choice
46
46
47 def findcmd(cmd, table, strict=True):
47 def findcmd(cmd, table, strict=True):
48 """Return (aliases, command table entry) for command string."""
48 """Return (aliases, command table entry) for command string."""
49 choice = findpossible(cmd, table, strict)
49 choice = findpossible(cmd, table, strict)
50
50
51 if cmd in choice:
51 if cmd in choice:
52 return choice[cmd]
52 return choice[cmd]
53
53
54 if len(choice) > 1:
54 if len(choice) > 1:
55 clist = choice.keys()
55 clist = choice.keys()
56 clist.sort()
56 clist.sort()
57 raise error.AmbiguousCommand(cmd, clist)
57 raise error.AmbiguousCommand(cmd, clist)
58
58
59 if choice:
59 if choice:
60 return choice.values()[0]
60 return choice.values()[0]
61
61
62 raise error.UnknownCommand(cmd)
62 raise error.UnknownCommand(cmd)
63
63
64 def findrepo(p):
64 def findrepo(p):
65 while not os.path.isdir(os.path.join(p, ".hg")):
65 while not os.path.isdir(os.path.join(p, ".hg")):
66 oldp, p = p, os.path.dirname(p)
66 oldp, p = p, os.path.dirname(p)
67 if p == oldp:
67 if p == oldp:
68 return None
68 return None
69
69
70 return p
70 return p
71
71
72 def bailifchanged(repo):
72 def bailifchanged(repo):
73 if repo.dirstate.p2() != nullid:
73 if repo.dirstate.p2() != nullid:
74 raise util.Abort(_('outstanding uncommitted merge'))
74 raise util.Abort(_('outstanding uncommitted merge'))
75 modified, added, removed, deleted = repo.status()[:4]
75 modified, added, removed, deleted = repo.status()[:4]
76 if modified or added or removed or deleted:
76 if modified or added or removed or deleted:
77 raise util.Abort(_("outstanding uncommitted changes"))
77 raise util.Abort(_("outstanding uncommitted changes"))
78
78
79 def logmessage(opts):
79 def logmessage(opts):
80 """ get the log message according to -m and -l option """
80 """ get the log message according to -m and -l option """
81 message = opts.get('message')
81 message = opts.get('message')
82 logfile = opts.get('logfile')
82 logfile = opts.get('logfile')
83
83
84 if message and logfile:
84 if message and logfile:
85 raise util.Abort(_('options --message and --logfile are mutually '
85 raise util.Abort(_('options --message and --logfile are mutually '
86 'exclusive'))
86 'exclusive'))
87 if not message and logfile:
87 if not message and logfile:
88 try:
88 try:
89 if logfile == '-':
89 if logfile == '-':
90 message = sys.stdin.read()
90 message = sys.stdin.read()
91 else:
91 else:
92 message = '\n'.join(util.readfile(logfile).splitlines())
92 message = '\n'.join(util.readfile(logfile).splitlines())
93 except IOError, inst:
93 except IOError, inst:
94 raise util.Abort(_("can't read commit message '%s': %s") %
94 raise util.Abort(_("can't read commit message '%s': %s") %
95 (logfile, inst.strerror))
95 (logfile, inst.strerror))
96 return message
96 return message
97
97
98 def loglimit(opts):
98 def loglimit(opts):
99 """get the log limit according to option -l/--limit"""
99 """get the log limit according to option -l/--limit"""
100 limit = opts.get('limit')
100 limit = opts.get('limit')
101 if limit:
101 if limit:
102 try:
102 try:
103 limit = int(limit)
103 limit = int(limit)
104 except ValueError:
104 except ValueError:
105 raise util.Abort(_('limit must be a positive integer'))
105 raise util.Abort(_('limit must be a positive integer'))
106 if limit <= 0:
106 if limit <= 0:
107 raise util.Abort(_('limit must be positive'))
107 raise util.Abort(_('limit must be positive'))
108 else:
108 else:
109 limit = None
109 limit = None
110 return limit
110 return limit
111
111
112 def makefilename(repo, pat, node,
112 def makefilename(repo, pat, node,
113 total=None, seqno=None, revwidth=None, pathname=None):
113 total=None, seqno=None, revwidth=None, pathname=None):
114 node_expander = {
114 node_expander = {
115 'H': lambda: hex(node),
115 'H': lambda: hex(node),
116 'R': lambda: str(repo.changelog.rev(node)),
116 'R': lambda: str(repo.changelog.rev(node)),
117 'h': lambda: short(node),
117 'h': lambda: short(node),
118 }
118 }
119 expander = {
119 expander = {
120 '%': lambda: '%',
120 '%': lambda: '%',
121 'b': lambda: os.path.basename(repo.root),
121 'b': lambda: os.path.basename(repo.root),
122 }
122 }
123
123
124 try:
124 try:
125 if node:
125 if node:
126 expander.update(node_expander)
126 expander.update(node_expander)
127 if node:
127 if node:
128 expander['r'] = (lambda:
128 expander['r'] = (lambda:
129 str(repo.changelog.rev(node)).zfill(revwidth or 0))
129 str(repo.changelog.rev(node)).zfill(revwidth or 0))
130 if total is not None:
130 if total is not None:
131 expander['N'] = lambda: str(total)
131 expander['N'] = lambda: str(total)
132 if seqno is not None:
132 if seqno is not None:
133 expander['n'] = lambda: str(seqno)
133 expander['n'] = lambda: str(seqno)
134 if total is not None and seqno is not None:
134 if total is not None and seqno is not None:
135 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
135 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
136 if pathname is not None:
136 if pathname is not None:
137 expander['s'] = lambda: os.path.basename(pathname)
137 expander['s'] = lambda: os.path.basename(pathname)
138 expander['d'] = lambda: os.path.dirname(pathname) or '.'
138 expander['d'] = lambda: os.path.dirname(pathname) or '.'
139 expander['p'] = lambda: pathname
139 expander['p'] = lambda: pathname
140
140
141 newname = []
141 newname = []
142 patlen = len(pat)
142 patlen = len(pat)
143 i = 0
143 i = 0
144 while i < patlen:
144 while i < patlen:
145 c = pat[i]
145 c = pat[i]
146 if c == '%':
146 if c == '%':
147 i += 1
147 i += 1
148 c = pat[i]
148 c = pat[i]
149 c = expander[c]()
149 c = expander[c]()
150 newname.append(c)
150 newname.append(c)
151 i += 1
151 i += 1
152 return ''.join(newname)
152 return ''.join(newname)
153 except KeyError, inst:
153 except KeyError, inst:
154 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
154 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
155 inst.args[0])
155 inst.args[0])
156
156
157 def makefileobj(repo, pat, node=None, total=None,
157 def makefileobj(repo, pat, node=None, total=None,
158 seqno=None, revwidth=None, mode='wb', pathname=None):
158 seqno=None, revwidth=None, mode='wb', pathname=None):
159
159
160 writable = mode not in ('r', 'rb')
160 writable = mode not in ('r', 'rb')
161
161
162 if not pat or pat == '-':
162 if not pat or pat == '-':
163 fp = writable and sys.stdout or sys.stdin
163 fp = writable and sys.stdout or sys.stdin
164 return os.fdopen(os.dup(fp.fileno()), mode)
164 return os.fdopen(os.dup(fp.fileno()), mode)
165 if hasattr(pat, 'write') and writable:
165 if hasattr(pat, 'write') and writable:
166 return pat
166 return pat
167 if hasattr(pat, 'read') and 'r' in mode:
167 if hasattr(pat, 'read') and 'r' in mode:
168 return pat
168 return pat
169 return open(makefilename(repo, pat, node, total, seqno, revwidth,
169 return open(makefilename(repo, pat, node, total, seqno, revwidth,
170 pathname),
170 pathname),
171 mode)
171 mode)
172
172
173 def openrevlog(repo, cmd, file_, opts):
173 def openrevlog(repo, cmd, file_, opts):
174 """opens the changelog, manifest, a filelog or a given revlog"""
174 """opens the changelog, manifest, a filelog or a given revlog"""
175 cl = opts['changelog']
175 cl = opts['changelog']
176 mf = opts['manifest']
176 mf = opts['manifest']
177 msg = None
177 msg = None
178 if cl and mf:
178 if cl and mf:
179 msg = _('cannot specify --changelog and --manifest at the same time')
179 msg = _('cannot specify --changelog and --manifest at the same time')
180 elif cl or mf:
180 elif cl or mf:
181 if file_:
181 if file_:
182 msg = _('cannot specify filename with --changelog or --manifest')
182 msg = _('cannot specify filename with --changelog or --manifest')
183 elif not repo:
183 elif not repo:
184 msg = _('cannot specify --changelog or --manifest '
184 msg = _('cannot specify --changelog or --manifest '
185 'without a repository')
185 'without a repository')
186 if msg:
186 if msg:
187 raise util.Abort(msg)
187 raise util.Abort(msg)
188
188
189 r = None
189 r = None
190 if repo:
190 if repo:
191 if cl:
191 if cl:
192 r = repo.changelog
192 r = repo.changelog
193 elif mf:
193 elif mf:
194 r = repo.manifest
194 r = repo.manifest
195 elif file_:
195 elif file_:
196 filelog = repo.file(file_)
196 filelog = repo.file(file_)
197 if len(filelog):
197 if len(filelog):
198 r = filelog
198 r = filelog
199 if not r:
199 if not r:
200 if not file_:
200 if not file_:
201 raise error.CommandError(cmd, _('invalid arguments'))
201 raise error.CommandError(cmd, _('invalid arguments'))
202 if not os.path.isfile(file_):
202 if not os.path.isfile(file_):
203 raise util.Abort(_("revlog '%s' not found") % file_)
203 raise util.Abort(_("revlog '%s' not found") % file_)
204 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
204 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
205 file_[:-2] + ".i")
205 file_[:-2] + ".i")
206 return r
206 return r
207
207
208 def copy(ui, repo, pats, opts, rename=False):
208 def copy(ui, repo, pats, opts, rename=False):
209 # called with the repo lock held
209 # called with the repo lock held
210 #
210 #
211 # hgsep => pathname that uses "/" to separate directories
211 # hgsep => pathname that uses "/" to separate directories
212 # ossep => pathname that uses os.sep to separate directories
212 # ossep => pathname that uses os.sep to separate directories
213 cwd = repo.getcwd()
213 cwd = repo.getcwd()
214 targets = {}
214 targets = {}
215 after = opts.get("after")
215 after = opts.get("after")
216 dryrun = opts.get("dry_run")
216 dryrun = opts.get("dry_run")
217 wctx = repo[None]
217 wctx = repo[None]
218
218
219 def walkpat(pat):
219 def walkpat(pat):
220 srcs = []
220 srcs = []
221 badstates = after and '?' or '?r'
221 badstates = after and '?' or '?r'
222 m = scmutil.match(repo, [pat], opts, globbed=True)
222 m = scmutil.match(repo, [pat], opts, globbed=True)
223 for abs in repo.walk(m):
223 for abs in repo.walk(m):
224 state = repo.dirstate[abs]
224 state = repo.dirstate[abs]
225 rel = m.rel(abs)
225 rel = m.rel(abs)
226 exact = m.exact(abs)
226 exact = m.exact(abs)
227 if state in badstates:
227 if state in badstates:
228 if exact and state == '?':
228 if exact and state == '?':
229 ui.warn(_('%s: not copying - file is not managed\n') % rel)
229 ui.warn(_('%s: not copying - file is not managed\n') % rel)
230 if exact and state == 'r':
230 if exact and state == 'r':
231 ui.warn(_('%s: not copying - file has been marked for'
231 ui.warn(_('%s: not copying - file has been marked for'
232 ' remove\n') % rel)
232 ' remove\n') % rel)
233 continue
233 continue
234 # abs: hgsep
234 # abs: hgsep
235 # rel: ossep
235 # rel: ossep
236 srcs.append((abs, rel, exact))
236 srcs.append((abs, rel, exact))
237 return srcs
237 return srcs
238
238
239 # abssrc: hgsep
239 # abssrc: hgsep
240 # relsrc: ossep
240 # relsrc: ossep
241 # otarget: ossep
241 # otarget: ossep
242 def copyfile(abssrc, relsrc, otarget, exact):
242 def copyfile(abssrc, relsrc, otarget, exact):
243 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
243 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
244 reltarget = repo.pathto(abstarget, cwd)
244 reltarget = repo.pathto(abstarget, cwd)
245 target = repo.wjoin(abstarget)
245 target = repo.wjoin(abstarget)
246 src = repo.wjoin(abssrc)
246 src = repo.wjoin(abssrc)
247 state = repo.dirstate[abstarget]
247 state = repo.dirstate[abstarget]
248
248
249 scmutil.checkportable(ui, abstarget)
249 scmutil.checkportable(ui, abstarget)
250
250
251 # check for collisions
251 # check for collisions
252 prevsrc = targets.get(abstarget)
252 prevsrc = targets.get(abstarget)
253 if prevsrc is not None:
253 if prevsrc is not None:
254 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
254 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
255 (reltarget, repo.pathto(abssrc, cwd),
255 (reltarget, repo.pathto(abssrc, cwd),
256 repo.pathto(prevsrc, cwd)))
256 repo.pathto(prevsrc, cwd)))
257 return
257 return
258
258
259 # check for overwrites
259 # check for overwrites
260 exists = os.path.lexists(target)
260 exists = os.path.lexists(target)
261 if not after and exists or after and state in 'mn':
261 if not after and exists or after and state in 'mn':
262 if not opts['force']:
262 if not opts['force']:
263 ui.warn(_('%s: not overwriting - file exists\n') %
263 ui.warn(_('%s: not overwriting - file exists\n') %
264 reltarget)
264 reltarget)
265 return
265 return
266
266
267 if after:
267 if after:
268 if not exists:
268 if not exists:
269 if rename:
269 if rename:
270 ui.warn(_('%s: not recording move - %s does not exist\n') %
270 ui.warn(_('%s: not recording move - %s does not exist\n') %
271 (relsrc, reltarget))
271 (relsrc, reltarget))
272 else:
272 else:
273 ui.warn(_('%s: not recording copy - %s does not exist\n') %
273 ui.warn(_('%s: not recording copy - %s does not exist\n') %
274 (relsrc, reltarget))
274 (relsrc, reltarget))
275 return
275 return
276 elif not dryrun:
276 elif not dryrun:
277 try:
277 try:
278 if exists:
278 if exists:
279 os.unlink(target)
279 os.unlink(target)
280 targetdir = os.path.dirname(target) or '.'
280 targetdir = os.path.dirname(target) or '.'
281 if not os.path.isdir(targetdir):
281 if not os.path.isdir(targetdir):
282 os.makedirs(targetdir)
282 os.makedirs(targetdir)
283 util.copyfile(src, target)
283 util.copyfile(src, target)
284 srcexists = True
284 except IOError, inst:
285 except IOError, inst:
285 if inst.errno == errno.ENOENT:
286 if inst.errno == errno.ENOENT:
286 ui.warn(_('%s: deleted in working copy\n') % relsrc)
287 ui.warn(_('%s: deleted in working copy\n') % relsrc)
288 srcexists = False
287 else:
289 else:
288 ui.warn(_('%s: cannot copy - %s\n') %
290 ui.warn(_('%s: cannot copy - %s\n') %
289 (relsrc, inst.strerror))
291 (relsrc, inst.strerror))
290 return True # report a failure
292 return True # report a failure
291
293
292 if ui.verbose or not exact:
294 if ui.verbose or not exact:
293 if rename:
295 if rename:
294 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
296 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
295 else:
297 else:
296 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
298 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
297
299
298 targets[abstarget] = abssrc
300 targets[abstarget] = abssrc
299
301
300 # fix up dirstate
302 # fix up dirstate
301 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
303 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
302 dryrun=dryrun, cwd=cwd)
304 dryrun=dryrun, cwd=cwd)
303 if rename and not dryrun:
305 if rename and not dryrun:
304 wctx.remove([abssrc], not after)
306 if not after and srcexists:
307 util.unlinkpath(repo.wjoin(abssrc))
308 wctx.forget([abssrc])
305
309
306 # pat: ossep
310 # pat: ossep
307 # dest ossep
311 # dest ossep
308 # srcs: list of (hgsep, hgsep, ossep, bool)
312 # srcs: list of (hgsep, hgsep, ossep, bool)
309 # return: function that takes hgsep and returns ossep
313 # return: function that takes hgsep and returns ossep
310 def targetpathfn(pat, dest, srcs):
314 def targetpathfn(pat, dest, srcs):
311 if os.path.isdir(pat):
315 if os.path.isdir(pat):
312 abspfx = scmutil.canonpath(repo.root, cwd, pat)
316 abspfx = scmutil.canonpath(repo.root, cwd, pat)
313 abspfx = util.localpath(abspfx)
317 abspfx = util.localpath(abspfx)
314 if destdirexists:
318 if destdirexists:
315 striplen = len(os.path.split(abspfx)[0])
319 striplen = len(os.path.split(abspfx)[0])
316 else:
320 else:
317 striplen = len(abspfx)
321 striplen = len(abspfx)
318 if striplen:
322 if striplen:
319 striplen += len(os.sep)
323 striplen += len(os.sep)
320 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
324 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
321 elif destdirexists:
325 elif destdirexists:
322 res = lambda p: os.path.join(dest,
326 res = lambda p: os.path.join(dest,
323 os.path.basename(util.localpath(p)))
327 os.path.basename(util.localpath(p)))
324 else:
328 else:
325 res = lambda p: dest
329 res = lambda p: dest
326 return res
330 return res
327
331
328 # pat: ossep
332 # pat: ossep
329 # dest ossep
333 # dest ossep
330 # srcs: list of (hgsep, hgsep, ossep, bool)
334 # srcs: list of (hgsep, hgsep, ossep, bool)
331 # return: function that takes hgsep and returns ossep
335 # return: function that takes hgsep and returns ossep
332 def targetpathafterfn(pat, dest, srcs):
336 def targetpathafterfn(pat, dest, srcs):
333 if matchmod.patkind(pat):
337 if matchmod.patkind(pat):
334 # a mercurial pattern
338 # a mercurial pattern
335 res = lambda p: os.path.join(dest,
339 res = lambda p: os.path.join(dest,
336 os.path.basename(util.localpath(p)))
340 os.path.basename(util.localpath(p)))
337 else:
341 else:
338 abspfx = scmutil.canonpath(repo.root, cwd, pat)
342 abspfx = scmutil.canonpath(repo.root, cwd, pat)
339 if len(abspfx) < len(srcs[0][0]):
343 if len(abspfx) < len(srcs[0][0]):
340 # A directory. Either the target path contains the last
344 # A directory. Either the target path contains the last
341 # component of the source path or it does not.
345 # component of the source path or it does not.
342 def evalpath(striplen):
346 def evalpath(striplen):
343 score = 0
347 score = 0
344 for s in srcs:
348 for s in srcs:
345 t = os.path.join(dest, util.localpath(s[0])[striplen:])
349 t = os.path.join(dest, util.localpath(s[0])[striplen:])
346 if os.path.lexists(t):
350 if os.path.lexists(t):
347 score += 1
351 score += 1
348 return score
352 return score
349
353
350 abspfx = util.localpath(abspfx)
354 abspfx = util.localpath(abspfx)
351 striplen = len(abspfx)
355 striplen = len(abspfx)
352 if striplen:
356 if striplen:
353 striplen += len(os.sep)
357 striplen += len(os.sep)
354 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
358 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
355 score = evalpath(striplen)
359 score = evalpath(striplen)
356 striplen1 = len(os.path.split(abspfx)[0])
360 striplen1 = len(os.path.split(abspfx)[0])
357 if striplen1:
361 if striplen1:
358 striplen1 += len(os.sep)
362 striplen1 += len(os.sep)
359 if evalpath(striplen1) > score:
363 if evalpath(striplen1) > score:
360 striplen = striplen1
364 striplen = striplen1
361 res = lambda p: os.path.join(dest,
365 res = lambda p: os.path.join(dest,
362 util.localpath(p)[striplen:])
366 util.localpath(p)[striplen:])
363 else:
367 else:
364 # a file
368 # a file
365 if destdirexists:
369 if destdirexists:
366 res = lambda p: os.path.join(dest,
370 res = lambda p: os.path.join(dest,
367 os.path.basename(util.localpath(p)))
371 os.path.basename(util.localpath(p)))
368 else:
372 else:
369 res = lambda p: dest
373 res = lambda p: dest
370 return res
374 return res
371
375
372
376
373 pats = scmutil.expandpats(pats)
377 pats = scmutil.expandpats(pats)
374 if not pats:
378 if not pats:
375 raise util.Abort(_('no source or destination specified'))
379 raise util.Abort(_('no source or destination specified'))
376 if len(pats) == 1:
380 if len(pats) == 1:
377 raise util.Abort(_('no destination specified'))
381 raise util.Abort(_('no destination specified'))
378 dest = pats.pop()
382 dest = pats.pop()
379 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
383 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
380 if not destdirexists:
384 if not destdirexists:
381 if len(pats) > 1 or matchmod.patkind(pats[0]):
385 if len(pats) > 1 or matchmod.patkind(pats[0]):
382 raise util.Abort(_('with multiple sources, destination must be an '
386 raise util.Abort(_('with multiple sources, destination must be an '
383 'existing directory'))
387 'existing directory'))
384 if util.endswithsep(dest):
388 if util.endswithsep(dest):
385 raise util.Abort(_('destination %s is not a directory') % dest)
389 raise util.Abort(_('destination %s is not a directory') % dest)
386
390
387 tfn = targetpathfn
391 tfn = targetpathfn
388 if after:
392 if after:
389 tfn = targetpathafterfn
393 tfn = targetpathafterfn
390 copylist = []
394 copylist = []
391 for pat in pats:
395 for pat in pats:
392 srcs = walkpat(pat)
396 srcs = walkpat(pat)
393 if not srcs:
397 if not srcs:
394 continue
398 continue
395 copylist.append((tfn(pat, dest, srcs), srcs))
399 copylist.append((tfn(pat, dest, srcs), srcs))
396 if not copylist:
400 if not copylist:
397 raise util.Abort(_('no files to copy'))
401 raise util.Abort(_('no files to copy'))
398
402
399 errors = 0
403 errors = 0
400 for targetpath, srcs in copylist:
404 for targetpath, srcs in copylist:
401 for abssrc, relsrc, exact in srcs:
405 for abssrc, relsrc, exact in srcs:
402 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
406 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
403 errors += 1
407 errors += 1
404
408
405 if errors:
409 if errors:
406 ui.warn(_('(consider using --after)\n'))
410 ui.warn(_('(consider using --after)\n'))
407
411
408 return errors != 0
412 return errors != 0
409
413
410 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
414 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
411 runargs=None, appendpid=False):
415 runargs=None, appendpid=False):
412 '''Run a command as a service.'''
416 '''Run a command as a service.'''
413
417
414 if opts['daemon'] and not opts['daemon_pipefds']:
418 if opts['daemon'] and not opts['daemon_pipefds']:
415 # Signal child process startup with file removal
419 # Signal child process startup with file removal
416 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
420 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
417 os.close(lockfd)
421 os.close(lockfd)
418 try:
422 try:
419 if not runargs:
423 if not runargs:
420 runargs = util.hgcmd() + sys.argv[1:]
424 runargs = util.hgcmd() + sys.argv[1:]
421 runargs.append('--daemon-pipefds=%s' % lockpath)
425 runargs.append('--daemon-pipefds=%s' % lockpath)
422 # Don't pass --cwd to the child process, because we've already
426 # Don't pass --cwd to the child process, because we've already
423 # changed directory.
427 # changed directory.
424 for i in xrange(1, len(runargs)):
428 for i in xrange(1, len(runargs)):
425 if runargs[i].startswith('--cwd='):
429 if runargs[i].startswith('--cwd='):
426 del runargs[i]
430 del runargs[i]
427 break
431 break
428 elif runargs[i].startswith('--cwd'):
432 elif runargs[i].startswith('--cwd'):
429 del runargs[i:i + 2]
433 del runargs[i:i + 2]
430 break
434 break
431 def condfn():
435 def condfn():
432 return not os.path.exists(lockpath)
436 return not os.path.exists(lockpath)
433 pid = util.rundetached(runargs, condfn)
437 pid = util.rundetached(runargs, condfn)
434 if pid < 0:
438 if pid < 0:
435 raise util.Abort(_('child process failed to start'))
439 raise util.Abort(_('child process failed to start'))
436 finally:
440 finally:
437 try:
441 try:
438 os.unlink(lockpath)
442 os.unlink(lockpath)
439 except OSError, e:
443 except OSError, e:
440 if e.errno != errno.ENOENT:
444 if e.errno != errno.ENOENT:
441 raise
445 raise
442 if parentfn:
446 if parentfn:
443 return parentfn(pid)
447 return parentfn(pid)
444 else:
448 else:
445 return
449 return
446
450
447 if initfn:
451 if initfn:
448 initfn()
452 initfn()
449
453
450 if opts['pid_file']:
454 if opts['pid_file']:
451 mode = appendpid and 'a' or 'w'
455 mode = appendpid and 'a' or 'w'
452 fp = open(opts['pid_file'], mode)
456 fp = open(opts['pid_file'], mode)
453 fp.write(str(os.getpid()) + '\n')
457 fp.write(str(os.getpid()) + '\n')
454 fp.close()
458 fp.close()
455
459
456 if opts['daemon_pipefds']:
460 if opts['daemon_pipefds']:
457 lockpath = opts['daemon_pipefds']
461 lockpath = opts['daemon_pipefds']
458 try:
462 try:
459 os.setsid()
463 os.setsid()
460 except AttributeError:
464 except AttributeError:
461 pass
465 pass
462 os.unlink(lockpath)
466 os.unlink(lockpath)
463 util.hidewindow()
467 util.hidewindow()
464 sys.stdout.flush()
468 sys.stdout.flush()
465 sys.stderr.flush()
469 sys.stderr.flush()
466
470
467 nullfd = os.open(util.nulldev, os.O_RDWR)
471 nullfd = os.open(util.nulldev, os.O_RDWR)
468 logfilefd = nullfd
472 logfilefd = nullfd
469 if logfile:
473 if logfile:
470 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
474 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
471 os.dup2(nullfd, 0)
475 os.dup2(nullfd, 0)
472 os.dup2(logfilefd, 1)
476 os.dup2(logfilefd, 1)
473 os.dup2(logfilefd, 2)
477 os.dup2(logfilefd, 2)
474 if nullfd not in (0, 1, 2):
478 if nullfd not in (0, 1, 2):
475 os.close(nullfd)
479 os.close(nullfd)
476 if logfile and logfilefd not in (0, 1, 2):
480 if logfile and logfilefd not in (0, 1, 2):
477 os.close(logfilefd)
481 os.close(logfilefd)
478
482
479 if runfn:
483 if runfn:
480 return runfn()
484 return runfn()
481
485
482 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
486 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
483 opts=None):
487 opts=None):
484 '''export changesets as hg patches.'''
488 '''export changesets as hg patches.'''
485
489
486 total = len(revs)
490 total = len(revs)
487 revwidth = max([len(str(rev)) for rev in revs])
491 revwidth = max([len(str(rev)) for rev in revs])
488
492
489 def single(rev, seqno, fp):
493 def single(rev, seqno, fp):
490 ctx = repo[rev]
494 ctx = repo[rev]
491 node = ctx.node()
495 node = ctx.node()
492 parents = [p.node() for p in ctx.parents() if p]
496 parents = [p.node() for p in ctx.parents() if p]
493 branch = ctx.branch()
497 branch = ctx.branch()
494 if switch_parent:
498 if switch_parent:
495 parents.reverse()
499 parents.reverse()
496 prev = (parents and parents[0]) or nullid
500 prev = (parents and parents[0]) or nullid
497
501
498 shouldclose = False
502 shouldclose = False
499 if not fp:
503 if not fp:
500 fp = makefileobj(repo, template, node, total=total, seqno=seqno,
504 fp = makefileobj(repo, template, node, total=total, seqno=seqno,
501 revwidth=revwidth, mode='ab')
505 revwidth=revwidth, mode='ab')
502 if fp != template:
506 if fp != template:
503 shouldclose = True
507 shouldclose = True
504 if fp != sys.stdout and hasattr(fp, 'name'):
508 if fp != sys.stdout and hasattr(fp, 'name'):
505 repo.ui.note("%s\n" % fp.name)
509 repo.ui.note("%s\n" % fp.name)
506
510
507 fp.write("# HG changeset patch\n")
511 fp.write("# HG changeset patch\n")
508 fp.write("# User %s\n" % ctx.user())
512 fp.write("# User %s\n" % ctx.user())
509 fp.write("# Date %d %d\n" % ctx.date())
513 fp.write("# Date %d %d\n" % ctx.date())
510 if branch and branch != 'default':
514 if branch and branch != 'default':
511 fp.write("# Branch %s\n" % branch)
515 fp.write("# Branch %s\n" % branch)
512 fp.write("# Node ID %s\n" % hex(node))
516 fp.write("# Node ID %s\n" % hex(node))
513 fp.write("# Parent %s\n" % hex(prev))
517 fp.write("# Parent %s\n" % hex(prev))
514 if len(parents) > 1:
518 if len(parents) > 1:
515 fp.write("# Parent %s\n" % hex(parents[1]))
519 fp.write("# Parent %s\n" % hex(parents[1]))
516 fp.write(ctx.description().rstrip())
520 fp.write(ctx.description().rstrip())
517 fp.write("\n\n")
521 fp.write("\n\n")
518
522
519 for chunk in patch.diff(repo, prev, node, opts=opts):
523 for chunk in patch.diff(repo, prev, node, opts=opts):
520 fp.write(chunk)
524 fp.write(chunk)
521
525
522 if shouldclose:
526 if shouldclose:
523 fp.close()
527 fp.close()
524
528
525 for seqno, rev in enumerate(revs):
529 for seqno, rev in enumerate(revs):
526 single(rev, seqno + 1, fp)
530 single(rev, seqno + 1, fp)
527
531
528 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
532 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
529 changes=None, stat=False, fp=None, prefix='',
533 changes=None, stat=False, fp=None, prefix='',
530 listsubrepos=False):
534 listsubrepos=False):
531 '''show diff or diffstat.'''
535 '''show diff or diffstat.'''
532 if fp is None:
536 if fp is None:
533 write = ui.write
537 write = ui.write
534 else:
538 else:
535 def write(s, **kw):
539 def write(s, **kw):
536 fp.write(s)
540 fp.write(s)
537
541
538 if stat:
542 if stat:
539 diffopts = diffopts.copy(context=0)
543 diffopts = diffopts.copy(context=0)
540 width = 80
544 width = 80
541 if not ui.plain():
545 if not ui.plain():
542 width = ui.termwidth()
546 width = ui.termwidth()
543 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
547 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
544 prefix=prefix)
548 prefix=prefix)
545 for chunk, label in patch.diffstatui(util.iterlines(chunks),
549 for chunk, label in patch.diffstatui(util.iterlines(chunks),
546 width=width,
550 width=width,
547 git=diffopts.git):
551 git=diffopts.git):
548 write(chunk, label=label)
552 write(chunk, label=label)
549 else:
553 else:
550 for chunk, label in patch.diffui(repo, node1, node2, match,
554 for chunk, label in patch.diffui(repo, node1, node2, match,
551 changes, diffopts, prefix=prefix):
555 changes, diffopts, prefix=prefix):
552 write(chunk, label=label)
556 write(chunk, label=label)
553
557
554 if listsubrepos:
558 if listsubrepos:
555 ctx1 = repo[node1]
559 ctx1 = repo[node1]
556 ctx2 = repo[node2]
560 ctx2 = repo[node2]
557 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
561 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
558 if node2 is not None:
562 if node2 is not None:
559 node2 = ctx2.substate[subpath][1]
563 node2 = ctx2.substate[subpath][1]
560 submatch = matchmod.narrowmatcher(subpath, match)
564 submatch = matchmod.narrowmatcher(subpath, match)
561 sub.diff(diffopts, node2, submatch, changes=changes,
565 sub.diff(diffopts, node2, submatch, changes=changes,
562 stat=stat, fp=fp, prefix=prefix)
566 stat=stat, fp=fp, prefix=prefix)
563
567
564 class changeset_printer(object):
568 class changeset_printer(object):
565 '''show changeset information when templating not requested.'''
569 '''show changeset information when templating not requested.'''
566
570
567 def __init__(self, ui, repo, patch, diffopts, buffered):
571 def __init__(self, ui, repo, patch, diffopts, buffered):
568 self.ui = ui
572 self.ui = ui
569 self.repo = repo
573 self.repo = repo
570 self.buffered = buffered
574 self.buffered = buffered
571 self.patch = patch
575 self.patch = patch
572 self.diffopts = diffopts
576 self.diffopts = diffopts
573 self.header = {}
577 self.header = {}
574 self.hunk = {}
578 self.hunk = {}
575 self.lastheader = None
579 self.lastheader = None
576 self.footer = None
580 self.footer = None
577
581
578 def flush(self, rev):
582 def flush(self, rev):
579 if rev in self.header:
583 if rev in self.header:
580 h = self.header[rev]
584 h = self.header[rev]
581 if h != self.lastheader:
585 if h != self.lastheader:
582 self.lastheader = h
586 self.lastheader = h
583 self.ui.write(h)
587 self.ui.write(h)
584 del self.header[rev]
588 del self.header[rev]
585 if rev in self.hunk:
589 if rev in self.hunk:
586 self.ui.write(self.hunk[rev])
590 self.ui.write(self.hunk[rev])
587 del self.hunk[rev]
591 del self.hunk[rev]
588 return 1
592 return 1
589 return 0
593 return 0
590
594
591 def close(self):
595 def close(self):
592 if self.footer:
596 if self.footer:
593 self.ui.write(self.footer)
597 self.ui.write(self.footer)
594
598
595 def show(self, ctx, copies=None, matchfn=None, **props):
599 def show(self, ctx, copies=None, matchfn=None, **props):
596 if self.buffered:
600 if self.buffered:
597 self.ui.pushbuffer()
601 self.ui.pushbuffer()
598 self._show(ctx, copies, matchfn, props)
602 self._show(ctx, copies, matchfn, props)
599 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
603 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
600 else:
604 else:
601 self._show(ctx, copies, matchfn, props)
605 self._show(ctx, copies, matchfn, props)
602
606
603 def _show(self, ctx, copies, matchfn, props):
607 def _show(self, ctx, copies, matchfn, props):
604 '''show a single changeset or file revision'''
608 '''show a single changeset or file revision'''
605 changenode = ctx.node()
609 changenode = ctx.node()
606 rev = ctx.rev()
610 rev = ctx.rev()
607
611
608 if self.ui.quiet:
612 if self.ui.quiet:
609 self.ui.write("%d:%s\n" % (rev, short(changenode)),
613 self.ui.write("%d:%s\n" % (rev, short(changenode)),
610 label='log.node')
614 label='log.node')
611 return
615 return
612
616
613 log = self.repo.changelog
617 log = self.repo.changelog
614 date = util.datestr(ctx.date())
618 date = util.datestr(ctx.date())
615
619
616 hexfunc = self.ui.debugflag and hex or short
620 hexfunc = self.ui.debugflag and hex or short
617
621
618 parents = [(p, hexfunc(log.node(p)))
622 parents = [(p, hexfunc(log.node(p)))
619 for p in self._meaningful_parentrevs(log, rev)]
623 for p in self._meaningful_parentrevs(log, rev)]
620
624
621 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
625 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
622 label='log.changeset')
626 label='log.changeset')
623
627
624 branch = ctx.branch()
628 branch = ctx.branch()
625 # don't show the default branch name
629 # don't show the default branch name
626 if branch != 'default':
630 if branch != 'default':
627 self.ui.write(_("branch: %s\n") % branch,
631 self.ui.write(_("branch: %s\n") % branch,
628 label='log.branch')
632 label='log.branch')
629 for bookmark in self.repo.nodebookmarks(changenode):
633 for bookmark in self.repo.nodebookmarks(changenode):
630 self.ui.write(_("bookmark: %s\n") % bookmark,
634 self.ui.write(_("bookmark: %s\n") % bookmark,
631 label='log.bookmark')
635 label='log.bookmark')
632 for tag in self.repo.nodetags(changenode):
636 for tag in self.repo.nodetags(changenode):
633 self.ui.write(_("tag: %s\n") % tag,
637 self.ui.write(_("tag: %s\n") % tag,
634 label='log.tag')
638 label='log.tag')
635 for parent in parents:
639 for parent in parents:
636 self.ui.write(_("parent: %d:%s\n") % parent,
640 self.ui.write(_("parent: %d:%s\n") % parent,
637 label='log.parent')
641 label='log.parent')
638
642
639 if self.ui.debugflag:
643 if self.ui.debugflag:
640 mnode = ctx.manifestnode()
644 mnode = ctx.manifestnode()
641 self.ui.write(_("manifest: %d:%s\n") %
645 self.ui.write(_("manifest: %d:%s\n") %
642 (self.repo.manifest.rev(mnode), hex(mnode)),
646 (self.repo.manifest.rev(mnode), hex(mnode)),
643 label='ui.debug log.manifest')
647 label='ui.debug log.manifest')
644 self.ui.write(_("user: %s\n") % ctx.user(),
648 self.ui.write(_("user: %s\n") % ctx.user(),
645 label='log.user')
649 label='log.user')
646 self.ui.write(_("date: %s\n") % date,
650 self.ui.write(_("date: %s\n") % date,
647 label='log.date')
651 label='log.date')
648
652
649 if self.ui.debugflag:
653 if self.ui.debugflag:
650 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
654 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
651 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
655 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
652 files):
656 files):
653 if value:
657 if value:
654 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
658 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
655 label='ui.debug log.files')
659 label='ui.debug log.files')
656 elif ctx.files() and self.ui.verbose:
660 elif ctx.files() and self.ui.verbose:
657 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
661 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
658 label='ui.note log.files')
662 label='ui.note log.files')
659 if copies and self.ui.verbose:
663 if copies and self.ui.verbose:
660 copies = ['%s (%s)' % c for c in copies]
664 copies = ['%s (%s)' % c for c in copies]
661 self.ui.write(_("copies: %s\n") % ' '.join(copies),
665 self.ui.write(_("copies: %s\n") % ' '.join(copies),
662 label='ui.note log.copies')
666 label='ui.note log.copies')
663
667
664 extra = ctx.extra()
668 extra = ctx.extra()
665 if extra and self.ui.debugflag:
669 if extra and self.ui.debugflag:
666 for key, value in sorted(extra.items()):
670 for key, value in sorted(extra.items()):
667 self.ui.write(_("extra: %s=%s\n")
671 self.ui.write(_("extra: %s=%s\n")
668 % (key, value.encode('string_escape')),
672 % (key, value.encode('string_escape')),
669 label='ui.debug log.extra')
673 label='ui.debug log.extra')
670
674
671 description = ctx.description().strip()
675 description = ctx.description().strip()
672 if description:
676 if description:
673 if self.ui.verbose:
677 if self.ui.verbose:
674 self.ui.write(_("description:\n"),
678 self.ui.write(_("description:\n"),
675 label='ui.note log.description')
679 label='ui.note log.description')
676 self.ui.write(description,
680 self.ui.write(description,
677 label='ui.note log.description')
681 label='ui.note log.description')
678 self.ui.write("\n\n")
682 self.ui.write("\n\n")
679 else:
683 else:
680 self.ui.write(_("summary: %s\n") %
684 self.ui.write(_("summary: %s\n") %
681 description.splitlines()[0],
685 description.splitlines()[0],
682 label='log.summary')
686 label='log.summary')
683 self.ui.write("\n")
687 self.ui.write("\n")
684
688
685 self.showpatch(changenode, matchfn)
689 self.showpatch(changenode, matchfn)
686
690
687 def showpatch(self, node, matchfn):
691 def showpatch(self, node, matchfn):
688 if not matchfn:
692 if not matchfn:
689 matchfn = self.patch
693 matchfn = self.patch
690 if matchfn:
694 if matchfn:
691 stat = self.diffopts.get('stat')
695 stat = self.diffopts.get('stat')
692 diff = self.diffopts.get('patch')
696 diff = self.diffopts.get('patch')
693 diffopts = patch.diffopts(self.ui, self.diffopts)
697 diffopts = patch.diffopts(self.ui, self.diffopts)
694 prev = self.repo.changelog.parents(node)[0]
698 prev = self.repo.changelog.parents(node)[0]
695 if stat:
699 if stat:
696 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
700 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
697 match=matchfn, stat=True)
701 match=matchfn, stat=True)
698 if diff:
702 if diff:
699 if stat:
703 if stat:
700 self.ui.write("\n")
704 self.ui.write("\n")
701 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
705 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
702 match=matchfn, stat=False)
706 match=matchfn, stat=False)
703 self.ui.write("\n")
707 self.ui.write("\n")
704
708
705 def _meaningful_parentrevs(self, log, rev):
709 def _meaningful_parentrevs(self, log, rev):
706 """Return list of meaningful (or all if debug) parentrevs for rev.
710 """Return list of meaningful (or all if debug) parentrevs for rev.
707
711
708 For merges (two non-nullrev revisions) both parents are meaningful.
712 For merges (two non-nullrev revisions) both parents are meaningful.
709 Otherwise the first parent revision is considered meaningful if it
713 Otherwise the first parent revision is considered meaningful if it
710 is not the preceding revision.
714 is not the preceding revision.
711 """
715 """
712 parents = log.parentrevs(rev)
716 parents = log.parentrevs(rev)
713 if not self.ui.debugflag and parents[1] == nullrev:
717 if not self.ui.debugflag and parents[1] == nullrev:
714 if parents[0] >= rev - 1:
718 if parents[0] >= rev - 1:
715 parents = []
719 parents = []
716 else:
720 else:
717 parents = [parents[0]]
721 parents = [parents[0]]
718 return parents
722 return parents
719
723
720
724
721 class changeset_templater(changeset_printer):
725 class changeset_templater(changeset_printer):
722 '''format changeset information.'''
726 '''format changeset information.'''
723
727
724 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
728 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
725 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
729 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
726 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
730 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
727 defaulttempl = {
731 defaulttempl = {
728 'parent': '{rev}:{node|formatnode} ',
732 'parent': '{rev}:{node|formatnode} ',
729 'manifest': '{rev}:{node|formatnode}',
733 'manifest': '{rev}:{node|formatnode}',
730 'file_copy': '{name} ({source})',
734 'file_copy': '{name} ({source})',
731 'extra': '{key}={value|stringescape}'
735 'extra': '{key}={value|stringescape}'
732 }
736 }
733 # filecopy is preserved for compatibility reasons
737 # filecopy is preserved for compatibility reasons
734 defaulttempl['filecopy'] = defaulttempl['file_copy']
738 defaulttempl['filecopy'] = defaulttempl['file_copy']
735 self.t = templater.templater(mapfile, {'formatnode': formatnode},
739 self.t = templater.templater(mapfile, {'formatnode': formatnode},
736 cache=defaulttempl)
740 cache=defaulttempl)
737 self.cache = {}
741 self.cache = {}
738
742
739 def use_template(self, t):
743 def use_template(self, t):
740 '''set template string to use'''
744 '''set template string to use'''
741 self.t.cache['changeset'] = t
745 self.t.cache['changeset'] = t
742
746
743 def _meaningful_parentrevs(self, ctx):
747 def _meaningful_parentrevs(self, ctx):
744 """Return list of meaningful (or all if debug) parentrevs for rev.
748 """Return list of meaningful (or all if debug) parentrevs for rev.
745 """
749 """
746 parents = ctx.parents()
750 parents = ctx.parents()
747 if len(parents) > 1:
751 if len(parents) > 1:
748 return parents
752 return parents
749 if self.ui.debugflag:
753 if self.ui.debugflag:
750 return [parents[0], self.repo['null']]
754 return [parents[0], self.repo['null']]
751 if parents[0].rev() >= ctx.rev() - 1:
755 if parents[0].rev() >= ctx.rev() - 1:
752 return []
756 return []
753 return parents
757 return parents
754
758
755 def _show(self, ctx, copies, matchfn, props):
759 def _show(self, ctx, copies, matchfn, props):
756 '''show a single changeset or file revision'''
760 '''show a single changeset or file revision'''
757
761
758 showlist = templatekw.showlist
762 showlist = templatekw.showlist
759
763
760 # showparents() behaviour depends on ui trace level which
764 # showparents() behaviour depends on ui trace level which
761 # causes unexpected behaviours at templating level and makes
765 # causes unexpected behaviours at templating level and makes
762 # it harder to extract it in a standalone function. Its
766 # it harder to extract it in a standalone function. Its
763 # behaviour cannot be changed so leave it here for now.
767 # behaviour cannot be changed so leave it here for now.
764 def showparents(**args):
768 def showparents(**args):
765 ctx = args['ctx']
769 ctx = args['ctx']
766 parents = [[('rev', p.rev()), ('node', p.hex())]
770 parents = [[('rev', p.rev()), ('node', p.hex())]
767 for p in self._meaningful_parentrevs(ctx)]
771 for p in self._meaningful_parentrevs(ctx)]
768 return showlist('parent', parents, **args)
772 return showlist('parent', parents, **args)
769
773
770 props = props.copy()
774 props = props.copy()
771 props.update(templatekw.keywords)
775 props.update(templatekw.keywords)
772 props['parents'] = showparents
776 props['parents'] = showparents
773 props['templ'] = self.t
777 props['templ'] = self.t
774 props['ctx'] = ctx
778 props['ctx'] = ctx
775 props['repo'] = self.repo
779 props['repo'] = self.repo
776 props['revcache'] = {'copies': copies}
780 props['revcache'] = {'copies': copies}
777 props['cache'] = self.cache
781 props['cache'] = self.cache
778
782
779 # find correct templates for current mode
783 # find correct templates for current mode
780
784
781 tmplmodes = [
785 tmplmodes = [
782 (True, None),
786 (True, None),
783 (self.ui.verbose, 'verbose'),
787 (self.ui.verbose, 'verbose'),
784 (self.ui.quiet, 'quiet'),
788 (self.ui.quiet, 'quiet'),
785 (self.ui.debugflag, 'debug'),
789 (self.ui.debugflag, 'debug'),
786 ]
790 ]
787
791
788 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
792 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
789 for mode, postfix in tmplmodes:
793 for mode, postfix in tmplmodes:
790 for type in types:
794 for type in types:
791 cur = postfix and ('%s_%s' % (type, postfix)) or type
795 cur = postfix and ('%s_%s' % (type, postfix)) or type
792 if mode and cur in self.t:
796 if mode and cur in self.t:
793 types[type] = cur
797 types[type] = cur
794
798
795 try:
799 try:
796
800
797 # write header
801 # write header
798 if types['header']:
802 if types['header']:
799 h = templater.stringify(self.t(types['header'], **props))
803 h = templater.stringify(self.t(types['header'], **props))
800 if self.buffered:
804 if self.buffered:
801 self.header[ctx.rev()] = h
805 self.header[ctx.rev()] = h
802 else:
806 else:
803 if self.lastheader != h:
807 if self.lastheader != h:
804 self.lastheader = h
808 self.lastheader = h
805 self.ui.write(h)
809 self.ui.write(h)
806
810
807 # write changeset metadata, then patch if requested
811 # write changeset metadata, then patch if requested
808 key = types['changeset']
812 key = types['changeset']
809 self.ui.write(templater.stringify(self.t(key, **props)))
813 self.ui.write(templater.stringify(self.t(key, **props)))
810 self.showpatch(ctx.node(), matchfn)
814 self.showpatch(ctx.node(), matchfn)
811
815
812 if types['footer']:
816 if types['footer']:
813 if not self.footer:
817 if not self.footer:
814 self.footer = templater.stringify(self.t(types['footer'],
818 self.footer = templater.stringify(self.t(types['footer'],
815 **props))
819 **props))
816
820
817 except KeyError, inst:
821 except KeyError, inst:
818 msg = _("%s: no key named '%s'")
822 msg = _("%s: no key named '%s'")
819 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
823 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
820 except SyntaxError, inst:
824 except SyntaxError, inst:
821 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
825 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
822
826
823 def show_changeset(ui, repo, opts, buffered=False):
827 def show_changeset(ui, repo, opts, buffered=False):
824 """show one changeset using template or regular display.
828 """show one changeset using template or regular display.
825
829
826 Display format will be the first non-empty hit of:
830 Display format will be the first non-empty hit of:
827 1. option 'template'
831 1. option 'template'
828 2. option 'style'
832 2. option 'style'
829 3. [ui] setting 'logtemplate'
833 3. [ui] setting 'logtemplate'
830 4. [ui] setting 'style'
834 4. [ui] setting 'style'
831 If all of these values are either the unset or the empty string,
835 If all of these values are either the unset or the empty string,
832 regular display via changeset_printer() is done.
836 regular display via changeset_printer() is done.
833 """
837 """
834 # options
838 # options
835 patch = False
839 patch = False
836 if opts.get('patch') or opts.get('stat'):
840 if opts.get('patch') or opts.get('stat'):
837 patch = scmutil.matchall(repo)
841 patch = scmutil.matchall(repo)
838
842
839 tmpl = opts.get('template')
843 tmpl = opts.get('template')
840 style = None
844 style = None
841 if tmpl:
845 if tmpl:
842 tmpl = templater.parsestring(tmpl, quoted=False)
846 tmpl = templater.parsestring(tmpl, quoted=False)
843 else:
847 else:
844 style = opts.get('style')
848 style = opts.get('style')
845
849
846 # ui settings
850 # ui settings
847 if not (tmpl or style):
851 if not (tmpl or style):
848 tmpl = ui.config('ui', 'logtemplate')
852 tmpl = ui.config('ui', 'logtemplate')
849 if tmpl:
853 if tmpl:
850 tmpl = templater.parsestring(tmpl)
854 tmpl = templater.parsestring(tmpl)
851 else:
855 else:
852 style = util.expandpath(ui.config('ui', 'style', ''))
856 style = util.expandpath(ui.config('ui', 'style', ''))
853
857
854 if not (tmpl or style):
858 if not (tmpl or style):
855 return changeset_printer(ui, repo, patch, opts, buffered)
859 return changeset_printer(ui, repo, patch, opts, buffered)
856
860
857 mapfile = None
861 mapfile = None
858 if style and not tmpl:
862 if style and not tmpl:
859 mapfile = style
863 mapfile = style
860 if not os.path.split(mapfile)[0]:
864 if not os.path.split(mapfile)[0]:
861 mapname = (templater.templatepath('map-cmdline.' + mapfile)
865 mapname = (templater.templatepath('map-cmdline.' + mapfile)
862 or templater.templatepath(mapfile))
866 or templater.templatepath(mapfile))
863 if mapname:
867 if mapname:
864 mapfile = mapname
868 mapfile = mapname
865
869
866 try:
870 try:
867 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
871 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
868 except SyntaxError, inst:
872 except SyntaxError, inst:
869 raise util.Abort(inst.args[0])
873 raise util.Abort(inst.args[0])
870 if tmpl:
874 if tmpl:
871 t.use_template(tmpl)
875 t.use_template(tmpl)
872 return t
876 return t
873
877
874 def finddate(ui, repo, date):
878 def finddate(ui, repo, date):
875 """Find the tipmost changeset that matches the given date spec"""
879 """Find the tipmost changeset that matches the given date spec"""
876
880
877 df = util.matchdate(date)
881 df = util.matchdate(date)
878 m = scmutil.matchall(repo)
882 m = scmutil.matchall(repo)
879 results = {}
883 results = {}
880
884
881 def prep(ctx, fns):
885 def prep(ctx, fns):
882 d = ctx.date()
886 d = ctx.date()
883 if df(d[0]):
887 if df(d[0]):
884 results[ctx.rev()] = d
888 results[ctx.rev()] = d
885
889
886 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
890 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
887 rev = ctx.rev()
891 rev = ctx.rev()
888 if rev in results:
892 if rev in results:
889 ui.status(_("Found revision %s from %s\n") %
893 ui.status(_("Found revision %s from %s\n") %
890 (rev, util.datestr(results[rev])))
894 (rev, util.datestr(results[rev])))
891 return str(rev)
895 return str(rev)
892
896
893 raise util.Abort(_("revision matching date not found"))
897 raise util.Abort(_("revision matching date not found"))
894
898
895 def walkchangerevs(repo, match, opts, prepare):
899 def walkchangerevs(repo, match, opts, prepare):
896 '''Iterate over files and the revs in which they changed.
900 '''Iterate over files and the revs in which they changed.
897
901
898 Callers most commonly need to iterate backwards over the history
902 Callers most commonly need to iterate backwards over the history
899 in which they are interested. Doing so has awful (quadratic-looking)
903 in which they are interested. Doing so has awful (quadratic-looking)
900 performance, so we use iterators in a "windowed" way.
904 performance, so we use iterators in a "windowed" way.
901
905
902 We walk a window of revisions in the desired order. Within the
906 We walk a window of revisions in the desired order. Within the
903 window, we first walk forwards to gather data, then in the desired
907 window, we first walk forwards to gather data, then in the desired
904 order (usually backwards) to display it.
908 order (usually backwards) to display it.
905
909
906 This function returns an iterator yielding contexts. Before
910 This function returns an iterator yielding contexts. Before
907 yielding each context, the iterator will first call the prepare
911 yielding each context, the iterator will first call the prepare
908 function on each context in the window in forward order.'''
912 function on each context in the window in forward order.'''
909
913
910 def increasing_windows(start, end, windowsize=8, sizelimit=512):
914 def increasing_windows(start, end, windowsize=8, sizelimit=512):
911 if start < end:
915 if start < end:
912 while start < end:
916 while start < end:
913 yield start, min(windowsize, end - start)
917 yield start, min(windowsize, end - start)
914 start += windowsize
918 start += windowsize
915 if windowsize < sizelimit:
919 if windowsize < sizelimit:
916 windowsize *= 2
920 windowsize *= 2
917 else:
921 else:
918 while start > end:
922 while start > end:
919 yield start, min(windowsize, start - end - 1)
923 yield start, min(windowsize, start - end - 1)
920 start -= windowsize
924 start -= windowsize
921 if windowsize < sizelimit:
925 if windowsize < sizelimit:
922 windowsize *= 2
926 windowsize *= 2
923
927
924 follow = opts.get('follow') or opts.get('follow_first')
928 follow = opts.get('follow') or opts.get('follow_first')
925
929
926 if not len(repo):
930 if not len(repo):
927 return []
931 return []
928
932
929 if follow:
933 if follow:
930 defrange = '%s:0' % repo['.'].rev()
934 defrange = '%s:0' % repo['.'].rev()
931 else:
935 else:
932 defrange = '-1:0'
936 defrange = '-1:0'
933 revs = scmutil.revrange(repo, opts['rev'] or [defrange])
937 revs = scmutil.revrange(repo, opts['rev'] or [defrange])
934 if not revs:
938 if not revs:
935 return []
939 return []
936 wanted = set()
940 wanted = set()
937 slowpath = match.anypats() or (match.files() and opts.get('removed'))
941 slowpath = match.anypats() or (match.files() and opts.get('removed'))
938 fncache = {}
942 fncache = {}
939 change = util.cachefunc(repo.changectx)
943 change = util.cachefunc(repo.changectx)
940
944
941 # First step is to fill wanted, the set of revisions that we want to yield.
945 # First step is to fill wanted, the set of revisions that we want to yield.
942 # When it does not induce extra cost, we also fill fncache for revisions in
946 # When it does not induce extra cost, we also fill fncache for revisions in
943 # wanted: a cache of filenames that were changed (ctx.files()) and that
947 # wanted: a cache of filenames that were changed (ctx.files()) and that
944 # match the file filtering conditions.
948 # match the file filtering conditions.
945
949
946 if not slowpath and not match.files():
950 if not slowpath and not match.files():
947 # No files, no patterns. Display all revs.
951 # No files, no patterns. Display all revs.
948 wanted = set(revs)
952 wanted = set(revs)
949 copies = []
953 copies = []
950
954
951 if not slowpath:
955 if not slowpath:
952 # We only have to read through the filelog to find wanted revisions
956 # We only have to read through the filelog to find wanted revisions
953
957
954 minrev, maxrev = min(revs), max(revs)
958 minrev, maxrev = min(revs), max(revs)
955 def filerevgen(filelog, last):
959 def filerevgen(filelog, last):
956 """
960 """
957 Only files, no patterns. Check the history of each file.
961 Only files, no patterns. Check the history of each file.
958
962
959 Examines filelog entries within minrev, maxrev linkrev range
963 Examines filelog entries within minrev, maxrev linkrev range
960 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
964 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
961 tuples in backwards order
965 tuples in backwards order
962 """
966 """
963 cl_count = len(repo)
967 cl_count = len(repo)
964 revs = []
968 revs = []
965 for j in xrange(0, last + 1):
969 for j in xrange(0, last + 1):
966 linkrev = filelog.linkrev(j)
970 linkrev = filelog.linkrev(j)
967 if linkrev < minrev:
971 if linkrev < minrev:
968 continue
972 continue
969 # only yield rev for which we have the changelog, it can
973 # only yield rev for which we have the changelog, it can
970 # happen while doing "hg log" during a pull or commit
974 # happen while doing "hg log" during a pull or commit
971 if linkrev >= cl_count:
975 if linkrev >= cl_count:
972 break
976 break
973
977
974 parentlinkrevs = []
978 parentlinkrevs = []
975 for p in filelog.parentrevs(j):
979 for p in filelog.parentrevs(j):
976 if p != nullrev:
980 if p != nullrev:
977 parentlinkrevs.append(filelog.linkrev(p))
981 parentlinkrevs.append(filelog.linkrev(p))
978 n = filelog.node(j)
982 n = filelog.node(j)
979 revs.append((linkrev, parentlinkrevs,
983 revs.append((linkrev, parentlinkrevs,
980 follow and filelog.renamed(n)))
984 follow and filelog.renamed(n)))
981
985
982 return reversed(revs)
986 return reversed(revs)
983 def iterfiles():
987 def iterfiles():
984 for filename in match.files():
988 for filename in match.files():
985 yield filename, None
989 yield filename, None
986 for filename_node in copies:
990 for filename_node in copies:
987 yield filename_node
991 yield filename_node
988 for file_, node in iterfiles():
992 for file_, node in iterfiles():
989 filelog = repo.file(file_)
993 filelog = repo.file(file_)
990 if not len(filelog):
994 if not len(filelog):
991 if node is None:
995 if node is None:
992 # A zero count may be a directory or deleted file, so
996 # A zero count may be a directory or deleted file, so
993 # try to find matching entries on the slow path.
997 # try to find matching entries on the slow path.
994 if follow:
998 if follow:
995 raise util.Abort(
999 raise util.Abort(
996 _('cannot follow nonexistent file: "%s"') % file_)
1000 _('cannot follow nonexistent file: "%s"') % file_)
997 slowpath = True
1001 slowpath = True
998 break
1002 break
999 else:
1003 else:
1000 continue
1004 continue
1001
1005
1002 if node is None:
1006 if node is None:
1003 last = len(filelog) - 1
1007 last = len(filelog) - 1
1004 else:
1008 else:
1005 last = filelog.rev(node)
1009 last = filelog.rev(node)
1006
1010
1007
1011
1008 # keep track of all ancestors of the file
1012 # keep track of all ancestors of the file
1009 ancestors = set([filelog.linkrev(last)])
1013 ancestors = set([filelog.linkrev(last)])
1010
1014
1011 # iterate from latest to oldest revision
1015 # iterate from latest to oldest revision
1012 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1016 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1013 if not follow:
1017 if not follow:
1014 if rev > maxrev:
1018 if rev > maxrev:
1015 continue
1019 continue
1016 else:
1020 else:
1017 # Note that last might not be the first interesting
1021 # Note that last might not be the first interesting
1018 # rev to us:
1022 # rev to us:
1019 # if the file has been changed after maxrev, we'll
1023 # if the file has been changed after maxrev, we'll
1020 # have linkrev(last) > maxrev, and we still need
1024 # have linkrev(last) > maxrev, and we still need
1021 # to explore the file graph
1025 # to explore the file graph
1022 if rev not in ancestors:
1026 if rev not in ancestors:
1023 continue
1027 continue
1024 # XXX insert 1327 fix here
1028 # XXX insert 1327 fix here
1025 if flparentlinkrevs:
1029 if flparentlinkrevs:
1026 ancestors.update(flparentlinkrevs)
1030 ancestors.update(flparentlinkrevs)
1027
1031
1028 fncache.setdefault(rev, []).append(file_)
1032 fncache.setdefault(rev, []).append(file_)
1029 wanted.add(rev)
1033 wanted.add(rev)
1030 if copied:
1034 if copied:
1031 copies.append(copied)
1035 copies.append(copied)
1032 if slowpath:
1036 if slowpath:
1033 # We have to read the changelog to match filenames against
1037 # We have to read the changelog to match filenames against
1034 # changed files
1038 # changed files
1035
1039
1036 if follow:
1040 if follow:
1037 raise util.Abort(_('can only follow copies/renames for explicit '
1041 raise util.Abort(_('can only follow copies/renames for explicit '
1038 'filenames'))
1042 'filenames'))
1039
1043
1040 # The slow path checks files modified in every changeset.
1044 # The slow path checks files modified in every changeset.
1041 for i in sorted(revs):
1045 for i in sorted(revs):
1042 ctx = change(i)
1046 ctx = change(i)
1043 matches = filter(match, ctx.files())
1047 matches = filter(match, ctx.files())
1044 if matches:
1048 if matches:
1045 fncache[i] = matches
1049 fncache[i] = matches
1046 wanted.add(i)
1050 wanted.add(i)
1047
1051
1048 class followfilter(object):
1052 class followfilter(object):
1049 def __init__(self, onlyfirst=False):
1053 def __init__(self, onlyfirst=False):
1050 self.startrev = nullrev
1054 self.startrev = nullrev
1051 self.roots = set()
1055 self.roots = set()
1052 self.onlyfirst = onlyfirst
1056 self.onlyfirst = onlyfirst
1053
1057
1054 def match(self, rev):
1058 def match(self, rev):
1055 def realparents(rev):
1059 def realparents(rev):
1056 if self.onlyfirst:
1060 if self.onlyfirst:
1057 return repo.changelog.parentrevs(rev)[0:1]
1061 return repo.changelog.parentrevs(rev)[0:1]
1058 else:
1062 else:
1059 return filter(lambda x: x != nullrev,
1063 return filter(lambda x: x != nullrev,
1060 repo.changelog.parentrevs(rev))
1064 repo.changelog.parentrevs(rev))
1061
1065
1062 if self.startrev == nullrev:
1066 if self.startrev == nullrev:
1063 self.startrev = rev
1067 self.startrev = rev
1064 return True
1068 return True
1065
1069
1066 if rev > self.startrev:
1070 if rev > self.startrev:
1067 # forward: all descendants
1071 # forward: all descendants
1068 if not self.roots:
1072 if not self.roots:
1069 self.roots.add(self.startrev)
1073 self.roots.add(self.startrev)
1070 for parent in realparents(rev):
1074 for parent in realparents(rev):
1071 if parent in self.roots:
1075 if parent in self.roots:
1072 self.roots.add(rev)
1076 self.roots.add(rev)
1073 return True
1077 return True
1074 else:
1078 else:
1075 # backwards: all parents
1079 # backwards: all parents
1076 if not self.roots:
1080 if not self.roots:
1077 self.roots.update(realparents(self.startrev))
1081 self.roots.update(realparents(self.startrev))
1078 if rev in self.roots:
1082 if rev in self.roots:
1079 self.roots.remove(rev)
1083 self.roots.remove(rev)
1080 self.roots.update(realparents(rev))
1084 self.roots.update(realparents(rev))
1081 return True
1085 return True
1082
1086
1083 return False
1087 return False
1084
1088
1085 # it might be worthwhile to do this in the iterator if the rev range
1089 # it might be worthwhile to do this in the iterator if the rev range
1086 # is descending and the prune args are all within that range
1090 # is descending and the prune args are all within that range
1087 for rev in opts.get('prune', ()):
1091 for rev in opts.get('prune', ()):
1088 rev = repo.changelog.rev(repo.lookup(rev))
1092 rev = repo.changelog.rev(repo.lookup(rev))
1089 ff = followfilter()
1093 ff = followfilter()
1090 stop = min(revs[0], revs[-1])
1094 stop = min(revs[0], revs[-1])
1091 for x in xrange(rev, stop - 1, -1):
1095 for x in xrange(rev, stop - 1, -1):
1092 if ff.match(x):
1096 if ff.match(x):
1093 wanted.discard(x)
1097 wanted.discard(x)
1094
1098
1095 # Now that wanted is correctly initialized, we can iterate over the
1099 # Now that wanted is correctly initialized, we can iterate over the
1096 # revision range, yielding only revisions in wanted.
1100 # revision range, yielding only revisions in wanted.
1097 def iterate():
1101 def iterate():
1098 if follow and not match.files():
1102 if follow and not match.files():
1099 ff = followfilter(onlyfirst=opts.get('follow_first'))
1103 ff = followfilter(onlyfirst=opts.get('follow_first'))
1100 def want(rev):
1104 def want(rev):
1101 return ff.match(rev) and rev in wanted
1105 return ff.match(rev) and rev in wanted
1102 else:
1106 else:
1103 def want(rev):
1107 def want(rev):
1104 return rev in wanted
1108 return rev in wanted
1105
1109
1106 for i, window in increasing_windows(0, len(revs)):
1110 for i, window in increasing_windows(0, len(revs)):
1107 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1111 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1108 for rev in sorted(nrevs):
1112 for rev in sorted(nrevs):
1109 fns = fncache.get(rev)
1113 fns = fncache.get(rev)
1110 ctx = change(rev)
1114 ctx = change(rev)
1111 if not fns:
1115 if not fns:
1112 def fns_generator():
1116 def fns_generator():
1113 for f in ctx.files():
1117 for f in ctx.files():
1114 if match(f):
1118 if match(f):
1115 yield f
1119 yield f
1116 fns = fns_generator()
1120 fns = fns_generator()
1117 prepare(ctx, fns)
1121 prepare(ctx, fns)
1118 for rev in nrevs:
1122 for rev in nrevs:
1119 yield change(rev)
1123 yield change(rev)
1120 return iterate()
1124 return iterate()
1121
1125
1122 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1126 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1123 join = lambda f: os.path.join(prefix, f)
1127 join = lambda f: os.path.join(prefix, f)
1124 bad = []
1128 bad = []
1125 oldbad = match.bad
1129 oldbad = match.bad
1126 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1130 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1127 names = []
1131 names = []
1128 wctx = repo[None]
1132 wctx = repo[None]
1129 cca = None
1133 cca = None
1130 abort, warn = scmutil.checkportabilityalert(ui)
1134 abort, warn = scmutil.checkportabilityalert(ui)
1131 if abort or warn:
1135 if abort or warn:
1132 cca = scmutil.casecollisionauditor(ui, abort, wctx)
1136 cca = scmutil.casecollisionauditor(ui, abort, wctx)
1133 for f in repo.walk(match):
1137 for f in repo.walk(match):
1134 exact = match.exact(f)
1138 exact = match.exact(f)
1135 if exact or f not in repo.dirstate:
1139 if exact or f not in repo.dirstate:
1136 if cca:
1140 if cca:
1137 cca(f)
1141 cca(f)
1138 names.append(f)
1142 names.append(f)
1139 if ui.verbose or not exact:
1143 if ui.verbose or not exact:
1140 ui.status(_('adding %s\n') % match.rel(join(f)))
1144 ui.status(_('adding %s\n') % match.rel(join(f)))
1141
1145
1142 if listsubrepos:
1146 if listsubrepos:
1143 for subpath in wctx.substate:
1147 for subpath in wctx.substate:
1144 sub = wctx.sub(subpath)
1148 sub = wctx.sub(subpath)
1145 try:
1149 try:
1146 submatch = matchmod.narrowmatcher(subpath, match)
1150 submatch = matchmod.narrowmatcher(subpath, match)
1147 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1151 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1148 except error.LookupError:
1152 except error.LookupError:
1149 ui.status(_("skipping missing subrepository: %s\n")
1153 ui.status(_("skipping missing subrepository: %s\n")
1150 % join(subpath))
1154 % join(subpath))
1151
1155
1152 if not dryrun:
1156 if not dryrun:
1153 rejected = wctx.add(names, prefix)
1157 rejected = wctx.add(names, prefix)
1154 bad.extend(f for f in rejected if f in match.files())
1158 bad.extend(f for f in rejected if f in match.files())
1155 return bad
1159 return bad
1156
1160
1157 def commit(ui, repo, commitfunc, pats, opts):
1161 def commit(ui, repo, commitfunc, pats, opts):
1158 '''commit the specified files or all outstanding changes'''
1162 '''commit the specified files or all outstanding changes'''
1159 date = opts.get('date')
1163 date = opts.get('date')
1160 if date:
1164 if date:
1161 opts['date'] = util.parsedate(date)
1165 opts['date'] = util.parsedate(date)
1162 message = logmessage(opts)
1166 message = logmessage(opts)
1163
1167
1164 # extract addremove carefully -- this function can be called from a command
1168 # extract addremove carefully -- this function can be called from a command
1165 # that doesn't support addremove
1169 # that doesn't support addremove
1166 if opts.get('addremove'):
1170 if opts.get('addremove'):
1167 scmutil.addremove(repo, pats, opts)
1171 scmutil.addremove(repo, pats, opts)
1168
1172
1169 return commitfunc(ui, repo, message, scmutil.match(repo, pats, opts), opts)
1173 return commitfunc(ui, repo, message, scmutil.match(repo, pats, opts), opts)
1170
1174
1171 def commiteditor(repo, ctx, subs):
1175 def commiteditor(repo, ctx, subs):
1172 if ctx.description():
1176 if ctx.description():
1173 return ctx.description()
1177 return ctx.description()
1174 return commitforceeditor(repo, ctx, subs)
1178 return commitforceeditor(repo, ctx, subs)
1175
1179
1176 def commitforceeditor(repo, ctx, subs):
1180 def commitforceeditor(repo, ctx, subs):
1177 edittext = []
1181 edittext = []
1178 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1182 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1179 if ctx.description():
1183 if ctx.description():
1180 edittext.append(ctx.description())
1184 edittext.append(ctx.description())
1181 edittext.append("")
1185 edittext.append("")
1182 edittext.append("") # Empty line between message and comments.
1186 edittext.append("") # Empty line between message and comments.
1183 edittext.append(_("HG: Enter commit message."
1187 edittext.append(_("HG: Enter commit message."
1184 " Lines beginning with 'HG:' are removed."))
1188 " Lines beginning with 'HG:' are removed."))
1185 edittext.append(_("HG: Leave message empty to abort commit."))
1189 edittext.append(_("HG: Leave message empty to abort commit."))
1186 edittext.append("HG: --")
1190 edittext.append("HG: --")
1187 edittext.append(_("HG: user: %s") % ctx.user())
1191 edittext.append(_("HG: user: %s") % ctx.user())
1188 if ctx.p2():
1192 if ctx.p2():
1189 edittext.append(_("HG: branch merge"))
1193 edittext.append(_("HG: branch merge"))
1190 if ctx.branch():
1194 if ctx.branch():
1191 edittext.append(_("HG: branch '%s'") % ctx.branch())
1195 edittext.append(_("HG: branch '%s'") % ctx.branch())
1192 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1196 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1193 edittext.extend([_("HG: added %s") % f for f in added])
1197 edittext.extend([_("HG: added %s") % f for f in added])
1194 edittext.extend([_("HG: changed %s") % f for f in modified])
1198 edittext.extend([_("HG: changed %s") % f for f in modified])
1195 edittext.extend([_("HG: removed %s") % f for f in removed])
1199 edittext.extend([_("HG: removed %s") % f for f in removed])
1196 if not added and not modified and not removed:
1200 if not added and not modified and not removed:
1197 edittext.append(_("HG: no files changed"))
1201 edittext.append(_("HG: no files changed"))
1198 edittext.append("")
1202 edittext.append("")
1199 # run editor in the repository root
1203 # run editor in the repository root
1200 olddir = os.getcwd()
1204 olddir = os.getcwd()
1201 os.chdir(repo.root)
1205 os.chdir(repo.root)
1202 text = repo.ui.edit("\n".join(edittext), ctx.user())
1206 text = repo.ui.edit("\n".join(edittext), ctx.user())
1203 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1207 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1204 os.chdir(olddir)
1208 os.chdir(olddir)
1205
1209
1206 if not text.strip():
1210 if not text.strip():
1207 raise util.Abort(_("empty commit message"))
1211 raise util.Abort(_("empty commit message"))
1208
1212
1209 return text
1213 return text
1210
1214
1211 def command(table):
1215 def command(table):
1212 '''returns a function object bound to table which can be used as
1216 '''returns a function object bound to table which can be used as
1213 a decorator for populating table as a command table'''
1217 a decorator for populating table as a command table'''
1214
1218
1215 def cmd(name, options, synopsis=None):
1219 def cmd(name, options, synopsis=None):
1216 def decorator(func):
1220 def decorator(func):
1217 if synopsis:
1221 if synopsis:
1218 table[name] = func, options[:], synopsis
1222 table[name] = func, options[:], synopsis
1219 else:
1223 else:
1220 table[name] = func, options[:]
1224 table[name] = func, options[:]
1221 return func
1225 return func
1222 return decorator
1226 return decorator
1223
1227
1224 return cmd
1228 return cmd
@@ -1,1123 +1,1109
1 # context.py - changeset and file context objects for mercurial
1 # context.py - changeset and file context objects for mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 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 nullid, nullrev, short, hex
8 from node import nullid, nullrev, short, hex
9 from i18n import _
9 from i18n import _
10 import ancestor, bdiff, error, util, scmutil, subrepo, patch, encoding
10 import ancestor, bdiff, error, util, scmutil, subrepo, patch, encoding
11 import os, errno, stat
11 import os, errno, stat
12
12
13 propertycache = util.propertycache
13 propertycache = util.propertycache
14
14
15 class changectx(object):
15 class changectx(object):
16 """A changecontext object makes access to data related to a particular
16 """A changecontext object makes access to data related to a particular
17 changeset convenient."""
17 changeset convenient."""
18 def __init__(self, repo, changeid=''):
18 def __init__(self, repo, changeid=''):
19 """changeid is a revision number, node, or tag"""
19 """changeid is a revision number, node, or tag"""
20 if changeid == '':
20 if changeid == '':
21 changeid = '.'
21 changeid = '.'
22 self._repo = repo
22 self._repo = repo
23 if isinstance(changeid, (long, int)):
23 if isinstance(changeid, (long, int)):
24 self._rev = changeid
24 self._rev = changeid
25 self._node = self._repo.changelog.node(changeid)
25 self._node = self._repo.changelog.node(changeid)
26 else:
26 else:
27 self._node = self._repo.lookup(changeid)
27 self._node = self._repo.lookup(changeid)
28 self._rev = self._repo.changelog.rev(self._node)
28 self._rev = self._repo.changelog.rev(self._node)
29
29
30 def __str__(self):
30 def __str__(self):
31 return short(self.node())
31 return short(self.node())
32
32
33 def __int__(self):
33 def __int__(self):
34 return self.rev()
34 return self.rev()
35
35
36 def __repr__(self):
36 def __repr__(self):
37 return "<changectx %s>" % str(self)
37 return "<changectx %s>" % str(self)
38
38
39 def __hash__(self):
39 def __hash__(self):
40 try:
40 try:
41 return hash(self._rev)
41 return hash(self._rev)
42 except AttributeError:
42 except AttributeError:
43 return id(self)
43 return id(self)
44
44
45 def __eq__(self, other):
45 def __eq__(self, other):
46 try:
46 try:
47 return self._rev == other._rev
47 return self._rev == other._rev
48 except AttributeError:
48 except AttributeError:
49 return False
49 return False
50
50
51 def __ne__(self, other):
51 def __ne__(self, other):
52 return not (self == other)
52 return not (self == other)
53
53
54 def __nonzero__(self):
54 def __nonzero__(self):
55 return self._rev != nullrev
55 return self._rev != nullrev
56
56
57 @propertycache
57 @propertycache
58 def _changeset(self):
58 def _changeset(self):
59 return self._repo.changelog.read(self.node())
59 return self._repo.changelog.read(self.node())
60
60
61 @propertycache
61 @propertycache
62 def _manifest(self):
62 def _manifest(self):
63 return self._repo.manifest.read(self._changeset[0])
63 return self._repo.manifest.read(self._changeset[0])
64
64
65 @propertycache
65 @propertycache
66 def _manifestdelta(self):
66 def _manifestdelta(self):
67 return self._repo.manifest.readdelta(self._changeset[0])
67 return self._repo.manifest.readdelta(self._changeset[0])
68
68
69 @propertycache
69 @propertycache
70 def _parents(self):
70 def _parents(self):
71 p = self._repo.changelog.parentrevs(self._rev)
71 p = self._repo.changelog.parentrevs(self._rev)
72 if p[1] == nullrev:
72 if p[1] == nullrev:
73 p = p[:-1]
73 p = p[:-1]
74 return [changectx(self._repo, x) for x in p]
74 return [changectx(self._repo, x) for x in p]
75
75
76 @propertycache
76 @propertycache
77 def substate(self):
77 def substate(self):
78 return subrepo.state(self, self._repo.ui)
78 return subrepo.state(self, self._repo.ui)
79
79
80 def __contains__(self, key):
80 def __contains__(self, key):
81 return key in self._manifest
81 return key in self._manifest
82
82
83 def __getitem__(self, key):
83 def __getitem__(self, key):
84 return self.filectx(key)
84 return self.filectx(key)
85
85
86 def __iter__(self):
86 def __iter__(self):
87 for f in sorted(self._manifest):
87 for f in sorted(self._manifest):
88 yield f
88 yield f
89
89
90 def changeset(self):
90 def changeset(self):
91 return self._changeset
91 return self._changeset
92 def manifest(self):
92 def manifest(self):
93 return self._manifest
93 return self._manifest
94 def manifestnode(self):
94 def manifestnode(self):
95 return self._changeset[0]
95 return self._changeset[0]
96
96
97 def rev(self):
97 def rev(self):
98 return self._rev
98 return self._rev
99 def node(self):
99 def node(self):
100 return self._node
100 return self._node
101 def hex(self):
101 def hex(self):
102 return hex(self._node)
102 return hex(self._node)
103 def user(self):
103 def user(self):
104 return self._changeset[1]
104 return self._changeset[1]
105 def date(self):
105 def date(self):
106 return self._changeset[2]
106 return self._changeset[2]
107 def files(self):
107 def files(self):
108 return self._changeset[3]
108 return self._changeset[3]
109 def description(self):
109 def description(self):
110 return self._changeset[4]
110 return self._changeset[4]
111 def branch(self):
111 def branch(self):
112 return encoding.tolocal(self._changeset[5].get("branch"))
112 return encoding.tolocal(self._changeset[5].get("branch"))
113 def extra(self):
113 def extra(self):
114 return self._changeset[5]
114 return self._changeset[5]
115 def tags(self):
115 def tags(self):
116 return self._repo.nodetags(self._node)
116 return self._repo.nodetags(self._node)
117 def bookmarks(self):
117 def bookmarks(self):
118 return self._repo.nodebookmarks(self._node)
118 return self._repo.nodebookmarks(self._node)
119
119
120 def parents(self):
120 def parents(self):
121 """return contexts for each parent changeset"""
121 """return contexts for each parent changeset"""
122 return self._parents
122 return self._parents
123
123
124 def p1(self):
124 def p1(self):
125 return self._parents[0]
125 return self._parents[0]
126
126
127 def p2(self):
127 def p2(self):
128 if len(self._parents) == 2:
128 if len(self._parents) == 2:
129 return self._parents[1]
129 return self._parents[1]
130 return changectx(self._repo, -1)
130 return changectx(self._repo, -1)
131
131
132 def children(self):
132 def children(self):
133 """return contexts for each child changeset"""
133 """return contexts for each child changeset"""
134 c = self._repo.changelog.children(self._node)
134 c = self._repo.changelog.children(self._node)
135 return [changectx(self._repo, x) for x in c]
135 return [changectx(self._repo, x) for x in c]
136
136
137 def ancestors(self):
137 def ancestors(self):
138 for a in self._repo.changelog.ancestors(self._rev):
138 for a in self._repo.changelog.ancestors(self._rev):
139 yield changectx(self._repo, a)
139 yield changectx(self._repo, a)
140
140
141 def descendants(self):
141 def descendants(self):
142 for d in self._repo.changelog.descendants(self._rev):
142 for d in self._repo.changelog.descendants(self._rev):
143 yield changectx(self._repo, d)
143 yield changectx(self._repo, d)
144
144
145 def _fileinfo(self, path):
145 def _fileinfo(self, path):
146 if '_manifest' in self.__dict__:
146 if '_manifest' in self.__dict__:
147 try:
147 try:
148 return self._manifest[path], self._manifest.flags(path)
148 return self._manifest[path], self._manifest.flags(path)
149 except KeyError:
149 except KeyError:
150 raise error.LookupError(self._node, path,
150 raise error.LookupError(self._node, path,
151 _('not found in manifest'))
151 _('not found in manifest'))
152 if '_manifestdelta' in self.__dict__ or path in self.files():
152 if '_manifestdelta' in self.__dict__ or path in self.files():
153 if path in self._manifestdelta:
153 if path in self._manifestdelta:
154 return self._manifestdelta[path], self._manifestdelta.flags(path)
154 return self._manifestdelta[path], self._manifestdelta.flags(path)
155 node, flag = self._repo.manifest.find(self._changeset[0], path)
155 node, flag = self._repo.manifest.find(self._changeset[0], path)
156 if not node:
156 if not node:
157 raise error.LookupError(self._node, path,
157 raise error.LookupError(self._node, path,
158 _('not found in manifest'))
158 _('not found in manifest'))
159
159
160 return node, flag
160 return node, flag
161
161
162 def filenode(self, path):
162 def filenode(self, path):
163 return self._fileinfo(path)[0]
163 return self._fileinfo(path)[0]
164
164
165 def flags(self, path):
165 def flags(self, path):
166 try:
166 try:
167 return self._fileinfo(path)[1]
167 return self._fileinfo(path)[1]
168 except error.LookupError:
168 except error.LookupError:
169 return ''
169 return ''
170
170
171 def filectx(self, path, fileid=None, filelog=None):
171 def filectx(self, path, fileid=None, filelog=None):
172 """get a file context from this changeset"""
172 """get a file context from this changeset"""
173 if fileid is None:
173 if fileid is None:
174 fileid = self.filenode(path)
174 fileid = self.filenode(path)
175 return filectx(self._repo, path, fileid=fileid,
175 return filectx(self._repo, path, fileid=fileid,
176 changectx=self, filelog=filelog)
176 changectx=self, filelog=filelog)
177
177
178 def ancestor(self, c2):
178 def ancestor(self, c2):
179 """
179 """
180 return the ancestor context of self and c2
180 return the ancestor context of self and c2
181 """
181 """
182 # deal with workingctxs
182 # deal with workingctxs
183 n2 = c2._node
183 n2 = c2._node
184 if n2 is None:
184 if n2 is None:
185 n2 = c2._parents[0]._node
185 n2 = c2._parents[0]._node
186 n = self._repo.changelog.ancestor(self._node, n2)
186 n = self._repo.changelog.ancestor(self._node, n2)
187 return changectx(self._repo, n)
187 return changectx(self._repo, n)
188
188
189 def walk(self, match):
189 def walk(self, match):
190 fset = set(match.files())
190 fset = set(match.files())
191 # for dirstate.walk, files=['.'] means "walk the whole tree".
191 # for dirstate.walk, files=['.'] means "walk the whole tree".
192 # follow that here, too
192 # follow that here, too
193 fset.discard('.')
193 fset.discard('.')
194 for fn in self:
194 for fn in self:
195 for ffn in fset:
195 for ffn in fset:
196 # match if the file is the exact name or a directory
196 # match if the file is the exact name or a directory
197 if ffn == fn or fn.startswith("%s/" % ffn):
197 if ffn == fn or fn.startswith("%s/" % ffn):
198 fset.remove(ffn)
198 fset.remove(ffn)
199 break
199 break
200 if match(fn):
200 if match(fn):
201 yield fn
201 yield fn
202 for fn in sorted(fset):
202 for fn in sorted(fset):
203 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
203 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
204 yield fn
204 yield fn
205
205
206 def sub(self, path):
206 def sub(self, path):
207 return subrepo.subrepo(self, path)
207 return subrepo.subrepo(self, path)
208
208
209 def diff(self, ctx2=None, match=None, **opts):
209 def diff(self, ctx2=None, match=None, **opts):
210 """Returns a diff generator for the given contexts and matcher"""
210 """Returns a diff generator for the given contexts and matcher"""
211 if ctx2 is None:
211 if ctx2 is None:
212 ctx2 = self.p1()
212 ctx2 = self.p1()
213 if ctx2 is not None and not isinstance(ctx2, changectx):
213 if ctx2 is not None and not isinstance(ctx2, changectx):
214 ctx2 = self._repo[ctx2]
214 ctx2 = self._repo[ctx2]
215 diffopts = patch.diffopts(self._repo.ui, opts)
215 diffopts = patch.diffopts(self._repo.ui, opts)
216 return patch.diff(self._repo, ctx2.node(), self.node(),
216 return patch.diff(self._repo, ctx2.node(), self.node(),
217 match=match, opts=diffopts)
217 match=match, opts=diffopts)
218
218
219 class filectx(object):
219 class filectx(object):
220 """A filecontext object makes access to data related to a particular
220 """A filecontext object makes access to data related to a particular
221 filerevision convenient."""
221 filerevision convenient."""
222 def __init__(self, repo, path, changeid=None, fileid=None,
222 def __init__(self, repo, path, changeid=None, fileid=None,
223 filelog=None, changectx=None):
223 filelog=None, changectx=None):
224 """changeid can be a changeset revision, node, or tag.
224 """changeid can be a changeset revision, node, or tag.
225 fileid can be a file revision or node."""
225 fileid can be a file revision or node."""
226 self._repo = repo
226 self._repo = repo
227 self._path = path
227 self._path = path
228
228
229 assert (changeid is not None
229 assert (changeid is not None
230 or fileid is not None
230 or fileid is not None
231 or changectx is not None), \
231 or changectx is not None), \
232 ("bad args: changeid=%r, fileid=%r, changectx=%r"
232 ("bad args: changeid=%r, fileid=%r, changectx=%r"
233 % (changeid, fileid, changectx))
233 % (changeid, fileid, changectx))
234
234
235 if filelog:
235 if filelog:
236 self._filelog = filelog
236 self._filelog = filelog
237
237
238 if changeid is not None:
238 if changeid is not None:
239 self._changeid = changeid
239 self._changeid = changeid
240 if changectx is not None:
240 if changectx is not None:
241 self._changectx = changectx
241 self._changectx = changectx
242 if fileid is not None:
242 if fileid is not None:
243 self._fileid = fileid
243 self._fileid = fileid
244
244
245 @propertycache
245 @propertycache
246 def _changectx(self):
246 def _changectx(self):
247 return changectx(self._repo, self._changeid)
247 return changectx(self._repo, self._changeid)
248
248
249 @propertycache
249 @propertycache
250 def _filelog(self):
250 def _filelog(self):
251 return self._repo.file(self._path)
251 return self._repo.file(self._path)
252
252
253 @propertycache
253 @propertycache
254 def _changeid(self):
254 def _changeid(self):
255 if '_changectx' in self.__dict__:
255 if '_changectx' in self.__dict__:
256 return self._changectx.rev()
256 return self._changectx.rev()
257 else:
257 else:
258 return self._filelog.linkrev(self._filerev)
258 return self._filelog.linkrev(self._filerev)
259
259
260 @propertycache
260 @propertycache
261 def _filenode(self):
261 def _filenode(self):
262 if '_fileid' in self.__dict__:
262 if '_fileid' in self.__dict__:
263 return self._filelog.lookup(self._fileid)
263 return self._filelog.lookup(self._fileid)
264 else:
264 else:
265 return self._changectx.filenode(self._path)
265 return self._changectx.filenode(self._path)
266
266
267 @propertycache
267 @propertycache
268 def _filerev(self):
268 def _filerev(self):
269 return self._filelog.rev(self._filenode)
269 return self._filelog.rev(self._filenode)
270
270
271 @propertycache
271 @propertycache
272 def _repopath(self):
272 def _repopath(self):
273 return self._path
273 return self._path
274
274
275 def __nonzero__(self):
275 def __nonzero__(self):
276 try:
276 try:
277 self._filenode
277 self._filenode
278 return True
278 return True
279 except error.LookupError:
279 except error.LookupError:
280 # file is missing
280 # file is missing
281 return False
281 return False
282
282
283 def __str__(self):
283 def __str__(self):
284 return "%s@%s" % (self.path(), short(self.node()))
284 return "%s@%s" % (self.path(), short(self.node()))
285
285
286 def __repr__(self):
286 def __repr__(self):
287 return "<filectx %s>" % str(self)
287 return "<filectx %s>" % str(self)
288
288
289 def __hash__(self):
289 def __hash__(self):
290 try:
290 try:
291 return hash((self._path, self._filenode))
291 return hash((self._path, self._filenode))
292 except AttributeError:
292 except AttributeError:
293 return id(self)
293 return id(self)
294
294
295 def __eq__(self, other):
295 def __eq__(self, other):
296 try:
296 try:
297 return (self._path == other._path
297 return (self._path == other._path
298 and self._filenode == other._filenode)
298 and self._filenode == other._filenode)
299 except AttributeError:
299 except AttributeError:
300 return False
300 return False
301
301
302 def __ne__(self, other):
302 def __ne__(self, other):
303 return not (self == other)
303 return not (self == other)
304
304
305 def filectx(self, fileid):
305 def filectx(self, fileid):
306 '''opens an arbitrary revision of the file without
306 '''opens an arbitrary revision of the file without
307 opening a new filelog'''
307 opening a new filelog'''
308 return filectx(self._repo, self._path, fileid=fileid,
308 return filectx(self._repo, self._path, fileid=fileid,
309 filelog=self._filelog)
309 filelog=self._filelog)
310
310
311 def filerev(self):
311 def filerev(self):
312 return self._filerev
312 return self._filerev
313 def filenode(self):
313 def filenode(self):
314 return self._filenode
314 return self._filenode
315 def flags(self):
315 def flags(self):
316 return self._changectx.flags(self._path)
316 return self._changectx.flags(self._path)
317 def filelog(self):
317 def filelog(self):
318 return self._filelog
318 return self._filelog
319
319
320 def rev(self):
320 def rev(self):
321 if '_changectx' in self.__dict__:
321 if '_changectx' in self.__dict__:
322 return self._changectx.rev()
322 return self._changectx.rev()
323 if '_changeid' in self.__dict__:
323 if '_changeid' in self.__dict__:
324 return self._changectx.rev()
324 return self._changectx.rev()
325 return self._filelog.linkrev(self._filerev)
325 return self._filelog.linkrev(self._filerev)
326
326
327 def linkrev(self):
327 def linkrev(self):
328 return self._filelog.linkrev(self._filerev)
328 return self._filelog.linkrev(self._filerev)
329 def node(self):
329 def node(self):
330 return self._changectx.node()
330 return self._changectx.node()
331 def hex(self):
331 def hex(self):
332 return hex(self.node())
332 return hex(self.node())
333 def user(self):
333 def user(self):
334 return self._changectx.user()
334 return self._changectx.user()
335 def date(self):
335 def date(self):
336 return self._changectx.date()
336 return self._changectx.date()
337 def files(self):
337 def files(self):
338 return self._changectx.files()
338 return self._changectx.files()
339 def description(self):
339 def description(self):
340 return self._changectx.description()
340 return self._changectx.description()
341 def branch(self):
341 def branch(self):
342 return self._changectx.branch()
342 return self._changectx.branch()
343 def extra(self):
343 def extra(self):
344 return self._changectx.extra()
344 return self._changectx.extra()
345 def manifest(self):
345 def manifest(self):
346 return self._changectx.manifest()
346 return self._changectx.manifest()
347 def changectx(self):
347 def changectx(self):
348 return self._changectx
348 return self._changectx
349
349
350 def data(self):
350 def data(self):
351 return self._filelog.read(self._filenode)
351 return self._filelog.read(self._filenode)
352 def path(self):
352 def path(self):
353 return self._path
353 return self._path
354 def size(self):
354 def size(self):
355 return self._filelog.size(self._filerev)
355 return self._filelog.size(self._filerev)
356
356
357 def cmp(self, fctx):
357 def cmp(self, fctx):
358 """compare with other file context
358 """compare with other file context
359
359
360 returns True if different than fctx.
360 returns True if different than fctx.
361 """
361 """
362 if (fctx._filerev is None and self._repo._encodefilterpats
362 if (fctx._filerev is None and self._repo._encodefilterpats
363 or self.size() == fctx.size()):
363 or self.size() == fctx.size()):
364 return self._filelog.cmp(self._filenode, fctx.data())
364 return self._filelog.cmp(self._filenode, fctx.data())
365
365
366 return True
366 return True
367
367
368 def renamed(self):
368 def renamed(self):
369 """check if file was actually renamed in this changeset revision
369 """check if file was actually renamed in this changeset revision
370
370
371 If rename logged in file revision, we report copy for changeset only
371 If rename logged in file revision, we report copy for changeset only
372 if file revisions linkrev points back to the changeset in question
372 if file revisions linkrev points back to the changeset in question
373 or both changeset parents contain different file revisions.
373 or both changeset parents contain different file revisions.
374 """
374 """
375
375
376 renamed = self._filelog.renamed(self._filenode)
376 renamed = self._filelog.renamed(self._filenode)
377 if not renamed:
377 if not renamed:
378 return renamed
378 return renamed
379
379
380 if self.rev() == self.linkrev():
380 if self.rev() == self.linkrev():
381 return renamed
381 return renamed
382
382
383 name = self.path()
383 name = self.path()
384 fnode = self._filenode
384 fnode = self._filenode
385 for p in self._changectx.parents():
385 for p in self._changectx.parents():
386 try:
386 try:
387 if fnode == p.filenode(name):
387 if fnode == p.filenode(name):
388 return None
388 return None
389 except error.LookupError:
389 except error.LookupError:
390 pass
390 pass
391 return renamed
391 return renamed
392
392
393 def parents(self):
393 def parents(self):
394 p = self._path
394 p = self._path
395 fl = self._filelog
395 fl = self._filelog
396 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
396 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
397
397
398 r = self._filelog.renamed(self._filenode)
398 r = self._filelog.renamed(self._filenode)
399 if r:
399 if r:
400 pl[0] = (r[0], r[1], None)
400 pl[0] = (r[0], r[1], None)
401
401
402 return [filectx(self._repo, p, fileid=n, filelog=l)
402 return [filectx(self._repo, p, fileid=n, filelog=l)
403 for p, n, l in pl if n != nullid]
403 for p, n, l in pl if n != nullid]
404
404
405 def p1(self):
405 def p1(self):
406 return self.parents()[0]
406 return self.parents()[0]
407
407
408 def p2(self):
408 def p2(self):
409 p = self.parents()
409 p = self.parents()
410 if len(p) == 2:
410 if len(p) == 2:
411 return p[1]
411 return p[1]
412 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
412 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
413
413
414 def children(self):
414 def children(self):
415 # hard for renames
415 # hard for renames
416 c = self._filelog.children(self._filenode)
416 c = self._filelog.children(self._filenode)
417 return [filectx(self._repo, self._path, fileid=x,
417 return [filectx(self._repo, self._path, fileid=x,
418 filelog=self._filelog) for x in c]
418 filelog=self._filelog) for x in c]
419
419
420 def annotate(self, follow=False, linenumber=None):
420 def annotate(self, follow=False, linenumber=None):
421 '''returns a list of tuples of (ctx, line) for each line
421 '''returns a list of tuples of (ctx, line) for each line
422 in the file, where ctx is the filectx of the node where
422 in the file, where ctx is the filectx of the node where
423 that line was last changed.
423 that line was last changed.
424 This returns tuples of ((ctx, linenumber), line) for each line,
424 This returns tuples of ((ctx, linenumber), line) for each line,
425 if "linenumber" parameter is NOT "None".
425 if "linenumber" parameter is NOT "None".
426 In such tuples, linenumber means one at the first appearance
426 In such tuples, linenumber means one at the first appearance
427 in the managed file.
427 in the managed file.
428 To reduce annotation cost,
428 To reduce annotation cost,
429 this returns fixed value(False is used) as linenumber,
429 this returns fixed value(False is used) as linenumber,
430 if "linenumber" parameter is "False".'''
430 if "linenumber" parameter is "False".'''
431
431
432 def decorate_compat(text, rev):
432 def decorate_compat(text, rev):
433 return ([rev] * len(text.splitlines()), text)
433 return ([rev] * len(text.splitlines()), text)
434
434
435 def without_linenumber(text, rev):
435 def without_linenumber(text, rev):
436 return ([(rev, False)] * len(text.splitlines()), text)
436 return ([(rev, False)] * len(text.splitlines()), text)
437
437
438 def with_linenumber(text, rev):
438 def with_linenumber(text, rev):
439 size = len(text.splitlines())
439 size = len(text.splitlines())
440 return ([(rev, i) for i in xrange(1, size + 1)], text)
440 return ([(rev, i) for i in xrange(1, size + 1)], text)
441
441
442 decorate = (((linenumber is None) and decorate_compat) or
442 decorate = (((linenumber is None) and decorate_compat) or
443 (linenumber and with_linenumber) or
443 (linenumber and with_linenumber) or
444 without_linenumber)
444 without_linenumber)
445
445
446 def pair(parent, child):
446 def pair(parent, child):
447 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
447 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
448 child[0][b1:b2] = parent[0][a1:a2]
448 child[0][b1:b2] = parent[0][a1:a2]
449 return child
449 return child
450
450
451 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
451 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
452 def getctx(path, fileid):
452 def getctx(path, fileid):
453 log = path == self._path and self._filelog or getlog(path)
453 log = path == self._path and self._filelog or getlog(path)
454 return filectx(self._repo, path, fileid=fileid, filelog=log)
454 return filectx(self._repo, path, fileid=fileid, filelog=log)
455 getctx = util.lrucachefunc(getctx)
455 getctx = util.lrucachefunc(getctx)
456
456
457 def parents(f):
457 def parents(f):
458 # we want to reuse filectx objects as much as possible
458 # we want to reuse filectx objects as much as possible
459 p = f._path
459 p = f._path
460 if f._filerev is None: # working dir
460 if f._filerev is None: # working dir
461 pl = [(n.path(), n.filerev()) for n in f.parents()]
461 pl = [(n.path(), n.filerev()) for n in f.parents()]
462 else:
462 else:
463 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
463 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
464
464
465 if follow:
465 if follow:
466 r = f.renamed()
466 r = f.renamed()
467 if r:
467 if r:
468 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
468 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
469
469
470 return [getctx(p, n) for p, n in pl if n != nullrev]
470 return [getctx(p, n) for p, n in pl if n != nullrev]
471
471
472 # use linkrev to find the first changeset where self appeared
472 # use linkrev to find the first changeset where self appeared
473 if self.rev() != self.linkrev():
473 if self.rev() != self.linkrev():
474 base = self.filectx(self.filerev())
474 base = self.filectx(self.filerev())
475 else:
475 else:
476 base = self
476 base = self
477
477
478 # This algorithm would prefer to be recursive, but Python is a
478 # This algorithm would prefer to be recursive, but Python is a
479 # bit recursion-hostile. Instead we do an iterative
479 # bit recursion-hostile. Instead we do an iterative
480 # depth-first search.
480 # depth-first search.
481
481
482 visit = [base]
482 visit = [base]
483 hist = {}
483 hist = {}
484 pcache = {}
484 pcache = {}
485 needed = {base: 1}
485 needed = {base: 1}
486 while visit:
486 while visit:
487 f = visit[-1]
487 f = visit[-1]
488 if f not in pcache:
488 if f not in pcache:
489 pcache[f] = parents(f)
489 pcache[f] = parents(f)
490
490
491 ready = True
491 ready = True
492 pl = pcache[f]
492 pl = pcache[f]
493 for p in pl:
493 for p in pl:
494 if p not in hist:
494 if p not in hist:
495 ready = False
495 ready = False
496 visit.append(p)
496 visit.append(p)
497 needed[p] = needed.get(p, 0) + 1
497 needed[p] = needed.get(p, 0) + 1
498 if ready:
498 if ready:
499 visit.pop()
499 visit.pop()
500 curr = decorate(f.data(), f)
500 curr = decorate(f.data(), f)
501 for p in pl:
501 for p in pl:
502 curr = pair(hist[p], curr)
502 curr = pair(hist[p], curr)
503 if needed[p] == 1:
503 if needed[p] == 1:
504 del hist[p]
504 del hist[p]
505 else:
505 else:
506 needed[p] -= 1
506 needed[p] -= 1
507
507
508 hist[f] = curr
508 hist[f] = curr
509 pcache[f] = []
509 pcache[f] = []
510
510
511 return zip(hist[base][0], hist[base][1].splitlines(True))
511 return zip(hist[base][0], hist[base][1].splitlines(True))
512
512
513 def ancestor(self, fc2, actx=None):
513 def ancestor(self, fc2, actx=None):
514 """
514 """
515 find the common ancestor file context, if any, of self, and fc2
515 find the common ancestor file context, if any, of self, and fc2
516
516
517 If actx is given, it must be the changectx of the common ancestor
517 If actx is given, it must be the changectx of the common ancestor
518 of self's and fc2's respective changesets.
518 of self's and fc2's respective changesets.
519 """
519 """
520
520
521 if actx is None:
521 if actx is None:
522 actx = self.changectx().ancestor(fc2.changectx())
522 actx = self.changectx().ancestor(fc2.changectx())
523
523
524 # the trivial case: changesets are unrelated, files must be too
524 # the trivial case: changesets are unrelated, files must be too
525 if not actx:
525 if not actx:
526 return None
526 return None
527
527
528 # the easy case: no (relevant) renames
528 # the easy case: no (relevant) renames
529 if fc2.path() == self.path() and self.path() in actx:
529 if fc2.path() == self.path() and self.path() in actx:
530 return actx[self.path()]
530 return actx[self.path()]
531 acache = {}
531 acache = {}
532
532
533 # prime the ancestor cache for the working directory
533 # prime the ancestor cache for the working directory
534 for c in (self, fc2):
534 for c in (self, fc2):
535 if c._filerev is None:
535 if c._filerev is None:
536 pl = [(n.path(), n.filenode()) for n in c.parents()]
536 pl = [(n.path(), n.filenode()) for n in c.parents()]
537 acache[(c._path, None)] = pl
537 acache[(c._path, None)] = pl
538
538
539 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
539 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
540 def parents(vertex):
540 def parents(vertex):
541 if vertex in acache:
541 if vertex in acache:
542 return acache[vertex]
542 return acache[vertex]
543 f, n = vertex
543 f, n = vertex
544 if f not in flcache:
544 if f not in flcache:
545 flcache[f] = self._repo.file(f)
545 flcache[f] = self._repo.file(f)
546 fl = flcache[f]
546 fl = flcache[f]
547 pl = [(f, p) for p in fl.parents(n) if p != nullid]
547 pl = [(f, p) for p in fl.parents(n) if p != nullid]
548 re = fl.renamed(n)
548 re = fl.renamed(n)
549 if re:
549 if re:
550 pl.append(re)
550 pl.append(re)
551 acache[vertex] = pl
551 acache[vertex] = pl
552 return pl
552 return pl
553
553
554 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
554 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
555 v = ancestor.ancestor(a, b, parents)
555 v = ancestor.ancestor(a, b, parents)
556 if v:
556 if v:
557 f, n = v
557 f, n = v
558 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
558 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
559
559
560 return None
560 return None
561
561
562 def ancestors(self):
562 def ancestors(self):
563 visit = {}
563 visit = {}
564 c = self
564 c = self
565 while True:
565 while True:
566 for parent in c.parents():
566 for parent in c.parents():
567 visit[(parent.rev(), parent.node())] = parent
567 visit[(parent.rev(), parent.node())] = parent
568 if not visit:
568 if not visit:
569 break
569 break
570 c = visit.pop(max(visit))
570 c = visit.pop(max(visit))
571 yield c
571 yield c
572
572
573 class workingctx(changectx):
573 class workingctx(changectx):
574 """A workingctx object makes access to data related to
574 """A workingctx object makes access to data related to
575 the current working directory convenient.
575 the current working directory convenient.
576 date - any valid date string or (unixtime, offset), or None.
576 date - any valid date string or (unixtime, offset), or None.
577 user - username string, or None.
577 user - username string, or None.
578 extra - a dictionary of extra values, or None.
578 extra - a dictionary of extra values, or None.
579 changes - a list of file lists as returned by localrepo.status()
579 changes - a list of file lists as returned by localrepo.status()
580 or None to use the repository status.
580 or None to use the repository status.
581 """
581 """
582 def __init__(self, repo, text="", user=None, date=None, extra=None,
582 def __init__(self, repo, text="", user=None, date=None, extra=None,
583 changes=None):
583 changes=None):
584 self._repo = repo
584 self._repo = repo
585 self._rev = None
585 self._rev = None
586 self._node = None
586 self._node = None
587 self._text = text
587 self._text = text
588 if date:
588 if date:
589 self._date = util.parsedate(date)
589 self._date = util.parsedate(date)
590 if user:
590 if user:
591 self._user = user
591 self._user = user
592 if changes:
592 if changes:
593 self._status = list(changes[:4])
593 self._status = list(changes[:4])
594 self._unknown = changes[4]
594 self._unknown = changes[4]
595 self._ignored = changes[5]
595 self._ignored = changes[5]
596 self._clean = changes[6]
596 self._clean = changes[6]
597 else:
597 else:
598 self._unknown = None
598 self._unknown = None
599 self._ignored = None
599 self._ignored = None
600 self._clean = None
600 self._clean = None
601
601
602 self._extra = {}
602 self._extra = {}
603 if extra:
603 if extra:
604 self._extra = extra.copy()
604 self._extra = extra.copy()
605 if 'branch' not in self._extra:
605 if 'branch' not in self._extra:
606 try:
606 try:
607 branch = encoding.fromlocal(self._repo.dirstate.branch())
607 branch = encoding.fromlocal(self._repo.dirstate.branch())
608 except UnicodeDecodeError:
608 except UnicodeDecodeError:
609 raise util.Abort(_('branch name not in UTF-8!'))
609 raise util.Abort(_('branch name not in UTF-8!'))
610 self._extra['branch'] = branch
610 self._extra['branch'] = branch
611 if self._extra['branch'] == '':
611 if self._extra['branch'] == '':
612 self._extra['branch'] = 'default'
612 self._extra['branch'] = 'default'
613
613
614 def __str__(self):
614 def __str__(self):
615 return str(self._parents[0]) + "+"
615 return str(self._parents[0]) + "+"
616
616
617 def __repr__(self):
617 def __repr__(self):
618 return "<workingctx %s>" % str(self)
618 return "<workingctx %s>" % str(self)
619
619
620 def __nonzero__(self):
620 def __nonzero__(self):
621 return True
621 return True
622
622
623 def __contains__(self, key):
623 def __contains__(self, key):
624 return self._repo.dirstate[key] not in "?r"
624 return self._repo.dirstate[key] not in "?r"
625
625
626 @propertycache
626 @propertycache
627 def _manifest(self):
627 def _manifest(self):
628 """generate a manifest corresponding to the working directory"""
628 """generate a manifest corresponding to the working directory"""
629
629
630 if self._unknown is None:
630 if self._unknown is None:
631 self.status(unknown=True)
631 self.status(unknown=True)
632
632
633 man = self._parents[0].manifest().copy()
633 man = self._parents[0].manifest().copy()
634 copied = self._repo.dirstate.copies()
634 copied = self._repo.dirstate.copies()
635 if len(self._parents) > 1:
635 if len(self._parents) > 1:
636 man2 = self.p2().manifest()
636 man2 = self.p2().manifest()
637 def getman(f):
637 def getman(f):
638 if f in man:
638 if f in man:
639 return man
639 return man
640 return man2
640 return man2
641 else:
641 else:
642 getman = lambda f: man
642 getman = lambda f: man
643 def cf(f):
643 def cf(f):
644 f = copied.get(f, f)
644 f = copied.get(f, f)
645 return getman(f).flags(f)
645 return getman(f).flags(f)
646 ff = self._repo.dirstate.flagfunc(cf)
646 ff = self._repo.dirstate.flagfunc(cf)
647 modified, added, removed, deleted = self._status
647 modified, added, removed, deleted = self._status
648 unknown = self._unknown
648 unknown = self._unknown
649 for i, l in (("a", added), ("m", modified), ("u", unknown)):
649 for i, l in (("a", added), ("m", modified), ("u", unknown)):
650 for f in l:
650 for f in l:
651 orig = copied.get(f, f)
651 orig = copied.get(f, f)
652 man[f] = getman(orig).get(orig, nullid) + i
652 man[f] = getman(orig).get(orig, nullid) + i
653 try:
653 try:
654 man.set(f, ff(f))
654 man.set(f, ff(f))
655 except OSError:
655 except OSError:
656 pass
656 pass
657
657
658 for f in deleted + removed:
658 for f in deleted + removed:
659 if f in man:
659 if f in man:
660 del man[f]
660 del man[f]
661
661
662 return man
662 return man
663
663
664 def __iter__(self):
664 def __iter__(self):
665 d = self._repo.dirstate
665 d = self._repo.dirstate
666 for f in d:
666 for f in d:
667 if d[f] != 'r':
667 if d[f] != 'r':
668 yield f
668 yield f
669
669
670 @propertycache
670 @propertycache
671 def _status(self):
671 def _status(self):
672 return self._repo.status()[:4]
672 return self._repo.status()[:4]
673
673
674 @propertycache
674 @propertycache
675 def _user(self):
675 def _user(self):
676 return self._repo.ui.username()
676 return self._repo.ui.username()
677
677
678 @propertycache
678 @propertycache
679 def _date(self):
679 def _date(self):
680 return util.makedate()
680 return util.makedate()
681
681
682 @propertycache
682 @propertycache
683 def _parents(self):
683 def _parents(self):
684 p = self._repo.dirstate.parents()
684 p = self._repo.dirstate.parents()
685 if p[1] == nullid:
685 if p[1] == nullid:
686 p = p[:-1]
686 p = p[:-1]
687 self._parents = [changectx(self._repo, x) for x in p]
687 self._parents = [changectx(self._repo, x) for x in p]
688 return self._parents
688 return self._parents
689
689
690 def status(self, ignored=False, clean=False, unknown=False):
690 def status(self, ignored=False, clean=False, unknown=False):
691 """Explicit status query
691 """Explicit status query
692 Unless this method is used to query the working copy status, the
692 Unless this method is used to query the working copy status, the
693 _status property will implicitly read the status using its default
693 _status property will implicitly read the status using its default
694 arguments."""
694 arguments."""
695 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
695 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
696 self._unknown = self._ignored = self._clean = None
696 self._unknown = self._ignored = self._clean = None
697 if unknown:
697 if unknown:
698 self._unknown = stat[4]
698 self._unknown = stat[4]
699 if ignored:
699 if ignored:
700 self._ignored = stat[5]
700 self._ignored = stat[5]
701 if clean:
701 if clean:
702 self._clean = stat[6]
702 self._clean = stat[6]
703 self._status = stat[:4]
703 self._status = stat[:4]
704 return stat
704 return stat
705
705
706 def manifest(self):
706 def manifest(self):
707 return self._manifest
707 return self._manifest
708 def user(self):
708 def user(self):
709 return self._user or self._repo.ui.username()
709 return self._user or self._repo.ui.username()
710 def date(self):
710 def date(self):
711 return self._date
711 return self._date
712 def description(self):
712 def description(self):
713 return self._text
713 return self._text
714 def files(self):
714 def files(self):
715 return sorted(self._status[0] + self._status[1] + self._status[2])
715 return sorted(self._status[0] + self._status[1] + self._status[2])
716
716
717 def modified(self):
717 def modified(self):
718 return self._status[0]
718 return self._status[0]
719 def added(self):
719 def added(self):
720 return self._status[1]
720 return self._status[1]
721 def removed(self):
721 def removed(self):
722 return self._status[2]
722 return self._status[2]
723 def deleted(self):
723 def deleted(self):
724 return self._status[3]
724 return self._status[3]
725 def unknown(self):
725 def unknown(self):
726 assert self._unknown is not None # must call status first
726 assert self._unknown is not None # must call status first
727 return self._unknown
727 return self._unknown
728 def ignored(self):
728 def ignored(self):
729 assert self._ignored is not None # must call status first
729 assert self._ignored is not None # must call status first
730 return self._ignored
730 return self._ignored
731 def clean(self):
731 def clean(self):
732 assert self._clean is not None # must call status first
732 assert self._clean is not None # must call status first
733 return self._clean
733 return self._clean
734 def branch(self):
734 def branch(self):
735 return encoding.tolocal(self._extra['branch'])
735 return encoding.tolocal(self._extra['branch'])
736 def extra(self):
736 def extra(self):
737 return self._extra
737 return self._extra
738
738
739 def tags(self):
739 def tags(self):
740 t = []
740 t = []
741 for p in self.parents():
741 for p in self.parents():
742 t.extend(p.tags())
742 t.extend(p.tags())
743 return t
743 return t
744
744
745 def bookmarks(self):
745 def bookmarks(self):
746 b = []
746 b = []
747 for p in self.parents():
747 for p in self.parents():
748 b.extend(p.bookmarks())
748 b.extend(p.bookmarks())
749 return b
749 return b
750
750
751 def children(self):
751 def children(self):
752 return []
752 return []
753
753
754 def flags(self, path):
754 def flags(self, path):
755 if '_manifest' in self.__dict__:
755 if '_manifest' in self.__dict__:
756 try:
756 try:
757 return self._manifest.flags(path)
757 return self._manifest.flags(path)
758 except KeyError:
758 except KeyError:
759 return ''
759 return ''
760
760
761 orig = self._repo.dirstate.copies().get(path, path)
761 orig = self._repo.dirstate.copies().get(path, path)
762
762
763 def findflag(ctx):
763 def findflag(ctx):
764 mnode = ctx.changeset()[0]
764 mnode = ctx.changeset()[0]
765 node, flag = self._repo.manifest.find(mnode, orig)
765 node, flag = self._repo.manifest.find(mnode, orig)
766 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
766 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
767 try:
767 try:
768 return ff(path)
768 return ff(path)
769 except OSError:
769 except OSError:
770 pass
770 pass
771
771
772 flag = findflag(self._parents[0])
772 flag = findflag(self._parents[0])
773 if flag is None and len(self.parents()) > 1:
773 if flag is None and len(self.parents()) > 1:
774 flag = findflag(self._parents[1])
774 flag = findflag(self._parents[1])
775 if flag is None or self._repo.dirstate[path] == 'r':
775 if flag is None or self._repo.dirstate[path] == 'r':
776 return ''
776 return ''
777 return flag
777 return flag
778
778
779 def filectx(self, path, filelog=None):
779 def filectx(self, path, filelog=None):
780 """get a file context from the working directory"""
780 """get a file context from the working directory"""
781 return workingfilectx(self._repo, path, workingctx=self,
781 return workingfilectx(self._repo, path, workingctx=self,
782 filelog=filelog)
782 filelog=filelog)
783
783
784 def ancestor(self, c2):
784 def ancestor(self, c2):
785 """return the ancestor context of self and c2"""
785 """return the ancestor context of self and c2"""
786 return self._parents[0].ancestor(c2) # punt on two parents for now
786 return self._parents[0].ancestor(c2) # punt on two parents for now
787
787
788 def walk(self, match):
788 def walk(self, match):
789 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
789 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
790 True, False))
790 True, False))
791
791
792 def dirty(self, missing=False):
792 def dirty(self, missing=False):
793 "check whether a working directory is modified"
793 "check whether a working directory is modified"
794 # check subrepos first
794 # check subrepos first
795 for s in self.substate:
795 for s in self.substate:
796 if self.sub(s).dirty():
796 if self.sub(s).dirty():
797 return True
797 return True
798 # check current working dir
798 # check current working dir
799 return (self.p2() or self.branch() != self.p1().branch() or
799 return (self.p2() or self.branch() != self.p1().branch() or
800 self.modified() or self.added() or self.removed() or
800 self.modified() or self.added() or self.removed() or
801 (missing and self.deleted()))
801 (missing and self.deleted()))
802
802
803 def add(self, list, prefix=""):
803 def add(self, list, prefix=""):
804 join = lambda f: os.path.join(prefix, f)
804 join = lambda f: os.path.join(prefix, f)
805 wlock = self._repo.wlock()
805 wlock = self._repo.wlock()
806 ui, ds = self._repo.ui, self._repo.dirstate
806 ui, ds = self._repo.ui, self._repo.dirstate
807 try:
807 try:
808 rejected = []
808 rejected = []
809 for f in list:
809 for f in list:
810 scmutil.checkportable(ui, join(f))
810 scmutil.checkportable(ui, join(f))
811 p = self._repo.wjoin(f)
811 p = self._repo.wjoin(f)
812 try:
812 try:
813 st = os.lstat(p)
813 st = os.lstat(p)
814 except OSError:
814 except OSError:
815 ui.warn(_("%s does not exist!\n") % join(f))
815 ui.warn(_("%s does not exist!\n") % join(f))
816 rejected.append(f)
816 rejected.append(f)
817 continue
817 continue
818 if st.st_size > 10000000:
818 if st.st_size > 10000000:
819 ui.warn(_("%s: up to %d MB of RAM may be required "
819 ui.warn(_("%s: up to %d MB of RAM may be required "
820 "to manage this file\n"
820 "to manage this file\n"
821 "(use 'hg revert %s' to cancel the "
821 "(use 'hg revert %s' to cancel the "
822 "pending addition)\n")
822 "pending addition)\n")
823 % (f, 3 * st.st_size // 1000000, join(f)))
823 % (f, 3 * st.st_size // 1000000, join(f)))
824 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
824 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
825 ui.warn(_("%s not added: only files and symlinks "
825 ui.warn(_("%s not added: only files and symlinks "
826 "supported currently\n") % join(f))
826 "supported currently\n") % join(f))
827 rejected.append(p)
827 rejected.append(p)
828 elif ds[f] in 'amn':
828 elif ds[f] in 'amn':
829 ui.warn(_("%s already tracked!\n") % join(f))
829 ui.warn(_("%s already tracked!\n") % join(f))
830 elif ds[f] == 'r':
830 elif ds[f] == 'r':
831 ds.normallookup(f)
831 ds.normallookup(f)
832 else:
832 else:
833 ds.add(f)
833 ds.add(f)
834 return rejected
834 return rejected
835 finally:
835 finally:
836 wlock.release()
836 wlock.release()
837
837
838 def forget(self, files):
838 def forget(self, files):
839 wlock = self._repo.wlock()
839 wlock = self._repo.wlock()
840 try:
840 try:
841 for f in files:
841 for f in files:
842 if self._repo.dirstate[f] != 'a':
842 if self._repo.dirstate[f] != 'a':
843 self._repo.dirstate.remove(f)
843 self._repo.dirstate.remove(f)
844 elif f not in self._repo.dirstate:
844 elif f not in self._repo.dirstate:
845 self._repo.ui.warn(_("%s not tracked!\n") % f)
845 self._repo.ui.warn(_("%s not tracked!\n") % f)
846 else:
846 else:
847 self._repo.dirstate.drop(f)
847 self._repo.dirstate.drop(f)
848 finally:
848 finally:
849 wlock.release()
849 wlock.release()
850
850
851 def ancestors(self):
851 def ancestors(self):
852 for a in self._repo.changelog.ancestors(
852 for a in self._repo.changelog.ancestors(
853 *[p.rev() for p in self._parents]):
853 *[p.rev() for p in self._parents]):
854 yield changectx(self._repo, a)
854 yield changectx(self._repo, a)
855
855
856 def remove(self, list, unlink=False):
857 wlock = self._repo.wlock()
858 try:
859 if unlink:
860 for f in list:
861 try:
862 util.unlinkpath(self._repo.wjoin(f))
863 except OSError, inst:
864 if inst.errno != errno.ENOENT:
865 raise
866 self.forget(list)
867 finally:
868 wlock.release()
869
870 def undelete(self, list):
856 def undelete(self, list):
871 pctxs = self.parents()
857 pctxs = self.parents()
872 wlock = self._repo.wlock()
858 wlock = self._repo.wlock()
873 try:
859 try:
874 for f in list:
860 for f in list:
875 if self._repo.dirstate[f] != 'r':
861 if self._repo.dirstate[f] != 'r':
876 self._repo.ui.warn(_("%s not removed!\n") % f)
862 self._repo.ui.warn(_("%s not removed!\n") % f)
877 else:
863 else:
878 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
864 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
879 t = fctx.data()
865 t = fctx.data()
880 self._repo.wwrite(f, t, fctx.flags())
866 self._repo.wwrite(f, t, fctx.flags())
881 self._repo.dirstate.normal(f)
867 self._repo.dirstate.normal(f)
882 finally:
868 finally:
883 wlock.release()
869 wlock.release()
884
870
885 def copy(self, source, dest):
871 def copy(self, source, dest):
886 p = self._repo.wjoin(dest)
872 p = self._repo.wjoin(dest)
887 if not os.path.lexists(p):
873 if not os.path.lexists(p):
888 self._repo.ui.warn(_("%s does not exist!\n") % dest)
874 self._repo.ui.warn(_("%s does not exist!\n") % dest)
889 elif not (os.path.isfile(p) or os.path.islink(p)):
875 elif not (os.path.isfile(p) or os.path.islink(p)):
890 self._repo.ui.warn(_("copy failed: %s is not a file or a "
876 self._repo.ui.warn(_("copy failed: %s is not a file or a "
891 "symbolic link\n") % dest)
877 "symbolic link\n") % dest)
892 else:
878 else:
893 wlock = self._repo.wlock()
879 wlock = self._repo.wlock()
894 try:
880 try:
895 if self._repo.dirstate[dest] in '?r':
881 if self._repo.dirstate[dest] in '?r':
896 self._repo.dirstate.add(dest)
882 self._repo.dirstate.add(dest)
897 self._repo.dirstate.copy(source, dest)
883 self._repo.dirstate.copy(source, dest)
898 finally:
884 finally:
899 wlock.release()
885 wlock.release()
900
886
901 class workingfilectx(filectx):
887 class workingfilectx(filectx):
902 """A workingfilectx object makes access to data related to a particular
888 """A workingfilectx object makes access to data related to a particular
903 file in the working directory convenient."""
889 file in the working directory convenient."""
904 def __init__(self, repo, path, filelog=None, workingctx=None):
890 def __init__(self, repo, path, filelog=None, workingctx=None):
905 """changeid can be a changeset revision, node, or tag.
891 """changeid can be a changeset revision, node, or tag.
906 fileid can be a file revision or node."""
892 fileid can be a file revision or node."""
907 self._repo = repo
893 self._repo = repo
908 self._path = path
894 self._path = path
909 self._changeid = None
895 self._changeid = None
910 self._filerev = self._filenode = None
896 self._filerev = self._filenode = None
911
897
912 if filelog:
898 if filelog:
913 self._filelog = filelog
899 self._filelog = filelog
914 if workingctx:
900 if workingctx:
915 self._changectx = workingctx
901 self._changectx = workingctx
916
902
917 @propertycache
903 @propertycache
918 def _changectx(self):
904 def _changectx(self):
919 return workingctx(self._repo)
905 return workingctx(self._repo)
920
906
921 def __nonzero__(self):
907 def __nonzero__(self):
922 return True
908 return True
923
909
924 def __str__(self):
910 def __str__(self):
925 return "%s@%s" % (self.path(), self._changectx)
911 return "%s@%s" % (self.path(), self._changectx)
926
912
927 def __repr__(self):
913 def __repr__(self):
928 return "<workingfilectx %s>" % str(self)
914 return "<workingfilectx %s>" % str(self)
929
915
930 def data(self):
916 def data(self):
931 return self._repo.wread(self._path)
917 return self._repo.wread(self._path)
932 def renamed(self):
918 def renamed(self):
933 rp = self._repo.dirstate.copied(self._path)
919 rp = self._repo.dirstate.copied(self._path)
934 if not rp:
920 if not rp:
935 return None
921 return None
936 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
922 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
937
923
938 def parents(self):
924 def parents(self):
939 '''return parent filectxs, following copies if necessary'''
925 '''return parent filectxs, following copies if necessary'''
940 def filenode(ctx, path):
926 def filenode(ctx, path):
941 return ctx._manifest.get(path, nullid)
927 return ctx._manifest.get(path, nullid)
942
928
943 path = self._path
929 path = self._path
944 fl = self._filelog
930 fl = self._filelog
945 pcl = self._changectx._parents
931 pcl = self._changectx._parents
946 renamed = self.renamed()
932 renamed = self.renamed()
947
933
948 if renamed:
934 if renamed:
949 pl = [renamed + (None,)]
935 pl = [renamed + (None,)]
950 else:
936 else:
951 pl = [(path, filenode(pcl[0], path), fl)]
937 pl = [(path, filenode(pcl[0], path), fl)]
952
938
953 for pc in pcl[1:]:
939 for pc in pcl[1:]:
954 pl.append((path, filenode(pc, path), fl))
940 pl.append((path, filenode(pc, path), fl))
955
941
956 return [filectx(self._repo, p, fileid=n, filelog=l)
942 return [filectx(self._repo, p, fileid=n, filelog=l)
957 for p, n, l in pl if n != nullid]
943 for p, n, l in pl if n != nullid]
958
944
959 def children(self):
945 def children(self):
960 return []
946 return []
961
947
962 def size(self):
948 def size(self):
963 return os.lstat(self._repo.wjoin(self._path)).st_size
949 return os.lstat(self._repo.wjoin(self._path)).st_size
964 def date(self):
950 def date(self):
965 t, tz = self._changectx.date()
951 t, tz = self._changectx.date()
966 try:
952 try:
967 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
953 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
968 except OSError, err:
954 except OSError, err:
969 if err.errno != errno.ENOENT:
955 if err.errno != errno.ENOENT:
970 raise
956 raise
971 return (t, tz)
957 return (t, tz)
972
958
973 def cmp(self, fctx):
959 def cmp(self, fctx):
974 """compare with other file context
960 """compare with other file context
975
961
976 returns True if different than fctx.
962 returns True if different than fctx.
977 """
963 """
978 # fctx should be a filectx (not a wfctx)
964 # fctx should be a filectx (not a wfctx)
979 # invert comparison to reuse the same code path
965 # invert comparison to reuse the same code path
980 return fctx.cmp(self)
966 return fctx.cmp(self)
981
967
982 class memctx(object):
968 class memctx(object):
983 """Use memctx to perform in-memory commits via localrepo.commitctx().
969 """Use memctx to perform in-memory commits via localrepo.commitctx().
984
970
985 Revision information is supplied at initialization time while
971 Revision information is supplied at initialization time while
986 related files data and is made available through a callback
972 related files data and is made available through a callback
987 mechanism. 'repo' is the current localrepo, 'parents' is a
973 mechanism. 'repo' is the current localrepo, 'parents' is a
988 sequence of two parent revisions identifiers (pass None for every
974 sequence of two parent revisions identifiers (pass None for every
989 missing parent), 'text' is the commit message and 'files' lists
975 missing parent), 'text' is the commit message and 'files' lists
990 names of files touched by the revision (normalized and relative to
976 names of files touched by the revision (normalized and relative to
991 repository root).
977 repository root).
992
978
993 filectxfn(repo, memctx, path) is a callable receiving the
979 filectxfn(repo, memctx, path) is a callable receiving the
994 repository, the current memctx object and the normalized path of
980 repository, the current memctx object and the normalized path of
995 requested file, relative to repository root. It is fired by the
981 requested file, relative to repository root. It is fired by the
996 commit function for every file in 'files', but calls order is
982 commit function for every file in 'files', but calls order is
997 undefined. If the file is available in the revision being
983 undefined. If the file is available in the revision being
998 committed (updated or added), filectxfn returns a memfilectx
984 committed (updated or added), filectxfn returns a memfilectx
999 object. If the file was removed, filectxfn raises an
985 object. If the file was removed, filectxfn raises an
1000 IOError. Moved files are represented by marking the source file
986 IOError. Moved files are represented by marking the source file
1001 removed and the new file added with copy information (see
987 removed and the new file added with copy information (see
1002 memfilectx).
988 memfilectx).
1003
989
1004 user receives the committer name and defaults to current
990 user receives the committer name and defaults to current
1005 repository username, date is the commit date in any format
991 repository username, date is the commit date in any format
1006 supported by util.parsedate() and defaults to current date, extra
992 supported by util.parsedate() and defaults to current date, extra
1007 is a dictionary of metadata or is left empty.
993 is a dictionary of metadata or is left empty.
1008 """
994 """
1009 def __init__(self, repo, parents, text, files, filectxfn, user=None,
995 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1010 date=None, extra=None):
996 date=None, extra=None):
1011 self._repo = repo
997 self._repo = repo
1012 self._rev = None
998 self._rev = None
1013 self._node = None
999 self._node = None
1014 self._text = text
1000 self._text = text
1015 self._date = date and util.parsedate(date) or util.makedate()
1001 self._date = date and util.parsedate(date) or util.makedate()
1016 self._user = user
1002 self._user = user
1017 parents = [(p or nullid) for p in parents]
1003 parents = [(p or nullid) for p in parents]
1018 p1, p2 = parents
1004 p1, p2 = parents
1019 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1005 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1020 files = sorted(set(files))
1006 files = sorted(set(files))
1021 self._status = [files, [], [], [], []]
1007 self._status = [files, [], [], [], []]
1022 self._filectxfn = filectxfn
1008 self._filectxfn = filectxfn
1023
1009
1024 self._extra = extra and extra.copy() or {}
1010 self._extra = extra and extra.copy() or {}
1025 if 'branch' not in self._extra:
1011 if 'branch' not in self._extra:
1026 self._extra['branch'] = 'default'
1012 self._extra['branch'] = 'default'
1027 elif self._extra.get('branch') == '':
1013 elif self._extra.get('branch') == '':
1028 self._extra['branch'] = 'default'
1014 self._extra['branch'] = 'default'
1029
1015
1030 def __str__(self):
1016 def __str__(self):
1031 return str(self._parents[0]) + "+"
1017 return str(self._parents[0]) + "+"
1032
1018
1033 def __int__(self):
1019 def __int__(self):
1034 return self._rev
1020 return self._rev
1035
1021
1036 def __nonzero__(self):
1022 def __nonzero__(self):
1037 return True
1023 return True
1038
1024
1039 def __getitem__(self, key):
1025 def __getitem__(self, key):
1040 return self.filectx(key)
1026 return self.filectx(key)
1041
1027
1042 def p1(self):
1028 def p1(self):
1043 return self._parents[0]
1029 return self._parents[0]
1044 def p2(self):
1030 def p2(self):
1045 return self._parents[1]
1031 return self._parents[1]
1046
1032
1047 def user(self):
1033 def user(self):
1048 return self._user or self._repo.ui.username()
1034 return self._user or self._repo.ui.username()
1049 def date(self):
1035 def date(self):
1050 return self._date
1036 return self._date
1051 def description(self):
1037 def description(self):
1052 return self._text
1038 return self._text
1053 def files(self):
1039 def files(self):
1054 return self.modified()
1040 return self.modified()
1055 def modified(self):
1041 def modified(self):
1056 return self._status[0]
1042 return self._status[0]
1057 def added(self):
1043 def added(self):
1058 return self._status[1]
1044 return self._status[1]
1059 def removed(self):
1045 def removed(self):
1060 return self._status[2]
1046 return self._status[2]
1061 def deleted(self):
1047 def deleted(self):
1062 return self._status[3]
1048 return self._status[3]
1063 def unknown(self):
1049 def unknown(self):
1064 return self._status[4]
1050 return self._status[4]
1065 def ignored(self):
1051 def ignored(self):
1066 return self._status[5]
1052 return self._status[5]
1067 def clean(self):
1053 def clean(self):
1068 return self._status[6]
1054 return self._status[6]
1069 def branch(self):
1055 def branch(self):
1070 return encoding.tolocal(self._extra['branch'])
1056 return encoding.tolocal(self._extra['branch'])
1071 def extra(self):
1057 def extra(self):
1072 return self._extra
1058 return self._extra
1073 def flags(self, f):
1059 def flags(self, f):
1074 return self[f].flags()
1060 return self[f].flags()
1075
1061
1076 def parents(self):
1062 def parents(self):
1077 """return contexts for each parent changeset"""
1063 """return contexts for each parent changeset"""
1078 return self._parents
1064 return self._parents
1079
1065
1080 def filectx(self, path, filelog=None):
1066 def filectx(self, path, filelog=None):
1081 """get a file context from the working directory"""
1067 """get a file context from the working directory"""
1082 return self._filectxfn(self._repo, self, path)
1068 return self._filectxfn(self._repo, self, path)
1083
1069
1084 def commit(self):
1070 def commit(self):
1085 """commit context to the repo"""
1071 """commit context to the repo"""
1086 return self._repo.commitctx(self)
1072 return self._repo.commitctx(self)
1087
1073
1088 class memfilectx(object):
1074 class memfilectx(object):
1089 """memfilectx represents an in-memory file to commit.
1075 """memfilectx represents an in-memory file to commit.
1090
1076
1091 See memctx for more details.
1077 See memctx for more details.
1092 """
1078 """
1093 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1079 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1094 """
1080 """
1095 path is the normalized file path relative to repository root.
1081 path is the normalized file path relative to repository root.
1096 data is the file content as a string.
1082 data is the file content as a string.
1097 islink is True if the file is a symbolic link.
1083 islink is True if the file is a symbolic link.
1098 isexec is True if the file is executable.
1084 isexec is True if the file is executable.
1099 copied is the source file path if current file was copied in the
1085 copied is the source file path if current file was copied in the
1100 revision being committed, or None."""
1086 revision being committed, or None."""
1101 self._path = path
1087 self._path = path
1102 self._data = data
1088 self._data = data
1103 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1089 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1104 self._copied = None
1090 self._copied = None
1105 if copied:
1091 if copied:
1106 self._copied = (copied, nullid)
1092 self._copied = (copied, nullid)
1107
1093
1108 def __nonzero__(self):
1094 def __nonzero__(self):
1109 return True
1095 return True
1110 def __str__(self):
1096 def __str__(self):
1111 return "%s@%s" % (self.path(), self._changectx)
1097 return "%s@%s" % (self.path(), self._changectx)
1112 def path(self):
1098 def path(self):
1113 return self._path
1099 return self._path
1114 def data(self):
1100 def data(self):
1115 return self._data
1101 return self._data
1116 def flags(self):
1102 def flags(self):
1117 return self._flags
1103 return self._flags
1118 def isexec(self):
1104 def isexec(self):
1119 return 'x' in self._flags
1105 return 'x' in self._flags
1120 def islink(self):
1106 def islink(self):
1121 return 'l' in self._flags
1107 return 'l' in self._flags
1122 def renamed(self):
1108 def renamed(self):
1123 return self._copied
1109 return self._copied
General Comments 0
You need to be logged in to leave comments. Login now