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