Skip to content

Commit

Permalink
feat(add): Merge deletions support (#1836)
Browse files Browse the repository at this point in the history
* init

* add fixture

* add tests

* refactor test errors

* add new bracnhes in merge fixture rep

* fix grammar

* add "delete poth" in fixture rep

* add "delete by both"

* fix test "delete by both"

* add data in MergeConflictError

* change docs

* docs: add @DanilKazanov as a contributor

* revert change docs for some reason

* fix-eslint

* Revert "refactor test errors"

This reverts commit 4e627f7.
  • Loading branch information
DanilKazanov committed Oct 31, 2023
1 parent 9f9ebf2 commit 90ea0e3
Show file tree
Hide file tree
Showing 24 changed files with 174 additions and 5 deletions.
11 changes: 11 additions & 0 deletions .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,17 @@
"test",
"bug"
]
},
{
"login": "DanilKazanov",
"name": "DanilKazanov",
"avatar_url": "https://avatars.githubusercontent.com/u/139755256?v=4",
"profile": "https://github.com/DanilKazanov",
"contributions": [
"code",
"doc",
"test"
]
}
],
"commitConvention": "angular"
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,9 @@ Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds
<td align="center"><a href="https://github.com/barry963"><img src="https://avatars.githubusercontent.com/u/5289896?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Barry</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=barry963" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=barry963" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=barry963" title="Tests">⚠️</a></td>
<td align="center"><a href="https://stackoverflow.com/users/1493081/alireza-mirian"><img src="https://avatars.githubusercontent.com/u/3150694?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Alireza Mirian</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=alirezamirian" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=alirezamirian" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=alirezamirian" title="Tests">⚠️</a> <a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3Aalirezamirian" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/DanilKazanov"><img src="https://avatars.githubusercontent.com/u/139755256?v=4?s=60" width="60px;" alt=""/><br /><sub><b>DanilKazanov</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=DanilKazanov" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=DanilKazanov" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=DanilKazanov" title="Tests">⚠️</a></td>
</tr>
</table>

