Skip to content

Commit 832e108

Browse files
committed
Merge remote-tracking branch 'origin/main' into filter-otel
2 parents e0b8b3b + 3c1148b commit 832e108

File tree

4 files changed

+159
-18
lines changed

4 files changed

+159
-18
lines changed

cli/flags/shared/shared.go

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -251,15 +251,7 @@ func NewFilterFlags(l log.Logger, opts *options.TerragruntOptions) cli.Flags {
251251
l.Warnf("Warning: You have uncommitted changes. The --filter-affected flag may not include all your local modifications.")
252252
}
253253

254-
defaultBranch := "main"
255-
256-
if b, err := gitRunner.Config(ctx.Context, "init.defaultBranch"); err == nil && b != "" {
257-
l.Debugf("Using default branch discovered from git config: %s", b)
258-
259-
defaultBranch = b
260-
} else {
261-
l.Warnf("Failed to get default branch from `git config init.defaultBranch`, using main.")
262-
}
254+
defaultBranch := gitRunner.GetDefaultBranch(ctx.Context, l)
263255

264256
opts.FilterQueries = append(opts.FilterQueries, fmt.Sprintf("[%s...HEAD]", defaultBranch))
265257

docs-starlight/src/content/docs/04-reference/04-experiments.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,9 @@ To transition the `filter-flag` feature to a stable release, the following must
169169
- [x] Add support for git-based filtering ([ref...ref] syntax)
170170
- [x] Add support for dependency/dependent traversal (... syntax)
171171
- [x] Add support for `--filters-file` flag
172-
- [ ] Add support for `--filter-allow-destroy` flag
172+
- [x] Add support for `--filter-allow-destroy` flag
173173
- [x] Add support for `--filter-affected` shorthand
174-
- [ ] Comprehensive integration testing across all commands
174+
- [x] Comprehensive integration testing across all commands
175175
- [ ] Backport legacy queue control flags (queue-exclude-dir, queue-include-dir, etc.) into equivalent filter patterns as aliases.
176176

177177
**Future Deprecations:**

docs-starlight/src/content/docs/06-troubleshooting/03-performance.mdx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ terragrunt run --all plan --provider-cache
3838

3939
#### Provider Cache - Gotchas
4040

41-
The provider cache server is a single server that is used by all Terragrunt runs being performed in a given Terragrunt invocation. You will see the most benefit if you are using it in a command that will perform multiple OpenTofu/Terraform operations, like with the `--all` flag and the `--graph` flag.
41+
The provider cache server is a single server that is used by all Terragrunt runs being performed in a given Terragrunt invocation. You will see the most benefit if you are using it in a command that will perform many OpenTofu/Terraform operations, like with the `--all` flag and the `--graph` flag.
4242

43-
When performing individual runs, like `terragrunt plan`, the provider cache server can be a net negative to performance, because starting and stopping the server might add more overhead than just downloading the providers. Whether this is the case depends on many factors, including network speed, the number of providers being downloaded, and whether or not the providers are already cached in the Terragrunt provider cache.
43+
When performing individual runs, like `terragrunt plan`, the provider cache server can be a net negative to performance, because starting and stopping the server might add more overhead than just downloading the providers (or using the [Automatic Provider Cache Dir](/docs/features/auto-provider-cache-dir) feature). Whether this is the case depends on many factors, including network speed, the number of providers being downloaded, and whether or not the providers are already cached in the Terragrunt provider cache.
4444

