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 | ||
| 444
by Martin Pool - cope on platforms with no urandom feature | 19 | import os, types, re, time, errno, sys | 
| 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 | |
| 694
by Martin Pool - weed out all remaining calls to bailout() and remove the function | 22 | from bzrlib.errors import BzrError | 
| 23 | from bzrlib.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.,:/_~-])') | |
| 969
by Martin Pool - Add less-sucky is_within_any | 41 | |
| 42 | _SLASH_RE = re.compile(r'[\\/]+') | |
| 43 | ||
| 1
by mbp at sourcefrog import from baz patch-364 | 44 | def quotefn(f): | 
| 779
by Martin Pool - better quotefn for windows: use doublequotes for strings with | 45 | """Return a quoted filename filename | 
| 46 | ||
| 47 |     This previously used backslash quoting, but that works poorly on
 | |
| 48 |     Windows."""
 | |
| 49 |     # TODO: I'm not really sure this is the best format either.x
 | |
| 50 | if _QUOTE_RE.search(f): | |
| 51 | return '"' + f + '"' | |
| 52 | else: | |
| 53 | return f | |
| 1
by mbp at sourcefrog import from baz patch-364 | 54 | |
| 55 | ||
| 56 | def file_kind(f): | |
| 57 | mode = os.lstat(f)[ST_MODE] | |
| 58 | if S_ISREG(mode): | |
| 59 | return 'file' | |
| 60 | elif S_ISDIR(mode): | |
| 61 | return 'directory' | |
| 20
by mbp at sourcefrog don't abort on trees that happen to contain symlinks | 62 | elif S_ISLNK(mode): | 
| 63 | return 'symlink' | |
| 1
by mbp at sourcefrog import from baz patch-364 | 64 | else: | 
| 488
by Martin Pool - new helper function kind_marker() | 65 | raise BzrError("can't handle file kind with mode %o of %r" % (mode, f)) | 
| 66 | ||
| 67 | ||
| 68 | def kind_marker(kind): | |
| 69 | if kind == 'file': | |
| 70 | return '' | |
| 71 | elif kind == 'directory': | |
| 72 | return '/' | |
| 73 | elif kind == 'symlink': | |
| 74 | return '@' | |
| 75 | else: | |
| 76 | raise BzrError('invalid file kind %r' % kind) | |
| 1
by mbp at sourcefrog import from baz patch-364 | 77 | |
| 78 | ||
| 79 | ||
| 779
by Martin Pool - better quotefn for windows: use doublequotes for strings with | 80 | def backup_file(fn): | 
| 81 | """Copy a file to a backup. | |
| 82 | ||
| 83 |     Backups are named in GNU-style, with a ~ suffix.
 | |
| 84 | ||
| 85 |     If the file is already a backup, it's not copied.
 | |
| 86 |     """
 | |
| 87 | import os | |
| 88 | if fn[-1] == '~': | |
| 89 |         return
 | |
| 90 | bfn = fn + '~' | |
| 91 | ||
| 92 | inf = file(fn, 'rb') | |
| 93 | try: | |
| 94 | content = inf.read() | |
| 95 | finally: | |
| 96 | inf.close() | |
| 97 | ||
| 98 | outf = file(bfn, 'wb') | |
| 99 | try: | |
| 100 | outf.write(content) | |
| 101 | finally: | |
| 102 | outf.close() | |
| 103 | ||
| 909
by Martin Pool - merge John's code to give the tree root an explicit file id | 104 | def rename(path_from, path_to): | 
| 105 | """Basically the same as os.rename() just special for win32""" | |
| 106 | if sys.platform == 'win32': | |
| 107 | try: | |
| 108 | os.remove(path_to) | |
| 109 | except OSError, e: | |
| 110 | if e.errno != e.ENOENT: | |
| 111 |                 raise
 | |
