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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ brief crate:serde
brief pypi:requests
```

Remote sources are shallow-cloned by default. Use `--depth 0` for a full clone, `--keep` to preserve the clone, or `--dir ./somewhere` to clone into a specific directory.
Remote sources are shallow-cloned by default. Use `--depth 0` for a full clone, `--keep` to preserve the clone, or `--dir ./somewhere` to clone into a specific directory. Use `--cache ./cache` to keep one shallow checkout per HTTPS URL and reuse it across runs. Cache mode cannot be combined with `--depth 0` or `--dir`.

JSON when piped, human-readable on a TTY. Force either with `--json` or `--human`. Use `--category test` to filter to a single category.

Expand Down
2 changes: 2 additions & 0 deletions cmd/brief/enrich.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func cmdEnrich(args []string) {
keep := fs.Bool("keep", false, "Keep downloaded remote source")
depth := fs.Int("depth", -1, "Git clone depth (0 = full clone, default shallow)")
dir := fs.String("dir", "", "Directory to clone remote source into")
cache := fs.String("cache", "", "Persistent shallow cache for HTTPS remotes (incompatible with -depth 0 and -dir)")
scanDepth := fs.Int("scan-depth", 0, "Max directory depth for language detection (0 = unlimited)")
skip := fs.String("skip", "", "Additional directories to skip, comma-separated")
_ = fs.Parse(args)
Expand All @@ -43,6 +44,7 @@ func cmdEnrich(args []string) {
Keep: *keep,
Depth: *depth,
Dir: *dir,
Cache: *cache,
})
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v\n", err)
Expand Down
2 changes: 2 additions & 0 deletions cmd/brief/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ func cmdScan(args []string) {
keep := fs.Bool("keep", false, "Keep downloaded remote source")
depth := fs.Int("depth", -1, "Git clone depth (0 = full clone, default shallow)")
dir := fs.String("dir", "", "Directory to clone remote source into")
cache := fs.String("cache", "", "Persistent shallow cache for HTTPS remotes (incompatible with -depth 0 and -dir)")
scanDepth := fs.Int("scan-depth", 0, "Max directory depth for language detection (0 = unlimited)")
skip := fs.String("skip", "", "Additional directories to skip, comma-separated")
tracked := fs.Bool("tracked", false, "Only consider files tracked by git")
Expand Down Expand Up @@ -104,6 +105,7 @@ func cmdScan(args []string) {
Keep: *keep,
Depth: *depth,
Dir: *dir,
Cache: *cache,
})
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v\n", err)
Expand Down
2 changes: 2 additions & 0 deletions cmd/brief/outline.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func cmdOutline(args []string) {
keep := fs.Bool("keep", false, "Keep downloaded remote source")
depth := fs.Int("depth", -1, "Git clone depth (0 = full clone, default shallow)")
dir := fs.String("dir", "", "Directory to clone remote source into")
cache := fs.String("cache", "", "Persistent shallow cache for HTTPS remotes (incompatible with -depth 0 and -dir)")
_ = fs.Parse(args)

path := "."
Expand All @@ -40,6 +41,7 @@ func cmdOutline(args []string) {
Keep: *keep,
Depth: *depth,
Dir: *dir,
Cache: *cache,
})
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v\n", err)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ toolchain go1.26.6
require (
github.com/BurntSushi/toml v1.6.0
github.com/git-pkgs/archives v0.5.0
github.com/git-pkgs/clone v0.5.0
github.com/git-pkgs/enrichment v0.6.5
github.com/git-pkgs/forge v0.8.0
github.com/git-pkgs/licensecheck v0.4.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ github.com/ecosyste-ms/ecosystems-go v0.4.0 h1:5A+zF+XWT8sYYYjlc2/tI1SmiDGzbHLyT
github.com/ecosyste-ms/ecosystems-go v0.4.0/go.mod h1:FVswCrp3DQkur1HjVqfDF/gYrDSEmiFflntcB1G0DbA=
github.com/git-pkgs/archives v0.5.0 h1:QdowC1jTOSbEOKTYGqVt8ZjIr+yJzy7cmLNWcLPj9Y8=
github.com/git-pkgs/archives v0.5.0/go.mod h1:tfio0OIuPKEBKHs/UCL5XBUvYmKpnvtnba2iDlfSd6g=
github.com/git-pkgs/clone v0.5.0 h1:0cYpr8PQpylqtvA9v1NBE9JXJeLLlrspJbvl2JYVLxU=
github.com/git-pkgs/clone v0.5.0/go.mod h1:nixjE44maKRXbu/dEiunJmHApWgLYp0IR7J2yWaYHv8=
github.com/git-pkgs/enrichment v0.6.5 h1:U0SPzWVGoK4R8TwojCTASBRTEV+QSs0IitdLmzI/g/k=
github.com/git-pkgs/enrichment v0.6.5/go.mod h1:Vt2PLMvWPOio9DLyC8Gdhh1yxsHwcRcG+L2Kkc9+kak=
github.com/git-pkgs/forge v0.8.0 h1:rfG6YujMGEN27Sj5Bd2cYIA1XR8tYd0fU5RdsvSunC4=
Expand Down
68 changes: 55 additions & 13 deletions remote/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ package remote

import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"strings"

gitclone "github.com/git-pkgs/clone"
forges "github.com/git-pkgs/forge"
"github.com/git-pkgs/purl"
"github.com/git-pkgs/registries"
Expand All @@ -27,6 +29,7 @@ type Options struct {
Keep bool // don't delete temp dir after scanning
Depth int // git clone depth (0 = full clone, -1 or unset = default shallow)
Dir string // directory to clone into (empty = temp dir)
Cache string // persistent shallow clone cache directory (empty = no cache)
}

// Resolve takes a source string and returns a local directory to scan.
Expand Down Expand Up @@ -134,22 +137,11 @@ func cloneURL(ctx context.Context, url, name string, opts Options) (*Source, err
managed = true
}

args := []string{"clone"}
if opts.Depth > 0 {
args = append(args, "--depth", fmt.Sprintf("%d", opts.Depth))
}
args = append(args, url, dir)

cmd := exec.CommandContext(ctx, "git", args...)
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
if err := cloneInto(ctx, url, dir, opts); err != nil {
if managed {
_ = os.RemoveAll(dir)
}
// git already wrote a (credential-redacted) failure message to
// stderr, so don't repeat the raw URL here.
return nil, fmt.Errorf("git clone failed: %w", err)
return nil, err
}

cleanup := func() {
Expand All @@ -160,3 +152,53 @@ func cloneURL(ctx context.Context, url, name string, opts Options) (*Source, err

return &Source{Dir: dir, Cleanup: cleanup, Origin: url}, nil
}

var ensureClone = func(ctx context.Context, url, dst string, full bool) error {
return gitclone.Ensure(ctx, gitclone.Retry{}, url, dst, "", full)
}

var prepareCloneCache = func(ctx context.Context, root, url, dst string) error {
cache := gitclone.Cache{Root: root}
_, err := cache.Prepare(ctx, url, "", dst)
return err
}

var execClone = func(ctx context.Context, url, dir string, depth int) error {
args := []string{"clone"}
if depth > 0 {
args = append(args, "--depth", fmt.Sprintf("%d", depth))
}
args = append(args, url, dir)

cmd := exec.CommandContext(ctx, "git", args...)
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
// Git already wrote a credential-redacted failure to stderr, so do
// not repeat the raw URL here.
return fmt.Errorf("git clone failed: %w", err)
}
return nil
}

func cloneInto(ctx context.Context, url, dir string, opts Options) error {
if !strings.HasPrefix(url, "https://") {
return execClone(ctx, url, dir, opts.Depth)
}
if opts.Cache != "" {
if opts.Dir != "" {
return errors.New("clone cache cannot be combined with an explicit clone directory")
}
if opts.Depth == 0 {
return errors.New("clone cache does not support full clones (depth 0)")
}
if err := prepareCloneCache(ctx, opts.Cache, url, dir); err != nil {
return fmt.Errorf("preparing clone cache: %w", err)
}
return nil
}
if err := ensureClone(ctx, url, dir, opts.Depth == 0); err != nil {
return fmt.Errorf("cloning remote: %w", err)
}
return nil
}
Loading