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

on:
push:
tags:
- 'v*'

permissions:
contents: write

jobs:
test:
name: Test before release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version: '1.22'

- name: Run tests
run: go test -race ./...

- name: Run integration tests
run: go test -tags=integration -race -timeout 5m ./...

lint:
name: Lint before release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version: '1.22'

- name: golangci-lint
uses: golangci/golangci-lint-action@v4
with:
version: latest

release:
name: Release
runs-on: ubuntu-latest
needs: [test, lint]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: actions/setup-go@v5
with:
go-version: '1.22'

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5
with:
distribution: goreleaser
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Uncomment if using Homebrew tap
# HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}

verify:
name: Verify release artifacts
runs-on: ${{ matrix.os }}
needs: release
strategy:
matrix:
include:
- os: ubuntu-latest
artifact: git-los_*_linux_x86_64.tar.gz
- os: macos-latest
artifact: git-los_*_darwin_x86_64.tar.gz

steps:
- name: Download release
uses: robinraju/release-downloader@v1.9
with:
latest: false
tag: ${{ github.ref_name }}
fileName: ${{ matrix.artifact }}
extract: true

- name: Verify binary works
run: |
chmod +x git-los
./git-los version
./git-los --help
126 changes: 126 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
# vim: set ts=2 sw=2 tw=0 fo=cnqoj

project_name: git-los

before:
hooks:
- go mod tidy
- go generate ./...

builds:
- id: git-los
binary: git-los
main: ./cmd/git-los
env:
- CGO_ENABLED=0
goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm64
ldflags:
- -s -w
- -X main.version={{.Version}}
- -X main.commit={{.Commit}}
- -X main.date={{.Date}}

