~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Aaron Bentley
  • Date: 2005-10-03 16:53:39 UTC
  • mto: (1185.25.1)
  • mto: This revision was merged to the branch mainline in revision 1419.
  • Revision ID: abentley@panoramicfeedback.com-20051003165339-9ee4d484477fd164
Ignored user-installed plugins

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Bazaar-NG -- distributed version control
2
 
 
 
2
#
3
3
# Copyright (C) 2005 by Canonical Ltd
4
 
 
 
4
#
5
5
# This program is free software; you can redistribute it and/or modify
6
6
# it under the terms of the GNU General Public License as published by
7
7
# the Free Software Foundation; either version 2 of the License, or
8
8
# (at your option) any later version.
9
 
 
 
9
#
10
10
# This program is distributed in the hope that it will be useful,
11
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
13
# GNU General Public License for more details.
14
 
 
 
14
#
15
15
# You should have received a copy of the GNU General Public License
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, sys
20
 
from stat import S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE
 
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 sys
 
28
import time
 
29
import types
21
30
 
 
31
import bzrlib
22
32
from bzrlib.errors import BzrError
23
33
from bzrlib.trace import mutter
24
 
import bzrlib
 
34
 
25
35
 
26
36
def make_readonly(filename):
27
37
    """Make a filename read-only."""
28
 
    # TODO: probably needs to be fixed for windows
29
38
    mod = os.stat(filename).st_mode
30
39
    mod = mod & 0777555
31
40
    os.chmod(filename, mod)
48
57
    # TODO: I'm not really sure this is the best format either.x
49
58
    global _QUOTE_RE
50
59
    if _QUOTE_RE == None:
51
 
        _QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])')
 
60
        _QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/\\_~-])')
52
61
        
53
62
    if _QUOTE_RE.search(f):
54
63
        return '"' + f + '"'
64
73
        return 'directory'
65
74
    elif S_ISLNK(mode):
66
75
        return 'symlink'
 
76
    elif S_ISCHR(mode):
 
77
        return 'chardev'
 
78
    elif S_ISBLK(mode):
 
79
        return 'block'
 
80
    elif S_ISFIFO(mode):
 
81
        return 'fifo'
 
82
    elif S_ISSOCK(mode):
 
83
        return 'socket'
67
84
    else:
68
 
        raise BzrError("can't handle file kind with mode %o of %r" % (mode, f))
 
85
        return 'unknown'
69
86
 
70
87
 
71
88
def kind_marker(kind):
79
96
        raise BzrError('invalid file kind %r' % kind)
80
97
 
81
98
 
82
 
 
83
99
def backup_file(fn):
84
100
    """Copy a file to a backup.
85
101
 
87
103
 
88
104
    If the file is already a backup, it's not copied.
89
105
    """
90
 
    import os
91
106
    if fn[-1] == '~':
92
107
        return
93
108
    bfn = fn + '~'
104
119
    finally:
105
120
        outf.close()
106
121
 
107
 
def rename(path_from, path_to):
108
 
    """Basically the same as os.rename() just special for win32"""
109
 
    if sys.platform == 'win32':
110
 
        try:
111
 
            os.remove(path_to)
112
 
        except OSError, e:
113
 
            if e.errno != e.ENOENT:
114
 
                raise
115
 
    os.rename(path_from, path_to)
116
 
 
117
 
 
118
 
 
 
122
if os.name == 'nt':
 
123
    import shutil
 
124
    rename = shutil.move
 
125
else:
 
126
    rename = os.rename
119
127
 
120
128
 
121
129
def isdir(f):
126
134
        return False
127
135
 
128
136
 
129
 
 
130
137
def isfile(f):
131
138
    """True if f is a regular file."""
132
139
    try:
145
152
    The empty string as a dir name is taken as top-of-tree and matches 
146
153
    everything.
147
154
    
148
 
    >>> is_inside('src', 'src/foo.c')
 
155
    >>> is_inside('src', os.path.join('src', 'foo.c'))
149
156
    True
150
157
    >>> is_inside('src', 'srccontrol')
151
158
    False
152
 
    >>> is_inside('src', 'src/a/a/a/foo.c')
 
159
    >>> is_inside('src', os.path.join('src', 'a', 'a', 'a', 'foo.c'))
153
160
    True
154
161
    >>> is_inside('foo.c', 'foo.c')
