Skip to content

Scan starting at arbitrary roots #110

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Aug 22, 2023
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
64 changes: 51 additions & 13 deletions git-sizer.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"context"
"encoding/json"
"errors"
"fmt"
Expand All @@ -19,7 +20,9 @@ import (
"github.com/github/git-sizer/sizes"
)

const usage = `usage: git-sizer [OPTS]
const usage = `usage: git-sizer [OPTS] [ROOT...]

Scan objects in your Git repository and emit statistics about them.

--threshold THRESHOLD minimum level of concern (i.e., number of stars)
that should be reported. Default:
Expand All @@ -45,12 +48,29 @@ const usage = `usage: git-sizer [OPTS]
be set via gitconfig: 'sizer.progress'.
--version only report the git-sizer version number

Object selection:

git-sizer traverses through your Git history to find objects to
process. By default, it processes all objects that are reachable from
any reference. You can tell it to process only some of your
references; see "Reference selection" below.

If explicit ROOTs are specified on the command line, each one should
be a string that 'git rev-parse' can convert into a single Git object
ID, like 'main', 'main~:src', or an abbreviated SHA-1. See
git-rev-parse(1) for details. In that case, git-sizer also treats
those objects as starting points for its traversal, and also includes
the Git objects that are reachable from those roots in the analysis.

As a special case, if one or more ROOTs are specified on the command
line but _no_ reference selection options, then _only_ the specified
ROOTs are traversed, and no references.

Reference selection:

By default, git-sizer processes all Git objects that are reachable
from any reference. The following options can be used to limit which
references to process. The last rule matching a reference determines
whether that reference is processed.
The following options can be used to limit which references to
process. The last rule matching a reference determines whether that
reference is processed.

--[no-]branches process [don't process] branches
--[no-]tags process [don't process] tags
Expand Down Expand Up @@ -93,14 +113,16 @@ var ReleaseVersion string
var BuildVersion string

func main() {
err := mainImplementation(os.Stdout, os.Stderr, os.Args[1:])
ctx := context.Background()

err := mainImplementation(ctx, os.Stdout, os.Stderr, os.Args[1:])
if err != nil {
fmt.Fprintf(os.Stderr, "error: %s\n", err)
os.Exit(1)
}
}

func mainImplementation(stdout, stderr io.Writer, args []string) error {
func mainImplementation(ctx context.Context, stdout, stderr io.Writer, args []string) error {
var nameStyle sizes.NameStyle = sizes.NameStyleFull
var cpuprofile string
var jsonOutput bool
Expand Down Expand Up @@ -216,10 +238,6 @@ func mainImplementation(stdout, stderr io.Writer, args []string) error {
return nil
}

if len(flags.Args()) != 0 {
return errors.New("excess arguments")
}

if repoErr != nil {
return fmt.Errorf("couldn't open Git repository: %w", repoErr)
}
Expand Down Expand Up @@ -273,7 +291,7 @@ func mainImplementation(stdout, stderr io.Writer, args []string) error {
progress = v
}

rg, err := rgb.Finish()
rg, err := rgb.Finish(len(flags.Args()) == 0)
if err != nil {
return err
}
Expand All @@ -288,7 +306,27 @@ func mainImplementation(stdout, stderr io.Writer, args []string) error {
progressMeter = meter.NewProgressMeter(stderr, 100*time.Millisecond)
}

historySize, err := sizes.ScanRepositoryUsingGraph(repo, rg, nameStyle, progressMeter)
refRoots, err := sizes.CollectReferences(ctx, repo, rg)
if err != nil {
return fmt.Errorf("determining which reference to scan: %w", err)
}

roots := make([]sizes.Root, 0, len(refRoots)+len(flags.Args()))
for _, refRoot := range refRoots {
roots = append(roots, refRoot)
}

for _, arg := range flags.Args() {
oid, err := repo.ResolveObject(arg)
if err != nil {
return fmt.Errorf("resolving command-line argument %q: %w", arg, err)
}
roots = append(roots, sizes.NewExplicitRoot(arg, oid))
}

historySize, err := sizes.ScanRepositoryUsingGraph(
ctx, repo, roots, nameStyle, progressMeter,
)
if err != nil {
return fmt.Errorf("error scanning repository: %w", err)
}
Expand Down
20 changes: 20 additions & 0 deletions git/obj_resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package git

import (
"bytes"
"fmt"
)

func (repo *Repository) ResolveObject(name string) (OID, error) {
cmd := repo.GitCommand("rev-parse", "--verify", "--end-of-options", name)
output, err := cmd.Output()
if err != nil {
return NullOID, fmt.Errorf("resolving object %q: %w", name, err)
}
oidString := string(bytes.TrimSpace(output))
oid, err := NewOID(oidString)
if err != nil {
return NullOID, fmt.Errorf("parsing output %q from 'rev-parse': %w", oidString, err)
}
return oid, nil
}
16 changes: 12 additions & 4 deletions git/ref_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,23 @@ func (_ allReferencesFilter) Filter(_ string) bool {

var AllReferencesFilter allReferencesFilter

type noReferencesFilter struct{}

func (_ noReferencesFilter) Filter(_ string) bool {
return false
}

var NoReferencesFilter noReferencesFilter

// PrefixFilter returns a `ReferenceFilter` that matches references
// whose names start with the specified `prefix`, which must match at
// a component boundary. For example,
//
// * Prefix "refs/foo" matches "refs/foo" and "refs/foo/bar" but not
// "refs/foobar".
// - Prefix "refs/foo" matches "refs/foo" and "refs/foo/bar" but not
// "refs/foobar".
//
// * Prefix "refs/foo/" matches "refs/foo/bar" but not "refs/foo" or
// "refs/foobar".
// - Prefix "refs/foo/" matches "refs/foo/bar" but not "refs/foo" or
// "refs/foobar".
func PrefixFilter(prefix string) ReferenceFilter {
if prefix == "" {
return AllReferencesFilter
Expand Down
Loading