Skip to content

Commit 99ec1bf

Browse files
author
Scott Arbeit
committed
Separated unreachable objects by type in the output.
1 parent e284089 commit 99ec1bf

5 files changed

Lines changed: 171 additions & 67 deletions

File tree

README.md

Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -89,66 +89,71 @@ Is your Git repository bursting at the seams?
8989

9090
## Usage
9191

92-
By default, `git-sizer` outputs its results in tabular format. For example, let's use it to analyze [the Linux repository](https://github.com/torvalds/linux), using the `--verbose` option so that all statistics are output:
92+
By default, `git-sizer` outputs its results in tabular format. For example, let's use it to analyze [the Linux repository](https://github.com/torvalds/linux) (as of April, 2025), using the `--verbose` option so that all statistics are output:
9393

9494
```
9595
$ git-sizer --verbose
96-
Processing blobs: 1652370
97-
Processing trees: 3396199
98-
Processing commits: 722647
99-
Matching commits to trees: 722647
100-
Processing annotated tags: 534
101-
Processing references: 539
96+
Processing blobs: 2928490
97+
Processing trees: 6510174
98+
Processing commits: 1351500
99+
Matching commits to trees: 1351500
100+
Processing annotated tags: 877
101+
Processing references: 883
102+
102103
| Name | Value | Level of concern |
103104
| ---------------------------- | --------- | ------------------------------ |
104-
| Overall repository size | | |
105+
| Repository statistics | | |
105106
| * Commits | | |
106-
| * Count | 723 k | * |
107-
| * Total size | 525 MiB | ** |
107+
| * Count | 1.35 M | ** |
108+
| * Total size | 1.11 GiB | **** |
108109
| * Trees | | |
109-
| * Count | 3.40 M | ** |
110-
| * Total size | 9.00 GiB | **** |
111-
| * Total tree entries | 264 M | ***** |
110+
| * Count | 6.51 M | **** |
111+
| * Total size | 19.0 GiB | ********** |
112+
| * Total tree entries | 547 M | ********** |
112113
| * Blobs | | |
113-
| * Count | 1.65 M | * |
114-
| * Total size | 55.8 GiB | ***** |
114+
| * Count | 2.93 M | * |
115+
| * Uncompressed total size | 115 GiB | ************ |
116+
| * On-disk size | | |
117+
| * Compressed total size | 5.68 GiB | ****** |
115118
| * Annotated tags | | |
116-
| * Count | 534 | |
119+
| * Count | 877 | |
117120
| * References | | |
118-
| * Count | 539 | |
121+
| * Count | 883 | |
122+
| * Branches | 1 | |
123+
| * Tags | 880 | |
124+
| * Remote-tracking refs | 2 | |
119125
| | | |
120126
| Biggest objects | | |
121127
| * Commits | | |
122128
| * Maximum size [1] | 72.7 KiB | * |
123129
| * Maximum parents [2] | 66 | ****** |
124130
| * Trees | | |
125-
| * Maximum entries [3] | 1.68 k | * |
131+
| * Maximum entries [3] | 2.60 k | ** |
126132
| * Blobs | | |
127-
| * Maximum size [4] | 13.5 MiB | * |
133+
| * Maximum size [4] | 22.8 MiB | ** |
128134
| | | |
129135
| History structure | | |
130-
| * Maximum history depth | 136 k | |
136+
| * Maximum history depth | 198 k | |
131137
| * Maximum tag depth [5] | 1 | |
132138
| | | |
133139
| Biggest checkouts | | |
134-
| * Number of directories [6] | 4.38 k | ** |
135-
| * Maximum path depth [7] | 13 | * |
136-
| * Maximum path length [8] | 134 B | * |
137-
| * Number of files [9] | 62.3 k | * |
138-
| * Total size of files [9] | 747 MiB | |
139-
| * Number of symlinks [10] | 40 | |
140+
| * Number of directories [6] | 5.89 k | ** |
141+
| * Maximum path depth [6] | 14 | * |
142+
| * Maximum path length [7] | 134 B | * |
143+
| * Number of files [8] | 88.7 k | * |
144+
| * Total size of files [9] | 1.41 GiB | * |
145+
| * Number of symlinks [6] | 78 | |
140146
| * Number of submodules | 0 | |
141147
142148
[1] 91cc53b0c78596a73fa708cceb7313e7168bb146
143149
[2] 2cde51fbd0f310c8a2c5f977e665c0ac3945b46d
144-
[3] 4f86eed5893207aca2c2da86b35b38f2e1ec1fc8 (refs/heads/master:arch/arm/boot/dts)
145-
[4] a02b6794337286bc12c907c33d5d75537c240bd0 (refs/heads/master:drivers/gpu/drm/amd/include/asic_reg/vega10/NBIO/nbio_6_1_sh_mask.h)
150+
[3] ac1d84c335bcbd5fc5d82b8e985d8a9cc4c67d79 (6a1d798feb65d2a67e6e2cafb0b0e4f430603226:arch/arm/boot/dts)
151+
[4] c20bf730dc553e5ae44ad9e769b1f8dface9fa9e (refs/heads/master:drivers/gpu/drm/amd/include/asic_reg/dcn/dcn_3_2_0_sh_mask.h)
146152
[5] 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c (refs/tags/v2.6.11)
147-
[6] 1459754b9d9acc2ffac8525bed6691e15913c6e2 (589b754df3f37ca0a1f96fccde7f91c59266f38a^{tree})
148-
[7] 78a269635e76ed927e17d7883f2d90313570fdbc (dae09011115133666e47c35673c0564b0a702db7^{tree})
149-
[8] ce5f2e31d3bdc1186041fdfd27a5ac96e728f2c5 (refs/heads/master^{tree})
150-
[9] 532bdadc08402b7a72a4b45a2e02e5c710b7d626 (e9ef1fe312b533592e39cddc1327463c30b0ed8d^{tree})
151-
[10] f29a5ea76884ac37e1197bef1941f62fda3f7b99 (f5308d1b83eba20e69df5e0926ba7257c8dd9074^{tree})
153+
[6] 549fc717f82345cf115dfa586ce076a8d1f296a6 (refs/heads/master^{tree})
154+
[7] b0da5ce619daec8138cf92dfcf00e7a51ce856a9 (d8763340d2cb6262fb86424315a1f92cabc0e23c^{tree})
155+
[8] fd94fec4e9c4e08df8e919e57fcc974c52c88c3c (3491aa04787f4d7e00da98d94b1b10001c398b5a^{tree})
156+
[9] 80e16948c5baba02ea2eeda7aa4b2478b68bbaf0 (524c03585fda36584cc7ada49a1827666d37eb4e^{tree})
152157
```
153158

