Skip to content

Commit 25d20f9

Browse files
authored
Merge pull request #75 from github/include-exclude-refs
Allow the user to select exactly which references to include in the analysis
2 parents a3d1a00 + 3bcb0cd commit 25d20f9

File tree

6 files changed

+486
-111
lines changed

6 files changed

+486
-111
lines changed

git-sizer.go

Lines changed: 174 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,60 +16,200 @@ import (
1616
"github.com/spf13/pflag"
1717
)
1818

19+
const Usage = `usage: git-sizer [OPTS]
20+
21+
-v, --verbose report all statistics, whether concerning or not
22+
--threshold THRESHOLD minimum level of concern (i.e., number of stars)
23+
that should be reported. Default:
24+
'--threshold=1'.
25+
--critical only report critical statistics
26+
--names=[none|hash|full] display names of large objects in the specified
27+
style: 'none' (omit footnotes entirely), 'hash'
28+
(show only the SHA-1s of objects), or 'full'
29+
(show full names). Default is '--names=full'.
30+
-j, --json output results in JSON format
31+
--json-version=[1|2] choose which JSON format version to output.
32+
Default: --json-version=1.
33+
--[no-]progress report (don't report) progress to stderr.
34+
--version only report the git-sizer version number
35+
36+
Reference selection:
37+
38+
By default, git-sizer processes all Git objects that are reachable from any
39+
reference. The following options can be used to limit which references to
40+
include. The last rule matching a reference determines whether that reference
41+
is processed:
42+
43+
--branches process branches
44+
--tags process tags
45+
--remotes process remote refs
46+
--include PREFIX process references with the specified PREFIX
47+
(e.g., '--include=refs/remotes/origin')
48+
--include-regexp REGEXP process references matching the specified
49+
regular expression (e.g.,
50+
'--include-regexp=refs/tags/release-.*')
51+
--exclude PREFIX don't process references with the specified
52+
PREFIX (e.g., '--exclude=refs/notes')
53+
--exclude-regexp REGEXP don't process references matching the specified
54+
regular expression
55+
--show-refs show which refs are being included/excluded
56+
57+
Prefixes must match at a boundary; for example 'refs/foo' matches
58+
'refs/foo' and 'refs/foo/bar' but not 'refs/foobar'. Regular
59+
expression patterns must match the full reference name.
60+
61+
`
62+
1963
var ReleaseVersion string
2064
var BuildVersion string
2165

2266
type NegatedBoolValue struct {
2367
value *bool
2468
}
2569

