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