From 9ea65116b786e3b5685310c6bb22dee379d468b5 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Wed, 25 Feb 2026 06:29:27 +0000 Subject: [PATCH 1/6] feat: Make the stable content hash easier to get hold of. Signed-off-by: Giles Cope --- buildcontext/git.go | 15 +++++++++ util/gitutil/detectgit.go | 28 ++++++++++++++++ util/gitutil/detectgit_test.go | 59 ++++++++++++++++++++++++++++++++++ variables/builtin.go | 1 + variables/builtin_test.go | 33 +++++++++++++++++++ variables/reserved/names.go | 2 ++ 6 files changed, 138 insertions(+) diff --git a/buildcontext/git.go b/buildcontext/git.go index d6a3c727ea..4102a3aae2 100644 --- a/buildcontext/git.go +++ b/buildcontext/git.go @@ -48,6 +48,8 @@ type resolvedGitProject struct { hash string // shortHash is the short git hash. shortHash string + // contentHash is the git tree hash (content-addressable). + contentHash string // branches is the git branches. branches []string // tags is the git tags. @@ -227,6 +229,7 @@ func (gr *gitResolver) resolveEarthProject( RemoteURL: gitURL, Hash: rgp.hash, ShortHash: rgp.shortHash, + ContentHash: rgp.contentHash, BranchOverrideTagArg: gr.gitBranchOverride != "", Branch: rgp.branches, Tags: rgp.tags, @@ -306,6 +309,7 @@ func (gr *gitResolver) resolveGitProject( "git rev-parse HEAD >/dest/git-hash ; " + "uname -m >/dest/uname-m ;" + "git rev-parse --short=8 HEAD >/dest/git-short-hash ; " + + "git rev-parse HEAD^{tree} >/dest/git-content-hash ; " + "git rev-parse --abbrev-ref HEAD >/dest/git-branch || touch /dest/git-branch ; " + "ls .git/refs/heads/ | head -n 1 >/dest/git-default-branch || touch /dest/git-default-branch ; " + "git describe --exact-match --tags >/dest/git-tags || touch /dest/git-tags ; " + @@ -380,6 +384,15 @@ func (gr *gitResolver) resolveGitProject( return nil, errors.Wrap(err, "read git-short-hash") } + var gitContentHashBytes []byte + + gitContentHashBytes, err = gitMetaRef.ReadFile(ctx, gwclient.ReadRequest{ + Filename: "git-content-hash", + }) + if err != nil { + return nil, errors.Wrap(err, "read git-content-hash") + } + var gitBranch string gitBranch, err = gr.readGitBranch(ctx, gitMetaRef) @@ -474,6 +487,7 @@ func (gr *gitResolver) resolveGitProject( gitHash := strings.SplitN(string(gitHashBytes), "\n", 2)[0] gitShortHash := strings.SplitN(string(gitShortHashBytes), "\n", 2)[0] + gitContentHash := strings.SplitN(string(gitContentHashBytes), "\n", 2)[0] gitBranches := strings.SplitN(gitBranch, "\n", 2) gitAuthorEmail := strings.SplitN(string(gitAuthorEmailBytes), "\n", 2)[0] gitAuthorName := strings.SplitN(string(gitAuthorNameBytes), "\n", 2)[0] @@ -537,6 +551,7 @@ func (gr *gitResolver) resolveGitProject( rgp = &resolvedGitProject{ hash: gitHash, shortHash: gitShortHash, + contentHash: gitContentHash, branches: gitBranches2, tags: gitTags2, committerTs: gitCommitterTs, diff --git a/util/gitutil/detectgit.go b/util/gitutil/detectgit.go index 0f34ec1213..cd207d5288 100644 --- a/util/gitutil/detectgit.go +++ b/util/gitutil/detectgit.go @@ -24,6 +24,8 @@ var ( ErrCouldNotDetectGitHash = errors.New("Could not auto-detect or parse Git hash") // ErrCouldNotDetectGitShortHash is an error returned when git short hash could not be detected. ErrCouldNotDetectGitShortHash = errors.New("Could not auto-detect or parse Git short hash") + // ErrCouldNotDetectGitContentHash is an error returned when git tree hash could not be detected. + ErrCouldNotDetectGitContentHash = errors.New("Could not auto-detect or parse Git content hash") // ErrCouldNotDetectGitBranch is an error returned when git branch could not be detected. ErrCouldNotDetectGitBranch = errors.New("Could not auto-detect or parse Git branch") // ErrCouldNotDetectGitTags is an error returned when git tags could not be detected. @@ -40,6 +42,7 @@ type GitMetadata struct { GitURL string Hash string ShortHash string + ContentHash string RelDir string AuthorName string AuthorEmail string @@ -96,6 +99,12 @@ func Metadata(ctx context.Context, dir, gitBranchOverride string) (*GitMetadata, // Keep going. } + contentHash, err := detectGitContentHash(ctx, dir) + if err != nil { + retErr = err + // Keep going. + } + branch, err := detectGitBranch(ctx, dir, gitBranchOverride) if err != nil { retErr = err @@ -160,6 +169,7 @@ func Metadata(ctx context.Context, dir, gitBranchOverride string) (*GitMetadata, GitURL: gitURL, Hash: hash, ShortHash: shortHash, + ContentHash: contentHash, BranchOverrideTagArg: gitBranchOverride != "", Branch: branch, Tags: tags, @@ -181,6 +191,7 @@ func (gm *GitMetadata) Clone() *GitMetadata { GitURL: gm.GitURL, Hash: gm.Hash, ShortHash: gm.ShortHash, + ContentHash: gm.ContentHash, BranchOverrideTagArg: gm.BranchOverrideTagArg, Branch: gm.Branch, Tags: gm.Tags, @@ -300,6 +311,23 @@ func detectGitShortHash(ctx context.Context, dir string) (string, error) { return strings.SplitN(outStr, "\n", 2)[0], nil } +func detectGitContentHash(ctx context.Context, dir string) (string, error) { + cmd := exec.CommandContext(ctx, "git", "rev-parse", "HEAD^{tree}") + cmd.Dir = dir + + out, err := cmd.Output() + if err != nil { + return "", errors.Wrapf(ErrCouldNotDetectGitContentHash, "returned error %s: %s", err.Error(), string(out)) + } + + outStr := string(out) + if outStr == "" { + return "", errors.Wrapf(ErrCouldNotDetectGitContentHash, "no git tree hash output") + } + + return strings.SplitN(outStr, "\n", 2)[0], nil +} + func detectGitBranch(ctx context.Context, dir, gitBranchOverride string) ([]string, error) { if gitBranchOverride != "" { return []string{gitBranchOverride}, nil diff --git a/util/gitutil/detectgit_test.go b/util/gitutil/detectgit_test.go index 7851b8d427..6dd41c6a87 100644 --- a/util/gitutil/detectgit_test.go +++ b/util/gitutil/detectgit_test.go @@ -1,6 +1,11 @@ package gitutil import ( + "context" + "os" + "os/exec" + "path/filepath" + "strings" "testing" ) @@ -52,3 +57,57 @@ func TestParseGitRemoteURL(t *testing.T) { Equal(t, test.expectedGitURL, gitURL) } } + +func TestDetectGitContentHash(t *testing.T) { + t.Parallel() + + // Create a temp git repo with a known commit. + dir := t.TempDir() + + run := func(args ...string) string { + t.Helper() + cmd := exec.Command(args[0], args[1:]...) + cmd.Dir = dir + cmd.Env = append(os.Environ(), + "GIT_AUTHOR_NAME=test", + "GIT_AUTHOR_EMAIL=test@test.com", + "GIT_COMMITTER_NAME=test", + "GIT_COMMITTER_EMAIL=test@test.com", + ) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("%v failed: %s\n%s", args, err, out) + } + return strings.TrimSpace(string(out)) + } + + run("git", "init") + run("git", "checkout", "-b", "main") + run("git", "config", "commit.gpgsign", "false") + + // Write a file and commit. + NoError(t, os.WriteFile(filepath.Join(dir, "hello.txt"), []byte("hello\n"), 0o644)) + run("git", "add", "hello.txt") + run("git", "commit", "--no-verify", "-m", "first") + + ctx := context.Background() + + contentHash, err := detectGitContentHash(ctx, dir) + NoError(t, err) + + // Tree hash for a single file "hello.txt" containing "hello\n" is deterministic. + Equal(t, "aaa96ced2d9a1c8e72c56b253a0e2fe78393feb7", contentHash) + + // The tree hash should differ from the commit hash. + commitHash := run("git", "rev-parse", "HEAD") + True(t, contentHash != commitHash) + + // Amend the commit (different commit hash, same tree hash). + run("git", "commit", "--no-verify", "--amend", "-m", "amended") + newCommitHash := run("git", "rev-parse", "HEAD") + True(t, commitHash != newCommitHash) + + contentHash2, err := detectGitContentHash(ctx, dir) + NoError(t, err) + Equal(t, contentHash, contentHash2) // tree hash unchanged +} diff --git a/variables/builtin.go b/variables/builtin.go index 456037b171..f7782b0e2f 100644 --- a/variables/builtin.go +++ b/variables/builtin.go @@ -81,6 +81,7 @@ func BuiltinArgs( ret.Add(arg.EarthlyGitHash, gitMeta.Hash) ret.Add(arg.EarthlyGitShortHash, gitMeta.ShortHash) + ret.Add(arg.EarthbuildGitContentHash, gitMeta.ContentHash) branch := firstOrZero(gitMeta.Branch) diff --git a/variables/builtin_test.go b/variables/builtin_test.go index fa861d8d11..f332613c38 100644 --- a/variables/builtin_test.go +++ b/variables/builtin_test.go @@ -2,6 +2,11 @@ package variables import ( "testing" + + "github.com/EarthBuild/earthbuild/domain" + "github.com/EarthBuild/earthbuild/features" + "github.com/EarthBuild/earthbuild/util/gitutil" + arg "github.com/EarthBuild/earthbuild/variables/reserved" ) func TestGetProjectName(t *testing.T) { @@ -71,3 +76,31 @@ func TestGetProjectName(t *testing.T) { Equal(t, tt.safe, ans) } } + +func TestBuiltinArgsContentHash(t *testing.T) { + t.Parallel() + + ftrs := &features.Features{} + gitMeta := &gitutil.GitMetadata{ + Hash: "abc123def456abc123def456abc123def456abc1", + ShortHash: "abc123de", + ContentHash: "deadbeef01234567890abcdef01234567890abcd", + } + + scope := BuiltinArgs(domain.Target{Target: "test"}, nil, gitMeta, DefaultArgs{}, ftrs, false, false) + + val, found := scope.Get(arg.EarthbuildGitContentHash) + Equal(t, true, found) + Equal(t, "deadbeef01234567890abcdef01234567890abcd", val) +} + +func TestBuiltinArgsContentHashNilGitMeta(t *testing.T) { + t.Parallel() + + ftrs := &features.Features{} + + scope := BuiltinArgs(domain.Target{Target: "test"}, nil, nil, DefaultArgs{}, ftrs, false, false) + + _, found := scope.Get(arg.EarthbuildGitContentHash) + Equal(t, false, found) +} diff --git a/variables/reserved/names.go b/variables/reserved/names.go index 414a876509..da8c8438eb 100644 --- a/variables/reserved/names.go +++ b/variables/reserved/names.go @@ -1,6 +1,7 @@ package reserved const ( + EarthbuildGitContentHash = "EARTHBUILD_GIT_CONTENT_HASH" EarthlyBuildSha = "EARTHLY_BUILD_SHA" EarthlyGitBranch = "EARTHLY_GIT_BRANCH" EarthlyGitCommitTimestamp = "EARTHLY_GIT_COMMIT_TIMESTAMP" @@ -46,6 +47,7 @@ var args map[string]struct{} func init() { args = map[string]struct{}{ + EarthbuildGitContentHash: {}, EarthlyBuildSha: {}, EarthlyGitBranch: {}, EarthlyGitCommitTimestamp: {}, From 0fd78297ca5ec55c46b31d6bf72a8ca2aefb9ad0 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Wed, 25 Feb 2026 08:15:56 +0000 Subject: [PATCH 2/6] fix: update docs Signed-off-by: Giles Cope --- docs/caching/caching-in-earthfiles.md | 8 ++++---- docs/earthfile/builtin-args.md | 13 ++++++++++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/caching/caching-in-earthfiles.md b/docs/caching/caching-in-earthfiles.md index bf883cc781..6612568844 100644 --- a/docs/caching/caching-in-earthfiles.md +++ b/docs/caching/caching-in-earthfiles.md @@ -105,7 +105,7 @@ As auto-skip relies on statically analyzing the structure of the build upfront, For basic `ARG` operations, auto-skip is able to infer the value of the `ARG` statically, and therefore, it is able to support it. Here is a practical example. -``` +```Earthfile # Supported ARG MY_ARG=foo BUILD $MY_ARG @@ -119,7 +119,7 @@ In the first case, the value of `MY_ARG` is known statically as its value can be Similarly, the auto-skip algorithm is able to propagate `ARG`s across targets, as long as the value of the `ARG` is known statically. Here is a practical example: -``` +```Earthfile # Supported ARG MY_ARG=foo BUILD +target --arg=$MY_ARG @@ -135,7 +135,7 @@ BUILD +target --arg=$MY_ARG Here is a practical example: -``` +```Earthfile # Supported and efficient (only +target2 is analyzed) ARG MY_ARG=bar IF [ $MY_ARG = "foo" ] @@ -201,7 +201,7 @@ In Earthly, like in Dockerfiles, ARGs declared in Earthfiles also behave as envi For this reason, it is best to declare ARGs as late as possible within the target they are used in, and try to avoid declaring `--global` ARGs as much as possible. If an ARG is not yet declared, it will not influence the cache state of a layer, allowing for more cache hits. Limiting the scope of ARGs as much as possible will yield better cache performance. -Watch out especially for ARGs that change often, such as the built-in ARG `EARTHLY_GIT_HASH`. Declaring this ARG as late as possible in the build will cause less cache misses. +Watch out especially for ARGs that change often, such as the built-in ARG `EARTHLY_GIT_HASH`. Declaring this ARG as late as possible in the build will cause less cache misses. If you need a git-derived identifier that doesn't change as often, consider `EARTHBUILD_GIT_CONTENT_HASH` instead — it only changes when the file tree actually changes, not on every commit. ### Secrets diff --git a/docs/earthfile/builtin-args.md b/docs/earthfile/builtin-args.md index ca0de631eb..078defa0e6 100644 --- a/docs/earthfile/builtin-args.md +++ b/docs/earthfile/builtin-args.md @@ -11,13 +11,16 @@ SAVE IMAGE --push some/name:$TAG ``` {% hint style='danger' %} + ##### Important + Earthly builtin args need to be pre-declared before they can be used. For example ```Dockerfile ARG EARTHLY_TARGET RUN echo "The current target is $EARTHLY_TARGET" ``` + {% endhint %} ### General args @@ -43,8 +46,8 @@ RUN echo "The current target is $EARTHLY_TARGET" ### Git-related args -| Name | Description | Example value | Feature Flag | -| --- | --- | --- |----------------------------------------| +| Name | Description | Example value | Feature Flag | +| ---- | ----------- | ------------- | ------------ | | `EARTHLY_GIT_AUTHOR` | The git author detected within the build context directory. If no git directory is detected, then the value is an empty string. This is currently the author's email address but the feature flag adds the name as well | `john@example.com` (or `John Doe ` with flag turned on) | `--earthly-git-author-individual-args` | | `EARTHLY_GIT_AUTHOR_EMAIL` | The git author email detected within the build context directory. If no git directory is detected, then the value is an empty string. | `john@example.com` | `--earthly-git-author-individual-args` | | `EARTHLY_GIT_AUTHOR_NAME` | The git author name detected within the build context directory. If no git directory is detected, then the value is an empty string. | `John Doe` | `--earthly-git-author-individual-args` | @@ -52,6 +55,7 @@ RUN echo "The current target is $EARTHLY_TARGET" | `EARTHLY_GIT_COMMIT_AUTHOR_TIMESTAMP` | The author timestamp, as unix seconds, of the git commit detected within the build context directory. If no git directory is detected, then the value is an empty string. | `1626881847` | | | `EARTHLY_GIT_BRANCH` | The git branch of the git commit detected within the build context directory. If no git directory is detected, then the value is an empty string. | `main` | | | `EARTHLY_GIT_COMMIT_TIMESTAMP` | The committer timestamp, as unix seconds, of the git commit detected within the build context directory. If no git directory is detected, then the value is an empty string. | `1626881847` | | +| `EARTHBUILD_GIT_CONTENT_HASH` | The git tree hash (`git rev-parse HEAD^{tree}`) detected within the build context directory. Unlike `EARTHLY_GIT_HASH`, this is content-addressable: it remains stable across amends, rebases, or cherry-picks that don't change the file tree. If no git directory is detected, then the value is an empty string. | `aaa96ced2d9a1c8e72c56b253a0e2fe78393feb7` | | | `EARTHLY_GIT_HASH` | The git hash detected within the build context directory. If no git directory is detected, then the value is an empty string. Take care when using this arg, as the frequently changing git hash may be cause for not using the cache. | `41cb5666ade67b29e42bef121144456d3977a67a` | | | `EARTHLY_GIT_ORIGIN_URL` | The git URL detected within the build context directory. If no git directory is detected, then the value is an empty string. Please note that this may be inconsistent, depending on whether an HTTPS or SSH URL was used. | `git@github.com:bar/buz.git` or `https://github.com/bar/buz.git` | | | `EARTHLY_GIT_PROJECT_NAME` | The git project name from within the git URL detected within the build context directory. If no git directory is detected, then the value is an empty string. | `bar/buz` | | @@ -62,7 +66,7 @@ RUN echo "The current target is $EARTHLY_TARGET" ### Platform-related args | Name | Description | Example value | -| --- | --- | --- | +| ---- | ----------- | ------------- | | `NATIVEARCH` | The native processor architecture of the build runner. | `arm`, `amd64`, `arm64` | | `NATIVEOS` | The native OS of the build runner. | `linux` | | `NATIVEPLATFORM` | The native platform of the build runner. | `linux/arm/v7`, `linux/amd64`, `darwin/arm64` | @@ -81,7 +85,9 @@ The default value of the `TARGETPLATFORM` arg is the native platform of the runn Under `LOCALLY`, the `TARGETPLATFORM` arg is always set to the user platform (the environment the `earthly` binary is invoked from) and it is not overridden by the `--platform` flag. {% hint style='info' %} + ##### Note + Under `LOCALLY` targets, it is important to declare the `TARGETPLATFORM` arg **after** the `LOCALLY` command, to ensure that it gets the appropriate user platform value. For example: ```Dockerfile @@ -90,4 +96,5 @@ my-target: ARG TARGETPLATFORM RUN echo "The target platform under LOCALLY is $TARGETPLATFORM" ``` + {% endhint %} From bd2fcac34a86d98f56d26066037f589d433cf7dd Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Wed, 25 Feb 2026 20:49:13 +0000 Subject: [PATCH 3/6] fix: drop Build Signed-off-by: Giles Cope --- docs/caching/caching-in-earthfiles.md | 2 +- docs/earthfile/builtin-args.md | 2 +- util/gitutil/detectgit_test.go | 8 ++++++-- variables/builtin.go | 2 +- variables/builtin_test.go | 4 ++-- variables/reserved/names.go | 4 ++-- 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/caching/caching-in-earthfiles.md b/docs/caching/caching-in-earthfiles.md index 6612568844..d58a3a51a5 100644 --- a/docs/caching/caching-in-earthfiles.md +++ b/docs/caching/caching-in-earthfiles.md @@ -201,7 +201,7 @@ In Earthly, like in Dockerfiles, ARGs declared in Earthfiles also behave as envi For this reason, it is best to declare ARGs as late as possible within the target they are used in, and try to avoid declaring `--global` ARGs as much as possible. If an ARG is not yet declared, it will not influence the cache state of a layer, allowing for more cache hits. Limiting the scope of ARGs as much as possible will yield better cache performance. -Watch out especially for ARGs that change often, such as the built-in ARG `EARTHLY_GIT_HASH`. Declaring this ARG as late as possible in the build will cause less cache misses. If you need a git-derived identifier that doesn't change as often, consider `EARTHBUILD_GIT_CONTENT_HASH` instead — it only changes when the file tree actually changes, not on every commit. +Watch out especially for ARGs that change often, such as the built-in ARG `EARTHLY_GIT_HASH`. Declaring this ARG as late as possible in the build will cause less cache misses. If you need a git-derived identifier that doesn't change as often, consider `EARTH_GIT_CONTENT_HASH` instead — it only changes when the file tree actually changes, not on every commit. ### Secrets diff --git a/docs/earthfile/builtin-args.md b/docs/earthfile/builtin-args.md index 078defa0e6..adeb05d683 100644 --- a/docs/earthfile/builtin-args.md +++ b/docs/earthfile/builtin-args.md @@ -55,7 +55,7 @@ RUN echo "The current target is $EARTHLY_TARGET" | `EARTHLY_GIT_COMMIT_AUTHOR_TIMESTAMP` | The author timestamp, as unix seconds, of the git commit detected within the build context directory. If no git directory is detected, then the value is an empty string. | `1626881847` | | | `EARTHLY_GIT_BRANCH` | The git branch of the git commit detected within the build context directory. If no git directory is detected, then the value is an empty string. | `main` | | | `EARTHLY_GIT_COMMIT_TIMESTAMP` | The committer timestamp, as unix seconds, of the git commit detected within the build context directory. If no git directory is detected, then the value is an empty string. | `1626881847` | | -| `EARTHBUILD_GIT_CONTENT_HASH` | The git tree hash (`git rev-parse HEAD^{tree}`) detected within the build context directory. Unlike `EARTHLY_GIT_HASH`, this is content-addressable: it remains stable across amends, rebases, or cherry-picks that don't change the file tree. If no git directory is detected, then the value is an empty string. | `aaa96ced2d9a1c8e72c56b253a0e2fe78393feb7` | | +| `EARTH_GIT_CONTENT_HASH` | The git tree hash (`git rev-parse HEAD^{tree}`) detected within the build context directory. Unlike `EARTHLY_GIT_HASH`, this is content-addressable: it remains stable across amends, rebases, or cherry-picks that don't change the file tree. If no git directory is detected, then the value is an empty string. | `aaa96ced2d9a1c8e72c56b253a0e2fe78393feb7` | | | `EARTHLY_GIT_HASH` | The git hash detected within the build context directory. If no git directory is detected, then the value is an empty string. Take care when using this arg, as the frequently changing git hash may be cause for not using the cache. | `41cb5666ade67b29e42bef121144456d3977a67a` | | | `EARTHLY_GIT_ORIGIN_URL` | The git URL detected within the build context directory. If no git directory is detected, then the value is an empty string. Please note that this may be inconsistent, depending on whether an HTTPS or SSH URL was used. | `git@github.com:bar/buz.git` or `https://github.com/bar/buz.git` | | | `EARTHLY_GIT_PROJECT_NAME` | The git project name from within the git URL detected within the build context directory. If no git directory is detected, then the value is an empty string. | `bar/buz` | | diff --git a/util/gitutil/detectgit_test.go b/util/gitutil/detectgit_test.go index 6dd41c6a87..88d48eaa16 100644 --- a/util/gitutil/detectgit_test.go +++ b/util/gitutil/detectgit_test.go @@ -66,18 +66,22 @@ func TestDetectGitContentHash(t *testing.T) { run := func(args ...string) string { t.Helper() - cmd := exec.Command(args[0], args[1:]...) + + cmd := exec.CommandContext(context.Background(), args[0], args[1:]...) //nolint:gosec // test helper cmd.Dir = dir + cmd.Env = append(os.Environ(), "GIT_AUTHOR_NAME=test", "GIT_AUTHOR_EMAIL=test@test.com", "GIT_COMMITTER_NAME=test", "GIT_COMMITTER_EMAIL=test@test.com", ) + out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("%v failed: %s\n%s", args, err, out) } + return strings.TrimSpace(string(out)) } @@ -86,7 +90,7 @@ func TestDetectGitContentHash(t *testing.T) { run("git", "config", "commit.gpgsign", "false") // Write a file and commit. - NoError(t, os.WriteFile(filepath.Join(dir, "hello.txt"), []byte("hello\n"), 0o644)) + NoError(t, os.WriteFile(filepath.Join(dir, "hello.txt"), []byte("hello\n"), 0o600)) run("git", "add", "hello.txt") run("git", "commit", "--no-verify", "-m", "first") diff --git a/variables/builtin.go b/variables/builtin.go index f7782b0e2f..ad36c516ba 100644 --- a/variables/builtin.go +++ b/variables/builtin.go @@ -81,7 +81,7 @@ func BuiltinArgs( ret.Add(arg.EarthlyGitHash, gitMeta.Hash) ret.Add(arg.EarthlyGitShortHash, gitMeta.ShortHash) - ret.Add(arg.EarthbuildGitContentHash, gitMeta.ContentHash) + ret.Add(arg.EarthGitContentHash, gitMeta.ContentHash) branch := firstOrZero(gitMeta.Branch) diff --git a/variables/builtin_test.go b/variables/builtin_test.go index f332613c38..23d6b9de04 100644 --- a/variables/builtin_test.go +++ b/variables/builtin_test.go @@ -89,7 +89,7 @@ func TestBuiltinArgsContentHash(t *testing.T) { scope := BuiltinArgs(domain.Target{Target: "test"}, nil, gitMeta, DefaultArgs{}, ftrs, false, false) - val, found := scope.Get(arg.EarthbuildGitContentHash) + val, found := scope.Get(arg.EarthGitContentHash) Equal(t, true, found) Equal(t, "deadbeef01234567890abcdef01234567890abcd", val) } @@ -101,6 +101,6 @@ func TestBuiltinArgsContentHashNilGitMeta(t *testing.T) { scope := BuiltinArgs(domain.Target{Target: "test"}, nil, nil, DefaultArgs{}, ftrs, false, false) - _, found := scope.Get(arg.EarthbuildGitContentHash) + _, found := scope.Get(arg.EarthGitContentHash) Equal(t, false, found) } diff --git a/variables/reserved/names.go b/variables/reserved/names.go index da8c8438eb..9326fc5d04 100644 --- a/variables/reserved/names.go +++ b/variables/reserved/names.go @@ -1,7 +1,7 @@ package reserved const ( - EarthbuildGitContentHash = "EARTHBUILD_GIT_CONTENT_HASH" + EarthGitContentHash = "EARTH_GIT_CONTENT_HASH" EarthlyBuildSha = "EARTHLY_BUILD_SHA" EarthlyGitBranch = "EARTHLY_GIT_BRANCH" EarthlyGitCommitTimestamp = "EARTHLY_GIT_COMMIT_TIMESTAMP" @@ -47,7 +47,7 @@ var args map[string]struct{} func init() { args = map[string]struct{}{ - EarthbuildGitContentHash: {}, + EarthGitContentHash: {}, EarthlyBuildSha: {}, EarthlyGitBranch: {}, EarthlyGitCommitTimestamp: {}, From 291df974e666592fdc9ea78b7f687b6bb2365518 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Wed, 25 Feb 2026 21:11:28 +0000 Subject: [PATCH 4/6] fix: clean up the code so less duplication Signed-off-by: Giles Cope --- buildcontext/git.go | 141 +++++++------------------------------- util/gitutil/detectgit.go | 43 +++--------- 2 files changed, 37 insertions(+), 147 deletions(-) diff --git a/buildcontext/git.go b/buildcontext/git.go index 4102a3aae2..fea8c71bef 100644 --- a/buildcontext/git.go +++ b/buildcontext/git.go @@ -366,31 +366,23 @@ func (gr *gitResolver) resolveGitProject( gitImage, string(unameM), platr.LLBNative().Architecture) } - var gitHashBytes []byte - - gitHashBytes, err = gitMetaRef.ReadFile(ctx, gwclient.ReadRequest{ - Filename: "git-hash", - }) - if err != nil { - return nil, errors.Wrap(err, "read git-hash") - } - - var gitShortHashBytes []byte - - gitShortHashBytes, err = gitMetaRef.ReadFile(ctx, gwclient.ReadRequest{ - Filename: "git-short-hash", - }) - if err != nil { - return nil, errors.Wrap(err, "read git-short-hash") + metaFiles := []string{ + "git-hash", "git-short-hash", "git-content-hash", + "git-default-branch", "git-tags", + "git-committer-ts", "git-author-ts", + "git-author-email", "git-author-name", + "git-body", "git-refs", "Earthfile-paths", } - var gitContentHashBytes []byte + meta := make(map[string][]byte, len(metaFiles)) - gitContentHashBytes, err = gitMetaRef.ReadFile(ctx, gwclient.ReadRequest{ - Filename: "git-content-hash", - }) - if err != nil { - return nil, errors.Wrap(err, "read git-content-hash") + for _, name := range metaFiles { + meta[name], err = gitMetaRef.ReadFile(ctx, gwclient.ReadRequest{ + Filename: name, + }) + if err != nil { + return nil, errors.Wrap(err, "read "+name) + } } var gitBranch string @@ -400,98 +392,17 @@ func (gr *gitResolver) resolveGitProject( return nil, errors.Wrap(err, "read git-branch") } - var gitDefaultBranchBytes []byte - - gitDefaultBranchBytes, err = gitMetaRef.ReadFile(ctx, gwclient.ReadRequest{ - Filename: "git-default-branch", - }) - if err != nil { - return nil, errors.Wrap(err, "read git-default-branch") - } - - var gitTagsBytes []byte - - gitTagsBytes, err = gitMetaRef.ReadFile(ctx, gwclient.ReadRequest{ - Filename: "git-tags", - }) - if err != nil { - return nil, errors.Wrap(err, "read git-tags") - } - - var gitCommitterTsBytes []byte - - gitCommitterTsBytes, err = gitMetaRef.ReadFile(ctx, gwclient.ReadRequest{ - Filename: "git-committer-ts", - }) - if err != nil { - return nil, errors.Wrap(err, "read git-committer-ts") - } - - var gitAuthorTsBytes []byte - - gitAuthorTsBytes, err = gitMetaRef.ReadFile(ctx, gwclient.ReadRequest{ - Filename: "git-author-ts", - }) - if err != nil { - return nil, errors.Wrap(err, "read git-author-ts") - } - - var gitAuthorEmailBytes []byte - - gitAuthorEmailBytes, err = gitMetaRef.ReadFile(ctx, gwclient.ReadRequest{ - Filename: "git-author-email", - }) - if err != nil { - return nil, errors.Wrap(err, "read git-author-email") - } - - var gitAuthorNameBytes []byte - - gitAuthorNameBytes, err = gitMetaRef.ReadFile(ctx, gwclient.ReadRequest{ - Filename: "git-author-name", - }) - if err != nil { - return nil, errors.Wrap(err, "read git-author-name") - } - - var gitBodyBytes []byte - - gitBodyBytes, err = gitMetaRef.ReadFile(ctx, gwclient.ReadRequest{ - Filename: "git-body", - }) - if err != nil { - return nil, errors.Wrap(err, "read git-body") - } - - var gitRefsBytes []byte - - gitRefsBytes, err = gitMetaRef.ReadFile(ctx, gwclient.ReadRequest{ - Filename: "git-refs", - }) - if err != nil { - return nil, errors.Wrap(err, "read git-refs") - } - - var earthfilePathsRaw []byte - - earthfilePathsRaw, err = gitMetaRef.ReadFile(ctx, gwclient.ReadRequest{ - Filename: "Earthfile-paths", - }) - if err != nil { - return nil, errors.Wrap(err, "read Earthfile-paths") - } - isNotHead := func(s string) bool { return s != "" && s != "HEAD" } - gitHash := strings.SplitN(string(gitHashBytes), "\n", 2)[0] - gitShortHash := strings.SplitN(string(gitShortHashBytes), "\n", 2)[0] - gitContentHash := strings.SplitN(string(gitContentHashBytes), "\n", 2)[0] + gitHash := strings.SplitN(string(meta["git-hash"]), "\n", 2)[0] + gitShortHash := strings.SplitN(string(meta["git-short-hash"]), "\n", 2)[0] + gitContentHash := strings.SplitN(string(meta["git-content-hash"]), "\n", 2)[0] gitBranches := strings.SplitN(gitBranch, "\n", 2) - gitAuthorEmail := strings.SplitN(string(gitAuthorEmailBytes), "\n", 2)[0] - gitAuthorName := strings.SplitN(string(gitAuthorNameBytes), "\n", 2)[0] - gitCoAuthors := gitutil.ParseCoAuthorsFromBody(string(gitBodyBytes)) + gitAuthorEmail := strings.SplitN(string(meta["git-author-email"]), "\n", 2)[0] + gitAuthorName := strings.SplitN(string(meta["git-author-name"]), "\n", 2)[0] + gitCoAuthors := gitutil.ParseCoAuthorsFromBody(string(meta["git-body"])) var gitBranches2 []string @@ -509,11 +420,11 @@ func (gr *gitResolver) resolveGitProject( gitBranches2 = []string{gitRef} } } else { - gitBranches2 = []string{strings.SplitN(string(gitDefaultBranchBytes), "\n", 2)[0]} + gitBranches2 = []string{strings.SplitN(string(meta["git-default-branch"]), "\n", 2)[0]} } } - gitTags := strings.SplitN(string(gitTagsBytes), "\n", 2) + gitTags := strings.SplitN(string(meta["git-tags"]), "\n", 2) var gitTags2 []string @@ -523,9 +434,9 @@ func (gr *gitResolver) resolveGitProject( } } - gitCommitterTs := strings.SplitN(string(gitCommitterTsBytes), "\n", 2)[0] - gitAuthorTs := strings.SplitN(string(gitAuthorTsBytes), "\n", 2)[0] - gitRefs := strings.Split(string(gitRefsBytes), "\n") + gitCommitterTs := strings.SplitN(string(meta["git-committer-ts"]), "\n", 2)[0] + gitAuthorTs := strings.SplitN(string(meta["git-author-ts"]), "\n", 2)[0] + gitRefs := strings.Split(string(meta["git-refs"]), "\n") var gitRefs2 []string @@ -560,7 +471,7 @@ func (gr *gitResolver) resolveGitProject( authorName: gitAuthorName, coAuthors: gitCoAuthors, refs: gitRefs2, - earthfilePaths: strings.Split(strings.TrimSpace(string(earthfilePathsRaw)), "\n"), + earthfilePaths: strings.Split(strings.TrimSpace(string(meta["Earthfile-paths"])), "\n"), state: pllb.Git( gitURL, gitHash, diff --git a/util/gitutil/detectgit.go b/util/gitutil/detectgit.go index cd207d5288..832396756d 100644 --- a/util/gitutil/detectgit.go +++ b/util/gitutil/detectgit.go @@ -277,55 +277,34 @@ func detectGitBaseDir(ctx context.Context, dir string) (string, error) { return strings.SplitN(outStr, "\n", 2)[0], nil } -func detectGitHash(ctx context.Context, dir string) (string, error) { - cmd := exec.CommandContext(ctx, "git", "rev-parse", "HEAD") +func gitRevParse(ctx context.Context, dir string, sentinel error, args ...string) (string, error) { + revParseArgs := append([]string{"rev-parse"}, args...) + cmd := exec.CommandContext(ctx, "git", revParseArgs...) //nolint:gosec cmd.Dir = dir out, err := cmd.Output() if err != nil { - return "", errors.Wrapf(ErrCouldNotDetectGitHash, "returned error %s: %s", err.Error(), string(out)) + return "", errors.Wrapf(sentinel, "returned error %s: %s", err.Error(), string(out)) } outStr := string(out) if outStr == "" { - return "", errors.Wrapf(ErrCouldNotDetectGitHash, "no remote origin url output") + return "", errors.Wrapf(sentinel, "no output") } return strings.SplitN(outStr, "\n", 2)[0], nil } -func detectGitShortHash(ctx context.Context, dir string) (string, error) { - cmd := exec.CommandContext(ctx, "git", "rev-parse", "--short=8", "HEAD") - cmd.Dir = dir - - out, err := cmd.Output() - if err != nil { - return "", errors.Wrapf(ErrCouldNotDetectGitShortHash, "returned error %s: %s", err.Error(), string(out)) - } - - outStr := string(out) - if outStr == "" { - return "", errors.Wrapf(ErrCouldNotDetectGitShortHash, "no remote origin url output") - } +func detectGitHash(ctx context.Context, dir string) (string, error) { + return gitRevParse(ctx, dir, ErrCouldNotDetectGitHash, "HEAD") +} - return strings.SplitN(outStr, "\n", 2)[0], nil +func detectGitShortHash(ctx context.Context, dir string) (string, error) { + return gitRevParse(ctx, dir, ErrCouldNotDetectGitShortHash, "--short=8", "HEAD") } func detectGitContentHash(ctx context.Context, dir string) (string, error) { - cmd := exec.CommandContext(ctx, "git", "rev-parse", "HEAD^{tree}") - cmd.Dir = dir - - out, err := cmd.Output() - if err != nil { - return "", errors.Wrapf(ErrCouldNotDetectGitContentHash, "returned error %s: %s", err.Error(), string(out)) - } - - outStr := string(out) - if outStr == "" { - return "", errors.Wrapf(ErrCouldNotDetectGitContentHash, "no git tree hash output") - } - - return strings.SplitN(outStr, "\n", 2)[0], nil + return gitRevParse(ctx, dir, ErrCouldNotDetectGitContentHash, "HEAD^{tree}") } func detectGitBranch(ctx context.Context, dir, gitBranchOverride string) ([]string, error) { From 09c22ec8aa83f33f0e488594258cd04604095f4d Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Fri, 27 Feb 2026 08:31:21 +0000 Subject: [PATCH 5/6] feat: Allow EARTH_ prefix to work for all git vars Signed-off-by: Giles Cope --- docs/caching/caching-in-earthfiles.md | 2 +- docs/earthfile/builtin-args.md | 28 ++++++++++++------------- variables/builtin.go | 17 +++++++++++++++ variables/reserved/names.go | 30 +++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 15 deletions(-) diff --git a/docs/caching/caching-in-earthfiles.md b/docs/caching/caching-in-earthfiles.md index d58a3a51a5..2f03b8f5c2 100644 --- a/docs/caching/caching-in-earthfiles.md +++ b/docs/caching/caching-in-earthfiles.md @@ -201,7 +201,7 @@ In Earthly, like in Dockerfiles, ARGs declared in Earthfiles also behave as envi For this reason, it is best to declare ARGs as late as possible within the target they are used in, and try to avoid declaring `--global` ARGs as much as possible. If an ARG is not yet declared, it will not influence the cache state of a layer, allowing for more cache hits. Limiting the scope of ARGs as much as possible will yield better cache performance. -Watch out especially for ARGs that change often, such as the built-in ARG `EARTHLY_GIT_HASH`. Declaring this ARG as late as possible in the build will cause less cache misses. If you need a git-derived identifier that doesn't change as often, consider `EARTH_GIT_CONTENT_HASH` instead — it only changes when the file tree actually changes, not on every commit. +Watch out especially for ARGs that change often, such as the built-in ARG `EARTH_GIT_HASH`. Declaring this ARG as late as possible in the build will cause less cache misses. If you need a git-derived identifier that doesn't change as often, consider `EARTH_GIT_CONTENT_HASH` instead — it only changes when the file tree actually changes, not on every commit. ### Secrets diff --git a/docs/earthfile/builtin-args.md b/docs/earthfile/builtin-args.md index adeb05d683..98c075f19a 100644 --- a/docs/earthfile/builtin-args.md +++ b/docs/earthfile/builtin-args.md @@ -48,20 +48,20 @@ RUN echo "The current target is $EARTHLY_TARGET" | Name | Description | Example value | Feature Flag | | ---- | ----------- | ------------- | ------------ | -| `EARTHLY_GIT_AUTHOR` | The git author detected within the build context directory. If no git directory is detected, then the value is an empty string. This is currently the author's email address but the feature flag adds the name as well | `john@example.com` (or `John Doe ` with flag turned on) | `--earthly-git-author-individual-args` | -| `EARTHLY_GIT_AUTHOR_EMAIL` | The git author email detected within the build context directory. If no git directory is detected, then the value is an empty string. | `john@example.com` | `--earthly-git-author-individual-args` | -| `EARTHLY_GIT_AUTHOR_NAME` | The git author name detected within the build context directory. If no git directory is detected, then the value is an empty string. | `John Doe` | `--earthly-git-author-individual-args` | -| `EARTHLY_GIT_CO_AUTHORS` | The git co-authors detected within the build context directory, separated by space. If no git directory is detected, then the value is an empty string. | `Jane Doe ` | | -| `EARTHLY_GIT_COMMIT_AUTHOR_TIMESTAMP` | The author timestamp, as unix seconds, of the git commit detected within the build context directory. If no git directory is detected, then the value is an empty string. | `1626881847` | | -| `EARTHLY_GIT_BRANCH` | The git branch of the git commit detected within the build context directory. If no git directory is detected, then the value is an empty string. | `main` | | -| `EARTHLY_GIT_COMMIT_TIMESTAMP` | The committer timestamp, as unix seconds, of the git commit detected within the build context directory. If no git directory is detected, then the value is an empty string. | `1626881847` | | -| `EARTH_GIT_CONTENT_HASH` | The git tree hash (`git rev-parse HEAD^{tree}`) detected within the build context directory. Unlike `EARTHLY_GIT_HASH`, this is content-addressable: it remains stable across amends, rebases, or cherry-picks that don't change the file tree. If no git directory is detected, then the value is an empty string. | `aaa96ced2d9a1c8e72c56b253a0e2fe78393feb7` | | -| `EARTHLY_GIT_HASH` | The git hash detected within the build context directory. If no git directory is detected, then the value is an empty string. Take care when using this arg, as the frequently changing git hash may be cause for not using the cache. | `41cb5666ade67b29e42bef121144456d3977a67a` | | -| `EARTHLY_GIT_ORIGIN_URL` | The git URL detected within the build context directory. If no git directory is detected, then the value is an empty string. Please note that this may be inconsistent, depending on whether an HTTPS or SSH URL was used. | `git@github.com:bar/buz.git` or `https://github.com/bar/buz.git` | | -| `EARTHLY_GIT_PROJECT_NAME` | The git project name from within the git URL detected within the build context directory. If no git directory is detected, then the value is an empty string. | `bar/buz` | | -| `EARTHLY_GIT_REFS` | The git references of the git commit detected within the build context directory, separated by space. If no git directory is detected, then the value is an empty string. | `issue-2735-git-ref main` | | -| `EARTHLY_GIT_SHORT_HASH` | The first 8 characters of the git hash detected within the build context directory. If no git directory is detected, then the value is an empty string. Take care when using this arg, as the frequently changing git hash may be cause for not using the cache. | `41cb5666` | | -| `EARTHLY_SOURCE_DATE_EPOCH` | The timestamp, as unix seconds, of the git commit detected within the build context directory. If no git directory is detected, then the value is `0` (the unix epoch) | `1626881847`, `0` | | +| `EARTH_GIT_AUTHOR` | The git author detected within the build context directory. If no git directory is detected, then the value is an empty string. This is currently the author's email address but the feature flag adds the name as well | `john@example.com` (or `John Doe ` with flag turned on) | `--earthly-git-author-individual-args` | +| `EARTH_GIT_AUTHOR_EMAIL` | The git author email detected within the build context directory. If no git directory is detected, then the value is an empty string. | `john@example.com` | `--earthly-git-author-individual-args` | +| `EARTH_GIT_AUTHOR_NAME` | The git author name detected within the build context directory. If no git directory is detected, then the value is an empty string. | `John Doe` | `--earthly-git-author-individual-args` | +| `EARTH_GIT_BRANCH` | The git branch of the git commit detected within the build context directory. If no git directory is detected, then the value is an empty string. | `main` | | +| `EARTH_GIT_CO_AUTHORS` | The git co-authors detected within the build context directory, separated by space. If no git directory is detected, then the value is an empty string. | `Jane Doe ` | | +| `EARTH_GIT_COMMIT_AUTHOR_TIMESTAMP` | The author timestamp, as unix seconds, of the git commit detected within the build context directory. If no git directory is detected, then the value is an empty string. | `1626881847` | | +| `EARTH_GIT_COMMIT_TIMESTAMP` | The committer timestamp, as unix seconds, of the git commit detected within the build context directory. If no git directory is detected, then the value is an empty string. | `1626881847` | | +| `EARTH_GIT_CONTENT_HASH` | The git tree hash (`git rev-parse HEAD^{tree}`) detected within the build context directory. Unlike `EARTH_GIT_HASH`, this is content-addressable: it remains stable across amends, rebases, or cherry-picks that don't change the file tree. If no git directory is detected, then the value is an empty string. | `aaa96ced2d9a1c8e72c56b253a0e2fe78393feb7` | | +| `EARTH_GIT_HASH` | The git hash detected within the build context directory. If no git directory is detected, then the value is an empty string. Take care when using this arg, as the frequently changing git hash may be cause for not using the cache. | `41cb5666ade67b29e42bef121144456d3977a67a` | | +| `EARTH_GIT_ORIGIN_URL` | The git URL detected within the build context directory. If no git directory is detected, then the value is an empty string. Please note that this may be inconsistent, depending on whether an HTTPS or SSH URL was used. | `git@github.com:bar/buz.git` or `https://github.com/bar/buz.git` | | +| `EARTH_GIT_PROJECT_NAME` | The git project name from within the git URL detected within the build context directory. If no git directory is detected, then the value is an empty string. | `bar/buz` | | +| `EARTH_GIT_REFS` | The git references of the git commit detected within the build context directory, separated by space. If no git directory is detected, then the value is an empty string. | `issue-2735-git-ref main` | | +| `EARTH_GIT_SHORT_HASH` | The first 8 characters of the git hash detected within the build context directory. If no git directory is detected, then the value is an empty string. Take care when using this arg, as the frequently changing git hash may be cause for not using the cache. | `41cb5666` | | +| `EARTH_SOURCE_DATE_EPOCH` | The timestamp, as unix seconds, of the git commit detected within the build context directory. If no git directory is detected, then the value is `0` (the unix epoch) | `1626881847`, `0` | | ### Platform-related args diff --git a/variables/builtin.go b/variables/builtin.go index ad36c516ba..d97e8e28ef 100644 --- a/variables/builtin.go +++ b/variables/builtin.go @@ -74,49 +74,66 @@ func BuiltinArgs( if gitMeta == nil { // Ensure SOURCE_DATE_EPOCH is always available ret.Add(arg.EarthlySourceDateEpoch, "0") + ret.Add(arg.EarthSourceDateEpoch, "0") return ret } // Populate git-related built-in args ret.Add(arg.EarthlyGitHash, gitMeta.Hash) + ret.Add(arg.EarthGitHash, gitMeta.Hash) ret.Add(arg.EarthlyGitShortHash, gitMeta.ShortHash) + ret.Add(arg.EarthGitShortHash, gitMeta.ShortHash) ret.Add(arg.EarthGitContentHash, gitMeta.ContentHash) branch := firstOrZero(gitMeta.Branch) ret.Add(arg.EarthlyGitBranch, branch) + ret.Add(arg.EarthGitBranch, branch) tag := firstOrZero(gitMeta.Tags) ret.Add(arg.EarthlyGitTag, tag) + ret.Add(arg.EarthGitTag, tag) ret.Add(arg.EarthlyGitOriginURL, gitMeta.RemoteURL) + ret.Add(arg.EarthGitOriginURL, gitMeta.RemoteURL) ret.Add(arg.EarthlyGitOriginURLScrubbed, stringutil.ScrubCredentials(gitMeta.RemoteURL)) + ret.Add(arg.EarthGitOriginURLScrubbed, stringutil.ScrubCredentials(gitMeta.RemoteURL)) ret.Add(arg.EarthlyGitProjectName, getProjectName(gitMeta.RemoteURL)) + ret.Add(arg.EarthGitProjectName, getProjectName(gitMeta.RemoteURL)) ret.Add(arg.EarthlyGitCommitTimestamp, gitMeta.CommitterTimestamp) + ret.Add(arg.EarthGitCommitTimestamp, gitMeta.CommitterTimestamp) if ftrs.GitCommitAuthorTimestamp { ret.Add(arg.EarthlyGitCommitAuthorTimestamp, gitMeta.AuthorTimestamp) + ret.Add(arg.EarthGitCommitAuthorTimestamp, gitMeta.AuthorTimestamp) } ret.Add(arg.EarthlySourceDateEpoch, max(gitMeta.CommitterTimestamp, "0")) + ret.Add(arg.EarthSourceDateEpoch, max(gitMeta.CommitterTimestamp, "0")) if ftrs.EarthlyGitAuthorArgs { ret.Add(arg.EarthlyGitAuthor, gitMeta.AuthorEmail) + ret.Add(arg.EarthGitAuthor, gitMeta.AuthorEmail) ret.Add(arg.EarthlyGitCoAuthors, strings.Join(gitMeta.CoAuthors, " ")) + ret.Add(arg.EarthGitCoAuthors, strings.Join(gitMeta.CoAuthors, " ")) } if ftrs.GitAuthorEmailNameArgs { if gitMeta.AuthorName != "" && gitMeta.AuthorEmail != "" { ret.Add(arg.EarthlyGitAuthor, fmt.Sprintf("%s <%s>", gitMeta.AuthorName, gitMeta.AuthorEmail)) + ret.Add(arg.EarthGitAuthor, fmt.Sprintf("%s <%s>", gitMeta.AuthorName, gitMeta.AuthorEmail)) } ret.Add(arg.EarthlyGitAuthorEmail, gitMeta.AuthorEmail) + ret.Add(arg.EarthGitAuthorEmail, gitMeta.AuthorEmail) ret.Add(arg.EarthlyGitAuthorName, gitMeta.AuthorName) + ret.Add(arg.EarthGitAuthorName, gitMeta.AuthorName) } if ftrs.GitRefs { ret.Add(arg.EarthlyGitRefs, strings.Join(gitMeta.Refs, " ")) + ret.Add(arg.EarthGitRefs, strings.Join(gitMeta.Refs, " ")) } return ret diff --git a/variables/reserved/names.go b/variables/reserved/names.go index 9326fc5d04..c33fe4f040 100644 --- a/variables/reserved/names.go +++ b/variables/reserved/names.go @@ -1,7 +1,22 @@ package reserved const ( + EarthGitAuthor = "EARTH_GIT_AUTHOR" + EarthGitAuthorEmail = "EARTH_GIT_AUTHOR_EMAIL" + EarthGitAuthorName = "EARTH_GIT_AUTHOR_NAME" + EarthGitBranch = "EARTH_GIT_BRANCH" + EarthGitCoAuthors = "EARTH_GIT_CO_AUTHORS" + EarthGitCommitAuthorTimestamp = "EARTH_GIT_COMMIT_AUTHOR_TIMESTAMP" + EarthGitCommitTimestamp = "EARTH_GIT_COMMIT_TIMESTAMP" EarthGitContentHash = "EARTH_GIT_CONTENT_HASH" + EarthGitHash = "EARTH_GIT_HASH" + EarthGitOriginURL = "EARTH_GIT_ORIGIN_URL" + EarthGitOriginURLScrubbed = "EARTH_GIT_ORIGIN_URL_SCRUBBED" + EarthGitProjectName = "EARTH_GIT_PROJECT_NAME" + EarthGitRefs = "EARTH_GIT_REFS" + EarthGitShortHash = "EARTH_GIT_SHORT_HASH" + EarthGitTag = "EARTH_GIT_TAG" + EarthSourceDateEpoch = "EARTH_SOURCE_DATE_EPOCH" EarthlyBuildSha = "EARTHLY_BUILD_SHA" EarthlyGitBranch = "EARTHLY_GIT_BRANCH" EarthlyGitCommitTimestamp = "EARTHLY_GIT_COMMIT_TIMESTAMP" @@ -47,7 +62,22 @@ var args map[string]struct{} func init() { args = map[string]struct{}{ + EarthGitAuthor: {}, + EarthGitAuthorEmail: {}, + EarthGitAuthorName: {}, + EarthGitBranch: {}, + EarthGitCoAuthors: {}, + EarthGitCommitAuthorTimestamp: {}, + EarthGitCommitTimestamp: {}, EarthGitContentHash: {}, + EarthGitHash: {}, + EarthGitOriginURL: {}, + EarthGitOriginURLScrubbed: {}, + EarthGitProjectName: {}, + EarthGitRefs: {}, + EarthGitShortHash: {}, + EarthGitTag: {}, + EarthSourceDateEpoch: {}, EarthlyBuildSha: {}, EarthlyGitBranch: {}, EarthlyGitCommitTimestamp: {}, From d8d03f841686919ea67e40a0965fa4b5c8b37de2 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Wed, 4 Mar 2026 19:23:16 +0000 Subject: [PATCH 6/6] chore: fmt Signed-off-by: Giles Cope --- variables/builtin.go | 1 + variables/reserved/names.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/variables/builtin.go b/variables/builtin.go index d97e8e28ef..91b467386a 100644 --- a/variables/builtin.go +++ b/variables/builtin.go @@ -75,6 +75,7 @@ func BuiltinArgs( // Ensure SOURCE_DATE_EPOCH is always available ret.Add(arg.EarthlySourceDateEpoch, "0") ret.Add(arg.EarthSourceDateEpoch, "0") + return ret } diff --git a/variables/reserved/names.go b/variables/reserved/names.go index c33fe4f040..9d2f73d260 100644 --- a/variables/reserved/names.go +++ b/variables/reserved/names.go @@ -67,7 +67,7 @@ func init() { EarthGitAuthorName: {}, EarthGitBranch: {}, EarthGitCoAuthors: {}, - EarthGitCommitAuthorTimestamp: {}, + EarthGitCommitAuthorTimestamp: {}, EarthGitCommitTimestamp: {}, EarthGitContentHash: {}, EarthGitHash: {},