154159
The output is a table showing the thing that was measured, its numerical value, and a rough indication of which values might be a cause for concern. In all cases, only objects that are reachable from references are included (i.e., not unreachable objects, nor objects that are reachable only from the reflogs).

git-sizer.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const usage = `usage: git-sizer [OPTS] [ROOT...]
4747
gitconfig: 'sizer.jsonVersion'.
4848
--[no-]progress report (don't report) progress to stderr. Can
4949
be set via gitconfig: 'sizer.progress'.
50-
--include-unreachable include unreachable objects
50+
--include-unreachable include unreachable objects in the analysis
5151
--version only report the git-sizer version number
5252
5353
Object selection:
@@ -353,8 +353,15 @@ func mainImplementation(ctx context.Context, stdout, stderr io.Writer, args []st
353353
historySize.ShowUnreachable = true
354354
unreachableStats, err := repo.GetUnreachableStats()
355355
if err == nil {
356-
historySize.UnreachableObjectCount = counts.Count64(unreachableStats.Count)
357-
historySize.UnreachableObjectSize = counts.Count64(unreachableStats.Size)
356+
// Store per-type unreachable stats for output
357+
historySize.UnreachableBlobsCount = counts.Count64(unreachableStats.Blobs.Count)
358+
historySize.UnreachableBlobsSize = counts.Count64(unreachableStats.Blobs.Size)
359+
historySize.UnreachableTreesCount = counts.Count64(unreachableStats.Trees.Count)
360+
historySize.UnreachableTreesSize = counts.Count64(unreachableStats.Trees.Size)
361+
historySize.UnreachableCommitsCount = counts.Count64(unreachableStats.Commits.Count)
362+
historySize.UnreachableCommitsSize = counts.Count64(unreachableStats.Commits.Size)
363+
historySize.UnreachableTagsCount = counts.Count64(unreachableStats.Tags.Count)
364+
historySize.UnreachableTagsSize = counts.Count64(unreachableStats.Tags.Size)
358365
}
359366
}
360367

@@ -374,6 +381,8 @@ func mainImplementation(ctx context.Context, stdout, stderr io.Writer, args []st
374381
}
375382
fmt.Fprintf(stdout, "%s\n", j)
376383
} else {
384+
// Print a blank line between progress output and the table
385+
fmt.Fprintln(stdout)
377386
if _, err := io.WriteString(
378387
stdout, historySize.TableString(rg.Groups(), threshold, nameStyle),
379388
); err != nil {

git/git.go

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -177,45 +177,91 @@ func (repo *Repository) GitPath(relPath string) (string, error) {
177177
return string(bytes.TrimSpace(out)), nil
178178
}
179179

180-
// UnreachableStats holds the count and size of unreachable objects.
180+
// UnreachableStats holds the count and size of unreachable objects, broken out by type.
181181
type UnreachableStats struct {
182-
Count int64
183-
Size int64
182+
Blobs struct {
183+
Count int64
184+
Size int64
185+
}
186+
Trees struct {
187+
Count int64
188+
Size int64
189+
}
190+
Commits struct {
191+
Count int64
192+
Size int64
193+
}
194+
Tags struct {
195+
Count int64
196+
Size int64
197+
}
184198
}
185199

186-
// GetUnreachableStats runs 'git fsck --unreachable --no-reflogs --full'
187-
// and returns the count and total size of unreachable objects.
188-
// This implementation collects all OIDs from fsck output and then uses
189-
// batch mode to efficiently retrieve their sizes.
200+
// GetUnreachableStats runs 'git fsck --unreachable --no-reflogs'
201+
// and returns the count and total size of unreachable objects, broken out by type.
190202
func (repo *Repository) GetUnreachableStats() (UnreachableStats, error) {
191-
// Run git fsck. Using CombinedOutput captures both stdout and stderr.
192-
cmd := repo.GitCommand("fsck", "--unreachable", "--no-reflogs", "--full")
203+
cmd := repo.GitCommand("fsck", "--unreachable", "--no-reflogs")
193204
output, err := cmd.Output()
194205
if err != nil {
195-
return UnreachableStats{Count: 0, Size: 0}, fmt.Errorf(
196-
"running 'git fsck --unreachable --no-reflogs --full': %w", err,
206+
return UnreachableStats{}, fmt.Errorf(
207+
"running 'git fsck --unreachable --no-reflogs': %w", err,
197208
)
198209
}
199210

200-
var oids []string
201-
count := int64(0)
211+
// Collect OIDs by type
212+
213+
oidsByType := map[string][]string{
214+
"blob": {},
215+
"tree": {},
216+
"commit": {},
217+
"tag": {},
218+
}
219+
countsByType := map[string]int64{
220+
"blob": 0,
221+
"tree": 0,
222+
"commit": 0,
223+
"tag": 0,
224+
}
225+
202226
for _, line := range bytes.Split(output, []byte{'\n'}) {
203227
fields := bytes.Fields(line)
204228
// Expected line format: "unreachable <type> <oid> ..."
205229
if len(fields) >= 3 && string(fields[0]) == "unreachable" {
206-
count++
207-
oid := string(fields[2])
208-
oids = append(oids, oid)
230+
typeStr := string(fields[1])
231+
if _, ok := oidsByType[typeStr]; ok {
232+
oid := string(fields[2])
233+
oidsByType[typeStr] = append(oidsByType[typeStr], oid)
234+
countsByType[typeStr]++
235+
}
209236
}
210237
}
211238

212-
// Retrieve the total size using batch mode.
213-
totalSize, err := repo.getTotalSizeFromOids(oids)
214-
if err != nil {
215-
return UnreachableStats{}, fmt.Errorf("failed to get sizes via batch mode: %w", err)
239+
var stats UnreachableStats
240+
var errBlob, errTree, errCommit, errTag error
241+
stats.Blobs.Count = countsByType["blob"]
242+
stats.Trees.Count = countsByType["tree"]
243+
stats.Commits.Count = countsByType["commit"]
244+
stats.Tags.Count = countsByType["tag"]
245+
246+
stats.Blobs.Size, errBlob = repo.getTotalSizeFromOids(oidsByType["blob"])
247+
stats.Trees.Size, errTree = repo.getTotalSizeFromOids(oidsByType["tree"])
248+
stats.Commits.Size, errCommit = repo.getTotalSizeFromOids(oidsByType["commit"])
249+
stats.Tags.Size, errTag = repo.getTotalSizeFromOids(oidsByType["tag"])
250+
251+
if errBlob != nil {
252+
return stats, fmt.Errorf("failed to get blob sizes: %w", errBlob)
253+
}
254+
if errTree != nil {
255+
return stats, fmt.Errorf("failed to get tree sizes: %w", errTree)
256+
}
257+
if errCommit != nil {
258+
return stats, fmt.Errorf("failed to get commit sizes: %w", errCommit)
259+
}
260+
if errTag != nil {
261+
return stats, fmt.Errorf("failed to get tag sizes: %w", errTag)
216262
}
217263

218-
return UnreachableStats{Count: count, Size: totalSize}, nil
264+
return stats, nil
219265
}
220266

221267
// getTotalSizeFromOids uses 'git cat-file --batch-check' to retrieve sizes for

sizes/output.go

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -613,12 +613,38 @@ func (s *HistorySize) contents(refGroups []RefGroup) tableContents {
613613
if s.ShowUnreachable {
614614
sections = append(sections, S(
615615
"Unreachable objects",
616-
I("unreachableObjectCount", "Count",
617-
"The total number of unreachable objects in the repository",
618-
nil, s.UnreachableObjectCount, metric, "", 1e7),
619-
I("unreachableObjectSize", "Uncompressed total size",
620-
"The total size of unreachable objects in the repository",
621-
nil, s.UnreachableObjectSize, binary, "B", 1e9),
616+
S("Blobs",
617+
I("unreachableBlobsCount", "Count",
618+
"The total number of unreachable blobs in the repository",
619+
nil, s.UnreachableBlobsCount, metric, "", 1.5e6),
620+
I("unreachableBlobsSize", "Uncompressed total size",
621+
"The total size of unreachable blobs in the repository",
622+
nil, s.UnreachableBlobsSize, binary, "B", 1e9),
623+
),
624+
S("Trees",
625+
I("unreachableTreesCount", "Count",
626+
"The total number of unreachable trees in the repository",
627+
nil, s.UnreachableTreesCount, metric, "", 1.5e6),
628+
I("unreachableTreesSize", "Total size",
629+
"The total size of unreachable trees in the repository",
630+
nil, s.UnreachableTreesSize, binary, "B", 2e9),
631+
),
632+
S("Commits",
633+
I("unreachableCommitsCount", "Count",
634+
"The total number of unreachable commits in the repository",
635+
nil, s.UnreachableCommitsCount, metric, "", 500e3),
636+
I("unreachableCommitsSize", "Total size",
637+
"The total size of unreachable commits in the repository",
638+
nil, s.UnreachableCommitsSize, binary, "B", 250e6),
639+
),
640+
S("Tags",
641+
I("unreachableTagsCount", "Count",
642+
"The total number of unreachable tags in the repository",
643+
nil, s.UnreachableTagsCount, metric, "", 25e3),
644+
I("unreachableTagsSize", "Total size",
645+
"The total size of unreachable tags in the repository",
646+
nil, s.UnreachableTagsSize, binary, "B", 250e6),
647+
),
622648
))
623649
}
624650

sizes/sizes.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,11 +214,29 @@ type HistorySize struct {
214214
// The actual size of the .git directory on disk.
215215
GitDirSize counts.Count64 `json:"git_dir_size"`
216216

217-
// The total number of unreachable objects in the repository.
218-
UnreachableObjectCount counts.Count64 `json:"unreachable_object_count"`
217+
// The total number of unreachable blobs in the repository.
218+
UnreachableBlobsCount counts.Count64 `json:"unreachable_blobs_count"`
219219

220-
// The total size of unreachable objects in the repository.
221-
UnreachableObjectSize counts.Count64 `json:"unreachable_object_size"`
220+
// The total size of unreachable blobs in the repository.
221+
UnreachableBlobsSize counts.Count64 `json:"unreachable_blobs_size"`
222+
223+
// The total number of unreachable trees in the repository.
224+
UnreachableTreesCount counts.Count64 `json:"unreachable_trees_count"`
225+
226+
// The total size of unreachable trees in the repository.
227+
UnreachableTreesSize counts.Count64 `json:"unreachable_trees_size"`
228+
229+
// The total number of unreachable commits in the repository.
230+
UnreachableCommitsCount counts.Count64 `json:"unreachable_commits_count"`
231+
232+
// The total size of unreachable commits in the repository.
233+
UnreachableCommitsSize counts.Count64 `json:"unreachable_commits_size"`
234+
235+
// The total number of unreachable tags in the repository.
236+
UnreachableTagsCount counts.Count64 `json:"unreachable_tags_count"`
237+
238+
// The total size of unreachable tags in the repository.
239+
UnreachableTagsSize counts.Count64 `json:"unreachable_tags_size"`
222240

223241
ShowUnreachable bool `json:"-"`
224242
}

0 commit comments

Comments
 (0)