~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Martin Pool
  • Date: 2005-09-22 06:19:33 UTC
  • Revision ID: mbp@sourcefrog.net-20050922061933-4b71d0f1e205b153
- keep track of number of checked revisions

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
18
 
19
19
import os, types, re, time, errno, sys
 
20
import sha
 
21
from cStringIO import StringIO
 
22
 
20
23
from stat import S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE
21
24
 
22
25
from bzrlib.errors import BzrError
37
40
    os.chmod(filename, mod)
38
41
 
39
42
 
40
 
_QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])')
 
43
_QUOTE_RE = None
 
44
 
 
45
 
41
46
def quotefn(f):
42
47
    """Return a quoted filename filename
43
48
 
44
49
    This previously used backslash quoting, but that works poorly on
45
50
    Windows."""
46
51
    # TODO: I'm not really sure this is the best format either.x
 
52
    global _QUOTE_RE
 
53
    if _QUOTE_RE == None:
 
54
        _QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])')
 
55
        
47
56
    if _QUOTE_RE.search(f):
48
57
        return '"' + f + '"'
49
58
    else:
81
90
 
82
91
    If the file is already a backup, it's not copied.
83
92
    """
84
 
    import os
85
93
    if fn[-1] == '~':
86
94
        return
87
95
    bfn = fn + '~'
131
139
 
132
140
def is_inside(dir, fname):
133
141
    """True if fname is inside dir.
 
142
    
 
143
    The parameters should typically be passed to os.path.normpath first, so
 
144
    that . and .. and repeated slashes are eliminated, and the separators
 
145
    are canonical for the platform.
 
146
    
 
147
    The empty string as a dir name is taken as top-of-tree and matches 
 
148
    everything.
 
149
    
 
150
    >>> is_inside('src', 'src/foo.c')
 
151
    True
 
152
    >>> is_inside('src', 'srccontrol')
 
153
    False
 
154
    >>> is_inside('src', 'src/a/a/a/foo.c')
 
155
    True
 
156
    >>> is_inside('foo.c', 'foo.c')
 
157
    True
 
158
    >>> is_inside('foo.c', '')
 
159
    False
 
160
    >>> is_inside('', 'foo.c')
 
161
    True
134
162
    """
135
 
    return os.path.commonprefix([dir, fname]) == dir
 
163
    # XXX: Most callers of this can actually do something smarter by 
 
164
    # looking at the inventory
 
165
    if dir == fname:
 
166
        return True
 
167
    
 
168
    if dir == '':
 
169
        return True
 
170
    
 
171
    if dir[-1] != os.sep:
 
172
        dir += os.sep
 
173
    
 
174
    return fname.startswith(dir)
136
175
 
137
176
 
138
177
def is_inside_any(dir_list, fname):
139
178
    """True if fname is inside any of given dirs."""
140
 
    # quick scan for perfect match
141
 
    if fname in dir_list:
142
 
        return True
143
 
    
144
179
    for dirname in dir_list:
145
180
        if is_inside(dirname, fname):
146
181
            return True
162
197
 
163
198
 
164
199
def sha_file(f):
165
 
    import sha
166
200
    if hasattr(f, 'tell'):
167
201
        assert f.tell() == 0
168
202
    s = sha.new()
175
209
    return s.hexdigest()
176
210
 
177
211
 
 
212
 
 
213
def sha_strings(strings):
 
214
    """Return the sha-1 of concatenation of strings"""
 
215
    s = sha.new()
 
216
    map(s.update, strings)
 
217
    return s.hexdigest()
 
218
 
 
219
 
178
220
def sha_string(f):
179
 
    import sha
180
221
    s = sha.new()
181
222
    s.update(f)
182
223
    return s.hexdigest()
184
225
 
185
226
 
186
227
def fingerprint_file(f):
187
 
    import sha
188
228
    s = sha.new()
189
229
    b = f.read()
190
230
    s.update(b)
239
279
    return realname, (username + '@' + socket.gethostname())
240
280
 
241
281
 
242
 
def _get_user_id():
 
282
def _get_user_id(branch):
243
283
    """Return the full user id from a file or environment variable.
244
284
 
245
 
    TODO: Allow taking this from a file in the branch directory too
246
 
    for per-branch ids."""
 
285
    e.g. "John Hacker <jhacker@foo.org>"
 
286
 
 
287
    branch
 
288
        A branch to use for a per-branch configuration, or None.
 
289
 
 
290
    The following are searched in order:
 
291
 
 
292
    1. $BZREMAIL
 
293
    2. .bzr/email for this branch.
 
294
    3. ~/.bzr.conf/email
 
295
    4. $EMAIL
 
296
    """
247
297
    v = os.environ.get('BZREMAIL')
248
298
    if v:
249
299
        return v.decode(bzrlib.user_encoding)
 
300
 
 
301
    if branch:
 
302
        try:
 
303
            return (branch.controlfile("email", "r") 
 
304
                    .read()
 
305
                    .decode(bzrlib.user_encoding)
 
306
                    .rstrip("\r\n"))
 
307
        except IOError, e:
 