<!-- markdownlint-restore -->
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
x��K
�0@]���4I�D\���� m+5����������4 }���"�c������e�m��椁IB��IC-�+�2plS�Թ 9Khȫ`��);����c4���4Á��Gz�8�a���?�����K5/;�C�SL��M���#a�YNUy�1�M�
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
x��1�0 @Q��;�J�&u%��8�qQ-*!N��#0����t� ia�*P�P�-e�:)�D�BOQ1)u �H�ʳ�Xb����}͉۪^�j���&���n�i������qz��|؜~�r[�����SN9$��G�|GM�H��gS�{�{�L]
Expand Down
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
x��An�0@Q�>����I채*�z��x,"B�ҡ�8}=B�o��e��&��eg�*P�A�ƞ��,�HD!���RnX�Grw�t1�ˈ1�*��z�6��-({ؓ�]�μL3|���������$_�ɺ��!˜cB�{���{�� WuVSX;�1� ?�L�
Expand Down
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
x��A�0E]��7��CKI�q��S ө c<��Gp�����2�}��R�ʓ*���TG��]�R��Z�ra)��މ��C��i)�3FY ��.rjS�T*�:�`x��q�����a|�6~���p��}��b�wP�&��-!���|�f�c�ș���X�g6o�M�
Binary file not shown.
2 changes: 1 addition & 1 deletion __tests__/__fixtures__/test-merge.git/refs/heads/g
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3d29b3f3368a0dce65a4fe0ccfbfd3f1ea0e5a08
f1b74b9c2a160e9a3ebec4857886f69443bcc4f4
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
8a631ca301310f67e0a30fa154b69ed3ba32346b
1 change: 1 addition & 0 deletions __tests__/__fixtures__/test-merge.git/refs/heads/h
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1a13b59272076e3288ad75ef173b4a8ffb0b00af
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
48056666b859c10e54f8f1c634efb4de35b1622f
1 change: 1 addition & 0 deletions __tests__/__fixtures__/test-merge.git/refs/heads/i
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ffc8e759595572caa7a6ee0ee97695405a2ecaa2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
4c0eab9b75df8b070d3037307839de49d3d350aa
71 changes: 71 additions & 0 deletions __tests__/test-merge.js
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,77 @@ describe('merge', () => {
expect(error.code).toBe(Errors.MergeNotSupportedError.code)
})

it("merge 'g' and 'g-delete-file' (delete by theirs)", async () => {
// Setup
const { fs, gitdir } = await makeFixture('test-merge')
// Test
let error = null
try {
await merge({
fs,
gitdir,
ours: 'g',
theirs: 'g-delete-file',
author: {
name: 'Mr. Test',
email: 'mrtest@example.com',
timestamp: 1262356920,
timezoneOffset: -0,
},
})
} catch (e) {
error = e
}
expect(error).not.toBeNull()
expect(error.code).toBe(Errors.MergeConflictError.code)
})

it("merge 'g-delete-file' and 'g' (delete by us)", async () => {
// Setup
const { fs, gitdir } = await makeFixture('test-merge')
// Test
let error = null
try {
await merge({
fs,
gitdir,
ours: 'g-delete-file',
theirs: 'g',
author: {
name: 'Mr. Test',
email: 'mrtest@example.com',
timestamp: 1262356920,
timezoneOffset: -0,
},
})
} catch (e) {
error = e
}
expect(error).not.toBeNull()
expect(error.code).toBe(Errors.MergeConflictError.code)
})

it("merge 'i' and 'i-delete-both' (delete by both)", async () => {
// Setup
const { fs, gitdir, dir } = await makeFixture('test-merge')
const deletedFile = `${dir}/o.txt`
// Test
const mergeReuslt = await merge({
fs,
gitdir,
ours: 'i',
theirs: 'i-delete-both',
author: {
name: 'Mr. Test',
email: 'mrtest@example.com',
timestamp: 1262356920,
timezoneOffset: -0,
},
})
expect(mergeReuslt).toBeTruthy()
expect(await fs.exists(deletedFile)).toBeFalsy()
})

it("merge two branches that modified the same file (no conflict)'", async () => {
// Setup
const { fs, gitdir } = await makeFixture('test-merge')
Expand Down
7 changes: 5 additions & 2 deletions src/errors/MergeConflictError.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import { BaseError } from './BaseError.js'
export class MergeConflictError extends BaseError {
/**
* @param {Array<string>} filepaths
* @param {Array<string>} bothModified
* @param {Array<string>} deleteByUs
* @param {Array<string>} deleteByTheirs
*/
constructor(filepaths) {
constructor(filepaths, bothModified, deleteByUs, deleteByTheirs) {
super(
`Automatic merge failed with one or more merge conflicts in the following files: ${filepaths.toString()}. Fix conflicts then commit the result.`
)
this.code = this.name = MergeConflictError.code
this.data = { filepaths }
this.data = { filepaths, bothModified, deleteByUs, deleteByTheirs }
}
}
/** @type {'MergeConflictError'} */
Expand Down
75 changes: 73 additions & 2 deletions src/utils/mergeTree.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ export async function mergeTree({
const theirTree = TREE({ ref: theirOid })

const unmergedFiles = []
const bothModified = []
const deleteByUs = []
const deleteByTheirs = []

const results = await _walk({
fs,
Expand Down Expand Up @@ -120,6 +123,7 @@ export async function mergeTree({
}).then(async r => {
if (!r.cleanMerge) {
unmergedFiles.push(filepath)
bothModified.push(filepath)
if (!abortOnConflict) {
const baseOid = await base.oid()
const ourOid = await ours.oid()
Expand All @@ -137,8 +141,70 @@ export async function mergeTree({
return r.mergeResult
})
}

// deleted by us
if (
base &&
!ours &&
theirs &&
(await base.type()) === 'blob' &&
(await theirs.type()) === 'blob'
) {
unmergedFiles.push(filepath)
deleteByUs.push(filepath)
if (!abortOnConflict) {
const baseOid = await base.oid()
const theirOid = await theirs.oid()

index.delete({ filepath })

index.insert({ filepath, oid: baseOid, stage: 1 })
index.insert({ filepath, oid: theirOid, stage: 3 })
}

return {
mode: await theirs.mode(),
oid: await theirs.oid(),
type: 'blob',
path,
}
}

// deleted by theirs
if (
base &&
ours &&
!theirs &&
(await base.type()) === 'blob' &&
(await ours.type()) === 'blob'
) {
unmergedFiles.push(filepath)
deleteByTheirs.push(filepath)
if (!abortOnConflict) {
const baseOid = await base.oid()
const ourOid = await ours.oid()

index.delete({ filepath })

index.insert({ filepath, oid: baseOid, stage: 1 })
index.insert({ filepath, oid: ourOid, stage: 2 })
}

return {
mode: await ours.mode(),
oid: await ours.oid(),
type: 'blob',
path,
}
}

// deleted by both
if (base && !ours && !theirs && (await base.type()) === 'blob') {
return undefined
}

// all other types of conflicts fail
// TODO: Merge conflicts involving deletions/additions
// TODO: Merge conflicts involving additions
throw new MergeNotSupportedError()
}
}
Expand Down Expand Up @@ -194,7 +260,12 @@ export async function mergeTree({
},
})
}
return new MergeConflictError(unmergedFiles)
return new MergeConflictError(
unmergedFiles,
bothModified,
deleteByUs,
deleteByTheirs
)
}

return results.oid
Expand Down

0 comments on commit 90ea0e3

Please sign in to comment.