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
154 changes: 152 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@ on:
branches:
- main

# Cancel duplicate runs on the same PR/branch, but keep history on main.
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

# Default to read-only. Individual jobs may grant additional scopes if needed.
permissions:
contents: read

jobs:
check:
lint:
runs-on: ubuntu-latest
timeout-minutes: 10

permissions:
contents: read
Expand All @@ -27,4 +33,148 @@ jobs:
go-version-file: go.mod

- run: go mod download
- run: make check
- run: make fmt-check
- run: make lint
- run: make typecheck
- run: make deadcode

test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]

runs-on: ${{ matrix.os }}
timeout-minutes: 15

permissions:
contents: read

steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false

- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version-file: go.mod

# The e2e test shells out to `git`. Confirm it's available so a missing
# binary fails loud here instead of producing a confusing test error.
- name: Verify git is available
run: git --version

- run: go mod download

- name: Run tests with race detector
# Tee verbose output to a file so we can attach it as an artifact on
# failure — Actions logs aren't accessible to anonymous viewers, and
# OS-specific failures are easier to triage with full -v output.
shell: bash
run: |
set -o pipefail
go test -race -shuffle=on -v ./... 2>&1 | tee test-output.txt

- name: Upload test output (always)
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: test-output-${{ matrix.os }}
path: test-output.txt
if-no-files-found: warn

- name: Enforce coverage threshold (Linux only)
if: matrix.os == 'ubuntu-latest'
run: make coverage

- name: Upload coverage profile
if: matrix.os == 'ubuntu-latest'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: coverage
path: coverage.out
if-no-files-found: error

vuln:
runs-on: ubuntu-latest
timeout-minutes: 5

permissions:
contents: read

steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false

- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version-file: go.mod

- run: go mod download
- run: make vuln

build:
strategy:
fail-fast: false
matrix:
include:
- goos: darwin
goarch: amd64
- goos: darwin
goarch: arm64
- goos: linux
goarch: amd64
- goos: linux
goarch: arm64
- goos: windows
goarch: amd64

runs-on: ubuntu-latest
timeout-minutes: 10

permissions:
contents: read

steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false

- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version-file: go.mod

- run: go mod download

# Cross-compile sanity check using the same flags as release.yml. We do
# not upload the binary here — release.yml is the canonical producer of
# signed, reproducible artifacts. This job exists so PRs catch breakage
# in a target before it reaches a tag.
- name: Cross-compile
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
run: |
go build -trimpath -buildvcs=true -o /dev/null ./cmd/git-real

actionlint:
runs-on: ubuntu-latest
timeout-minutes: 5

permissions:
contents: read

steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false

- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version-file: go.mod

- run: go mod download

- name: Lint workflow files (actionlint)
run: make actionlint
52 changes: 52 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: codeql

on:
push:
branches:
- main
pull_request:
branches:
- main
schedule:
# Weekly on Monday 06:17 UTC (off-peak, randomized minute to avoid
# contention with other repos scheduled on the hour).
- cron: "17 6 * * 1"

permissions:
contents: read

jobs:
analyze:
name: analyze (go)
runs-on: ubuntu-latest
timeout-minutes: 30

permissions:
contents: read
actions: read
security-events: write

strategy:
fail-fast: false
matrix:
language: [go]

steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false

- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version-file: go.mod

- name: Initialize CodeQL
uses: github/codeql-action/init@b2f9ef845756500b97acbdaf5c1dd4e9c1d15734 # v3.35.2
with:
languages: ${{ matrix.language }}
build-mode: autobuild

- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@b2f9ef845756500b97acbdaf5c1dd4e9c1d15734 # v3.35.2
with:
category: "/language:${{ matrix.language }}"
25 changes: 25 additions & 0 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: dependency-review

on:
pull_request:

permissions:
contents: read

jobs:
review:
runs-on: ubuntu-latest
timeout-minutes: 5
# Skip dependency-review on Dependabot's own PRs (it would re-review its
# own bumps, often spuriously) and let it run unblocking elsewhere — we'll
# tighten the policy once the repository's Dependency Graph status is
# confirmed enabled. Until then, this is best-effort and non-blocking.
continue-on-error: true

permissions:
contents: read

steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0
5 changes: 4 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,10 @@ jobs:

# Pin the build timestamp to the tag commit's author date so that
# rebuilding from the same source produces a byte-identical binary.
export SOURCE_DATE_EPOCH="$(git log -1 --format=%ct)"
# (declare and export separately so a `git log` failure isn't masked
# by `export`'s exit status — shellcheck SC2155)
SOURCE_DATE_EPOCH="$(git log -1 --format=%ct)"
export SOURCE_DATE_EPOCH

