Skip to content

Commit 21150ae

Browse files
fix(merge): auto-resolve tree deletion conflict
1 parent c14fb05 commit 21150ae

File tree

2 files changed

+89
-1
lines changed

2 files changed

+89
-1
lines changed

src/utils/mergeTree.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ export async function mergeTree({
139139
(base && (await base.type()) !== 'blob') ||
140140
(theirs && (await theirs.type()) !== 'blob')
141141
) {
142+
// Created tree (folder) with the same path
142143
if (
143144
(ours && (await ours.type()) === 'tree') &&
144145
!base &&
@@ -151,6 +152,11 @@ export async function mergeTree({
151152
}
152153
}
153154

155+
// Deleted tree (folder) with the same path
156+
if (!ours && !theirs && (base && (await base.type() === 'tree'))) {
157+
return undefined
158+
}
159+
154160
throw new MergeConflictError(filepath)
155161
}
156162

@@ -199,7 +205,7 @@ export async function mergeTree({
199205
return parent
200206
},
201207
})
202-
return results.oid
208+
return results?.oid
203209
}
204210

205211
async function modified(entry: WalkerEntry, base: WalkerEntry) {

tests/merge.test.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
init,
1010
log,
1111
merge,
12+
remove,
1213
resolveRef
1314
} from 'git-essentials'
1415
import { FsFixtureData, makeFsFixture } from './helpers/makeFsFixture'
@@ -519,6 +520,87 @@ describe('merge-e2e', () => {
519520
expect(newSubDirFiles.length).toBe(2)
520521
})
521522

523+
it('merge two branches that deleted same folder', async () => {
524+
// ARRANGE
525+
const { fs, dir } = await makeFsFixture()
526+
527+
// initializing new repo
528+
await init({ fs, dir, defaultBranch: branch1Name })
529+
await fs.mkdir(path.resolve(dir, newDirName))
530+
await fs.writeFile(path.resolve(dir, newDirName, '.gitkeep'), '')
531+
await add({ fs, dir, filepath: path.resolve(newDirName, '.gitkeep') })
532+
await commit({ fs, dir, message: 'first commit', author: { name: 'author0' } })
533+
await branch({ fs, dir, ref: branch2Name, checkout: false })
534+
535+
// deleting files to the branch1
536+
await fs.rm(path.resolve(dir, newDirName), { recursive: true })
537+
await remove({ fs, dir, filepath: path.resolve(newDirName, '.gitkeep') })
538+
await commit({ fs, dir, message: 'add files', author: { name: 'author1' } })
539+
540+
// deleting files to the branch2
541+
await checkout({ fs, dir, ref: branch2Name })
542+
await fs.rm(path.resolve(dir, newDirName), { recursive: true })
543+
await remove({ fs, dir, filepath: path.resolve(newDirName, '.gitkeep') })
544+
await commit({ fs, dir, message: 'add files', author: { name: 'author2' } })
545+
546+
// switching back to the branch1
547+
await checkout({ fs, dir, ref: branch1Name })
548+
549+
// ACT
550+
const m = await merge({ fs, dir, ours: branch1Name, theirs: branch2Name, author: { name: 'author3' } })
551+
await checkout({ fs, dir, ref: branch1Name })
552+
553+
// ASSERT
554+
expect(m.alreadyMerged).toBeFalsy()
555+
expect(m.mergeCommit).toBeTruthy()
556+
const rootDirFiles = await fs.readdir(dir)
557+
expect(rootDirFiles.length).toBe(1)
558+
})
559+
560+
it('merge two branches that deleted folder and it\'s subfolder', async () => {
561+
// ARRANGE
562+
const { fs, dir } = await makeFsFixture()
563+
564+
// initializing new repo
565+
await init({ fs, dir, defaultBranch: branch1Name })
566+
await fs.mkdir(path.resolve(dir, newDirName))
567+
568+
await fs.writeFile(path.resolve(dir, newDirName, '.gitkeep'), '')
569+
await add({ fs, dir, filepath: path.resolve(newDirName, '.gitkeep') })
570+
571+
await fs.mkdir(path.resolve(dir, newDirName, 'sub-folder'))
572+
await fs.writeFile(path.resolve(dir, newDirName, 'sub-folder', 'nested-file.txt'), 'some content')
573+
await add({ fs, dir, filepath: path.resolve(newDirName, 'sub-folder', 'nested-file.txt') })
574+
575+
await commit({ fs, dir, message: 'first commit', author: { name: 'author0' } })
576+
await branch({ fs, dir, ref: branch2Name, checkout: false })
577+
578+
// deleting files to the branch1
579+
await fs.rm(path.resolve(dir, newDirName), { recursive: true })
580+
await remove({ fs, dir, filepath: path.resolve(newDirName, '.gitkeep') })
581+
await remove({ fs, dir, filepath: path.resolve(newDirName, 'sub-folder', 'nested-file.txt') })
582+
await commit({ fs, dir, message: 'add files', author: { name: 'author1' } })
583+
584+
// deleting files to the branch2
585+
await checkout({ fs, dir, ref: branch2Name })
586+
await fs.rm(path.resolve(dir, newDirName, 'sub-folder'), { recursive: true })
587+
await remove({ fs, dir, filepath: path.resolve(newDirName, 'sub-folder', 'nested-file.txt') })
588+
await commit({ fs, dir, message: 'add files', author: { name: 'author2' } })
589+
590+
// switching back to the branch1
591+
await checkout({ fs, dir, ref: branch1Name })
592+
593+
// ACT
594+
const m = await merge({ fs, dir, ours: branch1Name, theirs: branch2Name, author: { name: 'author3' } })
595+
await checkout({ fs, dir, ref: branch1Name })
596+
597+
// ASSERT
598+
expect(m.alreadyMerged).toBeFalsy()
599+
expect(m.mergeCommit).toBeTruthy()
600+
const rootDirFiles = await fs.readdir(dir)
601+
expect(rootDirFiles.length).toBe(1)
602+
})
603+
522604
it('merge new folder and file with the same name should fail', async () => {
523605
// ARRANGE
524606
const { fs, dir } = await makeFsFixture()

0 commit comments

Comments
 (0)