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