~bzr-pqm/bzr/bzr.dev

1 by mbp at sourcefrog
import from baz patch-364
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
252 by Martin Pool
- Don't use host fqdn for default user name, because DNS tends
19
import os, types, re, time, errno
20 by mbp at sourcefrog
don't abort on trees that happen to contain symlinks
20
from stat import S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE
1 by mbp at sourcefrog
import from baz patch-364
21
183 by mbp at sourcefrog
pychecker fixups
22
from errors import bailout, BzrError
23
from trace import mutter
251 by mbp at sourcefrog
- factor out locale.getpreferredencoding()
24
import bzrlib
1 by mbp at sourcefrog
import from baz patch-364
25
26
def make_readonly(filename):
27
    """Make a filename read-only."""
28
    # TODO: probably needs to be fixed for windows
29
    mod = os.stat(filename).st_mode
30
    mod = mod & 0777555
31
    os.chmod(filename, mod)
32
33
34
def make_writable(filename):
35
    mod = os.stat(filename).st_mode
36
    mod = mod | 0200
37
    os.chmod(filename, mod)
38
39
40
_QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])')
41
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
48
49
50
def file_kind(f):
51
    mode = os.lstat(f)[ST_MODE]
52
    if S_ISREG(mode):
53
        return 'file'
54
    elif S_ISDIR(mode):
55
        return 'directory'
20 by mbp at sourcefrog
don't abort on trees that happen to contain symlinks
56
    elif S_ISLNK(mode):
57
        return 'symlink'
1 by mbp at sourcefrog
import from baz patch-364
58
    else:
183 by mbp at sourcefrog
pychecker fixups
59
        raise BzrError("can't handle file kind with mode %o of %r" % (mode, f)) 
1 by mbp at sourcefrog
import from baz patch-364
60
61
62
63
def isdir(f):
64
    """True if f is an accessible directory."""
65
    try:
66
        return S_ISDIR(os.lstat(f)[ST_MODE])
67
    except OSError:
68
        return False
69
70
71
72
def isfile(f):
73
    """True if f is a regular file."""
74
    try:
75
        return S_ISREG(os.lstat(f)[ST_MODE])
76
    except OSError:
77
        return False
78
79
80
def pumpfile(fromfile, tofile):
81
    """Copy contents of one file to another."""
82
    tofile.write(fromfile.read())
83
84
85
def uuid():
86
    """Return a new UUID"""
63 by mbp at sourcefrog
fix up uuid command
87
    try:
319 by Martin Pool
- remove trivial chomp() function
88
        return file('/proc/sys/kernel/random/uuid').readline().rstrip('\n')
63 by mbp at sourcefrog
fix up uuid command
89
    except IOError:
90
        return chomp(os.popen('uuidgen').readline())
91
1 by mbp at sourcefrog
import from baz patch-364
92
93
def sha_file(f):
94
    import sha
95
    if hasattr(f, 'tell'):
96
        assert f.tell() == 0
97
    s = sha.new()
320 by Martin Pool
- Compute SHA-1 of files in chunks
98
    BUFSIZE = 128<<10
99
    while True:
100
        b = f.read(BUFSIZE)
101
        if not b:
102
            break
103
        s.update(b)
1 by mbp at sourcefrog
import from baz patch-364
104
    return s.hexdigest()
105
106
107
def sha_string(f):
108
    import sha
109
    s = sha.new()
110
    s.update(f)
111
    return s.hexdigest()
112
113
114
124 by mbp at sourcefrog
- check file text for past revisions is correct
115
def fingerprint_file(f):
116
    import sha
117
    s = sha.new()
126 by mbp at sourcefrog
Use just one big read to fingerprint files
118
    b = f.read()
119
    s.update(b)
120
    size = len(b)
124 by mbp at sourcefrog
- check file text for past revisions is correct
121
    return {'size': size,
122
            'sha1': s.hexdigest()}
