CommitAndSafePush commits changes and safely pushes them to the remote repository. It fetches the latest remote changes, checks for conflicts, and handles them based on defaultPushChoice: - "1": Pull remote changes and merge (fails on conflicts) - "2": Overwrite remote changes with local changes usi
(ctx context.Context, root string, config *gitutil.Config, commitMsg string, author *object.Signature, defaultPushChoice string)
| 590 | // |
| 591 | // If h.Interactive is true and there are remote commits, the user will be prompted to choose how to proceed. |
| 592 | func (h *Helper) CommitAndSafePush(ctx context.Context, root string, config *gitutil.Config, commitMsg string, author *object.Signature, defaultPushChoice string) error { |
| 593 | // Set remote if not set (go-git complains if we try to fetch without setting remote) |
| 594 | err := gitutil.SetRemote(root, config) |
| 595 | if err != nil { |
| 596 | return fmt.Errorf("failed to set git remote: %w", err) |
| 597 | } |
| 598 | |
| 599 | // 1. Fetch latest from remote |
| 600 | err = gitutil.GitFetch(ctx, root, config) |
| 601 | if err != nil { |
| 602 | return fmt.Errorf("failed to fetch from remote: %w", err) |
| 603 | } |
| 604 | |
| 605 | // 2. Check status of the subpath |
| 606 | status, err := gitutil.RunGitStatus(root, config.Subpath, config.RemoteName(), "") |
| 607 | if err != nil { |
| 608 | return fmt.Errorf("failed to get git status: %w", err) |
| 609 | } |
| 610 | if status.Branch != config.DefaultBranch { |
| 611 | return fmt.Errorf("current branch %q does not match expected branch %q", status.Branch, config.DefaultBranch) |
| 612 | } |
| 613 | |
| 614 | // 3. Warn if there are remote commits |
| 615 | choice := defaultPushChoice |
| 616 | if status.RemoteCommits != 0 { |
| 617 | if h.Interactive { |
| 618 | h.PrintfWarn("Warning: There are changes on the remote branch that are not in your local branch.") |
| 619 | h.PrintfWarn("It's recommended to pull the latest changes before pushing to avoid overwriting remote changes.\n") |
| 620 | h.PrintfWarn("Please choose one of the following options to proceed:\n") |
| 621 | h.PrintfWarn("1: Pull remote changes to your local branch and fail on conflicts\n") |
| 622 | h.PrintfWarn("2: Overwrite remote changes with your local changes(Not supported for monorepos)\n") |
| 623 | h.PrintfWarn("3: Abort deploy and merge manually\n") |
| 624 | choice, err = SelectPrompt("Choose how to resolve remote changes", []string{"1", "2", "3"}, "1") |
| 625 | if err != nil { |
| 626 | return err |
| 627 | } |
| 628 | } |
| 629 | } |
| 630 | |
| 631 | // 4. Merge + push |
| 632 | // The push can still fail if there were new remote commits since the fetch. But that's okay, the user can just retry. |
| 633 | switch choice { |
| 634 | case "1": |
| 635 | err := gitutil.RunUpstreamMerge(ctx, config.RemoteName(), root, status.Branch, false) |
| 636 | if err != nil { |
| 637 | return fmt.Errorf("local is behind remote and failed to sync with remote: %w", err) |
| 638 | } |
| 639 | return gitutil.CommitAndPush(ctx, root, config, commitMsg, author) |
| 640 | case "2": |
| 641 | // Instead of a force push, we do a merge with favourLocal=true to ensure we don't lose history. |
| 642 | // This is not equivalent to a force push but is safer for users. |
| 643 | if config.Subpath != "" { |
| 644 | // force pushing in a monorepo can overwrite other subpaths |
| 645 | // we can check for changes in other subpaths but it is tricky and error prone |
| 646 | // monorepo setups are advanced use cases and we can require users to manually resolve remote changes |
| 647 | return fmt.Errorf("cannot overwrite remote changes in a monorepo setup. Merge remote changes manually") |
| 648 | } |
| 649 | err := gitutil.RunUpstreamMerge(ctx, config.RemoteName(), root, status.Branch, true) |