archives:
- id: default
format: tar.gz
# Use zip for Windows
format_overrides:
- goos: windows
format: zip
name_template: >-
{{ .ProjectName }}_
{{- .Version }}_
{{- .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
files:
- README.md
- LICENSE*
- CONTRIBUTING.md
- docs/*

checksum:
name_template: 'checksums.txt'
algorithm: sha256

snapshot:
name_template: "{{ incpatch .Version }}-next"

changelog:
sort: asc
use: github
groups:
- title: Features
regexp: "^.*feat[(\\w)]*:+.*$"
order: 0
- title: Bug fixes
regexp: "^.*fix[(\\w)]*:+.*$"
order: 1
- title: Documentation
regexp: "^.*docs[(\\w)]*:+.*$"
order: 2
- title: Other
order: 999
filters:
exclude:
- '^test:'
- '^chore:'
- Merge pull request
- Merge branch

release:
github:
owner: valent-au
name: git-los
draft: false
prerelease: auto
name_template: "{{.Tag}}"
header: |
## git-los {{.Tag}}

Git LFS custom transfer agent with native GCS and S3 support.

### Installation

Download the appropriate archive for your platform below, extract it, and add the binary to your PATH.

### Quick Start

```bash
# Configure git-los as your LFS transfer agent
git config --global lfs.standalonetransferagent git-los
git config --global lfs.customtransfer.git-los.path git-los
git config --global lfs.customtransfer.git-los.args agent

# Set your backend URL
git config lfs.url "gs://your-bucket/lfs"
# or
git config lfs.url "s3://your-bucket/lfs"
```

footer: |
**Full Changelog**: https://github.com/valent-au/git-los/compare/{{ .PreviousTag }}...{{ .Tag }}

brews:
- name: git-los
repository:
owner: valent-au
name: homebrew-tap
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
directory: Formula
homepage: https://github.com/valent-au/git-los
description: Git LFS custom transfer agent with native GCS and S3 support
license: Apache-2.0
skip_upload: auto
test: |
system "#{bin}/git-los", "version"
install: |
bin.install "git-los"
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,24 @@ git push # LFS objects upload to your bucket via git-los

## Documentation

### User Guides

- [Installation](./docs/installation.md) — Installing and setting up git-los
- [Configuration](./docs/configuration.md) — Backend setup and options
- [Troubleshooting](./docs/troubleshooting.md) — Common issues and solutions

### Example Workflows

- [Game Development](./docs/examples/game-dev.md) — Managing textures, models, and audio
- [ML Datasets](./docs/examples/ml-datasets.md) — Dataset versioning and model management

### Development

- [Contributing](./CONTRIBUTING.md) — How to contribute to git-los
- [Development Guide](./docs/development.md) — Building and testing locally

### Specifications

See the [spec](./spec/README.md) directory for detailed specifications:

- [Architecture](./spec/architecture.md) — System components and their interactions
Expand Down
36 changes: 30 additions & 6 deletions cmd/git-los/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"io"
"log/slog"
"os"
"os/exec"
Expand Down Expand Up @@ -48,6 +49,7 @@ func newDaemonRunCmd() *cobra.Command {
socketPath string
logLevel string
logFormat string
logFile string
)

cmd := &cobra.Command{
Expand All @@ -62,7 +64,7 @@ shut down after an idle period.`,
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return runDaemon(idleTimeout, maxConcurrent, socketPath, logLevel, logFormat)
return runDaemon(idleTimeout, maxConcurrent, socketPath, logLevel, logFormat, logFile)
},
}

Expand All @@ -76,6 +78,8 @@ shut down after an idle period.`,
"Log level (debug, info, warn, error)")
cmd.Flags().StringVar(&logFormat, "log-format", "json",
"Log format (json, text)")
cmd.Flags().StringVar(&logFile, "log-file", "",
"Log file path (enables rotation; default: stderr)")

return cmd
}
Expand All @@ -86,6 +90,7 @@ func newDaemonStartCmd() *cobra.Command {
maxConcurrent int
logLevel string
logFormat string
logFile string
)

cmd := &cobra.Command{
Expand All @@ -98,7 +103,7 @@ func newDaemonStartCmd() *cobra.Command {
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return runDaemonStart(cmd, idleTimeout, maxConcurrent, logLevel, logFormat)
return runDaemonStart(cmd, idleTimeout, maxConcurrent, logLevel, logFormat, logFile)
},
}

Expand All @@ -110,6 +115,8 @@ func newDaemonStartCmd() *cobra.Command {
"Log level (debug, info, warn, error)")
cmd.Flags().StringVar(&logFormat, "log-format", "json",
"Log format (json, text)")
cmd.Flags().StringVar(&logFile, "log-file", "",
"Log file path (enables rotation; default: none)")

return cmd
}
Expand Down Expand Up @@ -138,12 +145,26 @@ func newDaemonStatusCmd() *cobra.Command {
}
}

func runDaemon(idleTimeout time.Duration, maxConcurrent int, socketPath, logLevel, logFormat string) error {
func runDaemon(idleTimeout time.Duration, maxConcurrent int, socketPath, logLevel, logFormat, logFile string) error {
// Determine log output destination
var logOutput io.Writer = os.Stderr
var rotatingWriter *logging.RotatingWriter

if logFile != "" {
rw, err := logging.NewRotatingWriter(logging.DefaultRotateConfig(logFile))
if err != nil {
return fmt.Errorf("create log file: %w", err)
}
rotatingWriter = rw
logOutput = rw
defer rotatingWriter.Close()
}

// Set up structured logging with credential redaction
logging.SetDefault(logging.Config{
Level: logLevel,
Format: logFormat,
Output: os.Stderr,
Output: logOutput,
})

// Determine socket path
Expand Down Expand Up @@ -205,7 +226,7 @@ func runDaemon(idleTimeout time.Duration, maxConcurrent int, socketPath, logLeve
return nil
}

func runDaemonStart(cmd *cobra.Command, idleTimeout time.Duration, maxConcurrent int, logLevel, logFormat string) error {
func runDaemonStart(cmd *cobra.Command, idleTimeout time.Duration, maxConcurrent int, logLevel, logFormat, logFile string) error {
socketPath := daemon.GetSocketPath()

// Check if already running
Expand Down Expand Up @@ -238,6 +259,9 @@ func runDaemonStart(cmd *cobra.Command, idleTimeout time.Duration, maxConcurrent
if logFormat != "json" {
args = append(args, "--log-format", logFormat)
}
if logFile != "" {
args = append(args, "--log-file", logFile)
}

daemonCmd := exec.Command(execPath, args...)
daemonCmd.Stdin = nil
Expand Down Expand Up @@ -308,7 +332,7 @@ func runDaemonRestart(cmd *cobra.Command, args []string) error {
time.Sleep(100 * time.Millisecond)

// Start with default log settings
return runDaemonStart(cmd, daemon.DefaultIdleTimeout, daemon.DefaultMaxConcurrentTransfers, "info", "json")
return runDaemonStart(cmd, daemon.DefaultIdleTimeout, daemon.DefaultMaxConcurrentTransfers, "info", "json", "")
}

func runDaemonStatus(cmd *cobra.Command, args []string) error {
Expand Down
Loading
Loading