##// END OF EJS Templates
cmdutil: service: add appendpid parameter to append pids to pid file
Nicolas Dumazet -
r10012:2bfe1a23 default
parent child Browse files
Show More
@@ -1,1294 +1,1295 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, 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 copies = {}
273 copies = {}
274 ctx = repo['.']
274 ctx = repo['.']
275 for r in removed:
275 for r in removed:
276 if r not in ctx:
276 if r not in ctx:
277 continue
277 continue
278 fctx = ctx.filectx(r)
278 fctx = ctx.filectx(r)
279
279
280 def score(text):
280 def score(text):
281 if not len(text):
281 if not len(text):
282 return 0.0
282 return 0.0
283 if not fctx.cmp(text):
283 if not fctx.cmp(text):
284 return 1.0
284 return 1.0
285 if threshold == 1.0:
285 if threshold == 1.0:
286 return 0.0
286 return 0.0
287 orig = fctx.data()
287 orig = fctx.data()
288 # bdiff.blocks() returns blocks of matching lines
288 # bdiff.blocks() returns blocks of matching lines
289 # count the number of bytes in each
289 # count the number of bytes in each
290 equal = 0
290 equal = 0
291 alines = mdiff.splitnewlines(text)
291 alines = mdiff.splitnewlines(text)
292 matches = bdiff.blocks(text, orig)
292 matches = bdiff.blocks(text, orig)
293 for x1, x2, y1, y2 in matches:
293 for x1, x2, y1, y2 in matches:
294 for line in alines[x1:x2]:
294 for line in alines[x1:x2]:
295 equal += len(line)
295 equal += len(line)
296
296
297 lengths = len(text) + len(orig)
297 lengths = len(text) + len(orig)
298 return equal * 2.0 / lengths
298 return equal * 2.0 / lengths
299
299
300 for a in added:
300 for a in added:
301 bestscore = copies.get(a, (None, threshold))[1]
301 bestscore = copies.get(a, (None, threshold))[1]
302 myscore = score(repo.wread(a))
302 myscore = score(repo.wread(a))
303 if myscore >= bestscore:
303 if myscore >= bestscore:
304 copies[a] = (r, myscore)
304 copies[a] = (r, myscore)
305
305
306 for dest, v in copies.iteritems():
306 for dest, v in copies.iteritems():
307 source, score = v
307 source, score = v
308 yield source, dest, score
308 yield source, dest, score
309
309
310 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
310 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
311 if dry_run is None:
311 if dry_run is None:
312 dry_run = opts.get('dry_run')
312 dry_run = opts.get('dry_run')
313 if similarity is None:
313 if similarity is None:
314 similarity = float(opts.get('similarity') or 0)
314 similarity = float(opts.get('similarity') or 0)
315 # we'd use status here, except handling of symlinks and ignore is tricky
315 # we'd use status here, except handling of symlinks and ignore is tricky
316 added, unknown, deleted, removed = [], [], [], []
316 added, unknown, deleted, removed = [], [], [], []
317 audit_path = util.path_auditor(repo.root)
317 audit_path = util.path_auditor(repo.root)
318 m = match(repo, pats, opts)
318 m = match(repo, pats, opts)
319 for abs in repo.walk(m):
319 for abs in repo.walk(m):
320 target = repo.wjoin(abs)
320 target = repo.wjoin(abs)
321 good = True
321 good = True
322 try:
322 try:
323 audit_path(abs)
323 audit_path(abs)
324 except:
324 except:
325 good = False
325 good = False
326 rel = m.rel(abs)
326 rel = m.rel(abs)
327 exact = m.exact(abs)
327 exact = m.exact(abs)
328 if good and abs not in repo.dirstate:
328 if good and abs not in repo.dirstate:
329 unknown.append(abs)
329 unknown.append(abs)
330 if repo.ui.verbose or not exact:
330 if repo.ui.verbose or not exact:
331 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
331 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
332 elif repo.dirstate[abs] != 'r' and (not good or not util.lexists(target)
332 elif repo.dirstate[abs] != 'r' and (not good or not util.lexists(target)
333 or (os.path.isdir(target) and not os.path.islink(target))):
333 or (os.path.isdir(target) and not os.path.islink(target))):
334 deleted.append(abs)
334 deleted.append(abs)
335 if repo.ui.verbose or not exact:
335 if repo.ui.verbose or not exact:
336 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
336 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
337 # for finding renames
337 # for finding renames
338 elif repo.dirstate[abs] == 'r':
338 elif repo.dirstate[abs] == 'r':
339 removed.append(abs)
339 removed.append(abs)
340 elif repo.dirstate[abs] == 'a':
340 elif repo.dirstate[abs] == 'a':
341 added.append(abs)
341 added.append(abs)
342 if not dry_run:
342 if not dry_run:
343 repo.remove(deleted)
343 repo.remove(deleted)
344 repo.add(unknown)
344 repo.add(unknown)
345 if similarity > 0:
345 if similarity > 0:
346 for old, new, score in findrenames(repo, added + unknown,
346 for old, new, score in findrenames(repo, added + unknown,
347 removed + deleted, similarity):
347 removed + deleted, similarity):
348 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
348 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
349 repo.ui.status(_('recording removal of %s as rename to %s '
349 repo.ui.status(_('recording removal of %s as rename to %s '
350 '(%d%% similar)\n') %
350 '(%d%% similar)\n') %
351 (m.rel(old), m.rel(new), score * 100))
351 (m.rel(old), m.rel(new), score * 100))
352 if not dry_run:
352 if not dry_run:
353 repo.copy(old, new)
353 repo.copy(old, new)
354
354
355 def copy(ui, repo, pats, opts, rename=False):
355 def copy(ui, repo, pats, opts, rename=False):
356 # called with the repo lock held
356 # called with the repo lock held
357 #
357 #
358 # hgsep => pathname that uses "/" to separate directories
358 # hgsep => pathname that uses "/" to separate directories
359 # ossep => pathname that uses os.sep to separate directories
359 # ossep => pathname that uses os.sep to separate directories
360 cwd = repo.getcwd()
360 cwd = repo.getcwd()
361 targets = {}
361 targets = {}
362 after = opts.get("after")
362 after = opts.get("after")
363 dryrun = opts.get("dry_run")
363 dryrun = opts.get("dry_run")
364
364
365 def walkpat(pat):
365 def walkpat(pat):
366 srcs = []
366 srcs = []
367 m = match(repo, [pat], opts, globbed=True)
367 m = match(repo, [pat], opts, globbed=True)
368 for abs in repo.walk(m):
368 for abs in repo.walk(m):
369 state = repo.dirstate[abs]
369 state = repo.dirstate[abs]
370 rel = m.rel(abs)
370 rel = m.rel(abs)
371 exact = m.exact(abs)
371 exact = m.exact(abs)
372 if state in '?r':
372 if state in '?r':
373 if exact and state == '?':
373 if exact and state == '?':
374 ui.warn(_('%s: not copying - file is not managed\n') % rel)
374 ui.warn(_('%s: not copying - file is not managed\n') % rel)
375 if exact and state == 'r':
375 if exact and state == 'r':
376 ui.warn(_('%s: not copying - file has been marked for'
376 ui.warn(_('%s: not copying - file has been marked for'
377 ' remove\n') % rel)
377 ' remove\n') % rel)
378 continue
378 continue
379 # abs: hgsep
379 # abs: hgsep
380 # rel: ossep
380 # rel: ossep
381 srcs.append((abs, rel, exact))
381 srcs.append((abs, rel, exact))
382 return srcs
382 return srcs
383
383
384 # abssrc: hgsep
384 # abssrc: hgsep
385 # relsrc: ossep
385 # relsrc: ossep
386 # otarget: ossep
386 # otarget: ossep
387 def copyfile(abssrc, relsrc, otarget, exact):
387 def copyfile(abssrc, relsrc, otarget, exact):
388 abstarget = util.canonpath(repo.root, cwd, otarget)
388 abstarget = util.canonpath(repo.root, cwd, otarget)
389 reltarget = repo.pathto(abstarget, cwd)
389 reltarget = repo.pathto(abstarget, cwd)
390 target = repo.wjoin(abstarget)
390 target = repo.wjoin(abstarget)
391 src = repo.wjoin(abssrc)
391 src = repo.wjoin(abssrc)
392 state = repo.dirstate[abstarget]
392 state = repo.dirstate[abstarget]
393
393
394 # check for collisions
394 # check for collisions
395 prevsrc = targets.get(abstarget)
395 prevsrc = targets.get(abstarget)
396 if prevsrc is not None:
396 if prevsrc is not None:
397 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
397 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
398 (reltarget, repo.pathto(abssrc, cwd),
398 (reltarget, repo.pathto(abssrc, cwd),
399 repo.pathto(prevsrc, cwd)))
399 repo.pathto(prevsrc, cwd)))
400 return
400 return
401
401
402 # check for overwrites
402 # check for overwrites
403 exists = os.path.exists(target)
403 exists = os.path.exists(target)
404 if not after and exists or after and state in 'mn':
404 if not after and exists or after and state in 'mn':
405 if not opts['force']:
405 if not opts['force']:
406 ui.warn(_('%s: not overwriting - file exists\n') %
406 ui.warn(_('%s: not overwriting - file exists\n') %
407 reltarget)
407 reltarget)
408 return
408 return
409
409
410 if after:
410 if after:
411 if not exists:
411 if not exists:
412 return
412 return
413 elif not dryrun:
413 elif not dryrun:
414 try:
414 try:
415 if exists:
415 if exists:
416 os.unlink(target)
416 os.unlink(target)
417 targetdir = os.path.dirname(target) or '.'
417 targetdir = os.path.dirname(target) or '.'
418 if not os.path.isdir(targetdir):
418 if not os.path.isdir(targetdir):
419 os.makedirs(targetdir)
419 os.makedirs(targetdir)
420 util.copyfile(src, target)
420 util.copyfile(src, target)
421 except IOError, inst:
421 except IOError, inst:
422 if inst.errno == errno.ENOENT:
422 if inst.errno == errno.ENOENT:
423 ui.warn(_('%s: deleted in working copy\n') % relsrc)
423 ui.warn(_('%s: deleted in working copy\n') % relsrc)
424 else:
424 else:
425 ui.warn(_('%s: cannot copy - %s\n') %
425 ui.warn(_('%s: cannot copy - %s\n') %
426 (relsrc, inst.strerror))
426 (relsrc, inst.strerror))
427 return True # report a failure
427 return True # report a failure
428
428
429 if ui.verbose or not exact:
429 if ui.verbose or not exact:
430 if rename:
430 if rename:
431 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
431 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
432 else:
432 else:
433 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
433 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
434
434
435 targets[abstarget] = abssrc
435 targets[abstarget] = abssrc
436
436
437 # fix up dirstate
437 # fix up dirstate
438 origsrc = repo.dirstate.copied(abssrc) or abssrc
438 origsrc = repo.dirstate.copied(abssrc) or abssrc
439 if abstarget == origsrc: # copying back a copy?
439 if abstarget == origsrc: # copying back a copy?
440 if state not in 'mn' and not dryrun:
440 if state not in 'mn' and not dryrun:
441 repo.dirstate.normallookup(abstarget)
441 repo.dirstate.normallookup(abstarget)
442 else:
442 else:
443 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
443 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
444 if not ui.quiet:
444 if not ui.quiet:
445 ui.warn(_("%s has not been committed yet, so no copy "
445 ui.warn(_("%s has not been committed yet, so no copy "
446 "data will be stored for %s.\n")
446 "data will be stored for %s.\n")
447 % (repo.pathto(origsrc, cwd), reltarget))
447 % (repo.pathto(origsrc, cwd), reltarget))
448 if repo.dirstate[abstarget] in '?r' and not dryrun:
448 if repo.dirstate[abstarget] in '?r' and not dryrun:
449 repo.add([abstarget])
449 repo.add([abstarget])
450 elif not dryrun:
450 elif not dryrun:
451 repo.copy(origsrc, abstarget)
451 repo.copy(origsrc, abstarget)
452
452
453 if rename and not dryrun:
453 if rename and not dryrun:
454 repo.remove([abssrc], not after)
454 repo.remove([abssrc], not after)
455
455
456 # pat: ossep
456 # pat: ossep
457 # dest ossep
457 # dest ossep
458 # srcs: list of (hgsep, hgsep, ossep, bool)
458 # srcs: list of (hgsep, hgsep, ossep, bool)
459 # return: function that takes hgsep and returns ossep
459 # return: function that takes hgsep and returns ossep
460 def targetpathfn(pat, dest, srcs):
460 def targetpathfn(pat, dest, srcs):
461 if os.path.isdir(pat):
461 if os.path.isdir(pat):
462 abspfx = util.canonpath(repo.root, cwd, pat)
462 abspfx = util.canonpath(repo.root, cwd, pat)
463 abspfx = util.localpath(abspfx)
463 abspfx = util.localpath(abspfx)
464 if destdirexists:
464 if destdirexists:
465 striplen = len(os.path.split(abspfx)[0])
465 striplen = len(os.path.split(abspfx)[0])
466 else:
466 else:
467 striplen = len(abspfx)
467 striplen = len(abspfx)
468 if striplen:
468 if striplen:
469 striplen += len(os.sep)
469 striplen += len(os.sep)
470 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
470 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
471 elif destdirexists:
471 elif destdirexists:
472 res = lambda p: os.path.join(dest,
472 res = lambda p: os.path.join(dest,
473 os.path.basename(util.localpath(p)))
473 os.path.basename(util.localpath(p)))
474 else:
474 else:
475 res = lambda p: dest
475 res = lambda p: dest
476 return res
476 return res
477
477
478 # pat: ossep
478 # pat: ossep
479 # dest ossep
479 # dest ossep
480 # srcs: list of (hgsep, hgsep, ossep, bool)
480 # srcs: list of (hgsep, hgsep, ossep, bool)
481 # return: function that takes hgsep and returns ossep
481 # return: function that takes hgsep and returns ossep
482 def targetpathafterfn(pat, dest, srcs):
482 def targetpathafterfn(pat, dest, srcs):
483 if _match.patkind(pat):
483 if _match.patkind(pat):
484 # a mercurial pattern
484 # a mercurial pattern
485 res = lambda p: os.path.join(dest,
485 res = lambda p: os.path.join(dest,
486 os.path.basename(util.localpath(p)))
486 os.path.basename(util.localpath(p)))
487 else:
487 else:
488 abspfx = util.canonpath(repo.root, cwd, pat)
488 abspfx = util.canonpath(repo.root, cwd, pat)
489 if len(abspfx) < len(srcs[0][0]):
489 if len(abspfx) < len(srcs[0][0]):
490 # A directory. Either the target path contains the last
490 # A directory. Either the target path contains the last
491 # component of the source path or it does not.
491 # component of the source path or it does not.
492 def evalpath(striplen):
492 def evalpath(striplen):
493 score = 0
493 score = 0
494 for s in srcs:
494 for s in srcs:
495 t = os.path.join(dest, util.localpath(s[0])[striplen:])
495 t = os.path.join(dest, util.localpath(s[0])[striplen:])
496 if os.path.exists(t):
496 if os.path.exists(t):
497 score += 1
497 score += 1
498 return score
498 return score
499
499
500 abspfx = util.localpath(abspfx)
500 abspfx = util.localpath(abspfx)
501 striplen = len(abspfx)
501 striplen = len(abspfx)
502 if striplen:
502 if striplen:
503 striplen += len(os.sep)
503 striplen += len(os.sep)
504 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
504 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
505 score = evalpath(striplen)
505 score = evalpath(striplen)
506 striplen1 = len(os.path.split(abspfx)[0])
506 striplen1 = len(os.path.split(abspfx)[0])
507 if striplen1:
507 if striplen1:
508 striplen1 += len(os.sep)
508 striplen1 += len(os.sep)
509 if evalpath(striplen1) > score:
509 if evalpath(striplen1) > score:
510 striplen = striplen1
510 striplen = striplen1
511 res = lambda p: os.path.join(dest,
511 res = lambda p: os.path.join(dest,
512 util.localpath(p)[striplen:])
512 util.localpath(p)[striplen:])
513 else:
513 else:
514 # a file
514 # a file
515 if destdirexists:
515 if destdirexists:
516 res = lambda p: os.path.join(dest,
516 res = lambda p: os.path.join(dest,
517 os.path.basename(util.localpath(p)))
517 os.path.basename(util.localpath(p)))
518 else:
518 else:
519 res = lambda p: dest
519 res = lambda p: dest
520 return res
520 return res
521
521
522
522
523 pats = expandpats(pats)
523 pats = expandpats(pats)
524 if not pats:
524 if not pats:
525 raise util.Abort(_('no source or destination specified'))
525 raise util.Abort(_('no source or destination specified'))
526 if len(pats) == 1:
526 if len(pats) == 1:
527 raise util.Abort(_('no destination specified'))
527 raise util.Abort(_('no destination specified'))
528 dest = pats.pop()
528 dest = pats.pop()
529 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
529 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
530 if not destdirexists:
530 if not destdirexists:
531 if len(pats) > 1 or _match.patkind(pats[0]):
531 if len(pats) > 1 or _match.patkind(pats[0]):
532 raise util.Abort(_('with multiple sources, destination must be an '
532 raise util.Abort(_('with multiple sources, destination must be an '
533 'existing directory'))
533 'existing directory'))
534 if util.endswithsep(dest):
534 if util.endswithsep(dest):
535 raise util.Abort(_('destination %s is not a directory') % dest)
535 raise util.Abort(_('destination %s is not a directory') % dest)
536
536
537 tfn = targetpathfn
537 tfn = targetpathfn
538 if after:
538 if after:
539 tfn = targetpathafterfn
539 tfn = targetpathafterfn
540 copylist = []
540 copylist = []
541 for pat in pats:
541 for pat in pats:
542 srcs = walkpat(pat)
542 srcs = walkpat(pat)
543 if not srcs:
543 if not srcs:
544 continue
544 continue
545 copylist.append((tfn(pat, dest, srcs), srcs))
545 copylist.append((tfn(pat, dest, srcs), srcs))
546 if not copylist:
546 if not copylist:
547 raise util.Abort(_('no files to copy'))
547 raise util.Abort(_('no files to copy'))
548
548
549 errors = 0
549 errors = 0
550 for targetpath, srcs in copylist:
550 for targetpath, srcs in copylist:
551 for abssrc, relsrc, exact in srcs:
551 for abssrc, relsrc, exact in srcs:
552 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
552 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
553 errors += 1
553 errors += 1
554
554
555 if errors:
555 if errors:
556 ui.warn(_('(consider using --after)\n'))
556 ui.warn(_('(consider using --after)\n'))
557
557
558 return errors
558 return errors
559
559
560 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
560 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
561 runargs=None):
561 runargs=None, appendpid=False):
562 '''Run a command as a service.'''
562 '''Run a command as a service.'''
563
563
564 if opts['daemon'] and not opts['daemon_pipefds']:
564 if opts['daemon'] and not opts['daemon_pipefds']:
565 rfd, wfd = os.pipe()
565 rfd, wfd = os.pipe()
566 if not runargs:
566 if not runargs:
567 runargs = sys.argv[:]
567 runargs = sys.argv[:]
568 runargs.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
568 runargs.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
569 # Don't pass --cwd to the child process, because we've already
569 # Don't pass --cwd to the child process, because we've already
570 # changed directory.
570 # changed directory.
571 for i in xrange(1,len(runargs)):
571 for i in xrange(1,len(runargs)):
572 if runargs[i].startswith('--cwd='):
572 if runargs[i].startswith('--cwd='):
573 del runargs[i]
573 del runargs[i]
574 break
574 break
575 elif runargs[i].startswith('--cwd'):
575 elif runargs[i].startswith('--cwd'):
576 del runargs[i:i+2]
576 del runargs[i:i+2]
577 break
577 break
578 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
578 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
579 runargs[0], runargs)
579 runargs[0], runargs)
580 os.close(wfd)
580 os.close(wfd)
581 os.read(rfd, 1)
581 os.read(rfd, 1)
582 if parentfn:
582 if parentfn:
583 return parentfn(pid)
583 return parentfn(pid)
584 else:
584 else:
585 return
585 return
586
586
587 if initfn:
587 if initfn:
588 initfn()
588 initfn()
589
589
590 if opts['pid_file']:
590 if opts['pid_file']:
591 fp = open(opts['pid_file'], 'w')
591 mode = appendpid and 'a' or 'w'
592 fp = open(opts['pid_file'], mode)
592 fp.write(str(os.getpid()) + '\n')
593 fp.write(str(os.getpid()) + '\n')
593 fp.close()
594 fp.close()
594
595
595 if opts['daemon_pipefds']:
596 if opts['daemon_pipefds']:
596 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
597 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
597 os.close(rfd)
598 os.close(rfd)
598 try:
599 try:
599 os.setsid()
600 os.setsid()
600 except AttributeError:
601 except AttributeError:
601 pass
602 pass
602 os.write(wfd, 'y')
603 os.write(wfd, 'y')
603 os.close(wfd)
604 os.close(wfd)
604 sys.stdout.flush()
605 sys.stdout.flush()
605 sys.stderr.flush()
606 sys.stderr.flush()
606
607
607 nullfd = os.open(util.nulldev, os.O_RDWR)
608 nullfd = os.open(util.nulldev, os.O_RDWR)
608 logfilefd = nullfd
609 logfilefd = nullfd
609 if logfile:
610 if logfile:
610 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
611 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
611 os.dup2(nullfd, 0)
612 os.dup2(nullfd, 0)
612 os.dup2(logfilefd, 1)
613 os.dup2(logfilefd, 1)
613 os.dup2(logfilefd, 2)
614 os.dup2(logfilefd, 2)
614 if nullfd not in (0, 1, 2):
615 if nullfd not in (0, 1, 2):
615 os.close(nullfd)
616 os.close(nullfd)
616 if logfile and logfilefd not in (0, 1, 2):
617 if logfile and logfilefd not in (0, 1, 2):
617 os.close(logfilefd)
618 os.close(logfilefd)
618
619
619 if runfn:
620 if runfn:
620 return runfn()
621 return runfn()
621
622
622 class changeset_printer(object):
623 class changeset_printer(object):
623 '''show changeset information when templating not requested.'''
624 '''show changeset information when templating not requested.'''
624
625
625 def __init__(self, ui, repo, patch, diffopts, buffered):
626 def __init__(self, ui, repo, patch, diffopts, buffered):
626 self.ui = ui
627 self.ui = ui
627 self.repo = repo
628 self.repo = repo
628 self.buffered = buffered
629 self.buffered = buffered
629 self.patch = patch
630 self.patch = patch
630 self.diffopts = diffopts
631 self.diffopts = diffopts
631 self.header = {}
632 self.header = {}
632 self.hunk = {}
633 self.hunk = {}
633 self.lastheader = None
634 self.lastheader = None
634
635
635 def flush(self, rev):
636 def flush(self, rev):
636 if rev in self.header:
637 if rev in self.header:
637 h = self.header[rev]
638 h = self.header[rev]
638 if h != self.lastheader:
639 if h != self.lastheader:
639 self.lastheader = h
640 self.lastheader = h
640 self.ui.write(h)
641 self.ui.write(h)
641 del self.header[rev]
642 del self.header[rev]
642 if rev in self.hunk:
643 if rev in self.hunk:
643 self.ui.write(self.hunk[rev])
644 self.ui.write(self.hunk[rev])
644 del self.hunk[rev]
645 del self.hunk[rev]
645 return 1
646 return 1
646 return 0
647 return 0
647
648
648 def show(self, ctx, copies=(), **props):
649 def show(self, ctx, copies=(), **props):
649 if self.buffered:
650 if self.buffered:
650 self.ui.pushbuffer()
651 self.ui.pushbuffer()
651 self._show(ctx, copies, props)
652 self._show(ctx, copies, props)
652 self.hunk[ctx.rev()] = self.ui.popbuffer()
653 self.hunk[ctx.rev()] = self.ui.popbuffer()
653 else:
654 else:
654 self._show(ctx, copies, props)
655 self._show(ctx, copies, props)
655
656
656 def _show(self, ctx, copies, props):
657 def _show(self, ctx, copies, props):
657 '''show a single changeset or file revision'''
658 '''show a single changeset or file revision'''
658 changenode = ctx.node()
659 changenode = ctx.node()
659 rev = ctx.rev()
660 rev = ctx.rev()
660
661
661 if self.ui.quiet:
662 if self.ui.quiet:
662 self.ui.write("%d:%s\n" % (rev, short(changenode)))
663 self.ui.write("%d:%s\n" % (rev, short(changenode)))
663 return
664 return
664
665
665 log = self.repo.changelog
666 log = self.repo.changelog
666 date = util.datestr(ctx.date())
667 date = util.datestr(ctx.date())
667
668
668 hexfunc = self.ui.debugflag and hex or short
669 hexfunc = self.ui.debugflag and hex or short
669
670
670 parents = [(p, hexfunc(log.node(p)))
671 parents = [(p, hexfunc(log.node(p)))
671 for p in self._meaningful_parentrevs(log, rev)]
672 for p in self._meaningful_parentrevs(log, rev)]
672
673
673 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
674 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
674
675
675 branch = ctx.branch()
676 branch = ctx.branch()
676 # don't show the default branch name
677 # don't show the default branch name
677 if branch != 'default':
678 if branch != 'default':
678 branch = encoding.tolocal(branch)
679 branch = encoding.tolocal(branch)
679 self.ui.write(_("branch: %s\n") % branch)
680 self.ui.write(_("branch: %s\n") % branch)
680 for tag in self.repo.nodetags(changenode):
681 for tag in self.repo.nodetags(changenode):
681 self.ui.write(_("tag: %s\n") % tag)
682 self.ui.write(_("tag: %s\n") % tag)
682 for parent in parents:
683 for parent in parents:
683 self.ui.write(_("parent: %d:%s\n") % parent)
684 self.ui.write(_("parent: %d:%s\n") % parent)
684
685
685 if self.ui.debugflag:
686 if self.ui.debugflag:
686 mnode = ctx.manifestnode()
687 mnode = ctx.manifestnode()
687 self.ui.write(_("manifest: %d:%s\n") %
688 self.ui.write(_("manifest: %d:%s\n") %
688 (self.repo.manifest.rev(mnode), hex(mnode)))
689 (self.repo.manifest.rev(mnode), hex(mnode)))
689 self.ui.write(_("user: %s\n") % ctx.user())
690 self.ui.write(_("user: %s\n") % ctx.user())
690 self.ui.write(_("date: %s\n") % date)
691 self.ui.write(_("date: %s\n") % date)
691
692
692 if self.ui.debugflag:
693 if self.ui.debugflag:
693 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
694 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
694 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
695 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
695 files):
696 files):
696 if value:
697 if value:
697 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
698 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
698 elif ctx.files() and self.ui.verbose:
699 elif ctx.files() and self.ui.verbose:
699 self.ui.write(_("files: %s\n") % " ".join(ctx.files()))
700 self.ui.write(_("files: %s\n") % " ".join(ctx.files()))
700 if copies and self.ui.verbose:
701 if copies and self.ui.verbose:
701 copies = ['%s (%s)' % c for c in copies]
702 copies = ['%s (%s)' % c for c in copies]
702 self.ui.write(_("copies: %s\n") % ' '.join(copies))
703 self.ui.write(_("copies: %s\n") % ' '.join(copies))
703
704
704 extra = ctx.extra()
705 extra = ctx.extra()
705 if extra and self.ui.debugflag:
706 if extra and self.ui.debugflag:
706 for key, value in sorted(extra.items()):
707 for key, value in sorted(extra.items()):
707 self.ui.write(_("extra: %s=%s\n")
708 self.ui.write(_("extra: %s=%s\n")
708 % (key, value.encode('string_escape')))
709 % (key, value.encode('string_escape')))
709
710
710 description = ctx.description().strip()
711 description = ctx.description().strip()
711 if description:
712 if description:
712 if self.ui.verbose:
713 if self.ui.verbose:
713 self.ui.write(_("description:\n"))
714 self.ui.write(_("description:\n"))
714 self.ui.write(description)
715 self.ui.write(description)
715 self.ui.write("\n\n")
716 self.ui.write("\n\n")
716 else:
717 else:
717 self.ui.write(_("summary: %s\n") %
718 self.ui.write(_("summary: %s\n") %
718 description.splitlines()[0])
719 description.splitlines()[0])
719 self.ui.write("\n")
720 self.ui.write("\n")
720
721
721 self.showpatch(changenode)
722 self.showpatch(changenode)
722
723
723 def showpatch(self, node):
724 def showpatch(self, node):
724 if self.patch:
725 if self.patch:
725 prev = self.repo.changelog.parents(node)[0]
726 prev = self.repo.changelog.parents(node)[0]
726 chunks = patch.diff(self.repo, prev, node, match=self.patch,
727 chunks = patch.diff(self.repo, prev, node, match=self.patch,
727 opts=patch.diffopts(self.ui, self.diffopts))
728 opts=patch.diffopts(self.ui, self.diffopts))
728 for chunk in chunks:
729 for chunk in chunks:
729 self.ui.write(chunk)
730 self.ui.write(chunk)
730 self.ui.write("\n")
731 self.ui.write("\n")
731
732
732 def _meaningful_parentrevs(self, log, rev):
733 def _meaningful_parentrevs(self, log, rev):
733 """Return list of meaningful (or all if debug) parentrevs for rev.
734 """Return list of meaningful (or all if debug) parentrevs for rev.
734
735
735 For merges (two non-nullrev revisions) both parents are meaningful.
736 For merges (two non-nullrev revisions) both parents are meaningful.
736 Otherwise the first parent revision is considered meaningful if it
737 Otherwise the first parent revision is considered meaningful if it
737 is not the preceding revision.
738 is not the preceding revision.
738 """
739 """
739 parents = log.parentrevs(rev)
740 parents = log.parentrevs(rev)
740 if not self.ui.debugflag and parents[1] == nullrev:
741 if not self.ui.debugflag and parents[1] == nullrev:
741 if parents[0] >= rev - 1:
742 if parents[0] >= rev - 1:
742 parents = []
743 parents = []
743 else:
744 else:
744 parents = [parents[0]]
745 parents = [parents[0]]
745 return parents
746 return parents
746
747
747
748
748 class changeset_templater(changeset_printer):
749 class changeset_templater(changeset_printer):
749 '''format changeset information.'''
750 '''format changeset information.'''
750
751
751 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
752 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
752 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
753 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
753 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
754 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
754 self.t = templater.templater(mapfile, {'formatnode': formatnode},
755 self.t = templater.templater(mapfile, {'formatnode': formatnode},
755 cache={
756 cache={
756 'parent': '{rev}:{node|formatnode} ',
757 'parent': '{rev}:{node|formatnode} ',
757 'manifest': '{rev}:{node|formatnode}',
758 'manifest': '{rev}:{node|formatnode}',
758 'filecopy': '{name} ({source})',
759 'filecopy': '{name} ({source})',
759 'extra': '{key}={value|stringescape}'})
760 'extra': '{key}={value|stringescape}'})
760 # Cache mapping from rev to a tuple with tag date, tag
761 # Cache mapping from rev to a tuple with tag date, tag
761 # distance and tag name
762 # distance and tag name
762 self._latesttagcache = {-1: (0, 0, 'null')}
763 self._latesttagcache = {-1: (0, 0, 'null')}
763
764
764 def use_template(self, t):
765 def use_template(self, t):
765 '''set template string to use'''
766 '''set template string to use'''
766 self.t.cache['changeset'] = t
767 self.t.cache['changeset'] = t
767
768
768 def _meaningful_parentrevs(self, ctx):
769 def _meaningful_parentrevs(self, ctx):
769 """Return list of meaningful (or all if debug) parentrevs for rev.
770 """Return list of meaningful (or all if debug) parentrevs for rev.
770 """
771 """
771 parents = ctx.parents()
772 parents = ctx.parents()
772 if len(parents) > 1:
773 if len(parents) > 1:
773 return parents
774 return parents
774 if self.ui.debugflag:
775 if self.ui.debugflag:
775 return [parents[0], self.repo['null']]
776 return [parents[0], self.repo['null']]
776 if parents[0].rev() >= ctx.rev() - 1:
777 if parents[0].rev() >= ctx.rev() - 1:
777 return []
778 return []
778 return parents
779 return parents
779
780
780 def _latesttaginfo(self, rev):
781 def _latesttaginfo(self, rev):
781 '''return date, distance and name for the latest tag of rev'''
782 '''return date, distance and name for the latest tag of rev'''
782 todo = [rev]
783 todo = [rev]
783 while todo:
784 while todo:
784 rev = todo.pop()
785 rev = todo.pop()
785 if rev in self._latesttagcache:
786 if rev in self._latesttagcache:
786 continue
787 continue
787 ctx = self.repo[rev]
788 ctx = self.repo[rev]
788 tags = [t for t in ctx.tags() if self.repo.tagtype(t) == 'global']
789 tags = [t for t in ctx.tags() if self.repo.tagtype(t) == 'global']
789 if tags:
790 if tags:
790 self._latesttagcache[rev] = ctx.date()[0], 0, ':'.join(sorted(tags))
791 self._latesttagcache[rev] = ctx.date()[0], 0, ':'.join(sorted(tags))
791 continue
792 continue
792 try:
793 try:
793 # The tuples are laid out so the right one can be found by comparison.
794 # The tuples are laid out so the right one can be found by comparison.
794 pdate, pdist, ptag = max(
795 pdate, pdist, ptag = max(
795 self._latesttagcache[p.rev()] for p in ctx.parents())
796 self._latesttagcache[p.rev()] for p in ctx.parents())
796 except KeyError:
797 except KeyError:
797 # Cache miss - recurse
798 # Cache miss - recurse
798 todo.append(rev)
799 todo.append(rev)
799 todo.extend(p.rev() for p in ctx.parents())
800 todo.extend(p.rev() for p in ctx.parents())
800 continue
801 continue
801 self._latesttagcache[rev] = pdate, pdist + 1, ptag
802 self._latesttagcache[rev] = pdate, pdist + 1, ptag
802 return self._latesttagcache[rev]
803 return self._latesttagcache[rev]
803
804
804 def _show(self, ctx, copies, props):
805 def _show(self, ctx, copies, props):
805 '''show a single changeset or file revision'''
806 '''show a single changeset or file revision'''
806
807
807 def showlist(name, values, plural=None, **args):
808 def showlist(name, values, plural=None, **args):
808 '''expand set of values.
809 '''expand set of values.
809 name is name of key in template map.
810 name is name of key in template map.
810 values is list of strings or dicts.
811 values is list of strings or dicts.
811 plural is plural of name, if not simply name + 's'.
812 plural is plural of name, if not simply name + 's'.
812
813
813 expansion works like this, given name 'foo'.
814 expansion works like this, given name 'foo'.
814
815
815 if values is empty, expand 'no_foos'.
816 if values is empty, expand 'no_foos'.
816
817
817 if 'foo' not in template map, return values as a string,
818 if 'foo' not in template map, return values as a string,
818 joined by space.
819 joined by space.
819
820
820 expand 'start_foos'.
821 expand 'start_foos'.
821
822
822 for each value, expand 'foo'. if 'last_foo' in template
823 for each value, expand 'foo'. if 'last_foo' in template
823 map, expand it instead of 'foo' for last key.
824 map, expand it instead of 'foo' for last key.
824
825
825 expand 'end_foos'.
826 expand 'end_foos'.
826 '''
827 '''
827 if plural: names = plural
828 if plural: names = plural
828 else: names = name + 's'
829 else: names = name + 's'
829 if not values:
830 if not values:
830 noname = 'no_' + names
831 noname = 'no_' + names
831 if noname in self.t:
832 if noname in self.t:
832 yield self.t(noname, **args)
833 yield self.t(noname, **args)
833 return
834 return
834 if name not in self.t:
835 if name not in self.t:
835 if isinstance(values[0], str):
836 if isinstance(values[0], str):
836 yield ' '.join(values)
837 yield ' '.join(values)
837 else:
838 else:
838 for v in values:
839 for v in values:
839 yield dict(v, **args)
840 yield dict(v, **args)
840 return
841 return
841 startname = 'start_' + names
842 startname = 'start_' + names
842 if startname in self.t:
843 if startname in self.t:
843 yield self.t(startname, **args)
844 yield self.t(startname, **args)
844 vargs = args.copy()
845 vargs = args.copy()
845 def one(v, tag=name):
846 def one(v, tag=name):
846 try:
847 try:
847 vargs.update(v)
848 vargs.update(v)
848 except (AttributeError, ValueError):
849 except (AttributeError, ValueError):
849 try:
850 try:
850 for a, b in v:
851 for a, b in v:
851 vargs[a] = b
852 vargs[a] = b
852 except ValueError:
853 except ValueError:
853 vargs[name] = v
854 vargs[name] = v
854 return self.t(tag, **vargs)
855 return self.t(tag, **vargs)
855 lastname = 'last_' + name
856 lastname = 'last_' + name
856 if lastname in self.t:
857 if lastname in self.t:
857 last = values.pop()
858 last = values.pop()
858 else:
859 else:
859 last = None
860 last = None
860 for v in values:
861 for v in values:
861 yield one(v)
862 yield one(v)
862 if last is not None:
863 if last is not None:
863 yield one(last, tag=lastname)
864 yield one(last, tag=lastname)
864 endname = 'end_' + names
865 endname = 'end_' + names
865 if endname in self.t:
866 if endname in self.t:
866 yield self.t(endname, **args)
867 yield self.t(endname, **args)
867
868
868 def showbranches(**args):
869 def showbranches(**args):
869 branch = ctx.branch()
870 branch = ctx.branch()
870 if branch != 'default':
871 if branch != 'default':
871 branch = encoding.tolocal(branch)
872 branch = encoding.tolocal(branch)
872 return showlist('branch', [branch], plural='branches', **args)
873 return showlist('branch', [branch], plural='branches', **args)
873
874
874 def showparents(**args):
875 def showparents(**args):
875 parents = [[('rev', p.rev()), ('node', p.hex())]
876 parents = [[('rev', p.rev()), ('node', p.hex())]
876 for p in self._meaningful_parentrevs(ctx)]
877 for p in self._meaningful_parentrevs(ctx)]
877 return showlist('parent', parents, **args)
878 return showlist('parent', parents, **args)
878
879
879 def showtags(**args):
880 def showtags(**args):
880 return showlist('tag', ctx.tags(), **args)
881 return showlist('tag', ctx.tags(), **args)
881
882
882 def showextras(**args):
883 def showextras(**args):
883 for key, value in sorted(ctx.extra().items()):
884 for key, value in sorted(ctx.extra().items()):
884 args = args.copy()
885 args = args.copy()
885 args.update(dict(key=key, value=value))
886 args.update(dict(key=key, value=value))
886 yield self.t('extra', **args)
887 yield self.t('extra', **args)
887
888
888 def showcopies(**args):
889 def showcopies(**args):
889 c = [{'name': x[0], 'source': x[1]} for x in copies]
890 c = [{'name': x[0], 'source': x[1]} for x in copies]
890 return showlist('file_copy', c, plural='file_copies', **args)
891 return showlist('file_copy', c, plural='file_copies', **args)
891
892
892 files = []
893 files = []
893 def getfiles():
894 def getfiles():
894 if not files:
895 if not files:
895 files[:] = self.repo.status(ctx.parents()[0].node(),
896 files[:] = self.repo.status(ctx.parents()[0].node(),
896 ctx.node())[:3]
897 ctx.node())[:3]
897 return files
898 return files
898 def showfiles(**args):
899 def showfiles(**args):
899 return showlist('file', ctx.files(), **args)
900 return showlist('file', ctx.files(), **args)
900 def showmods(**args):
901 def showmods(**args):
901 return showlist('file_mod', getfiles()[0], **args)
902 return showlist('file_mod', getfiles()[0], **args)
902 def showadds(**args):
903 def showadds(**args):
903 return showlist('file_add', getfiles()[1], **args)
904 return showlist('file_add', getfiles()[1], **args)
904 def showdels(**args):
905 def showdels(**args):
905 return showlist('file_del', getfiles()[2], **args)
906 return showlist('file_del', getfiles()[2], **args)
906 def showmanifest(**args):
907 def showmanifest(**args):
907 args = args.copy()
908 args = args.copy()
908 args.update(dict(rev=self.repo.manifest.rev(ctx.changeset()[0]),
909 args.update(dict(rev=self.repo.manifest.rev(ctx.changeset()[0]),
909 node=hex(ctx.changeset()[0])))
910 node=hex(ctx.changeset()[0])))
910 return self.t('manifest', **args)
911 return self.t('manifest', **args)
911
912
912 def showdiffstat(**args):
913 def showdiffstat(**args):
913 diff = patch.diff(self.repo, ctx.parents()[0].node(), ctx.node())
914 diff = patch.diff(self.repo, ctx.parents()[0].node(), ctx.node())
914 files, adds, removes = 0, 0, 0
915 files, adds, removes = 0, 0, 0
915 for i in patch.diffstatdata(util.iterlines(diff)):
916 for i in patch.diffstatdata(util.iterlines(diff)):
916 files += 1
917 files += 1
917 adds += i[1]
918 adds += i[1]
918 removes += i[2]
919 removes += i[2]
919 return '%s: +%s/-%s' % (files, adds, removes)
920 return '%s: +%s/-%s' % (files, adds, removes)
920
921
921 def showlatesttag(**args):
922 def showlatesttag(**args):
922 return self._latesttaginfo(ctx.rev())[2]
923 return self._latesttaginfo(ctx.rev())[2]
923 def showlatesttagdistance(**args):
924 def showlatesttagdistance(**args):
924 return self._latesttaginfo(ctx.rev())[1]
925 return self._latesttaginfo(ctx.rev())[1]
925
926
926 defprops = {
927 defprops = {
927 'author': ctx.user(),
928 'author': ctx.user(),
928 'branches': showbranches,
929 'branches': showbranches,
929 'date': ctx.date(),
930 'date': ctx.date(),
930 'desc': ctx.description().strip(),
931 'desc': ctx.description().strip(),
931 'file_adds': showadds,
932 'file_adds': showadds,
932 'file_dels': showdels,
933 'file_dels': showdels,
933 'file_mods': showmods,
934 'file_mods': showmods,
934 'files': showfiles,
935 'files': showfiles,
935 'file_copies': showcopies,
936 'file_copies': showcopies,
936 'manifest': showmanifest,
937 'manifest': showmanifest,
937 'node': ctx.hex(),
938 'node': ctx.hex(),
938 'parents': showparents,
939 'parents': showparents,
939 'rev': ctx.rev(),
940 'rev': ctx.rev(),
940 'tags': showtags,
941 'tags': showtags,
941 'extras': showextras,
942 'extras': showextras,
942 'diffstat': showdiffstat,
943 'diffstat': showdiffstat,
943 'latesttag': showlatesttag,
944 'latesttag': showlatesttag,
944 'latesttagdistance': showlatesttagdistance,
945 'latesttagdistance': showlatesttagdistance,
945 }
946 }
946 props = props.copy()
947 props = props.copy()
947 props.update(defprops)
948 props.update(defprops)
948
949
949 # find correct templates for current mode
950 # find correct templates for current mode
950
951
951 tmplmodes = [
952 tmplmodes = [
952 (True, None),
953 (True, None),
953 (self.ui.verbose, 'verbose'),
954 (self.ui.verbose, 'verbose'),
954 (self.ui.quiet, 'quiet'),
955 (self.ui.quiet, 'quiet'),
955 (self.ui.debugflag, 'debug'),
956 (self.ui.debugflag, 'debug'),
956 ]
957 ]
957
958
958 types = {'header': '', 'changeset': 'changeset'}
959 types = {'header': '', 'changeset': 'changeset'}
959 for mode, postfix in tmplmodes:
960 for mode, postfix in tmplmodes:
960 for type in types:
961 for type in types:
961 cur = postfix and ('%s_%s' % (type, postfix)) or type
962 cur = postfix and ('%s_%s' % (type, postfix)) or type
962 if mode and cur in self.t:
963 if mode and cur in self.t:
963 types[type] = cur
964 types[type] = cur
964
965
965 try:
966 try:
966
967
967 # write header
968 # write header
968 if types['header']:
969 if types['header']:
969 h = templater.stringify(self.t(types['header'], **props))
970 h = templater.stringify(self.t(types['header'], **props))
970 if self.buffered:
971 if self.buffered:
971 self.header[ctx.rev()] = h
972 self.header[ctx.rev()] = h
972 else:
973 else:
973 self.ui.write(h)
974 self.ui.write(h)
974
975
975 # write changeset metadata, then patch if requested
976 # write changeset metadata, then patch if requested
976 key = types['changeset']
977 key = types['changeset']
977 self.ui.write(templater.stringify(self.t(key, **props)))
978 self.ui.write(templater.stringify(self.t(key, **props)))
978 self.showpatch(ctx.node())
979 self.showpatch(ctx.node())
979
980
980 except KeyError, inst:
981 except KeyError, inst:
981 msg = _("%s: no key named '%s'")
982 msg = _("%s: no key named '%s'")
982 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
983 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
983 except SyntaxError, inst:
984 except SyntaxError, inst:
984 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
985 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
985
986
986 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
987 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
987 """show one changeset using template or regular display.
988 """show one changeset using template or regular display.
988
989
989 Display format will be the first non-empty hit of:
990 Display format will be the first non-empty hit of:
990 1. option 'template'
991 1. option 'template'
991 2. option 'style'
992 2. option 'style'
992 3. [ui] setting 'logtemplate'
993 3. [ui] setting 'logtemplate'
993 4. [ui] setting 'style'
994 4. [ui] setting 'style'
994 If all of these values are either the unset or the empty string,
995 If all of these values are either the unset or the empty string,
995 regular display via changeset_printer() is done.
996 regular display via changeset_printer() is done.
996 """
997 """
997 # options
998 # options
998 patch = False
999 patch = False
999 if opts.get('patch'):
1000 if opts.get('patch'):
1000 patch = matchfn or matchall(repo)
1001 patch = matchfn or matchall(repo)
1001
1002
1002 tmpl = opts.get('template')
1003 tmpl = opts.get('template')
1003 style = None
1004 style = None
1004 if tmpl:
1005 if tmpl:
1005 tmpl = templater.parsestring(tmpl, quoted=False)
1006 tmpl = templater.parsestring(tmpl, quoted=False)
1006 else:
1007 else:
1007 style = opts.get('style')
1008 style = opts.get('style')
1008
1009
1009 # ui settings
1010 # ui settings
1010 if not (tmpl or style):
1011 if not (tmpl or style):
1011 tmpl = ui.config('ui', 'logtemplate')
1012 tmpl = ui.config('ui', 'logtemplate')
1012 if tmpl:
1013 if tmpl:
1013 tmpl = templater.parsestring(tmpl)
1014 tmpl = templater.parsestring(tmpl)
1014 else:
1015 else:
1015 style = ui.config('ui', 'style')
1016 style = ui.config('ui', 'style')
1016
1017
1017 if not (tmpl or style):
1018 if not (tmpl or style):
1018 return changeset_printer(ui, repo, patch, opts, buffered)
1019 return changeset_printer(ui, repo, patch, opts, buffered)
1019
1020
1020 mapfile = None
1021 mapfile = None
1021 if style and not tmpl:
1022 if style and not tmpl:
1022 mapfile = style
1023 mapfile = style
1023 if not os.path.split(mapfile)[0]:
1024 if not os.path.split(mapfile)[0]:
1024 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1025 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1025 or templater.templatepath(mapfile))
1026 or templater.templatepath(mapfile))
1026 if mapname: mapfile = mapname
1027 if mapname: mapfile = mapname
1027
1028
1028 try:
1029 try:
1029 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
1030 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
1030 except SyntaxError, inst:
1031 except SyntaxError, inst:
1031 raise util.Abort(inst.args[0])
1032 raise util.Abort(inst.args[0])
1032 if tmpl: t.use_template(tmpl)
1033 if tmpl: t.use_template(tmpl)
1033 return t
1034 return t
1034
1035
1035 def finddate(ui, repo, date):
1036 def finddate(ui, repo, date):
1036 """Find the tipmost changeset that matches the given date spec"""
1037 """Find the tipmost changeset that matches the given date spec"""
1037
1038
1038 df = util.matchdate(date)
1039 df = util.matchdate(date)
1039 m = matchall(repo)
1040 m = matchall(repo)
1040 results = {}
1041 results = {}
1041
1042
1042 def prep(ctx, fns):
1043 def prep(ctx, fns):
1043 d = ctx.date()
1044 d = ctx.date()
1044 if df(d[0]):
1045 if df(d[0]):
1045 results[ctx.rev()] = d
1046 results[ctx.rev()] = d
1046
1047
1047 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1048 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1048 rev = ctx.rev()
1049 rev = ctx.rev()
1049 if rev in results:
1050 if rev in results:
1050 ui.status(_("Found revision %s from %s\n") %
1051 ui.status(_("Found revision %s from %s\n") %
1051 (rev, util.datestr(results[rev])))
1052 (rev, util.datestr(results[rev])))
1052 return str(rev)
1053 return str(rev)
1053
1054
1054 raise util.Abort(_("revision matching date not found"))
1055 raise util.Abort(_("revision matching date not found"))
1055
1056
1056 def walkchangerevs(repo, match, opts, prepare):
1057 def walkchangerevs(repo, match, opts, prepare):
1057 '''Iterate over files and the revs in which they changed.
1058 '''Iterate over files and the revs in which they changed.
1058
1059
1059 Callers most commonly need to iterate backwards over the history
1060 Callers most commonly need to iterate backwards over the history
1060 in which they are interested. Doing so has awful (quadratic-looking)
1061 in which they are interested. Doing so has awful (quadratic-looking)
1061 performance, so we use iterators in a "windowed" way.
1062 performance, so we use iterators in a "windowed" way.
1062
1063
1063 We walk a window of revisions in the desired order. Within the
1064 We walk a window of revisions in the desired order. Within the
1064 window, we first walk forwards to gather data, then in the desired
1065 window, we first walk forwards to gather data, then in the desired
1065 order (usually backwards) to display it.
1066 order (usually backwards) to display it.
1066
1067
1067 This function returns an iterator yielding contexts. Before
1068 This function returns an iterator yielding contexts. Before
1068 yielding each context, the iterator will first call the prepare
1069 yielding each context, the iterator will first call the prepare
1069 function on each context in the window in forward order.'''
1070 function on each context in the window in forward order.'''
1070
1071
1071 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1072 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1072 if start < end:
1073 if start < end:
1073 while start < end:
1074 while start < end:
1074 yield start, min(windowsize, end-start)
1075 yield start, min(windowsize, end-start)
1075 start += windowsize
1076 start += windowsize
1076 if windowsize < sizelimit:
1077 if windowsize < sizelimit:
1077 windowsize *= 2
1078 windowsize *= 2
1078 else:
1079 else:
1079 while start > end:
1080 while start > end:
1080 yield start, min(windowsize, start-end-1)
1081 yield start, min(windowsize, start-end-1)
1081 start -= windowsize
1082 start -= windowsize
1082 if windowsize < sizelimit:
1083 if windowsize < sizelimit:
1083 windowsize *= 2
1084 windowsize *= 2
1084
1085
1085 follow = opts.get('follow') or opts.get('follow_first')
1086 follow = opts.get('follow') or opts.get('follow_first')
1086
1087
1087 if not len(repo):
1088 if not len(repo):
1088 return []
1089 return []
1089
1090
1090 if follow:
1091 if follow:
1091 defrange = '%s:0' % repo['.'].rev()
1092 defrange = '%s:0' % repo['.'].rev()
1092 else:
1093 else:
1093 defrange = '-1:0'
1094 defrange = '-1:0'
1094 revs = revrange(repo, opts['rev'] or [defrange])
1095 revs = revrange(repo, opts['rev'] or [defrange])
1095 wanted = set()
1096 wanted = set()
1096 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1097 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1097 fncache = {}
1098 fncache = {}
1098 change = util.cachefunc(repo.changectx)
1099 change = util.cachefunc(repo.changectx)
1099
1100
1100 if not slowpath and not match.files():
1101 if not slowpath and not match.files():
1101 # No files, no patterns. Display all revs.
1102 # No files, no patterns. Display all revs.
1102 wanted = set(revs)
1103 wanted = set(revs)
1103 copies = []
1104 copies = []
1104
1105
1105 if not slowpath:
1106 if not slowpath:
1106 # Only files, no patterns. Check the history of each file.
1107 # Only files, no patterns. Check the history of each file.
1107 def filerevgen(filelog, node):
1108 def filerevgen(filelog, node):
1108 cl_count = len(repo)
1109 cl_count = len(repo)
1109 if node is None:
1110 if node is None:
1110 last = len(filelog) - 1
1111 last = len(filelog) - 1
1111 else:
1112 else:
1112 last = filelog.rev(node)
1113 last = filelog.rev(node)
1113 for i, window in increasing_windows(last, nullrev):
1114 for i, window in increasing_windows(last, nullrev):
1114 revs = []
1115 revs = []
1115 for j in xrange(i - window, i + 1):
1116 for j in xrange(i - window, i + 1):
1116 n = filelog.node(j)
1117 n = filelog.node(j)
1117 revs.append((filelog.linkrev(j),
1118 revs.append((filelog.linkrev(j),
1118 follow and filelog.renamed(n)))
1119 follow and filelog.renamed(n)))
1119 for rev in reversed(revs):
1120 for rev in reversed(revs):
1120 # only yield rev for which we have the changelog, it can
1121 # only yield rev for which we have the changelog, it can
1121 # happen while doing "hg log" during a pull or commit
1122 # happen while doing "hg log" during a pull or commit
1122 if rev[0] < cl_count:
1123 if rev[0] < cl_count:
1123 yield rev
1124 yield rev
1124 def iterfiles():
1125 def iterfiles():
1125 for filename in match.files():
1126 for filename in match.files():
1126 yield filename, None
1127 yield filename, None
1127 for filename_node in copies:
1128 for filename_node in copies:
1128 yield filename_node
1129 yield filename_node
1129 minrev, maxrev = min(revs), max(revs)
1130 minrev, maxrev = min(revs), max(revs)
1130 for file_, node in iterfiles():
1131 for file_, node in iterfiles():
1131 filelog = repo.file(file_)
1132 filelog = repo.file(file_)
1132 if not len(filelog):
1133 if not len(filelog):
1133 if node is None:
1134 if node is None:
1134 # A zero count may be a directory or deleted file, so
1135 # A zero count may be a directory or deleted file, so
1135 # try to find matching entries on the slow path.
1136 # try to find matching entries on the slow path.
1136 if follow:
1137 if follow:
1137 raise util.Abort(_('cannot follow nonexistent file: "%s"') % file_)
1138 raise util.Abort(_('cannot follow nonexistent file: "%s"') % file_)
1138 slowpath = True
1139 slowpath = True
1139 break
1140 break
1140 else:
1141 else:
1141 continue
1142 continue
1142 for rev, copied in filerevgen(filelog, node):
1143 for rev, copied in filerevgen(filelog, node):
1143 if rev <= maxrev:
1144 if rev <= maxrev:
1144 if rev < minrev:
1145 if rev < minrev:
1145 break
1146 break
1146 fncache.setdefault(rev, [])
1147 fncache.setdefault(rev, [])
1147 fncache[rev].append(file_)
1148 fncache[rev].append(file_)
1148 wanted.add(rev)
1149 wanted.add(rev)
1149 if follow and copied:
1150 if follow and copied:
1150 copies.append(copied)
1151 copies.append(copied)
1151 if slowpath:
1152 if slowpath:
1152 if follow:
1153 if follow:
1153 raise util.Abort(_('can only follow copies/renames for explicit '
1154 raise util.Abort(_('can only follow copies/renames for explicit '
1154 'filenames'))
1155 'filenames'))
1155
1156
1156 # The slow path checks files modified in every changeset.
1157 # The slow path checks files modified in every changeset.
1157 def changerevgen():
1158 def changerevgen():
1158 for i, window in increasing_windows(len(repo) - 1, nullrev):
1159 for i, window in increasing_windows(len(repo) - 1, nullrev):
1159 for j in xrange(i - window, i + 1):
1160 for j in xrange(i - window, i + 1):
1160 yield change(j)
1161 yield change(j)
1161
1162
1162 for ctx in changerevgen():
1163 for ctx in changerevgen():
1163 matches = filter(match, ctx.files())
1164 matches = filter(match, ctx.files())
1164 if matches:
1165 if matches:
1165 fncache[ctx.rev()] = matches
1166 fncache[ctx.rev()] = matches
1166 wanted.add(ctx.rev())
1167 wanted.add(ctx.rev())
1167
1168
1168 class followfilter(object):
1169 class followfilter(object):
1169 def __init__(self, onlyfirst=False):
1170 def __init__(self, onlyfirst=False):
1170 self.startrev = nullrev
1171 self.startrev = nullrev
1171 self.roots = []
1172 self.roots = []
1172 self.onlyfirst = onlyfirst
1173 self.onlyfirst = onlyfirst
1173
1174
1174 def match(self, rev):
1175 def match(self, rev):
1175 def realparents(rev):
1176 def realparents(rev):
1176 if self.onlyfirst:
1177 if self.onlyfirst:
1177 return repo.changelog.parentrevs(rev)[0:1]
1178 return repo.changelog.parentrevs(rev)[0:1]
1178 else:
1179 else:
1179 return filter(lambda x: x != nullrev,
1180 return filter(lambda x: x != nullrev,
1180 repo.changelog.parentrevs(rev))
1181 repo.changelog.parentrevs(rev))
1181
1182
1182 if self.startrev == nullrev:
1183 if self.startrev == nullrev:
1183 self.startrev = rev
1184 self.startrev = rev
1184 return True
1185 return True
1185
1186
1186 if rev > self.startrev:
1187 if rev > self.startrev:
1187 # forward: all descendants
1188 # forward: all descendants
1188 if not self.roots:
1189 if not self.roots:
1189 self.roots.append(self.startrev)
1190 self.roots.append(self.startrev)
1190 for parent in realparents(rev):
1191 for parent in realparents(rev):
1191 if parent in self.roots:
1192 if parent in self.roots:
1192 self.roots.append(rev)
1193 self.roots.append(rev)
1193 return True
1194 return True
1194 else:
1195 else:
1195 # backwards: all parents
1196 # backwards: all parents
1196 if not self.roots:
1197 if not self.roots:
1197 self.roots.extend(realparents(self.startrev))
1198 self.roots.extend(realparents(self.startrev))
1198 if rev in self.roots:
1199 if rev in self.roots:
1199 self.roots.remove(rev)
1200 self.roots.remove(rev)
1200 self.roots.extend(realparents(rev))
1201 self.roots.extend(realparents(rev))
1201 return True
1202 return True
1202
1203
1203 return False
1204 return False
1204
1205
1205 # it might be worthwhile to do this in the iterator if the rev range
1206 # it might be worthwhile to do this in the iterator if the rev range
1206 # is descending and the prune args are all within that range
1207 # is descending and the prune args are all within that range
1207 for rev in opts.get('prune', ()):
1208 for rev in opts.get('prune', ()):
1208 rev = repo.changelog.rev(repo.lookup(rev))
1209 rev = repo.changelog.rev(repo.lookup(rev))
1209 ff = followfilter()
1210 ff = followfilter()
1210 stop = min(revs[0], revs[-1])
1211 stop = min(revs[0], revs[-1])
1211 for x in xrange(rev, stop-1, -1):
1212 for x in xrange(rev, stop-1, -1):
1212 if ff.match(x):
1213 if ff.match(x):
1213 wanted.discard(x)
1214 wanted.discard(x)
1214
1215
1215 def iterate():
1216 def iterate():
1216 if follow and not match.files():
1217 if follow and not match.files():
1217 ff = followfilter(onlyfirst=opts.get('follow_first'))
1218 ff = followfilter(onlyfirst=opts.get('follow_first'))
1218 def want(rev):
1219 def want(rev):
1219 return ff.match(rev) and rev in wanted
1220 return ff.match(rev) and rev in wanted
1220 else:
1221 else:
1221 def want(rev):
1222 def want(rev):
1222 return rev in wanted
1223 return rev in wanted
1223
1224
1224 for i, window in increasing_windows(0, len(revs)):
1225 for i, window in increasing_windows(0, len(revs)):
1225 change = util.cachefunc(repo.changectx)
1226 change = util.cachefunc(repo.changectx)
1226 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1227 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1227 for rev in sorted(nrevs):
1228 for rev in sorted(nrevs):
1228 fns = fncache.get(rev)
1229 fns = fncache.get(rev)
1229 ctx = change(rev)
1230 ctx = change(rev)
1230 if not fns:
1231 if not fns:
1231 def fns_generator():
1232 def fns_generator():
1232 for f in ctx.files():
1233 for f in ctx.files():
1233 if match(f):
1234 if match(f):
1234 yield f
1235 yield f
1235 fns = fns_generator()
1236 fns = fns_generator()
1236 prepare(ctx, fns)
1237 prepare(ctx, fns)
1237 for rev in nrevs:
1238 for rev in nrevs:
1238 yield change(rev)
1239 yield change(rev)
1239 return iterate()
1240 return iterate()
1240
1241
1241 def commit(ui, repo, commitfunc, pats, opts):
1242 def commit(ui, repo, commitfunc, pats, opts):
1242 '''commit the specified files or all outstanding changes'''
1243 '''commit the specified files or all outstanding changes'''
1243 date = opts.get('date')
1244 date = opts.get('date')
1244 if date:
1245 if date:
1245 opts['date'] = util.parsedate(date)
1246 opts['date'] = util.parsedate(date)
1246 message = logmessage(opts)
1247 message = logmessage(opts)
1247
1248
1248 # extract addremove carefully -- this function can be called from a command
1249 # extract addremove carefully -- this function can be called from a command
1249 # that doesn't support addremove
1250 # that doesn't support addremove
1250 if opts.get('addremove'):
1251 if opts.get('addremove'):
1251 addremove(repo, pats, opts)
1252 addremove(repo, pats, opts)
1252
1253
1253 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1254 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1254
1255
1255 def commiteditor(repo, ctx, subs):
1256 def commiteditor(repo, ctx, subs):
1256 if ctx.description():
1257 if ctx.description():
1257 return ctx.description()
1258 return ctx.description()
1258 return commitforceeditor(repo, ctx, subs)
1259 return commitforceeditor(repo, ctx, subs)
1259
1260
1260 def commitforceeditor(repo, ctx, subs):
1261 def commitforceeditor(repo, ctx, subs):
1261 edittext = []
1262 edittext = []
1262 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1263 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1263 if ctx.description():
1264 if ctx.description():
1264 edittext.append(ctx.description())
1265 edittext.append(ctx.description())
1265 edittext.append("")
1266 edittext.append("")
1266 edittext.append("") # Empty line between message and comments.
1267 edittext.append("") # Empty line between message and comments.
1267 edittext.append(_("HG: Enter commit message."
1268 edittext.append(_("HG: Enter commit message."
1268 " Lines beginning with 'HG:' are removed."))
1269 " Lines beginning with 'HG:' are removed."))
1269 edittext.append(_("HG: Leave message empty to abort commit."))
1270 edittext.append(_("HG: Leave message empty to abort commit."))
1270 edittext.append("HG: --")
1271 edittext.append("HG: --")
1271 edittext.append(_("HG: user: %s") % ctx.user())
1272 edittext.append(_("HG: user: %s") % ctx.user())
1272 if ctx.p2():
1273 if ctx.p2():
1273 edittext.append(_("HG: branch merge"))
1274 edittext.append(_("HG: branch merge"))
1274 if ctx.branch():
1275 if ctx.branch():
1275 edittext.append(_("HG: branch '%s'")
1276 edittext.append(_("HG: branch '%s'")
1276 % encoding.tolocal(ctx.branch()))
1277 % encoding.tolocal(ctx.branch()))
1277 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1278 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1278 edittext.extend([_("HG: added %s") % f for f in added])
1279 edittext.extend([_("HG: added %s") % f for f in added])
1279 edittext.extend([_("HG: changed %s") % f for f in modified])
1280 edittext.extend([_("HG: changed %s") % f for f in modified])
1280 edittext.extend([_("HG: removed %s") % f for f in removed])
1281 edittext.extend([_("HG: removed %s") % f for f in removed])
1281 if not added and not modified and not removed:
1282 if not added and not modified and not removed:
1282 edittext.append(_("HG: no files changed"))
1283 edittext.append(_("HG: no files changed"))
1283 edittext.append("")
1284 edittext.append("")
1284 # run editor in the repository root
1285 # run editor in the repository root
1285 olddir = os.getcwd()
1286 olddir = os.getcwd()
1286 os.chdir(repo.root)
1287 os.chdir(repo.root)
1287 text = repo.ui.edit("\n".join(edittext), ctx.user())
1288 text = repo.ui.edit("\n".join(edittext), ctx.user())
1288 text = re.sub("(?m)^HG:.*\n", "", text)
1289 text = re.sub("(?m)^HG:.*\n", "", text)
1289 os.chdir(olddir)
1290 os.chdir(olddir)
1290
1291
1291 if not text.strip():
1292 if not text.strip():
1292 raise util.Abort(_("empty commit message"))
1293 raise util.Abort(_("empty commit message"))
1293
1294
1294 return text
1295 return text
General Comments 0
You need to be logged in to leave comments. Login now