build/util/hg.py

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 """Functions for interacting with hg"""
michael@0 2 import os
michael@0 3 import re
michael@0 4 import subprocess
michael@0 5 from urlparse import urlsplit
michael@0 6 from ConfigParser import RawConfigParser
michael@0 7
michael@0 8 from util.commands import run_cmd, get_output, remove_path
michael@0 9 from util.retry import retry
michael@0 10
michael@0 11 import logging
michael@0 12 log = logging.getLogger(__name__)
michael@0 13
michael@0 14
michael@0 15 class DefaultShareBase:
michael@0 16 pass
michael@0 17 DefaultShareBase = DefaultShareBase()
michael@0 18
michael@0 19
michael@0 20 class HgUtilError(Exception):
michael@0 21 pass
michael@0 22
michael@0 23
michael@0 24 def _make_absolute(repo):
michael@0 25 if repo.startswith("file://"):
michael@0 26 path = repo[len("file://"):]
michael@0 27 repo = "file://%s" % os.path.abspath(path)
michael@0 28 elif "://" not in repo:
michael@0 29 repo = os.path.abspath(repo)
michael@0 30 return repo
michael@0 31
michael@0 32
michael@0 33 def make_hg_url(hgHost, repoPath, protocol='https', revision=None,
michael@0 34 filename=None):
michael@0 35 """construct a valid hg url from a base hg url (hg.mozilla.org),
michael@0 36 repoPath, revision and possible filename"""
michael@0 37 base = '%s://%s' % (protocol, hgHost)
michael@0 38 repo = '/'.join(p.strip('/') for p in [base, repoPath])
michael@0 39 if not filename:
michael@0 40 if not revision:
michael@0 41 return repo
michael@0 42 else:
michael@0 43 return '/'.join([p.strip('/') for p in [repo, 'rev', revision]])
michael@0 44 else:
michael@0 45 assert revision
michael@0 46 return '/'.join([p.strip('/') for p in [repo, 'raw-file', revision, filename]])
michael@0 47
michael@0 48
michael@0 49 def get_repo_name(repo):
michael@0 50 return repo.rstrip('/').split('/')[-1]
michael@0 51
michael@0 52
michael@0 53 def get_repo_path(repo):
michael@0 54 repo = _make_absolute(repo)
michael@0 55 if repo.startswith("/"):
michael@0 56 return repo.lstrip("/")
michael@0 57 else:
michael@0 58 return urlsplit(repo).path.lstrip("/")
michael@0 59
michael@0 60
michael@0 61 def get_revision(path):
michael@0 62 """Returns which revision directory `path` currently has checked out."""
michael@0 63 return get_output(['hg', 'parent', '--template', '{node|short}'], cwd=path)
michael@0 64
michael@0 65
michael@0 66 def get_branch(path):
michael@0 67 return get_output(['hg', 'branch'], cwd=path).strip()
michael@0 68
michael@0 69
michael@0 70 def get_branches(path):
michael@0 71 branches = []
michael@0 72 for line in get_output(['hg', 'branches', '-c'], cwd=path).splitlines():
michael@0 73 branches.append(line.split()[0])
michael@0 74 return branches
michael@0 75
michael@0 76
michael@0 77 def is_hg_cset(rev):
michael@0 78 """Retruns True if passed revision represents a valid HG revision
michael@0 79 (long or short(er) 40 bit hex)"""
michael@0 80 try:
michael@0 81 int(rev, 16)
michael@0 82 return True
michael@0 83 except (TypeError, ValueError):
michael@0 84 return False
michael@0 85
michael@0 86
michael@0 87 def hg_ver():
michael@0 88 """Returns the current version of hg, as a tuple of
michael@0 89 (major, minor, build)"""
michael@0 90 ver_string = get_output(['hg', '-q', 'version'])
michael@0 91 match = re.search("\(version ([0-9.]+)\)", ver_string)
michael@0 92 if match:
michael@0 93 bits = match.group(1).split(".")
michael@0 94 if len(bits) < 3:
michael@0 95 bits += (0,)
michael@0 96 ver = tuple(int(b) for b in bits)
michael@0 97 else:
michael@0 98 ver = (0, 0, 0)
michael@0 99 log.debug("Running hg version %s", ver)
michael@0 100 return ver
michael@0 101
michael@0 102
michael@0 103 def purge(dest):
michael@0 104 """Purge the repository of all untracked and ignored files."""
michael@0 105 try:
michael@0 106 run_cmd(['hg', '--config', 'extensions.purge=', 'purge',
michael@0 107 '-a', '--all', dest], cwd=dest)
michael@0 108 except subprocess.CalledProcessError, e:
michael@0 109 log.debug('purge failed: %s' % e)
michael@0 110 raise
michael@0 111
michael@0 112
michael@0 113 def update(dest, branch=None, revision=None):
michael@0 114 """Updates working copy `dest` to `branch` or `revision`. If neither is
michael@0 115 set then the working copy will be updated to the latest revision on the
michael@0 116 current branch. Local changes will be discarded."""
michael@0 117 # If we have a revision, switch to that
michael@0 118 if revision is not None:
michael@0 119 cmd = ['hg', 'update', '-C', '-r', revision]
michael@0 120 run_cmd(cmd, cwd=dest)
michael@0 121 else:
michael@0 122 # Check & switch branch
michael@0 123 local_branch = get_output(['hg', 'branch'], cwd=dest).strip()
michael@0 124
michael@0 125 cmd = ['hg', 'update', '-C']
michael@0 126
michael@0 127 # If this is different, checkout the other branch
michael@0 128 if branch and branch != local_branch:
michael@0 129 cmd.append(branch)
michael@0 130
michael@0 131 run_cmd(cmd, cwd=dest)
michael@0 132 return get_revision(dest)
michael@0 133
michael@0 134
michael@0 135 def clone(repo, dest, branch=None, revision=None, update_dest=True,
michael@0 136 clone_by_rev=False, mirrors=None, bundles=None):
michael@0 137 """Clones hg repo and places it at `dest`, replacing whatever else is
michael@0 138 there. The working copy will be empty.
michael@0 139
michael@0 140 If `revision` is set, only the specified revision and its ancestors will
michael@0 141 be cloned.
michael@0 142
michael@0 143 If `update_dest` is set, then `dest` will be updated to `revision` if
michael@0 144 set, otherwise to `branch`, otherwise to the head of default.
michael@0 145
michael@0 146 If `mirrors` is set, will try and clone from the mirrors before
michael@0 147 cloning from `repo`.
michael@0 148
michael@0 149 If `bundles` is set, will try and download the bundle first and
michael@0 150 unbundle it. If successful, will pull in new revisions from mirrors or
michael@0 151 the master repo. If unbundling fails, will fall back to doing a regular
michael@0 152 clone from mirrors or the master repo.
michael@0 153
michael@0 154 Regardless of how the repository ends up being cloned, the 'default' path
michael@0 155 will point to `repo`.
michael@0 156 """
michael@0 157 if os.path.exists(dest):
michael@0 158 remove_path(dest)
michael@0 159
michael@0 160 if bundles:
michael@0 161 log.info("Attempting to initialize clone with bundles")
michael@0 162 for bundle in bundles:
michael@0 163 if os.path.exists(dest):
michael@0 164 remove_path(dest)
michael@0 165 init(dest)
michael@0 166 log.info("Trying to use bundle %s", bundle)
michael@0 167 try:
michael@0 168 if not unbundle(bundle, dest):
michael@0 169 remove_path(dest)
michael@0 170 continue
michael@0 171 adjust_paths(dest, default=repo)
michael@0 172 # Now pull / update
michael@0 173 return pull(repo, dest, update_dest=update_dest,
michael@0 174 mirrors=mirrors, revision=revision, branch=branch)
michael@0 175 except Exception:
michael@0 176 remove_path(dest)
michael@0 177 log.exception("Problem unbundling/pulling from %s", bundle)
michael@0 178 continue
michael@0 179 else:
michael@0 180 log.info("Using bundles failed; falling back to clone")
michael@0 181
michael@0 182 if mirrors:
michael@0 183 log.info("Attempting to clone from mirrors")
michael@0 184 for mirror in mirrors:
michael@0 185 log.info("Cloning from %s", mirror)
michael@0 186 try:
michael@0 187 retval = clone(mirror, dest, branch, revision,
michael@0 188 update_dest=update_dest, clone_by_rev=clone_by_rev)
michael@0 189 adjust_paths(dest, default=repo)
michael@0 190 return retval
michael@0 191 except:
michael@0 192 log.exception("Problem cloning from mirror %s", mirror)
michael@0 193 continue
michael@0 194 else:
michael@0 195 log.info("Pulling from mirrors failed; falling back to %s", repo)
michael@0 196 # We may have a partial repo here; mercurial() copes with that
michael@0 197 # We need to make sure our paths are correct though
michael@0 198 if os.path.exists(os.path.join(dest, '.hg')):
michael@0 199 adjust_paths(dest, default=repo)
michael@0 200 return mercurial(repo, dest, branch, revision, autoPurge=True,
michael@0 201 update_dest=update_dest, clone_by_rev=clone_by_rev)
michael@0 202
michael@0 203 cmd = ['hg', 'clone']
michael@0 204 if not update_dest:
michael@0 205 cmd.append('-U')
michael@0 206
michael@0 207 if clone_by_rev:
michael@0 208 if revision:
michael@0 209 cmd.extend(['-r', revision])
michael@0 210 elif branch:
michael@0 211 # hg >= 1.6 supports -b branch for cloning
michael@0 212 ver = hg_ver()
michael@0 213 if ver >= (1, 6, 0):
michael@0 214 cmd.extend(['-b', branch])
michael@0 215
michael@0 216 cmd.extend([repo, dest])
michael@0 217 run_cmd(cmd)
michael@0 218
michael@0 219 if update_dest:
michael@0 220 return update(dest, branch, revision)
michael@0 221
michael@0 222
michael@0 223 def common_args(revision=None, branch=None, ssh_username=None, ssh_key=None):
michael@0 224 """Fill in common hg arguments, encapsulating logic checks that depend on
michael@0 225 mercurial versions and provided arguments"""
michael@0 226 args = []
michael@0 227 if ssh_username or ssh_key:
michael@0 228 opt = ['-e', 'ssh']
michael@0 229 if ssh_username:
michael@0 230 opt[1] += ' -l %s' % ssh_username
michael@0 231 if ssh_key:
michael@0 232 opt[1] += ' -i %s' % ssh_key
michael@0 233 args.extend(opt)
michael@0 234 if revision:
michael@0 235 args.extend(['-r', revision])
michael@0 236 elif branch:
michael@0 237 if hg_ver() >= (1, 6, 0):
michael@0 238 args.extend(['-b', branch])
michael@0 239 return args
michael@0 240
michael@0 241
michael@0 242 def pull(repo, dest, update_dest=True, mirrors=None, **kwargs):
michael@0 243 """Pulls changes from hg repo and places it in `dest`.
michael@0 244
michael@0 245 If `update_dest` is set, then `dest` will be updated to `revision` if
michael@0 246 set, otherwise to `branch`, otherwise to the head of default.
michael@0 247
michael@0 248 If `mirrors` is set, will try and pull from the mirrors first before
michael@0 249 `repo`."""
michael@0 250
michael@0 251 if mirrors:
michael@0 252 for mirror in mirrors:
michael@0 253 try:
michael@0 254 return pull(mirror, dest, update_dest=update_dest, **kwargs)
michael@0 255 except:
michael@0 256 log.exception("Problem pulling from mirror %s", mirror)
michael@0 257 continue
michael@0 258 else:
michael@0 259 log.info("Pulling from mirrors failed; falling back to %s", repo)
michael@0 260
michael@0 261 # Convert repo to an absolute path if it's a local repository
michael@0 262 repo = _make_absolute(repo)
michael@0 263 cmd = ['hg', 'pull']
michael@0 264 # Don't pass -r to "hg pull", except when it's a valid HG revision.
michael@0 265 # Pulling using tag names is dangerous: it uses the local .hgtags, so if
michael@0 266 # the tag has moved on the remote side you won't pull the new revision the
michael@0 267 # remote tag refers to.
michael@0 268 pull_kwargs = kwargs.copy()
michael@0 269 if 'revision' in pull_kwargs and \
michael@0 270 not is_hg_cset(pull_kwargs['revision']):
michael@0 271 del pull_kwargs['revision']
michael@0 272
michael@0 273 cmd.extend(common_args(**pull_kwargs))
michael@0 274
michael@0 275 cmd.append(repo)
michael@0 276 run_cmd(cmd, cwd=dest)
michael@0 277
michael@0 278 if update_dest:
michael@0 279 branch = None
michael@0 280 if 'branch' in kwargs and kwargs['branch']:
michael@0 281 branch = kwargs['branch']
michael@0 282 revision = None
michael@0 283 if 'revision' in kwargs and kwargs['revision']:
michael@0 284 revision = kwargs['revision']
michael@0 285 return update(dest, branch=branch, revision=revision)
michael@0 286
michael@0 287 # Defines the places of attributes in the tuples returned by `out'
michael@0 288 REVISION, BRANCH = 0, 1
michael@0 289
michael@0 290
michael@0 291 def out(src, remote, **kwargs):
michael@0 292 """Check for outgoing changesets present in a repo"""
michael@0 293 cmd = ['hg', '-q', 'out', '--template', '{node} {branches}\n']
michael@0 294 cmd.extend(common_args(**kwargs))
michael@0 295 cmd.append(remote)
michael@0 296 if os.path.exists(src):
michael@0 297 try:
michael@0 298 revs = []
michael@0 299 for line in get_output(cmd, cwd=src).rstrip().split("\n"):
michael@0 300 try:
michael@0 301 rev, branch = line.split()
michael@0 302 # Mercurial displays no branch at all if the revision is on
michael@0 303 # "default"
michael@0 304 except ValueError:
michael@0 305 rev = line.rstrip()
michael@0 306 branch = "default"
michael@0 307 revs.append((rev, branch))
michael@0 308 return revs
michael@0 309 except subprocess.CalledProcessError, inst:
michael@0 310 # In some situations, some versions of Mercurial return "1"
michael@0 311 # if no changes are found, so we need to ignore this return code
michael@0 312 if inst.returncode == 1:
michael@0 313 return []
michael@0 314 raise
michael@0 315
michael@0 316
michael@0 317 def push(src, remote, push_new_branches=True, force=False, **kwargs):
michael@0 318 cmd = ['hg', 'push']
michael@0 319 cmd.extend(common_args(**kwargs))
michael@0 320 if force:
michael@0 321 cmd.append('-f')
michael@0 322 if push_new_branches:
michael@0 323 cmd.append('--new-branch')
michael@0 324 cmd.append(remote)
michael@0 325 run_cmd(cmd, cwd=src)
michael@0 326
michael@0 327
michael@0 328 def mercurial(repo, dest, branch=None, revision=None, update_dest=True,
michael@0 329 shareBase=DefaultShareBase, allowUnsharedLocalClones=False,
michael@0 330 clone_by_rev=False, mirrors=None, bundles=None, autoPurge=False):
michael@0 331 """Makes sure that `dest` is has `revision` or `branch` checked out from
michael@0 332 `repo`.
michael@0 333
michael@0 334 Do what it takes to make that happen, including possibly clobbering
michael@0 335 dest.
michael@0 336
michael@0 337 If allowUnsharedLocalClones is True and we're trying to use the share
michael@0 338 extension but fail, then we will be able to clone from the shared repo to
michael@0 339 our destination. If this is False, the default, then if we don't have the
michael@0 340 share extension we will just clone from the remote repository.
michael@0 341
michael@0 342 If `clone_by_rev` is True, use 'hg clone -r <rev>' instead of 'hg clone'.
michael@0 343 This is slower, but useful when cloning repos with lots of heads.
michael@0 344
michael@0 345 If `mirrors` is set, will try and use the mirrors before `repo`.
michael@0 346
michael@0 347 If `bundles` is set, will try and download the bundle first and
michael@0 348 unbundle it instead of doing a full clone. If successful, will pull in
michael@0 349 new revisions from mirrors or the master repo. If unbundling fails, will
michael@0 350 fall back to doing a regular clone from mirrors or the master repo.
michael@0 351 """
michael@0 352 dest = os.path.abspath(dest)
michael@0 353 if shareBase is DefaultShareBase:
michael@0 354 shareBase = os.environ.get("HG_SHARE_BASE_DIR", None)
michael@0 355
michael@0 356 log.info("Reporting hg version in use")
michael@0 357 cmd = ['hg', '-q', 'version']
michael@0 358 run_cmd(cmd, cwd='.')
michael@0 359
michael@0 360 if shareBase:
michael@0 361 # Check that 'hg share' works
michael@0 362 try:
michael@0 363 log.info("Checking if share extension works")
michael@0 364 output = get_output(['hg', 'help', 'share'], dont_log=True)
michael@0 365 if 'no commands defined' in output:
michael@0 366 # Share extension is enabled, but not functional
michael@0 367 log.info("Disabling sharing since share extension doesn't seem to work (1)")
michael@0 368 shareBase = None
michael@0 369 elif 'unknown command' in output:
michael@0 370 # Share extension is disabled
michael@0 371 log.info("Disabling sharing since share extension doesn't seem to work (2)")
michael@0 372 shareBase = None
michael@0 373 except subprocess.CalledProcessError:
michael@0 374 # The command failed, so disable sharing
michael@0 375 log.info("Disabling sharing since share extension doesn't seem to work (3)")
michael@0 376 shareBase = None
michael@0 377
michael@0 378 # Check that our default path is correct
michael@0 379 if os.path.exists(os.path.join(dest, '.hg')):
michael@0 380 hgpath = path(dest, "default")
michael@0 381
michael@0 382 # Make sure that our default path is correct
michael@0 383 if hgpath != _make_absolute(repo):
michael@0 384 log.info("hg path isn't correct (%s should be %s); clobbering",
michael@0 385 hgpath, _make_absolute(repo))
michael@0 386 remove_path(dest)
michael@0 387
michael@0 388 # If the working directory already exists and isn't using share we update
michael@0 389 # the working directory directly from the repo, ignoring the sharing
michael@0 390 # settings
michael@0 391 if os.path.exists(dest):
michael@0 392 if not os.path.exists(os.path.join(dest, ".hg")):
michael@0 393 log.warning("%s doesn't appear to be a valid hg directory; clobbering", dest)
michael@0 394 remove_path(dest)
michael@0 395 elif not os.path.exists(os.path.join(dest, ".hg", "sharedpath")):
michael@0 396 try:
michael@0 397 if autoPurge:
michael@0 398 purge(dest)
michael@0 399 return pull(repo, dest, update_dest=update_dest, branch=branch,
michael@0 400 revision=revision,
michael@0 401 mirrors=mirrors)
michael@0 402 except subprocess.CalledProcessError:
michael@0 403 log.warning("Error pulling changes into %s from %s; clobbering", dest, repo)
michael@0 404 log.debug("Exception:", exc_info=True)
michael@0 405 remove_path(dest)
michael@0 406
michael@0 407 # If that fails for any reason, and sharing is requested, we'll try to
michael@0 408 # update the shared repository, and then update the working directory from
michael@0 409 # that.
michael@0 410 if shareBase:
michael@0 411 sharedRepo = os.path.join(shareBase, get_repo_path(repo))
michael@0 412 dest_sharedPath = os.path.join(dest, '.hg', 'sharedpath')
michael@0 413
michael@0 414 if os.path.exists(sharedRepo):
michael@0 415 hgpath = path(sharedRepo, "default")
michael@0 416
michael@0 417 # Make sure that our default path is correct
michael@0 418 if hgpath != _make_absolute(repo):
michael@0 419 log.info("hg path isn't correct (%s should be %s); clobbering",
michael@0 420 hgpath, _make_absolute(repo))
michael@0 421 # we need to clobber both the shared checkout and the dest,
michael@0 422 # since hgrc needs to be in both places
michael@0 423 remove_path(sharedRepo)
michael@0 424 remove_path(dest)
michael@0 425
michael@0 426 if os.path.exists(dest_sharedPath):
michael@0 427 # Make sure that the sharedpath points to sharedRepo
michael@0 428 dest_sharedPath_data = os.path.normpath(
michael@0 429 open(dest_sharedPath).read())
michael@0 430 norm_sharedRepo = os.path.normpath(os.path.join(sharedRepo, '.hg'))
michael@0 431 if dest_sharedPath_data != norm_sharedRepo:
michael@0 432 # Clobber!
michael@0 433 log.info("We're currently shared from %s, but are being requested to pull from %s (%s); clobbering",
michael@0 434 dest_sharedPath_data, repo, norm_sharedRepo)
michael@0 435 remove_path(dest)
michael@0 436
michael@0 437 try:
michael@0 438 log.info("Updating shared repo")
michael@0 439 mercurial(repo, sharedRepo, branch=branch, revision=revision,
michael@0 440 update_dest=False, shareBase=None, clone_by_rev=clone_by_rev,
michael@0 441 mirrors=mirrors, bundles=bundles, autoPurge=False)
michael@0 442 if os.path.exists(dest):
michael@0 443 if autoPurge:
michael@0 444 purge(dest)
michael@0 445 return update(dest, branch=branch, revision=revision)
michael@0 446
michael@0 447 try:
michael@0 448 log.info("Trying to share %s to %s", sharedRepo, dest)
michael@0 449 return share(sharedRepo, dest, branch=branch, revision=revision)
michael@0 450 except subprocess.CalledProcessError:
michael@0 451 if not allowUnsharedLocalClones:
michael@0 452 # Re-raise the exception so it gets caught below.
michael@0 453 # We'll then clobber dest, and clone from original repo
michael@0 454 raise
michael@0 455
michael@0 456 log.warning("Error calling hg share from %s to %s;"
michael@0 457 "falling back to normal clone from shared repo",
michael@0 458 sharedRepo, dest)
michael@0 459 # Do a full local clone first, and then update to the
michael@0 460 # revision we want
michael@0 461 # This lets us use hardlinks for the local clone if the OS
michael@0 462 # supports it
michael@0 463 clone(sharedRepo, dest, update_dest=False,
michael@0 464 mirrors=mirrors, bundles=bundles)
michael@0 465 return update(dest, branch=branch, revision=revision)
michael@0 466 except subprocess.CalledProcessError:
michael@0 467 log.warning(
michael@0 468 "Error updating %s from sharedRepo (%s): ", dest, sharedRepo)
michael@0 469 log.debug("Exception:", exc_info=True)
michael@0 470 remove_path(dest)
michael@0 471 # end if shareBase
michael@0 472
michael@0 473 if not os.path.exists(os.path.dirname(dest)):
michael@0 474 os.makedirs(os.path.dirname(dest))
michael@0 475
michael@0 476 # Share isn't available or has failed, clone directly from the source
michael@0 477 return clone(repo, dest, branch, revision,
michael@0 478 update_dest=update_dest, mirrors=mirrors,
michael@0 479 bundles=bundles, clone_by_rev=clone_by_rev)
michael@0 480
michael@0 481
michael@0 482 def apply_and_push(localrepo, remote, changer, max_attempts=10,
michael@0 483 ssh_username=None, ssh_key=None, force=False):
michael@0 484 """This function calls `changer' to make changes to the repo, and tries
michael@0 485 its hardest to get them to the origin repo. `changer' must be a
michael@0 486 callable object that receives two arguments: the directory of the local
michael@0 487 repository, and the attempt number. This function will push ALL
michael@0 488 changesets missing from remote."""
michael@0 489 assert callable(changer)
michael@0 490 branch = get_branch(localrepo)
michael@0 491 changer(localrepo, 1)
michael@0 492 for n in range(1, max_attempts + 1):
michael@0 493 new_revs = []
michael@0 494 try:
michael@0 495 new_revs = out(src=localrepo, remote=remote,
michael@0 496 ssh_username=ssh_username,
michael@0 497 ssh_key=ssh_key)
michael@0 498 if len(new_revs) < 1:
michael@0 499 raise HgUtilError("No revs to push")
michael@0 500 push(src=localrepo, remote=remote, ssh_username=ssh_username,
michael@0 501 ssh_key=ssh_key, force=force)
michael@0 502 return
michael@0 503 except subprocess.CalledProcessError, e:
michael@0 504 log.debug("Hit error when trying to push: %s" % str(e))
michael@0 505 if n == max_attempts:
michael@0 506 log.debug("Tried %d times, giving up" % max_attempts)
michael@0 507 for r in reversed(new_revs):
michael@0 508 run_cmd(['hg', '--config', 'extensions.mq=', 'strip', '-n',
michael@0 509 r[REVISION]], cwd=localrepo)
michael@0 510 raise HgUtilError("Failed to push")
michael@0 511 pull(remote, localrepo, update_dest=False,
michael@0 512 ssh_username=ssh_username, ssh_key=ssh_key)
michael@0 513 # After we successfully rebase or strip away heads the push is
michael@0 514 # is attempted again at the start of the loop
michael@0 515 try:
michael@0 516 run_cmd(['hg', '--config', 'ui.merge=internal:merge',
michael@0 517 'rebase'], cwd=localrepo)
michael@0 518 except subprocess.CalledProcessError, e:
michael@0 519 log.debug("Failed to rebase: %s" % str(e))
michael@0 520 update(localrepo, branch=branch)
michael@0 521 for r in reversed(new_revs):
michael@0 522 run_cmd(['hg', '--config', 'extensions.mq=', 'strip', '-n',
michael@0 523 r[REVISION]], cwd=localrepo)
michael@0 524 changer(localrepo, n + 1)
michael@0 525
michael@0 526
michael@0 527 def share(source, dest, branch=None, revision=None):
michael@0 528 """Creates a new working directory in "dest" that shares history with
michael@0 529 "source" using Mercurial's share extension"""
michael@0 530 run_cmd(['hg', 'share', '-U', source, dest])
michael@0 531 return update(dest, branch=branch, revision=revision)
michael@0 532
michael@0 533
michael@0 534 def cleanOutgoingRevs(reponame, remote, username, sshKey):
michael@0 535 outgoingRevs = retry(out, kwargs=dict(src=reponame, remote=remote,
michael@0 536 ssh_username=username,
michael@0 537 ssh_key=sshKey))
michael@0 538 for r in reversed(outgoingRevs):
michael@0 539 run_cmd(['hg', '--config', 'extensions.mq=', 'strip', '-n',
michael@0 540 r[REVISION]], cwd=reponame)
michael@0 541
michael@0 542
michael@0 543 def path(src, name='default'):
michael@0 544 """Returns the remote path associated with "name" """
michael@0 545 try:
michael@0 546 return get_output(['hg', 'path', name], cwd=src).strip()
michael@0 547 except subprocess.CalledProcessError:
michael@0 548 return None
michael@0 549
michael@0 550
michael@0 551 def init(dest):
michael@0 552 """Initializes an empty repo in `dest`"""
michael@0 553 run_cmd(['hg', 'init', dest])
michael@0 554
michael@0 555
michael@0 556 def unbundle(bundle, dest):
michael@0 557 """Unbundles the bundle located at `bundle` into `dest`.
michael@0 558
michael@0 559 `bundle` can be a local file or remote url."""
michael@0 560 try:
michael@0 561 get_output(['hg', 'unbundle', bundle], cwd=dest, include_stderr=True)
michael@0 562 return True
michael@0 563 except subprocess.CalledProcessError:
michael@0 564 return False
michael@0 565
michael@0 566
michael@0 567 def adjust_paths(dest, **paths):
michael@0 568 """Adjusts paths in `dest`/.hg/hgrc so that names in `paths` are set to
michael@0 569 paths[name].
michael@0 570
michael@0 571 Note that any comments in the hgrc will be lost if changes are made to the
michael@0 572 file."""
michael@0 573 hgrc = os.path.join(dest, '.hg', 'hgrc')
michael@0 574 config = RawConfigParser()
michael@0 575 config.read(hgrc)
michael@0 576
michael@0 577 if not config.has_section('paths'):
michael@0 578 config.add_section('paths')
michael@0 579
michael@0 580 changed = False
michael@0 581 for path_name, path_value in paths.items():
michael@0 582 if (not config.has_option('paths', path_name) or
michael@0 583 config.get('paths', path_name) != path_value):
michael@0 584 changed = True
michael@0 585 config.set('paths', path_name, path_value)
michael@0 586
michael@0 587 if changed:
michael@0 588 config.write(open(hgrc, 'w'))
michael@0 589
michael@0 590
michael@0 591 def commit(dest, msg, user=None):
michael@0 592 cmd = ['hg', 'commit', '-m', msg]
michael@0 593 if user:
michael@0 594 cmd.extend(['-u', user])
michael@0 595 run_cmd(cmd, cwd=dest)
michael@0 596 return get_revision(dest)
michael@0 597
michael@0 598
michael@0 599 def tag(dest, tags, user=None, msg=None, rev=None, force=None):
michael@0 600 cmd = ['hg', 'tag']
michael@0 601 if user:
michael@0 602 cmd.extend(['-u', user])
michael@0 603 if msg:
michael@0 604 cmd.extend(['-m', msg])
michael@0 605 if rev:
michael@0 606 cmd.extend(['-r', rev])
michael@0 607 if force:
michael@0 608 cmd.append('-f')
michael@0 609 cmd.extend(tags)
michael@0 610 run_cmd(cmd, cwd=dest)
michael@0 611 return get_revision(dest)

mercurial