| 112 | os.rename(path_from, path_to) | |
| 113 | ||
| 114 | ||
| 779
by Martin Pool - better quotefn for windows: use doublequotes for strings with | 115 | |
| 116 | ||
| 117 | ||
| 1
by mbp at sourcefrog import from baz patch-364 | 118 | def isdir(f): | 
| 119 | """True if f is an accessible directory.""" | |
| 120 | try: | |
| 121 | return S_ISDIR(os.lstat(f)[ST_MODE]) | |
| 122 | except OSError: | |
| 123 | return False | |
| 124 | ||
| 125 | ||
| 126 | ||
| 127 | def isfile(f): | |
| 128 | """True if f is a regular file.""" | |
| 129 | try: | |
| 130 | return S_ISREG(os.lstat(f)[ST_MODE]) | |
| 131 | except OSError: | |
| 132 | return False | |
| 133 | ||
| 134 | ||
| 485
by Martin Pool - move commit code into its own module | 135 | def is_inside(dir, fname): | 
| 136 | """True if fname is inside dir. | |
| 969
by Martin Pool - Add less-sucky is_within_any | 137 |     
 | 
| 138 |     The parameters should typically be passed to os.path.normpath first, so
 | |
| 139 |     that . and .. and repeated slashes are eliminated, and the separators
 | |
| 140 |     are canonical for the platform.
 | |
| 141 |     
 | |
| 979
by Martin Pool - fix bugs and add tests for doing commit of selected directories | 142 |     The empty string as a dir name is taken as top-of-tree and matches 
 | 
| 143 |     everything.
 | |
| 144 |     
 | |
| 969
by Martin Pool - Add less-sucky is_within_any | 145 |     >>> is_inside('src', 'src/foo.c')
 | 
| 146 |     True
 | |
| 147 |     >>> is_inside('src', 'srccontrol')
 | |
| 148 |     False
 | |
| 149 |     >>> is_inside('src', 'src/a/a/a/foo.c')
 | |
| 150 |     True
 | |
| 151 |     >>> is_inside('foo.c', 'foo.c')
 | |
| 152 |     True
 | |
| 979
by Martin Pool - fix bugs and add tests for doing commit of selected directories | 153 |     >>> is_inside('foo.c', '')
 | 
| 154 |     False
 | |
| 155 |     >>> is_inside('', 'foo.c')
 | |
| 156 |     True
 | |
| 485
by Martin Pool - move commit code into its own module | 157 |     """
 | 
| 969
by Martin Pool - Add less-sucky is_within_any | 158 |     # XXX: Most callers of this can actually do something smarter by 
 | 
| 159 |     # looking at the inventory
 | |
| 972
by Martin Pool - less dodgy is_inside function | 160 | if dir == fname: | 
| 161 | return True | |
| 162 | ||
| 979
by Martin Pool - fix bugs and add tests for doing commit of selected directories | 163 | if dir == '': | 
| 164 | return True | |
| 165 | ||
| 972
by Martin Pool - less dodgy is_inside function | 166 | if dir[-1] != os.sep: | 
| 167 | dir += os.sep | |
| 168 | ||
| 169 | return fname.startswith(dir) | |
| 170 | ||
| 485
by Martin Pool - move commit code into its own module | 171 | |
| 172 | def is_inside_any(dir_list, fname): | |
| 173 | """True if fname is inside any of given dirs.""" | |
| 174 | for dirname in dir_list: | |
| 175 | if is_inside(dirname, fname): | |
| 176 | return True | |
| 177 | else: | |
| 178 | return False | |
| 179 | ||
| 180 | ||
| 1
by mbp at sourcefrog import from baz patch-364 | 181 | def pumpfile(fromfile, tofile): | 
| 182 | """Copy contents of one file to another.""" | |
| 183 | tofile.write(fromfile.read()) | |
| 184 | ||
| 185 | ||
| 186 | def uuid(): | |
| 187 | """Return a new UUID""" | |
| 63
by mbp at sourcefrog fix up uuid command | 188 | try: | 
| 319
by Martin Pool - remove trivial chomp() function | 189 | return file('/proc/sys/kernel/random/uuid').readline().rstrip('\n') | 
| 63
by mbp at sourcefrog fix up uuid command | 190 | except IOError: | 
| 191 | return chomp(os.popen('uuidgen').readline()) | |
| 192 | ||
| 1
by mbp at sourcefrog import from baz patch-364 | 193 | |
| 194 | def sha_file(f): | |
| 195 | import sha | |
| 196 | if hasattr(f, 'tell'): | |
| 197 | assert f.tell() == 0 | |
| 198 | s = sha.new() | |
| 320
by Martin Pool - Compute SHA-1 of files in chunks | 199 | BUFSIZE = 128<<10 | 
| 200 | while True: | |
| 201 | b = f.read(BUFSIZE) | |
| 202 | if not b: | |
| 203 |             break
 | |
