##// END OF EJS Templates
serve: add and use portable spawnvp replacement...
Patrick Mezard -
r10237:2f7a38f3 default
parent child Browse files
Show More
@@ -1,1166 +1,1165 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, glob
10 import os, sys, errno, re, glob
11 import mdiff, bdiff, util, templater, patch, error, encoding, templatekw
11 import mdiff, bdiff, util, templater, patch, error, encoding, templatekw
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 = None
98 limit = None
99 return limit
99 return limit
100
100
101 def remoteui(src, opts):
101 def remoteui(src, opts):
102 'build a remote ui from ui or repo and opts'
102 'build a remote ui from ui or repo and opts'
103 if hasattr(src, 'baseui'): # looks like a repository
103 if hasattr(src, 'baseui'): # looks like a repository
104 dst = src.baseui.copy() # drop repo-specific config
104 dst = src.baseui.copy() # drop repo-specific config
105 src = src.ui # copy target options from repo
105 src = src.ui # copy target options from repo
106 else: # assume it's a global ui object
106 else: # assume it's a global ui object
107 dst = src.copy() # keep all global options
107 dst = src.copy() # keep all global options
108
108
109 # copy ssh-specific options
109 # copy ssh-specific options
110 for o in 'ssh', 'remotecmd':
110 for o in 'ssh', 'remotecmd':
111 v = opts.get(o) or src.config('ui', o)
111 v = opts.get(o) or src.config('ui', o)
112 if v:
112 if v:
113 dst.setconfig("ui", o, v)
113 dst.setconfig("ui", o, v)
114
114
115 # copy bundle-specific options
115 # copy bundle-specific options
116 r = src.config('bundle', 'mainreporoot')
116 r = src.config('bundle', 'mainreporoot')
117 if r:
117 if r:
118 dst.setconfig('bundle', 'mainreporoot', r)
118 dst.setconfig('bundle', 'mainreporoot', r)
119
119
120 # copy auth section settings
120 # copy auth section settings
121 for key, val in src.configitems('auth'):
121 for key, val in src.configitems('auth'):
122 dst.setconfig('auth', key, val)
122 dst.setconfig('auth', key, val)
123
123
124 return dst
124 return dst
125
125
126 def revpair(repo, revs):
126 def revpair(repo, revs):
127 '''return pair of nodes, given list of revisions. second item can
127 '''return pair of nodes, given list of revisions. second item can
128 be None, meaning use working dir.'''
128 be None, meaning use working dir.'''
129
129
130 def revfix(repo, val, defval):
130 def revfix(repo, val, defval):
131 if not val and val != 0 and defval is not None:
131 if not val and val != 0 and defval is not None:
132 val = defval
132 val = defval
133 return repo.lookup(val)
133 return repo.lookup(val)
134
134
135 if not revs:
135 if not revs:
136 return repo.dirstate.parents()[0], None
136 return repo.dirstate.parents()[0], None
137 end = None
137 end = None
138 if len(revs) == 1:
138 if len(revs) == 1:
139 if revrangesep in revs[0]:
139 if revrangesep in revs[0]:
140 start, end = revs[0].split(revrangesep, 1)
140 start, end = revs[0].split(revrangesep, 1)
141 start = revfix(repo, start, 0)
141 start = revfix(repo, start, 0)
142 end = revfix(repo, end, len(repo) - 1)
142 end = revfix(repo, end, len(repo) - 1)
143 else:
143 else:
144 start = revfix(repo, revs[0], None)
144 start = revfix(repo, revs[0], None)
145 elif len(revs) == 2:
145 elif len(revs) == 2:
146 if revrangesep in revs[0] or revrangesep in revs[1]:
146 if revrangesep in revs[0] or revrangesep in revs[1]:
147 raise util.Abort(_('too many revisions specified'))
147 raise util.Abort(_('too many revisions specified'))
148 start = revfix(repo, revs[0], None)
148 start = revfix(repo, revs[0], None)
149 end = revfix(repo, revs[1], None)
149 end = revfix(repo, revs[1], None)
150 else:
150 else:
151 raise util.Abort(_('too many revisions specified'))
151 raise util.Abort(_('too many revisions specified'))
152 return start, end
152 return start, end
153
153
154 def revrange(repo, revs):
154 def revrange(repo, revs):
155 """Yield revision as strings from a list of revision specifications."""
155 """Yield revision as strings from a list of revision specifications."""
156
156
157 def revfix(repo, val, defval):
157 def revfix(repo, val, defval):
158 if not val and val != 0 and defval is not None:
158 if not val and val != 0 and defval is not None:
159 return defval
159 return defval
160 return repo.changelog.rev(repo.lookup(val))
160 return repo.changelog.rev(repo.lookup(val))
161
161
162 seen, l = set(), []
162 seen, l = set(), []
163 for spec in revs:
163 for spec in revs:
164 if revrangesep in spec:
164 if revrangesep in spec:
165 start, end = spec.split(revrangesep, 1)
165 start, end = spec.split(revrangesep, 1)
166 start = revfix(repo, start, 0)
166 start = revfix(repo, start, 0)
167 end = revfix(repo, end, len(repo) - 1)
167 end = revfix(repo, end, len(repo) - 1)
168 step = start > end and -1 or 1
168 step = start > end and -1 or 1
169 for rev in xrange(start, end+step, step):
169 for rev in xrange(start, end+step, step):
170 if rev in seen:
170 if rev in seen:
171 continue
171 continue
172 seen.add(rev)
172 seen.add(rev)
173 l.append(rev)
173 l.append(rev)
174 else:
174 else:
175 rev = revfix(repo, spec, None)
175 rev = revfix(repo, spec, None)
176 if rev in seen:
176 if rev in seen:
177 continue
177 continue
178 seen.add(rev)
178 seen.add(rev)
179 l.append(rev)
179 l.append(rev)
180
180
181 return l
181 return l
182
182
183 def make_filename(repo, pat, node,
183 def make_filename(repo, pat, node,
184 total=None, seqno=None, revwidth=None, pathname=None):
184 total=None, seqno=None, revwidth=None, pathname=None):
185 node_expander = {
185 node_expander = {
186 'H': lambda: hex(node),
186 'H': lambda: hex(node),
187 'R': lambda: str(repo.changelog.rev(node)),
187 'R': lambda: str(repo.changelog.rev(node)),
188 'h': lambda: short(node),
188 'h': lambda: short(node),
189 }
189 }
190 expander = {
190 expander = {
191 '%': lambda: '%',
191 '%': lambda: '%',
192 'b': lambda: os.path.basename(repo.root),
192 'b': lambda: os.path.basename(repo.root),
193 }
193 }
194
194
195 try:
195 try:
196 if node:
196 if node:
197 expander.update(node_expander)
197 expander.update(node_expander)
198 if node:
198 if node:
199 expander['r'] = (lambda:
199 expander['r'] = (lambda:
200 str(repo.changelog.rev(node)).zfill(revwidth or 0))
200 str(repo.changelog.rev(node)).zfill(revwidth or 0))
201 if total is not None:
201 if total is not None:
202 expander['N'] = lambda: str(total)
202 expander['N'] = lambda: str(total)
203 if seqno is not None:
203 if seqno is not None:
204 expander['n'] = lambda: str(seqno)
204 expander['n'] = lambda: str(seqno)
205 if total is not None and seqno is not None:
205 if total is not None and seqno is not None:
206 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
206 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
207 if pathname is not None:
207 if pathname is not None:
208 expander['s'] = lambda: os.path.basename(pathname)
208 expander['s'] = lambda: os.path.basename(pathname)
209 expander['d'] = lambda: os.path.dirname(pathname) or '.'
209 expander['d'] = lambda: os.path.dirname(pathname) or '.'
210 expander['p'] = lambda: pathname
210 expander['p'] = lambda: pathname
211
211
212 newname = []
212 newname = []
213 patlen = len(pat)
213 patlen = len(pat)
214 i = 0
214 i = 0
215 while i < patlen:
215 while i < patlen:
216 c = pat[i]
216 c = pat[i]
217 if c == '%':
217 if c == '%':
218 i += 1
218 i += 1
219 c = pat[i]
219 c = pat[i]
220 c = expander[c]()
220 c = expander[c]()
221 newname.append(c)
221 newname.append(c)
222 i += 1
222 i += 1
223 return ''.join(newname)
223 return ''.join(newname)
224 except KeyError, inst:
224 except KeyError, inst:
225 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
225 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
226 inst.args[0])
226 inst.args[0])
227
227
228 def make_file(repo, pat, node=None,
228 def make_file(repo, pat, node=None,
229 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
229 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
230
230
231 writable = 'w' in mode or 'a' in mode
231 writable = 'w' in mode or 'a' in mode
232
232
233 if not pat or pat == '-':
233 if not pat or pat == '-':
234 return writable and sys.stdout or sys.stdin
234 return writable and sys.stdout or sys.stdin
235 if hasattr(pat, 'write') and writable:
235 if hasattr(pat, 'write') and writable:
236 return pat
236 return pat
237 if hasattr(pat, 'read') and 'r' in mode:
237 if hasattr(pat, 'read') and 'r' in mode:
238 return pat
238 return pat
239 return open(make_filename(repo, pat, node, total, seqno, revwidth,
239 return open(make_filename(repo, pat, node, total, seqno, revwidth,
240 pathname),
240 pathname),
241 mode)
241 mode)
242
242
243 def expandpats(pats):
243 def expandpats(pats):
244 if not util.expandglobs:
244 if not util.expandglobs:
245 return list(pats)
245 return list(pats)
246 ret = []
246 ret = []
247 for p in pats:
247 for p in pats:
248 kind, name = _match._patsplit(p, None)
248 kind, name = _match._patsplit(p, None)
249 if kind is None:
249 if kind is None:
250 try:
250 try:
251 globbed = glob.glob(name)
251 globbed = glob.glob(name)
252 except re.error:
252 except re.error:
253 globbed = [name]
253 globbed = [name]
254 if globbed:
254 if globbed:
255 ret.extend(globbed)
255 ret.extend(globbed)
256 continue
256 continue
257 ret.append(p)
257 ret.append(p)
258 return ret
258 return ret
259
259
260 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
260 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
261 if not globbed and default == 'relpath':
261 if not globbed and default == 'relpath':
262 pats = expandpats(pats or [])
262 pats = expandpats(pats or [])
263 m = _match.match(repo.root, repo.getcwd(), pats,
263 m = _match.match(repo.root, repo.getcwd(), pats,
264 opts.get('include'), opts.get('exclude'), default)
264 opts.get('include'), opts.get('exclude'), default)
265 def badfn(f, msg):
265 def badfn(f, msg):
266 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
266 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
267 m.bad = badfn
267 m.bad = badfn
268 return m
268 return m
269
269
270 def matchall(repo):
270 def matchall(repo):
271 return _match.always(repo.root, repo.getcwd())
271 return _match.always(repo.root, repo.getcwd())
272
272
273 def matchfiles(repo, files):
273 def matchfiles(repo, files):
274 return _match.exact(repo.root, repo.getcwd(), files)
274 return _match.exact(repo.root, repo.getcwd(), files)
275
275
276 def findrenames(repo, added, removed, threshold):
276 def findrenames(repo, added, removed, threshold):
277 '''find renamed files -- yields (before, after, score) tuples'''
277 '''find renamed files -- yields (before, after, score) tuples'''
278 copies = {}
278 copies = {}
279 ctx = repo['.']
279 ctx = repo['.']
280 for r in removed:
280 for r in removed:
281 if r not in ctx:
281 if r not in ctx:
282 continue
282 continue
283 fctx = ctx.filectx(r)
283 fctx = ctx.filectx(r)
284
284
285 def score(text):
285 def score(text):
286 if not len(text):
286 if not len(text):
287 return 0.0
287 return 0.0
288 if not fctx.cmp(text):
288 if not fctx.cmp(text):
289 return 1.0
289 return 1.0
290 if threshold == 1.0:
290 if threshold == 1.0:
291 return 0.0
291 return 0.0
292 orig = fctx.data()
292 orig = fctx.data()
293 # bdiff.blocks() returns blocks of matching lines
293 # bdiff.blocks() returns blocks of matching lines
294 # count the number of bytes in each
294 # count the number of bytes in each
295 equal = 0
295 equal = 0
296 alines = mdiff.splitnewlines(text)
296 alines = mdiff.splitnewlines(text)
297 matches = bdiff.blocks(text, orig)
297 matches = bdiff.blocks(text, orig)
298 for x1, x2, y1, y2 in matches:
298 for x1, x2, y1, y2 in matches:
299 for line in alines[x1:x2]:
299 for line in alines[x1:x2]:
300 equal += len(line)
300 equal += len(line)
301
301
302 lengths = len(text) + len(orig)
302 lengths = len(text) + len(orig)
303 return equal * 2.0 / lengths
303 return equal * 2.0 / lengths
304
304
305 for a in added:
305 for a in added:
306 bestscore = copies.get(a, (None, threshold))[1]
306 bestscore = copies.get(a, (None, threshold))[1]
307 myscore = score(repo.wread(a))
307 myscore = score(repo.wread(a))
308 if myscore >= bestscore:
308 if myscore >= bestscore:
309 copies[a] = (r, myscore)
309 copies[a] = (r, myscore)
310
310
311 for dest, v in copies.iteritems():
311 for dest, v in copies.iteritems():
312 source, score = v
312 source, score = v
313 yield source, dest, score
313 yield source, dest, score
314
314
315 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
315 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
316 if dry_run is None:
316 if dry_run is None:
317 dry_run = opts.get('dry_run')
317 dry_run = opts.get('dry_run')
318 if similarity is None:
318 if similarity is None:
319 similarity = float(opts.get('similarity') or 0)
319 similarity = float(opts.get('similarity') or 0)
320 # we'd use status here, except handling of symlinks and ignore is tricky
320 # we'd use status here, except handling of symlinks and ignore is tricky
321 added, unknown, deleted, removed = [], [], [], []
321 added, unknown, deleted, removed = [], [], [], []
322 audit_path = util.path_auditor(repo.root)
322 audit_path = util.path_auditor(repo.root)
323 m = match(repo, pats, opts)
323 m = match(repo, pats, opts)
324 for abs in repo.walk(m):
324 for abs in repo.walk(m):
325 target = repo.wjoin(abs)
325 target = repo.wjoin(abs)
326 good = True
326 good = True
327 try:
327 try:
328 audit_path(abs)
328 audit_path(abs)
329 except:
329 except:
330 good = False
330 good = False
331 rel = m.rel(abs)
331 rel = m.rel(abs)
332 exact = m.exact(abs)
332 exact = m.exact(abs)
333 if good and abs not in repo.dirstate:
333 if good and abs not in repo.dirstate:
334 unknown.append(abs)
334 unknown.append(abs)
335 if repo.ui.verbose or not exact:
335 if repo.ui.verbose or not exact:
336 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
336 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
337 elif repo.dirstate[abs] != 'r' and (not good or not util.lexists(target)
337 elif repo.dirstate[abs] != 'r' and (not good or not util.lexists(target)
338 or (os.path.isdir(target) and not os.path.islink(target))):
338 or (os.path.isdir(target) and not os.path.islink(target))):
339 deleted.append(abs)
339 deleted.append(abs)
340 if repo.ui.verbose or not exact:
340 if repo.ui.verbose or not exact:
341 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
341 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
342 # for finding renames
342 # for finding renames
343 elif repo.dirstate[abs] == 'r':
343 elif repo.dirstate[abs] == 'r':
344 removed.append(abs)
344 removed.append(abs)
345 elif repo.dirstate[abs] == 'a':
345 elif repo.dirstate[abs] == 'a':
346 added.append(abs)
346 added.append(abs)
347 if not dry_run:
347 if not dry_run:
348 repo.remove(deleted)
348 repo.remove(deleted)
349 repo.add(unknown)
349 repo.add(unknown)
350 if similarity > 0:
350 if similarity > 0:
351 for old, new, score in findrenames(repo, added + unknown,
351 for old, new, score in findrenames(repo, added + unknown,
352 removed + deleted, similarity):
352 removed + deleted, similarity):
353 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
353 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
354 repo.ui.status(_('recording removal of %s as rename to %s '
354 repo.ui.status(_('recording removal of %s as rename to %s '
355 '(%d%% similar)\n') %
355 '(%d%% similar)\n') %
356 (m.rel(old), m.rel(new), score * 100))
356 (m.rel(old), m.rel(new), score * 100))
357 if not dry_run:
357 if not dry_run:
358 repo.copy(old, new)
358 repo.copy(old, new)
359
359
360 def copy(ui, repo, pats, opts, rename=False):
360 def copy(ui, repo, pats, opts, rename=False):
361 # called with the repo lock held
361 # called with the repo lock held
362 #
362 #
363 # hgsep => pathname that uses "/" to separate directories
363 # hgsep => pathname that uses "/" to separate directories
364 # ossep => pathname that uses os.sep to separate directories
364 # ossep => pathname that uses os.sep to separate directories
365 cwd = repo.getcwd()
365 cwd = repo.getcwd()
366 targets = {}
366 targets = {}
367 after = opts.get("after")
367 after = opts.get("after")
368 dryrun = opts.get("dry_run")
368 dryrun = opts.get("dry_run")
369
369
370 def walkpat(pat):
370 def walkpat(pat):
371 srcs = []
371 srcs = []
372 m = match(repo, [pat], opts, globbed=True)
372 m = match(repo, [pat], opts, globbed=True)
373 for abs in repo.walk(m):
373 for abs in repo.walk(m):
374 state = repo.dirstate[abs]
374 state = repo.dirstate[abs]
375 rel = m.rel(abs)
375 rel = m.rel(abs)
376 exact = m.exact(abs)
376 exact = m.exact(abs)
377 if state in '?r':
377 if state in '?r':
378 if exact and state == '?':
378 if exact and state == '?':
379 ui.warn(_('%s: not copying - file is not managed\n') % rel)
379 ui.warn(_('%s: not copying - file is not managed\n') % rel)
380 if exact and state == 'r':
380 if exact and state == 'r':
381 ui.warn(_('%s: not copying - file has been marked for'
381 ui.warn(_('%s: not copying - file has been marked for'
382 ' remove\n') % rel)
382 ' remove\n') % rel)
383 continue
383 continue
384 # abs: hgsep
384 # abs: hgsep
385 # rel: ossep
385 # rel: ossep
386 srcs.append((abs, rel, exact))
386 srcs.append((abs, rel, exact))
387 return srcs
387 return srcs
388
388
389 # abssrc: hgsep
389 # abssrc: hgsep
390 # relsrc: ossep
390 # relsrc: ossep
391 # otarget: ossep
391 # otarget: ossep
392 def copyfile(abssrc, relsrc, otarget, exact):
392 def copyfile(abssrc, relsrc, otarget, exact):
393 abstarget = util.canonpath(repo.root, cwd, otarget)
393 abstarget = util.canonpath(repo.root, cwd, otarget)
394 reltarget = repo.pathto(abstarget, cwd)
394 reltarget = repo.pathto(abstarget, cwd)
395 target = repo.wjoin(abstarget)
395 target = repo.wjoin(abstarget)
396 src = repo.wjoin(abssrc)
396 src = repo.wjoin(abssrc)
397 state = repo.dirstate[abstarget]
397 state = repo.dirstate[abstarget]
398
398
399 # check for collisions
399 # check for collisions
400 prevsrc = targets.get(abstarget)
400 prevsrc = targets.get(abstarget)
401 if prevsrc is not None:
401 if prevsrc is not None:
402 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
402 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
403 (reltarget, repo.pathto(abssrc, cwd),
403 (reltarget, repo.pathto(abssrc, cwd),
404 repo.pathto(prevsrc, cwd)))
404 repo.pathto(prevsrc, cwd)))
405 return
405 return
406
406
407 # check for overwrites
407 # check for overwrites
408 exists = os.path.exists(target)
408 exists = os.path.exists(target)
409 if not after and exists or after and state in 'mn':
409 if not after and exists or after and state in 'mn':
410 if not opts['force']:
410 if not opts['force']:
411 ui.warn(_('%s: not overwriting - file exists\n') %
411 ui.warn(_('%s: not overwriting - file exists\n') %
412 reltarget)
412 reltarget)
413 return
413 return
414
414
415 if after:
415 if after:
416 if not exists:
416 if not exists:
417 return
417 return
418 elif not dryrun:
418 elif not dryrun:
419 try:
419 try:
420 if exists:
420 if exists:
421 os.unlink(target)
421 os.unlink(target)
422 targetdir = os.path.dirname(target) or '.'
422 targetdir = os.path.dirname(target) or '.'
423 if not os.path.isdir(targetdir):
423 if not os.path.isdir(targetdir):
424 os.makedirs(targetdir)
424 os.makedirs(targetdir)
425 util.copyfile(src, target)
425 util.copyfile(src, target)
426 except IOError, inst:
426 except IOError, inst:
427 if inst.errno == errno.ENOENT:
427 if inst.errno == errno.ENOENT:
428 ui.warn(_('%s: deleted in working copy\n') % relsrc)
428 ui.warn(_('%s: deleted in working copy\n') % relsrc)
429 else:
429 else:
430 ui.warn(_('%s: cannot copy - %s\n') %
430 ui.warn(_('%s: cannot copy - %s\n') %
431 (relsrc, inst.strerror))
431 (relsrc, inst.strerror))
432 return True # report a failure
432 return True # report a failure
433
433
434 if ui.verbose or not exact:
434 if ui.verbose or not exact:
435 if rename:
435 if rename:
436 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
436 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
437 else:
437 else:
438 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
438 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
439
439
440 targets[abstarget] = abssrc
440 targets[abstarget] = abssrc
441
441
442 # fix up dirstate
442 # fix up dirstate
443 origsrc = repo.dirstate.copied(abssrc) or abssrc
443 origsrc = repo.dirstate.copied(abssrc) or abssrc
444 if abstarget == origsrc: # copying back a copy?
444 if abstarget == origsrc: # copying back a copy?
445 if state not in 'mn' and not dryrun:
445 if state not in 'mn' and not dryrun:
446 repo.dirstate.normallookup(abstarget)
446 repo.dirstate.normallookup(abstarget)
447 else:
447 else:
448 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
448 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
449 if not ui.quiet:
449 if not ui.quiet:
450 ui.warn(_("%s has not been committed yet, so no copy "
450 ui.warn(_("%s has not been committed yet, so no copy "
451 "data will be stored for %s.\n")
451 "data will be stored for %s.\n")
452 % (repo.pathto(origsrc, cwd), reltarget))
452 % (repo.pathto(origsrc, cwd), reltarget))
453 if repo.dirstate[abstarget] in '?r' and not dryrun:
453 if repo.dirstate[abstarget] in '?r' and not dryrun:
454 repo.add([abstarget])
454 repo.add([abstarget])
455 elif not dryrun:
455 elif not dryrun:
456 repo.copy(origsrc, abstarget)
456 repo.copy(origsrc, abstarget)
457
457
458 if rename and not dryrun:
458 if rename and not dryrun:
459 repo.remove([abssrc], not after)
459 repo.remove([abssrc], not after)
460
460
461 # pat: ossep
461 # pat: ossep
462 # dest ossep
462 # dest ossep
463 # srcs: list of (hgsep, hgsep, ossep, bool)
463 # srcs: list of (hgsep, hgsep, ossep, bool)
464 # return: function that takes hgsep and returns ossep
464 # return: function that takes hgsep and returns ossep
465 def targetpathfn(pat, dest, srcs):
465 def targetpathfn(pat, dest, srcs):
466 if os.path.isdir(pat):
466 if os.path.isdir(pat):
467 abspfx = util.canonpath(repo.root, cwd, pat)
467 abspfx = util.canonpath(repo.root, cwd, pat)
468 abspfx = util.localpath(abspfx)
468 abspfx = util.localpath(abspfx)
469 if destdirexists:
469 if destdirexists:
470 striplen = len(os.path.split(abspfx)[0])
470 striplen = len(os.path.split(abspfx)[0])
471 else:
471 else:
472 striplen = len(abspfx)
472 striplen = len(abspfx)
473 if striplen:
473 if striplen:
474 striplen += len(os.sep)
474 striplen += len(os.sep)
475 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
475 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
476 elif destdirexists:
476 elif destdirexists:
477 res = lambda p: os.path.join(dest,
477 res = lambda p: os.path.join(dest,
478 os.path.basename(util.localpath(p)))
478 os.path.basename(util.localpath(p)))
479 else:
479 else:
480 res = lambda p: dest
480 res = lambda p: dest
481 return res
481 return res
482
482
483 # pat: ossep
483 # pat: ossep
484 # dest ossep
484 # dest ossep
485 # srcs: list of (hgsep, hgsep, ossep, bool)
485 # srcs: list of (hgsep, hgsep, ossep, bool)
486 # return: function that takes hgsep and returns ossep
486 # return: function that takes hgsep and returns ossep
487 def targetpathafterfn(pat, dest, srcs):
487 def targetpathafterfn(pat, dest, srcs):
488 if _match.patkind(pat):
488 if _match.patkind(pat):
489 # a mercurial pattern
489 # a mercurial pattern
490 res = lambda p: os.path.join(dest,
490 res = lambda p: os.path.join(dest,
491 os.path.basename(util.localpath(p)))
491 os.path.basename(util.localpath(p)))
492 else:
492 else:
493 abspfx = util.canonpath(repo.root, cwd, pat)
493 abspfx = util.canonpath(repo.root, cwd, pat)
494 if len(abspfx) < len(srcs[0][0]):
494 if len(abspfx) < len(srcs[0][0]):
495 # A directory. Either the target path contains the last
495 # A directory. Either the target path contains the last
496 # component of the source path or it does not.
496 # component of the source path or it does not.
497 def evalpath(striplen):
497 def evalpath(striplen):
498 score = 0
498 score = 0
499 for s in srcs:
499 for s in srcs:
500 t = os.path.join(dest, util.localpath(s[0])[striplen:])
500 t = os.path.join(dest, util.localpath(s[0])[striplen:])
501 if os.path.exists(t):
501 if os.path.exists(t):
502 score += 1
502 score += 1
503 return score
503 return score
504
504
505 abspfx = util.localpath(abspfx)
505 abspfx = util.localpath(abspfx)
506 striplen = len(abspfx)
506 striplen = len(abspfx)
507 if striplen:
507 if striplen:
508 striplen += len(os.sep)
508 striplen += len(os.sep)
509 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
509 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
510 score = evalpath(striplen)
510 score = evalpath(striplen)
511 striplen1 = len(os.path.split(abspfx)[0])
511 striplen1 = len(os.path.split(abspfx)[0])
512 if striplen1:
512 if striplen1:
513 striplen1 += len(os.sep)
513 striplen1 += len(os.sep)
514 if evalpath(striplen1) > score:
514 if evalpath(striplen1) > score:
515 striplen = striplen1
515 striplen = striplen1
516 res = lambda p: os.path.join(dest,
516 res = lambda p: os.path.join(dest,
517 util.localpath(p)[striplen:])
517 util.localpath(p)[striplen:])
518 else:
518 else:
519 # a file
519 # a file
520 if destdirexists:
520 if destdirexists:
521 res = lambda p: os.path.join(dest,
521 res = lambda p: os.path.join(dest,
522 os.path.basename(util.localpath(p)))
522 os.path.basename(util.localpath(p)))
523 else:
523 else:
524 res = lambda p: dest
524 res = lambda p: dest
525 return res
525 return res
526
526
527
527
528 pats = expandpats(pats)
528 pats = expandpats(pats)
529 if not pats:
529 if not pats:
530 raise util.Abort(_('no source or destination specified'))
530 raise util.Abort(_('no source or destination specified'))
531 if len(pats) == 1:
531 if len(pats) == 1:
532 raise util.Abort(_('no destination specified'))
532 raise util.Abort(_('no destination specified'))
533 dest = pats.pop()
533 dest = pats.pop()
534 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
534 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
535 if not destdirexists:
535 if not destdirexists:
536 if len(pats) > 1 or _match.patkind(pats[0]):
536 if len(pats) > 1 or _match.patkind(pats[0]):
537 raise util.Abort(_('with multiple sources, destination must be an '
537 raise util.Abort(_('with multiple sources, destination must be an '
538 'existing directory'))
538 'existing directory'))
539 if util.endswithsep(dest):
539 if util.endswithsep(dest):
540 raise util.Abort(_('destination %s is not a directory') % dest)
540 raise util.Abort(_('destination %s is not a directory') % dest)
541
541
542 tfn = targetpathfn
542 tfn = targetpathfn
543 if after:
543 if after:
544 tfn = targetpathafterfn
544 tfn = targetpathafterfn
545 copylist = []
545 copylist = []
546 for pat in pats:
546 for pat in pats:
547 srcs = walkpat(pat)
547 srcs = walkpat(pat)
548 if not srcs:
548 if not srcs:
549 continue
549 continue
550 copylist.append((tfn(pat, dest, srcs), srcs))
550 copylist.append((tfn(pat, dest, srcs), srcs))
551 if not copylist:
551 if not copylist:
552 raise util.Abort(_('no files to copy'))
552 raise util.Abort(_('no files to copy'))
553
553
554 errors = 0
554 errors = 0
555 for targetpath, srcs in copylist:
555 for targetpath, srcs in copylist:
556 for abssrc, relsrc, exact in srcs:
556 for abssrc, relsrc, exact in srcs:
557 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
557 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
558 errors += 1
558 errors += 1
559
559
560 if errors:
560 if errors:
561 ui.warn(_('(consider using --after)\n'))
561 ui.warn(_('(consider using --after)\n'))
562
562
563 return errors
563 return errors
564
564
565 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
565 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
566 runargs=None, appendpid=False):
566 runargs=None, appendpid=False):
567 '''Run a command as a service.'''
567 '''Run a command as a service.'''
568
568
569 if opts['daemon'] and not opts['daemon_pipefds']:
569 if opts['daemon'] and not opts['daemon_pipefds']:
570 rfd, wfd = os.pipe()
570 rfd, wfd = os.pipe()
571 if not runargs:
571 if not runargs:
572 runargs = sys.argv[:]
572 runargs = sys.argv[:]
573 runargs.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
573 runargs.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
574 # Don't pass --cwd to the child process, because we've already
574 # Don't pass --cwd to the child process, because we've already
575 # changed directory.
575 # changed directory.
576 for i in xrange(1,len(runargs)):
576 for i in xrange(1,len(runargs)):
577 if runargs[i].startswith('--cwd='):
577 if runargs[i].startswith('--cwd='):
578 del runargs[i]
578 del runargs[i]
579 break
579 break
580 elif runargs[i].startswith('--cwd'):
580 elif runargs[i].startswith('--cwd'):
581 del runargs[i:i+2]
581 del runargs[i:i+2]
582 break
582 break
583 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
583 pid = util.spawndetached(runargs)
584 runargs[0], runargs)
585 os.close(wfd)
584 os.close(wfd)
586 os.read(rfd, 1)
585 os.read(rfd, 1)
587 if parentfn:
586 if parentfn:
588 return parentfn(pid)
587 return parentfn(pid)
589 else:
588 else:
590 return
589 return
591
590
592 if initfn:
591 if initfn:
593 initfn()
592 initfn()
594
593
595 if opts['pid_file']:
594 if opts['pid_file']:
596 mode = appendpid and 'a' or 'w'
595 mode = appendpid and 'a' or 'w'
597 fp = open(opts['pid_file'], mode)
596 fp = open(opts['pid_file'], mode)
598 fp.write(str(os.getpid()) + '\n')
597 fp.write(str(os.getpid()) + '\n')
599 fp.close()
598 fp.close()
600
599
601 if opts['daemon_pipefds']:
600 if opts['daemon_pipefds']:
602 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
601 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
603 os.close(rfd)
602 os.close(rfd)
604 try:
603 try:
605 os.setsid()
604 os.setsid()
606 except AttributeError:
605 except AttributeError:
607 pass
606 pass
608 os.write(wfd, 'y')
607 os.write(wfd, 'y')
609 os.close(wfd)
608 os.close(wfd)
610 sys.stdout.flush()
609 sys.stdout.flush()
611 sys.stderr.flush()
610 sys.stderr.flush()
612
611
613 nullfd = os.open(util.nulldev, os.O_RDWR)
612 nullfd = os.open(util.nulldev, os.O_RDWR)
614 logfilefd = nullfd
613 logfilefd = nullfd
615 if logfile:
614 if logfile:
616 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
615 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
617 os.dup2(nullfd, 0)
616 os.dup2(nullfd, 0)
618 os.dup2(logfilefd, 1)
617 os.dup2(logfilefd, 1)
619 os.dup2(logfilefd, 2)
618 os.dup2(logfilefd, 2)
620 if nullfd not in (0, 1, 2):
619 if nullfd not in (0, 1, 2):
621 os.close(nullfd)
620 os.close(nullfd)
622 if logfile and logfilefd not in (0, 1, 2):
621 if logfile and logfilefd not in (0, 1, 2):
623 os.close(logfilefd)
622 os.close(logfilefd)
624
623
625 if runfn:
624 if runfn:
626 return runfn()
625 return runfn()
627
626
628 class changeset_printer(object):
627 class changeset_printer(object):
629 '''show changeset information when templating not requested.'''
628 '''show changeset information when templating not requested.'''
630
629
631 def __init__(self, ui, repo, patch, diffopts, buffered):
630 def __init__(self, ui, repo, patch, diffopts, buffered):
632 self.ui = ui
631 self.ui = ui
633 self.repo = repo
632 self.repo = repo
634 self.buffered = buffered
633 self.buffered = buffered
635 self.patch = patch
634 self.patch = patch
636 self.diffopts = diffopts
635 self.diffopts = diffopts
637 self.header = {}
636 self.header = {}
638 self.hunk = {}
637 self.hunk = {}
639 self.lastheader = None
638 self.lastheader = None
640 self.footer = None
639 self.footer = None
641
640
642 def flush(self, rev):
641 def flush(self, rev):
643 if rev in self.header:
642 if rev in self.header:
644 h = self.header[rev]
643 h = self.header[rev]
645 if h != self.lastheader:
644 if h != self.lastheader:
646 self.lastheader = h
645 self.lastheader = h
647 self.ui.write(h)
646 self.ui.write(h)
648 del self.header[rev]
647 del self.header[rev]
649 if rev in self.hunk:
648 if rev in self.hunk:
650 self.ui.write(self.hunk[rev])
649 self.ui.write(self.hunk[rev])
651 del self.hunk[rev]
650 del self.hunk[rev]
652 return 1
651 return 1
653 return 0
652 return 0
654
653
655 def close(self):
654 def close(self):
656 if self.footer:
655 if self.footer:
657 self.ui.write(self.footer)
656 self.ui.write(self.footer)
658
657
659 def show(self, ctx, copies=None, **props):
658 def show(self, ctx, copies=None, **props):
660 if self.buffered:
659 if self.buffered:
661 self.ui.pushbuffer()
660 self.ui.pushbuffer()
662 self._show(ctx, copies, props)
661 self._show(ctx, copies, props)
663 self.hunk[ctx.rev()] = self.ui.popbuffer()
662 self.hunk[ctx.rev()] = self.ui.popbuffer()
664 else:
663 else:
665 self._show(ctx, copies, props)
664 self._show(ctx, copies, props)
666
665
667 def _show(self, ctx, copies, props):
666 def _show(self, ctx, copies, props):
668 '''show a single changeset or file revision'''
667 '''show a single changeset or file revision'''
669 changenode = ctx.node()
668 changenode = ctx.node()
670 rev = ctx.rev()
669 rev = ctx.rev()
671
670
672 if self.ui.quiet:
671 if self.ui.quiet:
673 self.ui.write("%d:%s\n" % (rev, short(changenode)))
672 self.ui.write("%d:%s\n" % (rev, short(changenode)))
674 return
673 return
675
674
676 log = self.repo.changelog
675 log = self.repo.changelog
677 date = util.datestr(ctx.date())
676 date = util.datestr(ctx.date())
678
677
679 hexfunc = self.ui.debugflag and hex or short
678 hexfunc = self.ui.debugflag and hex or short
680
679
681 parents = [(p, hexfunc(log.node(p)))
680 parents = [(p, hexfunc(log.node(p)))
682 for p in self._meaningful_parentrevs(log, rev)]
681 for p in self._meaningful_parentrevs(log, rev)]
683
682
684 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
683 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
685
684
686 branch = ctx.branch()
685 branch = ctx.branch()
687 # don't show the default branch name
686 # don't show the default branch name
688 if branch != 'default':
687 if branch != 'default':
689 branch = encoding.tolocal(branch)
688 branch = encoding.tolocal(branch)
690 self.ui.write(_("branch: %s\n") % branch)
689 self.ui.write(_("branch: %s\n") % branch)
691 for tag in self.repo.nodetags(changenode):
690 for tag in self.repo.nodetags(changenode):
692 self.ui.write(_("tag: %s\n") % tag)
691 self.ui.write(_("tag: %s\n") % tag)
693 for parent in parents:
692 for parent in parents:
694 self.ui.write(_("parent: %d:%s\n") % parent)
693 self.ui.write(_("parent: %d:%s\n") % parent)
695
694
696 if self.ui.debugflag:
695 if self.ui.debugflag:
697 mnode = ctx.manifestnode()
696 mnode = ctx.manifestnode()
698 self.ui.write(_("manifest: %d:%s\n") %
697 self.ui.write(_("manifest: %d:%s\n") %
699 (self.repo.manifest.rev(mnode), hex(mnode)))
698 (self.repo.manifest.rev(mnode), hex(mnode)))
700 self.ui.write(_("user: %s\n") % ctx.user())
699 self.ui.write(_("user: %s\n") % ctx.user())
701 self.ui.write(_("date: %s\n") % date)
700 self.ui.write(_("date: %s\n") % date)
702
701
703 if self.ui.debugflag:
702 if self.ui.debugflag:
704 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
703 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
705 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
704 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
706 files):
705 files):
707 if value:
706 if value:
708 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
707 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
709 elif ctx.files() and self.ui.verbose:
708 elif ctx.files() and self.ui.verbose:
710 self.ui.write(_("files: %s\n") % " ".join(ctx.files()))
709 self.ui.write(_("files: %s\n") % " ".join(ctx.files()))
711 if copies and self.ui.verbose:
710 if copies and self.ui.verbose:
712 copies = ['%s (%s)' % c for c in copies]
711 copies = ['%s (%s)' % c for c in copies]
713 self.ui.write(_("copies: %s\n") % ' '.join(copies))
712 self.ui.write(_("copies: %s\n") % ' '.join(copies))
714
713
715 extra = ctx.extra()
714 extra = ctx.extra()
716 if extra and self.ui.debugflag:
715 if extra and self.ui.debugflag:
717 for key, value in sorted(extra.items()):
716 for key, value in sorted(extra.items()):
718 self.ui.write(_("extra: %s=%s\n")
717 self.ui.write(_("extra: %s=%s\n")
719 % (key, value.encode('string_escape')))
718 % (key, value.encode('string_escape')))
720
719
721 description = ctx.description().strip()
720 description = ctx.description().strip()
722 if description:
721 if description:
723 if self.ui.verbose:
722 if self.ui.verbose:
724 self.ui.write(_("description:\n"))
723 self.ui.write(_("description:\n"))
725 self.ui.write(description)
724 self.ui.write(description)
726 self.ui.write("\n\n")
725 self.ui.write("\n\n")
727 else:
726 else:
728 self.ui.write(_("summary: %s\n") %
727 self.ui.write(_("summary: %s\n") %
729 description.splitlines()[0])
728 description.splitlines()[0])
730 self.ui.write("\n")
729 self.ui.write("\n")
731
730
732 self.showpatch(changenode)
731 self.showpatch(changenode)
733
732
734 def showpatch(self, node):
733 def showpatch(self, node):
735 if self.patch:
734 if self.patch:
736 prev = self.repo.changelog.parents(node)[0]
735 prev = self.repo.changelog.parents(node)[0]
737 chunks = patch.diff(self.repo, prev, node, match=self.patch,
736 chunks = patch.diff(self.repo, prev, node, match=self.patch,
738 opts=patch.diffopts(self.ui, self.diffopts))
737 opts=patch.diffopts(self.ui, self.diffopts))
739 for chunk in chunks:
738 for chunk in chunks:
740 self.ui.write(chunk)
739 self.ui.write(chunk)
741 self.ui.write("\n")
740 self.ui.write("\n")
742
741
743 def _meaningful_parentrevs(self, log, rev):
742 def _meaningful_parentrevs(self, log, rev):
744 """Return list of meaningful (or all if debug) parentrevs for rev.
743 """Return list of meaningful (or all if debug) parentrevs for rev.
745
744
746 For merges (two non-nullrev revisions) both parents are meaningful.
745 For merges (two non-nullrev revisions) both parents are meaningful.
747 Otherwise the first parent revision is considered meaningful if it
746 Otherwise the first parent revision is considered meaningful if it
748 is not the preceding revision.
747 is not the preceding revision.
749 """
748 """
750 parents = log.parentrevs(rev)
749 parents = log.parentrevs(rev)
751 if not self.ui.debugflag and parents[1] == nullrev:
750 if not self.ui.debugflag and parents[1] == nullrev:
752 if parents[0] >= rev - 1:
751 if parents[0] >= rev - 1:
753 parents = []
752 parents = []
754 else:
753 else:
755 parents = [parents[0]]
754 parents = [parents[0]]
756 return parents
755 return parents
757
756
758
757
759 class changeset_templater(changeset_printer):
758 class changeset_templater(changeset_printer):
760 '''format changeset information.'''
759 '''format changeset information.'''
761
760
762 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
761 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
763 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
762 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
764 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
763 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
765 defaulttempl = {
764 defaulttempl = {
766 'parent': '{rev}:{node|formatnode} ',
765 'parent': '{rev}:{node|formatnode} ',
767 'manifest': '{rev}:{node|formatnode}',
766 'manifest': '{rev}:{node|formatnode}',
768 'file_copy': '{name} ({source})',
767 'file_copy': '{name} ({source})',
769 'extra': '{key}={value|stringescape}'
768 'extra': '{key}={value|stringescape}'
770 }
769 }
771 # filecopy is preserved for compatibility reasons
770 # filecopy is preserved for compatibility reasons
772 defaulttempl['filecopy'] = defaulttempl['file_copy']
771 defaulttempl['filecopy'] = defaulttempl['file_copy']
773 self.t = templater.templater(mapfile, {'formatnode': formatnode},
772 self.t = templater.templater(mapfile, {'formatnode': formatnode},
774 cache=defaulttempl)
773 cache=defaulttempl)
775 self.cache = {}
774 self.cache = {}
776
775
777 def use_template(self, t):
776 def use_template(self, t):
778 '''set template string to use'''
777 '''set template string to use'''
779 self.t.cache['changeset'] = t
778 self.t.cache['changeset'] = t
780
779
781 def _meaningful_parentrevs(self, ctx):
780 def _meaningful_parentrevs(self, ctx):
782 """Return list of meaningful (or all if debug) parentrevs for rev.
781 """Return list of meaningful (or all if debug) parentrevs for rev.
783 """
782 """
784 parents = ctx.parents()
783 parents = ctx.parents()
785 if len(parents) > 1:
784 if len(parents) > 1:
786 return parents
785 return parents
787 if self.ui.debugflag:
786 if self.ui.debugflag:
788 return [parents[0], self.repo['null']]
787 return [parents[0], self.repo['null']]
789 if parents[0].rev() >= ctx.rev() - 1:
788 if parents[0].rev() >= ctx.rev() - 1:
790 return []
789 return []
791 return parents
790 return parents
792
791
793 def _show(self, ctx, copies, props):
792 def _show(self, ctx, copies, props):
794 '''show a single changeset or file revision'''
793 '''show a single changeset or file revision'''
795
794
796 showlist = templatekw.showlist
795 showlist = templatekw.showlist
797
796
798 # showparents() behaviour depends on ui trace level which
797 # showparents() behaviour depends on ui trace level which
799 # causes unexpected behaviours at templating level and makes
798 # causes unexpected behaviours at templating level and makes
800 # it harder to extract it in a standalone function. Its
799 # it harder to extract it in a standalone function. Its
801 # behaviour cannot be changed so leave it here for now.
800 # behaviour cannot be changed so leave it here for now.
802 def showparents(repo, ctx, templ, **args):
801 def showparents(repo, ctx, templ, **args):
803 parents = [[('rev', p.rev()), ('node', p.hex())]
802 parents = [[('rev', p.rev()), ('node', p.hex())]
804 for p in self._meaningful_parentrevs(ctx)]
803 for p in self._meaningful_parentrevs(ctx)]
805 return showlist(templ, 'parent', parents, **args)
804 return showlist(templ, 'parent', parents, **args)
806
805
807 props = props.copy()
806 props = props.copy()
808 props.update(templatekw.keywords)
807 props.update(templatekw.keywords)
809 props['parents'] = showparents
808 props['parents'] = showparents
810 props['templ'] = self.t
809 props['templ'] = self.t
811 props['ctx'] = ctx
810 props['ctx'] = ctx
812 props['repo'] = self.repo
811 props['repo'] = self.repo
813 props['revcache'] = {'copies': copies}
812 props['revcache'] = {'copies': copies}
814 props['cache'] = self.cache
813 props['cache'] = self.cache
815
814
816 # find correct templates for current mode
815 # find correct templates for current mode
817
816
818 tmplmodes = [
817 tmplmodes = [
819 (True, None),
818 (True, None),
820 (self.ui.verbose, 'verbose'),
819 (self.ui.verbose, 'verbose'),
821 (self.ui.quiet, 'quiet'),
820 (self.ui.quiet, 'quiet'),
822 (self.ui.debugflag, 'debug'),
821 (self.ui.debugflag, 'debug'),
823 ]
822 ]
824
823
825 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
824 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
826 for mode, postfix in tmplmodes:
825 for mode, postfix in tmplmodes:
827 for type in types:
826 for type in types:
828 cur = postfix and ('%s_%s' % (type, postfix)) or type
827 cur = postfix and ('%s_%s' % (type, postfix)) or type
829 if mode and cur in self.t:
828 if mode and cur in self.t:
830 types[type] = cur
829 types[type] = cur
831
830
832 try:
831 try:
833
832
834 # write header
833 # write header
835 if types['header']:
834 if types['header']:
836 h = templater.stringify(self.t(types['header'], **props))
835 h = templater.stringify(self.t(types['header'], **props))
837 if self.buffered:
836 if self.buffered:
838 self.header[ctx.rev()] = h
837 self.header[ctx.rev()] = h
839 else:
838 else:
840 self.ui.write(h)
839 self.ui.write(h)
841
840
842 # write changeset metadata, then patch if requested
841 # write changeset metadata, then patch if requested
843 key = types['changeset']
842 key = types['changeset']
844 self.ui.write(templater.stringify(self.t(key, **props)))
843 self.ui.write(templater.stringify(self.t(key, **props)))
845 self.showpatch(ctx.node())
844 self.showpatch(ctx.node())
846
845
847 if types['footer']:
846 if types['footer']:
848 if not self.footer:
847 if not self.footer:
849 self.footer = templater.stringify(self.t(types['footer'],
848 self.footer = templater.stringify(self.t(types['footer'],
850 **props))
849 **props))
851
850
852 except KeyError, inst:
851 except KeyError, inst:
853 msg = _("%s: no key named '%s'")
852 msg = _("%s: no key named '%s'")
854 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
853 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
855 except SyntaxError, inst:
854 except SyntaxError, inst:
856 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
855 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
857
856
858 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
857 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
859 """show one changeset using template or regular display.
858 """show one changeset using template or regular display.
860
859
861 Display format will be the first non-empty hit of:
860 Display format will be the first non-empty hit of:
862 1. option 'template'
861 1. option 'template'
863 2. option 'style'
862 2. option 'style'
864 3. [ui] setting 'logtemplate'
863 3. [ui] setting 'logtemplate'
865 4. [ui] setting 'style'
864 4. [ui] setting 'style'
866 If all of these values are either the unset or the empty string,
865 If all of these values are either the unset or the empty string,
867 regular display via changeset_printer() is done.
866 regular display via changeset_printer() is done.
868 """
867 """
869 # options
868 # options
870 patch = False
869 patch = False
871 if opts.get('patch'):
870 if opts.get('patch'):
872 patch = matchfn or matchall(repo)
871 patch = matchfn or matchall(repo)
873
872
874 tmpl = opts.get('template')
873 tmpl = opts.get('template')
875 style = None
874 style = None
876 if tmpl:
875 if tmpl:
877 tmpl = templater.parsestring(tmpl, quoted=False)
876 tmpl = templater.parsestring(tmpl, quoted=False)
878 else:
877 else:
879 style = opts.get('style')
878 style = opts.get('style')
880
879
881 # ui settings
880 # ui settings
882 if not (tmpl or style):
881 if not (tmpl or style):
883 tmpl = ui.config('ui', 'logtemplate')
882 tmpl = ui.config('ui', 'logtemplate')
884 if tmpl:
883 if tmpl:
885 tmpl = templater.parsestring(tmpl)
884 tmpl = templater.parsestring(tmpl)
886 else:
885 else:
887 style = ui.config('ui', 'style')
886 style = ui.config('ui', 'style')
888
887
889 if not (tmpl or style):
888 if not (tmpl or style):
890 return changeset_printer(ui, repo, patch, opts, buffered)
889 return changeset_printer(ui, repo, patch, opts, buffered)
891
890
892 mapfile = None
891 mapfile = None
893 if style and not tmpl:
892 if style and not tmpl:
894 mapfile = style
893 mapfile = style
895 if not os.path.split(mapfile)[0]:
894 if not os.path.split(mapfile)[0]:
896 mapname = (templater.templatepath('map-cmdline.' + mapfile)
895 mapname = (templater.templatepath('map-cmdline.' + mapfile)
897 or templater.templatepath(mapfile))
896 or templater.templatepath(mapfile))
898 if mapname: mapfile = mapname
897 if mapname: mapfile = mapname
899
898
900 try:
899 try:
901 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
900 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
902 except SyntaxError, inst:
901 except SyntaxError, inst:
903 raise util.Abort(inst.args[0])
902 raise util.Abort(inst.args[0])
904 if tmpl: t.use_template(tmpl)
903 if tmpl: t.use_template(tmpl)
905 return t
904 return t
906
905
907 def finddate(ui, repo, date):
906 def finddate(ui, repo, date):
908 """Find the tipmost changeset that matches the given date spec"""
907 """Find the tipmost changeset that matches the given date spec"""
909
908
910 df = util.matchdate(date)
909 df = util.matchdate(date)
911 m = matchall(repo)
910 m = matchall(repo)
912 results = {}
911 results = {}
913
912
914 def prep(ctx, fns):
913 def prep(ctx, fns):
915 d = ctx.date()
914 d = ctx.date()
916 if df(d[0]):
915 if df(d[0]):
917 results[ctx.rev()] = d
916 results[ctx.rev()] = d
918
917
919 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
918 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
920 rev = ctx.rev()
919 rev = ctx.rev()
921 if rev in results:
920 if rev in results:
922 ui.status(_("Found revision %s from %s\n") %
921 ui.status(_("Found revision %s from %s\n") %
923 (rev, util.datestr(results[rev])))
922 (rev, util.datestr(results[rev])))
924 return str(rev)
923 return str(rev)
925
924
926 raise util.Abort(_("revision matching date not found"))
925 raise util.Abort(_("revision matching date not found"))
927
926
928 def walkchangerevs(repo, match, opts, prepare):
927 def walkchangerevs(repo, match, opts, prepare):
929 '''Iterate over files and the revs in which they changed.
928 '''Iterate over files and the revs in which they changed.
930
929
931 Callers most commonly need to iterate backwards over the history
930 Callers most commonly need to iterate backwards over the history
932 in which they are interested. Doing so has awful (quadratic-looking)
931 in which they are interested. Doing so has awful (quadratic-looking)
933 performance, so we use iterators in a "windowed" way.
932 performance, so we use iterators in a "windowed" way.
934
933
935 We walk a window of revisions in the desired order. Within the
934 We walk a window of revisions in the desired order. Within the
936 window, we first walk forwards to gather data, then in the desired
935 window, we first walk forwards to gather data, then in the desired
937 order (usually backwards) to display it.
936 order (usually backwards) to display it.
938
937
939 This function returns an iterator yielding contexts. Before
938 This function returns an iterator yielding contexts. Before
940 yielding each context, the iterator will first call the prepare
939 yielding each context, the iterator will first call the prepare
941 function on each context in the window in forward order.'''
940 function on each context in the window in forward order.'''
942
941
943 def increasing_windows(start, end, windowsize=8, sizelimit=512):
942 def increasing_windows(start, end, windowsize=8, sizelimit=512):
944 if start < end:
943 if start < end:
945 while start < end:
944 while start < end:
946 yield start, min(windowsize, end-start)
945 yield start, min(windowsize, end-start)
947 start += windowsize
946 start += windowsize
948 if windowsize < sizelimit:
947 if windowsize < sizelimit:
949 windowsize *= 2
948 windowsize *= 2
950 else:
949 else:
951 while start > end:
950 while start > end:
952 yield start, min(windowsize, start-end-1)
951 yield start, min(windowsize, start-end-1)
953 start -= windowsize
952 start -= windowsize
954 if windowsize < sizelimit:
953 if windowsize < sizelimit:
955 windowsize *= 2
954 windowsize *= 2
956
955
957 follow = opts.get('follow') or opts.get('follow_first')
956 follow = opts.get('follow') or opts.get('follow_first')
958
957
959 if not len(repo):
958 if not len(repo):
960 return []
959 return []
961
960
962 if follow:
961 if follow:
963 defrange = '%s:0' % repo['.'].rev()
962 defrange = '%s:0' % repo['.'].rev()
964 else:
963 else:
965 defrange = '-1:0'
964 defrange = '-1:0'
966 revs = revrange(repo, opts['rev'] or [defrange])
965 revs = revrange(repo, opts['rev'] or [defrange])
967 wanted = set()
966 wanted = set()
968 slowpath = match.anypats() or (match.files() and opts.get('removed'))
967 slowpath = match.anypats() or (match.files() and opts.get('removed'))
969 fncache = {}
968 fncache = {}
970 change = util.cachefunc(repo.changectx)
969 change = util.cachefunc(repo.changectx)
971
970
972 if not slowpath and not match.files():
971 if not slowpath and not match.files():
973 # No files, no patterns. Display all revs.
972 # No files, no patterns. Display all revs.
974 wanted = set(revs)
973 wanted = set(revs)
975 copies = []
974 copies = []
976
975
977 if not slowpath:
976 if not slowpath:
978 # Only files, no patterns. Check the history of each file.
977 # Only files, no patterns. Check the history of each file.
979 def filerevgen(filelog, node):
978 def filerevgen(filelog, node):
980 cl_count = len(repo)
979 cl_count = len(repo)
981 if node is None:
980 if node is None:
982 last = len(filelog) - 1
981 last = len(filelog) - 1
983 else:
982 else:
984 last = filelog.rev(node)
983 last = filelog.rev(node)
985 for i, window in increasing_windows(last, nullrev):
984 for i, window in increasing_windows(last, nullrev):
986 revs = []
985 revs = []
987 for j in xrange(i - window, i + 1):
986 for j in xrange(i - window, i + 1):
988 n = filelog.node(j)
987 n = filelog.node(j)
989 revs.append((filelog.linkrev(j),
988 revs.append((filelog.linkrev(j),
990 follow and filelog.renamed(n)))
989 follow and filelog.renamed(n)))
991 for rev in reversed(revs):
990 for rev in reversed(revs):
992 # only yield rev for which we have the changelog, it can
991 # only yield rev for which we have the changelog, it can
993 # happen while doing "hg log" during a pull or commit
992 # happen while doing "hg log" during a pull or commit
994 if rev[0] < cl_count:
993 if rev[0] < cl_count:
995 yield rev
994 yield rev
996 def iterfiles():
995 def iterfiles():
997 for filename in match.files():
996 for filename in match.files():
998 yield filename, None
997 yield filename, None
999 for filename_node in copies:
998 for filename_node in copies:
1000 yield filename_node
999 yield filename_node
1001 minrev, maxrev = min(revs), max(revs)
1000 minrev, maxrev = min(revs), max(revs)
1002 for file_, node in iterfiles():
1001 for file_, node in iterfiles():
1003 filelog = repo.file(file_)
1002 filelog = repo.file(file_)
1004 if not len(filelog):
1003 if not len(filelog):
1005 if node is None:
1004 if node is None:
1006 # A zero count may be a directory or deleted file, so
1005 # A zero count may be a directory or deleted file, so
1007 # try to find matching entries on the slow path.
1006 # try to find matching entries on the slow path.
1008 if follow:
1007 if follow:
1009 raise util.Abort(_('cannot follow nonexistent file: "%s"') % file_)
1008 raise util.Abort(_('cannot follow nonexistent file: "%s"') % file_)
1010 slowpath = True
1009 slowpath = True
1011 break
1010 break
1012 else:
1011 else:
1013 continue
1012 continue
1014 for rev, copied in filerevgen(filelog, node):
1013 for rev, copied in filerevgen(filelog, node):
1015 if rev <= maxrev:
1014 if rev <= maxrev:
1016 if rev < minrev:
1015 if rev < minrev:
1017 break
1016 break
1018 fncache.setdefault(rev, [])
1017 fncache.setdefault(rev, [])
1019 fncache[rev].append(file_)
1018 fncache[rev].append(file_)
1020 wanted.add(rev)
1019 wanted.add(rev)
1021 if follow and copied:
1020 if follow and copied:
1022 copies.append(copied)
1021 copies.append(copied)
1023 if slowpath:
1022 if slowpath:
1024 if follow:
1023 if follow:
1025 raise util.Abort(_('can only follow copies/renames for explicit '
1024 raise util.Abort(_('can only follow copies/renames for explicit '
1026 'filenames'))
1025 'filenames'))
1027
1026
1028 # The slow path checks files modified in every changeset.
1027 # The slow path checks files modified in every changeset.
1029 def changerevgen():
1028 def changerevgen():
1030 for i, window in increasing_windows(len(repo) - 1, nullrev):
1029 for i, window in increasing_windows(len(repo) - 1, nullrev):
1031 for j in xrange(i - window, i + 1):
1030 for j in xrange(i - window, i + 1):
1032 yield change(j)
1031 yield change(j)
1033
1032
1034 for ctx in changerevgen():
1033 for ctx in changerevgen():
1035 matches = filter(match, ctx.files())
1034 matches = filter(match, ctx.files())
1036 if matches:
1035 if matches:
1037 fncache[ctx.rev()] = matches
1036 fncache[ctx.rev()] = matches
1038 wanted.add(ctx.rev())
1037 wanted.add(ctx.rev())
1039
1038
1040 class followfilter(object):
1039 class followfilter(object):
1041 def __init__(self, onlyfirst=False):
1040 def __init__(self, onlyfirst=False):
1042 self.startrev = nullrev
1041 self.startrev = nullrev
1043 self.roots = set()
1042 self.roots = set()
1044 self.onlyfirst = onlyfirst
1043 self.onlyfirst = onlyfirst
1045
1044
1046 def match(self, rev):
1045 def match(self, rev):
1047 def realparents(rev):
1046 def realparents(rev):
1048 if self.onlyfirst:
1047 if self.onlyfirst:
1049 return repo.changelog.parentrevs(rev)[0:1]
1048 return repo.changelog.parentrevs(rev)[0:1]
1050 else:
1049 else:
1051 return filter(lambda x: x != nullrev,
1050 return filter(lambda x: x != nullrev,
1052 repo.changelog.parentrevs(rev))
1051 repo.changelog.parentrevs(rev))
1053
1052
1054 if self.startrev == nullrev:
1053 if self.startrev == nullrev:
1055 self.startrev = rev
1054 self.startrev = rev
1056 return True
1055 return True
1057
1056
1058 if rev > self.startrev:
1057 if rev > self.startrev:
1059 # forward: all descendants
1058 # forward: all descendants
1060 if not self.roots:
1059 if not self.roots:
1061 self.roots.add(self.startrev)
1060 self.roots.add(self.startrev)
1062 for parent in realparents(rev):
1061 for parent in realparents(rev):
1063 if parent in self.roots:
1062 if parent in self.roots:
1064 self.roots.add(rev)
1063 self.roots.add(rev)
1065 return True
1064 return True
1066 else:
1065 else:
1067 # backwards: all parents
1066 # backwards: all parents
1068 if not self.roots:
1067 if not self.roots:
1069 self.roots.update(realparents(self.startrev))
1068 self.roots.update(realparents(self.startrev))
1070 if rev in self.roots:
1069 if rev in self.roots:
1071 self.roots.remove(rev)
1070 self.roots.remove(rev)
1072 self.roots.update(realparents(rev))
1071 self.roots.update(realparents(rev))
1073 return True
1072 return True
1074
1073
1075 return False
1074 return False
1076
1075
1077 # it might be worthwhile to do this in the iterator if the rev range
1076 # it might be worthwhile to do this in the iterator if the rev range
1078 # is descending and the prune args are all within that range
1077 # is descending and the prune args are all within that range
1079 for rev in opts.get('prune', ()):
1078 for rev in opts.get('prune', ()):
1080 rev = repo.changelog.rev(repo.lookup(rev))
1079 rev = repo.changelog.rev(repo.lookup(rev))
1081 ff = followfilter()
1080 ff = followfilter()
1082 stop = min(revs[0], revs[-1])
1081 stop = min(revs[0], revs[-1])
1083 for x in xrange(rev, stop-1, -1):
1082 for x in xrange(rev, stop-1, -1):
1084 if ff.match(x):
1083 if ff.match(x):
1085 wanted.discard(x)
1084 wanted.discard(x)
1086
1085
1087 def iterate():
1086 def iterate():
1088 if follow and not match.files():
1087 if follow and not match.files():
1089 ff = followfilter(onlyfirst=opts.get('follow_first'))
1088 ff = followfilter(onlyfirst=opts.get('follow_first'))
1090 def want(rev):
1089 def want(rev):
1091 return ff.match(rev) and rev in wanted
1090 return ff.match(rev) and rev in wanted
1092 else:
1091 else:
1093 def want(rev):
1092 def want(rev):
1094 return rev in wanted
1093 return rev in wanted
1095
1094
1096 for i, window in increasing_windows(0, len(revs)):
1095 for i, window in increasing_windows(0, len(revs)):
1097 change = util.cachefunc(repo.changectx)
1096 change = util.cachefunc(repo.changectx)
1098 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1097 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1099 for rev in sorted(nrevs):
1098 for rev in sorted(nrevs):
1100 fns = fncache.get(rev)
1099 fns = fncache.get(rev)
1101 ctx = change(rev)
1100 ctx = change(rev)
1102 if not fns:
1101 if not fns:
1103 def fns_generator():
1102 def fns_generator():
1104 for f in ctx.files():
1103 for f in ctx.files():
1105 if match(f):
1104 if match(f):
1106 yield f
1105 yield f
1107 fns = fns_generator()
1106 fns = fns_generator()
1108 prepare(ctx, fns)
1107 prepare(ctx, fns)
1109 for rev in nrevs:
1108 for rev in nrevs:
1110 yield change(rev)
1109 yield change(rev)
1111 return iterate()
1110 return iterate()
1112
1111
1113 def commit(ui, repo, commitfunc, pats, opts):
1112 def commit(ui, repo, commitfunc, pats, opts):
1114 '''commit the specified files or all outstanding changes'''
1113 '''commit the specified files or all outstanding changes'''
1115 date = opts.get('date')
1114 date = opts.get('date')
1116 if date:
1115 if date:
1117 opts['date'] = util.parsedate(date)
1116 opts['date'] = util.parsedate(date)
1118 message = logmessage(opts)
1117 message = logmessage(opts)
1119
1118
1120 # extract addremove carefully -- this function can be called from a command
1119 # extract addremove carefully -- this function can be called from a command
1121 # that doesn't support addremove
1120 # that doesn't support addremove
1122 if opts.get('addremove'):
1121 if opts.get('addremove'):
1123 addremove(repo, pats, opts)
1122 addremove(repo, pats, opts)
1124
1123
1125 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1124 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1126
1125
1127 def commiteditor(repo, ctx, subs):
1126 def commiteditor(repo, ctx, subs):
1128 if ctx.description():
1127 if ctx.description():
1129 return ctx.description()
1128 return ctx.description()
1130 return commitforceeditor(repo, ctx, subs)
1129 return commitforceeditor(repo, ctx, subs)
1131
1130
1132 def commitforceeditor(repo, ctx, subs):
1131 def commitforceeditor(repo, ctx, subs):
1133 edittext = []
1132 edittext = []
1134 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1133 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1135 if ctx.description():
1134 if ctx.description():
1136 edittext.append(ctx.description())
1135 edittext.append(ctx.description())
1137 edittext.append("")
1136 edittext.append("")
1138 edittext.append("") # Empty line between message and comments.
1137 edittext.append("") # Empty line between message and comments.
1139 edittext.append(_("HG: Enter commit message."
1138 edittext.append(_("HG: Enter commit message."
1140 " Lines beginning with 'HG:' are removed."))
1139 " Lines beginning with 'HG:' are removed."))
1141 edittext.append(_("HG: Leave message empty to abort commit."))
1140 edittext.append(_("HG: Leave message empty to abort commit."))
1142 edittext.append("HG: --")
1141 edittext.append("HG: --")
1143 edittext.append(_("HG: user: %s") % ctx.user())
1142 edittext.append(_("HG: user: %s") % ctx.user())
1144 if ctx.p2():
1143 if ctx.p2():
1145 edittext.append(_("HG: branch merge"))
1144 edittext.append(_("HG: branch merge"))
1146 if ctx.branch():
1145 if ctx.branch():
1147 edittext.append(_("HG: branch '%s'")
1146 edittext.append(_("HG: branch '%s'")
1148 % encoding.tolocal(ctx.branch()))
1147 % encoding.tolocal(ctx.branch()))
1149 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1148 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1150 edittext.extend([_("HG: added %s") % f for f in added])
1149 edittext.extend([_("HG: added %s") % f for f in added])
1151 edittext.extend([_("HG: changed %s") % f for f in modified])
1150 edittext.extend([_("HG: changed %s") % f for f in modified])
1152 edittext.extend([_("HG: removed %s") % f for f in removed])
1151 edittext.extend([_("HG: removed %s") % f for f in removed])
1153 if not added and not modified and not removed:
1152 if not added and not modified and not removed:
1154 edittext.append(_("HG: no files changed"))
1153 edittext.append(_("HG: no files changed"))
1155 edittext.append("")
1154 edittext.append("")
1156 # run editor in the repository root
1155 # run editor in the repository root
1157 olddir = os.getcwd()
1156 olddir = os.getcwd()
1158 os.chdir(repo.root)
1157 os.chdir(repo.root)
1159 text = repo.ui.edit("\n".join(edittext), ctx.user())
1158 text = repo.ui.edit("\n".join(edittext), ctx.user())
1160 text = re.sub("(?m)^HG:.*\n", "", text)
1159 text = re.sub("(?m)^HG:.*\n", "", text)
1161 os.chdir(olddir)
1160 os.chdir(olddir)
1162
1161
1163 if not text.strip():
1162 if not text.strip():
1164 raise util.Abort(_("empty commit message"))
1163 raise util.Abort(_("empty commit message"))
1165
1164
1166 return text
1165 return text
@@ -1,259 +1,264 b''
1 # posix.py - Posix utility function implementations for Mercurial
1 # posix.py - Posix utility function implementations for Mercurial
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 from i18n import _
8 from i18n import _
9 import osutil
9 import osutil
10 import os, sys, errno, stat, getpass, pwd, grp, fcntl
10 import os, sys, errno, stat, getpass, pwd, grp, fcntl
11
11
12 posixfile = open
12 posixfile = open
13 nulldev = '/dev/null'
13 nulldev = '/dev/null'
14 normpath = os.path.normpath
14 normpath = os.path.normpath
15 samestat = os.path.samestat
15 samestat = os.path.samestat
16 rename = os.rename
16 rename = os.rename
17 expandglobs = False
17 expandglobs = False
18
18
19 umask = os.umask(0)
19 umask = os.umask(0)
20 os.umask(umask)
20 os.umask(umask)
21
21
22 def openhardlinks():
22 def openhardlinks():
23 '''return true if it is safe to hold open file handles to hardlinks'''
23 '''return true if it is safe to hold open file handles to hardlinks'''
24 return True
24 return True
25
25
26 def rcfiles(path):
26 def rcfiles(path):
27 rcs = [os.path.join(path, 'hgrc')]
27 rcs = [os.path.join(path, 'hgrc')]
28 rcdir = os.path.join(path, 'hgrc.d')
28 rcdir = os.path.join(path, 'hgrc.d')
29 try:
29 try:
30 rcs.extend([os.path.join(rcdir, f)
30 rcs.extend([os.path.join(rcdir, f)
31 for f, kind in osutil.listdir(rcdir)
31 for f, kind in osutil.listdir(rcdir)
32 if f.endswith(".rc")])
32 if f.endswith(".rc")])
33 except OSError:
33 except OSError:
34 pass
34 pass
35 return rcs
35 return rcs
36
36
37 def system_rcpath():
37 def system_rcpath():
38 path = []
38 path = []
39 # old mod_python does not set sys.argv
39 # old mod_python does not set sys.argv
40 if len(getattr(sys, 'argv', [])) > 0:
40 if len(getattr(sys, 'argv', [])) > 0:
41 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
41 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
42 '/../etc/mercurial'))
42 '/../etc/mercurial'))
43 path.extend(rcfiles('/etc/mercurial'))
43 path.extend(rcfiles('/etc/mercurial'))
44 return path
44 return path
45
45
46 def user_rcpath():
46 def user_rcpath():
47 return [os.path.expanduser('~/.hgrc')]
47 return [os.path.expanduser('~/.hgrc')]
48
48
49 def parse_patch_output(output_line):
49 def parse_patch_output(output_line):
50 """parses the output produced by patch and returns the filename"""
50 """parses the output produced by patch and returns the filename"""
51 pf = output_line[14:]
51 pf = output_line[14:]
52 if os.sys.platform == 'OpenVMS':
52 if os.sys.platform == 'OpenVMS':
53 if pf[0] == '`':
53 if pf[0] == '`':
54 pf = pf[1:-1] # Remove the quotes
54 pf = pf[1:-1] # Remove the quotes
55 else:
55 else:
56 if pf.startswith("'") and pf.endswith("'") and " " in pf:
56 if pf.startswith("'") and pf.endswith("'") and " " in pf:
57 pf = pf[1:-1] # Remove the quotes
57 pf = pf[1:-1] # Remove the quotes
58 return pf
58 return pf
59
59
60 def sshargs(sshcmd, host, user, port):
60 def sshargs(sshcmd, host, user, port):
61 '''Build argument list for ssh'''
61 '''Build argument list for ssh'''
62 args = user and ("%s@%s" % (user, host)) or host
62 args = user and ("%s@%s" % (user, host)) or host
63 return port and ("%s -p %s" % (args, port)) or args
63 return port and ("%s -p %s" % (args, port)) or args
64
64
65 def is_exec(f):
65 def is_exec(f):
66 """check whether a file is executable"""
66 """check whether a file is executable"""
67 return (os.lstat(f).st_mode & 0100 != 0)
67 return (os.lstat(f).st_mode & 0100 != 0)
68
68
69 def set_flags(f, l, x):
69 def set_flags(f, l, x):
70 s = os.lstat(f).st_mode
70 s = os.lstat(f).st_mode
71 if l:
71 if l:
72 if not stat.S_ISLNK(s):
72 if not stat.S_ISLNK(s):
73 # switch file to link
73 # switch file to link
74 data = open(f).read()
74 data = open(f).read()
75 os.unlink(f)
75 os.unlink(f)
76 try:
76 try:
77 os.symlink(data, f)
77 os.symlink(data, f)
78 except:
78 except:
79 # failed to make a link, rewrite file
79 # failed to make a link, rewrite file
80 open(f, "w").write(data)
80 open(f, "w").write(data)
81 # no chmod needed at this point
81 # no chmod needed at this point
82 return
82 return
83 if stat.S_ISLNK(s):
83 if stat.S_ISLNK(s):
84 # switch link to file
84 # switch link to file
85 data = os.readlink(f)
85 data = os.readlink(f)
86 os.unlink(f)
86 os.unlink(f)
87 open(f, "w").write(data)
87 open(f, "w").write(data)
88 s = 0666 & ~umask # avoid restatting for chmod
88 s = 0666 & ~umask # avoid restatting for chmod
89
89
90 sx = s & 0100
90 sx = s & 0100
91 if x and not sx:
91 if x and not sx:
92 # Turn on +x for every +r bit when making a file executable
92 # Turn on +x for every +r bit when making a file executable
93 # and obey umask.
93 # and obey umask.
94 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
94 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
95 elif not x and sx:
95 elif not x and sx:
96 # Turn off all +x bits
96 # Turn off all +x bits
97 os.chmod(f, s & 0666)
97 os.chmod(f, s & 0666)
98
98
99 def set_binary(fd):
99 def set_binary(fd):
100 pass
100 pass
101
101
102 def pconvert(path):
102 def pconvert(path):
103 return path
103 return path
104
104
105 def localpath(path):
105 def localpath(path):
106 return path
106 return path
107
107
108 def samefile(fpath1, fpath2):
108 def samefile(fpath1, fpath2):
109 """Returns whether path1 and path2 refer to the same file. This is only
109 """Returns whether path1 and path2 refer to the same file. This is only
110 guaranteed to work for files, not directories."""
110 guaranteed to work for files, not directories."""
111 return os.path.samefile(fpath1, fpath2)
111 return os.path.samefile(fpath1, fpath2)
112
112
113 def samedevice(fpath1, fpath2):
113 def samedevice(fpath1, fpath2):
114 """Returns whether fpath1 and fpath2 are on the same device. This is only
114 """Returns whether fpath1 and fpath2 are on the same device. This is only
115 guaranteed to work for files, not directories."""
115 guaranteed to work for files, not directories."""
116 st1 = os.lstat(fpath1)
116 st1 = os.lstat(fpath1)
117 st2 = os.lstat(fpath2)
117 st2 = os.lstat(fpath2)
118 return st1.st_dev == st2.st_dev
118 return st1.st_dev == st2.st_dev
119
119
120 if sys.platform == 'darwin':
120 if sys.platform == 'darwin':
121 def realpath(path):
121 def realpath(path):
122 '''
122 '''
123 Returns the true, canonical file system path equivalent to the given
123 Returns the true, canonical file system path equivalent to the given
124 path.
124 path.
125
125
126 Equivalent means, in this case, resulting in the same, unique
126 Equivalent means, in this case, resulting in the same, unique
127 file system link to the path. Every file system entry, whether a file,
127 file system link to the path. Every file system entry, whether a file,
128 directory, hard link or symbolic link or special, will have a single
128 directory, hard link or symbolic link or special, will have a single
129 path preferred by the system, but may allow multiple, differing path
129 path preferred by the system, but may allow multiple, differing path
130 lookups to point to it.
130 lookups to point to it.
131
131
132 Most regular UNIX file systems only allow a file system entry to be
132 Most regular UNIX file systems only allow a file system entry to be
133 looked up by its distinct path. Obviously, this does not apply to case
133 looked up by its distinct path. Obviously, this does not apply to case
134 insensitive file systems, whether case preserving or not. The most
134 insensitive file systems, whether case preserving or not. The most
135 complex issue to deal with is file systems transparently reencoding the
135 complex issue to deal with is file systems transparently reencoding the
136 path, such as the non-standard Unicode normalisation required for HFS+
136 path, such as the non-standard Unicode normalisation required for HFS+
137 and HFSX.
137 and HFSX.
138 '''
138 '''
139 # Constants copied from /usr/include/sys/fcntl.h
139 # Constants copied from /usr/include/sys/fcntl.h
140 F_GETPATH = 50
140 F_GETPATH = 50
141 O_SYMLINK = 0x200000
141 O_SYMLINK = 0x200000
142
142
143 try:
143 try:
144 fd = os.open(path, O_SYMLINK)
144 fd = os.open(path, O_SYMLINK)
145 except OSError, err:
145 except OSError, err:
146 if err.errno is errno.ENOENT:
146 if err.errno is errno.ENOENT:
147 return path
147 return path
148 raise
148 raise
149
149
150 try:
150 try:
151 return fcntl.fcntl(fd, F_GETPATH, '\0' * 1024).rstrip('\0')
151 return fcntl.fcntl(fd, F_GETPATH, '\0' * 1024).rstrip('\0')
152 finally:
152 finally:
153 os.close(fd)
153 os.close(fd)
154 else:
154 else:
155 # Fallback to the likely inadequate Python builtin function.
155 # Fallback to the likely inadequate Python builtin function.
156 realpath = os.path.realpath
156 realpath = os.path.realpath
157
157
158 def shellquote(s):
158 def shellquote(s):
159 if os.sys.platform == 'OpenVMS':
159 if os.sys.platform == 'OpenVMS':
160 return '"%s"' % s
160 return '"%s"' % s
161 else:
161 else:
162 return "'%s'" % s.replace("'", "'\\''")
162 return "'%s'" % s.replace("'", "'\\''")
163
163
164 def quotecommand(cmd):
164 def quotecommand(cmd):
165 return cmd
165 return cmd
166
166
167 def popen(command, mode='r'):
167 def popen(command, mode='r'):
168 return os.popen(command, mode)
168 return os.popen(command, mode)
169
169
170 def testpid(pid):
170 def testpid(pid):
171 '''return False if pid dead, True if running or not sure'''
171 '''return False if pid dead, True if running or not sure'''
172 if os.sys.platform == 'OpenVMS':
172 if os.sys.platform == 'OpenVMS':
173 return True
173 return True
174 try:
174 try:
175 os.kill(pid, 0)
175 os.kill(pid, 0)
176 return True
176 return True
177 except OSError, inst:
177 except OSError, inst:
178 return inst.errno != errno.ESRCH
178 return inst.errno != errno.ESRCH
179
179
180 def explain_exit(code):
180 def explain_exit(code):
181 """return a 2-tuple (desc, code) describing a subprocess status
181 """return a 2-tuple (desc, code) describing a subprocess status
182 (codes from kill are negative - not os.system/wait encoding)"""
182 (codes from kill are negative - not os.system/wait encoding)"""
183 if code >= 0:
183 if code >= 0:
184 return _("exited with status %d") % code, code
184 return _("exited with status %d") % code, code
185 return _("killed by signal %d") % -code, -code
185 return _("killed by signal %d") % -code, -code
186
186
187 def isowner(st):
187 def isowner(st):
188 """Return True if the stat object st is from the current user."""
188 """Return True if the stat object st is from the current user."""
189 return st.st_uid == os.getuid()
189 return st.st_uid == os.getuid()
190
190
191 def find_exe(command):
191 def find_exe(command):
192 '''Find executable for command searching like which does.
192 '''Find executable for command searching like which does.
193 If command is a basename then PATH is searched for command.
193 If command is a basename then PATH is searched for command.
194 PATH isn't searched if command is an absolute or relative path.
194 PATH isn't searched if command is an absolute or relative path.
195 If command isn't found None is returned.'''
195 If command isn't found None is returned.'''
196 if sys.platform == 'OpenVMS':
196 if sys.platform == 'OpenVMS':
197 return command
197 return command
198
198
199 def findexisting(executable):
199 def findexisting(executable):
200 'Will return executable if existing file'
200 'Will return executable if existing file'
201 if os.path.exists(executable):
201 if os.path.exists(executable):
202 return executable
202 return executable
203 return None
203 return None
204
204
205 if os.sep in command:
205 if os.sep in command:
206 return findexisting(command)
206 return findexisting(command)
207
207
208 for path in os.environ.get('PATH', '').split(os.pathsep):
208 for path in os.environ.get('PATH', '').split(os.pathsep):
209 executable = findexisting(os.path.join(path, command))
209 executable = findexisting(os.path.join(path, command))
210 if executable is not None:
210 if executable is not None:
211 return executable
211 return executable
212 return None
212 return None
213
213
214 def set_signal_handler():
214 def set_signal_handler():
215 pass
215 pass
216
216
217 def statfiles(files):
217 def statfiles(files):
218 'Stat each file in files and yield stat or None if file does not exist.'
218 'Stat each file in files and yield stat or None if file does not exist.'
219 lstat = os.lstat
219 lstat = os.lstat
220 for nf in files:
220 for nf in files:
221 try:
221 try:
222 st = lstat(nf)
222 st = lstat(nf)
223 except OSError, err:
223 except OSError, err:
224 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
224 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
225 raise
225 raise
226 st = None
226 st = None
227 yield st
227 yield st
228
228
229 def getuser():
229 def getuser():
230 '''return name of current user'''
230 '''return name of current user'''
231 return getpass.getuser()
231 return getpass.getuser()
232
232
233 def expand_glob(pats):
233 def expand_glob(pats):
234 '''On Windows, expand the implicit globs in a list of patterns'''
234 '''On Windows, expand the implicit globs in a list of patterns'''
235 return list(pats)
235 return list(pats)
236
236
237 def username(uid=None):
237 def username(uid=None):
238 """Return the name of the user with the given uid.
238 """Return the name of the user with the given uid.
239
239
240 If uid is None, return the name of the current user."""
240 If uid is None, return the name of the current user."""
241
241
242 if uid is None:
242 if uid is None:
243 uid = os.getuid()
243 uid = os.getuid()
244 try:
244 try:
245 return pwd.getpwuid(uid)[0]
245 return pwd.getpwuid(uid)[0]
246 except KeyError:
246 except KeyError:
247 return str(uid)
247 return str(uid)
248
248
249 def groupname(gid=None):
249 def groupname(gid=None):
250 """Return the name of the group with the given gid.
250 """Return the name of the group with the given gid.
251
251
252 If gid is None, return the name of the current group."""
252 If gid is None, return the name of the current group."""
253
253
254 if gid is None:
254 if gid is None:
255 gid = os.getgid()
255 gid = os.getgid()
256 try:
256 try:
257 return grp.getgrgid(gid)[0]
257 return grp.getgrgid(gid)[0]
258 except KeyError:
258 except KeyError:
259 return str(gid)
259 return str(gid)
260
261 def spawndetached(args):
262 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
263 args[0], args)
264
@@ -1,330 +1,361 b''
1 # windows.py - Windows utility function implementations for Mercurial
1 # windows.py - Windows utility function implementations for Mercurial
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 from i18n import _
8 from i18n import _
9 import osutil, error
9 import osutil, error
10 import errno, msvcrt, os, re, sys, random
10 import errno, msvcrt, os, re, sys, random, subprocess
11
11
12 nulldev = 'NUL:'
12 nulldev = 'NUL:'
13 umask = 002
13 umask = 002
14
14
15 # wrap osutil.posixfile to provide friendlier exceptions
15 # wrap osutil.posixfile to provide friendlier exceptions
16 def posixfile(name, mode='r', buffering=-1):
16 def posixfile(name, mode='r', buffering=-1):
17 try:
17 try:
18 return osutil.posixfile(name, mode, buffering)
18 return osutil.posixfile(name, mode, buffering)
19 except WindowsError, err:
19 except WindowsError, err:
20 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
20 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
21 posixfile.__doc__ = osutil.posixfile.__doc__
21 posixfile.__doc__ = osutil.posixfile.__doc__
22
22
23 class winstdout(object):
23 class winstdout(object):
24 '''stdout on windows misbehaves if sent through a pipe'''
24 '''stdout on windows misbehaves if sent through a pipe'''
25
25
26 def __init__(self, fp):
26 def __init__(self, fp):
27 self.fp = fp
27 self.fp = fp
28
28
29 def __getattr__(self, key):
29 def __getattr__(self, key):
30 return getattr(self.fp, key)
30 return getattr(self.fp, key)
31
31
32 def close(self):
32 def close(self):
33 try:
33 try:
34 self.fp.close()
34 self.fp.close()
35 except: pass
35 except: pass
36
36
37 def write(self, s):
37 def write(self, s):
38 try:
38 try:
39 # This is workaround for "Not enough space" error on
39 # This is workaround for "Not enough space" error on
40 # writing large size of data to console.
40 # writing large size of data to console.
41 limit = 16000
41 limit = 16000
42 l = len(s)
42 l = len(s)
43 start = 0
43 start = 0
44 self.softspace = 0;
44 self.softspace = 0;
45 while start < l:
45 while start < l:
46 end = start + limit
46 end = start + limit
47 self.fp.write(s[start:end])
47 self.fp.write(s[start:end])
48 start = end
48 start = end
49 except IOError, inst:
49 except IOError, inst:
50 if inst.errno != 0: raise
50 if inst.errno != 0: raise
51 self.close()
51 self.close()
52 raise IOError(errno.EPIPE, 'Broken pipe')
52 raise IOError(errno.EPIPE, 'Broken pipe')
53
53
54 def flush(self):
54 def flush(self):
55 try:
55 try:
56 return self.fp.flush()
56 return self.fp.flush()
57 except IOError, inst:
57 except IOError, inst:
58 if inst.errno != errno.EINVAL: raise
58 if inst.errno != errno.EINVAL: raise
59 self.close()
59 self.close()
60 raise IOError(errno.EPIPE, 'Broken pipe')
60 raise IOError(errno.EPIPE, 'Broken pipe')
61
61
62 sys.stdout = winstdout(sys.stdout)
62 sys.stdout = winstdout(sys.stdout)
63
63
64 def _is_win_9x():
64 def _is_win_9x():
65 '''return true if run on windows 95, 98 or me.'''
65 '''return true if run on windows 95, 98 or me.'''
66 try:
66 try:
67 return sys.getwindowsversion()[3] == 1
67 return sys.getwindowsversion()[3] == 1
68 except AttributeError:
68 except AttributeError:
69 return 'command' in os.environ.get('comspec', '')
69 return 'command' in os.environ.get('comspec', '')
70
70
71 def openhardlinks():
71 def openhardlinks():
72 return not _is_win_9x() and "win32api" in globals()
72 return not _is_win_9x() and "win32api" in globals()
73
73
74 def system_rcpath():
74 def system_rcpath():
75 try:
75 try:
76 return system_rcpath_win32()
76 return system_rcpath_win32()
77 except:
77 except:
78 return [r'c:\mercurial\mercurial.ini']
78 return [r'c:\mercurial\mercurial.ini']
79
79
80 def user_rcpath():
80 def user_rcpath():
81 '''return os-specific hgrc search path to the user dir'''
81 '''return os-specific hgrc search path to the user dir'''
82 try:
82 try:
83 path = user_rcpath_win32()
83 path = user_rcpath_win32()
84 except:
84 except:
85 home = os.path.expanduser('~')
85 home = os.path.expanduser('~')
86 path = [os.path.join(home, 'mercurial.ini'),
86 path = [os.path.join(home, 'mercurial.ini'),
87 os.path.join(home, '.hgrc')]
87 os.path.join(home, '.hgrc')]
88 userprofile = os.environ.get('USERPROFILE')
88 userprofile = os.environ.get('USERPROFILE')
89 if userprofile:
89 if userprofile:
90 path.append(os.path.join(userprofile, 'mercurial.ini'))
90 path.append(os.path.join(userprofile, 'mercurial.ini'))
91 path.append(os.path.join(userprofile, '.hgrc'))
91 path.append(os.path.join(userprofile, '.hgrc'))
92 return path
92 return path
93
93
94 def parse_patch_output(output_line):
94 def parse_patch_output(output_line):
95 """parses the output produced by patch and returns the filename"""
95 """parses the output produced by patch and returns the filename"""
96 pf = output_line[14:]
96 pf = output_line[14:]
97 if pf[0] == '`':
97 if pf[0] == '`':
98 pf = pf[1:-1] # Remove the quotes
98 pf = pf[1:-1] # Remove the quotes
99 return pf
99 return pf
100
100
101 def sshargs(sshcmd, host, user, port):
101 def sshargs(sshcmd, host, user, port):
102 '''Build argument list for ssh or Plink'''
102 '''Build argument list for ssh or Plink'''
103 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
103 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
104 args = user and ("%s@%s" % (user, host)) or host
104 args = user and ("%s@%s" % (user, host)) or host
105 return port and ("%s %s %s" % (args, pflag, port)) or args
105 return port and ("%s %s %s" % (args, pflag, port)) or args
106
106
107 def testpid(pid):
107 def testpid(pid):
108 '''return False if pid dead, True if running or not known'''
108 '''return False if pid dead, True if running or not known'''
109 return True
109 return True
110
110
111 def set_flags(f, l, x):
111 def set_flags(f, l, x):
112 pass
112 pass
113
113
114 def set_binary(fd):
114 def set_binary(fd):
115 # When run without console, pipes may expose invalid
115 # When run without console, pipes may expose invalid
116 # fileno(), usually set to -1.
116 # fileno(), usually set to -1.
117 if hasattr(fd, 'fileno') and fd.fileno() >= 0:
117 if hasattr(fd, 'fileno') and fd.fileno() >= 0:
118 msvcrt.setmode(fd.fileno(), os.O_BINARY)
118 msvcrt.setmode(fd.fileno(), os.O_BINARY)
119
119
120 def pconvert(path):
120 def pconvert(path):
121 return '/'.join(path.split(os.sep))
121 return '/'.join(path.split(os.sep))
122
122
123 def localpath(path):
123 def localpath(path):
124 return path.replace('/', '\\')
124 return path.replace('/', '\\')
125
125
126 def normpath(path):
126 def normpath(path):
127 return pconvert(os.path.normpath(path))
127 return pconvert(os.path.normpath(path))
128
128
129 def realpath(path):
129 def realpath(path):
130 '''
130 '''
131 Returns the true, canonical file system path equivalent to the given
131 Returns the true, canonical file system path equivalent to the given
132 path.
132 path.
133 '''
133 '''
134 # TODO: There may be a more clever way to do this that also handles other,
134 # TODO: There may be a more clever way to do this that also handles other,
135 # less common file systems.
135 # less common file systems.
136 return os.path.normpath(os.path.normcase(os.path.realpath(path)))
136 return os.path.normpath(os.path.normcase(os.path.realpath(path)))
137
137
138 def samestat(s1, s2):
138 def samestat(s1, s2):
139 return False
139 return False
140
140
141 # A sequence of backslashes is special iff it precedes a double quote:
141 # A sequence of backslashes is special iff it precedes a double quote:
142 # - if there's an even number of backslashes, the double quote is not
142 # - if there's an even number of backslashes, the double quote is not
143 # quoted (i.e. it ends the quoted region)
143 # quoted (i.e. it ends the quoted region)
144 # - if there's an odd number of backslashes, the double quote is quoted
144 # - if there's an odd number of backslashes, the double quote is quoted
145 # - in both cases, every pair of backslashes is unquoted into a single
145 # - in both cases, every pair of backslashes is unquoted into a single
146 # backslash
146 # backslash
147 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
147 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
148 # So, to quote a string, we must surround it in double quotes, double
148 # So, to quote a string, we must surround it in double quotes, double
149 # the number of backslashes that preceed double quotes and add another
149 # the number of backslashes that preceed double quotes and add another
150 # backslash before every double quote (being careful with the double
150 # backslash before every double quote (being careful with the double
151 # quote we've appended to the end)
151 # quote we've appended to the end)
152 _quotere = None
152 _quotere = None
153 def shellquote(s):
153 def shellquote(s):
154 global _quotere
154 global _quotere
155 if _quotere is None:
155 if _quotere is None:
156 _quotere = re.compile(r'(\\*)("|\\$)')
156 _quotere = re.compile(r'(\\*)("|\\$)')
157 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
157 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
158
158
159 def quotecommand(cmd):
159 def quotecommand(cmd):
160 """Build a command string suitable for os.popen* calls."""
160 """Build a command string suitable for os.popen* calls."""
161 # The extra quotes are needed because popen* runs the command
161 # The extra quotes are needed because popen* runs the command
162 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
162 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
163 return '"' + cmd + '"'
163 return '"' + cmd + '"'
164
164
165 def popen(command, mode='r'):
165 def popen(command, mode='r'):
166 # Work around "popen spawned process may not write to stdout
166 # Work around "popen spawned process may not write to stdout
167 # under windows"
167 # under windows"
168 # http://bugs.python.org/issue1366
168 # http://bugs.python.org/issue1366
169 command += " 2> %s" % nulldev
169 command += " 2> %s" % nulldev
170 return os.popen(quotecommand(command), mode)
170 return os.popen(quotecommand(command), mode)
171
171
172 def explain_exit(code):
172 def explain_exit(code):
173 return _("exited with status %d") % code, code
173 return _("exited with status %d") % code, code
174
174
175 # if you change this stub into a real check, please try to implement the
175 # if you change this stub into a real check, please try to implement the
176 # username and groupname functions above, too.
176 # username and groupname functions above, too.
177 def isowner(st):
177 def isowner(st):
178 return True
178 return True
179
179
180 def find_exe(command):
180 def find_exe(command):
181 '''Find executable for command searching like cmd.exe does.
181 '''Find executable for command searching like cmd.exe does.
182 If command is a basename then PATH is searched for command.
182 If command is a basename then PATH is searched for command.
183 PATH isn't searched if command is an absolute or relative path.
183 PATH isn't searched if command is an absolute or relative path.
184 An extension from PATHEXT is found and added if not present.
184 An extension from PATHEXT is found and added if not present.
185 If command isn't found None is returned.'''
185 If command isn't found None is returned.'''
186 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
186 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
187 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
187 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
188 if os.path.splitext(command)[1].lower() in pathexts:
188 if os.path.splitext(command)[1].lower() in pathexts:
189 pathexts = ['']
189 pathexts = ['']
190
190
191 def findexisting(pathcommand):
191 def findexisting(pathcommand):
192 'Will append extension (if needed) and return existing file'
192 'Will append extension (if needed) and return existing file'
193 for ext in pathexts:
193 for ext in pathexts:
194 executable = pathcommand + ext
194 executable = pathcommand + ext
195 if os.path.exists(executable):
195 if os.path.exists(executable):
196 return executable
196 return executable
197 return None
197 return None
198
198
199 if os.sep in command:
199 if os.sep in command:
200 return findexisting(command)
200 return findexisting(command)
201
201
202 for path in os.environ.get('PATH', '').split(os.pathsep):
202 for path in os.environ.get('PATH', '').split(os.pathsep):
203 executable = findexisting(os.path.join(path, command))
203 executable = findexisting(os.path.join(path, command))
204 if executable is not None:
204 if executable is not None:
205 return executable
205 return executable
206 return findexisting(os.path.expanduser(os.path.expandvars(command)))
206 return findexisting(os.path.expanduser(os.path.expandvars(command)))
207
207
208 def set_signal_handler():
208 def set_signal_handler():
209 try:
209 try:
210 set_signal_handler_win32()
210 set_signal_handler_win32()
211 except NameError:
211 except NameError:
212 pass
212 pass
213
213
214 def statfiles(files):
214 def statfiles(files):
215 '''Stat each file in files and yield stat or None if file does not exist.
215 '''Stat each file in files and yield stat or None if file does not exist.
216 Cluster and cache stat per directory to minimize number of OS stat calls.'''
216 Cluster and cache stat per directory to minimize number of OS stat calls.'''
217 ncase = os.path.normcase
217 ncase = os.path.normcase
218 sep = os.sep
218 sep = os.sep
219 dircache = {} # dirname -> filename -> status | None if file does not exist
219 dircache = {} # dirname -> filename -> status | None if file does not exist
220 for nf in files:
220 for nf in files:
221 nf = ncase(nf)
221 nf = ncase(nf)
222 dir, base = os.path.split(nf)
222 dir, base = os.path.split(nf)
223 if not dir:
223 if not dir:
224 dir = '.'
224 dir = '.'
225 cache = dircache.get(dir, None)
225 cache = dircache.get(dir, None)
226 if cache is None:
226 if cache is None:
227 try:
227 try:
228 dmap = dict([(ncase(n), s)
228 dmap = dict([(ncase(n), s)
229 for n, k, s in osutil.listdir(dir, True)])
229 for n, k, s in osutil.listdir(dir, True)])
230 except OSError, err:
230 except OSError, err:
231 # handle directory not found in Python version prior to 2.5
231 # handle directory not found in Python version prior to 2.5
232 # Python <= 2.4 returns native Windows code 3 in errno
232 # Python <= 2.4 returns native Windows code 3 in errno
233 # Python >= 2.5 returns ENOENT and adds winerror field
233 # Python >= 2.5 returns ENOENT and adds winerror field
234 # EINVAL is raised if dir is not a directory.
234 # EINVAL is raised if dir is not a directory.
235 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
235 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
236 errno.ENOTDIR):
236 errno.ENOTDIR):
237 raise
237 raise
238 dmap = {}
238 dmap = {}
239 cache = dircache.setdefault(dir, dmap)
239 cache = dircache.setdefault(dir, dmap)
240 yield cache.get(base, None)
240 yield cache.get(base, None)
241
241
242 def getuser():
242 def getuser():
243 '''return name of current user'''
243 '''return name of current user'''
244 raise error.Abort(_('user name not available - set USERNAME '
244 raise error.Abort(_('user name not available - set USERNAME '
245 'environment variable'))
245 'environment variable'))
246
246
247 def username(uid=None):
247 def username(uid=None):
248 """Return the name of the user with the given uid.
248 """Return the name of the user with the given uid.
249
249
250 If uid is None, return the name of the current user."""
250 If uid is None, return the name of the current user."""
251 return None
251 return None
252
252
253 def groupname(gid=None):
253 def groupname(gid=None):
254 """Return the name of the group with the given gid.
254 """Return the name of the group with the given gid.
255
255
256 If gid is None, return the name of the current group."""
256 If gid is None, return the name of the current group."""
257 return None
257 return None
258
258
259 def _removedirs(name):
259 def _removedirs(name):
260 """special version of os.removedirs that does not remove symlinked
260 """special version of os.removedirs that does not remove symlinked
261 directories or junction points if they actually contain files"""
261 directories or junction points if they actually contain files"""
262 if osutil.listdir(name):
262 if osutil.listdir(name):
263 return
263 return
264 os.rmdir(name)
264 os.rmdir(name)
265 head, tail = os.path.split(name)
265 head, tail = os.path.split(name)
266 if not tail:
266 if not tail:
267 head, tail = os.path.split(head)
267 head, tail = os.path.split(head)
268 while head and tail:
268 while head and tail:
269 try:
269 try:
270 if osutil.listdir(head):
270 if osutil.listdir(head):
271 return
271 return
272 os.rmdir(head)
272 os.rmdir(head)
273 except:
273 except:
274 break
274 break
275 head, tail = os.path.split(head)
275 head, tail = os.path.split(head)
276
276
277 def unlink(f):
277 def unlink(f):
278 """unlink and remove the directory if it is empty"""
278 """unlink and remove the directory if it is empty"""
279 os.unlink(f)
279 os.unlink(f)
280 # try removing directories that might now be empty
280 # try removing directories that might now be empty
281 try:
281 try:
282 _removedirs(os.path.dirname(f))
282 _removedirs(os.path.dirname(f))
283 except OSError:
283 except OSError:
284 pass
284 pass
285
285
286 def rename(src, dst):
286 def rename(src, dst):
287 '''atomically rename file src to dst, replacing dst if it exists'''
287 '''atomically rename file src to dst, replacing dst if it exists'''
288 try:
288 try:
289 os.rename(src, dst)
289 os.rename(src, dst)
290 except OSError, err: # FIXME: check err (EEXIST ?)
290 except OSError, err: # FIXME: check err (EEXIST ?)
291
291
292 # On windows, rename to existing file is not allowed, so we
292 # On windows, rename to existing file is not allowed, so we
293 # must delete destination first. But if a file is open, unlink
293 # must delete destination first. But if a file is open, unlink
294 # schedules it for delete but does not delete it. Rename
294 # schedules it for delete but does not delete it. Rename
295 # happens immediately even for open files, so we rename
295 # happens immediately even for open files, so we rename
296 # destination to a temporary name, then delete that. Then
296 # destination to a temporary name, then delete that. Then
297 # rename is safe to do.
297 # rename is safe to do.
298 # The temporary name is chosen at random to avoid the situation
298 # The temporary name is chosen at random to avoid the situation
299 # where a file is left lying around from a previous aborted run.
299 # where a file is left lying around from a previous aborted run.
300 # The usual race condition this introduces can't be avoided as
300 # The usual race condition this introduces can't be avoided as
301 # we need the name to rename into, and not the file itself. Due
301 # we need the name to rename into, and not the file itself. Due
302 # to the nature of the operation however, any races will at worst
302 # to the nature of the operation however, any races will at worst
303 # lead to the rename failing and the current operation aborting.
303 # lead to the rename failing and the current operation aborting.
304
304
305 def tempname(prefix):
305 def tempname(prefix):
306 for tries in xrange(10):
306 for tries in xrange(10):
307 temp = '%s-%08x' % (prefix, random.randint(0, 0xffffffff))
307 temp = '%s-%08x' % (prefix, random.randint(0, 0xffffffff))
308 if not os.path.exists(temp):
308 if not os.path.exists(temp):
309 return temp
309 return temp
310 raise IOError, (errno.EEXIST, "No usable temporary filename found")
310 raise IOError, (errno.EEXIST, "No usable temporary filename found")
311
311
312 temp = tempname(dst)
312 temp = tempname(dst)
313 os.rename(dst, temp)
313 os.rename(dst, temp)
314 try:
314 try:
315 os.unlink(temp)
315 os.unlink(temp)
316 except:
316 except:
317 # Some rude AV-scanners on Windows may cause the unlink to
317 # Some rude AV-scanners on Windows may cause the unlink to
318 # fail. Not aborting here just leaks the temp file, whereas
318 # fail. Not aborting here just leaks the temp file, whereas
319 # aborting at this point may leave serious inconsistencies.
319 # aborting at this point may leave serious inconsistencies.
320 # Ideally, we would notify the user here.
320 # Ideally, we would notify the user here.
321 pass
321 pass
322 os.rename(src, dst)
322 os.rename(src, dst)
323
323
324 def spawndetached(args):
325 # No standard library function really spawns a fully detached
326 # process under win32 because they allocate pipes or other objects
327 # to handle standard streams communications. Passing these objects
328 # to the child process requires handle inheritance to be enabled
329 # which makes really detached processes impossible.
330 class STARTUPINFO:
331 dwFlags = subprocess.STARTF_USESHOWWINDOW
332 hStdInput = None
333 hStdOutput = None
334 hStdError = None
335 wShowWindow = subprocess.SW_HIDE
336
337 args = subprocess.list2cmdline(args)
338 # Not running the command in shell mode makes python26 hang when
339 # writing to hgweb output socket.
340 comspec = os.environ.get("COMSPEC", "cmd.exe")
341 args = comspec + " /c " + args
342 hp, ht, pid, tid = subprocess.CreateProcess(
343 None, args,
344 # no special security
345 None, None,
346 # Do not inherit handles
347 0,
348 # DETACHED_PROCESS
349 0x00000008,
350 os.environ,
351 os.getcwd(),
352 STARTUPINFO())
353 return pid
354
324 try:
355 try:
325 # override functions with win32 versions if possible
356 # override functions with win32 versions if possible
326 from win32 import *
357 from win32 import *
327 except ImportError:
358 except ImportError:
328 pass
359 pass
329
360
330 expandglobs = True
361 expandglobs = True
General Comments 0
You need to be logged in to leave comments. Login now