def reset_git_repo(target_dir, git_reference, retry_depth=None): """ hard reset git clone in target_dir to given git_reference :param target_dir: str, filesystem path where the repo is cloned :param git_reference: str, any valid git reference :param retry_depth: int, if the repo was cloned with --shallow, this is the expected depth of the commit :return: str and int, commit ID of HEAD and commit depth of git_reference """ deepen = retry_depth or 0 base_commit_depth = 0 for _ in range(GIT_FETCH_RETRY): try: if not deepen: cmd = ['git', 'rev-list', '--count', git_reference] base_commit_depth = int( subprocess.check_output(cmd, cwd=target_dir)) - 1 cmd = ["git", "reset", "--hard", git_reference] logger.debug("Resetting current HEAD: '%s'", cmd) subprocess.check_call(cmd, cwd=target_dir) break except subprocess.CalledProcessError: if not deepen: raise OsbsCommitNotFound( 'cannot find commit {} in repo {}'.format( git_reference, target_dir)) deepen *= 2 cmd = ["git", "fetch", "--depth", str(deepen)] subprocess.check_call(cmd, cwd=target_dir) logger.debug("Couldn't find commit %s, increasing depth with '%s'", git_reference, cmd) else: raise OsbsCommitNotFound('cannot find commit {} in repo {}'.format( git_reference, target_dir)) cmd = ["git", "rev-parse", "HEAD"] logger.debug("getting SHA-1 of provided ref '%s'", git_reference) commit_id = subprocess.check_output(cmd, cwd=target_dir, universal_newlines=True) commit_id = commit_id.strip() logger.info("commit ID = %s", commit_id) final_commit_depth = None if not deepen: cmd = ['git', 'rev-list', '--count', 'HEAD'] final_commit_depth = int(subprocess.check_output( cmd, cwd=target_dir)) - base_commit_depth return commit_id, final_commit_depth
def clone_git_repo(git_url, target_dir=None, commit=None, retry_times=GIT_MAX_RETRIES, branch=None, depth=None): """ clone provided git repo to target_dir, optionally checkout provided commit :param git_url: str, git repo to clone :param target_dir: str, filesystem path where the repo should be cloned :param commit: str, commit to checkout, SHA-1 or ref :param retry_times: int, number of retries for git clone :param branch: str, optional branch of the commit, required if depth is provided :param depth: int, optional expected depth :return: str, int, commit ID of HEAD """ retry_delay = GIT_BACKOFF_FACTOR target_dir = target_dir or os.path.join(tempfile.mkdtemp(), "repo") commit = commit or "master" logger.info("cloning git repo '%s'", git_url) logger.debug("url = '%s', dir = '%s', commit = '%s'", git_url, target_dir, commit) cmd = ["git", "clone"] if branch: cmd += ["-b", branch, "--single-branch"] if depth: cmd += ["--depth", str(depth)] elif depth: logger.warning("branch not provided for %s, depth setting ignored", git_url) depth = None cmd += [git_url, target_dir] logger.debug("cloning '%s'", cmd) repo_commit = '' repo_depth = None for counter in range(retry_times + 1): try: # we are using check_output, even though we aren't using # the return value, but we will get 'output' in exception subprocess.check_output(cmd, stderr=subprocess.STDOUT) try: repo_commit, repo_depth = reset_git_repo(target_dir, commit, depth) except OsbsCommitNotFound as exc: raise OsbsCommitNotFound("Commit {} is not reachable in branch {}, reason: {}" .format(commit, branch, exc)) break except subprocess.CalledProcessError as exc: if counter != retry_times: logger.info("retrying command '%s':\n '%s'", cmd, exc.output) time.sleep(retry_delay * (2 ** counter)) else: raise OsbsException("Unable to clone git repo '%s' " "branch '%s'" % (git_url, branch), cause=exc, traceback=sys.exc_info()[2]) return ClonedRepoData(target_dir, repo_commit, repo_depth)