hub/commands/sync.go

138 строки
3.6 KiB
Go

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()
}