4545
When in doubt, [measure the performance](#measuring-performance) before and after enabling the provider cache server to see if it's a net win for your use case.
4646

@@ -50,17 +50,17 @@ Under the hood, Terragrunt dependency blocks leverage the OpenTofu/Terraform `ou
5050

5151
The OpenTofu/Terraform `output -json` command does a bit more work than simply fetching output values from state, and a significant portion of that slowdown is loading providers, which it doesn't really need in most cases.
5252

53-
You can significantly improve the performance of dependency blocks by using the `--dependency-fetch-output-from-state` flag. When the flag is set, Terragrunt will directly fetch the backend state file from S3 and parse it directly, avoiding any overhead incurred by calling the `output -json` command.
53+
You can significantly improve the performance of dependency blocks by using the [`dependency-fetch-output-from-state`](https://terragrunt.gruntwork.io/docs/reference/experiments/#dependency-fetch-output-from-state) experiment. When the experiment is active, Terragrunt will resolve outputs by directly fetching the backend state file from S3 and parse it directly, avoiding any overhead incurred by calling the `output -json` command of OpenTofu/Terraform.
5454

5555
For example:
5656

57-
```shell
58-
terragrunt run --all plan --dependency-fetch-output-from-state
57+
```bash
58+
terragrunt run --all plan --experiment=dependency-fetch-output-from-state
5959
```
6060

6161
#### Fetching Output From State - Gotchas
6262

63-
The first thing you need to be aware of when considering usage of the `--dependency-fetch-output-from-state` flag is that it only works for S3 backends. If you are using a different backend, this flag won't do anything.
63+
The first thing you need to be aware of when considering usage of the `dependency-fetch-output-from-state` experiment is that it only works for S3 backends. If you are using a different backend, this experiment won't do anything.
6464

6565
Next, you should be aware that there is no guarantee that OpenTofu/Terraform will maintain the existing schema of their state files, so there is also no guarantee that the flag will work as expected in future versions of OpenTofu/Terraform.
6666

@@ -93,7 +93,7 @@ You can use configurations like the `--warmup` flag to do some warmup runs befor
9393
Here's an example of how to use Hyperfine to benchmark the performance of Terragrunt with two different configurations:
9494

9595
```shell
96-
hyperfine -w 3 -r 5 'terragrunt run --all plan' 'terragrunt run --all plan --dependency-fetch-output-from-state'
96+
hyperfine -w 3 -r 5 'terragrunt run --all plan' 'terragrunt run --all plan --experiment=dependency-fetch-output-from-state'
9797
```
9898

9999
### Terragrunt Developer

internal/git/git.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828

2929
"github.com/go-git/go-git/v6"
3030
"github.com/go-git/go-git/v6/storage/filesystem"
31+
"github.com/gruntwork-io/terragrunt/pkg/log"
3132
)
3233

