Auto detect and merge lockfile conflicts#3544
Conversation
src/lockfile/parse.js
Outdated
|
|
||
| export default function(str: string, fileLoc: string = 'lockfile'): Object { | ||
| str = stripBOM(str); | ||
| const GIT_MERGE_CONFLICT_START = '<<<<<<<'; |
There was a problem hiding this comment.
Drop the "GIT_", other tools will generate similar conflict markers.
afffeb6 to
37116f4
Compare
src/lockfile/parse.js
Outdated
| let variantTwo = []; | ||
| while (lines.length) { | ||
| const line = lines.shift(); | ||
| if (line.startsWith(MERGE_CONFLCIT_END)) { |
src/lockfile/parse.js
Outdated
| if (line === MERGE_CONFLICT_SEP) { | ||
| break; | ||
| } else { | ||
| variantOne.push(line); |
There was a problem hiding this comment.
can't you just push directly into variants[0]? or at least use let variantOne = variants[0] so that the concat becomes unneeded
src/lockfile/parse.js
Outdated
| variants[0] = variants[0].concat(variantOne); | ||
|
|
||
| // get the second variant | ||
| let variantTwo = []; |
37116f4 to
b0c3929
Compare
|
Addressed nits, thanks @arcanis! |
src/lockfile/parse.js
Outdated
| // get the first variant | ||
| while (lines.length) { | ||
| const line = lines.shift(); | ||
| if (line === MERGE_CONFLICT_SEP) { |
There was a problem hiding this comment.
This feature looks awesome.
Curious if you all are wanting to support git's diff3 merge conflict style (https://git-scm.com/docs/git-merge#_how_conflicts_are_presented). I think this code will break for users using diff3 because the first variant would include both the real variant and the common parent ancestor of both conflicts. I wouldn't mind making a PR in the future for supporting diff3.
Sorry if I'm overlooking something.
| */ | ||
|
|
||
| function parseWithConflict(str: string, fileLoc: string): ParseResult { | ||
| const variants = extractConflictVariants(str); |
There was a problem hiding this comment.
We assume here that only 1 merge conflict exists. We could handle 2+ conflicts if call parseWithConflict recursively.
There was a problem hiding this comment.
It supports multiple conflicts in the same file, but during a same conflict pass. I think it should be good enough for a first iteration :)
There was a problem hiding this comment.
@arcanis cool, would be nice to add an extra test for in that case.
__tests__/lockfile.js
Outdated
| expect(actual).toEqual(expected); | ||
| }); | ||
|
|
||
| test('parse merge conflicts', () => { |
There was a problem hiding this comment.
better name -> 'parse single merge conflict'
|
@kittens neat! Could you please add git diff3 conflictstyle support? |
bf5d212 to
29b549f
Compare
29b549f to
1ca53f3
Compare
|
Rebased, fixed up the tests, made flow more strict. Added support for diff3 by discarding the common ancestor from the parsed result. It seems to me like the information shown is purely for humans to make better assumptions about what to do in case of a conflict, which doesn't apply here. Let me know if that is wrong. Anything else needed here to get this merged? |
BYK
left a comment
There was a problem hiding this comment.
Overall LGTM.
One question: should we make this behavior opt-in via a --merge flag?
| }); | ||
|
|
||
| test('parse single merge conflict', () => { | ||
| const file = ` |
There was a problem hiding this comment.
Shall we not put these into a separate file? I can see both ways so just raising the question, not strong opinions.
There was a problem hiding this comment.
It's much easier to manage it like this, and we care more about the text input than we care about where it is coming from for the purpose of this test.
__tests__/lockfile.js
Outdated
|
|
||
| const {type, object} = parse(file); | ||
| expect(type).toEqual('merge'); | ||
| expect(object.a.no).toEqual('yes'); |
There was a problem hiding this comment.
Why not
expect(object).toEqual({
a: { no: 'yes' },
b: { foo: 'bar' },
c: { bar: 'foo' },
d: { yes: 'no' },
});
| <<<<<<< HEAD | ||
| b: | ||
| foo: "bar" | ||
| ======= |
There was a problem hiding this comment.
Why not try adding both, which is most probably what is needed when installing packages?
There was a problem hiding this comment.
Ah, it might not be obvious but in this example, the format is actually wrong because yarn lockfiles don't support colons :, which is why it results in a conflict.
|
|
||
| export function extractConflictVariants(str: string): Array<string> { | ||
| const variants: Array<Array<string>> = [[], []]; | ||
| const lines = str.split(/\n/g); |
There was a problem hiding this comment.
Is this safe or should we do [\r\n] instead?
src/lockfile/parse.js
Outdated
| if (line.startsWith(MERGE_CONFLICT_START)) { | ||
| // get the first variant | ||
| while (lines.length) { | ||
| const line = lines.shift(); |
There was a problem hiding this comment.
Better to not mask the outer variable?
src/lockfile/parse.js
Outdated
| * Check if a lockfile has merge conflicts. | ||
| */ | ||
| function hasMergeConflicts(str: string): boolean { | ||
| return str.includes(MERGE_CONFLICT_START); |
There was a problem hiding this comment.
Wouldn't it be better to check if all 3 markers exist?
1ca53f3 to
2c8a0b2
Compare
|
I just tried this on Jest jestjs/jest#3906 and it worked flawlessly, this feature is so awesome. @kittens thanks for the idea and getting us 99% there! |
**Summary** Follow up to #3544. Currently, `yarn` happily keeps moving if it detects merge conflicts and is able to resolve them in the lockfile. That said it doesn't persist the resolution by saving the lockfile to disk again. This patch ensures writing the lockfile if it is "dirty". This patch also causes `yarn` to throw an error if there are merge conflicts in the file and `--frozen-lockfile` option is true. **Test plan** Existing unit tests. Also try running `yarn install` with the following files: `package.json` ``` { "name": "yarnlock-auto-merge", "version": "1.0.0", "main": "index.js", "author": "Burak Yigit Kaya <byk@fb.com>", "license": "MIT", "dependencies": { "left-pad": "^1.1.3", "right-pad": "^1.0.1" } } ``` `yarn.lock` ``` <<<<<<< HEAD left-pad@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.1.3.tgz#612f61c033f3a9e08e939f1caebeea41b6f3199a" ======= right-pad@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/right-pad/-/right-pad-1.0.1.tgz#8ca08c2cbb5b55e74dafa96bf7fd1a27d568c8d0" >>>>>>> right-pad ``` Without the patch, `yarn` won't update the lockfile. With the patch, the lockfile is replaced with the merged version.
) **Summary** Follow up to #3544. Currently, `yarn` happily keeps moving if it detects merge conflicts and is able to resolve them in the lockfile. That said it doesn't persist the resolution by saving the lockfile to disk again. This patch ensures writing the lockfile if it is "dirty". This patch also causes `yarn` to throw an error if there are merge conflicts in the file and `--frozen-lockfile` option is true. **Test plan** Existing unit tests. Also try running `yarn install` with the following files: `package.json` ``` { "name": "yarnlock-auto-merge", "version": "1.0.0", "main": "index.js", "author": "Burak Yigit Kaya <byk@fb.com>", "license": "MIT", "dependencies": { "left-pad": "^1.1.3", "right-pad": "^1.0.1" } } ``` `yarn.lock` ``` <<<<<<< HEAD left-pad@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.1.3.tgz#612f61c033f3a9e08e939f1caebeea41b6f3199a" ======= right-pad@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/right-pad/-/right-pad-1.0.1.tgz#8ca08c2cbb5b55e74dafa96bf7fd1a27d568c8d0" >>>>>>> right-pad ``` Without the patch, `yarn` won't update the lockfile. With the patch, the lockfile is replaced with the merged version.
|
@cpojer, it's a bad habit to do this: |
|
Would be nice to update the |
|
What are implications of using |
|
@Vanuan I expect that to not change the file at all but use the merged version in-memory. |
|
So how do I resolve conflicts without upgrading packages? Only by hand? |
|
@Vanuan I don't really follow that reasoning. |
|
Hm. Somehow I was under impression that it would. I've seen 3 situations where yarn.lock ends up changed:
The 3rd situation is mitigated by this PR. But it isn't clear whether conflicted packages would be resolved again potentially upgrading the version of unrelated packages. Well, it looks like
In this case |
|
The "upgrade" i'm referring to is when
So the question is whether package version gets resolved again in case of merge conflicts. |
|
@Vanuan it is quite hard to keep track of what needs to be done under a closed PR. Would you mind summarizing this in a new issue as a feature request or a bug so it is clear to anyone outside of this PR what is asked to be done. This way it would be asier to reason about and work on it. |
|
Thanks for you patience. This probably doesn't belong here on GitHub. |
|
I'm locking this. The conversation that evolved isn't related to the original issue, if you have concerns about this issue, please send a pull request to fix it. Thanks! |


Summary
Git merge conflicts are common with
yarn.lock. This PR allows you to runyarnto automatically merge the two versions ofyarn.lockwhich will run Yarn and regenerate a new lockfile.Test plan
Added tests.