| 204 | s.update(b) | |
| 1
by mbp at sourcefrog import from baz patch-364 | 205 | return s.hexdigest() | 
| 206 | ||
| 207 | ||
| 208 | def sha_string(f): | |
| 209 | import sha | |
| 210 | s = sha.new() | |
| 211 | s.update(f) | |
| 212 | return s.hexdigest() | |
| 213 | ||
| 214 | ||
| 215 | ||
| 124
by mbp at sourcefrog - check file text for past revisions is correct | 216 | def fingerprint_file(f): | 
| 217 | import sha | |
| 218 | s = sha.new() | |
| 126
by mbp at sourcefrog Use just one big read to fingerprint files | 219 | b = f.read() | 
| 220 | s.update(b) | |
| 221 | size = len(b) | |
| 124
by mbp at sourcefrog - check file text for past revisions is correct | 222 | return {'size': size, | 
| 223 | 'sha1': s.hexdigest()} | |
| 224 | ||
| 225 | ||
| 258
by Martin Pool - Take email from ~/.bzr.conf/email | 226 | def config_dir(): | 
| 227 | """Return per-user configuration directory. | |
| 228 | ||
| 229 |     By default this is ~/.bzr.conf/
 | |
| 230 |     
 | |
| 231 |     TODO: Global option --config-dir to override this.
 | |
| 232 |     """
 | |
| 233 | return os.path.expanduser("~/.bzr.conf") | |
| 234 | ||
| 235 | ||
| 252
by Martin Pool - Don't use host fqdn for default user name, because DNS tends | 236 | def _auto_user_id(): | 
| 246
by mbp at sourcefrog - unicode decoding in getting email and userid strings | 237 | """Calculate automatic user identification. | 
| 238 | ||
| 239 |     Returns (realname, email).
 | |
| 240 | ||
| 252
by Martin Pool - Don't use host fqdn for default user name, because DNS tends | 241 |     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 | 242 | |
| 252
by Martin Pool - Don't use host fqdn for default user name, because DNS tends | 243 |     This previously used the FQDN as the default domain, but that can
 | 
| 244 |     be very slow on machines where DNS is broken.  So now we simply
 | |
| 245 |     use the hostname.
 | |
| 1
by mbp at sourcefrog import from baz patch-364 | 246 |     """
 | 
| 251
by mbp at sourcefrog - factor out locale.getpreferredencoding() | 247 | import socket | 
| 246
by mbp at sourcefrog - unicode decoding in getting email and userid strings | 248 | |
| 249 |     # XXX: Any good way to get real user name on win32?
 | |
| 250 | ||
| 1
by mbp at sourcefrog import from baz patch-364 | 251 | try: | 
| 252 | import pwd | |
| 253 | uid = os.getuid() | |
| 254 | w = pwd.getpwuid(uid) | |
| 251
by mbp at sourcefrog - factor out locale.getpreferredencoding() | 255 | gecos = w.pw_gecos.decode(bzrlib.user_encoding) | 
| 256 | username = w.pw_name.decode(bzrlib.user_encoding) | |
| 25
by Martin Pool cope when gecos field doesn't have a comma | 257 | comma = gecos.find(',') | 
| 258 | if comma == -1: | |
| 259 | realname = gecos | |
| 260 | else: | |
| 261 | realname = gecos[:comma] | |
| 256
by Martin Pool - More handling of auto-username case | 262 | if not realname: | 
| 263 | realname = username | |
| 246
by mbp at sourcefrog - unicode decoding in getting email and userid strings | 264 | |
| 1
by mbp at sourcefrog import from baz patch-364 | 265 | except ImportError: | 
| 246
by mbp at sourcefrog - unicode decoding in getting email and userid strings | 266 | import getpass | 
| 256
by Martin Pool - More handling of auto-username case | 267 | realname = username = getpass.getuser().decode(bzrlib.user_encoding) | 
| 252
by Martin Pool - Don't use host fqdn for default user name, because DNS tends | 268 | |
| 256
by Martin Pool - More handling of auto-username case | 269 | return realname, (username + '@' + socket.gethostname()) | 
| 252
by Martin Pool - Don't use host fqdn for default user name, because DNS tends | 270 | |
| 271 | ||
| 272 | def _get_user_id(): | |
| 258
by Martin Pool - Take email from ~/.bzr.conf/email | 273 | """Return the full user id from a file or environment variable. | 
| 274 | ||
| 275 |     TODO: Allow taking this from a file in the branch directory too
 | |
| 276 |     for per-branch ids."""
 | |
