/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

- more refactoring and tests of commandline

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
from shutil import copyfile
 
20
from stat import (S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE,
 
21
                  S_ISCHR, S_ISBLK, S_ISFIFO, S_ISSOCK)
 
22
from cStringIO import StringIO
 
23
import errno
 
24
import os
 
25
import re
 
26
import sha
 
27
import string
 
28
import sys
 
29
import time
 
30
import types
 
31
 
 
32
import bzrlib
 
33
from bzrlib.config import config_dir, _get_user_id
 
34
from bzrlib.errors import BzrError
 
35
from bzrlib.trace import mutter
 
36
 
 
37
 
 
38
def make_readonly(filename):
 
39
    """Make a filename read-only."""
 
40
    mod = os.stat(filename).st_mode
 
41
    mod = mod & 0777555
 
42
    os.chmod(filename, mod)
 
43
 
 
44
 
 
45
def make_writable(filename):
 
46
    mod = os.stat(filename).st_mode
 
47
    mod = mod | 0200
 
48
    os.chmod(filename, mod)
 
49
 
 
50
 
 
51
_QUOTE_RE = None
 
52
 
 
53
 
 
54
def quotefn(f):
 
55
    """Return a quoted filename filename
 
56
 
 
57
    This previously used backslash quoting, but that works poorly on
 
58
    Windows."""
 
59
    # TODO: I'm not really sure this is the best format either.x
 
60
    global _QUOTE_RE
 
61
    if _QUOTE_RE == None:
 
62
        _QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/\\_~-])')
 
63
        
 
64
    if _QUOTE_RE.search(f):
 
65
        return '"' + f + '"'
 
66
    else:
 
67
        return f
 
68
 
 
69
 
 
70
def file_kind(f):
 
71
    mode = os.lstat(f)[ST_MODE]
 
72
    if S_ISREG(mode):
 
73
        return 'file'
 
74
    elif S_ISDIR(mode):
 
75
        return 'directory'
 
76
    elif S_ISLNK(mode):
 
77
        return 'symlink'
 
78
    elif S_ISCHR(mode):
 
79
        return 'chardev'
 
80
    elif S_ISBLK(mode):
 
81
        return 'block'
 
82
    elif S_ISFIFO(mode):
 
83
        return 'fifo'
 
84
    elif S_ISSOCK(mode):
 
85
        return 'socket'
 
86
    else:
 
87
        return 'unknown'
 
88
 
 
89
 
 
90
def kind_marker(kind):
 
91
    if kind == 'file':
 
92
        return ''
 
93
    elif kind == 'directory':
 
94
        return '/'
 
95
    elif kind == 'symlink':
 
96
        return '@'
 
97
    else:
 
98
        raise BzrError('invalid file kind %r' % kind)
 
99
 
 
100
def lexists(f):
 
101
    try:
 
102
        if hasattr(os, 'lstat'):
 
103
            os.lstat(f)
 
104
        else:
 
105
            os.stat(f)
 
106
        return True
 
107
    except OSError,e:
 
108
        if e.errno == errno.ENOENT:
 
109
            return False;
 
110
        else:
 
111
            raise BzrError("lstat/stat of (%r): %r" % (f, e))
 
112
 
 
113
def normalizepath(f):
 
114
    if hasattr(os.path, 'realpath'):
 
115
        F = os.path.realpath
 
116
    else:
 
117
        F = os.path.abspath
 
118
    [p,e] = os.path.split(f)
 
119
    if e == "" or e == "." or e == "..":
 
120
        return F(f)
 
121
    else:
 
122
        return os.path.join(F(p), e)
 
123
    
 
124
 
 
125
def backup_file(fn):
 
126
    """Copy a file to a backup.
 
127
 
 
128
    Backups are named in GNU-style, with a ~ suffix.
 
129
 
 
130
    If the file is already a backup, it's not copied.
 
131
    """
 
132
    if fn[-1] == '~':
 
133
        return
 
134
    bfn = fn + '~'
 
135
 
 
136
    if has_symlinks() and os.path.islink(fn):
 
137
        target = os.readlink(fn)
 
138
        os.symlink(target, bfn)
 
139
        return
 
140
    inf = file(fn, 'rb')
 
141
    try:
 
142
        content = inf.read()
 
143
    finally:
 
144
        inf.close()
 
145
    
 
146
    outf = file(bfn, 'wb')
 
147
    try:
 
148
        outf.write(content)
 
149
    finally:
 
150
        outf.close()
 
151
 
 
152
if os.name == 'nt':
 
153
    import shutil
 
154
    rename = shutil.move
 
155
else:
 
156
    rename = os.rename
 
157
 
 
158
 
 
159
def isdir(f):
 
160
    """True if f is an accessible directory."""
 
161
    try:
 
162
        return S_ISDIR(os.lstat(f)[ST_MODE])
 
