~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Aaron Bentley
  • Date: 2005-08-10 15:05:26 UTC
  • mto: (1092.1.41) (1185.3.4) (974.1.47)
  • mto: This revision was merged to the branch mainline in revision 1110.
  • Revision ID: abentley@panoramicfeedback.com-20050810150526-468a7e2e3dc299e4
Switched to using a set of interesting file_ids instead of SourceFile attribute

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
 
19
import os, types, re, time, errno, sys
20
20
from stat import S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE
21
21
 
22
 
from errors import bailout, BzrError
23
 
from trace import mutter
 
22
from bzrlib.errors import BzrError
 
23
from bzrlib.trace import mutter
24
24
import bzrlib
25
25
 
26
26
def make_readonly(filename):
38
38
 
39
39
 
40
40
_QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])')
 
41
 
 
42
_SLASH_RE = re.compile(r'[\\/]+')
 
43
 
41
44
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
 
45
    """Return a quoted filename filename
 
46
 
 
47
    This previously used backslash quoting, but that works poorly on
 
48
    Windows."""
 
49
    # TODO: I'm not really sure this is the best format either.x
 
50
    if _QUOTE_RE.search(f):
 
51
        return '"' + f + '"'
 
52
    else:
 
53
        return f
48
54
 
49
55
 
50
56
def file_kind(f):
56
62
    elif S_ISLNK(mode):
57
63
        return 'symlink'
58
64
    else:
59
 
        raise BzrError("can't handle file kind with mode %o of %r" % (mode, f)) 
 
65
        raise BzrError("can't handle file kind with mode %o of %r" % (mode, f))
 
66
 
 
67
 
 
68
def kind_marker(kind):
 
69
    if kind == 'file':
 
70
        return ''
 
71
    elif kind == 'directory':
 
72
        return '/'
 
73
    elif kind == 'symlink':
 
74
        return '@'
 
75
    else:
 
76
        raise BzrError('invalid file kind %r' % kind)
 
77
 
 
78
 
 
79
 
 
80
def backup_file(fn):
 
81
    """Copy a file to a backup.
 
82
 
 
83
    Backups are named in GNU-style, with a ~ suffix.
 
84
 
 
85
    If the file is already a backup, it's not copied.
 
86
    """
 
87
    import os
 
88
    if fn[-1] == '~':
 
89
        return
 
90
    bfn = fn + '~'
 
91
 
 
92
    inf = file(fn, 'rb')
 
93
    try:
 
94
        content = inf.read()
 
95
    finally:
 
96
        inf.close()
 
97
    
 
98
    outf = file(bfn, 'wb')
 
99
    try:
 
100
        outf.write(content)
 
101
    finally:
 
102
        outf.close()
 
103
 
 
104
def rename(path_from, path_to):
 
105
    """Basically the same as os.rename() just special for win32"""
 
106
    if sys.platform == 'win32':
 
107
        try:
 
108
            os.remove(path_to)
 
109
        except OSError, e:
 
110
            if e.errno != e.ENOENT:
 
111
                raise
 
112
    os.rename(path_from, path_to)
 
113
 
 
114
 
60
115
 
61
116
 
62
117
 
77
132
        return False
78
133
 
79
134
 
 
135
def is_inside(dir, fname):
 
136
    """True if fname is inside dir.
 
137
    
 
138
    The parameters should typically be passed to os.path.normpath first, so
 
139
    that . and .. and repeated slashes are eliminated, and the separators
 
140
    are canonical for the platform.
 
141
    
 
142
    >>> is_inside('src', 'src/foo.c')
 
143
    True
 
144
    >>> is_inside('src', 'srccontrol')
 
145
    False
 
146
    >>> is_inside('src', 'src/a/a/a/foo.c')
 
147
    True
 
148
    >>> is_inside('foo.c', 'foo.c')
 
149
    True
 
150
    """
 
151
    # XXX: Most callers of this can actually do something smarter by 
 
152
    # looking at the inventory
 
153
 
 
154
    if dir == fname:
 
155
        return True
 
156
    
 
157
    if dir[-1] != os.sep:
 
158
        dir += os.sep
 
159
    
 
160
    return fname.startswith(dir)
 
161
 
 
162
 
 
163
def is_inside_any(dir_list, fname):
 
164
    """True if fname is inside any of given dirs."""
 
165
    for dirname in dir_list:
 
166
        if is_inside(dirname, fname):
 
167
            return True
 
168
    else:
 
169
        return False
 
170
 
 
171
 
80
172
def pumpfile(fromfile, tofile):
81
173
    """Copy contents of one file to another."""
82
174
    tofile.write(fromfile.read())
218
310
    if e:
219
311
        m = _EMAIL_RE.search(e)
220
312
        if not m:
221
 
            bailout("%r doesn't seem to contain a reasonable email address" % e)
 
313
            raise BzrError("%r doesn't seem to contain a reasonable email address" % e)
222
314
        return m.group(0)
223
315
 
224
316
    return _auto_user_id()[1]
266
358
        tt = time.localtime(t)
267
359
        offset = local_time_offset(t)
268
360
    else:
