Merge Methods, Merge Trains, Mergeability Checks, Conflict Resolution, and Integration Strategies: Guided Hands-On Workflow and Core Operations
Inspect a disposable project, compare merge-commit and fast-forward outcomes, create and repair a controlled conflict, inspect MR/API state, and model a merge train without requiring a paid tier.
Learning objectives
- Capture the project merge method and target/source SHAs before mutation.
- Demonstrate merge-commit and fast-forward outcomes safely in a disposable project or local mirror.
- Engineer a deterministic text conflict, preserve both tips, resolve it locally, and verify the MR becomes mergeable.
- Compare Git commit graphs with GitLab MR/API metadata after integration.
- Model merge-train ordering and pipeline identity without requiring Premium/Ultimate.
1. Scenario and safety boundary
Use a disposable project such as
GROUP/integration-sandbox. You need Git, an
authenticated glab, and Maintainer/Owner only if you
choose the optional project-setting changes. No new token, runner,
cloud account, protected production branch, or paid feature is
required.
-
Mandatory refs:
ch09-target,ch09-merge,ch09-ff, andch09-conflict. - Safety: never change the production/default branch merge method solely for training. Use a dedicated disposable project.
- Evidence: record every source tip and target tip before deleting any branch.
2. Preflight: capture current configuration and graph
REPO="GROUP/integration-sandbox"
PROJECT_ID="123456"
BASE="main" # replace after checking the API
mkdir -p ch09-evidence
glab auth status
glab api "projects/$PROJECT_ID" --jq '{path_with_namespace,default_branch,merge_method,squash_option,remove_source_branch_after_merge,merge_pipelines_enabled,merge_trains_enabled}' | tee ch09-evidence/project-before.json
git fetch origin --prune
git log --graph --decorate --oneline --all -n 25 | tee ch09-evidence/graph-before.txt
3. First prove the graph mechanics locally
Before changing hosted settings, create a local scratch repository. This makes the Git mechanics observable without any platform permission.
rm -rf /tmp/ch09-graph-lab
mkdir /tmp/ch09-graph-lab && cd /tmp/ch09-graph-lab
git init -b main
git config user.name "GitLab Course Lab"
git config user.email "lab@example.invalid"
printf 'base
' > app.txt
git add app.txt && git commit -m "A: base"
git switch -c feature-merge
printf 'merge-path
' >> app.txt
git commit -am "B: merge-path"
git switch main
printf 'target-change
' > target.txt
git add target.txt && git commit -m "C: target advances"
git merge --no-ff feature-merge -m "M: explicit merge commit"
git log --graph --decorate --oneline --all
# Separate fast-forward example.
git switch -c ff-base HEAD~2
git switch -c feature-ff
printf 'ff-path
' > ff.txt
git add ff.txt && git commit -m "D: ff change"
git switch ff-base
git merge --ff-only feature-ff
git log --graph --decorate --oneline --all
4. Optional: compare two methods in the disposable GitLab project
The Projects API reports merge_method as
merge, rebase_merge, or ff.
If you own the disposable project, record the original value and
change it only long enough to test the intended method. The UI path
is Settings → Merge requests → Merge method.
ORIGINAL_METHOD="$(glab api "projects/$PROJECT_ID" --jq .merge_method)"
printf 'original_merge_method=%s
' "$ORIGINAL_METHOD" | tee ch09-evidence/original-method.txt
# Read-only after any UI change:
glab api "projects/$PROJECT_ID" --jq '{merge_method,squash_option}' | tee ch09-evidence/method-current.json
5. Engineer a controlled conflict and preserve both tips
Create two branches from the same known target and change the same line differently. Keep the original SHAs in evidence before resolving.
cd /path/to/integration-sandbox
git fetch origin "$BASE"
git switch -C ch09-target "origin/$BASE"
printf 'mode=baseline
' > conflict.txt
git add conflict.txt && git commit -m "ch09: conflict baseline"
git push -u origin ch09-target
TARGET_SHA_BEFORE="$(git rev-parse HEAD)"
git switch -c ch09-conflict
printf 'mode=feature
' > conflict.txt
git commit -am "ch09: feature interpretation"
SOURCE_SHA_BEFORE="$(git rev-parse HEAD)"
git push -u origin ch09-conflict
# Advance the target with a competing semantic change.
git switch ch09-target
printf 'mode=target
' > conflict.txt
git commit -am "ch09: target interpretation"
git push origin ch09-target
TARGET_SHA_AFTER_ADVANCE="$(git rev-parse HEAD)"
printf 'source=%s
target_before=%s
target_after=%s
' "$SOURCE_SHA_BEFORE" "$TARGET_SHA_BEFORE" "$TARGET_SHA_AFTER_ADVANCE" | tee ch09-evidence/conflict-tips.txt
6. Open the MR and inspect the failed mergeability check
glab mr create -R "$REPO" --source-branch ch09-conflict --target-branch ch09-target --title "ch09: controlled conflict" --description "Disposable conflict-resolution lab." --yes
MR_IID="REPLACE_WITH_IID"
glab api "projects/$PROJECT_ID/merge_requests/$MR_IID" --jq '{iid,state,sha,source_branch,target_branch,has_conflicts,detailed_merge_status,diff_refs}' | tee ch09-evidence/mr-conflict-before.json
Expected state: after GitLab finishes checking, the MR should report a conflict-related mergeability state. Preserve that evidence rather than immediately editing the file.
7. Resolve locally: understand before you edit
Fetch the target into the source branch and inspect the conflict block. The correct resolution for this synthetic example is a third explicit value that documents the combined intent.
git switch ch09-conflict
git fetch origin ch09-target
git merge --no-commit --no-ff origin/ch09-target || true
git status
git diff -- conflict.txt
# Resolve semantically; do not blindly choose ours/theirs.
printf 'mode=feature-plus-target
' > conflict.txt
git add conflict.txt
git commit -m "ch09: reconcile feature and target semantics"
RESOLVED_SHA="$(git rev-parse HEAD)"
git push origin ch09-conflict
# Poll until GitLab finishes mergeability checking.
for i in 1 2 3 4 5; do
glab api "projects/$PROJECT_ID/merge_requests/$MR_IID" --jq '{sha,has_conflicts,detailed_merge_status}' | tee "ch09-evidence/mr-after-$i.json"
sleep 2
done
The lesson does not hide the failed git merge; that
non-zero exit is the evidence that created the conflict state. The
repair is the explicit resolution commit.
8. Premium/Ultimate merge-train simulation
A merge train is not “auto-merge with a nicer queue.” It requires merge-request pipelines plus merged-results pipelines. Model three entries:
train_fixture:
target: main
cars:
- mr: 101
candidate: target + MR101
status: passed
- mr: 102
candidate: target + MR101 + MR102
status: running
- mr: 103
candidate: target + MR101 + MR102 + MR103
status: waiting
If MR101 leaves the train or fails, later candidate context can change and later pipelines may need to be recreated. This ordered context is the central distinction from independent MR pipelines.
9. Compare GitLab metadata with the actual graph
git fetch origin --prune
git log --graph --decorate --oneline --all -n 40 | tee ch09-evidence/graph-after.txt
glab api "projects/$PROJECT_ID/merge_requests/$MR_IID" --jq '{iid,state,sha,merge_commit_sha,squash_commit_sha,detailed_merge_status,merged_at}' | tee ch09-evidence/mr-after.json
Do not infer target identity from the MR source SHA. A merge commit
or squash can create new SHAs. Verify the remote target ref
independently with git ls-remote or
git rev-parse origin/<target>.
10. Challenge: select the right integration surface
Your team needs a linear target history, must retain each source commit, and refuses to merge if the source is behind the target. Which project method fits best? Fast-forward without squash. If the team also wants an explicit branch-boundary merge commit while still requiring up-to-date source history, choose semi-linear.
Knowledge check
Why is the local graph lab useful before changing a GitLab project setting?
It proves the underlying Git mechanics without changing hosted governance or affecting teammates.
What evidence should be captured before resolving a conflict?
At minimum the source tip SHA, target tip SHA, MR identity/state, and the conflict itself; these let you reconstruct what was actually resolved.
Why does the lesson intentionally keep the failing merge command visible?
The failure is diagnostic evidence. Hiding it would obscure the cause and make the repair less auditable.
What makes a merge train different from several independent MR pipelines?
Each train entry is validated in ordered context that includes earlier train entries, reducing the chance that individually green MRs break the target when combined.
Which method gives a linear history while retaining all source commits?
Fast-forward merge with squashing disabled, assuming a fast-forward path exists.
Summary
You inspected before mutation, modeled graph mechanics locally, created a real conflict, preserved both tips, repaired the conflict semantically, and compared GitLab MR metadata with the resulting Git graph. Merge trains remained an honest Premium/Ultimate simulation rather than a hidden paid requirement.
Official references
- GitLab Docs — Merge methods
- GitLab Docs — Squash and merge
- GitLab Docs — Merge conflicts
- GitLab Docs — Merge trains
- GitLab Docs — Merged results pipelines
- GitLab Docs — Merge request pipelines
- GitLab Docs — Auto-merge
- GitLab Docs — Merge requests API
- GitLab Docs — Projects API
- GitLab Docs — Project settings
- GitLab Docs — Merge requests
- GitLab Docs — Default branch
- GitLab Docs — Merge trains API
- GitLab Docs — REST API pagination
- GitLab Docs — glab API
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.