~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Aaron Bentley
  • Date: 2005-09-29 04:59:47 UTC
  • mto: (1393.1.21) (1185.14.1)
  • mto: This revision was merged to the branch mainline in revision 1396.
  • Revision ID: aaron.bentley@utoronto.ca-20050929045947-ff08a7f6578f9657
Conflict handling where OTHER is deleted

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
# along with this program; if not, write to the Free Software
17
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
18
 
19
 
import os, types, re, time, errno
20
 
from stat import S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE
 
19
import os, types, re, time, errno, sys
 
20
from stat import (S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE,
 
21
        S_ISCHR, S_ISBLK, S_ISFIFO, S_ISSOCK)
21
22
 
22
 
from errors import bailout, BzrError
23
 
from trace import mutter
 
23
from bzrlib.errors import BzrError
 
24
from bzrlib.trace import mutter
24
25
import bzrlib
 
26
from shutil import copyfile
25
27
 
26
28
def make_readonly(filename):
27
29
    """Make a filename read-only."""
37
39
    os.chmod(filename, mod)
38
40
 
39
41
 
40
 
_QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])')
 
42
_QUOTE_RE = None
 
43
 
 
44
 
41
45
def quotefn(f):
42
 
    """Return shell-quoted filename"""
43
 
    ## We could be a bit more terse by using double-quotes etc
44
 
    f = _QUOTE_RE.sub(r'\\\1', f)
45
 
    if f[0] == '~':
46
 
        f[0:1] = r'\~' 
47
 
    return 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
48
59
 
49
60
 
50
61
def file_kind(f):
55
66
        return 'directory'
56
67
    elif S_ISLNK(mode):
57
68
        return 'symlink'
58
 
    else:
59
 
        raise BzrError("can't handle file kind with mode %o of %r" % (mode, f)) 
 
69
    elif S_ISCHR(mode):
 
70
        return 'chardev'
 
71
    elif S_ISBLK(mode):
 
72
        return 'block'
 
73
    elif S_ISFIFO(mode):
 
74
        return 'fifo'
 
75
    elif S_ISSOCK(mode):
 
76
        return 'socket'
 
77
    else:
 
78
        return 'unknown'
 
79
 
 
80
 
 
81
def kind_marker(kind):
 
82
    if kind == 'file':
 
83
        return ''
 
84
    elif kind == 'directory':
 
85
        return '/'
 
86
    elif kind == 'symlink':
 
87
        return '@'
 
88
    else:
 
89
        raise BzrError('invalid file kind %r' % kind)
 
90
 
 
91
 
 
92
 
 
93
def backup_file(fn):
 
94
    """Copy a file to a backup.
 
95
 
 
96
    Backups are named in GNU-style, with a ~ suffix.
 
97
 
 
98
    If the file is already a backup, it's not copied.
 
99
    """
 
100
    import os
 
101
    if fn[-1] == '~':
 
102
        return
 
103
    bfn = fn + '~'
 
104
 
 
105
    inf = file(fn, 'rb')
 
106
    try:
 
107
        content = inf.read()
 
108
    finally:
 
109
        inf.close()
 
110
    
 
111
    outf = file(bfn, 'wb')
 
112
    try:
 
113
        outf.write(content)
 
114
    finally:
 
115
        outf.close()
 
116
 
 
117
def rename(path_from, path_to):
 
118
    """Basically the same as os.rename() just special for win32"""
 
119
    if sys.platform == 'win32':
 
120
        try:
 
121
            os.remove(path_to)
 
122
        except OSError, e:
 
123
            if e.errno != e.ENOENT:
 
124
                raise
 
125
    os.rename(path_from, path_to)
 
126
 
 
127
 
60
128
 
61
129
 
62
130
 
77
145
        return False
78
146
 
79
147
 
 
148
def is_inside(dir, fname):
 
149
    """True if fname is inside dir.
 
150
    
 
151
    The parameters should typically be passed to os.path.normpath first, so
 
152
    that . and .. and repeated slashes are eliminated, and the separators
 
153
    are canonical for the platform.
 
154
    
 
155
    The empty string as a dir name is taken as top-of-tree and matches 
 
156
    everything.
 
157
    
 
158
    >>> is_inside('src', 'src/foo.c')
 
159
    True
 
160
    >>> is_inside('src', 'srccontrol')
 
161
    False
 
162
    >>> is_inside('src', 'src/a/a/a/foo.c')
 
163
    True
 
164
    >>> is_inside('foo.c', 'foo.c')
 
165
    True
 
166
    >>> is_inside('foo.c', '')
 
167
    False
 
168
    >>> is_inside('', 'foo.c')
 
169
    True
 
170
    """
 
