-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathreport.go
More file actions
162 lines (141 loc) · 4.09 KB
/
Copy pathreport.go
File metadata and controls
162 lines (141 loc) · 4.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"github.com/urfave/cli/v3"
"github.com/boyvinall/dirtygit/scanner"
)
// reportBranchEntry is one local branch row in the report.
type reportBranchEntry struct {
scanner.LocalBranchRef
// ShownInTUI is true when this branch appears in the TUI Branches pane for its repository
ShownInTUI bool `json:"shown_in_tui"`
IsLocalOnly bool `json:"is_local_only"`
}
// reportFileEntry is one porcelain status entry for a file.
type reportFileEntry struct {
// Staging and Worktree are the single-character git status codes (e.g. "M", "A", "?").
Staging string `json:"staging"`
Worktree string `json:"worktree"`
Path string `json:"path"`
OriginalPath string `json:"original_path"`
}
// reportRepo is the per-repository section of the report.
type reportRepo struct {
Path string `json:"path"`
IsClean bool `json:"is_clean"`
// Files are the uncommitted working-tree changes (porcelain entries).
Files []reportFileEntry `json:"files"`
// CurrentBranch is the checked-out branch short name, or the short HEAD hash when detached.
CurrentBranch string `json:"current_branch"`
Detached bool `json:"detached"`
// Branches lists all local branches including those excluded by config (see ExcludedByConfig).
Branches []reportBranchEntry `json:"branches"`
}
// report is the top-level JSON structure for the report subcommand.
type report struct {
// Repos are the repositories shown in the TUI repository pane (dirty or diverged),
// in alphabetical order.
Repos []reportRepo `json:"repos"`
}
func buildReport(mgs *scanner.MultiGitStatus) report {
paths := mgs.SortedRepoPaths()
repos := make([]reportRepo, 0, len(paths))
for _, path := range paths {
rs, ok := mgs.Get(path)
if !ok {
continue
}
// Files
files := make([]reportFileEntry, 0, len(rs.Porcelain.Entries))
for _, e := range rs.Porcelain.Entries {
files = append(files, reportFileEntry{
Staging: string(e.Staging),
Worktree: string(e.Worktree),
Path: e.Path,
OriginalPath: e.OriginalPath,
})
}
// Build a set of branch names that survived FilterLocalOnlyForConfig.
filteredSet := make(map[string]struct{}, len(rs.FilteredBranches))
for _, lb := range rs.FilteredBranches {
filteredSet[lb.Name] = struct{}{}
}
branches := make([]reportBranchEntry, 0, len(rs.Branches))
for _, lb := range rs.Branches {
_, survived := filteredSet[lb.Name]
branches = append(branches, reportBranchEntry{
LocalBranchRef: lb,
ShownInTUI: survived,
IsLocalOnly: lb.IsLocalOnly(),
})
}
repos = append(repos, reportRepo{
Path: path,
IsClean: rs.Porcelain.ToGitStatus().IsClean(),
Files: files,
CurrentBranch: rs.Branch,
Detached: rs.Detached,
Branches: branches,
})
}
return report{Repos: repos}
}
func runReport(ctx context.Context, config *scanner.Config, outputFile string) error {
mgs, err := scanner.Scan(ctx, config)
if err != nil {
return err
}
r := buildReport(mgs)
if outputFile != "" {
f, err := os.Create(outputFile)
if err != nil {
return err
}
defer f.Close()
enc := json.NewEncoder(f)
enc.SetIndent("", " ")
err = enc.Encode(r)
if err != nil {
return err
}
}
printReportSummary(r)
return nil
}
func printReportSummary(r report) {
if len(r.Repos) == 0 {
fmt.Println("No dirty or diverged repositories found.")
return
}
for _, repo := range r.Repos {
fmt.Println(repo.Path)
for _, b := range repo.Branches {
if b.ShownInTUI {
fmt.Printf(" %s\n", b.DisplayName())
}
}
}
}
func reportCommand() *cli.Command {
return &cli.Command{
Name: "report",
Usage: "Report on dirty/diverged repositories (equivalent to TUI state)",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "output-file",
Aliases: []string{"o"},
Usage: "Write json report to this file",
},
},
Action: func(ctx context.Context, cmd *cli.Command) error {
config, err := loadConfig(cmd, defaultConfig)
if err != nil {
return err
}
return runReport(ctx, config, cmd.String("output-file"))
},
}
}