bzr branch
http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
| 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 | ||
| 19 | import os, types, re, time, types | |
| 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 | |
| 22 | from errors import bailout | |
| 23 | ||
| 24 | def make_readonly(filename): | |
| 25 | """Make a filename read-only.""" | |
| 26 |     # TODO: probably needs to be fixed for windows
 | |
| 27 | mod = os.stat(filename).st_mode | |
| 28 | mod = mod & 0777555 | |
| 29 | os.chmod(filename, mod) | |
| 30 | ||
| 31 | ||
| 32 | def make_writable(filename): | |
| 33 | mod = os.stat(filename).st_mode | |
| 34 | mod = mod | 0200 | |
| 35 | os.chmod(filename, mod) | |
| 36 | ||
| 37 | ||
| 38 | _QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])') | |
| 39 | def quotefn(f): | |
| 40 | """Return shell-quoted filename""" | |
| 41 |     ## We could be a bit more terse by using double-quotes etc
 | |
| 42 | f = _QUOTE_RE.sub(r'\\\1', f) | |
| 43 | if f[0] == '~': | |
| 44 | f[0:1] = r'\~' | |
| 45 | return f | |
| 46 | ||
| 47 | ||
| 48 | def file_kind(f): | |
| 49 | mode = os.lstat(f)[ST_MODE] | |
| 50 | if S_ISREG(mode): | |
| 51 | return 'file' | |
| 52 | elif S_ISDIR(mode): | |
| 53 | return 'directory' | |
| 20
by mbp at sourcefrog don't abort on trees that happen to contain symlinks | 54 | elif S_ISLNK(mode): | 
| 55 | return 'symlink' | |
| 1
by mbp at sourcefrog import from baz patch-364 | 56 | else: | 
| 20
by mbp at sourcefrog don't abort on trees that happen to contain symlinks | 57 | bailout("can't handle file kind with mode %o of %r" % (mode, f)) | 
| 1
by mbp at sourcefrog import from baz patch-364 | 58 | |
| 59 | ||
| 60 | ||
| 61 | def isdir(f): | |
| 62 | """True if f is an accessible directory.""" | |
| 63 | try: | |
| 64 | return S_ISDIR(os.lstat(f)[ST_MODE]) | |
| 65 | except OSError: | |
| 66 | return False | |
| 67 | ||
| 68 | ||
| 69 | ||
| 70 | def isfile(f): | |
| 71 | """True if f is a regular file.""" | |
| 72 | try: | |
| 73 | return S_ISREG(os.lstat(f)[ST_MODE]) | |
| 74 | except OSError: | |
| 75 | return False | |
| 76 | ||
| 77 | ||
| 78 | def pumpfile(fromfile, tofile): | |
| 79 | """Copy contents of one file to another.""" | |
| 80 | tofile.write(fromfile.read()) | |
| 81 | ||
| 82 | ||
| 83 | def uuid(): | |
| 84 | """Return a new UUID""" | |
| 85 | ||
| 86 |     ## XXX: Could alternatively read /proc/sys/kernel/random/uuid on
 | |
| 87 |     ## Linux, but we need something portable for other systems;
 | |
| 88 |     ## preferably an implementation in Python.
 | |
| 63
by mbp at sourcefrog fix up uuid command | 89 | try: | 
| 90 | return chomp(file('/proc/sys/kernel/random/uuid').readline()) | |
| 91 | except IOError: | |
| 92 | return chomp(os.popen('uuidgen').readline()) | |
| 93 | ||
| 1
by mbp at sourcefrog import from baz patch-364 | 94 | |
| 95 | def chomp(s): | |
| 96 | if s and (s[-1] == '\n'): | |
| 97 | return s[:-1] | |
| 98 | else: | |
| 99 | return s | |
| 100 | ||
| 101 | ||
| 102 | def sha_file(f): | |
| 103 | import sha | |
| 104 |     ## TODO: Maybe read in chunks to handle big files
 | |
