Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support pathspecs in --cut #134

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10"]
python-version: ["3.8", "3.9", "3.10", "3.11"]
os: [ubuntu-latest, windows-latest, macOS-latest]

steps:
Expand Down
6 changes: 4 additions & 2 deletions docs/man.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ SYNOPSIS
========

*git revise* [<options>] [<target>]
*git revise* [<options>] --cut <target> [--] [<pathspec>...]

DESCRIPTION
===========
Expand Down Expand Up @@ -93,8 +94,9 @@ Main modes of operation

.. option:: -c, --cut

Interactively select hunks from <target>. The chosen hunks are split into
a second commit immediately after the target.
Interactively select hunks from <target>, optionally limited by <pathspec>.
The chosen hunks are split into a second commit immediately after the
target.

After splitting is complete, both commits' messages are edited.

Expand Down
14 changes: 11 additions & 3 deletions gitrevise/tui.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
)


def build_parser() -> ArgumentParser:
def build_parser(allow_pathspecs: bool) -> ArgumentParser:
parser = ArgumentParser(
description="""\
Rebase staged changes onto the given commit, and rewrite history to
Expand All @@ -36,6 +36,12 @@ def build_parser() -> ArgumentParser:
nargs="?",
help="target commit to apply fixups to",
)
if allow_pathspecs:
parser.add_argument(
"pathspecs",
nargs="*",
help="make --cut select only from matching files",
)
parser.add_argument("--ref", default="HEAD", help="reference to update")
parser.add_argument(
"--reauthor",
Expand Down Expand Up @@ -201,7 +207,7 @@ def noninteractive(

# If the commit should be cut, prompt the user to perform the cut.
if args.cut:
current = cut_commit(current)
current = cut_commit(current, args.pathspecs)

# Add or remove GPG signatures.
if repo.sign_commits != bool(current.gpgsig):
Expand Down Expand Up @@ -258,7 +264,9 @@ def inner_main(args: Namespace, repo: Repository) -> None:


def main(argv: Optional[List[str]] = None) -> None:
args = build_parser().parse_args(argv)
args = build_parser(allow_pathspecs=True).parse_args(argv)
if not args.cut:
args = build_parser(allow_pathspecs=False).parse_args(argv)
try:
with Repository() as repo:
inner_main(args, repo)
Expand Down
12 changes: 10 additions & 2 deletions gitrevise/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ def update_head(ref: Reference[Commit], new: Commit, expected: Optional[Tree]) -
)


def cut_commit(commit: Commit) -> Commit:
def cut_commit(commit: Commit, pathspecs: Optional[List[str]] = None) -> Commit:
"""Perform a ``cut`` operation on the given commit, and return the
modified commit."""

Expand All @@ -274,7 +274,15 @@ def cut_commit(commit: Commit) -> Commit:

# Run an interactive git-reset to allow picking which pieces of the
# patch should go into the first part.
index.git("reset", "--patch", final_tree.persist().hex(), "--", ".", stdout=None)
index.git(
"reset",
"--patch",
final_tree.persist().hex(),
"--",
*(pathspecs if pathspecs else []),
cwd=Path.cwd(),
stdout=None,
)

# Write out the newly created tree.
mid_tree = index.tree()
Expand Down
11 changes: 9 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,17 @@ def main(
# pylint: disable=redefined-builtin
input: Optional[bytes] = None,
check: bool = True,
capture_output: bool = False,
) -> "subprocess.CompletedProcess[bytes]":
cmd = [sys.executable, "-m", "gitrevise", *args]
print("Running", cmd, dict(cwd=cwd, input=input, check=check))
return subprocess.run(cmd, cwd=cwd, input=input, check=check)
print(
"Running",
cmd,
dict(cwd=cwd, input=input, check=check, capture_output=capture_output),
)
return subprocess.run(
cmd, cwd=cwd, input=input, check=check, capture_output=capture_output
)


@contextmanager
Expand Down
66 changes: 66 additions & 0 deletions tests/test_cut.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from textwrap import dedent

import pytest

from gitrevise.odb import Repository

from .conftest import bash, editor_main
Expand Down Expand Up @@ -84,3 +88,65 @@ def test_cut_root(repo: Repository) -> None:

assert new_u != new
assert new_u != prev


@pytest.mark.parametrize("pathspec", ["subdir/file2", "file2"])
def test_cut_pathspec(
repo: Repository, monkeypatch: pytest.MonkeyPatch, pathspec: str
) -> None:
bash(
"""
echo "Hello, World" >> file1
git add file1
git commit -m "commit 1"

echo "Append f1" >> file1
mkdir subdir
echo "Make f2" >> subdir/file2
git add file1 subdir/file2
git commit -m "commit 2"
"""
)

if pathspec == "file2":
monkeypatch.chdir(repo.workdir / "subdir")
with editor_main(["--cut", "HEAD", pathspec], input=b"y\n") as ed:
with ed.next_file() as f:
assert f.startswith_dedent("[1] commit 2\n")
f.replace_dedent("extracted changes\n")

with ed.next_file() as f:
assert f.startswith_dedent("[2] commit 2\n")
f.replace_dedent("remaining changes\n")

assert (
repo.git("show", "HEAD~", "--format=%s").decode()
== dedent(
"""
extracted changes

diff --git a/subdir/file2 b/subdir/file2
new file mode 100644
index 0000000..93350fe
--- /dev/null
+++ b/subdir/file2
@@ -0,0 +1 @@
+Make f2"""
)[1:]
)

assert (
repo.git("show", "HEAD", "--format=%s").decode()
== dedent(
"""
remaining changes

diff --git a/file1 b/file1
index 3fa0d4b..ada44cf 100644
--- a/file1
+++ b/file1
@@ -1 +1,2 @@
Hello, World
+Append f1"""
)[1:]
)
10 changes: 10 additions & 0 deletions tests/test_edit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from gitrevise.odb import Repository

from .conftest import main


# pylint: disable=unused-argument
def test_edit_illegal_extra_argument(repo: Repository) -> None:
process = main(["--edit", "HEAD", "HEAD"], check=False, capture_output=True)
assert b"error: unrecognized arguments" in process.stderr
assert process.returncode != 0
10 changes: 6 additions & 4 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ envlist =
py38
py39
py310
py311
mypy
lint
format
Expand All @@ -23,7 +24,7 @@ passenv = PROGRAMFILES* # to locate git-bash on windows
[testenv:mypy]
description = typecheck with mypy
commands = mypy gitrevise tests {posargs}
basepython = python3.10
basepython = python3.11
deps =
{[testenv]deps}
mypy ~= 0.971
Expand All @@ -33,7 +34,7 @@ description = lint with pylint and isort
commands =
isort --check gitrevise tests
pylint gitrevise tests {posargs}
basepython = python3.10
basepython = python3.11
deps =
{[testenv]deps}
isort ~= 5.10.1
Expand All @@ -42,11 +43,12 @@ deps =
[testenv:format]
description = validate formatting
commands = black --check . {posargs}
basepython = python3.10
basepython = python3.11
deps = black ~= 22.6.0

[gh-actions]
python =
3.8: py38
3.9: py39
3.10: py310, mypy, lint, format
3.10: py310
3.11: py311, mypy, lint, format
Loading