123
124
258 by Martin Pool
- Take email from ~/.bzr.conf/email
125
def config_dir():
126
    """Return per-user configuration directory.
127
128
    By default this is ~/.bzr.conf/
129
    
130
    TODO: Global option --config-dir to override this.
131
    """
132
    return os.path.expanduser("~/.bzr.conf")
133
134
252 by Martin Pool
- Don't use host fqdn for default user name, because DNS tends
135
def _auto_user_id():
246 by mbp at sourcefrog
- unicode decoding in getting email and userid strings
136
    """Calculate automatic user identification.
137
138
    Returns (realname, email).
139
252 by Martin Pool
- Don't use host fqdn for default user name, because DNS tends
140
    Only used when none is set in the environment or the id file.
246 by mbp at sourcefrog
- unicode decoding in getting email and userid strings
141
252 by Martin Pool
- Don't use host fqdn for default user name, because DNS tends
142
    This previously used the FQDN as the default domain, but that can
143
    be very slow on machines where DNS is broken.  So now we simply
144
    use the hostname.
1 by mbp at sourcefrog
import from baz patch-364
145
    """
251 by mbp at sourcefrog
- factor out locale.getpreferredencoding()
146
    import socket
246 by mbp at sourcefrog
- unicode decoding in getting email and userid strings
147
148
    # XXX: Any good way to get real user name on win32?
149
1 by mbp at sourcefrog
import from baz patch-364
150
    try:
151
        import pwd
152
        uid = os.getuid()
153
        w = pwd.getpwuid(uid)
251 by mbp at sourcefrog
- factor out locale.getpreferredencoding()
154
        gecos = w.pw_gecos.decode(bzrlib.user_encoding)
155
        username = w.pw_name.decode(bzrlib.user_encoding)
25 by Martin Pool
cope when gecos field doesn't have a comma
156
        comma = gecos.find(',')
157
        if comma == -1:
158
            realname = gecos
159
        else:
160
            realname = gecos[:comma]
256 by Martin Pool
- More handling of auto-username case
161
        if not realname:
162
            realname = username
246 by mbp at sourcefrog
- unicode decoding in getting email and userid strings
163
1 by mbp at sourcefrog
import from baz patch-364
164
    except ImportError:
246 by mbp at sourcefrog
- unicode decoding in getting email and userid strings
165
        import getpass
256 by Martin Pool
- More handling of auto-username case
166
        realname = username = getpass.getuser().decode(bzrlib.user_encoding)
252 by Martin Pool
- Don't use host fqdn for default user name, because DNS tends
167
256 by Martin Pool
- More handling of auto-username case
168
    return realname, (username + '@' + socket.gethostname())
252 by Martin Pool
- Don't use host fqdn for default user name, because DNS tends
169
170
171
def _get_user_id():
258 by Martin Pool
- Take email from ~/.bzr.conf/email
172
    """Return the full user id from a file or environment variable.
173
174
    TODO: Allow taking this from a file in the branch directory too
175
    for per-branch ids."""
252 by Martin Pool
- Don't use host fqdn for default user name, because DNS tends
176
    v = os.environ.get('BZREMAIL')
177
    if v:
178
        return v.decode(bzrlib.user_encoding)
179
    
180
    try:
258 by Martin Pool
- Take email from ~/.bzr.conf/email
181
        return (open(os.path.join(config_dir(), "email"))
252 by Martin Pool
- Don't use host fqdn for default user name, because DNS tends
182
                .read()
183
                .decode(bzrlib.user_encoding)
184
                .rstrip("\r\n"))
256 by Martin Pool
- More handling of auto-username case
185
    except IOError, e:
186
        if e.errno != errno.ENOENT:
252 by Martin Pool
- Don't use host fqdn for default user name, because DNS tends
187
            raise e
188
189
    v = os.environ.get('EMAIL')
190
    if v:
191
        return v.decode(bzrlib.user_encoding)
192
    else:    
193
        return None
246 by mbp at sourcefrog
- unicode decoding in getting email and userid strings
194
195
196
def username():
197
    """Return email-style username.
198
199
    Something similar to 'Martin Pool <mbp@sourcefrog.net>'
200
254 by Martin Pool
- Doc cleanups from Magnus Therning
201
    TODO: Check it's reasonably well-formed.
246 by mbp at sourcefrog
- unicode decoding in getting email and userid strings
202
    """
252 by Martin Pool
- Don't use host fqdn for default user name, because DNS tends
203
    v = _get_user_id()
204
    if v:
205
        return v
206
    
207
    name, email = _auto_user_id()
246 by mbp at sourcefrog
- unicode decoding in getting email and userid strings
208
    if name:
209
        return '%s <%s>' % (name, email)
210
    else:
211
        return email
1 by mbp at sourcefrog
import from baz patch-364
212
213
183 by mbp at sourcefrog
pychecker fixups
214
_EMAIL_RE = re.compile(r'[\w+.-]+@[\w+.-]+')
1 by mbp at sourcefrog
import from baz patch-364
215
def user_email():
216
    """Return just the email component of a username."""
252 by Martin Pool
- Don't use host fqdn for default user name, because DNS tends
217
    e = _get_user_id()
1 by mbp at sourcefrog
import from baz patch-364
218
    if e:
183 by mbp at sourcefrog
pychecker fixups
219
        m = _EMAIL_RE.search(e)
1 by mbp at sourcefrog
import from baz patch-364
220
        if not m:
252 by Martin Pool
- Don't use host fqdn for default user name, because DNS tends
221
            bailout("%r doesn't seem to contain a reasonable email address" % e)
1 by mbp at sourcefrog
import from baz patch-364
222
        return m.group(0)
223
252 by Martin Pool
- Don't use host fqdn for default user name, because DNS tends
224
    return _auto_user_id()[1]
1 by mbp at sourcefrog
import from baz patch-364
225
    
226
227
228
def compare_files(a, b):
229
    """Returns true if equal in contents"""
74 by mbp at sourcefrog
compare_files: read in one page at a time rather than
230
    BUFSIZE = 4096
231
    while True:
232
        ai = a.read(BUFSIZE)
233
        bi = b.read(BUFSIZE)
234
        if ai != bi:
235
            return False
236
        if ai == '':
237
            return True
1 by mbp at sourcefrog
import from baz patch-364
238
239
240
49 by mbp at sourcefrog
fix local-time-offset calculation
241
def local_time_offset(t=None):
242
    """Return offset of local zone from GMT, either at present or at time t."""
73 by mbp at sourcefrog
fix time.localtime call for python 2.3
243
    # python2.3 localtime() can't take None
183 by mbp at sourcefrog
pychecker fixups
244
    if t == None:
73 by mbp at sourcefrog
fix time.localtime call for python 2.3
245
        t = time.time()
246
        
49 by mbp at sourcefrog
fix local-time-offset calculation
247
    if time.localtime(t).tm_isdst and time.daylight:
8 by mbp at sourcefrog
store committer's timezone in revision and show
248
        return -time.altzone
249
    else:
250
        return -time.timezone
251
252
    
253
def format_date(t, offset=0, timezone='original'):
1 by mbp at sourcefrog
import from baz patch-364
254
    ## TODO: Perhaps a global option to use either universal or local time?
255
    ## Or perhaps just let people set $TZ?
256
    assert isinstance(t, float)
257
    
8 by mbp at sourcefrog
store committer's timezone in revision and show
258
    if timezone == 'utc':
1 by mbp at sourcefrog
import from baz patch-364
259
        tt = time.gmtime(t)
260
        offset = 0
8 by mbp at sourcefrog
store committer's timezone in revision and show
261
    elif timezone == 'original':
