Skip to content

DataDog/commit-headless

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

31 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

commit-headless

A binary tool and GitHub Action for creating signed commits from headless workflows

For the Action, please see the action branch and the associated action/ release tags. For example usage, see Examples.

commit-headless is focused on turning local commits into signed commits on the remote. It does this using the GitHub API, more specifically the createCommitOnBranch mutation. When commits are created using the API (instead of via git push), the commits will be signed and verified by GitHub on behalf of the owner of the credentials used to access the API.

NOTE: One limitation of creating commits using the GraphQL API is that it does not expose any mechanism to set or change file modes. It merely takes the file contents, base64 encoded. This means that if you rely on commit-headless to push binary files (or executable scripts), the file in the resulting commit will not retain that executable bit.

Usage

There are two ways to create signed headless commits with this tool: push and commit.

Both of these commands take a target owner/repository (eg, --target/-T DataDog/commit-headless) and remote branch name (eg, --branch bot-branch) as required flags and expect to find a GitHub token in one of the following environment variables:

  • HEADLESS_TOKEN
  • GITHUB_TOKEN
  • GH_TOKEN

In normal usage, commit-headless will print only the reference to the last commit created on the remote, allowing this to easily be captured in a script.

More on the specifics for each command below. See also: commit-headless <command> --help

Specifying the expected head commit

When creating remote commits via API, commit-headless must specify the "expected head sha" of the remote branch. By default, commit-headless will query the GitHub API to get the current HEAD commit of the remote branch and use that as the "expected head sha". This introduces some risk, especially for active branches or long running jobs, as a new commit introduced after the job starts will not be considered when pushing the new commits. The commit itself will not be replaced, but the changes it introduces may be lost.

For example, consider an auto-formatting job. It runs gofmt over the entire codebase. If the job starts on commit A and formats a file main.go, and while the job is running the branch gains commit B, which adds new changes to main.go, when the lint job finishes the formatted version of main.go from commit A will be pushed to the remote, and overwrite the changes to main.go introduced in commit B.

You can avoid this by specifying --head-sha. This will skip auto discovery of the remote branch HEAD and instead require that the remote branch HEAD matches the value of --head-sha. If the remote branch HEAD does not match --head-sha, the push will fail (which is likely what you want).

Creating a new branch

Note that, by default, both of these commands expect the remote branch to already exist. If your workflow primarily works on new branches, you should additionally add the --create-branch flag and supply a commit hash to use as a branch point via --head-sha. With this flag, commit-headless will create the branch on GitHub from that commit hash if it doesn't already exist.

Example: commit-headless <command> [flags...] --head-sha=$(git rev-parse main HEAD) --create-branch ...

commit-headless push

In addition to the required target and branch flags, the push command expects a list of commit hashes as arguments or a list of commit hashes in reverse chronological order (newest first) on standard input.

It will iterate over the supplied commits, extract the set of changed files and commit message, then craft new remote commits corresponding to each local commit.

The remote commit will have the original commit message, with "Co-authored-by" trailer for the original commit author.

You can use commit-headless push via:

commit-headless push [flags...] HASH1 HASH2 HASH3 ...

Or, using git log (note --oneline):

git log --oneline main.. | commit-headless push [flags...]

commit-headless commit

This command is more geared for creating single commits at a time. It takes a list of files to commit changes to, and those files will either be updated/added or deleted in a single commit.

Note that you cannot delete a file without also adding --force for safety reasons.

Usage example:

# Commit changes to these two files
commit-headless commit [flags...] -- README.md .gitlab-ci.yml

# Remove a file, add another one, and commit
rm file/i/do/not/want
echo "hello" > hi-there.txt
commit-headless commit [flags...] --force -- hi-there.txt file/i/do/not/want

# Commit a change with a custom message
commit-headless commit [flags...] -m"ran a pipeline" -- output.txt

Try it!

You can easily try commit-headless locally. Create a commit with a different author (to demonstrate how commit-headless attributes changes to the original author), and run it with a GitHub token.

For example, create a commit locally and push it to a new branch using the current branch as the branch point:

cd ~/Code/repo
echo "bot commit here" >> README.md
git add README.md
git commit --author='A U Thor <[email protected]>' --message="test bot commit"
# Assuming a github token in $GITHUB_TOKEN or $HEADLESS_TOKEN
commit-headless push \
    --target=owner/repo \
    --branch=bot-branch \
    --head-sha="$(git rev-parse HEAD^)" \ # use the previous commit as our branch point
    --create-branch \
    "$(git rev-parse HEAD)" # push the commit we just created

Action Releases

On a merge to main, if there's not already a tagged release for the current version (in version.go), a new tag will be created on the action branch.

The action branch contains prebuilt binaries of commit-headless to avoid having to use Docker based (composite) actions, or to avoid having to download the binary when the action runs.

Because the workflow uses the rendered action (and the built binary) to create the commit to the action branch we are fairly safe from releasing a broken version of the action.

Assuming the previous step works, the workflow will then create a tag of the form action/vVERSION.

For more on the action release, see the workflow.

About

A binary tool and GitHub action for creating signed commits from headless workflows

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published