Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion src/components/Tables/ConfigOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,20 @@ const valueTypeLinks: { [key: string]: string } = {
};

const valueTypeFormatLinks: { [key: string]: string } = {
template: '/configuration/data-types#template',
template: '/configuration/data-types#legacy-template',
'date-time': '/configuration/data-types#timestamp',
duration: '/configuration/data-types#duration',
};

// A simple-template field carries a per-set title (e.g. "Message template"); render
// the title as the field type, linking to the matching data-types section.
const simpleTemplateTitleLinks: { [key: string]: string } = {
'Message template': '/configuration/data-types#message-template',
'Copy title template': '/configuration/data-types#copy-title-template',
'Copy body template': '/configuration/data-types#copy-body-template',
'Workflow input template': '/configuration/data-types#workflow-input-template',
};

export type OptionDefinitionRef = string;

export interface OptionDefinitionProperties {
Expand Down Expand Up @@ -189,6 +198,19 @@ export function getValueType(schema: object, definition: any): React.ReactElemen
);
} else if ('const' in definition) {
valueType = <HighlightCode>{definition.const}</HighlightCode>;
} else if (definition.format === 'simple-template') {
// A simple-template field IS a named data type: show its title and link to the
// matching data-types section, like Template/Timestamp/Duration.
const title: string = definition.title ?? 'simple-template';
const titleLink = simpleTemplateTitleLinks[title];
valueType =
titleLink !== undefined ? (
<a color="primary" style={{ textDecoration: 'underline' }} href={titleLink}>
{title}
</a>
) : (
<HighlightCode>{title}</HighlightCode>
);
} else if ('format' in definition) {
const formatLink = valueTypeFormatLinks[definition.format];
if (formatLink !== undefined) {
Expand All @@ -198,6 +220,13 @@ export function getValueType(schema: object, definition: any): React.ReactElemen
</a>
);
}
} else if (
definition.additionalProperties &&
typeof definition.additionalProperties === 'object'
) {
// A map field (e.g. github_actions inputs): show "map of <value type>" so the
// templated value type still links to its data-types section.
valueType = <>map of {getValueType(schema, definition.additionalProperties)}</>;
} else {
valueType = <HighlightCode>{definition.type}</HighlightCode>;
}
Expand Down
18 changes: 16 additions & 2 deletions src/components/Tables/OptionsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as yaml from 'js-yaml';

