diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 13e4b16..e54461c 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -20,6 +20,6 @@ jobs: with: path: ~/.cache/pre-commit key: pre-commit|${{ hashFiles('pyproject.toml', '.pre-commit-config.yaml') }} - - run: uv run --group pre-commit pre-commit run --show-diff-on-failure --color=always --all-files + - run: uv run --locked --group pre-commit pre-commit run --show-diff-on-failure --color=always --all-files - uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0 if: ${{ !cancelled() }} diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 4a06a7c..5e4ad1e 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -5,7 +5,6 @@ on: push: branches: [main, stable] paths: ['src/**', 'pyproject.toml'] - tags: ['*'] jobs: typing: runs-on: ubuntu-latest @@ -23,4 +22,4 @@ jobs: with: path: ./.mypy_cache key: mypy|${{ hashFiles('pyproject.toml') }} - - run: uv run tox run -e typing + - run: uv run --locked tox run -e typing diff --git a/CHANGES.md b/CHANGES.md index da64d08..27a7d4d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,18 @@ +## Version 0.2.0 + +Released 2025-01-14 + +- If a PR already exists from a previous failed run, force push to update the + existing branch and PR. +- Add shortcut `repo.commit(message)` method. + +## Version 0.1.1 + +Released 2025-01-14 + +- Submit is disabled by default. +- Error if target and work branch are the same. + ## Version 0.1.0 Released 2025-01-14 diff --git a/docs/script.md b/docs/script.md index 46e57d3..23fcb19 100644 --- a/docs/script.md +++ b/docs/script.md @@ -44,3 +44,47 @@ develop and preview your changes first. [uv]: https://docs.astral.sh/uv/ [gh]: https://cli.github.com/ + +## Automatic Commit + +After calling `modify`, the script will automatically add any tracked files +and create a commit if it detects there are uncommitted changes. + +If you add a completely new file, it will not be tracked by Git yet, and this +won't be detected or committed. Therefore, you should call +{meth}`.Repo.add_files` to track any new files. Other modifications, such as +changing an existing file or using {meth}`.Repo.rm_files`, will already be +tracked by Git. + +You can set {attr}`.GitRepo.add_untracked` to also detect and add completely +new untracked files. This is disabled by default as it might end up adding files +that were generated as a side effect of other changes. + +```python +class MyScript(GitHubScript): + def modify(self, repo: GitHubRepo) -> None: + repo.add_untracked = True + ... +``` + +## Merge vs PR + +By default, the GitHub provider creates PRs. You can instruct a repo to merge +and push directly to the target instead. This is disabled by default because it +provides one less opportunity to ensure your script worked correctly. + +Set {attr}`GitHubRepo.direct_submit` to `True` to enable this merge and push +behavior. + +```python +class MyScript(GitHubScript): + def modify(self, repo: GitHubRepo) -> None: + repo.direct_submit = True + ... +``` + +## Updating + +You may have run your script with submit enabled, then noticed that more is +needed. If a branch and open PR already exist from a previous run of the script +that, a force push will be used to update. diff --git a/pyproject.toml b/pyproject.toml index 00bfbc0..214d51f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "modify-repos" -version = "0.1.0" +version = "0.2.0" description = "Apply changes across multiple repos at once." readme = "README.md" authors = [{ name = "David Lord" }] diff --git a/src/modify_repos/repo/git.py b/src/modify_repos/repo/git.py index f3b3d81..5cca072 100644 --- a/src/modify_repos/repo/git.py +++ b/src/modify_repos/repo/git.py @@ -5,6 +5,7 @@ from shutil import which from subprocess import CompletedProcess from ..utils import run_cmd +from ..utils import wrap_text from .base import Repo @@ -64,6 +65,20 @@ class GitRepo(Repo): self.git_cmd("merge", "--ff-only", self.script.branch) self.git_cmd("push") + def commit(self, message: str, add: bool = False) -> None: + """Create a commit with the given message. + + :param message: The commit message. + :param add: Update tracked files while committing. Disabled by default. + Alternatively, call {meth}`add_files` first. + """ + args = ["commit", "--message", wrap_text(message, width=72)] + + if add: + args.insert(1, "-a") + + self.git_cmd(*args) + def add_files( self, *items: str | Path, update: bool = False, all: bool = False ) -> None: diff --git a/src/modify_repos/repo/github.py b/src/modify_repos/repo/github.py index be0493e..6d812ab 100644 --- a/src/modify_repos/repo/github.py +++ b/src/modify_repos/repo/github.py @@ -66,14 +66,21 @@ class GitHubRepo(GitRepo): super().submit() return - self.git_cmd("push", "--set-upstream", "origin", self.script.branch) - self.gh_cmd( - "pr", - "create", - "--base", - self.script.target, - "--title", - self.script.title, - "--body", - self.script.body, - ) + result = self.gh_cmd("pr", "view", "--json", "closed", "--jq", ".closed") + has_pr = not result.returncode and result.stdout.strip() == "false" + + if not has_pr: + self.git_cmd("push", "--set-upstream", "origin", self.script.branch) + self.gh_cmd( + "pr", + "create", + "--base", + self.script.target, + "--title", + self.script.title, + "--body", + self.script.body, + ) + else: + # If open PR already exists from previous run, force push. + self.git_cmd("push", "--force", "origin", self.script.branch) diff --git a/src/modify_repos/script/base.py b/src/modify_repos/script/base.py index 48dc386..a90f7a2 100644 --- a/src/modify_repos/script/base.py +++ b/src/modify_repos/script/base.py @@ -41,7 +41,11 @@ class Script[RepoType: Repo]: """ def __init__(self, *, submit: bool = False) -> None: - """ """ + if self.target == self.branch: + raise ValueError( + "Work branch name must be different than target branch name." + ) + source_file = inspect.getsourcefile(self.__class__) if source_file is None: diff --git a/src/modify_repos/script/github.py b/src/modify_repos/script/github.py index 258df06..e22d889 100644 --- a/src/modify_repos/script/github.py +++ b/src/modify_repos/script/github.py @@ -17,7 +17,7 @@ class GitHubScript(Script[GitHubRepo]): orgs: list[str] """The list of GitHub users/orgs to clone repositories from.""" - def __init__(self, *, submit: bool = True, orgs: list[str] | None = None) -> None: + def __init__(self, *, submit: bool = False, orgs: list[str] | None = None) -> None: super().__init__(submit=submit) if orgs is not None: diff --git a/uv.lock b/uv.lock index 1008e0f..e8b749a 100644 --- a/uv.lock +++ b/uv.lock @@ -335,7 +335,7 @@ wheels = [ [[package]] name = "modify-repos" -version = "0.1.0" +version = "0.2.0" source = { editable = "." } dependencies = [ { name = "click" },