diff --git a/README.md b/README.md index d65f46e..0838aac 100644 --- a/README.md +++ b/README.md @@ -6,19 +6,35 @@ [![image](https://img.shields.io/pypi/pyversions/git-rb.svg)](https://pypi.python.org/pypi/git-rb) [![Actions status](https://github.com/geo7/git-rb/workflows/CI/badge.svg)](https://github.com/geo7/git-rb/actions) -`git-rb` is a command-line tool to simplify interactive rebase workflow in Git, -often I'd find my self copying hashes from `git log` output in order to paste -into `git rebase -i`. This tools only purpose is to simplify that. +`git-rb` is a simple command-line tool to simplify interactive rebase workflow +in Git, often I'd find my self copying hashes from `git log` output in order to +paste into `git rebase -i`. This tools only purpose is to simplify that. Code is self contained within `main.py`, so you could just copy the whole script. +> **Note**: _I had this a form of this script kicking around for a while and +> figured I'd try using [Gemini +> CLI](https://github.com/google-gemini/gemini-cli) to do the rest. Results +> were mixed, I wasn't able to '_set and forget_', but it was still useful._ + ## Usage -Typically I'll have the following in `.gitconfig`: +### `.gitconfig` +Typically I'll have the following in `.gitconfig`: ``` [alias] - rb = !<> + rb = !uvx git-rb ``` -To provide the alias `git rb`. +To provide alias `git rb`. + +### Example + +Running this from sklearn gives: + +![Screenshot of git-rb's help message](imgs/img3.png) + +Entering '6' in the prompt just runs `git rebase -i `, nothing fancy! + +![Screenshot of git-rb in action](imgs/img1.png) diff --git a/git_rb/main.py b/git_rb/main.py index 670d033..b8a9386 100644 --- a/git_rb/main.py +++ b/git_rb/main.py @@ -38,12 +38,8 @@ def create_parser() -> argparse.ArgumentParser: def main() -> None: parser = create_parser() parser.parse_args() - # Verify we're in a git repo run_git_command(["rev-parse", "--is-inside-work-tree"]) - console.print(f"[cyan]Fetching the last {LAST_N_COMMITS} commits...[/cyan]") - - # Get commits with custom format log_format = "%h|~|%d|~|%s|~|%ar|~|%an" log_output = run_git_command(["log", f"-n{LAST_N_COMMITS}", f"--pretty=format:{log_format}"]) @@ -91,19 +87,17 @@ def main() -> None: console.print(table) - # Get user selection try: selection = Prompt.ask("Enter the number of the commit to rebase from", default="q") if selection.lower() == "q": console.print("Aborting.") - sys.exit(0) # Exit with 0 on abort + sys.exit(0) index = int(selection) - 1 if not 0 <= index < len(commits): sys.stderr.write("[red]Error: Number out of range.[/red]\n") sys.exit(1) - # Execute rebase rebase_hash = commits[index]["hash"] console.print(f"\n[green]Running command:[/green] git rebase -i {rebase_hash}^") try: diff --git a/imgs/img1.png b/imgs/img1.png new file mode 100644 index 0000000..cbee99b Binary files /dev/null and b/imgs/img1.png differ diff --git a/imgs/img3.png b/imgs/img3.png new file mode 100644 index 0000000..4a1fd38 Binary files /dev/null and b/imgs/img3.png differ diff --git a/pyproject.toml b/pyproject.toml index f468519..5821858 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "git-rb" version = "0.1.1" -description = "Interface for git rebase." +description = "Simple CLI for interactive git rebase workflow." authors = [{ name = "George Lenton", email = "georgelenton@gmail.com" }] license = { text = "MIT" } readme = "README.md" @@ -13,6 +13,7 @@ dependencies = [ [project.urls] Homepage = "https://github.com/geo7/git-rb" +Repository = "https://github.com/geo7/git-rb" [project.scripts] git-rb = "git_rb.main:main" @@ -75,16 +76,15 @@ unfixable = [] "__init__.py" = ["F401"] "scratch/*" = ["ALL"] "tests/*" = [ - "ANN001", # Missing type annotation for function argument `tmpdir` "ANN201", # Missing return type annotation for public function `test_get_file_types` "ARG001", # Unused function argument: `mock_prompt_ask` "D100", # Missing docstring in public module "D101", # Missing docstring in public class "D102", # Missing docstring in public method + "D205", # 1 blank line required between summary line and description "S101", # Use of `assert` detected "S607", # Starting a process with a partial executable path - ] [tool.ruff.lint.isort] diff --git a/tests/test_git_rb.py b/tests/test_git_rb.py index 252ae6f..fe37850 100644 --- a/tests/test_git_rb.py +++ b/tests/test_git_rb.py @@ -6,6 +6,11 @@ def test_version() -> None: + """ + GIVEN: the pyproject.toml file has a version. + WHEN: the package is imported. + THEN: the __version__ matches the pyproject.toml version. + """ with open("pyproject.toml") as f: pyproject_version = [line for line in f if line.startswith("version = ")] assert len(pyproject_version) == 1 @@ -13,7 +18,11 @@ def test_version() -> None: def test_git_rb_help(git_repo: Path, run_git_rb): - """Test that --help displays correctly.""" + """ + GIVEN: a git repository. + WHEN: git-rb is run with the --help flag. + THEN: the help message is displayed correctly. + """ exit_code, stdout, stderr = run_git_rb("--help", cwd=git_repo) assert exit_code == 0 assert "usage: git-rb [-h]" in stdout @@ -23,7 +32,11 @@ def test_git_rb_help(git_repo: Path, run_git_rb): @patch.object(Prompt, "ask") def test_git_rb_abort(mock_prompt_ask, git_repo: Path, run_git_rb): - """Test user aborting the rebase.""" + """ + GIVEN: a git repository. + WHEN: the user enters 'q' to abort. + THEN: the rebase is aborted. + """ mock_prompt_ask.return_value = "q" exit_code, stdout, stderr = run_git_rb(cwd=git_repo) @@ -36,33 +49,43 @@ def test_git_rb_abort(mock_prompt_ask, git_repo: Path, run_git_rb): @patch.object(Prompt, "ask") def test_git_rb_invalid_input(mock_prompt_ask, git_repo: Path, run_git_rb): - """Test invalid user input for selection.""" + """ + GIVEN: a git repository. + WHEN: the user enters invalid input. + THEN: an error message is displayed. + """ mock_prompt_ask.return_value = "abc" exit_code, stdout, stderr = run_git_rb(cwd=git_repo) assert exit_code == 1 assert "Error: Invalid input. Please enter a number." in stderr - assert "Fetching the last 15 commits..." in stdout assert "Recent Commits" in stdout @patch.object(Prompt, "ask") def test_git_rb_out_of_range_input(mock_prompt_ask, git_repo: Path, run_git_rb): - """Test user inputting a number out of range.""" + """ + GIVEN: a git repository. + WHEN: the user enters a number out of range. + THEN: an error message is displayed. + """ mock_prompt_ask.return_value = "99" exit_code, stdout, stderr = run_git_rb(cwd=git_repo) assert exit_code == 1 assert "Error: Number out of range." in stderr - assert "Fetching the last 15 commits..." in stdout assert "Recent Commits" in stdout @patch.object(Prompt, "ask") def test_git_rb_not_in_git_repo(mock_prompt_ask, tmp_path: Path, run_git_rb): - """Test running git-rb outside a Git repository.""" + """ + GIVEN: a directory that is not a git repository. + WHEN: git-rb is run. + THEN: an error message is displayed. + """ exit_code, stdout, stderr = run_git_rb(cwd=tmp_path) assert exit_code == 1 @@ -72,7 +95,11 @@ def test_git_rb_not_in_git_repo(mock_prompt_ask, tmp_path: Path, run_git_rb): @patch.object(Prompt, "ask") def test_git_rb_no_commits(mock_prompt_ask, git_repo_no_commits, run_git_rb): - """Test a successful interactive rebase scenario.""" + """ + GIVEN: a git repository with no commits. + WHEN: git-rb is run. + THEN: an error message is displayed. + """ mock_prompt_ask.return_value = "2" exit_code, _, stderr = run_git_rb(cwd=git_repo_no_commits) assert exit_code == 1