3334
const (
@@ -495,6 +496,154 @@ func (g *GitRunner) GetHeadCommit(ctx context.Context) string {
495496
return strings.TrimSpace(stdout.String())
496497
}
497498

499+
// GetDefaultBranch implements the hybrid approach to detect the default branch:
500+
// 1. Tries to determine the default branch of the remote repository using the fast local method first
501+
// 2. Falls back to the network method if the local method fails
502+
// 3. Attempts to update local cache for future use
503+
// Returns the branch name (e.g., "main") or an error if both methods fail.
504+
func (g *GitRunner) GetDefaultBranch(ctx context.Context, l log.Logger) string {
505+
branch, err := g.GetDefaultBranchLocal(ctx)
506+
if err == nil && branch != "" {
507+
return branch
508+
}
509+
510+
branch, err = g.GetDefaultBranchRemote(ctx)
511+
if err == nil && branch != "" {
512+
err = g.SetRemoteHeadAuto(ctx)
513+
if err != nil {
514+
l.Warnf("Failed to update local cache for default branch: %v", err)
515+
}
516+
517+
return branch
518+
}
519+
520+
l.Debugf("Failed to determine default branch of remote repository, attempting to get default branch of local repository")
521+
522+
if b, err := g.Config(ctx, "init.defaultBranch"); err == nil && b != "" {
523+
return b
524+
}
525+
526+
l.Debugf("Failed to determine default branch of local repository, using 'main' as fallback")
527+
528+
return "main"
529+
}
530+
531+
// GetDefaultBranchLocal attempts to get the default branch using the local cached remote HEAD.
532+
// Returns the branch name (e.g., "main") if successful, or an error if the local ref is not set.
533+
// This is fast and works offline, but requires that `git remote set-head origin --auto` has been run.
534+
func (g *GitRunner) GetDefaultBranchLocal(ctx context.Context) (string, error) {
535+
if err := g.RequiresWorkDir(); err != nil {
536+
return "", err
537+
}
538+
539+
cmd := g.prepareCommand(ctx, "rev-parse", "--abbrev-ref", "origin/HEAD")
540+
541+
var stdout, stderr bytes.Buffer
542+
543+
cmd.Stdout = &stdout
544+
cmd.Stderr = &stderr
545+
546+
if err := cmd.Run(); err != nil {
547+
return "", &WrappedError{
548+
Op: "git_rev_parse_origin_head",
549+
Context: stderr.String(),
550+
Err: ErrCommandSpawn,
551+
}
552+
}
553+
554+
result := strings.TrimSpace(stdout.String())
555+
556+
// If the result is just "origin/HEAD", the local ref is not properly set
557+
if result == "origin/HEAD" {
558+
return "", &WrappedError{
559+
Op: "git_rev_parse_origin_head",
560+
Context: "local origin/HEAD ref not set",
561+
Err: ErrNoMatchingReference,
562+
}
563+
}
564+
565+
if after, ok := strings.CutPrefix(result, "origin/"); ok {
566+
return after, nil
567+
}
568+
569+
return result, nil
570+
}
571+
572+
// GetDefaultBranchRemote queries the remote repository to determine the default branch.
573+
// This is the most accurate method but requires network access.
574+
// Returns the branch name (e.g., "main") if successful.
575+
func (g *GitRunner) GetDefaultBranchRemote(ctx context.Context) (string, error) {
576+
if err := g.RequiresWorkDir(); err != nil {
577+
return "", err
578+
}
579+
580+
cmd := g.prepareCommand(ctx, "ls-remote", "--symref", "origin", "HEAD")
581+
582+
var stdout, stderr bytes.Buffer
583+
584+
cmd.Stdout = &stdout
585+
cmd.Stderr = &stderr
586+
587+
if err := cmd.Run(); err != nil {
588+
return "", &WrappedError{
589+
Op: "git_ls_remote_symref",
590+
Context: stderr.String(),
591+
Err: ErrCommandSpawn,
592+
}
593+
}
594+
595+
// Parse output: "ref: refs/heads/main HEAD"
596+
output := stdout.String()
597+
lines := strings.SplitSeq(strings.TrimSpace(output), "\n")
598+
599+
for line := range lines {
600+
if line == "" {
601+
continue
602+
}
603+
604+
if strings.HasPrefix(line, "ref:") {
605+
parts := strings.Fields(line)
606+
if len(parts) >= 2 { //nolint:mnd
607+
ref := parts[1]
608+
609+
if after, ok := strings.CutPrefix(ref, "refs/heads/"); ok {
610+
return after, nil
611+
}
612+
}
613+
}
614+
}
615+
616+
return "", &WrappedError{
617+
Op: "git_ls_remote_symref",
618+
Context: "could not parse default branch from ls-remote output",
619+
Err: ErrNoMatchingReference,
620+
}
621+
}
622+
623+
// SetRemoteHeadAuto runs `git remote set-head origin --auto` to update the local cached remote HEAD.
624+
// This makes future calls to GetDefaultBranchLocal faster.
625+
func (g *GitRunner) SetRemoteHeadAuto(ctx context.Context) error {
626+
if err := g.RequiresWorkDir(); err != nil {
627+
return err
628+
}
629+
630+
cmd := g.prepareCommand(ctx, "remote", "set-head", "origin", "--auto")
631+
632+
var stderr bytes.Buffer
633+
634+
cmd.Stderr = &stderr
635+
636+
if err := cmd.Run(); err != nil {
637+
return &WrappedError{
638+
Op: "git_remote_set_head",
639+
Context: stderr.String(),
640+
Err: ErrCommandSpawn,
641+
}
642+
}
643+
644+
return nil
645+
}
646+
498647
func (g *GitRunner) prepareCommand(ctx context.Context, name string, args ...string) *exec.Cmd {
499648
cmd := exec.CommandContext(ctx, g.GitPath, append([]string{name}, args...)...)
500649

0 commit comments

Comments
 (0)