Delivery Method
via controls how file changes are delivered to the target repository.
push (default)
Section titled “push (default)”Commits directly to the default branch. All file changes are bundled into a single atomic commit.
spec: via: push commit_message: "ci: sync shared files" # optional, auto-generated if omittedUse this for low-risk files (LICENSE, CODEOWNERS, security policies) or routine syncs of already-reviewed templates.
pull_request
Section titled “pull_request”Creates a branch, commits all files, and opens a pull request for review before merging.
spec: via: pull_request commit_message: "ci: sync shared files" # optional, auto-generated if omitted branch: gh-infra/sync-shared # optional, see below pr_title: "Sync shared files" # optional, defaults to commit_message pr_body: | # optional, supports Markdown Automated file sync by gh-infra. Updates CI workflows and shared config.Use this when changes need review — for example, CI workflows or Dockerfiles that could break builds.
If a pull request already exists for the branch, gh-infra updates it instead of creating a new one.
Pull request branch
Section titled “Pull request branch”Without branch, the branch is gh-infra/sync-<owner>-<name>: <name> is the repository for File and metadata.name for FileSet. An unnamed FileSet uses its sorted repository names joined by +, e.g. gh-infra/sync-babarot-gomi+gh-infra.
Each apply resets the branch to the default branch before committing, so two pull_request resources that would use the same branch on the same repository are rejected by validate, plan, and apply. This happens when both set the same branch, or neither sets one and they share a name: two File manifests for one repository, or unnamed FileSets that list the same repositories. Set a distinct branch, or a distinct metadata.name on a FileSet.
Related fields
Section titled “Related fields”| Field | Used by | Default | Description |
|---|---|---|---|
commit_message |
both | auto | Commit message for the sync commit |
branch |
pull_request |
gh-infra/sync-<owner>-<name> |
Branch name for the pull request (see Pull request branch) |
pr_title |
pull_request |
value of commit_message |
Pull request title |
pr_body |
pull_request |
auto | Pull request body (supports Markdown) |
File modes
Section titled “File modes”Set executable on a file entry to control its executable bit, for example for git hooks and scripts:
spec: files: - path: .githooks/pre-commit source: ./templates/pre-commit executable: true| Value | Result |
|---|---|
true |
The file is committed with mode 100755 |
false |
The file is committed with mode 100644, clearing an existing executable bit |
| (omitted) | An existing file keeps its current mode; a new file is created as 100644 |
plan compares the mode as well as the content, so adding executable: true to a file whose content is already in sync shows a mode change (mode 100644 → 100755). With reconcile: create_only, the mode is applied only when the file is created.
Only regular files can be marked: a symlink or submodule at the path is reported as an error. Executable files cannot be created in an empty repository, because the Contents API used there cannot set a mode; create the first commit without them.
Templated messages
Section titled “Templated messages”commit_message, pr_title, and pr_body accept <% %> expressions, rendered per target repository at apply time. plan and validate also render them, so a template error is reported before anything is written:
| Variable | Value |
|---|---|
<% .Repo.Name %>, <% .Repo.Owner %>, <% .Repo.FullName %> |
Target repository, as in Templating |
<% .Source.URL %> |
Value of the GH_INFRA_SOURCE_URL environment variable, or empty if unset |
.Vars is not available in messages. A multi-line commit_message is split Git-style: the first line becomes the commit headline and the rest becomes the body. When pr_title is omitted, only the headline is used as the title.
Link each sync commit back to the change that triggered it by setting GH_INFRA_SOURCE_URL in CI, for example to ${{ github.event.head_commit.url }} in a workflow that applies on push (see Apply on merge). plan and validate render the messages with an empty URL to check them. Wrap the line in if so nothing is emitted when the variable is unset:
spec: commit_message: |- ci: sync CI workflow for <% .Repo.Name %>
<% if .Source.URL %>Source: <% .Source.URL %><% end %>Empty repositories
Section titled “Empty repositories”For repositories with no commits yet, gh-infra falls back to the Contents API. Each file becomes a separate commit. With via: pull_request there is no branch to open a pull request from, so the files are pushed to the default branch; plan shows via push for that repository, including one the same apply creates. See GraphQL Commit API vs Contents API for details.