Skip to content
Closed
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
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,14 @@ Every command group maps to a section of the
maintenance. [Docs](https://docs.mergify.com/merge-protections/freeze/)
- **`mergify config`** — Validate your configuration and simulate actions
before you merge. [Docs](https://docs.mergify.com/configuration/file-format/#validating-with-the-cli)
- **`mergify merge-driver`** — Git merge drivers that merge by file
format instead of by line, falling back to git's line merge when they
cannot. `json` merges JSON structurally, keeping ours' formatting:

```shell
git config merge.json.driver "mergify merge-driver json --marker-size %L --path %P %A %O %B"
echo '*.json merge=json' >> .gitattributes
```
- **`mergify self-update`** — Update the CLI to the latest release.
- **`mergify completions <shell>`** — Print a shell completion script
([see below](#shell-completions)).
Expand Down
1 change: 1 addition & 0 deletions crates/mergify-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ mergify-config = { path = "../mergify-config" }
mergify-core = { path = "../mergify-core" }
mergify-events = { path = "../mergify-events" }
mergify-freeze = { path = "../mergify-freeze" }
mergify-json-merge = { path = "../mergify-json-merge" }
mergify-queue = { path = "../mergify-queue" }
mergify-stack = { path = "../mergify-stack" }
# Shared TTY/color palette — the binary resolves `--color` once at
Expand Down
76 changes: 75 additions & 1 deletion crates/mergify-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ const NATIVE_COMMANDS: &[(&str, &str)] = &[
("freeze", "create"),
("freeze", "update"),
("freeze", "delete"),
("merge-driver", "json"),
("stack", "checkout"),
("stack", "drop"),
("stack", "edit"),
Expand Down Expand Up @@ -294,6 +295,9 @@ enum NativeCommand {
/// `mergify self-update [--force] [--check]` — replace the
/// running binary with the latest release.
SelfUpdate(self_update::Options),
/// `mergify merge-driver json <ours> <base> <theirs>` — run by git
/// as a merge driver.
MergeDriverJson(MergeDriverJsonCli),
}

struct StackEditOpts {
Expand Down Expand Up @@ -720,7 +724,8 @@ fn init_tracing(verbose: u8, debug: bool, color: mergify_tui::ColorChoice) {
let directives = format!(
"warn,mergify_cli={level},mergify_core={level},mergify_stack={level},\
mergify_ci={level},mergify_queue={level},mergify_freeze={level},\
mergify_config={level},mergify_tui={level},mergify_auth={level}"
mergify_config={level},mergify_tui={level},mergify_auth={level},\
mergify_json_merge={level}"
);
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(directives));
let _ = tracing_subscriber::fmt()
Expand Down Expand Up @@ -814,6 +819,9 @@ fn dispatch_from_parsed(parsed: CliRoot) -> Dispatch {
StackSubcommand::Setup(cli) => Dispatch::Native(NativeCommand::StackSetup(cli.into())),
},
Subcommands::SelfUpdate(cli) => Dispatch::Native(NativeCommand::SelfUpdate(cli.into())),
Subcommands::MergeDriver(MergeDriverArgs {
command: MergeDriverSubcommand::Json(cli),
}) => Dispatch::Native(NativeCommand::MergeDriverJson(cli)),
Subcommands::Completions(cli) => Dispatch::Native(NativeCommand::Completions(cli.shell)),
Subcommands::Internal(InternalArgs {
command:
Expand Down Expand Up @@ -2564,6 +2572,16 @@ fn run_native(cmd: NativeCommand) -> ExitCode {
self_update::run(&opts).await?;
Ok(mergify_core::ExitCode::Success)
}
NativeCommand::MergeDriverJson(cli) => {
mergify_json_merge::run(&mergify_json_merge::DriverOptions {
ours: &cli.ours,
base: &cli.base,
theirs: &cli.theirs,
marker_size: cli.marker_size,
path: cli.path.as_deref(),
})?;
Ok(mergify_core::ExitCode::Success)
}
NativeCommand::InternalRebaseTodoRewrite(opts) => {
let action = match opts.action {
InternalRebaseAction::Edit => {
Expand Down Expand Up @@ -2911,6 +2929,16 @@ enum Subcommands {
/// the merge queue from merging — for release windows, incidents,
/// or code freezes.
Freeze(FreezeArgs),
/// Git merge drivers that resolve conflicts by file format.
///
/// Commands git runs in place of its line merge for the paths a
/// `merge` attribute assigns them, configured with
/// `git config merge.<name>.driver "mergify merge-driver <format>
/// %A %O %B"`. Each one merges by structure where it can and falls
/// back to git's own line merge, conflict markers included, where
/// it cannot.
#[command(name = "merge-driver")]
MergeDriver(MergeDriverArgs),
/// Create and maintain stacked pull requests.
///
/// Manage a stack of dependent branches and their pull requests:
Expand Down Expand Up @@ -4592,6 +4620,51 @@ enum AuthSubcommand {
Status,
}

#[derive(clap::Args)]
struct MergeDriverArgs {
#[command(subcommand)]
command: MergeDriverSubcommand,
}

#[derive(Subcommand)]
enum MergeDriverSubcommand {
/// Merge a JSON file by structure instead of by line.
///
/// Two edits to different keys, or to different elements of an
/// array, merge even when they sit on neighbouring lines. The
/// result keeps ours' formatting and key order, with theirs'
/// changes spliced in as theirs wrote them. It declines — and falls
/// back to `git merge-file`, leaving conflict markers — when both
/// sides changed the same value differently, when one side changed
/// a key the other deleted, and when both inserted into the same
/// place in an array. Configure it with
/// `git config merge.json.driver "mergify merge-driver json
/// --marker-size %L --path %P %A %O %B"` and `*.json merge=json`
/// in `.gitattributes`.
Json(MergeDriverJsonCli),
}

#[derive(clap::Args)]
struct MergeDriverJsonCli {
/// Conflict-marker length for the line-merge fallback (git's `%L`).
#[arg(long, value_name = "N")]
marker_size: Option<u32>,

/// Path being merged (git's `%P`), named in messages.
#[arg(long, value_name = "PATH")]
path: Option<String>,

/// Our version (git's `%A`), overwritten with the result.
ours: PathBuf,

/// The merge base (git's `%O`); empty when both sides added the
/// file.
base: PathBuf,

/// Their version (git's `%B`).
theirs: PathBuf,
}

#[derive(clap::Args)]
struct FreezeArgs {
/// Mergify token. Falls back to ``MERGIFY_TOKEN``, then the
Expand Down Expand Up @@ -4769,6 +4842,7 @@ mod tests {
"queue",
"events",
"freeze",
"merge-driver",
"stack",
"self-update",
"completions"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2447,6 +2447,129 @@ expression: schema
"subcommandRequired": true,
"usage": "mergify freeze [OPTIONS] <COMMAND>"
},
{
"about": "Git merge drivers that resolve conflicts by file format",
"aliases": [],
"args": [],
"commands": [
{
"about": "Merge a JSON file by structure instead of by line",
"aliases": [],
"args": [
{
"default": null,
"env": null,
"global": false,
"help": "Conflict-marker length for the line-merge fallback (git's `%L`)",
"id": "marker_size",
"kind": "option",
"long": "marker-size",
"longHelp": "Conflict-marker length for the line-merge fallback (git's `%L`)",
"numArgs": "1",
"possibleValues": [],
"required": false,
"short": null,
"valueHint": null,
"valueNames": [
"N"
]
},
{
"default": null,
"env": null,
"global": false,
"help": "Path being merged (git's `%P`), named in messages",
"id": "path",
"kind": "option",
"long": "path",
"longHelp": "Path being merged (git's `%P`), named in messages",
"numArgs": "1",
"possibleValues": [],
"required": false,
"short": null,
"valueHint": null,
"valueNames": [
"PATH"
]
},
{
"default": null,
"env": null,
"global": false,
"help": "Our version (git's `%A`), overwritten with the result",
"id": "ours",
"kind": "positional",
"long": null,
"longHelp": "Our version (git's `%A`), overwritten with the result",
"numArgs": "1",
"possibleValues": [],
"required": true,
"short": null,
"valueHint": "anyPath",
"valueNames": [
"OURS"
]
},
{
"default": null,
"env": null,
"global": false,
"help": "The merge base (git's `%O`); empty when both sides added the file",
"id": "base",
"kind": "positional",
"long": null,
"longHelp": "The merge base (git's `%O`); empty when both sides added the file",
"numArgs": "1",
"possibleValues": [],
"required": true,
"short": null,
"valueHint": "anyPath",
"valueNames": [
"BASE"
]
},
{
"default": null,
"env": null,
"global": false,
"help": "Their version (git's `%B`)",
"id": "theirs",
"kind": "positional",
"long": null,
"longHelp": "Their version (git's `%B`)",
"numArgs": "1",
"possibleValues": [],
"required": true,
"short": null,
"valueHint": "anyPath",
"valueNames": [
"THEIRS"
]
}
],
"commands": [],
"longAbout": "Merge a JSON file by structure instead of by line.\n\nTwo edits to different keys, or to different elements of an array, merge even when they sit on neighbouring lines. The result keeps ours' formatting and key order, with theirs' changes spliced in as theirs wrote them. It declines — and falls back to `git merge-file`, leaving conflict markers — when both sides changed the same value differently, when one side changed a key the other deleted, and when both inserted into the same place in an array. Configure it with `git config merge.json.driver \"mergify merge-driver json --marker-size %L --path %P %A %O %B\"` and `*.json merge=json` in `.gitattributes`.",
"name": "json",
"path": [
"mergify",
"merge-driver",
"json"
],
"source": "native",
"subcommandRequired": false,
"usage": "mergify merge-driver json [OPTIONS] <OURS> <BASE> <THEIRS>"
}
],
"longAbout": "Git merge drivers that resolve conflicts by file format.\n\nCommands git runs in place of its line merge for the paths a `merge` attribute assigns them, configured with `git config merge.<name>.driver \"mergify merge-driver <format> %A %O %B\"`. Each one merges by structure where it can and falls back to git's own line merge, conflict markers included, where it cannot.",
"name": "merge-driver",
"path": [
"mergify",
"merge-driver"
],
"source": "native",
"subcommandRequired": true,
"usage": "mergify merge-driver [OPTIONS] <COMMAND>"
},
{
"about": "Create and maintain stacked pull requests",
"aliases": [],
Expand Down
Loading
Loading