Merging
@catmint-fs/git supports fast-forward merges, merge commits, and conflict detection. The merge engine performs a full three-way merge when necessary.
Basic Merge
// Merge feature branch into the current branch
const result = await repo.merge("feature/login");
The merge method returns an object describing the outcome:
interface MergeResult {
type: "fast-forward" | "merge-commit" | "already-up-to-date" | "conflict";
oid?: string; // The resulting commit OID (for fast-forward and merge-commit)
conflicts?: string[]; // Array of conflicted file paths (when type is "conflict")
}
Merge Strategies
Fast-Forward
When the current branch has no commits ahead of the target, git can simply move the branch pointer forward:
await repo.checkout("main");
const result = await repo.merge("feature/login");
if (result.type === "fast-forward") {
console.log("Fast-forwarded to", result.oid);
}
Merge Commit
When both branches have diverged, a merge commit is created with two parents:
const result = await repo.merge("feature/login");
if (result.type === "merge-commit") {
const commit = await repo.readCommit(result.oid!);
console.log(commit.parents.length); // 2
}
Already Up To Date
If the target branch is an ancestor of the current branch, no merge is needed:
const result = await repo.merge("feature/old");
console.log(result.type); // "already-up-to-date"
Conflict Handling
When the same file has been modified differently on both branches, the merge produces conflicts:
const result = await repo.merge("feature/conflicting");
if (result.type === "conflict") {
console.log("Conflicts in:", result.conflicts);
// ["src/config.ts", "README.md"]
}
Resolving Conflicts
When a conflict occurs, the conflicting files in the working tree contain standard git conflict markers:
<<<<<<< HEAD
const port = 3000;
=======
const port = 8080;
>>>>>>> feature/conflicting
To resolve:
- Edit the conflicting files to resolve the markers
- Stage the resolved files with
add - Complete the merge with a
commit
const result = await repo.merge("feature/conflicting");
if (result.type === "conflict") {
// Read, resolve, and write the conflicted file
const content = await layer.readFile("src/config.ts", "utf-8");
const resolved = content
.replace(/<<<<<<< HEAD\n/, "")
.replace(/=======\n.*\n>>>>>>> .*\n/s, "");
await layer.writeFile("/src/config.ts", resolved);
// Stage and commit the resolution
await repo.add("src/config.ts");
await repo.commit({
message: "Merge feature/conflicting, resolve config conflict",
author: { name: "Alice", email: "alice@example.com" },
});
}
Aborting a Merge
If you want to cancel a conflicted merge and restore the pre-merge state:
await repo.abortMerge();
This resets HEAD, the index, and the working tree to the state before the merge was attempted.
Merge with Custom Commit Message
For merge commits, a default message is generated (e.g., "Merge branch 'feature/login'"). You can provide a custom author for the merge commit via repository config:
await repo.setConfig("user.name", "Alice");
await repo.setConfig("user.email", "alice@example.com");
const result = await repo.merge("feature/login");
Full Merge Workflow
import { createMemoryLayer } from "@catmint-fs/core";
import { initRepository } from "@catmint-fs/git";
const layer = createMemoryLayer();
const repo = await initRepository(layer);
const author = { name: "Alice", email: "alice@example.com" };
// Initial commit on main
await layer.writeFile("/app.ts", "export const version = 1;");
await repo.add("app.ts");
await repo.commit({ message: "Initial commit", author });
// Create a feature branch with changes
await repo.createBranch("feature/v2");
await repo.checkout("feature/v2");
await layer.writeFile("/app.ts", "export const version = 2;");
await repo.add("app.ts");
await repo.commit({ message: "Bump version to 2", author });
// Back to main
await repo.checkout("main");
// Merge the feature branch
const result = await repo.merge("feature/v2");
console.log(result.type); // "fast-forward"
// Clean up
await repo.deleteBranch("feature/v2");
