sync.go 3.6 KB
Newer Older
1 2 3 4 5 6 7
package commands

import (
	"fmt"
	"regexp"
	"strings"

8 9 10 11
	"github.com/github/hub/v2/git"
	"github.com/github/hub/v2/github"
	"github.com/github/hub/v2/ui"
	"github.com/github/hub/v2/utils"
12 13 14 15
)

var cmdSync = &Command{
	Run:   sync,
16
	Usage: "sync [--color]",
17
	Long: `Fetch git objects from upstream and update local branches.
18 19 20 21 22

- 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.

M
Mislav Marohnić 已提交
23
If a local branch does not have any upstream configuration, but has a
24 25
same-named branch on the remote, treat that as its upstream branch.

26 27 28
## Options:
	--color[=<WHEN>]
		Enable colored output even if stdout is not a terminal. <WHEN> can be one
29
		of "always" (default for ''--color''), "never", or "auto" (default).
30

31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
## 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)

48
	defaultBranch := localRepo.DefaultBranch(remote).ShortName()
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
	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

78 79
	colorize := colorizeOutput(args.Flag.HasReceived("--color"), args.Flag.Value("--color"))
	if colorize {
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
		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 {
117
				ui.Errorf("warning: '%s' seems to contain unpushed commits\n", branch)
118 119 120 121 122 123 124 125 126 127 128 129 130
			}
		} 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 {
131
				ui.Errorf("warning: '%s' was deleted on %s, but appears not merged into '%s'\n", branch, remote.Name, defaultBranch)
132 133 134 135 136 137
			}
		}
	}

	args.NoForward()
}