func cmdSyncBranch()

in git-codereview/sync.go [133:304]


func cmdSyncBranch(args []string) {
	os.Setenv("GIT_EDITOR", ":")       // do not bring up editor during merge, commit
	os.Setenv("GIT_GOFMT_HOOK", "off") // do not require gofmt during merge

	var cont, mergeBackToParent bool
	flags.BoolVar(&cont, "continue", false, "continue after merge conflicts")
	flags.BoolVar(&mergeBackToParent, "merge-back-to-parent", false, "for shutting down the dev branch")
	flags.Parse(args)
	if len(flag.Args()) > 0 {
		fmt.Fprintf(stderr(), "Usage: %s sync-branch %s [-continue]\n", progName, globalFlags)
		exit(2)
	}

	parent := config()["parent-branch"]
	if parent == "" {
		dief("cannot sync-branch: codereview.cfg does not list parent-branch")
	}

	branch := config()["branch"]
	if parent == "" {
		dief("cannot sync-branch: codereview.cfg does not list branch")
	}

	b := CurrentBranch()
	if b.DetachedHead() {
		dief("cannot sync-branch: on detached head")
	}
	if len(b.Pending()) > 0 {
		dief("cannot sync-branch: pending changes exist\n" +
			"\trun 'git codereview pending' to see them")
	}

	if cont {
		// Note: There is no -merge-back-to-parent -continue
		// because -merge-back-to-parent never has merge conflicts.
		// (It requires that the parent be fully merged into the
		// dev branch or it won't even attempt the reverse merge.)
		if mergeBackToParent {
			dief("cannot use -continue with -merge-back-to-parent")
		}
		if _, err := os.Stat(syncBranchStatusFile()); err != nil {
			dief("cannot sync-branch -continue: no pending sync-branch status file found")
		}
		syncBranchContinue(syncBranchContinueFlag, b, readSyncBranchStatus())
		return
	}

	if _, err := cmdOutputErr("git", "rev-parse", "--abbrev-ref", "MERGE_HEAD"); err == nil {
		diePendingMerge("sync-branch")
	}

	// Don't sync with staged or unstaged changes.
	// rebase is going to complain if we don't, and we can give a nicer error.
	checkStaged("sync")
	checkUnstaged("sync")

	// Make sure client is up-to-date on current branch.
	// Note that this does a remote fetch of b.OriginBranch() (aka branch).
	cmdSync(nil)

	// Pull down parent commits too.
	quiet := "-q"
	if *verbose > 0 {
		quiet = "-v"
	}
	run("git", "fetch", quiet, "origin", "refs/heads/"+parent+":refs/remotes/origin/"+parent)

	// Write the status file to make sure we can, before starting a merge.
	status := &syncBranchStatus{
		Local:      b.Name,
		Parent:     parent,
		ParentHash: gitHash("origin/" + parent),
		Branch:     branch,
		BranchHash: gitHash("origin/" + branch),
	}
	writeSyncBranchStatus(status)

	parentHash, err := cmdOutputErr("git", "rev-parse", "origin/"+parent)
	if err != nil {
		dief("cannot sync-branch: cannot resolve origin/%s: %v\n%s", parent, err, parentHash)
	}
	branchHash, err := cmdOutputErr("git", "rev-parse", "origin/"+branch)
	if err != nil {
		dief("cannot sync-branch: cannot resolve origin/%s: %v\n%s", branch, err, branchHash)
	}
	parentHash = trim(parentHash)
	branchHash = trim(branchHash)

	// Only --merge-back-to-parent when there's nothing waiting
	// to be merged in from parent. If a non-trivial merge needs
	// to be done, it should be done first on the dev branch,
	// not the parent branch.
	if mergeBackToParent {
		other := cmdOutput("git", "log", "--format=format:+ %cd %h %s", "--date=short", "origin/"+branch+"..origin/"+parent)
		if other != "" {
			dief("cannot sync-branch --merge-back-to-parent: parent has new commits.\n"+
				"\trun 'git sync-branch' to bring them into this branch first:\n%s",
				other)
		}
	}

	// Start the merge.
	if mergeBackToParent {
		// Change HEAD back to "parent" and merge "branch" into it,
		// even though we could instead merge "parent" into "branch".
		// This way the parent-branch lineage ends up the first parent
		// of the merge, the same as it would when we are doing it by hand
		// with a plain "git merge". This may help the display of the
		// merge graph in some tools more closely reflect what we did.
		run("git", "reset", "--hard", "origin/"+parent)
		_, err = cmdOutputErr("git", "merge", "--no-ff", "origin/"+branch)
	} else {
		_, err = cmdOutputErr("git", "merge", "origin/"+parent)
	}

	// Resolve codereview.cfg the right way - never take it from the merge.
	// For a regular sync-branch we keep the branch's.
	// For a merge-back-to-parent we take the parent's.
	// The codereview.cfg contains the branch config and we don't want
	// it to change.
	what := branchHash
	if mergeBackToParent {
		what = parentHash
	}
	cmdOutputDir(repoRoot(), "git", "checkout", what, "--", "codereview.cfg")

	if mergeBackToParent {
		syncBranchContinue(syncBranchMergeBackFlag, b, status)
		return
	}

	if err != nil {
		// Check whether the only listed file is codereview.cfg and try again if so.
		// Build list of unmerged files.
		for _, s := range nonBlankLines(cmdOutputDir(repoRoot(), "git", "status", "-b", "--porcelain")) {
			// Unmerged status is anything with a U and also AA and DD.
			if len(s) >= 4 && s[2] == ' ' && (s[0] == 'U' || s[1] == 'U' || s[0:2] == "AA" || s[0:2] == "DD") {
				status.Conflicts = append(status.Conflicts, s[3:])
			}
		}
		if len(status.Conflicts) == 0 {
			// Must have been codereview.cfg that was the problem.
			// Try continuing the merge.
			// Note that as of Git 2.12, git merge --continue is a synonym for git commit,
			// but older Gits do not have merge --continue.
			var out string
			out, err = cmdOutputErr("git", "commit", "-m", "TEMPORARY MERGE MESSAGE")
			if err != nil {
				printf("git commit failed with no apparent unmerged files:\n%s\n", out)
			}
		} else {
			writeSyncBranchStatus(status)
		}
	}

	if err != nil {
		if len(status.Conflicts) == 0 {
			dief("cannot sync-branch: git merge failed but no conflicts found\n"+
				"(unexpected error, please ask for help!)\n\ngit status:\n%s\ngit status -b --porcelain:\n%s",
				cmdOutputDir(repoRoot(), "git", "status"),
				cmdOutputDir(repoRoot(), "git", "status", "-b", "--porcelain"))
		}
		dief("sync-branch: merge conflicts in:\n\t- %s\n\n"+
			"Please fix them (use 'git status' to see the list again),\n"+
			"then 'git add' or 'git rm' to resolve them,\n"+
			"and then 'git sync-branch -continue' to continue.\n"+
			"Or run 'git merge --abort' to give up on this sync-branch.\n",
			strings.Join(status.Conflicts, "\n\t- "))
	}

	syncBranchContinue("", b, status)
}