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
110 changes: 110 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
name: CI

on:
pull_request:
push:
branches:
- main
- master

permissions:
contents: read

jobs:
format:
name: Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: go.mod
cache: true
- name: Check formatting
shell: bash
run: test -z "$(gofmt -l $(find . -type f -name '*.go' -not -path './vendor/*'))"

vet:
name: Vet
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: go.mod
cache: true
- run: go vet ./...

test:
name: Tests (${{ matrix.name }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- name: Linux
os: ubuntu-latest
- name: Windows
os: windows-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: go.mod
cache: true
- run: go test ./...

race:
name: Race tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: go.mod
cache: true
- run: go test -race ./...

build:
name: Build (${{ matrix.name }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- name: Linux
os: ubuntu-latest
- name: Windows
os: windows-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: go.mod
cache: true
- name: Build
run: go build -trimpath ./...

smoke:
name: CLI smoke test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: go.mod
cache: true
- run: go run ./cmd/gitmulti --help
- run: go run ./cmd/gitmulti version --json
108 changes: 108 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
name: Release

on:
push:
tags:
- 'v*'

permissions:
contents: read

jobs:
verify:
name: Verify release source
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: go.mod
cache: true
- run: make check

package:
name: Package ${{ matrix.goos }}/${{ matrix.goarch }}
needs: verify
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
attestations: write
strategy:
fail-fast: false
matrix:
include:
- goos: linux
goarch: amd64
- goos: linux
goarch: arm64
- goos: darwin
goarch: amd64
- goos: darwin
goarch: arm64
- goos: windows
goarch: amd64
- goos: windows
goarch: arm64
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: go.mod
cache: true
- name: Build archive
env:
VERSION: ${{ github.ref_name }}
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
shell: bash
run: |
set -eu
mkdir -p dist
binary=gitmulti
if [ "$GOOS" = windows ]; then
binary=gitmulti.exe
fi
build_date=$(date -u +%Y-%m-%dT%H:%M:%SZ)
GOOS="$GOOS" GOARCH="$GOARCH" CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -X github.com/ramonzx6/gitmulti/internal/cli/buildinfo.Version=$VERSION -X github.com/ramonzx6/gitmulti/internal/cli/buildinfo.Commit=$GITHUB_SHA -X github.com/ramonzx6/gitmulti/internal/cli/buildinfo.BuildDate=$build_date" -o "dist/$binary" ./cmd/gitmulti
cp LICENSE README.md dist/
archive=$(printf 'gitmulti_%s_%s_%s' "$VERSION" "$GOOS" "$GOARCH")
if [ "$GOOS" = windows ]; then
(cd dist && zip -q "../dist/$archive.zip" "$binary" LICENSE README.md)
(cd dist && sha256sum "$archive.zip" > "$archive.zip.sha256")
else
(cd dist && tar -czf "../dist/$archive.tar.gz" "$binary" LICENSE README.md)
(cd dist && sha256sum "$archive.tar.gz" > "$archive.tar.gz.sha256")
fi
rm -f "dist/$binary" dist/LICENSE dist/README.md
- name: Attest release archive
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0
with:
subject-path: dist/gitmulti_*
- name: Upload archive
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: gitmulti-${{ matrix.goos }}-${{ matrix.goarch }}
path: dist/*
if-no-files-found: error

publish:
name: Publish GitHub release
needs: package
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
path: dist
merge-multiple: true
- name: Create release and upload archives
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: gh release create "$GITHUB_REF_NAME" dist/* --verify-tag --generate-notes --title "$GITHUB_REF_NAME"
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
.env
gitmulti
/.venv/
/gitmulti
/site/
test/
gitmulti.exe
/gitmulti.exe
reviewtmp/
91 changes: 87 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,92 @@
# Contributing

By submitting an issue, pull request, patch, suggestion, or other contribution to this project, you agree that your contribution may be used, modified, and distributed by the project owner as part of this project.
GitMulti is source-available under the [PolyForm Strict License 1.0.0](LICENSE).
Before contributing, make sure your proposed use and contribution are allowed
by that license.

You grant the project owner a perpetual, worldwide, non-exclusive, royalty-free license to use, reproduce, modify, distribute, sublicense, and otherwise incorporate your contribution into this project.
## Before opening a change

Contributions do not give you ownership of the project or permission to use the project outside the terms of the project license.
1. Read the applicable AGENTS.md instructions.
2. Keep changes focused and preserve unrelated work.
3. Add or update tests for behavior changes and regressions.
4. Update the relevant page in `docs/` when command flags, output, or exit
behavior changes. Keep the README's documentation links current.
5. Do not include credentials, private repository URLs, or generated binaries.

This project is licensed under the PolyForm Strict License 1.0.0. See [LICENSE](LICENSE) for details.
The CLI is intentionally shell-free: pass Git arguments as positional values
after -- and avoid introducing shell parsing or command interpolation. Changes
that execute Git should preserve cancellation, timeouts, and per-repository
error reporting.

User-facing commands live in `internal/cli/command/<name>`. Keep each command's
constructor, private rendering or policy helpers, and focused tests in that
package. Shared behavior belongs in a neutral `internal/cli` subpackage; leaf
commands must not import the CLI root or sibling commands.

## Local validation

Go 1.24 or newer and Git are required. Run the complete project gate before
submitting:

~~~sh
make check
~~~

The gate runs:

- gofmt validation (make fmt-check)
- go vet ./... (make vet)
- go test ./... (make test)
- go test -race ./... (make race)
- a metadata-bearing native build (make build)
- --help and version --json smoke tests (make smoke)

Individual commands are available when iterating:

~~~sh
make fmt # format Go files in place
make fmt-check
make vet
make test
make race
make build
make smoke
~~~

Documentation changes also require a strict MkDocs build. Create an isolated
environment, install the pinned documentation dependency, and build the site:

~~~sh
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -r requirements-docs.txt
make docs-build
~~~

On PowerShell, activate the environment with
`.\.venv\Scripts\Activate.ps1` before installing the dependency.

If a required tool or platform-specific check is unavailable, report it as
skipped in the change description rather than treating it as passed. The
GitHub Actions workflow repeats formatting, vet, tests, race tests, and native
and Windows builds.

## Change and commit conventions

Use Conventional Commit subjects such as:

~~~text
feat(status): add upstream diagnostics
fix(exec): preserve cancellation errors
test(repository): cover nested boundaries
docs: describe snapshot redaction
~~~

Review the final diff and test output. Do not commit unrelated changes. The
repository owner handles integration, publishing, and releases.

## Reporting a vulnerability

Do not disclose secrets or a suspected vulnerability in a public issue. Contact
the project owner privately with a minimal reproduction and the affected
version. Do not include credentials in logs, screenshots, or patches.
50 changes: 50 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
GO ?= go
GOFMT ?= gofmt
DOCS_PYTHON ?= python3
DOCS_REQUIREMENTS ?= requirements-docs.txt
BINARY ?= gitmulti
VERSION ?= dev
COMMIT ?= unknown
BUILD_DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)

LDFLAGS ?= -s -w -X github.com/ramonzx6/gitmulti/internal/cli/buildinfo.Version=$(VERSION) -X github.com/ramonzx6/gitmulti/internal/cli/buildinfo.Commit=$(COMMIT) -X github.com/ramonzx6/gitmulti/internal/cli/buildinfo.BuildDate=$(BUILD_DATE)

.PHONY: all fmt fmt-check vet test race build smoke check docs-install docs-build docs-serve clean

all: check

fmt:
$(GOFMT) -w $$(find . -type f -name '*.go' -not -path './vendor/*')

fmt-check:
@test -z "$$($(GOFMT) -l $$(find . -type f -name '*.go' -not -path './vendor/*'))"

vet:
$(GO) vet ./...

test:
$(GO) test ./...

race:
$(GO) test -race ./...

build:
$(GO) build -trimpath -ldflags "$(LDFLAGS)" -o "$(BINARY)" ./cmd/gitmulti

smoke: build
@./$(BINARY) --help >/dev/null
@./$(BINARY) version --json >/dev/null

check: fmt-check vet test race build smoke

docs-install:
$(DOCS_PYTHON) -m pip install -r "$(DOCS_REQUIREMENTS)"

docs-build:
$(DOCS_PYTHON) -m mkdocs build --strict

docs-serve:
$(DOCS_PYTHON) -m mkdocs serve

clean:
rm -f "$(BINARY)" "$(BINARY).exe"
Loading
Loading