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