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
99 changes: 99 additions & 0 deletions bin/gitstack.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# gitstack.sh next # <-- checkout next branch in stack (e.g. feature-2 -> feature-3)
# gitstack.sh push [stack] # <-- force-push all branches in a stack to remote
# gitstack.sh pr [gh-args] # <-- create GitHub PR targeting correct parent branch
# gitstack.sh mr [glab-args] # <-- create GitLab MR targeting correct parent branch
#
# Description:
# create -> Creates a new branch named "<base_name>-0".
Expand All @@ -27,6 +28,7 @@
# next -> Checkout next branch in stack (e.g. feature-2 -> feature-3).
# push -> Force-push all branches in a stack to remote (uses current stack if none specified).
# pr -> Create GitHub PR targeting correct parent branch in stack.
# mr -> Create GitLab MR targeting correct parent branch in stack.
# -----------------------------------------------------------------------------

function usage() {
Expand All @@ -44,6 +46,7 @@ function usage() {
echo " $0 next (Checkout next branch in stack)"
echo " $0 push [stack] (Force-push all branches in a stack to remote)"
echo " $0 pr [gh-args] (Create GitHub PR targeting correct parent branch)"
echo " $0 mr [glab-args] (Create GitLab MR targeting correct parent branch)"
echo
echo "Commands:"
echo " create Creates a new branch named '<base_name>-0'."
Expand All @@ -57,6 +60,7 @@ function usage() {
echo " next Checkout next branch in stack (e.g. feature-2 -> feature-3)."
echo " push Force-push all branches in a stack to remote. Uses current stack if none specified."
echo " pr Create GitHub PR targeting correct parent branch. Passes additional args to 'gh pr create'."
echo " mr Create GitLab MR targeting correct parent branch. Passes additional args to 'glab mr create'."
exit 1
}

Expand Down Expand Up @@ -972,6 +976,98 @@ function create_github_pr() {
fi
}

# Create GitLab MR targeting correct parent branch in stack
# Usage: create_gitlab_mr [additional-glab-args...]
function create_gitlab_mr() {
# Handle help flags
for arg in "$@"; do
if [[ "$arg" == "--help" || "$arg" == "-h" ]]; then
echo "git stack mr - Create GitLab MR targeting correct parent branch"
echo
echo "Usage: git stack mr [glab-args...]"
echo
echo "This command automatically:"
echo " • Detects your current stack position (e.g., feature-2)"
echo " • Calculates the correct parent branch (feature-1, or main if feature-0)"
echo " • Creates MR targeting that parent branch"
echo
echo "Examples:"
echo " git stack mr # Basic MR creation"
echo " git stack mr --draft # Create draft MR"
echo " git stack mr --reviewer @user # Add reviewer"
echo
echo "Additional arguments are passed to 'glab mr create'."
exit 0
fi
done

# Check if glab CLI is available
if ! command -v glab &> /dev/null; then
echo "Error: GitLab CLI (glab) is not installed or not in PATH."
echo "Install it from: https://gitlab.com/gitlab-org/cli#installation"
exit 1
fi

# Check if we're on a stack branch
if ! get_stack_info; then
echo "Error: Current branch is not part of a stack (should match '<base>-<number>' pattern)."
echo "Cannot determine parent branch for MR creation."
exit 1
fi

local current_branch
current_branch=$(git rev-parse --abbrev-ref HEAD)
local parent_branch

# Determine parent branch
if [ "$STACK_NUM" -eq 0 ]; then
# For feature-0, target main or master
if git rev-parse --verify main &>/dev/null; then
parent_branch="main"
elif git rev-parse --verify master &>/dev/null; then
parent_branch="master"
else
echo "Error: Cannot find main or master branch to target."
exit 1
fi
else
# For feature-N (N > 0), target feature-(N-1)
local parent_num=$((STACK_NUM - 1))
parent_branch="${STACK_BASE}-${parent_num}"

# Verify parent branch exists
if ! git rev-parse --verify "$parent_branch" &>/dev/null; then
echo "Error: Parent branch '$parent_branch' does not exist."
echo "Stack may be incomplete or corrupted."
exit 1
fi
fi

# Show confirmation
echo "Creating GitLab MR:"
echo " From: $current_branch"
echo " To: $parent_branch"
echo

# Confirm with user
read -r -p "Proceed with MR creation? [Y/n] " response
response=${response:-y} # Default to yes

if [[ ! "$response" =~ ^[Yy]$ ]]; then
echo "MR creation cancelled."
exit 0
fi

# Create MR with glab CLI
echo "Running: glab mr create -b $parent_branch $*"
if glab mr create -b "$parent_branch" "$@"; then
echo "✅ GitLab MR created successfully!"
else
echo "❌ Failed to create GitLab MR"
exit 1
fi
}

# Only process arguments if script is run directly (not sourced)
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
subcommand="$1"
Expand Down Expand Up @@ -1013,6 +1109,9 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
pr)
create_github_pr "$@"
;;
mr)
create_gitlab_mr "$@"
;;
*)
usage
;;
Expand Down
192 changes: 141 additions & 51 deletions bin/gitstack_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,13 @@ function test_get_stack_info() {
echo "Testing get_stack_info..."

# Create and checkout a test branch
git checkout -b test-123 2>/dev/null
echo "Created test-123 branch"
if ! git checkout -b test-123; then
echo "Failed to create test-123 branch. Trying to checkout existing branch..."
if ! git checkout test-123; then
fail "Could not create or checkout test-123 branch"
fi
fi
echo "Created/checked out test-123 branch"

if get_stack_info; then
echo "Stack info: BASE=$STACK_BASE NUM=$STACK_NUM"
Expand All @@ -59,8 +64,14 @@ function test_get_stack_info() {
fi

# Test with non-stack branch
git checkout -b not-a-stack-branch 2>/dev/null
echo "Created not-a-stack-branch"
if ! git checkout -b not-a-stack-branch; then
echo "Failed to create not-a-stack-branch. Trying to checkout existing branch..."
if ! git checkout not-a-stack-branch; then
fail "Could not create or checkout not-a-stack-branch"
fi
fi
echo "Created/checked out not-a-stack-branch"

if get_stack_info; then
fail "get_stack_info incorrectly identified not-a-stack-branch as a stack branch"
else
Expand All @@ -77,11 +88,15 @@ function test_get_stack_branches() {
echo "Testing get_stack_branches..."

# Create test branches
git checkout -b bar-1 2>/dev/null
git checkout -b bar-2 2>/dev/null
git checkout -b bar-3 2>/dev/null
git checkout -b other-1 2>/dev/null
echo "Created test branches"
for branch in bar-1 bar-2 bar-3 other-1; do
if ! git checkout -b "$branch"; then
echo "Failed to create $branch. Trying to checkout existing branch..."
if ! git checkout "$branch"; then
fail "Could not create or checkout $branch"
fi
fi
done
echo "Created/checked out test branches"

# Get branches and check count
local branches
Expand Down Expand Up @@ -116,12 +131,15 @@ function test_list_stacks() {
echo "Testing list_stacks..."

# Create some test stacks
git checkout -b feature-0 2>/dev/null
git checkout -b feature-1 2>/dev/null
git checkout -b bugfix-0 2>/dev/null
git checkout -b bugfix-1 2>/dev/null
git checkout -b other-branch 2>/dev/null
echo "Created test branches"
for branch in feature-0 feature-1 bugfix-0 bugfix-1 other-branch; do
if ! git checkout -b "$branch"; then
echo "Failed to create $branch. Trying to checkout existing branch..."
if ! git checkout "$branch"; then
fail "Could not create or checkout $branch"
fi
fi
done
echo "Created/checked out test branches"

# Get all stack bases
local stack_bases
Expand Down Expand Up @@ -505,6 +523,112 @@ function test_push_command() {
rm -f test1.txt test2.txt test3.txt
}

# Test MR creation functionality
function test_mr_command() {
echo "Testing MR command..."

# Create a test stack
git checkout main 2>/dev/null || git checkout master 2>/dev/null
"$SCRIPT_DIR/gitstack.sh" create mr-test
echo "test1" > test1.txt
git add test1.txt
git commit -m "test1"

"$SCRIPT_DIR/gitstack.sh" increment
echo "test2" > test2.txt
git add test2.txt
git commit -m "test2"

"$SCRIPT_DIR/gitstack.sh" increment
echo "test3" > test3.txt
git add test3.txt
git commit -m "test3"

# Create a temporary mock script
local mock_script="/tmp/mock_glab_$$.sh"
echo '#!/bin/bash
# Print all arguments for debugging
echo "[MOCK GLAB] args: $@" >&2
if [ "$1" = "mr" ] && [ "$2" = "create" ]; then
from_branch=$(git rev-parse --abbrev-ref HEAD)
to_branch=""
while [[ $# -gt 0 ]]; do
if [[ "$1" == "-b" ]]; then
shift
to_branch="$1"
break
fi
shift
done
echo "Mock: Creating MR from $from_branch to $to_branch"
exit 0
fi
exit 1' > "$mock_script"
chmod +x "$mock_script"

# Temporarily modify PATH to use our mock
local original_path="$PATH"
export PATH="/tmp:$PATH"
mv "$mock_script" "/tmp/glab"

# Test MR creation from middle branch
git checkout mr-test-1
local output
output=$(echo y | "$SCRIPT_DIR/gitstack.sh" mr 2>&1)
echo "$output"
if echo "$output" | grep -q "Mock: Creating MR from mr-test-1 to mr-test-0"; then
echo "✅ MR command correctly targets previous branch"
else
fail "MR command failed to target correct branch"
fi

# Test MR creation from first branch
git checkout mr-test-0
output=$(echo y | "$SCRIPT_DIR/gitstack.sh" mr 2>&1)
echo "$output"
if echo "$output" | grep -q "Mock: Creating MR from mr-test-0 to main"; then
echo "✅ MR command correctly targets main for first branch"
else
fail "MR command failed to target main for first branch"
fi

# Test with additional arguments
output=$(echo y | "$SCRIPT_DIR/gitstack.sh" mr --draft --reviewer @user 2>&1)
echo "$output"
if echo "$output" | grep -q "Mock: Creating MR from mr-test-0 to main"; then
echo "✅ MR command correctly passes additional arguments"
else
fail "MR command failed to pass additional arguments"
fi

# Test help flag (no prompt expected)
output=$("$SCRIPT_DIR/gitstack.sh" mr --help 2>&1)
echo "$output"
if echo "$output" | grep -q "git stack mr - Create GitLab MR"; then
echo "✅ MR command shows help text"
else
fail "MR command failed to show help text"
fi

# Test error when not on stack branch (no prompt expected)
git checkout -b not-a-stack-branch
output=$("$SCRIPT_DIR/gitstack.sh" mr 2>&1 || true)
echo "$output"
if echo "$output" | grep -q "Error: Current branch is not part of a stack"; then
echo "✅ MR command correctly errors on non-stack branch"
else
fail "MR command failed to error on non-stack branch"
fi

# Clean up
git checkout main 2>/dev/null || git checkout master 2>/dev/null
"$SCRIPT_DIR/gitstack.sh" delete -f mr-test
git branch -D not-a-stack-branch 2>/dev/null || true
rm -f test1.txt test2.txt test3.txt
rm -f "/tmp/glab"
export PATH="$original_path"
}

# Run all tests
function run_all_tests() {
source_gitstack
Expand All @@ -517,6 +641,7 @@ function run_all_tests() {
test_stack_navigation
test_convert_to_stack
test_push_command
test_mr_command
}

# Create a temporary test directory
Expand Down Expand Up @@ -544,45 +669,10 @@ echo "Starting git stack tests..."
# Run all tests
run_all_tests

# Optional: Clean up any existing test branches from previous runs
git branch -D foo-0 foo-1 foo-2 2>/dev/null || true

# 1. Create stack with base name 'foo'
"$SCRIPT_DIR/gitstack.sh" create foo
if [ "$(current_branch)" != "foo-0" ]; then
fail "Expected current branch to be 'foo-0' after create, got '$(current_branch)'"
fi
echo "✅ Successfully created and checked out 'foo-0'"

# 2. Increment -> foo-1
"$SCRIPT_DIR/gitstack.sh" increment
if [ "$(current_branch)" != "foo-1" ]; then
fail "Expected current branch to be 'foo-1' after increment, got '$(current_branch)'"
fi
echo "✅ Successfully incremented to 'foo-1'"

# 3. Increment -> foo-2
"$SCRIPT_DIR/gitstack.sh" increment
if [ "$(current_branch)" != "foo-2" ]; then
fail "Expected current branch to be 'foo-2' after increment, got '$(current_branch)'"
fi
echo "✅ Successfully incremented to 'foo-2'"

echo "Deleting stack with '$SCRIPT_DIR/gitstack.sh delete -f foo'"
"$SCRIPT_DIR/gitstack.sh" delete -f foo

# Verify no foo-* branches remain
if git rev-parse --verify foo-0 &>/dev/null || \
git rev-parse --verify foo-1 &>/dev/null || \
git rev-parse --verify foo-2 &>/dev/null; then
fail "Expected no foo-* branches to exist after delete -f foo"
fi
echo "✅ Successfully deleted all foo-* branches"

echo
echo "🎉 All tests passed!"

# Clean up
echo "Cleaning up test repository..."
cd - > /dev/null || exit 1
cd - > /dev/null || true
rm -rf "$TEST_DIR"