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