computeGitDiff produces a unified diff string for the repository by combining `git diff HEAD` (staged + unstaged changes) with diffs for untracked files.
(ctx context.Context, logger slog.Logger, gitBin string, repoRoot string)
| 397 | // combining `git diff HEAD` (staged + unstaged changes) with diffs |
| 398 | // for untracked files. |
| 399 | func computeGitDiff(ctx context.Context, logger slog.Logger, gitBin string, repoRoot string) (string, error) { |
| 400 | var diffParts []string |
| 401 | |
| 402 | // Check if the repo has any commits. |
| 403 | hasCommits := true |
| 404 | checkCmd := exec.CommandContext(ctx, gitBin, "-C", repoRoot, "rev-parse", "HEAD") |
| 405 | if err := checkCmd.Run(); err != nil { |
| 406 | hasCommits = false |
| 407 | } |
| 408 | |
| 409 | if hasCommits { |
| 410 | // `git diff HEAD` captures both staged and unstaged changes |
| 411 | // relative to HEAD in a single unified diff. |
| 412 | cmd := exec.CommandContext(ctx, gitBin, "-C", repoRoot, "diff", "HEAD") |
| 413 | out, err := cmd.Output() |
| 414 | if err != nil { |
| 415 | return "", xerrors.Errorf("git diff HEAD: %w", err) |
| 416 | } |
| 417 | if len(out) > 0 { |
| 418 | diffParts = append(diffParts, string(out)) |
| 419 | } |
| 420 | } |
| 421 | |
| 422 | // Show untracked files as diffs too. |
| 423 | // `git ls-files --others --exclude-standard` lists untracked, |
| 424 | // non-ignored files. |
| 425 | lsCmd := exec.CommandContext(ctx, gitBin, "-C", repoRoot, "ls-files", "--others", "--exclude-standard") |
| 426 | lsOut, err := lsCmd.Output() |
| 427 | if err != nil { |
| 428 | logger.Debug(ctx, "failed to list untracked files", slog.F("root", repoRoot), slog.Error(err)) |
| 429 | return strings.Join(diffParts, ""), nil |
| 430 | } |
| 431 | |
| 432 | untrackedFiles := strings.Split(strings.TrimSpace(string(lsOut)), "\n") |
| 433 | for _, f := range untrackedFiles { |
| 434 | f = strings.TrimSpace(f) |
| 435 | if f == "" { |
| 436 | continue |
| 437 | } |
| 438 | // Use `git diff --no-index /dev/null <file>` to generate |
| 439 | // a unified diff for untracked files. |
| 440 | var stdout bytes.Buffer |
| 441 | untrackedCmd := exec.CommandContext(ctx, gitBin, "-C", repoRoot, "diff", "--no-index", "--", "/dev/null", f) |
| 442 | untrackedCmd.Stdout = &stdout |
| 443 | // git diff --no-index exits with 1 when files differ, |
| 444 | // which is expected. We ignore the error and check for |
| 445 | // output instead. |
| 446 | _ = untrackedCmd.Run() |
| 447 | if stdout.Len() > 0 { |
| 448 | diffParts = append(diffParts, stdout.String()) |
| 449 | } |
| 450 | } |
| 451 | |
| 452 | return strings.Join(diffParts, ""), nil |
| 453 | } |
no test coverage detected