Skip to content

Commit

Permalink
EARTHLY_GIT_AUTHOR and CO_AUTHOR args (#2260)
Browse files Browse the repository at this point in the history
Adds `EARTHLY_GIT_AUTHOR` and `EARTHLY_GIT_CO_AUTHORS` args, when the
`--earthly-git-author-args` feature flag is set.

Signed-off-by: Alex Couture-Beil <alex@earthly.dev>
  • Loading branch information
alexcb committed Oct 12, 2022
1 parent 47f6973 commit e20c104
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 4 deletions.
27 changes: 25 additions & 2 deletions buildcontext/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ type resolvedGitProject struct {
// tags is the git tags.
tags []string
// ts is the git commit timestamp.
ts string
ts string
author string
coAuthors []string
// state is the state holding the git files.
state pllb.State
}
Expand Down Expand Up @@ -149,6 +151,8 @@ func (gr *gitResolver) resolveEarthProject(ctx context.Context, gwClient gwclien
Branch: rgp.branches,
Tags: rgp.tags,
Timestamp: rgp.ts,
Author: rgp.author,
CoAuthors: rgp.coAuthors,
},
Features: localBuildFile.ftrs,
}, nil
Expand Down Expand Up @@ -194,7 +198,10 @@ func (gr *gitResolver) resolveGitProject(ctx context.Context, gwClient gwclient.
"git rev-parse --short=8 HEAD >/dest/git-short-hash ; " +
"git rev-parse --abbrev-ref HEAD >/dest/git-branch || touch /dest/git-branch ; " +
"git describe --exact-match --tags >/dest/git-tags || touch /dest/git-tags ; " +
"git log -1 --format=%ct >/dest/git-ts || touch /dest/git-ts",
"git log -1 --format=%ct >/dest/git-ts || touch /dest/git-ts ; " +
"git log -1 --format=%ae >/dest/git-author || touch /dest/git-author ; " +
"git log -1 --format=%b >/dest/git-body || touch /dest/git-body ; " +
"",
}),
llb.Dir("/git-src"),
llb.ReadonlyRootFS(),
Expand Down Expand Up @@ -241,10 +248,24 @@ func (gr *gitResolver) resolveGitProject(ctx context.Context, gwClient gwclient.
if err != nil {
return nil, errors.Wrap(err, "read git-ts")
}
gitAuthorBytes, err := gitMetaRef.ReadFile(ctx, gwclient.ReadRequest{
Filename: "git-author",
})
if err != nil {
return nil, errors.Wrap(err, "read git-author")
}
gitBodyBytes, err := gitMetaRef.ReadFile(ctx, gwclient.ReadRequest{
Filename: "git-body",
})
if err != nil {
return nil, errors.Wrap(err, "read git-body")
}

gitHash := strings.SplitN(string(gitHashBytes), "\n", 2)[0]
gitShortHash := strings.SplitN(string(gitShortHashBytes), "\n", 2)[0]
gitBranches := strings.SplitN(string(gitBranchBytes), "\n", 2)
gitAuthor := strings.SplitN(string(gitAuthorBytes), "\n", 2)[0]
gitCoAuthors := gitutil.ParseCoAuthorsFromBody(string(gitBodyBytes))
var gitBranches2 []string
for _, gitBranch := range gitBranches {
if gitBranch != "" {
Expand Down Expand Up @@ -274,6 +295,8 @@ func (gr *gitResolver) resolveGitProject(ctx context.Context, gwClient gwclient.
branches: gitBranches2,
tags: gitTags2,
ts: gitTs,
author: gitAuthor,
coAuthors: gitCoAuthors,
state: pllb.Git(
gitURL,
gitHash,
Expand Down
1 change: 1 addition & 0 deletions features/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Features struct {
CheckDuplicateImages bool `long:"check-duplicate-images" description:"check for duplicate images during output"`
EarthlyVersionArg bool `long:"earthly-version-arg" description:"includes EARTHLY_VERSION and EARTHLY_BUILD_SHA ARGs"`
EarthlyLocallyArg bool `long:"earthly-locally-arg" description:"includes EARTHLY_LOCALLY ARG"`
EarthlyGitAuthorArgs bool `long:"earthly-git-author-args" description:"includes EARTHLY_GIT_AUTHOR and EARTHLY_GIT_CO_AUTHORS ARGs"`
ExplicitGlobal bool `long:"explicit-global" description:"require base target args to have explicit settings to be considered global args"`
UseCacheCommand bool `long:"use-cache-command" description:"allow use of CACHE command in Earthfiles"`
UseHostCommand bool `long:"use-host-command" description:"allow use of HOST command in Earthfiles"`
Expand Down
5 changes: 4 additions & 1 deletion tests/git-metadata/Earthfile
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ git config --global user.name \"test name\"
git clone git@git.example.com:testuser/repo.git
mv Earthfile repo/
git -C repo add Earthfile
git -C repo commit -m test
git -C repo commit -m \"test
Co-authored-by: Testy McTest <testy@earthly.dev>
Co-authored-by: Coverage McCover <cover@earthly.dev>\"
git -C repo push
export expectedsha=\$(git -C repo rev-parse HEAD)
Expand Down
11 changes: 10 additions & 1 deletion tests/git-metadata/test.earth
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
VERSION 0.6
VERSION --earthly-git-author-args 0.6

# Note: this is tests both locally and remotely
# the population of git metadata occurs in two **different** functions respectively:
# - gitutil/detectgit.go: Metadata (locally-reference)
# - buildcontext/git.go: resolveGitProject (remotely-referenced)

test-git-metadata:
FROM alpine
ARG --required expectedsha
ARG EARTHLY_GIT_SHORT_HASH
ARG EARTHLY_GIT_HASH
ARG EARTHLY_GIT_AUTHOR
ARG EARTHLY_GIT_CO_AUTHORS
RUN test "$EARTHLY_GIT_HASH" = "$expectedsha"
RUN test -n "$EARTHLY_GIT_SHORT_HASH"
RUN echo "$EARTHLY_GIT_HASH" | grep "$EARTHLY_GIT_SHORT_HASH"
RUN test "$EARTHLY_GIT_AUTHOR" = "onlyspammersemailthis@earthly.dev"
RUN test "$EARTHLY_GIT_CO_AUTHORS" = "testy@earthly.dev cover@earthly.dev"
86 changes: 86 additions & 0 deletions util/gitutil/detectgit.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ type GitMetadata struct {
Branch []string
Tags []string
Timestamp string
Author string
CoAuthors []string
}

// Metadata performs git metadata detection on the provided directory.
Expand Down Expand Up @@ -92,6 +94,16 @@ func Metadata(ctx context.Context, dir string) (*GitMetadata, error) {
retErr = err
// Keep going.
}
author, err := detectGitAuthor(ctx, dir)
if err != nil {
retErr = err
// Keep going.
}
coAuthors, err := detectGitCoAuthors(ctx, dir)
if err != nil {
retErr = err
// Keep going.
}

relDir, isRel, err := gitRelDir(baseDir, dir)
if err != nil {
Expand All @@ -111,6 +123,8 @@ func Metadata(ctx context.Context, dir string) (*GitMetadata, error) {
Branch: branch,
Tags: tags,
Timestamp: timestamp,
Author: author,
CoAuthors: coAuthors,
}, retErr
}

Expand All @@ -126,6 +140,8 @@ func (gm *GitMetadata) Clone() *GitMetadata {
Branch: gm.Branch,
Tags: gm.Tags,
Timestamp: gm.Timestamp,
Author: gm.Author,
CoAuthors: gm.CoAuthors,
}
}

Expand Down Expand Up @@ -251,17 +267,87 @@ func detectGitTags(ctx context.Context, dir string) ([]string, error) {
func detectGitTimestamp(ctx context.Context, dir string) (string, error) {
cmd := exec.CommandContext(ctx, "git", "log", "-1", "--format=%ct")
cmd.Dir = dir
cmd.Stderr = nil // force capture of stderr on errors
out, err := cmd.Output()
if err != nil {
exitError, ok := err.(*exec.ExitError)
if ok && strings.Contains(string(exitError.Stderr), "does not have any commits yet") {
return "", nil
}
return "", errors.Wrap(err, "detect git timestamp")
}
outStr := string(out)
if outStr == "" {
return "", nil
}
return strings.SplitN(outStr, "\n", 2)[0], nil
}

func detectGitAuthor(ctx context.Context, dir string) (string, error) {
cmd := exec.CommandContext(ctx, "git", "log", "-1", "--format=%ae")
cmd.Dir = dir
cmd.Stderr = nil // force capture of stderr on errors
out, err := cmd.Output()
if err != nil {
exitError, ok := err.(*exec.ExitError)
if ok && strings.Contains(string(exitError.Stderr), "does not have any commits yet") {
return "", nil
}
return "", errors.Wrap(err, "detect git author")
}
outStr := string(out)
if outStr == "" {
return "", nil
}
return strings.SplitN(outStr, "\n", 2)[0], nil
}

func detectGitCoAuthors(ctx context.Context, dir string) ([]string, error) {
cmd := exec.CommandContext(ctx, "git", "log", "-1", "--format=%b")
cmd.Dir = dir
cmd.Stderr = nil // force capture of stderr on errors
out, err := cmd.Output()
if err != nil {
exitError, ok := err.(*exec.ExitError)
if ok && strings.Contains(string(exitError.Stderr), "does not have any commits yet") {
return nil, nil
}
if out != nil && strings.Contains(string(out), "does not have any commits yet") {
return nil, nil
}
return nil, errors.Wrap(err, "detect git co-authors")
}
return ParseCoAuthorsFromBody(string(out)), nil
}

// ParseCoAuthorsFromBody returns a list of coauthor emails from a git body
func ParseCoAuthorsFromBody(body string) []string {
coAuthors := []string{}
coAuthorsSeen := map[string]struct{}{}
for _, s := range strings.Split(body, "\n") {
s = strings.TrimSpace(s)
splits := strings.Split(s, " ")
n := len(splits)
if n > 2 {
if splits[0] == "Co-authored-by:" {
email := splits[n-1]
n = len(email)
if n > 2 {
if email[0] == '<' && email[n-1] == '>' {
email = email[1:(n - 1)]
_, seen := coAuthorsSeen[email]
if !seen {
coAuthors = append(coAuthors, email)
coAuthorsSeen[email] = struct{}{}
}
}
}
}
}
}
return coAuthors
}

// gitRelDir returns the relative path from git root (where .git directory locates in the project)
// This function validates the input data (basePath, path) as well.
func gitRelDir(basePath string, path string) (string, bool, error) {
Expand Down
4 changes: 4 additions & 0 deletions variables/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ func BuiltinArgs(target domain.Target, platr *platutil.Resolver, gitMeta *gituti
} else {
ret.AddInactive(arg.EarthlySourceDateEpoch, gitMeta.Timestamp)
}
if ftrs.EarthlyGitAuthorArgs {
ret.AddInactive(arg.EarthlyGitAuthor, gitMeta.Author)
ret.AddInactive(arg.EarthlyGitCoAuthors, strings.Join(gitMeta.CoAuthors, " "))
}
} else {
// Ensure SOURCE_DATE_EPOCH is always available
ret.AddInactive(arg.EarthlySourceDateEpoch, "0")
Expand Down
4 changes: 4 additions & 0 deletions variables/reserved/names.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const (
EarthlyGitOriginURL = "EARTHLY_GIT_ORIGIN_URL"
EarthlyGitOriginURLScrubbed = "EARTHLY_GIT_ORIGIN_URL_SCRUBBED"
EarthlyGitProjectName = "EARTHLY_GIT_PROJECT_NAME"
EarthlyGitAuthor = "EARTHLY_GIT_AUTHOR"
EarthlyGitCoAuthors = "EARTHLY_GIT_CO_AUTHORS"
EarthlyGitShortHash = "EARTHLY_GIT_SHORT_HASH"
EarthlyGitTag = "EARTHLY_GIT_TAG"
EarthlyLocally = "EARTHLY_LOCALLY"
Expand Down Expand Up @@ -41,6 +43,8 @@ func init() {
EarthlyBuildSha: struct{}{},
EarthlyGitBranch: struct{}{},
EarthlyGitCommitTimestamp: struct{}{},
EarthlyGitAuthor: struct{}{},
EarthlyGitCoAuthors: struct{}{},
EarthlyGitHash: struct{}{},
EarthlyGitOriginURL: struct{}{},
EarthlyGitOriginURLScrubbed: struct{}{},
Expand Down

0 comments on commit e20c104

Please sign in to comment.