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