155
162
    True
165
172
    
166
173
    if dir == '':
167
174
        return True
168
 
    
 
175
 
169
176
    if dir[-1] != os.sep:
170
177
        dir += os.sep
171
 
    
 
178
 
172
179
    return fname.startswith(dir)
173
180
 
174
181
 
186
193
    tofile.write(fromfile.read())
187
194
 
188
195
 
189
 
def uuid():
190
 
    """Return a new UUID"""
191
 
    try:
192
 
        return file('/proc/sys/kernel/random/uuid').readline().rstrip('\n')
193
 
    except IOError:
194
 
        return chomp(os.popen('uuidgen').readline())
195
 
 
196
 
 
197
196
def sha_file(f):
198
 
    import sha
199
197
    if hasattr(f, 'tell'):
200
198
        assert f.tell() == 0
201
199
    s = sha.new()
208
206
    return s.hexdigest()
209
207
 
210
208
 
 
209
 
 
210
def sha_strings(strings):
 
211
    """Return the sha-1 of concatenation of strings"""
 
212
    s = sha.new()
 
213
    map(s.update, strings)
 
214
    return s.hexdigest()
 
215
 
 
216
 
211
217
def sha_string(f):
212
 
    import sha
213
218
    s = sha.new()
214
219
    s.update(f)
215
220
    return s.hexdigest()
216
221
 
217
222
 
218
 
 
219
223
def fingerprint_file(f):
220
 
    import sha
221
224
    s = sha.new()
222
225
    b = f.read()
223
226
    s.update(b)
233
236
    
234
237
    TODO: Global option --config-dir to override this.
235
238
    """
236
 
    return os.path.expanduser("~/.bzr.conf")
 
239
    return os.path.join(os.path.expanduser("~"), ".bzr.conf")
237
240
 
238
241
 
239
242
def _auto_user_id():
343
346
    if e:
344
347
        m = re.search(r'[\w+.-]+@[\w+.-]+', e)
345
348
        if not m:
346
 
            raise BzrError("%r doesn't seem to contain a reasonable email address" % e)
 
349
            raise BzrError("%r doesn't seem to contain "
 
350
                           "a reasonable email address" % e)
347
351
        return m.group(0)
348
352
 
349
353
    return _auto_user_id()[1]
350
 
    
351
354
 
352
355
 
353
356
def compare_files(a, b):
362
365
            return True
363
366
 
364
367
 
365
 
 
366
368
def local_time_offset(t=None):
367
369
    """Return offset of local zone from GMT, either at present or at time t."""
368
370
    # python2.3 localtime() can't take None
407
409
    """Return size of given open file."""
408
410
    return os.fstat(f.fileno())[ST_SIZE]
409
411
 
410
 
 
411
 
if hasattr(os, 'urandom'): # python 2.4 and later
 
412
# Define rand_bytes based on platform.
 
413
try:
 
414
    # Python 2.4 and later have os.urandom,
 
415
    # but it doesn't work on some arches
 
416
    os.urandom(1)
412
417
    rand_bytes = os.urandom
413
 
elif sys.platform == 'linux2':
414
 
    rand_bytes = file('/dev/urandom', 'rb').read
415
 
else:
416
 
    # not well seeded, but better than nothing
417
 
    def rand_bytes(n):
418
 
        import random
419
 
        s = ''
420
 
        while n:
421
 
            s += chr(random.randint(0, 255))
422
 
            n -= 1
423
 
        return s
424
 
 
 
418
except (NotImplementedError, AttributeError):
 
419
    # If python doesn't have os.urandom, or it doesn't work,
 
420
    # then try to first pull random data from /dev/urandom
 
421
    if os.path.exists("/dev/urandom"):
 
422
        rand_bytes = file('/dev/urandom', 'rb').read
 
423
    # Otherwise, use this hack as a last resort
 
424
    else:
 
425
        # not well seeded, but better than nothing
 
426
        def rand_bytes(n):
 
427
            import random
 
428
            s = ''
 
429
            while n:
 
430
                s += chr(random.randint(0, 255))
 
431
                n -= 1
 
432
            return s
425
433
 
426
434
## TODO: We could later have path objects that remember their list
427
435
## decomposition (might be too tricksy though.)
473
481
        return os.path.join(p1, p2)
474
482
    
475
483
 
476
 
def extern_command(cmd, ignore_errors = False):
477
 
    mutter('external command: %s' % `cmd`)
478
 
    if os.system(cmd):
479
 
        if not ignore_errors:
480
 
            raise BzrError('command failed')
481
 
 
482
 
 
483
484
def _read_config_value(name):
484
485
    """Read a config value from the file ~/.bzr.conf/<name>