163
    except OSError:
 
164
        return False
 
165
 
 
166
 
 
167
def isfile(f):
 
168
    """True if f is a regular file."""
 
169
    try:
 
170
        return S_ISREG(os.lstat(f)[ST_MODE])
 
171
    except OSError:
 
172
        return False
 
173
 
 
174
def islink(f):
 
175
    """True if f is a symlink."""
 
176
    try:
 
177
        return S_ISLNK(os.lstat(f)[ST_MODE])
 
178
    except OSError:
 
179
        return False
 
180
 
 
181
def is_inside(dir, fname):
 
182
    """True if fname is inside dir.
 
183
    
 
184
    The parameters should typically be passed to os.path.normpath first, so
 
185
    that . and .. and repeated slashes are eliminated, and the separators
 
186
    are canonical for the platform.
 
187
    
 
188
    The empty string as a dir name is taken as top-of-tree and matches 
 
189
    everything.
 
190
    
 
191
    >>> is_inside('src', os.path.join('src', 'foo.c'))
 
192
    True
 
193
    >>> is_inside('src', 'srccontrol')
 
194
    False
 
195
    >>> is_inside('src', os.path.join('src', 'a', 'a', 'a', 'foo.c'))
 
196
    True
 
197
    >>> is_inside('foo.c', 'foo.c')
 
198
    True
 
199
    >>> is_inside('foo.c', '')
 
200
    False
 
201
    >>> is_inside('', 'foo.c')
 
202
    True
 
203
    """
 
204
    # XXX: Most callers of this can actually do something smarter by 
 
205
    # looking at the inventory
 
206
    if dir == fname:
 
207
        return True
 
208
    
 
209
    if dir == '':
 
210
        return True
 
211
 
 
212
    if dir[-1] != os.sep:
 
213
        dir += os.sep
 
214
 
 
215
    return fname.startswith(dir)
 
216
 
 
217
 
 
218
def is_inside_any(dir_list, fname):
 
219
    """True if fname is inside any of given dirs."""
 
220
    for dirname in dir_list:
 
221
        if is_inside(dirname, fname):
 
222
            return True
 
223
    else:
 
224
        return False
 
225
 
 
226
 
 
227
def pumpfile(fromfile, tofile):
 
228
    """Copy contents of one file to another."""
 
229
    tofile.write(fromfile.read())
 
230
 
 
231
 
 
232
def sha_file(f):
 
233
    if hasattr(f, 'tell'):
 
234
        assert f.tell() == 0
 
235
    s = sha.new()
 
236
    BUFSIZE = 128<<10
 
237
    while True:
 
238
        b = f.read(BUFSIZE)
 
239
        if not b:
 
240
            break
 
241
        s.update(b)
 
242
    return s.hexdigest()
 
243
 
 
244
 
 
245
 
 
246
def sha_strings(strings):
 
247
    """Return the sha-1 of concatenation of strings"""
 
248
    s = sha.new()
 
249
    map(s.update, strings)
 
250
    return s.hexdigest()
 
251
 
 
252
 
 
253
def sha_string(f):
 
254
    s = sha.new()
 
255
    s.update(f)
 
256
    return s.hexdigest()
 
257
 
 
258
 
 
259
def fingerprint_file(f):
 
260
    s = sha.new()
 
261
    b = f.read()
 
262
    s.update(b)
 
263
    size = len(b)
 
264
    return {'size': size,
 
265
            'sha1': s.hexdigest()}
 
266
 
 
267
 
 
268
def compare_files(a, b):
 
269
    """Returns true if equal in contents"""
 
270
    BUFSIZE = 4096
 
271
    while True:
 
272
        ai = a.read(BUFSIZE)
 
273
        bi = b.read(BUFSIZE)
 
274
        if ai != bi:
 
275
            return False
 
276
        if ai == '':
 
277
            return True
 
278
 
 
279
 
 
280
def local_time_offset(t=None):
 
281
    """Return offset of local zone from GMT, either at present or at time t."""
 
282
    # python2.3 localtime() can't take None
 
283
    if t == None:
 
284
        t = time.time()
 
285
        
 
286
    if time.localtime(t).tm_isdst and time.daylight:
 
287
        return -time.altzone
 
288
    else:
 
289
        return -time.timezone
 
290
 
 
291
    
 
292
def format_date(t, offset=0, timezone='original'):
 
293
    ## TODO: Perhaps a global option to use either universal or local time?
 
294
    ## Or perhaps just let people set $TZ?
 
295
    assert isinstance(t, float)
 
296
    
 
297
    if timezone == 'utc':
 
298
        tt = time.gmtime(t)
 
299
        offset = 0
 
300
    elif timezone == 'original':
 
301
        if offset == None:
 
302
            offset = 0
 
303
        tt = time.gmtime(t + offset)
 
