/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Martin Pool
  • Date: 2005-09-13 06:24:27 UTC
  • Revision ID: mbp@sourcefrog.net-20050913062426-330489777cdc9099
- more progress on fetch on top of weaves

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Bazaar-NG -- distributed version control
 
2
 
 
3
# Copyright (C) 2005 by Canonical Ltd
 
4
 
 
5
# This program is free software; you can redistribute it and/or modify
 
6
# it under the terms of the GNU General Public License as published by
 
7
# the Free Software Foundation; either version 2 of the License, or
 
8
# (at your option) any later version.
 
9
 
 
10
# This program is distributed in the hope that it will be useful,
 
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
# GNU General Public License for more details.
 
14
 
 
15
# You should have received a copy of the GNU General Public License
 
16
# along with this program; if not, write to the Free Software
 
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
18
 
 
19
import os, types, re, time, errno, sys
 
20
from cStringIO import StringIO
 
21
 
 
22
from stat import S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE
 
23
 
 
24
from bzrlib.errors import BzrError
 
25
from bzrlib.trace import mutter
 
26
import bzrlib
 
27
 
 
28
def make_readonly(filename):
 
29
    """Make a filename read-only."""
 
30
    # TODO: probably needs to be fixed for windows
 
31
    mod = os.stat(filename).st_mode
 
32
    mod = mod & 0777555
 
33
    os.chmod(filename, mod)
 
34
 
 
35
 
 
36
def make_writable(filename):
 
37
    mod = os.stat(filename).st_mode
 
38
    mod = mod | 0200
 
39
    os.chmod(filename, mod)
 
40
 
 
41
 
 
42
_QUOTE_RE = None
 
43
 
 
44
 
 
45
def quotefn(f):
 
46
    """Return a quoted filename filename
 
47
 
 
48
    This previously used backslash quoting, but that works poorly on
 
49
    Windows."""
 
50
    # TODO: I'm not really sure this is the best format either.x
 
51
    global _QUOTE_RE
 
52
    if _QUOTE_RE == None:
 
53
        _QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])')
 
54
        
 
55
    if _QUOTE_RE.search(f):
 
56
        return '"' + f + '"'
 
57
    else:
 
58
        return f
 
59
 
 
60
 
 
61
def file_kind(f):
 
62
    mode = os.lstat(f)[ST_MODE]
 
63
    if S_ISREG(mode):
 
64
        return 'file'
 
65
    elif S_ISDIR(mode):
 
66
        return 'directory'
 
67
    elif S_ISLNK(mode):
 
68
        return 'symlink'
 
69
    else:
 
70
        raise BzrError("can't handle file kind with mode %o of %r" % (mode, f))
 
71
 
 
72
 
 
73
def kind_marker(kind):
 
74
    if kind == 'file':
 
75
        return ''
 
76
    elif kind == 'directory':
 
77
        return '/'
 
78
    elif kind == 'symlink':
 
79
        return '@'
 
80
    else:
 
81
        raise BzrError('invalid file kind %r' % kind)
 
82
 
 
83
 
 
84
 
 
85
def backup_file(fn):
 
86
    """Copy a file to a backup.
 
87
 
 
88
    Backups are named in GNU-style, with a ~ suffix.
 
89
 
 
90
    If the file is already a backup, it's not copied.
 
91
    """
 
92
    import os
 
93
    if fn[-1] == '~':
 
94
        return
 
95
    bfn = fn + '~'
 
96
 
 
97
    inf = file(fn, 'rb')
 
98
    try:
 
99
        content = inf.read()
 
100
    finally:
 
101
        inf.close()
 
102
    
 
103
    outf = file(bfn, 'wb')
 
104
    try:
 
105
        outf.write(content)
 
106
    finally:
 
107
        outf.close()
 
108
 
 
109
def rename(path_from, path_to):
 
110
    """Basically the same as os.rename() just special for win32"""
 
111
    if sys.platform == 'win32':
 
112
        try:
 
113
            os.remove(path_to)
 
114
        except OSError, e:
 
115
            if e.errno != e.ENOENT:
 
116
                raise
 
117
    os.rename(path_from, path_to)
 
118
 
 
119
 
 
120
 
 
121
 
 
122
 
 
123
def isdir(f):
 
124
    """True if f is an accessible directory."""
 
125
    try:
 
