##// END OF EJS Templates
Find right hg command for detached process...
Patrick Mezard -
r10239:8e4be44a default
parent child Browse files
Show More
@@ -1,478 +1,478 b''
1 1 # server.py - common entry point for inotify status server
2 2 #
3 3 # Copyright 2009 Nicolas Dumazet <nicdumz@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 from mercurial.i18n import _
9 9 from mercurial import cmdutil, osutil, util
10 10 import common
11 11
12 12 import errno
13 13 import os
14 14 import socket
15 15 import stat
16 16 import struct
17 17 import sys
18 18 import tempfile
19 19
20 20 class AlreadyStartedException(Exception): pass
21 21
22 22 def join(a, b):
23 23 if a:
24 24 if a[-1] == '/':
25 25 return a + b
26 26 return a + '/' + b
27 27 return b
28 28
29 29 def split(path):
30 30 c = path.rfind('/')
31 31 if c == -1:
32 32 return '', path
33 33 return path[:c], path[c+1:]
34 34
35 35 walk_ignored_errors = (errno.ENOENT, errno.ENAMETOOLONG)
36 36
37 37 def walk(dirstate, absroot, root):
38 38 '''Like os.walk, but only yields regular files.'''
39 39
40 40 # This function is critical to performance during startup.
41 41
42 42 def walkit(root, reporoot):
43 43 files, dirs = [], []
44 44
45 45 try:
46 46 fullpath = join(absroot, root)
47 47 for name, kind in osutil.listdir(fullpath):
48 48 if kind == stat.S_IFDIR:
49 49 if name == '.hg':
50 50 if not reporoot:
51 51 return
52 52 else:
53 53 dirs.append(name)
54 54 path = join(root, name)
55 55 if dirstate._ignore(path):
56 56 continue
57 57 for result in walkit(path, False):
58 58 yield result
59 59 elif kind in (stat.S_IFREG, stat.S_IFLNK):
60 60 files.append(name)
61 61 yield fullpath, dirs, files
62 62
63 63 except OSError, err:
64 64 if err.errno == errno.ENOTDIR:
65 65 # fullpath was a directory, but has since been replaced
66 66 # by a file.
67 67 yield fullpath, dirs, files
68 68 elif err.errno not in walk_ignored_errors:
69 69 raise
70 70
71 71 return walkit(root, root == '')
72 72
73 73 class directory(object):
74 74 """
75 75 Representing a directory
76 76
77 77 * path is the relative path from repo root to this directory
78 78 * files is a dict listing the files in this directory
79 79 - keys are file names
80 80 - values are file status
81 81 * dirs is a dict listing the subdirectories
82 82 - key are subdirectories names
83 83 - values are directory objects
84 84 """
85 85 def __init__(self, relpath=''):
86 86 self.path = relpath
87 87 self.files = {}
88 88 self.dirs = {}
89 89
90 90 def dir(self, relpath):
91 91 """
92 92 Returns the directory contained at the relative path relpath.
93 93 Creates the intermediate directories if necessary.
94 94 """
95 95 if not relpath:
96 96 return self
97 97 l = relpath.split('/')
98 98 ret = self
99 99 while l:
100 100 next = l.pop(0)
101 101 try:
102 102 ret = ret.dirs[next]
103 103 except KeyError:
104 104 d = directory(join(ret.path, next))
105 105 ret.dirs[next] = d
106 106 ret = d
107 107 return ret
108 108
109 109 def walk(self, states, visited=None):
110 110 """
111 111 yield (filename, status) pairs for items in the trees
112 112 that have status in states.
113 113 filenames are relative to the repo root
114 114 """
115 115 for file, st in self.files.iteritems():
116 116 if st in states:
117 117 yield join(self.path, file), st
118 118 for dir in self.dirs.itervalues():
119 119 if visited is not None:
120 120 visited.add(dir.path)
121 121 for e in dir.walk(states):
122 122 yield e
123 123
124 124 def lookup(self, states, path, visited):
125 125 """
126 126 yield root-relative filenames that match path, and whose
127 127 status are in states:
128 128 * if path is a file, yield path
129 129 * if path is a directory, yield directory files
130 130 * if path is not tracked, yield nothing
131 131 """
132 132 if path[-1] == '/':
133 133 path = path[:-1]
134 134
135 135 paths = path.split('/')
136 136
137 137 # we need to check separately for last node
138 138 last = paths.pop()
139 139
140 140 tree = self
141 141 try:
142 142 for dir in paths:
143 143 tree = tree.dirs[dir]
144 144 except KeyError:
145 145 # path is not tracked
146 146 visited.add(tree.path)
147 147 return
148 148
149 149 try:
150 150 # if path is a directory, walk it
151 151 target = tree.dirs[last]
152 152 visited.add(target.path)
153 153 for file, st in target.walk(states, visited):
154 154 yield file
155 155 except KeyError:
156 156 try:
157 157 if tree.files[last] in states:
158 158 # path is a file
159 159 visited.add(tree.path)
160 160 yield path
161 161 except KeyError:
162 162 # path is not tracked
163 163 pass
164 164
165 165 class repowatcher(object):
166 166 """
167 167 Watches inotify events
168 168 """
169 169 statuskeys = 'almr!?'
170 170
171 171 def __init__(self, ui, dirstate, root):
172 172 self.ui = ui
173 173 self.dirstate = dirstate
174 174
175 175 self.wprefix = join(root, '')
176 176 self.prefixlen = len(self.wprefix)
177 177
178 178 self.tree = directory()
179 179 self.statcache = {}
180 180 self.statustrees = dict([(s, directory()) for s in self.statuskeys])
181 181
182 182 self.ds_info = self.dirstate_info()
183 183
184 184 self.last_event = None
185 185
186 186
187 187 def handle_timeout(self):
188 188 pass
189 189
190 190 def dirstate_info(self):
191 191 try:
192 192 st = os.lstat(self.wprefix + '.hg/dirstate')
193 193 return st.st_mtime, st.st_ino
194 194 except OSError, err:
195 195 if err.errno != errno.ENOENT:
196 196 raise
197 197 return 0, 0
198 198
199 199 def filestatus(self, fn, st):
200 200 try:
201 201 type_, mode, size, time = self.dirstate._map[fn][:4]
202 202 except KeyError:
203 203 type_ = '?'
204 204 if type_ == 'n':
205 205 st_mode, st_size, st_mtime = st
206 206 if size == -1:
207 207 return 'l'
208 208 if size and (size != st_size or (mode ^ st_mode) & 0100):
209 209 return 'm'
210 210 if time != int(st_mtime):
211 211 return 'l'
212 212 return 'n'
213 213 if type_ == '?' and self.dirstate._ignore(fn):
214 214 return 'i'
215 215 return type_
216 216
217 217 def updatefile(self, wfn, osstat):
218 218 '''
219 219 update the file entry of an existing file.
220 220
221 221 osstat: (mode, size, time) tuple, as returned by os.lstat(wfn)
222 222 '''
223 223
224 224 self._updatestatus(wfn, self.filestatus(wfn, osstat))
225 225
226 226 def deletefile(self, wfn, oldstatus):
227 227 '''
228 228 update the entry of a file which has been deleted.
229 229
230 230 oldstatus: char in statuskeys, status of the file before deletion
231 231 '''
232 232 if oldstatus == 'r':
233 233 newstatus = 'r'
234 234 elif oldstatus in 'almn':
235 235 newstatus = '!'
236 236 else:
237 237 newstatus = None
238 238
239 239 self.statcache.pop(wfn, None)
240 240 self._updatestatus(wfn, newstatus)
241 241
242 242 def _updatestatus(self, wfn, newstatus):
243 243 '''
244 244 Update the stored status of a file.
245 245
246 246 newstatus: - char in (statuskeys + 'ni'), new status to apply.
247 247 - or None, to stop tracking wfn
248 248 '''
249 249 root, fn = split(wfn)
250 250 d = self.tree.dir(root)
251 251
252 252 oldstatus = d.files.get(fn)
253 253 # oldstatus can be either:
254 254 # - None : fn is new
255 255 # - a char in statuskeys: fn is a (tracked) file
256 256
257 257 if self.ui.debugflag and oldstatus != newstatus:
258 258 self.ui.note(_('status: %r %s -> %s\n') %
259 259 (wfn, oldstatus, newstatus))
260 260
261 261 if oldstatus and oldstatus in self.statuskeys \
262 262 and oldstatus != newstatus:
263 263 del self.statustrees[oldstatus].dir(root).files[fn]
264 264
265 265 if newstatus in (None, 'i'):
266 266 d.files.pop(fn, None)
267 267 elif oldstatus != newstatus:
268 268 d.files[fn] = newstatus
269 269 if newstatus != 'n':
270 270 self.statustrees[newstatus].dir(root).files[fn] = newstatus
271 271
272 272 def check_deleted(self, key):
273 273 # Files that had been deleted but were present in the dirstate
274 274 # may have vanished from the dirstate; we must clean them up.
275 275 nuke = []
276 276 for wfn, ignore in self.statustrees[key].walk(key):
277 277 if wfn not in self.dirstate:
278 278 nuke.append(wfn)
279 279 for wfn in nuke:
280 280 root, fn = split(wfn)
281 281 del self.statustrees[key].dir(root).files[fn]
282 282 del self.tree.dir(root).files[fn]
283 283
284 284 def update_hgignore(self):
285 285 # An update of the ignore file can potentially change the
286 286 # states of all unknown and ignored files.
287 287
288 288 # XXX If the user has other ignore files outside the repo, or
289 289 # changes their list of ignore files at run time, we'll
290 290 # potentially never see changes to them. We could get the
291 291 # client to report to us what ignore data they're using.
292 292 # But it's easier to do nothing than to open that can of
293 293 # worms.
294 294
295 295 if '_ignore' in self.dirstate.__dict__:
296 296 delattr(self.dirstate, '_ignore')
297 297 self.ui.note(_('rescanning due to .hgignore change\n'))
298 298 self.handle_timeout()
299 299 self.scan()
300 300
301 301 def getstat(self, wpath):
302 302 try:
303 303 return self.statcache[wpath]
304 304 except KeyError:
305 305 try:
306 306 return self.stat(wpath)
307 307 except OSError, err:
308 308 if err.errno != errno.ENOENT:
309 309 raise
310 310
311 311 def stat(self, wpath):
312 312 try:
313 313 st = os.lstat(join(self.wprefix, wpath))
314 314 ret = st.st_mode, st.st_size, st.st_mtime
315 315 self.statcache[wpath] = ret
316 316 return ret
317 317 except OSError:
318 318 self.statcache.pop(wpath, None)
319 319 raise
320 320
321 321 class socketlistener(object):
322 322 """
323 323 Listens for client queries on unix socket inotify.sock
324 324 """
325 325 def __init__(self, ui, root, repowatcher, timeout):
326 326 self.ui = ui
327 327 self.repowatcher = repowatcher
328 328 self.sock = socket.socket(socket.AF_UNIX)
329 329 self.sockpath = join(root, '.hg/inotify.sock')
330 330 self.realsockpath = None
331 331 try:
332 332 self.sock.bind(self.sockpath)
333 333 except socket.error, err:
334 334 if err[0] == errno.EADDRINUSE:
335 335 raise AlreadyStartedException( _('cannot start: socket is '
336 336 'already bound'))
337 337 if err[0] == "AF_UNIX path too long":
338 338 if os.path.islink(self.sockpath) and \
339 339 not os.path.exists(self.sockpath):
340 340 raise util.Abort('inotify-server: cannot start: '
341 341 '.hg/inotify.sock is a broken symlink')
342 342 tempdir = tempfile.mkdtemp(prefix="hg-inotify-")
343 343 self.realsockpath = os.path.join(tempdir, "inotify.sock")
344 344 try:
345 345 self.sock.bind(self.realsockpath)
346 346 os.symlink(self.realsockpath, self.sockpath)
347 347 except (OSError, socket.error), inst:
348 348 try:
349 349 os.unlink(self.realsockpath)
350 350 except:
351 351 pass
352 352 os.rmdir(tempdir)
353 353 if inst.errno == errno.EEXIST:
354 354 raise AlreadyStartedException(_('cannot start: tried '
355 355 'linking .hg/inotify.sock to a temporary socket but'
356 356 ' .hg/inotify.sock already exists'))
357 357 raise
358 358 else:
359 359 raise
360 360 self.sock.listen(5)
361 361 self.fileno = self.sock.fileno
362 362
363 363 def answer_stat_query(self, cs):
364 364 names = cs.read().split('\0')
365 365
366 366 states = names.pop()
367 367
368 368 self.ui.note(_('answering query for %r\n') % states)
369 369
370 370 visited = set()
371 371 if not names:
372 372 def genresult(states, tree):
373 373 for fn, state in tree.walk(states):
374 374 yield fn
375 375 else:
376 376 def genresult(states, tree):
377 377 for fn in names:
378 378 for f in tree.lookup(states, fn, visited):
379 379 yield f
380 380
381 381 return ['\0'.join(r) for r in [
382 382 genresult('l', self.repowatcher.statustrees['l']),
383 383 genresult('m', self.repowatcher.statustrees['m']),
384 384 genresult('a', self.repowatcher.statustrees['a']),
385 385 genresult('r', self.repowatcher.statustrees['r']),
386 386 genresult('!', self.repowatcher.statustrees['!']),
387 387 '?' in states
388 388 and genresult('?', self.repowatcher.statustrees['?'])
389 389 or [],
390 390 [],
391 391 'c' in states and genresult('n', self.repowatcher.tree) or [],
392 392 visited
393 393 ]]
394 394
395 395 def answer_dbug_query(self):
396 396 return ['\0'.join(self.repowatcher.debug())]
397 397
398 398 def accept_connection(self):
399 399 sock, addr = self.sock.accept()
400 400
401 401 cs = common.recvcs(sock)
402 402 version = ord(cs.read(1))
403 403
404 404 if version != common.version:
405 405 self.ui.warn(_('received query from incompatible client '
406 406 'version %d\n') % version)
407 407 try:
408 408 # try to send back our version to the client
409 409 # this way, the client too is informed of the mismatch
410 410 sock.sendall(chr(common.version))
411 411 except:
412 412 pass
413 413 return
414 414
415 415 type = cs.read(4)
416 416
417 417 if type == 'STAT':
418 418 results = self.answer_stat_query(cs)
419 419 elif type == 'DBUG':
420 420 results = self.answer_dbug_query()
421 421 else:
422 422 self.ui.warn(_('unrecognized query type: %s\n') % type)
423 423 return
424 424
425 425 try:
426 426 try:
427 427 v = chr(common.version)
428 428
429 429 sock.sendall(v + type + struct.pack(common.resphdrfmts[type],
430 430 *map(len, results)))
431 431 sock.sendall(''.join(results))
432 432 finally:
433 433 sock.shutdown(socket.SHUT_WR)
434 434 except socket.error, err:
435 435 if err[0] != errno.EPIPE:
436 436 raise
437 437
438 438 if sys.platform == 'linux2':
439 439 import linuxserver as _server
440 440 else:
441 441 raise ImportError
442 442
443 443 master = _server.master
444 444
445 445 def start(ui, dirstate, root, opts):
446 446 timeout = opts.get('timeout')
447 447 if timeout:
448 448 timeout = float(timeout) * 1e3
449 449
450 450 class service(object):
451 451 def init(self):
452 452 try:
453 453 self.master = master(ui, dirstate, root, timeout)
454 454 except AlreadyStartedException, inst:
455 455 raise util.Abort("inotify-server: %s" % inst)
456 456
457 457 def run(self):
458 458 try:
459 459 self.master.run()
460 460 finally:
461 461 self.master.shutdown()
462 462
463 463 if 'inserve' not in sys.argv:
464 runargs = [sys.argv[0], 'inserve', '-R', root]
464 runargs = util.hgcmd() + ['inserve', '-R', root]
465 465 else:
466 runargs = sys.argv[:]
466 runargs = util.hgcmd() + sys.argv[1:]
467 467
468 468 pidfile = ui.config('inotify', 'pidfile')
469 469 if opts['daemon'] and pidfile is not None and 'pid-file' not in runargs:
470 470 runargs.append("--pid-file=%s" % pidfile)
471 471
472 472 service = service()
473 473 logfile = ui.config('inotify', 'log')
474 474
475 475 appendpid = ui.configbool('inotify', 'appendpid', False)
476 476
477 477 cmdutil.service(opts, initfn=service.init, runfn=service.run,
478 478 logfile=logfile, runargs=runargs, appendpid=appendpid)
@@ -1,1172 +1,1172 b''
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 from node import hex, nullid, nullrev, short
9 9 from i18n import _
10 10 import os, sys, errno, re, glob, tempfile, time
11 11 import mdiff, bdiff, util, templater, patch, error, encoding, templatekw
12 12 import match as _match
13 13
14 14 revrangesep = ':'
15 15
16 16 def findpossible(cmd, table, strict=False):
17 17 """
18 18 Return cmd -> (aliases, command table entry)
19 19 for each matching command.
20 20 Return debug commands (or their aliases) only if no normal command matches.
21 21 """
22 22 choice = {}
23 23 debugchoice = {}
24 24 for e in table.keys():
25 25 aliases = e.lstrip("^").split("|")
26 26 found = None
27 27 if cmd in aliases:
28 28 found = cmd
29 29 elif not strict:
30 30 for a in aliases:
31 31 if a.startswith(cmd):
32 32 found = a
33 33 break
34 34 if found is not None:
35 35 if aliases[0].startswith("debug") or found.startswith("debug"):
36 36 debugchoice[found] = (aliases, table[e])
37 37 else:
38 38 choice[found] = (aliases, table[e])
39 39
40 40 if not choice and debugchoice:
41 41 choice = debugchoice
42 42
43 43 return choice
44 44
45 45 def findcmd(cmd, table, strict=True):
46 46 """Return (aliases, command table entry) for command string."""
47 47 choice = findpossible(cmd, table, strict)
48 48
49 49 if cmd in choice:
50 50 return choice[cmd]
51 51
52 52 if len(choice) > 1:
53 53 clist = choice.keys()
54 54 clist.sort()
55 55 raise error.AmbiguousCommand(cmd, clist)
56 56
57 57 if choice:
58 58 return choice.values()[0]
59 59
60 60 raise error.UnknownCommand(cmd)
61 61
62 62 def bail_if_changed(repo):
63 63 if repo.dirstate.parents()[1] != nullid:
64 64 raise util.Abort(_('outstanding uncommitted merge'))
65 65 modified, added, removed, deleted = repo.status()[:4]
66 66 if modified or added or removed or deleted:
67 67 raise util.Abort(_("outstanding uncommitted changes"))
68 68
69 69 def logmessage(opts):
70 70 """ get the log message according to -m and -l option """
71 71 message = opts.get('message')
72 72 logfile = opts.get('logfile')
73 73
74 74 if message and logfile:
75 75 raise util.Abort(_('options --message and --logfile are mutually '
76 76 'exclusive'))
77 77 if not message and logfile:
78 78 try:
79 79 if logfile == '-':
80 80 message = sys.stdin.read()
81 81 else:
82 82 message = open(logfile).read()
83 83 except IOError, inst:
84 84 raise util.Abort(_("can't read commit message '%s': %s") %
85 85 (logfile, inst.strerror))
86 86 return message
87 87
88 88 def loglimit(opts):
89 89 """get the log limit according to option -l/--limit"""
90 90 limit = opts.get('limit')
91 91 if limit:
92 92 try:
93 93 limit = int(limit)
94 94 except ValueError:
95 95 raise util.Abort(_('limit must be a positive integer'))
96 96 if limit <= 0: raise util.Abort(_('limit must be positive'))
97 97 else:
98 98 limit = None
99 99 return limit
100 100
101 101 def remoteui(src, opts):
102 102 'build a remote ui from ui or repo and opts'
103 103 if hasattr(src, 'baseui'): # looks like a repository
104 104 dst = src.baseui.copy() # drop repo-specific config
105 105 src = src.ui # copy target options from repo
106 106 else: # assume it's a global ui object
107 107 dst = src.copy() # keep all global options
108 108
109 109 # copy ssh-specific options
110 110 for o in 'ssh', 'remotecmd':
111 111 v = opts.get(o) or src.config('ui', o)
112 112 if v:
113 113 dst.setconfig("ui", o, v)
114 114
115 115 # copy bundle-specific options
116 116 r = src.config('bundle', 'mainreporoot')
117 117 if r:
118 118 dst.setconfig('bundle', 'mainreporoot', r)
119 119
120 120 # copy auth section settings
121 121 for key, val in src.configitems('auth'):
122 122 dst.setconfig('auth', key, val)
123 123
124 124 return dst
125 125
126 126 def revpair(repo, revs):
127 127 '''return pair of nodes, given list of revisions. second item can
128 128 be None, meaning use working dir.'''
129 129
130 130 def revfix(repo, val, defval):
131 131 if not val and val != 0 and defval is not None:
132 132 val = defval
133 133 return repo.lookup(val)
134 134
135 135 if not revs:
136 136 return repo.dirstate.parents()[0], None
137 137 end = None
138 138 if len(revs) == 1:
139 139 if revrangesep in revs[0]:
140 140 start, end = revs[0].split(revrangesep, 1)
141 141 start = revfix(repo, start, 0)
142 142 end = revfix(repo, end, len(repo) - 1)
143 143 else:
144 144 start = revfix(repo, revs[0], None)
145 145 elif len(revs) == 2:
146 146 if revrangesep in revs[0] or revrangesep in revs[1]:
147 147 raise util.Abort(_('too many revisions specified'))
148 148 start = revfix(repo, revs[0], None)
149 149 end = revfix(repo, revs[1], None)
150 150 else:
151 151 raise util.Abort(_('too many revisions specified'))
152 152 return start, end
153 153
154 154 def revrange(repo, revs):
155 155 """Yield revision as strings from a list of revision specifications."""
156 156
157 157 def revfix(repo, val, defval):
158 158 if not val and val != 0 and defval is not None:
159 159 return defval
160 160 return repo.changelog.rev(repo.lookup(val))
161 161
162 162 seen, l = set(), []
163 163 for spec in revs:
164 164 if revrangesep in spec:
165 165 start, end = spec.split(revrangesep, 1)
166 166 start = revfix(repo, start, 0)
167 167 end = revfix(repo, end, len(repo) - 1)
168 168 step = start > end and -1 or 1
169 169 for rev in xrange(start, end+step, step):
170 170 if rev in seen:
171 171 continue
172 172 seen.add(rev)
173 173 l.append(rev)
174 174 else:
175 175 rev = revfix(repo, spec, None)
176 176 if rev in seen:
177 177 continue
178 178 seen.add(rev)
179 179 l.append(rev)
180 180
181 181 return l
182 182
183 183 def make_filename(repo, pat, node,
184 184 total=None, seqno=None, revwidth=None, pathname=None):
185 185 node_expander = {
186 186 'H': lambda: hex(node),
187 187 'R': lambda: str(repo.changelog.rev(node)),
188 188 'h': lambda: short(node),
189 189 }
190 190 expander = {
191 191 '%': lambda: '%',
192 192 'b': lambda: os.path.basename(repo.root),
193 193 }
194 194
195 195 try:
196 196 if node:
197 197 expander.update(node_expander)
198 198 if node:
199 199 expander['r'] = (lambda:
200 200 str(repo.changelog.rev(node)).zfill(revwidth or 0))
201 201 if total is not None:
202 202 expander['N'] = lambda: str(total)
203 203 if seqno is not None:
204 204 expander['n'] = lambda: str(seqno)
205 205 if total is not None and seqno is not None:
206 206 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
207 207 if pathname is not None:
208 208 expander['s'] = lambda: os.path.basename(pathname)
209 209 expander['d'] = lambda: os.path.dirname(pathname) or '.'
210 210 expander['p'] = lambda: pathname
211 211
212 212 newname = []
213 213 patlen = len(pat)
214 214 i = 0
215 215 while i < patlen:
216 216 c = pat[i]
217 217 if c == '%':
218 218 i += 1
219 219 c = pat[i]
220 220 c = expander[c]()
221 221 newname.append(c)
222 222 i += 1
223 223 return ''.join(newname)
224 224 except KeyError, inst:
225 225 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
226 226 inst.args[0])
227 227
228 228 def make_file(repo, pat, node=None,
229 229 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
230 230
231 231 writable = 'w' in mode or 'a' in mode
232 232
233 233 if not pat or pat == '-':
234 234 return writable and sys.stdout or sys.stdin
235 235 if hasattr(pat, 'write') and writable:
236 236 return pat
237 237 if hasattr(pat, 'read') and 'r' in mode:
238 238 return pat
239 239 return open(make_filename(repo, pat, node, total, seqno, revwidth,
240 240 pathname),
241 241 mode)
242 242
243 243 def expandpats(pats):
244 244 if not util.expandglobs:
245 245 return list(pats)
246 246 ret = []
247 247 for p in pats:
248 248 kind, name = _match._patsplit(p, None)
249 249 if kind is None:
250 250 try:
251 251 globbed = glob.glob(name)
252 252 except re.error:
253 253 globbed = [name]
254 254 if globbed:
255 255 ret.extend(globbed)
256 256 continue
257 257 ret.append(p)
258 258 return ret
259 259
260 260 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
261 261 if not globbed and default == 'relpath':
262 262 pats = expandpats(pats or [])
263 263 m = _match.match(repo.root, repo.getcwd(), pats,
264 264 opts.get('include'), opts.get('exclude'), default)
265 265 def badfn(f, msg):
266 266 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
267 267 m.bad = badfn
268 268 return m
269 269
270 270 def matchall(repo):
271 271 return _match.always(repo.root, repo.getcwd())
272 272
273 273 def matchfiles(repo, files):
274 274 return _match.exact(repo.root, repo.getcwd(), files)
275 275
276 276 def findrenames(repo, added, removed, threshold):
277 277 '''find renamed files -- yields (before, after, score) tuples'''
278 278 copies = {}
279 279 ctx = repo['.']
280 280 for r in removed:
281 281 if r not in ctx:
282 282 continue
283 283 fctx = ctx.filectx(r)
284 284
285 285 def score(text):
286 286 if not len(text):
287 287 return 0.0
288 288 if not fctx.cmp(text):
289 289 return 1.0
290 290 if threshold == 1.0:
291 291 return 0.0
292 292 orig = fctx.data()
293 293 # bdiff.blocks() returns blocks of matching lines
294 294 # count the number of bytes in each
295 295 equal = 0
296 296 alines = mdiff.splitnewlines(text)
297 297 matches = bdiff.blocks(text, orig)
298 298 for x1, x2, y1, y2 in matches:
299 299 for line in alines[x1:x2]:
300 300 equal += len(line)
301 301
302 302 lengths = len(text) + len(orig)
303 303 return equal * 2.0 / lengths
304 304
305 305 for a in added:
306 306 bestscore = copies.get(a, (None, threshold))[1]
307 307 myscore = score(repo.wread(a))
308 308 if myscore >= bestscore:
309 309 copies[a] = (r, myscore)
310 310
311 311 for dest, v in copies.iteritems():
312 312 source, score = v
313 313 yield source, dest, score
314 314
315 315 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
316 316 if dry_run is None:
317 317 dry_run = opts.get('dry_run')
318 318 if similarity is None:
319 319 similarity = float(opts.get('similarity') or 0)
320 320 # we'd use status here, except handling of symlinks and ignore is tricky
321 321 added, unknown, deleted, removed = [], [], [], []
322 322 audit_path = util.path_auditor(repo.root)
323 323 m = match(repo, pats, opts)
324 324 for abs in repo.walk(m):
325 325 target = repo.wjoin(abs)
326 326 good = True
327 327 try:
328 328 audit_path(abs)
329 329 except:
330 330 good = False
331 331 rel = m.rel(abs)
332 332 exact = m.exact(abs)
333 333 if good and abs not in repo.dirstate:
334 334 unknown.append(abs)
335 335 if repo.ui.verbose or not exact:
336 336 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
337 337 elif repo.dirstate[abs] != 'r' and (not good or not util.lexists(target)
338 338 or (os.path.isdir(target) and not os.path.islink(target))):
339 339 deleted.append(abs)
340 340 if repo.ui.verbose or not exact:
341 341 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
342 342 # for finding renames
343 343 elif repo.dirstate[abs] == 'r':
344 344 removed.append(abs)
345 345 elif repo.dirstate[abs] == 'a':
346 346 added.append(abs)
347 347 if not dry_run:
348 348 repo.remove(deleted)
349 349 repo.add(unknown)
350 350 if similarity > 0:
351 351 for old, new, score in findrenames(repo, added + unknown,
352 352 removed + deleted, similarity):
353 353 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
354 354 repo.ui.status(_('recording removal of %s as rename to %s '
355 355 '(%d%% similar)\n') %
356 356 (m.rel(old), m.rel(new), score * 100))
357 357 if not dry_run:
358 358 repo.copy(old, new)
359 359
360 360 def copy(ui, repo, pats, opts, rename=False):
361 361 # called with the repo lock held
362 362 #
363 363 # hgsep => pathname that uses "/" to separate directories
364 364 # ossep => pathname that uses os.sep to separate directories
365 365 cwd = repo.getcwd()
366 366 targets = {}
367 367 after = opts.get("after")
368 368 dryrun = opts.get("dry_run")
369 369
370 370 def walkpat(pat):
371 371 srcs = []
372 372 m = match(repo, [pat], opts, globbed=True)
373 373 for abs in repo.walk(m):
374 374 state = repo.dirstate[abs]
375 375 rel = m.rel(abs)
376 376 exact = m.exact(abs)
377 377 if state in '?r':
378 378 if exact and state == '?':
379 379 ui.warn(_('%s: not copying - file is not managed\n') % rel)
380 380 if exact and state == 'r':
381 381 ui.warn(_('%s: not copying - file has been marked for'
382 382 ' remove\n') % rel)
383 383 continue
384 384 # abs: hgsep
385 385 # rel: ossep
386 386 srcs.append((abs, rel, exact))
387 387 return srcs
388 388
389 389 # abssrc: hgsep
390 390 # relsrc: ossep
391 391 # otarget: ossep
392 392 def copyfile(abssrc, relsrc, otarget, exact):
393 393 abstarget = util.canonpath(repo.root, cwd, otarget)
394 394 reltarget = repo.pathto(abstarget, cwd)
395 395 target = repo.wjoin(abstarget)
396 396 src = repo.wjoin(abssrc)
397 397 state = repo.dirstate[abstarget]
398 398
399 399 # check for collisions
400 400 prevsrc = targets.get(abstarget)
401 401 if prevsrc is not None:
402 402 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
403 403 (reltarget, repo.pathto(abssrc, cwd),
404 404 repo.pathto(prevsrc, cwd)))
405 405 return
406 406
407 407 # check for overwrites
408 408 exists = os.path.exists(target)
409 409 if not after and exists or after and state in 'mn':
410 410 if not opts['force']:
411 411 ui.warn(_('%s: not overwriting - file exists\n') %
412 412 reltarget)
413 413 return
414 414
415 415 if after:
416 416 if not exists:
417 417 return
418 418 elif not dryrun:
419 419 try:
420 420 if exists:
421 421 os.unlink(target)
422 422 targetdir = os.path.dirname(target) or '.'
423 423 if not os.path.isdir(targetdir):
424 424 os.makedirs(targetdir)
425 425 util.copyfile(src, target)
426 426 except IOError, inst:
427 427 if inst.errno == errno.ENOENT:
428 428 ui.warn(_('%s: deleted in working copy\n') % relsrc)
429 429 else:
430 430 ui.warn(_('%s: cannot copy - %s\n') %
431 431 (relsrc, inst.strerror))
432 432 return True # report a failure
433 433
434 434 if ui.verbose or not exact:
435 435 if rename:
436 436 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
437 437 else:
438 438 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
439 439
440 440 targets[abstarget] = abssrc
441 441
442 442 # fix up dirstate
443 443 origsrc = repo.dirstate.copied(abssrc) or abssrc
444 444 if abstarget == origsrc: # copying back a copy?
445 445 if state not in 'mn' and not dryrun:
446 446 repo.dirstate.normallookup(abstarget)
447 447 else:
448 448 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
449 449 if not ui.quiet:
450 450 ui.warn(_("%s has not been committed yet, so no copy "
451 451 "data will be stored for %s.\n")
452 452 % (repo.pathto(origsrc, cwd), reltarget))
453 453 if repo.dirstate[abstarget] in '?r' and not dryrun:
454 454 repo.add([abstarget])
455 455 elif not dryrun:
456 456 repo.copy(origsrc, abstarget)
457 457
458 458 if rename and not dryrun:
459 459 repo.remove([abssrc], not after)
460 460
461 461 # pat: ossep
462 462 # dest ossep
463 463 # srcs: list of (hgsep, hgsep, ossep, bool)
464 464 # return: function that takes hgsep and returns ossep
465 465 def targetpathfn(pat, dest, srcs):
466 466 if os.path.isdir(pat):
467 467 abspfx = util.canonpath(repo.root, cwd, pat)
468 468 abspfx = util.localpath(abspfx)
469 469 if destdirexists:
470 470 striplen = len(os.path.split(abspfx)[0])
471 471 else:
472 472 striplen = len(abspfx)
473 473 if striplen:
474 474 striplen += len(os.sep)
475 475 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
476 476 elif destdirexists:
477 477 res = lambda p: os.path.join(dest,
478 478 os.path.basename(util.localpath(p)))
479 479 else:
480 480 res = lambda p: dest
481 481 return res
482 482
483 483 # pat: ossep
484 484 # dest ossep
485 485 # srcs: list of (hgsep, hgsep, ossep, bool)
486 486 # return: function that takes hgsep and returns ossep
487 487 def targetpathafterfn(pat, dest, srcs):
488 488 if _match.patkind(pat):
489 489 # a mercurial pattern
490 490 res = lambda p: os.path.join(dest,
491 491 os.path.basename(util.localpath(p)))
492 492 else:
493 493 abspfx = util.canonpath(repo.root, cwd, pat)
494 494 if len(abspfx) < len(srcs[0][0]):
495 495 # A directory. Either the target path contains the last
496 496 # component of the source path or it does not.
497 497 def evalpath(striplen):
498 498 score = 0
499 499 for s in srcs:
500 500 t = os.path.join(dest, util.localpath(s[0])[striplen:])
501 501 if os.path.exists(t):
502 502 score += 1
503 503 return score
504 504
505 505 abspfx = util.localpath(abspfx)
506 506 striplen = len(abspfx)
507 507 if striplen:
508 508 striplen += len(os.sep)
509 509 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
510 510 score = evalpath(striplen)
511 511 striplen1 = len(os.path.split(abspfx)[0])
512 512 if striplen1:
513 513 striplen1 += len(os.sep)
514 514 if evalpath(striplen1) > score:
515 515 striplen = striplen1
516 516 res = lambda p: os.path.join(dest,
517 517 util.localpath(p)[striplen:])
518 518 else:
519 519 # a file
520 520 if destdirexists:
521 521 res = lambda p: os.path.join(dest,
522 522 os.path.basename(util.localpath(p)))
523 523 else:
524 524 res = lambda p: dest
525 525 return res
526 526
527 527
528 528 pats = expandpats(pats)
529 529 if not pats:
530 530 raise util.Abort(_('no source or destination specified'))
531 531 if len(pats) == 1:
532 532 raise util.Abort(_('no destination specified'))
533 533 dest = pats.pop()
534 534 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
535 535 if not destdirexists:
536 536 if len(pats) > 1 or _match.patkind(pats[0]):
537 537 raise util.Abort(_('with multiple sources, destination must be an '
538 538 'existing directory'))
539 539 if util.endswithsep(dest):
540 540 raise util.Abort(_('destination %s is not a directory') % dest)
541 541
542 542 tfn = targetpathfn
543 543 if after:
544 544 tfn = targetpathafterfn
545 545 copylist = []
546 546 for pat in pats:
547 547 srcs = walkpat(pat)
548 548 if not srcs:
549 549 continue
550 550 copylist.append((tfn(pat, dest, srcs), srcs))
551 551 if not copylist:
552 552 raise util.Abort(_('no files to copy'))
553 553
554 554 errors = 0
555 555 for targetpath, srcs in copylist:
556 556 for abssrc, relsrc, exact in srcs:
557 557 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
558 558 errors += 1
559 559
560 560 if errors:
561 561 ui.warn(_('(consider using --after)\n'))
562 562
563 563 return errors
564 564
565 565 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
566 566 runargs=None, appendpid=False):
567 567 '''Run a command as a service.'''
568 568
569 569 if opts['daemon'] and not opts['daemon_pipefds']:
570 570 # Signal child process startup with file removal
571 571 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
572 572 os.close(lockfd)
573 573 try:
574 574 if not runargs:
575 runargs = sys.argv[:]
575 runargs = util.hgcmd() + sys.argv[1:]
576 576 runargs.append('--daemon-pipefds=%s' % lockpath)
577 577 # Don't pass --cwd to the child process, because we've already
578 578 # changed directory.
579 579 for i in xrange(1,len(runargs)):
580 580 if runargs[i].startswith('--cwd='):
581 581 del runargs[i]
582 582 break
583 583 elif runargs[i].startswith('--cwd'):
584 584 del runargs[i:i+2]
585 585 break
586 586 pid = util.spawndetached(runargs)
587 587 while os.path.exists(lockpath):
588 588 time.sleep(0.1)
589 589 finally:
590 590 try:
591 591 os.unlink(lockpath)
592 592 except OSError, e:
593 593 if e.errno != errno.ENOENT:
594 594 raise
595 595 if parentfn:
596 596 return parentfn(pid)
597 597 else:
598 598 return
599 599
600 600 if initfn:
601 601 initfn()
602 602
603 603 if opts['pid_file']:
604 604 mode = appendpid and 'a' or 'w'
605 605 fp = open(opts['pid_file'], mode)
606 606 fp.write(str(os.getpid()) + '\n')
607 607 fp.close()
608 608
609 609 if opts['daemon_pipefds']:
610 610 lockpath = opts['daemon_pipefds']
611 611 try:
612 612 os.setsid()
613 613 except AttributeError:
614 614 pass
615 615 os.unlink(lockpath)
616 616 sys.stdout.flush()
617 617 sys.stderr.flush()
618 618
619 619 nullfd = os.open(util.nulldev, os.O_RDWR)
620 620 logfilefd = nullfd
621 621 if logfile:
622 622 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
623 623 os.dup2(nullfd, 0)
624 624 os.dup2(logfilefd, 1)
625 625 os.dup2(logfilefd, 2)
626 626 if nullfd not in (0, 1, 2):
627 627 os.close(nullfd)
628 628 if logfile and logfilefd not in (0, 1, 2):
629 629 os.close(logfilefd)
630 630
631 631 if runfn:
632 632 return runfn()
633 633
634 634 class changeset_printer(object):
635 635 '''show changeset information when templating not requested.'''
636 636
637 637 def __init__(self, ui, repo, patch, diffopts, buffered):
638 638 self.ui = ui
639 639 self.repo = repo
640 640 self.buffered = buffered
641 641 self.patch = patch
642 642 self.diffopts = diffopts
643 643 self.header = {}
644 644 self.hunk = {}
645 645 self.lastheader = None
646 646 self.footer = None
647 647
648 648 def flush(self, rev):
649 649 if rev in self.header:
650 650 h = self.header[rev]
651 651 if h != self.lastheader:
652 652 self.lastheader = h
653 653 self.ui.write(h)
654 654 del self.header[rev]
655 655 if rev in self.hunk:
656 656 self.ui.write(self.hunk[rev])
657 657 del self.hunk[rev]
658 658 return 1
659 659 return 0
660 660
661 661 def close(self):
662 662 if self.footer:
663 663 self.ui.write(self.footer)
664 664
665 665 def show(self, ctx, copies=None, **props):
666 666 if self.buffered:
667 667 self.ui.pushbuffer()
668 668 self._show(ctx, copies, props)
669 669 self.hunk[ctx.rev()] = self.ui.popbuffer()
670 670 else:
671 671 self._show(ctx, copies, props)
672 672
673 673 def _show(self, ctx, copies, props):
674 674 '''show a single changeset or file revision'''
675 675 changenode = ctx.node()
676 676 rev = ctx.rev()
677 677
678 678 if self.ui.quiet:
679 679 self.ui.write("%d:%s\n" % (rev, short(changenode)))
680 680 return
681 681
682 682 log = self.repo.changelog
683 683 date = util.datestr(ctx.date())
684 684
685 685 hexfunc = self.ui.debugflag and hex or short
686 686
687 687 parents = [(p, hexfunc(log.node(p)))
688 688 for p in self._meaningful_parentrevs(log, rev)]
689 689
690 690 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
691 691
692 692 branch = ctx.branch()
693 693 # don't show the default branch name
694 694 if branch != 'default':
695 695 branch = encoding.tolocal(branch)
696 696 self.ui.write(_("branch: %s\n") % branch)
697 697 for tag in self.repo.nodetags(changenode):
698 698 self.ui.write(_("tag: %s\n") % tag)
699 699 for parent in parents:
700 700 self.ui.write(_("parent: %d:%s\n") % parent)
701 701
702 702 if self.ui.debugflag:
703 703 mnode = ctx.manifestnode()
704 704 self.ui.write(_("manifest: %d:%s\n") %
705 705 (self.repo.manifest.rev(mnode), hex(mnode)))
706 706 self.ui.write(_("user: %s\n") % ctx.user())
707 707 self.ui.write(_("date: %s\n") % date)
708 708
709 709 if self.ui.debugflag:
710 710 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
711 711 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
712 712 files):
713 713 if value:
714 714 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
715 715 elif ctx.files() and self.ui.verbose:
716 716 self.ui.write(_("files: %s\n") % " ".join(ctx.files()))
717 717 if copies and self.ui.verbose:
718 718 copies = ['%s (%s)' % c for c in copies]
719 719 self.ui.write(_("copies: %s\n") % ' '.join(copies))
720 720
721 721 extra = ctx.extra()
722 722 if extra and self.ui.debugflag:
723 723 for key, value in sorted(extra.items()):
724 724 self.ui.write(_("extra: %s=%s\n")
725 725 % (key, value.encode('string_escape')))
726 726
727 727 description = ctx.description().strip()
728 728 if description:
729 729 if self.ui.verbose:
730 730 self.ui.write(_("description:\n"))
731 731 self.ui.write(description)
732 732 self.ui.write("\n\n")
733 733 else:
734 734 self.ui.write(_("summary: %s\n") %
735 735 description.splitlines()[0])
736 736 self.ui.write("\n")
737 737
738 738 self.showpatch(changenode)
739 739
740 740 def showpatch(self, node):
741 741 if self.patch:
742 742 prev = self.repo.changelog.parents(node)[0]
743 743 chunks = patch.diff(self.repo, prev, node, match=self.patch,
744 744 opts=patch.diffopts(self.ui, self.diffopts))
745 745 for chunk in chunks:
746 746 self.ui.write(chunk)
747 747 self.ui.write("\n")
748 748
749 749 def _meaningful_parentrevs(self, log, rev):
750 750 """Return list of meaningful (or all if debug) parentrevs for rev.
751 751
752 752 For merges (two non-nullrev revisions) both parents are meaningful.
753 753 Otherwise the first parent revision is considered meaningful if it
754 754 is not the preceding revision.
755 755 """
756 756 parents = log.parentrevs(rev)
757 757 if not self.ui.debugflag and parents[1] == nullrev:
758 758 if parents[0] >= rev - 1:
759 759 parents = []
760 760 else:
761 761 parents = [parents[0]]
762 762 return parents
763 763
764 764
765 765 class changeset_templater(changeset_printer):
766 766 '''format changeset information.'''
767 767
768 768 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
769 769 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
770 770 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
771 771 defaulttempl = {
772 772 'parent': '{rev}:{node|formatnode} ',
773 773 'manifest': '{rev}:{node|formatnode}',
774 774 'file_copy': '{name} ({source})',
775 775 'extra': '{key}={value|stringescape}'
776 776 }
777 777 # filecopy is preserved for compatibility reasons
778 778 defaulttempl['filecopy'] = defaulttempl['file_copy']
779 779 self.t = templater.templater(mapfile, {'formatnode': formatnode},
780 780 cache=defaulttempl)
781 781 self.cache = {}
782 782
783 783 def use_template(self, t):
784 784 '''set template string to use'''
785 785 self.t.cache['changeset'] = t
786 786
787 787 def _meaningful_parentrevs(self, ctx):
788 788 """Return list of meaningful (or all if debug) parentrevs for rev.
789 789 """
790 790 parents = ctx.parents()
791 791 if len(parents) > 1:
792 792 return parents
793 793 if self.ui.debugflag:
794 794 return [parents[0], self.repo['null']]
795 795 if parents[0].rev() >= ctx.rev() - 1:
796 796 return []
797 797 return parents
798 798
799 799 def _show(self, ctx, copies, props):
800 800 '''show a single changeset or file revision'''
801 801
802 802 showlist = templatekw.showlist
803 803
804 804 # showparents() behaviour depends on ui trace level which
805 805 # causes unexpected behaviours at templating level and makes
806 806 # it harder to extract it in a standalone function. Its
807 807 # behaviour cannot be changed so leave it here for now.
808 808 def showparents(repo, ctx, templ, **args):
809 809 parents = [[('rev', p.rev()), ('node', p.hex())]
810 810 for p in self._meaningful_parentrevs(ctx)]
811 811 return showlist(templ, 'parent', parents, **args)
812 812
813 813 props = props.copy()
814 814 props.update(templatekw.keywords)
815 815 props['parents'] = showparents
816 816 props['templ'] = self.t
817 817 props['ctx'] = ctx
818 818 props['repo'] = self.repo
819 819 props['revcache'] = {'copies': copies}
820 820 props['cache'] = self.cache
821 821
822 822 # find correct templates for current mode
823 823
824 824 tmplmodes = [
825 825 (True, None),
826 826 (self.ui.verbose, 'verbose'),
827 827 (self.ui.quiet, 'quiet'),
828 828 (self.ui.debugflag, 'debug'),
829 829 ]
830 830
831 831 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
832 832 for mode, postfix in tmplmodes:
833 833 for type in types:
834 834 cur = postfix and ('%s_%s' % (type, postfix)) or type
835 835 if mode and cur in self.t:
836 836 types[type] = cur
837 837
838 838 try:
839 839
840 840 # write header
841 841 if types['header']:
842 842 h = templater.stringify(self.t(types['header'], **props))
843 843 if self.buffered:
844 844 self.header[ctx.rev()] = h
845 845 else:
846 846 self.ui.write(h)
847 847
848 848 # write changeset metadata, then patch if requested
849 849 key = types['changeset']
850 850 self.ui.write(templater.stringify(self.t(key, **props)))
851 851 self.showpatch(ctx.node())
852 852
853 853 if types['footer']:
854 854 if not self.footer:
855 855 self.footer = templater.stringify(self.t(types['footer'],
856 856 **props))
857 857
858 858 except KeyError, inst:
859 859 msg = _("%s: no key named '%s'")
860 860 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
861 861 except SyntaxError, inst:
862 862 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
863 863
864 864 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
865 865 """show one changeset using template or regular display.
866 866
867 867 Display format will be the first non-empty hit of:
868 868 1. option 'template'
869 869 2. option 'style'
870 870 3. [ui] setting 'logtemplate'
871 871 4. [ui] setting 'style'
872 872 If all of these values are either the unset or the empty string,
873 873 regular display via changeset_printer() is done.
874 874 """
875 875 # options
876 876 patch = False
877 877 if opts.get('patch'):
878 878 patch = matchfn or matchall(repo)
879 879
880 880 tmpl = opts.get('template')
881 881 style = None
882 882 if tmpl:
883 883 tmpl = templater.parsestring(tmpl, quoted=False)
884 884 else:
885 885 style = opts.get('style')
886 886
887 887 # ui settings
888 888 if not (tmpl or style):
889 889 tmpl = ui.config('ui', 'logtemplate')
890 890 if tmpl:
891 891 tmpl = templater.parsestring(tmpl)
892 892 else:
893 893 style = ui.config('ui', 'style')
894 894
895 895 if not (tmpl or style):
896 896 return changeset_printer(ui, repo, patch, opts, buffered)
897 897
898 898 mapfile = None
899 899 if style and not tmpl:
900 900 mapfile = style
901 901 if not os.path.split(mapfile)[0]:
902 902 mapname = (templater.templatepath('map-cmdline.' + mapfile)
903 903 or templater.templatepath(mapfile))
904 904 if mapname: mapfile = mapname
905 905
906 906 try:
907 907 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
908 908 except SyntaxError, inst:
909 909 raise util.Abort(inst.args[0])
910 910 if tmpl: t.use_template(tmpl)
911 911 return t
912 912
913 913 def finddate(ui, repo, date):
914 914 """Find the tipmost changeset that matches the given date spec"""
915 915
916 916 df = util.matchdate(date)
917 917 m = matchall(repo)
918 918 results = {}
919 919
920 920 def prep(ctx, fns):
921 921 d = ctx.date()
922 922 if df(d[0]):
923 923 results[ctx.rev()] = d
924 924
925 925 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
926 926 rev = ctx.rev()
927 927 if rev in results:
928 928 ui.status(_("Found revision %s from %s\n") %
929 929 (rev, util.datestr(results[rev])))
930 930 return str(rev)
931 931
932 932 raise util.Abort(_("revision matching date not found"))
933 933
934 934 def walkchangerevs(repo, match, opts, prepare):
935 935 '''Iterate over files and the revs in which they changed.
936 936
937 937 Callers most commonly need to iterate backwards over the history
938 938 in which they are interested. Doing so has awful (quadratic-looking)
939 939 performance, so we use iterators in a "windowed" way.
940 940
941 941 We walk a window of revisions in the desired order. Within the
942 942 window, we first walk forwards to gather data, then in the desired
943 943 order (usually backwards) to display it.
944 944
945 945 This function returns an iterator yielding contexts. Before
946 946 yielding each context, the iterator will first call the prepare
947 947 function on each context in the window in forward order.'''
948 948
949 949 def increasing_windows(start, end, windowsize=8, sizelimit=512):
950 950 if start < end:
951 951 while start < end:
952 952 yield start, min(windowsize, end-start)
953 953 start += windowsize
954 954 if windowsize < sizelimit:
955 955 windowsize *= 2
956 956 else:
957 957 while start > end:
958 958 yield start, min(windowsize, start-end-1)
959 959 start -= windowsize
960 960 if windowsize < sizelimit:
961 961 windowsize *= 2
962 962
963 963 follow = opts.get('follow') or opts.get('follow_first')
964 964
965 965 if not len(repo):
966 966 return []
967 967
968 968 if follow:
969 969 defrange = '%s:0' % repo['.'].rev()
970 970 else:
971 971 defrange = '-1:0'
972 972 revs = revrange(repo, opts['rev'] or [defrange])
973 973 wanted = set()
974 974 slowpath = match.anypats() or (match.files() and opts.get('removed'))
975 975 fncache = {}
976 976 change = util.cachefunc(repo.changectx)
977 977
978 978 if not slowpath and not match.files():
979 979 # No files, no patterns. Display all revs.
980 980 wanted = set(revs)
981 981 copies = []
982 982
983 983 if not slowpath:
984 984 # Only files, no patterns. Check the history of each file.
985 985 def filerevgen(filelog, node):
986 986 cl_count = len(repo)
987 987 if node is None:
988 988 last = len(filelog) - 1
989 989 else:
990 990 last = filelog.rev(node)
991 991 for i, window in increasing_windows(last, nullrev):
992 992 revs = []
993 993 for j in xrange(i - window, i + 1):
994 994 n = filelog.node(j)
995 995 revs.append((filelog.linkrev(j),
996 996 follow and filelog.renamed(n)))
997 997 for rev in reversed(revs):
998 998 # only yield rev for which we have the changelog, it can
999 999 # happen while doing "hg log" during a pull or commit
1000 1000 if rev[0] < cl_count:
1001 1001 yield rev
1002 1002 def iterfiles():
1003 1003 for filename in match.files():
1004 1004 yield filename, None
1005 1005 for filename_node in copies:
1006 1006 yield filename_node
1007 1007 minrev, maxrev = min(revs), max(revs)
1008 1008 for file_, node in iterfiles():
1009 1009 filelog = repo.file(file_)
1010 1010 if not len(filelog):
1011 1011 if node is None:
1012 1012 # A zero count may be a directory or deleted file, so
1013 1013 # try to find matching entries on the slow path.
1014 1014 if follow:
1015 1015 raise util.Abort(_('cannot follow nonexistent file: "%s"') % file_)
1016 1016 slowpath = True
1017 1017 break
1018 1018 else:
1019 1019 continue
1020 1020 for rev, copied in filerevgen(filelog, node):
1021 1021 if rev <= maxrev:
1022 1022 if rev < minrev:
1023 1023 break
1024 1024 fncache.setdefault(rev, [])
1025 1025 fncache[rev].append(file_)
1026 1026 wanted.add(rev)
1027 1027 if follow and copied:
1028 1028 copies.append(copied)
1029 1029 if slowpath:
1030 1030 if follow:
1031 1031 raise util.Abort(_('can only follow copies/renames for explicit '
1032 1032 'filenames'))
1033 1033
1034 1034 # The slow path checks files modified in every changeset.
1035 1035 def changerevgen():
1036 1036 for i, window in increasing_windows(len(repo) - 1, nullrev):
1037 1037 for j in xrange(i - window, i + 1):
1038 1038 yield change(j)
1039 1039
1040 1040 for ctx in changerevgen():
1041 1041 matches = filter(match, ctx.files())
1042 1042 if matches:
1043 1043 fncache[ctx.rev()] = matches
1044 1044 wanted.add(ctx.rev())
1045 1045
1046 1046 class followfilter(object):
1047 1047 def __init__(self, onlyfirst=False):
1048 1048 self.startrev = nullrev
1049 1049 self.roots = set()
1050 1050 self.onlyfirst = onlyfirst
1051 1051
1052 1052 def match(self, rev):
1053 1053 def realparents(rev):
1054 1054 if self.onlyfirst:
1055 1055 return repo.changelog.parentrevs(rev)[0:1]
1056 1056 else:
1057 1057 return filter(lambda x: x != nullrev,
1058 1058 repo.changelog.parentrevs(rev))
1059 1059
1060 1060 if self.startrev == nullrev:
1061 1061 self.startrev = rev
1062 1062 return True
1063 1063
1064 1064 if rev > self.startrev:
1065 1065 # forward: all descendants
1066 1066 if not self.roots:
1067 1067 self.roots.add(self.startrev)
1068 1068 for parent in realparents(rev):
1069 1069 if parent in self.roots:
1070 1070 self.roots.add(rev)
1071 1071 return True
1072 1072 else:
1073 1073 # backwards: all parents
1074 1074 if not self.roots:
1075 1075 self.roots.update(realparents(self.startrev))
1076 1076 if rev in self.roots:
1077 1077 self.roots.remove(rev)
1078 1078 self.roots.update(realparents(rev))
1079 1079 return True
1080 1080
1081 1081 return False
1082 1082
1083 1083 # it might be worthwhile to do this in the iterator if the rev range
1084 1084 # is descending and the prune args are all within that range
1085 1085 for rev in opts.get('prune', ()):
1086 1086 rev = repo.changelog.rev(repo.lookup(rev))
1087 1087 ff = followfilter()
1088 1088 stop = min(revs[0], revs[-1])
1089 1089 for x in xrange(rev, stop-1, -1):
1090 1090 if ff.match(x):
1091 1091 wanted.discard(x)
1092 1092
1093 1093 def iterate():
1094 1094 if follow and not match.files():
1095 1095 ff = followfilter(onlyfirst=opts.get('follow_first'))
1096 1096 def want(rev):
1097 1097 return ff.match(rev) and rev in wanted
1098 1098 else:
1099 1099 def want(rev):
1100 1100 return rev in wanted
1101 1101
1102 1102 for i, window in increasing_windows(0, len(revs)):
1103 1103 change = util.cachefunc(repo.changectx)
1104 1104 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1105 1105 for rev in sorted(nrevs):
1106 1106 fns = fncache.get(rev)
1107 1107 ctx = change(rev)
1108 1108 if not fns:
1109 1109 def fns_generator():
1110 1110 for f in ctx.files():
1111 1111 if match(f):
1112 1112 yield f
1113 1113 fns = fns_generator()
1114 1114 prepare(ctx, fns)
1115 1115 for rev in nrevs:
1116 1116 yield change(rev)
1117 1117 return iterate()
1118 1118
1119 1119 def commit(ui, repo, commitfunc, pats, opts):
1120 1120 '''commit the specified files or all outstanding changes'''
1121 1121 date = opts.get('date')
1122 1122 if date:
1123 1123 opts['date'] = util.parsedate(date)
1124 1124 message = logmessage(opts)
1125 1125
1126 1126 # extract addremove carefully -- this function can be called from a command
1127 1127 # that doesn't support addremove
1128 1128 if opts.get('addremove'):
1129 1129 addremove(repo, pats, opts)
1130 1130
1131 1131 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1132 1132
1133 1133 def commiteditor(repo, ctx, subs):
1134 1134 if ctx.description():
1135 1135 return ctx.description()
1136 1136 return commitforceeditor(repo, ctx, subs)
1137 1137
1138 1138 def commitforceeditor(repo, ctx, subs):
1139 1139 edittext = []
1140 1140 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1141 1141 if ctx.description():
1142 1142 edittext.append(ctx.description())
1143 1143 edittext.append("")
1144 1144 edittext.append("") # Empty line between message and comments.
1145 1145 edittext.append(_("HG: Enter commit message."
1146 1146 " Lines beginning with 'HG:' are removed."))
1147 1147 edittext.append(_("HG: Leave message empty to abort commit."))
1148 1148 edittext.append("HG: --")
1149 1149 edittext.append(_("HG: user: %s") % ctx.user())
1150 1150 if ctx.p2():
1151 1151 edittext.append(_("HG: branch merge"))
1152 1152 if ctx.branch():
1153 1153 edittext.append(_("HG: branch '%s'")
1154 1154 % encoding.tolocal(ctx.branch()))
1155 1155 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1156 1156 edittext.extend([_("HG: added %s") % f for f in added])
1157 1157 edittext.extend([_("HG: changed %s") % f for f in modified])
1158 1158 edittext.extend([_("HG: removed %s") % f for f in removed])
1159 1159 if not added and not modified and not removed:
1160 1160 edittext.append(_("HG: no files changed"))
1161 1161 edittext.append("")
1162 1162 # run editor in the repository root
1163 1163 olddir = os.getcwd()
1164 1164 os.chdir(repo.root)
1165 1165 text = repo.ui.edit("\n".join(edittext), ctx.user())
1166 1166 text = re.sub("(?m)^HG:.*\n", "", text)
1167 1167 os.chdir(olddir)
1168 1168
1169 1169 if not text.strip():
1170 1170 raise util.Abort(_("empty commit message"))
1171 1171
1172 1172 return text
@@ -1,264 +1,266 b''
1 1 # posix.py - Posix utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9 import osutil
10 10 import os, sys, errno, stat, getpass, pwd, grp, fcntl
11 11
12 12 posixfile = open
13 13 nulldev = '/dev/null'
14 14 normpath = os.path.normpath
15 15 samestat = os.path.samestat
16 16 rename = os.rename
17 17 expandglobs = False
18 18
19 19 umask = os.umask(0)
20 20 os.umask(umask)
21 21
22 22 def openhardlinks():
23 23 '''return true if it is safe to hold open file handles to hardlinks'''
24 24 return True
25 25
26 26 def rcfiles(path):
27 27 rcs = [os.path.join(path, 'hgrc')]
28 28 rcdir = os.path.join(path, 'hgrc.d')
29 29 try:
30 30 rcs.extend([os.path.join(rcdir, f)
31 31 for f, kind in osutil.listdir(rcdir)
32 32 if f.endswith(".rc")])
33 33 except OSError:
34 34 pass
35 35 return rcs
36 36
37 37 def system_rcpath():
38 38 path = []
39 39 # old mod_python does not set sys.argv
40 40 if len(getattr(sys, 'argv', [])) > 0:
41 41 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
42 42 '/../etc/mercurial'))
43 43 path.extend(rcfiles('/etc/mercurial'))
44 44 return path
45 45
46 46 def user_rcpath():
47 47 return [os.path.expanduser('~/.hgrc')]
48 48
49 49 def parse_patch_output(output_line):
50 50 """parses the output produced by patch and returns the filename"""
51 51 pf = output_line[14:]
52 52 if os.sys.platform == 'OpenVMS':
53 53 if pf[0] == '`':
54 54 pf = pf[1:-1] # Remove the quotes
55 55 else:
56 56 if pf.startswith("'") and pf.endswith("'") and " " in pf:
57 57 pf = pf[1:-1] # Remove the quotes
58 58 return pf
59 59
60 60 def sshargs(sshcmd, host, user, port):
61 61 '''Build argument list for ssh'''
62 62 args = user and ("%s@%s" % (user, host)) or host
63 63 return port and ("%s -p %s" % (args, port)) or args
64 64
65 65 def is_exec(f):
66 66 """check whether a file is executable"""
67 67 return (os.lstat(f).st_mode & 0100 != 0)
68 68
69 69 def set_flags(f, l, x):
70 70 s = os.lstat(f).st_mode
71 71 if l:
72 72 if not stat.S_ISLNK(s):
73 73 # switch file to link
74 74 data = open(f).read()
75 75 os.unlink(f)
76 76 try:
77 77 os.symlink(data, f)
78 78 except:
79 79 # failed to make a link, rewrite file
80 80 open(f, "w").write(data)
81 81 # no chmod needed at this point
82 82 return
83 83 if stat.S_ISLNK(s):
84 84 # switch link to file
85 85 data = os.readlink(f)
86 86 os.unlink(f)
87 87 open(f, "w").write(data)
88 88 s = 0666 & ~umask # avoid restatting for chmod
89 89
90 90 sx = s & 0100
91 91 if x and not sx:
92 92 # Turn on +x for every +r bit when making a file executable
93 93 # and obey umask.
94 94 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
95 95 elif not x and sx:
96 96 # Turn off all +x bits
97 97 os.chmod(f, s & 0666)
98 98
99 99 def set_binary(fd):
100 100 pass
101 101
102 102 def pconvert(path):
103 103 return path
104 104
105 105 def localpath(path):
106 106 return path
107 107
108 108 def samefile(fpath1, fpath2):
109 109 """Returns whether path1 and path2 refer to the same file. This is only
110 110 guaranteed to work for files, not directories."""
111 111 return os.path.samefile(fpath1, fpath2)
112 112
113 113 def samedevice(fpath1, fpath2):
114 114 """Returns whether fpath1 and fpath2 are on the same device. This is only
115 115 guaranteed to work for files, not directories."""
116 116 st1 = os.lstat(fpath1)
117 117 st2 = os.lstat(fpath2)
118 118 return st1.st_dev == st2.st_dev
119 119
120 120 if sys.platform == 'darwin':
121 121 def realpath(path):
122 122 '''
123 123 Returns the true, canonical file system path equivalent to the given
124 124 path.
125 125
126 126 Equivalent means, in this case, resulting in the same, unique
127 127 file system link to the path. Every file system entry, whether a file,
128 128 directory, hard link or symbolic link or special, will have a single
129 129 path preferred by the system, but may allow multiple, differing path
130 130 lookups to point to it.
131 131
132 132 Most regular UNIX file systems only allow a file system entry to be
133 133 looked up by its distinct path. Obviously, this does not apply to case
134 134 insensitive file systems, whether case preserving or not. The most
135 135 complex issue to deal with is file systems transparently reencoding the
136 136 path, such as the non-standard Unicode normalisation required for HFS+
137 137 and HFSX.
138 138 '''
139 139 # Constants copied from /usr/include/sys/fcntl.h
140 140 F_GETPATH = 50
141 141 O_SYMLINK = 0x200000
142 142
143 143 try:
144 144 fd = os.open(path, O_SYMLINK)
145 145 except OSError, err:
146 146 if err.errno is errno.ENOENT:
147 147 return path
148 148 raise
149 149
150 150 try:
151 151 return fcntl.fcntl(fd, F_GETPATH, '\0' * 1024).rstrip('\0')
152 152 finally:
153 153 os.close(fd)
154 154 else:
155 155 # Fallback to the likely inadequate Python builtin function.
156 156 realpath = os.path.realpath
157 157
158 158 def shellquote(s):
159 159 if os.sys.platform == 'OpenVMS':
160 160 return '"%s"' % s
161 161 else:
162 162 return "'%s'" % s.replace("'", "'\\''")
163 163
164 164 def quotecommand(cmd):
165 165 return cmd
166 166
167 167 def popen(command, mode='r'):
168 168 return os.popen(command, mode)
169 169
170 170 def testpid(pid):
171 171 '''return False if pid dead, True if running or not sure'''
172 172 if os.sys.platform == 'OpenVMS':
173 173 return True
174 174 try:
175 175 os.kill(pid, 0)
176 176 return True
177 177 except OSError, inst:
178 178 return inst.errno != errno.ESRCH
179 179
180 180 def explain_exit(code):
181 181 """return a 2-tuple (desc, code) describing a subprocess status
182 182 (codes from kill are negative - not os.system/wait encoding)"""
183 183 if code >= 0:
184 184 return _("exited with status %d") % code, code
185 185 return _("killed by signal %d") % -code, -code
186 186
187 187 def isowner(st):
188 188 """Return True if the stat object st is from the current user."""
189 189 return st.st_uid == os.getuid()
190 190
191 191 def find_exe(command):
192 192 '''Find executable for command searching like which does.
193 193 If command is a basename then PATH is searched for command.
194 194 PATH isn't searched if command is an absolute or relative path.
195 195 If command isn't found None is returned.'''
196 196 if sys.platform == 'OpenVMS':
197 197 return command
198 198
199 199 def findexisting(executable):
200 200 'Will return executable if existing file'
201 201 if os.path.exists(executable):
202 202 return executable
203 203 return None
204 204
205 205 if os.sep in command:
206 206 return findexisting(command)
207 207
208 208 for path in os.environ.get('PATH', '').split(os.pathsep):
209 209 executable = findexisting(os.path.join(path, command))
210 210 if executable is not None:
211 211 return executable
212 212 return None
213 213
214 214 def set_signal_handler():
215 215 pass
216 216
217 217 def statfiles(files):
218 218 'Stat each file in files and yield stat or None if file does not exist.'
219 219 lstat = os.lstat
220 220 for nf in files:
221 221 try:
222 222 st = lstat(nf)
223 223 except OSError, err:
224 224 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
225 225 raise
226 226 st = None
227 227 yield st
228 228
229 229 def getuser():
230 230 '''return name of current user'''
231 231 return getpass.getuser()
232 232
233 233 def expand_glob(pats):
234 234 '''On Windows, expand the implicit globs in a list of patterns'''
235 235 return list(pats)
236 236
237 237 def username(uid=None):
238 238 """Return the name of the user with the given uid.
239 239
240 240 If uid is None, return the name of the current user."""
241 241
242 242 if uid is None:
243 243 uid = os.getuid()
244 244 try:
245 245 return pwd.getpwuid(uid)[0]
246 246 except KeyError:
247 247 return str(uid)
248 248
249 249 def groupname(gid=None):
250 250 """Return the name of the group with the given gid.
251 251
252 252 If gid is None, return the name of the current group."""
253 253
254 254 if gid is None:
255 255 gid = os.getgid()
256 256 try:
257 257 return grp.getgrgid(gid)[0]
258 258 except KeyError:
259 259 return str(gid)
260 260
261 261 def spawndetached(args):
262 262 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
263 263 args[0], args)
264 264
265 def gethgcmd():
266 return sys.argv[:1]
@@ -1,1276 +1,1287 b''
1 1 # util.py - Mercurial utility functions and platform specfic implementations
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2, incorporated herein by reference.
9 9
10 10 """Mercurial utility functions and platform specfic implementations.
11 11
12 12 This contains helper routines that are independent of the SCM core and
13 13 hide platform-specific details from the core.
14 14 """
15 15
16 16 from i18n import _
17 17 import error, osutil, encoding
18 18 import cStringIO, errno, re, shutil, sys, tempfile, traceback
19 19 import os, stat, time, calendar, textwrap
20 20 import imp
21 21
22 22 # Python compatibility
23 23
24 24 def sha1(s):
25 25 return _fastsha1(s)
26 26
27 27 def _fastsha1(s):
28 28 # This function will import sha1 from hashlib or sha (whichever is
29 29 # available) and overwrite itself with it on the first call.
30 30 # Subsequent calls will go directly to the imported function.
31 31 try:
32 32 from hashlib import sha1 as _sha1
33 33 except ImportError:
34 34 from sha import sha as _sha1
35 35 global _fastsha1, sha1
36 36 _fastsha1 = sha1 = _sha1
37 37 return _sha1(s)
38 38
39 39 import subprocess
40 40 closefds = os.name == 'posix'
41 41
42 42 def popen2(cmd, env=None, newlines=False):
43 43 # Setting bufsize to -1 lets the system decide the buffer size.
44 44 # The default for bufsize is 0, meaning unbuffered. This leads to
45 45 # poor performance on Mac OS X: http://bugs.python.org/issue4194
46 46 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
47 47 close_fds=closefds,
48 48 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
49 49 universal_newlines=newlines,
50 50 env=env)
51 51 return p.stdin, p.stdout
52 52
53 53 def popen3(cmd, env=None, newlines=False):
54 54 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
55 55 close_fds=closefds,
56 56 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
57 57 stderr=subprocess.PIPE,
58 58 universal_newlines=newlines,
59 59 env=env)
60 60 return p.stdin, p.stdout, p.stderr
61 61
62 62 def version():
63 63 """Return version information if available."""
64 64 try:
65 65 import __version__
66 66 return __version__.version
67 67 except ImportError:
68 68 return 'unknown'
69 69
70 70 # used by parsedate
71 71 defaultdateformats = (
72 72 '%Y-%m-%d %H:%M:%S',
73 73 '%Y-%m-%d %I:%M:%S%p',
74 74 '%Y-%m-%d %H:%M',
75 75 '%Y-%m-%d %I:%M%p',
76 76 '%Y-%m-%d',
77 77 '%m-%d',
78 78 '%m/%d',
79 79 '%m/%d/%y',
80 80 '%m/%d/%Y',
81 81 '%a %b %d %H:%M:%S %Y',
82 82 '%a %b %d %I:%M:%S%p %Y',
83 83 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
84 84 '%b %d %H:%M:%S %Y',
85 85 '%b %d %I:%M:%S%p %Y',
86 86 '%b %d %H:%M:%S',
87 87 '%b %d %I:%M:%S%p',
88 88 '%b %d %H:%M',
89 89 '%b %d %I:%M%p',
90 90 '%b %d %Y',
91 91 '%b %d',
92 92 '%H:%M:%S',
93 93 '%I:%M:%S%p',
94 94 '%H:%M',
95 95 '%I:%M%p',
96 96 )
97 97
98 98 extendeddateformats = defaultdateformats + (
99 99 "%Y",
100 100 "%Y-%m",
101 101 "%b",
102 102 "%b %Y",
103 103 )
104 104
105 105 def cachefunc(func):
106 106 '''cache the result of function calls'''
107 107 # XXX doesn't handle keywords args
108 108 cache = {}
109 109 if func.func_code.co_argcount == 1:
110 110 # we gain a small amount of time because
111 111 # we don't need to pack/unpack the list
112 112 def f(arg):
113 113 if arg not in cache:
114 114 cache[arg] = func(arg)
115 115 return cache[arg]
116 116 else:
117 117 def f(*args):
118 118 if args not in cache:
119 119 cache[args] = func(*args)
120 120 return cache[args]
121 121
122 122 return f
123 123
124 124 def lrucachefunc(func):
125 125 '''cache most recent results of function calls'''
126 126 cache = {}
127 127 order = []
128 128 if func.func_code.co_argcount == 1:
129 129 def f(arg):
130 130 if arg not in cache:
131 131 if len(cache) > 20:
132 132 del cache[order.pop(0)]
133 133 cache[arg] = func(arg)
134 134 else:
135 135 order.remove(arg)
136 136 order.append(arg)
137 137 return cache[arg]
138 138 else:
139 139 def f(*args):
140 140 if args not in cache:
141 141 if len(cache) > 20:
142 142 del cache[order.pop(0)]
143 143 cache[args] = func(*args)
144 144 else:
145 145 order.remove(args)
146 146 order.append(args)
147 147 return cache[args]
148 148
149 149 return f
150 150
151 151 class propertycache(object):
152 152 def __init__(self, func):
153 153 self.func = func
154 154 self.name = func.__name__
155 155 def __get__(self, obj, type=None):
156 156 result = self.func(obj)
157 157 setattr(obj, self.name, result)
158 158 return result
159 159
160 160 def pipefilter(s, cmd):
161 161 '''filter string S through command CMD, returning its output'''
162 162 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
163 163 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
164 164 pout, perr = p.communicate(s)
165 165 return pout
166 166
167 167 def tempfilter(s, cmd):
168 168 '''filter string S through a pair of temporary files with CMD.
169 169 CMD is used as a template to create the real command to be run,
170 170 with the strings INFILE and OUTFILE replaced by the real names of
171 171 the temporary files generated.'''
172 172 inname, outname = None, None
173 173 try:
174 174 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
175 175 fp = os.fdopen(infd, 'wb')
176 176 fp.write(s)
177 177 fp.close()
178 178 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
179 179 os.close(outfd)
180 180 cmd = cmd.replace('INFILE', inname)
181 181 cmd = cmd.replace('OUTFILE', outname)
182 182 code = os.system(cmd)
183 183 if sys.platform == 'OpenVMS' and code & 1:
184 184 code = 0
185 185 if code: raise Abort(_("command '%s' failed: %s") %
186 186 (cmd, explain_exit(code)))
187 187 return open(outname, 'rb').read()
188 188 finally:
189 189 try:
190 190 if inname: os.unlink(inname)
191 191 except: pass
192 192 try:
193 193 if outname: os.unlink(outname)
194 194 except: pass
195 195
196 196 filtertable = {
197 197 'tempfile:': tempfilter,
198 198 'pipe:': pipefilter,
199 199 }
200 200
201 201 def filter(s, cmd):
202 202 "filter a string through a command that transforms its input to its output"
203 203 for name, fn in filtertable.iteritems():
204 204 if cmd.startswith(name):
205 205 return fn(s, cmd[len(name):].lstrip())
206 206 return pipefilter(s, cmd)
207 207
208 208 def binary(s):
209 209 """return true if a string is binary data"""
210 210 return bool(s and '\0' in s)
211 211
212 212 def increasingchunks(source, min=1024, max=65536):
213 213 '''return no less than min bytes per chunk while data remains,
214 214 doubling min after each chunk until it reaches max'''
215 215 def log2(x):
216 216 if not x:
217 217 return 0
218 218 i = 0
219 219 while x:
220 220 x >>= 1
221 221 i += 1
222 222 return i - 1
223 223
224 224 buf = []
225 225 blen = 0
226 226 for chunk in source:
227 227 buf.append(chunk)
228 228 blen += len(chunk)
229 229 if blen >= min:
230 230 if min < max:
231 231 min = min << 1
232 232 nmin = 1 << log2(blen)
233 233 if nmin > min:
234 234 min = nmin
235 235 if min > max:
236 236 min = max
237 237 yield ''.join(buf)
238 238 blen = 0
239 239 buf = []
240 240 if buf:
241 241 yield ''.join(buf)
242 242
243 243 Abort = error.Abort
244 244
245 245 def always(fn): return True
246 246 def never(fn): return False
247 247
248 248 def pathto(root, n1, n2):
249 249 '''return the relative path from one place to another.
250 250 root should use os.sep to separate directories
251 251 n1 should use os.sep to separate directories
252 252 n2 should use "/" to separate directories
253 253 returns an os.sep-separated path.
254 254
255 255 If n1 is a relative path, it's assumed it's
256 256 relative to root.
257 257 n2 should always be relative to root.
258 258 '''
259 259 if not n1: return localpath(n2)
260 260 if os.path.isabs(n1):
261 261 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
262 262 return os.path.join(root, localpath(n2))
263 263 n2 = '/'.join((pconvert(root), n2))
264 264 a, b = splitpath(n1), n2.split('/')
265 265 a.reverse()
266 266 b.reverse()
267 267 while a and b and a[-1] == b[-1]:
268 268 a.pop()
269 269 b.pop()
270 270 b.reverse()
271 271 return os.sep.join((['..'] * len(a)) + b) or '.'
272 272
273 273 def canonpath(root, cwd, myname):
274 274 """return the canonical path of myname, given cwd and root"""
275 275 if endswithsep(root):
276 276 rootsep = root
277 277 else:
278 278 rootsep = root + os.sep
279 279 name = myname
280 280 if not os.path.isabs(name):
281 281 name = os.path.join(root, cwd, name)
282 282 name = os.path.normpath(name)
283 283 audit_path = path_auditor(root)
284 284 if name != rootsep and name.startswith(rootsep):
285 285 name = name[len(rootsep):]
286 286 audit_path(name)
287 287 return pconvert(name)
288 288 elif name == root:
289 289 return ''
290 290 else:
291 291 # Determine whether `name' is in the hierarchy at or beneath `root',
292 292 # by iterating name=dirname(name) until that causes no change (can't
293 293 # check name == '/', because that doesn't work on windows). For each
294 294 # `name', compare dev/inode numbers. If they match, the list `rel'
295 295 # holds the reversed list of components making up the relative file
296 296 # name we want.
297 297 root_st = os.stat(root)
298 298 rel = []
299 299 while True:
300 300 try:
301 301 name_st = os.stat(name)
302 302 except OSError:
303 303 break
304 304 if samestat(name_st, root_st):
305 305 if not rel:
306 306 # name was actually the same as root (maybe a symlink)
307 307 return ''
308 308 rel.reverse()
309 309 name = os.path.join(*rel)
310 310 audit_path(name)
311 311 return pconvert(name)
312 312 dirname, basename = os.path.split(name)
313 313 rel.append(basename)
314 314 if dirname == name:
315 315 break
316 316 name = dirname
317 317
318 318 raise Abort('%s not under root' % myname)
319 319
320 320 _hgexecutable = None
321 321
322 322 def main_is_frozen():
323 323 """return True if we are a frozen executable.
324 324
325 325 The code supports py2exe (most common, Windows only) and tools/freeze
326 326 (portable, not much used).
327 327 """
328 328 return (hasattr(sys, "frozen") or # new py2exe
329 329 hasattr(sys, "importers") or # old py2exe
330 330 imp.is_frozen("__main__")) # tools/freeze
331 331
332 332 def hgexecutable():
333 333 """return location of the 'hg' executable.
334 334
335 335 Defaults to $HG or 'hg' in the search path.
336 336 """
337 337 if _hgexecutable is None:
338 338 hg = os.environ.get('HG')
339 339 if hg:
340 340 set_hgexecutable(hg)
341 341 elif main_is_frozen():
342 342 set_hgexecutable(sys.executable)
343 343 else:
344 344 exe = find_exe('hg') or os.path.basename(sys.argv[0])
345 345 set_hgexecutable(exe)
346 346 return _hgexecutable
347 347
348 348 def set_hgexecutable(path):
349 349 """set location of the 'hg' executable"""
350 350 global _hgexecutable
351 351 _hgexecutable = path
352 352
353 353 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
354 354 '''enhanced shell command execution.
355 355 run with environment maybe modified, maybe in different dir.
356 356
357 357 if command fails and onerr is None, return status. if ui object,
358 358 print error message and return status, else raise onerr object as
359 359 exception.'''
360 360 def py2shell(val):
361 361 'convert python object into string that is useful to shell'
362 362 if val is None or val is False:
363 363 return '0'
364 364 if val is True:
365 365 return '1'
366 366 return str(val)
367 367 origcmd = cmd
368 368 if os.name == 'nt':
369 369 cmd = '"%s"' % cmd
370 370 env = dict(os.environ)
371 371 env.update((k, py2shell(v)) for k, v in environ.iteritems())
372 372 env['HG'] = hgexecutable()
373 373 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
374 374 env=env, cwd=cwd)
375 375 if sys.platform == 'OpenVMS' and rc & 1:
376 376 rc = 0
377 377 if rc and onerr:
378 378 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
379 379 explain_exit(rc)[0])
380 380 if errprefix:
381 381 errmsg = '%s: %s' % (errprefix, errmsg)
382 382 try:
383 383 onerr.warn(errmsg + '\n')
384 384 except AttributeError:
385 385 raise onerr(errmsg)
386 386 return rc
387 387
388 388 def checksignature(func):
389 389 '''wrap a function with code to check for calling errors'''
390 390 def check(*args, **kwargs):
391 391 try:
392 392 return func(*args, **kwargs)
393 393 except TypeError:
394 394 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
395 395 raise error.SignatureError
396 396 raise
397 397
398 398 return check
399 399
400 400 # os.path.lexists is not available on python2.3
401 401 def lexists(filename):
402 402 "test whether a file with this name exists. does not follow symlinks"
403 403 try:
404 404 os.lstat(filename)
405 405 except:
406 406 return False
407 407 return True
408 408
409 409 def unlink(f):
410 410 """unlink and remove the directory if it is empty"""
411 411 os.unlink(f)
412 412 # try removing directories that might now be empty
413 413 try:
414 414 os.removedirs(os.path.dirname(f))
415 415 except OSError:
416 416 pass
417 417
418 418 def copyfile(src, dest):
419 419 "copy a file, preserving mode and atime/mtime"
420 420 if os.path.islink(src):
421 421 try:
422 422 os.unlink(dest)
423 423 except:
424 424 pass
425 425 os.symlink(os.readlink(src), dest)
426 426 else:
427 427 try:
428 428 shutil.copyfile(src, dest)
429 429 shutil.copystat(src, dest)
430 430 except shutil.Error, inst:
431 431 raise Abort(str(inst))
432 432
433 433 def copyfiles(src, dst, hardlink=None):
434 434 """Copy a directory tree using hardlinks if possible"""
435 435
436 436 if hardlink is None:
437 437 hardlink = (os.stat(src).st_dev ==
438 438 os.stat(os.path.dirname(dst)).st_dev)
439 439
440 440 if os.path.isdir(src):
441 441 os.mkdir(dst)
442 442 for name, kind in osutil.listdir(src):
443 443 srcname = os.path.join(src, name)
444 444 dstname = os.path.join(dst, name)
445 445 copyfiles(srcname, dstname, hardlink)
446 446 else:
447 447 if hardlink:
448 448 try:
449 449 os_link(src, dst)
450 450 except (IOError, OSError):
451 451 hardlink = False
452 452 shutil.copy(src, dst)
453 453 else:
454 454 shutil.copy(src, dst)
455 455
456 456 class path_auditor(object):
457 457 '''ensure that a filesystem path contains no banned components.
458 458 the following properties of a path are checked:
459 459
460 460 - under top-level .hg
461 461 - starts at the root of a windows drive
462 462 - contains ".."
463 463 - traverses a symlink (e.g. a/symlink_here/b)
464 464 - inside a nested repository'''
465 465
466 466 def __init__(self, root):
467 467 self.audited = set()
468 468 self.auditeddir = set()
469 469 self.root = root
470 470
471 471 def __call__(self, path):
472 472 if path in self.audited:
473 473 return
474 474 normpath = os.path.normcase(path)
475 475 parts = splitpath(normpath)
476 476 if (os.path.splitdrive(path)[0]
477 477 or parts[0].lower() in ('.hg', '.hg.', '')
478 478 or os.pardir in parts):
479 479 raise Abort(_("path contains illegal component: %s") % path)
480 480 if '.hg' in path.lower():
481 481 lparts = [p.lower() for p in parts]
482 482 for p in '.hg', '.hg.':
483 483 if p in lparts[1:]:
484 484 pos = lparts.index(p)
485 485 base = os.path.join(*parts[:pos])
486 486 raise Abort(_('path %r is inside repo %r') % (path, base))
487 487 def check(prefix):
488 488 curpath = os.path.join(self.root, prefix)
489 489 try:
490 490 st = os.lstat(curpath)
491 491 except OSError, err:
492 492 # EINVAL can be raised as invalid path syntax under win32.
493 493 # They must be ignored for patterns can be checked too.
494 494 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
495 495 raise
496 496 else:
497 497 if stat.S_ISLNK(st.st_mode):
498 498 raise Abort(_('path %r traverses symbolic link %r') %
499 499 (path, prefix))
500 500 elif (stat.S_ISDIR(st.st_mode) and
501 501 os.path.isdir(os.path.join(curpath, '.hg'))):
502 502 raise Abort(_('path %r is inside repo %r') %
503 503 (path, prefix))
504 504 parts.pop()
505 505 prefixes = []
506 506 while parts:
507 507 prefix = os.sep.join(parts)
508 508 if prefix in self.auditeddir:
509 509 break
510 510 check(prefix)
511 511 prefixes.append(prefix)
512 512 parts.pop()
513 513
514 514 self.audited.add(path)
515 515 # only add prefixes to the cache after checking everything: we don't
516 516 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
517 517 self.auditeddir.update(prefixes)
518 518
519 519 def nlinks(pathname):
520 520 """Return number of hardlinks for the given file."""
521 521 return os.lstat(pathname).st_nlink
522 522
523 523 if hasattr(os, 'link'):
524 524 os_link = os.link
525 525 else:
526 526 def os_link(src, dst):
527 527 raise OSError(0, _("Hardlinks not supported"))
528 528
529 529 def lookup_reg(key, name=None, scope=None):
530 530 return None
531 531
532 532 if os.name == 'nt':
533 533 from windows import *
534 534 else:
535 535 from posix import *
536 536
537 537 def makelock(info, pathname):
538 538 try:
539 539 return os.symlink(info, pathname)
540 540 except OSError, why:
541 541 if why.errno == errno.EEXIST:
542 542 raise
543 543 except AttributeError: # no symlink in os
544 544 pass
545 545
546 546 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
547 547 os.write(ld, info)
548 548 os.close(ld)
549 549
550 550 def readlock(pathname):
551 551 try:
552 552 return os.readlink(pathname)
553 553 except OSError, why:
554 554 if why.errno not in (errno.EINVAL, errno.ENOSYS):
555 555 raise
556 556 except AttributeError: # no symlink in os
557 557 pass
558 558 return posixfile(pathname).read()
559 559
560 560 def fstat(fp):
561 561 '''stat file object that may not have fileno method.'''
562 562 try:
563 563 return os.fstat(fp.fileno())
564 564 except AttributeError:
565 565 return os.stat(fp.name)
566 566
567 567 # File system features
568 568
569 569 def checkcase(path):
570 570 """
571 571 Check whether the given path is on a case-sensitive filesystem
572 572
573 573 Requires a path (like /foo/.hg) ending with a foldable final
574 574 directory component.
575 575 """
576 576 s1 = os.stat(path)
577 577 d, b = os.path.split(path)
578 578 p2 = os.path.join(d, b.upper())
579 579 if path == p2:
580 580 p2 = os.path.join(d, b.lower())
581 581 try:
582 582 s2 = os.stat(p2)
583 583 if s2 == s1:
584 584 return False
585 585 return True
586 586 except:
587 587 return True
588 588
589 589 _fspathcache = {}
590 590 def fspath(name, root):
591 591 '''Get name in the case stored in the filesystem
592 592
593 593 The name is either relative to root, or it is an absolute path starting
594 594 with root. Note that this function is unnecessary, and should not be
595 595 called, for case-sensitive filesystems (simply because it's expensive).
596 596 '''
597 597 # If name is absolute, make it relative
598 598 if name.lower().startswith(root.lower()):
599 599 l = len(root)
600 600 if name[l] == os.sep or name[l] == os.altsep:
601 601 l = l + 1
602 602 name = name[l:]
603 603
604 604 if not os.path.exists(os.path.join(root, name)):
605 605 return None
606 606
607 607 seps = os.sep
608 608 if os.altsep:
609 609 seps = seps + os.altsep
610 610 # Protect backslashes. This gets silly very quickly.
611 611 seps.replace('\\','\\\\')
612 612 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
613 613 dir = os.path.normcase(os.path.normpath(root))
614 614 result = []
615 615 for part, sep in pattern.findall(name):
616 616 if sep:
617 617 result.append(sep)
618 618 continue
619 619
620 620 if dir not in _fspathcache:
621 621 _fspathcache[dir] = os.listdir(dir)
622 622 contents = _fspathcache[dir]
623 623
624 624 lpart = part.lower()
625 625 lenp = len(part)
626 626 for n in contents:
627 627 if lenp == len(n) and n.lower() == lpart:
628 628 result.append(n)
629 629 break
630 630 else:
631 631 # Cannot happen, as the file exists!
632 632 result.append(part)
633 633 dir = os.path.join(dir, lpart)
634 634
635 635 return ''.join(result)
636 636
637 637 def checkexec(path):
638 638 """
639 639 Check whether the given path is on a filesystem with UNIX-like exec flags
640 640
641 641 Requires a directory (like /foo/.hg)
642 642 """
643 643
644 644 # VFAT on some Linux versions can flip mode but it doesn't persist
645 645 # a FS remount. Frequently we can detect it if files are created
646 646 # with exec bit on.
647 647
648 648 try:
649 649 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
650 650 fh, fn = tempfile.mkstemp("", "", path)
651 651 try:
652 652 os.close(fh)
653 653 m = os.stat(fn).st_mode & 0777
654 654 new_file_has_exec = m & EXECFLAGS
655 655 os.chmod(fn, m ^ EXECFLAGS)
656 656 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
657 657 finally:
658 658 os.unlink(fn)
659 659 except (IOError, OSError):
660 660 # we don't care, the user probably won't be able to commit anyway
661 661 return False
662 662 return not (new_file_has_exec or exec_flags_cannot_flip)
663 663
664 664 def checklink(path):
665 665 """check whether the given path is on a symlink-capable filesystem"""
666 666 # mktemp is not racy because symlink creation will fail if the
667 667 # file already exists
668 668 name = tempfile.mktemp(dir=path)
669 669 try:
670 670 os.symlink(".", name)
671 671 os.unlink(name)
672 672 return True
673 673 except (OSError, AttributeError):
674 674 return False
675 675
676 676 def needbinarypatch():
677 677 """return True if patches should be applied in binary mode by default."""
678 678 return os.name == 'nt'
679 679
680 680 def endswithsep(path):
681 681 '''Check path ends with os.sep or os.altsep.'''
682 682 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
683 683
684 684 def splitpath(path):
685 685 '''Split path by os.sep.
686 686 Note that this function does not use os.altsep because this is
687 687 an alternative of simple "xxx.split(os.sep)".
688 688 It is recommended to use os.path.normpath() before using this
689 689 function if need.'''
690 690 return path.split(os.sep)
691 691
692 692 def gui():
693 693 '''Are we running in a GUI?'''
694 694 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
695 695
696 696 def mktempcopy(name, emptyok=False, createmode=None):
697 697 """Create a temporary file with the same contents from name
698 698
699 699 The permission bits are copied from the original file.
700 700
701 701 If the temporary file is going to be truncated immediately, you
702 702 can use emptyok=True as an optimization.
703 703
704 704 Returns the name of the temporary file.
705 705 """
706 706 d, fn = os.path.split(name)
707 707 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
708 708 os.close(fd)
709 709 # Temporary files are created with mode 0600, which is usually not
710 710 # what we want. If the original file already exists, just copy
711 711 # its mode. Otherwise, manually obey umask.
712 712 try:
713 713 st_mode = os.lstat(name).st_mode & 0777
714 714 except OSError, inst:
715 715 if inst.errno != errno.ENOENT:
716 716 raise
717 717 st_mode = createmode
718 718 if st_mode is None:
719 719 st_mode = ~umask
720 720 st_mode &= 0666
721 721 os.chmod(temp, st_mode)
722 722 if emptyok:
723 723 return temp
724 724 try:
725 725 try:
726 726 ifp = posixfile(name, "rb")
727 727 except IOError, inst:
728 728 if inst.errno == errno.ENOENT:
729 729 return temp
730 730 if not getattr(inst, 'filename', None):
731 731 inst.filename = name
732 732 raise
733 733 ofp = posixfile(temp, "wb")
734 734 for chunk in filechunkiter(ifp):
735 735 ofp.write(chunk)
736 736 ifp.close()
737 737 ofp.close()
738 738 except:
739 739 try: os.unlink(temp)
740 740 except: pass
741 741 raise
742 742 return temp
743 743
744 744 class atomictempfile(object):
745 745 """file-like object that atomically updates a file
746 746
747 747 All writes will be redirected to a temporary copy of the original
748 748 file. When rename is called, the copy is renamed to the original
749 749 name, making the changes visible.
750 750 """
751 751 def __init__(self, name, mode, createmode):
752 752 self.__name = name
753 753 self._fp = None
754 754 self.temp = mktempcopy(name, emptyok=('w' in mode),
755 755 createmode=createmode)
756 756 self._fp = posixfile(self.temp, mode)
757 757
758 758 def __getattr__(self, name):
759 759 return getattr(self._fp, name)
760 760
761 761 def rename(self):
762 762 if not self._fp.closed:
763 763 self._fp.close()
764 764 rename(self.temp, localpath(self.__name))
765 765
766 766 def __del__(self):
767 767 if not self._fp:
768 768 return
769 769 if not self._fp.closed:
770 770 try:
771 771 os.unlink(self.temp)
772 772 except: pass
773 773 self._fp.close()
774 774
775 775 def makedirs(name, mode=None):
776 776 """recursive directory creation with parent mode inheritance"""
777 777 try:
778 778 os.mkdir(name)
779 779 if mode is not None:
780 780 os.chmod(name, mode)
781 781 return
782 782 except OSError, err:
783 783 if err.errno == errno.EEXIST:
784 784 return
785 785 if err.errno != errno.ENOENT:
786 786 raise
787 787 parent = os.path.abspath(os.path.dirname(name))
788 788 makedirs(parent, mode)
789 789 makedirs(name, mode)
790 790
791 791 class opener(object):
792 792 """Open files relative to a base directory
793 793
794 794 This class is used to hide the details of COW semantics and
795 795 remote file access from higher level code.
796 796 """
797 797 def __init__(self, base, audit=True):
798 798 self.base = base
799 799 if audit:
800 800 self.audit_path = path_auditor(base)
801 801 else:
802 802 self.audit_path = always
803 803 self.createmode = None
804 804
805 805 @propertycache
806 806 def _can_symlink(self):
807 807 return checklink(self.base)
808 808
809 809 def _fixfilemode(self, name):
810 810 if self.createmode is None:
811 811 return
812 812 os.chmod(name, self.createmode & 0666)
813 813
814 814 def __call__(self, path, mode="r", text=False, atomictemp=False):
815 815 self.audit_path(path)
816 816 f = os.path.join(self.base, path)
817 817
818 818 if not text and "b" not in mode:
819 819 mode += "b" # for that other OS
820 820
821 821 nlink = -1
822 822 if mode not in ("r", "rb"):
823 823 try:
824 824 nlink = nlinks(f)
825 825 except OSError:
826 826 nlink = 0
827 827 d = os.path.dirname(f)
828 828 if not os.path.isdir(d):
829 829 makedirs(d, self.createmode)
830 830 if atomictemp:
831 831 return atomictempfile(f, mode, self.createmode)
832 832 if nlink > 1:
833 833 rename(mktempcopy(f), f)
834 834 fp = posixfile(f, mode)
835 835 if nlink == 0:
836 836 self._fixfilemode(f)
837 837 return fp
838 838
839 839 def symlink(self, src, dst):
840 840 self.audit_path(dst)
841 841 linkname = os.path.join(self.base, dst)
842 842 try:
843 843 os.unlink(linkname)
844 844 except OSError:
845 845 pass
846 846
847 847 dirname = os.path.dirname(linkname)
848 848 if not os.path.exists(dirname):
849 849 makedirs(dirname, self.createmode)
850 850
851 851 if self._can_symlink:
852 852 try:
853 853 os.symlink(src, linkname)
854 854 except OSError, err:
855 855 raise OSError(err.errno, _('could not symlink to %r: %s') %
856 856 (src, err.strerror), linkname)
857 857 else:
858 858 f = self(dst, "w")
859 859 f.write(src)
860 860 f.close()
861 861 self._fixfilemode(dst)
862 862
863 863 class chunkbuffer(object):
864 864 """Allow arbitrary sized chunks of data to be efficiently read from an
865 865 iterator over chunks of arbitrary size."""
866 866
867 867 def __init__(self, in_iter):
868 868 """in_iter is the iterator that's iterating over the input chunks.
869 869 targetsize is how big a buffer to try to maintain."""
870 870 self.iter = iter(in_iter)
871 871 self.buf = ''
872 872 self.targetsize = 2**16
873 873
874 874 def read(self, l):
875 875 """Read L bytes of data from the iterator of chunks of data.
876 876 Returns less than L bytes if the iterator runs dry."""
877 877 if l > len(self.buf) and self.iter:
878 878 # Clamp to a multiple of self.targetsize
879 879 targetsize = max(l, self.targetsize)
880 880 collector = cStringIO.StringIO()
881 881 collector.write(self.buf)
882 882 collected = len(self.buf)
883 883 for chunk in self.iter:
884 884 collector.write(chunk)
885 885 collected += len(chunk)
886 886 if collected >= targetsize:
887 887 break
888 888 if collected < targetsize:
889 889 self.iter = False
890 890 self.buf = collector.getvalue()
891 891 if len(self.buf) == l:
892 892 s, self.buf = str(self.buf), ''
893 893 else:
894 894 s, self.buf = self.buf[:l], buffer(self.buf, l)
895 895 return s
896 896
897 897 def filechunkiter(f, size=65536, limit=None):
898 898 """Create a generator that produces the data in the file size
899 899 (default 65536) bytes at a time, up to optional limit (default is
900 900 to read all data). Chunks may be less than size bytes if the
901 901 chunk is the last chunk in the file, or the file is a socket or
902 902 some other type of file that sometimes reads less data than is
903 903 requested."""
904 904 assert size >= 0
905 905 assert limit is None or limit >= 0
906 906 while True:
907 907 if limit is None: nbytes = size
908 908 else: nbytes = min(limit, size)
909 909 s = nbytes and f.read(nbytes)
910 910 if not s: break
911 911 if limit: limit -= len(s)
912 912 yield s
913 913
914 914 def makedate():
915 915 lt = time.localtime()
916 916 if lt[8] == 1 and time.daylight:
917 917 tz = time.altzone
918 918 else:
919 919 tz = time.timezone
920 920 return time.mktime(lt), tz
921 921
922 922 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
923 923 """represent a (unixtime, offset) tuple as a localized time.
924 924 unixtime is seconds since the epoch, and offset is the time zone's
925 925 number of seconds away from UTC. if timezone is false, do not
926 926 append time zone to string."""
927 927 t, tz = date or makedate()
928 928 if "%1" in format or "%2" in format:
929 929 sign = (tz > 0) and "-" or "+"
930 930 minutes = abs(tz) // 60
931 931 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
932 932 format = format.replace("%2", "%02d" % (minutes % 60))
933 933 s = time.strftime(format, time.gmtime(float(t) - tz))
934 934 return s
935 935
936 936 def shortdate(date=None):
937 937 """turn (timestamp, tzoff) tuple into iso 8631 date."""
938 938 return datestr(date, format='%Y-%m-%d')
939 939
940 940 def strdate(string, format, defaults=[]):
941 941 """parse a localized time string and return a (unixtime, offset) tuple.
942 942 if the string cannot be parsed, ValueError is raised."""
943 943 def timezone(string):
944 944 tz = string.split()[-1]
945 945 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
946 946 sign = (tz[0] == "+") and 1 or -1
947 947 hours = int(tz[1:3])
948 948 minutes = int(tz[3:5])
949 949 return -sign * (hours * 60 + minutes) * 60
950 950 if tz == "GMT" or tz == "UTC":
951 951 return 0
952 952 return None
953 953
954 954 # NOTE: unixtime = localunixtime + offset
955 955 offset, date = timezone(string), string
956 956 if offset != None:
957 957 date = " ".join(string.split()[:-1])
958 958
959 959 # add missing elements from defaults
960 960 for part in defaults:
961 961 found = [True for p in part if ("%"+p) in format]
962 962 if not found:
963 963 date += "@" + defaults[part]
964 964 format += "@%" + part[0]
965 965
966 966 timetuple = time.strptime(date, format)
967 967 localunixtime = int(calendar.timegm(timetuple))
968 968 if offset is None:
969 969 # local timezone
970 970 unixtime = int(time.mktime(timetuple))
971 971 offset = unixtime - localunixtime
972 972 else:
973 973 unixtime = localunixtime + offset
974 974 return unixtime, offset
975 975
976 976 def parsedate(date, formats=None, defaults=None):
977 977 """parse a localized date/time string and return a (unixtime, offset) tuple.
978 978
979 979 The date may be a "unixtime offset" string or in one of the specified
980 980 formats. If the date already is a (unixtime, offset) tuple, it is returned.
981 981 """
982 982 if not date:
983 983 return 0, 0
984 984 if isinstance(date, tuple) and len(date) == 2:
985 985 return date
986 986 if not formats:
987 987 formats = defaultdateformats
988 988 date = date.strip()
989 989 try:
990 990 when, offset = map(int, date.split(' '))
991 991 except ValueError:
992 992 # fill out defaults
993 993 if not defaults:
994 994 defaults = {}
995 995 now = makedate()
996 996 for part in "d mb yY HI M S".split():
997 997 if part not in defaults:
998 998 if part[0] in "HMS":
999 999 defaults[part] = "00"
1000 1000 else:
1001 1001 defaults[part] = datestr(now, "%" + part[0])
1002 1002
1003 1003 for format in formats:
1004 1004 try:
1005 1005 when, offset = strdate(date, format, defaults)
1006 1006 except (ValueError, OverflowError):
1007 1007 pass
1008 1008 else:
1009 1009 break
1010 1010 else:
1011 1011 raise Abort(_('invalid date: %r ') % date)
1012 1012 # validate explicit (probably user-specified) date and
1013 1013 # time zone offset. values must fit in signed 32 bits for
1014 1014 # current 32-bit linux runtimes. timezones go from UTC-12
1015 1015 # to UTC+14
1016 1016 if abs(when) > 0x7fffffff:
1017 1017 raise Abort(_('date exceeds 32 bits: %d') % when)
1018 1018 if offset < -50400 or offset > 43200:
1019 1019 raise Abort(_('impossible time zone offset: %d') % offset)
1020 1020 return when, offset
1021 1021
1022 1022 def matchdate(date):
1023 1023 """Return a function that matches a given date match specifier
1024 1024
1025 1025 Formats include:
1026 1026
1027 1027 '{date}' match a given date to the accuracy provided
1028 1028
1029 1029 '<{date}' on or before a given date
1030 1030
1031 1031 '>{date}' on or after a given date
1032 1032
1033 1033 """
1034 1034
1035 1035 def lower(date):
1036 1036 d = dict(mb="1", d="1")
1037 1037 return parsedate(date, extendeddateformats, d)[0]
1038 1038
1039 1039 def upper(date):
1040 1040 d = dict(mb="12", HI="23", M="59", S="59")
1041 1041 for days in "31 30 29".split():
1042 1042 try:
1043 1043 d["d"] = days
1044 1044 return parsedate(date, extendeddateformats, d)[0]
1045 1045 except:
1046 1046 pass
1047 1047 d["d"] = "28"
1048 1048 return parsedate(date, extendeddateformats, d)[0]
1049 1049
1050 1050 date = date.strip()
1051 1051 if date[0] == "<":
1052 1052 when = upper(date[1:])
1053 1053 return lambda x: x <= when
1054 1054 elif date[0] == ">":
1055 1055 when = lower(date[1:])
1056 1056 return lambda x: x >= when
1057 1057 elif date[0] == "-":
1058 1058 try:
1059 1059 days = int(date[1:])
1060 1060 except ValueError:
1061 1061 raise Abort(_("invalid day spec: %s") % date[1:])
1062 1062 when = makedate()[0] - days * 3600 * 24
1063 1063 return lambda x: x >= when
1064 1064 elif " to " in date:
1065 1065 a, b = date.split(" to ")
1066 1066 start, stop = lower(a), upper(b)
1067 1067 return lambda x: x >= start and x <= stop
1068 1068 else:
1069 1069 start, stop = lower(date), upper(date)
1070 1070 return lambda x: x >= start and x <= stop
1071 1071
1072 1072 def shortuser(user):
1073 1073 """Return a short representation of a user name or email address."""
1074 1074 f = user.find('@')
1075 1075 if f >= 0:
1076 1076 user = user[:f]
1077 1077 f = user.find('<')
1078 1078 if f >= 0:
1079 1079 user = user[f+1:]
1080 1080 f = user.find(' ')
1081 1081 if f >= 0:
1082 1082 user = user[:f]
1083 1083 f = user.find('.')
1084 1084 if f >= 0:
1085 1085 user = user[:f]
1086 1086 return user
1087 1087
1088 1088 def email(author):
1089 1089 '''get email of author.'''
1090 1090 r = author.find('>')
1091 1091 if r == -1: r = None
1092 1092 return author[author.find('<')+1:r]
1093 1093
1094 1094 def ellipsis(text, maxlength=400):
1095 1095 """Trim string to at most maxlength (default: 400) characters."""
1096 1096 if len(text) <= maxlength:
1097 1097 return text
1098 1098 else:
1099 1099 return "%s..." % (text[:maxlength-3])
1100 1100
1101 1101 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1102 1102 '''yield every hg repository under path, recursively.'''
1103 1103 def errhandler(err):
1104 1104 if err.filename == path:
1105 1105 raise err
1106 1106 if followsym and hasattr(os.path, 'samestat'):
1107 1107 def _add_dir_if_not_there(dirlst, dirname):
1108 1108 match = False
1109 1109 samestat = os.path.samestat
1110 1110 dirstat = os.stat(dirname)
1111 1111 for lstdirstat in dirlst:
1112 1112 if samestat(dirstat, lstdirstat):
1113 1113 match = True
1114 1114 break
1115 1115 if not match:
1116 1116 dirlst.append(dirstat)
1117 1117 return not match
1118 1118 else:
1119 1119 followsym = False
1120 1120
1121 1121 if (seen_dirs is None) and followsym:
1122 1122 seen_dirs = []
1123 1123 _add_dir_if_not_there(seen_dirs, path)
1124 1124 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1125 1125 dirs.sort()
1126 1126 if '.hg' in dirs:
1127 1127 yield root # found a repository
1128 1128 qroot = os.path.join(root, '.hg', 'patches')
1129 1129 if os.path.isdir(os.path.join(qroot, '.hg')):
1130 1130 yield qroot # we have a patch queue repo here
1131 1131 if recurse:
1132 1132 # avoid recursing inside the .hg directory
1133 1133 dirs.remove('.hg')
1134 1134 else:
1135 1135 dirs[:] = [] # don't descend further
1136 1136 elif followsym:
1137 1137 newdirs = []
1138 1138 for d in dirs:
1139 1139 fname = os.path.join(root, d)
1140 1140 if _add_dir_if_not_there(seen_dirs, fname):
1141 1141 if os.path.islink(fname):
1142 1142 for hgname in walkrepos(fname, True, seen_dirs):
1143 1143 yield hgname
1144 1144 else:
1145 1145 newdirs.append(d)
1146 1146 dirs[:] = newdirs
1147 1147
1148 1148 _rcpath = None
1149 1149
1150 1150 def os_rcpath():
1151 1151 '''return default os-specific hgrc search path'''
1152 1152 path = system_rcpath()
1153 1153 path.extend(user_rcpath())
1154 1154 path = [os.path.normpath(f) for f in path]
1155 1155 return path
1156 1156
1157 1157 def rcpath():
1158 1158 '''return hgrc search path. if env var HGRCPATH is set, use it.
1159 1159 for each item in path, if directory, use files ending in .rc,
1160 1160 else use item.
1161 1161 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1162 1162 if no HGRCPATH, use default os-specific path.'''
1163 1163 global _rcpath
1164 1164 if _rcpath is None:
1165 1165 if 'HGRCPATH' in os.environ:
1166 1166 _rcpath = []
1167 1167 for p in os.environ['HGRCPATH'].split(os.pathsep):
1168 1168 if not p: continue
1169 1169 p = expandpath(p)
1170 1170 if os.path.isdir(p):
1171 1171 for f, kind in osutil.listdir(p):
1172 1172 if f.endswith('.rc'):
1173 1173 _rcpath.append(os.path.join(p, f))
1174 1174 else:
1175 1175 _rcpath.append(p)
1176 1176 else:
1177 1177 _rcpath = os_rcpath()
1178 1178 return _rcpath
1179 1179
1180 1180 def bytecount(nbytes):
1181 1181 '''return byte count formatted as readable string, with units'''
1182 1182
1183 1183 units = (
1184 1184 (100, 1<<30, _('%.0f GB')),
1185 1185 (10, 1<<30, _('%.1f GB')),
1186 1186 (1, 1<<30, _('%.2f GB')),
1187 1187 (100, 1<<20, _('%.0f MB')),
1188 1188 (10, 1<<20, _('%.1f MB')),
1189 1189 (1, 1<<20, _('%.2f MB')),
1190 1190 (100, 1<<10, _('%.0f KB')),
1191 1191 (10, 1<<10, _('%.1f KB')),
1192 1192 (1, 1<<10, _('%.2f KB')),
1193 1193 (1, 1, _('%.0f bytes')),
1194 1194 )
1195 1195
1196 1196 for multiplier, divisor, format in units:
1197 1197 if nbytes >= divisor * multiplier:
1198 1198 return format % (nbytes / float(divisor))
1199 1199 return units[-1][2] % nbytes
1200 1200
1201 1201 def drop_scheme(scheme, path):
1202 1202 sc = scheme + ':'
1203 1203 if path.startswith(sc):
1204 1204 path = path[len(sc):]
1205 1205 if path.startswith('//'):
1206 1206 if scheme == 'file':
1207 1207 i = path.find('/', 2)
1208 1208 if i == -1:
1209 1209 return ''
1210 1210 # On Windows, absolute paths are rooted at the current drive
1211 1211 # root. On POSIX they are rooted at the file system root.
1212 1212 if os.name == 'nt':
1213 1213 droot = os.path.splitdrive(os.getcwd())[0] + '/'
1214 1214 path = os.path.join(droot, path[i+1:])
1215 1215 else:
1216 1216 path = path[i:]
1217 1217 else:
1218 1218 path = path[2:]
1219 1219 return path
1220 1220
1221 1221 def uirepr(s):
1222 1222 # Avoid double backslash in Windows path repr()
1223 1223 return repr(s).replace('\\\\', '\\')
1224 1224
1225 1225 def termwidth():
1226 1226 if 'COLUMNS' in os.environ:
1227 1227 try:
1228 1228 return int(os.environ['COLUMNS'])
1229 1229 except ValueError:
1230 1230 pass
1231 1231 try:
1232 1232 import termios, array, fcntl
1233 1233 for dev in (sys.stdout, sys.stdin):
1234 1234 try:
1235 1235 try:
1236 1236 fd = dev.fileno()
1237 1237 except AttributeError:
1238 1238 continue
1239 1239 if not os.isatty(fd):
1240 1240 continue
1241 1241 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
1242 1242 return array.array('h', arri)[1]
1243 1243 except ValueError:
1244 1244 pass
1245 1245 except IOError, e:
1246 1246 if e[0] == errno.EINVAL:
1247 1247 pass
1248 1248 else:
1249 1249 raise
1250 1250 except ImportError:
1251 1251 pass
1252 1252 return 80
1253 1253
1254 1254 def wrap(line, hangindent, width=None):
1255 1255 if width is None:
1256 1256 width = termwidth() - 2
1257 1257 if width <= hangindent:
1258 1258 # adjust for weird terminal size
1259 1259 width = max(78, hangindent + 1)
1260 1260 padding = '\n' + ' ' * hangindent
1261 1261 # To avoid corrupting multi-byte characters in line, we must wrap
1262 1262 # a Unicode string instead of a bytestring.
1263 1263 try:
1264 1264 u = line.decode(encoding.encoding)
1265 1265 w = padding.join(textwrap.wrap(u, width=width - hangindent))
1266 1266 return w.encode(encoding.encoding)
1267 1267 except UnicodeDecodeError:
1268 1268 return padding.join(textwrap.wrap(line, width=width - hangindent))
1269 1269
1270 1270 def iterlines(iterator):
1271 1271 for chunk in iterator:
1272 1272 for line in chunk.splitlines():
1273 1273 yield line
1274 1274
1275 1275 def expandpath(path):
1276 1276 return os.path.expanduser(os.path.expandvars(path))
1277
1278 def hgcmd():
1279 """Return the command used to execute current hg
1280
1281 This is different from hgexecutable() because on Windows we want
1282 to avoid things opening new shell windows like batch files, so we
1283 get either the python call or current executable.
1284 """
1285 if main_is_frozen():
1286 return [sys.executable]
1287 return gethgcmd()
@@ -1,361 +1,364 b''
1 1 # windows.py - Windows utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9 import osutil, error
10 10 import errno, msvcrt, os, re, sys, random, subprocess
11 11
12 12 nulldev = 'NUL:'
13 13 umask = 002
14 14
15 15 # wrap osutil.posixfile to provide friendlier exceptions
16 16 def posixfile(name, mode='r', buffering=-1):
17 17 try:
18 18 return osutil.posixfile(name, mode, buffering)
19 19 except WindowsError, err:
20 20 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
21 21 posixfile.__doc__ = osutil.posixfile.__doc__
22 22
23 23 class winstdout(object):
24 24 '''stdout on windows misbehaves if sent through a pipe'''
25 25
26 26 def __init__(self, fp):
27 27 self.fp = fp
28 28
29 29 def __getattr__(self, key):
30 30 return getattr(self.fp, key)
31 31
32 32 def close(self):
33 33 try:
34 34 self.fp.close()
35 35 except: pass
36 36
37 37 def write(self, s):
38 38 try:
39 39 # This is workaround for "Not enough space" error on
40 40 # writing large size of data to console.
41 41 limit = 16000
42 42 l = len(s)
43 43 start = 0
44 44 self.softspace = 0;
45 45 while start < l:
46 46 end = start + limit
47 47 self.fp.write(s[start:end])
48 48 start = end
49 49 except IOError, inst:
50 50 if inst.errno != 0: raise
51 51 self.close()
52 52 raise IOError(errno.EPIPE, 'Broken pipe')
53 53
54 54 def flush(self):
55 55 try:
56 56 return self.fp.flush()
57 57 except IOError, inst:
58 58 if inst.errno != errno.EINVAL: raise
59 59 self.close()
60 60 raise IOError(errno.EPIPE, 'Broken pipe')
61 61
62 62 sys.stdout = winstdout(sys.stdout)
63 63
64 64 def _is_win_9x():
65 65 '''return true if run on windows 95, 98 or me.'''
66 66 try:
67 67 return sys.getwindowsversion()[3] == 1
68 68 except AttributeError:
69 69 return 'command' in os.environ.get('comspec', '')
70 70
71 71 def openhardlinks():
72 72 return not _is_win_9x() and "win32api" in globals()
73 73
74 74 def system_rcpath():
75 75 try:
76 76 return system_rcpath_win32()
77 77 except:
78 78 return [r'c:\mercurial\mercurial.ini']
79 79
80 80 def user_rcpath():
81 81 '''return os-specific hgrc search path to the user dir'''
82 82 try:
83 83 path = user_rcpath_win32()
84 84 except:
85 85 home = os.path.expanduser('~')
86 86 path = [os.path.join(home, 'mercurial.ini'),
87 87 os.path.join(home, '.hgrc')]
88 88 userprofile = os.environ.get('USERPROFILE')
89 89 if userprofile:
90 90 path.append(os.path.join(userprofile, 'mercurial.ini'))
91 91 path.append(os.path.join(userprofile, '.hgrc'))
92 92 return path
93 93
94 94 def parse_patch_output(output_line):
95 95 """parses the output produced by patch and returns the filename"""
96 96 pf = output_line[14:]
97 97 if pf[0] == '`':
98 98 pf = pf[1:-1] # Remove the quotes
99 99 return pf
100 100
101 101 def sshargs(sshcmd, host, user, port):
102 102 '''Build argument list for ssh or Plink'''
103 103 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
104 104 args = user and ("%s@%s" % (user, host)) or host
105 105 return port and ("%s %s %s" % (args, pflag, port)) or args
106 106
107 107 def testpid(pid):
108 108 '''return False if pid dead, True if running or not known'''
109 109 return True
110 110
111 111 def set_flags(f, l, x):
112 112 pass
113 113
114 114 def set_binary(fd):
115 115 # When run without console, pipes may expose invalid
116 116 # fileno(), usually set to -1.
117 117 if hasattr(fd, 'fileno') and fd.fileno() >= 0:
118 118 msvcrt.setmode(fd.fileno(), os.O_BINARY)
119 119
120 120 def pconvert(path):
121 121 return '/'.join(path.split(os.sep))
122 122
123 123 def localpath(path):
124 124 return path.replace('/', '\\')
125 125
126 126 def normpath(path):
127 127 return pconvert(os.path.normpath(path))
128 128
129 129 def realpath(path):
130 130 '''
131 131 Returns the true, canonical file system path equivalent to the given
132 132 path.
133 133 '''
134 134 # TODO: There may be a more clever way to do this that also handles other,
135 135 # less common file systems.
136 136 return os.path.normpath(os.path.normcase(os.path.realpath(path)))
137 137
138 138 def samestat(s1, s2):
139 139 return False
140 140
141 141 # A sequence of backslashes is special iff it precedes a double quote:
142 142 # - if there's an even number of backslashes, the double quote is not
143 143 # quoted (i.e. it ends the quoted region)
144 144 # - if there's an odd number of backslashes, the double quote is quoted
145 145 # - in both cases, every pair of backslashes is unquoted into a single
146 146 # backslash
147 147 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
148 148 # So, to quote a string, we must surround it in double quotes, double
149 149 # the number of backslashes that preceed double quotes and add another
150 150 # backslash before every double quote (being careful with the double
151 151 # quote we've appended to the end)
152 152 _quotere = None
153 153 def shellquote(s):
154 154 global _quotere
155 155 if _quotere is None:
156 156 _quotere = re.compile(r'(\\*)("|\\$)')
157 157 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
158 158
159 159 def quotecommand(cmd):
160 160 """Build a command string suitable for os.popen* calls."""
161 161 # The extra quotes are needed because popen* runs the command
162 162 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
163 163 return '"' + cmd + '"'
164 164
165 165 def popen(command, mode='r'):
166 166 # Work around "popen spawned process may not write to stdout
167 167 # under windows"
168 168 # http://bugs.python.org/issue1366
169 169 command += " 2> %s" % nulldev
170 170 return os.popen(quotecommand(command), mode)
171 171
172 172 def explain_exit(code):
173 173 return _("exited with status %d") % code, code
174 174
175 175 # if you change this stub into a real check, please try to implement the
176 176 # username and groupname functions above, too.
177 177 def isowner(st):
178 178 return True
179 179
180 180 def find_exe(command):
181 181 '''Find executable for command searching like cmd.exe does.
182 182 If command is a basename then PATH is searched for command.
183 183 PATH isn't searched if command is an absolute or relative path.
184 184 An extension from PATHEXT is found and added if not present.
185 185 If command isn't found None is returned.'''
186 186 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
187 187 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
188 188 if os.path.splitext(command)[1].lower() in pathexts:
189 189 pathexts = ['']
190 190
191 191 def findexisting(pathcommand):
192 192 'Will append extension (if needed) and return existing file'
193 193 for ext in pathexts:
194 194 executable = pathcommand + ext
195 195 if os.path.exists(executable):
196 196 return executable
197 197 return None
198 198
199 199 if os.sep in command:
200 200 return findexisting(command)
201 201
202 202 for path in os.environ.get('PATH', '').split(os.pathsep):
203 203 executable = findexisting(os.path.join(path, command))
204 204 if executable is not None:
205 205 return executable
206 206 return findexisting(os.path.expanduser(os.path.expandvars(command)))
207 207
208 208 def set_signal_handler():
209 209 try:
210 210 set_signal_handler_win32()
211 211 except NameError:
212 212 pass
213 213
214 214 def statfiles(files):
215 215 '''Stat each file in files and yield stat or None if file does not exist.
216 216 Cluster and cache stat per directory to minimize number of OS stat calls.'''
217 217 ncase = os.path.normcase
218 218 sep = os.sep
219 219 dircache = {} # dirname -> filename -> status | None if file does not exist
220 220 for nf in files:
221 221 nf = ncase(nf)
222 222 dir, base = os.path.split(nf)
223 223 if not dir:
224 224 dir = '.'
225 225 cache = dircache.get(dir, None)
226 226 if cache is None:
227 227 try:
228 228 dmap = dict([(ncase(n), s)
229 229 for n, k, s in osutil.listdir(dir, True)])
230 230 except OSError, err:
231 231 # handle directory not found in Python version prior to 2.5
232 232 # Python <= 2.4 returns native Windows code 3 in errno
233 233 # Python >= 2.5 returns ENOENT and adds winerror field
234 234 # EINVAL is raised if dir is not a directory.
235 235 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
236 236 errno.ENOTDIR):
237 237 raise
238 238 dmap = {}
239 239 cache = dircache.setdefault(dir, dmap)
240 240 yield cache.get(base, None)
241 241
242 242 def getuser():
243 243 '''return name of current user'''
244 244 raise error.Abort(_('user name not available - set USERNAME '
245 245 'environment variable'))
246 246
247 247 def username(uid=None):
248 248 """Return the name of the user with the given uid.
249 249
250 250 If uid is None, return the name of the current user."""
251 251 return None
252 252
253 253 def groupname(gid=None):
254 254 """Return the name of the group with the given gid.
255 255
256 256 If gid is None, return the name of the current group."""
257 257 return None
258 258
259 259 def _removedirs(name):
260 260 """special version of os.removedirs that does not remove symlinked
261 261 directories or junction points if they actually contain files"""
262 262 if osutil.listdir(name):
263 263 return
264 264 os.rmdir(name)
265 265 head, tail = os.path.split(name)
266 266 if not tail:
267 267 head, tail = os.path.split(head)
268 268 while head and tail:
269 269 try:
270 270 if osutil.listdir(head):
271 271 return
272 272 os.rmdir(head)
273 273 except:
274 274 break
275 275 head, tail = os.path.split(head)
276 276
277 277 def unlink(f):
278 278 """unlink and remove the directory if it is empty"""
279 279 os.unlink(f)
280 280 # try removing directories that might now be empty
281 281 try:
282 282 _removedirs(os.path.dirname(f))
283 283 except OSError:
284 284 pass
285 285
286 286 def rename(src, dst):
287 287 '''atomically rename file src to dst, replacing dst if it exists'''
288 288 try:
289 289 os.rename(src, dst)
290 290 except OSError, err: # FIXME: check err (EEXIST ?)
291 291
292 292 # On windows, rename to existing file is not allowed, so we
293 293 # must delete destination first. But if a file is open, unlink
294 294 # schedules it for delete but does not delete it. Rename
295 295 # happens immediately even for open files, so we rename
296 296 # destination to a temporary name, then delete that. Then
297 297 # rename is safe to do.
298 298 # The temporary name is chosen at random to avoid the situation
299 299 # where a file is left lying around from a previous aborted run.
300 300 # The usual race condition this introduces can't be avoided as
301 301 # we need the name to rename into, and not the file itself. Due
302 302 # to the nature of the operation however, any races will at worst
303 303 # lead to the rename failing and the current operation aborting.
304 304
305 305 def tempname(prefix):
306 306 for tries in xrange(10):
307 307 temp = '%s-%08x' % (prefix, random.randint(0, 0xffffffff))
308 308 if not os.path.exists(temp):
309 309 return temp
310 310 raise IOError, (errno.EEXIST, "No usable temporary filename found")
311 311
312 312 temp = tempname(dst)
313 313 os.rename(dst, temp)
314 314 try:
315 315 os.unlink(temp)
316 316 except:
317 317 # Some rude AV-scanners on Windows may cause the unlink to
318 318 # fail. Not aborting here just leaks the temp file, whereas
319 319 # aborting at this point may leave serious inconsistencies.
320 320 # Ideally, we would notify the user here.
321 321 pass
322 322 os.rename(src, dst)
323 323
324 324 def spawndetached(args):
325 325 # No standard library function really spawns a fully detached
326 326 # process under win32 because they allocate pipes or other objects
327 327 # to handle standard streams communications. Passing these objects
328 328 # to the child process requires handle inheritance to be enabled
329 329 # which makes really detached processes impossible.
330 330 class STARTUPINFO:
331 331 dwFlags = subprocess.STARTF_USESHOWWINDOW
332 332 hStdInput = None
333 333 hStdOutput = None
334 334 hStdError = None
335 335 wShowWindow = subprocess.SW_HIDE
336 336
337 337 args = subprocess.list2cmdline(args)
338 338 # Not running the command in shell mode makes python26 hang when
339 339 # writing to hgweb output socket.
340 340 comspec = os.environ.get("COMSPEC", "cmd.exe")
341 341 args = comspec + " /c " + args
342 342 hp, ht, pid, tid = subprocess.CreateProcess(
343 343 None, args,
344 344 # no special security
345 345 None, None,
346 346 # Do not inherit handles
347 347 0,
348 348 # DETACHED_PROCESS
349 349 0x00000008,
350 350 os.environ,
351 351 os.getcwd(),
352 352 STARTUPINFO())
353 353 return pid
354 354
355 def gethgcmd():
356 return [sys.executable] + sys.argv[:1]
357
355 358 try:
356 359 # override functions with win32 versions if possible
357 360 from win32 import *
358 361 except ImportError:
359 362 pass
360 363
361 364 expandglobs = True
General Comments 0
You need to be logged in to leave comments. Login now