~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Martin Pool
  • Date: 2005-07-04 11:57:18 UTC
  • Revision ID: mbp@sourcefrog.net-20050704115718-b532986c0714e7a7
- don't write precursor field in new revision xml
- make parents more primary; remove more precursor code
- test commit of revision with parents

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
 
 
23
20
from stat import S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE
24
21
 
25
22
from bzrlib.errors import BzrError
40
37
    os.chmod(filename, mod)
41
38
 
42
39
 
43
 
_QUOTE_RE = None
44
 
 
45
 
 
 
40
_QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])')
46
41
def quotefn(f):
47
42
    """Return a quoted filename filename
48
43
 
49
44
    This previously used backslash quoting, but that works poorly on
50
45
    Windows."""
51
46
    # 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
 
        
56
47
    if _QUOTE_RE.search(f):
57
48
        return '"' + f + '"'
58
49
    else:
90
81
 
91
82
    If the file is already a backup, it's not copied.
92
83
    """
 
84
    import os
93
85
    if fn[-1] == '~':
94
86
        return
95
87
    bfn = fn + '~'
106
98
    finally:
107
99
        outf.close()
108
100
 
109
 
def rename(path_from, path_to):
110
 
    """Basically the same as os.rename() just special for win32"""
111
 
    if sys.platform == 'win32':
112
 
        try:
113
 
            os.remove(path_to)
114
 
        except OSError, e:
115
 
            if e.errno != e.ENOENT:
116
 
                raise
117
 
    os.rename(path_from, path_to)
118
 
 
119
 
 
120
101
 
121
102
 
122
103
 
139
120
 
140
121
def is_inside(dir, fname):
141
122
    """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
162
123
    """
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)
 
124
    return os.path.commonprefix([dir, fname]) == dir
175
125
 
176
126
 
177
127
def is_inside_any(dir_list, fname):
178
128
    """True if fname is inside any of given dirs."""
 
129
    # quick scan for perfect match
 
130
    if fname in dir_list:
 
131
        return True
 
132
    
179
133
    for dirname in dir_list:
180
134
        if is_inside(dirname, fname):
181
135
            return True
197
151
 
198
152
 
199
153
def sha_file(f):
 
154
    import sha
200
155
    if hasattr(f, 'tell'):
201
156
        assert f.tell() == 0
202
157
    s = sha.new()
209
164
    return s.hexdigest()
210
165
 
211
166
 
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
 
 
220
167
def sha_string(f):
 
168
    import sha
221
169
    s = sha.new()
222
170
    s.update(f)
223
171
    return s.hexdigest()
225
173
 
226
174
 
227
175
def fingerprint_file(f):
 
176
    import sha
228
177
    s = sha.new()
229
178
    b = f.read()
230
179
    s.update(b)
279
228
    return realname, (username + '@' + socket.gethostname())
280
229
 
281
230
 
282
 
def _get_user_id(branch):
 
231
def _get_user_id():
283
232
    """Return the full user id from a file or environment variable.
284
233
 
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
 
    """
 
234
    TODO: Allow taking this from a file in the branch directory too
 
235
    for per-branch ids."""
297
236
    v = os.environ.get('BZREMAIL')
298
237
    if v:
299
238
        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
312
239
    
313
240
    try:
