def check(self) -> bool: """Check if is SVN.""" try: run_on_cmdline(logger, f"svn info {self.remote}") return True except (SubprocessCommandError, RuntimeError): return False
def _fetch_impl(self, version: Version) -> Version: """Get the revision of the remote and place it at the local path.""" if version.tag: branch_path = f"tags/{version.tag}/" branch = "" elif version.branch and version.branch != self.DEFAULT_BRANCH: branch_path = f"branches/{version.branch}" branch = version.branch else: branch = branch_path = self.DEFAULT_BRANCH revision = version.revision or self._get_revision(branch_path) if not revision.isdigit(): raise RuntimeError(f"{revision} must be a number for SVN") rev_arg = f"--revision {revision}" if revision else "" complete_path = "/".join([self.remote, branch_path, self.source]).strip("/") # When exporting a file, the destination directory must already exist pathlib.Path(os.path.dirname(self.local_path)).mkdir(parents=True, exist_ok=True) run_on_cmdline( logger, f"svn export --force {rev_arg} {complete_path} {self.local_path}") return Version(tag=version.tag, branch=branch, revision=revision)
def check_path(path: str = ".") -> bool: """Check if is SVN.""" try: with in_directory(path): run_on_cmdline(logger, "svn info") return True except (SubprocessCommandError, RuntimeError): return False
def is_git(self) -> bool: """Check if is git.""" try: with in_directory(self._path): run_on_cmdline(logger, "git status") return True except (SubprocessCommandError, RuntimeError): return False
def is_git(self) -> bool: """Check if the set url is git.""" if self._remote.endswith(".git"): return True try: run_on_cmdline(logger, f"git ls-remote --heads {self._remote}") return True except (SubprocessCommandError, RuntimeError): return False
def test_run_on_cmdline(name, cmd, cmd_result, expectation): with patch("dfetch.util.cmdline.subprocess.run") as subprocess_mock: subprocess_mock.side_effect = cmd_result logger_mock = MagicMock() if isinstance(expectation, CompletedProcess): assert expectation == run_on_cmdline(logger_mock, cmd) else: with pytest.raises(expectation): run_on_cmdline(logger_mock, cmd)
def _files_in_path(url_path: str) -> List[str]: return [ str(line) for line in run_on_cmdline(logger, f"svn list {url_path}") .stdout.decode() .splitlines() ]
def list_tool_info() -> None: """Print out version information.""" result = run_on_cmdline(logger, "svn --version") first_line = result.stdout.decode().split("\n")[0] tool, version = first_line.replace(",", "").split("version", maxsplit=1) VCS._log_tool(tool, version)
def get_diff(self, old_revision: str, new_revision: Optional[str]) -> str: """Get the diff between two revisions.""" cmd = f"svn diff {self.local_path} -r {old_revision}" if new_revision: cmd += f":{new_revision}" return "\n".join(run_on_cmdline(logger, cmd).stdout.decode().splitlines())
def list_tool_info() -> None: """Print out version information.""" result = run_on_cmdline(logger, "git --version") tool, version = result.stdout.decode().strip().split("version", maxsplit=1) VCS._log_tool(tool, version)
def _get_info_from_target(target: str = "") -> Dict[str, str]: result = run_on_cmdline(logger, f"svn info {target.strip()}").stdout.decode() return { key.strip(): value.strip() for key, value in ( line.split(":", maxsplit=1) for line in result.split(os.linesep) if line ) }
def _get_info_from_target(target: str = "") -> Dict[str, str]: result = run_on_cmdline(logger, f"svn info {target}") info = {} for line in result.stdout.decode().split(os.linesep): if line: key, value = f"{line} ".split(":", 1) info[key.strip()] = value.strip() return info
def get_last_file_hash(self, path: str) -> str: """Get the hash of a specific file.""" with in_directory(self._path): result = run_on_cmdline( logger, ["git", "log", "-n", "1", "--pretty=format:%H", "--", path], ) return str(result.stdout.decode())
def get_current_hash(self) -> str: """Get the last revision.""" with in_directory(self._path): result = run_on_cmdline( logger, ["git", "log", "-n", "1", "--pretty=format:%H"], ) return str(result.stdout.decode())
def _get_last_changed_revision(target: str) -> str: if os.path.isdir(target): last_digits = re.compile(r"(?P<digits>\d+)(?!.*\d)") version = run_on_cmdline( logger, f"svnversion {target.strip()}" ).stdout.decode() parsed_version = last_digits.search(version) if parsed_version: return parsed_version.group("digits") raise RuntimeError(f"svnversion output was unexpected: {version}") return str( run_on_cmdline( logger, f"svn info --show-item last-changed-revision {target.strip()}" ) .stdout.decode() .strip() )
def externals() -> List[External]: """Get list of externals.""" result = run_on_cmdline( logger, [ "svn", "propget", "svn:externals", "-R", ], ) repo_root = SvnRepo._get_info_from_target()["Repository Root"] externals = [] path_pattern = r"([^\s^-]+)\s+-" for entry in result.stdout.decode().split(os.linesep * 2): match = None for match in re.finditer(path_pattern, entry): pass if match: local_path = match.group(1) entry = re.sub(path_pattern, "", entry) for match in re.finditer( r"([^-\s\d][^\s]+)(?:@)(\d+)\s+([^\s]+)|([^-\s\d][^\s]+)\s+([^\s]+)", entry, ): url = match.group(1) or match.group(4) name = match.group(3) or match.group(5) rev = "" if not match.group(2) else match.group(2).strip() url, branch, tag, src = SvnRepo._split_url(url, repo_root) externals += [ External( name=name, toplevel=os.getcwd(), path="/".join(os.path.join(local_path, name).split(os.sep)), revision=rev, url=url, branch=branch, tag=tag, src=src, ) ] return externals
def _ls_remote(remote: str) -> Dict[str, str]: result = run_on_cmdline( logger, f"git ls-remote --heads --tags {remote}" ).stdout.decode() info = {} for line in filter(lambda x: x, result.split("\n")): sha, ref = [part.strip() for part in f"{line} ".split("\t", maxsplit=1)] # Annotated tag commit (more important) if ref.endswith("^{}"): info[ref.strip("^{}")] = sha elif ref not in info: info[ref] = sha return info
def externals() -> List[External]: """Get list of externals.""" result = run_on_cmdline( logger, [ "svn", "propget", "svn:externals", "-R", ], ) repo_root = SvnRepo._get_info_from_target()["Repository Root"] externals = [] for entry in result.stdout.decode().split(os.linesep * 2): match = None for match in re.finditer(r"([^\s^-]+)\s+-", entry): pass if match: local_path = match.group(1) for match in itertools.chain( re.finditer(r"([^\s]*)@(\d+)\s+([^\s]*)", entry), re.finditer(r"\.\s-\s+([^\s]+)(\s+)([^\s]+)", entry), ): url, branch, tag, src = SvnRepo._split_url( match.group(1), repo_root) externals += [ External( name=match.group(3), toplevel=os.getcwd(), path="/".join( os.path.join(local_path, match.group(3)).split(os.sep)), revision=match.group(2).strip(), url=url, branch=branch, tag=tag, src=src, ) ] return externals
def _ls_remote(remote: str) -> Dict[str, str]: result = run_on_cmdline(logger, f"git ls-remote {remote}") info = {} for line in result.stdout.decode().split("\n"): if line: key, value = f"{line} ".split("\t", 1) if not value.startswith("refs/pull"): # Annotated tag commit (more important) if value.strip().endswith("^{}"): info[value.strip().strip("^{}")] = key.strip() else: if value.strip() not in info: info[value.strip()] = key.strip() return info
def create_diff(self, old_hash: str, new_hash: Optional[str]) -> str: """Generate a relative diff patch.""" with in_directory(self._path): cmd = [ "git", "diff", "--relative", "--binary", # Add binary content "--no-ext-diff", # Don't allow external diff tools "--no-color", old_hash, ] if new_hash: cmd.append(new_hash) result = run_on_cmdline(logger, cmd) return str(result.stdout.decode())
def submodules() -> List[Submodule]: """Get a list of submodules in the current directory.""" result = run_on_cmdline( logger, [ "git", "submodule", "foreach", "--quiet", "echo $name $sm_path $sha1 $toplevel", ], ) submodules = [] for line in result.stdout.decode().split("\n"): if line: name, sm_path, sha, toplevel = line.split(" ") url = GitLocalRepo._get_submodule_urls(toplevel)[name] branch, tag = GitRemote(url).find_branch_tip_or_tag_from_sha(sha) if not (branch or tag): branch = GitLocalRepo( os.path.join(os.getcwd(), sm_path) ).find_branch_containing_sha(sha) submodules += [ Submodule( name=name, toplevel=toplevel, path=sm_path, sha=sha, url=url, branch=branch, tag=tag, ) ] if not submodules and os.path.isfile(".gitmodules"): logger.warning( "This repository probably has submodules, " "but they might not have been initialized yet. " "Try updating them with 'git submodule update --init' and rerun the command." ) return submodules
def find_branch_containing_sha(self, sha: str) -> str: """Try to find the branch that contains the given sha.""" if not os.path.isdir(os.path.join(self._path, GitLocalRepo.METADATA_DIR)): return "" with in_directory(self._path): result = run_on_cmdline( logger, ["git", "branch", "--contains", sha], ) branches: List[str] = [ branch.strip() for branch in result.stdout.decode().split("*") if branch.strip() and "HEAD detached at" not in branch.strip() ] return "" if not branches else branches[0]
def _get_submodule_urls(toplevel: str) -> Dict[str, str]: result = run_on_cmdline( logger, [ "git", "config", "--file", toplevel + "/.gitmodules", "--get-regexp", "url", ], ) return { str(match.group(1)): str(match.group(2)) for match in re.finditer(r"submodule\.(.*)\.url\s+(.*)", result.stdout.decode()) }
def _find_branch_in_local_repo_containing_sha(repo_path: str, sha: str) -> str: if not os.path.isdir(repo_path): return "" with in_directory(repo_path): if not os.path.isdir(GitRepo.METADATA_DIR): return "" result = run_on_cmdline( logger, ["git", "branch", "--contains", sha], ) branches: List[str] = [] for branch in result.stdout.decode().split("*"): branch = branch.strip() if branch and "HEAD detached at" not in branch: branches.append(branch) return branches[0] if len(branches) == 1 else ""
def get_git_version() -> Tuple[str, str]: """Get the name and version of git.""" result = run_on_cmdline(logger, "git --version") tool, version = result.stdout.decode().strip().split("version", maxsplit=1) return (str(tool), str(version))
def _checkout_version(remote: str, version: str, src: Optional[str]) -> None: """Checkout a specific version from a given remote. Args: remote (str): Url or path to a remote git repository version (str): A target to checkout, can be branch, tag or sha src (Optional[str]): Optional path to subdirectory or file in repo """ run_on_cmdline(logger, "git init") run_on_cmdline(logger, f"git remote add origin {remote}") run_on_cmdline(logger, "git checkout -b dfetch-local-branch") if src: run_on_cmdline(logger, "git config core.sparsecheckout true") with open(".git/info/sparse-checkout", "a") as sparse_checkout_file: sparse_checkout_file.write("/" + src) run_on_cmdline(logger, f"git fetch --depth 1 origin {version}") run_on_cmdline(logger, "git reset --hard FETCH_HEAD") if src: for file_to_copy in os.listdir(src): shutil.move(src + "/" + file_to_copy, ".") safe_rmtree(src)
def checkout_version( self, remote: str, version: str, src: Optional[str], must_keeps: Optional[List[str]], ) -> None: """Checkout a specific version from a given remote. Args: remote (str): Url or path to a remote git repository version (str): A target to checkout, can be branch, tag or sha src (Optional[str]): Optional path to subdirectory or file in repo must_keeps (Optional[List[str]]): Optional list of glob patterns to keep """ with in_directory(self._path): run_on_cmdline(logger, "git init") run_on_cmdline(logger, f"git remote add origin {remote}") run_on_cmdline(logger, "git checkout -b dfetch-local-branch") if src: run_on_cmdline(logger, "git config core.sparsecheckout true") with open( ".git/info/sparse-checkout", "a", encoding="utf-8" ) as sparse_checkout_file: sparse_checkout_file.write( "\n".join(list([f"/{src}"] + (must_keeps or []))) ) run_on_cmdline(logger, f"git fetch --depth 1 origin {version}") run_on_cmdline(logger, "git reset --hard FETCH_HEAD") if src: full_src = src if not os.path.isdir(src): src = os.path.dirname(src) if not src: return try: for file_to_copy in os.listdir(src): shutil.move(src + "/" + file_to_copy, ".") safe_rmtree(PurePath(src).parts[0]) except FileNotFoundError: logger.warning( f"The 'src:' filter '{full_src}' didn't match any files from '{remote}'" )
def _list_of_tags(self) -> List[str]: """Get list of all available tags.""" result = run_on_cmdline(logger, f"svn ls {self.remote}/tags") return [ str(tag).strip("/\r") for tag in result.stdout.decode().split("\n") if tag ]
def _export(url: str, rev: str = "", dst: str = ".") -> None: run_on_cmdline( logger, ["svn", "export", "--force"] + rev.split(" ") + [url, dst], )