mirror of
https://github.com/grdl/git-get.git
synced 2026-02-10 06:09:22 +00:00
Merge pull request #22 from grdl/issue-14-crash-on-empty-repo
Fix a git-list crash on empty repo or repo without a remote
This commit is contained in:
39
.github/dependabot.yml
vendored
Normal file
39
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
# Go modules
|
||||||
|
- package-ecosystem: "gomod"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
day: "monday"
|
||||||
|
time: "09:00"
|
||||||
|
open-pull-requests-limit: 5
|
||||||
|
reviewers:
|
||||||
|
- "grdl"
|
||||||
|
assignees:
|
||||||
|
- "grdl"
|
||||||
|
commit-message:
|
||||||
|
prefix: "deps"
|
||||||
|
include: "scope"
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
|
- "go"
|
||||||
|
|
||||||
|
# GitHub Actions
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
day: "monday"
|
||||||
|
time: "09:00"
|
||||||
|
open-pull-requests-limit: 3
|
||||||
|
reviewers:
|
||||||
|
- "grdl"
|
||||||
|
assignees:
|
||||||
|
- "grdl"
|
||||||
|
commit-message:
|
||||||
|
prefix: "ci"
|
||||||
|
include: "scope"
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
|
- "github-actions"
|
||||||
19
.github/workflows/build.yml
vendored
19
.github/workflows/build.yml
vendored
@@ -1,19 +0,0 @@
|
|||||||
name: build
|
|
||||||
|
|
||||||
on:
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: '1.24'
|
|
||||||
- name: Set up Git
|
|
||||||
run: git config --global user.email "grdl@example.com" && git config --global user.name "grdl"
|
|
||||||
- name: Run go test
|
|
||||||
run: CGO_ENABLED=0 GOOS=linux go test ./... -v
|
|
||||||
142
.github/workflows/ci.yml
vendored
Normal file
142
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master, main]
|
||||||
|
pull_request:
|
||||||
|
branches: [master, main]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Test
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go-version: ['1.24']
|
||||||
|
os: [ubuntu-latest, macos-latest]
|
||||||
|
# TODO: fix tests on windows
|
||||||
|
# os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go-version }}
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Download dependencies
|
||||||
|
run: go mod download
|
||||||
|
|
||||||
|
- name: Verify dependencies
|
||||||
|
run: go mod verify
|
||||||
|
|
||||||
|
- name: Set up Git (for tests)
|
||||||
|
run: |
|
||||||
|
git config --global user.email "test@example.com"
|
||||||
|
git config --global user.name "CI Test"
|
||||||
|
|
||||||
|
- name: Run tests with coverage
|
||||||
|
run: go test -race -coverprofile coverage.out -covermode=atomic ./...
|
||||||
|
|
||||||
|
- name: Upload coverage to Codecov
|
||||||
|
if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.24'
|
||||||
|
uses: codecov/codecov-action@v4
|
||||||
|
with:
|
||||||
|
file: ./coverage.out
|
||||||
|
flags: unittests
|
||||||
|
name: codecov-umbrella
|
||||||
|
fail_ci_if_error: false
|
||||||
|
|
||||||
|
lint:
|
||||||
|
name: Lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.24'
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Run golangci-lint
|
||||||
|
uses: golangci/golangci-lint-action@v6
|
||||||
|
|
||||||
|
# TODO: Fix linting errors
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
args: --timeout=5m
|
||||||
|
|
||||||
|
security:
|
||||||
|
name: Security
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.24'
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Run Trivy vulnerability scanner
|
||||||
|
uses: aquasecurity/trivy-action@master
|
||||||
|
with:
|
||||||
|
scan-type: 'fs'
|
||||||
|
scan-ref: '.'
|
||||||
|
format: 'sarif'
|
||||||
|
output: 'trivy-results.sarif'
|
||||||
|
|
||||||
|
- name: Upload Trivy scan results to GitHub Security tab
|
||||||
|
uses: github/codeql-action/upload-sarif@v3
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
sarif_file: 'trivy-results.sarif'
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
needs: [test, lint, security]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.24'
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Build binaries
|
||||||
|
run: |
|
||||||
|
go build -v -o bin/git-get ./cmd/get
|
||||||
|
go build -v -o bin/git-list ./cmd/list
|
||||||
|
|
||||||
|
- name: Test binaries
|
||||||
|
run: |
|
||||||
|
./bin/git-get --version
|
||||||
|
./bin/git-list --version
|
||||||
|
|
||||||
|
- name: Upload build artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: binaries
|
||||||
|
path: bin/
|
||||||
|
retention-days: 30
|
||||||
71
.github/workflows/codeql-analysis.yml
vendored
71
.github/workflows/codeql-analysis.yml
vendored
@@ -1,71 +0,0 @@
|
|||||||
# For most projects, this workflow file will not need changing; you simply need
|
|
||||||
# to commit it to your repository.
|
|
||||||
#
|
|
||||||
# You may wish to alter this file to override the set of languages analyzed,
|
|
||||||
# or to provide custom queries or build logic.
|
|
||||||
#
|
|
||||||
# ******** NOTE ********
|
|
||||||
# We have attempted to detect the languages in your repository. Please check
|
|
||||||
# the `language` matrix defined below to confirm you have the correct set of
|
|
||||||
# supported CodeQL languages.
|
|
||||||
#
|
|
||||||
name: "CodeQL"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ master ]
|
|
||||||
pull_request:
|
|
||||||
# The branches below must be a subset of the branches above
|
|
||||||
branches: [ master ]
|
|
||||||
schedule:
|
|
||||||
- cron: '34 21 * * 1'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
name: Analyze
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
security-events: write
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
language: [ 'go' ]
|
|
||||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
|
||||||
# Learn more:
|
|
||||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v1
|
|
||||||
with:
|
|
||||||
languages: ${{ matrix.language }}
|
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
|
||||||
# By default, queries listed here will override any specified in a config file.
|
|
||||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
|
||||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
|
||||||
- name: Autobuild
|
|
||||||
uses: github/codeql-action/autobuild@v1
|
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
|
||||||
# 📚 https://git.io/JvXDl
|
|
||||||
|
|
||||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
|
||||||
# and modify them (or add more) to build your code if your project
|
|
||||||
# uses a compiled language
|
|
||||||
|
|
||||||
#- run: |
|
|
||||||
# make bootstrap
|
|
||||||
# make release
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v1
|
|
||||||
46
.github/workflows/codeql.yml
vendored
Normal file
46
.github/workflows/codeql.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
name: "CodeQL Security Analysis"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master, main]
|
||||||
|
pull_request:
|
||||||
|
branches: [master, main]
|
||||||
|
schedule:
|
||||||
|
- cron: '30 2 * * 1' # Run weekly on Mondays at 2:30 AM UTC
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze (${{ matrix.language }})
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 360
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- language: go
|
||||||
|
build-mode: autobuild
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v3
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
build-mode: ${{ matrix.build-mode }}
|
||||||
|
# Enable additional security-and-quality query pack
|
||||||
|
queries: +security-and-quality
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v3
|
||||||
|
with:
|
||||||
|
category: "/language:${{matrix.language}}"
|
||||||
24
.github/workflows/release-simple.yml
vendored
Normal file
24
.github/workflows/release-simple.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
name: release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
goreleaser:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.24'
|
||||||
|
- name: Run GoReleaser
|
||||||
|
uses: goreleaser/goreleaser-action@v6
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
args: release --clean
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GORELEASER_TOKEN }}
|
||||||
62
.github/workflows/release.yml
vendored
62
.github/workflows/release.yml
vendored
@@ -1,24 +1,78 @@
|
|||||||
name: release
|
name: Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- '*'
|
- 'v*'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
security-events: write
|
||||||
|
id-token: write # For SLSA provenance
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
goreleaser:
|
validate:
|
||||||
|
name: Validate Release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.24'
|
go-version: '1.24'
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: go test -race ./...
|
||||||
|
|
||||||
|
- name: Run lints
|
||||||
|
uses: golangci/golangci-lint-action@v6
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
|
||||||
|
- name: Validate GoReleaser config
|
||||||
|
uses: goreleaser/goreleaser-action@v6
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
args: check
|
||||||
|
|
||||||
|
release:
|
||||||
|
name: GoReleaser
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: validate
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.24'
|
||||||
|
cache: true
|
||||||
|
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v6
|
uses: goreleaser/goreleaser-action@v6
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: release --rm-dist
|
args: release --clean
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GORELEASER_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GORELEASER_TOKEN }}
|
||||||
|
|
||||||
|
provenance:
|
||||||
|
name: Generate SLSA Provenance
|
||||||
|
needs: release
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
|
||||||
|
with:
|
||||||
|
base64-subjects: "${{ needs.release.outputs.hashes }}"
|
||||||
|
upload-assets: true
|
||||||
|
secrets:
|
||||||
|
registry-password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
115
.golangci.yml
Normal file
115
.golangci.yml
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
version: 2
|
||||||
|
|
||||||
|
run:
|
||||||
|
timeout: 5m
|
||||||
|
go: '1.24'
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
errcheck:
|
||||||
|
check-type-assertions: true
|
||||||
|
check-blank: true
|
||||||
|
|
||||||
|
govet:
|
||||||
|
enable-all: true
|
||||||
|
disable:
|
||||||
|
- shadow
|
||||||
|
|
||||||
|
gocyclo:
|
||||||
|
min-complexity: 15
|
||||||
|
|
||||||
|
dupl:
|
||||||
|
threshold: 100
|
||||||
|
|
||||||
|
goconst:
|
||||||
|
min-len: 3
|
||||||
|
min-occurrences: 3
|
||||||
|
|
||||||
|
lll:
|
||||||
|
line-length: 120
|
||||||
|
|
||||||
|
unparam:
|
||||||
|
check-exported: false
|
||||||
|
|
||||||
|
nakedret:
|
||||||
|
max-func-lines: 30
|
||||||
|
|
||||||
|
prealloc:
|
||||||
|
simple: true
|
||||||
|
range-loops: true
|
||||||
|
for-loops: false
|
||||||
|
|
||||||
|
gocritic:
|
||||||
|
enabled-tags:
|
||||||
|
- diagnostic
|
||||||
|
- experimental
|
||||||
|
- opinionated
|
||||||
|
- performance
|
||||||
|
- style
|
||||||
|
|
||||||
|
funlen:
|
||||||
|
lines: 100
|
||||||
|
statements: 50
|
||||||
|
|
||||||
|
godox:
|
||||||
|
keywords:
|
||||||
|
- NOTE
|
||||||
|
- OPTIMIZE
|
||||||
|
- HACK
|
||||||
|
|
||||||
|
dogsled:
|
||||||
|
max-blank-identifiers: 2
|
||||||
|
|
||||||
|
whitespace:
|
||||||
|
multi-if: false
|
||||||
|
multi-func: false
|
||||||
|
|
||||||
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- bodyclose
|
||||||
|
- dogsled
|
||||||
|
- dupl
|
||||||
|
- errcheck
|
||||||
|
- funlen
|
||||||
|
- gochecknoinits
|
||||||
|
- goconst
|
||||||
|
- gocritic
|
||||||
|
- gocyclo
|
||||||
|
- godox
|
||||||
|
- goprintffuncname
|
||||||
|
- gosec
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- lll
|
||||||
|
- misspell
|
||||||
|
- nakedret
|
||||||
|
- noctx
|
||||||
|
- nolintlint
|
||||||
|
- prealloc
|
||||||
|
- revive
|
||||||
|
- staticcheck
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
||||||
|
- unused
|
||||||
|
- whitespace
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-rules:
|
||||||
|
- path: _test\.go
|
||||||
|
linters:
|
||||||
|
- funlen
|
||||||
|
- goconst
|
||||||
|
- lll
|
||||||
|
|
||||||
|
- path: pkg/git/test/
|
||||||
|
linters:
|
||||||
|
- goconst
|
||||||
|
|
||||||
|
exclude-use-default: false
|
||||||
|
max-issues-per-linter: 0
|
||||||
|
max-same-issues: 0
|
||||||
|
|
||||||
|
output:
|
||||||
|
format: colored-line-number
|
||||||
|
print-issued-lines: true
|
||||||
|
print-linter-name: true
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
version: 2
|
||||||
|
|
||||||
before:
|
before:
|
||||||
hooks:
|
hooks:
|
||||||
- go mod download
|
- go mod download
|
||||||
@@ -30,18 +32,16 @@ builds:
|
|||||||
|
|
||||||
archives:
|
archives:
|
||||||
- id: archive
|
- id: archive
|
||||||
builds:
|
ids:
|
||||||
- git-get
|
- git-get
|
||||||
- git-list
|
- git-list
|
||||||
replacements:
|
name_template: "git-get_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
||||||
darwin: macOS
|
formats:
|
||||||
linux: linux
|
- tar.gz
|
||||||
windows: windows
|
- zip
|
||||||
386: i386
|
|
||||||
amd64: x86_64
|
|
||||||
format_overrides:
|
format_overrides:
|
||||||
- goos: windows
|
- goos: windows
|
||||||
format: zip
|
formats: [zip]
|
||||||
# Don't include any additional files into the archives (such as README, CHANGELOG etc).
|
# Don't include any additional files into the archives (such as README, CHANGELOG etc).
|
||||||
files:
|
files:
|
||||||
- none*
|
- none*
|
||||||
@@ -50,23 +50,30 @@ checksum:
|
|||||||
name_template: 'checksums.txt'
|
name_template: 'checksums.txt'
|
||||||
|
|
||||||
changelog:
|
changelog:
|
||||||
skip: true
|
sort: asc
|
||||||
|
use: github
|
||||||
|
filters:
|
||||||
|
exclude:
|
||||||
|
- '^docs:'
|
||||||
|
- '^test:'
|
||||||
|
- '^chore'
|
||||||
|
- typo
|
||||||
|
|
||||||
release:
|
release:
|
||||||
github:
|
github:
|
||||||
owner: grdl
|
owner: grdl
|
||||||
name: git-get
|
name: git-get
|
||||||
|
|
||||||
|
|
||||||
brews:
|
brews:
|
||||||
- name: git-get
|
- name: git-get
|
||||||
tap:
|
repository:
|
||||||
owner: grdl
|
owner: grdl
|
||||||
name: homebrew-tap
|
name: homebrew-tap
|
||||||
|
branch: main
|
||||||
commit_author:
|
commit_author:
|
||||||
name: Grzegorz Dlugoszewski
|
name: Grzegorz Dlugoszewski
|
||||||
email: git-get@grdl.dev
|
email: git-get@grdl.dev
|
||||||
folder: Formula
|
directory: Formula
|
||||||
homepage: https://github.com/grdl/git-get/
|
homepage: https://github.com/grdl/git-get/
|
||||||
description: Better way to clone, organize and manage multiple git repositories
|
description: Better way to clone, organize and manage multiple git repositories
|
||||||
test: |
|
test: |
|
||||||
@@ -75,8 +82,10 @@ brews:
|
|||||||
bin.install "git-get", "git-list"
|
bin.install "git-get", "git-list"
|
||||||
|
|
||||||
nfpms:
|
nfpms:
|
||||||
- license: MIT
|
- id: packages
|
||||||
maintainer: grdl
|
package_name: git-get
|
||||||
|
license: MIT
|
||||||
|
maintainer: Grzegorz Dlugoszewski <git-get@grdl.dev>
|
||||||
homepage: https://github.com/grdl/git-get
|
homepage: https://github.com/grdl/git-get
|
||||||
bindir: /usr/local/bin
|
bindir: /usr/local/bin
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -85,3 +94,19 @@ nfpms:
|
|||||||
formats:
|
formats:
|
||||||
- deb
|
- deb
|
||||||
- rpm
|
- rpm
|
||||||
|
|
||||||
|
scoops:
|
||||||
|
- name: git-get
|
||||||
|
repository:
|
||||||
|
owner: grdl
|
||||||
|
name: git-get
|
||||||
|
branch: master
|
||||||
|
directory: bucket
|
||||||
|
url_template: "https://github.com/grdl/git-get/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
|
||||||
|
commit_author:
|
||||||
|
name: Grzegorz Dlugoszewski
|
||||||
|
email: git-get@grdl.dev
|
||||||
|
commit_msg_template: "Scoop update for {{ .ProjectName }} version {{ .Tag }}"
|
||||||
|
homepage: "https://github.com/grdl/git-get"
|
||||||
|
description: "Better way to clone, organize and manage multiple git repositories"
|
||||||
|
license: MIT
|
||||||
|
|||||||
34
README.md
34
README.md
@@ -88,7 +88,8 @@ brew install grdl/tap/git-get
|
|||||||
|
|
||||||
**Option 2: Using Scoop**
|
**Option 2: Using Scoop**
|
||||||
```powershell
|
```powershell
|
||||||
# Coming soon
|
scoop bucket add git-get https://github.com/grdl/git-get
|
||||||
|
scoop install git-get
|
||||||
```
|
```
|
||||||
|
|
||||||
### Building from Source
|
### Building from Source
|
||||||
@@ -269,6 +270,24 @@ go tool cover -html=coverage.out
|
|||||||
go test -v ./pkg/git
|
go test -v ./pkg/git
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Linting
|
||||||
|
|
||||||
|
This project uses comprehensive linting with golangci-lint. The linting configuration includes 25+ linters for code quality, security, and style checking.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install golangci-lint (if not already installed)
|
||||||
|
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||||
|
|
||||||
|
# Run linting with the project's configuration
|
||||||
|
golangci-lint run
|
||||||
|
|
||||||
|
# Run with verbose output
|
||||||
|
golangci-lint run -v
|
||||||
|
|
||||||
|
# Fix auto-fixable issues
|
||||||
|
golangci-lint run --fix
|
||||||
|
```
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### Common Issues
|
### Common Issues
|
||||||
@@ -317,12 +336,13 @@ We welcome contributions!
|
|||||||
1. **Fork the repository**
|
1. **Fork the repository**
|
||||||
2. **Create a feature branch**: `git checkout -b feature/amazing-feature`
|
2. **Create a feature branch**: `git checkout -b feature/amazing-feature`
|
||||||
3. **Install dependencies**: `go mod download`
|
3. **Install dependencies**: `go mod download`
|
||||||
3. **Make changes and add tests**
|
4. **Make changes and add tests**
|
||||||
4. **Run tests**: `go test ./...`
|
5. **Format**: `go fmt ./...`
|
||||||
5. **Run linter**: `golangci-lint run`
|
6. **Run tests**: `go test ./...`
|
||||||
6. **Commit changes**: `git commit -m 'Add amazing feature'`
|
7. **Run linter**: `golangci-lint run`
|
||||||
7. **Push to branch**: `git push origin feature/amazing-feature`
|
8. **Commit changes**: `git commit -m 'Add amazing feature'`
|
||||||
8. **Open a Pull Request**
|
9. **Push to branch**: `git push origin feature/amazing-feature`
|
||||||
|
10. **Open a Pull Request**
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package git
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"git-get/pkg/git/test"
|
"git-get/pkg/git/test"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -57,7 +58,7 @@ func TestExists(t *testing.T) {
|
|||||||
want: errDirNotExist,
|
want: errDirNotExist,
|
||||||
}, {
|
}, {
|
||||||
name: "dir exists",
|
name: "dir exists",
|
||||||
path: "/tmp/",
|
path: os.TempDir(),
|
||||||
want: nil,
|
want: nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
dotgit = ".git"
|
dotgit = ".git"
|
||||||
untracked = "??" // Untracked files are marked as "??" in git status output.
|
untracked = "??" // Untracked files are marked as "??" in git status output.
|
||||||
master = "master"
|
main = "main"
|
||||||
head = "HEAD"
|
head = "HEAD"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -108,9 +108,20 @@ func (r *Repo) Untracked() (int, error) {
|
|||||||
|
|
||||||
// CurrentBranch returns the short name currently checked-out branch for the Repository.
|
// CurrentBranch returns the short name currently checked-out branch for the Repository.
|
||||||
// If Repo is in a detached head state, it will return "HEAD".
|
// If Repo is in a detached head state, it will return "HEAD".
|
||||||
|
// If the repository has no commits yet, it returns "main" (for new repositories).
|
||||||
func (r *Repo) CurrentBranch() (string, error) {
|
func (r *Repo) CurrentBranch() (string, error) {
|
||||||
out, err := run.Git("rev-parse", "--symbolic-full-name", "--abbrev-ref", "HEAD").OnRepo(r.path).AndCaptureLine()
|
out, err := run.Git("rev-parse", "--symbolic-full-name", "--abbrev-ref", "HEAD").OnRepo(r.path).AndCaptureLine()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// Check if this is a new repository without commits
|
||||||
|
if strings.Contains(err.Error(), "ambiguous argument 'HEAD'") {
|
||||||
|
// Try to get the default branch name from git config
|
||||||
|
defaultBranch, branchErr := run.Git("config", "--get", "init.defaultBranch").OnRepo(r.path).AndCaptureLine()
|
||||||
|
if branchErr == nil && defaultBranch != "" {
|
||||||
|
return defaultBranch, nil
|
||||||
|
}
|
||||||
|
// Fall back to "main" as the modern default
|
||||||
|
return "main", nil
|
||||||
|
}
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,6 +186,10 @@ func (r *Repo) Remote() (string, error) {
|
|||||||
// https://stackoverflow.com/a/16880000/1085632
|
// https://stackoverflow.com/a/16880000/1085632
|
||||||
out, err := run.Git("ls-remote", "--get-url").OnRepo(r.path).AndCaptureLine()
|
out, err := run.Git("ls-remote", "--get-url").OnRepo(r.path).AndCaptureLine()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// Check if this is a repository without any remotes configured
|
||||||
|
if strings.Contains(err.Error(), "No remote configured to list refs from") {
|
||||||
|
return "", nil // Return empty string instead of error for missing remote
|
||||||
|
}
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,16 +114,15 @@ func TestCurrentBranch(t *testing.T) {
|
|||||||
repoMaker func(*testing.T) *test.Repo
|
repoMaker func(*testing.T) *test.Repo
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
// TODO: maybe add wantErr to check if error is returned correctly?
|
|
||||||
// {
|
|
||||||
// name: "empty",
|
|
||||||
// repoMaker: newTestRepo,
|
|
||||||
// want: "",
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
name: "only master branch",
|
name: "empty repo without commits",
|
||||||
|
repoMaker: test.RepoEmpty,
|
||||||
|
want: "main",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "only main branch",
|
||||||
repoMaker: test.RepoWithCommit,
|
repoMaker: test.RepoWithCommit,
|
||||||
want: master,
|
want: main,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "checked out new branch",
|
name: "checked out new branch",
|
||||||
@@ -164,19 +163,19 @@ func TestBranches(t *testing.T) {
|
|||||||
want: []string{""},
|
want: []string{""},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "only master branch",
|
name: "only main branch",
|
||||||
repoMaker: test.RepoWithCommit,
|
repoMaker: test.RepoWithCommit,
|
||||||
want: []string{"master"},
|
want: []string{"main"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "new branch",
|
name: "new branch",
|
||||||
repoMaker: test.RepoWithBranch,
|
repoMaker: test.RepoWithBranch,
|
||||||
want: []string{"feature/branch", "master"},
|
want: []string{"feature/branch", "main"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "checked out new tag",
|
name: "checked out new tag",
|
||||||
repoMaker: test.RepoWithTag,
|
repoMaker: test.RepoWithTag,
|
||||||
want: []string{"master"},
|
want: []string{"main"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,7 +204,7 @@ func TestUpstream(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "empty",
|
name: "empty",
|
||||||
repoMaker: test.RepoEmpty,
|
repoMaker: test.RepoEmpty,
|
||||||
branch: "master",
|
branch: "main",
|
||||||
want: "",
|
want: "",
|
||||||
},
|
},
|
||||||
// TODO: add wantErr
|
// TODO: add wantErr
|
||||||
@@ -216,10 +215,10 @@ func TestUpstream(t *testing.T) {
|
|||||||
want: "",
|
want: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "master with upstream",
|
name: "main with upstream",
|
||||||
repoMaker: test.RepoWithBranchWithUpstream,
|
repoMaker: test.RepoWithBranchWithUpstream,
|
||||||
branch: "master",
|
branch: "main",
|
||||||
want: "origin/master",
|
want: "origin/main",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "branch with upstream",
|
name: "branch with upstream",
|
||||||
@@ -261,7 +260,7 @@ func TestAheadBehind(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "fresh clone",
|
name: "fresh clone",
|
||||||
repoMaker: test.RepoWithBranchWithUpstream,
|
repoMaker: test.RepoWithBranchWithUpstream,
|
||||||
branch: "master",
|
branch: "main",
|
||||||
want: []int{0, 0},
|
want: []int{0, 0},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -359,6 +358,57 @@ func TestCleanupFailedClone(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRemote(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
repoMaker func(*testing.T) *test.Repo
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty repo without remote",
|
||||||
|
repoMaker: test.RepoEmpty,
|
||||||
|
want: "",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "repo with commit but no remote",
|
||||||
|
repoMaker: test.RepoWithCommit,
|
||||||
|
want: "",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "repo with upstream",
|
||||||
|
repoMaker: test.RepoWithBranchWithUpstream,
|
||||||
|
want: "", // This will contain the actual remote URL but we just test it doesn't error
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
r, _ := Open(test.repoMaker(t).Path())
|
||||||
|
got, err := r.Remote()
|
||||||
|
|
||||||
|
if test.wantErr && err == nil {
|
||||||
|
t.Errorf("expected error but got none")
|
||||||
|
}
|
||||||
|
if !test.wantErr && err != nil {
|
||||||
|
t.Errorf("unexpected error: %q", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For repos with remote, just check no error occurred
|
||||||
|
if test.name == "repo with upstream" {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error for repo with remote: %q", err)
|
||||||
|
}
|
||||||
|
} else if got != test.want {
|
||||||
|
t.Errorf("expected %q; got %q", test.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func createTestDirTree(t *testing.T) string {
|
func createTestDirTree(t *testing.T) string {
|
||||||
root := test.TempDir(t, "")
|
root := test.TempDir(t, "")
|
||||||
err := os.MkdirAll(filepath.Join(root, "a", "b", "c"), os.ModePerm)
|
err := os.MkdirAll(filepath.Join(root, "a", "b", "c"), os.ModePerm)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,17 +18,14 @@ func TempDir(t *testing.T, parent string) string {
|
|||||||
|
|
||||||
// Automatically remove temp dir when the test is over.
|
// Automatically remove temp dir when the test is over.
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
err := os.RemoveAll(dir)
|
removeTestDir(t, dir)
|
||||||
if err != nil {
|
|
||||||
t.Errorf("failed removing test repo %s", dir)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return dir
|
return dir
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Repo) init() {
|
func (r *Repo) init() {
|
||||||
err := run.Git("init", "--quiet", r.path).AndShutUp()
|
err := run.Git("init", "--quiet", "--initial-branch=main", r.path).AndShutUp()
|
||||||
checkFatal(r.t, err)
|
checkFatal(r.t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,3 +90,17 @@ func checkFatal(t *testing.T, err error) {
|
|||||||
t.Fatalf("failed making test repo: %+v", err)
|
t.Fatalf("failed making test repo: %+v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// removeTestDir removes a test directory
|
||||||
|
func removeTestDir(t *testing.T, dir string) {
|
||||||
|
// Skip cleanup on Windows to avoid file locking issues in CI
|
||||||
|
// The CI runner environment is destroyed after tests anyway
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := os.RemoveAll(dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("warning: failed removing test repo %s: %v", dir, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,14 +16,14 @@ import (
|
|||||||
//
|
//
|
||||||
// Examples of different compositions:
|
// Examples of different compositions:
|
||||||
//
|
//
|
||||||
// - run.Git("clone", <URL>).AndShow()
|
// - run.Git("clone", <URL>).AndShow()
|
||||||
// means running "git clone <URL>" and printing the progress into stdout
|
// means running "git clone <URL>" and printing the progress into stdout
|
||||||
//
|
//
|
||||||
// - run.Git("branch","-a").OnRepo(<REPO>).AndCaptureLines()
|
// - run.Git("branch","-a").OnRepo(<REPO>).AndCaptureLines()
|
||||||
// means running "git branch -a" inside <REPO> and returning a slice of branch names
|
// means running "git branch -a" inside <REPO> and returning a slice of branch names
|
||||||
//
|
//
|
||||||
// - run.Git("pull").OnRepo(<REPO>).AndShutUp()
|
// - run.Git("pull").OnRepo(<REPO>).AndShutUp()
|
||||||
// means running "git pull" inside <REPO> and not printing any output
|
// means running "git pull" inside <REPO> and not printing any output
|
||||||
type Cmd struct {
|
type Cmd struct {
|
||||||
cmd *exec.Cmd
|
cmd *exec.Cmd
|
||||||
args string
|
args string
|
||||||
|
|||||||
20
pkg/url.go
20
pkg/url.go
@@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
urlpkg "net/url"
|
urlpkg "net/url"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -70,7 +69,7 @@ func ParseURL(rawURL string, defaultHost string, defaultScheme string) (url *url
|
|||||||
return url, nil
|
return url, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// URLToPath cleans up the URL and converts it into a path string with correct separators for the current OS.
|
// URLToPath cleans up the URL and converts it into a path string.
|
||||||
// Eg, ssh://git@github.com:22/~user/repo.git => github.com/user/repo
|
// Eg, ssh://git@github.com:22/~user/repo.git => github.com/user/repo
|
||||||
//
|
//
|
||||||
// If skipHost is true, it removes the host part from the path.
|
// If skipHost is true, it removes the host part from the path.
|
||||||
@@ -82,18 +81,23 @@ func URLToPath(url urlpkg.URL, skipHost bool) string {
|
|||||||
// Remove tilde (~) char from username.
|
// Remove tilde (~) char from username.
|
||||||
url.Path = strings.ReplaceAll(url.Path, "~", "")
|
url.Path = strings.ReplaceAll(url.Path, "~", "")
|
||||||
|
|
||||||
// Remove leading and trailing slashes (correct separator is added by the filepath.Join() below).
|
// Remove leading and trailing slashes.
|
||||||
url.Path = strings.Trim(url.Path, "/")
|
url.Path = strings.Trim(url.Path, "/")
|
||||||
|
|
||||||
// Remove trailing ".git" from repo name.
|
// Remove trailing ".git" from repo name.
|
||||||
url.Path = strings.TrimSuffix(url.Path, ".git")
|
url.Path = strings.TrimSuffix(url.Path, ".git")
|
||||||
|
|
||||||
// Replace slashes with separator correct for the current OS.
|
|
||||||
url.Path = strings.ReplaceAll(url.Path, "/", string(filepath.Separator))
|
|
||||||
|
|
||||||
if skipHost {
|
if skipHost {
|
||||||
url.Host = ""
|
return url.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
return filepath.Join(url.Host, url.Path)
|
if url.Host == "" {
|
||||||
|
return url.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
if url.Path == "" {
|
||||||
|
return url.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.Host + "/" + url.Path
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user