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
5 changes: 1 addition & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,8 @@ jobs:
with:
python-version: '3.10'

- name: Make lbranch executable
run: chmod +x bin/lbranch

- name: Set default branch
run: git config --global init.defaultBranch main

- name: Run unittest
run: python -m unittest test.py
run: python -m unittest test/test_lbranch.py
44 changes: 1 addition & 43 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,6 @@
# lbranch
lbranch ("last branch") is a git utility that shows your recently checked out branches in chronological order, with an optional interactive checkout.

## Installation
1. Ensure you have Python 3.6+ installed
2. Clone this repository:
```bash
git clone https://github.com/yourusername/lbranch.git ~/.lbranch
```
3. Create a symlink or add to your PATH:
```bash
# Option 1: Symlink to /usr/local/bin
ln -s ~/.lbranch/bin/lbranch /usr/local/bin/lbranch
# Option 2: Add to your PATH in ~/.zshrc or ~/.bashrc
export PATH="$PATH:~/.lbranch/bin"
```
4. (Optional) Add an alias in your shell config:
```bash
alias lb=lbranch
```

## Usage
```bash
# Show last 5 branches
Expand All @@ -45,30 +27,6 @@ Last 5 branches:
## Requirements
- Python 3.6+
- Git
- Unix-like environment (Linux, macOS, WSL)

## Development
To run tests:
```bash
python test.py
```

## Uninstall
```bash
# Remove symlink (if you used Option 1)
rm /usr/local/bin/lbranch

# Remove the repository
rm -rf ~/.lbranch

# Remove PATH addition (if you used Option 2)
# Edit ~/.zshrc or ~/.bashrc and remove the line:
# export PATH="$PATH:~/.lbranch/bin"

# Remove alias (if you added it)
# Edit ~/.zshrc or ~/.bashrc and remove the line:
# alias lb=lbranch
```

## License
Distributed under the MIT License. See `LICENSE` file for more information.
Distributed under the MIT License. See `LICENSE`
127 changes: 0 additions & 127 deletions bin/lbranch

This file was deleted.

8 changes: 8 additions & 0 deletions lbranch/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""
lbranch - A Git utility that shows recently checked out branches in chronological order.
"""

from .main import main

__version__ = "0.1.0"
__all__ = ["main"]
134 changes: 134 additions & 0 deletions lbranch/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#!/usr/bin/env python3

# Last Branch (lbranch) - Git branch history utility
# Usage: lbranch [count] [-c|--choose]

import subprocess
import sys
import re

# Colors for output
RED = '\033[0;31m'
GREEN = '\033[0;32m'
BLUE = '\033[0;34m'
NC = '\033[0m' # No Color

def print_error(message):
"""Print error message and exit"""
print(f"{RED}Error: {message}{NC}", file=sys.stderr)
sys.exit(1)

def run_command(cmd, check=True, capture_output=True):
"""Run a shell command and handle errors"""
try:
result = subprocess.run(
cmd,
check=check,
text=True,
shell=isinstance(cmd, str),
capture_output=capture_output
)
return result
except subprocess.CalledProcessError as e:
if not check:
return e
print_error(f"Command failed: {e}")
sys.exit(1)

def main():
"""Main entry point for the lbranch command."""
# Check if git is installed
try:
run_command(["git", "--version"], capture_output=True)
except FileNotFoundError:
print_error("git command not found. Please install git first.")

# Check if we're in a git repository
if run_command(["git", "rev-parse", "--is-inside-work-tree"], check=False, capture_output=True).returncode != 0:
print_error("Not a git repository. Please run this command from within a git repository.")

# Check if the repository has any commits
if run_command(["git", "rev-parse", "--verify", "HEAD"], check=False, capture_output=True).returncode != 0:
print(f"{BLUE}No branch history found - repository has no commits yet{NC}")
sys.exit(0)

# Parse arguments
branch_count = 5
choose_mode = False

for arg in sys.argv[1:]:
if arg in ['-c', '--choose']:
choose_mode = True
elif re.match(r'^\d+$', arg):
branch_count = int(arg)
else:
print_error(f"Invalid argument: {arg}")

# Get current branch name
try:
current_branch = run_command(["git", "symbolic-ref", "--short", "HEAD"], capture_output=True).stdout.strip()
except subprocess.CalledProcessError:
current_branch = run_command(["git", "rev-parse", "--short", "HEAD"], capture_output=True).stdout.strip()

# Get unique branch history
reflog_output = run_command(["git", "reflog"], capture_output=True).stdout

branches = []
for line in reflog_output.splitlines():
# Look for checkout lines without using grep
if 'checkout: moving from' in line.lower():
# Parse the branch name after "from"
parts = line.split()
try:
from_index = parts.index("from")
if from_index + 1 < len(parts):
branch = parts[from_index + 1]

# Skip empty, current branch, or branches starting with '{'
if not branch or branch == current_branch or branch.startswith('{'):
continue

# Only add branch if it's not already in the list
if branch not in branches:
branches.append(branch)
except ValueError:
continue # "from" not found in this line

# Limit to requested number of branches
total_branches = len(branches)
if total_branches == 0:
print(f"{BLUE}Last {branch_count} branches:{NC}")
sys.exit(0)

branch_limit = min(branch_count, total_branches)
branches = branches[:branch_limit] # Limit to requested count

# Display branches
print(f"{BLUE}Last {branch_count} branches:{NC}")
for i, branch in enumerate(branches, 1):
print(f"{i}) {branch}")

# Handle choose mode
if choose_mode:
try:
print(f"\n{GREEN}Enter branch number to checkout (1-{branch_count}):{NC}")
branch_num = input()

if not re.match(r'^\d+$', branch_num) or int(branch_num) < 1 or int(branch_num) > branch_count:
print_error(f"Invalid selection: {branch_num}")

selected_branch = branches[int(branch_num) - 1]
print(f"\nChecking out: {selected_branch}")

# Attempt to checkout the branch
result = run_command(["git", "checkout", selected_branch], check=False, capture_output=True)
if result.returncode != 0:
print_error(f"Failed to checkout branch:\n{result.stderr}")

print(f"{GREEN}Successfully checked out {selected_branch}{NC}")
except KeyboardInterrupt:
print("\nOperation cancelled.")
sys.exit(1)

if __name__ == '__main__':
main()
34 changes: 34 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "lbranch"
version = "0.1.0"
description = "A Git utility that shows recently checked out branches in chronological order"
readme = "README.md"
requires-python = ">=3.7"
license = "MIT"
authors = [
{ name = "Charles Danielsson" }
]
classifiers = [
"Development Status :: 4 - Beta",
"Environment :: Console",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Topic :: Software Development :: Version Control :: Git",
]
dependencies = []

[project.scripts]
lbranch = "lbranch.main:main"

[tool.hatch.build]
packages = ["lbranch"]
1 change: 1 addition & 0 deletions test/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# This file makes the test directory a Python package
Loading