Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
80 changes: 80 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: CI

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]

jobs:
test:
runs-on: ubuntu-latest

strategy:
matrix:
go-version: [1.24]

steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}

- name: Configure Git
run: |
git config --global user.email "ci@amberpixels.io"
git config --global user.name "GitHub CI"
git config --global init.defaultBranch main

- name: Cache Go modules
uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-

- name: Download dependencies
run: go mod download

- name: Verify dependencies
run: go mod verify

- name: Run go vet
run: go vet ./...

- name: Run tests
run: go test -v -count=1 ./...

- name: Debug failing tests (if main tests fail)
if: failure()
run: |
echo "=== Debugging TestUndoLog and TestUndoMerge ==="
go test -v -run "TestGitUndoSuite/(TestUndoLog|TestUndoMerge)" ./internal/app || true
echo "=== Git version and config ==="
git --version
git config --list --show-origin || true

- name: Test build with version info
run: |
TEST_VERSION="v0.0.0-ci-test-$(date +%Y%m%d%H%M%S)"
go build -ldflags "-X main.version=${TEST_VERSION}" -o build/git-undo-versioned ./cmd/git-undo
echo "Built binary with test version: ${TEST_VERSION}"

# Test that the binary outputs the correct version
BINARY_VERSION=$(./build/git-undo-versioned --version 2>/dev/null || echo "version command not found")
echo "Binary reported version: ${BINARY_VERSION}"

# Verify the version matches (allowing for some flexibility in output format)
if echo "${BINARY_VERSION}" | grep -q "${TEST_VERSION}"; then
echo "✅ Version verification successful!"
else
echo "❌ Version verification failed!"
echo "Expected: ${TEST_VERSION}"
echo "Got: ${BINARY_VERSION}"
exit 1
fi
49 changes: 49 additions & 0 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Integration Tests

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]

jobs:
integration:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Determine integration test mode
id: test-mode
run: |
if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
echo "mode=production" >> $GITHUB_OUTPUT
echo "dockerfile=scripts/integration/Dockerfile" >> $GITHUB_OUTPUT
echo "description=real user experience (published releases)" >> $GITHUB_OUTPUT
else
echo "mode=development" >> $GITHUB_OUTPUT
echo "dockerfile=scripts/integration/Dockerfile.dev" >> $GITHUB_OUTPUT
echo "description=current branch changes" >> $GITHUB_OUTPUT
fi

- name: Build and run integration tests
run: |
echo "🧪 Integration test mode: ${{ steps.test-mode.outputs.mode }}"
echo "📝 Testing: ${{ steps.test-mode.outputs.description }}"
echo "🐳 Using dockerfile: ${{ steps.test-mode.outputs.dockerfile }}"
echo ""

echo "Building integration test image..."
docker build -f "${{ steps.test-mode.outputs.dockerfile }}" -t git-undo-integration:ci .

echo "Running integration tests..."
docker run --rm git-undo-integration:ci

- name: Clean up Docker images
if: always()
run: |
docker rmi git-undo-integration:ci || true
62 changes: 57 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,49 +10,101 @@ MAIN_FILE := $(CMD_DIR)/main.go
BINARY_NAME := git-undo
INSTALL_DIR := $(shell go env GOPATH)/bin