126
        return S_ISDIR(os.lstat(f)[ST_MODE])
 
127
    except OSError:
 
128
        return False
 
129
 
 
130
 
 
131
 
 
132
def isfile(f):
 
133
    """True if f is a regular file."""
 
134
    try:
 
135
        return S_ISREG(os.lstat(f)[ST_MODE])
 
136
    except OSError:
 
137
        return False
 
138
 
 
139
 
 
140
def is_inside(dir, fname):
 
141
    """True if fname is inside dir.
 
142
    
 
143
    The parameters should typically be passed to os.path.normpath first, so
 
144
    that . and .. and repeated slashes are eliminated, and the separators
 
145
    are canonical for the platform.
 
146
    
 
147
    The empty string as a dir name is taken as top-of-tree and matches 
 
148
    everything.
 
149
    
 
150
    >>> is_inside('src', 'src/foo.c')
 
151
    True
 
152
    >>> is_inside('src', 'srccontrol')
 
153
    False
 
154
    >>> is_inside('src', 'src/a/a/a/foo.c')
 
155
    True
 
156
    >>> is_inside('foo.c', 'foo.c')
 
157
    True
 
158
    >>> is_inside('foo.c', '')
 
159
    False
 
160
    >>> is_inside('', 'foo.c')
 
161
    True
 
162
    """
 
163
    # XXX: Most callers of this can actually do something smarter by 
 
164
    # looking at the inventory
 
165
    if dir == fname:
 
166
        return True
 
167
    
 
168
    if dir == '':
 
169
        return True
 
170
    
 
171
    if dir[-1] != os.sep:
 
172
        dir += os.sep
 
173
    
 
174
    return fname.startswith(dir)
 
175
 
 
176
 
 
177
def is_inside_any(dir_list, fname):
 
178
    """True if fname is inside any of given dirs."""
 
179
    for dirname in dir_list:
 
180
        if is_inside(dirname, fname):
 
181
            return True
 
182
    else:
 
183
        return False
 
184
 
 
185
 
 
186
def pumpfile(fromfile, tofile):
 
187
    """Copy contents of one file to another."""
 
188
    tofile.write(fromfile.read())
 
189
 
 
190
 
 
191
def uuid():
 
192
    """Return a new UUID"""
 
193
    try:
 
194
        return file('/proc/sys/kernel/random/uuid').readline().rstrip('\n')
 
195
    except IOError:
 
196
        return chomp(os.popen('uuidgen').readline())
 
197
 
 
198
 
 
199
def sha_file(f):
 
200
    import sha
 
201
    if hasattr(f, 'tell'):
 
202
        assert f.tell() == 0
 
203
    s = sha.new()
 
204
    BUFSIZE = 128<<10
 
205
    while True:
 
206
        b = f.read(BUFSIZE)
 
207
        if not b:
 
208
            break
 
209
        s.update(b)
 
210
    return s.hexdigest()
 
211
 
 
212
 
 
213
def sha_string(f):
 
214
    import sha
 
215
    s = sha.new()
 
216
    s.update(f)
 
217
    return s.hexdigest()
 
218
 
 
219
 
 
220
 
 
221
def fingerprint_file(f):
 
222
    import sha
 
223
    s = sha.new()
 
224
    b = f.read()
 
225
    s.update(b)
 
226
    size = len(b)
 
227
    return {'size': size,
 
228
            'sha1': s.hexdigest()}
 
229
 
 
230
 
 
231
def config_dir():
 
232
    """Return per-user configuration directory.
 
233
 
 
234
    By default this is ~/.bzr.conf/
 
235
    
 
236
    TODO: Global option --config-dir to override this.
 
237
    """
 
238
    return os.path.expanduser("~/.bzr.conf")
 
239
 
 
240
 
 
241
def _auto_user_id():
 
242
    """Calculate automatic user identification.
 
243
 
 
244
    Returns (realname, email).
 
245
 
 
246
    Only used when none is set in the environment or the id file.
 
247
 
 
248
    This previously used the FQDN as the default domain, but that can
 
249
    be very slow on machines where DNS is broken.  So now we simply
 
250
    use the hostname.
 
251
    """
 
252
    import socket
 
253
 
 
254
    # XXX: Any good way to get real user name on win32?
 
255
 
 
256
    try:
 
257
        import pwd
 
258
        uid = os.getuid()
 
