git merge
See original GitHub issueOK let’s break this down into tasks…
File: src/commands/merge.js
pseudocode
import diff3 from 'node-diff3' // at least this exists
// find most recent common ancestor of ref a and ref b
let o = await findMergeBase(a, b)
// for each file, determine whether it is present or absent or modified (see http://gitlet.maryrosecook.com/docs/gitlet.html#section-217)
let diff = await findChangedFiles(a, o, b)
for (let file of diff) {
// for simple cases of add, remove, or modify files
updateMergeIndex(file);
updateWorkTree(file);
// for files that changed on both branches, compute the diff3.
if (file.a !== file.o && file.b !== file.o && file.a !== file.b) {
diff = await diff3(file.a, file.o, file.b)
// If the diff3 merge was unsuccessful, mark the conflict
if (diff.length > 1 || diff[0].conflict) {
markConflictInIndex(file)
}
// regardless save the result to the work dir
fs.writeFile(file.name, formatDiff3(diff))
}
}
Tasks:
- implement
findMergeBase(a, b)
- ~implement
findChangedFiles(a, o, b)
~ - ~implement
updateMergeIndex(file)
~ - ~implement
updateWorkTree(file)
~ - ~implement
markConflictInIndex(file)
~ - ~implement
formatDiff3(diff)
~ - implement
mergeTree(a, o, b)
- implement
mergeFile(a, o, b)
copied and pasted from my work document:
This is trickier, and would involve implementing more of the merge
in isomorphic-git. This would undoubtedly be a good thing for everyone who uses isomorphic-git, but it is a bit of a time commitment to do well.
Simple cases involving merges that don’t involve the same files should be doable in just a couple days. The algorithm is something like:
- Compute the nearest common ancestor of commit A and B, call it O. (This code already exists as
findMergeBase
) - Compute two tree patches: O -> A and O -> B. (This algorithm should be very similar to that used in
statusMatrix
I think) Note: this should also be used to speed up and makecheckout
safer. - Check that the two tree patches do not contain any operations that happen to the same file.
- If there are no operations on the same file, apply both patches to O and create a merge commit D whose parents are A and B.
- Move the current branch to point to D.
- Attempt to checkout the current branch.
We need a datastructure for storing tree patches. I propose something like:
const patches = [
{filepath: 'TODO.MD', op: 'rm', before: 'f4c8920', after: null},
{filepath: 'TODO.md', op: 'write', before: null, after: 'ec63514'},
{filepath: 'lib', op: 'mkdir', before: null, after: 'he3414c'},
{filepath: 'lib/app.rb', op: 'write', before: null, after: '00750ed'},
]
Including before
oids makes it possible to safely detect if files have changed between the time the patch is computed and the time the patch is performed. For instance, a “safe delete with rollback” might consist of:
- create a temporary directory ‘tmp’
- move
<file>
totmp/<before>
(e.g. mv ‘TODO.MD’ to ‘tmp/f4c8920…’) - compute the SHA of
tmp/<before>
and make sure it matchesbefore
. - if not, move the file back and trigger a rollback.
- if so, delete
tmp/<before>
The after
oids should simply be written to the indicated filepath. For directories, I guess the tree oid could be computed and verified afterwards, but if all the file oids match it should be correct.
Edit:
So… actually most of that is not needed. There’s no need to compute two “tree patches” for O -> A and O -> B. There’s no need to merge the patches or apply patches. In the end I just merged the commits in a single call to walkBeta1
.
Issue Analytics
- State:
- Created 6 years ago
- Reactions:4
- Comments:13 (10 by maintainers)
Top GitHub Comments
I’d love to! I just haven’t had time to work on it. 😞
Status update
v0.9.0 features preliminary support for fast-forward merges and a “pull” command! https://isomorphic-git.github.io/docs/pull.html This should greatly simplify workflows that consist of mostly “clone” and “pull” commands.