308
            if e.errno != errno.ENOENT:
 
309
                raise
 
310
        except BzrError, e:
 
311
            pass
250
312
    
251
313
    try:
252
314
        return (open(os.path.join(config_dir(), "email"))
264
326
        return None
265
327
 
266
328
 
267
 
def username():
 
329
def username(branch):
268
330
    """Return email-style username.
269
331
 
270
332
    Something similar to 'Martin Pool <mbp@sourcefrog.net>'
271
333
 
272
334
    TODO: Check it's reasonably well-formed.
273
335
    """
274
 
    v = _get_user_id()
 
336
    v = _get_user_id(branch)
275
337
    if v:
276
338
        return v
277
339
    
282
344
        return email
283
345
 
284
346
 
285
 
_EMAIL_RE = re.compile(r'[\w+.-]+@[\w+.-]+')
286
 
def user_email():
 
347
def user_email(branch):
287
348
    """Return just the email component of a username."""
288
 
    e = _get_user_id()
 
349
    e = _get_user_id(branch)
289
350
    if e:
290
 
        m = _EMAIL_RE.search(e)
 
351
        m = re.search(r'[\w+.-]+@[\w+.-]+', e)
291
352
        if not m:
292
353
            raise BzrError("%r doesn't seem to contain a reasonable email address" % e)
293
354
        return m.group(0)
337
398
        tt = time.localtime(t)
338
399
        offset = local_time_offset(t)
339
400
    else:
340
 
        raise BzrError("unsupported timezone format %r",
341
 
                ['options are "utc", "original", "local"'])
 
401
        raise BzrError("unsupported timezone format %r" % timezone,
 
402
                       ['options are "utc", "original", "local"'])
342
403
 
343
404
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
344
405
            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
438
499
        raise
439
500
 
440
501
 
441
 
def _get_editor():
442
 
    """Return a sequence of possible editor binaries for the current platform"""
443
 
    e = _read_config_value("editor")
444
 
    if e is not None:
445
 
        yield e
446
 
        
447
 
    if os.name == "windows":
448
 
        yield "notepad.exe"
449
 
    elif os.name == "posix":
450
 
        try:
451
 
            yield os.environ["EDITOR"]
452
 
        except KeyError:
453
 
            yield "/usr/bin/vi"
454
 
 
455
 
 
456
 
def _run_editor(filename):
457
 
    """Try to execute an editor to edit the commit message. Returns True on success,
458
 
    False on failure"""
459
 
    for e in _get_editor():
460
 
        x = os.spawnvp(os.P_WAIT, e, (e, filename))
461
 
        if x == 0:
462
 
            return True
463
 
        elif x == 127:
464
 
            continue
465
 
        else:
466
 
            break
467
 
    raise BzrError("Could not start any editor. Please specify $EDITOR or use ~/.bzr.conf/editor")
468
 
    return False
469
 
                          
470
 
 
471
 
def get_text_message(infotext, ignoreline = "default"):
472
 
    import tempfile
 
502
 
 
503
def split_lines(s):
 
504
    """Split s into lines, but without removing the newline characters."""
 
505
    return StringIO(s).readlines()
473
506
    
474
 
    if ignoreline == "default":
475
 
        ignoreline = "-- This line and the following will be ignored --"
476
 
        
477
 
    try:
478
 
        tmp_fileno, msgfilename = tempfile.mkstemp()
479
 
        msgfile = os.close(tmp_fileno)
480
 
        if infotext is not None and infotext != "":
481
 
            hasinfo = True
482
 
            msgfile = file(msgfilename, "w")
483
 
            msgfile.write("\n\n%s\n\n%s" % (ignoreline, infotext))
484
 
            msgfile.close()
485
 
        else:
486
 
            hasinfo = False
487
 
 
488
 
        if not _run_editor(msgfilename):
489
 
            return None
490
 
        
491
 
        started = False
492
 
        msg = []
493
 
        lastline, nlines = 0, 0
494
 
        for line in file(msgfilename, "r"):
495
 
            stripped_line = line.strip()
496
 
            # strip empty line before the log message starts
497
 
            if not started:
498
 
                if stripped_line != "":
499
 
                    started = True
500
 
                else:
501
 
                    continue
502
 
            # check for the ignore line only if there
503
 
            # is additional information at the end
504
 
            if hasinfo and stripped_line == ignoreline:
505
 
                break
506
 
            nlines += 1
507
 
            # keep track of the last line that had some content
508
 
            if stripped_line != "":
509
 
                lastline = nlines
510
 
            msg.append(line)
511
 
            
512
 
        if len(msg) == 0:
513
 
            return None
514
 
        # delete empty lines at the end
515
 
        del msg[lastline:]
516
 
        # add a newline at the end, if needed
517
 
        if not msg[-1].endswith("\n"):
518
 
            return "%s%s" % ("".join(msg), "\n")
519
 
        else:
520
 
            return "".join(msg)
521
 
    finally:
522
 
        # delete the msg file in any case
523
 
        try: os.unlink(msgfilename)
524
 
        except IOError: pass