Skip to content

Commit

Permalink
Fixes rebase and cherry-pick conflict resolution (#5102)
Browse files Browse the repository at this point in the history
* Fixes rebase conflict resolution

* Adds cherry-pick support

* Update packages/gatsby/content/advanced/error-codes.md

Co-authored-by: Kristoffer K. <merceyz@users.noreply.github.com>

Co-authored-by: Kristoffer K. <merceyz@users.noreply.github.com>
  • Loading branch information
arcanis and merceyz committed Nov 29, 2022
1 parent 84c62e6 commit 59785b0
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 5 deletions.
34 changes: 34 additions & 0 deletions .yarn/versions/a95040d3.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
releases:
"@yarnpkg/cli": patch
"@yarnpkg/core": patch
"@yarnpkg/plugin-essentials": patch

declined:
- "@yarnpkg/plugin-compat"
- "@yarnpkg/plugin-constraints"
- "@yarnpkg/plugin-dlx"
- "@yarnpkg/plugin-exec"
- "@yarnpkg/plugin-file"
- "@yarnpkg/plugin-git"
- "@yarnpkg/plugin-github"
- "@yarnpkg/plugin-http"
- "@yarnpkg/plugin-init"
- "@yarnpkg/plugin-interactive-tools"
- "@yarnpkg/plugin-link"
- "@yarnpkg/plugin-nm"
- "@yarnpkg/plugin-npm"
- "@yarnpkg/plugin-npm-cli"
- "@yarnpkg/plugin-pack"
- "@yarnpkg/plugin-patch"
- "@yarnpkg/plugin-pnp"
- "@yarnpkg/plugin-pnpm"
- "@yarnpkg/plugin-stage"
- "@yarnpkg/plugin-typescript"
- "@yarnpkg/plugin-version"
- "@yarnpkg/plugin-workspace-tools"
- "@yarnpkg/builder"
- "@yarnpkg/doctor"
- "@yarnpkg/extensions"
- "@yarnpkg/nm"
- "@yarnpkg/pnpify"
- "@yarnpkg/sdks"
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,101 @@ exports[`Features Merge Conflict Resolution it should properly fix merge conflic
",
}
`;
exports[`Features Merge Conflict Resolution it should support fixing cherry-pick conflicts 1`] = `
"# This file is generated by running "yarn install" inside your project.
# Manual changes might be lost - proceed with caution!
__metadata:
version: 7
cacheKey: 0
"no-deps@npm:*":
<<<<<<< HEAD
version: 1.0.0
resolution: "no-deps@npm:1.0.0"
checksum: <checksum stripped>
=======
version: 2.0.0
resolution: "no-deps@npm:2.0.0"
checksum: <checksum stripped>
>>>>>>> 0000000 (commit-2.0.0)
languageName: node
linkType: hard
"root-workspace-0b6124@workspace:.":
version: 0.0.0-use.local
resolution: "root-workspace-0b6124@workspace:."
dependencies:
no-deps: "npm:*"
languageName: unknown
linkType: soft
"
`;
exports[`Features Merge Conflict Resolution it should support fixing cherry-pick conflicts 2`] = `
{
"code": 0,
"stderr": "",
"stdout": "➤ YN0048: Automatically fixed merge conflicts 👍
YN0000: ┌ Resolution step
YN0000: └ Completed
YN0000: ┌ Fetch step
YN0019: │ no-deps-npm-2.0.0-8c4f3f8395-9c77ddf9a6.zip appears to be unused - removing
YN0000: └ Completed
YN0000: ┌ Link step
YN0000: └ Completed
YN0000: Done
",
}
`;
exports[`Features Merge Conflict Resolution it should support fixing rebase conflicts 1`] = `
"# This file is generated by running "yarn install" inside your project.
# Manual changes might be lost - proceed with caution!
__metadata:
version: 7
cacheKey: 0
"no-deps@npm:*":
<<<<<<< HEAD
version: 2.0.0
resolution: "no-deps@npm:2.0.0"
checksum: <checksum stripped>
=======
version: 1.0.0
resolution: "no-deps@npm:1.0.0"
checksum: <checksum stripped>
>>>>>>> 0000000 (commit-1.0.0)
languageName: node
linkType: hard
"root-workspace-0b6124@workspace:.":
version: 0.0.0-use.local
resolution: "root-workspace-0b6124@workspace:."
dependencies:
no-deps: "npm:*"
languageName: unknown
linkType: soft
"
`;
exports[`Features Merge Conflict Resolution it should support fixing rebase conflicts 2`] = `
{
"code": 0,
"stderr": "",
"stdout": "➤ YN0048: Automatically fixed merge conflicts 👍
YN0000: ┌ Resolution step
YN0000: └ Completed
YN0000: ┌ Fetch step
YN0019: │ no-deps-npm-1.0.0-cf533b267a-8bd88a447c.zip appears to be unused - removing
YN0000: └ Completed
YN0000: ┌ Link step
YN0000: └ Completed
YN0000: Done
",
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ describe(`Features`, () => {
let lockfile = await xfs.readFilePromise(`${path}/yarn.lock`, `utf8`);
lockfile = lockfile.replace(/(checksum: ).*/g, `$1<checksum stripped>`);

await expect(lockfile).toMatchSnapshot();
expect(lockfile).toMatchSnapshot();
await expect(run(`install`)).resolves.toMatchSnapshot();
},
),
Expand Down Expand Up @@ -103,7 +103,93 @@ describe(`Features`, () => {
let lockfile = await xfs.readFilePromise(`${path}/yarn.lock`, `utf8`);
lockfile = lockfile.replace(/(checksum: ).*/g, `$1<checksum stripped>`);

await expect(lockfile).toMatchSnapshot();
expect(lockfile).toMatchSnapshot();
await expect(run(`install`)).resolves.toMatchSnapshot();
},
),
);

test(
`it should support fixing rebase conflicts`,
makeTemporaryEnv(
{},
async ({path, run, source}) => {
await execFile(`git`, [`init`], {cwd: path});
await execFile(`git`, [`config`, `user.email`, `you@example.com`], {cwd: path});
await execFile(`git`, [`config`, `user.name`, `Your Name`], {cwd: path});
await execFile(`git`, [`config`, `commit.gpgSign`, `false`], {cwd: path});

await run(`install`);
await xfs.writeJsonPromise(`${path}/package.json`, {dependencies: {[`no-deps`]: `*`}});

await execFile(`git`, [`add`, `-A`], {cwd: path});
await execFile(`git`, [`commit`, `-a`, `-m`, `my-commit`], {cwd: path});

await execFile(`git`, [`checkout`, `master`], {cwd: path});
await execFile(`git`, [`checkout`, `-b`, `1.0.0`], {cwd: path});
await run(`set`, `resolution`, `no-deps@npm:*`, `npm:1.0.0`);
await execFile(`git`, [`add`, `-A`], {cwd: path});
await execFile(`git`, [`commit`, `-a`, `-m`, `commit-1.0.0`], {cwd: path});

await execFile(`git`, [`checkout`, `master`], {cwd: path});
await execFile(`git`, [`checkout`, `-b`, `2.0.0`], {cwd: path});
await run(`set`, `resolution`, `no-deps@npm:*`, `npm:2.0.0`);
await execFile(`git`, [`add`, `-A`], {cwd: path});
await execFile(`git`, [`commit`, `-a`, `-m`, `commit-2.0.0`], {cwd: path});

await execFile(`git`, [`checkout`, `master`], {cwd: path});
await execFile(`git`, [`merge`, `1.0.0`], {cwd: path});

await expect(execFile(`git`, [`rebase`, `2.0.0`], {cwd: path, env: {LC_ALL: `C`}})).rejects.toThrow(/CONFLICT/);

let lockfile = await xfs.readFilePromise(`${path}/yarn.lock`, `utf8`);
lockfile = lockfile.replace(/(checksum: ).*/g, `$1<checksum stripped>`);
lockfile = lockfile.replace(/(>>>>>>>).*(\(commit-1.0.0\))/g, `$1 0000000 $2`);

expect(lockfile).toMatchSnapshot();
await expect(run(`install`)).resolves.toMatchSnapshot();
},
),
);

test(
`it should support fixing cherry-pick conflicts`,
makeTemporaryEnv(
{},
async ({path, run, source}) => {
await execFile(`git`, [`init`], {cwd: path});
await execFile(`git`, [`config`, `user.email`, `you@example.com`], {cwd: path});
await execFile(`git`, [`config`, `user.name`, `Your Name`], {cwd: path});
await execFile(`git`, [`config`, `commit.gpgSign`, `false`], {cwd: path});

await run(`install`);
await xfs.writeJsonPromise(`${path}/package.json`, {dependencies: {[`no-deps`]: `*`}});

await execFile(`git`, [`add`, `-A`], {cwd: path});
await execFile(`git`, [`commit`, `-a`, `-m`, `my-commit`], {cwd: path});

await execFile(`git`, [`checkout`, `master`], {cwd: path});
await execFile(`git`, [`checkout`, `-b`, `1.0.0`], {cwd: path});
await run(`set`, `resolution`, `no-deps@npm:*`, `npm:1.0.0`);
await execFile(`git`, [`add`, `-A`], {cwd: path});
await execFile(`git`, [`commit`, `-a`, `-m`, `commit-1.0.0`], {cwd: path});

await execFile(`git`, [`checkout`, `master`], {cwd: path});
await execFile(`git`, [`checkout`, `-b`, `2.0.0`], {cwd: path});
await run(`set`, `resolution`, `no-deps@npm:*`, `npm:2.0.0`);
await execFile(`git`, [`add`, `-A`], {cwd: path});
await execFile(`git`, [`commit`, `-a`, `-m`, `commit-2.0.0`], {cwd: path});

await execFile(`git`, [`checkout`, `master`], {cwd: path});
await execFile(`git`, [`merge`, `1.0.0`], {cwd: path});

await expect(execFile(`git`, [`cherry-pick`, `2.0.0`], {cwd: path, env: {LC_ALL: `C`}})).rejects.toThrow(/CONFLICT/);

let lockfile = await xfs.readFilePromise(`${path}/yarn.lock`, `utf8`);
lockfile = lockfile.replace(/(checksum: ).*/g, `$1<checksum stripped>`);
lockfile = lockfile.replace(/(>>>>>>>).*(\(commit-)/g, `$1 0000000 $2`);

expect(lockfile).toMatchSnapshot();
await expect(run(`install`)).resolves.toMatchSnapshot();
},
),
Expand Down
7 changes: 7 additions & 0 deletions packages/gatsby/content/advanced/error-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -404,3 +404,10 @@ Yarn failed to locate a package version that could satisfy the requested range.
- The registry may not have been set properly (so Yarn is querying the public npm registry instead of your internal one)

- The version may have been unpublished (although this shouldn't be possible for the public registry)

## YN0083 - `AUTOMERGE_GIT_ERROR`

When autofixing merge conflicts, Yarn needs to know what are the two lockfile versions it must merge together. To do that, it'll run `git rev-parse MERGE_HEAD HEAD` and/or `git rev-parse REBASE_HEAD HEAD`, depending on the situation. If both of those commands fail, the merge cannot succeed.

This may happen if someone accidentally committed the lockfile without first resolving the merge conflicts - should that happen, you'll need to revert the lockfile to an earlier working version and run `yarn install`.

16 changes: 14 additions & 2 deletions packages/plugin-essentials/sources/commands/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,12 +360,24 @@ async function autofixMergeConflicts(configuration: Configuration, immutable: bo
if (immutable)
throw new ReportError(MessageName.AUTOMERGE_IMMUTABLE, `Cannot autofix a lockfile when running an immutable install`);

const commits = await execUtils.execvp(`git`, [`rev-parse`, `MERGE_HEAD`, `HEAD`], {
let commits = await execUtils.execvp(`git`, [`rev-parse`, `MERGE_HEAD`, `HEAD`], {
cwd: configuration.projectCwd,
});

if (commits.code !== 0) {
commits = await execUtils.execvp(`git`, [`rev-parse`, `REBASE_HEAD`, `HEAD`], {
cwd: configuration.projectCwd,
});
}

if (commits.code !== 0) {
commits = await execUtils.execvp(`git`, [`rev-parse`, `CHERRY_PICK_HEAD`, `HEAD`], {
cwd: configuration.projectCwd,
});
}

if (commits.code !== 0)
throw new ReportError(MessageName.AUTOMERGE_GIT_ERROR, `Git returned an error when trying to find the commits pertaining to the merge conflict`);
throw new ReportError(MessageName.AUTOMERGE_GIT_ERROR, `Git returned an error when trying to find the commits pertaining to the conflict`);

let variants = await Promise.all(commits.stdout.trim().split(/\n/).map(async hash => {
const content = await execUtils.execvp(`git`, [`show`, `${hash}:./${Filename.lockfile}`], {
Expand Down
1 change: 0 additions & 1 deletion packages/yarnpkg-core/sources/MessageName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ export enum MessageName {
NETWORK_UNSAFE_HTTP = 81,
RESOLUTION_FAILED = 82,
AUTOMERGE_GIT_ERROR = 83,
AUTOMERGE_LOCKFILE_VERSION_MISMATCH = 84,
}

export function stringifyMessageName(name: MessageName | number): string {
Expand Down

0 comments on commit 59785b0

Please sign in to comment.