Merging tasks.json
Two branches that each add a task both call it the next free ID, say5. A plain Git merge of .taskgraph/tasks/tasks.json then conflicts, and resolving it by hand can easily leave a dependency pointing at the wrong task 5.
TaskGraph ships a Git merge driver for tasks.json. Git runs it during git merge, git pull, git rebase and git cherry-pick, and it merges the file task by task instead of line by line.
What it does
- New tasks on both branches are all kept. When both branches used the same ID, the incoming branch’s task gets the next free ID. The same goes for new subtasks of an existing task.
- References follow the renumbered task. On the incoming side, task and subtask
dependencies,metadata.dependencySourceskeys,related/discovered-fromlinks (metadata.taskLinks) and a subtask’sparentIdare rewritten to the new ID. - Only IDs that collide move. An incoming task whose ID is still free keeps it.
- Changes to different fields merge cleanly. One branch sets task 3 to
donewhile the other edits its description: both changes land.
archive.json
The driver also merges.taskgraph/tasks/archive.json, so two branches that both ran taskgraph archive don’t conflict:
- Archived tasks from both branches are all kept, tag by tag.
highestIdtakes the higher value, so no archived ID is handed out again. - IDs never move. Tasks may depend on an archived ID, so the same ID on both branches is one task, merged field by field (
archivedAttakes the earlier time). Two different tasks archived under one ID are reported as a clash on their fields. - An ID the incoming branch archived and this branch reused is a clash. Say the incoming branch added task 9 and archived it while this branch added its own task 9. Both are kept, and the driver marks
archive.jsonas conflicted:
move also moves dependencies on task 9 to the new ID, so check any that meant the archived task.
The driver tells the tasks apart by title, and it can only check when Git runs it, which is when both branches changed the file. If only the incoming branch touched archive.json, Git takes its version without a merge and the reused ID isn’t reported; taskgraph archive restore 9 then says the ID is already in tasks.json.
archive.json has a driver name of its own, taskgraph-archive, so a teammate whose clone was set up by an older TaskGraph (which only knows taskgraph) gets Git’s normal text merge for it, as before, until they upgrade and run taskgraph init --merge-driver.
The driver reads the other file of the pair from the working tree, which during a merge still holds this branch’s version, so the order in which Git merges the two files doesn’t matter.
When both branches change the same field
If both branches set the same field of the same task to different values, the driver can’t pick one for you. It:- keeps this branch’s value (the branch you’re merging into, or the commit being rebased onto; when this branch deleted the field, the incoming value),
- still writes a valid
tasks.jsonwith everything else merged, sotaskgraphkeeps working, - lists each clash with the incoming value, and
- exits with an error, so Git marks the file as conflicted and stops the merge.
taskgraph set-status and friends), then git add .taskgraph/tasks/tasks.json and finish the merge. The incoming version is also in Git’s index: git show :3:.taskgraph/tasks/tasks.json.
Some fields never clash:
A task removed on one branch and changed on the other is kept and reported as a clash. Removed on one branch and untouched on the other, it goes.
Two tasks with the same ID inside one branch’s file (left over from a hand merge) aren’t merged one by one: the whole task list is reported as a clash and this branch’s list is kept.
If one branch’s
tasks.json isn’t a JSON object (for example, it was committed with conflict markers in it), the driver leaves this branch’s file untouched, says which side couldn’t be read, and marks the file as conflicted.
Setting it up
New projects get it fromtaskgraph init when tasks are stored in Git (the default inside a Git repository). For a project that was set up before, a fresh clone, or a project set up before archive.json had a driver, run:
A merge setup of your own
If.gitattributes already gives tasks.json or archive.json a merge attribute (such as merge=union), or .git/config has a merge.taskgraph.driver that TaskGraph didn’t write, setup leaves it as it is and says so:
git config --unset merge.taskgraph.driver (or delete the .gitattributes line), then taskgraph init --merge-driver again.
Which command Git runs
Whentaskgraph is on your PATH, Git runs it by name, so upgrading TaskGraph upgrades the driver. Otherwise (a project-local install or npx) Git runs the CLI that ran init, by its full path. Rerun taskgraph init --merge-driver if you move or reinstall it.
When TaskGraph isn’t installed
The command first checks that the CLI is still there. If it isn’t (taskgraph was uninstalled, the path moved, or the clone is on a machine without it), Git falls back to its own text merge, git merge-file. You get an ordinary conflict with <<<<<<< ours / >>>>>>> theirs markers, the same as in a repository without the driver. A merge never silently drops the incoming side.
Not covered
Projects that keep tasks out of Git (init --no-git-tasks) don’t need the driver, and init doesn’t set it up.