259
        w = pwd.getpwuid(uid)
 
260
        gecos = w.pw_gecos.decode(bzrlib.user_encoding)
 
261
        username = w.pw_name.decode(bzrlib.user_encoding)
 
262
        comma = gecos.find(',')
 
263
        if comma == -1:
 
264
            realname = gecos
 
265
        else:
 
266
            realname = gecos[:comma]
 
267
        if not realname:
 
268
            realname = username
 
269
 
 
270
    except ImportError:
 
271
        import getpass
 
272
        realname = username = getpass.getuser().decode(bzrlib.user_encoding)
 
273
 
 
274
    return realname, (username + '@' + socket.gethostname())
 
275
 
 
276
 
 
277
def _get_user_id(branch):
 
278
    """Return the full user id from a file or environment variable.
 
279
 
 
280
    e.g. "John Hacker <jhacker@foo.org>"
 
281
 
 
282
    branch
 
283
        A branch to use for a per-branch configuration, or None.
 
284
 
 
285
    The following are searched in order:
 
286
 
 
287
    1. $BZREMAIL
 
288
    2. .bzr/email for this branch.
 
289
    3. ~/.bzr.conf/email
 
290
    4. $EMAIL
 
291
    """
 
292
    v = os.environ.get('BZREMAIL')
 
293
    if v:
 
294
        return v.decode(bzrlib.user_encoding)
 
295
 
 
296
    if branch:
 
297
        try:
 
298
            return (branch.controlfile("email", "r") 
 
299
                    .read()
 
300
                    .decode(bzrlib.user_encoding)
 
301
                    .rstrip("\r\n"))
 
302
        except IOError, e:
 
303
            if e.errno != errno.ENOENT:
 
304
                raise
 
305
        except BzrError, e:
 
306
            pass
 
307
    
 
308
    try:
 
309
        return (open(os.path.join(config_dir(), "email"))
 
310
                .read()
 
311
                .decode(bzrlib.user_encoding)
 
312
                .rstrip("\r\n"))
 
313
    except IOError, e:
 
314
        if e.errno != errno.ENOENT:
 
315
            raise e
 
316
 
 
317
    v = os.environ.get('EMAIL')
 
318
    if v:
 
319
        return v.decode(bzrlib.user_encoding)
 
320
    else:    
 
321
        return None
 
322
 
 
323
 
 
324
def username(branch):
 
325
    """Return email-style username.
 
326
 
 
327
    Something similar to 'Martin Pool <mbp@sourcefrog.net>'
 
328
 
 
329
    TODO: Check it's reasonably well-formed.
 
330
    """
 
331
    v = _get_user_id(branch)
 
332
    if v:
 
333
        return v
 
334
    
 
335
    name, email = _auto_user_id()
 
336
    if name:
 
337
        return '%s <%s>' % (name, email)
 
338
    else:
 
339
        return email
 
340
 
 
341
 
 
342
def user_email(branch):
 
343
    """Return just the email component of a username."""
 
344
    e = _get_user_id(branch)
 
345
    if e:
 
346
        m = re.search(r'[\w+.-]+@[\w+.-]+', e)
 
347
        if not m:
 
348
            raise BzrError("%r doesn't seem to contain a reasonable email address" % e)
 
349
        return m.group(0)
 
350
 
 
351
    return _auto_user_id()[1]
 
352
    
 
353
 
 
354
 
 
355
def compare_files(a, b):
 
356
    """Returns true if equal in contents"""
 
357
    BUFSIZE = 4096
 
358
    while True:
 
359
        ai = a.read(BUFSIZE)
 
360
        bi = b.read(BUFSIZE)
 
361
        if ai != bi:
 
362
            return False
 
363
        if ai == '':
 
364
            return True
 
365
 
 
366
 
 
367
 
 
368
def local_time_offset(t=None):
 
369
    """Return offset of local zone from GMT, either at present or at time t."""
 
370
    # python2.3 localtime() can't take None
 
371
    if t == None:
 
372
        t = time.time()
 
373
        
 
374
    if time.localtime(t).tm_isdst and time.daylight:
 
375
        return -time.altzone
 
376
    else:
 
377
        return -time.timezone
 
378
 
 
379
    
 
380
def format_date(t, offset=0, timezone='original'):
 
381
    ## TODO: Perhaps a global option to use either universal or local time?
 