| 105 | if hasattr(f, 'tell'): | |
| 106 | assert f.tell() == 0 | |
| 107 | s = sha.new() | |
| 108 | s.update(f.read()) | |
| 109 | return s.hexdigest() | |
| 110 | ||
| 111 | ||
| 112 | def sha_string(f): | |
| 113 | import sha | |
| 114 | s = sha.new() | |
| 115 | s.update(f) | |
| 116 | return s.hexdigest() | |
| 117 | ||
| 118 | ||
| 119 | ||
| 120 | def username(): | |
| 121 | """Return email-style username. | |
| 122 | ||
| 123 |     Something similar to 'Martin Pool <mbp@sourcefrog.net>'
 | |
| 124 | ||
| 125 |     :todo: Check it's reasonably well-formed.
 | |
| 126 | ||
| 127 |     :todo: Allow taking it from a dotfile to help people on windows
 | |
| 128 |            who can't easily set variables.
 | |
| 129 | ||
| 130 |     :todo: Cope without pwd module, which is only on unix. 
 | |
| 131 |     """
 | |
| 132 | e = os.environ.get('BZREMAIL') or os.environ.get('EMAIL') | |
| 133 | if e: return e | |
| 134 | ||
| 135 | import socket | |
| 136 | ||
| 137 | try: | |
| 138 | import pwd | |
| 139 | uid = os.getuid() | |
| 140 | w = pwd.getpwuid(uid) | |
| 25
by Martin Pool cope when gecos field doesn't have a comma | 141 | gecos = w.pw_gecos | 
| 142 | comma = gecos.find(',') | |
| 143 | if comma == -1: | |
| 144 | realname = gecos | |
| 145 | else: | |
| 146 | realname = gecos[:comma] | |
| 1
by mbp at sourcefrog import from baz patch-364 | 147 | return '%s <%s@%s>' % (realname, w.pw_name, socket.getfqdn()) | 
| 148 | except ImportError: | |
| 149 |         pass
 | |
| 150 | ||
| 151 | import getpass, socket | |
| 152 | return '<%s@%s>' % (getpass.getuser(), socket.getfqdn()) | |
| 153 | ||
| 154 | ||
| 155 | def user_email(): | |
| 156 | """Return just the email component of a username.""" | |
| 157 | e = os.environ.get('BZREMAIL') or os.environ.get('EMAIL') | |
| 158 | if e: | |
| 159 | import re | |
| 160 | m = re.search(r'[\w+.-]+@[\w+.-]+', e) | |
| 161 | if not m: | |
| 162 | bailout('%r is not a reasonable email address' % e) | |
| 163 | return m.group(0) | |
| 164 | ||
| 165 | ||
| 166 | import getpass, socket | |
| 167 | return '%s@%s' % (getpass.getuser(), socket.getfqdn()) | |
| 168 | ||
| 169 | ||
| 170 | ||
| 171 | ||
| 172 | def compare_files(a, b): | |
| 173 | """Returns true if equal in contents""" | |
| 174 |     # TODO: don't read the whole thing in one go.
 | |
