##// END OF EJS Templates
templater: ability to display diffstat for log-like commands
Alexander Solovyov <piranha at piranha.org.ua> -
r7879:5c4026a2 default
parent child Browse files
Show More
@@ -1,1194 +1,1204 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 hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, bisect, stat
10 import os, sys, bisect, stat
11 import mdiff, bdiff, util, templater, templatefilters, patch, errno, error
11 import mdiff, bdiff, util, templater, templatefilters, patch, errno, error
12 import match as _match
12 import match as _match
13
13
14 revrangesep = ':'
14 revrangesep = ':'
15
15
16 def findpossible(cmd, table, strict=False):
16 def findpossible(cmd, table, strict=False):
17 """
17 """
18 Return cmd -> (aliases, command table entry)
18 Return cmd -> (aliases, command table entry)
19 for each matching command.
19 for each matching command.
20 Return debug commands (or their aliases) only if no normal command matches.
20 Return debug commands (or their aliases) only if no normal command matches.
21 """
21 """
22 choice = {}
22 choice = {}
23 debugchoice = {}
23 debugchoice = {}
24 for e in table.keys():
24 for e in table.keys():
25 aliases = e.lstrip("^").split("|")
25 aliases = e.lstrip("^").split("|")
26 found = None
26 found = None
27 if cmd in aliases:
27 if cmd in aliases:
28 found = cmd
28 found = cmd
29 elif not strict:
29 elif not strict:
30 for a in aliases:
30 for a in aliases:
31 if a.startswith(cmd):
31 if a.startswith(cmd):
32 found = a
32 found = a
33 break
33 break
34 if found is not None:
34 if found is not None:
35 if aliases[0].startswith("debug") or found.startswith("debug"):
35 if aliases[0].startswith("debug") or found.startswith("debug"):
36 debugchoice[found] = (aliases, table[e])
36 debugchoice[found] = (aliases, table[e])
37 else:
37 else:
38 choice[found] = (aliases, table[e])
38 choice[found] = (aliases, table[e])
39
39
40 if not choice and debugchoice:
40 if not choice and debugchoice:
41 choice = debugchoice
41 choice = debugchoice
42
42
43 return choice
43 return choice
44
44
45 def findcmd(cmd, table, strict=True):
45 def findcmd(cmd, table, strict=True):
46 """Return (aliases, command table entry) for command string."""
46 """Return (aliases, command table entry) for command string."""
47 choice = findpossible(cmd, table, strict)
47 choice = findpossible(cmd, table, strict)
48
48
49 if cmd in choice:
49 if cmd in choice:
50 return choice[cmd]
50 return choice[cmd]
51
51
52 if len(choice) > 1:
52 if len(choice) > 1:
53 clist = choice.keys()
53 clist = choice.keys()
54 clist.sort()
54 clist.sort()
55 raise error.AmbiguousCommand(cmd, clist)
55 raise error.AmbiguousCommand(cmd, clist)
56
56
57 if choice:
57 if choice:
58 return choice.values()[0]
58 return choice.values()[0]
59
59
60 raise error.UnknownCommand(cmd)
60 raise error.UnknownCommand(cmd)
61
61
62 def bail_if_changed(repo):
62 def bail_if_changed(repo):
63 if repo.dirstate.parents()[1] != nullid:
63 if repo.dirstate.parents()[1] != nullid:
64 raise util.Abort(_('outstanding uncommitted merge'))
64 raise util.Abort(_('outstanding uncommitted merge'))
65 modified, added, removed, deleted = repo.status()[:4]
65 modified, added, removed, deleted = repo.status()[:4]
66 if modified or added or removed or deleted:
66 if modified or added or removed or deleted:
67 raise util.Abort(_("outstanding uncommitted changes"))
67 raise util.Abort(_("outstanding uncommitted changes"))
68
68
69 def logmessage(opts):
69 def logmessage(opts):
70 """ get the log message according to -m and -l option """
70 """ get the log message according to -m and -l option """
71 message = opts.get('message')
71 message = opts.get('message')
72 logfile = opts.get('logfile')
72 logfile = opts.get('logfile')
73
73
74 if message and logfile:
74 if message and logfile:
75 raise util.Abort(_('options --message and --logfile are mutually '
75 raise util.Abort(_('options --message and --logfile are mutually '
76 'exclusive'))
76 'exclusive'))
77 if not message and logfile:
77 if not message and logfile:
78 try:
78 try:
79 if logfile == '-':
79 if logfile == '-':
80 message = sys.stdin.read()
80 message = sys.stdin.read()
81 else:
81 else:
82 message = open(logfile).read()
82 message = open(logfile).read()
83 except IOError, inst:
83 except IOError, inst:
84 raise util.Abort(_("can't read commit message '%s': %s") %
84 raise util.Abort(_("can't read commit message '%s': %s") %
85 (logfile, inst.strerror))
85 (logfile, inst.strerror))
86 return message
86 return message
87
87
88 def loglimit(opts):
88 def loglimit(opts):
89 """get the log limit according to option -l/--limit"""
89 """get the log limit according to option -l/--limit"""
90 limit = opts.get('limit')
90 limit = opts.get('limit')
91 if limit:
91 if limit:
92 try:
92 try:
93 limit = int(limit)
93 limit = int(limit)
94 except ValueError:
94 except ValueError:
95 raise util.Abort(_('limit must be a positive integer'))
95 raise util.Abort(_('limit must be a positive integer'))
96 if limit <= 0: raise util.Abort(_('limit must be positive'))
96 if limit <= 0: raise util.Abort(_('limit must be positive'))
97 else:
97 else:
98 limit = sys.maxint
98 limit = sys.maxint
99 return limit
99 return limit
100
100
101 def setremoteconfig(ui, opts):
101 def setremoteconfig(ui, opts):
102 "copy remote options to ui tree"
102 "copy remote options to ui tree"
103 if opts.get('ssh'):
103 if opts.get('ssh'):
104 ui.setconfig("ui", "ssh", opts['ssh'])
104 ui.setconfig("ui", "ssh", opts['ssh'])
105 if opts.get('remotecmd'):
105 if opts.get('remotecmd'):
106 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
106 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
107
107
108 def revpair(repo, revs):
108 def revpair(repo, revs):
109 '''return pair of nodes, given list of revisions. second item can
109 '''return pair of nodes, given list of revisions. second item can
110 be None, meaning use working dir.'''
110 be None, meaning use working dir.'''
111
111
112 def revfix(repo, val, defval):
112 def revfix(repo, val, defval):
113 if not val and val != 0 and defval is not None:
113 if not val and val != 0 and defval is not None:
114 val = defval
114 val = defval
115 return repo.lookup(val)
115 return repo.lookup(val)
116
116
117 if not revs:
117 if not revs:
118 return repo.dirstate.parents()[0], None
118 return repo.dirstate.parents()[0], None
119 end = None
119 end = None
120 if len(revs) == 1:
120 if len(revs) == 1:
121 if revrangesep in revs[0]:
121 if revrangesep in revs[0]:
122 start, end = revs[0].split(revrangesep, 1)
122 start, end = revs[0].split(revrangesep, 1)
123 start = revfix(repo, start, 0)
123 start = revfix(repo, start, 0)
124 end = revfix(repo, end, len(repo) - 1)
124 end = revfix(repo, end, len(repo) - 1)
125 else:
125 else:
126 start = revfix(repo, revs[0], None)
126 start = revfix(repo, revs[0], None)
127 elif len(revs) == 2:
127 elif len(revs) == 2:
128 if revrangesep in revs[0] or revrangesep in revs[1]:
128 if revrangesep in revs[0] or revrangesep in revs[1]:
129 raise util.Abort(_('too many revisions specified'))
129 raise util.Abort(_('too many revisions specified'))
130 start = revfix(repo, revs[0], None)
130 start = revfix(repo, revs[0], None)
131 end = revfix(repo, revs[1], None)
131 end = revfix(repo, revs[1], None)
132 else:
132 else:
133 raise util.Abort(_('too many revisions specified'))
133 raise util.Abort(_('too many revisions specified'))
134 return start, end
134 return start, end
135
135
136 def revrange(repo, revs):
136 def revrange(repo, revs):
137 """Yield revision as strings from a list of revision specifications."""
137 """Yield revision as strings from a list of revision specifications."""
138
138
139 def revfix(repo, val, defval):
139 def revfix(repo, val, defval):
140 if not val and val != 0 and defval is not None:
140 if not val and val != 0 and defval is not None:
141 return defval
141 return defval
142 return repo.changelog.rev(repo.lookup(val))
142 return repo.changelog.rev(repo.lookup(val))
143
143
144 seen, l = {}, []
144 seen, l = {}, []
145 for spec in revs:
145 for spec in revs:
146 if revrangesep in spec:
146 if revrangesep in spec:
147 start, end = spec.split(revrangesep, 1)
147 start, end = spec.split(revrangesep, 1)
148 start = revfix(repo, start, 0)
148 start = revfix(repo, start, 0)
149 end = revfix(repo, end, len(repo) - 1)
149 end = revfix(repo, end, len(repo) - 1)
150 step = start > end and -1 or 1
150 step = start > end and -1 or 1
151 for rev in xrange(start, end+step, step):
151 for rev in xrange(start, end+step, step):
152 if rev in seen:
152 if rev in seen:
153 continue
153 continue
154 seen[rev] = 1
154 seen[rev] = 1
155 l.append(rev)
155 l.append(rev)
156 else:
156 else:
157 rev = revfix(repo, spec, None)
157 rev = revfix(repo, spec, None)
158 if rev in seen:
158 if rev in seen:
159 continue
159 continue
160 seen[rev] = 1
160 seen[rev] = 1
161 l.append(rev)
161 l.append(rev)
162
162
163 return l
163 return l
164
164
165 def make_filename(repo, pat, node,
165 def make_filename(repo, pat, node,
166 total=None, seqno=None, revwidth=None, pathname=None):
166 total=None, seqno=None, revwidth=None, pathname=None):
167 node_expander = {
167 node_expander = {
168 'H': lambda: hex(node),
168 'H': lambda: hex(node),
169 'R': lambda: str(repo.changelog.rev(node)),
169 'R': lambda: str(repo.changelog.rev(node)),
170 'h': lambda: short(node),
170 'h': lambda: short(node),
171 }
171 }
172 expander = {
172 expander = {
173 '%': lambda: '%',
173 '%': lambda: '%',
174 'b': lambda: os.path.basename(repo.root),
174 'b': lambda: os.path.basename(repo.root),
175 }
175 }
176
176
177 try:
177 try:
178 if node:
178 if node:
179 expander.update(node_expander)
179 expander.update(node_expander)
180 if node:
180 if node:
181 expander['r'] = (lambda:
181 expander['r'] = (lambda:
182 str(repo.changelog.rev(node)).zfill(revwidth or 0))
182 str(repo.changelog.rev(node)).zfill(revwidth or 0))
183 if total is not None:
183 if total is not None:
184 expander['N'] = lambda: str(total)
184 expander['N'] = lambda: str(total)
185 if seqno is not None:
185 if seqno is not None:
186 expander['n'] = lambda: str(seqno)
186 expander['n'] = lambda: str(seqno)
187 if total is not None and seqno is not None:
187 if total is not None and seqno is not None:
188 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
188 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
189 if pathname is not None:
189 if pathname is not None:
190 expander['s'] = lambda: os.path.basename(pathname)
190 expander['s'] = lambda: os.path.basename(pathname)
191 expander['d'] = lambda: os.path.dirname(pathname) or '.'
191 expander['d'] = lambda: os.path.dirname(pathname) or '.'
192 expander['p'] = lambda: pathname
192 expander['p'] = lambda: pathname
193
193
194 newname = []
194 newname = []
195 patlen = len(pat)
195 patlen = len(pat)
196 i = 0
196 i = 0
197 while i < patlen:
197 while i < patlen:
198 c = pat[i]
198 c = pat[i]
199 if c == '%':
199 if c == '%':
200 i += 1
200 i += 1
201 c = pat[i]
201 c = pat[i]
202 c = expander[c]()
202 c = expander[c]()
203 newname.append(c)
203 newname.append(c)
204 i += 1
204 i += 1
205 return ''.join(newname)
205 return ''.join(newname)
206 except KeyError, inst:
206 except KeyError, inst:
207 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
207 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
208 inst.args[0])
208 inst.args[0])
209
209
210 def make_file(repo, pat, node=None,
210 def make_file(repo, pat, node=None,
211 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
211 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
212
212
213 writable = 'w' in mode or 'a' in mode
213 writable = 'w' in mode or 'a' in mode
214
214
215 if not pat or pat == '-':
215 if not pat or pat == '-':
216 return writable and sys.stdout or sys.stdin
216 return writable and sys.stdout or sys.stdin
217 if hasattr(pat, 'write') and writable:
217 if hasattr(pat, 'write') and writable:
218 return pat
218 return pat
219 if hasattr(pat, 'read') and 'r' in mode:
219 if hasattr(pat, 'read') and 'r' in mode:
220 return pat
220 return pat
221 return open(make_filename(repo, pat, node, total, seqno, revwidth,
221 return open(make_filename(repo, pat, node, total, seqno, revwidth,
222 pathname),
222 pathname),
223 mode)
223 mode)
224
224
225 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
225 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
226 if not globbed and default == 'relpath':
226 if not globbed and default == 'relpath':
227 pats = util.expand_glob(pats or [])
227 pats = util.expand_glob(pats or [])
228 m = _match.match(repo.root, repo.getcwd(), pats,
228 m = _match.match(repo.root, repo.getcwd(), pats,
229 opts.get('include'), opts.get('exclude'), default)
229 opts.get('include'), opts.get('exclude'), default)
230 def badfn(f, msg):
230 def badfn(f, msg):
231 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
231 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
232 return False
232 return False
233 m.bad = badfn
233 m.bad = badfn
234 return m
234 return m
235
235
236 def matchall(repo):
236 def matchall(repo):
237 return _match.always(repo.root, repo.getcwd())
237 return _match.always(repo.root, repo.getcwd())
238
238
239 def matchfiles(repo, files):
239 def matchfiles(repo, files):
240 return _match.exact(repo.root, repo.getcwd(), files)
240 return _match.exact(repo.root, repo.getcwd(), files)
241
241
242 def findrenames(repo, added=None, removed=None, threshold=0.5):
242 def findrenames(repo, added=None, removed=None, threshold=0.5):
243 '''find renamed files -- yields (before, after, score) tuples'''
243 '''find renamed files -- yields (before, after, score) tuples'''
244 if added is None or removed is None:
244 if added is None or removed is None:
245 added, removed = repo.status()[1:3]
245 added, removed = repo.status()[1:3]
246 ctx = repo['.']
246 ctx = repo['.']
247 for a in added:
247 for a in added:
248 aa = repo.wread(a)
248 aa = repo.wread(a)
249 bestname, bestscore = None, threshold
249 bestname, bestscore = None, threshold
250 for r in removed:
250 for r in removed:
251 rr = ctx.filectx(r).data()
251 rr = ctx.filectx(r).data()
252
252
253 # bdiff.blocks() returns blocks of matching lines
253 # bdiff.blocks() returns blocks of matching lines
254 # count the number of bytes in each
254 # count the number of bytes in each
255 equal = 0
255 equal = 0
256 alines = mdiff.splitnewlines(aa)
256 alines = mdiff.splitnewlines(aa)
257 matches = bdiff.blocks(aa, rr)
257 matches = bdiff.blocks(aa, rr)
258 for x1,x2,y1,y2 in matches:
258 for x1,x2,y1,y2 in matches:
259 for line in alines[x1:x2]:
259 for line in alines[x1:x2]:
260 equal += len(line)
260 equal += len(line)
261
261
262 lengths = len(aa) + len(rr)
262 lengths = len(aa) + len(rr)
263 if lengths:
263 if lengths:
264 myscore = equal*2.0 / lengths
264 myscore = equal*2.0 / lengths
265 if myscore >= bestscore:
265 if myscore >= bestscore:
266 bestname, bestscore = r, myscore
266 bestname, bestscore = r, myscore
267 if bestname:
267 if bestname:
268 yield bestname, a, bestscore
268 yield bestname, a, bestscore
269
269
270 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
270 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
271 if dry_run is None:
271 if dry_run is None:
272 dry_run = opts.get('dry_run')
272 dry_run = opts.get('dry_run')
273 if similarity is None:
273 if similarity is None:
274 similarity = float(opts.get('similarity') or 0)
274 similarity = float(opts.get('similarity') or 0)
275 add, remove = [], []
275 add, remove = [], []
276 mapping = {}
276 mapping = {}
277 audit_path = util.path_auditor(repo.root)
277 audit_path = util.path_auditor(repo.root)
278 m = match(repo, pats, opts)
278 m = match(repo, pats, opts)
279 for abs in repo.walk(m):
279 for abs in repo.walk(m):
280 target = repo.wjoin(abs)
280 target = repo.wjoin(abs)
281 good = True
281 good = True
282 try:
282 try:
283 audit_path(abs)
283 audit_path(abs)
284 except:
284 except:
285 good = False
285 good = False
286 rel = m.rel(abs)
286 rel = m.rel(abs)
287 exact = m.exact(abs)
287 exact = m.exact(abs)
288 if good and abs not in repo.dirstate:
288 if good and abs not in repo.dirstate:
289 add.append(abs)
289 add.append(abs)
290 mapping[abs] = rel, m.exact(abs)
290 mapping[abs] = rel, m.exact(abs)
291 if repo.ui.verbose or not exact:
291 if repo.ui.verbose or not exact:
292 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
292 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
293 if repo.dirstate[abs] != 'r' and (not good or not util.lexists(target)
293 if repo.dirstate[abs] != 'r' and (not good or not util.lexists(target)
294 or (os.path.isdir(target) and not os.path.islink(target))):
294 or (os.path.isdir(target) and not os.path.islink(target))):
295 remove.append(abs)
295 remove.append(abs)
296 mapping[abs] = rel, exact
296 mapping[abs] = rel, exact
297 if repo.ui.verbose or not exact:
297 if repo.ui.verbose or not exact:
298 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
298 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
299 if not dry_run:
299 if not dry_run:
300 repo.remove(remove)
300 repo.remove(remove)
301 repo.add(add)
301 repo.add(add)
302 if similarity > 0:
302 if similarity > 0:
303 for old, new, score in findrenames(repo, add, remove, similarity):
303 for old, new, score in findrenames(repo, add, remove, similarity):
304 oldrel, oldexact = mapping[old]
304 oldrel, oldexact = mapping[old]
305 newrel, newexact = mapping[new]
305 newrel, newexact = mapping[new]
306 if repo.ui.verbose or not oldexact or not newexact:
306 if repo.ui.verbose or not oldexact or not newexact:
307 repo.ui.status(_('recording removal of %s as rename to %s '
307 repo.ui.status(_('recording removal of %s as rename to %s '
308 '(%d%% similar)\n') %
308 '(%d%% similar)\n') %
309 (oldrel, newrel, score * 100))
309 (oldrel, newrel, score * 100))
310 if not dry_run:
310 if not dry_run:
311 repo.copy(old, new)
311 repo.copy(old, new)
312
312
313 def copy(ui, repo, pats, opts, rename=False):
313 def copy(ui, repo, pats, opts, rename=False):
314 # called with the repo lock held
314 # called with the repo lock held
315 #
315 #
316 # hgsep => pathname that uses "/" to separate directories
316 # hgsep => pathname that uses "/" to separate directories
317 # ossep => pathname that uses os.sep to separate directories
317 # ossep => pathname that uses os.sep to separate directories
318 cwd = repo.getcwd()
318 cwd = repo.getcwd()
319 targets = {}
319 targets = {}
320 after = opts.get("after")
320 after = opts.get("after")
321 dryrun = opts.get("dry_run")
321 dryrun = opts.get("dry_run")
322
322
323 def walkpat(pat):
323 def walkpat(pat):
324 srcs = []
324 srcs = []
325 m = match(repo, [pat], opts, globbed=True)
325 m = match(repo, [pat], opts, globbed=True)
326 for abs in repo.walk(m):
326 for abs in repo.walk(m):
327 state = repo.dirstate[abs]
327 state = repo.dirstate[abs]
328 rel = m.rel(abs)
328 rel = m.rel(abs)
329 exact = m.exact(abs)
329 exact = m.exact(abs)
330 if state in '?r':
330 if state in '?r':
331 if exact and state == '?':
331 if exact and state == '?':
332 ui.warn(_('%s: not copying - file is not managed\n') % rel)
332 ui.warn(_('%s: not copying - file is not managed\n') % rel)
333 if exact and state == 'r':
333 if exact and state == 'r':
334 ui.warn(_('%s: not copying - file has been marked for'
334 ui.warn(_('%s: not copying - file has been marked for'
335 ' remove\n') % rel)
335 ' remove\n') % rel)
336 continue
336 continue
337 # abs: hgsep
337 # abs: hgsep
338 # rel: ossep
338 # rel: ossep
339 srcs.append((abs, rel, exact))
339 srcs.append((abs, rel, exact))
340 return srcs
340 return srcs
341
341
342 # abssrc: hgsep
342 # abssrc: hgsep
343 # relsrc: ossep
343 # relsrc: ossep
344 # otarget: ossep
344 # otarget: ossep
345 def copyfile(abssrc, relsrc, otarget, exact):
345 def copyfile(abssrc, relsrc, otarget, exact):
346 abstarget = util.canonpath(repo.root, cwd, otarget)
346 abstarget = util.canonpath(repo.root, cwd, otarget)
347 reltarget = repo.pathto(abstarget, cwd)
347 reltarget = repo.pathto(abstarget, cwd)
348 target = repo.wjoin(abstarget)
348 target = repo.wjoin(abstarget)
349 src = repo.wjoin(abssrc)
349 src = repo.wjoin(abssrc)
350 state = repo.dirstate[abstarget]
350 state = repo.dirstate[abstarget]
351
351
352 # check for collisions
352 # check for collisions
353 prevsrc = targets.get(abstarget)
353 prevsrc = targets.get(abstarget)
354 if prevsrc is not None:
354 if prevsrc is not None:
355 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
355 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
356 (reltarget, repo.pathto(abssrc, cwd),
356 (reltarget, repo.pathto(abssrc, cwd),
357 repo.pathto(prevsrc, cwd)))
357 repo.pathto(prevsrc, cwd)))
358 return
358 return
359
359
360 # check for overwrites
360 # check for overwrites
361 exists = os.path.exists(target)
361 exists = os.path.exists(target)
362 if (not after and exists or after and state in 'mn'):
362 if (not after and exists or after and state in 'mn'):
363 if not opts['force']:
363 if not opts['force']:
364 ui.warn(_('%s: not overwriting - file exists\n') %
364 ui.warn(_('%s: not overwriting - file exists\n') %
365 reltarget)
365 reltarget)
366 return
366 return
367
367
368 if after:
368 if after:
369 if not exists:
369 if not exists:
370 return
370 return
371 elif not dryrun:
371 elif not dryrun:
372 try:
372 try:
373 if exists:
373 if exists:
374 os.unlink(target)
374 os.unlink(target)
375 targetdir = os.path.dirname(target) or '.'
375 targetdir = os.path.dirname(target) or '.'
376 if not os.path.isdir(targetdir):
376 if not os.path.isdir(targetdir):
377 os.makedirs(targetdir)
377 os.makedirs(targetdir)
378 util.copyfile(src, target)
378 util.copyfile(src, target)
379 except IOError, inst:
379 except IOError, inst:
380 if inst.errno == errno.ENOENT:
380 if inst.errno == errno.ENOENT:
381 ui.warn(_('%s: deleted in working copy\n') % relsrc)
381 ui.warn(_('%s: deleted in working copy\n') % relsrc)
382 else:
382 else:
383 ui.warn(_('%s: cannot copy - %s\n') %
383 ui.warn(_('%s: cannot copy - %s\n') %
384 (relsrc, inst.strerror))
384 (relsrc, inst.strerror))
385 return True # report a failure
385 return True # report a failure
386
386
387 if ui.verbose or not exact:
387 if ui.verbose or not exact:
388 action = rename and "moving" or "copying"
388 action = rename and "moving" or "copying"
389 ui.status(_('%s %s to %s\n') % (action, relsrc, reltarget))
389 ui.status(_('%s %s to %s\n') % (action, relsrc, reltarget))
390
390
391 targets[abstarget] = abssrc
391 targets[abstarget] = abssrc
392
392
393 # fix up dirstate
393 # fix up dirstate
394 origsrc = repo.dirstate.copied(abssrc) or abssrc
394 origsrc = repo.dirstate.copied(abssrc) or abssrc
395 if abstarget == origsrc: # copying back a copy?
395 if abstarget == origsrc: # copying back a copy?
396 if state not in 'mn' and not dryrun:
396 if state not in 'mn' and not dryrun:
397 repo.dirstate.normallookup(abstarget)
397 repo.dirstate.normallookup(abstarget)
398 else:
398 else:
399 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
399 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
400 if not ui.quiet:
400 if not ui.quiet:
401 ui.warn(_("%s has not been committed yet, so no copy "
401 ui.warn(_("%s has not been committed yet, so no copy "
402 "data will be stored for %s.\n")
402 "data will be stored for %s.\n")
403 % (repo.pathto(origsrc, cwd), reltarget))
403 % (repo.pathto(origsrc, cwd), reltarget))
404 if repo.dirstate[abstarget] in '?r' and not dryrun:
404 if repo.dirstate[abstarget] in '?r' and not dryrun:
405 repo.add([abstarget])
405 repo.add([abstarget])
406 elif not dryrun:
406 elif not dryrun:
407 repo.copy(origsrc, abstarget)
407 repo.copy(origsrc, abstarget)
408
408
409 if rename and not dryrun:
409 if rename and not dryrun:
410 repo.remove([abssrc], not after)
410 repo.remove([abssrc], not after)
411
411
412 # pat: ossep
412 # pat: ossep
413 # dest ossep
413 # dest ossep
414 # srcs: list of (hgsep, hgsep, ossep, bool)
414 # srcs: list of (hgsep, hgsep, ossep, bool)
415 # return: function that takes hgsep and returns ossep
415 # return: function that takes hgsep and returns ossep
416 def targetpathfn(pat, dest, srcs):
416 def targetpathfn(pat, dest, srcs):
417 if os.path.isdir(pat):
417 if os.path.isdir(pat):
418 abspfx = util.canonpath(repo.root, cwd, pat)
418 abspfx = util.canonpath(repo.root, cwd, pat)
419 abspfx = util.localpath(abspfx)
419 abspfx = util.localpath(abspfx)
420 if destdirexists:
420 if destdirexists:
421 striplen = len(os.path.split(abspfx)[0])
421 striplen = len(os.path.split(abspfx)[0])
422 else:
422 else:
423 striplen = len(abspfx)
423 striplen = len(abspfx)
424 if striplen:
424 if striplen:
425 striplen += len(os.sep)
425 striplen += len(os.sep)
426 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
426 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
427 elif destdirexists:
427 elif destdirexists:
428 res = lambda p: os.path.join(dest,
428 res = lambda p: os.path.join(dest,
429 os.path.basename(util.localpath(p)))
429 os.path.basename(util.localpath(p)))
430 else:
430 else:
431 res = lambda p: dest
431 res = lambda p: dest
432 return res
432 return res
433
433
434 # pat: ossep
434 # pat: ossep
435 # dest ossep
435 # dest ossep
436 # srcs: list of (hgsep, hgsep, ossep, bool)
436 # srcs: list of (hgsep, hgsep, ossep, bool)
437 # return: function that takes hgsep and returns ossep
437 # return: function that takes hgsep and returns ossep
438 def targetpathafterfn(pat, dest, srcs):
438 def targetpathafterfn(pat, dest, srcs):
439 if util.patkind(pat, None)[0]:
439 if util.patkind(pat, None)[0]:
440 # a mercurial pattern
440 # a mercurial pattern
441 res = lambda p: os.path.join(dest,
441 res = lambda p: os.path.join(dest,
442 os.path.basename(util.localpath(p)))
442 os.path.basename(util.localpath(p)))
443 else:
443 else:
444 abspfx = util.canonpath(repo.root, cwd, pat)
444 abspfx = util.canonpath(repo.root, cwd, pat)
445 if len(abspfx) < len(srcs[0][0]):
445 if len(abspfx) < len(srcs[0][0]):
446 # A directory. Either the target path contains the last
446 # A directory. Either the target path contains the last
447 # component of the source path or it does not.
447 # component of the source path or it does not.
448 def evalpath(striplen):
448 def evalpath(striplen):
449 score = 0
449 score = 0
450 for s in srcs:
450 for s in srcs:
451 t = os.path.join(dest, util.localpath(s[0])[striplen:])
451 t = os.path.join(dest, util.localpath(s[0])[striplen:])
452 if os.path.exists(t):
452 if os.path.exists(t):
453 score += 1
453 score += 1
454 return score
454 return score
455
455
456 abspfx = util.localpath(abspfx)
456 abspfx = util.localpath(abspfx)
457 striplen = len(abspfx)
457 striplen = len(abspfx)
458 if striplen:
458 if striplen:
459 striplen += len(os.sep)
459 striplen += len(os.sep)
460 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
460 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
461 score = evalpath(striplen)
461 score = evalpath(striplen)
462 striplen1 = len(os.path.split(abspfx)[0])
462 striplen1 = len(os.path.split(abspfx)[0])
463 if striplen1:
463 if striplen1:
464 striplen1 += len(os.sep)
464 striplen1 += len(os.sep)
465 if evalpath(striplen1) > score:
465 if evalpath(striplen1) > score:
466 striplen = striplen1
466 striplen = striplen1
467 res = lambda p: os.path.join(dest,
467 res = lambda p: os.path.join(dest,
468 util.localpath(p)[striplen:])
468 util.localpath(p)[striplen:])
469 else:
469 else:
470 # a file
470 # a file
471 if destdirexists:
471 if destdirexists:
472 res = lambda p: os.path.join(dest,
472 res = lambda p: os.path.join(dest,
473 os.path.basename(util.localpath(p)))
473 os.path.basename(util.localpath(p)))
474 else:
474 else:
475 res = lambda p: dest
475 res = lambda p: dest
476 return res
476 return res
477
477
478
478
479 pats = util.expand_glob(pats)
479 pats = util.expand_glob(pats)
480 if not pats:
480 if not pats:
481 raise util.Abort(_('no source or destination specified'))
481 raise util.Abort(_('no source or destination specified'))
482 if len(pats) == 1:
482 if len(pats) == 1:
483 raise util.Abort(_('no destination specified'))
483 raise util.Abort(_('no destination specified'))
484 dest = pats.pop()
484 dest = pats.pop()
485 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
485 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
486 if not destdirexists:
486 if not destdirexists:
487 if len(pats) > 1 or util.patkind(pats[0], None)[0]:
487 if len(pats) > 1 or util.patkind(pats[0], None)[0]:
488 raise util.Abort(_('with multiple sources, destination must be an '
488 raise util.Abort(_('with multiple sources, destination must be an '
489 'existing directory'))
489 'existing directory'))
490 if util.endswithsep(dest):
490 if util.endswithsep(dest):
491 raise util.Abort(_('destination %s is not a directory') % dest)
491 raise util.Abort(_('destination %s is not a directory') % dest)
492
492
493 tfn = targetpathfn
493 tfn = targetpathfn
494 if after:
494 if after:
495 tfn = targetpathafterfn
495 tfn = targetpathafterfn
496 copylist = []
496 copylist = []
497 for pat in pats:
497 for pat in pats:
498 srcs = walkpat(pat)
498 srcs = walkpat(pat)
499 if not srcs:
499 if not srcs:
500 continue
500 continue
501 copylist.append((tfn(pat, dest, srcs), srcs))
501 copylist.append((tfn(pat, dest, srcs), srcs))
502 if not copylist:
502 if not copylist:
503 raise util.Abort(_('no files to copy'))
503 raise util.Abort(_('no files to copy'))
504
504
505 errors = 0
505 errors = 0
506 for targetpath, srcs in copylist:
506 for targetpath, srcs in copylist:
507 for abssrc, relsrc, exact in srcs:
507 for abssrc, relsrc, exact in srcs:
508 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
508 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
509 errors += 1
509 errors += 1
510
510
511 if errors:
511 if errors:
512 ui.warn(_('(consider using --after)\n'))
512 ui.warn(_('(consider using --after)\n'))
513
513
514 return errors
514 return errors
515
515
516 def service(opts, parentfn=None, initfn=None, runfn=None):
516 def service(opts, parentfn=None, initfn=None, runfn=None):
517 '''Run a command as a service.'''
517 '''Run a command as a service.'''
518
518
519 if opts['daemon'] and not opts['daemon_pipefds']:
519 if opts['daemon'] and not opts['daemon_pipefds']:
520 rfd, wfd = os.pipe()
520 rfd, wfd = os.pipe()
521 args = sys.argv[:]
521 args = sys.argv[:]
522 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
522 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
523 # Don't pass --cwd to the child process, because we've already
523 # Don't pass --cwd to the child process, because we've already
524 # changed directory.
524 # changed directory.
525 for i in xrange(1,len(args)):
525 for i in xrange(1,len(args)):
526 if args[i].startswith('--cwd='):
526 if args[i].startswith('--cwd='):
527 del args[i]
527 del args[i]
528 break
528 break
529 elif args[i].startswith('--cwd'):
529 elif args[i].startswith('--cwd'):
530 del args[i:i+2]
530 del args[i:i+2]
531 break
531 break
532 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
532 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
533 args[0], args)
533 args[0], args)
534 os.close(wfd)
534 os.close(wfd)
535 os.read(rfd, 1)
535 os.read(rfd, 1)
536 if parentfn:
536 if parentfn:
537 return parentfn(pid)
537 return parentfn(pid)
538 else:
538 else:
539 os._exit(0)
539 os._exit(0)
540
540
541 if initfn:
541 if initfn:
542 initfn()
542 initfn()
543
543
544 if opts['pid_file']:
544 if opts['pid_file']:
545 fp = open(opts['pid_file'], 'w')
545 fp = open(opts['pid_file'], 'w')
546 fp.write(str(os.getpid()) + '\n')
546 fp.write(str(os.getpid()) + '\n')
547 fp.close()
547 fp.close()
548
548
549 if opts['daemon_pipefds']:
549 if opts['daemon_pipefds']:
550 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
550 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
551 os.close(rfd)
551 os.close(rfd)
552 try:
552 try:
553 os.setsid()
553 os.setsid()
554 except AttributeError:
554 except AttributeError:
555 pass
555 pass
556 os.write(wfd, 'y')
556 os.write(wfd, 'y')
557 os.close(wfd)
557 os.close(wfd)
558 sys.stdout.flush()
558 sys.stdout.flush()
559 sys.stderr.flush()
559 sys.stderr.flush()
560 fd = os.open(util.nulldev, os.O_RDWR)
560 fd = os.open(util.nulldev, os.O_RDWR)
561 if fd != 0: os.dup2(fd, 0)
561 if fd != 0: os.dup2(fd, 0)
562 if fd != 1: os.dup2(fd, 1)
562 if fd != 1: os.dup2(fd, 1)
563 if fd != 2: os.dup2(fd, 2)
563 if fd != 2: os.dup2(fd, 2)
564 if fd not in (0, 1, 2): os.close(fd)
564 if fd not in (0, 1, 2): os.close(fd)
565
565
566 if runfn:
566 if runfn:
567 return runfn()
567 return runfn()
568
568
569 class changeset_printer(object):
569 class changeset_printer(object):
570 '''show changeset information when templating not requested.'''
570 '''show changeset information when templating not requested.'''
571
571
572 def __init__(self, ui, repo, patch, diffopts, buffered):
572 def __init__(self, ui, repo, patch, diffopts, buffered):
573 self.ui = ui
573 self.ui = ui
574 self.repo = repo
574 self.repo = repo
575 self.buffered = buffered
575 self.buffered = buffered
576 self.patch = patch
576 self.patch = patch
577 self.diffopts = diffopts
577 self.diffopts = diffopts
578 self.header = {}
578 self.header = {}
579 self.hunk = {}
579 self.hunk = {}
580 self.lastheader = None
580 self.lastheader = None
581
581
582 def flush(self, rev):
582 def flush(self, rev):
583 if rev in self.header:
583 if rev in self.header:
584 h = self.header[rev]
584 h = self.header[rev]
585 if h != self.lastheader:
585 if h != self.lastheader:
586 self.lastheader = h
586 self.lastheader = h
587 self.ui.write(h)
587 self.ui.write(h)
588 del self.header[rev]
588 del self.header[rev]
589 if rev in self.hunk:
589 if rev in self.hunk:
590 self.ui.write(self.hunk[rev])
590 self.ui.write(self.hunk[rev])
591 del self.hunk[rev]
591 del self.hunk[rev]
592 return 1
592 return 1
593 return 0
593 return 0
594
594
595 def show(self, ctx, copies=(), **props):
595 def show(self, ctx, copies=(), **props):
596 if self.buffered:
596 if self.buffered:
597 self.ui.pushbuffer()
597 self.ui.pushbuffer()
598 self._show(ctx, copies, props)
598 self._show(ctx, copies, props)
599 self.hunk[ctx.rev()] = self.ui.popbuffer()
599 self.hunk[ctx.rev()] = self.ui.popbuffer()
600 else:
600 else:
601 self._show(ctx, copies, props)
601 self._show(ctx, copies, props)
602
602
603 def _show(self, ctx, copies, props):
603 def _show(self, ctx, copies, props):
604 '''show a single changeset or file revision'''
604 '''show a single changeset or file revision'''
605 changenode = ctx.node()
605 changenode = ctx.node()
606 rev = ctx.rev()
606 rev = ctx.rev()
607
607
608 if self.ui.quiet:
608 if self.ui.quiet:
609 self.ui.write("%d:%s\n" % (rev, short(changenode)))
609 self.ui.write("%d:%s\n" % (rev, short(changenode)))
610 return
610 return
611
611
612 log = self.repo.changelog
612 log = self.repo.changelog
613 changes = log.read(changenode)
613 changes = log.read(changenode)
614 date = util.datestr(changes[2])
614 date = util.datestr(changes[2])
615 extra = changes[5]
615 extra = changes[5]
616 branch = extra.get("branch")
616 branch = extra.get("branch")
617
617
618 hexfunc = self.ui.debugflag and hex or short
618 hexfunc = self.ui.debugflag and hex or short
619
619
620 parents = [(p, hexfunc(log.node(p)))
620 parents = [(p, hexfunc(log.node(p)))
621 for p in self._meaningful_parentrevs(log, rev)]
621 for p in self._meaningful_parentrevs(log, rev)]
622
622
623 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
623 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
624
624
625 # don't show the default branch name
625 # don't show the default branch name
626 if branch != 'default':
626 if branch != 'default':
627 branch = util.tolocal(branch)
627 branch = util.tolocal(branch)
628 self.ui.write(_("branch: %s\n") % branch)
628 self.ui.write(_("branch: %s\n") % branch)
629 for tag in self.repo.nodetags(changenode):
629 for tag in self.repo.nodetags(changenode):
630 self.ui.write(_("tag: %s\n") % tag)
630 self.ui.write(_("tag: %s\n") % tag)
631 for parent in parents:
631 for parent in parents:
632 self.ui.write(_("parent: %d:%s\n") % parent)
632 self.ui.write(_("parent: %d:%s\n") % parent)
633
633
634 if self.ui.debugflag:
634 if self.ui.debugflag:
635 self.ui.write(_("manifest: %d:%s\n") %
635 self.ui.write(_("manifest: %d:%s\n") %
636 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
636 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
637 self.ui.write(_("user: %s\n") % changes[1])
637 self.ui.write(_("user: %s\n") % changes[1])
638 self.ui.write(_("date: %s\n") % date)
638 self.ui.write(_("date: %s\n") % date)
639
639
640 if self.ui.debugflag:
640 if self.ui.debugflag:
641 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
641 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
642 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
642 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
643 files):
643 files):
644 if value:
644 if value:
645 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
645 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
646 elif changes[3] and self.ui.verbose:
646 elif changes[3] and self.ui.verbose:
647 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
647 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
648 if copies and self.ui.verbose:
648 if copies and self.ui.verbose:
649 copies = ['%s (%s)' % c for c in copies]
649 copies = ['%s (%s)' % c for c in copies]
650 self.ui.write(_("copies: %s\n") % ' '.join(copies))
650 self.ui.write(_("copies: %s\n") % ' '.join(copies))
651
651
652 if extra and self.ui.debugflag:
652 if extra and self.ui.debugflag:
653 for key, value in util.sort(extra.items()):
653 for key, value in util.sort(extra.items()):
654 self.ui.write(_("extra: %s=%s\n")
654 self.ui.write(_("extra: %s=%s\n")
655 % (key, value.encode('string_escape')))
655 % (key, value.encode('string_escape')))
656
656
657 description = changes[4].strip()
657 description = changes[4].strip()
658 if description:
658 if description:
659 if self.ui.verbose:
659 if self.ui.verbose:
660 self.ui.write(_("description:\n"))
660 self.ui.write(_("description:\n"))
661 self.ui.write(description)
661 self.ui.write(description)
662 self.ui.write("\n\n")
662 self.ui.write("\n\n")
663 else:
663 else:
664 self.ui.write(_("summary: %s\n") %
664 self.ui.write(_("summary: %s\n") %
665 description.splitlines()[0])
665 description.splitlines()[0])
666 self.ui.write("\n")
666 self.ui.write("\n")
667
667
668 self.showpatch(changenode)
668 self.showpatch(changenode)
669
669
670 def showpatch(self, node):
670 def showpatch(self, node):
671 if self.patch:
671 if self.patch:
672 prev = self.repo.changelog.parents(node)[0]
672 prev = self.repo.changelog.parents(node)[0]
673 chunks = patch.diff(self.repo, prev, node, match=self.patch,
673 chunks = patch.diff(self.repo, prev, node, match=self.patch,
674 opts=patch.diffopts(self.ui, self.diffopts))
674 opts=patch.diffopts(self.ui, self.diffopts))
675 for chunk in chunks:
675 for chunk in chunks:
676 self.ui.write(chunk)
676 self.ui.write(chunk)
677 self.ui.write("\n")
677 self.ui.write("\n")
678
678
679 def _meaningful_parentrevs(self, log, rev):
679 def _meaningful_parentrevs(self, log, rev):
680 """Return list of meaningful (or all if debug) parentrevs for rev.
680 """Return list of meaningful (or all if debug) parentrevs for rev.
681
681
682 For merges (two non-nullrev revisions) both parents are meaningful.
682 For merges (two non-nullrev revisions) both parents are meaningful.
683 Otherwise the first parent revision is considered meaningful if it
683 Otherwise the first parent revision is considered meaningful if it
684 is not the preceding revision.
684 is not the preceding revision.
685 """
685 """
686 parents = log.parentrevs(rev)
686 parents = log.parentrevs(rev)
687 if not self.ui.debugflag and parents[1] == nullrev:
687 if not self.ui.debugflag and parents[1] == nullrev:
688 if parents[0] >= rev - 1:
688 if parents[0] >= rev - 1:
689 parents = []
689 parents = []
690 else:
690 else:
691 parents = [parents[0]]
691 parents = [parents[0]]
692 return parents
692 return parents
693
693
694
694
695 class changeset_templater(changeset_printer):
695 class changeset_templater(changeset_printer):
696 '''format changeset information.'''
696 '''format changeset information.'''
697
697
698 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
698 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
699 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
699 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
700 filters = templatefilters.filters.copy()
700 filters = templatefilters.filters.copy()
701 filters['formatnode'] = (ui.debugflag and (lambda x: x)
701 filters['formatnode'] = (ui.debugflag and (lambda x: x)
702 or (lambda x: x[:12]))
702 or (lambda x: x[:12]))
703 self.t = templater.templater(mapfile, filters,
703 self.t = templater.templater(mapfile, filters,
704 cache={
704 cache={
705 'parent': '{rev}:{node|formatnode} ',
705 'parent': '{rev}:{node|formatnode} ',
706 'manifest': '{rev}:{node|formatnode}',
706 'manifest': '{rev}:{node|formatnode}',
707 'filecopy': '{name} ({source})'})
707 'filecopy': '{name} ({source})'})
708
708
709 def use_template(self, t):
709 def use_template(self, t):
710 '''set template string to use'''
710 '''set template string to use'''
711 self.t.cache['changeset'] = t
711 self.t.cache['changeset'] = t
712
712
713 def _meaningful_parentrevs(self, ctx):
713 def _meaningful_parentrevs(self, ctx):
714 """Return list of meaningful (or all if debug) parentrevs for rev.
714 """Return list of meaningful (or all if debug) parentrevs for rev.
715 """
715 """
716 parents = ctx.parents()
716 parents = ctx.parents()
717 if len(parents) > 1:
717 if len(parents) > 1:
718 return parents
718 return parents
719 if self.ui.debugflag:
719 if self.ui.debugflag:
720 return [parents[0], self.repo['null']]
720 return [parents[0], self.repo['null']]
721 if parents[0].rev() >= ctx.rev() - 1:
721 if parents[0].rev() >= ctx.rev() - 1:
722 return []
722 return []
723 return parents
723 return parents
724
724
725 def _show(self, ctx, copies, props):
725 def _show(self, ctx, copies, props):
726 '''show a single changeset or file revision'''
726 '''show a single changeset or file revision'''
727
727
728 def showlist(name, values, plural=None, **args):
728 def showlist(name, values, plural=None, **args):
729 '''expand set of values.
729 '''expand set of values.
730 name is name of key in template map.
730 name is name of key in template map.
731 values is list of strings or dicts.
731 values is list of strings or dicts.
732 plural is plural of name, if not simply name + 's'.
732 plural is plural of name, if not simply name + 's'.
733
733
734 expansion works like this, given name 'foo'.
734 expansion works like this, given name 'foo'.
735
735
736 if values is empty, expand 'no_foos'.
736 if values is empty, expand 'no_foos'.
737
737
738 if 'foo' not in template map, return values as a string,
738 if 'foo' not in template map, return values as a string,
739 joined by space.
739 joined by space.
740
740
741 expand 'start_foos'.
741 expand 'start_foos'.
742
742
743 for each value, expand 'foo'. if 'last_foo' in template
743 for each value, expand 'foo'. if 'last_foo' in template
744 map, expand it instead of 'foo' for last key.
744 map, expand it instead of 'foo' for last key.
745
745
746 expand 'end_foos'.
746 expand 'end_foos'.
747 '''
747 '''
748 if plural: names = plural
748 if plural: names = plural
749 else: names = name + 's'
749 else: names = name + 's'
750 if not values:
750 if not values:
751 noname = 'no_' + names
751 noname = 'no_' + names
752 if noname in self.t:
752 if noname in self.t:
753 yield self.t(noname, **args)
753 yield self.t(noname, **args)
754 return
754 return
755 if name not in self.t:
755 if name not in self.t:
756 if isinstance(values[0], str):
756 if isinstance(values[0], str):
757 yield ' '.join(values)
757 yield ' '.join(values)
758 else:
758 else:
759 for v in values:
759 for v in values:
760 yield dict(v, **args)
760 yield dict(v, **args)
761 return
761 return
762 startname = 'start_' + names
762 startname = 'start_' + names
763 if startname in self.t:
763 if startname in self.t:
764 yield self.t(startname, **args)
764 yield self.t(startname, **args)
765 vargs = args.copy()
765 vargs = args.copy()
766 def one(v, tag=name):
766 def one(v, tag=name):
767 try:
767 try:
768 vargs.update(v)
768 vargs.update(v)
769 except (AttributeError, ValueError):
769 except (AttributeError, ValueError):
770 try:
770 try:
771 for a, b in v:
771 for a, b in v:
772 vargs[a] = b
772 vargs[a] = b
773 except ValueError:
773 except ValueError:
774 vargs[name] = v
774 vargs[name] = v
775 return self.t(tag, **vargs)
775 return self.t(tag, **vargs)
776 lastname = 'last_' + name
776 lastname = 'last_' + name
777 if lastname in self.t:
777 if lastname in self.t:
778 last = values.pop()
778 last = values.pop()
779 else:
779 else:
780 last = None
780 last = None
781 for v in values:
781 for v in values:
782 yield one(v)
782 yield one(v)
783 if last is not None:
783 if last is not None:
784 yield one(last, tag=lastname)
784 yield one(last, tag=lastname)
785 endname = 'end_' + names
785 endname = 'end_' + names
786 if endname in self.t:
786 if endname in self.t:
787 yield self.t(endname, **args)
787 yield self.t(endname, **args)
788
788
789 def showbranches(**args):
789 def showbranches(**args):
790 branch = ctx.branch()
790 branch = ctx.branch()
791 if branch != 'default':
791 if branch != 'default':
792 branch = util.tolocal(branch)
792 branch = util.tolocal(branch)
793 return showlist('branch', [branch], plural='branches', **args)
793 return showlist('branch', [branch], plural='branches', **args)
794
794
795 def showparents(**args):
795 def showparents(**args):
796 parents = [[('rev', p.rev()), ('node', p.hex())]
796 parents = [[('rev', p.rev()), ('node', p.hex())]
797 for p in self._meaningful_parentrevs(ctx)]
797 for p in self._meaningful_parentrevs(ctx)]
798 return showlist('parent', parents, **args)
798 return showlist('parent', parents, **args)
799
799
800 def showtags(**args):
800 def showtags(**args):
801 return showlist('tag', ctx.tags(), **args)
801 return showlist('tag', ctx.tags(), **args)
802
802
803 def showextras(**args):
803 def showextras(**args):
804 for key, value in util.sort(ctx.extra().items()):
804 for key, value in util.sort(ctx.extra().items()):
805 args = args.copy()
805 args = args.copy()
806 args.update(dict(key=key, value=value))
806 args.update(dict(key=key, value=value))
807 yield self.t('extra', **args)
807 yield self.t('extra', **args)
808
808
809 def showcopies(**args):
809 def showcopies(**args):
810 c = [{'name': x[0], 'source': x[1]} for x in copies]
810 c = [{'name': x[0], 'source': x[1]} for x in copies]
811 return showlist('file_copy', c, plural='file_copies', **args)
811 return showlist('file_copy', c, plural='file_copies', **args)
812
812
813 files = []
813 files = []
814 def getfiles():
814 def getfiles():
815 if not files:
815 if not files:
816 files[:] = self.repo.status(ctx.parents()[0].node(),
816 files[:] = self.repo.status(ctx.parents()[0].node(),
817 ctx.node())[:3]
817 ctx.node())[:3]
818 return files
818 return files
819 def showfiles(**args):
819 def showfiles(**args):
820 return showlist('file', ctx.files(), **args)
820 return showlist('file', ctx.files(), **args)
821 def showmods(**args):
821 def showmods(**args):
822 return showlist('file_mod', getfiles()[0], **args)
822 return showlist('file_mod', getfiles()[0], **args)
823 def showadds(**args):
823 def showadds(**args):
824 return showlist('file_add', getfiles()[1], **args)
824 return showlist('file_add', getfiles()[1], **args)
825 def showdels(**args):
825 def showdels(**args):
826 return showlist('file_del', getfiles()[2], **args)
826 return showlist('file_del', getfiles()[2], **args)
827 def showmanifest(**args):
827 def showmanifest(**args):
828 args = args.copy()
828 args = args.copy()
829 args.update(dict(rev=self.repo.manifest.rev(ctx.changeset()[0]),
829 args.update(dict(rev=self.repo.manifest.rev(ctx.changeset()[0]),
830 node=hex(ctx.changeset()[0])))
830 node=hex(ctx.changeset()[0])))
831 return self.t('manifest', **args)
831 return self.t('manifest', **args)
832
832
833 def showdiffstat(**args):
834 diff = patch.diff(self.repo, ctx.parents()[0].node(), ctx.node())
835 files, adds, removes = 0, 0, 0
836 for i in patch.diffstatdata(util.iterlines(diff)):
837 files += 1
838 adds += i[1]
839 removes += i[2]
840 return '%s: +%s/-%s' % (files, adds, removes)
841
833 defprops = {
842 defprops = {
834 'author': ctx.user(),
843 'author': ctx.user(),
835 'branches': showbranches,
844 'branches': showbranches,
836 'date': ctx.date(),
845 'date': ctx.date(),
837 'desc': ctx.description().strip(),
846 'desc': ctx.description().strip(),
838 'file_adds': showadds,
847 'file_adds': showadds,
839 'file_dels': showdels,
848 'file_dels': showdels,
840 'file_mods': showmods,
849 'file_mods': showmods,
841 'files': showfiles,
850 'files': showfiles,
842 'file_copies': showcopies,
851 'file_copies': showcopies,
843 'manifest': showmanifest,
852 'manifest': showmanifest,
844 'node': ctx.hex(),
853 'node': ctx.hex(),
845 'parents': showparents,
854 'parents': showparents,
846 'rev': ctx.rev(),
855 'rev': ctx.rev(),
847 'tags': showtags,
856 'tags': showtags,
848 'extras': showextras,
857 'extras': showextras,
858 'diffstat': showdiffstat,
849 }
859 }
850 props = props.copy()
860 props = props.copy()
851 props.update(defprops)
861 props.update(defprops)
852
862
853 try:
863 try:
854 if self.ui.debugflag and 'header_debug' in self.t:
864 if self.ui.debugflag and 'header_debug' in self.t:
855 key = 'header_debug'
865 key = 'header_debug'
856 elif self.ui.quiet and 'header_quiet' in self.t:
866 elif self.ui.quiet and 'header_quiet' in self.t:
857 key = 'header_quiet'
867 key = 'header_quiet'
858 elif self.ui.verbose and 'header_verbose' in self.t:
868 elif self.ui.verbose and 'header_verbose' in self.t:
859 key = 'header_verbose'
869 key = 'header_verbose'
860 elif 'header' in self.t:
870 elif 'header' in self.t:
861 key = 'header'
871 key = 'header'
862 else:
872 else:
863 key = ''
873 key = ''
864 if key:
874 if key:
865 h = templater.stringify(self.t(key, **props))
875 h = templater.stringify(self.t(key, **props))
866 if self.buffered:
876 if self.buffered:
867 self.header[ctx.rev()] = h
877 self.header[ctx.rev()] = h
868 else:
878 else:
869 self.ui.write(h)
879 self.ui.write(h)
870 if self.ui.debugflag and 'changeset_debug' in self.t:
880 if self.ui.debugflag and 'changeset_debug' in self.t:
871 key = 'changeset_debug'
881 key = 'changeset_debug'
872 elif self.ui.quiet and 'changeset_quiet' in self.t:
882 elif self.ui.quiet and 'changeset_quiet' in self.t:
873 key = 'changeset_quiet'
883 key = 'changeset_quiet'
874 elif self.ui.verbose and 'changeset_verbose' in self.t:
884 elif self.ui.verbose and 'changeset_verbose' in self.t:
875 key = 'changeset_verbose'
885 key = 'changeset_verbose'
876 else:
886 else:
877 key = 'changeset'
887 key = 'changeset'
878 self.ui.write(templater.stringify(self.t(key, **props)))
888 self.ui.write(templater.stringify(self.t(key, **props)))
879 self.showpatch(ctx.node())
889 self.showpatch(ctx.node())
880 except KeyError, inst:
890 except KeyError, inst:
881 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
891 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
882 inst.args[0]))
892 inst.args[0]))
883 except SyntaxError, inst:
893 except SyntaxError, inst:
884 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
894 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
885
895
886 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
896 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
887 """show one changeset using template or regular display.
897 """show one changeset using template or regular display.
888
898
889 Display format will be the first non-empty hit of:
899 Display format will be the first non-empty hit of:
890 1. option 'template'
900 1. option 'template'
891 2. option 'style'
901 2. option 'style'
892 3. [ui] setting 'logtemplate'
902 3. [ui] setting 'logtemplate'
893 4. [ui] setting 'style'
903 4. [ui] setting 'style'
894 If all of these values are either the unset or the empty string,
904 If all of these values are either the unset or the empty string,
895 regular display via changeset_printer() is done.
905 regular display via changeset_printer() is done.
896 """
906 """
897 # options
907 # options
898 patch = False
908 patch = False
899 if opts.get('patch'):
909 if opts.get('patch'):
900 patch = matchfn or matchall(repo)
910 patch = matchfn or matchall(repo)
901
911
902 tmpl = opts.get('template')
912 tmpl = opts.get('template')
903 mapfile = None
913 mapfile = None
904 if tmpl:
914 if tmpl:
905 tmpl = templater.parsestring(tmpl, quoted=False)
915 tmpl = templater.parsestring(tmpl, quoted=False)
906 else:
916 else:
907 mapfile = opts.get('style')
917 mapfile = opts.get('style')
908 # ui settings
918 # ui settings
909 if not mapfile:
919 if not mapfile:
910 tmpl = ui.config('ui', 'logtemplate')
920 tmpl = ui.config('ui', 'logtemplate')
911 if tmpl:
921 if tmpl:
912 tmpl = templater.parsestring(tmpl)
922 tmpl = templater.parsestring(tmpl)
913 else:
923 else:
914 mapfile = ui.config('ui', 'style')
924 mapfile = ui.config('ui', 'style')
915
925
916 if tmpl or mapfile:
926 if tmpl or mapfile:
917 if mapfile:
927 if mapfile:
918 if not os.path.split(mapfile)[0]:
928 if not os.path.split(mapfile)[0]:
919 mapname = (templater.templatepath('map-cmdline.' + mapfile)
929 mapname = (templater.templatepath('map-cmdline.' + mapfile)
920 or templater.templatepath(mapfile))
930 or templater.templatepath(mapfile))
921 if mapname: mapfile = mapname
931 if mapname: mapfile = mapname
922 try:
932 try:
923 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
933 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
924 except SyntaxError, inst:
934 except SyntaxError, inst:
925 raise util.Abort(inst.args[0])
935 raise util.Abort(inst.args[0])
926 if tmpl: t.use_template(tmpl)
936 if tmpl: t.use_template(tmpl)
927 return t
937 return t
928 return changeset_printer(ui, repo, patch, opts, buffered)
938 return changeset_printer(ui, repo, patch, opts, buffered)
929
939
930 def finddate(ui, repo, date):
940 def finddate(ui, repo, date):
931 """Find the tipmost changeset that matches the given date spec"""
941 """Find the tipmost changeset that matches the given date spec"""
932 df = util.matchdate(date)
942 df = util.matchdate(date)
933 get = util.cachefunc(lambda r: repo[r].changeset())
943 get = util.cachefunc(lambda r: repo[r].changeset())
934 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
944 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
935 results = {}
945 results = {}
936 for st, rev, fns in changeiter:
946 for st, rev, fns in changeiter:
937 if st == 'add':
947 if st == 'add':
938 d = get(rev)[2]
948 d = get(rev)[2]
939 if df(d[0]):
949 if df(d[0]):
940 results[rev] = d
950 results[rev] = d
941 elif st == 'iter':
951 elif st == 'iter':
942 if rev in results:
952 if rev in results:
943 ui.status(_("Found revision %s from %s\n") %
953 ui.status(_("Found revision %s from %s\n") %
944 (rev, util.datestr(results[rev])))
954 (rev, util.datestr(results[rev])))
945 return str(rev)
955 return str(rev)
946
956
947 raise util.Abort(_("revision matching date not found"))
957 raise util.Abort(_("revision matching date not found"))
948
958
949 def walkchangerevs(ui, repo, pats, change, opts):
959 def walkchangerevs(ui, repo, pats, change, opts):
950 '''Iterate over files and the revs in which they changed.
960 '''Iterate over files and the revs in which they changed.
951
961
952 Callers most commonly need to iterate backwards over the history
962 Callers most commonly need to iterate backwards over the history
953 in which they are interested. Doing so has awful (quadratic-looking)
963 in which they are interested. Doing so has awful (quadratic-looking)
954 performance, so we use iterators in a "windowed" way.
964 performance, so we use iterators in a "windowed" way.
955
965
956 We walk a window of revisions in the desired order. Within the
966 We walk a window of revisions in the desired order. Within the
957 window, we first walk forwards to gather data, then in the desired
967 window, we first walk forwards to gather data, then in the desired
958 order (usually backwards) to display it.
968 order (usually backwards) to display it.
959
969
960 This function returns an (iterator, matchfn) tuple. The iterator
970 This function returns an (iterator, matchfn) tuple. The iterator
961 yields 3-tuples. They will be of one of the following forms:
971 yields 3-tuples. They will be of one of the following forms:
962
972
963 "window", incrementing, lastrev: stepping through a window,
973 "window", incrementing, lastrev: stepping through a window,
964 positive if walking forwards through revs, last rev in the
974 positive if walking forwards through revs, last rev in the
965 sequence iterated over - use to reset state for the current window
975 sequence iterated over - use to reset state for the current window
966
976
967 "add", rev, fns: out-of-order traversal of the given file names
977 "add", rev, fns: out-of-order traversal of the given file names
968 fns, which changed during revision rev - use to gather data for
978 fns, which changed during revision rev - use to gather data for
969 possible display
979 possible display
970
980
971 "iter", rev, None: in-order traversal of the revs earlier iterated
981 "iter", rev, None: in-order traversal of the revs earlier iterated
972 over with "add" - use to display data'''
982 over with "add" - use to display data'''
973
983
974 def increasing_windows(start, end, windowsize=8, sizelimit=512):
984 def increasing_windows(start, end, windowsize=8, sizelimit=512):
975 if start < end:
985 if start < end:
976 while start < end:
986 while start < end:
977 yield start, min(windowsize, end-start)
987 yield start, min(windowsize, end-start)
978 start += windowsize
988 start += windowsize
979 if windowsize < sizelimit:
989 if windowsize < sizelimit:
980 windowsize *= 2
990 windowsize *= 2
981 else:
991 else:
982 while start > end:
992 while start > end:
983 yield start, min(windowsize, start-end-1)
993 yield start, min(windowsize, start-end-1)
984 start -= windowsize
994 start -= windowsize
985 if windowsize < sizelimit:
995 if windowsize < sizelimit:
986 windowsize *= 2
996 windowsize *= 2
987
997
988 m = match(repo, pats, opts)
998 m = match(repo, pats, opts)
989 follow = opts.get('follow') or opts.get('follow_first')
999 follow = opts.get('follow') or opts.get('follow_first')
990
1000
991 if not len(repo):
1001 if not len(repo):
992 return [], m
1002 return [], m
993
1003
994 if follow:
1004 if follow:
995 defrange = '%s:0' % repo['.'].rev()
1005 defrange = '%s:0' % repo['.'].rev()
996 else:
1006 else:
997 defrange = '-1:0'
1007 defrange = '-1:0'
998 revs = revrange(repo, opts['rev'] or [defrange])
1008 revs = revrange(repo, opts['rev'] or [defrange])
999 wanted = {}
1009 wanted = {}
1000 slowpath = m.anypats() or (m.files() and opts.get('removed'))
1010 slowpath = m.anypats() or (m.files() and opts.get('removed'))
1001 fncache = {}
1011 fncache = {}
1002
1012
1003 if not slowpath and not m.files():
1013 if not slowpath and not m.files():
1004 # No files, no patterns. Display all revs.
1014 # No files, no patterns. Display all revs.
1005 wanted = dict.fromkeys(revs)
1015 wanted = dict.fromkeys(revs)
1006 copies = []
1016 copies = []
1007 if not slowpath:
1017 if not slowpath:
1008 # Only files, no patterns. Check the history of each file.
1018 # Only files, no patterns. Check the history of each file.
1009 def filerevgen(filelog, node):
1019 def filerevgen(filelog, node):
1010 cl_count = len(repo)
1020 cl_count = len(repo)
1011 if node is None:
1021 if node is None:
1012 last = len(filelog) - 1
1022 last = len(filelog) - 1
1013 else:
1023 else:
1014 last = filelog.rev(node)
1024 last = filelog.rev(node)
1015 for i, window in increasing_windows(last, nullrev):
1025 for i, window in increasing_windows(last, nullrev):
1016 revs = []
1026 revs = []
1017 for j in xrange(i - window, i + 1):
1027 for j in xrange(i - window, i + 1):
1018 n = filelog.node(j)
1028 n = filelog.node(j)
1019 revs.append((filelog.linkrev(j),
1029 revs.append((filelog.linkrev(j),
1020 follow and filelog.renamed(n)))
1030 follow and filelog.renamed(n)))
1021 revs.reverse()
1031 revs.reverse()
1022 for rev in revs:
1032 for rev in revs:
1023 # only yield rev for which we have the changelog, it can
1033 # only yield rev for which we have the changelog, it can
1024 # happen while doing "hg log" during a pull or commit
1034 # happen while doing "hg log" during a pull or commit
1025 if rev[0] < cl_count:
1035 if rev[0] < cl_count:
1026 yield rev
1036 yield rev
1027 def iterfiles():
1037 def iterfiles():
1028 for filename in m.files():
1038 for filename in m.files():
1029 yield filename, None
1039 yield filename, None
1030 for filename_node in copies:
1040 for filename_node in copies:
1031 yield filename_node
1041 yield filename_node
1032 minrev, maxrev = min(revs), max(revs)
1042 minrev, maxrev = min(revs), max(revs)
1033 for file_, node in iterfiles():
1043 for file_, node in iterfiles():
1034 filelog = repo.file(file_)
1044 filelog = repo.file(file_)
1035 if not len(filelog):
1045 if not len(filelog):
1036 if node is None:
1046 if node is None:
1037 # A zero count may be a directory or deleted file, so
1047 # A zero count may be a directory or deleted file, so
1038 # try to find matching entries on the slow path.
1048 # try to find matching entries on the slow path.
1039 if follow:
1049 if follow:
1040 raise util.Abort(_('cannot follow nonexistent file: "%s"') % file_)
1050 raise util.Abort(_('cannot follow nonexistent file: "%s"') % file_)
1041 slowpath = True
1051 slowpath = True
1042 break
1052 break
1043 else:
1053 else:
1044 ui.warn(_('%s:%s copy source revision cannot be found!\n')
1054 ui.warn(_('%s:%s copy source revision cannot be found!\n')
1045 % (file_, short(node)))
1055 % (file_, short(node)))
1046 continue
1056 continue
1047 for rev, copied in filerevgen(filelog, node):
1057 for rev, copied in filerevgen(filelog, node):
1048 if rev <= maxrev:
1058 if rev <= maxrev:
1049 if rev < minrev:
1059 if rev < minrev:
1050 break
1060 break
1051 fncache.setdefault(rev, [])
1061 fncache.setdefault(rev, [])
1052 fncache[rev].append(file_)
1062 fncache[rev].append(file_)
1053 wanted[rev] = 1
1063 wanted[rev] = 1
1054 if follow and copied:
1064 if follow and copied:
1055 copies.append(copied)
1065 copies.append(copied)
1056 if slowpath:
1066 if slowpath:
1057 if follow:
1067 if follow:
1058 raise util.Abort(_('can only follow copies/renames for explicit '
1068 raise util.Abort(_('can only follow copies/renames for explicit '
1059 'file names'))
1069 'file names'))
1060
1070
1061 # The slow path checks files modified in every changeset.
1071 # The slow path checks files modified in every changeset.
1062 def changerevgen():
1072 def changerevgen():
1063 for i, window in increasing_windows(len(repo) - 1, nullrev):
1073 for i, window in increasing_windows(len(repo) - 1, nullrev):
1064 for j in xrange(i - window, i + 1):
1074 for j in xrange(i - window, i + 1):
1065 yield j, change(j)[3]
1075 yield j, change(j)[3]
1066
1076
1067 for rev, changefiles in changerevgen():
1077 for rev, changefiles in changerevgen():
1068 matches = filter(m, changefiles)
1078 matches = filter(m, changefiles)
1069 if matches:
1079 if matches:
1070 fncache[rev] = matches
1080 fncache[rev] = matches
1071 wanted[rev] = 1
1081 wanted[rev] = 1
1072
1082
1073 class followfilter:
1083 class followfilter:
1074 def __init__(self, onlyfirst=False):
1084 def __init__(self, onlyfirst=False):
1075 self.startrev = nullrev
1085 self.startrev = nullrev
1076 self.roots = []
1086 self.roots = []
1077 self.onlyfirst = onlyfirst
1087 self.onlyfirst = onlyfirst
1078
1088
1079 def match(self, rev):
1089 def match(self, rev):
1080 def realparents(rev):
1090 def realparents(rev):
1081 if self.onlyfirst:
1091 if self.onlyfirst:
1082 return repo.changelog.parentrevs(rev)[0:1]
1092 return repo.changelog.parentrevs(rev)[0:1]
1083 else:
1093 else:
1084 return filter(lambda x: x != nullrev,
1094 return filter(lambda x: x != nullrev,
1085 repo.changelog.parentrevs(rev))
1095 repo.changelog.parentrevs(rev))
1086
1096
1087 if self.startrev == nullrev:
1097 if self.startrev == nullrev:
1088 self.startrev = rev
1098 self.startrev = rev
1089 return True
1099 return True
1090
1100
1091 if rev > self.startrev:
1101 if rev > self.startrev:
1092 # forward: all descendants
1102 # forward: all descendants
1093 if not self.roots:
1103 if not self.roots:
1094 self.roots.append(self.startrev)
1104 self.roots.append(self.startrev)
1095 for parent in realparents(rev):
1105 for parent in realparents(rev):
1096 if parent in self.roots:
1106 if parent in self.roots:
1097 self.roots.append(rev)
1107 self.roots.append(rev)
1098 return True
1108 return True
1099 else:
1109 else:
1100 # backwards: all parents
1110 # backwards: all parents
1101 if not self.roots:
1111 if not self.roots:
1102 self.roots.extend(realparents(self.startrev))
1112 self.roots.extend(realparents(self.startrev))
1103 if rev in self.roots:
1113 if rev in self.roots:
1104 self.roots.remove(rev)
1114 self.roots.remove(rev)
1105 self.roots.extend(realparents(rev))
1115 self.roots.extend(realparents(rev))
1106 return True
1116 return True
1107
1117
1108 return False
1118 return False
1109
1119
1110 # it might be worthwhile to do this in the iterator if the rev range
1120 # it might be worthwhile to do this in the iterator if the rev range
1111 # is descending and the prune args are all within that range
1121 # is descending and the prune args are all within that range
1112 for rev in opts.get('prune', ()):
1122 for rev in opts.get('prune', ()):
1113 rev = repo.changelog.rev(repo.lookup(rev))
1123 rev = repo.changelog.rev(repo.lookup(rev))
1114 ff = followfilter()
1124 ff = followfilter()
1115 stop = min(revs[0], revs[-1])
1125 stop = min(revs[0], revs[-1])
1116 for x in xrange(rev, stop-1, -1):
1126 for x in xrange(rev, stop-1, -1):
1117 if ff.match(x) and x in wanted:
1127 if ff.match(x) and x in wanted:
1118 del wanted[x]
1128 del wanted[x]
1119
1129
1120 def iterate():
1130 def iterate():
1121 if follow and not m.files():
1131 if follow and not m.files():
1122 ff = followfilter(onlyfirst=opts.get('follow_first'))
1132 ff = followfilter(onlyfirst=opts.get('follow_first'))
1123 def want(rev):
1133 def want(rev):
1124 if ff.match(rev) and rev in wanted:
1134 if ff.match(rev) and rev in wanted:
1125 return True
1135 return True
1126 return False
1136 return False
1127 else:
1137 else:
1128 def want(rev):
1138 def want(rev):
1129 return rev in wanted
1139 return rev in wanted
1130
1140
1131 for i, window in increasing_windows(0, len(revs)):
1141 for i, window in increasing_windows(0, len(revs)):
1132 yield 'window', revs[0] < revs[-1], revs[-1]
1142 yield 'window', revs[0] < revs[-1], revs[-1]
1133 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1143 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1134 for rev in util.sort(list(nrevs)):
1144 for rev in util.sort(list(nrevs)):
1135 fns = fncache.get(rev)
1145 fns = fncache.get(rev)
1136 if not fns:
1146 if not fns:
1137 def fns_generator():
1147 def fns_generator():
1138 for f in change(rev)[3]:
1148 for f in change(rev)[3]:
1139 if m(f):
1149 if m(f):
1140 yield f
1150 yield f
1141 fns = fns_generator()
1151 fns = fns_generator()
1142 yield 'add', rev, fns
1152 yield 'add', rev, fns
1143 for rev in nrevs:
1153 for rev in nrevs:
1144 yield 'iter', rev, None
1154 yield 'iter', rev, None
1145 return iterate(), m
1155 return iterate(), m
1146
1156
1147 def commit(ui, repo, commitfunc, pats, opts):
1157 def commit(ui, repo, commitfunc, pats, opts):
1148 '''commit the specified files or all outstanding changes'''
1158 '''commit the specified files or all outstanding changes'''
1149 date = opts.get('date')
1159 date = opts.get('date')
1150 if date:
1160 if date:
1151 opts['date'] = util.parsedate(date)
1161 opts['date'] = util.parsedate(date)
1152 message = logmessage(opts)
1162 message = logmessage(opts)
1153
1163
1154 # extract addremove carefully -- this function can be called from a command
1164 # extract addremove carefully -- this function can be called from a command
1155 # that doesn't support addremove
1165 # that doesn't support addremove
1156 if opts.get('addremove'):
1166 if opts.get('addremove'):
1157 addremove(repo, pats, opts)
1167 addremove(repo, pats, opts)
1158
1168
1159 m = match(repo, pats, opts)
1169 m = match(repo, pats, opts)
1160 if pats:
1170 if pats:
1161 modified, added, removed = repo.status(match=m)[:3]
1171 modified, added, removed = repo.status(match=m)[:3]
1162 files = util.sort(modified + added + removed)
1172 files = util.sort(modified + added + removed)
1163
1173
1164 def is_dir(f):
1174 def is_dir(f):
1165 name = f + '/'
1175 name = f + '/'
1166 i = bisect.bisect(files, name)
1176 i = bisect.bisect(files, name)
1167 return i < len(files) and files[i].startswith(name)
1177 return i < len(files) and files[i].startswith(name)
1168
1178
1169 for f in m.files():
1179 for f in m.files():
1170 if f == '.':
1180 if f == '.':
1171 continue
1181 continue
1172 if f not in files:
1182 if f not in files:
1173 rf = repo.wjoin(f)
1183 rf = repo.wjoin(f)
1174 rel = repo.pathto(f)
1184 rel = repo.pathto(f)
1175 try:
1185 try:
1176 mode = os.lstat(rf)[stat.ST_MODE]
1186 mode = os.lstat(rf)[stat.ST_MODE]
1177 except OSError:
1187 except OSError:
1178 if is_dir(f): # deleted directory ?
1188 if is_dir(f): # deleted directory ?
1179 continue
1189 continue
1180 raise util.Abort(_("file %s not found!") % rel)
1190 raise util.Abort(_("file %s not found!") % rel)
1181 if stat.S_ISDIR(mode):
1191 if stat.S_ISDIR(mode):
1182 if not is_dir(f):
1192 if not is_dir(f):
1183 raise util.Abort(_("no match under directory %s!")
1193 raise util.Abort(_("no match under directory %s!")
1184 % rel)
1194 % rel)
1185 elif not (stat.S_ISREG(mode) or stat.S_ISLNK(mode)):
1195 elif not (stat.S_ISREG(mode) or stat.S_ISLNK(mode)):
1186 raise util.Abort(_("can't commit %s: "
1196 raise util.Abort(_("can't commit %s: "
1187 "unsupported file type!") % rel)
1197 "unsupported file type!") % rel)
1188 elif f not in repo.dirstate:
1198 elif f not in repo.dirstate:
1189 raise util.Abort(_("file %s not tracked!") % rel)
1199 raise util.Abort(_("file %s not tracked!") % rel)
1190 m = matchfiles(repo, files)
1200 m = matchfiles(repo, files)
1191 try:
1201 try:
1192 return commitfunc(ui, repo, message, m, opts)
1202 return commitfunc(ui, repo, message, m, opts)
1193 except ValueError, inst:
1203 except ValueError, inst:
1194 raise util.Abort(str(inst))
1204 raise util.Abort(str(inst))
@@ -1,408 +1,410 b''
1 # help.py - help data for mercurial
1 # help.py - help data for mercurial
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 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 i18n import _
8 from i18n import _
9
9
10 helptable = (
10 helptable = (
11 (["dates"], _("Date Formats"),
11 (["dates"], _("Date Formats"),
12 _(r'''
12 _(r'''
13 Some commands allow the user to specify a date, e.g.:
13 Some commands allow the user to specify a date, e.g.:
14 * backout, commit, import, tag: Specify the commit date.
14 * backout, commit, import, tag: Specify the commit date.
15 * log, revert, update: Select revision(s) by date.
15 * log, revert, update: Select revision(s) by date.
16
16
17 Many date formats are valid. Here are some examples:
17 Many date formats are valid. Here are some examples:
18
18
19 "Wed Dec 6 13:18:29 2006" (local timezone assumed)
19 "Wed Dec 6 13:18:29 2006" (local timezone assumed)
20 "Dec 6 13:18 -0600" (year assumed, time offset provided)
20 "Dec 6 13:18 -0600" (year assumed, time offset provided)
21 "Dec 6 13:18 UTC" (UTC and GMT are aliases for +0000)
21 "Dec 6 13:18 UTC" (UTC and GMT are aliases for +0000)
22 "Dec 6" (midnight)
22 "Dec 6" (midnight)
23 "13:18" (today assumed)
23 "13:18" (today assumed)
24 "3:39" (3:39AM assumed)
24 "3:39" (3:39AM assumed)
25 "3:39pm" (15:39)
25 "3:39pm" (15:39)
26 "2006-12-06 13:18:29" (ISO 8601 format)
26 "2006-12-06 13:18:29" (ISO 8601 format)
27 "2006-12-6 13:18"
27 "2006-12-6 13:18"
28 "2006-12-6"
28 "2006-12-6"
29 "12-6"
29 "12-6"
30 "12/6"
30 "12/6"
31 "12/6/6" (Dec 6 2006)
31 "12/6/6" (Dec 6 2006)
32
32
33 Lastly, there is Mercurial's internal format:
33 Lastly, there is Mercurial's internal format:
34
34
35 "1165432709 0" (Wed Dec 6 13:18:29 2006 UTC)
35 "1165432709 0" (Wed Dec 6 13:18:29 2006 UTC)
36
36
37 This is the internal representation format for dates. unixtime is
37 This is the internal representation format for dates. unixtime is
38 the number of seconds since the epoch (1970-01-01 00:00 UTC). offset
38 the number of seconds since the epoch (1970-01-01 00:00 UTC). offset
39 is the offset of the local timezone, in seconds west of UTC (negative
39 is the offset of the local timezone, in seconds west of UTC (negative
40 if the timezone is east of UTC).
40 if the timezone is east of UTC).
41
41
42 The log command also accepts date ranges:
42 The log command also accepts date ranges:
43
43
44 "<{datetime}" - at or before a given date/time
44 "<{datetime}" - at or before a given date/time
45 ">{datetime}" - on or after a given date/time
45 ">{datetime}" - on or after a given date/time
46 "{datetime} to {datetime}" - a date range, inclusive
46 "{datetime} to {datetime}" - a date range, inclusive
47 "-{days}" - within a given number of days of today
47 "-{days}" - within a given number of days of today
48 ''')),
48 ''')),
49
49
50 (["patterns"], _("File Name Patterns"),
50 (["patterns"], _("File Name Patterns"),
51 _(r'''
51 _(r'''
52 Mercurial accepts several notations for identifying one or more
52 Mercurial accepts several notations for identifying one or more
53 files at a time.
53 files at a time.
54
54
55 By default, Mercurial treats filenames as shell-style extended
55 By default, Mercurial treats filenames as shell-style extended
56 glob patterns.
56 glob patterns.
57
57
58 Alternate pattern notations must be specified explicitly.
58 Alternate pattern notations must be specified explicitly.
59
59
60 To use a plain path name without any pattern matching, start it
60 To use a plain path name without any pattern matching, start it
61 with "path:". These path names must completely match starting at
61 with "path:". These path names must completely match starting at
62 the current repository root.
62 the current repository root.
63
63
64 To use an extended glob, start a name with "glob:". Globs are
64 To use an extended glob, start a name with "glob:". Globs are
65 rooted at the current directory; a glob such as "*.c" will only
65 rooted at the current directory; a glob such as "*.c" will only
66 match files in the current directory ending with ".c".
66 match files in the current directory ending with ".c".
67
67
68 The supported glob syntax extensions are "**" to match any string
68 The supported glob syntax extensions are "**" to match any string
69 across path separators and "{a,b}" to mean "a or b".
69 across path separators and "{a,b}" to mean "a or b".
70
70
71 To use a Perl/Python regular expression, start a name with "re:".
71 To use a Perl/Python regular expression, start a name with "re:".
72 Regexp pattern matching is anchored at the root of the repository.
72 Regexp pattern matching is anchored at the root of the repository.
73
73
74 Plain examples:
74 Plain examples:
75
75
76 path:foo/bar a name bar in a directory named foo in the root of
76 path:foo/bar a name bar in a directory named foo in the root of
77 the repository
77 the repository
78 path:path:name a file or directory named "path:name"
78 path:path:name a file or directory named "path:name"
79
79
80 Glob examples:
80 Glob examples:
81
81
82 glob:*.c any name ending in ".c" in the current directory
82 glob:*.c any name ending in ".c" in the current directory
83 *.c any name ending in ".c" in the current directory
83 *.c any name ending in ".c" in the current directory
84 **.c any name ending in ".c" in any subdirectory of the
84 **.c any name ending in ".c" in any subdirectory of the
85 current directory including itself.
85 current directory including itself.
86 foo/*.c any name ending in ".c" in the directory foo
86 foo/*.c any name ending in ".c" in the directory foo
87 foo/**.c any name ending in ".c" in any subdirectory of foo
87 foo/**.c any name ending in ".c" in any subdirectory of foo
88 including itself.
88 including itself.
89
89
90 Regexp examples:
90 Regexp examples:
91
91
92 re:.*\.c$ any name ending in ".c", anywhere in the repository
92 re:.*\.c$ any name ending in ".c", anywhere in the repository
93
93
94 ''')),
94 ''')),
95
95
96 (['environment', 'env'], _('Environment Variables'),
96 (['environment', 'env'], _('Environment Variables'),
97 _(r'''
97 _(r'''
98 HG::
98 HG::
99 Path to the 'hg' executable, automatically passed when running hooks,
99 Path to the 'hg' executable, automatically passed when running hooks,
100 extensions or external tools. If unset or empty, this is the hg
100 extensions or external tools. If unset or empty, this is the hg
101 executable's name if it's frozen, or an executable named 'hg'
101 executable's name if it's frozen, or an executable named 'hg'
102 (with %PATHEXT% [defaulting to COM/EXE/BAT/CMD] extensions on
102 (with %PATHEXT% [defaulting to COM/EXE/BAT/CMD] extensions on
103 Windows) is searched.
103 Windows) is searched.
104
104
105 HGEDITOR::
105 HGEDITOR::
106 This is the name of the editor to run when committing. See EDITOR.
106 This is the name of the editor to run when committing. See EDITOR.
107
107
108 (deprecated, use .hgrc)
108 (deprecated, use .hgrc)
109
109
110 HGENCODING::
110 HGENCODING::
111 This overrides the default locale setting detected by Mercurial.
111 This overrides the default locale setting detected by Mercurial.
112 This setting is used to convert data including usernames,
112 This setting is used to convert data including usernames,
113 changeset descriptions, tag names, and branches. This setting can
113 changeset descriptions, tag names, and branches. This setting can
114 be overridden with the --encoding command-line option.
114 be overridden with the --encoding command-line option.
115
115
116 HGENCODINGMODE::
116 HGENCODINGMODE::
117 This sets Mercurial's behavior for handling unknown characters
117 This sets Mercurial's behavior for handling unknown characters
118 while transcoding user input. The default is "strict", which
118 while transcoding user input. The default is "strict", which
119 causes Mercurial to abort if it can't map a character. Other
119 causes Mercurial to abort if it can't map a character. Other
120 settings include "replace", which replaces unknown characters, and
120 settings include "replace", which replaces unknown characters, and
121 "ignore", which drops them. This setting can be overridden with
121 "ignore", which drops them. This setting can be overridden with
122 the --encodingmode command-line option.
122 the --encodingmode command-line option.
123
123
124 HGMERGE::
124 HGMERGE::
125 An executable to use for resolving merge conflicts. The program
125 An executable to use for resolving merge conflicts. The program
126 will be executed with three arguments: local file, remote file,
126 will be executed with three arguments: local file, remote file,
127 ancestor file.
127 ancestor file.
128
128
129 (deprecated, use .hgrc)
129 (deprecated, use .hgrc)
130
130
131 HGRCPATH::
131 HGRCPATH::
132 A list of files or directories to search for hgrc files. Item
132 A list of files or directories to search for hgrc files. Item
133 separator is ":" on Unix, ";" on Windows. If HGRCPATH is not set,
133 separator is ":" on Unix, ";" on Windows. If HGRCPATH is not set,
134 platform default search path is used. If empty, only the .hg/hgrc
134 platform default search path is used. If empty, only the .hg/hgrc
135 from the current repository is read.
135 from the current repository is read.
136
136
137 For each element in HGRCPATH:
137 For each element in HGRCPATH:
138 * if it's a directory, all directories ending with .rc are added
138 * if it's a directory, all directories ending with .rc are added
139 * otherwise, the directory itself will be added
139 * otherwise, the directory itself will be added
140
140
141 HGUSER::
141 HGUSER::
142 This is the string used as the author of a commit. If not set,
142 This is the string used as the author of a commit. If not set,
143 available values will be considered in this order:
143 available values will be considered in this order:
144
144
145 * HGUSER (deprecated)
145 * HGUSER (deprecated)
146 * hgrc files from the HGRCPATH
146 * hgrc files from the HGRCPATH
147 * EMAIL
147 * EMAIL
148 * interactive prompt
148 * interactive prompt
149 * LOGNAME (with '@hostname' appended)
149 * LOGNAME (with '@hostname' appended)
150
150
151 (deprecated, use .hgrc)
151 (deprecated, use .hgrc)
152
152
153 EMAIL::
153 EMAIL::
154 May be used as the author of a commit; see HGUSER.
154 May be used as the author of a commit; see HGUSER.
155
155
156 LOGNAME::
156 LOGNAME::
157 May be used as the author of a commit; see HGUSER.
157 May be used as the author of a commit; see HGUSER.
158
158
159 VISUAL::
159 VISUAL::
160 This is the name of the editor to use when committing. See EDITOR.
160 This is the name of the editor to use when committing. See EDITOR.
161
161
162 EDITOR::
162 EDITOR::
163 Sometimes Mercurial needs to open a text file in an editor
163 Sometimes Mercurial needs to open a text file in an editor
164 for a user to modify, for example when writing commit messages.
164 for a user to modify, for example when writing commit messages.
165 The editor it uses is determined by looking at the environment
165 The editor it uses is determined by looking at the environment
166 variables HGEDITOR, VISUAL and EDITOR, in that order. The first
166 variables HGEDITOR, VISUAL and EDITOR, in that order. The first
167 non-empty one is chosen. If all of them are empty, the editor
167 non-empty one is chosen. If all of them are empty, the editor
168 defaults to 'vi'.
168 defaults to 'vi'.
169
169
170 PYTHONPATH::
170 PYTHONPATH::
171 This is used by Python to find imported modules and may need to be set
171 This is used by Python to find imported modules and may need to be set
172 appropriately if this Mercurial is not installed system-wide.
172 appropriately if this Mercurial is not installed system-wide.
173 ''')),
173 ''')),
174
174
175 (['revs', 'revisions'], _('Specifying Single Revisions'),
175 (['revs', 'revisions'], _('Specifying Single Revisions'),
176 _(r'''
176 _(r'''
177 Mercurial supports several ways to specify individual
177 Mercurial supports several ways to specify individual
178 revisions.
178 revisions.
179
179
180 A plain integer is treated as a revision number. Negative
180 A plain integer is treated as a revision number. Negative
181 integers are treated as toplogical offsets from the tip, with
181 integers are treated as toplogical offsets from the tip, with
182 -1 denoting the tip. As such, negative numbers are only useful
182 -1 denoting the tip. As such, negative numbers are only useful
183 if you've memorized your local tree numbers and want to save
183 if you've memorized your local tree numbers and want to save
184 typing a single digit. This editor suggests copy and paste.
184 typing a single digit. This editor suggests copy and paste.
185
185
186 A 40-digit hexadecimal string is treated as a unique revision
186 A 40-digit hexadecimal string is treated as a unique revision
187 identifier.
187 identifier.
188
188
189 A hexadecimal string less than 40 characters long is treated as a
189 A hexadecimal string less than 40 characters long is treated as a
190 unique revision identifier, and referred to as a short-form
190 unique revision identifier, and referred to as a short-form
191 identifier. A short-form identifier is only valid if it is the
191 identifier. A short-form identifier is only valid if it is the
192 prefix of exactly one full-length identifier.
192 prefix of exactly one full-length identifier.
193
193
194 Any other string is treated as a tag name, which is a symbolic
194 Any other string is treated as a tag name, which is a symbolic
195 name associated with a revision identifier. Tag names may not
195 name associated with a revision identifier. Tag names may not
196 contain the ":" character.
196 contain the ":" character.
197
197
198 The reserved name "tip" is a special tag that always identifies
198 The reserved name "tip" is a special tag that always identifies
199 the most recent revision.
199 the most recent revision.
200
200
201 The reserved name "null" indicates the null revision. This is the
201 The reserved name "null" indicates the null revision. This is the
202 revision of an empty repository, and the parent of revision 0.
202 revision of an empty repository, and the parent of revision 0.
203
203
204 The reserved name "." indicates the working directory parent. If
204 The reserved name "." indicates the working directory parent. If
205 no working directory is checked out, it is equivalent to null.
205 no working directory is checked out, it is equivalent to null.
206 If an uncommitted merge is in progress, "." is the revision of
206 If an uncommitted merge is in progress, "." is the revision of
207 the first parent.
207 the first parent.
208 ''')),
208 ''')),
209
209
210 (['mrevs', 'multirevs'], _('Specifying Multiple Revisions'),
210 (['mrevs', 'multirevs'], _('Specifying Multiple Revisions'),
211 _(r'''
211 _(r'''
212 When Mercurial accepts more than one revision, they may be
212 When Mercurial accepts more than one revision, they may be
213 specified individually, or provided as a topologically continuous
213 specified individually, or provided as a topologically continuous
214 range, separated by the ":" character.
214 range, separated by the ":" character.
215
215
216 The syntax of range notation is [BEGIN]:[END], where BEGIN and END
216 The syntax of range notation is [BEGIN]:[END], where BEGIN and END
217 are revision identifiers. Both BEGIN and END are optional. If
217 are revision identifiers. Both BEGIN and END are optional. If
218 BEGIN is not specified, it defaults to revision number 0. If END
218 BEGIN is not specified, it defaults to revision number 0. If END
219 is not specified, it defaults to the tip. The range ":" thus
219 is not specified, it defaults to the tip. The range ":" thus
220 means "all revisions".
220 means "all revisions".
221
221
222 If BEGIN is greater than END, revisions are treated in reverse
222 If BEGIN is greater than END, revisions are treated in reverse
223 order.
223 order.
224
224
225 A range acts as a closed interval. This means that a range of 3:5
225 A range acts as a closed interval. This means that a range of 3:5
226 gives 3, 4 and 5. Similarly, a range of 9:6 gives 9, 8, 7, and 6.
226 gives 3, 4 and 5. Similarly, a range of 9:6 gives 9, 8, 7, and 6.
227 ''')),
227 ''')),
228
228
229 (['diffs'], _('Diff Formats'),
229 (['diffs'], _('Diff Formats'),
230 _(r'''
230 _(r'''
231 Mercurial's default format for showing changes between two versions
231 Mercurial's default format for showing changes between two versions
232 of a file is compatible with the unified format of GNU diff, which
232 of a file is compatible with the unified format of GNU diff, which
233 can be used by GNU patch and many other standard tools.
233 can be used by GNU patch and many other standard tools.
234
234
235 While this standard format is often enough, it does not encode the
235 While this standard format is often enough, it does not encode the
236 following information:
236 following information:
237
237
238 - executable status and other permission bits
238 - executable status and other permission bits
239 - copy or rename information
239 - copy or rename information
240 - changes in binary files
240 - changes in binary files
241 - creation or deletion of empty files
241 - creation or deletion of empty files
242
242
243 Mercurial also supports the extended diff format from the git VCS
243 Mercurial also supports the extended diff format from the git VCS
244 which addresses these limitations. The git diff format is not
244 which addresses these limitations. The git diff format is not
245 produced by default because a few widespread tools still do not
245 produced by default because a few widespread tools still do not
246 understand this format.
246 understand this format.
247
247
248 This means that when generating diffs from a Mercurial repository
248 This means that when generating diffs from a Mercurial repository
249 (e.g. with "hg export"), you should be careful about things like
249 (e.g. with "hg export"), you should be careful about things like
250 file copies and renames or other things mentioned above, because
250 file copies and renames or other things mentioned above, because
251 when applying a standard diff to a different repository, this extra
251 when applying a standard diff to a different repository, this extra
252 information is lost. Mercurial's internal operations (like push and
252 information is lost. Mercurial's internal operations (like push and
253 pull) are not affected by this, because they use an internal binary
253 pull) are not affected by this, because they use an internal binary
254 format for communicating changes.
254 format for communicating changes.
255
255
256 To make Mercurial produce the git extended diff format, use the
256 To make Mercurial produce the git extended diff format, use the
257 --git option available for many commands, or set 'git = True' in the
257 --git option available for many commands, or set 'git = True' in the
258 [diff] section of your hgrc. You do not need to set this option when
258 [diff] section of your hgrc. You do not need to set this option when
259 importing diffs in this format or using them in the mq extension.
259 importing diffs in this format or using them in the mq extension.
260 ''')),
260 ''')),
261 (['templating'], _('Template Usage'),
261 (['templating'], _('Template Usage'),
262 _(r'''
262 _(r'''
263 Mercurial allows you to customize output of commands through
263 Mercurial allows you to customize output of commands through
264 templates. You can either pass in a template from the command line,
264 templates. You can either pass in a template from the command line,
265 via the --template option, or select an existing template-style (--style).
265 via the --template option, or select an existing template-style (--style).
266
266
267 You can customize output for any "log-like" command: log, outgoing,
267 You can customize output for any "log-like" command: log, outgoing,
268 incoming, tip, parents, heads and glog.
268 incoming, tip, parents, heads and glog.
269
269
270 Three styles are packaged with Mercurial: default (the style used
270 Three styles are packaged with Mercurial: default (the style used
271 when no explicit preference is passed), compact and changelog. Usage:
271 when no explicit preference is passed), compact and changelog. Usage:
272
272
273 $ hg log -r1 --style changelog
273 $ hg log -r1 --style changelog
274
274
275 A template is a piece of text, with markup to invoke variable expansion:
275 A template is a piece of text, with markup to invoke variable expansion:
276
276
277 $ hg log -r1 --template "{node}\n"
277 $ hg log -r1 --template "{node}\n"
278 b56ce7b07c52de7d5fd79fb89701ea538af65746
278 b56ce7b07c52de7d5fd79fb89701ea538af65746
279
279
280 Strings in curly braces are called keywords. The availability of
280 Strings in curly braces are called keywords. The availability of
281 keywords depends on the exact context of the templater. These keywords
281 keywords depends on the exact context of the templater. These keywords
282 are usually available for templating a log-like command:
282 are usually available for templating a log-like command:
283
283
284 - author: String. The unmodified author of the changeset.
284 - author: String. The unmodified author of the changeset.
285 - branches: String. The name of the branch on which the changeset
285 - branches: String. The name of the branch on which the changeset
286 was committed. Will be empty if the branch name was default.
286 was committed. Will be empty if the branch name was default.
287 - date: Date information. The date when the changeset was committed.
287 - date: Date information. The date when the changeset was committed.
288 - desc: String. The text of the changeset description.
288 - desc: String. The text of the changeset description.
289 - diffstat: String. Statistics of changes with the following format:
290 "modified files: +added/-removed lines"
289 - files: List of strings. All files modified, added, or removed by
291 - files: List of strings. All files modified, added, or removed by
290 this changeset.
292 this changeset.
291 - file_adds: List of strings. Files added by this changeset.
293 - file_adds: List of strings. Files added by this changeset.
292 - file_mods: List of strings. Files modified by this changeset.
294 - file_mods: List of strings. Files modified by this changeset.
293 - file_dels: List of strings. Files removed by this changeset.
295 - file_dels: List of strings. Files removed by this changeset.
294 - node: String. The changeset identification hash, as a 40-character
296 - node: String. The changeset identification hash, as a 40-character
295 hexadecimal string.
297 hexadecimal string.
296 - parents: List of strings. The parents of the changeset.
298 - parents: List of strings. The parents of the changeset.
297 - rev: Integer. The repository-local changeset revision number.
299 - rev: Integer. The repository-local changeset revision number.
298 - tags: List of strings. Any tags associated with the changeset.
300 - tags: List of strings. Any tags associated with the changeset.
299
301
300 The "date" keyword does not produce human-readable output. If you
302 The "date" keyword does not produce human-readable output. If you
301 want to use a date in your output, you can use a filter to process it.
303 want to use a date in your output, you can use a filter to process it.
302 Filters are functions which return a string based on the input variable.
304 Filters are functions which return a string based on the input variable.
303 You can also use a chain of filters to get the desired output:
305 You can also use a chain of filters to get the desired output:
304
306
305 $ hg tip --template "{date|isodate}\n"
307 $ hg tip --template "{date|isodate}\n"
306 2008-08-21 18:22 +0000
308 2008-08-21 18:22 +0000
307
309
308 List of filters:
310 List of filters:
309
311
310 - addbreaks: Any text. Add an XHTML "<br />" tag before the end of
312 - addbreaks: Any text. Add an XHTML "<br />" tag before the end of
311 every line except the last.
313 every line except the last.
312 - age: Date. Returns a human-readable date/time difference between
314 - age: Date. Returns a human-readable date/time difference between
313 the given date/time and the current date/time.
315 the given date/time and the current date/time.
314 - basename: Any text. Treats the text as a path, and returns the
316 - basename: Any text. Treats the text as a path, and returns the
315 last component of the path after splitting by the path
317 last component of the path after splitting by the path
316 separator (ignoring trailing seprators). For example,
318 separator (ignoring trailing seprators). For example,
317 "foo/bar/baz" becomes "baz" and "foo/bar//" becomes "bar".
319 "foo/bar/baz" becomes "baz" and "foo/bar//" becomes "bar".
318 - date: Date. Returns a date in a Unix date format, including
320 - date: Date. Returns a date in a Unix date format, including
319 the timezone: "Mon Sep 04 15:13:13 2006 0700".
321 the timezone: "Mon Sep 04 15:13:13 2006 0700".
320 - domain: Any text. Finds the first string that looks like an email
322 - domain: Any text. Finds the first string that looks like an email
321 address, and extracts just the domain component.
323 address, and extracts just the domain component.
322 Example: 'User <user@example.com>' becomes 'example.com'.
324 Example: 'User <user@example.com>' becomes 'example.com'.
323 - email: Any text. Extracts the first string that looks like an email
325 - email: Any text. Extracts the first string that looks like an email
324 address. Example: 'User <user@example.com>' becomes
326 address. Example: 'User <user@example.com>' becomes
325 'user@example.com'.
327 'user@example.com'.
326 - escape: Any text. Replaces the special XML/XHTML characters "&",
328 - escape: Any text. Replaces the special XML/XHTML characters "&",
327 "<" and ">" with XML entities.
329 "<" and ">" with XML entities.
328 - fill68: Any text. Wraps the text to fit in 68 columns.
330 - fill68: Any text. Wraps the text to fit in 68 columns.
329 - fill76: Any text. Wraps the text to fit in 76 columns.
331 - fill76: Any text. Wraps the text to fit in 76 columns.
330 - firstline: Any text. Returns the first line of text.
332 - firstline: Any text. Returns the first line of text.
331 - hgdate: Date. Returns the date as a pair of numbers:
333 - hgdate: Date. Returns the date as a pair of numbers:
332 "1157407993 25200" (Unix timestamp, timezone offset).
334 "1157407993 25200" (Unix timestamp, timezone offset).
333 - isodate: Date. Returns the date in ISO 8601 format.
335 - isodate: Date. Returns the date in ISO 8601 format.
334 - obfuscate: Any text. Returns the input text rendered as a sequence
336 - obfuscate: Any text. Returns the input text rendered as a sequence
335 of XML entities.
337 of XML entities.
336 - person: Any text. Returns the text before an email address.
338 - person: Any text. Returns the text before an email address.
337 - rfc822date: Date. Returns a date using the same format used
339 - rfc822date: Date. Returns a date using the same format used
338 in email headers.
340 in email headers.
339 - short: Changeset hash. Returns the short form of a changeset hash,
341 - short: Changeset hash. Returns the short form of a changeset hash,
340 i.e. a 12-byte hexadecimal string.
342 i.e. a 12-byte hexadecimal string.
341 - shortdate: Date. Returns a date like "2006-09-18".
343 - shortdate: Date. Returns a date like "2006-09-18".
342 - strip: Any text. Strips all leading and trailing whitespace.
344 - strip: Any text. Strips all leading and trailing whitespace.
343 - tabindent: Any text. Returns the text, with every line except the
345 - tabindent: Any text. Returns the text, with every line except the
344 first starting with a tab character.
346 first starting with a tab character.
345 - urlescape: Any text. Escapes all "special" characters. For example,
347 - urlescape: Any text. Escapes all "special" characters. For example,
346 "foo bar" becomes "foo%20bar".
348 "foo bar" becomes "foo%20bar".
347 - user: Any text. Returns the user portion of an email address.
349 - user: Any text. Returns the user portion of an email address.
348 ''')),
350 ''')),
349
351
350 (['urls'], _('Url Paths'),
352 (['urls'], _('Url Paths'),
351 _(r'''
353 _(r'''
352 Valid URLs are of the form:
354 Valid URLs are of the form:
353
355
354 local/filesystem/path (or file://local/filesystem/path)
356 local/filesystem/path (or file://local/filesystem/path)
355 http://[user[:pass]@]host[:port]/[path]
357 http://[user[:pass]@]host[:port]/[path]
356 https://[user[:pass]@]host[:port]/[path]
358 https://[user[:pass]@]host[:port]/[path]
357 ssh://[user[:pass]@]host[:port]/[path]
359 ssh://[user[:pass]@]host[:port]/[path]
358
360
359 Paths in the local filesystem can either point to Mercurial
361 Paths in the local filesystem can either point to Mercurial
360 repositories or to bundle files (as created by 'hg bundle' or
362 repositories or to bundle files (as created by 'hg bundle' or
361 'hg incoming --bundle').
363 'hg incoming --bundle').
362
364
363 An optional identifier after # indicates a particular branch, tag,
365 An optional identifier after # indicates a particular branch, tag,
364 or changeset to use from the remote repository.
366 or changeset to use from the remote repository.
365
367
366 Some features, such as pushing to http:// and https:// URLs are
368 Some features, such as pushing to http:// and https:// URLs are
367 only possible if the feature is explicitly enabled on the
369 only possible if the feature is explicitly enabled on the
368 remote Mercurial server.
370 remote Mercurial server.
369
371
370 Some notes about using SSH with Mercurial:
372 Some notes about using SSH with Mercurial:
371 - SSH requires an accessible shell account on the destination machine
373 - SSH requires an accessible shell account on the destination machine
372 and a copy of hg in the remote path or specified with as remotecmd.
374 and a copy of hg in the remote path or specified with as remotecmd.
373 - path is relative to the remote user's home directory by default.
375 - path is relative to the remote user's home directory by default.
374 Use an extra slash at the start of a path to specify an absolute path:
376 Use an extra slash at the start of a path to specify an absolute path:
375 ssh://example.com//tmp/repository
377 ssh://example.com//tmp/repository
376 - Mercurial doesn't use its own compression via SSH; the right thing
378 - Mercurial doesn't use its own compression via SSH; the right thing
377 to do is to configure it in your ~/.ssh/config, e.g.:
379 to do is to configure it in your ~/.ssh/config, e.g.:
378 Host *.mylocalnetwork.example.com
380 Host *.mylocalnetwork.example.com
379 Compression no
381 Compression no
380 Host *
382 Host *
381 Compression yes
383 Compression yes
382 Alternatively specify "ssh -C" as your ssh command in your hgrc or
384 Alternatively specify "ssh -C" as your ssh command in your hgrc or
383 with the --ssh command line option.
385 with the --ssh command line option.
384
386
385 These urls can all be stored in your hgrc with path aliases under the
387 These urls can all be stored in your hgrc with path aliases under the
386 [paths] section like so:
388 [paths] section like so:
387 [paths]
389 [paths]
388 alias1 = URL1
390 alias1 = URL1
389 alias2 = URL2
391 alias2 = URL2
390 ...
392 ...
391
393
392 You can then use the alias for any command that uses a url (for example
394 You can then use the alias for any command that uses a url (for example
393 'hg pull alias1' would pull from the 'alias1' path).
395 'hg pull alias1' would pull from the 'alias1' path).
394
396
395 Two path aliases are special because they are used as defaults
397 Two path aliases are special because they are used as defaults
396 when you do not provide the url to a command:
398 when you do not provide the url to a command:
397
399
398 default:
400 default:
399 When you create a repository with hg clone, the clone command saves
401 When you create a repository with hg clone, the clone command saves
400 the location of the source repository as the new repository's
402 the location of the source repository as the new repository's
401 'default' path. This is then used when you omit path from push-
403 'default' path. This is then used when you omit path from push-
402 and pull-like commands (including in and out).
404 and pull-like commands (including in and out).
403
405
404 default-push:
406 default-push:
405 The push command will look for a path named 'default-push', and
407 The push command will look for a path named 'default-push', and
406 prefer it over 'default' if both are defined.
408 prefer it over 'default' if both are defined.
407 ''')),
409 ''')),
408 )
410 )
@@ -1,2019 +1,2024 b''
1 """
1 """
2 util.py - Mercurial utility functions and platform specfic implementations
2 util.py - Mercurial utility functions and platform specfic implementations
3
3
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7
7
8 This software may be used and distributed according to the terms
8 This software may be used and distributed according to the terms
9 of the GNU General Public License, incorporated herein by reference.
9 of the GNU General Public License, incorporated herein by reference.
10
10
11 This contains helper routines that are independent of the SCM core and hide
11 This contains helper routines that are independent of the SCM core and hide
12 platform-specific details from the core.
12 platform-specific details from the core.
13 """
13 """
14
14
15 from i18n import _
15 from i18n import _
16 import cStringIO, errno, getpass, re, shutil, sys, tempfile, traceback, error
16 import cStringIO, errno, getpass, re, shutil, sys, tempfile, traceback, error
17 import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil
17 import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil
18 import imp, unicodedata
18 import imp, unicodedata
19
19
20 # Python compatibility
20 # Python compatibility
21
21
22 try:
22 try:
23 set = set
23 set = set
24 frozenset = frozenset
24 frozenset = frozenset
25 except NameError:
25 except NameError:
26 from sets import Set as set, ImmutableSet as frozenset
26 from sets import Set as set, ImmutableSet as frozenset
27
27
28 _md5 = None
28 _md5 = None
29 def md5(s):
29 def md5(s):
30 global _md5
30 global _md5
31 if _md5 is None:
31 if _md5 is None:
32 try:
32 try:
33 import hashlib
33 import hashlib
34 _md5 = hashlib.md5
34 _md5 = hashlib.md5
35 except ImportError:
35 except ImportError:
36 import md5
36 import md5
37 _md5 = md5.md5
37 _md5 = md5.md5
38 return _md5(s)
38 return _md5(s)
39
39
40 _sha1 = None
40 _sha1 = None
41 def sha1(s):
41 def sha1(s):
42 global _sha1
42 global _sha1
43 if _sha1 is None:
43 if _sha1 is None:
44 try:
44 try:
45 import hashlib
45 import hashlib
46 _sha1 = hashlib.sha1
46 _sha1 = hashlib.sha1
47 except ImportError:
47 except ImportError:
48 import sha
48 import sha
49 _sha1 = sha.sha
49 _sha1 = sha.sha
50 return _sha1(s)
50 return _sha1(s)
51
51
52 try:
52 try:
53 import subprocess
53 import subprocess
54 subprocess.Popen # trigger ImportError early
54 subprocess.Popen # trigger ImportError early
55 closefds = os.name == 'posix'
55 closefds = os.name == 'posix'
56 def popen2(cmd, mode='t', bufsize=-1):
56 def popen2(cmd, mode='t', bufsize=-1):
57 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
57 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
58 close_fds=closefds,
58 close_fds=closefds,
59 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
59 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
60 return p.stdin, p.stdout
60 return p.stdin, p.stdout
61 def popen3(cmd, mode='t', bufsize=-1):
61 def popen3(cmd, mode='t', bufsize=-1):
62 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
62 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
63 close_fds=closefds,
63 close_fds=closefds,
64 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
64 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
65 stderr=subprocess.PIPE)
65 stderr=subprocess.PIPE)
66 return p.stdin, p.stdout, p.stderr
66 return p.stdin, p.stdout, p.stderr
67 def Popen3(cmd, capturestderr=False, bufsize=-1):
67 def Popen3(cmd, capturestderr=False, bufsize=-1):
68 stderr = capturestderr and subprocess.PIPE or None
68 stderr = capturestderr and subprocess.PIPE or None
69 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
69 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
70 close_fds=closefds,
70 close_fds=closefds,
71 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
71 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
72 stderr=stderr)
72 stderr=stderr)
73 p.fromchild = p.stdout
73 p.fromchild = p.stdout
74 p.tochild = p.stdin
74 p.tochild = p.stdin
75 p.childerr = p.stderr
75 p.childerr = p.stderr
76 return p
76 return p
77 except ImportError:
77 except ImportError:
78 subprocess = None
78 subprocess = None
79 from popen2 import Popen3
79 from popen2 import Popen3
80 popen2 = os.popen2
80 popen2 = os.popen2
81 popen3 = os.popen3
81 popen3 = os.popen3
82
82
83
83
84 _encodingfixup = {'646': 'ascii', 'ANSI_X3.4-1968': 'ascii'}
84 _encodingfixup = {'646': 'ascii', 'ANSI_X3.4-1968': 'ascii'}
85
85
86 try:
86 try:
87 _encoding = os.environ.get("HGENCODING")
87 _encoding = os.environ.get("HGENCODING")
88 if sys.platform == 'darwin' and not _encoding:
88 if sys.platform == 'darwin' and not _encoding:
89 # On darwin, getpreferredencoding ignores the locale environment and
89 # On darwin, getpreferredencoding ignores the locale environment and
90 # always returns mac-roman. We override this if the environment is
90 # always returns mac-roman. We override this if the environment is
91 # not C (has been customized by the user).
91 # not C (has been customized by the user).
92 locale.setlocale(locale.LC_CTYPE, '')
92 locale.setlocale(locale.LC_CTYPE, '')
93 _encoding = locale.getlocale()[1]
93 _encoding = locale.getlocale()[1]
94 if not _encoding:
94 if not _encoding:
95 _encoding = locale.getpreferredencoding() or 'ascii'
95 _encoding = locale.getpreferredencoding() or 'ascii'
96 _encoding = _encodingfixup.get(_encoding, _encoding)
96 _encoding = _encodingfixup.get(_encoding, _encoding)
97 except locale.Error:
97 except locale.Error:
98 _encoding = 'ascii'
98 _encoding = 'ascii'
99 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
99 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
100 _fallbackencoding = 'ISO-8859-1'
100 _fallbackencoding = 'ISO-8859-1'
101
101
102 def tolocal(s):
102 def tolocal(s):
103 """
103 """
104 Convert a string from internal UTF-8 to local encoding
104 Convert a string from internal UTF-8 to local encoding
105
105
106 All internal strings should be UTF-8 but some repos before the
106 All internal strings should be UTF-8 but some repos before the
107 implementation of locale support may contain latin1 or possibly
107 implementation of locale support may contain latin1 or possibly
108 other character sets. We attempt to decode everything strictly
108 other character sets. We attempt to decode everything strictly
109 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
109 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
110 replace unknown characters.
110 replace unknown characters.
111 """
111 """
112 for e in ('UTF-8', _fallbackencoding):
112 for e in ('UTF-8', _fallbackencoding):
113 try:
113 try:
114 u = s.decode(e) # attempt strict decoding
114 u = s.decode(e) # attempt strict decoding
115 return u.encode(_encoding, "replace")
115 return u.encode(_encoding, "replace")
116 except LookupError, k:
116 except LookupError, k:
117 raise Abort(_("%s, please check your locale settings") % k)
117 raise Abort(_("%s, please check your locale settings") % k)
118 except UnicodeDecodeError:
118 except UnicodeDecodeError:
119 pass
119 pass
120 u = s.decode("utf-8", "replace") # last ditch
120 u = s.decode("utf-8", "replace") # last ditch
121 return u.encode(_encoding, "replace")
121 return u.encode(_encoding, "replace")
122
122
123 def fromlocal(s):
123 def fromlocal(s):
124 """
124 """
125 Convert a string from the local character encoding to UTF-8
125 Convert a string from the local character encoding to UTF-8
126
126
127 We attempt to decode strings using the encoding mode set by
127 We attempt to decode strings using the encoding mode set by
128 HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
128 HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
129 characters will cause an error message. Other modes include
129 characters will cause an error message. Other modes include
130 'replace', which replaces unknown characters with a special
130 'replace', which replaces unknown characters with a special
131 Unicode character, and 'ignore', which drops the character.
131 Unicode character, and 'ignore', which drops the character.
132 """
132 """
133 try:
133 try:
134 return s.decode(_encoding, _encodingmode).encode("utf-8")
134 return s.decode(_encoding, _encodingmode).encode("utf-8")
135 except UnicodeDecodeError, inst:
135 except UnicodeDecodeError, inst:
136 sub = s[max(0, inst.start-10):inst.start+10]
136 sub = s[max(0, inst.start-10):inst.start+10]
137 raise Abort("decoding near '%s': %s!" % (sub, inst))
137 raise Abort("decoding near '%s': %s!" % (sub, inst))
138 except LookupError, k:
138 except LookupError, k:
139 raise Abort(_("%s, please check your locale settings") % k)
139 raise Abort(_("%s, please check your locale settings") % k)
140
140
141 def colwidth(s):
141 def colwidth(s):
142 "Find the column width of a UTF-8 string for display"
142 "Find the column width of a UTF-8 string for display"
143 d = s.decode(_encoding, 'replace')
143 d = s.decode(_encoding, 'replace')
144 if hasattr(unicodedata, 'east_asian_width'):
144 if hasattr(unicodedata, 'east_asian_width'):
145 w = unicodedata.east_asian_width
145 w = unicodedata.east_asian_width
146 return sum([w(c) in 'WF' and 2 or 1 for c in d])
146 return sum([w(c) in 'WF' and 2 or 1 for c in d])
147 return len(d)
147 return len(d)
148
148
149 def version():
149 def version():
150 """Return version information if available."""
150 """Return version information if available."""
151 try:
151 try:
152 import __version__
152 import __version__
153 return __version__.version
153 return __version__.version
154 except ImportError:
154 except ImportError:
155 return 'unknown'
155 return 'unknown'
156
156
157 # used by parsedate
157 # used by parsedate
158 defaultdateformats = (
158 defaultdateformats = (
159 '%Y-%m-%d %H:%M:%S',
159 '%Y-%m-%d %H:%M:%S',
160 '%Y-%m-%d %I:%M:%S%p',
160 '%Y-%m-%d %I:%M:%S%p',
161 '%Y-%m-%d %H:%M',
161 '%Y-%m-%d %H:%M',
162 '%Y-%m-%d %I:%M%p',
162 '%Y-%m-%d %I:%M%p',
163 '%Y-%m-%d',
163 '%Y-%m-%d',
164 '%m-%d',
164 '%m-%d',
165 '%m/%d',
165 '%m/%d',
166 '%m/%d/%y',
166 '%m/%d/%y',
167 '%m/%d/%Y',
167 '%m/%d/%Y',
168 '%a %b %d %H:%M:%S %Y',
168 '%a %b %d %H:%M:%S %Y',
169 '%a %b %d %I:%M:%S%p %Y',
169 '%a %b %d %I:%M:%S%p %Y',
170 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
170 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
171 '%b %d %H:%M:%S %Y',
171 '%b %d %H:%M:%S %Y',
172 '%b %d %I:%M:%S%p %Y',
172 '%b %d %I:%M:%S%p %Y',
173 '%b %d %H:%M:%S',
173 '%b %d %H:%M:%S',
174 '%b %d %I:%M:%S%p',
174 '%b %d %I:%M:%S%p',
175 '%b %d %H:%M',
175 '%b %d %H:%M',
176 '%b %d %I:%M%p',
176 '%b %d %I:%M%p',
177 '%b %d %Y',
177 '%b %d %Y',
178 '%b %d',
178 '%b %d',
179 '%H:%M:%S',
179 '%H:%M:%S',
180 '%I:%M:%SP',
180 '%I:%M:%SP',
181 '%H:%M',
181 '%H:%M',
182 '%I:%M%p',
182 '%I:%M%p',
183 )
183 )
184
184
185 extendeddateformats = defaultdateformats + (
185 extendeddateformats = defaultdateformats + (
186 "%Y",
186 "%Y",
187 "%Y-%m",
187 "%Y-%m",
188 "%b",
188 "%b",
189 "%b %Y",
189 "%b %Y",
190 )
190 )
191
191
192 # differences from SafeConfigParser:
192 # differences from SafeConfigParser:
193 # - case-sensitive keys
193 # - case-sensitive keys
194 # - allows values that are not strings (this means that you may not
194 # - allows values that are not strings (this means that you may not
195 # be able to save the configuration to a file)
195 # be able to save the configuration to a file)
196 class configparser(ConfigParser.SafeConfigParser):
196 class configparser(ConfigParser.SafeConfigParser):
197 def optionxform(self, optionstr):
197 def optionxform(self, optionstr):
198 return optionstr
198 return optionstr
199
199
200 def set(self, section, option, value):
200 def set(self, section, option, value):
201 return ConfigParser.ConfigParser.set(self, section, option, value)
201 return ConfigParser.ConfigParser.set(self, section, option, value)
202
202
203 def _interpolate(self, section, option, rawval, vars):
203 def _interpolate(self, section, option, rawval, vars):
204 if not isinstance(rawval, basestring):
204 if not isinstance(rawval, basestring):
205 return rawval
205 return rawval
206 return ConfigParser.SafeConfigParser._interpolate(self, section,
206 return ConfigParser.SafeConfigParser._interpolate(self, section,
207 option, rawval, vars)
207 option, rawval, vars)
208
208
209 def cachefunc(func):
209 def cachefunc(func):
210 '''cache the result of function calls'''
210 '''cache the result of function calls'''
211 # XXX doesn't handle keywords args
211 # XXX doesn't handle keywords args
212 cache = {}
212 cache = {}
213 if func.func_code.co_argcount == 1:
213 if func.func_code.co_argcount == 1:
214 # we gain a small amount of time because
214 # we gain a small amount of time because
215 # we don't need to pack/unpack the list
215 # we don't need to pack/unpack the list
216 def f(arg):
216 def f(arg):
217 if arg not in cache:
217 if arg not in cache:
218 cache[arg] = func(arg)
218 cache[arg] = func(arg)
219 return cache[arg]
219 return cache[arg]
220 else:
220 else:
221 def f(*args):
221 def f(*args):
222 if args not in cache:
222 if args not in cache:
223 cache[args] = func(*args)
223 cache[args] = func(*args)
224 return cache[args]
224 return cache[args]
225
225
226 return f
226 return f
227
227
228 def pipefilter(s, cmd):
228 def pipefilter(s, cmd):
229 '''filter string S through command CMD, returning its output'''
229 '''filter string S through command CMD, returning its output'''
230 (pin, pout) = popen2(cmd, 'b')
230 (pin, pout) = popen2(cmd, 'b')
231 def writer():
231 def writer():
232 try:
232 try:
233 pin.write(s)
233 pin.write(s)
234 pin.close()
234 pin.close()
235 except IOError, inst:
235 except IOError, inst:
236 if inst.errno != errno.EPIPE:
236 if inst.errno != errno.EPIPE:
237 raise
237 raise
238
238
239 # we should use select instead on UNIX, but this will work on most
239 # we should use select instead on UNIX, but this will work on most
240 # systems, including Windows
240 # systems, including Windows
241 w = threading.Thread(target=writer)
241 w = threading.Thread(target=writer)
242 w.start()
242 w.start()
243 f = pout.read()
243 f = pout.read()
244 pout.close()
244 pout.close()
245 w.join()
245 w.join()
246 return f
246 return f
247
247
248 def tempfilter(s, cmd):
248 def tempfilter(s, cmd):
249 '''filter string S through a pair of temporary files with CMD.
249 '''filter string S through a pair of temporary files with CMD.
250 CMD is used as a template to create the real command to be run,
250 CMD is used as a template to create the real command to be run,
251 with the strings INFILE and OUTFILE replaced by the real names of
251 with the strings INFILE and OUTFILE replaced by the real names of
252 the temporary files generated.'''
252 the temporary files generated.'''
253 inname, outname = None, None
253 inname, outname = None, None
254 try:
254 try:
255 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
255 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
256 fp = os.fdopen(infd, 'wb')
256 fp = os.fdopen(infd, 'wb')
257 fp.write(s)
257 fp.write(s)
258 fp.close()
258 fp.close()
259 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
259 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
260 os.close(outfd)
260 os.close(outfd)
261 cmd = cmd.replace('INFILE', inname)
261 cmd = cmd.replace('INFILE', inname)
262 cmd = cmd.replace('OUTFILE', outname)
262 cmd = cmd.replace('OUTFILE', outname)
263 code = os.system(cmd)
263 code = os.system(cmd)
264 if sys.platform == 'OpenVMS' and code & 1:
264 if sys.platform == 'OpenVMS' and code & 1:
265 code = 0
265 code = 0
266 if code: raise Abort(_("command '%s' failed: %s") %
266 if code: raise Abort(_("command '%s' failed: %s") %
267 (cmd, explain_exit(code)))
267 (cmd, explain_exit(code)))
268 return open(outname, 'rb').read()
268 return open(outname, 'rb').read()
269 finally:
269 finally:
270 try:
270 try:
271 if inname: os.unlink(inname)
271 if inname: os.unlink(inname)
272 except: pass
272 except: pass
273 try:
273 try:
274 if outname: os.unlink(outname)
274 if outname: os.unlink(outname)
275 except: pass
275 except: pass
276
276
277 filtertable = {
277 filtertable = {
278 'tempfile:': tempfilter,
278 'tempfile:': tempfilter,
279 'pipe:': pipefilter,
279 'pipe:': pipefilter,
280 }
280 }
281
281
282 def filter(s, cmd):
282 def filter(s, cmd):
283 "filter a string through a command that transforms its input to its output"
283 "filter a string through a command that transforms its input to its output"
284 for name, fn in filtertable.iteritems():
284 for name, fn in filtertable.iteritems():
285 if cmd.startswith(name):
285 if cmd.startswith(name):
286 return fn(s, cmd[len(name):].lstrip())
286 return fn(s, cmd[len(name):].lstrip())
287 return pipefilter(s, cmd)
287 return pipefilter(s, cmd)
288
288
289 def binary(s):
289 def binary(s):
290 """return true if a string is binary data"""
290 """return true if a string is binary data"""
291 if s and '\0' in s:
291 if s and '\0' in s:
292 return True
292 return True
293 return False
293 return False
294
294
295 def unique(g):
295 def unique(g):
296 """return the uniq elements of iterable g"""
296 """return the uniq elements of iterable g"""
297 return dict.fromkeys(g).keys()
297 return dict.fromkeys(g).keys()
298
298
299 def sort(l):
299 def sort(l):
300 if not isinstance(l, list):
300 if not isinstance(l, list):
301 l = list(l)
301 l = list(l)
302 l.sort()
302 l.sort()
303 return l
303 return l
304
304
305 def increasingchunks(source, min=1024, max=65536):
305 def increasingchunks(source, min=1024, max=65536):
306 '''return no less than min bytes per chunk while data remains,
306 '''return no less than min bytes per chunk while data remains,
307 doubling min after each chunk until it reaches max'''
307 doubling min after each chunk until it reaches max'''
308 def log2(x):
308 def log2(x):
309 if not x:
309 if not x:
310 return 0
310 return 0
311 i = 0
311 i = 0
312 while x:
312 while x:
313 x >>= 1
313 x >>= 1
314 i += 1
314 i += 1
315 return i - 1
315 return i - 1
316
316
317 buf = []
317 buf = []
318 blen = 0
318 blen = 0
319 for chunk in source:
319 for chunk in source:
320 buf.append(chunk)
320 buf.append(chunk)
321 blen += len(chunk)
321 blen += len(chunk)
322 if blen >= min:
322 if blen >= min:
323 if min < max:
323 if min < max:
324 min = min << 1
324 min = min << 1
325 nmin = 1 << log2(blen)
325 nmin = 1 << log2(blen)
326 if nmin > min:
326 if nmin > min:
327 min = nmin
327 min = nmin
328 if min > max:
328 if min > max:
329 min = max
329 min = max
330 yield ''.join(buf)
330 yield ''.join(buf)
331 blen = 0
331 blen = 0
332 buf = []
332 buf = []
333 if buf:
333 if buf:
334 yield ''.join(buf)
334 yield ''.join(buf)
335
335
336 class Abort(Exception):
336 class Abort(Exception):
337 """Raised if a command needs to print an error and exit."""
337 """Raised if a command needs to print an error and exit."""
338
338
339 def always(fn): return True
339 def always(fn): return True
340 def never(fn): return False
340 def never(fn): return False
341
341
342 def expand_glob(pats):
342 def expand_glob(pats):
343 '''On Windows, expand the implicit globs in a list of patterns'''
343 '''On Windows, expand the implicit globs in a list of patterns'''
344 if os.name != 'nt':
344 if os.name != 'nt':
345 return list(pats)
345 return list(pats)
346 ret = []
346 ret = []
347 for p in pats:
347 for p in pats:
348 kind, name = patkind(p, None)
348 kind, name = patkind(p, None)
349 if kind is None:
349 if kind is None:
350 globbed = glob.glob(name)
350 globbed = glob.glob(name)
351 if globbed:
351 if globbed:
352 ret.extend(globbed)
352 ret.extend(globbed)
353 continue
353 continue
354 # if we couldn't expand the glob, just keep it around
354 # if we couldn't expand the glob, just keep it around
355 ret.append(p)
355 ret.append(p)
356 return ret
356 return ret
357
357
358 def patkind(name, default):
358 def patkind(name, default):
359 """Split a string into an optional pattern kind prefix and the
359 """Split a string into an optional pattern kind prefix and the
360 actual pattern."""
360 actual pattern."""
361 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
361 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
362 if name.startswith(prefix + ':'): return name.split(':', 1)
362 if name.startswith(prefix + ':'): return name.split(':', 1)
363 return default, name
363 return default, name
364
364
365 def globre(pat, head='^', tail='$'):
365 def globre(pat, head='^', tail='$'):
366 "convert a glob pattern into a regexp"
366 "convert a glob pattern into a regexp"
367 i, n = 0, len(pat)
367 i, n = 0, len(pat)
368 res = ''
368 res = ''
369 group = 0
369 group = 0
370 def peek(): return i < n and pat[i]
370 def peek(): return i < n and pat[i]
371 while i < n:
371 while i < n:
372 c = pat[i]
372 c = pat[i]
373 i = i+1
373 i = i+1
374 if c == '*':
374 if c == '*':
375 if peek() == '*':
375 if peek() == '*':
376 i += 1
376 i += 1
377 res += '.*'
377 res += '.*'
378 else:
378 else:
379 res += '[^/]*'
379 res += '[^/]*'
380 elif c == '?':
380 elif c == '?':
381 res += '.'
381 res += '.'
382 elif c == '[':
382 elif c == '[':
383 j = i
383 j = i
384 if j < n and pat[j] in '!]':
384 if j < n and pat[j] in '!]':
385 j += 1
385 j += 1
386 while j < n and pat[j] != ']':
386 while j < n and pat[j] != ']':
387 j += 1
387 j += 1
388 if j >= n:
388 if j >= n:
389 res += '\\['
389 res += '\\['
390 else:
390 else:
391 stuff = pat[i:j].replace('\\','\\\\')
391 stuff = pat[i:j].replace('\\','\\\\')
392 i = j + 1
392 i = j + 1
393 if stuff[0] == '!':
393 if stuff[0] == '!':
394 stuff = '^' + stuff[1:]
394 stuff = '^' + stuff[1:]
395 elif stuff[0] == '^':
395 elif stuff[0] == '^':
396 stuff = '\\' + stuff
396 stuff = '\\' + stuff
397 res = '%s[%s]' % (res, stuff)
397 res = '%s[%s]' % (res, stuff)
398 elif c == '{':
398 elif c == '{':
399 group += 1
399 group += 1
400 res += '(?:'
400 res += '(?:'
401 elif c == '}' and group:
401 elif c == '}' and group:
402 res += ')'
402 res += ')'
403 group -= 1
403 group -= 1
404 elif c == ',' and group:
404 elif c == ',' and group:
405 res += '|'
405 res += '|'
406 elif c == '\\':
406 elif c == '\\':
407 p = peek()
407 p = peek()
408 if p:
408 if p:
409 i += 1
409 i += 1
410 res += re.escape(p)
410 res += re.escape(p)
411 else:
411 else:
412 res += re.escape(c)
412 res += re.escape(c)
413 else:
413 else:
414 res += re.escape(c)
414 res += re.escape(c)
415 return head + res + tail
415 return head + res + tail
416
416
417 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
417 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
418
418
419 def pathto(root, n1, n2):
419 def pathto(root, n1, n2):
420 '''return the relative path from one place to another.
420 '''return the relative path from one place to another.
421 root should use os.sep to separate directories
421 root should use os.sep to separate directories
422 n1 should use os.sep to separate directories
422 n1 should use os.sep to separate directories
423 n2 should use "/" to separate directories
423 n2 should use "/" to separate directories
424 returns an os.sep-separated path.
424 returns an os.sep-separated path.
425
425
426 If n1 is a relative path, it's assumed it's
426 If n1 is a relative path, it's assumed it's
427 relative to root.
427 relative to root.
428 n2 should always be relative to root.
428 n2 should always be relative to root.
429 '''
429 '''
430 if not n1: return localpath(n2)
430 if not n1: return localpath(n2)
431 if os.path.isabs(n1):
431 if os.path.isabs(n1):
432 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
432 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
433 return os.path.join(root, localpath(n2))
433 return os.path.join(root, localpath(n2))
434 n2 = '/'.join((pconvert(root), n2))
434 n2 = '/'.join((pconvert(root), n2))
435 a, b = splitpath(n1), n2.split('/')
435 a, b = splitpath(n1), n2.split('/')
436 a.reverse()
436 a.reverse()
437 b.reverse()
437 b.reverse()
438 while a and b and a[-1] == b[-1]:
438 while a and b and a[-1] == b[-1]:
439 a.pop()
439 a.pop()
440 b.pop()
440 b.pop()
441 b.reverse()
441 b.reverse()
442 return os.sep.join((['..'] * len(a)) + b) or '.'
442 return os.sep.join((['..'] * len(a)) + b) or '.'
443
443
444 def canonpath(root, cwd, myname):
444 def canonpath(root, cwd, myname):
445 """return the canonical path of myname, given cwd and root"""
445 """return the canonical path of myname, given cwd and root"""
446 if root == os.sep:
446 if root == os.sep:
447 rootsep = os.sep
447 rootsep = os.sep
448 elif endswithsep(root):
448 elif endswithsep(root):
449 rootsep = root
449 rootsep = root
450 else:
450 else:
451 rootsep = root + os.sep
451 rootsep = root + os.sep
452 name = myname
452 name = myname
453 if not os.path.isabs(name):
453 if not os.path.isabs(name):
454 name = os.path.join(root, cwd, name)
454 name = os.path.join(root, cwd, name)
455 name = os.path.normpath(name)
455 name = os.path.normpath(name)
456 audit_path = path_auditor(root)
456 audit_path = path_auditor(root)
457 if name != rootsep and name.startswith(rootsep):
457 if name != rootsep and name.startswith(rootsep):
458 name = name[len(rootsep):]
458 name = name[len(rootsep):]
459 audit_path(name)
459 audit_path(name)
460 return pconvert(name)
460 return pconvert(name)
461 elif name == root:
461 elif name == root:
462 return ''
462 return ''
463 else:
463 else:
464 # Determine whether `name' is in the hierarchy at or beneath `root',
464 # Determine whether `name' is in the hierarchy at or beneath `root',
465 # by iterating name=dirname(name) until that causes no change (can't
465 # by iterating name=dirname(name) until that causes no change (can't
466 # check name == '/', because that doesn't work on windows). For each
466 # check name == '/', because that doesn't work on windows). For each
467 # `name', compare dev/inode numbers. If they match, the list `rel'
467 # `name', compare dev/inode numbers. If they match, the list `rel'
468 # holds the reversed list of components making up the relative file
468 # holds the reversed list of components making up the relative file
469 # name we want.
469 # name we want.
470 root_st = os.stat(root)
470 root_st = os.stat(root)
471 rel = []
471 rel = []
472 while True:
472 while True:
473 try:
473 try:
474 name_st = os.stat(name)
474 name_st = os.stat(name)
475 except OSError:
475 except OSError:
476 break
476 break
477 if samestat(name_st, root_st):
477 if samestat(name_st, root_st):
478 if not rel:
478 if not rel:
479 # name was actually the same as root (maybe a symlink)
479 # name was actually the same as root (maybe a symlink)
480 return ''
480 return ''
481 rel.reverse()
481 rel.reverse()
482 name = os.path.join(*rel)
482 name = os.path.join(*rel)
483 audit_path(name)
483 audit_path(name)
484 return pconvert(name)
484 return pconvert(name)
485 dirname, basename = os.path.split(name)
485 dirname, basename = os.path.split(name)
486 rel.append(basename)
486 rel.append(basename)
487 if dirname == name:
487 if dirname == name:
488 break
488 break
489 name = dirname
489 name = dirname
490
490
491 raise Abort('%s not under root' % myname)
491 raise Abort('%s not under root' % myname)
492
492
493 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None, dflt_pat='glob'):
493 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None, dflt_pat='glob'):
494 """build a function to match a set of file patterns
494 """build a function to match a set of file patterns
495
495
496 arguments:
496 arguments:
497 canonroot - the canonical root of the tree you're matching against
497 canonroot - the canonical root of the tree you're matching against
498 cwd - the current working directory, if relevant
498 cwd - the current working directory, if relevant
499 names - patterns to find
499 names - patterns to find
500 inc - patterns to include
500 inc - patterns to include
501 exc - patterns to exclude
501 exc - patterns to exclude
502 dflt_pat - if a pattern in names has no explicit type, assume this one
502 dflt_pat - if a pattern in names has no explicit type, assume this one
503 src - where these patterns came from (e.g. .hgignore)
503 src - where these patterns came from (e.g. .hgignore)
504
504
505 a pattern is one of:
505 a pattern is one of:
506 'glob:<glob>' - a glob relative to cwd
506 'glob:<glob>' - a glob relative to cwd
507 're:<regexp>' - a regular expression
507 're:<regexp>' - a regular expression
508 'path:<path>' - a path relative to canonroot
508 'path:<path>' - a path relative to canonroot
509 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
509 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
510 'relpath:<path>' - a path relative to cwd
510 'relpath:<path>' - a path relative to cwd
511 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
511 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
512 '<something>' - one of the cases above, selected by the dflt_pat argument
512 '<something>' - one of the cases above, selected by the dflt_pat argument
513
513
514 returns:
514 returns:
515 a 3-tuple containing
515 a 3-tuple containing
516 - list of roots (places where one should start a recursive walk of the fs);
516 - list of roots (places where one should start a recursive walk of the fs);
517 this often matches the explicit non-pattern names passed in, but also
517 this often matches the explicit non-pattern names passed in, but also
518 includes the initial part of glob: patterns that has no glob characters
518 includes the initial part of glob: patterns that has no glob characters
519 - a bool match(filename) function
519 - a bool match(filename) function
520 - a bool indicating if any patterns were passed in
520 - a bool indicating if any patterns were passed in
521 """
521 """
522
522
523 # a common case: no patterns at all
523 # a common case: no patterns at all
524 if not names and not inc and not exc:
524 if not names and not inc and not exc:
525 return [], always, False
525 return [], always, False
526
526
527 def contains_glob(name):
527 def contains_glob(name):
528 for c in name:
528 for c in name:
529 if c in _globchars: return True
529 if c in _globchars: return True
530 return False
530 return False
531
531
532 def regex(kind, name, tail):
532 def regex(kind, name, tail):
533 '''convert a pattern into a regular expression'''
533 '''convert a pattern into a regular expression'''
534 if not name:
534 if not name:
535 return ''
535 return ''
536 if kind == 're':
536 if kind == 're':
537 return name
537 return name
538 elif kind == 'path':
538 elif kind == 'path':
539 return '^' + re.escape(name) + '(?:/|$)'
539 return '^' + re.escape(name) + '(?:/|$)'
540 elif kind == 'relglob':
540 elif kind == 'relglob':
541 return globre(name, '(?:|.*/)', tail)
541 return globre(name, '(?:|.*/)', tail)
542 elif kind == 'relpath':
542 elif kind == 'relpath':
543 return re.escape(name) + '(?:/|$)'
543 return re.escape(name) + '(?:/|$)'
544 elif kind == 'relre':
544 elif kind == 'relre':
545 if name.startswith('^'):
545 if name.startswith('^'):
546 return name
546 return name
547 return '.*' + name
547 return '.*' + name
548 return globre(name, '', tail)
548 return globre(name, '', tail)
549
549
550 def matchfn(pats, tail):
550 def matchfn(pats, tail):
551 """build a matching function from a set of patterns"""
551 """build a matching function from a set of patterns"""
552 if not pats:
552 if not pats:
553 return
553 return
554 try:
554 try:
555 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
555 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
556 if len(pat) > 20000:
556 if len(pat) > 20000:
557 raise OverflowError()
557 raise OverflowError()
558 return re.compile(pat).match
558 return re.compile(pat).match
559 except OverflowError:
559 except OverflowError:
560 # We're using a Python with a tiny regex engine and we
560 # We're using a Python with a tiny regex engine and we
561 # made it explode, so we'll divide the pattern list in two
561 # made it explode, so we'll divide the pattern list in two
562 # until it works
562 # until it works
563 l = len(pats)
563 l = len(pats)
564 if l < 2:
564 if l < 2:
565 raise
565 raise
566 a, b = matchfn(pats[:l//2], tail), matchfn(pats[l//2:], tail)
566 a, b = matchfn(pats[:l//2], tail), matchfn(pats[l//2:], tail)
567 return lambda s: a(s) or b(s)
567 return lambda s: a(s) or b(s)
568 except re.error:
568 except re.error:
569 for k, p in pats:
569 for k, p in pats:
570 try:
570 try:
571 re.compile('(?:%s)' % regex(k, p, tail))
571 re.compile('(?:%s)' % regex(k, p, tail))
572 except re.error:
572 except re.error:
573 if src:
573 if src:
574 raise Abort("%s: invalid pattern (%s): %s" %
574 raise Abort("%s: invalid pattern (%s): %s" %
575 (src, k, p))
575 (src, k, p))
576 else:
576 else:
577 raise Abort("invalid pattern (%s): %s" % (k, p))
577 raise Abort("invalid pattern (%s): %s" % (k, p))
578 raise Abort("invalid pattern")
578 raise Abort("invalid pattern")
579
579
580 def globprefix(pat):
580 def globprefix(pat):
581 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
581 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
582 root = []
582 root = []
583 for p in pat.split('/'):
583 for p in pat.split('/'):
584 if contains_glob(p): break
584 if contains_glob(p): break
585 root.append(p)
585 root.append(p)
586 return '/'.join(root) or '.'
586 return '/'.join(root) or '.'
587
587
588 def normalizepats(names, default):
588 def normalizepats(names, default):
589 pats = []
589 pats = []
590 roots = []
590 roots = []
591 anypats = False
591 anypats = False
592 for kind, name in [patkind(p, default) for p in names]:
592 for kind, name in [patkind(p, default) for p in names]:
593 if kind in ('glob', 'relpath'):
593 if kind in ('glob', 'relpath'):
594 name = canonpath(canonroot, cwd, name)
594 name = canonpath(canonroot, cwd, name)
595 elif kind in ('relglob', 'path'):
595 elif kind in ('relglob', 'path'):
596 name = normpath(name)
596 name = normpath(name)
597
597
598 pats.append((kind, name))
598 pats.append((kind, name))
599
599
600 if kind in ('glob', 're', 'relglob', 'relre'):
600 if kind in ('glob', 're', 'relglob', 'relre'):
601 anypats = True
601 anypats = True
602
602
603 if kind == 'glob':
603 if kind == 'glob':
604 root = globprefix(name)
604 root = globprefix(name)
605 roots.append(root)
605 roots.append(root)
606 elif kind in ('relpath', 'path'):
606 elif kind in ('relpath', 'path'):
607 roots.append(name or '.')
607 roots.append(name or '.')
608 elif kind == 'relglob':
608 elif kind == 'relglob':
609 roots.append('.')
609 roots.append('.')
610 return roots, pats, anypats
610 return roots, pats, anypats
611
611
612 roots, pats, anypats = normalizepats(names, dflt_pat)
612 roots, pats, anypats = normalizepats(names, dflt_pat)
613
613
614 patmatch = matchfn(pats, '$') or always
614 patmatch = matchfn(pats, '$') or always
615 incmatch = always
615 incmatch = always
616 if inc:
616 if inc:
617 dummy, inckinds, dummy = normalizepats(inc, 'glob')
617 dummy, inckinds, dummy = normalizepats(inc, 'glob')
618 incmatch = matchfn(inckinds, '(?:/|$)')
618 incmatch = matchfn(inckinds, '(?:/|$)')
619 excmatch = never
619 excmatch = never
620 if exc:
620 if exc:
621 dummy, exckinds, dummy = normalizepats(exc, 'glob')
621 dummy, exckinds, dummy = normalizepats(exc, 'glob')
622 excmatch = matchfn(exckinds, '(?:/|$)')
622 excmatch = matchfn(exckinds, '(?:/|$)')
623
623
624 if not names and inc and not exc:
624 if not names and inc and not exc:
625 # common case: hgignore patterns
625 # common case: hgignore patterns
626 match = incmatch
626 match = incmatch
627 else:
627 else:
628 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
628 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
629
629
630 return (roots, match, (inc or exc or anypats) and True)
630 return (roots, match, (inc or exc or anypats) and True)
631
631
632 _hgexecutable = None
632 _hgexecutable = None
633
633
634 def main_is_frozen():
634 def main_is_frozen():
635 """return True if we are a frozen executable.
635 """return True if we are a frozen executable.
636
636
637 The code supports py2exe (most common, Windows only) and tools/freeze
637 The code supports py2exe (most common, Windows only) and tools/freeze
638 (portable, not much used).
638 (portable, not much used).
639 """
639 """
640 return (hasattr(sys, "frozen") or # new py2exe
640 return (hasattr(sys, "frozen") or # new py2exe
641 hasattr(sys, "importers") or # old py2exe
641 hasattr(sys, "importers") or # old py2exe
642 imp.is_frozen("__main__")) # tools/freeze
642 imp.is_frozen("__main__")) # tools/freeze
643
643
644 def hgexecutable():
644 def hgexecutable():
645 """return location of the 'hg' executable.
645 """return location of the 'hg' executable.
646
646
647 Defaults to $HG or 'hg' in the search path.
647 Defaults to $HG or 'hg' in the search path.
648 """
648 """
649 if _hgexecutable is None:
649 if _hgexecutable is None:
650 hg = os.environ.get('HG')
650 hg = os.environ.get('HG')
651 if hg:
651 if hg:
652 set_hgexecutable(hg)
652 set_hgexecutable(hg)
653 elif main_is_frozen():
653 elif main_is_frozen():
654 set_hgexecutable(sys.executable)
654 set_hgexecutable(sys.executable)
655 else:
655 else:
656 set_hgexecutable(find_exe('hg') or 'hg')
656 set_hgexecutable(find_exe('hg') or 'hg')
657 return _hgexecutable
657 return _hgexecutable
658
658
659 def set_hgexecutable(path):
659 def set_hgexecutable(path):
660 """set location of the 'hg' executable"""
660 """set location of the 'hg' executable"""
661 global _hgexecutable
661 global _hgexecutable
662 _hgexecutable = path
662 _hgexecutable = path
663
663
664 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
664 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
665 '''enhanced shell command execution.
665 '''enhanced shell command execution.
666 run with environment maybe modified, maybe in different dir.
666 run with environment maybe modified, maybe in different dir.
667
667
668 if command fails and onerr is None, return status. if ui object,
668 if command fails and onerr is None, return status. if ui object,
669 print error message and return status, else raise onerr object as
669 print error message and return status, else raise onerr object as
670 exception.'''
670 exception.'''
671 def py2shell(val):
671 def py2shell(val):
672 'convert python object into string that is useful to shell'
672 'convert python object into string that is useful to shell'
673 if val in (None, False):
673 if val in (None, False):
674 return '0'
674 return '0'
675 if val == True:
675 if val == True:
676 return '1'
676 return '1'
677 return str(val)
677 return str(val)
678 oldenv = {}
678 oldenv = {}
679 for k in environ:
679 for k in environ:
680 oldenv[k] = os.environ.get(k)
680 oldenv[k] = os.environ.get(k)
681 if cwd is not None:
681 if cwd is not None:
682 oldcwd = os.getcwd()
682 oldcwd = os.getcwd()
683 origcmd = cmd
683 origcmd = cmd
684 if os.name == 'nt':
684 if os.name == 'nt':
685 cmd = '"%s"' % cmd
685 cmd = '"%s"' % cmd
686 try:
686 try:
687 for k, v in environ.iteritems():
687 for k, v in environ.iteritems():
688 os.environ[k] = py2shell(v)
688 os.environ[k] = py2shell(v)
689 os.environ['HG'] = hgexecutable()
689 os.environ['HG'] = hgexecutable()
690 if cwd is not None and oldcwd != cwd:
690 if cwd is not None and oldcwd != cwd:
691 os.chdir(cwd)
691 os.chdir(cwd)
692 rc = os.system(cmd)
692 rc = os.system(cmd)
693 if sys.platform == 'OpenVMS' and rc & 1:
693 if sys.platform == 'OpenVMS' and rc & 1:
694 rc = 0
694 rc = 0
695 if rc and onerr:
695 if rc and onerr:
696 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
696 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
697 explain_exit(rc)[0])
697 explain_exit(rc)[0])
698 if errprefix:
698 if errprefix:
699 errmsg = '%s: %s' % (errprefix, errmsg)
699 errmsg = '%s: %s' % (errprefix, errmsg)
700 try:
700 try:
701 onerr.warn(errmsg + '\n')
701 onerr.warn(errmsg + '\n')
702 except AttributeError:
702 except AttributeError:
703 raise onerr(errmsg)
703 raise onerr(errmsg)
704 return rc
704 return rc
705 finally:
705 finally:
706 for k, v in oldenv.iteritems():
706 for k, v in oldenv.iteritems():
707 if v is None:
707 if v is None:
708 del os.environ[k]
708 del os.environ[k]
709 else:
709 else:
710 os.environ[k] = v
710 os.environ[k] = v
711 if cwd is not None and oldcwd != cwd:
711 if cwd is not None and oldcwd != cwd:
712 os.chdir(oldcwd)
712 os.chdir(oldcwd)
713
713
714 def checksignature(func):
714 def checksignature(func):
715 '''wrap a function with code to check for calling errors'''
715 '''wrap a function with code to check for calling errors'''
716 def check(*args, **kwargs):
716 def check(*args, **kwargs):
717 try:
717 try:
718 return func(*args, **kwargs)
718 return func(*args, **kwargs)
719 except TypeError:
719 except TypeError:
720 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
720 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
721 raise error.SignatureError
721 raise error.SignatureError
722 raise
722 raise
723
723
724 return check
724 return check
725
725
726 # os.path.lexists is not available on python2.3
726 # os.path.lexists is not available on python2.3
727 def lexists(filename):
727 def lexists(filename):
728 "test whether a file with this name exists. does not follow symlinks"
728 "test whether a file with this name exists. does not follow symlinks"
729 try:
729 try:
730 os.lstat(filename)
730 os.lstat(filename)
731 except:
731 except:
732 return False
732 return False
733 return True
733 return True
734
734
735 def rename(src, dst):
735 def rename(src, dst):
736 """forcibly rename a file"""
736 """forcibly rename a file"""
737 try:
737 try:
738 os.rename(src, dst)
738 os.rename(src, dst)
739 except OSError, err: # FIXME: check err (EEXIST ?)
739 except OSError, err: # FIXME: check err (EEXIST ?)
740 # on windows, rename to existing file is not allowed, so we
740 # on windows, rename to existing file is not allowed, so we
741 # must delete destination first. but if file is open, unlink
741 # must delete destination first. but if file is open, unlink
742 # schedules it for delete but does not delete it. rename
742 # schedules it for delete but does not delete it. rename
743 # happens immediately even for open files, so we rename
743 # happens immediately even for open files, so we rename
744 # destination to a temporary name, then delete that. then
744 # destination to a temporary name, then delete that. then
745 # rename is safe to do.
745 # rename is safe to do.
746 temp = dst + "-force-rename"
746 temp = dst + "-force-rename"
747 os.rename(dst, temp)
747 os.rename(dst, temp)
748 os.unlink(temp)
748 os.unlink(temp)
749 os.rename(src, dst)
749 os.rename(src, dst)
750
750
751 def unlink(f):
751 def unlink(f):
752 """unlink and remove the directory if it is empty"""
752 """unlink and remove the directory if it is empty"""
753 os.unlink(f)
753 os.unlink(f)
754 # try removing directories that might now be empty
754 # try removing directories that might now be empty
755 try:
755 try:
756 os.removedirs(os.path.dirname(f))
756 os.removedirs(os.path.dirname(f))
757 except OSError:
757 except OSError:
758 pass
758 pass
759
759
760 def copyfile(src, dest):
760 def copyfile(src, dest):
761 "copy a file, preserving mode and atime/mtime"
761 "copy a file, preserving mode and atime/mtime"
762 if os.path.islink(src):
762 if os.path.islink(src):
763 try:
763 try:
764 os.unlink(dest)
764 os.unlink(dest)
765 except:
765 except:
766 pass
766 pass
767 os.symlink(os.readlink(src), dest)
767 os.symlink(os.readlink(src), dest)
768 else:
768 else:
769 try:
769 try:
770 shutil.copyfile(src, dest)
770 shutil.copyfile(src, dest)
771 shutil.copystat(src, dest)
771 shutil.copystat(src, dest)
772 except shutil.Error, inst:
772 except shutil.Error, inst:
773 raise Abort(str(inst))
773 raise Abort(str(inst))
774
774
775 def copyfiles(src, dst, hardlink=None):
775 def copyfiles(src, dst, hardlink=None):
776 """Copy a directory tree using hardlinks if possible"""
776 """Copy a directory tree using hardlinks if possible"""
777
777
778 if hardlink is None:
778 if hardlink is None:
779 hardlink = (os.stat(src).st_dev ==
779 hardlink = (os.stat(src).st_dev ==
780 os.stat(os.path.dirname(dst)).st_dev)
780 os.stat(os.path.dirname(dst)).st_dev)
781
781
782 if os.path.isdir(src):
782 if os.path.isdir(src):
783 os.mkdir(dst)
783 os.mkdir(dst)
784 for name, kind in osutil.listdir(src):
784 for name, kind in osutil.listdir(src):
785 srcname = os.path.join(src, name)
785 srcname = os.path.join(src, name)
786 dstname = os.path.join(dst, name)
786 dstname = os.path.join(dst, name)
787 copyfiles(srcname, dstname, hardlink)
787 copyfiles(srcname, dstname, hardlink)
788 else:
788 else:
789 if hardlink:
789 if hardlink:
790 try:
790 try:
791 os_link(src, dst)
791 os_link(src, dst)
792 except (IOError, OSError):
792 except (IOError, OSError):
793 hardlink = False
793 hardlink = False
794 shutil.copy(src, dst)
794 shutil.copy(src, dst)
795 else:
795 else:
796 shutil.copy(src, dst)
796 shutil.copy(src, dst)
797
797
798 class path_auditor(object):
798 class path_auditor(object):
799 '''ensure that a filesystem path contains no banned components.
799 '''ensure that a filesystem path contains no banned components.
800 the following properties of a path are checked:
800 the following properties of a path are checked:
801
801
802 - under top-level .hg
802 - under top-level .hg
803 - starts at the root of a windows drive
803 - starts at the root of a windows drive
804 - contains ".."
804 - contains ".."
805 - traverses a symlink (e.g. a/symlink_here/b)
805 - traverses a symlink (e.g. a/symlink_here/b)
806 - inside a nested repository'''
806 - inside a nested repository'''
807
807
808 def __init__(self, root):
808 def __init__(self, root):
809 self.audited = set()
809 self.audited = set()
810 self.auditeddir = set()
810 self.auditeddir = set()
811 self.root = root
811 self.root = root
812
812
813 def __call__(self, path):
813 def __call__(self, path):
814 if path in self.audited:
814 if path in self.audited:
815 return
815 return
816 normpath = os.path.normcase(path)
816 normpath = os.path.normcase(path)
817 parts = splitpath(normpath)
817 parts = splitpath(normpath)
818 if (os.path.splitdrive(path)[0]
818 if (os.path.splitdrive(path)[0]
819 or parts[0].lower() in ('.hg', '.hg.', '')
819 or parts[0].lower() in ('.hg', '.hg.', '')
820 or os.pardir in parts):
820 or os.pardir in parts):
821 raise Abort(_("path contains illegal component: %s") % path)
821 raise Abort(_("path contains illegal component: %s") % path)
822 if '.hg' in path.lower():
822 if '.hg' in path.lower():
823 lparts = [p.lower() for p in parts]
823 lparts = [p.lower() for p in parts]
824 for p in '.hg', '.hg.':
824 for p in '.hg', '.hg.':
825 if p in lparts[1:]:
825 if p in lparts[1:]:
826 pos = lparts.index(p)
826 pos = lparts.index(p)
827 base = os.path.join(*parts[:pos])
827 base = os.path.join(*parts[:pos])
828 raise Abort(_('path %r is inside repo %r') % (path, base))
828 raise Abort(_('path %r is inside repo %r') % (path, base))
829 def check(prefix):
829 def check(prefix):
830 curpath = os.path.join(self.root, prefix)
830 curpath = os.path.join(self.root, prefix)
831 try:
831 try:
832 st = os.lstat(curpath)
832 st = os.lstat(curpath)
833 except OSError, err:
833 except OSError, err:
834 # EINVAL can be raised as invalid path syntax under win32.
834 # EINVAL can be raised as invalid path syntax under win32.
835 # They must be ignored for patterns can be checked too.
835 # They must be ignored for patterns can be checked too.
836 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
836 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
837 raise
837 raise
838 else:
838 else:
839 if stat.S_ISLNK(st.st_mode):
839 if stat.S_ISLNK(st.st_mode):
840 raise Abort(_('path %r traverses symbolic link %r') %
840 raise Abort(_('path %r traverses symbolic link %r') %
841 (path, prefix))
841 (path, prefix))
842 elif (stat.S_ISDIR(st.st_mode) and
842 elif (stat.S_ISDIR(st.st_mode) and
843 os.path.isdir(os.path.join(curpath, '.hg'))):
843 os.path.isdir(os.path.join(curpath, '.hg'))):
844 raise Abort(_('path %r is inside repo %r') %
844 raise Abort(_('path %r is inside repo %r') %
845 (path, prefix))
845 (path, prefix))
846 parts.pop()
846 parts.pop()
847 prefixes = []
847 prefixes = []
848 for n in range(len(parts)):
848 for n in range(len(parts)):
849 prefix = os.sep.join(parts)
849 prefix = os.sep.join(parts)
850 if prefix in self.auditeddir:
850 if prefix in self.auditeddir:
851 break
851 break
852 check(prefix)
852 check(prefix)
853 prefixes.append(prefix)
853 prefixes.append(prefix)
854 parts.pop()
854 parts.pop()
855
855
856 self.audited.add(path)
856 self.audited.add(path)
857 # only add prefixes to the cache after checking everything: we don't
857 # only add prefixes to the cache after checking everything: we don't
858 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
858 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
859 self.auditeddir.update(prefixes)
859 self.auditeddir.update(prefixes)
860
860
861 def _makelock_file(info, pathname):
861 def _makelock_file(info, pathname):
862 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
862 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
863 os.write(ld, info)
863 os.write(ld, info)
864 os.close(ld)
864 os.close(ld)
865
865
866 def _readlock_file(pathname):
866 def _readlock_file(pathname):
867 return posixfile(pathname).read()
867 return posixfile(pathname).read()
868
868
869 def nlinks(pathname):
869 def nlinks(pathname):
870 """Return number of hardlinks for the given file."""
870 """Return number of hardlinks for the given file."""
871 return os.lstat(pathname).st_nlink
871 return os.lstat(pathname).st_nlink
872
872
873 if hasattr(os, 'link'):
873 if hasattr(os, 'link'):
874 os_link = os.link
874 os_link = os.link
875 else:
875 else:
876 def os_link(src, dst):
876 def os_link(src, dst):
877 raise OSError(0, _("Hardlinks not supported"))
877 raise OSError(0, _("Hardlinks not supported"))
878
878
879 def fstat(fp):
879 def fstat(fp):
880 '''stat file object that may not have fileno method.'''
880 '''stat file object that may not have fileno method.'''
881 try:
881 try:
882 return os.fstat(fp.fileno())
882 return os.fstat(fp.fileno())
883 except AttributeError:
883 except AttributeError:
884 return os.stat(fp.name)
884 return os.stat(fp.name)
885
885
886 posixfile = file
886 posixfile = file
887
887
888 def openhardlinks():
888 def openhardlinks():
889 '''return true if it is safe to hold open file handles to hardlinks'''
889 '''return true if it is safe to hold open file handles to hardlinks'''
890 return True
890 return True
891
891
892 def _statfiles(files):
892 def _statfiles(files):
893 'Stat each file in files and yield stat or None if file does not exist.'
893 'Stat each file in files and yield stat or None if file does not exist.'
894 lstat = os.lstat
894 lstat = os.lstat
895 for nf in files:
895 for nf in files:
896 try:
896 try:
897 st = lstat(nf)
897 st = lstat(nf)
898 except OSError, err:
898 except OSError, err:
899 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
899 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
900 raise
900 raise
901 st = None
901 st = None
902 yield st
902 yield st
903
903
904 def _statfiles_clustered(files):
904 def _statfiles_clustered(files):
905 '''Stat each file in files and yield stat or None if file does not exist.
905 '''Stat each file in files and yield stat or None if file does not exist.
906 Cluster and cache stat per directory to minimize number of OS stat calls.'''
906 Cluster and cache stat per directory to minimize number of OS stat calls.'''
907 ncase = os.path.normcase
907 ncase = os.path.normcase
908 sep = os.sep
908 sep = os.sep
909 dircache = {} # dirname -> filename -> status | None if file does not exist
909 dircache = {} # dirname -> filename -> status | None if file does not exist
910 for nf in files:
910 for nf in files:
911 nf = ncase(nf)
911 nf = ncase(nf)
912 pos = nf.rfind(sep)
912 pos = nf.rfind(sep)
913 if pos == -1:
913 if pos == -1:
914 dir, base = '.', nf
914 dir, base = '.', nf
915 else:
915 else:
916 dir, base = nf[:pos+1], nf[pos+1:]
916 dir, base = nf[:pos+1], nf[pos+1:]
917 cache = dircache.get(dir, None)
917 cache = dircache.get(dir, None)
918 if cache is None:
918 if cache is None:
919 try:
919 try:
920 dmap = dict([(ncase(n), s)
920 dmap = dict([(ncase(n), s)
921 for n, k, s in osutil.listdir(dir, True)])
921 for n, k, s in osutil.listdir(dir, True)])
922 except OSError, err:
922 except OSError, err:
923 # handle directory not found in Python version prior to 2.5
923 # handle directory not found in Python version prior to 2.5
924 # Python <= 2.4 returns native Windows code 3 in errno
924 # Python <= 2.4 returns native Windows code 3 in errno
925 # Python >= 2.5 returns ENOENT and adds winerror field
925 # Python >= 2.5 returns ENOENT and adds winerror field
926 # EINVAL is raised if dir is not a directory.
926 # EINVAL is raised if dir is not a directory.
927 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
927 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
928 errno.ENOTDIR):
928 errno.ENOTDIR):
929 raise
929 raise
930 dmap = {}
930 dmap = {}
931 cache = dircache.setdefault(dir, dmap)
931 cache = dircache.setdefault(dir, dmap)
932 yield cache.get(base, None)
932 yield cache.get(base, None)
933
933
934 if sys.platform == 'win32':
934 if sys.platform == 'win32':
935 statfiles = _statfiles_clustered
935 statfiles = _statfiles_clustered
936 else:
936 else:
937 statfiles = _statfiles
937 statfiles = _statfiles
938
938
939 getuser_fallback = None
939 getuser_fallback = None
940
940
941 def getuser():
941 def getuser():
942 '''return name of current user'''
942 '''return name of current user'''
943 try:
943 try:
944 return getpass.getuser()
944 return getpass.getuser()
945 except ImportError:
945 except ImportError:
946 # import of pwd will fail on windows - try fallback
946 # import of pwd will fail on windows - try fallback
947 if getuser_fallback:
947 if getuser_fallback:
948 return getuser_fallback()
948 return getuser_fallback()
949 # raised if win32api not available
949 # raised if win32api not available
950 raise Abort(_('user name not available - set USERNAME '
950 raise Abort(_('user name not available - set USERNAME '
951 'environment variable'))
951 'environment variable'))
952
952
953 def username(uid=None):
953 def username(uid=None):
954 """Return the name of the user with the given uid.
954 """Return the name of the user with the given uid.
955
955
956 If uid is None, return the name of the current user."""
956 If uid is None, return the name of the current user."""
957 try:
957 try:
958 import pwd
958 import pwd
959 if uid is None:
959 if uid is None:
960 uid = os.getuid()
960 uid = os.getuid()
961 try:
961 try:
962 return pwd.getpwuid(uid)[0]
962 return pwd.getpwuid(uid)[0]
963 except KeyError:
963 except KeyError:
964 return str(uid)
964 return str(uid)
965 except ImportError:
965 except ImportError:
966 return None
966 return None
967
967
968 def groupname(gid=None):
968 def groupname(gid=None):
969 """Return the name of the group with the given gid.
969 """Return the name of the group with the given gid.
970
970
971 If gid is None, return the name of the current group."""
971 If gid is None, return the name of the current group."""
972 try:
972 try:
973 import grp
973 import grp
974 if gid is None:
974 if gid is None:
975 gid = os.getgid()
975 gid = os.getgid()
976 try:
976 try:
977 return grp.getgrgid(gid)[0]
977 return grp.getgrgid(gid)[0]
978 except KeyError:
978 except KeyError:
979 return str(gid)
979 return str(gid)
980 except ImportError:
980 except ImportError:
981 return None
981 return None
982
982
983 # File system features
983 # File system features
984
984
985 def checkcase(path):
985 def checkcase(path):
986 """
986 """
987 Check whether the given path is on a case-sensitive filesystem
987 Check whether the given path is on a case-sensitive filesystem
988
988
989 Requires a path (like /foo/.hg) ending with a foldable final
989 Requires a path (like /foo/.hg) ending with a foldable final
990 directory component.
990 directory component.
991 """
991 """
992 s1 = os.stat(path)
992 s1 = os.stat(path)
993 d, b = os.path.split(path)
993 d, b = os.path.split(path)
994 p2 = os.path.join(d, b.upper())
994 p2 = os.path.join(d, b.upper())
995 if path == p2:
995 if path == p2:
996 p2 = os.path.join(d, b.lower())
996 p2 = os.path.join(d, b.lower())
997 try:
997 try:
998 s2 = os.stat(p2)
998 s2 = os.stat(p2)
999 if s2 == s1:
999 if s2 == s1:
1000 return False
1000 return False
1001 return True
1001 return True
1002 except:
1002 except:
1003 return True
1003 return True
1004
1004
1005 _fspathcache = {}
1005 _fspathcache = {}
1006 def fspath(name, root):
1006 def fspath(name, root):
1007 '''Get name in the case stored in the filesystem
1007 '''Get name in the case stored in the filesystem
1008
1008
1009 The name is either relative to root, or it is an absolute path starting
1009 The name is either relative to root, or it is an absolute path starting
1010 with root. Note that this function is unnecessary, and should not be
1010 with root. Note that this function is unnecessary, and should not be
1011 called, for case-sensitive filesystems (simply because it's expensive).
1011 called, for case-sensitive filesystems (simply because it's expensive).
1012 '''
1012 '''
1013 # If name is absolute, make it relative
1013 # If name is absolute, make it relative
1014 if name.lower().startswith(root.lower()):
1014 if name.lower().startswith(root.lower()):
1015 l = len(root)
1015 l = len(root)
1016 if name[l] == os.sep or name[l] == os.altsep:
1016 if name[l] == os.sep or name[l] == os.altsep:
1017 l = l + 1
1017 l = l + 1
1018 name = name[l:]
1018 name = name[l:]
1019
1019
1020 if not os.path.exists(os.path.join(root, name)):
1020 if not os.path.exists(os.path.join(root, name)):
1021 return None
1021 return None
1022
1022
1023 seps = os.sep
1023 seps = os.sep
1024 if os.altsep:
1024 if os.altsep:
1025 seps = seps + os.altsep
1025 seps = seps + os.altsep
1026 # Protect backslashes. This gets silly very quickly.
1026 # Protect backslashes. This gets silly very quickly.
1027 seps.replace('\\','\\\\')
1027 seps.replace('\\','\\\\')
1028 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
1028 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
1029 dir = os.path.normcase(os.path.normpath(root))
1029 dir = os.path.normcase(os.path.normpath(root))
1030 result = []
1030 result = []
1031 for part, sep in pattern.findall(name):
1031 for part, sep in pattern.findall(name):
1032 if sep:
1032 if sep:
1033 result.append(sep)
1033 result.append(sep)
1034 continue
1034 continue
1035
1035
1036 if dir not in _fspathcache:
1036 if dir not in _fspathcache:
1037 _fspathcache[dir] = os.listdir(dir)
1037 _fspathcache[dir] = os.listdir(dir)
1038 contents = _fspathcache[dir]
1038 contents = _fspathcache[dir]
1039
1039
1040 lpart = part.lower()
1040 lpart = part.lower()
1041 for n in contents:
1041 for n in contents:
1042 if n.lower() == lpart:
1042 if n.lower() == lpart:
1043 result.append(n)
1043 result.append(n)
1044 break
1044 break
1045 else:
1045 else:
1046 # Cannot happen, as the file exists!
1046 # Cannot happen, as the file exists!
1047 result.append(part)
1047 result.append(part)
1048 dir = os.path.join(dir, lpart)
1048 dir = os.path.join(dir, lpart)
1049
1049
1050 return ''.join(result)
1050 return ''.join(result)
1051
1051
1052 def checkexec(path):
1052 def checkexec(path):
1053 """
1053 """
1054 Check whether the given path is on a filesystem with UNIX-like exec flags
1054 Check whether the given path is on a filesystem with UNIX-like exec flags
1055
1055
1056 Requires a directory (like /foo/.hg)
1056 Requires a directory (like /foo/.hg)
1057 """
1057 """
1058
1058
1059 # VFAT on some Linux versions can flip mode but it doesn't persist
1059 # VFAT on some Linux versions can flip mode but it doesn't persist
1060 # a FS remount. Frequently we can detect it if files are created
1060 # a FS remount. Frequently we can detect it if files are created
1061 # with exec bit on.
1061 # with exec bit on.
1062
1062
1063 try:
1063 try:
1064 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
1064 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
1065 fh, fn = tempfile.mkstemp("", "", path)
1065 fh, fn = tempfile.mkstemp("", "", path)
1066 try:
1066 try:
1067 os.close(fh)
1067 os.close(fh)
1068 m = os.stat(fn).st_mode & 0777
1068 m = os.stat(fn).st_mode & 0777
1069 new_file_has_exec = m & EXECFLAGS
1069 new_file_has_exec = m & EXECFLAGS
1070 os.chmod(fn, m ^ EXECFLAGS)
1070 os.chmod(fn, m ^ EXECFLAGS)
1071 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
1071 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
1072 finally:
1072 finally:
1073 os.unlink(fn)
1073 os.unlink(fn)
1074 except (IOError, OSError):
1074 except (IOError, OSError):
1075 # we don't care, the user probably won't be able to commit anyway
1075 # we don't care, the user probably won't be able to commit anyway
1076 return False
1076 return False
1077 return not (new_file_has_exec or exec_flags_cannot_flip)
1077 return not (new_file_has_exec or exec_flags_cannot_flip)
1078
1078
1079 def checklink(path):
1079 def checklink(path):
1080 """check whether the given path is on a symlink-capable filesystem"""
1080 """check whether the given path is on a symlink-capable filesystem"""
1081 # mktemp is not racy because symlink creation will fail if the
1081 # mktemp is not racy because symlink creation will fail if the
1082 # file already exists
1082 # file already exists
1083 name = tempfile.mktemp(dir=path)
1083 name = tempfile.mktemp(dir=path)
1084 try:
1084 try:
1085 os.symlink(".", name)
1085 os.symlink(".", name)
1086 os.unlink(name)
1086 os.unlink(name)
1087 return True
1087 return True
1088 except (OSError, AttributeError):
1088 except (OSError, AttributeError):
1089 return False
1089 return False
1090
1090
1091 _umask = os.umask(0)
1091 _umask = os.umask(0)
1092 os.umask(_umask)
1092 os.umask(_umask)
1093
1093
1094 def needbinarypatch():
1094 def needbinarypatch():
1095 """return True if patches should be applied in binary mode by default."""
1095 """return True if patches should be applied in binary mode by default."""
1096 return os.name == 'nt'
1096 return os.name == 'nt'
1097
1097
1098 def endswithsep(path):
1098 def endswithsep(path):
1099 '''Check path ends with os.sep or os.altsep.'''
1099 '''Check path ends with os.sep or os.altsep.'''
1100 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1100 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1101
1101
1102 def splitpath(path):
1102 def splitpath(path):
1103 '''Split path by os.sep.
1103 '''Split path by os.sep.
1104 Note that this function does not use os.altsep because this is
1104 Note that this function does not use os.altsep because this is
1105 an alternative of simple "xxx.split(os.sep)".
1105 an alternative of simple "xxx.split(os.sep)".
1106 It is recommended to use os.path.normpath() before using this
1106 It is recommended to use os.path.normpath() before using this
1107 function if need.'''
1107 function if need.'''
1108 return path.split(os.sep)
1108 return path.split(os.sep)
1109
1109
1110 def gui():
1110 def gui():
1111 '''Are we running in a GUI?'''
1111 '''Are we running in a GUI?'''
1112 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
1112 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
1113
1113
1114 def lookup_reg(key, name=None, scope=None):
1114 def lookup_reg(key, name=None, scope=None):
1115 return None
1115 return None
1116
1116
1117 # Platform specific variants
1117 # Platform specific variants
1118 if os.name == 'nt':
1118 if os.name == 'nt':
1119 import msvcrt
1119 import msvcrt
1120 nulldev = 'NUL:'
1120 nulldev = 'NUL:'
1121
1121
1122 class winstdout:
1122 class winstdout:
1123 '''stdout on windows misbehaves if sent through a pipe'''
1123 '''stdout on windows misbehaves if sent through a pipe'''
1124
1124
1125 def __init__(self, fp):
1125 def __init__(self, fp):
1126 self.fp = fp
1126 self.fp = fp
1127
1127
1128 def __getattr__(self, key):
1128 def __getattr__(self, key):
1129 return getattr(self.fp, key)
1129 return getattr(self.fp, key)
1130
1130
1131 def close(self):
1131 def close(self):
1132 try:
1132 try:
1133 self.fp.close()
1133 self.fp.close()
1134 except: pass
1134 except: pass
1135
1135
1136 def write(self, s):
1136 def write(self, s):
1137 try:
1137 try:
1138 # This is workaround for "Not enough space" error on
1138 # This is workaround for "Not enough space" error on
1139 # writing large size of data to console.
1139 # writing large size of data to console.
1140 limit = 16000
1140 limit = 16000
1141 l = len(s)
1141 l = len(s)
1142 start = 0
1142 start = 0
1143 while start < l:
1143 while start < l:
1144 end = start + limit
1144 end = start + limit
1145 self.fp.write(s[start:end])
1145 self.fp.write(s[start:end])
1146 start = end
1146 start = end
1147 except IOError, inst:
1147 except IOError, inst:
1148 if inst.errno != 0: raise
1148 if inst.errno != 0: raise
1149 self.close()
1149 self.close()
1150 raise IOError(errno.EPIPE, 'Broken pipe')
1150 raise IOError(errno.EPIPE, 'Broken pipe')
1151
1151
1152 def flush(self):
1152 def flush(self):
1153 try:
1153 try:
1154 return self.fp.flush()
1154 return self.fp.flush()
1155 except IOError, inst:
1155 except IOError, inst:
1156 if inst.errno != errno.EINVAL: raise
1156 if inst.errno != errno.EINVAL: raise
1157 self.close()
1157 self.close()
1158 raise IOError(errno.EPIPE, 'Broken pipe')
1158 raise IOError(errno.EPIPE, 'Broken pipe')
1159
1159
1160 sys.stdout = winstdout(sys.stdout)
1160 sys.stdout = winstdout(sys.stdout)
1161
1161
1162 def _is_win_9x():
1162 def _is_win_9x():
1163 '''return true if run on windows 95, 98 or me.'''
1163 '''return true if run on windows 95, 98 or me.'''
1164 try:
1164 try:
1165 return sys.getwindowsversion()[3] == 1
1165 return sys.getwindowsversion()[3] == 1
1166 except AttributeError:
1166 except AttributeError:
1167 return 'command' in os.environ.get('comspec', '')
1167 return 'command' in os.environ.get('comspec', '')
1168
1168
1169 def openhardlinks():
1169 def openhardlinks():
1170 return not _is_win_9x and "win32api" in locals()
1170 return not _is_win_9x and "win32api" in locals()
1171
1171
1172 def system_rcpath():
1172 def system_rcpath():
1173 try:
1173 try:
1174 return system_rcpath_win32()
1174 return system_rcpath_win32()
1175 except:
1175 except:
1176 return [r'c:\mercurial\mercurial.ini']
1176 return [r'c:\mercurial\mercurial.ini']
1177
1177
1178 def user_rcpath():
1178 def user_rcpath():
1179 '''return os-specific hgrc search path to the user dir'''
1179 '''return os-specific hgrc search path to the user dir'''
1180 try:
1180 try:
1181 path = user_rcpath_win32()
1181 path = user_rcpath_win32()
1182 except:
1182 except:
1183 home = os.path.expanduser('~')
1183 home = os.path.expanduser('~')
1184 path = [os.path.join(home, 'mercurial.ini'),
1184 path = [os.path.join(home, 'mercurial.ini'),
1185 os.path.join(home, '.hgrc')]
1185 os.path.join(home, '.hgrc')]
1186 userprofile = os.environ.get('USERPROFILE')
1186 userprofile = os.environ.get('USERPROFILE')
1187 if userprofile:
1187 if userprofile:
1188 path.append(os.path.join(userprofile, 'mercurial.ini'))
1188 path.append(os.path.join(userprofile, 'mercurial.ini'))
1189 path.append(os.path.join(userprofile, '.hgrc'))
1189 path.append(os.path.join(userprofile, '.hgrc'))
1190 return path
1190 return path
1191
1191
1192 def parse_patch_output(output_line):
1192 def parse_patch_output(output_line):
1193 """parses the output produced by patch and returns the file name"""
1193 """parses the output produced by patch and returns the file name"""
1194 pf = output_line[14:]
1194 pf = output_line[14:]
1195 if pf[0] == '`':
1195 if pf[0] == '`':
1196 pf = pf[1:-1] # Remove the quotes
1196 pf = pf[1:-1] # Remove the quotes
1197 return pf
1197 return pf
1198
1198
1199 def sshargs(sshcmd, host, user, port):
1199 def sshargs(sshcmd, host, user, port):
1200 '''Build argument list for ssh or Plink'''
1200 '''Build argument list for ssh or Plink'''
1201 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
1201 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
1202 args = user and ("%s@%s" % (user, host)) or host
1202 args = user and ("%s@%s" % (user, host)) or host
1203 return port and ("%s %s %s" % (args, pflag, port)) or args
1203 return port and ("%s %s %s" % (args, pflag, port)) or args
1204
1204
1205 def testpid(pid):
1205 def testpid(pid):
1206 '''return False if pid dead, True if running or not known'''
1206 '''return False if pid dead, True if running or not known'''
1207 return True
1207 return True
1208
1208
1209 def set_flags(f, l, x):
1209 def set_flags(f, l, x):
1210 pass
1210 pass
1211
1211
1212 def set_binary(fd):
1212 def set_binary(fd):
1213 # When run without console, pipes may expose invalid
1213 # When run without console, pipes may expose invalid
1214 # fileno(), usually set to -1.
1214 # fileno(), usually set to -1.
1215 if hasattr(fd, 'fileno') and fd.fileno() >= 0:
1215 if hasattr(fd, 'fileno') and fd.fileno() >= 0:
1216 msvcrt.setmode(fd.fileno(), os.O_BINARY)
1216 msvcrt.setmode(fd.fileno(), os.O_BINARY)
1217
1217
1218 def pconvert(path):
1218 def pconvert(path):
1219 return '/'.join(splitpath(path))
1219 return '/'.join(splitpath(path))
1220
1220
1221 def localpath(path):
1221 def localpath(path):
1222 return path.replace('/', '\\')
1222 return path.replace('/', '\\')
1223
1223
1224 def normpath(path):
1224 def normpath(path):
1225 return pconvert(os.path.normpath(path))
1225 return pconvert(os.path.normpath(path))
1226
1226
1227 makelock = _makelock_file
1227 makelock = _makelock_file
1228 readlock = _readlock_file
1228 readlock = _readlock_file
1229
1229
1230 def samestat(s1, s2):
1230 def samestat(s1, s2):
1231 return False
1231 return False
1232
1232
1233 # A sequence of backslashes is special iff it precedes a double quote:
1233 # A sequence of backslashes is special iff it precedes a double quote:
1234 # - if there's an even number of backslashes, the double quote is not
1234 # - if there's an even number of backslashes, the double quote is not
1235 # quoted (i.e. it ends the quoted region)
1235 # quoted (i.e. it ends the quoted region)
1236 # - if there's an odd number of backslashes, the double quote is quoted
1236 # - if there's an odd number of backslashes, the double quote is quoted
1237 # - in both cases, every pair of backslashes is unquoted into a single
1237 # - in both cases, every pair of backslashes is unquoted into a single
1238 # backslash
1238 # backslash
1239 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
1239 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
1240 # So, to quote a string, we must surround it in double quotes, double
1240 # So, to quote a string, we must surround it in double quotes, double
1241 # the number of backslashes that preceed double quotes and add another
1241 # the number of backslashes that preceed double quotes and add another
1242 # backslash before every double quote (being careful with the double
1242 # backslash before every double quote (being careful with the double
1243 # quote we've appended to the end)
1243 # quote we've appended to the end)
1244 _quotere = None
1244 _quotere = None
1245 def shellquote(s):
1245 def shellquote(s):
1246 global _quotere
1246 global _quotere
1247 if _quotere is None:
1247 if _quotere is None:
1248 _quotere = re.compile(r'(\\*)("|\\$)')
1248 _quotere = re.compile(r'(\\*)("|\\$)')
1249 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
1249 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
1250
1250
1251 def quotecommand(cmd):
1251 def quotecommand(cmd):
1252 """Build a command string suitable for os.popen* calls."""
1252 """Build a command string suitable for os.popen* calls."""
1253 # The extra quotes are needed because popen* runs the command
1253 # The extra quotes are needed because popen* runs the command
1254 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
1254 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
1255 return '"' + cmd + '"'
1255 return '"' + cmd + '"'
1256
1256
1257 def popen(command, mode='r'):
1257 def popen(command, mode='r'):
1258 # Work around "popen spawned process may not write to stdout
1258 # Work around "popen spawned process may not write to stdout
1259 # under windows"
1259 # under windows"
1260 # http://bugs.python.org/issue1366
1260 # http://bugs.python.org/issue1366
1261 command += " 2> %s" % nulldev
1261 command += " 2> %s" % nulldev
1262 return os.popen(quotecommand(command), mode)
1262 return os.popen(quotecommand(command), mode)
1263
1263
1264 def explain_exit(code):
1264 def explain_exit(code):
1265 return _("exited with status %d") % code, code
1265 return _("exited with status %d") % code, code
1266
1266
1267 # if you change this stub into a real check, please try to implement the
1267 # if you change this stub into a real check, please try to implement the
1268 # username and groupname functions above, too.
1268 # username and groupname functions above, too.
1269 def isowner(fp, st=None):
1269 def isowner(fp, st=None):
1270 return True
1270 return True
1271
1271
1272 def find_exe(command):
1272 def find_exe(command):
1273 '''Find executable for command searching like cmd.exe does.
1273 '''Find executable for command searching like cmd.exe does.
1274 If command is a basename then PATH is searched for command.
1274 If command is a basename then PATH is searched for command.
1275 PATH isn't searched if command is an absolute or relative path.
1275 PATH isn't searched if command is an absolute or relative path.
1276 An extension from PATHEXT is found and added if not present.
1276 An extension from PATHEXT is found and added if not present.
1277 If command isn't found None is returned.'''
1277 If command isn't found None is returned.'''
1278 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
1278 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
1279 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
1279 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
1280 if os.path.splitext(command)[1].lower() in pathexts:
1280 if os.path.splitext(command)[1].lower() in pathexts:
1281 pathexts = ['']
1281 pathexts = ['']
1282
1282
1283 def findexisting(pathcommand):
1283 def findexisting(pathcommand):
1284 'Will append extension (if needed) and return existing file'
1284 'Will append extension (if needed) and return existing file'
1285 for ext in pathexts:
1285 for ext in pathexts:
1286 executable = pathcommand + ext
1286 executable = pathcommand + ext
1287 if os.path.exists(executable):
1287 if os.path.exists(executable):
1288 return executable
1288 return executable
1289 return None
1289 return None
1290
1290
1291 if os.sep in command:
1291 if os.sep in command:
1292 return findexisting(command)
1292 return findexisting(command)
1293
1293
1294 for path in os.environ.get('PATH', '').split(os.pathsep):
1294 for path in os.environ.get('PATH', '').split(os.pathsep):
1295 executable = findexisting(os.path.join(path, command))
1295 executable = findexisting(os.path.join(path, command))
1296 if executable is not None:
1296 if executable is not None:
1297 return executable
1297 return executable
1298 return None
1298 return None
1299
1299
1300 def set_signal_handler():
1300 def set_signal_handler():
1301 try:
1301 try:
1302 set_signal_handler_win32()
1302 set_signal_handler_win32()
1303 except NameError:
1303 except NameError:
1304 pass
1304 pass
1305
1305
1306 try:
1306 try:
1307 # override functions with win32 versions if possible
1307 # override functions with win32 versions if possible
1308 from util_win32 import *
1308 from util_win32 import *
1309 if not _is_win_9x():
1309 if not _is_win_9x():
1310 posixfile = posixfile_nt
1310 posixfile = posixfile_nt
1311 except ImportError:
1311 except ImportError:
1312 pass
1312 pass
1313
1313
1314 else:
1314 else:
1315 nulldev = '/dev/null'
1315 nulldev = '/dev/null'
1316
1316
1317 def rcfiles(path):
1317 def rcfiles(path):
1318 rcs = [os.path.join(path, 'hgrc')]
1318 rcs = [os.path.join(path, 'hgrc')]
1319 rcdir = os.path.join(path, 'hgrc.d')
1319 rcdir = os.path.join(path, 'hgrc.d')
1320 try:
1320 try:
1321 rcs.extend([os.path.join(rcdir, f)
1321 rcs.extend([os.path.join(rcdir, f)
1322 for f, kind in osutil.listdir(rcdir)
1322 for f, kind in osutil.listdir(rcdir)
1323 if f.endswith(".rc")])
1323 if f.endswith(".rc")])
1324 except OSError:
1324 except OSError:
1325 pass
1325 pass
1326 return rcs
1326 return rcs
1327
1327
1328 def system_rcpath():
1328 def system_rcpath():
1329 path = []
1329 path = []
1330 # old mod_python does not set sys.argv
1330 # old mod_python does not set sys.argv
1331 if len(getattr(sys, 'argv', [])) > 0:
1331 if len(getattr(sys, 'argv', [])) > 0:
1332 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
1332 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
1333 '/../etc/mercurial'))
1333 '/../etc/mercurial'))
1334 path.extend(rcfiles('/etc/mercurial'))
1334 path.extend(rcfiles('/etc/mercurial'))
1335 return path
1335 return path
1336
1336
1337 def user_rcpath():
1337 def user_rcpath():
1338 return [os.path.expanduser('~/.hgrc')]
1338 return [os.path.expanduser('~/.hgrc')]
1339
1339
1340 def parse_patch_output(output_line):
1340 def parse_patch_output(output_line):
1341 """parses the output produced by patch and returns the file name"""
1341 """parses the output produced by patch and returns the file name"""
1342 pf = output_line[14:]
1342 pf = output_line[14:]
1343 if os.sys.platform == 'OpenVMS':
1343 if os.sys.platform == 'OpenVMS':
1344 if pf[0] == '`':
1344 if pf[0] == '`':
1345 pf = pf[1:-1] # Remove the quotes
1345 pf = pf[1:-1] # Remove the quotes
1346 else:
1346 else:
1347 if pf.startswith("'") and pf.endswith("'") and " " in pf:
1347 if pf.startswith("'") and pf.endswith("'") and " " in pf:
1348 pf = pf[1:-1] # Remove the quotes
1348 pf = pf[1:-1] # Remove the quotes
1349 return pf
1349 return pf
1350
1350
1351 def sshargs(sshcmd, host, user, port):
1351 def sshargs(sshcmd, host, user, port):
1352 '''Build argument list for ssh'''
1352 '''Build argument list for ssh'''
1353 args = user and ("%s@%s" % (user, host)) or host
1353 args = user and ("%s@%s" % (user, host)) or host
1354 return port and ("%s -p %s" % (args, port)) or args
1354 return port and ("%s -p %s" % (args, port)) or args
1355
1355
1356 def is_exec(f):
1356 def is_exec(f):
1357 """check whether a file is executable"""
1357 """check whether a file is executable"""
1358 return (os.lstat(f).st_mode & 0100 != 0)
1358 return (os.lstat(f).st_mode & 0100 != 0)
1359
1359
1360 def set_flags(f, l, x):
1360 def set_flags(f, l, x):
1361 s = os.lstat(f).st_mode
1361 s = os.lstat(f).st_mode
1362 if l:
1362 if l:
1363 if not stat.S_ISLNK(s):
1363 if not stat.S_ISLNK(s):
1364 # switch file to link
1364 # switch file to link
1365 data = file(f).read()
1365 data = file(f).read()
1366 os.unlink(f)
1366 os.unlink(f)
1367 try:
1367 try:
1368 os.symlink(data, f)
1368 os.symlink(data, f)
1369 except:
1369 except:
1370 # failed to make a link, rewrite file
1370 # failed to make a link, rewrite file
1371 file(f, "w").write(data)
1371 file(f, "w").write(data)
1372 # no chmod needed at this point
1372 # no chmod needed at this point
1373 return
1373 return
1374 if stat.S_ISLNK(s):
1374 if stat.S_ISLNK(s):
1375 # switch link to file
1375 # switch link to file
1376 data = os.readlink(f)
1376 data = os.readlink(f)
1377 os.unlink(f)
1377 os.unlink(f)
1378 file(f, "w").write(data)
1378 file(f, "w").write(data)
1379 s = 0666 & ~_umask # avoid restatting for chmod
1379 s = 0666 & ~_umask # avoid restatting for chmod
1380
1380
1381 sx = s & 0100
1381 sx = s & 0100
1382 if x and not sx:
1382 if x and not sx:
1383 # Turn on +x for every +r bit when making a file executable
1383 # Turn on +x for every +r bit when making a file executable
1384 # and obey umask.
1384 # and obey umask.
1385 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
1385 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
1386 elif not x and sx:
1386 elif not x and sx:
1387 # Turn off all +x bits
1387 # Turn off all +x bits
1388 os.chmod(f, s & 0666)
1388 os.chmod(f, s & 0666)
1389
1389
1390 def set_binary(fd):
1390 def set_binary(fd):
1391 pass
1391 pass
1392
1392
1393 def pconvert(path):
1393 def pconvert(path):
1394 return path
1394 return path
1395
1395
1396 def localpath(path):
1396 def localpath(path):
1397 return path
1397 return path
1398
1398
1399 normpath = os.path.normpath
1399 normpath = os.path.normpath
1400 samestat = os.path.samestat
1400 samestat = os.path.samestat
1401
1401
1402 def makelock(info, pathname):
1402 def makelock(info, pathname):
1403 try:
1403 try:
1404 os.symlink(info, pathname)
1404 os.symlink(info, pathname)
1405 except OSError, why:
1405 except OSError, why:
1406 if why.errno == errno.EEXIST:
1406 if why.errno == errno.EEXIST:
1407 raise
1407 raise
1408 else:
1408 else:
1409 _makelock_file(info, pathname)
1409 _makelock_file(info, pathname)
1410
1410
1411 def readlock(pathname):
1411 def readlock(pathname):
1412 try:
1412 try:
1413 return os.readlink(pathname)
1413 return os.readlink(pathname)
1414 except OSError, why:
1414 except OSError, why:
1415 if why.errno in (errno.EINVAL, errno.ENOSYS):
1415 if why.errno in (errno.EINVAL, errno.ENOSYS):
1416 return _readlock_file(pathname)
1416 return _readlock_file(pathname)
1417 else:
1417 else:
1418 raise
1418 raise
1419
1419
1420 def shellquote(s):
1420 def shellquote(s):
1421 if os.sys.platform == 'OpenVMS':
1421 if os.sys.platform == 'OpenVMS':
1422 return '"%s"' % s
1422 return '"%s"' % s
1423 else:
1423 else:
1424 return "'%s'" % s.replace("'", "'\\''")
1424 return "'%s'" % s.replace("'", "'\\''")
1425
1425
1426 def quotecommand(cmd):
1426 def quotecommand(cmd):
1427 return cmd
1427 return cmd
1428
1428
1429 def popen(command, mode='r'):
1429 def popen(command, mode='r'):
1430 return os.popen(command, mode)
1430 return os.popen(command, mode)
1431
1431
1432 def testpid(pid):
1432 def testpid(pid):
1433 '''return False if pid dead, True if running or not sure'''
1433 '''return False if pid dead, True if running or not sure'''
1434 if os.sys.platform == 'OpenVMS':
1434 if os.sys.platform == 'OpenVMS':
1435 return True
1435 return True
1436 try:
1436 try:
1437 os.kill(pid, 0)
1437 os.kill(pid, 0)
1438 return True
1438 return True
1439 except OSError, inst:
1439 except OSError, inst:
1440 return inst.errno != errno.ESRCH
1440 return inst.errno != errno.ESRCH
1441
1441
1442 def explain_exit(code):
1442 def explain_exit(code):
1443 """return a 2-tuple (desc, code) describing a process's status"""
1443 """return a 2-tuple (desc, code) describing a process's status"""
1444 if os.WIFEXITED(code):
1444 if os.WIFEXITED(code):
1445 val = os.WEXITSTATUS(code)
1445 val = os.WEXITSTATUS(code)
1446 return _("exited with status %d") % val, val
1446 return _("exited with status %d") % val, val
1447 elif os.WIFSIGNALED(code):
1447 elif os.WIFSIGNALED(code):
1448 val = os.WTERMSIG(code)
1448 val = os.WTERMSIG(code)
1449 return _("killed by signal %d") % val, val
1449 return _("killed by signal %d") % val, val
1450 elif os.WIFSTOPPED(code):
1450 elif os.WIFSTOPPED(code):
1451 val = os.WSTOPSIG(code)
1451 val = os.WSTOPSIG(code)
1452 return _("stopped by signal %d") % val, val
1452 return _("stopped by signal %d") % val, val
1453 raise ValueError(_("invalid exit code"))
1453 raise ValueError(_("invalid exit code"))
1454
1454
1455 def isowner(fp, st=None):
1455 def isowner(fp, st=None):
1456 """Return True if the file object f belongs to the current user.
1456 """Return True if the file object f belongs to the current user.
1457
1457
1458 The return value of a util.fstat(f) may be passed as the st argument.
1458 The return value of a util.fstat(f) may be passed as the st argument.
1459 """
1459 """
1460 if st is None:
1460 if st is None:
1461 st = fstat(fp)
1461 st = fstat(fp)
1462 return st.st_uid == os.getuid()
1462 return st.st_uid == os.getuid()
1463
1463
1464 def find_exe(command):
1464 def find_exe(command):
1465 '''Find executable for command searching like which does.
1465 '''Find executable for command searching like which does.
1466 If command is a basename then PATH is searched for command.
1466 If command is a basename then PATH is searched for command.
1467 PATH isn't searched if command is an absolute or relative path.
1467 PATH isn't searched if command is an absolute or relative path.
1468 If command isn't found None is returned.'''
1468 If command isn't found None is returned.'''
1469 if sys.platform == 'OpenVMS':
1469 if sys.platform == 'OpenVMS':
1470 return command
1470 return command
1471
1471
1472 def findexisting(executable):
1472 def findexisting(executable):
1473 'Will return executable if existing file'
1473 'Will return executable if existing file'
1474 if os.path.exists(executable):
1474 if os.path.exists(executable):
1475 return executable
1475 return executable
1476 return None
1476 return None
1477
1477
1478 if os.sep in command:
1478 if os.sep in command:
1479 return findexisting(command)
1479 return findexisting(command)
1480
1480
1481 for path in os.environ.get('PATH', '').split(os.pathsep):
1481 for path in os.environ.get('PATH', '').split(os.pathsep):
1482 executable = findexisting(os.path.join(path, command))
1482 executable = findexisting(os.path.join(path, command))
1483 if executable is not None:
1483 if executable is not None:
1484 return executable
1484 return executable
1485 return None
1485 return None
1486
1486
1487 def set_signal_handler():
1487 def set_signal_handler():
1488 pass
1488 pass
1489
1489
1490 def mktempcopy(name, emptyok=False, createmode=None):
1490 def mktempcopy(name, emptyok=False, createmode=None):
1491 """Create a temporary file with the same contents from name
1491 """Create a temporary file with the same contents from name
1492
1492
1493 The permission bits are copied from the original file.
1493 The permission bits are copied from the original file.
1494
1494
1495 If the temporary file is going to be truncated immediately, you
1495 If the temporary file is going to be truncated immediately, you
1496 can use emptyok=True as an optimization.
1496 can use emptyok=True as an optimization.
1497
1497
1498 Returns the name of the temporary file.
1498 Returns the name of the temporary file.
1499 """
1499 """
1500 d, fn = os.path.split(name)
1500 d, fn = os.path.split(name)
1501 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1501 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1502 os.close(fd)
1502 os.close(fd)
1503 # Temporary files are created with mode 0600, which is usually not
1503 # Temporary files are created with mode 0600, which is usually not
1504 # what we want. If the original file already exists, just copy
1504 # what we want. If the original file already exists, just copy
1505 # its mode. Otherwise, manually obey umask.
1505 # its mode. Otherwise, manually obey umask.
1506 try:
1506 try:
1507 st_mode = os.lstat(name).st_mode & 0777
1507 st_mode = os.lstat(name).st_mode & 0777
1508 except OSError, inst:
1508 except OSError, inst:
1509 if inst.errno != errno.ENOENT:
1509 if inst.errno != errno.ENOENT:
1510 raise
1510 raise
1511 st_mode = createmode
1511 st_mode = createmode
1512 if st_mode is None:
1512 if st_mode is None:
1513 st_mode = ~_umask
1513 st_mode = ~_umask
1514 st_mode &= 0666
1514 st_mode &= 0666
1515 os.chmod(temp, st_mode)
1515 os.chmod(temp, st_mode)
1516 if emptyok:
1516 if emptyok:
1517 return temp
1517 return temp
1518 try:
1518 try:
1519 try:
1519 try:
1520 ifp = posixfile(name, "rb")
1520 ifp = posixfile(name, "rb")
1521 except IOError, inst:
1521 except IOError, inst:
1522 if inst.errno == errno.ENOENT:
1522 if inst.errno == errno.ENOENT:
1523 return temp
1523 return temp
1524 if not getattr(inst, 'filename', None):
1524 if not getattr(inst, 'filename', None):
1525 inst.filename = name
1525 inst.filename = name
1526 raise
1526 raise
1527 ofp = posixfile(temp, "wb")
1527 ofp = posixfile(temp, "wb")
1528 for chunk in filechunkiter(ifp):
1528 for chunk in filechunkiter(ifp):
1529 ofp.write(chunk)
1529 ofp.write(chunk)
1530 ifp.close()
1530 ifp.close()
1531 ofp.close()
1531 ofp.close()
1532 except:
1532 except:
1533 try: os.unlink(temp)
1533 try: os.unlink(temp)
1534 except: pass
1534 except: pass
1535 raise
1535 raise
1536 return temp
1536 return temp
1537
1537
1538 class atomictempfile(posixfile):
1538 class atomictempfile(posixfile):
1539 """file-like object that atomically updates a file
1539 """file-like object that atomically updates a file
1540
1540
1541 All writes will be redirected to a temporary copy of the original
1541 All writes will be redirected to a temporary copy of the original
1542 file. When rename is called, the copy is renamed to the original
1542 file. When rename is called, the copy is renamed to the original
1543 name, making the changes visible.
1543 name, making the changes visible.
1544 """
1544 """
1545 def __init__(self, name, mode, createmode):
1545 def __init__(self, name, mode, createmode):
1546 self.__name = name
1546 self.__name = name
1547 self.temp = mktempcopy(name, emptyok=('w' in mode),
1547 self.temp = mktempcopy(name, emptyok=('w' in mode),
1548 createmode=createmode)
1548 createmode=createmode)
1549 posixfile.__init__(self, self.temp, mode)
1549 posixfile.__init__(self, self.temp, mode)
1550
1550
1551 def rename(self):
1551 def rename(self):
1552 if not self.closed:
1552 if not self.closed:
1553 posixfile.close(self)
1553 posixfile.close(self)
1554 rename(self.temp, localpath(self.__name))
1554 rename(self.temp, localpath(self.__name))
1555
1555
1556 def __del__(self):
1556 def __del__(self):
1557 if not self.closed:
1557 if not self.closed:
1558 try:
1558 try:
1559 os.unlink(self.temp)
1559 os.unlink(self.temp)
1560 except: pass
1560 except: pass
1561 posixfile.close(self)
1561 posixfile.close(self)
1562
1562
1563 def makedirs(name, mode=None):
1563 def makedirs(name, mode=None):
1564 """recursive directory creation with parent mode inheritance"""
1564 """recursive directory creation with parent mode inheritance"""
1565 try:
1565 try:
1566 os.mkdir(name)
1566 os.mkdir(name)
1567 if mode is not None:
1567 if mode is not None:
1568 os.chmod(name, mode)
1568 os.chmod(name, mode)
1569 return
1569 return
1570 except OSError, err:
1570 except OSError, err:
1571 if err.errno == errno.EEXIST:
1571 if err.errno == errno.EEXIST:
1572 return
1572 return
1573 if err.errno != errno.ENOENT:
1573 if err.errno != errno.ENOENT:
1574 raise
1574 raise
1575 parent = os.path.abspath(os.path.dirname(name))
1575 parent = os.path.abspath(os.path.dirname(name))
1576 makedirs(parent, mode)
1576 makedirs(parent, mode)
1577 makedirs(name, mode)
1577 makedirs(name, mode)
1578
1578
1579 class opener(object):
1579 class opener(object):
1580 """Open files relative to a base directory
1580 """Open files relative to a base directory
1581
1581
1582 This class is used to hide the details of COW semantics and
1582 This class is used to hide the details of COW semantics and
1583 remote file access from higher level code.
1583 remote file access from higher level code.
1584 """
1584 """
1585 def __init__(self, base, audit=True):
1585 def __init__(self, base, audit=True):
1586 self.base = base
1586 self.base = base
1587 if audit:
1587 if audit:
1588 self.audit_path = path_auditor(base)
1588 self.audit_path = path_auditor(base)
1589 else:
1589 else:
1590 self.audit_path = always
1590 self.audit_path = always
1591 self.createmode = None
1591 self.createmode = None
1592
1592
1593 def __getattr__(self, name):
1593 def __getattr__(self, name):
1594 if name == '_can_symlink':
1594 if name == '_can_symlink':
1595 self._can_symlink = checklink(self.base)
1595 self._can_symlink = checklink(self.base)
1596 return self._can_symlink
1596 return self._can_symlink
1597 raise AttributeError(name)
1597 raise AttributeError(name)
1598
1598
1599 def _fixfilemode(self, name):
1599 def _fixfilemode(self, name):
1600 if self.createmode is None:
1600 if self.createmode is None:
1601 return
1601 return
1602 os.chmod(name, self.createmode & 0666)
1602 os.chmod(name, self.createmode & 0666)
1603
1603
1604 def __call__(self, path, mode="r", text=False, atomictemp=False):
1604 def __call__(self, path, mode="r", text=False, atomictemp=False):
1605 self.audit_path(path)
1605 self.audit_path(path)
1606 f = os.path.join(self.base, path)
1606 f = os.path.join(self.base, path)
1607
1607
1608 if not text and "b" not in mode:
1608 if not text and "b" not in mode:
1609 mode += "b" # for that other OS
1609 mode += "b" # for that other OS
1610
1610
1611 nlink = -1
1611 nlink = -1
1612 if mode not in ("r", "rb"):
1612 if mode not in ("r", "rb"):
1613 try:
1613 try:
1614 nlink = nlinks(f)
1614 nlink = nlinks(f)
1615 except OSError:
1615 except OSError:
1616 nlink = 0
1616 nlink = 0
1617 d = os.path.dirname(f)
1617 d = os.path.dirname(f)
1618 if not os.path.isdir(d):
1618 if not os.path.isdir(d):
1619 makedirs(d, self.createmode)
1619 makedirs(d, self.createmode)
1620 if atomictemp:
1620 if atomictemp:
1621 return atomictempfile(f, mode, self.createmode)
1621 return atomictempfile(f, mode, self.createmode)
1622 if nlink > 1:
1622 if nlink > 1:
1623 rename(mktempcopy(f), f)
1623 rename(mktempcopy(f), f)
1624 fp = posixfile(f, mode)
1624 fp = posixfile(f, mode)
1625 if nlink == 0:
1625 if nlink == 0:
1626 self._fixfilemode(f)
1626 self._fixfilemode(f)
1627 return fp
1627 return fp
1628
1628
1629 def symlink(self, src, dst):
1629 def symlink(self, src, dst):
1630 self.audit_path(dst)
1630 self.audit_path(dst)
1631 linkname = os.path.join(self.base, dst)
1631 linkname = os.path.join(self.base, dst)
1632 try:
1632 try:
1633 os.unlink(linkname)
1633 os.unlink(linkname)
1634 except OSError:
1634 except OSError:
1635 pass
1635 pass
1636
1636
1637 dirname = os.path.dirname(linkname)
1637 dirname = os.path.dirname(linkname)
1638 if not os.path.exists(dirname):
1638 if not os.path.exists(dirname):
1639 makedirs(dirname, self.createmode)
1639 makedirs(dirname, self.createmode)
1640
1640
1641 if self._can_symlink:
1641 if self._can_symlink:
1642 try:
1642 try:
1643 os.symlink(src, linkname)
1643 os.symlink(src, linkname)
1644 except OSError, err:
1644 except OSError, err:
1645 raise OSError(err.errno, _('could not symlink to %r: %s') %
1645 raise OSError(err.errno, _('could not symlink to %r: %s') %
1646 (src, err.strerror), linkname)
1646 (src, err.strerror), linkname)
1647 else:
1647 else:
1648 f = self(dst, "w")
1648 f = self(dst, "w")
1649 f.write(src)
1649 f.write(src)
1650 f.close()
1650 f.close()
1651 self._fixfilemode(dst)
1651 self._fixfilemode(dst)
1652
1652
1653 class chunkbuffer(object):
1653 class chunkbuffer(object):
1654 """Allow arbitrary sized chunks of data to be efficiently read from an
1654 """Allow arbitrary sized chunks of data to be efficiently read from an
1655 iterator over chunks of arbitrary size."""
1655 iterator over chunks of arbitrary size."""
1656
1656
1657 def __init__(self, in_iter):
1657 def __init__(self, in_iter):
1658 """in_iter is the iterator that's iterating over the input chunks.
1658 """in_iter is the iterator that's iterating over the input chunks.
1659 targetsize is how big a buffer to try to maintain."""
1659 targetsize is how big a buffer to try to maintain."""
1660 self.iter = iter(in_iter)
1660 self.iter = iter(in_iter)
1661 self.buf = ''
1661 self.buf = ''
1662 self.targetsize = 2**16
1662 self.targetsize = 2**16
1663
1663
1664 def read(self, l):
1664 def read(self, l):
1665 """Read L bytes of data from the iterator of chunks of data.
1665 """Read L bytes of data from the iterator of chunks of data.
1666 Returns less than L bytes if the iterator runs dry."""
1666 Returns less than L bytes if the iterator runs dry."""
1667 if l > len(self.buf) and self.iter:
1667 if l > len(self.buf) and self.iter:
1668 # Clamp to a multiple of self.targetsize
1668 # Clamp to a multiple of self.targetsize
1669 targetsize = max(l, self.targetsize)
1669 targetsize = max(l, self.targetsize)
1670 collector = cStringIO.StringIO()
1670 collector = cStringIO.StringIO()
1671 collector.write(self.buf)
1671 collector.write(self.buf)
1672 collected = len(self.buf)
1672 collected = len(self.buf)
1673 for chunk in self.iter:
1673 for chunk in self.iter:
1674 collector.write(chunk)
1674 collector.write(chunk)
1675 collected += len(chunk)
1675 collected += len(chunk)
1676 if collected >= targetsize:
1676 if collected >= targetsize:
1677 break
1677 break
1678 if collected < targetsize:
1678 if collected < targetsize:
1679 self.iter = False
1679 self.iter = False
1680 self.buf = collector.getvalue()
1680 self.buf = collector.getvalue()
1681 if len(self.buf) == l:
1681 if len(self.buf) == l:
1682 s, self.buf = str(self.buf), ''
1682 s, self.buf = str(self.buf), ''
1683 else:
1683 else:
1684 s, self.buf = self.buf[:l], buffer(self.buf, l)
1684 s, self.buf = self.buf[:l], buffer(self.buf, l)
1685 return s
1685 return s
1686
1686
1687 def filechunkiter(f, size=65536, limit=None):
1687 def filechunkiter(f, size=65536, limit=None):
1688 """Create a generator that produces the data in the file size
1688 """Create a generator that produces the data in the file size
1689 (default 65536) bytes at a time, up to optional limit (default is
1689 (default 65536) bytes at a time, up to optional limit (default is
1690 to read all data). Chunks may be less than size bytes if the
1690 to read all data). Chunks may be less than size bytes if the
1691 chunk is the last chunk in the file, or the file is a socket or
1691 chunk is the last chunk in the file, or the file is a socket or
1692 some other type of file that sometimes reads less data than is
1692 some other type of file that sometimes reads less data than is
1693 requested."""
1693 requested."""
1694 assert size >= 0
1694 assert size >= 0
1695 assert limit is None or limit >= 0
1695 assert limit is None or limit >= 0
1696 while True:
1696 while True:
1697 if limit is None: nbytes = size
1697 if limit is None: nbytes = size
1698 else: nbytes = min(limit, size)
1698 else: nbytes = min(limit, size)
1699 s = nbytes and f.read(nbytes)
1699 s = nbytes and f.read(nbytes)
1700 if not s: break
1700 if not s: break
1701 if limit: limit -= len(s)
1701 if limit: limit -= len(s)
1702 yield s
1702 yield s
1703
1703
1704 def makedate():
1704 def makedate():
1705 lt = time.localtime()
1705 lt = time.localtime()
1706 if lt[8] == 1 and time.daylight:
1706 if lt[8] == 1 and time.daylight:
1707 tz = time.altzone
1707 tz = time.altzone
1708 else:
1708 else:
1709 tz = time.timezone
1709 tz = time.timezone
1710 return time.mktime(lt), tz
1710 return time.mktime(lt), tz
1711
1711
1712 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1712 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1713 """represent a (unixtime, offset) tuple as a localized time.
1713 """represent a (unixtime, offset) tuple as a localized time.
1714 unixtime is seconds since the epoch, and offset is the time zone's
1714 unixtime is seconds since the epoch, and offset is the time zone's
1715 number of seconds away from UTC. if timezone is false, do not
1715 number of seconds away from UTC. if timezone is false, do not
1716 append time zone to string."""
1716 append time zone to string."""
1717 t, tz = date or makedate()
1717 t, tz = date or makedate()
1718 if "%1" in format or "%2" in format:
1718 if "%1" in format or "%2" in format:
1719 sign = (tz > 0) and "-" or "+"
1719 sign = (tz > 0) and "-" or "+"
1720 minutes = abs(tz) / 60
1720 minutes = abs(tz) / 60
1721 format = format.replace("%1", "%c%02d" % (sign, minutes / 60))
1721 format = format.replace("%1", "%c%02d" % (sign, minutes / 60))
1722 format = format.replace("%2", "%02d" % (minutes % 60))
1722 format = format.replace("%2", "%02d" % (minutes % 60))
1723 s = time.strftime(format, time.gmtime(float(t) - tz))
1723 s = time.strftime(format, time.gmtime(float(t) - tz))
1724 return s
1724 return s
1725
1725
1726 def shortdate(date=None):
1726 def shortdate(date=None):
1727 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1727 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1728 return datestr(date, format='%Y-%m-%d')
1728 return datestr(date, format='%Y-%m-%d')
1729
1729
1730 def strdate(string, format, defaults=[]):
1730 def strdate(string, format, defaults=[]):
1731 """parse a localized time string and return a (unixtime, offset) tuple.
1731 """parse a localized time string and return a (unixtime, offset) tuple.
1732 if the string cannot be parsed, ValueError is raised."""
1732 if the string cannot be parsed, ValueError is raised."""
1733 def timezone(string):
1733 def timezone(string):
1734 tz = string.split()[-1]
1734 tz = string.split()[-1]
1735 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1735 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1736 sign = (tz[0] == "+") and 1 or -1
1736 sign = (tz[0] == "+") and 1 or -1
1737 hours = int(tz[1:3])
1737 hours = int(tz[1:3])
1738 minutes = int(tz[3:5])
1738 minutes = int(tz[3:5])
1739 return -sign * (hours * 60 + minutes) * 60
1739 return -sign * (hours * 60 + minutes) * 60
1740 if tz == "GMT" or tz == "UTC":
1740 if tz == "GMT" or tz == "UTC":
1741 return 0
1741 return 0
1742 return None
1742 return None
1743
1743
1744 # NOTE: unixtime = localunixtime + offset
1744 # NOTE: unixtime = localunixtime + offset
1745 offset, date = timezone(string), string
1745 offset, date = timezone(string), string
1746 if offset != None:
1746 if offset != None:
1747 date = " ".join(string.split()[:-1])
1747 date = " ".join(string.split()[:-1])
1748
1748
1749 # add missing elements from defaults
1749 # add missing elements from defaults
1750 for part in defaults:
1750 for part in defaults:
1751 found = [True for p in part if ("%"+p) in format]
1751 found = [True for p in part if ("%"+p) in format]
1752 if not found:
1752 if not found:
1753 date += "@" + defaults[part]
1753 date += "@" + defaults[part]
1754 format += "@%" + part[0]
1754 format += "@%" + part[0]
1755
1755
1756 timetuple = time.strptime(date, format)
1756 timetuple = time.strptime(date, format)
1757 localunixtime = int(calendar.timegm(timetuple))
1757 localunixtime = int(calendar.timegm(timetuple))
1758 if offset is None:
1758 if offset is None:
1759 # local timezone
1759 # local timezone
1760 unixtime = int(time.mktime(timetuple))
1760 unixtime = int(time.mktime(timetuple))
1761 offset = unixtime - localunixtime
1761 offset = unixtime - localunixtime
1762 else:
1762 else:
1763 unixtime = localunixtime + offset
1763 unixtime = localunixtime + offset
1764 return unixtime, offset
1764 return unixtime, offset
1765
1765
1766 def parsedate(date, formats=None, defaults=None):
1766 def parsedate(date, formats=None, defaults=None):
1767 """parse a localized date/time string and return a (unixtime, offset) tuple.
1767 """parse a localized date/time string and return a (unixtime, offset) tuple.
1768
1768
1769 The date may be a "unixtime offset" string or in one of the specified
1769 The date may be a "unixtime offset" string or in one of the specified
1770 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1770 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1771 """
1771 """
1772 if not date:
1772 if not date:
1773 return 0, 0
1773 return 0, 0
1774 if isinstance(date, tuple) and len(date) == 2:
1774 if isinstance(date, tuple) and len(date) == 2:
1775 return date
1775 return date
1776 if not formats:
1776 if not formats:
1777 formats = defaultdateformats
1777 formats = defaultdateformats
1778 date = date.strip()
1778 date = date.strip()
1779 try:
1779 try:
1780 when, offset = map(int, date.split(' '))
1780 when, offset = map(int, date.split(' '))
1781 except ValueError:
1781 except ValueError:
1782 # fill out defaults
1782 # fill out defaults
1783 if not defaults:
1783 if not defaults:
1784 defaults = {}
1784 defaults = {}
1785 now = makedate()
1785 now = makedate()
1786 for part in "d mb yY HI M S".split():
1786 for part in "d mb yY HI M S".split():
1787 if part not in defaults:
1787 if part not in defaults:
1788 if part[0] in "HMS":
1788 if part[0] in "HMS":
1789 defaults[part] = "00"
1789 defaults[part] = "00"
1790 else:
1790 else:
1791 defaults[part] = datestr(now, "%" + part[0])
1791 defaults[part] = datestr(now, "%" + part[0])
1792
1792
1793 for format in formats:
1793 for format in formats:
1794 try:
1794 try:
1795 when, offset = strdate(date, format, defaults)
1795 when, offset = strdate(date, format, defaults)
1796 except (ValueError, OverflowError):
1796 except (ValueError, OverflowError):
1797 pass
1797 pass
1798 else:
1798 else:
1799 break
1799 break
1800 else:
1800 else:
1801 raise Abort(_('invalid date: %r ') % date)
1801 raise Abort(_('invalid date: %r ') % date)
1802 # validate explicit (probably user-specified) date and
1802 # validate explicit (probably user-specified) date and
1803 # time zone offset. values must fit in signed 32 bits for
1803 # time zone offset. values must fit in signed 32 bits for
1804 # current 32-bit linux runtimes. timezones go from UTC-12
1804 # current 32-bit linux runtimes. timezones go from UTC-12
1805 # to UTC+14
1805 # to UTC+14
1806 if abs(when) > 0x7fffffff:
1806 if abs(when) > 0x7fffffff:
1807 raise Abort(_('date exceeds 32 bits: %d') % when)
1807 raise Abort(_('date exceeds 32 bits: %d') % when)
1808 if offset < -50400 or offset > 43200:
1808 if offset < -50400 or offset > 43200:
1809 raise Abort(_('impossible time zone offset: %d') % offset)
1809 raise Abort(_('impossible time zone offset: %d') % offset)
1810 return when, offset
1810 return when, offset
1811
1811
1812 def matchdate(date):
1812 def matchdate(date):
1813 """Return a function that matches a given date match specifier
1813 """Return a function that matches a given date match specifier
1814
1814
1815 Formats include:
1815 Formats include:
1816
1816
1817 '{date}' match a given date to the accuracy provided
1817 '{date}' match a given date to the accuracy provided
1818
1818
1819 '<{date}' on or before a given date
1819 '<{date}' on or before a given date
1820
1820
1821 '>{date}' on or after a given date
1821 '>{date}' on or after a given date
1822
1822
1823 """
1823 """
1824
1824
1825 def lower(date):
1825 def lower(date):
1826 d = dict(mb="1", d="1")
1826 d = dict(mb="1", d="1")
1827 return parsedate(date, extendeddateformats, d)[0]
1827 return parsedate(date, extendeddateformats, d)[0]
1828
1828
1829 def upper(date):
1829 def upper(date):
1830 d = dict(mb="12", HI="23", M="59", S="59")
1830 d = dict(mb="12", HI="23", M="59", S="59")
1831 for days in "31 30 29".split():
1831 for days in "31 30 29".split():
1832 try:
1832 try:
1833 d["d"] = days
1833 d["d"] = days
1834 return parsedate(date, extendeddateformats, d)[0]
1834 return parsedate(date, extendeddateformats, d)[0]
1835 except:
1835 except:
1836 pass
1836 pass
1837 d["d"] = "28"
1837 d["d"] = "28"
1838 return parsedate(date, extendeddateformats, d)[0]
1838 return parsedate(date, extendeddateformats, d)[0]
1839
1839
1840 if date[0] == "<":
1840 if date[0] == "<":
1841 when = upper(date[1:])
1841 when = upper(date[1:])
1842 return lambda x: x <= when
1842 return lambda x: x <= when
1843 elif date[0] == ">":
1843 elif date[0] == ">":
1844 when = lower(date[1:])
1844 when = lower(date[1:])
1845 return lambda x: x >= when
1845 return lambda x: x >= when
1846 elif date[0] == "-":
1846 elif date[0] == "-":
1847 try:
1847 try:
1848 days = int(date[1:])
1848 days = int(date[1:])
1849 except ValueError:
1849 except ValueError:
1850 raise Abort(_("invalid day spec: %s") % date[1:])
1850 raise Abort(_("invalid day spec: %s") % date[1:])
1851 when = makedate()[0] - days * 3600 * 24
1851 when = makedate()[0] - days * 3600 * 24
1852 return lambda x: x >= when
1852 return lambda x: x >= when
1853 elif " to " in date:
1853 elif " to " in date:
1854 a, b = date.split(" to ")
1854 a, b = date.split(" to ")
1855 start, stop = lower(a), upper(b)
1855 start, stop = lower(a), upper(b)
1856 return lambda x: x >= start and x <= stop
1856 return lambda x: x >= start and x <= stop
1857 else:
1857 else:
1858 start, stop = lower(date), upper(date)
1858 start, stop = lower(date), upper(date)
1859 return lambda x: x >= start and x <= stop
1859 return lambda x: x >= start and x <= stop
1860
1860
1861 def shortuser(user):
1861 def shortuser(user):
1862 """Return a short representation of a user name or email address."""
1862 """Return a short representation of a user name or email address."""
1863 f = user.find('@')
1863 f = user.find('@')
1864 if f >= 0:
1864 if f >= 0:
1865 user = user[:f]
1865 user = user[:f]
1866 f = user.find('<')
1866 f = user.find('<')
1867 if f >= 0:
1867 if f >= 0:
1868 user = user[f+1:]
1868 user = user[f+1:]
1869 f = user.find(' ')
1869 f = user.find(' ')
1870 if f >= 0:
1870 if f >= 0:
1871 user = user[:f]
1871 user = user[:f]
1872 f = user.find('.')
1872 f = user.find('.')
1873 if f >= 0:
1873 if f >= 0:
1874 user = user[:f]
1874 user = user[:f]
1875 return user
1875 return user
1876
1876
1877 def email(author):
1877 def email(author):
1878 '''get email of author.'''
1878 '''get email of author.'''
1879 r = author.find('>')
1879 r = author.find('>')
1880 if r == -1: r = None
1880 if r == -1: r = None
1881 return author[author.find('<')+1:r]
1881 return author[author.find('<')+1:r]
1882
1882
1883 def ellipsis(text, maxlength=400):
1883 def ellipsis(text, maxlength=400):
1884 """Trim string to at most maxlength (default: 400) characters."""
1884 """Trim string to at most maxlength (default: 400) characters."""
1885 if len(text) <= maxlength:
1885 if len(text) <= maxlength:
1886 return text
1886 return text
1887 else:
1887 else:
1888 return "%s..." % (text[:maxlength-3])
1888 return "%s..." % (text[:maxlength-3])
1889
1889
1890 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1890 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1891 '''yield every hg repository under path, recursively.'''
1891 '''yield every hg repository under path, recursively.'''
1892 def errhandler(err):
1892 def errhandler(err):
1893 if err.filename == path:
1893 if err.filename == path:
1894 raise err
1894 raise err
1895 if followsym and hasattr(os.path, 'samestat'):
1895 if followsym and hasattr(os.path, 'samestat'):
1896 def _add_dir_if_not_there(dirlst, dirname):
1896 def _add_dir_if_not_there(dirlst, dirname):
1897 match = False
1897 match = False
1898 samestat = os.path.samestat
1898 samestat = os.path.samestat
1899 dirstat = os.stat(dirname)
1899 dirstat = os.stat(dirname)
1900 for lstdirstat in dirlst:
1900 for lstdirstat in dirlst:
1901 if samestat(dirstat, lstdirstat):
1901 if samestat(dirstat, lstdirstat):
1902 match = True
1902 match = True
1903 break
1903 break
1904 if not match:
1904 if not match:
1905 dirlst.append(dirstat)
1905 dirlst.append(dirstat)
1906 return not match
1906 return not match
1907 else:
1907 else:
1908 followsym = False
1908 followsym = False
1909
1909
1910 if (seen_dirs is None) and followsym:
1910 if (seen_dirs is None) and followsym:
1911 seen_dirs = []
1911 seen_dirs = []
1912 _add_dir_if_not_there(seen_dirs, path)
1912 _add_dir_if_not_there(seen_dirs, path)
1913 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1913 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1914 if '.hg' in dirs:
1914 if '.hg' in dirs:
1915 yield root # found a repository
1915 yield root # found a repository
1916 qroot = os.path.join(root, '.hg', 'patches')
1916 qroot = os.path.join(root, '.hg', 'patches')
1917 if os.path.isdir(os.path.join(qroot, '.hg')):
1917 if os.path.isdir(os.path.join(qroot, '.hg')):
1918 yield qroot # we have a patch queue repo here
1918 yield qroot # we have a patch queue repo here
1919 if recurse:
1919 if recurse:
1920 # avoid recursing inside the .hg directory
1920 # avoid recursing inside the .hg directory
1921 dirs.remove('.hg')
1921 dirs.remove('.hg')
1922 else:
1922 else:
1923 dirs[:] = [] # don't descend further
1923 dirs[:] = [] # don't descend further
1924 elif followsym:
1924 elif followsym:
1925 newdirs = []
1925 newdirs = []
1926 for d in dirs:
1926 for d in dirs:
1927 fname = os.path.join(root, d)
1927 fname = os.path.join(root, d)
1928 if _add_dir_if_not_there(seen_dirs, fname):
1928 if _add_dir_if_not_there(seen_dirs, fname):
1929 if os.path.islink(fname):
1929 if os.path.islink(fname):
1930 for hgname in walkrepos(fname, True, seen_dirs):
1930 for hgname in walkrepos(fname, True, seen_dirs):
1931 yield hgname
1931 yield hgname
1932 else:
1932 else:
1933 newdirs.append(d)
1933 newdirs.append(d)
1934 dirs[:] = newdirs
1934 dirs[:] = newdirs
1935
1935
1936 _rcpath = None
1936 _rcpath = None
1937
1937
1938 def os_rcpath():
1938 def os_rcpath():
1939 '''return default os-specific hgrc search path'''
1939 '''return default os-specific hgrc search path'''
1940 path = system_rcpath()
1940 path = system_rcpath()
1941 path.extend(user_rcpath())
1941 path.extend(user_rcpath())
1942 path = [os.path.normpath(f) for f in path]
1942 path = [os.path.normpath(f) for f in path]
1943 return path
1943 return path
1944
1944
1945 def rcpath():
1945 def rcpath():
1946 '''return hgrc search path. if env var HGRCPATH is set, use it.
1946 '''return hgrc search path. if env var HGRCPATH is set, use it.
1947 for each item in path, if directory, use files ending in .rc,
1947 for each item in path, if directory, use files ending in .rc,
1948 else use item.
1948 else use item.
1949 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1949 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1950 if no HGRCPATH, use default os-specific path.'''
1950 if no HGRCPATH, use default os-specific path.'''
1951 global _rcpath
1951 global _rcpath
1952 if _rcpath is None:
1952 if _rcpath is None:
1953 if 'HGRCPATH' in os.environ:
1953 if 'HGRCPATH' in os.environ:
1954 _rcpath = []
1954 _rcpath = []
1955 for p in os.environ['HGRCPATH'].split(os.pathsep):
1955 for p in os.environ['HGRCPATH'].split(os.pathsep):
1956 if not p: continue
1956 if not p: continue
1957 if os.path.isdir(p):
1957 if os.path.isdir(p):
1958 for f, kind in osutil.listdir(p):
1958 for f, kind in osutil.listdir(p):
1959 if f.endswith('.rc'):
1959 if f.endswith('.rc'):
1960 _rcpath.append(os.path.join(p, f))
1960 _rcpath.append(os.path.join(p, f))
1961 else:
1961 else:
1962 _rcpath.append(p)
1962 _rcpath.append(p)
1963 else:
1963 else:
1964 _rcpath = os_rcpath()
1964 _rcpath = os_rcpath()
1965 return _rcpath
1965 return _rcpath
1966
1966
1967 def bytecount(nbytes):
1967 def bytecount(nbytes):
1968 '''return byte count formatted as readable string, with units'''
1968 '''return byte count formatted as readable string, with units'''
1969
1969
1970 units = (
1970 units = (
1971 (100, 1<<30, _('%.0f GB')),
1971 (100, 1<<30, _('%.0f GB')),
1972 (10, 1<<30, _('%.1f GB')),
1972 (10, 1<<30, _('%.1f GB')),
1973 (1, 1<<30, _('%.2f GB')),
1973 (1, 1<<30, _('%.2f GB')),
1974 (100, 1<<20, _('%.0f MB')),
1974 (100, 1<<20, _('%.0f MB')),
1975 (10, 1<<20, _('%.1f MB')),
1975 (10, 1<<20, _('%.1f MB')),
1976 (1, 1<<20, _('%.2f MB')),
1976 (1, 1<<20, _('%.2f MB')),
1977 (100, 1<<10, _('%.0f KB')),
1977 (100, 1<<10, _('%.0f KB')),
1978 (10, 1<<10, _('%.1f KB')),
1978 (10, 1<<10, _('%.1f KB')),
1979 (1, 1<<10, _('%.2f KB')),
1979 (1, 1<<10, _('%.2f KB')),
1980 (1, 1, _('%.0f bytes')),
1980 (1, 1, _('%.0f bytes')),
1981 )
1981 )
1982
1982
1983 for multiplier, divisor, format in units:
1983 for multiplier, divisor, format in units:
1984 if nbytes >= divisor * multiplier:
1984 if nbytes >= divisor * multiplier:
1985 return format % (nbytes / float(divisor))
1985 return format % (nbytes / float(divisor))
1986 return units[-1][2] % nbytes
1986 return units[-1][2] % nbytes
1987
1987
1988 def drop_scheme(scheme, path):
1988 def drop_scheme(scheme, path):
1989 sc = scheme + ':'
1989 sc = scheme + ':'
1990 if path.startswith(sc):
1990 if path.startswith(sc):
1991 path = path[len(sc):]
1991 path = path[len(sc):]
1992 if path.startswith('//'):
1992 if path.startswith('//'):
1993 path = path[2:]
1993 path = path[2:]
1994 return path
1994 return path
1995
1995
1996 def uirepr(s):
1996 def uirepr(s):
1997 # Avoid double backslash in Windows path repr()
1997 # Avoid double backslash in Windows path repr()
1998 return repr(s).replace('\\\\', '\\')
1998 return repr(s).replace('\\\\', '\\')
1999
1999
2000 def termwidth():
2000 def termwidth():
2001 if 'COLUMNS' in os.environ:
2001 if 'COLUMNS' in os.environ:
2002 try:
2002 try:
2003 return int(os.environ['COLUMNS'])
2003 return int(os.environ['COLUMNS'])
2004 except ValueError:
2004 except ValueError:
2005 pass
2005 pass
2006 try:
2006 try:
2007 import termios, array, fcntl
2007 import termios, array, fcntl
2008 for dev in (sys.stdout, sys.stdin):
2008 for dev in (sys.stdout, sys.stdin):
2009 try:
2009 try:
2010 fd = dev.fileno()
2010 fd = dev.fileno()
2011 if not os.isatty(fd):
2011 if not os.isatty(fd):
2012 continue
2012 continue
2013 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
2013 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
2014 return array.array('h', arri)[1]
2014 return array.array('h', arri)[1]
2015 except ValueError:
2015 except ValueError:
2016 pass
2016 pass
2017 except ImportError:
2017 except ImportError:
2018 pass
2018 pass
2019 return 80
2019 return 80
2020
2021 def iterlines(iterator):
2022 for chunk in iterator:
2023 for line in chunk.splitlines():
2024 yield line
@@ -1,130 +1,130 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 hg init a
3 hg init a
4 cd a
4 cd a
5 echo a > a
5 echo a > a
6 hg add a
6 hg add a
7 echo line 1 > b
7 echo line 1 > b
8 echo line 2 >> b
8 echo line 2 >> b
9 hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
9 hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
10 hg add b
10 hg add b
11 echo other 1 > c
11 echo other 1 > c
12 echo other 2 >> c
12 echo other 2 >> c
13 echo >> c
13 echo >> c
14 echo other 3 >> c
14 echo other 3 >> c
15 hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
15 hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
16 hg add c
16 hg add c
17 hg commit -m 'no person' -d '1200000 0' -u 'other@place'
17 hg commit -m 'no person' -d '1200000 0' -u 'other@place'
18 echo c >> c
18 echo c >> c
19 hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
19 hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
20 echo foo > .hg/branch
20 echo foo > .hg/branch
21 hg commit -m 'new branch' -d '1400000 0' -u 'person'
21 hg commit -m 'new branch' -d '1400000 0' -u 'person'
22 hg co -q 3
22 hg co -q 3
23 echo other 4 >> d
23 echo other 4 >> d
24 hg add d
24 hg add d
25 hg commit -m 'new head' -d '1500000 0' -u 'person'
25 hg commit -m 'new head' -d '1500000 0' -u 'person'
26 hg merge -q foo
26 hg merge -q foo
27 hg commit -m 'merge' -d '1500001 0' -u 'person'
27 hg commit -m 'merge' -d '1500001 0' -u 'person'
28 # second branch starting at nullrev
28 # second branch starting at nullrev
29 hg update null
29 hg update null
30 echo second > second
30 echo second > second
31 hg add second
31 hg add second
32 hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
32 hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
33 echo third > third
33 echo third > third
34 hg add third
34 hg add third
35 hg commit -m third -d "2020-01-01 10:01"
35 hg commit -m third -d "2020-01-01 10:01"
36
36
37 # make sure user/global hgrc does not affect tests
37 # make sure user/global hgrc does not affect tests
38 echo '[ui]' > .hg/hgrc
38 echo '[ui]' > .hg/hgrc
39 echo 'logtemplate =' >> .hg/hgrc
39 echo 'logtemplate =' >> .hg/hgrc
40 echo 'style =' >> .hg/hgrc
40 echo 'style =' >> .hg/hgrc
41
41
42 echo '# default style is like normal output'
42 echo '# default style is like normal output'
43 echo '# normal'
43 echo '# normal'
44 hg log > log.out
44 hg log > log.out
45 hg log --style default > style.out
45 hg log --style default > style.out
46 diff log.out style.out
46 diff log.out style.out
47 echo '# verbose'
47 echo '# verbose'
48 hg log -v > log.out
48 hg log -v > log.out
49 hg log -v --style default > style.out
49 hg log -v --style default > style.out
50 diff log.out style.out
50 diff log.out style.out
51 echo '# debug'
51 echo '# debug'
52 hg log --debug > log.out
52 hg log --debug > log.out
53 hg log --debug --style default > style.out
53 hg log --debug --style default > style.out
54 diff log.out style.out
54 diff log.out style.out
55
55
56 echo '# revision with no copies (used to print a traceback)'
56 echo '# revision with no copies (used to print a traceback)'
57 hg tip -v --template '\n'
57 hg tip -v --template '\n'
58
58
59 echo '# compact style works'
59 echo '# compact style works'
60 hg log --style compact
60 hg log --style compact
61 hg log -v --style compact
61 hg log -v --style compact
62 hg log --debug --style compact
62 hg log --debug --style compact
63
63
64 echo '# error if style not readable'
64 echo '# error if style not readable'
65 touch q
65 touch q
66 chmod 0 q
66 chmod 0 q
67 hg log --style ./q
67 hg log --style ./q
68
68
69 echo '# error if no style'
69 echo '# error if no style'
70 hg log --style notexist
70 hg log --style notexist
71
71
72 echo '# error if style missing key'
72 echo '# error if style missing key'
73 echo 'q = q' > t
73 echo 'q = q' > t
74 hg log --style ./t
74 hg log --style ./t
75
75
76 echo '# error if include fails'
76 echo '# error if include fails'
77 echo 'changeset = q' >> t
77 echo 'changeset = q' >> t
78 hg log --style ./t
78 hg log --style ./t
79
79
80 echo '# include works'
80 echo '# include works'
81 rm q
81 rm q
82 echo '{rev}' > q
82 echo '{rev}' > q
83 hg log --style ./t
83 hg log --style ./t
84
84
85 echo '# ui.style works'
85 echo '# ui.style works'
86 echo '[ui]' > .hg/hgrc
86 echo '[ui]' > .hg/hgrc
87 echo 'style = t' >> .hg/hgrc
87 echo 'style = t' >> .hg/hgrc
88 hg log
88 hg log
89
89
90 echo '# issue338'
90 echo '# issue338'
91 hg log --style=changelog > changelog
91 hg log --style=changelog > changelog
92 cat changelog
92 cat changelog
93
93
94 echo "# keys work"
94 echo "# keys work"
95 for key in author branches date desc file_adds file_dels file_mods \
95 for key in author branches date desc file_adds file_dels file_mods \
96 files manifest node parents rev tags; do
96 files manifest node parents rev tags diffstat; do
97 for mode in '' --verbose --debug; do
97 for mode in '' --verbose --debug; do
98 hg log $mode --template "$key$mode: {$key}\n"
98 hg log $mode --template "$key$mode: {$key}\n"
99 done
99 done
100 done
100 done
101
101
102 echo '# filters work'
102 echo '# filters work'
103 hg log --template '{author|domain}\n'
103 hg log --template '{author|domain}\n'
104 hg log --template '{author|person}\n'
104 hg log --template '{author|person}\n'
105 hg log --template '{author|user}\n'
105 hg log --template '{author|user}\n'
106 hg log --template '{date|age}\n' > /dev/null || exit 1
106 hg log --template '{date|age}\n' > /dev/null || exit 1
107 hg log -l1 --template '{date|age}\n'
107 hg log -l1 --template '{date|age}\n'
108 hg log --template '{date|date}\n'
108 hg log --template '{date|date}\n'
109 hg log --template '{date|isodate}\n'
109 hg log --template '{date|isodate}\n'
110 hg log --template '{date|isodatesec}\n'
110 hg log --template '{date|isodatesec}\n'
111 hg log --template '{date|rfc822date}\n'
111 hg log --template '{date|rfc822date}\n'
112 hg log --template '{desc|firstline}\n'
112 hg log --template '{desc|firstline}\n'
113 hg log --template '{node|short}\n'
113 hg log --template '{node|short}\n'
114 hg log --template '<changeset author="{author|xmlescape}"/>\n'
114 hg log --template '<changeset author="{author|xmlescape}"/>\n'
115
115
116 echo '# formatnode filter works'
116 echo '# formatnode filter works'
117 echo '# quiet'
117 echo '# quiet'
118 hg -q log -r 0 --template '#node|formatnode#\n'
118 hg -q log -r 0 --template '#node|formatnode#\n'
119 echo '# normal'
119 echo '# normal'
120 hg log -r 0 --template '#node|formatnode#\n'
120 hg log -r 0 --template '#node|formatnode#\n'
121 echo '# verbose'
121 echo '# verbose'
122 hg -v log -r 0 --template '#node|formatnode#\n'
122 hg -v log -r 0 --template '#node|formatnode#\n'
123 echo '# debug'
123 echo '# debug'
124 hg --debug log -r 0 --template '#node|formatnode#\n'
124 hg --debug log -r 0 --template '#node|formatnode#\n'
125
125
126 echo '# error on syntax'
126 echo '# error on syntax'
127 echo 'x = "f' >> t
127 echo 'x = "f' >> t
128 hg log
128 hg log
129
129
130 echo '# done'
130 echo '# done'
@@ -1,640 +1,667 b''
1 created new head
1 created new head
2 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
2 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
3 created new head
3 created new head
4 # default style is like normal output
4 # default style is like normal output
5 # normal
5 # normal
6 # verbose
6 # verbose
7 # debug
7 # debug
8 # revision with no copies (used to print a traceback)
8 # revision with no copies (used to print a traceback)
9
9
10 # compact style works
10 # compact style works
11 8[tip] 946e2bd9c565 2020-01-01 10:01 +0000 test
11 8[tip] 946e2bd9c565 2020-01-01 10:01 +0000 test
12 third
12 third
13
13
14 7:-1 29114dbae42b 1970-01-12 13:46 +0000 user
14 7:-1 29114dbae42b 1970-01-12 13:46 +0000 user
15 second
15 second
16
16
17 6:5,4 c7b487c6c50e 1970-01-18 08:40 +0000 person
17 6:5,4 c7b487c6c50e 1970-01-18 08:40 +0000 person
18 merge
18 merge
19
19
20 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
20 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
21 new head
21 new head
22
22
23 4 32a18f097fcc 1970-01-17 04:53 +0000 person
23 4 32a18f097fcc 1970-01-17 04:53 +0000 person
24 new branch
24 new branch
25
25
26 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
26 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
27 no user, no domain
27 no user, no domain
28
28
29 2 97054abb4ab8 1970-01-14 21:20 +0000 other
29 2 97054abb4ab8 1970-01-14 21:20 +0000 other
30 no person
30 no person
31
31
32 1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
32 1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
33 other 1
33 other 1
34
34
35 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
35 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
36 line 1
36 line 1
37
37
38 8[tip] 946e2bd9c565 2020-01-01 10:01 +0000 test
38 8[tip] 946e2bd9c565 2020-01-01 10:01 +0000 test
39 third
39 third
40
40
41 7:-1 29114dbae42b 1970-01-12 13:46 +0000 user
41 7:-1 29114dbae42b 1970-01-12 13:46 +0000 user
42 second
42 second
43
43
44 6:5,4 c7b487c6c50e 1970-01-18 08:40 +0000 person
44 6:5,4 c7b487c6c50e 1970-01-18 08:40 +0000 person
45 merge
45 merge
46
46
47 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
47 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
48 new head
48 new head
49
49
50 4 32a18f097fcc 1970-01-17 04:53 +0000 person
50 4 32a18f097fcc 1970-01-17 04:53 +0000 person
51 new branch
51 new branch
52
52
53 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
53 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
54 no user, no domain
54 no user, no domain
55
55
56 2 97054abb4ab8 1970-01-14 21:20 +0000 other
56 2 97054abb4ab8 1970-01-14 21:20 +0000 other
57 no person
57 no person
58
58
59 1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
59 1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
60 other 1
60 other 1
61
61
62 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
62 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
63 line 1
63 line 1
64
64
65 8[tip]:7,-1 946e2bd9c565 2020-01-01 10:01 +0000 test
65 8[tip]:7,-1 946e2bd9c565 2020-01-01 10:01 +0000 test
66 third
66 third
67
67
68 7:-1,-1 29114dbae42b 1970-01-12 13:46 +0000 user
68 7:-1,-1 29114dbae42b 1970-01-12 13:46 +0000 user
69 second
69 second
70
70
71 6:5,4 c7b487c6c50e 1970-01-18 08:40 +0000 person
71 6:5,4 c7b487c6c50e 1970-01-18 08:40 +0000 person
72 merge
72 merge
73
73
74 5:3,-1 13207e5a10d9 1970-01-18 08:40 +0000 person
74 5:3,-1 13207e5a10d9 1970-01-18 08:40 +0000 person
75 new head
75 new head
76
76
77 4:3,-1 32a18f097fcc 1970-01-17 04:53 +0000 person
77 4:3,-1 32a18f097fcc 1970-01-17 04:53 +0000 person
78 new branch
78 new branch
79
79
80 3:2,-1 10e46f2dcbf4 1970-01-16 01:06 +0000 person
80 3:2,-1 10e46f2dcbf4 1970-01-16 01:06 +0000 person
81 no user, no domain
81 no user, no domain
82
82
83 2:1,-1 97054abb4ab8 1970-01-14 21:20 +0000 other
83 2:1,-1 97054abb4ab8 1970-01-14 21:20 +0000 other
84 no person
84 no person
85
85
86 1:0,-1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
86 1:0,-1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
87 other 1
87 other 1
88
88
89 0:-1,-1 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
89 0:-1,-1 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
90 line 1
90 line 1
91
91
92 # error if style not readable
92 # error if style not readable
93 abort: Permission denied: ./q
93 abort: Permission denied: ./q
94 # error if no style
94 # error if no style
95 abort: style not found: notexist
95 abort: style not found: notexist
96 # error if style missing key
96 # error if style missing key
97 abort: ./t: no key named 'changeset'
97 abort: ./t: no key named 'changeset'
98 # error if include fails
98 # error if include fails
99 abort: template file ./q: Permission denied
99 abort: template file ./q: Permission denied
100 # include works
100 # include works
101 8
101 8
102 7
102 7
103 6
103 6
104 5
104 5
105 4
105 4
106 3
106 3
107 2
107 2
108 1
108 1
109 0
109 0
110 # ui.style works
110 # ui.style works
111 8
111 8
112 7
112 7
113 6
113 6
114 5
114 5
115 4
115 4
116 3
116 3
117 2
117 2
118 1
118 1
119 0
119 0
120 # issue338
120 # issue338
121 2020-01-01 test <test>
121 2020-01-01 test <test>
122
122
123 * third:
123 * third:
124 third
124 third
125 [946e2bd9c565] [tip]
125 [946e2bd9c565] [tip]
126
126
127 1970-01-12 User Name <user@hostname>
127 1970-01-12 User Name <user@hostname>
128
128
129 * second:
129 * second:
130 second
130 second
131 [29114dbae42b]
131 [29114dbae42b]
132
132
133 1970-01-18 person <person>
133 1970-01-18 person <person>
134
134
135 * merge
135 * merge
136 [c7b487c6c50e]
136 [c7b487c6c50e]
137
137
138 * d:
138 * d:
139 new head
139 new head
140 [13207e5a10d9]
140 [13207e5a10d9]
141
141
142 1970-01-17 person <person>
142 1970-01-17 person <person>
143
143
144 * new branch
144 * new branch
145 [32a18f097fcc]
145 [32a18f097fcc]
146
146
147 1970-01-16 person <person>
147 1970-01-16 person <person>
148
148
149 * c:
149 * c:
150 no user, no domain
150 no user, no domain
151 [10e46f2dcbf4]
151 [10e46f2dcbf4]
152
152
153 1970-01-14 other <other@place>
153 1970-01-14 other <other@place>
154
154
155 * c:
155 * c:
156 no person
156 no person
157 [97054abb4ab8]
157 [97054abb4ab8]
158
158
159 1970-01-13 A. N. Other <other@place>
159 1970-01-13 A. N. Other <other@place>
160
160
161 * b:
161 * b:
162 other 1 other 2
162 other 1 other 2
163
163
164 other 3
164 other 3
165 [b608e9d1a3f0]
165 [b608e9d1a3f0]
166
166
167 1970-01-12 User Name <user@hostname>
167 1970-01-12 User Name <user@hostname>
168
168
169 * a:
169 * a:
170 line 1 line 2
170 line 1 line 2
171 [1e4e1b8f71e0]
171 [1e4e1b8f71e0]
172
172
173 # keys work
173 # keys work
174 author: test
174 author: test
175 author: User Name <user@hostname>
175 author: User Name <user@hostname>
176 author: person
176 author: person
177 author: person
177 author: person
178 author: person
178 author: person
179 author: person
179 author: person
180 author: other@place
180 author: other@place
181 author: A. N. Other <other@place>
181 author: A. N. Other <other@place>
182 author: User Name <user@hostname>
182 author: User Name <user@hostname>
183 author--verbose: test
183 author--verbose: test
184 author--verbose: User Name <user@hostname>
184 author--verbose: User Name <user@hostname>
185 author--verbose: person
185 author--verbose: person
186 author--verbose: person
186 author--verbose: person
187 author--verbose: person
187 author--verbose: person
188 author--verbose: person
188 author--verbose: person
189 author--verbose: other@place
189 author--verbose: other@place
190 author--verbose: A. N. Other <other@place>
190 author--verbose: A. N. Other <other@place>
191 author--verbose: User Name <user@hostname>
191 author--verbose: User Name <user@hostname>
192 author--debug: test
192 author--debug: test
193 author--debug: User Name <user@hostname>
193 author--debug: User Name <user@hostname>
194 author--debug: person
194 author--debug: person
195 author--debug: person
195 author--debug: person
196 author--debug: person
196 author--debug: person
197 author--debug: person
197 author--debug: person
198 author--debug: other@place
198 author--debug: other@place
199 author--debug: A. N. Other <other@place>
199 author--debug: A. N. Other <other@place>
200 author--debug: User Name <user@hostname>
200 author--debug: User Name <user@hostname>
201 branches:
201 branches:
202 branches:
202 branches:
203 branches:
203 branches:
204 branches:
204 branches:
205 branches: foo
205 branches: foo
206 branches:
206 branches:
207 branches:
207 branches:
208 branches:
208 branches:
209 branches:
209 branches:
210 branches--verbose:
210 branches--verbose:
211 branches--verbose:
211 branches--verbose:
212 branches--verbose:
212 branches--verbose:
213 branches--verbose:
213 branches--verbose:
214 branches--verbose: foo
214 branches--verbose: foo
215 branches--verbose:
215 branches--verbose:
216 branches--verbose:
216 branches--verbose:
217 branches--verbose:
217 branches--verbose:
218 branches--verbose:
218 branches--verbose:
219 branches--debug:
219 branches--debug:
220 branches--debug:
220 branches--debug:
221 branches--debug:
221 branches--debug:
222 branches--debug:
222 branches--debug:
223 branches--debug: foo
223 branches--debug: foo
224 branches--debug:
224 branches--debug:
225 branches--debug:
225 branches--debug:
226 branches--debug:
226 branches--debug:
227 branches--debug:
227 branches--debug:
228 date: 1577872860.00
228 date: 1577872860.00
229 date: 1000000.00
229 date: 1000000.00
230 date: 1500001.00
230 date: 1500001.00
231 date: 1500000.00
231 date: 1500000.00
232 date: 1400000.00
232 date: 1400000.00
233 date: 1300000.00
233 date: 1300000.00
234 date: 1200000.00
234 date: 1200000.00
235 date: 1100000.00
235 date: 1100000.00
236 date: 1000000.00
236 date: 1000000.00
237 date--verbose: 1577872860.00
237 date--verbose: 1577872860.00
238 date--verbose: 1000000.00
238 date--verbose: 1000000.00
239 date--verbose: 1500001.00
239 date--verbose: 1500001.00
240 date--verbose: 1500000.00
240 date--verbose: 1500000.00
241 date--verbose: 1400000.00
241 date--verbose: 1400000.00
242 date--verbose: 1300000.00
242 date--verbose: 1300000.00
243 date--verbose: 1200000.00
243 date--verbose: 1200000.00
244 date--verbose: 1100000.00
244 date--verbose: 1100000.00
245 date--verbose: 1000000.00
245 date--verbose: 1000000.00
246 date--debug: 1577872860.00
246 date--debug: 1577872860.00
247 date--debug: 1000000.00
247 date--debug: 1000000.00
248 date--debug: 1500001.00
248 date--debug: 1500001.00
249 date--debug: 1500000.00
249 date--debug: 1500000.00
250 date--debug: 1400000.00
250 date--debug: 1400000.00
251 date--debug: 1300000.00
251 date--debug: 1300000.00
252 date--debug: 1200000.00
252 date--debug: 1200000.00
253 date--debug: 1100000.00
253 date--debug: 1100000.00
254 date--debug: 1000000.00
254 date--debug: 1000000.00
255 desc: third
255 desc: third
256 desc: second
256 desc: second
257 desc: merge
257 desc: merge
258 desc: new head
258 desc: new head
259 desc: new branch
259 desc: new branch
260 desc: no user, no domain
260 desc: no user, no domain
261 desc: no person
261 desc: no person
262 desc: other 1
262 desc: other 1
263 other 2
263 other 2
264
264
265 other 3
265 other 3
266 desc: line 1
266 desc: line 1
267 line 2
267 line 2
268 desc--verbose: third
268 desc--verbose: third
269 desc--verbose: second
269 desc--verbose: second
270 desc--verbose: merge
270 desc--verbose: merge
271 desc--verbose: new head
271 desc--verbose: new head
272 desc--verbose: new branch
272 desc--verbose: new branch
273 desc--verbose: no user, no domain
273 desc--verbose: no user, no domain
274 desc--verbose: no person
274 desc--verbose: no person
275 desc--verbose: other 1
275 desc--verbose: other 1
276 other 2
276 other 2
277
277
278 other 3
278 other 3
279 desc--verbose: line 1
279 desc--verbose: line 1
280 line 2
280 line 2
281 desc--debug: third
281 desc--debug: third
282 desc--debug: second
282 desc--debug: second
283 desc--debug: merge
283 desc--debug: merge
284 desc--debug: new head
284 desc--debug: new head
285 desc--debug: new branch
285 desc--debug: new branch
286 desc--debug: no user, no domain
286 desc--debug: no user, no domain
287 desc--debug: no person
287 desc--debug: no person
288 desc--debug: other 1
288 desc--debug: other 1
289 other 2
289 other 2
290
290
291 other 3
291 other 3
292 desc--debug: line 1
292 desc--debug: line 1
293 line 2
293 line 2
294 file_adds: third
294 file_adds: third
295 file_adds: second
295 file_adds: second
296 file_adds:
296 file_adds:
297 file_adds: d
297 file_adds: d
298 file_adds:
298 file_adds:
299 file_adds:
299 file_adds:
300 file_adds: c
300 file_adds: c
301 file_adds: b
301 file_adds: b
302 file_adds: a
302 file_adds: a
303 file_adds--verbose: third
303 file_adds--verbose: third
304 file_adds--verbose: second
304 file_adds--verbose: second
305 file_adds--verbose:
305 file_adds--verbose:
306 file_adds--verbose: d
306 file_adds--verbose: d
307 file_adds--verbose:
307 file_adds--verbose:
308 file_adds--verbose:
308 file_adds--verbose:
309 file_adds--verbose: c
309 file_adds--verbose: c
310 file_adds--verbose: b
310 file_adds--verbose: b
311 file_adds--verbose: a
311 file_adds--verbose: a
312 file_adds--debug: third
312 file_adds--debug: third
313 file_adds--debug: second
313 file_adds--debug: second
314 file_adds--debug:
314 file_adds--debug:
315 file_adds--debug: d
315 file_adds--debug: d
316 file_adds--debug:
316 file_adds--debug:
317 file_adds--debug:
317 file_adds--debug:
318 file_adds--debug: c
318 file_adds--debug: c
319 file_adds--debug: b
319 file_adds--debug: b
320 file_adds--debug: a
320 file_adds--debug: a
321 file_dels:
321 file_dels:
322 file_dels:
322 file_dels:
323 file_dels:
323 file_dels:
324 file_dels:
324 file_dels:
325 file_dels:
325 file_dels:
326 file_dels:
326 file_dels:
327 file_dels:
327 file_dels:
328 file_dels:
328 file_dels:
329 file_dels:
329 file_dels:
330 file_dels--verbose:
330 file_dels--verbose:
331 file_dels--verbose:
331 file_dels--verbose:
332 file_dels--verbose:
332 file_dels--verbose:
333 file_dels--verbose:
333 file_dels--verbose:
334 file_dels--verbose:
334 file_dels--verbose:
335 file_dels--verbose:
335 file_dels--verbose:
336 file_dels--verbose:
336 file_dels--verbose:
337 file_dels--verbose:
337 file_dels--verbose:
338 file_dels--verbose:
338 file_dels--verbose:
339 file_dels--debug:
339 file_dels--debug:
340 file_dels--debug:
340 file_dels--debug:
341 file_dels--debug:
341 file_dels--debug:
342 file_dels--debug:
342 file_dels--debug:
343 file_dels--debug:
343 file_dels--debug:
344 file_dels--debug:
344 file_dels--debug:
345 file_dels--debug:
345 file_dels--debug:
346 file_dels--debug:
346 file_dels--debug:
347 file_dels--debug:
347 file_dels--debug:
348 file_mods:
348 file_mods:
349 file_mods:
349 file_mods:
350 file_mods:
350 file_mods:
351 file_mods:
351 file_mods:
352 file_mods:
352 file_mods:
353 file_mods: c
353 file_mods: c
354 file_mods:
354 file_mods:
355 file_mods:
355 file_mods:
356 file_mods:
356 file_mods:
357 file_mods--verbose:
357 file_mods--verbose:
358 file_mods--verbose:
358 file_mods--verbose:
359 file_mods--verbose:
359 file_mods--verbose:
360 file_mods--verbose:
360 file_mods--verbose:
361 file_mods--verbose:
361 file_mods--verbose:
362 file_mods--verbose: c
362 file_mods--verbose: c
363 file_mods--verbose:
363 file_mods--verbose:
364 file_mods--verbose:
364 file_mods--verbose:
365 file_mods--verbose:
365 file_mods--verbose:
366 file_mods--debug:
366 file_mods--debug:
367 file_mods--debug:
367 file_mods--debug:
368 file_mods--debug:
368 file_mods--debug:
369 file_mods--debug:
369 file_mods--debug:
370 file_mods--debug:
370 file_mods--debug:
371 file_mods--debug: c
371 file_mods--debug: c
372 file_mods--debug:
372 file_mods--debug:
373 file_mods--debug:
373 file_mods--debug:
374 file_mods--debug:
374 file_mods--debug:
375 files: third
375 files: third
376 files: second
376 files: second
377 files:
377 files:
378 files: d
378 files: d
379 files:
379 files:
380 files: c
380 files: c
381 files: c
381 files: c
382 files: b
382 files: b
383 files: a
383 files: a
384 files--verbose: third
384 files--verbose: third
385 files--verbose: second
385 files--verbose: second
386 files--verbose:
386 files--verbose:
387 files--verbose: d
387 files--verbose: d
388 files--verbose:
388 files--verbose:
389 files--verbose: c
389 files--verbose: c
390 files--verbose: c
390 files--verbose: c
391 files--verbose: b
391 files--verbose: b
392 files--verbose: a
392 files--verbose: a
393 files--debug: third
393 files--debug: third
394 files--debug: second
394 files--debug: second
395 files--debug:
395 files--debug:
396 files--debug: d
396 files--debug: d
397 files--debug:
397 files--debug:
398 files--debug: c
398 files--debug: c
399 files--debug: c
399 files--debug: c
400 files--debug: b
400 files--debug: b
401 files--debug: a
401 files--debug: a
402 manifest: 8:8a0d8faab8b2
402 manifest: 8:8a0d8faab8b2
403 manifest: 7:f2dbc354b94e
403 manifest: 7:f2dbc354b94e
404 manifest: 6:91015e9dbdd7
404 manifest: 6:91015e9dbdd7
405 manifest: 5:4dc3def4f9b4
405 manifest: 5:4dc3def4f9b4
406 manifest: 4:90ae8dda64e1
406 manifest: 4:90ae8dda64e1
407 manifest: 3:cb5a1327723b
407 manifest: 3:cb5a1327723b
408 manifest: 2:6e0e82995c35
408 manifest: 2:6e0e82995c35
409 manifest: 1:4e8d705b1e53
409 manifest: 1:4e8d705b1e53
410 manifest: 0:a0c8bcbbb45c
410 manifest: 0:a0c8bcbbb45c
411 manifest--verbose: 8:8a0d8faab8b2
411 manifest--verbose: 8:8a0d8faab8b2
412 manifest--verbose: 7:f2dbc354b94e
412 manifest--verbose: 7:f2dbc354b94e
413 manifest--verbose: 6:91015e9dbdd7
413 manifest--verbose: 6:91015e9dbdd7
414 manifest--verbose: 5:4dc3def4f9b4
414 manifest--verbose: 5:4dc3def4f9b4
415 manifest--verbose: 4:90ae8dda64e1
415 manifest--verbose: 4:90ae8dda64e1
416 manifest--verbose: 3:cb5a1327723b
416 manifest--verbose: 3:cb5a1327723b
417 manifest--verbose: 2:6e0e82995c35
417 manifest--verbose: 2:6e0e82995c35
418 manifest--verbose: 1:4e8d705b1e53
418 manifest--verbose: 1:4e8d705b1e53
419 manifest--verbose: 0:a0c8bcbbb45c
419 manifest--verbose: 0:a0c8bcbbb45c
420 manifest--debug: 8:8a0d8faab8b2eee97dcfccabbcb18f413c9d097b
420 manifest--debug: 8:8a0d8faab8b2eee97dcfccabbcb18f413c9d097b
421 manifest--debug: 7:f2dbc354b94e5ec0b4f10680ee0cee816101d0bf
421 manifest--debug: 7:f2dbc354b94e5ec0b4f10680ee0cee816101d0bf
422 manifest--debug: 6:91015e9dbdd76a6791085d12b0a0ec7fcd22ffbf
422 manifest--debug: 6:91015e9dbdd76a6791085d12b0a0ec7fcd22ffbf
423 manifest--debug: 5:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
423 manifest--debug: 5:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
424 manifest--debug: 4:90ae8dda64e1a876c792bccb9af66284f6018363
424 manifest--debug: 4:90ae8dda64e1a876c792bccb9af66284f6018363
425 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
425 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
426 manifest--debug: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
426 manifest--debug: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
427 manifest--debug: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
427 manifest--debug: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
428 manifest--debug: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
428 manifest--debug: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
429 node: 946e2bd9c565394777d74d9669045b39e856e3ea
429 node: 946e2bd9c565394777d74d9669045b39e856e3ea
430 node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
430 node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
431 node: c7b487c6c50ef1cf464cafdc4f4f5e615fc5999f
431 node: c7b487c6c50ef1cf464cafdc4f4f5e615fc5999f
432 node: 13207e5a10d9fd28ec424934298e176197f2c67f
432 node: 13207e5a10d9fd28ec424934298e176197f2c67f
433 node: 32a18f097fcccf76ef282f62f8a85b3adf8d13c4
433 node: 32a18f097fcccf76ef282f62f8a85b3adf8d13c4
434 node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
434 node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
435 node: 97054abb4ab824450e9164180baf491ae0078465
435 node: 97054abb4ab824450e9164180baf491ae0078465
436 node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
436 node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
437 node: 1e4e1b8f71e05681d422154f5421e385fec3454f
437 node: 1e4e1b8f71e05681d422154f5421e385fec3454f
438 node--verbose: 946e2bd9c565394777d74d9669045b39e856e3ea
438 node--verbose: 946e2bd9c565394777d74d9669045b39e856e3ea
439 node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
439 node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
440 node--verbose: c7b487c6c50ef1cf464cafdc4f4f5e615fc5999f
440 node--verbose: c7b487c6c50ef1cf464cafdc4f4f5e615fc5999f
441 node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
441 node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
442 node--verbose: 32a18f097fcccf76ef282f62f8a85b3adf8d13c4
442 node--verbose: 32a18f097fcccf76ef282f62f8a85b3adf8d13c4
443 node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
443 node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
444 node--verbose: 97054abb4ab824450e9164180baf491ae0078465
444 node--verbose: 97054abb4ab824450e9164180baf491ae0078465
445 node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
445 node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
446 node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
446 node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
447 node--debug: 946e2bd9c565394777d74d9669045b39e856e3ea
447 node--debug: 946e2bd9c565394777d74d9669045b39e856e3ea
448 node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
448 node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
449 node--debug: c7b487c6c50ef1cf464cafdc4f4f5e615fc5999f
449 node--debug: c7b487c6c50ef1cf464cafdc4f4f5e615fc5999f
450 node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
450 node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
451 node--debug: 32a18f097fcccf76ef282f62f8a85b3adf8d13c4
451 node--debug: 32a18f097fcccf76ef282f62f8a85b3adf8d13c4
452 node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
452 node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
453 node--debug: 97054abb4ab824450e9164180baf491ae0078465
453 node--debug: 97054abb4ab824450e9164180baf491ae0078465
454 node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
454 node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
455 node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
455 node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
456 parents:
456 parents:
457 parents: -1:000000000000
457 parents: -1:000000000000
458 parents: 5:13207e5a10d9 4:32a18f097fcc
458 parents: 5:13207e5a10d9 4:32a18f097fcc
459 parents: 3:10e46f2dcbf4
459 parents: 3:10e46f2dcbf4
460 parents:
460 parents:
461 parents:
461 parents:
462 parents:
462 parents:
463 parents:
463 parents:
464 parents:
464 parents:
465 parents--verbose:
465 parents--verbose:
466 parents--verbose: -1:000000000000
466 parents--verbose: -1:000000000000
467 parents--verbose: 5:13207e5a10d9 4:32a18f097fcc
467 parents--verbose: 5:13207e5a10d9 4:32a18f097fcc
468 parents--verbose: 3:10e46f2dcbf4
468 parents--verbose: 3:10e46f2dcbf4
469 parents--verbose:
469 parents--verbose:
470 parents--verbose:
470 parents--verbose:
471 parents--verbose:
471 parents--verbose:
472 parents--verbose:
472 parents--verbose:
473 parents--verbose:
473 parents--verbose:
474 parents--debug: 7:29114dbae42b9f078cf2714dbe3a86bba8ec7453 -1:0000000000000000000000000000000000000000
474 parents--debug: 7:29114dbae42b9f078cf2714dbe3a86bba8ec7453 -1:0000000000000000000000000000000000000000
475 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
475 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
476 parents--debug: 5:13207e5a10d9fd28ec424934298e176197f2c67f 4:32a18f097fcccf76ef282f62f8a85b3adf8d13c4
476 parents--debug: 5:13207e5a10d9fd28ec424934298e176197f2c67f 4:32a18f097fcccf76ef282f62f8a85b3adf8d13c4
477 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
477 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
478 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
478 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
479 parents--debug: 2:97054abb4ab824450e9164180baf491ae0078465 -1:0000000000000000000000000000000000000000
479 parents--debug: 2:97054abb4ab824450e9164180baf491ae0078465 -1:0000000000000000000000000000000000000000
480 parents--debug: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965 -1:0000000000000000000000000000000000000000
480 parents--debug: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965 -1:0000000000000000000000000000000000000000
481 parents--debug: 0:1e4e1b8f71e05681d422154f5421e385fec3454f -1:0000000000000000000000000000000000000000
481 parents--debug: 0:1e4e1b8f71e05681d422154f5421e385fec3454f -1:0000000000000000000000000000000000000000
482 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
482 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
483 rev: 8
483 rev: 8
484 rev: 7
484 rev: 7
485 rev: 6
485 rev: 6
486 rev: 5
486 rev: 5
487 rev: 4
487 rev: 4
488 rev: 3
488 rev: 3
489 rev: 2
489 rev: 2
490 rev: 1
490 rev: 1
491 rev: 0
491 rev: 0
492 rev--verbose: 8
492 rev--verbose: 8
493 rev--verbose: 7
493 rev--verbose: 7
494 rev--verbose: 6
494 rev--verbose: 6
495 rev--verbose: 5
495 rev--verbose: 5
496 rev--verbose: 4
496 rev--verbose: 4
497 rev--verbose: 3
497 rev--verbose: 3
498 rev--verbose: 2
498 rev--verbose: 2
499 rev--verbose: 1
499 rev--verbose: 1
500 rev--verbose: 0
500 rev--verbose: 0
501 rev--debug: 8
501 rev--debug: 8
502 rev--debug: 7
502 rev--debug: 7
503 rev--debug: 6
503 rev--debug: 6
504 rev--debug: 5
504 rev--debug: 5
505 rev--debug: 4
505 rev--debug: 4
506 rev--debug: 3
506 rev--debug: 3
507 rev--debug: 2
507 rev--debug: 2
508 rev--debug: 1
508 rev--debug: 1
509 rev--debug: 0
509 rev--debug: 0
510 tags: tip
510 tags: tip
511 tags:
511 tags:
512 tags:
512 tags:
513 tags:
513 tags:
514 tags:
514 tags:
515 tags:
515 tags:
516 tags:
516 tags:
517 tags:
517 tags:
518 tags:
518 tags:
519 tags--verbose: tip
519 tags--verbose: tip
520 tags--verbose:
520 tags--verbose:
521 tags--verbose:
521 tags--verbose:
522 tags--verbose:
522 tags--verbose:
523 tags--verbose:
523 tags--verbose:
524 tags--verbose:
524 tags--verbose:
525 tags--verbose:
525 tags--verbose:
526 tags--verbose:
526 tags--verbose:
527 tags--verbose:
527 tags--verbose:
528 tags--debug: tip
528 tags--debug: tip
529 tags--debug:
529 tags--debug:
530 tags--debug:
530 tags--debug:
531 tags--debug:
531 tags--debug:
532 tags--debug:
532 tags--debug:
533 tags--debug:
533 tags--debug:
534 tags--debug:
534 tags--debug:
535 tags--debug:
535 tags--debug:
536 tags--debug:
536 tags--debug:
537 diffstat: 1: +1/-0
538 diffstat: 1: +1/-0
539 diffstat: 0: +0/-0
540 diffstat: 1: +1/-0
541 diffstat: 0: +0/-0
542 diffstat: 1: +1/-0
543 diffstat: 1: +4/-0
544 diffstat: 1: +2/-0
545 diffstat: 1: +1/-0
546 diffstat--verbose: 1: +1/-0
547 diffstat--verbose: 1: +1/-0
548 diffstat--verbose: 0: +0/-0
549 diffstat--verbose: 1: +1/-0
550 diffstat--verbose: 0: +0/-0
551 diffstat--verbose: 1: +1/-0
552 diffstat--verbose: 1: +4/-0
553 diffstat--verbose: 1: +2/-0
554 diffstat--verbose: 1: +1/-0
555 diffstat--debug: 1: +1/-0
556 diffstat--debug: 1: +1/-0
557 diffstat--debug: 0: +0/-0
558 diffstat--debug: 1: +1/-0
559 diffstat--debug: 0: +0/-0
560 diffstat--debug: 1: +1/-0
561 diffstat--debug: 1: +4/-0
562 diffstat--debug: 1: +2/-0
563 diffstat--debug: 1: +1/-0
537 # filters work
564 # filters work
538
565
539 hostname
566 hostname
540
567
541
568
542
569
543
570
544 place
571 place
545 place
572 place
546 hostname
573 hostname
547 test
574 test
548 User Name
575 User Name
549 person
576 person
550 person
577 person
551 person
578 person
552 person
579 person
553 other
580 other
554 A. N. Other
581 A. N. Other
555 User Name
582 User Name
556 test
583 test
557 user
584 user
558 person
585 person
559 person
586 person
560 person
587 person
561 person
588 person
562 other
589 other
563 other
590 other
564 user
591 user
565 in the future
592 in the future
566 Wed Jan 01 10:01:00 2020 +0000
593 Wed Jan 01 10:01:00 2020 +0000
567 Mon Jan 12 13:46:40 1970 +0000
594 Mon Jan 12 13:46:40 1970 +0000
568 Sun Jan 18 08:40:01 1970 +0000
595 Sun Jan 18 08:40:01 1970 +0000
569 Sun Jan 18 08:40:00 1970 +0000
596 Sun Jan 18 08:40:00 1970 +0000
570 Sat Jan 17 04:53:20 1970 +0000
597 Sat Jan 17 04:53:20 1970 +0000
571 Fri Jan 16 01:06:40 1970 +0000
598 Fri Jan 16 01:06:40 1970 +0000
572 Wed Jan 14 21:20:00 1970 +0000
599 Wed Jan 14 21:20:00 1970 +0000
573 Tue Jan 13 17:33:20 1970 +0000
600 Tue Jan 13 17:33:20 1970 +0000
574 Mon Jan 12 13:46:40 1970 +0000
601 Mon Jan 12 13:46:40 1970 +0000
575 2020-01-01 10:01 +0000
602 2020-01-01 10:01 +0000
576 1970-01-12 13:46 +0000
603 1970-01-12 13:46 +0000
577 1970-01-18 08:40 +0000
604 1970-01-18 08:40 +0000
578 1970-01-18 08:40 +0000
605 1970-01-18 08:40 +0000
579 1970-01-17 04:53 +0000
606 1970-01-17 04:53 +0000
580 1970-01-16 01:06 +0000
607 1970-01-16 01:06 +0000
581 1970-01-14 21:20 +0000
608 1970-01-14 21:20 +0000
582 1970-01-13 17:33 +0000
609 1970-01-13 17:33 +0000
583 1970-01-12 13:46 +0000
610 1970-01-12 13:46 +0000
584 2020-01-01 10:01:00 +0000
611 2020-01-01 10:01:00 +0000
585 1970-01-12 13:46:40 +0000
612 1970-01-12 13:46:40 +0000
586 1970-01-18 08:40:01 +0000
613 1970-01-18 08:40:01 +0000
587 1970-01-18 08:40:00 +0000
614 1970-01-18 08:40:00 +0000
588 1970-01-17 04:53:20 +0000
615 1970-01-17 04:53:20 +0000
589 1970-01-16 01:06:40 +0000
616 1970-01-16 01:06:40 +0000
590 1970-01-14 21:20:00 +0000
617 1970-01-14 21:20:00 +0000
591 1970-01-13 17:33:20 +0000
618 1970-01-13 17:33:20 +0000
592 1970-01-12 13:46:40 +0000
619 1970-01-12 13:46:40 +0000
593 Wed, 01 Jan 2020 10:01:00 +0000
620 Wed, 01 Jan 2020 10:01:00 +0000
594 Mon, 12 Jan 1970 13:46:40 +0000
621 Mon, 12 Jan 1970 13:46:40 +0000
595 Sun, 18 Jan 1970 08:40:01 +0000
622 Sun, 18 Jan 1970 08:40:01 +0000
596 Sun, 18 Jan 1970 08:40:00 +0000
623 Sun, 18 Jan 1970 08:40:00 +0000
597 Sat, 17 Jan 1970 04:53:20 +0000
624 Sat, 17 Jan 1970 04:53:20 +0000
598 Fri, 16 Jan 1970 01:06:40 +0000
625 Fri, 16 Jan 1970 01:06:40 +0000
599 Wed, 14 Jan 1970 21:20:00 +0000
626 Wed, 14 Jan 1970 21:20:00 +0000
600 Tue, 13 Jan 1970 17:33:20 +0000
627 Tue, 13 Jan 1970 17:33:20 +0000
601 Mon, 12 Jan 1970 13:46:40 +0000
628 Mon, 12 Jan 1970 13:46:40 +0000
602 third
629 third
603 second
630 second
604 merge
631 merge
605 new head
632 new head
606 new branch
633 new branch
607 no user, no domain
634 no user, no domain
608 no person
635 no person
609 other 1
636 other 1
610 line 1
637 line 1
611 946e2bd9c565
638 946e2bd9c565
612 29114dbae42b
639 29114dbae42b
613 c7b487c6c50e
640 c7b487c6c50e
614 13207e5a10d9
641 13207e5a10d9
615 32a18f097fcc
642 32a18f097fcc
616 10e46f2dcbf4
643 10e46f2dcbf4
617 97054abb4ab8
644 97054abb4ab8
618 b608e9d1a3f0
645 b608e9d1a3f0
619 1e4e1b8f71e0
646 1e4e1b8f71e0
620 <changeset author="test"/>
647 <changeset author="test"/>
621 <changeset author="User Name &lt;user@hostname&gt;"/>
648 <changeset author="User Name &lt;user@hostname&gt;"/>
622 <changeset author="person"/>
649 <changeset author="person"/>
623 <changeset author="person"/>
650 <changeset author="person"/>
624 <changeset author="person"/>
651 <changeset author="person"/>
625 <changeset author="person"/>
652 <changeset author="person"/>
626 <changeset author="other@place"/>
653 <changeset author="other@place"/>
627 <changeset author="A. N. Other &lt;other@place&gt;"/>
654 <changeset author="A. N. Other &lt;other@place&gt;"/>
628 <changeset author="User Name &lt;user@hostname&gt;"/>
655 <changeset author="User Name &lt;user@hostname&gt;"/>
629 # formatnode filter works
656 # formatnode filter works
630 # quiet
657 # quiet
631 1e4e1b8f71e0
658 1e4e1b8f71e0
632 # normal
659 # normal
633 1e4e1b8f71e0
660 1e4e1b8f71e0
634 # verbose
661 # verbose
635 1e4e1b8f71e0
662 1e4e1b8f71e0
636 # debug
663 # debug
637 1e4e1b8f71e05681d422154f5421e385fec3454f
664 1e4e1b8f71e05681d422154f5421e385fec3454f
638 # error on syntax
665 # error on syntax
639 abort: t:3: unmatched quotes
666 abort: t:3: unmatched quotes
640 # done
667 # done
General Comments 0
You need to be logged in to leave comments. Login now