| 252
by Martin Pool - Don't use host fqdn for default user name, because DNS tends | 277 | v = os.environ.get('BZREMAIL') | 
| 278 | if v: | |
| 279 | return v.decode(bzrlib.user_encoding) | |
| 280 | ||
| 281 | try: | |
| 258
by Martin Pool - Take email from ~/.bzr.conf/email | 282 | return (open(os.path.join(config_dir(), "email")) | 
| 252
by Martin Pool - Don't use host fqdn for default user name, because DNS tends | 283 | .read() | 
| 284 | .decode(bzrlib.user_encoding) | |
| 285 | .rstrip("\r\n")) | |
| 256
by Martin Pool - More handling of auto-username case | 286 | except IOError, e: | 
| 287 | if e.errno != errno.ENOENT: | |
| 252
by Martin Pool - Don't use host fqdn for default user name, because DNS tends | 288 | raise e | 
| 289 | ||
| 290 | v = os.environ.get('EMAIL') | |
| 291 | if v: | |
| 292 | return v.decode(bzrlib.user_encoding) | |
| 293 | else: | |
| 294 | return None | |
| 246
by mbp at sourcefrog - unicode decoding in getting email and userid strings | 295 | |
| 296 | ||
| 297 | def username(): | |
| 298 | """Return email-style username. | |
| 299 | ||
| 300 |     Something similar to 'Martin Pool <mbp@sourcefrog.net>'
 | |
| 301 | ||
| 254
by Martin Pool - Doc cleanups from Magnus Therning | 302 |     TODO: Check it's reasonably well-formed.
 | 
| 246
by mbp at sourcefrog - unicode decoding in getting email and userid strings | 303 |     """
 | 
