---
title: Git Expressions
description: Filter units and stacks based on Git diffs using Git expressions
slug: features/filter/git
sidebar:
  order: 6
---

import { Aside } from '@astrojs/starlight/components';
import FileTree from '@components/vendored/starlight/FileTree.astro';

Match units and stacks based on changes between Git references. This is useful for targeting infrastructure that has been modified, added, or removed between commits, branches, or tags.

Git-based expressions are written between `[` and `]` characters, and use the `...` operator to indicate the range of changes to compare.

```bash
# Compare between two references
terragrunt find --filter '[main...HEAD]'

# Shorthand: compare reference to HEAD
terragrunt find --filter '[main]'

# Compare between specific commits
terragrunt find --filter '[abc123...def456]'

# Compare between tags
terragrunt find --filter '[v1.0.0...v2.0.0]'

# Compare using relative references
terragrunt find --filter '[HEAD~1...HEAD]'

# Compare between branches
terragrunt find --filter '[feature-branch...main]'
```

<FileTree>
- .
  - **modified-unit** \<-- Matched by [main...HEAD] (terragrunt.hcl was modified)
    - terragrunt.hcl (modified)
  - **new-unit** \<-- Matched by [main...HEAD] (terragrunt.hcl was added)
    - terragrunt.hcl (added)
  - **removed-unit** \<-- Matched by [main...HEAD] (terragrunt.hcl was removed)
    - (directory removed)
  - unchanged-unit
    - terragrunt.hcl (unchanged)
</FileTree>

## How it works

When evaluating a Git-based filter, Terragrunt will first generate a worktree for every reference that needs to be evaluated, and assess the diffs between Git references.

e.g. For a filter like `[main...HEAD]`, Terragrunt will generate a worktree for `main` and one for `HEAD` in temporary directories, and use `git diff` to assess the diffs between the two references.

Then, for any unit that is discovered within those worktrees, Terragrunt will enqueue that unit for a run in the run queue _in the worktree where it was discovered_.

In the example above, the `modified-unit` will be discovered in a "to" temporary directory (e.g. `/tmp/.../terragrunt-worktree-HEAD.../modified-unit`), whereas the `removed-unit` would be discovered in the "from" temporary directory (e.g. `/tmp/.../terragrunt-worktree-main.../removed-unit`).

This is important to recognize, as it's how destroys will be possible despite the unit no longer being present in the current working directory. As a consequence, however, you may find that paths don't behave how you expect, as you will be performing runs in the temporary directories created for the relevant worktrees.

<Aside type="caution" title="Remote State Recommended">

When using Git-based filter expressions (e.g. `[HEAD~1...HEAD]`), it is **strongly recommended** to use remote state configurations. Units discovered using Git-based filter expressions may not properly detect dependency outputs when using local state, which can lead to unexpected outcomes such as mock outputs being used instead of actual dependency outputs.

</Aside>

<Aside type="tip" title="Testing with Local State">

If you need to test with local state while using Git-based filter expressions, you can work around the limitations by placing state files in a separate location using absolute paths in your `remote_state` configuration. This ensures that state files are stored consistently regardless of which worktree directory Terragrunt is operating in.

For example, instead of using a relative path:

```hcl
remote_state {
  backend = "local"
  config = {
    path = "${get_parent_terragrunt_dir()}/.state/${path_relative_to_include()}/tofu.tfstate"
  }
}
```

Consider using an absolute path to a shared location:

```hcl
remote_state {
  backend = "local"
  config = {
    path = "/tmp/terragrunt-state/${path_relative_to_include()}/tofu.tfstate"
  }
}
```

Note that this is a workaround for testing purposes only. For production use, remote state backends (such as S3, GCS, or Azure Storage) are strongly recommended.

</Aside>

## Interaction with the run command

When using Git-based expressions and the `run` command, you are required to use one of the `plan` or `apply` commands, and not the `-destroy` flag.

This is because whether a unit will be destroyed is determined by logic relevant to inspecting changes in Git.

When units are added or modified between two Git references, they will be planned or applied. When the units are removed between two Git references, they will be planned for destruction (with `plan -destroy`) or destroyed (with `apply -destroy`).

In the scenario above, running the following:

```bash
terragrunt run --all --filter '[main...HEAD]' -- plan
```

Will result in the following:

- `modified-unit` will be planned (`tofu plan`)
- `new-unit` will be planned (`tofu plan`)
- `removed-unit` will be planned for destruction (`tofu plan -destroy`)
- `unchanged-unit` will be ignored

<Aside type="tip" title="Allowing destroys">

When using Git-based filter expressions and the run command, Terragrunt won't destroy units that are removed between the two Git references unless you use the `--filter-allow-destroy` flag.

```bash
terragrunt run --all --filter '[main...HEAD]' --filter-allow-destroy -- destroy
```

This is a safeguard to prevent unintended destruction of infrastructure.

</Aside>

## The `--filter-affected` flag

For the common use case of comparing the default branch (typically `main`) with `HEAD`, you can use the `--filter-affected` flag as a convenient shorthand:

```bash
# Find components affected by changes between main and HEAD
terragrunt find --filter-affected

# Equivalent to:
terragrunt find --filter '[main...HEAD]'
```

The `--filter-affected` flag automatically detects your repository's default branch and compares it with `HEAD`.

