nx 23.1.0 → 23.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/nx.js +2 -1
- package/dist/release/changelog-renderer/index.js +4 -5
- package/dist/release/index.d.ts +1 -1
- package/dist/src/ai/configure-ai-agents-disclaimer.d.ts +5 -0
- package/dist/src/ai/configure-ai-agents-disclaimer.js +31 -0
- package/dist/src/analytics/analytics.d.ts +6 -0
- package/dist/src/analytics/analytics.js +16 -8
- package/dist/src/analytics/index.d.ts +1 -1
- package/dist/src/analytics/index.js +2 -1
- package/dist/src/command-line/add/add.js +13 -1
- package/dist/src/command-line/affected/affected.js +1 -0
- package/dist/src/command-line/configure-ai-agents/configure-ai-agents.js +2 -2
- package/dist/src/command-line/graph/graph.d.ts +1 -0
- package/dist/src/command-line/graph/graph.js +15 -6
- package/dist/src/command-line/import/import.js +2 -3
- package/dist/src/command-line/init/ai-agent-prompts.d.ts +1 -1
- package/dist/src/command-line/init/ai-agent-prompts.js +7 -6
- package/dist/src/command-line/init/command-object.js +10 -2
- package/dist/src/command-line/init/configure-plugins.js +3 -2
- package/dist/src/command-line/init/implementation/add-nx-to-monorepo.js +4 -2
- package/dist/src/command-line/init/implementation/add-nx-to-nest.js +4 -3
- package/dist/src/command-line/init/implementation/add-nx-to-npm-repo.js +4 -3
- package/dist/src/command-line/init/implementation/add-nx-to-turborepo.js +4 -3
- package/dist/src/command-line/init/implementation/angular/index.js +4 -2
- package/dist/src/command-line/init/implementation/angular/integrated-workspace.js +2 -5
- package/dist/src/command-line/init/implementation/angular/legacy-angular-versions.js +3 -0
- package/dist/src/command-line/init/implementation/dot-nx/add-nx-scripts.js +4 -1
- package/dist/src/command-line/init/implementation/utils.d.ts +3 -3
- package/dist/src/command-line/init/implementation/utils.js +22 -3
- package/dist/src/command-line/init/init-v2.js +4 -5
- package/dist/src/command-line/migrate/execute-migration.d.ts +72 -0
- package/dist/src/command-line/migrate/execute-migration.js +389 -0
- package/dist/src/command-line/migrate/migrate-ui-api.js +17 -12
- package/dist/src/command-line/migrate/migrate.d.ts +5 -45
- package/dist/src/command-line/migrate/migrate.js +122 -402
- package/dist/src/command-line/nx-cloud/connect/connect-to-nx-cloud.js +1 -1
- package/dist/src/command-line/nx-cloud/connect/view-logs.js +2 -0
- package/dist/src/command-line/release/changelog.js +5 -0
- package/dist/src/command-line/release/index.d.ts +1 -1
- package/dist/src/command-line/release/utils/git.js +7 -3
- package/dist/src/command-line/release/utils/release-graph.d.ts +19 -2
- package/dist/src/command-line/release/utils/release-graph.js +71 -19
- package/dist/src/command-line/release/utils/remote-release-clients/github.js +7 -1
- package/dist/src/command-line/release/utils/remote-release-clients/gitlab.js +1 -1
- package/dist/src/command-line/release/utils/shared.d.ts +2 -2
- package/dist/src/command-line/release/version/release-group-processor.d.ts +24 -0
- package/dist/src/command-line/release/version/release-group-processor.js +80 -6
- package/dist/src/command-line/release/version/version-actions.d.ts +27 -2
- package/dist/src/command-line/release/version/version-actions.js +9 -1
- package/dist/src/command-line/release/version.js +9 -5
- package/dist/src/command-line/reset/reset.js +6 -1
- package/dist/src/command-line/run/run-one.js +1 -0
- package/dist/src/command-line/run-many/run-many.js +1 -0
- package/dist/src/command-line/show/show-target/info.js +11 -2
- package/dist/src/command-line/show/show-target/inputs.js +50 -153
- package/dist/src/command-line/show/show-target/outputs.js +48 -173
- package/dist/src/command-line/show/show-target/utils.d.ts +28 -0
- package/dist/src/command-line/show/show-target/utils.js +83 -3
- package/dist/src/command-line/watch/watch.js +13 -0
- package/dist/src/command-line/yargs-utils/shared-options.d.ts +1 -1
- package/dist/src/commands-runner/create-command-graph.js +5 -0
- package/dist/src/config/nx-json.d.ts +5 -3
- package/dist/src/core/graph/main.js +1 -1
- package/dist/src/core/graph/styles.js +1 -1
- package/dist/src/daemon/client/client.d.ts +52 -3
- package/dist/src/daemon/client/client.js +177 -55
- package/dist/src/daemon/client/daemon-environment.d.ts +17 -0
- package/dist/src/daemon/client/daemon-environment.js +110 -2
- package/dist/src/daemon/client/daemon-socket-messenger.js +4 -0
- package/dist/src/daemon/message-types/daemon-message.d.ts +32 -0
- package/dist/src/daemon/message-types/daemon-message.js +25 -0
- package/dist/src/daemon/server/file-watching/file-watcher-sockets.d.ts +5 -0
- package/dist/src/daemon/server/file-watching/file-watcher-sockets.js +16 -0
- package/dist/src/daemon/server/handle-tasks-execution-hooks.d.ts +1 -1
- package/dist/src/daemon/server/latest-nx.js +5 -2
- package/dist/src/daemon/server/server.js +31 -4
- package/dist/src/daemon/server/shutdown-utils.d.ts +5 -0
- package/dist/src/daemon/server/shutdown-utils.js +11 -4
- package/dist/src/daemon/socket-utils.d.ts +1 -0
- package/dist/src/daemon/socket-utils.js +24 -3
- package/dist/src/daemon/tmp-dir.d.ts +43 -3
- package/dist/src/daemon/tmp-dir.js +437 -15
- package/dist/src/devkit-internals.d.ts +96 -3
- package/dist/src/devkit-internals.js +200 -4
- package/dist/src/generators/internal-utils/format-changed-files-with-prettier-if-available.d.ts +5 -0
- package/dist/src/generators/internal-utils/format-changed-files-with-prettier-if-available.js +9 -2
- package/dist/src/generators/tree.d.ts +4 -2
- package/dist/src/generators/utils/nx-json.js +13 -3
- package/dist/src/hasher/check-task-files.d.ts +86 -0
- package/dist/src/hasher/check-task-files.js +378 -0
- package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.d.ts +12 -0
- package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.js +250 -0
- package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.md +50 -0
- package/dist/src/native/index.d.ts +31 -2
- package/dist/src/native/index.js +71 -28
- package/dist/src/native/native-bindings.js +3 -0
- package/dist/src/native/native-file-cache-location.d.ts +17 -1
- package/dist/src/native/native-file-cache-location.js +86 -12
- package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
- package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
- package/dist/src/nx-cloud/utilities/get-cloud-options.js +5 -2
- package/dist/src/nx-cloud/utilities/onboarding.js +5 -2
- package/dist/src/plugins/js/lock-file/bun-parser.js +43 -10
- package/dist/src/plugins/js/lock-file/npm-parser.js +71 -27
- package/dist/src/plugins/js/lock-file/pnpm-parser.js +110 -17
- package/dist/src/plugins/js/lock-file/project-graph-pruning.js +15 -9
- package/dist/src/plugins/js/lock-file/utils/pnpm-normalizer.js +3 -0
- package/dist/src/plugins/js/lock-file/yarn-parser.js +32 -5
- package/dist/src/plugins/js/project-graph/affected/npm-packages.js +51 -20
- package/dist/src/plugins/js/project-graph/build-dependencies/target-project-locator.js +14 -4
- package/dist/src/plugins/js/utils/register.d.ts +18 -0
- package/dist/src/plugins/js/utils/register.js +81 -0
- package/dist/src/plugins/js/utils/typescript.d.ts +9 -0
- package/dist/src/plugins/js/utils/typescript.js +15 -0
- package/dist/src/project-graph/affected/affected-project-graph-models.d.ts +1 -1
- package/dist/src/project-graph/affected/affected-project-graph.d.ts +1 -1
- package/dist/src/project-graph/affected/affected-project-graph.js +2 -2
- package/dist/src/project-graph/affected/locators/project-glob-changes.js +5 -2
- package/dist/src/project-graph/build-project-graph.js +7 -1
- package/dist/src/project-graph/error-types.d.ts +12 -1
- package/dist/src/project-graph/error-types.js +68 -3
- package/dist/src/project-graph/file-utils.js +6 -1
- package/dist/src/project-graph/plugins/isolation/isolated-plugin.d.ts +1 -0
- package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +22 -1
- package/dist/src/project-graph/plugins/isolation/messaging.d.ts +16 -3
- package/dist/src/project-graph/plugins/isolation/messaging.js +3 -0
- package/dist/src/project-graph/plugins/isolation/plugin-worker.js +58 -8
- package/dist/src/project-graph/plugins/loaded-nx-plugin.js +1 -0
- package/dist/src/project-graph/plugins/transpiler.js +4 -0
- package/dist/src/project-graph/project-graph.js +23 -0
- package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.d.ts +10 -12
- package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.js +93 -63
- package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.d.ts +5 -8
- package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.js +16 -15
- package/dist/src/project-graph/utils/project-configuration/source-maps.d.ts +12 -5
- package/dist/src/project-graph/utils/project-configuration/source-maps.js +21 -7
- package/dist/src/project-graph/utils/project-configuration/target-defaults.d.ts +1 -1
- package/dist/src/project-graph/utils/project-configuration/target-defaults.js +26 -27
- package/dist/src/project-graph/utils/project-configuration/target-merging.js +50 -8
- package/dist/src/project-graph/utils/project-configuration/target-normalization.js +149 -6
- package/dist/src/project-graph/utils/project-configuration/utils.d.ts +15 -0
- package/dist/src/project-graph/utils/project-configuration/utils.js +42 -6
- package/dist/src/project-graph/utils/project-configuration-utils.d.ts +15 -12
- package/dist/src/project-graph/utils/project-configuration-utils.js +79 -101
- package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +11 -4
- package/dist/src/tasks-runner/life-cycles/performance-analysis.js +15 -6
- package/dist/src/tasks-runner/life-cycles/performance-life-cycle.js +5 -4
- package/dist/src/tasks-runner/life-cycles/performance-report.d.ts +18 -8
- package/dist/src/tasks-runner/life-cycles/performance-report.js +66 -29
- package/dist/src/tasks-runner/run-command.js +4 -2
- package/dist/src/tasks-runner/task-env.d.ts +9 -0
- package/dist/src/tasks-runner/task-env.js +31 -4
- package/dist/src/tasks-runner/task-orchestrator.d.ts +4 -1
- package/dist/src/tasks-runner/task-orchestrator.js +23 -10
- package/dist/src/tasks-runner/utils.js +2 -6
- package/dist/src/utils/acknowledge-build-scripts.d.ts +13 -0
- package/dist/src/utils/acknowledge-build-scripts.js +102 -0
- package/dist/src/utils/analytics-prompt.d.ts +0 -6
- package/dist/src/utils/analytics-prompt.js +0 -51
- package/dist/src/utils/assert-workspace-validity.js +7 -2
- package/dist/src/utils/catalog/manager-utils.d.ts +1 -2
- package/dist/src/utils/catalog/manager-utils.js +22 -5
- package/dist/src/utils/catalog/pnpm-manager.d.ts +1 -0
- package/dist/src/utils/catalog/pnpm-manager.js +3 -15
- package/dist/src/utils/catalog/yarn-manager.d.ts +1 -0
- package/dist/src/utils/catalog/yarn-manager.js +3 -15
- package/dist/src/utils/command-line-utils.js +30 -20
- package/dist/src/utils/fileutils.d.ts +5 -0
- package/dist/src/utils/fileutils.js +7 -2
- package/dist/src/utils/git-revision.d.ts +12 -0
- package/dist/src/utils/git-revision.js +25 -0
- package/dist/src/utils/git-utils.d.ts +16 -3
- package/dist/src/utils/git-utils.js +146 -52
- package/dist/src/utils/git-worktrees.d.ts +30 -0
- package/dist/src/utils/git-worktrees.js +212 -0
- package/dist/src/utils/ignore.d.ts +128 -1
- package/dist/src/utils/ignore.js +182 -12
- package/dist/src/utils/long-running-target.d.ts +10 -0
- package/dist/src/utils/long-running-target.js +19 -0
- package/dist/src/utils/min-release-age/behavior/bun.js +7 -11
- package/dist/src/utils/min-release-age/behavior/npm.js +5 -2
- package/dist/src/utils/min-release-age/behavior/pnpm.js +9 -7
- package/dist/src/utils/min-release-age/packument.js +1 -1
- package/dist/src/utils/nx-tmp-dir.d.ts +18 -0
- package/dist/src/utils/nx-tmp-dir.js +40 -0
- package/dist/src/utils/owned-private-dir.d.ts +132 -0
- package/dist/src/utils/owned-private-dir.js +323 -0
- package/dist/src/utils/package-json.js +11 -7
- package/dist/src/utils/package-manager-config/bunfig.d.ts +14 -0
- package/dist/src/utils/package-manager-config/bunfig.js +49 -0
- package/dist/src/utils/package-manager-config/npmrc.d.ts +23 -0
- package/dist/src/utils/package-manager-config/npmrc.js +124 -0
- package/dist/src/utils/package-manager-config/pnpm-config.d.ts +17 -0
- package/dist/src/utils/package-manager-config/pnpm-config.js +60 -0
- package/dist/src/utils/package-manager.d.ts +23 -2
- package/dist/src/utils/package-manager.js +264 -43
- package/dist/src/utils/provenance.js +18 -16
- package/dist/src/utils/registry-config/bun.d.ts +2 -0
- package/dist/src/utils/registry-config/bun.js +257 -0
- package/dist/src/utils/registry-config/index.d.ts +13 -0
- package/dist/src/utils/registry-config/index.js +102 -0
- package/dist/src/utils/registry-config/pnpm.d.ts +2 -0
- package/dist/src/utils/registry-config/pnpm.js +1551 -0
- package/dist/src/utils/registry-config/utils.d.ts +195 -0
- package/dist/src/utils/registry-config/utils.js +496 -0
- package/dist/src/utils/registry-config/yarn-berry.d.ts +2 -0
- package/dist/src/utils/registry-config/yarn-berry.js +612 -0
- package/dist/src/utils/registry-config/yarn-classic.d.ts +2 -0
- package/dist/src/utils/registry-config/yarn-classic.js +1001 -0
- package/dist/src/utils/safe-spawn.d.ts +24 -0
- package/dist/src/utils/safe-spawn.js +104 -0
- package/dist/src/utils/shell-quoting.d.ts +15 -0
- package/dist/src/utils/shell-quoting.js +35 -0
- package/dist/src/utils/wait-for-socket-connection.d.ts +11 -0
- package/dist/src/utils/wait-for-socket-connection.js +14 -8
- package/dist/src/utils/workspace-id.d.ts +20 -0
- package/dist/src/utils/workspace-id.js +54 -0
- package/migrations.json +13 -1
- package/package.json +22 -17
- package/dist/src/migrations/update-17-3-0/nx-release-path.d.ts +0 -3
- package/dist/src/migrations/update-17-3-0/nx-release-path.js +0 -47
- package/dist/src/utils/min-release-age/npmrc.d.ts +0 -15
- package/dist/src/utils/min-release-age/npmrc.js +0 -45
|
@@ -41,8 +41,13 @@ function readJsonFile(path, options) {
|
|
|
41
41
|
*/
|
|
42
42
|
function readYamlFile(path, options) {
|
|
43
43
|
const content = (0, node_fs_1.readFileSync)(path, 'utf-8');
|
|
44
|
-
const { load } = require('@zkochan/js-yaml');
|
|
45
|
-
|
|
44
|
+
const { load, FAILSAFE_SCHEMA } = require('@zkochan/js-yaml');
|
|
45
|
+
const { failsafe, ...loadOptions } = options ?? {};
|
|
46
|
+
return load(content, {
|
|
47
|
+
...loadOptions,
|
|
48
|
+
...(failsafe ? { schema: FAILSAFE_SCHEMA } : {}),
|
|
49
|
+
filename: path,
|
|
50
|
+
});
|
|
46
51
|
}
|
|
47
52
|
/**
|
|
48
53
|
* Serializes the given data to JSON and writes it to a file.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git parses any argument beginning with `-` as an option rather than a
|
|
3
|
+
* revision, so a flag-shaped revision could smuggle options such as
|
|
4
|
+
* `--upload-pack` into the git commands Nx runs.
|
|
5
|
+
*/
|
|
6
|
+
export declare function assertValidGitRevision(revision: string): void;
|
|
7
|
+
/**
|
|
8
|
+
* For refs Nx itself recorded from `git rev-parse` and later read back off
|
|
9
|
+
* disk, where anything other than a commit sha means the value was tampered
|
|
10
|
+
* with rather than that the user picked an unusual revision.
|
|
11
|
+
*/
|
|
12
|
+
export declare function assertValidGitSha(sha: string): void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertValidGitRevision = assertValidGitRevision;
|
|
4
|
+
exports.assertValidGitSha = assertValidGitSha;
|
|
5
|
+
/**
|
|
6
|
+
* Git parses any argument beginning with `-` as an option rather than a
|
|
7
|
+
* revision, so a flag-shaped revision could smuggle options such as
|
|
8
|
+
* `--upload-pack` into the git commands Nx runs.
|
|
9
|
+
*/
|
|
10
|
+
function assertValidGitRevision(revision) {
|
|
11
|
+
if (revision.startsWith('-')) {
|
|
12
|
+
throw new Error(`Invalid git revision: "${revision}". Git revisions cannot start with "-".`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const COMMIT_SHA = /^[0-9a-f]{7,40}$/i;
|
|
16
|
+
/**
|
|
17
|
+
* For refs Nx itself recorded from `git rev-parse` and later read back off
|
|
18
|
+
* disk, where anything other than a commit sha means the value was tampered
|
|
19
|
+
* with rather than that the user picked an unusual revision.
|
|
20
|
+
*/
|
|
21
|
+
function assertValidGitSha(sha) {
|
|
22
|
+
if (!COMMIT_SHA.test(sha)) {
|
|
23
|
+
throw new Error(`Invalid git commit sha: "${sha}". Expected a hexadecimal commit sha.`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -28,9 +28,7 @@ export declare class GitRepository {
|
|
|
28
28
|
hasFilterRepoInstalled(): Promise<boolean>;
|
|
29
29
|
filterRepo(source: string, destination: string): Promise<void>;
|
|
30
30
|
filterBranch(source: string, destination: string, branchName: string): Promise<void>;
|
|
31
|
-
private
|
|
32
|
-
private quotePath;
|
|
33
|
-
private quoteArg;
|
|
31
|
+
private execGit;
|
|
34
32
|
}
|
|
35
33
|
export interface VcsRemoteInfo {
|
|
36
34
|
domain: string;
|
|
@@ -38,6 +36,21 @@ export interface VcsRemoteInfo {
|
|
|
38
36
|
}
|
|
39
37
|
export declare function parseVcsRemoteUrl(url: string): VcsRemoteInfo | null;
|
|
40
38
|
export declare function getVcsRemoteInfo(directory?: string): VcsRemoteInfo | null;
|
|
39
|
+
export declare function getGitRootPath(cwd?: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Path of `directory` relative to its git root, posix-separated so it is
|
|
42
|
+
* identical on every OS, and '' when the directory is the git root itself.
|
|
43
|
+
* Null outside a git repository.
|
|
44
|
+
*/
|
|
45
|
+
export declare function getGitRootRelativePath(directory: string): string | null;
|
|
46
|
+
/** A shallow clone's truncated history has no stable root commit. */
|
|
47
|
+
export declare function isShallowRepository(directory?: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* SHA of the repository's first commit. Merged unrelated histories leave
|
|
50
|
+
* several root commits — the sorted-first one is picked so every clone
|
|
51
|
+
* agrees. Null when there are no commits, or outside a git repository.
|
|
52
|
+
*/
|
|
53
|
+
export declare function getFirstCommitSha(directory?: string): string | null;
|
|
41
54
|
export declare function isGitRepository(directory?: string): boolean;
|
|
42
55
|
export declare function hasUncommittedChanges(directory?: string): boolean;
|
|
43
56
|
export declare function getUncommittedChangesSnapshot(directory?: string): string;
|
|
@@ -4,6 +4,10 @@ exports.GitRepository = void 0;
|
|
|
4
4
|
exports.cloneFromUpstream = cloneFromUpstream;
|
|
5
5
|
exports.parseVcsRemoteUrl = parseVcsRemoteUrl;
|
|
6
6
|
exports.getVcsRemoteInfo = getVcsRemoteInfo;
|
|
7
|
+
exports.getGitRootPath = getGitRootPath;
|
|
8
|
+
exports.getGitRootRelativePath = getGitRootRelativePath;
|
|
9
|
+
exports.isShallowRepository = isShallowRepository;
|
|
10
|
+
exports.getFirstCommitSha = getFirstCommitSha;
|
|
7
11
|
exports.isGitRepository = isGitRepository;
|
|
8
12
|
exports.hasUncommittedChanges = hasUncommittedChanges;
|
|
9
13
|
exports.getUncommittedChangesSnapshot = getUncommittedChangesSnapshot;
|
|
@@ -16,9 +20,9 @@ const crypto = tslib_1.__importStar(require("crypto"));
|
|
|
16
20
|
const fs = tslib_1.__importStar(require("fs"));
|
|
17
21
|
const path_1 = require("path");
|
|
18
22
|
const logger_1 = require("./logger");
|
|
19
|
-
function
|
|
23
|
+
function execFileAsync(file, args, execOptions) {
|
|
20
24
|
return new Promise((res, rej) => {
|
|
21
|
-
(0, child_process_1.
|
|
25
|
+
(0, child_process_1.execFile)(file, args, { ...execOptions, windowsHide: true }, (err, stdout) => {
|
|
22
26
|
if (err) {
|
|
23
27
|
return rej(err);
|
|
24
28
|
}
|
|
@@ -29,7 +33,14 @@ function execAsync(command, execOptions) {
|
|
|
29
33
|
async function cloneFromUpstream(url, destination, { originName, depth } = {
|
|
30
34
|
originName: 'origin',
|
|
31
35
|
}) {
|
|
32
|
-
await
|
|
36
|
+
await execFileAsync('git', [
|
|
37
|
+
'clone',
|
|
38
|
+
url,
|
|
39
|
+
destination,
|
|
40
|
+
...(depth ? ['--depth', `${depth}`] : []),
|
|
41
|
+
'--origin',
|
|
42
|
+
originName,
|
|
43
|
+
], {
|
|
33
44
|
cwd: (0, path_1.dirname)(destination),
|
|
34
45
|
maxBuffer: 10 * 1024 * 1024,
|
|
35
46
|
});
|
|
@@ -41,25 +52,25 @@ class GitRepository {
|
|
|
41
52
|
this.root = this.getGitRootPath(this.directory);
|
|
42
53
|
}
|
|
43
54
|
getGitRootPath(cwd) {
|
|
44
|
-
return (
|
|
45
|
-
cwd,
|
|
46
|
-
windowsHide: true,
|
|
47
|
-
})
|
|
48
|
-
.toString()
|
|
49
|
-
.trim();
|
|
55
|
+
return getGitRootPath(cwd);
|
|
50
56
|
}
|
|
51
57
|
async hasUncommittedChanges() {
|
|
52
|
-
const data = await this.
|
|
58
|
+
const data = await this.execGit(['status', '--porcelain']);
|
|
53
59
|
return data.trim() !== '';
|
|
54
60
|
}
|
|
55
61
|
async addFetchRemote(remoteName, branch) {
|
|
56
|
-
return await this.
|
|
62
|
+
return await this.execGit([
|
|
63
|
+
'config',
|
|
64
|
+
'--add',
|
|
65
|
+
`remote.${remoteName}.fetch`,
|
|
66
|
+
`+refs/heads/${branch}:refs/remotes/${remoteName}/${branch}`,
|
|
67
|
+
]);
|
|
57
68
|
}
|
|
58
69
|
async showStat() {
|
|
59
|
-
return await this.
|
|
70
|
+
return await this.execGit(['show', '--stat']);
|
|
60
71
|
}
|
|
61
72
|
async listBranches() {
|
|
62
|
-
return (await this.
|
|
73
|
+
return (await this.execGit(['ls-remote', '--heads', '--quiet']))
|
|
63
74
|
.trim()
|
|
64
75
|
.split('\n')
|
|
65
76
|
.map((s) => s
|
|
@@ -70,45 +81,58 @@ class GitRepository {
|
|
|
70
81
|
async getGitFiles(path) {
|
|
71
82
|
// Use -z to return file names exactly as they are stored in git, separated by NULL (\x00) character.
|
|
72
83
|
// This avoids problems with special characters in file names.
|
|
73
|
-
return (await this.
|
|
84
|
+
return (await this.execGit(['ls-files', '-z', '--', path]))
|
|
74
85
|
.trim()
|
|
75
86
|
.split('\x00')
|
|
76
87
|
.map((s) => s.trim())
|
|
77
88
|
.filter(Boolean);
|
|
78
89
|
}
|
|
79
90
|
async reset(ref) {
|
|
80
|
-
return await this.
|
|
91
|
+
return await this.execGit(['reset', '--hard', ref]);
|
|
81
92
|
}
|
|
82
93
|
async mergeUnrelatedHistories(ref, message) {
|
|
83
|
-
return await this.
|
|
94
|
+
return await this.execGit([
|
|
95
|
+
'merge',
|
|
96
|
+
ref,
|
|
97
|
+
'-X',
|
|
98
|
+
'ours',
|
|
99
|
+
'--allow-unrelated-histories',
|
|
100
|
+
'-m',
|
|
101
|
+
message,
|
|
102
|
+
]);
|
|
84
103
|
}
|
|
85
104
|
async fetch(remote, ref) {
|
|
86
|
-
return await this.
|
|
105
|
+
return await this.execGit(['fetch', remote, ...(ref ? [ref] : [])]);
|
|
87
106
|
}
|
|
88
107
|
async checkout(branch, opts) {
|
|
89
|
-
return await this.
|
|
108
|
+
return await this.execGit([
|
|
109
|
+
'checkout',
|
|
110
|
+
...(opts.new ? ['-b'] : []),
|
|
111
|
+
branch,
|
|
112
|
+
...(opts.base ? [opts.base] : []),
|
|
113
|
+
]);
|
|
90
114
|
}
|
|
91
115
|
async move(path, destination) {
|
|
92
|
-
return await this.
|
|
116
|
+
return await this.execGit(['mv', '--', path, destination]);
|
|
93
117
|
}
|
|
94
118
|
async push(ref, remoteName) {
|
|
95
|
-
return await this.
|
|
119
|
+
return await this.execGit(['push', '-u', '-f', remoteName, ref]);
|
|
96
120
|
}
|
|
97
121
|
async commit(message) {
|
|
98
|
-
return await this.
|
|
122
|
+
return await this.execGit(['commit', '-am', message]);
|
|
99
123
|
}
|
|
100
124
|
async amendCommit() {
|
|
101
|
-
return await this.
|
|
125
|
+
return await this.execGit(['commit', '--amend', '-a', '--no-edit']);
|
|
102
126
|
}
|
|
103
127
|
async deleteGitRemote(name) {
|
|
104
|
-
return await this.
|
|
128
|
+
return await this.execGit(['remote', 'rm', name]);
|
|
105
129
|
}
|
|
106
130
|
async addGitRemote(name, url) {
|
|
107
|
-
return await this.
|
|
131
|
+
return await this.execGit(['remote', 'add', name, url]);
|
|
108
132
|
}
|
|
109
133
|
async hasFilterRepoInstalled() {
|
|
110
134
|
try {
|
|
111
|
-
await this.
|
|
135
|
+
await this.execGit(['filter-repo', '--help']);
|
|
112
136
|
return true;
|
|
113
137
|
}
|
|
114
138
|
catch {
|
|
@@ -121,35 +145,57 @@ class GitRepository {
|
|
|
121
145
|
// NOTE: filter-repo requires POSIX path to work
|
|
122
146
|
const sourcePosixPath = source.split(path_1.sep).join(path_1.posix.sep);
|
|
123
147
|
const destinationPosixPath = destination.split(path_1.sep).join(path_1.posix.sep);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
148
|
+
const sourcePath = ensureTrailingSlash(sourcePosixPath);
|
|
149
|
+
const destinationPath = ensureTrailingSlash(destinationPosixPath);
|
|
150
|
+
await this.execGit([
|
|
151
|
+
'filter-repo',
|
|
152
|
+
'-f',
|
|
153
|
+
...(source !== '' ? ['--path', sourcePosixPath] : []),
|
|
154
|
+
...(source !== destination
|
|
155
|
+
? ['--path-rename', `${sourcePath}:${destinationPath}`]
|
|
156
|
+
: []),
|
|
157
|
+
]);
|
|
127
158
|
}
|
|
128
159
|
async filterBranch(source, destination, branchName) {
|
|
129
160
|
// We need non-ASCII file names to not be quoted, or else filter-branch will exclude them.
|
|
130
|
-
await this.
|
|
161
|
+
await this.execGit(['config', 'core.quotepath', 'false']);
|
|
131
162
|
// NOTE: filter-repo requires POSIX path to work
|
|
132
163
|
const sourcePosixPath = source.split(path_1.sep).join(path_1.posix.sep);
|
|
133
164
|
const destinationPosixPath = destination.split(path_1.sep).join(path_1.posix.sep);
|
|
134
165
|
// First, if the source is not a root project, then only include commits relevant to the subdirectory.
|
|
135
166
|
if (source !== '') {
|
|
136
|
-
const indexFilterCommand =
|
|
137
|
-
await this.
|
|
167
|
+
const indexFilterCommand = `node ${quoteForShell((0, path_1.join)(__dirname, 'git-utils.index-filter.js'))}`;
|
|
168
|
+
await this.execGit([
|
|
169
|
+
'filter-branch',
|
|
170
|
+
'-f',
|
|
171
|
+
'--index-filter',
|
|
172
|
+
indexFilterCommand,
|
|
173
|
+
'--prune-empty',
|
|
174
|
+
'--',
|
|
175
|
+
branchName,
|
|
176
|
+
], {
|
|
138
177
|
NX_IMPORT_SOURCE: sourcePosixPath,
|
|
139
178
|
NX_IMPORT_DESTINATION: destinationPosixPath,
|
|
140
179
|
});
|
|
141
180
|
}
|
|
142
181
|
// Then, move files to their new location if necessary.
|
|
143
182
|
if (source === '' || source !== destination) {
|
|
144
|
-
const treeFilterCommand =
|
|
145
|
-
await this.
|
|
183
|
+
const treeFilterCommand = `node ${quoteForShell((0, path_1.join)(__dirname, 'git-utils.tree-filter.js'))}`;
|
|
184
|
+
await this.execGit([
|
|
185
|
+
'filter-branch',
|
|
186
|
+
'-f',
|
|
187
|
+
'--tree-filter',
|
|
188
|
+
treeFilterCommand,
|
|
189
|
+
'--',
|
|
190
|
+
branchName,
|
|
191
|
+
], {
|
|
146
192
|
NX_IMPORT_SOURCE: sourcePosixPath,
|
|
147
193
|
NX_IMPORT_DESTINATION: destinationPosixPath,
|
|
148
194
|
});
|
|
149
195
|
}
|
|
150
196
|
}
|
|
151
|
-
|
|
152
|
-
return
|
|
197
|
+
execGit(args, env) {
|
|
198
|
+
return execFileAsync('git', args, {
|
|
153
199
|
cwd: this.root,
|
|
154
200
|
maxBuffer: 10 * 1024 * 1024,
|
|
155
201
|
env: {
|
|
@@ -158,25 +204,14 @@ class GitRepository {
|
|
|
158
204
|
},
|
|
159
205
|
});
|
|
160
206
|
}
|
|
161
|
-
quotePath(path, ensureTrailingSlash) {
|
|
162
|
-
return this.quoteArg(ensureTrailingSlash && path !== '' && !path.endsWith('/')
|
|
163
|
-
? `${path}/`
|
|
164
|
-
: path);
|
|
165
|
-
}
|
|
166
|
-
quoteArg(arg) {
|
|
167
|
-
return process.platform === 'win32'
|
|
168
|
-
? // Windows/CMD only understands double-quotes, single-quotes are treated as part of the file name
|
|
169
|
-
// Bash and other shells will substitute `$` in file names with a variable value.
|
|
170
|
-
`"${arg
|
|
171
|
-
// Need to keep two slashes for Windows or else the path will be invalid.
|
|
172
|
-
// e.g. 'C:\Users\bob\projects\repo' is invalid, but 'C:\\Users\\bob\\projects\\repo' is valid
|
|
173
|
-
.replaceAll('\\', '\\\\')}"`
|
|
174
|
-
: // e.g. `git mv "$$file.txt" "libs/a/$$file.txt"` will not work since `$$` is swapped with the PID of the last process.
|
|
175
|
-
// Using single-quotes prevents this substitution.
|
|
176
|
-
`'${arg}'`;
|
|
177
|
-
}
|
|
178
207
|
}
|
|
179
208
|
exports.GitRepository = GitRepository;
|
|
209
|
+
function ensureTrailingSlash(path) {
|
|
210
|
+
return path !== '' && !path.endsWith('/') ? `${path}/` : path;
|
|
211
|
+
}
|
|
212
|
+
function quoteForShell(arg) {
|
|
213
|
+
return `'${arg.replaceAll("'", "'\"'\"'")}'`;
|
|
214
|
+
}
|
|
180
215
|
function parseVcsRemoteUrl(url) {
|
|
181
216
|
// Remove whitespace and handle common URL formats
|
|
182
217
|
const cleanUrl = url.trim();
|
|
@@ -260,6 +295,65 @@ function getVcsRemoteInfo(directory) {
|
|
|
260
295
|
return null;
|
|
261
296
|
}
|
|
262
297
|
}
|
|
298
|
+
function getGitRootPath(cwd) {
|
|
299
|
+
return (0, child_process_1.execFileSync)('git', ['rev-parse', '--show-toplevel'], {
|
|
300
|
+
cwd,
|
|
301
|
+
windowsHide: true,
|
|
302
|
+
})
|
|
303
|
+
.toString()
|
|
304
|
+
.trim();
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Path of `directory` relative to its git root, posix-separated so it is
|
|
308
|
+
* identical on every OS, and '' when the directory is the git root itself.
|
|
309
|
+
* Null outside a git repository.
|
|
310
|
+
*/
|
|
311
|
+
function getGitRootRelativePath(directory) {
|
|
312
|
+
try {
|
|
313
|
+
return (0, path_1.relative)(getGitRootPath(directory), directory)
|
|
314
|
+
.split(path_1.sep)
|
|
315
|
+
.join(path_1.posix.sep);
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/** A shallow clone's truncated history has no stable root commit. */
|
|
322
|
+
function isShallowRepository(directory) {
|
|
323
|
+
try {
|
|
324
|
+
return ((0, child_process_1.execFileSync)('git', ['rev-parse', '--is-shallow-repository'], {
|
|
325
|
+
encoding: 'utf8',
|
|
326
|
+
stdio: 'pipe',
|
|
327
|
+
cwd: directory,
|
|
328
|
+
windowsHide: true,
|
|
329
|
+
}).trim() === 'true');
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* SHA of the repository's first commit. Merged unrelated histories leave
|
|
337
|
+
* several root commits — the sorted-first one is picked so every clone
|
|
338
|
+
* agrees. Null when there are no commits, or outside a git repository.
|
|
339
|
+
*/
|
|
340
|
+
function getFirstCommitSha(directory) {
|
|
341
|
+
try {
|
|
342
|
+
const roots = (0, child_process_1.execFileSync)('git', ['rev-list', '--max-parents=0', 'HEAD'], {
|
|
343
|
+
encoding: 'utf8',
|
|
344
|
+
stdio: 'pipe',
|
|
345
|
+
cwd: directory,
|
|
346
|
+
windowsHide: true,
|
|
347
|
+
})
|
|
348
|
+
.trim()
|
|
349
|
+
.split(/\r?\n/)
|
|
350
|
+
.filter(Boolean);
|
|
351
|
+
return roots.sort()[0] ?? null;
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
263
357
|
function isGitRepository(directory) {
|
|
264
358
|
try {
|
|
265
359
|
(0, child_process_1.execSync)('git rev-parse --is-inside-work-tree', {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Roots of the git linked worktrees that live inside `workspaceRoot`,
|
|
3
|
+
* relative to it and separator-normalized.
|
|
4
|
+
*
|
|
5
|
+
* Reads git's own registry rather than probing the workspace, so it costs one
|
|
6
|
+
* `readdir` plus a small file per worktree. Worktrees outside the workspace
|
|
7
|
+
* are dropped - nothing walks them. Submodules use the same gitfile mechanism
|
|
8
|
+
* but register under `<git-dir>/modules`, so they never appear here.
|
|
9
|
+
*/
|
|
10
|
+
export declare function nestedWorktreeRoots(workspaceRoot: string): string[];
|
|
11
|
+
/**
|
|
12
|
+
* Whether `path` sits inside `root`, comparing whole path segments so that
|
|
13
|
+
* `wt-other` is not read as living inside `wt`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isInside(path: string, root: string): boolean;
|
|
16
|
+
/** Advice for duplicate project names that come from nested git worktrees. */
|
|
17
|
+
export interface WorktreeConflictAdvice {
|
|
18
|
+
/** Paths to add to `.gitignore`. */
|
|
19
|
+
ignoreTargets: string[];
|
|
20
|
+
/**
|
|
21
|
+
* Whether ignoring them settles every duplicate. When false the caller still
|
|
22
|
+
* owes the reader the ordinary advice for the ones left over.
|
|
23
|
+
*/
|
|
24
|
+
explainsAllConflicts: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* What to tell someone whose duplicate project names come from git worktrees
|
|
28
|
+
* nested in the workspace, or null when none of them do.
|
|
29
|
+
*/
|
|
30
|
+
export declare function analyzeWorktreeConflicts(workspaceRoot: string, conflicts: Map<string, string[]>): WorktreeConflictAdvice | null;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.nestedWorktreeRoots = nestedWorktreeRoots;
|
|
4
|
+
exports.isInside = isInside;
|
|
5
|
+
exports.analyzeWorktreeConflicts = analyzeWorktreeConflicts;
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const GITDIR_PREFIX = 'gitdir:';
|
|
9
|
+
function readRecordedPath(file, base) {
|
|
10
|
+
let contents;
|
|
11
|
+
try {
|
|
12
|
+
contents = (0, node_fs_1.readFileSync)(file, 'utf-8').trim();
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const raw = contents.startsWith(GITDIR_PREFIX)
|
|
18
|
+
? contents.slice(GITDIR_PREFIX.length).trim()
|
|
19
|
+
: contents;
|
|
20
|
+
return raw ? (0, node_path_1.resolve)(base, raw) : null;
|
|
21
|
+
}
|
|
22
|
+
function isDirectory(path) {
|
|
23
|
+
try {
|
|
24
|
+
return (0, node_fs_1.statSync)(path).isDirectory();
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The nearest directory at or above `from` holding a `.git`, or null when
|
|
32
|
+
* there is no repository above it.
|
|
33
|
+
*
|
|
34
|
+
* The workspace root and the repository root are often the same directory, but
|
|
35
|
+
* a workspace nested in a larger repo is ordinary - and its worktrees are
|
|
36
|
+
* registered against the repository, not against the workspace.
|
|
37
|
+
*/
|
|
38
|
+
function findGitRoot(from) {
|
|
39
|
+
let current = (0, node_path_1.resolve)(from);
|
|
40
|
+
while (!(0, node_fs_1.existsSync)((0, node_path_1.join)(current, '.git'))) {
|
|
41
|
+
const parent = (0, node_path_1.dirname)(current);
|
|
42
|
+
if (parent === current) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
current = parent;
|
|
46
|
+
}
|
|
47
|
+
return current;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* `<git-dir>/worktrees`, where git registers every linked worktree of the
|
|
51
|
+
* repository `workspaceRoot` belongs to - found by walking up, since the
|
|
52
|
+
* workspace need not be the repository root. Null when there is no `.git`, or
|
|
53
|
+
* when it is a gitfile that names nothing. The registry itself is not checked -
|
|
54
|
+
* reading it is what tells us whether anything is registered.
|
|
55
|
+
*/
|
|
56
|
+
function worktreeRegistry(workspaceRoot) {
|
|
57
|
+
const gitRoot = findGitRoot(workspaceRoot);
|
|
58
|
+
if (!gitRoot) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const dotGit = (0, node_path_1.join)(gitRoot, '.git');
|
|
62
|
+
let gitDir;
|
|
63
|
+
try {
|
|
64
|
+
gitDir = (0, node_fs_1.statSync)(dotGit).isDirectory()
|
|
65
|
+
? dotGit
|
|
66
|
+
: readRecordedPath(dotGit, gitRoot);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
if (!gitDir) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
// Running from inside a linked worktree lands on
|
|
75
|
+
// `<main>/.git/worktrees/<name>`, whose `commondir` names the real git dir.
|
|
76
|
+
// Ignored unless it names a directory that exists. That is a sanity check
|
|
77
|
+
// on a path out of a file we did not write, not a bound on where it may
|
|
78
|
+
// point - it can still name any directory on the machine.
|
|
79
|
+
const commonDir = readRecordedPath((0, node_path_1.join)(gitDir, 'commondir'), gitDir);
|
|
80
|
+
return (0, node_path_1.join)(commonDir && isDirectory(commonDir) ? commonDir : gitDir, 'worktrees');
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Roots of the git linked worktrees that live inside `workspaceRoot`,
|
|
84
|
+
* relative to it and separator-normalized.
|
|
85
|
+
*
|
|
86
|
+
* Reads git's own registry rather than probing the workspace, so it costs one
|
|
87
|
+
* `readdir` plus a small file per worktree. Worktrees outside the workspace
|
|
88
|
+
* are dropped - nothing walks them. Submodules use the same gitfile mechanism
|
|
89
|
+
* but register under `<git-dir>/modules`, so they never appear here.
|
|
90
|
+
*/
|
|
91
|
+
function nestedWorktreeRoots(workspaceRoot) {
|
|
92
|
+
const registry = worktreeRegistry(workspaceRoot);
|
|
93
|
+
if (!registry) {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
let entries;
|
|
97
|
+
try {
|
|
98
|
+
entries = (0, node_fs_1.readdirSync)(registry);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
const roots = [];
|
|
104
|
+
for (const entry of entries) {
|
|
105
|
+
// Points at the worktree's own `.git` gitfile, so its parent is the root.
|
|
106
|
+
const gitfile = readRecordedPath((0, node_path_1.join)(registry, entry, 'gitdir'), (0, node_path_1.join)(registry, entry));
|
|
107
|
+
if (!gitfile || !(0, node_fs_1.existsSync)(gitfile)) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const root = (0, node_path_1.relative)(workspaceRoot, (0, node_path_1.dirname)(gitfile));
|
|
111
|
+
// Neither the workspace itself nor anything outside it is a nested
|
|
112
|
+
// worktree Nx would walk into. Compared by whole segments, because `..` is
|
|
113
|
+
// a traversal and `..hidden` is an ordinary directory name; and by
|
|
114
|
+
// absoluteness, because `relative` across Windows drives returns its
|
|
115
|
+
// second argument, which is outside by definition and carries no `..`.
|
|
116
|
+
if (!root || (0, node_path_1.isAbsolute)(root) || root.split(node_path_1.sep)[0] === '..') {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
roots.push(root.split(node_path_1.sep).join('/'));
|
|
120
|
+
}
|
|
121
|
+
return roots;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Whether `path` sits inside `root`, comparing whole path segments so that
|
|
125
|
+
* `wt-other` is not read as living inside `wt`.
|
|
126
|
+
*/
|
|
127
|
+
function isInside(path, root) {
|
|
128
|
+
return path === root || path.startsWith(`${root}/`);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* What to tell someone whose duplicate project names come from git worktrees
|
|
132
|
+
* nested in the workspace, or null when none of them do.
|
|
133
|
+
*/
|
|
134
|
+
function analyzeWorktreeConflicts(workspaceRoot, conflicts) {
|
|
135
|
+
const worktrees = nestedWorktreeRoots(workspaceRoot);
|
|
136
|
+
if (!worktrees.length) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
const offending = [];
|
|
140
|
+
let explainsAllConflicts = true;
|
|
141
|
+
for (const roots of conflicts.values()) {
|
|
142
|
+
const fromWorktrees = worktrees.filter((worktree) => roots.some((root) => isInside(root, worktree)));
|
|
143
|
+
// What would still be defined twice once the worktrees are out of the way.
|
|
144
|
+
const remaining = roots.filter((root) => !worktrees.some((worktree) => isInside(root, worktree)));
|
|
145
|
+
// Worth naming whenever a worktree is involved, even if ignoring it
|
|
146
|
+
// doesn't settle the whole conflict.
|
|
147
|
+
for (const worktree of fromWorktrees) {
|
|
148
|
+
if (!offending.includes(worktree)) {
|
|
149
|
+
offending.push(worktree);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (!fromWorktrees.length || remaining.length > 1) {
|
|
153
|
+
explainsAllConflicts = false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (!offending.length) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
ignoreTargets: ignoreTargetsFor(workspaceRoot, offending, worktrees),
|
|
161
|
+
explainsAllConflicts,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The worktree roots themselves, or the one directory holding them.
|
|
166
|
+
*
|
|
167
|
+
* Collapsing to the directory is only worth anything when it saves lines, and
|
|
168
|
+
* only safe when it holds nothing else. With a single worktree it saves
|
|
169
|
+
* nothing and risks everything: a lone worktree in `apps/` would have us name
|
|
170
|
+
* `apps/`, which holds only that worktree today and is where the reader will
|
|
171
|
+
* put projects tomorrow.
|
|
172
|
+
*/
|
|
173
|
+
function ignoreTargetsFor(workspaceRoot, offending, worktrees) {
|
|
174
|
+
if (offending.length < 2) {
|
|
175
|
+
return offending.map(anchored);
|
|
176
|
+
}
|
|
177
|
+
const parent = commonParent(offending);
|
|
178
|
+
return (parent && holdsOnlyWorktrees(workspaceRoot, parent, worktrees)
|
|
179
|
+
? [parent]
|
|
180
|
+
: offending).map(anchored);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* A gitignore pattern rooted at the workspace rather than matched anywhere.
|
|
184
|
+
*
|
|
185
|
+
* Without the leading slash a single-segment path like `wt1` is a name, not a
|
|
186
|
+
* location - it would ignore every `wt1` at any depth. Applied to all of them
|
|
187
|
+
* so the emitted line reads the same way wherever it came from.
|
|
188
|
+
*/
|
|
189
|
+
function anchored(root) {
|
|
190
|
+
return `/${root}`;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* The directory every one of `roots` sits directly inside, or null when they
|
|
194
|
+
* don't share one - including when it would be the workspace root, which is
|
|
195
|
+
* never something to ignore.
|
|
196
|
+
*/
|
|
197
|
+
function commonParent(roots) {
|
|
198
|
+
const parents = new Set(roots.map((root) => root.split('/').slice(0, -1).join('/')));
|
|
199
|
+
const [parent] = parents;
|
|
200
|
+
return parents.size === 1 && parent ? parent : null;
|
|
201
|
+
}
|
|
202
|
+
function holdsOnlyWorktrees(workspaceRoot, directory, worktrees) {
|
|
203
|
+
let entries;
|
|
204
|
+
try {
|
|
205
|
+
entries = (0, node_fs_1.readdirSync)((0, node_path_1.join)(workspaceRoot, directory));
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
return (entries.length > 0 &&
|
|
211
|
+
entries.every((entry) => worktrees.includes(`${directory}/${entry}`)));
|
|
212
|
+
}
|