Peter Pentchev | b91fc93 | 2023-01-19 15:27:13 +0200 | [diff] [blame] | 1 | # SPDX-FileCopyrightText: StorPool <support@storpool.com> |
| 2 | # SPDX-License-Identifier: BSD-2-Clause |
| 3 | """Clone a Git repository.""" |
| 4 | |
| 5 | from __future__ import annotations |
| 6 | |
Peter Pentchev | 81fa766 | 2024-01-18 13:19:34 +0200 | [diff] [blame] | 7 | import subprocess # noqa: S404 |
Peter Pentchev | b91fc93 | 2023-01-19 15:27:13 +0200 | [diff] [blame] | 8 | |
| 9 | from typing import TYPE_CHECKING |
| 10 | |
| 11 | from . import defs |
| 12 | |
| 13 | if TYPE_CHECKING: |
| 14 | import pathlib |
| 15 | |
| 16 | from typing import Final |
| 17 | |
| 18 | |
| 19 | class GitError(defs.GApplyError): |
| 20 | """An error that occurred during a Git-related operation.""" |
| 21 | |
| 22 | |
| 23 | def repo_clone(cfg: defs.Config, repo: defs.Repo, tempd: pathlib.Path) -> pathlib.Path: |
| 24 | """Clone a Git repository.""" |
| 25 | loc: Final = cfg.repo_urls.get(repo.origin.upper()) |
| 26 | if loc is None: |
| 27 | raise GitError(f"Unknown repository origin {repo.origin!r}") |
| 28 | cfg.log.info( |
| 29 | "Cloning the %(origin)s %(repo)s repository from %(loc)s", |
| 30 | {"origin": repo.origin, "repo": repo.repo, "loc": loc.url.geturl()}, |
| 31 | ) |
| 32 | |
| 33 | repo_url: Final = loc.url._replace( |
| 34 | path=loc.url.path + ("" if loc.url.path.endswith("/") else "/") + repo.repo |
| 35 | ) |
| 36 | repo_dir: Final = tempd / repo.origin / repo.repo |
| 37 | if repo_dir.exists() or repo_dir.is_symlink(): |
| 38 | raise GitError(f"Did not expect {repo_dir} to exist") |
| 39 | repo_dir.parent.mkdir(mode=0o755, exist_ok=True, parents=True) |
| 40 | |
| 41 | cfg.log.debug( |
| 42 | "About to clone %(repo_url)s into %(repo_dir)s", |
| 43 | {"repo_url": repo_url.geturl(), "repo_dir": repo_dir}, |
| 44 | ) |
| 45 | try: |
| 46 | subprocess.run( |
Peter Pentchev | 78ab37b | 2024-01-18 13:07:33 +0200 | [diff] [blame] | 47 | ["git", "clone", repo_url.geturl(), repo.repo, "-b", "master"], # noqa: S603,S607 |
Peter Pentchev | b91fc93 | 2023-01-19 15:27:13 +0200 | [diff] [blame] | 48 | check=True, |
| 49 | cwd=repo_dir.parent, |
| 50 | ) |
| 51 | except (OSError, subprocess.CalledProcessError) as err: |
| 52 | raise GitError( |
| 53 | f"Could not run `git clone {repo_url.geturl()} {repo.repo}` in {tempd}: {err}" |
| 54 | ) from err |
| 55 | if not repo_dir.is_dir(): |
| 56 | raise GitError(f"`git clone` did not create {repo_dir}") |
| 57 | |
| 58 | return repo_dir |
| 59 | |
| 60 | |
| 61 | def list_change_ids(cfg: defs.Config, repo_dir: pathlib.Path) -> list[str]: |
| 62 | """Get the Change-Id fields of all the commits reachable from the current head.""" |
| 63 | cfg.log.info("Getting the change IDs in %(repo_dir)s", {"repo_dir": repo_dir}) |
| 64 | try: |
| 65 | lines = [ |
| 66 | line |
| 67 | for line in subprocess.check_output( |
Peter Pentchev | 78ab37b | 2024-01-18 13:07:33 +0200 | [diff] [blame] | 68 | ["git", "log", "--pretty=%(trailers:key=Change-Id)", "--reverse"], # noqa: S607 |
Peter Pentchev | b91fc93 | 2023-01-19 15:27:13 +0200 | [diff] [blame] | 69 | cwd=repo_dir, |
| 70 | encoding="UTF-8", |
Peter Pentchev | 78ab37b | 2024-01-18 13:07:33 +0200 | [diff] [blame] | 71 | shell=False, # noqa: S603 |
Peter Pentchev | b91fc93 | 2023-01-19 15:27:13 +0200 | [diff] [blame] | 72 | ).splitlines() |
| 73 | if line |
| 74 | ] |
| 75 | except (OSError, subprocess.CalledProcessError) as err: |
| 76 | raise GitError(f"Could not run `git log` for change IDs in {repo_dir}: {err}") from err |
| 77 | except ValueError as err: |
| 78 | raise GitError( |
| 79 | f"Could not decode the output of `git log` in {repo_dir} into UTF-8 change IDs: {err}" |
| 80 | ) from err |
| 81 | |
| 82 | def parse_line(line: str) -> str: |
| 83 | """Parse a "Change-Id: Ixxx" line.""" |
| 84 | fields: Final = line.split() |
| 85 | # The magic value will go away once we can use structural pattern matching |
| 86 | if ( |
Peter Pentchev | 5ce7fdb | 2024-01-18 13:25:01 +0200 | [diff] [blame^] | 87 | len(fields) != 2 # noqa: PLR2004 |
| 88 | or fields[0] != "Change-Id:" |
Peter Pentchev | b91fc93 | 2023-01-19 15:27:13 +0200 | [diff] [blame] | 89 | or not fields[1].startswith("I") |
| 90 | ): |
| 91 | raise GitError(f"Unexpected `git log` ouput for change IDs: {line!r}") |
| 92 | return fields[1] |
| 93 | |
| 94 | return [parse_line(line) for line in lines] |