diff --git a/gomonorepo/cmd/gomonorepo/main.go b/gomonorepo/cmd/gomonorepo/main.go index ee0f2be..5a545f2 100644 --- a/gomonorepo/cmd/gomonorepo/main.go +++ b/gomonorepo/cmd/gomonorepo/main.go @@ -2,7 +2,6 @@ package main import ( "context" - "fmt" "os" "os/signal" "syscall" @@ -50,6 +49,6 @@ func main() { if fErr, ok := err.(*flags.Error); ok && fErr.Type == flags.ErrHelp { parser.WriteHelp(os.Stdout) } else if err != nil { - fmt.Fprint(os.Stderr, err.Error()) + opts.Errorf("Fatal: %s", err.Error()) } } diff --git a/gomonorepo/git_commands.go b/gomonorepo/git_commands.go index 31b3582..54079ea 100644 --- a/gomonorepo/git_commands.go +++ b/gomonorepo/git_commands.go @@ -24,7 +24,7 @@ func getIgnoredDirectories( cmd.Stderr = stderr err := cmd.Run() if err != nil { - return nil, fmt.Errorf("failed to run git diff: %w\n%s", err, stderr.String()) + return nil, fmt.Errorf("failed to get ignored files: %w\n%s", err, stderr.String()) } result := make([]string, 0, 8) @@ -41,7 +41,7 @@ func getIgnoredDirectories( return result, nil } -func listChangedFiles(ctx context.Context, parent string) ([]string, error) { +func listChangedFiles(ctx context.Context, opts *AppOptions, parent string, patched bool) ([]string, error) { stdout, done := GetBuffer() defer done(stdout) stderr, done := GetBuffer() @@ -51,6 +51,14 @@ func listChangedFiles(ctx context.Context, parent string) ([]string, error) { cmd.Stderr = stderr err := cmd.Run() if err != nil { + if !patched && strings.HasPrefix(stderr.String(), "fatal: ambiguous argument '"+parent+"'") { + opts.Infof("Parent %q not found locally, attempting to fetch it from remote.\n", parent) + err = tryFetchParentRevision(ctx, parent) + if err != nil { + return nil, err + } + return listChangedFiles(ctx, opts, parent, true) + } return nil, fmt.Errorf("failed to run git diff: %w\n%s", err, stderr.String()) } @@ -66,18 +74,84 @@ func listChangedFiles(ctx context.Context, parent string) ([]string, error) { return result, nil } +func tryFetchParentRevision(ctx context.Context, parent string) error { + remote, err := getConfiguredRemoteName(ctx) + if err != nil { + return err + } + branch := parent + if strings.HasPrefix(parent, remote) { + branch = strings.TrimPrefix(parent, remote+"/") + } + + stdout, done := GetBuffer() + defer done(stdout) + stderr, done := GetBuffer() + defer done(stderr) + + cmd := exec.CommandContext(ctx, "git", "fetch", remote, branch) + cmd.Stdout = stdout + cmd.Stderr = stderr + err = cmd.Run() + if err != nil { + return fmt.Errorf("failed to fetch parent revision: %w\n%s", err, stderr.String()) + } + return nil +} + +// getConfiguredRemoteName returns the first remote name configured in the git repository. +func getConfiguredRemoteName(ctx context.Context) (string, error) { + stdout, done := GetBuffer() + defer done(stdout) + stderr, done := GetBuffer() + defer done(stderr) + + cmd := exec.CommandContext(ctx, "git", "remote") + cmd.Stdout = stdout + cmd.Stderr = stderr + err := cmd.Run() + if err != nil { + return "", fmt.Errorf("failed to get remote name: %w\n%s", err, stderr.String()) + } + + // Get the first remote name + remotes := strings.Split(stdout.String(), "\n") + if len(remotes) == 0 { + return "", fmt.Errorf("no remotes found") + } + + return remotes[0], nil +} + // getCurrentBranch returns the current branch name. -func getCurrentBranch(ctx context.Context) (string, error) { +func getCurrentBranch(ctx context.Context) (remote string, branch string, err error) { stdout, done := GetBuffer() defer done(stdout) stderr, done := GetBuffer() defer done(stderr) + + // Get the current branch / revision name excluding the remote: cmd := exec.CommandContext(ctx, "git", "rev-parse", "--abbrev-ref", "HEAD") cmd.Stdout = stdout cmd.Stderr = stderr - err := cmd.Run() + err = cmd.Run() + if err != nil { + return "", "", fmt.Errorf("failed to get branch name: %w\n%s", err, stderr.String()) + } + branch = strings.TrimSpace(stdout.String()) + + stdout.Reset() + stderr.Reset() + + // Get the branch name AND its upstream remote (e.g. origin/main): + cmd = exec.CommandContext(ctx, "git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}") + cmd.Stdout = stdout + cmd.Stderr = stderr + err = cmd.Run() if err != nil { - return "", fmt.Errorf("failed to run git diff: %w\n%s", err, stderr.String()) + // noop. there is no remote. + return "", branch, nil } - return strings.TrimSpace(stdout.String()), nil + remote = strings.TrimSuffix(strings.TrimSpace(stdout.String()), "/"+branch) + return remote, branch, nil } diff --git a/gomonorepo/module_changes.go b/gomonorepo/module_changes.go index eab4a14..97f7894 100644 --- a/gomonorepo/module_changes.go +++ b/gomonorepo/module_changes.go @@ -33,16 +33,16 @@ func listAllChangedModulesWithTree( return set.Make(tree.AllModules...), nil } - changedFiles, err := listChangedFiles(ctx, parentCommit) + changedFiles, err := listChangedFiles(ctx, opts, parentCommit, false) if err != nil { return nil, err } if len(changedFiles) == 0 { - current, err := getCurrentBranch(ctx) + remote, current, err := getCurrentBranch(ctx) if err != nil { return nil, err } - if current == parentCommit { + if current == parentCommit || remote+"/"+current == parentCommit { opts.Infof("Currently on %q with no changes; will run command on all %d moduels.\n", current, len(tree.AllModules))