| 74
by mbp at sourcefrog compare_files: read in one page at a time rather than | 175 | BUFSIZE = 4096 | 
| 176 | while True: | |
| 177 | ai = a.read(BUFSIZE) | |
| 178 | bi = b.read(BUFSIZE) | |
| 179 | if ai != bi: | |
| 180 | return False | |
| 181 | if ai == '': | |
| 182 | return True | |
| 1
by mbp at sourcefrog import from baz patch-364 | 183 | |
| 184 | ||
| 185 | ||
| 49
by mbp at sourcefrog fix local-time-offset calculation | 186 | def local_time_offset(t=None): | 
| 187 | """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 | 188 |     # python2.3 localtime() can't take None
 | 
| 189 | if t is None: | |
| 190 | t = time.time() | |
| 191 | ||
| 49
by mbp at sourcefrog fix local-time-offset calculation | 192 | if time.localtime(t).tm_isdst and time.daylight: | 
| 8
by mbp at sourcefrog store committer's timezone in revision and show | 193 | return -time.altzone | 
| 194 | else: | |
| 195 | return -time.timezone | |
| 196 | ||
| 197 | ||
| 198 | def format_date(t, offset=0, timezone='original'): | |
| 1
by mbp at sourcefrog import from baz patch-364 | 199 |     ## TODO: Perhaps a global option to use either universal or local time?
 | 
| 200 |     ## Or perhaps just let people set $TZ?
 | |
| 201 | import time | |
| 202 | ||
| 203 | assert isinstance(t, float) | |
| 204 | ||
| 8
by mbp at sourcefrog store committer's timezone in revision and show | 205 | if timezone == 'utc': | 
| 1
by mbp at sourcefrog import from baz patch-364 | 206 | tt = time.gmtime(t) | 
| 207 | offset = 0 | |
| 8
by mbp at sourcefrog store committer's timezone in revision and show | 208 | elif timezone == 'original': | 
| 23
by mbp at sourcefrog format_date: handle revisions with no timezone offset | 209 | if offset == None: | 
| 210 | offset = 0 | |
| 16
by mbp at sourcefrog fix inverted calculation for original timezone -> utc | 211 | tt = time.gmtime(t + offset) | 
| 12
by mbp at sourcefrog new --timezone option for bzr log | 212 | elif timezone == 'local': | 
| 1
by mbp at sourcefrog import from baz patch-364 | 213 | tt = time.localtime(t) | 
| 49
by mbp at sourcefrog fix local-time-offset calculation | 214 | offset = local_time_offset(t) | 
| 12
by mbp at sourcefrog new --timezone option for bzr log | 215 | else: | 
| 216 | bailout("unsupported timezone format %r", | |
| 217 | ['options are "utc", "original", "local"']) | |
| 8
by mbp at sourcefrog store committer's timezone in revision and show | 218 | |
| 1
by mbp at sourcefrog import from baz patch-364 | 219 | return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt) | 
| 8
by mbp at sourcefrog store committer's timezone in revision and show | 220 | + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60)) | 
| 1
by mbp at sourcefrog import from baz patch-364 | 221 | |
| 222 | ||
| 223 | def compact_date(when): | |
| 224 | return time.strftime('%Y%m%d%H%M%S', time.gmtime(when)) | |
| 225 | ||
| 226 | ||
| 227 | ||
| 228 | def filesize(f): | |
| 229 | """Return size of given open file.""" | |
| 230 | return os.fstat(f.fileno())[ST_SIZE] | |
| 231 | ||
| 232 | ||
| 233 | if hasattr(os, 'urandom'): # python 2.4 and later | |
| 234 | rand_bytes = os.urandom | |
| 235 | else: | |
| 236 |     # FIXME: No good on non-Linux
 | |
| 237 | _rand_file = file('/dev/urandom', 'rb') | |
| 238 | rand_bytes = _rand_file.read | |
| 239 | ||
| 240 | ||
| 241 | ## TODO: We could later have path objects that remember their list
 | |
| 242 | ## decomposition (might be too tricksy though.)
 | |
| 243 | ||
| 244 | def splitpath(p): | |
| 245 | """Turn string into list of parts. | |
| 246 | ||
| 247 |     >>> splitpath('a')
 | |
| 248 |     ['a']
 | |
| 249 |     >>> splitpath('a/b')
 | |
| 250 |     ['a', 'b']
 | |
| 251 |     >>> splitpath('a/./b')
 | |
| 252 |     ['a', 'b']
 | |
| 253 |     >>> splitpath('a/.b')
 | |
| 254 |     ['a', '.b']
 | |
| 255 |     >>> splitpath('a/../b')
 | |
| 256 |     Traceback (most recent call last):
 | |
| 257 |     ...
 | |
| 258 |     BzrError: ("sorry, '..' not allowed in path", [])
 | |
| 259 |     """
 | |
| 260 | assert isinstance(p, types.StringTypes) | |
| 261 | ps = [f for f in p.split('/') if f != '.'] | |
| 262 | for f in ps: | |
| 263 | if f == '..': | |
| 264 | bailout("sorry, %r not allowed in path" % f) | |
| 265 | return ps | |
| 266 | ||
| 267 | def joinpath(p): | |
| 268 | assert isinstance(p, list) | |
| 269 | for f in p: | |
| 270 | if (f == '..') or (f is None) or (f == ''): | |
| 271 | bailout("sorry, %r not allowed in path" % f) | |
| 272 | return '/'.join(p) | |
| 273 | ||
| 274 | ||
| 275 | def appendpath(p1, p2): | |
| 276 | if p1 == '': | |
| 277 | return p2 | |
| 278 | else: | |
| 279 | return p1 + '/' + p2 | |
| 280 | ||
| 281 | ||
| 282 | def extern_command(cmd, ignore_errors = False): | |
| 283 | mutter('external command: %s' % `cmd`) | |
| 284 | if os.system(cmd): | |
| 285 | if not ignore_errors: | |
| 286 | bailout('command failed') | |
| 287 |