forked from mislav/hub
-
Notifications
You must be signed in to change notification settings - Fork 0
/
sync.go
137 lines (113 loc) · 3.6 KB
/
sync.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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package commands
import (
"fmt"
"regexp"
"strings"
"github.com/github/hub/git"
"github.com/github/hub/github"
"github.com/github/hub/ui"
"github.com/github/hub/utils"
)
var cmdSync = &Command{
Run: sync,
Usage: "sync [--color]",
Long: `Fetch git objects from upstream and update local branches.
- If the local branch is outdated, fast-forward it;
- If the local branch contains unpushed work, warn about it;
- If the branch seems merged and its upstream branch was deleted, delete it.
If a local branch does not have any upstream configuration, but has a
same-named branch on the remote, treat that as its upstream branch.
## Options:
--color[=<WHEN>]
Enable colored output even if stdout is not a terminal. <WHEN> can be one
of "always" (default for '--color'), "never", or "auto" (default).
## See also:
hub(1), git-fetch(1)
`,
}
func init() {
CmdRunner.Use(cmdSync)
}
func sync(cmd *Command, args *Args) {
localRepo, err := github.LocalRepo()
utils.Check(err)
remote, err := localRepo.MainRemote()
utils.Check(err)
defaultBranch := localRepo.DefaultBranch(remote).ShortName()
fullDefaultBranch := fmt.Sprintf("refs/remotes/%s/%s", remote.Name, defaultBranch)
currentBranch := ""
if curBranch, err := localRepo.CurrentBranch(); err == nil {
currentBranch = curBranch.ShortName()
}
err = git.Spawn("fetch", "--prune", "--quiet", "--progress", remote.Name)
utils.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()
utils.Check(err)
var green,
lightGreen,
red,
lightRed,
resetColor string
colorize := colorizeOutput(args.Flag.HasReceived("--color"), args.Flag.Value("--color"))
if colorize {
green = "\033[32m"
lightGreen = "\033[32;1m"
red = "\033[31m"
lightRed = "\033[31;1m"
resetColor = "\033[0m"
}
for _, branch := range branches {
fullBranch := fmt.Sprintf("refs/heads/%s", branch)
remoteBranch := fmt.Sprintf("refs/remotes/%s/%s", remote.Name, branch)
gone := false
if branchToRemote[branch] == remote.Name {
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)
utils.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)
}
ui.Printf("%sUpdated branch %s%s%s (was %s).\n", green, lightGreen, branch, resetColor, diff.A[0:7])
} else {
ui.Errorf("warning: `%s' seems to contain unpushed commits\n", branch)
}
} else if gone {
diff, err := git.NewRange(fullBranch, fullDefaultBranch)
utils.Check(err)
if diff.IsAncestor() {
if branch == currentBranch {
git.Quiet("checkout", "--quiet", defaultBranch)
currentBranch = defaultBranch
}
git.Quiet("branch", "-D", branch)
ui.Printf("%sDeleted branch %s%s%s (was %s).\n", red, lightRed, branch, resetColor, diff.A[0:7])
} else {
ui.Errorf("warning: `%s' was deleted on %s, but appears not merged into %s\n", branch, remote.Name, defaultBranch)
}
}
}
args.NoForward()
}