23 by mbp at sourcefrog
format_date: handle revisions with no timezone offset
262
        if offset == None:
263
            offset = 0
16 by mbp at sourcefrog
fix inverted calculation for original timezone -> utc
264
        tt = time.gmtime(t + offset)
12 by mbp at sourcefrog
new --timezone option for bzr log
265
    elif timezone == 'local':
1 by mbp at sourcefrog
import from baz patch-364
266
        tt = time.localtime(t)
49 by mbp at sourcefrog
fix local-time-offset calculation
267
        offset = local_time_offset(t)
12 by mbp at sourcefrog
new --timezone option for bzr log
268
    else:
269
        bailout("unsupported timezone format %r",
270
                ['options are "utc", "original", "local"'])
8 by mbp at sourcefrog
store committer's timezone in revision and show
271
1 by mbp at sourcefrog
import from baz patch-364
272
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
8 by mbp at sourcefrog
store committer's timezone in revision and show
273
            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
1 by mbp at sourcefrog
import from baz patch-364
274
275
276
def compact_date(when):
277
    return time.strftime('%Y%m%d%H%M%S', time.gmtime(when))
278
    
279
280
281
def filesize(f):
282
    """Return size of given open file."""
283
    return os.fstat(f.fileno())[ST_SIZE]
284
285
286
if hasattr(os, 'urandom'): # python 2.4 and later
287
    rand_bytes = os.urandom
288
else:
289
    # FIXME: No good on non-Linux
290
    _rand_file = file('/dev/urandom', 'rb')
291
    rand_bytes = _rand_file.read
292
293
294
## TODO: We could later have path objects that remember their list
295
## decomposition (might be too tricksy though.)
296
297
def splitpath(p):
298
    """Turn string into list of parts.
299
300
    >>> splitpath('a')
301
    ['a']
302
    >>> splitpath('a/b')
303
    ['a', 'b']
304
    >>> splitpath('a/./b')
305
    ['a', 'b']
306
    >>> splitpath('a/.b')
307
    ['a', '.b']
308
    >>> splitpath('a/../b')
184 by mbp at sourcefrog
pychecker fixups
309
    Traceback (most recent call last):
1 by mbp at sourcefrog
import from baz patch-364
310
    ...
311
    BzrError: ("sorry, '..' not allowed in path", [])
312
    """
313
    assert isinstance(p, types.StringTypes)
271 by Martin Pool
- Windows path fixes
314
315
    # split on either delimiter because people might use either on
316
    # Windows
317
    ps = re.split(r'[\\/]', p)
318
319
    rps = []
1 by mbp at sourcefrog
import from baz patch-364
320
    for f in ps:
321
        if f == '..':
322
            bailout("sorry, %r not allowed in path" % f)
271 by Martin Pool
- Windows path fixes
323
        elif (f == '.') or (f == ''):
324
            pass
325
        else:
326
            rps.append(f)
327
    return rps
1 by mbp at sourcefrog
import from baz patch-364
328
329
def joinpath(p):
330
    assert isinstance(p, list)
331
    for f in p:
183 by mbp at sourcefrog
pychecker fixups
332
        if (f == '..') or (f == None) or (f == ''):
1 by mbp at sourcefrog
import from baz patch-364
333
            bailout("sorry, %r not allowed in path" % f)
271 by Martin Pool
- Windows path fixes
334
    return os.path.join(*p)
1 by mbp at sourcefrog
import from baz patch-364
335
336
337
def appendpath(p1, p2):
338
    if p1 == '':
339
        return p2
340
    else:
271 by Martin Pool
- Windows path fixes
341
        return os.path.join(p1, p2)
1 by mbp at sourcefrog
import from baz patch-364
342
    
343
344
def extern_command(cmd, ignore_errors = False):
345
    mutter('external command: %s' % `cmd`)
346
    if os.system(cmd):
347
        if not ignore_errors:
348
            bailout('command failed')
349