mirror of
https://github.com/grdl/git-get.git
synced 2026-02-04 19:09:45 +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:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
security-events: write
|
||||
id-token: write # For SLSA provenance
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
validate:
|
||||
name: Validate Release
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
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 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
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
version: latest
|
||||
args: release --rm-dist
|
||||
args: release --clean
|
||||
env:
|
||||
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:
|
||||
hooks:
|
||||
- go mod download
|
||||
@@ -30,18 +32,16 @@ builds:
|
||||
|
||||
archives:
|
||||
- id: archive
|
||||
builds:
|
||||
ids:
|
||||
- git-get
|
||||
- git-list
|
||||
replacements:
|
||||
darwin: macOS
|
||||
linux: linux
|
||||
windows: windows
|
||||
386: i386
|
||||
amd64: x86_64
|
||||
name_template: "git-get_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
||||
formats:
|
||||
- tar.gz
|
||||
- zip
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
formats: [zip]
|
||||
# Don't include any additional files into the archives (such as README, CHANGELOG etc).
|
||||
files:
|
||||
- none*
|
||||
@@ -50,23 +50,30 @@ checksum:
|
||||
name_template: 'checksums.txt'
|
||||
|
||||
changelog:
|
||||
skip: true
|
||||
sort: asc
|
||||
use: github
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
- '^chore'
|
||||
- typo
|
||||
|
||||
release:
|
||||
github:
|
||||
owner: grdl
|
||||
name: git-get
|
||||
|
||||
|
||||
brews:
|
||||
- name: git-get
|
||||
tap:
|
||||
repository:
|
||||
owner: grdl
|
||||
name: homebrew-tap
|
||||
branch: main
|
||||
commit_author:
|
||||
name: Grzegorz Dlugoszewski
|
||||
email: git-get@grdl.dev
|
||||
folder: Formula
|
||||
directory: Formula
|
||||
homepage: https://github.com/grdl/git-get/
|
||||
description: Better way to clone, organize and manage multiple git repositories
|
||||
test: |
|
||||
@@ -75,8 +82,10 @@ brews:
|
||||
bin.install "git-get", "git-list"
|
||||
|
||||
nfpms:
|
||||
- license: MIT
|
||||
maintainer: grdl
|
||||
- id: packages
|
||||
package_name: git-get
|
||||
license: MIT
|
||||
maintainer: Grzegorz Dlugoszewski <git-get@grdl.dev>
|
||||
homepage: https://github.com/grdl/git-get
|
||||
bindir: /usr/local/bin
|
||||
dependencies:
|
||||
@@ -85,3 +94,19 @@ nfpms:
|
||||
formats:
|
||||
- deb
|
||||
- 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**
|
||||
```powershell
|
||||
# Coming soon
|
||||
scoop bucket add git-get https://github.com/grdl/git-get
|
||||
scoop install git-get
|
||||
```
|
||||
|
||||
### Building from Source
|
||||
@@ -269,6 +270,24 @@ go tool cover -html=coverage.out
|
||||
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
|
||||
|
||||
### Common Issues
|
||||
@@ -317,12 +336,13 @@ We welcome contributions!
|
||||
1. **Fork the repository**
|
||||
2. **Create a feature branch**: `git checkout -b feature/amazing-feature`
|
||||
3. **Install dependencies**: `go mod download`
|
||||
3. **Make changes and add tests**
|
||||
4. **Run tests**: `go test ./...`
|
||||
5. **Run linter**: `golangci-lint run`
|
||||
6. **Commit changes**: `git commit -m 'Add amazing feature'`
|
||||
7. **Push to branch**: `git push origin feature/amazing-feature`
|
||||
8. **Open a Pull Request**
|
||||
4. **Make changes and add tests**
|
||||
5. **Format**: `go fmt ./...`
|
||||
6. **Run tests**: `go test ./...`
|
||||
7. **Run linter**: `golangci-lint run`
|
||||
8. **Commit changes**: `git commit -m 'Add amazing feature'`
|
||||
9. **Push to branch**: `git push origin feature/amazing-feature`
|
||||
10. **Open a Pull Request**
|
||||
|
||||
|
||||
## License
|
||||
|
||||
@@ -3,6 +3,7 @@ package git
|
||||
import (
|
||||
"errors"
|
||||
"git-get/pkg/git/test"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -57,7 +58,7 @@ func TestExists(t *testing.T) {
|
||||
want: errDirNotExist,
|
||||
}, {
|
||||
name: "dir exists",
|
||||
path: "/tmp/",
|
||||
path: os.TempDir(),
|
||||
want: nil,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
const (
|
||||
dotgit = ".git"
|
||||
untracked = "??" // Untracked files are marked as "??" in git status output.
|
||||
master = "master"
|
||||
main = "main"
|
||||
head = "HEAD"
|
||||
)
|
||||
|
||||
@@ -108,9 +108,20 @@ func (r *Repo) Untracked() (int, error) {
|
||||
|
||||
// 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 the repository has no commits yet, it returns "main" (for new repositories).
|
||||
func (r *Repo) CurrentBranch() (string, error) {
|
||||
out, err := run.Git("rev-parse", "--symbolic-full-name", "--abbrev-ref", "HEAD").OnRepo(r.path).AndCaptureLine()
|
||||
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
|
||||
}
|
||||
|
||||
@@ -175,6 +186,10 @@ func (r *Repo) Remote() (string, error) {
|
||||
// https://stackoverflow.com/a/16880000/1085632
|
||||
out, err := run.Git("ls-remote", "--get-url").OnRepo(r.path).AndCaptureLine()
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -114,16 +114,15 @@ func TestCurrentBranch(t *testing.T) {
|
||||
repoMaker func(*testing.T) *test.Repo
|
||||
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,
|
||||
want: master,
|
||||
want: main,
|
||||
},
|
||||
{
|
||||
name: "checked out new branch",
|
||||
@@ -164,19 +163,19 @@ func TestBranches(t *testing.T) {
|
||||
want: []string{""},
|
||||
},
|
||||
{
|
||||
name: "only master branch",
|
||||
name: "only main branch",
|
||||
repoMaker: test.RepoWithCommit,
|
||||
want: []string{"master"},
|
||||
want: []string{"main"},
|
||||
},
|
||||
{
|
||||
name: "new branch",
|
||||
repoMaker: test.RepoWithBranch,
|
||||
want: []string{"feature/branch", "master"},
|
||||
want: []string{"feature/branch", "main"},
|
||||
},
|
||||
{
|
||||
name: "checked out new tag",
|
||||
repoMaker: test.RepoWithTag,
|
||||
want: []string{"master"},
|
||||
want: []string{"main"},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -205,7 +204,7 @@ func TestUpstream(t *testing.T) {
|
||||
{
|
||||
name: "empty",
|
||||
repoMaker: test.RepoEmpty,
|
||||
branch: "master",
|
||||
branch: "main",
|
||||
want: "",
|
||||
},
|
||||
// TODO: add wantErr
|
||||
@@ -216,10 +215,10 @@ func TestUpstream(t *testing.T) {
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "master with upstream",
|
||||
name: "main with upstream",
|
||||
repoMaker: test.RepoWithBranchWithUpstream,
|
||||
branch: "master",
|
||||
want: "origin/master",
|
||||
branch: "main",
|
||||
want: "origin/main",
|
||||
},
|
||||
{
|
||||
name: "branch with upstream",
|
||||
@@ -261,7 +260,7 @@ func TestAheadBehind(t *testing.T) {
|
||||
{
|
||||
name: "fresh clone",
|
||||
repoMaker: test.RepoWithBranchWithUpstream,
|
||||
branch: "master",
|
||||
branch: "main",
|
||||
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 {
|
||||
root := test.TempDir(t, "")
|
||||
err := os.MkdirAll(filepath.Join(root, "a", "b", "c"), os.ModePerm)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -17,17 +18,14 @@ func TempDir(t *testing.T, parent string) string {
|
||||
|
||||
// Automatically remove temp dir when the test is over.
|
||||
t.Cleanup(func() {
|
||||
err := os.RemoveAll(dir)
|
||||
if err != nil {
|
||||
t.Errorf("failed removing test repo %s", dir)
|
||||
}
|
||||
removeTestDir(t, dir)
|
||||
})
|
||||
|
||||
return dir
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -92,3 +90,17 @@ func checkFatal(t *testing.T, err error) {
|
||||
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:
|
||||
//
|
||||
// - run.Git("clone", <URL>).AndShow()
|
||||
// means running "git clone <URL>" and printing the progress into stdout
|
||||
// - run.Git("clone", <URL>).AndShow()
|
||||
// means running "git clone <URL>" and printing the progress into stdout
|
||||
//
|
||||
// - run.Git("branch","-a").OnRepo(<REPO>).AndCaptureLines()
|
||||
// means running "git branch -a" inside <REPO> and returning a slice of branch names
|
||||
// - run.Git("branch","-a").OnRepo(<REPO>).AndCaptureLines()
|
||||
// means running "git branch -a" inside <REPO> and returning a slice of branch names
|
||||
//
|
||||
// - run.Git("pull").OnRepo(<REPO>).AndShutUp()
|
||||
// means running "git pull" inside <REPO> and not printing any output
|
||||
// - run.Git("pull").OnRepo(<REPO>).AndShutUp()
|
||||
// means running "git pull" inside <REPO> and not printing any output
|
||||
type Cmd struct {
|
||||
cmd *exec.Cmd
|
||||
args string
|
||||
|
||||
20
pkg/url.go
20
pkg/url.go
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
urlpkg "net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
@@ -70,7 +69,7 @@ func ParseURL(rawURL string, defaultHost string, defaultScheme string) (url *url
|
||||
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
|
||||
//
|
||||
// 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.
|
||||
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, "/")
|
||||
|
||||
// Remove trailing ".git" from repo name.
|
||||
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 {
|
||||
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