485
486
    Return None if the file does not exist"""
492
493
        raise
493
494
 
494
495
 
495
 
def _get_editor():
496
 
    """Return a sequence of possible editor binaries for the current platform"""
497
 
    e = _read_config_value("editor")
498
 
    if e is not None:
499
 
        yield e
500
 
        
501
 
    if os.name == "windows":
502
 
        yield "notepad.exe"
503
 
    elif os.name == "posix":
504
 
        try:
505
 
            yield os.environ["EDITOR"]
506
 
        except KeyError:
507
 
            yield "/usr/bin/vi"
508
 
 
509
 
 
510
 
def _run_editor(filename):
511
 
    """Try to execute an editor to edit the commit message. Returns True on success,
512
 
    False on failure"""
513
 
    for e in _get_editor():
514
 
        x = os.spawnvp(os.P_WAIT, e, (e, filename))
515
 
        if x == 0:
516
 
            return True
517
 
        elif x == 127:
518
 
            continue
519
 
        else:
520
 
            break
521
 
    raise BzrError("Could not start any editor. Please specify $EDITOR or use ~/.bzr.conf/editor")
522
 
    return False
523
 
                          
524
 
 
525
 
def get_text_message(infotext, ignoreline = "default"):
526
 
    import tempfile
527
 
    
528
 
    if ignoreline == "default":
529
 
        ignoreline = "-- This line and the following will be ignored --"
530
 
        
 
496
def split_lines(s):
 
497
    """Split s into lines, but without removing the newline characters."""
 
498
    return StringIO(s).readlines()
 
499
 
 
500
 
 
501
def hardlinks_good():
 
502
    return sys.platform not in ('win32', 'cygwin', 'darwin')
 
503
 
 
504
 
 
505
def link_or_copy(src, dest):
 
506
    """Hardlink a file, or copy it if it can't be hardlinked."""
 
507
    if not hardlinks_good():
 
508
        copyfile(src, dest)
 
509
        return
531
510
    try:
532
 
        tmp_fileno, msgfilename = tempfile.mkstemp()
533
 
        msgfile = os.close(tmp_fileno)
534
 
        if infotext is not None and infotext != "":
535
 
            hasinfo = True
536
 
            msgfile = file(msgfilename, "w")
537
 
            msgfile.write("\n\n%s\n\n%s" % (ignoreline, infotext))
538
 
            msgfile.close()
539
 
        else:
540
 
            hasinfo = False
541
 
 
542
 
        if not _run_editor(msgfilename):
543
 
            return None
544
 
        
545
 
        started = False
546
 
        msg = []
547
 
        lastline, nlines = 0, 0
548
 
        for line in file(msgfilename, "r"):
549
 
            stripped_line = line.strip()
550
 
            # strip empty line before the log message starts
551
 
            if not started:
552
 
                if stripped_line != "":
553
 
                    started = True
554
 
                else:
555
 
                    continue
556
 
            # check for the ignore line only if there
557
 
            # is additional information at the end
558
 
            if hasinfo and stripped_line == ignoreline:
559
 
                break
560
 
            nlines += 1
561
 
            # keep track of the last line that had some content
562
 
            if stripped_line != "":
563
 
                lastline = nlines
564
 
            msg.append(line)
565
 
            
566
 
        if len(msg) == 0:
567
 
            return None
568
 
        # delete empty lines at the end
569
 
        del msg[lastline:]
570
 
        # add a newline at the end, if needed
571
 
        if not msg[-1].endswith("\n"):
572
 
            return "%s%s" % ("".join(msg), "\n")
573
 
        else:
574
 
            return "".join(msg)
575
 
    finally:
576
 
        # delete the msg file in any case
577
 
        try: os.unlink(msgfilename)
578
 
        except IOError: pass
 
511
        os.link(src, dest)
 
512
    except (OSError, IOError), e:
 
513
        if e.errno != errno.EXDEV:
 
514
            raise
 
515
        copyfile(src, dest)