269
 
        bailout("unsupported timezone format %r",
 
361
        raise BzrError("unsupported timezone format %r",
270
362
                ['options are "utc", "original", "local"'])
271
363
 
272
364
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
285
377
 
286
378
if hasattr(os, 'urandom'): # python 2.4 and later
287
379
    rand_bytes = os.urandom
 
380
elif sys.platform == 'linux2':
 
381
    rand_bytes = file('/dev/urandom', 'rb').read
288
382
else:
289
 
    # FIXME: No good on non-Linux
290
 
    _rand_file = file('/dev/urandom', 'rb')
291
 
    rand_bytes = _rand_file.read
 
383
    # not well seeded, but better than nothing
 
384
    def rand_bytes(n):
 
385
        import random
 
386
        s = ''
 
387
        while n:
 
388
            s += chr(random.randint(0, 255))
 
389
            n -= 1
 
390
        return s
292
391
 
293
392
 
294
393
## TODO: We could later have path objects that remember their list
308
407
    >>> splitpath('a/../b')
309
408
    Traceback (most recent call last):
310
409
    ...
311
 
    BzrError: ("sorry, '..' not allowed in path", [])
 
410
    BzrError: sorry, '..' not allowed in path
312
411
    """
313
412
    assert isinstance(p, types.StringTypes)
314
413
 
319
418
    rps = []
320
419
    for f in ps:
321
420
        if f == '..':
322
 
            bailout("sorry, %r not allowed in path" % f)
 
421
            raise BzrError("sorry, %r not allowed in path" % f)
323
422
        elif (f == '.') or (f == ''):
324
423
            pass
325
424
        else:
330
429
    assert isinstance(p, list)
331
430
    for f in p:
332
431
        if (f == '..') or (f == None) or (f == ''):
333
 
            bailout("sorry, %r not allowed in path" % f)
 
432
            raise BzrError("sorry, %r not allowed in path" % f)
334
433
    return os.path.join(*p)
335
434
 
336
435
 
345
444
    mutter('external command: %s' % `cmd`)
346
445
    if os.system(cmd):
347
446
        if not ignore_errors:
348
 
            bailout('command failed')
349
 
 
 
447
            raise BzrError('command failed')
 
448
 
 
449
 
 
450
def _read_config_value(name):
 
451
    """Read a config value from the file ~/.bzr.conf/<name>
 
452
    Return None if the file does not exist"""
 
453
    try:
 
454
        f = file(os.path.join(config_dir(), name), "r")
 
455
        return f.read().decode(bzrlib.user_encoding).rstrip("\r\n")
 
456
    except IOError, e:
 
457
        if e.errno == errno.ENOENT:
 
458
            return None
 
459
        raise
 
460
 
 
461
 
 
462
def _get_editor():
 
463
    """Return a sequence of possible editor binaries for the current platform"""
 
464
    e = _read_config_value("editor")
 
465
    if e is not None:
 
466
        yield e
 
467
        
 
468
    if os.name == "windows":
 
469
        yield "notepad.exe"
 
470
    elif os.name == "posix":
 
471
        try:
 
472
            yield os.environ["EDITOR"]
 
473
        except KeyError:
 
474
            yield "/usr/bin/vi"
 
475
 
 
476
 
 
477
def _run_editor(filename):
 
478
    """Try to execute an editor to edit the commit message. Returns True on success,
 
479
    False on failure"""
 
480
    for e in _get_editor():
 
481
        x = os.spawnvp(os.P_WAIT, e, (e, filename))
 
482
        if x == 0:
 
483
            return True
 
484
        elif x == 127:
 
485
            continue
 
486
        else:
 
487
            break
 
488
    raise BzrError("Could not start any editor. Please specify $EDITOR or use ~/.bzr.conf/editor")
 
489
    return False
 
490
                          
 
491
 
 
492
def get_text_message(infotext, ignoreline = "default"):
 
493
    import tempfile
 
494
    
 
495
    if ignoreline == "default":
 
496
        ignoreline = "-- This line and the following will be ignored --"
 
497
        
 
498
    try:
 
499
        tmp_fileno, msgfilename = tempfile.mkstemp()
 
500
        msgfile = os.close(tmp_fileno)
 
501
        if infotext is not None and infotext != "":
 
502
            hasinfo = True
 
503
            msgfile = file(msgfilename, "w")
 
504
            msgfile.write("\n\n%s\n\n%s" % (ignoreline, infotext))
 
505
            msgfile.close()
 
506
        else:
 
507
            hasinfo = False
 
508
 
 
509
        if not _run_editor(msgfilename):
 
510
            return None
 
511
        
 
512
        started = False
 
513
        msg = []
 
514
        lastline, nlines = 0, 0
 
515
        for line in file(msgfilename, "r"):
 
516
            stripped_line = line.strip()
 
517
            # strip empty line before the log message starts
 
518
            if not started:
 
519
                if stripped_line != "":
 
520
                    started = True
 
521
                else:
 
522
                    continue
 
523
            # check for the ignore line only if there
 
524
            # is additional information at the end
 
525
            if hasinfo and stripped_line == ignoreline:
 
526
                break
 
527
            nlines += 1
 
528
            # keep track of the last line that had some content
 
529
            if stripped_line != "":
 
530
                lastline = nlines
 
531
            msg.append(line)
 
532
            
 
533
        if len(msg) == 0:
 
534
            return None
 
535
        # delete empty lines at the end
 
536
        del msg[lastline:]
 
537
        # add a newline at the end, if needed
 
538
        if not msg[-1].endswith("\n"):
 
539
            return "%s%s" % ("".join(msg), "\n")
 
540
        else:
 
541
            return "".join(msg)
 
542
    finally:
 
543
        # delete the msg file in any case
 
544
        try: os.unlink(msgfilename)
 
545
        except IOError: pass