Skip to content

Merge a branch in a Git repository

Merges one branch into another and pushes the result back into the repository Clarive hosts. Unlike Create a branch and Create a tag, which write a ref in place, this op needs a working copy, so it makes a throwaway clone on the Clarive server, merges there and pushes. The clone is deleted whether the merge worked or not.

That shape decides most of the behaviour worth knowing. The hosted repository is only changed by the final push, so a conflict leaves it exactly as it was. Every run pays for a full clone of every repository you list.

the repository Clarive hosts clone push temporary clone on the server check out both branches merge the topic branch push the target branch deleted afterwards either way conflict or rejected push: hosted repository unchanged

Fields

Repository

The repositories to merge in. More than one entry is allowed, and each one is cloned, merged and pushed separately, one after another. The picker offers variables as well as repository resources, and a variable that resolves to several ids separated by commas counts as several repositories.

Every entry costs a full clone of that repository on every run. On large histories this is where the op spends its time.

Blank stops the op with a missing-repository error.

Topic Branch

The branch being merged in, the source side. Expands ${var} against the stash. It has to be resolvable in the fresh clone, so a branch that exists in the hosted repository, or a tag, or a commit id. A name that resolves to nothing stops the op.

Into Branch

The branch that receives the merge, the target side. Expands ${var}. This one has to be a real branch in the hosted repository. The op does not create it, and it is this branch that gets pushed back at the end.

Blank stops the op with a missing-target error, as does a blank Topic Branch.

Message

The commit message for the merge commit. Optional. Expands ${var}.

Keep double quotes out of the value. Every " is rewritten as \" before the message reaches Git, and the message is handed over as a plain argument with no shell in between, so the backslashes are stored in the commit. A message typed as fix the "quoted" thing lands in the history as fix the \"quoted\" thing. Single quotes and other punctuation pass through untouched.

Leave it blank and Git writes a one-line message of its own, Merge branch 'topic' with your topic branch name in it. No list of the commits being brought in is appended, unless merge.log has been turned on in the Git configuration on the Clarive server. Fill the field in and your text is used as the whole message, and the commit list is suppressed even where that setting would add one.

The message only matters when a merge commit is actually created. On a fast-forward, no commit is made and the message is discarded with no warning.

Options

Extra options handed to the merge, as one string. Expands ${var}. The string is split on whitespace, so -s recursive -X theirs arrives as four separate options. There is no shell and no quoting: an option whose value contains a space cannot be written here.

What you type here goes in front of the options the op adds for itself, which are --no-ff when No Fast-Forward? is ticked, the Message value when you filled it in, and --no-edit. Where two options contradict each other Git takes the later one, so an --ff-only typed here is overridden by the op's own --no-ff: the merge succeeds and writes a merge commit instead of refusing. To make --ff-only mean anything, untick No Fast-Forward?.

--squash at the default settings is a hard failure. Git rejects it alongside --no-ff with options '--squash' and '--no-ff' cannot be used together and the op stops on that repository without touching it. Untick No Fast-Forward? and the squash runs, but it only stages the result in the throwaway clone. Nothing is committed, so the push sends nothing and the op reports success with the hosted repository unchanged.

--no-commit is the quieter version of the same trap. It works at either No Fast-Forward? setting, merges into the working copy, and stops short of committing. The push has nothing to send and the op reports success.

No Fast-Forward?

Checked by default on a new op.

Checked, a merge commit is always created, even when the target could have moved forward without one. The history keeps a visible record of the merge, and the commit carries Message.

Unchecked, a merge that can fast-forward does so: the target branch pointer moves to the topic branch and no merge commit appears. Message is ignored in that case. When the two branches have genuinely diverged, a merge commit is created regardless of this setting.

Silent successes

A merge that has nothing to do reports success. The most common case is a topic branch already contained in the target: Git says the branch is up to date, the push sends nothing, and the op returns without complaint. Nothing in the rule distinguishes that from a merge that actually moved the branch.

The op returns nothing either way, so a Return Key on it holds an empty value and cannot be used as a "did it merge" flag. To find out, read the target branch before and after and compare, or check the merge result in a later step.

The clone and merge output does not reach the job log. You see the op's own start and end, and on a failure you get the error text with Git's output attached. There is no setting that makes the successful path louder.

Failure, ordering and rollback

Repositories are processed in picker order. A conflict, a checkout that cannot resolve a branch, or a push the hosted repository rejects stops the op on that repository. The temporary clone goes away, so that repository is left untouched, but repositories earlier in the list keep the merges they already received.

A push is rejected when the target branch moved in the hosted repository between the clone and the push. Two rules merging into the same branch at the same time can hit this. The merge work is not saved anywhere; the op has to run again.

Both directions are enabled on a new op, so a merge left at its defaults runs again during a rollback pass, where it normally finds the branch already merged and does nothing. It does not undo the merge. Untick Run Rollback, and if you need a real reversal build it yourself from a tag taken before the merge.

Combining with other ops

Run Create a tag in a Git repository on the target branch first if you want a point to return to, then merge, then Delete a reference in a Git repository to retire the topic branch.

Guard the op with IF var condition THEN so a merge only happens for the topic statuses and environments that should trigger it. Wrap it in TRY with CATCH when a conflict should mark the topic rather than stop the job, and RETRY around it covers the rejected-push case where running again is the right answer.

For a set of repositories only known at run time, put the op inside FOREACH CI.

Examples

Merge a topic's feature branch into the integration branch with an explicit message and a merge commit you can see in the history.

Repository          ${repository}
Topic Branch        feature/${topic_id}
Into Branch         develop
Message             Merge ${topic_id} into develop
Options
No Fast-Forward?    checked

Bring a release branch into the production branch, taking the incoming side on any conflicting file.

Repository          ${repository}
Topic Branch        release/${version}
Into Branch         ${production_branch}
Message             Release ${version}
Options             -s recursive -X theirs
No Fast-Forward?    checked

Move a target branch forward without adding a merge commit, for a linear history. The message is left empty because it would be discarded anyway.

Repository          ${repository}
Topic Branch        ${job_branch}
Into Branch         env/${bl}
Message
Options
No Fast-Forward?    unchecked