382
    ## Or perhaps just let people set $TZ?
 
383
    assert isinstance(t, float)
 
384
    
 
385
    if timezone == 'utc':
 
386
        tt = time.gmtime(t)
 
387
        offset = 0
 
388
    elif timezone == 'original':
 
389
        if offset == None:
 
390
            offset = 0
 
391
        tt = time.gmtime(t + offset)
 
392
    elif timezone == 'local':
 
393
        tt = time.localtime(t)
 
394
        offset = local_time_offset(t)
 
395
    else:
 
396
        raise BzrError("unsupported timezone format %r" % timezone,
 
397
                       ['options are "utc", "original", "local"'])
 
398
 
 
399
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
 
400
            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
 
401
 
 
402
 
 
403
def compact_date(when):
 
404
    return time.strftime('%Y%m%d%H%M%S', time.gmtime(when))
 
405
    
 
406
 
 
407
 
 
408
def filesize(f):
 
409
    """Return size of given open file."""
 
410
    return os.fstat(f.fileno())[ST_SIZE]
 
411
 
 
412
 
 
413
if hasattr(os, 'urandom'): # python 2.4 and later
 
414
    rand_bytes = os.urandom
 
415
elif sys.platform == 'linux2':
 
416
    rand_bytes = file('/dev/urandom', 'rb').read
 
417
else:
 
418
    # not well seeded, but better than nothing
 
419
    def rand_bytes(n):
 
420
        import random
 
421
        s = ''
 
422
        while n:
 
423
            s += chr(random.randint(0, 255))
 
424
            n -= 1
 
425
        return s
 
426
 
 
427
 
 
428
## TODO: We could later have path objects that remember their list
 
429
## decomposition (might be too tricksy though.)
 
430
 
 
431
def splitpath(p):
 
432
    """Turn string into list of parts.
 
433
 
 
434
    >>> splitpath('a')
 
435
    ['a']
 
436
    >>> splitpath('a/b')
 
437
    ['a', 'b']
 
438
    >>> splitpath('a/./b')
 
439
    ['a', 'b']
 
440
    >>> splitpath('a/.b')
 
441
    ['a', '.b']
 
442
    >>> splitpath('a/../b')
 
443
    Traceback (most recent call last):
 
444
    ...
 
445
    BzrError: sorry, '..' not allowed in path
 
446
    """
 
447
    assert isinstance(p, types.StringTypes)
 
448
 
 
449
    # split on either delimiter because people might use either on
 
450
    # Windows
 
451
    ps = re.split(r'[\\/]', p)
 
452
 
 
453
    rps = []
 
454
    for f in ps:
 
455
        if f == '..':
 
456
            raise BzrError("sorry, %r not allowed in path" % f)
 
457
        elif (f == '.') or (f == ''):
 
458
            pass
 
459
        else:
 
460
            rps.append(f)
 
461
    return rps
 
462
 
 
463
def joinpath(p):
 
464
    assert isinstance(p, list)
 
465
    for f in p:
 
466
        if (f == '..') or (f == None) or (f == ''):
 
467
            raise BzrError("sorry, %r not allowed in path" % f)
 
468
    return os.path.join(*p)
 
469
 
 
470
 
 
471
def appendpath(p1, p2):
 
472
    if p1 == '':
 
473
        return p2
 
474
    else:
 
475
        return os.path.join(p1, p2)
 
476
    
 
477
 
 
478
def extern_command(cmd, ignore_errors = False):
 
479
    mutter('external command: %s' % `cmd`)
 
480
    if os.system(cmd):
 
481
        if not ignore_errors:
 
482
            raise BzrError('command failed')
 
483
 
 
484
 
 
485
def _read_config_value(name):
 
486
    """Read a config value from the file ~/.bzr.conf/<name>
 
487
    Return None if the file does not exist"""
 
488
    try:
 
489
        f = file(os.path.join(config_dir(), name), "r")
 
490
        return f.read().decode(bzrlib.user_encoding).rstrip("\r\n")
 
491
    except IOError, e:
 
492
        if e.errno == errno.ENOENT:
 
493
            return None
 
494
        raise
 
495
 
 
496
 
 
497
 
 
498
def split_lines(s):
 
499
    """Split s into lines, but without removing the newline characters."""
 
500
    return StringIO(s).readlines()
 
501