import configSchema from '../../util/sanitizedConfigSchema';
import { extractTemplateVariables } from '../../util/templateVariables';
import Badge from '../Badge/Badge';
import {
ConfigSchema,
Expand Down Expand Up @@ -50,6 +51,19 @@ export function OptionsTableBase(
: '';
const defaultIsMultiline = hasDefault && defaultDump.includes('\n');
const isDeprecated = Boolean(definition.deprecated);
// A simple-template field's description carries an "Allowed variables: …"
// list. Once the schema publishes x-mergify-template-variables, the data
// type renders a richer <TemplateVariablesTable>, so the duplicate list is
// stripped here. Until that annotation is synced in, keep the list — it is
// the only variable reference readers have.
const rawDescription = (definition as OptionDefinition).description;
const hasPublishedVariables =
definition.format === 'simple-template' &&
extractTemplateVariables(definition).length > 0;
const description =
hasPublishedVariables && typeof rawDescription === 'string'
? rawDescription.replace(/\s*Allowed variables:.*$/s, '').trim()
: rawDescription;
const id = `${idPrefix}${optionKey}`;
const href = `#${encodeURIComponent(id)}`;

Expand Down Expand Up @@ -86,11 +100,11 @@ export function OptionsTableBase(
<code>{defaultDump}</code>
</pre>
)}
{definition.description !== undefined && (
{description !== undefined && (
<div
className={styles.description}
dangerouslySetInnerHTML={{
__html: renderMarkdown(definition.description),
__html: renderMarkdown(description),
}}
/>
)}
Expand Down
46 changes: 46 additions & 0 deletions src/components/Tables/TemplateVariablesTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import configSchema from '../../util/sanitizedConfigSchema';
import { extractTemplateVariables } from '../../util/templateVariables';

import { ConfigSchema, Def } from './ConfigOptions';
import { renderMarkdown } from './utils';

interface TemplateVariablesTableProps extends Def {
field: string;
}

export default function TemplateVariablesTable({ def, field }: TemplateVariablesTableProps) {
const schema = configSchema as unknown as ConfigSchema;
const definition = schema.$defs[def]?.properties?.[field];
const variables = extractTemplateVariables(definition);

// Astro's React SSR rejects a component that conditionally returns null/undefined,
// so render an empty fragment (not null) when the field has no published variables
// — the graceful state before the engine schema with x-mergify-template-variables
// is synced into the docs.
if (variables.length === 0) {
return <></>;
}

return (
<div className="table-wrap">
<table>
<thead>
<tr>
<th>Variable</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{variables.map((variable) => (
<tr key={variable.name}>
<td>
<code>{`{{ ${variable.name} }}`}</code>
</td>
<td dangerouslySetInnerHTML={{ __html: renderMarkdown(variable.description) }} />
</tr>
))}
</tbody>
</table>
</div>
);
Comment thread
Copilot marked this conversation as resolved.
}
63 changes: 62 additions & 1 deletion src/content/docs/configuration/data-types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ description: The different data types you can find in Mergify configuration file
---

import OptionsTable from '../../../components/Tables/OptionsTable';
import TemplateVariablesTable from '../../../components/Tables/TemplateVariablesTable';

When using templates or conditions, data are made of different types. You will
find below the different data types that are available and exposed in Mergify
Expand Down Expand Up @@ -256,7 +257,67 @@ priority_rules:
priority: 550
```

## Template
## Templates

Some fields are rendered as templates before Mergify uses them. Most accept
*variable substitution* (short `{{ name }}` placeholders filled with pull
request data); a few still use the legacy [Jinja2](#legacy-template) language.

### Variable substitution

Several fields let you insert pull request data using variables:

```text
Thank you {{ author }} for your contribution!
```

renders to:

```text
Thank you jd for your contribution!
```

when the pull request author login is `jd`.

A variable is written as `{{ name }}` (surrounding spaces are optional). Every
other character is kept as-is. Each templated field accepts a specific set of
variables, documented as its data type below.

### Message template

Used by the [`comment`](/workflow/actions/comment),
[`review`](/workflow/actions/review) and [`close`](/workflow/actions/close)
message fields.

<TemplateVariablesTable def="CommentActionModel" field="message" />

### Copy title template

Used by the [`copy`](/workflow/actions/copy) and
[`backport`](/workflow/actions/backport) `title` field.

<TemplateVariablesTable def="CopyActionModel" field="title" />

### Copy body template

Used by the [`copy`](/workflow/actions/copy) and
[`backport`](/workflow/actions/backport) `body` field.

<TemplateVariablesTable def="CopyActionModel" field="body" />

### Workflow input template

Used by the [`github_actions`](/workflow/actions/github_actions) workflow input
values.

<TemplateVariablesTable def="GhaActionModelDispatch" field="inputs" />

### Legacy template

:::note
Jinja2 is deprecated for fields that support [variable
substitution](#variable-substitution) and stops working on 2026-09-30.
:::

The template data type is a regular string that is rendered using the [Jinja2
template language](https://jinja.palletsprojects.com/templates/).
Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/workflow/actions/assign.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pull requests that require their attention.
<ActionOptionsTable def="AssignActionModel"/>

As the list of users in `add_users` or `remove_users` is based on
[templates](/configuration/data-types#template), you can use, e.g.,
[templates](/configuration/data-types#legacy-template), you can use, e.g.,
`{{author}}` to assign the pull request to its author.

:::caution
Expand Down
24 changes: 5 additions & 19 deletions src/content/docs/workflow/actions/backport.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,6 @@ strings.

<ActionOptionsTable def="BackportActionModel" />

As the title and body are templates, you can leverage any pull request
attributes to use as content, e.g., `{{author}}`.

Note that the `commits` attribute here will be the list of cherry
picked commits.

On top of that, you can also use the following additional variables:

- `{{ destination_branch }}`: the name of the destination branch.

- `{{ cherry_pick_error }}`: the cherry pick error message if any (only
available in body).

<BackportLimitations />

## Examples
Expand Down Expand Up @@ -101,10 +88,11 @@ In this configuration, a pull request is backported when it has the label `backp
Then, when the backport is created and passes the check named
`continuous-integration`, it will be automatically merged.

### Implementing `-x` option
### Including the cherry-picked commits

If you are used to the `-x` option of `git cherry-pick` that includes which
commits has been cherry-picked, you can implement the same thing with Mergify:
To record which commits were cherry-picked (similar to the `-x` option of `git
cherry-pick`), include their SHAs in the backport body with the
`{{ cherry_picked_commits }}` variable:

```yaml
pull_request_rules:
Expand All @@ -116,9 +104,7 @@ pull_request_rules:
body: |
{{ body }}

{% for c in commits %}
(cherry picked from commit {{ c.sha }})
{% endfor %}
{{ cherry_picked_commits }}
branches:
- stable
```
Expand Down
13 changes: 0 additions & 13 deletions src/content/docs/workflow/actions/copy.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,6 @@ request will be copied. The branch names should be specified as strings.

<ActionOptionsTable def="CopyActionModel" />

As the title and body are templates, you can leverage any pull request
attributes to use as content, e.g., `{{author}}`.

Note that the `commits` attribute here will be the list of cherry
picked commits.

On top of that, you can also use the following additional variables:

- `{{ destination_branch }}`: the name of the destination branch.

- `{{ cherry_pick_error }}`: the cherry pick error message if any (only
available in body).

<CopyLimitations />

## Examples
Expand Down
3 changes: 2 additions & 1 deletion src/content/docs/workflow/actions/github_actions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ following rule.

Here, the `hello_world_workflow.yaml` workflow accepts two
inputs, which are defined as `name` and `age`. The `dynamic_workflow.yaml`
takes the [template](/configuration/data-types#template) input `author`.
takes the `author` input, set with [variable
substitution](/configuration/data-types#variable-substitution).

```yaml
pull_request_rules:
Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/workflow/actions/merge.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ commit_message_format:
### Migrating from `commit_message_template`

The legacy `commit_message_template` setting is a
[template](/configuration/data-types#template) that renders the entire
[template](/configuration/data-types#legacy-template) that renders the entire
commit message. `commit_message_format` covers the common patterns
declaratively, with predictable output and no template engine.

Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/workflow/actions/post_check.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ status of a pull request based on Mergify's evaluation.
<ActionOptionsTable def="PostCheckActionModel" />

As the `title` and `summary` are
[templates](/configuration/data-types#template), you can benefit from any [pull
[templates](/configuration/data-types#legacy-template), you can benefit from any [pull
request attributes](/configuration/conditions#attributes-list), e.g. `{{author}}`,
and also these additional variables:

Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/workflow/rule-syntax.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ pull_request_rules:
- conflict
```

The `{{author}}` placeholder is a [template](/configuration/data-types#template)
The `{{author}}` placeholder is a [template](/configuration/data-types#legacy-template)
that Mergify fills in with the pull request's data. Browse every available
action and its parameters in the [Actions catalog](/workflow/actions).

Expand Down
58 changes: 58 additions & 0 deletions src/util/templateVariables.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { describe, expect, it } from 'vitest';

import { extractTemplateVariables } from './templateVariables';

describe('extractTemplateVariables', () => {
it('reads variables from a top-level simple-template field', () => {
const definition = {
type: 'string',
format: 'simple-template',
'x-mergify-template-variables': [{ name: 'author', description: 'PR author login' }],
};
expect(extractTemplateVariables(definition)).toEqual([
{ name: 'author', description: 'PR author login' },
]);
});

it('finds variables inside an anyOf branch (optional field like comment.message)', () => {
const definition = {
anyOf: [
{
type: 'string',
format: 'simple-template',
'x-mergify-template-variables': [{ name: 'number', description: 'PR number' }],
},
{ type: 'null' },
],
};
expect(extractTemplateVariables(definition)).toEqual([
{ name: 'number', description: 'PR number' },
]);
});

it('finds variables under additionalProperties → anyOf (dict field like github_actions inputs)', () => {
const definition = {
type: 'object',
additionalProperties: {
anyOf: [
{ type: 'integer' },
{ type: 'boolean' },
{
type: 'string',
format: 'simple-template',
'x-mergify-template-variables': [{ name: 'base', description: 'base branch' }],
},
],
},
};
expect(extractTemplateVariables(definition)).toEqual([
{ name: 'base', description: 'base branch' },
]);
});

it('returns [] when no template variables are present', () => {
expect(extractTemplateVariables({ type: 'string' })).toEqual([]);
expect(extractTemplateVariables(null)).toEqual([]);
expect(extractTemplateVariables(undefined)).toEqual([]);
});
});
Loading
Loading