Skip to content

Commit 9e8b14f

Browse files
committed
Allow arbitrary reachability roots to be fed in
Instead of only traversing objects starting at references, allow the user to specify explicit Git objects via the command line. In that case, the traversal includes objects reachable from those objects.
1 parent 897baa1 commit 9e8b14f

9 files changed

+290
-104
lines changed

git-sizer.go

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import (
2020
"github.com/github/git-sizer/sizes"
2121
)
2222

23-
const usage = `usage: git-sizer [OPTS]
23+
const usage = `usage: git-sizer [OPTS] [ROOT...]
24+
25+
Scan objects in your Git repository and emit statistics about them.
2426
2527
--threshold THRESHOLD minimum level of concern (i.e., number of stars)
2628
that should be reported. Default:
@@ -46,12 +48,29 @@ const usage = `usage: git-sizer [OPTS]
4648
be set via gitconfig: 'sizer.progress'.
4749
--version only report the git-sizer version number
4850
51+
Object selection:
52+
53+
git-sizer traverses through your Git history to find objects to
54+
process. By default, it processes all objects that are reachable from
55+
any reference. You can tell it to process only some of your
56+
references; see "Reference selection" below.
57+
58+
If explicit ROOTs are specified on the command line, each one should
59+
be a string that 'git rev-parse' can convert into a single Git object
60+
ID, like 'main', 'main~:src', or an abbreviated SHA-1. See
61+
git-rev-parse(1) for details. In that case, git-sizer also treats
62+
those objects as starting points for its traversal, and also includes
63+
the Git objects that are reachable from those roots in the analysis.
64+
65+
As a special case, if one or more ROOTs are specified on the command
66+
line but _no_ reference selection options, then _only_ the specified
67+
ROOTs are traversed, and no references.
68+
4969
Reference selection:
5070
51-
By default, git-sizer processes all Git objects that are reachable
52-
from any reference. The following options can be used to limit which
53-
references to process. The last rule matching a reference determines
54-
whether that reference is processed.
71+
The following options can be used to limit which references to
72+
process. The last rule matching a reference determines whether that
73+
reference is processed.
5574
5675
--[no-]branches process [don't process] branches
5776
--[no-]tags process [don't process] tags
@@ -220,10 +239,6 @@ func mainImplementation(ctx context.Context, stdout, stderr io.Writer, args []st
220239
return nil
221240
}
222241

223-
if len(flags.Args()) != 0 {
224-
return errors.New("excess arguments")
225-
}
226-
227242
if repoErr != nil {
228243
return fmt.Errorf("couldn't open Git repository: %w", repoErr)
229244
}
@@ -277,7 +292,7 @@ func mainImplementation(ctx context.Context, stdout, stderr io.Writer, args []st
277292
progress = v
278293
}
279294

280-
rg, err := rgb.Finish()
295+
rg, err := rgb.Finish(len(flags.Args()) == 0)
281296
if err != nil {
282297
return err
283298
}
@@ -297,8 +312,21 @@ func mainImplementation(ctx context.Context, stdout, stderr io.Writer, args []st
297312
return fmt.Errorf("determining which reference to scan: %w", err)
298313
}
299314

315+
roots := make([]sizes.Root, 0, len(refRoots)+len(flags.Args()))
316+
for _, refRoot := range refRoots {
317+
roots = append(roots, refRoot)
318+
}
319+
320+
for _, arg := range flags.Args() {
321+
oid, err := repo.ResolveObject(arg)
322+
if err != nil {
323+
return fmt.Errorf("resolving command-line argument %q: %w", arg, err)
324+
}
325+
roots = append(roots, sizes.NewExplicitRoot(arg, oid))
326+
}
327+
300328
historySize, err := sizes.ScanRepositoryUsingGraph(
301-
ctx, repo, refRoots, nameStyle, progressMeter,
329+
ctx, repo, roots, nameStyle, progressMeter,
302330
)
303331
if err != nil {
304332
return fmt.Errorf("error scanning repository: %w", err)

git/obj_resolver.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package git
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
)
7+
8+
func (repo *Repository) ResolveObject(name string) (OID, error) {
9+
cmd := repo.GitCommand("rev-parse", "--verify", "--end-of-options", name)
10+
output, err := cmd.Output()
11+
if err != nil {
12+
return NullOID, fmt.Errorf("resolving object %q: %w", name, err)
13+
}
14+
oidString := string(bytes.TrimSpace(output))
15+
oid, err := NewOID(oidString)
16+
if err != nil {
17+
return NullOID, fmt.Errorf("parsing output %q from 'rev-parse': %w", oidString, err)
18+
}
19+
return oid, nil
20+
}

git/ref_filter.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,23 @@ func (_ allReferencesFilter) Filter(_ string) bool {
8383

8484
var AllReferencesFilter allReferencesFilter
8585

86+
type noReferencesFilter struct{}
87+
88+
func (_ noReferencesFilter) Filter(_ string) bool {
89+
return false
90+
}
91+
92+
var NoReferencesFilter noReferencesFilter
93+
8694
// PrefixFilter returns a `ReferenceFilter` that matches references
8795
// whose names start with the specified `prefix`, which must match at
8896
// a component boundary. For example,
8997
//
90-
// * Prefix "refs/foo" matches "refs/foo" and "refs/foo/bar" but not
91-
// "refs/foobar".
98+
// - Prefix "refs/foo" matches "refs/foo" and "refs/foo/bar" but not
99+
// "refs/foobar".
92100
//
93-
// * Prefix "refs/foo/" matches "refs/foo/bar" but not "refs/foo" or
94-
// "refs/foobar".
101+
// - Prefix "refs/foo/" matches "refs/foo/bar" but not "refs/foo" or
102+
// "refs/foobar".
95103
func PrefixFilter(prefix string) ReferenceFilter {
96104
if prefix == "" {
97105
return AllReferencesFilter

git_sizer_test.go

Lines changed: 128 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -567,54 +567,112 @@ func TestBomb(t *testing.T) {
567567

568568
repo := testRepo.Repository(t)
569569

570-
refRoots, err := sizes.CollectReferences(ctx, repo, refGrouper{})
571-
require.NoError(t, err)
570+
t.Run("full", func(t *testing.T) {
571+
refRoots, err := sizes.CollectReferences(ctx, repo, refGrouper{})
572+
require.NoError(t, err)
572573

573-
h, err := sizes.ScanRepositoryUsingGraph(
574-
ctx, repo,
575-
refRoots, sizes.NameStyleFull, meter.NoProgressMeter,
576-
)
577-
require.NoError(t, err)
574+
roots := make([]sizes.Root, 0, len(refRoots))
575+
for _, refRoot := range refRoots {
576+
roots = append(roots, refRoot)
577+
}
578+
579+
h, err := sizes.ScanRepositoryUsingGraph(
580+
ctx, repo, roots, sizes.NameStyleFull, meter.NoProgressMeter,
581+
)
582+
require.NoError(t, err)
583+
584+
assert.Equal(t, counts.Count32(1), h.UniqueCommitCount, "unique commit count")
585+
assert.Equal(t, counts.Count64(172), h.UniqueCommitSize, "unique commit size")
586+
assert.Equal(t, counts.Count32(172), h.MaxCommitSize, "max commit size")
587+
assert.Equal(t, "refs/heads/master", h.MaxCommitSizeCommit.BestPath(), "max commit size commit")
588+
assert.Equal(t, counts.Count32(1), h.MaxHistoryDepth, "max history depth")
589+
assert.Equal(t, counts.Count32(0), h.MaxParentCount, "max parent count")
590+
assert.Equal(t, "refs/heads/master", h.MaxParentCountCommit.BestPath(), "max parent count commit")
591+
592+
assert.Equal(t, counts.Count32(10), h.UniqueTreeCount, "unique tree count")
593+
assert.Equal(t, counts.Count64(2910), h.UniqueTreeSize, "unique tree size")
594+
assert.Equal(t, counts.Count64(100), h.UniqueTreeEntries, "unique tree entries")
595+
assert.Equal(t, counts.Count32(10), h.MaxTreeEntries, "max tree entries")
596+
assert.Equal(t, "refs/heads/master:d0/d0/d0/d0/d0/d0/d0/d0/d0", h.MaxTreeEntriesTree.BestPath(), "max tree entries tree")
597+
598+
assert.Equal(t, counts.Count32(1), h.UniqueBlobCount, "unique blob count")
599+
assert.Equal(t, counts.Count64(6), h.UniqueBlobSize, "unique blob size")
600+
assert.Equal(t, counts.Count32(6), h.MaxBlobSize, "max blob size")
601+
assert.Equal(t, "refs/heads/master:d0/d0/d0/d0/d0/d0/d0/d0/d0/f0", h.MaxBlobSizeBlob.BestPath(), "max blob size blob")
602+
603+
assert.Equal(t, counts.Count32(0), h.UniqueTagCount, "unique tag count")
604+
assert.Equal(t, counts.Count32(0), h.MaxTagDepth, "max tag depth")
605+
606+
assert.Equal(t, counts.Count32(1), h.ReferenceCount, "reference count")
607+
608+
assert.Equal(t, counts.Count32(10), h.MaxPathDepth, "max path depth")
609+
assert.Equal(t, "refs/heads/master^{tree}", h.MaxPathDepthTree.BestPath(), "max path depth tree")
610+
assert.Equal(t, counts.Count32(29), h.MaxPathLength, "max path length")
611+
assert.Equal(t, "refs/heads/master^{tree}", h.MaxPathLengthTree.BestPath(), "max path length tree")
612+
613+
assert.Equal(t, counts.Count32((pow(10, 10)-1)/(10-1)), h.MaxExpandedTreeCount, "max expanded tree count")
614+
assert.Equal(t, "refs/heads/master^{tree}", h.MaxExpandedTreeCountTree.BestPath(), "max expanded tree count tree")
615+
assert.Equal(t, counts.Count32(0xffffffff), h.MaxExpandedBlobCount, "max expanded blob count")
616+
assert.Equal(t, "refs/heads/master^{tree}", h.MaxExpandedBlobCountTree.BestPath(), "max expanded blob count tree")
617+
assert.Equal(t, counts.Count64(6*pow(10, 10)), h.MaxExpandedBlobSize, "max expanded blob size")
618+
assert.Equal(t, "refs/heads/master^{tree}", h.MaxExpandedBlobSizeTree.BestPath(), "max expanded blob size tree")
619+
assert.Equal(t, counts.Count32(0), h.MaxExpandedLinkCount, "max expanded link count")
620+
assert.Nil(t, h.MaxExpandedLinkCountTree, "max expanded link count tree")
621+
assert.Equal(t, counts.Count32(0), h.MaxExpandedSubmoduleCount, "max expanded submodule count")
622+
assert.Nil(t, h.MaxExpandedSubmoduleCountTree, "max expanded submodule count tree")
623+
})
624+
625+
t.Run("partial", func(t *testing.T) {
626+
name := "master:d0/d0"
627+
oid, err := repo.ResolveObject(name)
628+
require.NoError(t, err)
629+
roots := []sizes.Root{sizes.NewExplicitRoot(name, oid)}
578630

579-
assert.Equal(t, counts.Count32(1), h.UniqueCommitCount, "unique commit count")
580-
assert.Equal(t, counts.Count64(172), h.UniqueCommitSize, "unique commit size")
581-
assert.Equal(t, counts.Count32(172), h.MaxCommitSize, "max commit size")
582-
assert.Equal(t, "refs/heads/master", h.MaxCommitSizeCommit.Path(), "max commit size commit")
583-
assert.Equal(t, counts.Count32(1), h.MaxHistoryDepth, "max history depth")
584-
assert.Equal(t, counts.Count32(0), h.MaxParentCount, "max parent count")
585-
assert.Equal(t, "refs/heads/master", h.MaxParentCountCommit.Path(), "max parent count commit")
586-
587-
assert.Equal(t, counts.Count32(10), h.UniqueTreeCount, "unique tree count")
588-
assert.Equal(t, counts.Count64(2910), h.UniqueTreeSize, "unique tree size")
589-
assert.Equal(t, counts.Count64(100), h.UniqueTreeEntries, "unique tree entries")
590-
assert.Equal(t, counts.Count32(10), h.MaxTreeEntries, "max tree entries")
591-
assert.Equal(t, "refs/heads/master:d0/d0/d0/d0/d0/d0/d0/d0/d0", h.MaxTreeEntriesTree.Path(), "max tree entries tree")
592-
593-
assert.Equal(t, counts.Count32(1), h.UniqueBlobCount, "unique blob count")
594-
assert.Equal(t, counts.Count64(6), h.UniqueBlobSize, "unique blob size")
595-
assert.Equal(t, counts.Count32(6), h.MaxBlobSize, "max blob size")
596-
assert.Equal(t, "refs/heads/master:d0/d0/d0/d0/d0/d0/d0/d0/d0/f0", h.MaxBlobSizeBlob.Path(), "max blob size blob")
597-
598-
assert.Equal(t, counts.Count32(0), h.UniqueTagCount, "unique tag count")
599-
assert.Equal(t, counts.Count32(0), h.MaxTagDepth, "max tag depth")
600-
601-
assert.Equal(t, counts.Count32(1), h.ReferenceCount, "reference count")
602-
603-
assert.Equal(t, counts.Count32(10), h.MaxPathDepth, "max path depth")
604-
assert.Equal(t, "refs/heads/master^{tree}", h.MaxPathDepthTree.Path(), "max path depth tree")
605-
assert.Equal(t, counts.Count32(29), h.MaxPathLength, "max path length")
606-
assert.Equal(t, "refs/heads/master^{tree}", h.MaxPathLengthTree.Path(), "max path length tree")
607-
608-
assert.Equal(t, counts.Count32((pow(10, 10)-1)/(10-1)), h.MaxExpandedTreeCount, "max expanded tree count")
609-
assert.Equal(t, "refs/heads/master^{tree}", h.MaxExpandedTreeCountTree.Path(), "max expanded tree count tree")
610-
assert.Equal(t, counts.Count32(0xffffffff), h.MaxExpandedBlobCount, "max expanded blob count")
611-
assert.Equal(t, "refs/heads/master^{tree}", h.MaxExpandedBlobCountTree.Path(), "max expanded blob count tree")
612-
assert.Equal(t, counts.Count64(6*pow(10, 10)), h.MaxExpandedBlobSize, "max expanded blob size")
613-
assert.Equal(t, "refs/heads/master^{tree}", h.MaxExpandedBlobSizeTree.Path(), "max expanded blob size tree")
614-
assert.Equal(t, counts.Count32(0), h.MaxExpandedLinkCount, "max expanded link count")
615-
assert.Nil(t, h.MaxExpandedLinkCountTree, "max expanded link count tree")
616-
assert.Equal(t, counts.Count32(0), h.MaxExpandedSubmoduleCount, "max expanded submodule count")
617-
assert.Nil(t, h.MaxExpandedSubmoduleCountTree, "max expanded submodule count tree")
631+
h, err := sizes.ScanRepositoryUsingGraph(
632+
ctx, repo, roots, sizes.NameStyleFull, meter.NoProgressMeter,
633+
)
634+
require.NoError(t, err)
635+
636+
assert.Equal(t, counts.Count32(0), h.UniqueCommitCount, "unique commit count")
637+
assert.Equal(t, counts.Count64(0), h.UniqueCommitSize, "unique commit size")
638+
assert.Equal(t, counts.Count32(0), h.MaxCommitSize, "max commit size")
639+
assert.Nil(t, h.MaxCommitSizeCommit)
640+
assert.Equal(t, counts.Count32(0), h.MaxHistoryDepth, "max history depth")
641+
assert.Equal(t, counts.Count32(0), h.MaxParentCount, "max parent count")
642+
assert.Nil(t, h.MaxParentCountCommit, "max parent count commit")
643+
644+
assert.Equal(t, counts.Count32(8), h.UniqueTreeCount, "unique tree count")
645+
assert.Equal(t, counts.Count64(2330), h.UniqueTreeSize, "unique tree size")
646+
assert.Equal(t, counts.Count64(80), h.UniqueTreeEntries, "unique tree entries")
647+
assert.Equal(t, counts.Count32(10), h.MaxTreeEntries, "max tree entries")
648+
assert.Equal(t, "master:d0/d0/d0/d0/d0/d0/d0/d0/d0", h.MaxTreeEntriesTree.BestPath(), "max tree entries tree")
649+
650+
assert.Equal(t, counts.Count32(1), h.UniqueBlobCount, "unique blob count")
651+
assert.Equal(t, counts.Count64(6), h.UniqueBlobSize, "unique blob size")
652+
assert.Equal(t, counts.Count32(6), h.MaxBlobSize, "max blob size")
653+
assert.Equal(t, "master:d0/d0/d0/d0/d0/d0/d0/d0/d0/f0", h.MaxBlobSizeBlob.BestPath(), "max blob size blob")
654+
655+
assert.Equal(t, counts.Count32(0), h.UniqueTagCount, "unique tag count")
656+
assert.Equal(t, counts.Count32(0), h.MaxTagDepth, "max tag depth")
657+
658+
assert.Equal(t, counts.Count32(0), h.ReferenceCount, "reference count")
659+
660+
assert.Equal(t, counts.Count32(8), h.MaxPathDepth, "max path depth")
661+
assert.Equal(t, "master:d0/d0", h.MaxPathDepthTree.BestPath(), "max path depth tree")
662+
assert.Equal(t, counts.Count32(23), h.MaxPathLength, "max path length")
663+
assert.Equal(t, "master:d0/d0", h.MaxPathLengthTree.BestPath(), "max path length tree")
664+
665+
assert.Equal(t, counts.Count32((pow(10, 8)-1)/(10-1)), h.MaxExpandedTreeCount, "max expanded tree count")
666+
assert.Equal(t, "master:d0/d0", h.MaxExpandedTreeCountTree.BestPath(), "max expanded tree count tree")
667+
assert.Equal(t, counts.Count32(pow(10, 8)), h.MaxExpandedBlobCount, "max expanded blob count")
668+
assert.Equal(t, "master:d0/d0", h.MaxExpandedBlobCountTree.BestPath(), "max expanded blob count tree")
669+
assert.Equal(t, counts.Count64(6*pow(10, 8)), h.MaxExpandedBlobSize, "max expanded blob size")
670+
assert.Equal(t, "master:d0/d0", h.MaxExpandedBlobSizeTree.BestPath(), "max expanded blob size tree")
671+
assert.Equal(t, counts.Count32(0), h.MaxExpandedLinkCount, "max expanded link count")
672+
assert.Nil(t, h.MaxExpandedLinkCountTree, "max expanded link count tree")
673+
assert.Equal(t, counts.Count32(0), h.MaxExpandedSubmoduleCount, "max expanded submodule count")
674+
assert.Nil(t, h.MaxExpandedSubmoduleCountTree, "max expanded submodule count tree")
675+
})
618676
}
619677

620678
func TestTaggedTags(t *testing.T) {
@@ -650,9 +708,14 @@ func TestTaggedTags(t *testing.T) {
650708
refRoots, err := sizes.CollectReferences(ctx, repo, refGrouper{})
651709
require.NoError(t, err)
652710

711+
roots := make([]sizes.Root, 0, len(refRoots))
712+
for _, refRoot := range refRoots {
713+
roots = append(roots, refRoot)
714+
}
715+
653716
h, err := sizes.ScanRepositoryUsingGraph(
654717
context.Background(), repo,
655-
refRoots, sizes.NameStyleNone, meter.NoProgressMeter,
718+
roots, sizes.NameStyleNone, meter.NoProgressMeter,
656719
)
657720
require.NoError(t, err, "scanning repository")
658721
assert.Equal(t, counts.Count32(3), h.MaxTagDepth, "tag depth")
@@ -679,9 +742,14 @@ func TestFromSubdir(t *testing.T) {
679742
refRoots, err := sizes.CollectReferences(ctx, repo, refGrouper{})
680743
require.NoError(t, err)
681744

745+
roots := make([]sizes.Root, 0, len(refRoots))
746+
for _, refRoot := range refRoots {
747+
roots = append(roots, refRoot)
748+
}
749+
682750
h, err := sizes.ScanRepositoryUsingGraph(
683751
context.Background(), testRepo.Repository(t),
684-
refRoots, sizes.NameStyleNone, meter.NoProgressMeter,
752+
roots, sizes.NameStyleNone, meter.NoProgressMeter,
685753
)
686754
require.NoError(t, err, "scanning repository")
687755
assert.Equal(t, counts.Count32(2), h.MaxPathDepth, "max path depth")
@@ -738,10 +806,15 @@ func TestSubmodule(t *testing.T) {
738806
mainRefRoots, err := sizes.CollectReferences(ctx, mainRepo, refGrouper{})
739807
require.NoError(t, err)
740808

809+
mainRoots := make([]sizes.Root, 0, len(mainRefRoots))
810+
for _, refRoot := range mainRefRoots {
811+
mainRoots = append(mainRoots, refRoot)
812+
}
813+
741814
// Analyze the main repo:
742815
h, err := sizes.ScanRepositoryUsingGraph(
743816
context.Background(), mainTestRepo.Repository(t),
744-
mainRefRoots, sizes.NameStyleNone, meter.NoProgressMeter,
817+
mainRoots, sizes.NameStyleNone, meter.NoProgressMeter,
745818
)
746819
require.NoError(t, err, "scanning repository")
747820
assert.Equal(t, counts.Count32(2), h.UniqueBlobCount, "unique blob count")
@@ -758,9 +831,14 @@ func TestSubmodule(t *testing.T) {
758831
submRefRoots2, err := sizes.CollectReferences(ctx, submRepo2, refGrouper{})
759832
require.NoError(t, err)
760833

834+
submRoots2 := make([]sizes.Root, 0, len(submRefRoots2))
835+
for _, refRoot := range submRefRoots2 {
836+
submRoots2 = append(submRoots2, refRoot)
837+
}
838+
761839
h, err = sizes.ScanRepositoryUsingGraph(
762840
context.Background(), submRepo2,
763-
submRefRoots2, sizes.NameStyleNone, meter.NoProgressMeter,
841+
submRoots2, sizes.NameStyleNone, meter.NoProgressMeter,
764842
)
765843
require.NoError(t, err, "scanning repository")
766844
assert.Equal(t, counts.Count32(2), h.UniqueBlobCount, "unique blob count")

internal/refopts/ref_group_builder.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,9 +254,14 @@ func (rgb *RefGroupBuilder) AddRefopts(flags *pflag.FlagSet) {
254254

255255
// Finish collects the information gained from processing the options
256256
// and returns a `sizes.RefGrouper`.
257-
func (rgb *RefGroupBuilder) Finish() (sizes.RefGrouper, error) {
257+
func (rgb *RefGroupBuilder) Finish(defaultAll bool) (sizes.RefGrouper, error) {
258258
if rgb.topLevelGroup.filter == nil {
259-
rgb.topLevelGroup.filter = git.AllReferencesFilter
259+
// User didn't specify any reference options.
260+
if defaultAll {
261+
rgb.topLevelGroup.filter = git.AllReferencesFilter
262+
} else {
263+
rgb.topLevelGroup.filter = git.NoReferencesFilter
264+
}
260265
}
261266

262267
refGrouper := refGrouper{

sizes/explicit_root.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package sizes
2+
3+
import "github.com/github/git-sizer/git"
4+
5+
type ExplicitRoot struct {
6+
name string
7+
oid git.OID
8+
}
9+
10+
func NewExplicitRoot(name string, oid git.OID) ExplicitRoot {
11+
return ExplicitRoot{
12+
name: name,
13+
oid: oid,
14+
}
15+
}
16+
17+
func (er ExplicitRoot) Name() string { return er.name }
18+
func (er ExplicitRoot) OID() git.OID { return er.oid }
19+
func (er ExplicitRoot) Walk() bool { return true }

0 commit comments

Comments
 (0)