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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
|
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"path/filepath"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
var ErrorReposHasLocalBranches error = fmt.Errorf("repo still has local branches")
var ErrorMergeBranch error = fmt.Errorf("trunk has things not in the branch")
var ErrorMergeTrunk error = fmt.Errorf("branch has things not in trunk")
func doClean() error {
if argv.Clean.Pub != nil {
if err := doCleanPub(); err != nil {
badExit(err)
}
log.Info("finished attempt at cleaning devel branches")
return nil
}
if argv.Clean.Devel != nil {
if err := doCleanDevel(); err != nil {
badExit(err)
}
log.Info("finished attempt at cleaning devel branches")
return nil
}
if argv.Clean.User != nil {
if err := doCleanUser(); err != nil {
log.Info(err)
okExit("")
}
return nil
}
return nil
}
func doCleanUser() error {
if _, count, _, err := IsEverythingOnMaster(); err != nil {
if count == 0 {
log.Info("No repos are on the master branch")
return nil
}
log.Info("Not all repos are on the master branch")
// return err
}
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if err := doCleanUserRepo(repo); err != nil {
log.Info(repo.GetGoPath(), err)
return err
}
}
return nil
}
func doesLocalBranchExist(repo *gitpb.Repo, branch string) bool {
return repo.Exists(filepath.Join(".git/refs/heads", branch))
}
func doCleanDevel() error {
var total int
var count int
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
total += 1
devel := repo.GetDevelBranchName()
if !doesLocalBranchExist(repo, devel) {
if argv.Verbose {
log.Info("local branch was already deleted:", repo.GetGoPath())
}
continue
}
if repo.GetCurrentBranchName() != repo.GetMasterBranchName() {
log.Info("Repo not on master branch:", repo.GetGoPath())
continue
}
if repo.IsDirty() {
log.Info("Repo is dirty:", repo.GetGoPath())
continue
}
count += 1
if err := justDeleteTheDevelBranchAlready(repo); err != nil {
log.Info("justDeleteTheDevel() err", repo.GetGoPath(), err)
}
}
log.Info("")
log.Printf("attempted cleaning %d devel branches of %d total branches\n", count, total)
return nil
}
/*
func exactDevelRepo(repo *gitpb.Repo) error {
devel := repo.GetDevelBranchName()
master := repo.GetMasterBranchName()
err := isBranchSubsetOfTrunk(repo, devel, master)
if err != nil {
return err
}
return nil
}
*/
func checkhashes(repo *gitpb.Repo, hashes []string, refpath string) ([]string, error) {
if !repo.Exists(refpath) {
return hashes, nil
}
r, err := repo.RunStrict([]string{"cat", refpath})
if err != nil {
return hashes, err
}
newhash := r.Stdout[0]
for _, hash := range hashes {
if newhash != hash {
return hashes, fmt.Errorf("%s hash broke %s %s", repo.GetGoPath(), newhash, hash)
}
}
hashes = append(hashes, newhash)
return hashes, nil
}
// removes all local branches
func doCleanUserRepo(repo *gitpb.Repo) error {
if repo.IsDirty() {
return nil
}
bruser := repo.GetUserBranchName()
brdevel := repo.GetDevelBranchName()
if repo.GetUserVersion() == "uerr" {
// already deleted
return nil
}
log.Info("trying", bruser, repo.GetUserVersion())
if repo.IsBranchRemote(bruser) {
log.Info("forge is designed to always have local only user branches", bruser)
return fmt.Errorf("forge is designed to always have local only user branches")
}
b1 := repo.CountDiffObjects(bruser, brdevel) // should be zero
if b1 == 0 {
cmd := []string{"git", "branch", "-D", bruser}
log.Info("USER IS IN DEVEL", repo.GetGoPath(), cmd)
err := repo.RunVerbose(cmd)
return err
}
return fmt.Errorf("%s branch has things not in %s count=%d", bruser, brdevel, b1)
}
/*
// verifies that the branch is a pure subset of the other branch
// sorry about the 'master' 'slave' nameing thing. I guess that isn't
// 'cool' to use anymore. I can't think of other terms that aren't reserved words.
func isBranchSubsetOfTrunk(repo *gitpb.Repo, branch string, trunk string) error {
b1 := countGitDiffLog(repo, branch, trunk) // should be zero
b2 := countGitDiffLog(repo, trunk, branch) // can be greater than 1
// log.Info(branch, "vs", trunk, "count", b1, b2)
if b1 == 0 && b2 == 0 {
// log.Info("branch and trunk are identical ==", branch, b1, trunk, b2)
return nil
}
if argv.Verbose {
log.Printf("%-40s NOT EXACT %s %s (%d) (%d)\n", repo.GetGoPath(), branch, trunk, b1, b2)
}
if b1 == 0 {
cmd := []string{"git", "merge", trunk}
log.Printf("%-40s branch %s needs merge with trunk %s len(%d) %s\n", repo.GetGoPath(), branch, trunk, b2, cmd)
return ErrorMergeBranch
}
if b2 == 0 {
log.Printf("%-40s trunk %s needs merge with branch %s len(%d)\n", repo.GetGoPath(), branch, trunk, b2)
return ErrorMergeTrunk
}
return fmt.Errorf("branch not clean to delete. needs merge %d %d", b1, b2)
}
*/
// count all objects only in branch1
// if zero, that means branch1 is entirely contained in branch2 and can be safely deleted
/*
func countGitDiffLog(repo *gitpb.Repo, branch1, branch2 string) int {
cmd := repo.ConstructGitDiffLog(branch1, branch2)
r, err := repo.RunStrict(cmd)
if err != nil {
return -1
}
// log.Info("countDiffObjects()", cmd, len(r.Stdout), strings.Join(r.Stdout, " "))
return len(r.Stdout)
}
*/
func doCleanPub() error {
total := 0
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if repo.GetTargetVersion() != "" {
repo.SetTargetVersion("")
configSave = true
total += 1
}
}
log.Printf("clearing %d total repos\n", total)
return nil
}
// if you call this, there is no going back. no checks anymore. nothing
// it deletes the 'devel' branch. git branch -D "devel". END OF STORY
func justDeleteTheDevelBranchAlready(repo *gitpb.Repo) error {
branch := repo.GetDevelBranchName()
remote := filepath.Join("origin", branch)
if me.forge.Config.IsReadOnly(repo.GetGoPath()) {
if repo.IsDevelRemote() {
// just make sure the remote & local branches are the same
return repo.DeleteLocalDevelBranch()
}
}
// check against remote if it exists
if repo.IsDevelRemote() {
b1 := repo.CountDiffObjects(branch, remote) // should be zero
if b1 == 0 {
cmd := []string{"git", "branch", "-D", repo.GetDevelBranchName()}
log.Info("DEVEL IS IN REMOTE", repo.GetGoPath(), cmd)
err := repo.RunVerbose(cmd)
return err
}
cmd := []string{"git", "push"}
log.Info("DEVEL LOCAL NEEDS GIT PUSH TO REMOTE", repo.GetGoPath(), cmd)
err := repo.RunVerbose(cmd)
return err
}
// remote doesn't exist, check against master
master := repo.GetMasterBranchName()
b1 := repo.CountDiffObjects(branch, master) // should be zero
if b1 == 0 {
cmd := []string{"git", "branch", "-D", repo.GetDevelBranchName()}
log.Info("DEVEL IS IN REMOTE", repo.GetGoPath(), cmd)
err := repo.RunVerbose(cmd)
return err
}
cmd := []string{"git", "merge something somehow"}
log.Info("DEVEL LOCAL NEEDS GIT MERGE TO MASTER", repo.GetGoPath(), cmd, b1)
// _, err := repo.RunVerbose(cmd)
return nil
}
|