Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
15 changes: 15 additions & 0 deletions docs/commands/branching.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ These commands are implemented as Git aliases and scripts in the Hug tool suite,
| `hug bll` | **B**ranch **L**ist **L**ong | Detailed local branch list with tracking info |
| `hug bc` | **B**ranch **C**reate | Create a new branch and switch to it |
| `hug br` | **B**ranch **R**ename | Rename the current branch |
| `hug brestore` | **B**ranch **Restore** | Restore a branch from a backup |
| `hug bdel` | **B**ranch **DEL**ete (safe) | Delete merged local branch |
| `hug bdelf` | **B**ranch **DEL**ete **F**orce | Force-delete local branch |
| `hug bdelr` | **B**ranch **DEL**ete **R**emote | Delete remote branch |
Expand Down Expand Up @@ -92,6 +93,20 @@ These commands are implemented as Git aliases and scripts in the Hug tool suite,
```
- **Safety**: Prompts for confirmation if the new name exists.

## Branch Restoration

### `hug brestore [new-branch-name]`
- **Description**: Restores a branch from a backup created by `hug rb`. It presents an interactive list of available backups to choose from. If `new-branch-name` is provided, the backup is restored to that name; otherwise, it defaults to the original branch name.
- **Example**:
```shell
# Interactively restore to the original branch name
hug brestore