26-
func (b *NegatedBoolValue) Set(s string) error {
27-
v, err := strconv.ParseBool(s)
28-
*b.value = !v
70+
func (v *NegatedBoolValue) Set(s string) error {
71+
b, err := strconv.ParseBool(s)
72+
*v.value = !b
2973
return err
3074
}
3175

32-
func (b *NegatedBoolValue) Get() interface{} {
33-
return !*b.value
76+
func (v *NegatedBoolValue) Get() interface{} {
77+
return !*v.value
3478
}
3579

36-
func (b *NegatedBoolValue) String() string {
37-
if b == nil || b.value == nil {
80+
func (v *NegatedBoolValue) String() string {
81+
if v == nil || v.value == nil {
3882
return "true"
3983
} else {
40-
return strconv.FormatBool(!*b.value)
84+
return strconv.FormatBool(!*v.value)
4185
}
4286
}
4387

4488
func (v *NegatedBoolValue) Type() string {
4589
return "bool"
4690
}
4791

92+
type filterValue struct {
93+
filter *git.IncludeExcludeFilter
94+
polarity git.Polarity
95+
pattern string
96+
regexp bool
97+
}
98+
99+
func (v *filterValue) Set(s string) error {
100+
var polarity git.Polarity
101+
var filter git.ReferenceFilter
102+
103+
if v.regexp {
104+
polarity = v.polarity
105+
var err error
106+
filter, err = git.RegexpFilter(s)
107+
if err != nil {
108+
return fmt.Errorf("invalid regexp: %q", s)
109+
}
110+
} else if v.pattern == "" {
111+
polarity = v.polarity
112+
filter = git.PrefixFilter(s)
113+
} else {
114+
// Allow a boolean value to alter the polarity:
115+
b, err := strconv.ParseBool(s)
116+
if err != nil {
117+
return err
118+
}
119+
if b {
120+
polarity = git.Include
121+
} else {
122+
polarity = git.Exclude
123+
}
124+
filter = git.PrefixFilter(v.pattern)
125+
}
126+
127+
switch polarity {
128+
case git.Include:
129+
v.filter.Include(filter)
130+
case git.Exclude:
131+
v.filter.Exclude(filter)
132+
}
133+
134+
return nil
135+
}
136+
137+
func (v *filterValue) Get() interface{} {
138+
return nil
139+
}
140+
141+
func (v *filterValue) String() string {
142+
return ""
143+
}
144+
145+
func (v *filterValue) Type() string {
146+
if v.regexp {
147+
return "regexp"
148+
} else if v.pattern == "" {
149+
return "prefix"
150+
} else {
151+
return ""
152+
}
153+
}
154+
48155
func main() {
49-
err := mainImplementation()
156+
err := mainImplementation(os.Args[1:])
50157
if err != nil {
51158
fmt.Fprintf(os.Stderr, "error: %s\n", err)
52159
os.Exit(1)
53160
}
54161
}
55162

56-
func mainImplementation() error {
57-
var processBranches bool
58-
var processTags bool
59-
var processRemotes bool
163+
func mainImplementation(args []string) error {
60164
var nameStyle sizes.NameStyle = sizes.NameStyleFull
61165
var cpuprofile string
62166
var jsonOutput bool
63167
var jsonVersion uint
64168
var threshold sizes.Threshold = 1
65169
var progress bool
66170
var version bool
171+
var filter git.IncludeExcludeFilter
172+
var showRefs bool
173+
174+
flags := pflag.NewFlagSet("git-sizer", pflag.ContinueOnError)
175+
flags.Usage = func() {
176+
fmt.Print(Usage)
177+
}
178+
179+
flags.Var(
180+
&filterValue{&filter, git.Include, "", false}, "include",
181+
"include specified references",
182+
)
183+
flags.Var(
184+
&filterValue{&filter, git.Include, "", true}, "include-regexp",
185+
"include references matching the specified regular expression",
186+
)
187+
flags.Var(
188+
&filterValue{&filter, git.Exclude, "", false}, "exclude",
189+
"exclude specified references",
190+
)
191+
flags.Var(
192+
&filterValue{&filter, git.Exclude, "", true}, "exclude-regexp",
193+
"exclude references matching the specified regular expression",
194+
)
67195

68-
flags := pflag.NewFlagSet("", pflag.ContinueOnError)
196+
flag := flags.VarPF(
197+
&filterValue{&filter, git.Include, "refs/heads/", false}, "branches", "",
198+
"process all branches",
199+
)
200+
flag.NoOptDefVal = "true"
201+
202+
flag = flags.VarPF(
203+
&filterValue{&filter, git.Include, "refs/tags/", false}, "tags", "",
204+
"process all tags",
205+
)
206+
flag.NoOptDefVal = "true"
69207

70-
flags.BoolVar(&processBranches, "branches", false, "process all branches")
71-
flags.BoolVar(&processTags, "tags", false, "process all tags")
72-
flags.BoolVar(&processRemotes, "remotes", false, "process all remote-tracking branches")
208+
flag = flags.VarPF(
209+
&filterValue{&filter, git.Include, "refs/remotes/", false}, "remotes", "",
210+
"process all remotes",
211+
)
212+
flag.NoOptDefVal = "true"
73213

74214
flags.VarP(
75215
sizes.NewThresholdFlagValue(&threshold, 0),
@@ -105,6 +245,7 @@ func mainImplementation() error {
105245
atty = false
106246
}
107247
flags.BoolVar(&progress, "progress", atty, "report progress to stderr")
248+
flags.BoolVar(&showRefs, "show-refs", false, "list the references being processed")
108249
flags.BoolVar(&version, "version", false, "report the git-sizer version number")
109250
flags.Var(&NegatedBoolValue{&progress}, "no-progress", "suppress progress output")
110251
flags.Lookup("no-progress").NoOptDefVal = "true"
@@ -114,7 +255,7 @@ func mainImplementation() error {
114255

115256
flags.SortFlags = false
116257

117-
err = flags.Parse(os.Args[1:])
258+
err = flags.Parse(args)
118259
if err != nil {
119260
if err == pflag.ErrHelp {
120261
return nil
@@ -144,9 +285,7 @@ func mainImplementation() error {
144285
return nil
145286
}
146287

147-
args := flags.Args()
148-
149-
if len(args) != 0 {
288+
if len(flags.Args()) != 0 {
150289
return errors.New("excess arguments")
151290
}
152291

@@ -158,24 +297,23 @@ func mainImplementation() error {
158297

159298
var historySize sizes.HistorySize
160299

161-
var filter git.ReferenceFilter
162-
if processBranches || processTags || processRemotes {
163-
var filters []git.ReferenceFilter
164-
if processBranches {
165-
filters = append(filters, git.BranchesFilter)
300+
var refFilter git.ReferenceFilter = filter.Filter
301+
302+
if showRefs {
303+
oldRefFilter := refFilter
304+
fmt.Fprintf(os.Stderr, "References (included references marked with '+'):\n")
305+
refFilter = func(refname string) bool {
306+
b := oldRefFilter(refname)
307+
if b {
308+
fmt.Fprintf(os.Stderr, "+ %s\n", refname)
309+
} else {
310+
fmt.Fprintf(os.Stderr, " %s\n", refname)
311+
}
312+
return b
166313
}
167-
if processTags {
168-
filters = append(filters, git.TagsFilter)
169-
}
170-
if processRemotes {
171-
filters = append(filters, git.RemotesFilter)
172-
}
173-
filter = git.OrFilter(filters...)
174-
} else {
175-
filter = git.AllReferencesFilter
176314
}
177315

178-
historySize, err = sizes.ScanRepositoryUsingGraph(repo, filter, nameStyle, progress)
316+
historySize, err = sizes.ScanRepositoryUsingGraph(repo, refFilter, nameStyle, progress)
179317
if err != nil {
180318
return fmt.Errorf("error scanning repository: %s", err)
181319
}

git/git.go

Lines changed: 0 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -289,76 +289,6 @@ func (l *BatchObjectIter) Close() error {
289289
return err
290290
}
291291

292-
type ReferenceFilter func(Reference) bool
293-
294-
func AllReferencesFilter(_ Reference) bool {
295-
return true
296-
}
297-
298-
func PrefixFilter(prefix string) ReferenceFilter {
299-
return func(r Reference) bool {
300-
return strings.HasPrefix(r.Refname, prefix)
301-
}
302-
}
303-
304-
var (
305-
BranchesFilter ReferenceFilter = PrefixFilter("refs/heads/")
306-
TagsFilter ReferenceFilter = PrefixFilter("refs/tags/")
307-
RemotesFilter ReferenceFilter = PrefixFilter("refs/remotes/")
308-
)
309-
310-
func notNilFilters(filters ...ReferenceFilter) []ReferenceFilter {
311-
var ret []ReferenceFilter
312-
for _, filter := range filters {
313-
if filter != nil {
314-
ret = append(ret, filter)
315-
}
316-
}
317-
return ret
318-
}
319-
320-
func OrFilter(filters ...ReferenceFilter) ReferenceFilter {
321-
filters = notNilFilters(filters...)
322-
if len(filters) == 0 {
323-
return AllReferencesFilter
324-
} else if len(filters) == 1 {
325-
return filters[0]
326-
} else {
327-
return func(r Reference) bool {
328-
for _, filter := range filters {
329-
if filter(r) {
330-
return true
331-
}
332-
}
333-
return false
334-
}
335-
}
336-
}
337-
338-
func AndFilter(filters ...ReferenceFilter) ReferenceFilter {
339-
filters = notNilFilters(filters...)
340-
if len(filters) == 0 {
341-
return AllReferencesFilter
342-
} else if len(filters) == 1 {
343-
return filters[0]
344-
} else {
345-
return func(r Reference) bool {
346-
for _, filter := range filters {
347-
if !filter(r) {
348-
return false
349-
}
350-
}
351-
return true
352-
}
353-
}
354-
}
355-
356-
func NotFilter(filter ReferenceFilter) ReferenceFilter {
357-
return func(r Reference) bool {
358-
return !filter(r)
359-
}
360-
}
361-
362292
// Parse a `cat-file --batch[-check]` output header line (including
363293
// the trailing LF). `spec`, if not "", is used in error messages.
364294
func parseBatchHeader(spec string, header string) (OID, ObjectType, counts.Count32, error) {

0 commit comments

Comments
 (0)