314
241
        return (open(os.path.join(config_dir(), "email"))
326
253
        return None
327
254
 
328
255
 
329
 
def username(branch):
 
256
def username():
330
257
    """Return email-style username.
331
258
 
332
259
    Something similar to 'Martin Pool <mbp@sourcefrog.net>'
333
260
 
334
261
    TODO: Check it's reasonably well-formed.
335
262
    """
336
 
    v = _get_user_id(branch)
 
263
    v = _get_user_id()
337
264
    if v:
338
265
        return v
339
266
    
344
271
        return email
345
272
 
346
273
 
347
 
def user_email(branch):
 
274
_EMAIL_RE = re.compile(r'[\w+.-]+@[\w+.-]+')
 
275
def user_email():
348
276
    """Return just the email component of a username."""
349
 
    e = _get_user_id(branch)
 
277
    e = _get_user_id()
350
278
    if e:
351
 
        m = re.search(r'[\w+.-]+@[\w+.-]+', e)
 
279
        m = _EMAIL_RE.search(e)
352
280
        if not m:
353
281
            raise BzrError("%r doesn't seem to contain a reasonable email address" % e)
354
282
        return m.group(0)
398
326
        tt = time.localtime(t)
399
327
        offset = local_time_offset(t)
400
328
    else:
401
 
        raise BzrError("unsupported timezone format %r" % timezone,
402
 
                       ['options are "utc", "original", "local"'])
 
329
        raise BzrError("unsupported timezone format %r",
 
330
                ['options are "utc", "original", "local"'])
403
331
 
404
332
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
405
333
            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
499
427
        raise
500
428
 
501
429
 
502
 
 
503
 
def split_lines(s):
504
 
    """Split s into lines, but without removing the newline characters."""
505
 
    return StringIO(s).readlines()
 
430
def _get_editor():
 
431
    """Return a sequence of possible editor binaries for the current platform"""
 
432
    e = _read_config_value("editor")
 
433
    if e is not None:
 
434
        yield e
 
435
        
 
436
    if os.name == "windows":
 
437
        yield "notepad.exe"
 
438
    elif os.name == "posix":
 
439
        try:
 
440
            yield os.environ["EDITOR"]
 
441
        except KeyError:
 
442
            yield "/usr/bin/vi"
 
443
 
 
444
 
 
445
def _run_editor(filename):
 
446
    """Try to execute an editor to edit the commit message. Returns True on success,
 
447
    False on failure"""
 
448
    for e in _get_editor():
 
449
        x = os.spawnvp(os.P_WAIT, e, (e, filename))
 
450
        if x == 0:
 
451
            return True
 
452
        elif x == 127:
 
453
            continue
 
454
        else:
 
455
            break
 
456
    raise BzrError("Could not start any editor. Please specify $EDITOR or use ~/.bzr.conf/editor")
 
457
    return False
 
458
                          
 
459
 
 
460
def get_text_message(infotext, ignoreline = "default"):
 
461
    import tempfile
506
462
    
 
463
    if ignoreline == "default":
 
464
        ignoreline = "-- This line and the following will be ignored --"
 
465
        
 
466
    try:
 
467
        tmp_fileno, msgfilename = tempfile.mkstemp()
 
468
        msgfile = os.close(tmp_fileno)
 
469
        if infotext is not None and infotext != "":
 
470
            hasinfo = True
 
471
            msgfile = file(msgfilename, "w")
 
472
            msgfile.write("\n\n%s\n\n%s" % (ignoreline, infotext))
 
473
            msgfile.close()
 
474
        else:
 
475
            hasinfo = False
 
476
 
 
477
        if not _run_editor(msgfilename):
 
478
            return None
 
479
        
 
480
        started = False
 
481
        msg = []
 
482
        lastline, nlines = 0, 0
 
483
        for line in file(msgfilename, "r"):
 
484
            stripped_line = line.strip()
 
485
            # strip empty line before the log message starts
 
486
            if not started:
 
487
                if stripped_line != "":
 
488
                    started = True
 
489
                else:
 
490
                    continue
 
491
            # check for the ignore line only if there
 
492
            # is additional information at the end
 
493
            if hasinfo and stripped_line == ignoreline:
 
494
                break
 
495
            nlines += 1
 
496
            # keep track of the last line that had some content
 
497
            if stripped_line != "":
 
498
                lastline = nlines
 
499
            msg.append(line)
 
500
            
 
501
        if len(msg) == 0:
 
502
            return None
 
503
        # delete empty lines at the end
 
504
        del msg[lastline:]
 
505
        # add a newline at the end, if needed
 
506
        if not msg[-1].endswith("\n"):
 
507
            return "%s%s" % ("".join(msg), "\n")
 
508
        else:
 
509
            return "".join(msg)
 
510
    finally:
 
511
        # delete the msg file in any case
 
512
        try: os.unlink(msgfilename)
 
513
        except IOError: pass