log = logging.getLogger(__name__)
-def checkout_repo(repo_url, dest_path, branch):
+def enforce_repo_state(repo_url, dest_path, branch):
"""
Use git to either clone or update a given repo, forcing it to switch to the
specified branch.
:raises: BranchNotFoundError if the branch is not found;
RuntimeError for other errors
"""
- if not os.path.isdir(dest_path):
- log.info("Cloning %s %s from upstream", repo_url, branch)
- proc = subprocess.Popen(
- ('git', 'clone', '--branch', branch, repo_url, dest_path),
- cwd=os.path.dirname(dest_path),
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
+ try:
+ if not os.path.isdir(dest_path):
+ clone_repo(repo_url, dest_path, branch)
+ elif time.time() - os.stat('/etc/passwd').st_mtime > 60:
+ # only do this at most once per minute
+ fetch_branch(dest_path, branch)
+ out = subprocess.check_output(('touch', dest_path))
+ if out:
+ log.info(out)
+ else:
+ log.info("%s was just updated; assuming it is current", branch)
+
+ reset_repo(repo_url, dest_path, branch)
+ except BranchNotFoundError:
+ shutil.rmtree(dest_path, ignore_errors=True)
+ raise
+
+
+def clone_repo(repo_url, dest_path, branch):
+ """
+ Clone a repo into a path
+
+ :param repo_url: The full URL to the repo (not including the branch)
+ :param dest_path: The full path to the destination directory
+ :param branch: The branch.
+ :raises: BranchNotFoundError if the branch is not found;
+ RuntimeError for other errors
+ """
+ log.info("Cloning %s %s from upstream", repo_url, branch)
+ proc = subprocess.Popen(
+ ('git', 'clone', '--branch', branch, repo_url, dest_path),
+ cwd=os.path.dirname(dest_path),
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+ if proc.wait() != 0:
not_found_str = "Remote branch %s not found" % branch
out = proc.stdout.read()
- if proc.wait() != 0:
- log.error(out)
- if not_found_str in out:
- raise BranchNotFoundError(branch, repo_url)
- else:
- raise RuntimeError("git clone failed!")
- elif time.time() - os.stat('/etc/passwd').st_mtime > 60:
- # only do this at most once per minute
- log.info("Fetching %s from upstream", branch)
- out = subprocess.check_output(('git', 'fetch', '-p', 'origin'),
- cwd=dest_path)
- if out:
- log.info(out)
- out = subprocess.check_output(('touch', dest_path))
- if out:
- log.info(out)
- else:
- log.info("%s was just updated; assuming it is current", branch)
+ log.error(out)
+ if not_found_str in out:
+ raise BranchNotFoundError(branch, repo_url)
+ else:
+ raise RuntimeError("git clone failed!")
+
+def fetch_branch(dest_path, branch):
+ """
+ Call "git fetch -p origin <branch>"
+
+ :param dest_path: The full path to the destination directory
+ :param branch: The branch.
+ :raises: BranchNotFoundError if the branch is not found;
+ RuntimeError for other errors
+ """
+ log.info("Fetching %s from upstream", branch)
+ proc = subprocess.Popen(
+ ('git', 'fetch', '-p', 'origin', branch),
+ cwd=dest_path,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+ if proc.wait() != 0:
+ not_found_str = "fatal: Couldn't find remote ref %s" % branch
+ out = proc.stdout.read()
+ log.error(out)
+ if not_found_str in out:
+ raise BranchNotFoundError(branch)
+ else:
+ raise RuntimeError("git fetch failed!")
+
+
+def reset_repo(repo_url, dest_path, branch):
+ """
+
+ :param repo_url: The full URL to the repo (not including the branch)
+ :param dest_path: The full path to the destination directory
+ :param branch: The branch.
+ :raises: BranchNotFoundError if the branch is not found;
+ RuntimeError for other errors
+ """
# This try/except block will notice if the requested branch doesn't
# exist, whether it was cloned or fetched.
try:
cwd=dest_path,
)
except subprocess.CalledProcessError:
- shutil.rmtree(dest_path)
raise BranchNotFoundError(branch, repo_url)
class BranchNotFoundError(ValueError):
- def __init__(self, branch, repo):
+ def __init__(self, branch, repo=None):
self.branch = branch
self.repo = repo
def __str__(self):
- return "Branch {branch} not found in repo: {repo}".format(
- branch=self.branch, repo=self.repo)
+ if self.repo:
+ repo_str = " in repo: %s" % self.repo
+ else:
+ repo_str = ""
+ return "Branch {branch} not found{repo_str}!".format(
+ branch=self.branch, repo_str=repo_str)
shutil.rmtree(self.local_dir, ignore_errors=True)
def test_existing_branch(self):
- repo_utils.checkout_repo(self.empty_repo, self.local_dir, 'master')
+ repo_utils.enforce_repo_state(self.empty_repo, self.local_dir,
+ 'master')
assert os.path.exists(self.local_dir)
def test_non_existing_branch(self):
with raises(repo_utils.BranchNotFoundError):
- repo_utils.checkout_repo(self.empty_repo, self.local_dir, 'blah')
+ repo_utils.enforce_repo_state(self.empty_repo, self.local_dir,
+ 'blah')
assert not os.path.exists(self.local_dir)
def test_multiple_calls_same_branch(self):
- repo_utils.checkout_repo(self.empty_repo, self.local_dir, 'master')
+ repo_utils.enforce_repo_state(self.empty_repo, self.local_dir,
+ 'master')
assert os.path.exists(self.local_dir)
- repo_utils.checkout_repo(self.empty_repo, self.local_dir, 'master')
+ repo_utils.enforce_repo_state(self.empty_repo, self.local_dir,
+ 'master')
assert os.path.exists(self.local_dir)
- repo_utils.checkout_repo(self.empty_repo, self.local_dir, 'master')
+ repo_utils.enforce_repo_state(self.empty_repo, self.local_dir,
+ 'master')
assert os.path.exists(self.local_dir)
def test_multiple_calls_different_branches(self):
with raises(repo_utils.BranchNotFoundError):
- repo_utils.checkout_repo(self.empty_repo, self.local_dir, 'blah')
+ repo_utils.enforce_repo_state(self.empty_repo, self.local_dir,
+ 'blah1')
assert not os.path.exists(self.local_dir)
- repo_utils.checkout_repo(self.empty_repo, self.local_dir, 'master')
+ repo_utils.enforce_repo_state(self.empty_repo, self.local_dir,
+ 'master')
assert os.path.exists(self.local_dir)
- repo_utils.checkout_repo(self.empty_repo, self.local_dir, 'master')
+ repo_utils.enforce_repo_state(self.empty_repo, self.local_dir,
+ 'master')
assert os.path.exists(self.local_dir)
with raises(repo_utils.BranchNotFoundError):
- repo_utils.checkout_repo(self.empty_repo, self.local_dir, 'blah')
+ repo_utils.enforce_repo_state(self.empty_repo, self.local_dir,
+ 'blah2')
assert not os.path.exists(self.local_dir)
- repo_utils.checkout_repo(self.empty_repo, self.local_dir, 'master')
+ repo_utils.enforce_repo_state(self.empty_repo, self.local_dir,
+ 'master')
assert os.path.exists(self.local_dir)