171
    # XXX: Most callers of this can actually do something smarter by 
 
172
    # looking at the inventory
 
173
    if dir == fname:
 
174
        return True
 
175
    
 
176
    if dir == '':
 
177
        return True
 
178
    
 
179
    if dir[-1] != os.sep:
 
180
        dir += os.sep
 
181
    
 
182
    return fname.startswith(dir)
 
183
 
 
184
 
 
185
def is_inside_any(dir_list, fname):
 
186
    """True if fname is inside any of given dirs."""
 
187
    for dirname in dir_list:
 
188
        if is_inside(dirname, fname):
 
189
            return True
 
190
    else:
 
191
        return False
 
192
 
 
193
 
80
194
def pumpfile(fromfile, tofile):
81
195
    """Copy contents of one file to another."""
82
196
    tofile.write(fromfile.read())
84
198
 
85
199
def uuid():
86
200
    """Return a new UUID"""
87
 
    
88
 
    ## XXX: Could alternatively read /proc/sys/kernel/random/uuid on
89
 
    ## Linux, but we need something portable for other systems;
90
 
    ## preferably an implementation in Python.
91
201
    try:
92
 
        return chomp(file('/proc/sys/kernel/random/uuid').readline())
 
202
        return file('/proc/sys/kernel/random/uuid').readline().rstrip('\n')
93
203
    except IOError:
94
204
        return chomp(os.popen('uuidgen').readline())
95
205
 
96
206
 
97
 
def chomp(s):
98
 
    if s and (s[-1] == '\n'):
99
 
        return s[:-1]
100
 
    else:
101
 
        return s
102
 
 
103
 
 
104
207
def sha_file(f):
105
208
    import sha
106
 
    ## TODO: Maybe read in chunks to handle big files
107
209
    if hasattr(f, 'tell'):
108
210
        assert f.tell() == 0
109
211
    s = sha.new()
110
 
    s.update(f.read())
 
212
    BUFSIZE = 128<<10
 
213
    while True:
 
214
        b = f.read(BUFSIZE)
 
215
        if not b:
 
216
            break
 
217
        s.update(b)
111
218
    return s.hexdigest()
112
219
 
113
220
 
175
282
    return realname, (username + '@' + socket.gethostname())
176
283
 
177
284
 
178
 
def _get_user_id():
 
285
def _get_user_id(branch):
179
286
    """Return the full user id from a file or environment variable.
180
287
 
181
 
    TODO: Allow taking this from a file in the branch directory too
182
 
    for per-branch ids."""
 
288
    e.g. "John Hacker <jhacker@foo.org>"
 
289
 
 
290
    branch
 
291
        A branch to use for a per-branch configuration, or None.
 
292
 
 
293
    The following are searched in order:
 
294
 
 
295
    1. $BZREMAIL
 
296
    2. .bzr/email for this branch.
 
297
    3. ~/.bzr.conf/email
 
298
    4. $EMAIL
 
299
    """
183
300
    v = os.environ.get('BZREMAIL')
184
301
    if v:
185
302
        return v.decode(bzrlib.user_encoding)
 
303
 
 
304
    if branch:
 
305
        try:
 
306
            return (branch.controlfile("email", "r") 
 
307
                    .read()
 
308
                    .decode(bzrlib.user_encoding)
 
309
                    .rstrip("\r\n"))
 
310
        except IOError, e:
 
311
            if e.errno != errno.ENOENT:
 
312
                raise
 
313
        except BzrError, e:
 
314
            pass
186
315
    
187
316
    try:
188
317
        return (open(os.path.join(config_dir(), "email"))
200
329
        return None
201
330
 
202
331
 
203
 
def username():
 
332
def username(branch):
204
333
    """Return email-style username.
205
334
 
206
335
    Something similar to 'Martin Pool <mbp@sourcefrog.net>'
207
336
 
208
337
    TODO: Check it's reasonably well-formed.