# Restore a backup to a new branch named 'my-recovered-feature'
hug brestore my-recovered-feature
```
- **Safety**: If the target branch name already exists, it warns you and asks for confirmation before overwriting the existing branch.

## Branch Deletion

### `hug bdel <branch>`
Expand Down
99 changes: 99 additions & 0 deletions git-config/bin/git-brestore
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/usr/bin/env bash
CMD_BASE="$(readlink -f "$0" 2>/dev/null || greadlink -f "$0")" || CMD_BASE="$0"; CMD_BASE="$(dirname "$CMD_BASE")"
# shellcheck source=../lib/hug-common
. "$CMD_BASE/../lib/hug-common"
# shellcheck source=../lib/hug-git-kit
. "$CMD_BASE/../lib/hug-git-kit"
set -euo pipefail # Exit on error, undefined vars, pipe failures

show_help() {
cat << EOF
hug brestore: Restore a branch from a backup created by 'hug rb'.

USAGE:
hug brestore [new-branch-name] [-h, --help]

OPTIONS:
new-branch-name (Optional) The name of the new branch to create.
If not provided, it defaults to the original branch name.
-h, --help Show this help

DESCRIPTION:
Lists all available backup branches and prompts you to select one to restore.
If the target branch name already exists, it will ask for confirmation
before overwriting.

EXAMPLES:
hug brestore # Interactively restore to the original branch name
hug brestore my-new-feature # Restore a backup to a new branch named 'my-new-feature'
EOF
}

# Parse command-line arguments
eval "$(parse_common_flags "$@")"

new_branch_name=""
if [[ $# -gt 0 ]]; then
# check if the argument is not a flag
if [[ "$1" != -* ]]; then
new_branch_name="$1"
shift
fi
fi

# Additional arguments are not allowed
if [[ $# -gt 0 ]]; then
error "Unknown arguments: $*"
fi

# Main logic

check_git_repo

# Get a list of backup branches, sorted by most recent first
mapfile -t backup_branches < <(git for-each-ref --sort=-committerdate 'refs/heads/hug-backups/**' --format='%(refname:short)')

# Check if there are any backups
if [[ ${#backup_branches[@]} -eq 0 ]]; then
info "No backup branches found."
exit 0
fi

# Display the backup branches to the user
info "Available backup branches:"
for i in "${!backup_branches[@]}"; do
printf " %d) %s\n" "$((i + 1))" "${backup_branches[i]}"
done

# Prompt the user for a selection
printf "\nEnter the number of the branch to restore: "
read -r choice

# Validate the user's input
if ! [[ "$choice" =~ ^[0-9]+$ ]] || [[ "$choice" -lt 1 ]] || [[ "$choice" -gt ${#backup_branches[@]} ]]; then
error "Invalid selection. Please enter a number between 1 and ${#backup_branches[@]}."
fi

selected_backup_branch="${backup_branches[$((choice - 1))]}"

# Determine the target branch name
if [[ -n "$new_branch_name" ]]; then
target_branch_name="$new_branch_name"
else
# Extract the original branch name from the backup name
# e.g., hug-backups/2023-11/02-1234.my-feature -> my-feature
target_branch_name=$(basename "$selected_backup_branch" | cut -d'.' -f2-)
fi

# Check if the target branch already exists
if git rev-parse --verify "$target_branch_name" >/dev/null 2>&1; then
warning "Branch '$target_branch_name' already exists and will be overwritten."
prompt_confirm "Are you sure you want to continue?"
fi

# Restore the branch
if git branch -f "$target_branch_name" "$selected_backup_branch"; then
success "Branch '$target_branch_name' has been restored from backup '$selected_backup_branch'."
else
error "Failed to restore branch '$target_branch_name'."
fi
142 changes: 142 additions & 0 deletions tests/unit/test_brestore_workflow.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#!/usr/bin/env bats
# Integration tests for the brestore command

# Load test helpers
load '../test_helper'

setup() {
require_hug
TEST_REPO=$(create_test_repo)
cd "$TEST_REPO"
}

teardown() {
cleanup_test_repo
}

@test "brestore: no backups found" {
run hug brestore
assert_success
assert_output --partial "No backup branches found."
}

@test "brestore: restore to original branch name" {
# Create a feature branch and a backup
git checkout -b my-feature
echo "feature content" > feature.txt
git add feature.txt
git commit -m "add feature"
echo "y" | hug rb main

# Switch to main before deleting the branch
git checkout main

# Delete the feature branch
git branch -D my-feature

# Restore the branch
run hug brestore << EOF
1
EOF
assert_success
assert_output --partial "Branch 'my-feature' has been restored"

# Verify the branch exists and has the correct content
run git checkout my-feature
assert_success
assert_file_contains "feature.txt" "feature content"
}

@test "brestore: restore to a new branch name" {
# Create a feature branch and a backup
git checkout -b my-feature
echo "feature content" > feature.txt
git add feature.txt
git commit -m "add feature"
echo "y" | hug rb main

# Restore the branch to a new name
run hug brestore my-new-feature << EOF
1
EOF
assert_success
assert_output --partial "Branch 'my-new-feature' has been restored"

# Verify the new branch exists
run git checkout my-new-feature
assert_success
assert_file_contains "feature.txt" "feature content"
}

@test "brestore: restore with existing branch and confirmation" {
# Create a feature branch and a backup
git checkout -b my-feature
echo "feature content" > feature.txt
git add feature.txt
git commit -m "add feature"
echo "y" | hug rb main

# Switch to main before creating the conflicting branch
git checkout main

# Delete the branch if it exists
git branch -D my-feature 2>/dev/null || true

# Create a conflicting branch
git checkout -b my-feature
echo "conflicting content" > feature.txt
git add feature.txt
git commit -m "conflicting commit"

# Switch away from the branch before trying to overwrite it
git checkout main

# Try to restore, confirming the overwrite
run hug brestore << EOF
1
y
EOF
assert_success
assert_output --partial "Branch 'my-feature' already exists and will be overwritten."
assert_output --partial "Branch 'my-feature' has been restored"

# Verify the branch was overwritten
run git log -1 --pretty=%s my-feature
assert_output "add feature"
}

@test "brestore: restore with existing branch and abort" {
# Create a feature branch and a backup
git checkout -b my-feature
echo "feature content" > feature.txt
git add feature.txt
git commit -m "add feature"
echo "y" | hug rb main

# Switch to main before creating the conflicting branch
git checkout main

# Delete the branch if it exists
git branch -D my-feature 2>/dev/null || true

# Create a conflicting branch
git checkout -b my-feature
echo "conflicting content" > another-file.txt
git add another-file.txt
git commit -m "conflicting commit"

# Switch away from the branch before trying to overwrite it
git checkout main

# Try to restore, but abort
run hug brestore << EOF
1
n
EOF
assert_failure
assert_output --partial "Branch 'my-feature' already exists and will be overwritten."

# Verify the branch was NOT overwritten
run git log -1 --pretty=%s my-feature
assert_output "conflicting commit"
}