| 252
by Martin Pool - Don't use host fqdn for default user name, because DNS tends | 304 | v = _get_user_id() | 
| 305 | if v: | |
| 306 | return v | |
| 307 | ||
| 308 | name, email = _auto_user_id() | |
| 246
by mbp at sourcefrog - unicode decoding in getting email and userid strings | 309 | if name: | 
| 310 | return '%s <%s>' % (name, email) | |
| 311 | else: | |
| 312 | return email | |
| 1
by mbp at sourcefrog import from baz patch-364 | 313 | |
| 314 | ||
| 183
by mbp at sourcefrog pychecker fixups | 315 | _EMAIL_RE = re.compile(r'[\w+.-]+@[\w+.-]+') | 
| 1
by mbp at sourcefrog import from baz patch-364 | 316 | def user_email(): | 
| 317 | """Return just the email component of a username.""" | |
| 252
by Martin Pool - Don't use host fqdn for default user name, because DNS tends | 318 | e = _get_user_id() | 
| 1
by mbp at sourcefrog import from baz patch-364 | 319 | if e: | 
| 183
by mbp at sourcefrog pychecker fixups | 320 | m = _EMAIL_RE.search(e) | 
| 1
by mbp at sourcefrog import from baz patch-364 | 321 | if not m: | 
| 694
by Martin Pool - weed out all remaining calls to bailout() and remove the function | 322 | raise BzrError("%r doesn't seem to contain a reasonable email address" % e) | 
| 1
by mbp at sourcefrog import from baz patch-364 | 323 | return m.group(0) | 
| 324 | ||
| 252
by Martin Pool - Don't use host fqdn for default user name, because DNS tends | 325 | return _auto_user_id()[1] | 
| 1
by mbp at sourcefrog import from baz patch-364 | 326 | |
| 327 | ||
| 328 | ||
| 329 | def compare_files(a, b): | |
| 330 | """Returns true if equal in contents""" | |
| 74
by mbp at sourcefrog compare_files: read in one page at a time rather than | 331 | BUFSIZE = 4096 | 
| 332 | while True: | |
| 333 | ai = a.read(BUFSIZE) | |
| 334 | bi = b.read(BUFSIZE) | |
| 335 | if ai != bi: | |
| 336 | return False | |
| 337 | if ai == '': | |
| 338 | return True | |
| 1
by mbp at sourcefrog import from baz patch-364 | 339 | |
| 340 | ||
| 341 | ||
| 49
by mbp at sourcefrog fix local-time-offset calculation | 342 | def local_time_offset(t=None): | 
| 343 | """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 | 344 |     # python2.3 localtime() can't take None
 | 
| 183
by mbp at sourcefrog pychecker fixups | 345 | if t == None: | 
| 73
by mbp at sourcefrog fix time.localtime call for python 2.3 | 346 | t = time.time() | 
| 347 | ||
| 49
by mbp at sourcefrog fix local-time-offset calculation | 348 | if time.localtime(t).tm_isdst and time.daylight: | 
| 8
by mbp at sourcefrog store committer's timezone in revision and show | 349 | return -time.altzone | 
| 350 | else: | |
| 351 | return -time.timezone | |
| 352 | ||
| 353 | ||
| 354 | def format_date(t, offset=0, timezone='original'): | |
| 1
by mbp at sourcefrog import from baz patch-364 | 355 |     ## TODO: Perhaps a global option to use either universal or local time?
 | 
| 356 |     ## Or perhaps just let people set $TZ?
 | |
| 357 | assert isinstance(t, float) | |
| 358 | ||
| 8
by mbp at sourcefrog store committer's timezone in revision and show | 359 | if timezone == 'utc': | 
| 1
by mbp at sourcefrog import from baz patch-364 | 360 | tt = time.gmtime(t) | 
| 361 | offset = 0 | |
| 8
by mbp at sourcefrog store committer's timezone in revision and show | 362 | elif timezone == 'original': | 
| 23
by mbp at sourcefrog format_date: handle revisions with no timezone offset | 363 | if offset == None: | 
| 364 | offset = 0 | |
| 16
by mbp at sourcefrog fix inverted calculation for original timezone -> utc | 365 | tt = time.gmtime(t + offset) | 
| 12
by mbp at sourcefrog new --timezone option for bzr log | 366 | elif timezone == 'local': | 
| 1
by mbp at sourcefrog import from baz patch-364 | 367 | tt = time.localtime(t) | 
| 49
by mbp at sourcefrog fix local-time-offset calculation | 368 | offset = local_time_offset(t) | 
| 12
by mbp at sourcefrog new --timezone option for bzr log | 369 | else: | 
| 997
by Martin Pool - better error from format_date() | 370 | raise BzrError("unsupported timezone format %r" % timezone, | 
| 371 | ['options are "utc", "original", "local"']) | |
| 8
by mbp at sourcefrog store committer's timezone in revision and show | 372 | |
| 1
by mbp at sourcefrog import from baz patch-364 | 373 | return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt) | 
| 8
by mbp at sourcefrog store committer's timezone in revision and show | 374 | + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60)) | 
| 1
by mbp at sourcefrog import from baz patch-364 | 375 | |
| 376 | ||
| 377 | def compact_date(when): | |
| 378 | return time.strftime('%Y%m%d%H%M%S', time.gmtime(when)) | |
| 379 | ||
| 380 | ||
| 381 | ||
| 382 | def filesize(f): | |
| 383 | """Return size of given open file.""" | |
| 384 | return os.fstat(f.fileno())[ST_SIZE] | |
| 385 | ||
| 386 | ||
| 387 | if hasattr(os, 'urandom'): # python 2.4 and later | |
| 388 | rand_bytes = os.urandom | |
| 444
by Martin Pool - cope on platforms with no urandom feature | 389 | elif sys.platform == 'linux2': | 
| 390 | rand_bytes = file('/dev/urandom', 'rb').read | |
| 1
by mbp at sourcefrog import from baz patch-364 | 391 | else: | 
| 444
by Martin Pool - cope on platforms with no urandom feature | 392 |     # not well seeded, but better than nothing
 | 
| 393 | def rand_bytes(n): | |
| 394 | import random | |
| 395 | s = '' | |
| 396 | while n: | |
| 397 | s += chr(random.randint(0, 255)) | |
| 398 | n -= 1 | |
| 399 | return s | |
| 1
by mbp at sourcefrog import from baz patch-364 | 400 | |
| 401 | ||
| 402 | ## TODO: We could later have path objects that remember their list
 | |
| 403 | ## decomposition (might be too tricksy though.)
 | |
| 404 | ||
| 405 | def splitpath(p): | |
| 406 | """Turn string into list of parts. | |
| 407 | ||
| 408 |     >>> splitpath('a')
 | |