304
    elif timezone == 'local':
 
305
        tt = time.localtime(t)
 
306
        offset = local_time_offset(t)
 
307
    else:
 
308
        raise BzrError("unsupported timezone format %r" % timezone,
 
309
                       ['options are "utc", "original", "local"'])
 
310
 
 
311
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
 
312
            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
 
313
 
 
314
 
 
315
def compact_date(when):
 
316
    return time.strftime('%Y%m%d%H%M%S', time.gmtime(when))
 
317
    
 
318
 
 
319
 
 
320
def filesize(f):
 
321
    """Return size of given open file."""
 
322
    return os.fstat(f.fileno())[ST_SIZE]
 
323
 
 
324
# Define rand_bytes based on platform.
 
325
try:
 
326
    # Python 2.4 and later have os.urandom,
 
327
    # but it doesn't work on some arches
 
328
    os.urandom(1)
 
329
    rand_bytes = os.urandom
 
330
except (NotImplementedError, AttributeError):
 
331
    # If python doesn't have os.urandom, or it doesn't work,
 
332
    # then try to first pull random data from /dev/urandom
 
333
    if os.path.exists("/dev/urandom"):
 
334
        rand_bytes = file('/dev/urandom', 'rb').read
 
335
    # Otherwise, use this hack as a last resort
 
336
    else:
 
337
        # not well seeded, but better than nothing
 
338
        def rand_bytes(n):
 
339
            import random
 
340
            s = ''
 
341
            while n:
 
342
                s += chr(random.randint(0, 255))
 
343
                n -= 1
 
344
            return s
 
345
 
 
346
## TODO: We could later have path objects that remember their list
 
347
## decomposition (might be too tricksy though.)
 
348
 
 
349
def splitpath(p):
 
350
    """Turn string into list of parts.
 
351
 
 
352
    >>> splitpath('a')
 
353
    ['a']
 
354
    >>> splitpath('a/b')
 
355
    ['a', 'b']
 
356
    >>> splitpath('a/./b')
 
357
    ['a', 'b']
 
358
    >>> splitpath('a/.b')
 
359
    ['a', '.b']
 
360
    >>> splitpath('a/../b')
 
361
    Traceback (most recent call last):
 
362
    ...
 
363
    BzrError: sorry, '..' not allowed in path
 
364
    """
 
365
    assert isinstance(p, types.StringTypes)
 
366
 
 
367
    # split on either delimiter because people might use either on
 
368
    # Windows
 
369
    ps = re.split(r'[\\/]', p)
 
370
 
 
371
    rps = []
 
372
    for f in ps:
 
373
        if f == '..':
 
374
            raise BzrError("sorry, %r not allowed in path" % f)
 
375
        elif (f == '.') or (f == ''):
 
376
            pass
 
377
        else:
 
378
            rps.append(f)
 
379
    return rps
 
380
 
 
381
def joinpath(p):
 
382
    assert isinstance(p, list)
 
383
    for f in p:
 
384
        if (f == '..') or (f == None) or (f == ''):
 
385
            raise BzrError("sorry, %r not allowed in path" % f)
 
386
    return os.path.join(*p)
 
387
 
 
388
 
 
389
def appendpath(p1, p2):
 
390
    if p1 == '':
 
391
        return p2
 
392
    else:
 
393
        return os.path.join(p1, p2)
 
394
    
 
395
 
 
396
def split_lines(s):
 
397
    """Split s into lines, but without removing the newline characters."""
 
398
    return StringIO(s).readlines()
 
399
 
 
400
 
 
401
def hardlinks_good():
 
402
    return sys.platform not in ('win32', 'cygwin', 'darwin')
 
403
 
 
404
 
 
405
def link_or_copy(src, dest):
 
406
    """Hardlink a file, or copy it if it can't be hardlinked."""
 
407
    if not hardlinks_good():
 
408
        copyfile(src, dest)
 
409
        return
 
410
    try:
 
411
        os.link(src, dest)
 
412
    except (OSError, IOError), e:
 
413
        if e.errno != errno.EXDEV:
 
414
            raise
 
415
        copyfile(src, dest)
 
416
 
 
417
 
 
418
def has_symlinks():
 
419
    if hasattr(os, 'symlink'):
 
420
        return True
 
421
    else:
 
422
        return False
 
423
        
 
424
 
 
425
def contains_whitespace(s):
 
426
    """True if there are any whitespace characters in s."""
 
427
    for ch in string.whitespace:
 
428
        if ch in s:
 
429
            return True
 
430
    else:
 
431
        return False
 
432
 
 
433
 
 
434
def contains_linebreaks(s):
 
435
    """True if there is any vertical whitespace in s."""
 
436
    for ch in '\f\n\r':
 
437
        if ch in s:
 
438
            return True
 
439
    else:
 
440
        return False