# Build version with git information
VERSION_TAG := $(shell git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
VERSION_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
VERSION_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
VERSION_DATE := $(shell date +%Y%m%d%H%M%S)

# Conditionally include branch in version string
ifeq ($(VERSION_BRANCH),main)
VERSION := $(VERSION_TAG)-$(VERSION_DATE)-$(VERSION_COMMIT)
else ifeq ($(VERSION_BRANCH),unknown)
VERSION := $(VERSION_TAG)-$(VERSION_DATE)-$(VERSION_COMMIT)
else
VERSION := $(VERSION_TAG)-$(VERSION_DATE)-$(VERSION_COMMIT)-$(VERSION_BRANCH)
endif

# Default target
all: build

# Build the binary
.PHONY: build
build:
@mkdir -p $(BUILD_DIR)
@go build -o $(BUILD_DIR)/$(BINARY_NAME) $(MAIN_FILE)
@go build -ldflags "-X main.version=$(VERSION)" -o $(BUILD_DIR)/$(BINARY_NAME) $(MAIN_FILE)

# Run the binary
.PHONY: run
run: build
./$(BUILD_DIR)/$(BINARY_NAME)

# Run tests
.PHONY: test
test:
@go test -v ./...

# Run integration tests in dev mode (test current changes)
.PHONY: integration-test-dev
integration-test-dev:
@./scripts/run-integration.sh --dev

# Run integration tests in production mode (test real user experience)
.PHONY: integration-test-prod
integration-test-prod:
@./scripts/run-integration.sh --prod

# Run integration tests (alias for dev mode)
.PHONY: integration-test
integration-test: integration-test-dev

# Run all tests (unit + integration dev)
.PHONY: test-all
test-all: test integration-test-dev

# Tidy: format and vet the code
.PHONY: tidy
tidy:
@go fmt $(PKGS)
@go vet $(PKGS)
@go mod tidy

# Install golangci-lint only if it's not already installed
.PHONY: lint-install
lint-install:
@if ! [ -x "$(GOLANGCI_LINT)" ]; then \
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest; \
fi

# Lint the code using golangci-lint
# todo reuse var if possible
.PHONY: lint
lint: lint-install
$(shell which golangci-lint) run

# Install the binary globally with aliases
# Install the binary globally with custom version info
.PHONY: binary-install
binary-install:
@go install $(CMD_DIR)
@echo "Installing git-undo with version: $(VERSION)"
@go install -ldflags "-X main.version=$(VERSION)" $(CMD_DIR)

.PHONY: install
install:
./install.sh

.PHONY: uninstall
uninstall:
./uninstall.sh

# Uninstall the binary and remove the alias
.PHONY: binary-uninstall
binary-uninstall:
rm -f $(INSTALL_DIR)/$(BINARY_NAME)

# Phony targets
.PHONY: all build run test tidy lint-install lint binary-install binary-uninstall install
.PHONY: buildscripts
buildscripts:
@./scripts/build.sh

.PHONY: update
update:
./update.sh
60 changes: 59 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# git-undo ⏪✨

*A universal Ctrl + Z for Git commands.* 🔄
*A universal "Ctrl + Z" for Git commands.* 🔄

`git-undo` tracks every mutating Git command you run and can roll it back with a single `git undo` 🚀
No reflog spelunking, no cherry‑picks—just instant reversal. ⚡
Expand All @@ -12,6 +12,7 @@ No reflog spelunking, no cherry‑picks—just instant reversal. ⚡
- [cURL one‑liner](#curl-one-liner-preferred)
- [Manual clone](#manual-clone)
- [Shell‑hook integration](#shell-hook-integration)
- [Using go install](#using-go-install)
4. [Quick Start](#quick-start)
5. [Usage](#usage)
6. [Supported Git Commands](#supported-git-commands)
Expand Down Expand Up @@ -61,6 +62,12 @@ and appends a `source` line to your `.zshrc`.
- The installer drops [`scripts/git-undo-hook.bash`](scripts/git-undo-hook.bash) into `~/.config/git-undo/`
and appends a `source` line to your `.bashrc` / `.bash_profile` (depending on your OS).

### Using go install

```bash
go install github.com/amberpixels/git-undo/cmd/git-undo@latest
```

## Quick Start
```bash
git add .
Expand All @@ -83,6 +90,33 @@ git undo undo # redo last undo (like Ctrl+Shift+Z)
| `git undo --dry-run` | Print what *would* be executed, do nothing |
| `git undo --log` | Dump your logged command history |

### Version Information

Check the version of git-undo:

```bash
git undo version # Standard version command
git undo self version # The same (just a consistent way for other `git undo self` commands)
```

The version detection works in the following priority:
1. Git tag version (if in a git repository with tags)
2. Build-time version (set during compilation)
3. "unknown" (fallback)

### Self-Management Commands

Update git-undo to the latest version:

```bash
git undo self update
```

Uninstall git-undo:

```bash
git undo self uninstall
```

## Supported Git Commands
* `commit`
Expand Down Expand Up @@ -114,6 +148,30 @@ make lint # golangci‑lint
make build # compile to ./build/git-undo
make install # installs Go binary and adds zsh hook
```

## Development

### Building with Version Information

To build git-undo with a specific version:

```bash
# Using git describe
VERSION=$(git describe --tags --always 2>/dev/null || echo "dev")
go build -ldflags "-X main.version=$VERSION" ./cmd/git-undo

# Or manually specify version
go build -ldflags "-X main.version=v1.2.3" ./cmd/git-undo
```

### Testing

Run the test suite:

```bash
go test ./...
```

## Contributing & Feedback
Spotted a bug or missing undo case?
Opening an issue or PR makes the tool better for everyone.
Expand Down
10 changes: 9 additions & 1 deletion cmd/git-undo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ import (
"fmt"
"os"

gitundo "github.com/amberpixels/git-undo"
"github.com/amberpixels/git-undo/internal/app"
)

// Build-time version information
// This can be set during build using: go build -ldflags "-X main.version=v1.0.0".
var version = "dev"

func main() {
var verbose, dryRun bool
for _, arg := range os.Args[1:] {
Expand All @@ -18,7 +23,10 @@ func main() {
}
}

application := app.New(".", verbose, dryRun)
application := app.New(".", version, verbose, dryRun)
// Set embedded scripts from root package
app.SetEmbeddedScripts(application, gitundo.GetUpdateScript(), gitundo.GetUninstallScript())

if err := application.Run(os.Args[1:]); err != nil {
_, _ = fmt.Fprintln(os.Stderr, redColor+"git-undo ❌: "+grayColor+err.Error()+resetColor)
os.Exit(1)
Expand Down
21 changes: 21 additions & 0 deletions embed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package gitundo

import (
_ "embed"
)

//go:embed update.sh
var updateScript string

//go:embed uninstall.sh
var uninstallScript string

// GetUpdateScript returns the embedded update script content.
func GetUpdateScript() string {
return updateScript
}

// GetUninstallScript returns the embedded uninstall script content.
func GetUninstallScript() string {
return uninstallScript
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ module github.com/amberpixels/git-undo

go 1.24

toolchain go1.24.3

require (
github.com/mattn/go-shellwords v1.0.12
github.com/stretchr/testify v1.10.0
Expand Down
Loading
Loading