| 409 |     ['a']
 | |
| 410 |     >>> splitpath('a/b')
 | |
| 411 |     ['a', 'b']
 | |
| 412 |     >>> splitpath('a/./b')
 | |
| 413 |     ['a', 'b']
 | |
| 414 |     >>> splitpath('a/.b')
 | |
| 415 |     ['a', '.b']
 | |
| 416 |     >>> splitpath('a/../b')
 | |
| 184
by mbp at sourcefrog pychecker fixups | 417 |     Traceback (most recent call last):
 | 
| 1
by mbp at sourcefrog import from baz patch-364 | 418 |     ...
 | 
| 694
by Martin Pool - weed out all remaining calls to bailout() and remove the function | 419 |     BzrError: sorry, '..' not allowed in path
 | 
| 1
by mbp at sourcefrog import from baz patch-364 | 420 |     """
 | 
| 421 | assert isinstance(p, types.StringTypes) | |
| 271
by Martin Pool - Windows path fixes | 422 | |
| 423 |     # split on either delimiter because people might use either on
 | |
| 424 |     # Windows
 | |
| 425 | ps = re.split(r'[\\/]', p) | |
| 426 | ||
| 427 | rps = [] | |
| 1
by mbp at sourcefrog import from baz patch-364 | 428 | for f in ps: | 
| 429 | if f == '..': | |
| 694
by Martin Pool - weed out all remaining calls to bailout() and remove the function | 430 | raise BzrError("sorry, %r not allowed in path" % f) | 
| 271
by Martin Pool - Windows path fixes | 431 | elif (f == '.') or (f == ''): | 
| 432 |             pass
 | |
| 433 | else: | |
| 434 | rps.append(f) | |
| 435 | return rps | |
| 1
by mbp at sourcefrog import from baz patch-364 | 436 | |
| 437 | def joinpath(p): | |
| 438 | assert isinstance(p, list) | |
| 439 | for f in p: | |
| 183
by mbp at sourcefrog pychecker fixups | 440 | if (f == '..') or (f == None) or (f == ''): | 
| 694
by Martin Pool - weed out all remaining calls to bailout() and remove the function | 441 | raise BzrError("sorry, %r not allowed in path" % f) | 
| 271
by Martin Pool - Windows path fixes | 442 | return os.path.join(*p) | 
| 1
by mbp at sourcefrog import from baz patch-364 | 443 | |
| 444 | ||
| 445 | def appendpath(p1, p2): | |
| 446 | if p1 == '': | |
| 447 | return p2 | |
| 448 | else: | |
| 271
by Martin Pool - Windows path fixes | 449 | return os.path.join(p1, p2) | 
| 1
by mbp at sourcefrog import from baz patch-364 | 450 | |
| 451 | ||
| 452 | def extern_command(cmd, ignore_errors = False): | |
| 453 | mutter('external command: %s' % `cmd`) | |
| 454 | if os.system(cmd): | |
| 455 | if not ignore_errors: | |
| 694
by Martin Pool - weed out all remaining calls to bailout() and remove the function | 456 | raise BzrError('command failed') | 
| 1
by mbp at sourcefrog import from baz patch-364 | 457 | |
| 763
by Martin Pool - Patch from Torsten Marek to take commit messages through an | 458 | |
| 459 | def _read_config_value(name): | |
| 460 | """Read a config value from the file ~/.bzr.conf/<name> | |
| 461 |     Return None if the file does not exist"""
 | |
| 462 | try: | |
| 463 | f = file(os.path.join(config_dir(), name), "r") | |
| 464 | return f.read().decode(bzrlib.user_encoding).rstrip("\r\n") | |
| 465 | except IOError, e: | |
| 466 | if e.errno == errno.ENOENT: | |
| 467 | return None | |
| 468 |         raise
 | |
| 469 | ||
| 470 | ||
| 471 | def _get_editor(): | |
| 472 | """Return a sequence of possible editor binaries for the current platform""" | |
| 473 | e = _read_config_value("editor") | |
| 474 | if e is not None: | |
| 475 | yield e | |
| 476 | ||
| 477 | if os.name == "windows": | |
| 478 | yield "notepad.exe" | |
| 479 | elif os.name == "posix": | |
| 480 | try: | |
| 481 | yield os.environ["EDITOR"] | |
| 482 | except KeyError: | |
| 483 | yield "/usr/bin/vi" | |
| 484 | ||
| 485 | ||
| 486 | def _run_editor(filename): | |
| 487 | """Try to execute an editor to edit the commit message. Returns True on success, | |
| 488 |     False on failure"""
 | |
| 489 | for e in _get_editor(): | |
| 490 | x = os.spawnvp(os.P_WAIT, e, (e, filename)) | |
| 491 | if x == 0: | |
| 492 | return True | |
| 493 | elif x == 127: | |
| 494 |             continue
 | |
| 495 | else: | |
| 496 |             break
 | |
| 497 | raise BzrError("Could not start any editor. Please specify $EDITOR or use ~/.bzr.conf/editor") | |
| 498 | return False | |
| 499 | ||
| 500 | ||
| 501 | def get_text_message(infotext, ignoreline = "default"): | |
| 502 | import tempfile | |
| 503 | ||
| 504 | if ignoreline == "default": | |
| 505 | ignoreline = "-- This line and the following will be ignored --" | |
| 506 | ||
| 507 | try: | |
| 508 | tmp_fileno, msgfilename = tempfile.mkstemp() | |
| 509 | msgfile = os.close(tmp_fileno) | |
| 510 | if infotext is not None and infotext != "": | |
| 511 | hasinfo = True | |
| 512 | msgfile = file(msgfilename, "w") | |
| 513 | msgfile.write("\n\n%s\n\n%s" % (ignoreline, infotext)) | |
| 514 | msgfile.close() | |
| 515 | else: | |
| 516 | hasinfo = False | |
| 517 | ||
| 518 | if not _run_editor(msgfilename): | |
| 519 | return None | |
| 520 | ||
| 521 | started = False | |
| 522 | msg = [] | |
| 523 | lastline, nlines = 0, 0 | |
| 524 | for line in file(msgfilename, "r"): | |
| 525 | stripped_line = line.strip() | |
| 526 |             # strip empty line before the log message starts
 | |
| 527 | if not started: | |
| 528 | if stripped_line != "": | |
| 529 | started = True | |
| 530 | else: | |
| 531 |                     continue
 | |
| 532 |             # check for the ignore line only if there
 | |
| 533 |             # is additional information at the end
 | |
| 534 | if hasinfo and stripped_line == ignoreline: | |
| 535 |                 break
 | |
| 536 | nlines += 1 | |
| 537 |             # keep track of the last line that had some content
 | |
| 538 | if stripped_line != "": | |
| 539 | lastline = nlines | |
| 540 | msg.append(line) | |
| 541 | ||
| 542 | if len(msg) == 0: | |
| 543 | return None | |
| 544 |         # delete empty lines at the end
 | |
| 545 | del msg[lastline:] | |
| 546 |         # add a newline at the end, if needed
 | |
| 547 | if not msg[-1].endswith("\n"): | |
| 548 | return "%s%s" % ("".join(msg), "\n") | |
| 549 | else: | |
| 550 | return "".join(msg) | |
| 551 | finally: | |
| 552 |         # delete the msg file in any case
 | |
| 553 | try: os.unlink(msgfilename) | |
| 554 | except IOError: pass |