~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Martin Pool
  • Date: 2005-07-22 22:36:54 UTC
  • Revision ID: mbp@sourcefrog.net-20050722223654-93bf9d8cc0f0c128
todo

Show diffs side-by-side

added added

removed removed

Lines of Context:
37
37
    os.chmod(filename, mod)
38
38
 
39
39
 
40
 
_QUOTE_RE = None
41
 
 
42
 
 
 
40
_QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])')
43
41
def quotefn(f):
44
42
    """Return a quoted filename filename
45
43
 
46
44
    This previously used backslash quoting, but that works poorly on
47
45
    Windows."""
48
46
    # TODO: I'm not really sure this is the best format either.x
49
 
    global _QUOTE_RE
50
 
    if _QUOTE_RE == None:
51
 
        _QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])')
52
 
        
53
47
    if _QUOTE_RE.search(f):
54
48
        return '"' + f + '"'
55
49
    else:
137
131
 
138
132
def is_inside(dir, fname):
139
133
    """True if fname is inside dir.
140
 
    
141
 
    The parameters should typically be passed to os.path.normpath first, so
142
 
    that . and .. and repeated slashes are eliminated, and the separators
143
 
    are canonical for the platform.
144
 
    
145
 
    The empty string as a dir name is taken as top-of-tree and matches 
146
 
    everything.
147
 
    
148
 
    >>> is_inside('src', 'src/foo.c')
149
 
    True
150
 
    >>> is_inside('src', 'srccontrol')
151
 
    False
152
 
    >>> is_inside('src', 'src/a/a/a/foo.c')
153
 
    True
154
 
    >>> is_inside('foo.c', 'foo.c')
155
 
    True
156
 
    >>> is_inside('foo.c', '')
157
 
    False
158
 
    >>> is_inside('', 'foo.c')
159
 
    True
160
134
    """
161
 
    # XXX: Most callers of this can actually do something smarter by 
162
 
    # looking at the inventory
163
 
    if dir == fname:
164
 
        return True
165
 
    
166
 
    if dir == '':
167
 
        return True
168
 
    
169
 
    if dir[-1] != os.sep:
170
 
        dir += os.sep
171
 
    
172
 
    return fname.startswith(dir)
 
135
    return os.path.commonprefix([dir, fname]) == dir
173
136
 
174
137
 
175
138
def is_inside_any(dir_list, fname):
176
139
    """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
    
177
144
    for dirname in dir_list:
178
145
        if is_inside(dirname, fname):
179
146
            return True
272
239
    return realname, (username + '@' + socket.gethostname())
273
240
 
274
241
 
275
 
def _get_user_id(branch):
 
242
def _get_user_id():
276
243
    """Return the full user id from a file or environment variable.
277
244
 
278
 
    e.g. "John Hacker <jhacker@foo.org>"
279
 
 
280
 
    branch
281
 
        A branch to use for a per-branch configuration, or None.
282
 
 
283
 
    The following are searched in order:
284
 
 
285
 
    1. $BZREMAIL
286
 
    2. .bzr/email for this branch.
287
 
    3. ~/.bzr.conf/email
288
 
    4. $EMAIL
289
 
    """
 
245
    TODO: Allow taking this from a file in the branch directory too
 
246
    for per-branch ids."""
290
247
    v = os.environ.get('BZREMAIL')
291
248
    if v:
292
249
        return v.decode(bzrlib.user_encoding)
293
 
 
294
 
    if branch:
295
 
        try:
296
 
            return (branch.controlfile("email", "r") 
297
 
                    .read()
298
 
                    .decode(bzrlib.user_encoding)
299
 
                    .rstrip("\r\n"))
300
 
        except IOError, e:
301
 
            if e.errno != errno.ENOENT:
302
 
                raise
303
 
        except BzrError, e:
304
 
            pass
305
250
    
306
251
    try:
307
252
        return (open(os.path.join(config_dir(), "email"))
319
264
        return None
320
265
 
321
266
 
322
 
def username(branch):
 
267
def username():
323
268
    """Return email-style username.
324
269
 
325
270
    Something similar to 'Martin Pool <mbp@sourcefrog.net>'
326
271
 
327
272
    TODO: Check it's reasonably well-formed.
328
273
    """
329
 
    v = _get_user_id(branch)
 
274
    v = _get_user_id()
330
275
    if v:
331
276
        return v
332
277
    
337
282
        return email
338
283
 
339
284
 
340
 
def user_email(branch):
 
285
_EMAIL_RE = re.compile(r'[\w+.-]+@[\w+.-]+')
 
286
def user_email():
341
287
    """Return just the email component of a username."""
342
 
    e = _get_user_id(branch)
 
288
    e = _get_user_id()
343
289
    if e:
344
 
        m = re.search(r'[\w+.-]+@[\w+.-]+', e)
 
290
        m = _EMAIL_RE.search(e)
345
291
        if not m:
346
292
            raise BzrError("%r doesn't seem to contain a reasonable email address" % e)
347
293
        return m.group(0)
391
337
        tt = time.localtime(t)
392
338
        offset = local_time_offset(t)
393
339
    else:
394
 
        raise BzrError("unsupported timezone format %r" % timezone,
395
 
                       ['options are "utc", "original", "local"'])
 
340
        raise BzrError("unsupported timezone format %r",
 
341
                ['options are "utc", "original", "local"'])
396
342
 
397
343
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
398
344
            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
407
353
    """Return size of given open file."""
408
354
    return os.fstat(f.fileno())[ST_SIZE]
409
355
 
410
 
# Define rand_bytes based on platform.
411
 
try:
412
 
    # Python 2.4 and later have os.urandom,
413
 
    # but it doesn't work on some arches
414
 
    os.urandom(1)
 
356
 
 
357
if hasattr(os, 'urandom'): # python 2.4 and later
415
358
    rand_bytes = os.urandom
416
 
except (NotImplementedError, AttributeError):
417
 
    # If python doesn't have os.urandom, or it doesn't work,
418
 
    # then try to first pull random data from /dev/urandom
419
 
    if os.path.exists("/dev/urandom"):
420
 
        rand_bytes = file('/dev/urandom', 'rb').read
421
 
    # Otherwise, use this hack as a last resort
422
 
    else:
423
 
        # not well seeded, but better than nothing
424
 
        def rand_bytes(n):
425
 
            import random
426
 
            s = ''
427
 
            while n:
428
 
                s += chr(random.randint(0, 255))
429
 
                n -= 1
430
 
            return s
 
359
elif sys.platform == 'linux2':
 
360
    rand_bytes = file('/dev/urandom', 'rb').read
 
361
else:
 
362
    # not well seeded, but better than nothing
 
363
    def rand_bytes(n):
 
364
        import random
 
365
        s = ''
 
366
        while n:
 
367
            s += chr(random.randint(0, 255))
 
368
            n -= 1
 
369
        return s
 
370
 
431
371
 
432
372
## TODO: We could later have path objects that remember their list
433
373
## decomposition (might be too tricksy though.)
498
438
        raise
499
439
 
500
440
 
 
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
 
473
    
 
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