go build \
-trimpath \
Expand Down
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ LDFLAGS := -s -w \
-X main.commit=$(COMMIT) \
-X main.date=$(DATE)

.PHONY: build fmt fmt-check lint typecheck deadcode test coverage check
.PHONY: build fmt fmt-check lint typecheck deadcode test test-race coverage vuln actionlint check

build:
$(GO) build -trimpath -buildvcs=true -ldflags='$(LDFLAGS)' -o git-real ./cmd/git-real
Expand Down Expand Up @@ -51,7 +51,16 @@ deadcode:
test:
$(GO) test ./...

test-race:
$(GO) test -race -shuffle=on ./...

coverage:
COVERAGE_THRESHOLD=$(COVERAGE_THRESHOLD) bash ./scripts/check-coverage.sh

vuln:
$(GO) tool govulncheck ./...

actionlint:
$(GO) tool actionlint

check: fmt-check lint typecheck deadcode coverage
2 changes: 2 additions & 0 deletions cmd/git-real/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func TestMainEndToEnd(t *testing.T) {
runGit(t, repoDir, "config", "user.name", "GitReal Test")
runGit(t, repoDir, "config", "user.email", "test@example.com")
runGit(t, repoDir, "config", "commit.gpgsign", "false")
runGit(t, repoDir, "config", "core.autocrlf", "false")

writeFile(t, filepath.Join(repoDir, "file.txt"), "base\n")
runGit(t, repoDir, "add", "file.txt")
Expand Down Expand Up @@ -129,6 +130,7 @@ func TestMainOnceCancelledByContext(t *testing.T) {
runGit(t, repoDir, "config", "user.name", "GitReal Test")
runGit(t, repoDir, "config", "user.email", "test@example.com")
runGit(t, repoDir, "config", "commit.gpgsign", "false")
runGit(t, repoDir, "config", "core.autocrlf", "false")

writeFile(t, filepath.Join(repoDir, "file.txt"), "base\n")
runGit(t, repoDir, "add", "file.txt")
Expand Down
15 changes: 14 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,30 @@ module github.com/watany-dev/gitreal
go 1.26

tool (
github.com/rhysd/actionlint/cmd/actionlint
golang.org/x/tools/cmd/deadcode
golang.org/x/vuln/cmd/govulncheck
honnef.co/go/tools/cmd/staticcheck
)

require (
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/fatih/color v1.19.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.21 // indirect
github.com/mattn/go-shellwords v1.0.12 // indirect
github.com/rhysd/actionlint v1.7.12 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect
golang.org/x/mod v0.35.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa // indirect
golang.org/x/telemetry v0.0.0-20260421165255-392afab6f40e // indirect
golang.org/x/tools v0.44.0 // indirect
golang.org/x/vuln v1.3.0 // indirect
honnef.co/go/tools v0.7.0 // indirect
)
37 changes: 33 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,20 +1,49 @@
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU=
github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w=
github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/rhysd/actionlint v1.7.12 h1:vQ4GeJN86C0QH+gTUQcs8McmK62OLT3kmakPMtEWYnY=
github.com/rhysd/actionlint v1.7.12/go.mod h1:krOUhujIsJusovkaYzQ/VNH8PFexjNKqU0q5XI/4w+g=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=
go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ=
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa h1:efT73AJZfAAUV7SOip6pWGkwJDzIGiKBZGVzHYa+ve4=
golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa/go.mod h1:kHjTxDEnAu6/Nl9lDkzjWpR+bmKfxeiRuSDlsMb70gE=
golang.org/x/telemetry v0.0.0-20260421165255-392afab6f40e h1:OXgN37M6hqjaAvb7CJK9vJ+7Z/6lvIm5bXho5poo/Wk=
golang.org/x/telemetry v0.0.0-20260421165255-392afab6f40e/go.mod h1:kHjTxDEnAu6/Nl9lDkzjWpR+bmKfxeiRuSDlsMb70gE=
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=
golang.org/x/vuln v1.3.0 h1:hZYzR8uRhYhDSX88d+40TWbKAVw7BIvRWm26rtEn8jw=
golang.org/x/vuln v1.3.0/go.mod h1:MIY2PaR1y52stzZM3uHBboUAdVJvSVMl5nP3OQrwQaE=
honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU=
honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc=
Loading
Loading