@niroai/niro 0.7.0 → 0.8.0
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/CHANGELOG.md +46 -0
- package/package.json +1 -1
- package/src/commands/add-repo.js +1 -0
- package/src/commands/init.js +2 -9
- package/src/commands/takeover.js +37 -2
- package/src/lib/folderSync.js +44 -2
- package/src/lib/git.js +24 -1
- package/src/lib/resolveContext.js +85 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
Notable changes to `@niroai/niro`. Dates are release dates.
|
|
4
4
|
|
|
5
|
+
## 0.8.0 — 2026-08-26
|
|
6
|
+
|
|
7
|
+
Needed before a server has the layered store turned on. On an older client the new
|
|
8
|
+
server-side setting is not just unusable, it is misleading.
|
|
9
|
+
|
|
10
|
+
### Upgrading
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g @niroai/niro@latest
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Nothing extra is needed from 0.7.0.
|
|
17
|
+
|
|
18
|
+
### Why this release exists
|
|
19
|
+
|
|
20
|
+
A working copy is a private index of the branch in your folder, including edits you have not
|
|
21
|
+
committed. The server can now keep several of them per developer, and can store one as a
|
|
22
|
+
layer over the shared project rather than a full copy. Three things in 0.7.0 get in the way.
|
|
23
|
+
|
|
24
|
+
**A second working copy was refused on your own machine.** 0.7.0 decided by itself that one
|
|
25
|
+
folder already held the project, and never asked the server. Once the server allows several,
|
|
26
|
+
that refusal is simply wrong. Worse, run unattended, by an agent or a script, it threw an
|
|
27
|
+
error advising `niro discard-temp-project` in the other folder. Following that advice
|
|
28
|
+
destroys a working copy that did not need destroying. The client now asks the server and
|
|
29
|
+
does what it says.
|
|
30
|
+
|
|
31
|
+
**A git worktree of a repo with no remote could not be onboarded at all.** Niro records
|
|
32
|
+
onboarding against the folder that was onboarded, which is the main checkout. A linked
|
|
33
|
+
worktree has no entry of its own, so the repo could not be named and `niro new-temp-project`
|
|
34
|
+
failed. It now resolves the main working tree through `git rev-parse --git-common-dir`.
|
|
35
|
+
That combination, a worktree of a remote-less repo, is the layered store's main use case.
|
|
36
|
+
|
|
37
|
+
**Large files were dropped from a working copy without telling you.** The size warning
|
|
38
|
+
existed only in `niro init`. A developer ran `niro new-temp-project` and lost their three
|
|
39
|
+
largest source files, the ones the whole branch was about. Niro then answered questions
|
|
40
|
+
about those files from the shared project's older copy, confidently, with nothing on screen
|
|
41
|
+
to say the working copy did not contain them. The warning now runs on both commands, and it
|
|
42
|
+
quotes the limit the server reports rather than a number compiled into the client, which
|
|
43
|
+
said 256 KiB while a server raised to 2 MiB was accepting the file happily.
|
|
44
|
+
|
|
45
|
+
### Compatibility
|
|
46
|
+
|
|
47
|
+
Against an older server, the per-file limit is not reported and the client falls back to its
|
|
48
|
+
built-in figure, so the warning quotes an estimate rather than the server's real cap. Nothing
|
|
49
|
+
else changes.
|
|
50
|
+
|
|
5
51
|
## 0.7.0 — 2026-08-17
|
|
6
52
|
|
|
7
53
|
A data-loss release. If you use `niro watch`, upgrade.
|
package/package.json
CHANGED
package/src/commands/add-repo.js
CHANGED
|
@@ -174,6 +174,7 @@ async function addRepo(pathArg, opts = {}) {
|
|
|
174
174
|
// Get filter recommendation
|
|
175
175
|
console.log(` Analyzing project structure...`);
|
|
176
176
|
const filterResult = await agentApi.filterManifest(gitId, manifest);
|
|
177
|
+
folderSync.warnOversizeFiles(manifest, filterResult, (m) => console.log(color.yellow(m)));
|
|
177
178
|
const includedCount = filterResult.included_count || filterResult.includedCount || 0;
|
|
178
179
|
const excludedCount = filterResult.excluded_count || filterResult.excludedCount || 0;
|
|
179
180
|
|
package/src/commands/init.js
CHANGED
|
@@ -422,16 +422,9 @@ async function init(pathArg, opts = {}) {
|
|
|
422
422
|
process.stdout.write(`\n Scanning ${color.cyan(targetDir)}...`);
|
|
423
423
|
const manifest = folderSync.buildManifest(targetDir);
|
|
424
424
|
process.stdout.write(` ${manifest.length} files found.\n`);
|
|
425
|
-
for (const m of manifest) {
|
|
426
|
-
if (m.size_bytes > folderSync.PER_FILE_CAP_BYTES) {
|
|
427
|
-
console.log(warn(
|
|
428
|
-
` ⚠ ${m.path} (${Math.round(m.size_bytes / 1024)} KiB) exceeds the ` +
|
|
429
|
-
`256 KiB per-file cap — it will be skipped and not indexed.`
|
|
430
|
-
));
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
425
|
const filterResult = await agentApi.filterManifest(gitId, manifest);
|
|
426
|
+
// After the filter, not before: the limit is the SERVER's, and it reports it back to us.
|
|
427
|
+
folderSync.warnOversizeFiles(manifest, filterResult, (m) => console.log(warn(m)));
|
|
435
428
|
const includedPaths = filterResult.included_paths || filterResult.includedPaths || [];
|
|
436
429
|
const includedCount = filterResult.included_count || filterResult.includedCount || includedPaths.length;
|
|
437
430
|
const excludedCount = filterResult.excluded_count || filterResult.excludedCount || 0;
|
package/src/commands/takeover.js
CHANGED
|
@@ -48,7 +48,18 @@ async function takeover(pathArg, opts = {}) {
|
|
|
48
48
|
const ctx = await resolveCtxLib.resolveContext(targetDir, { apiUrl: opts.apiUrl });
|
|
49
49
|
if (ctx.state === "error") throw new Error(`Could not resolve project: ${ctx.error}`);
|
|
50
50
|
if (!ctx.projects.length) {
|
|
51
|
-
|
|
51
|
+
// Two different dead ends, and telling them apart is the difference between a fix and a guess.
|
|
52
|
+
// A folder with no git remote is not a broken setup: `niro init` never required one. What is
|
|
53
|
+
// missing is the LOCAL record naming the repo, and for a git worktree that record belongs to the
|
|
54
|
+
// main checkout, not to the folder you are standing in. The old message sent every such user off
|
|
55
|
+
// to add a remote, which does not help: the repo keeps the URL it was registered with.
|
|
56
|
+
if (ctx.state === "no_origin") {
|
|
57
|
+
throw new Error(
|
|
58
|
+
"This folder has no git remote, and no Niro repo is recorded for it on this machine. "
|
|
59
|
+
+ "Run `niro init` here, or, if this is a git worktree, run it once in the main checkout, "
|
|
60
|
+
+ "then re-run this command.");
|
|
61
|
+
}
|
|
62
|
+
throw new Error("This folder's git remote does not match any Niro project. Run `niro init` first.");
|
|
52
63
|
}
|
|
53
64
|
let source = ctx.projects[0];
|
|
54
65
|
if (opts.project) {
|
|
@@ -83,8 +94,29 @@ async function takeover(pathArg, opts = {}) {
|
|
|
83
94
|
// already reflecting this folder, and the branch it names has moved on. The server relabels it, which
|
|
84
95
|
// is right, and a prompt there would be pure friction.
|
|
85
96
|
const here = path.resolve(repoRoot);
|
|
86
|
-
|
|
97
|
+
// Matched on the REPO as well as the project, and that second clause is the whole point.
|
|
98
|
+
//
|
|
99
|
+
// The hazard this guard exists for is one REPO checked out in two folders on two branches, both
|
|
100
|
+
// uploading into one copy until every answer is a blend of the two. Matching on the project alone
|
|
101
|
+
// also caught the case immediately below this block: a DIFFERENT repo of the same project, which is
|
|
102
|
+
// the multi-repo extend the very next call performs on purpose (ADR-016).
|
|
103
|
+
//
|
|
104
|
+
// So a developer whose change spans ai-orchestrator and ai-assistant was told their project's copy
|
|
105
|
+
// was "already held by another folder" and asked to take it away from themselves, when the server
|
|
106
|
+
// would have happily added the second repo to the same copy. Verified 2026-08-22.
|
|
107
|
+
//
|
|
108
|
+
// It stands down only when it can PROVE the other folder holds a different repo. An entry written
|
|
109
|
+
// before sourceGitId existed records no repo at all, and for those the honest answer is "cannot
|
|
110
|
+
// tell", which has to mean warn: skipping on an unknown would quietly remove the guard for exactly
|
|
111
|
+
// the older entries most likely to have been left behind. Two tests cover that case.
|
|
112
|
+
// ...and it does not apply at all when the server allows several copies per developer. The hazard
|
|
113
|
+
// named above is two folders uploading into ONE copy; when each folder gets its own, there is no
|
|
114
|
+
// blending to prevent. Refusing anyway is what made LAYERED_STORE_MULTIPLE_WORKTREES_PER_DEVELOPER
|
|
115
|
+
// unreachable: the server answers `multiple_worktrees_allowed: true` on every resolve and the CLI
|
|
116
|
+
// refused the second worktree regardless.
|
|
117
|
+
const holder = ctx.multipleWorktreesAllowed ? null : watchState.allLocal(api.currentBaseUrl()).find((e) =>
|
|
87
118
|
e.takeover && e.takeover.sourceProjectId === source.project_id
|
|
119
|
+
&& (e.takeover.sourceGitId == null || e.takeover.sourceGitId === source.git_id)
|
|
88
120
|
&& path.resolve(e.folderPath) !== here);
|
|
89
121
|
if (holder) {
|
|
90
122
|
const onBranch = holder.branch ? ` on branch \`${holder.branch}\`` : "";
|
|
@@ -154,6 +186,9 @@ async function takeover(pathArg, opts = {}) {
|
|
|
154
186
|
const manifest = folderSync.buildManifest(repoRoot);
|
|
155
187
|
const filterResult = await agentApi.filterManifest(newGitId, manifest);
|
|
156
188
|
const includedPaths = filterResult.included_paths || filterResult.includedPaths || [];
|
|
189
|
+
// The gap that cost a day: this command had no oversize warning at all, so a working copy
|
|
190
|
+
// silently omitted the three largest source files in the repo.
|
|
191
|
+
folderSync.warnOversizeFiles(manifest, filterResult, (m) => console.log(warn(m)));
|
|
157
192
|
const sizes = {};
|
|
158
193
|
for (const m of manifest) sizes[m.path] = m.size_bytes;
|
|
159
194
|
console.log(` ${step("●")} Uploading working tree: ${includedPaths.length} files ` +
|
package/src/lib/folderSync.js
CHANGED
|
@@ -126,8 +126,9 @@ const IGNORED_DIRS = new Set([
|
|
|
126
126
|
"dist", ".venv", "vendor", ".idea", ".gradle", "out",
|
|
127
127
|
]);
|
|
128
128
|
|
|
129
|
-
//
|
|
130
|
-
// /filter
|
|
129
|
+
// Fallback only, for a server too old to report its own limit. The real limit comes back from
|
|
130
|
+
// /filter as max_file_size_bytes, because a duplicated number is wrong the moment either side is
|
|
131
|
+
// configured: this constant said 256 KiB while a server raised to 2 MiB took the file happily.
|
|
131
132
|
const PER_FILE_CAP_BYTES = 262144;
|
|
132
133
|
|
|
133
134
|
const DEFAULT_CONCURRENCY = 4;
|
|
@@ -482,6 +483,46 @@ async function doInitialSync(gitId, targetDir, includedPaths, { sizes } = {}) {
|
|
|
482
483
|
return hashes;
|
|
483
484
|
}
|
|
484
485
|
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Tell the developer which files the server is going to drop for being too large.
|
|
489
|
+
*
|
|
490
|
+
* <p>This is not cosmetic. `niro new-temp-project` silently dropped a developer's three largest source
|
|
491
|
+
* files -- the ones the whole branch was about -- because the warning lived only in `niro init`. Niro
|
|
492
|
+
* then answered questions about those files from the shared project's older copy, confidently, with
|
|
493
|
+
* nothing on screen to say the working copy did not contain them.
|
|
494
|
+
*
|
|
495
|
+
* Uses the limit the SERVER reports, so it cannot drift from what the server actually did. Falls back
|
|
496
|
+
* to the built-in constant only when talking to a server too old to report one.
|
|
497
|
+
*
|
|
498
|
+
* Cross-referenced against excluded_paths where the server sends it, so a file dropped for some other
|
|
499
|
+
* reason (a doc, a lockfile, a property file) is not blamed on its size.
|
|
500
|
+
*
|
|
501
|
+
* @returns the number of files warned about
|
|
502
|
+
*/
|
|
503
|
+
function warnOversizeFiles(manifest, filterResult, log) {
|
|
504
|
+
const cap = Number(filterResult.max_file_size_bytes || filterResult.maxFileSizeBytes) || PER_FILE_CAP_BYTES;
|
|
505
|
+
// Over the limit AND a file that would otherwise have been indexed. The second half is what keeps
|
|
506
|
+
// this honest: a 900 KiB markdown file is over the limit too, but it was never going to be indexed,
|
|
507
|
+
// so blaming its size teaches the reader to ignore the warning.
|
|
508
|
+
//
|
|
509
|
+
// Deliberately NOT cross-referenced against excluded_paths, though that was the first attempt. It
|
|
510
|
+
// reads as the more precise test and is strictly worse: a file the server never saw is absent from
|
|
511
|
+
// that list, so a 3 MB source file went unwarned in testing. Size plus type needs no agreement
|
|
512
|
+
// between two lists to be right.
|
|
513
|
+
const hits = (manifest || []).filter((m) =>
|
|
514
|
+
m.size_bytes > cap && isUploadableName(m.path.split("/").pop()));
|
|
515
|
+
for (const m of hits) {
|
|
516
|
+
log(` \u26a0 ${m.path} (${Math.round(m.size_bytes / 1024)} KiB) is over this server's `
|
|
517
|
+
+ `${Math.round(cap / 1024)} KiB per-file limit \u2014 it will NOT be indexed.`);
|
|
518
|
+
}
|
|
519
|
+
if (hits.length) {
|
|
520
|
+
log(` ${hits.length} file(s) will be missing from this project. Niro will answer about them `
|
|
521
|
+
+ "from whatever version it already has, if any.");
|
|
522
|
+
}
|
|
523
|
+
return hits.length;
|
|
524
|
+
}
|
|
525
|
+
|
|
485
526
|
module.exports = {
|
|
486
527
|
buildManifest,
|
|
487
528
|
walkDir,
|
|
@@ -500,4 +541,5 @@ module.exports = {
|
|
|
500
541
|
IGNORED_DIRS,
|
|
501
542
|
BATCH_SIZE,
|
|
502
543
|
PER_FILE_CAP_BYTES,
|
|
544
|
+
warnOversizeFiles,
|
|
503
545
|
};
|
package/src/lib/git.js
CHANGED
|
@@ -99,6 +99,28 @@ function inspect(startDir) {
|
|
|
99
99
|
return { repoRoot: found.repoRoot, originUrl, branch };
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/**
|
|
103
|
+
* The root of the MAIN working tree for `repoRoot`.
|
|
104
|
+
*
|
|
105
|
+
* A linked worktree (`git worktree add`) is its own folder on its own branch, but Niro onboarding is
|
|
106
|
+
* recorded against the folder that was actually onboarded, which is the main checkout. Without this a
|
|
107
|
+
* worktree has NO local identity: nothing in ~/.niro/projects.json is keyed by its path, so the repo it
|
|
108
|
+
* belongs to cannot be named at all. That is why `niro new-temp-project` could not be run from a
|
|
109
|
+
* worktree of a repo with no git remote, which is the layered store's main use case.
|
|
110
|
+
*
|
|
111
|
+
* Falls back to `repoRoot` itself when this IS the main working tree, when git cannot answer, or when
|
|
112
|
+
* the common dir is not the usual `<root>/.git` (a bare repo, or an unusual layout). That fallback is
|
|
113
|
+
* the behaviour that existed before this function, so it can only add an answer, never change one.
|
|
114
|
+
*/
|
|
115
|
+
function mainWorktreeRoot(repoRoot) {
|
|
116
|
+
if (!repoRoot) return null;
|
|
117
|
+
const common = gitOut("git rev-parse --git-common-dir", repoRoot);
|
|
118
|
+
if (!common) return repoRoot;
|
|
119
|
+
const abs = path.resolve(repoRoot, common);
|
|
120
|
+
if (path.basename(abs) !== ".git") return repoRoot;
|
|
121
|
+
return path.dirname(abs);
|
|
122
|
+
}
|
|
123
|
+
|
|
102
124
|
/**
|
|
103
125
|
* Run ONE `git check-ignore --stdin` in `repoRoot` and report BOTH the matched
|
|
104
126
|
* paths AND whether the check itself succeeded. Paths must be POSIX-relative to
|
|
@@ -187,4 +209,5 @@ function isTracked(repoRoot, relativePath) {
|
|
|
187
209
|
}
|
|
188
210
|
}
|
|
189
211
|
|
|
190
|
-
module.exports = { inspect, deriveRepoName, checkIgnored, checkIgnoredPaths,
|
|
212
|
+
module.exports = { inspect, mainWorktreeRoot, deriveRepoName, checkIgnored, checkIgnoredPaths,
|
|
213
|
+
redactRemoteCreds, isTracked };
|
|
@@ -27,9 +27,17 @@
|
|
|
27
27
|
*
|
|
28
28
|
* Never logs secrets. Resolution failures degrade gracefully (state: "error")
|
|
29
29
|
* rather than throwing, so callers/agents can branch on `state`.
|
|
30
|
+
*
|
|
31
|
+
* A git URL is not an identity every repo has. `niro init` on a folder with no remote registers it
|
|
32
|
+
* with a synthetic `local://<name>` URL (see init.js / add-repo.js) that this module never sends back,
|
|
33
|
+
* and a folder that gains a remote AFTER onboarding keeps the URL it had at the time. Either way the
|
|
34
|
+
* URL join can never match, so when it comes back empty this falls back to the repo id recorded for
|
|
35
|
+
* the folder locally, and asks the server by that instead. See resolveByLocalGitId below.
|
|
30
36
|
*/
|
|
37
|
+
const path = require("path");
|
|
31
38
|
const git = require("./git");
|
|
32
39
|
const api = require("./api");
|
|
40
|
+
const watchState = require("./watchState");
|
|
33
41
|
|
|
34
42
|
async function resolveContext(startPath = process.cwd(), opts = {}) {
|
|
35
43
|
const { repoRoot, originUrl, branch } = git.inspect(startPath);
|
|
@@ -39,7 +47,11 @@ async function resolveContext(startPath = process.cwd(), opts = {}) {
|
|
|
39
47
|
}
|
|
40
48
|
|
|
41
49
|
if (!originUrl) {
|
|
42
|
-
|
|
50
|
+
// No remote to ask about, but the folder may still be a repo Niro knows: onboarding does not
|
|
51
|
+
// require one. Try the local identity before giving up.
|
|
52
|
+
return resolveByLocalGitId(repoRoot, branch, opts, {
|
|
53
|
+
gitRoot: repoRoot, repoUrl: null, branch: branch || null, state: "no_origin", projects: [],
|
|
54
|
+
});
|
|
43
55
|
}
|
|
44
56
|
|
|
45
57
|
// Omit branch when null (detached HEAD) -> URL-only resolve. Do NOT normalize
|
|
@@ -62,13 +74,83 @@ async function resolveContext(startPath = process.cwd(), opts = {}) {
|
|
|
62
74
|
}
|
|
63
75
|
|
|
64
76
|
const projects = normalizeProjects(data);
|
|
65
|
-
|
|
77
|
+
const byUrl = {
|
|
66
78
|
gitRoot: repoRoot,
|
|
67
79
|
repoUrl: originUrl,
|
|
68
80
|
branch: branch || null,
|
|
69
81
|
state: projects.length ? "resolved" : "unresolved",
|
|
70
82
|
projects,
|
|
83
|
+
// Whether THIS server lets a developer hold more than one working copy of a project. It is a
|
|
84
|
+
// property of the server's configuration, not of the match, and it was being dropped here: the
|
|
85
|
+
// payload carries it on every resolve precisely so the CLI can stop applying a limit the server
|
|
86
|
+
// no longer enforces, and nothing in the CLI read it. The effect was that the server-side
|
|
87
|
+
// multi-worktree support could not be reached from any client.
|
|
88
|
+
multipleWorktreesAllowed: !!(data && data.multiple_worktrees_allowed),
|
|
71
89
|
};
|
|
90
|
+
if (projects.length) return byUrl;
|
|
91
|
+
// The remote matched nothing. That is the normal answer for a folder onboarded before it had this
|
|
92
|
+
// remote, whose stored URL is the `local://` form. Same fallback as the no-origin case.
|
|
93
|
+
return resolveByLocalGitId(repoRoot, branch, opts, byUrl);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Resolve by the repo id this machine already recorded for the folder, and return `unresolved`
|
|
98
|
+
* unchanged when that cannot be done. Only ever called after the URL join came back empty, so it can
|
|
99
|
+
* add a match but never replace one.
|
|
100
|
+
*
|
|
101
|
+
* The id is exact where a URL is a guess: `local://demo` is derived from the folder name, so two
|
|
102
|
+
* unrelated folders both called `demo` would resolve to each other, while the id names one repo.
|
|
103
|
+
*/
|
|
104
|
+
async function resolveByLocalGitId(repoRoot, branch, opts, unresolved) {
|
|
105
|
+
const apiUrl = opts.apiUrl || api.currentBaseUrl();
|
|
106
|
+
for (const gitId of localGitIdCandidates(repoRoot, apiUrl)) {
|
|
107
|
+
const query = { git_id: gitId };
|
|
108
|
+
if (branch) query.branch = branch;
|
|
109
|
+
let data;
|
|
110
|
+
try {
|
|
111
|
+
data = await api.get("/api/projects/resolve", { query, apiUrl: opts.apiUrl });
|
|
112
|
+
} catch {
|
|
113
|
+
// A server that predates `git_id` rejects the whole request, and would reject it for every
|
|
114
|
+
// candidate. Give back exactly what this folder resolved to before the parameter existed.
|
|
115
|
+
return unresolved;
|
|
116
|
+
}
|
|
117
|
+
const projects = normalizeProjects(data);
|
|
118
|
+
if (projects.length) {
|
|
119
|
+
return {
|
|
120
|
+
...unresolved,
|
|
121
|
+
state: "resolved",
|
|
122
|
+
projects,
|
|
123
|
+
multipleWorktreesAllowed: !!(data && data.multiple_worktrees_allowed),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return unresolved;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Repo ids worth asking the server about for this folder, most specific first.
|
|
132
|
+
*
|
|
133
|
+
* Two folders are consulted: this one, and the MAIN working tree when this is a linked worktree. The
|
|
134
|
+
* second is the one that matters. A worktree is never itself onboarded, so it has no entry of its own,
|
|
135
|
+
* and looking only at the folder we are standing in is why a worktree could not be resolved at all.
|
|
136
|
+
*
|
|
137
|
+
* A folder that has already been taken over holds the COPY's gitId in `gitId`; the repo to resolve is
|
|
138
|
+
* the source it was taken from, which is why `takeover.sourceGitId` wins where it is present. Without
|
|
139
|
+
* that a re-run would ask about a temporary project's own repo, which the server correctly refuses.
|
|
140
|
+
*/
|
|
141
|
+
function localGitIdCandidates(repoRoot, apiUrl) {
|
|
142
|
+
const folders = [path.resolve(repoRoot)];
|
|
143
|
+
const main = git.mainWorktreeRoot(repoRoot);
|
|
144
|
+
if (main && path.resolve(main) !== folders[0]) folders.push(path.resolve(main));
|
|
145
|
+
|
|
146
|
+
const ids = [];
|
|
147
|
+
for (const folder of folders) {
|
|
148
|
+
const entry = watchState.get(folder, apiUrl);
|
|
149
|
+
if (!entry) continue;
|
|
150
|
+
const id = (entry.takeover && entry.takeover.sourceGitId) || entry.gitId;
|
|
151
|
+
if (id && !ids.includes(id)) ids.push(id);
|
|
152
|
+
}
|
|
153
|
+
return ids;
|
|
72
154
|
}
|
|
73
155
|
|
|
74
156
|
/**
|
|
@@ -129,4 +211,4 @@ function projectIdFromCreateResponse(project) {
|
|
|
129
211
|
return id.trim();
|
|
130
212
|
}
|
|
131
213
|
|
|
132
|
-
module.exports = { resolveContext, normalizeProjects, projectIdFromCreateResponse };
|
|
214
|
+
module.exports = { resolveContext, normalizeProjects, projectIdFromCreateResponse, localGitIdCandidates };
|