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