-
Notifications
You must be signed in to change notification settings - Fork 2
/
main.go
119 lines (102 loc) · 3.22 KB
/
main.go
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
package main
import (
"fmt"
"os"
"regexp"
"strings"
"github.com/jacobwgillespie/git-sync/git"
)
var (
green = "\033[32m"
lightGreen = "\033[32;1m"
red = "\033[31m"
lightRed = "\033[31;1m"
resetColor = "\033[0m"
)
func main() {
remote, err := git.MainRemote()
check(err)
defaultBranch := git.BranchShortName(git.DefaultBranch(remote))
fullDefaultBranch := fmt.Sprintf("refs/remotes/%s/%s", remote, defaultBranch)
currentBranch := ""
if current, err := git.CurrentBranch(); err == nil {
currentBranch = git.BranchShortName(current)
}
err = git.Spawn("fetch", "--prune", "--quiet", "--progress", remote)
check(err)
branchToRemote := map[string]string{}
if lines, err := git.ConfigAll("branch.*.remote"); err == nil {
configRe := regexp.MustCompile(`^branch\.(.+?)\.remote (.+)`)
for _, line := range lines {
if matches := configRe.FindStringSubmatch(line); len(matches) > 0 {
branchToRemote[matches[1]] = matches[2]
}
}
}
branches, err := git.LocalBranches()
check(err)
for _, branch := range branches {
fullBranch := fmt.Sprintf("refs/heads/%s", branch)
remoteBranch := fmt.Sprintf("refs/remotes/%s/%s", remote, branch)
gone := false
if branchToRemote[branch] == remote {
if upstream, err := git.SymbolicFullName(fmt.Sprintf("%s@{upstream}", branch)); err == nil {
remoteBranch = upstream
} else {
remoteBranch = ""
gone = true
}
} else if !git.HasFile(strings.Split(remoteBranch, "/")...) {
remoteBranch = ""
}
if remoteBranch != "" {
diff, err := git.NewRange(fullBranch, remoteBranch)
check(err)
if diff.IsIdentical() {
continue
} else if diff.IsAncestor() {
if branch == currentBranch {
git.Quiet("merge", "--ff-only", "--quiet", remoteBranch)
} else {
git.Quiet("update-ref", fullBranch, remoteBranch)
}
fmt.Printf("%sUpdated branch %s%s%s (was %s).\n", green, lightGreen, branch, resetColor, diff.A[0:7])
} else {
fmt.Fprintf(os.Stderr, "warning: '%s' seems to contain unpushed commits\n", branch)
}
} else if gone {
diff, err := git.NewRange(fullBranch, fullDefaultBranch)
check(err)
// Determine if branch has been merged with a merge
shouldDelete := diff.IsAncestor()
// Otherwise, try to determine if branch has been squash-merged
if !shouldDelete {
ancestorHash, err := git.MergeBase(fullDefaultBranch, fullBranch)
check(err)
treeHash, err := git.TreeRef(fullBranch)
check(err)
danglingCommit, err := git.CommitTree(treeHash, "-p", ancestorHash, "-m", fmt.Sprintf("Dangling branch %s", branch))
check(err)
result, err := git.Cherry(fullDefaultBranch, danglingCommit)
check(err)
shouldDelete = strings.HasPrefix(result, "-")
}
if shouldDelete {
if branch == currentBranch {
git.Quiet("checkout", "--quiet", defaultBranch)
currentBranch = defaultBranch
}
git.Quiet("branch", "-D", branch)
fmt.Printf("%sDeleted branch %s%s%s (was %s).\n", red, lightRed, branch, resetColor, diff.A[0:7])
} else {
fmt.Fprintf(os.Stderr, "warning: '%s' was deleted on %s, but appears not merged into '%s'\n", branch, remote, defaultBranch)
}
}
}
}
func check(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
}