Skip to content
Merged
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
28 changes: 22 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 = !<<sh command to run this git-rb script>>
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 <hash on line 6>`, nothing fancy!

![Screenshot of git-rb in action](imgs/img1.png)
8 changes: 1 addition & 7 deletions git_rb/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}"])

Expand Down Expand Up @@ -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:
Expand Down
Binary file added imgs/img1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added imgs/img3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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]
Expand Down
43 changes: 35 additions & 8 deletions tests/test_git_rb.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,23 @@


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
assert pyproject_version[0].strip().split(" = ")[-1].replace('"', "") == __version__


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
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand Down