209
338
    """
210
 
    v = _get_user_id()
 
339
    v = _get_user_id(branch)
211
340
    if v:
212
341
        return v
213
342
    
218
347
        return email
219
348
 
220
349
 
221
 
_EMAIL_RE = re.compile(r'[\w+.-]+@[\w+.-]+')
222
 
def user_email():
 
350
def user_email(branch):
223
351
    """Return just the email component of a username."""
224
 
    e = _get_user_id()
 
352
    e = _get_user_id(branch)
225
353
    if e:
226
 
        m = _EMAIL_RE.search(e)
 
354
        m = re.search(r'[\w+.-]+@[\w+.-]+', e)
227
355
        if not m:
228
 
            bailout("%r doesn't seem to contain a reasonable email address" % e)
 
356
            raise BzrError("%r doesn't seem to contain a reasonable email address" % e)
229
357
        return m.group(0)
230
358
 
231
359
    return _auto_user_id()[1]
234
362
 
235
363
def compare_files(a, b):
236
364
    """Returns true if equal in contents"""
237
 
    # TODO: don't read the whole thing in one go.
238
365
    BUFSIZE = 4096
239
366
    while True:
240
367
        ai = a.read(BUFSIZE)
274
401
        tt = time.localtime(t)
275
402
        offset = local_time_offset(t)
276
403
    else:
277
 
        bailout("unsupported timezone format %r",
278
 
                ['options are "utc", "original", "local"'])
 
404
        raise BzrError("unsupported timezone format %r" % timezone,
 
405
                       ['options are "utc", "original", "local"'])
279
406
 
280
407
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
281
408
            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
290
417
    """Return size of given open file."""
291
418
    return os.fstat(f.fileno())[ST_SIZE]
292
419
 
293
 
 
294
 
if hasattr(os, 'urandom'): # python 2.4 and later
 
420
# Define rand_bytes based on platform.
 
421
try:
 
422
    # Python 2.4 and later have os.urandom,
 
423
    # but it doesn't work on some arches
 
424
    os.urandom(1)
295
425
    rand_bytes = os.urandom
296
 
else:
297
 
    # FIXME: No good on non-Linux
298
 
    _rand_file = file('/dev/urandom', 'rb')
299
 
    rand_bytes = _rand_file.read
300
 
 
 
426
except (NotImplementedError, AttributeError):
 
427
    # If python doesn't have os.urandom, or it doesn't work,
 
428
    # then try to first pull random data from /dev/urandom
 
429
    if os.path.exists("/dev/urandom"):
 
430
        rand_bytes = file('/dev/urandom', 'rb').read
 
431
    # Otherwise, use this hack as a last resort
 
432
    else:
 
433
        # not well seeded, but better than nothing
 
434
        def rand_bytes(n):
 
435
            import random
 
436
            s = ''
 
437
            while n:
 
438
                s += chr(random.randint(0, 255))
 
439
                n -= 1
 
440
            return s
301
441
 
302
442
## TODO: We could later have path objects that remember their list
303
443
## decomposition (might be too tricksy though.)
316
456
    >>> splitpath('a/../b')
317
457
    Traceback (most recent call last):
318
458
    ...
319
 
    BzrError: ("sorry, '..' not allowed in path", [])
 
459
    BzrError: sorry, '..' not allowed in path
320
460
    """
321
461
    assert isinstance(p, types.StringTypes)
322
462
 
327
467
    rps = []
328
468
    for f in ps:
329
469
        if f == '..':
330
 
            bailout("sorry, %r not allowed in path" % f)
 
470
            raise BzrError("sorry, %r not allowed in path" % f)
331
471
        elif (f == '.') or (f == ''):
332
472
            pass
333
473
        else:
338
478
    assert isinstance(p, list)
339
479
    for f in p:
340
480
        if (f == '..') or (f == None) or (f == ''):
341
 
            bailout("sorry, %r not allowed in path" % f)
 
481
            raise BzrError("sorry, %r not allowed in path" % f)
342
482
    return os.path.join(*p)
343
483
 
344
484
 
349
489
        return os.path.join(p1, p2)
350
490
    
351
491
 
352
 
def extern_command(cmd, ignore_errors = False):
353
 
    mutter('external command: %s' % `cmd`)
354
 
    if os.system(cmd):
355
 
        if not ignore_errors:
356
 
            bailout('command failed')
357
 
 
 
492
def _read_config_value(name):
 
493
    """Read a config value from the file ~/.bzr.conf/<name>
 
494
    Return None if the file does not exist"""
 
495
    try:
 
496
        f = file(os.path.join(config_dir(), name), "r")
 
497
        return f.read().decode(bzrlib.user_encoding).rstrip("\r\n")
 
498
    except IOError, e:
 
499
        if e.errno == errno.ENOENT:
 
500
            return None
 
501
        raise
 
502
 
 
503
def hardlinks_good():
 
504
    return sys.platform not in ('win32', 'cygwin', 'darwin')
 
505
 
 
506
def link_or_copy(src, dest):
 
507
    """Hardlink a file, or copy it if it can't be hardlinked."""
 
508
    if not hardlinks_good():
 
509
        copyfile(src, dest)
 
510
        return
 
511
    try:
 
512
        os.link(src, dest)
 
513
    except (OSError, IOError), e:
 
514
        if e.errno != errno.EXDEV:
 
515
            raise
 
516
        copyfile(src, dest)