@gitkraken/core-gitlens 0.15.4 → 0.15.6
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 +25 -1
- package/dist/git/cache.d.ts +9 -12
- package/dist/git/cache.js +34 -16
- package/dist/git/cache.js.map +1 -1
- package/dist/git/context.d.ts +19 -2
- package/dist/git/context.js.map +1 -1
- package/dist/git/providers/provider.d.ts +7 -0
- package/dist/git/service.d.ts +5 -1
- package/dist/git/service.js +8 -4
- package/dist/git/service.js.map +1 -1
- package/dist/git/watching/changeEvent.d.ts +25 -1
- package/dist/git/watching/changeEvent.js +36 -2
- package/dist/git/watching/changeEvent.js.map +1 -1
- package/dist/git/watching/configSections.d.ts +35 -0
- package/dist/git/watching/configSections.js +210 -0
- package/dist/git/watching/configSections.js.map +1 -0
- package/dist/git/watching/watchGroup.d.ts +36 -3
- package/dist/git/watching/watchGroup.js +107 -2
- package/dist/git/watching/watchGroup.js.map +1 -1
- package/dist/git/watching/watchService.d.ts +14 -3
- package/dist/git/watching/watchService.js +10 -7
- package/dist/git/watching/watchService.js.map +1 -1
- package/dist/git/watching/watchSession.d.ts +52 -21
- package/dist/git/watching/watchSession.js +164 -42
- package/dist/git/watching/watchSession.js.map +1 -1
- package/dist/git/watching/worktreeSummary.d.ts +9 -7
- package/dist/git/watching/worktreeSummary.js +21 -10
- package/dist/git/watching/worktreeSummary.js.map +1 -1
- package/dist/git-cli/cliGitProvider.d.ts +6 -1
- package/dist/git-cli/cliGitProvider.js +45 -5
- package/dist/git-cli/cliGitProvider.js.map +1 -1
- package/dist/git-cli/providers/branches.d.ts +2 -2
- package/dist/git-cli/providers/branches.js +17 -15
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/config.d.ts +13 -0
- package/dist/git-cli/providers/config.js +38 -1
- package/dist/git-cli/providers/config.js.map +1 -1
- package/dist/git-cli/providers/operations.d.ts +35 -1
- package/dist/git-cli/providers/operations.js +175 -16
- package/dist/git-cli/providers/operations.js.map +1 -1
- package/dist/git-cli/providers/stash.d.ts +1 -0
- package/dist/git-cli/providers/stash.js +9 -3
- package/dist/git-cli/providers/stash.js.map +1 -1
- package/dist/git-cli/providers/worktrees.js +11 -4
- package/dist/git-cli/providers/worktrees.js.map +1 -1
- package/dist/git-cli/service.js +5 -1
- package/dist/git-cli/service.js.map +1 -1
- package/dist/plus/integrations/collectionMetadata.js +4 -2
- package/dist/plus/integrations/collectionMetadata.js.map +1 -1
- package/dist/plus/integrations/providers/issueSorts.d.ts +0 -1
- package/dist/plus/integrations/providers/providerErrors.d.ts +22 -0
- package/dist/plus/integrations/providers/providerErrors.js +43 -2
- package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.js +12 -8
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/reads/drains.d.ts +0 -1
- package/dist/plus/integrations/results.d.ts +13 -3
- package/dist/plus/integrations/results.js +12 -2
- package/dist/plus/integrations/results.js.map +1 -1
- package/docs/integrations.md +14 -0
- package/package.json +1 -1
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
//#region ../git/src/watching/configSections.ts
|
|
2
|
+
const utf8 = new TextDecoder("utf-8", { fatal: true });
|
|
3
|
+
/** Parses a config file's bytes; `undefined` when they aren't UTF-8 or git would reject them */
|
|
4
|
+
function parseGitConfigBytes(data) {
|
|
5
|
+
let text;
|
|
6
|
+
try {
|
|
7
|
+
text = utf8.decode(data);
|
|
8
|
+
} catch {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
return parseGitConfigSections(text);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Parses a config file's text the way git does (`config.c`): a header is `[section]`, `[section "subsection"]` (the
|
|
15
|
+
* subsection kept as written, `\` escaping the next character) or the legacy `[section.subsection]` (lowercased); a
|
|
16
|
+
* variable may follow a header on its line; `#` and `;` start a comment outside quotes; a value may be quoted, and `\`
|
|
17
|
+
* escapes `"`, `\`, `n`, `t` and `b`, or continues the value on the next line. `undefined` for anything git rejects
|
|
18
|
+
* (a malformed header or name, an unknown escape, a line ending inside quotes), and for a variable before any header,
|
|
19
|
+
* which has no section to compare.
|
|
20
|
+
*/
|
|
21
|
+
function parseGitConfigSections(text) {
|
|
22
|
+
const sections = /* @__PURE__ */ new Map();
|
|
23
|
+
let index = 0;
|
|
24
|
+
let eof = false;
|
|
25
|
+
/** Git's `get_next_char`: CRLF reads as LF, and the end of the text reads as a final LF */
|
|
26
|
+
const next = () => {
|
|
27
|
+
if (index >= text.length) {
|
|
28
|
+
eof = true;
|
|
29
|
+
return "\n";
|
|
30
|
+
}
|
|
31
|
+
const c = text[index++];
|
|
32
|
+
if (c === "\r" && text[index] === "\n") {
|
|
33
|
+
index++;
|
|
34
|
+
return "\n";
|
|
35
|
+
}
|
|
36
|
+
return c;
|
|
37
|
+
};
|
|
38
|
+
/** The rest of a `[section "subsection"]` header, from the whitespace after the section name */
|
|
39
|
+
const readSubsection = (first) => {
|
|
40
|
+
let c = first;
|
|
41
|
+
do {
|
|
42
|
+
if (c === "\n") return void 0;
|
|
43
|
+
c = next();
|
|
44
|
+
} while (isSpace(c));
|
|
45
|
+
if (c !== "\"") return void 0;
|
|
46
|
+
let subsection = "";
|
|
47
|
+
for (;;) {
|
|
48
|
+
c = next();
|
|
49
|
+
if (c === "\n") return void 0;
|
|
50
|
+
if (c === "\"") break;
|
|
51
|
+
if (c === "\\") {
|
|
52
|
+
c = next();
|
|
53
|
+
if (c === "\n") return void 0;
|
|
54
|
+
}
|
|
55
|
+
subsection += c;
|
|
56
|
+
}
|
|
57
|
+
return next() === "]" ? subsection : void 0;
|
|
58
|
+
};
|
|
59
|
+
/** A header's full section name, from just after its `[` */
|
|
60
|
+
const readHeader = () => {
|
|
61
|
+
let name = "";
|
|
62
|
+
for (;;) {
|
|
63
|
+
const c = next();
|
|
64
|
+
if (eof) return void 0;
|
|
65
|
+
if (c === "]") return name;
|
|
66
|
+
if (isSpace(c)) {
|
|
67
|
+
const subsection = readSubsection(c);
|
|
68
|
+
return subsection != null ? `${name}.${subsection}` : void 0;
|
|
69
|
+
}
|
|
70
|
+
if (!isKeyChar(c) && c !== ".") return void 0;
|
|
71
|
+
name += c.toLowerCase();
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
/** Git's `parse_value`: a value, from just after its `=` to the end of its (last continued) line */
|
|
75
|
+
const readValue = () => {
|
|
76
|
+
let value = "";
|
|
77
|
+
let quoted = false;
|
|
78
|
+
let comment = false;
|
|
79
|
+
let trimTo;
|
|
80
|
+
for (;;) {
|
|
81
|
+
let c = next();
|
|
82
|
+
if (c === "\n") {
|
|
83
|
+
if (quoted) return void 0;
|
|
84
|
+
return trimTo != null ? value.substring(0, trimTo) : value;
|
|
85
|
+
}
|
|
86
|
+
if (comment) continue;
|
|
87
|
+
if (isSpace(c) && !quoted) {
|
|
88
|
+
trimTo ??= value.length;
|
|
89
|
+
if (value.length) value += c;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (!quoted && (c === ";" || c === "#")) {
|
|
93
|
+
comment = true;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
trimTo = void 0;
|
|
97
|
+
if (c === "\\") {
|
|
98
|
+
c = next();
|
|
99
|
+
switch (c) {
|
|
100
|
+
case "\n": continue;
|
|
101
|
+
case "t":
|
|
102
|
+
c = " ";
|
|
103
|
+
break;
|
|
104
|
+
case "b":
|
|
105
|
+
c = "\b";
|
|
106
|
+
break;
|
|
107
|
+
case "n":
|
|
108
|
+
c = "\n";
|
|
109
|
+
break;
|
|
110
|
+
case "\\":
|
|
111
|
+
case "\"": break;
|
|
112
|
+
default: return;
|
|
113
|
+
}
|
|
114
|
+
value += c;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (c === "\"") {
|
|
118
|
+
quoted = !quoted;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
value += c;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
let section;
|
|
125
|
+
let comment = false;
|
|
126
|
+
for (;;) {
|
|
127
|
+
const c = next();
|
|
128
|
+
if (c === "\n") {
|
|
129
|
+
if (eof) return sections;
|
|
130
|
+
comment = false;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (comment || isSpace(c)) continue;
|
|
134
|
+
if (c === "#" || c === ";") {
|
|
135
|
+
comment = true;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (c === "[") {
|
|
139
|
+
const name = readHeader();
|
|
140
|
+
if (!name) return void 0;
|
|
141
|
+
section = sections.get(name);
|
|
142
|
+
if (section == null) {
|
|
143
|
+
section = [];
|
|
144
|
+
sections.set(name, section);
|
|
145
|
+
}
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (section == null || !isAlpha(c)) return void 0;
|
|
149
|
+
let key = c.toLowerCase();
|
|
150
|
+
let k;
|
|
151
|
+
for (;;) {
|
|
152
|
+
k = next();
|
|
153
|
+
if (eof || !isKeyChar(k)) break;
|
|
154
|
+
key += k.toLowerCase();
|
|
155
|
+
}
|
|
156
|
+
while (k === " " || k === " ") k = next();
|
|
157
|
+
if (k === "\n") {
|
|
158
|
+
section.push(key);
|
|
159
|
+
if (eof) return sections;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (k !== "=") return void 0;
|
|
163
|
+
const value = readValue();
|
|
164
|
+
if (value == null) return void 0;
|
|
165
|
+
section.push(`${key}=${value}`);
|
|
166
|
+
if (eof) return sections;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Compares two reads of a config file section by section. A changed `branch.<name>` section (a subsection of
|
|
171
|
+
* `branch`) names that local branch; any other changed section, the bare `[branch]` included, makes the change
|
|
172
|
+
* unconfined.
|
|
173
|
+
*/
|
|
174
|
+
function diffGitConfigSections(before, after) {
|
|
175
|
+
const branches = /* @__PURE__ */ new Set();
|
|
176
|
+
let changed = false;
|
|
177
|
+
let confined = true;
|
|
178
|
+
const compare = (name) => {
|
|
179
|
+
if (sameVariables(before.get(name), after.get(name))) return;
|
|
180
|
+
changed = true;
|
|
181
|
+
if (name.startsWith("branch.") && name.length > 7) branches.add(`refs/heads/${name.substring(7)}`);
|
|
182
|
+
else confined = false;
|
|
183
|
+
};
|
|
184
|
+
for (const name of before.keys()) compare(name);
|
|
185
|
+
for (const name of after.keys()) if (!before.has(name)) compare(name);
|
|
186
|
+
return changed ? {
|
|
187
|
+
branches,
|
|
188
|
+
confined
|
|
189
|
+
} : void 0;
|
|
190
|
+
}
|
|
191
|
+
function sameVariables(a, b) {
|
|
192
|
+
if (a == null || b == null) return a === b;
|
|
193
|
+
if (a.length !== b.length) return false;
|
|
194
|
+
return a.every((v, i) => v === b[i]);
|
|
195
|
+
}
|
|
196
|
+
/** Git's own `isspace`, which leaves out `\v` and `\f`: a value keeps them, so they must not compare as trimmed */
|
|
197
|
+
function isSpace(c) {
|
|
198
|
+
return c === " " || c === " " || c === "\n" || c === "\r";
|
|
199
|
+
}
|
|
200
|
+
function isAlpha(c) {
|
|
201
|
+
return c >= "a" && c <= "z" || c >= "A" && c <= "Z";
|
|
202
|
+
}
|
|
203
|
+
/** Git's `iskeychar`: an ASCII letter or digit, or `-` */
|
|
204
|
+
function isKeyChar(c) {
|
|
205
|
+
return isAlpha(c) || c >= "0" && c <= "9" || c === "-";
|
|
206
|
+
}
|
|
207
|
+
//#endregion
|
|
208
|
+
export { diffGitConfigSections, parseGitConfigBytes, parseGitConfigSections };
|
|
209
|
+
|
|
210
|
+
//# sourceMappingURL=configSections.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configSections.js","names":[],"sources":["../../../../git/src/watching/configSections.ts"],"sourcesContent":["/**\n * A git config file's variables by section, as git reads them: each key is a section's full name (`core`,\n * `branch.feature/x`: the section name lowercased, then its subsection), and each value lists that section's variables\n * in file order as `<key>=<value>` (the key lowercased, the value unquoted and unescaped), or a bare `<key>` for one\n * with no value. A section whose header repeats lists the variables of every occurrence.\n */\nexport type GitConfigSections = ReadonlyMap<string, readonly string[]>;\n\n/**\n * What changed between two reads of the same config file: every local branch (full `refs/heads/` name) whose\n * `branch.<name>` section changed, and whether that is all that changed. `undefined` when nothing changed.\n */\nexport interface GitConfigSectionsChange {\n\treadonly branches: ReadonlySet<string>;\n\treadonly confined: boolean;\n}\n\nconst utf8 = new TextDecoder('utf-8', { fatal: true });\n\n/** Parses a config file's bytes; `undefined` when they aren't UTF-8 or git would reject them */\nexport function parseGitConfigBytes(data: Uint8Array): GitConfigSections | undefined {\n\tlet text: string;\n\ttry {\n\t\ttext = utf8.decode(data);\n\t} catch {\n\t\treturn undefined;\n\t}\n\treturn parseGitConfigSections(text);\n}\n\n/**\n * Parses a config file's text the way git does (`config.c`): a header is `[section]`, `[section \"subsection\"]` (the\n * subsection kept as written, `\\` escaping the next character) or the legacy `[section.subsection]` (lowercased); a\n * variable may follow a header on its line; `#` and `;` start a comment outside quotes; a value may be quoted, and `\\`\n * escapes `\"`, `\\`, `n`, `t` and `b`, or continues the value on the next line. `undefined` for anything git rejects\n * (a malformed header or name, an unknown escape, a line ending inside quotes), and for a variable before any header,\n * which has no section to compare.\n */\nexport function parseGitConfigSections(text: string): GitConfigSections | undefined {\n\tconst sections = new Map<string, string[]>();\n\tlet index = 0;\n\tlet eof = false;\n\n\t/** Git's `get_next_char`: CRLF reads as LF, and the end of the text reads as a final LF */\n\tconst next = (): string => {\n\t\tif (index >= text.length) {\n\t\t\teof = true;\n\t\t\treturn '\\n';\n\t\t}\n\n\t\tconst c = text[index++];\n\t\tif (c === '\\r' && text[index] === '\\n') {\n\t\t\tindex++;\n\t\t\treturn '\\n';\n\t\t}\n\t\treturn c;\n\t};\n\n\t/** The rest of a `[section \"subsection\"]` header, from the whitespace after the section name */\n\tconst readSubsection = (first: string): string | undefined => {\n\t\tlet c = first;\n\t\tdo {\n\t\t\tif (c === '\\n') return undefined;\n\n\t\t\tc = next();\n\t\t} while (isSpace(c));\n\t\tif (c !== '\"') return undefined;\n\n\t\tlet subsection = '';\n\t\tfor (;;) {\n\t\t\tc = next();\n\t\t\tif (c === '\\n') return undefined;\n\t\t\tif (c === '\"') break;\n\n\t\t\tif (c === '\\\\') {\n\t\t\t\tc = next();\n\t\t\t\tif (c === '\\n') return undefined;\n\t\t\t}\n\n\t\t\tsubsection += c;\n\t\t}\n\t\treturn next() === ']' ? subsection : undefined;\n\t};\n\n\t/** A header's full section name, from just after its `[` */\n\tconst readHeader = (): string | undefined => {\n\t\tlet name = '';\n\t\tfor (;;) {\n\t\t\tconst c = next();\n\t\t\tif (eof) return undefined;\n\t\t\tif (c === ']') return name;\n\n\t\t\tif (isSpace(c)) {\n\t\t\t\tconst subsection = readSubsection(c);\n\t\t\t\treturn subsection != null ? `${name}.${subsection}` : undefined;\n\t\t\t}\n\t\t\tif (!isKeyChar(c) && c !== '.') return undefined;\n\n\t\t\tname += c.toLowerCase();\n\t\t}\n\t};\n\n\t/** Git's `parse_value`: a value, from just after its `=` to the end of its (last continued) line */\n\tconst readValue = (): string | undefined => {\n\t\tlet value = '';\n\t\tlet quoted = false;\n\t\tlet comment = false;\n\t\tlet trimTo: number | undefined;\n\t\tfor (;;) {\n\t\t\tlet c = next();\n\t\t\tif (c === '\\n') {\n\t\t\t\tif (quoted) return undefined;\n\n\t\t\t\treturn trimTo != null ? value.substring(0, trimTo) : value;\n\t\t\t}\n\t\t\tif (comment) continue;\n\n\t\t\tif (isSpace(c) && !quoted) {\n\t\t\t\ttrimTo ??= value.length;\n\t\t\t\tif (value.length) {\n\t\t\t\t\tvalue += c;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!quoted && (c === ';' || c === '#')) {\n\t\t\t\tcomment = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttrimTo = undefined;\n\t\t\tif (c === '\\\\') {\n\t\t\t\tc = next();\n\t\t\t\tswitch (c) {\n\t\t\t\t\tcase '\\n':\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tcase 't':\n\t\t\t\t\t\tc = '\\t';\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'b':\n\t\t\t\t\t\tc = '\\b';\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'n':\n\t\t\t\t\t\tc = '\\n';\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase '\\\\':\n\t\t\t\t\tcase '\"':\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\tvalue += c;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (c === '\"') {\n\t\t\t\tquoted = !quoted;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvalue += c;\n\t\t}\n\t};\n\n\tlet section: string[] | undefined;\n\tlet comment = false;\n\tfor (;;) {\n\t\tconst c = next();\n\t\tif (c === '\\n') {\n\t\t\tif (eof) return sections;\n\n\t\t\tcomment = false;\n\t\t\tcontinue;\n\t\t}\n\t\tif (comment || isSpace(c)) continue;\n\n\t\tif (c === '#' || c === ';') {\n\t\t\tcomment = true;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (c === '[') {\n\t\t\tconst name = readHeader();\n\t\t\tif (!name) return undefined;\n\n\t\t\tsection = sections.get(name);\n\t\t\tif (section == null) {\n\t\t\t\tsection = [];\n\t\t\t\tsections.set(name, section);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (section == null || !isAlpha(c)) return undefined;\n\n\t\t// Git's `get_value`: the name runs to the first character a name can't hold\n\t\tlet key = c.toLowerCase();\n\t\tlet k: string;\n\t\tfor (;;) {\n\t\t\tk = next();\n\t\t\tif (eof || !isKeyChar(k)) break;\n\n\t\t\tkey += k.toLowerCase();\n\t\t}\n\t\twhile (k === ' ' || k === '\\t') {\n\t\t\tk = next();\n\t\t}\n\n\t\tif (k === '\\n') {\n\t\t\tsection.push(key);\n\t\t\tif (eof) return sections;\n\n\t\t\tcontinue;\n\t\t}\n\t\tif (k !== '=') return undefined;\n\n\t\tconst value = readValue();\n\t\tif (value == null) return undefined;\n\n\t\tsection.push(`${key}=${value}`);\n\t\tif (eof) return sections;\n\t}\n}\n\n/**\n * Compares two reads of a config file section by section. A changed `branch.<name>` section (a subsection of\n * `branch`) names that local branch; any other changed section, the bare `[branch]` included, makes the change\n * unconfined.\n */\nexport function diffGitConfigSections(\n\tbefore: GitConfigSections,\n\tafter: GitConfigSections,\n): GitConfigSectionsChange | undefined {\n\tconst branches = new Set<string>();\n\tlet changed = false;\n\tlet confined = true;\n\n\tconst compare = (name: string): void => {\n\t\tif (sameVariables(before.get(name), after.get(name))) return;\n\n\t\tchanged = true;\n\t\tif (name.startsWith('branch.') && name.length > 7) {\n\t\t\tbranches.add(`refs/heads/${name.substring(7)}`);\n\t\t} else {\n\t\t\tconfined = false;\n\t\t}\n\t};\n\n\tfor (const name of before.keys()) {\n\t\tcompare(name);\n\t}\n\tfor (const name of after.keys()) {\n\t\tif (!before.has(name)) {\n\t\t\tcompare(name);\n\t\t}\n\t}\n\treturn changed ? { branches: branches, confined: confined } : undefined;\n}\n\nfunction sameVariables(a: readonly string[] | undefined, b: readonly string[] | undefined): boolean {\n\tif (a == null || b == null) return a === b;\n\tif (a.length !== b.length) return false;\n\n\treturn a.every((v, i) => v === b[i]);\n}\n\n/** Git's own `isspace`, which leaves out `\\v` and `\\f`: a value keeps them, so they must not compare as trimmed */\nfunction isSpace(c: string): boolean {\n\treturn c === ' ' || c === '\\t' || c === '\\n' || c === '\\r';\n}\n\nfunction isAlpha(c: string): boolean {\n\treturn (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');\n}\n\n/** Git's `iskeychar`: an ASCII letter or digit, or `-` */\nfunction isKeyChar(c: string): boolean {\n\treturn isAlpha(c) || (c >= '0' && c <= '9') || c === '-';\n}\n"],"mappings":"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"}
|
|
@@ -11,9 +11,12 @@ export interface WatchGroupHooks {
|
|
|
11
11
|
/**
|
|
12
12
|
* `ref` is the full ref name (or ref directory) or pseudo-ref (`HEAD`, `ORIG_HEAD`) whose file changed, with a
|
|
13
13
|
* trailing `.lock` stripped; `false` when config changed without moving refs, and `undefined` when ref movement
|
|
14
|
-
* is unknown. `overflow` marks the every-kind rescan of a watcher that dropped events
|
|
14
|
+
* is unknown. `overflow` marks the every-kind rescan of a watcher that dropped events, which no single write can
|
|
15
|
+
* account for. `configBranches`, with a `config` change, are the local branches (full ref names) whose
|
|
16
|
+
* `branch.<name>` sections are all that changed in the shared `.git/config`; without it, the config change may be
|
|
17
|
+
* anywhere.
|
|
15
18
|
*/
|
|
16
|
-
readonly onRepoChanged: (repoPath: string, changes: RepositoryChange[], ref?: string | false, overflow?: boolean) => void;
|
|
19
|
+
readonly onRepoChanged: (repoPath: string, changes: RepositoryChange[], ref?: string | false, overflow?: boolean, configBranches?: ReadonlySet<string>) => void;
|
|
17
20
|
readonly onFetchHeadChanged?: (repoPath: string) => void;
|
|
18
21
|
readonly onIgnoresChanged?: (repoPath: string) => void;
|
|
19
22
|
/** The refs whose loose files a poll stats for this session, read at each poll */
|
|
@@ -49,20 +52,39 @@ interface SessionEntry {
|
|
|
49
52
|
*
|
|
50
53
|
* For a standard (non-worktree) repo, the common and root directories
|
|
51
54
|
* are the same, but we still create separate watchers for clean separation.
|
|
55
|
+
*
|
|
56
|
+
* Given a `readFile`, the group keeps the shared `.git/config` as it last read it, section by section. A report of
|
|
57
|
+
* that file (or its lock), from whichever watcher, is read and compared with it once, and every session hears what
|
|
58
|
+
* changed: only some branches' `branch.<name>` sections (`config`, confined to them), anything else (`config` and
|
|
59
|
+
* `remotes`, unconfined), or nothing at all, when an earlier report's read already delivered the change. Reads run
|
|
60
|
+
* one at a time, so each compares with the one before it and none is skipped. The first read, taken as the group
|
|
61
|
+
* starts, is the baseline and delivers nothing; a report arriving before it completes may describe a change it
|
|
62
|
+
* already holds, so it is delivered unconfined. A read that fails or doesn't settle within a few seconds, or a file
|
|
63
|
+
* git couldn't parse, is unconfined too.
|
|
52
64
|
*/
|
|
53
65
|
export declare class WatchGroup implements UnifiedDisposable {
|
|
54
66
|
readonly commonGitDir: string;
|
|
55
67
|
private readonly provider;
|
|
56
68
|
/** When given, a watcher that reports it can't be trusted is polled; otherwise none is asked */
|
|
57
69
|
private readonly poller?;
|
|
70
|
+
/** When given, reports of the shared `.git/config` are classified by what changed in it (see above) */
|
|
71
|
+
private readonly readFile?;
|
|
58
72
|
private readonly _sessions;
|
|
59
73
|
private _commonWatcher;
|
|
60
74
|
private _commonReliability;
|
|
61
75
|
private _commonTarget;
|
|
62
76
|
private _disposed;
|
|
77
|
+
/** The shared `.git/config`, by section, as the last read that could parse it found it */
|
|
78
|
+
private _config;
|
|
79
|
+
/** The baseline read is in flight */
|
|
80
|
+
private _configBaselining;
|
|
81
|
+
private _configReading;
|
|
82
|
+
private _configRereadQueued;
|
|
63
83
|
constructor(commonGitDir: string, provider: FileWatchingProvider,
|
|
64
84
|
/** When given, a watcher that reports it can't be trusted is polled; otherwise none is asked */
|
|
65
|
-
poller?: GitDirPoller | undefined
|
|
85
|
+
poller?: GitDirPoller | undefined,
|
|
86
|
+
/** When given, reports of the shared `.git/config` are classified by what changed in it (see above) */
|
|
87
|
+
readFile?: ((path: string) => Promise<Uint8Array>) | undefined);
|
|
66
88
|
[Symbol.dispose](): void;
|
|
67
89
|
/** Dispose the entire group (common watcher + all root watchers). */
|
|
68
90
|
dispose(): void;
|
|
@@ -97,6 +119,17 @@ export declare class WatchGroup implements UnifiedDisposable {
|
|
|
97
119
|
private onCommonEvent;
|
|
98
120
|
private deliverOverflow;
|
|
99
121
|
private onRootEvent;
|
|
122
|
+
private onConfigReported;
|
|
123
|
+
private readConfigBaseline;
|
|
124
|
+
/** Reads and compares until no report came in during the last read: it may describe a change the read missed */
|
|
125
|
+
private readConfigChanges;
|
|
126
|
+
/** `undefined` when the file couldn't be read or parsed, or the read didn't settle in time */
|
|
127
|
+
private readConfig;
|
|
128
|
+
/**
|
|
129
|
+
* Tells every session of a change to the shared `.git/config`: confined to some branches' sections, or (`undefined`,
|
|
130
|
+
* or a change outside them) anywhere, which may have changed a remote's settings too. Either way no ref moved
|
|
131
|
+
*/
|
|
132
|
+
private deliverConfigChange;
|
|
100
133
|
}
|
|
101
134
|
//#endregion
|
|
102
135
|
//# sourceMappingURL=watchGroup.d.ts.map
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { some } from "../../utils/iterable.js";
|
|
2
|
-
import { isDescendant, relative } from "../../utils/path.js";
|
|
2
|
+
import { isDescendant, joinPaths, relative } from "../../utils/path.js";
|
|
3
3
|
import { GitDirProbeTarget } from "./gitDirPoller.js";
|
|
4
4
|
import { dotGitGlobCombined, dotGitGlobCommon, dotGitGlobRoot, getGitDirRefProbes, gitDirCommonProbes, gitDirExcludes, gitDirProbesByEmittedPath, gitDirRootProbes, shouldIgnoreRepoPath } from "./watcherPatterns.js";
|
|
5
5
|
import { allWatchedRepositoryChanges, classifyGitDirChange } from "./classifyChange.js";
|
|
6
|
+
import { diffGitConfigSections, parseGitConfigBytes } from "./configSections.js";
|
|
6
7
|
//#region ../git/src/watching/watchGroup.ts
|
|
7
8
|
/**
|
|
9
|
+
* How long a read of the shared `.git/config` may take before it counts as failed (unconfined). Reads run one at a
|
|
10
|
+
* time, so one that never settles, as on a hung network or WSL mount, would otherwise hold back every later report.
|
|
11
|
+
*/
|
|
12
|
+
const configReadTimeoutMs = 5e3;
|
|
13
|
+
/**
|
|
8
14
|
* Manages filesystem watchers for a single physical `.git` directory,
|
|
9
15
|
* shared across all worktrees that reference it.
|
|
10
16
|
*
|
|
@@ -15,20 +21,38 @@ import { allWatchedRepositoryChanges, classifyGitDirChange } from "./classifyCha
|
|
|
15
21
|
*
|
|
16
22
|
* For a standard (non-worktree) repo, the common and root directories
|
|
17
23
|
* are the same, but we still create separate watchers for clean separation.
|
|
24
|
+
*
|
|
25
|
+
* Given a `readFile`, the group keeps the shared `.git/config` as it last read it, section by section. A report of
|
|
26
|
+
* that file (or its lock), from whichever watcher, is read and compared with it once, and every session hears what
|
|
27
|
+
* changed: only some branches' `branch.<name>` sections (`config`, confined to them), anything else (`config` and
|
|
28
|
+
* `remotes`, unconfined), or nothing at all, when an earlier report's read already delivered the change. Reads run
|
|
29
|
+
* one at a time, so each compares with the one before it and none is skipped. The first read, taken as the group
|
|
30
|
+
* starts, is the baseline and delivers nothing; a report arriving before it completes may describe a change it
|
|
31
|
+
* already holds, so it is delivered unconfined. A read that fails or doesn't settle within a few seconds, or a file
|
|
32
|
+
* git couldn't parse, is unconfined too.
|
|
18
33
|
*/
|
|
19
34
|
var WatchGroup = class {
|
|
20
35
|
commonGitDir;
|
|
21
36
|
provider;
|
|
22
37
|
poller;
|
|
38
|
+
readFile;
|
|
23
39
|
_sessions = /* @__PURE__ */ new Map();
|
|
24
40
|
_commonWatcher;
|
|
25
41
|
_commonReliability = "reliable";
|
|
26
42
|
_commonTarget;
|
|
27
43
|
_disposed = false;
|
|
28
|
-
|
|
44
|
+
/** The shared `.git/config`, by section, as the last read that could parse it found it */
|
|
45
|
+
_config;
|
|
46
|
+
/** The baseline read is in flight */
|
|
47
|
+
_configBaselining = false;
|
|
48
|
+
_configReading = false;
|
|
49
|
+
_configRereadQueued = false;
|
|
50
|
+
constructor(commonGitDir, provider, poller, readFile) {
|
|
29
51
|
this.commonGitDir = commonGitDir;
|
|
30
52
|
this.provider = provider;
|
|
31
53
|
this.poller = poller;
|
|
54
|
+
this.readFile = readFile;
|
|
55
|
+
if (readFile != null) this.readConfigBaseline();
|
|
32
56
|
}
|
|
33
57
|
[Symbol.dispose]() {
|
|
34
58
|
this.dispose();
|
|
@@ -223,6 +247,10 @@ var WatchGroup = class {
|
|
|
223
247
|
if (!isDescendant(event.path, this.commonGitDir)) return;
|
|
224
248
|
const relativePath = relative(this.commonGitDir, event.path);
|
|
225
249
|
if (shouldIgnoreRepoPath(relativePath)) return;
|
|
250
|
+
if (this.readFile != null && isConfigFile(relativePath)) {
|
|
251
|
+
this.onConfigReported();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
226
254
|
const isFetchHead = relativePath === "FETCH_HEAD";
|
|
227
255
|
const isExclude = relativePath === "info/exclude";
|
|
228
256
|
const changes = isFetchHead ? void 0 : classifyGitDirChange(relativePath);
|
|
@@ -260,11 +288,88 @@ var WatchGroup = class {
|
|
|
260
288
|
entry.hooks.onFetchHeadChanged?.(entry.repoPath);
|
|
261
289
|
return;
|
|
262
290
|
}
|
|
291
|
+
if (entry.coversCommon && this.readFile != null && isConfigFile(relativePath)) {
|
|
292
|
+
this.onConfigReported();
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
263
295
|
const changes = classifyGitDirChange(relativePath);
|
|
264
296
|
if (changes != null) entry.hooks.onRepoChanged(entry.repoPath, changes, getChangedRef(relativePath));
|
|
265
297
|
if (relativePath === "info/exclude") entry.hooks.onIgnoresChanged?.(entry.repoPath);
|
|
266
298
|
}
|
|
299
|
+
onConfigReported() {
|
|
300
|
+
if (this._configBaselining) {
|
|
301
|
+
this.deliverConfigChange(void 0);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (this._configReading) {
|
|
305
|
+
this._configRereadQueued = true;
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
this.readConfigChanges();
|
|
309
|
+
}
|
|
310
|
+
async readConfigBaseline() {
|
|
311
|
+
this._configBaselining = true;
|
|
312
|
+
this._configReading = true;
|
|
313
|
+
try {
|
|
314
|
+
const config = await this.readConfig();
|
|
315
|
+
if (this._disposed) return;
|
|
316
|
+
this._config = config;
|
|
317
|
+
} finally {
|
|
318
|
+
this._configBaselining = false;
|
|
319
|
+
this._configReading = false;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/** Reads and compares until no report came in during the last read: it may describe a change the read missed */
|
|
323
|
+
async readConfigChanges() {
|
|
324
|
+
this._configReading = true;
|
|
325
|
+
try {
|
|
326
|
+
do {
|
|
327
|
+
this._configRereadQueued = false;
|
|
328
|
+
const config = await this.readConfig();
|
|
329
|
+
if (this._disposed) return;
|
|
330
|
+
const previous = this._config;
|
|
331
|
+
if (config == null || previous == null) {
|
|
332
|
+
this._config ??= config;
|
|
333
|
+
this.deliverConfigChange(void 0);
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
this._config = config;
|
|
337
|
+
const change = diffGitConfigSections(previous, config);
|
|
338
|
+
if (change != null) this.deliverConfigChange(change);
|
|
339
|
+
} while (this._configRereadQueued);
|
|
340
|
+
} finally {
|
|
341
|
+
this._configReading = false;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
/** `undefined` when the file couldn't be read or parsed, or the read didn't settle in time */
|
|
345
|
+
async readConfig() {
|
|
346
|
+
const readFile = this.readFile;
|
|
347
|
+
if (readFile == null) return void 0;
|
|
348
|
+
let timer;
|
|
349
|
+
try {
|
|
350
|
+
const data = await Promise.race([readFile(joinPaths(this.commonGitDir, "config")), new Promise((resolve) => {
|
|
351
|
+
timer = setTimeout(resolve, configReadTimeoutMs, void 0);
|
|
352
|
+
})]);
|
|
353
|
+
return data != null ? parseGitConfigBytes(data) : void 0;
|
|
354
|
+
} catch {
|
|
355
|
+
return;
|
|
356
|
+
} finally {
|
|
357
|
+
clearTimeout(timer);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Tells every session of a change to the shared `.git/config`: confined to some branches' sections, or (`undefined`,
|
|
362
|
+
* or a change outside them) anywhere, which may have changed a remote's settings too. Either way no ref moved
|
|
363
|
+
*/
|
|
364
|
+
deliverConfigChange(change) {
|
|
365
|
+
for (const entry of this._sessions.values()) if (change?.confined) entry.hooks.onRepoChanged(entry.repoPath, ["config"], false, false, change.branches);
|
|
366
|
+
else entry.hooks.onRepoChanged(entry.repoPath, ["config", "remotes"], false);
|
|
367
|
+
}
|
|
267
368
|
};
|
|
369
|
+
/** The shared `.git/config` (or the lock git writes it through), relative to the common git dir */
|
|
370
|
+
function isConfigFile(relativePath) {
|
|
371
|
+
return relativePath === "config" || relativePath === "config.lock";
|
|
372
|
+
}
|
|
268
373
|
/**
|
|
269
374
|
* The ref a `.git`-relative path names: a loose ref (or ref directory) under `refs/`, or the `HEAD`/`ORIG_HEAD`
|
|
270
375
|
* pseudo-refs. Git writes a ref as `<name>.lock` and renames it over `<name>`, so the lock file names the same ref.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watchGroup.js","names":[],"sources":["../../../../git/src/watching/watchGroup.ts"],"sourcesContent":["import type { UnifiedDisposable } from '@gitlens/utils/disposable.js';\nimport { some } from '@gitlens/utils/iterable.js';\nimport { isDescendant, relative } from '@gitlens/utils/path.js';\nimport type { GitDir, RepositoryChange } from '../models/repository.js';\nimport { allWatchedRepositoryChanges, classifyGitDirChange } from './classifyChange.js';\nimport type { GitDirPoller, GitDirProbe } from './gitDirPoller.js';\nimport { GitDirProbeTarget } from './gitDirPoller.js';\nimport type {\n\tFileWatcher,\n\tFileWatcherOptions,\n\tFileWatcherReliability,\n\tFileWatchEvent,\n\tFileWatchingProvider,\n} from './provider.js';\nimport {\n\tdotGitGlobCombined,\n\tdotGitGlobCommon,\n\tdotGitGlobRoot,\n\tgetGitDirRefProbes,\n\tgitDirCommonProbes,\n\tgitDirExcludes,\n\tgitDirProbesByEmittedPath,\n\tgitDirRootProbes,\n\tshouldIgnoreRepoPath,\n} from './watcherPatterns.js';\n\n/**\n * Hooks that the WatchGroup fires when events are interpreted.\n * These are raw, un-debounced events — the session handles debouncing.\n */\nexport interface WatchGroupHooks {\n\t/**\n\t * `ref` is the full ref name (or ref directory) or pseudo-ref (`HEAD`, `ORIG_HEAD`) whose file changed, with a\n\t * trailing `.lock` stripped; `false` when config changed without moving refs, and `undefined` when ref movement\n\t * is unknown. `overflow` marks the every-kind rescan of a watcher that dropped events.\n\t */\n\treadonly onRepoChanged: (\n\t\trepoPath: string,\n\t\tchanges: RepositoryChange[],\n\t\tref?: string | false,\n\t\toverflow?: boolean,\n\t) => void;\n\treadonly onFetchHeadChanged?: (repoPath: string) => void;\n\treadonly onIgnoresChanged?: (repoPath: string) => void;\n\t/** The refs whose loose files a poll stats for this session, read at each poll */\n\treadonly getProbeRefs?: () => Iterable<string>;\n\t/** A watcher this session hears through went `unwatchable`, or stopped being it (see {@link WatchGroup.isUnwatchable}) */\n\treadonly onUnwatchableChanged?: (repoPath: string) => void;\n}\n\n/**\n * Tracks one session's connection to a WatchGroup.\n * Each session has its own root watcher but shares the common watcher.\n */\ninterface SessionEntry {\n\treadonly repoPath: string;\n\treadonly gitDirPath: string;\n\treadonly rootWatcher: FileWatcher;\n\t/** True when the root watcher is on the common dir itself, so it already sees every common file */\n\treadonly coversCommon: boolean;\n\t/** For a linked session, its `<n>` in `<common>/worktrees/<n>`; otherwise `undefined` */\n\treadonly worktreeId: string | undefined;\n\treadonly hooks: WatchGroupHooks;\n\treliability: FileWatcherReliability;\n\t/** Polls in place of (or beside) the root watcher while it isn't `reliable` */\n\ttarget?: GitDirProbeTarget;\n}\n\n/**\n * Manages filesystem watchers for a single physical `.git` directory,\n * shared across all worktrees that reference it.\n *\n * Structure:\n * - 1 common watcher: config, refs/**, info/exclude, etc. Delivers to linked sessions only; a\n * session rooted at the common dir hears the same files from its own combined root watcher.\n * - N root watchers: one per worktree (index, HEAD, *_HEAD, etc.)\n *\n * For a standard (non-worktree) repo, the common and root directories\n * are the same, but we still create separate watchers for clean separation.\n */\nexport class WatchGroup implements UnifiedDisposable {\n\tprivate readonly _sessions = new Map<string, SessionEntry>();\n\tprivate _commonWatcher: FileWatcher | undefined;\n\tprivate _commonReliability: FileWatcherReliability = 'reliable';\n\tprivate _commonTarget: GitDirProbeTarget | undefined;\n\tprivate _disposed = false;\n\n\tconstructor(\n\t\treadonly commonGitDir: string,\n\t\tprivate readonly provider: FileWatchingProvider,\n\t\t/** When given, a watcher that reports it can't be trusted is polled; otherwise none is asked */\n\t\tprivate readonly poller?: GitDirPoller,\n\t) {}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\t/** Dispose the entire group (common watcher + all root watchers). */\n\tdispose(): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._disposed = true;\n\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\tentry.rootWatcher.dispose();\n\t\t\tthis.stopPolling(entry);\n\t\t}\n\t\tthis._sessions.clear();\n\n\t\tthis.disposeCommonWatcher();\n\t}\n\n\tget sessions(): ReadonlyMap<string, SessionEntry> {\n\t\treturn this._sessions;\n\t}\n\n\t/** True when a watcher the session hears through — its root watcher, or a linked session's common one — can't watch */\n\tisUnwatchable(repoPath: string): boolean {\n\t\tconst entry = this._sessions.get(repoPath);\n\t\tif (entry == null) return false;\n\n\t\treturn (\n\t\t\tentry.reliability === 'unwatchable' || (!entry.coversCommon && this._commonReliability === 'unwatchable')\n\t\t);\n\t}\n\n\t/** Add a session to this group. Creates the root watcher for the session's gitDir. */\n\taddSession(repoPath: string, gitDir: GitDir, hooks: WatchGroupHooks): void {\n\t\tif (this._disposed) return;\n\t\tif (this._sessions.has(repoPath)) return; // Already tracked\n\n\t\t// Determine the glob pattern for the root watcher\n\t\tconst isStandard = gitDir.commonUri == null;\n\t\tconst rootPattern = isStandard ? dotGitGlobCombined : dotGitGlobRoot;\n\n\t\t// The watcher can report its reliability before `createWatcher` returns, so before the entry exists\n\t\tlet entry: SessionEntry | undefined;\n\t\tlet initialReliability: FileWatcherReliability = 'reliable';\n\n\t\tconst rootWatcher = this.provider.createWatcher(\n\t\t\tgitDir.uri.fsPath,\n\t\t\trootPattern,\n\t\t\t(event: FileWatchEvent) => {\n\t\t\t\tconst e = this._sessions.get(repoPath);\n\t\t\t\tif (e != null) {\n\t\t\t\t\tif (event.reason !== 'overflow') {\n\t\t\t\t\t\te.target?.forget(event.path);\n\t\t\t\t\t}\n\t\t\t\t\tthis.onRootEvent(event, e);\n\t\t\t\t}\n\t\t\t},\n\t\t\tthis.getWatcherOptions(state => {\n\t\t\t\tif (entry == null) {\n\t\t\t\t\tinitialReliability = state;\n\t\t\t\t} else if (this._sessions.get(repoPath) === entry) {\n\t\t\t\t\tthis.setRootReliability(entry, state);\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\n\t\tconst entryGitDirPath = gitDir.uri.fsPath;\n\t\tentry = {\n\t\t\trepoPath: repoPath,\n\t\t\tgitDirPath: entryGitDirPath,\n\t\t\trootWatcher: rootWatcher,\n\t\t\tcoversCommon: isStandard,\n\t\t\tworktreeId: isStandard ? undefined : this.getWorktreeId(entryGitDirPath),\n\t\t\thooks: hooks,\n\t\t\treliability: 'reliable',\n\t\t};\n\n\t\tthis._sessions.set(repoPath, entry);\n\t\tif (initialReliability !== 'reliable') {\n\t\t\tthis.setRootReliability(entry, initialReliability, true);\n\t\t}\n\n\t\t// Start the common watcher if this is a worktree repo and we haven't yet.\n\t\t// Standard repos don't need a separate common watcher because their root\n\t\t// watcher uses dotGitGlobCombined which already covers all patterns.\n\t\tif (!isStandard) {\n\t\t\tthis.ensureCommonWatcher();\n\t\t}\n\t}\n\n\t/** Remove a session from this group. Disposes its root watcher. */\n\tremoveSession(repoPath: string): void {\n\t\tconst entry = this._sessions.get(repoPath);\n\t\tif (entry == null) return;\n\n\t\tentry.rootWatcher.dispose();\n\t\tthis.stopPolling(entry);\n\t\tthis._sessions.delete(repoPath);\n\n\t\t// Once no linked session is left, dispose the common watcher — it delivers to linked sessions only\n\t\tif (this._commonWatcher != null && !some(this._sessions.values(), e => !e.coversCommon)) {\n\t\t\tthis.disposeCommonWatcher();\n\t\t}\n\t}\n\n\tprivate getWorktreeId(gitDirPath: string): string | undefined {\n\t\tif (!isDescendant(gitDirPath, this.commonGitDir)) return undefined;\n\n\t\tconst rel = relative(this.commonGitDir, gitDirPath);\n\t\tif (!rel.startsWith('worktrees/')) return undefined;\n\n\t\tconst id = rel.substring(10);\n\t\treturn id.length > 0 && !id.includes('/') ? id : undefined;\n\t}\n\n\tprivate ensureCommonWatcher(): void {\n\t\tif (this._commonWatcher != null || this._disposed) return;\n\n\t\t// The watcher can report its reliability before `createWatcher` returns\n\t\tlet watcher: FileWatcher | undefined;\n\t\tlet initialReliability: FileWatcherReliability = 'reliable';\n\t\tconst options = this.getWatcherOptions(state => {\n\t\t\tif (watcher == null) {\n\t\t\t\tinitialReliability = state;\n\t\t\t} else if (this._commonWatcher === watcher) {\n\t\t\t\tthis.setCommonReliability(state);\n\t\t\t}\n\t\t});\n\n\t\twatcher = this.provider.createWatcher(\n\t\t\tthis.commonGitDir,\n\t\t\tdotGitGlobCommon,\n\t\t\t(event: FileWatchEvent) => {\n\t\t\t\tif (event.reason !== 'overflow') {\n\t\t\t\t\tthis._commonTarget?.forget(event.path);\n\t\t\t\t}\n\t\t\t\tthis.onCommonEvent(event);\n\t\t\t},\n\t\t\toptions,\n\t\t);\n\t\tthis._commonWatcher = watcher;\n\t\tif (initialReliability !== 'reliable') {\n\t\t\tthis.setCommonReliability(initialReliability, true);\n\t\t}\n\t}\n\n\tprivate disposeCommonWatcher(): void {\n\t\tthis._commonWatcher?.dispose();\n\t\tthis._commonWatcher = undefined;\n\t\tthis._commonReliability = 'reliable';\n\t\tif (this._commonTarget != null) {\n\t\t\tthis.poller?.delete(this._commonTarget);\n\t\t\tthis._commonTarget = undefined;\n\t\t}\n\t}\n\n\t/** Hosts that didn't opt into polling get exactly the options they always had */\n\tprivate getWatcherOptions(onDidChangeReliability: (state: FileWatcherReliability) => void): FileWatcherOptions {\n\t\treturn this.poller != null\n\t\t\t? { excludes: gitDirExcludes, onDidChangeReliability: onDidChangeReliability }\n\t\t\t: { excludes: gitDirExcludes };\n\t}\n\n\tprivate setRootReliability(entry: SessionEntry, state: FileWatcherReliability, initial?: boolean): void {\n\t\tconst previous = entry.reliability;\n\t\tif (previous === state || this.poller == null) return;\n\n\t\tentry.reliability = state;\n\t\tif (state === 'reliable') {\n\t\t\tthis.stopPolling(entry);\n\t\t} else if (entry.target == null) {\n\t\t\tconst target = new GitDirProbeTarget(\n\t\t\t\tentry.gitDirPath,\n\t\t\t\tthis.poller,\n\t\t\t\t() => this.getRootProbes(entry),\n\t\t\t\tgitDirProbesByEmittedPath,\n\t\t\t\t(event, probe) => {\n\t\t\t\t\tif (entry.target !== target) return;\n\n\t\t\t\t\tthis.onRootEvent(event, entry);\n\t\t\t\t\tif (probe.path === 'logs/HEAD' && !entry.coversCommon) {\n\t\t\t\t\t\tthis.onLinkedHeadMoved();\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t);\n\t\t\tentry.target = target;\n\t\t\tthis.poller.add(target);\n\t\t}\n\n\t\tif ((state === 'unwatchable') === (previous === 'unwatchable')) return;\n\n\t\t// The watch went down or came back: whatever changed meanwhile went unseen\n\t\tif (!initial) {\n\t\t\tthis.deliverOverflow(entry);\n\t\t}\n\t\tentry.hooks.onUnwatchableChanged?.(entry.repoPath);\n\t}\n\n\tprivate setCommonReliability(state: FileWatcherReliability, initial?: boolean): void {\n\t\tconst previous = this._commonReliability;\n\t\tif (previous === state || this.poller == null) return;\n\n\t\tthis._commonReliability = state;\n\t\tif (state === 'reliable') {\n\t\t\tif (this._commonTarget != null) {\n\t\t\t\tthis.poller.delete(this._commonTarget);\n\t\t\t\tthis._commonTarget = undefined;\n\t\t\t}\n\t\t} else if (this._commonTarget == null) {\n\t\t\tconst target = new GitDirProbeTarget(\n\t\t\t\tthis.commonGitDir,\n\t\t\t\tthis.poller,\n\t\t\t\t() => this.getCommonProbes(),\n\t\t\t\tgitDirProbesByEmittedPath,\n\t\t\t\tevent => {\n\t\t\t\t\tif (this._commonTarget === target) {\n\t\t\t\t\t\tthis.onCommonEvent(event);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t);\n\t\t\tthis._commonTarget = target;\n\t\t\tthis.poller.add(target);\n\t\t}\n\n\t\tif ((state === 'unwatchable') === (previous === 'unwatchable')) return;\n\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\tif (entry.coversCommon) continue;\n\n\t\t\tif (!initial) {\n\t\t\t\tthis.deliverOverflow(entry);\n\t\t\t}\n\t\t\tentry.hooks.onUnwatchableChanged?.(entry.repoPath);\n\t\t}\n\t}\n\n\tprivate stopPolling(entry: SessionEntry): void {\n\t\tif (entry.target == null) return;\n\n\t\tthis.poller?.delete(entry.target);\n\t\tentry.target = undefined;\n\t}\n\n\t/**\n\t * A session rooted at the common dir polls the common files too, with the refs of every session in the group: its\n\t * combined watcher would hear them all. While the common dir is polled too, the poller stats each shared file once\n\t * for both, and each keeps its own readings, so what one watcher reported is held back from its own sessions only\n\t */\n\tprivate getRootProbes(entry: SessionEntry): readonly GitDirProbe[] {\n\t\tif (!entry.coversCommon) return gitDirRootProbes;\n\n\t\tconst refs = new Set<string>();\n\t\tfor (const e of this._sessions.values()) {\n\t\t\tfor (const ref of e.hooks.getProbeRefs?.() ?? []) {\n\t\t\t\trefs.add(ref);\n\t\t\t}\n\t\t}\n\t\treturn [...gitDirRootProbes, ...gitDirCommonProbes, ...getGitDirRefProbes(refs)];\n\t}\n\n\t/**\n\t * A linked worktree's `logs/HEAD` moved — a commit or reset there, on a branch a polled session rooted at the common\n\t * dir may not probe — so each such session hears a `heads` change its combined watcher would have heard\n\t */\n\tprivate onLinkedHeadMoved(): void {\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\tif (!entry.coversCommon || entry.target == null) continue;\n\n\t\t\tentry.hooks.onRepoChanged(entry.repoPath, ['heads']);\n\t\t}\n\t}\n\n\t/** The common files, with the refs of every linked session (the only ones the common watcher delivers to) */\n\tprivate getCommonProbes(): GitDirProbe[] {\n\t\tconst refs = new Set<string>();\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\tif (entry.coversCommon) continue;\n\n\t\t\tfor (const ref of entry.hooks.getProbeRefs?.() ?? []) {\n\t\t\t\trefs.add(ref);\n\t\t\t}\n\t\t}\n\t\treturn [...gitDirCommonProbes, ...getGitDirRefProbes(refs)];\n\t}\n\n\tprivate onCommonEvent(event: FileWatchEvent): void {\n\t\tif (event.reason === 'overflow') {\n\t\t\tfor (const entry of this._sessions.values()) {\n\t\t\t\tif (entry.coversCommon) continue;\n\n\t\t\t\tthis.deliverOverflow(entry);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Compute relative path from common git dir\n\t\tif (!isDescendant(event.path, this.commonGitDir)) return;\n\n\t\tconst relativePath = relative(this.commonGitDir, event.path);\n\t\tif (shouldIgnoreRepoPath(relativePath)) return;\n\n\t\tconst isFetchHead = relativePath === 'FETCH_HEAD';\n\t\tconst isExclude = relativePath === 'info/exclude';\n\t\tconst changes = isFetchHead ? undefined : classifyGitDirChange(relativePath);\n\t\tif (changes == null && !isFetchHead && !isExclude) return;\n\n\t\tconst ref = changes != null ? getChangedRef(relativePath) : undefined;\n\n\t\t// `worktrees/<n>/HEAD`: the owner hears its own HEAD from its root watcher. Its `locked` and `gitdir` aren't in\n\t\t// the root glob, so the owner hears those only from here\n\t\tlet ownerId: string | undefined;\n\t\tif (changes != null && relativePath.length > 10 && relativePath.startsWith('worktrees/')) {\n\t\t\tconst idEnd = relativePath.indexOf('/', 10);\n\t\t\tif (idEnd !== -1 && relativePath.substring(idEnd + 1) === 'HEAD') {\n\t\t\t\townerId = relativePath.substring(10, idEnd);\n\t\t\t}\n\t\t}\n\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\t// A session rooted at the common dir sees every common file (hooks included) through its own\n\t\t\t// combined root watcher, so delivering here too would duplicate it\n\t\t\tif (entry.coversCommon) continue;\n\n\t\t\tif (isFetchHead) {\n\t\t\t\t// FETCH_HEAD lives in the main .git dir; each worktree updates its last-fetched state\n\t\t\t\tentry.hooks.onFetchHeadChanged?.(entry.repoPath);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (changes != null && (ownerId == null || entry.worktreeId !== ownerId)) {\n\t\t\t\tentry.hooks.onRepoChanged(entry.repoPath, changes, ref);\n\t\t\t}\n\n\t\t\tif (isExclude) {\n\t\t\t\tentry.hooks.onIgnoresChanged?.(entry.repoPath);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate deliverOverflow(entry: SessionEntry): void {\n\t\t// Every kind, no ref (ref kinds stay unnamed). Never `'unknown'` — the cache would unregister the path\n\t\tentry.hooks.onRepoChanged(entry.repoPath, [...allWatchedRepositoryChanges], undefined, true);\n\t\tentry.hooks.onFetchHeadChanged?.(entry.repoPath);\n\t\tentry.hooks.onIgnoresChanged?.(entry.repoPath);\n\t}\n\n\tprivate onRootEvent(event: FileWatchEvent, entry: SessionEntry): void {\n\t\tif (event.reason === 'overflow') {\n\t\t\tthis.deliverOverflow(entry);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!isDescendant(event.path, entry.gitDirPath)) return;\n\n\t\tconst relativePath = relative(entry.gitDirPath, event.path);\n\t\tif (shouldIgnoreRepoPath(relativePath)) return;\n\n\t\t// Check for FETCH_HEAD before interpretation\n\t\tif (relativePath === 'FETCH_HEAD') {\n\t\t\tentry.hooks.onFetchHeadChanged?.(entry.repoPath);\n\t\t\treturn;\n\t\t}\n\n\t\tconst changes = classifyGitDirChange(relativePath);\n\t\tif (changes != null) {\n\t\t\tentry.hooks.onRepoChanged(entry.repoPath, changes, getChangedRef(relativePath));\n\t\t}\n\n\t\tif (relativePath === 'info/exclude') {\n\t\t\tentry.hooks.onIgnoresChanged?.(entry.repoPath);\n\t\t}\n\t}\n}\n\n/**\n * The ref a `.git`-relative path names: a loose ref (or ref directory) under `refs/`, or the `HEAD`/`ORIG_HEAD`\n * pseudo-refs. Git writes a ref as `<name>.lock` and renames it over `<name>`, so the lock file names the same ref.\n */\nfunction getChangedRef(relativePath: string): string | false | undefined {\n\tconst ref = relativePath.endsWith('.lock') ? relativePath.substring(0, relativePath.length - 5) : relativePath;\n\t// Remote config still invalidates remote and branch caches, but doesn't itself move any ref.\n\tif (ref === 'config' || ref === 'config.worktree') return false;\n\n\treturn ref.startsWith('refs/') || ref === 'HEAD' || ref === 'ORIG_HEAD' ? ref : undefined;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"watchGroup.js","names":[],"sources":["../../../../git/src/watching/watchGroup.ts"],"sourcesContent":["import type { UnifiedDisposable } from '@gitlens/utils/disposable.js';\nimport { some } from '@gitlens/utils/iterable.js';\nimport { isDescendant, joinPaths, relative } from '@gitlens/utils/path.js';\nimport type { GitDir, RepositoryChange } from '../models/repository.js';\nimport { allWatchedRepositoryChanges, classifyGitDirChange } from './classifyChange.js';\nimport type { GitConfigSections, GitConfigSectionsChange } from './configSections.js';\nimport { diffGitConfigSections, parseGitConfigBytes } from './configSections.js';\nimport type { GitDirPoller, GitDirProbe } from './gitDirPoller.js';\nimport { GitDirProbeTarget } from './gitDirPoller.js';\nimport type {\n\tFileWatcher,\n\tFileWatcherOptions,\n\tFileWatcherReliability,\n\tFileWatchEvent,\n\tFileWatchingProvider,\n} from './provider.js';\nimport {\n\tdotGitGlobCombined,\n\tdotGitGlobCommon,\n\tdotGitGlobRoot,\n\tgetGitDirRefProbes,\n\tgitDirCommonProbes,\n\tgitDirExcludes,\n\tgitDirProbesByEmittedPath,\n\tgitDirRootProbes,\n\tshouldIgnoreRepoPath,\n} from './watcherPatterns.js';\n\n/**\n * How long a read of the shared `.git/config` may take before it counts as failed (unconfined). Reads run one at a\n * time, so one that never settles, as on a hung network or WSL mount, would otherwise hold back every later report.\n */\nconst configReadTimeoutMs = 5000;\n\n/**\n * Hooks that the WatchGroup fires when events are interpreted.\n * These are raw, un-debounced events — the session handles debouncing.\n */\nexport interface WatchGroupHooks {\n\t/**\n\t * `ref` is the full ref name (or ref directory) or pseudo-ref (`HEAD`, `ORIG_HEAD`) whose file changed, with a\n\t * trailing `.lock` stripped; `false` when config changed without moving refs, and `undefined` when ref movement\n\t * is unknown. `overflow` marks the every-kind rescan of a watcher that dropped events, which no single write can\n\t * account for. `configBranches`, with a `config` change, are the local branches (full ref names) whose\n\t * `branch.<name>` sections are all that changed in the shared `.git/config`; without it, the config change may be\n\t * anywhere.\n\t */\n\treadonly onRepoChanged: (\n\t\trepoPath: string,\n\t\tchanges: RepositoryChange[],\n\t\tref?: string | false,\n\t\toverflow?: boolean,\n\t\tconfigBranches?: ReadonlySet<string>,\n\t) => void;\n\treadonly onFetchHeadChanged?: (repoPath: string) => void;\n\treadonly onIgnoresChanged?: (repoPath: string) => void;\n\t/** The refs whose loose files a poll stats for this session, read at each poll */\n\treadonly getProbeRefs?: () => Iterable<string>;\n\t/** A watcher this session hears through went `unwatchable`, or stopped being it (see {@link WatchGroup.isUnwatchable}) */\n\treadonly onUnwatchableChanged?: (repoPath: string) => void;\n}\n\n/**\n * Tracks one session's connection to a WatchGroup.\n * Each session has its own root watcher but shares the common watcher.\n */\ninterface SessionEntry {\n\treadonly repoPath: string;\n\treadonly gitDirPath: string;\n\treadonly rootWatcher: FileWatcher;\n\t/** True when the root watcher is on the common dir itself, so it already sees every common file */\n\treadonly coversCommon: boolean;\n\t/** For a linked session, its `<n>` in `<common>/worktrees/<n>`; otherwise `undefined` */\n\treadonly worktreeId: string | undefined;\n\treadonly hooks: WatchGroupHooks;\n\treliability: FileWatcherReliability;\n\t/** Polls in place of (or beside) the root watcher while it isn't `reliable` */\n\ttarget?: GitDirProbeTarget;\n}\n\n/**\n * Manages filesystem watchers for a single physical `.git` directory,\n * shared across all worktrees that reference it.\n *\n * Structure:\n * - 1 common watcher: config, refs/**, info/exclude, etc. Delivers to linked sessions only; a\n * session rooted at the common dir hears the same files from its own combined root watcher.\n * - N root watchers: one per worktree (index, HEAD, *_HEAD, etc.)\n *\n * For a standard (non-worktree) repo, the common and root directories\n * are the same, but we still create separate watchers for clean separation.\n *\n * Given a `readFile`, the group keeps the shared `.git/config` as it last read it, section by section. A report of\n * that file (or its lock), from whichever watcher, is read and compared with it once, and every session hears what\n * changed: only some branches' `branch.<name>` sections (`config`, confined to them), anything else (`config` and\n * `remotes`, unconfined), or nothing at all, when an earlier report's read already delivered the change. Reads run\n * one at a time, so each compares with the one before it and none is skipped. The first read, taken as the group\n * starts, is the baseline and delivers nothing; a report arriving before it completes may describe a change it\n * already holds, so it is delivered unconfined. A read that fails or doesn't settle within a few seconds, or a file\n * git couldn't parse, is unconfined too.\n */\nexport class WatchGroup implements UnifiedDisposable {\n\tprivate readonly _sessions = new Map<string, SessionEntry>();\n\tprivate _commonWatcher: FileWatcher | undefined;\n\tprivate _commonReliability: FileWatcherReliability = 'reliable';\n\tprivate _commonTarget: GitDirProbeTarget | undefined;\n\tprivate _disposed = false;\n\n\t/** The shared `.git/config`, by section, as the last read that could parse it found it */\n\tprivate _config: GitConfigSections | undefined;\n\t/** The baseline read is in flight */\n\tprivate _configBaselining = false;\n\tprivate _configReading = false;\n\tprivate _configRereadQueued = false;\n\n\tconstructor(\n\t\treadonly commonGitDir: string,\n\t\tprivate readonly provider: FileWatchingProvider,\n\t\t/** When given, a watcher that reports it can't be trusted is polled; otherwise none is asked */\n\t\tprivate readonly poller?: GitDirPoller,\n\t\t/** When given, reports of the shared `.git/config` are classified by what changed in it (see above) */\n\t\tprivate readonly readFile?: (path: string) => Promise<Uint8Array>,\n\t) {\n\t\tif (readFile != null) {\n\t\t\tvoid this.readConfigBaseline();\n\t\t}\n\t}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\t/** Dispose the entire group (common watcher + all root watchers). */\n\tdispose(): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._disposed = true;\n\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\tentry.rootWatcher.dispose();\n\t\t\tthis.stopPolling(entry);\n\t\t}\n\t\tthis._sessions.clear();\n\n\t\tthis.disposeCommonWatcher();\n\t}\n\n\tget sessions(): ReadonlyMap<string, SessionEntry> {\n\t\treturn this._sessions;\n\t}\n\n\t/** True when a watcher the session hears through — its root watcher, or a linked session's common one — can't watch */\n\tisUnwatchable(repoPath: string): boolean {\n\t\tconst entry = this._sessions.get(repoPath);\n\t\tif (entry == null) return false;\n\n\t\treturn (\n\t\t\tentry.reliability === 'unwatchable' || (!entry.coversCommon && this._commonReliability === 'unwatchable')\n\t\t);\n\t}\n\n\t/** Add a session to this group. Creates the root watcher for the session's gitDir. */\n\taddSession(repoPath: string, gitDir: GitDir, hooks: WatchGroupHooks): void {\n\t\tif (this._disposed) return;\n\t\tif (this._sessions.has(repoPath)) return; // Already tracked\n\n\t\t// Determine the glob pattern for the root watcher\n\t\tconst isStandard = gitDir.commonUri == null;\n\t\tconst rootPattern = isStandard ? dotGitGlobCombined : dotGitGlobRoot;\n\n\t\t// The watcher can report its reliability before `createWatcher` returns, so before the entry exists\n\t\tlet entry: SessionEntry | undefined;\n\t\tlet initialReliability: FileWatcherReliability = 'reliable';\n\n\t\tconst rootWatcher = this.provider.createWatcher(\n\t\t\tgitDir.uri.fsPath,\n\t\t\trootPattern,\n\t\t\t(event: FileWatchEvent) => {\n\t\t\t\tconst e = this._sessions.get(repoPath);\n\t\t\t\tif (e != null) {\n\t\t\t\t\tif (event.reason !== 'overflow') {\n\t\t\t\t\t\te.target?.forget(event.path);\n\t\t\t\t\t}\n\t\t\t\t\tthis.onRootEvent(event, e);\n\t\t\t\t}\n\t\t\t},\n\t\t\tthis.getWatcherOptions(state => {\n\t\t\t\tif (entry == null) {\n\t\t\t\t\tinitialReliability = state;\n\t\t\t\t} else if (this._sessions.get(repoPath) === entry) {\n\t\t\t\t\tthis.setRootReliability(entry, state);\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\n\t\tconst entryGitDirPath = gitDir.uri.fsPath;\n\t\tentry = {\n\t\t\trepoPath: repoPath,\n\t\t\tgitDirPath: entryGitDirPath,\n\t\t\trootWatcher: rootWatcher,\n\t\t\tcoversCommon: isStandard,\n\t\t\tworktreeId: isStandard ? undefined : this.getWorktreeId(entryGitDirPath),\n\t\t\thooks: hooks,\n\t\t\treliability: 'reliable',\n\t\t};\n\n\t\tthis._sessions.set(repoPath, entry);\n\t\tif (initialReliability !== 'reliable') {\n\t\t\tthis.setRootReliability(entry, initialReliability, true);\n\t\t}\n\n\t\t// Start the common watcher if this is a worktree repo and we haven't yet.\n\t\t// Standard repos don't need a separate common watcher because their root\n\t\t// watcher uses dotGitGlobCombined which already covers all patterns.\n\t\tif (!isStandard) {\n\t\t\tthis.ensureCommonWatcher();\n\t\t}\n\t}\n\n\t/** Remove a session from this group. Disposes its root watcher. */\n\tremoveSession(repoPath: string): void {\n\t\tconst entry = this._sessions.get(repoPath);\n\t\tif (entry == null) return;\n\n\t\tentry.rootWatcher.dispose();\n\t\tthis.stopPolling(entry);\n\t\tthis._sessions.delete(repoPath);\n\n\t\t// Once no linked session is left, dispose the common watcher — it delivers to linked sessions only\n\t\tif (this._commonWatcher != null && !some(this._sessions.values(), e => !e.coversCommon)) {\n\t\t\tthis.disposeCommonWatcher();\n\t\t}\n\t}\n\n\tprivate getWorktreeId(gitDirPath: string): string | undefined {\n\t\tif (!isDescendant(gitDirPath, this.commonGitDir)) return undefined;\n\n\t\tconst rel = relative(this.commonGitDir, gitDirPath);\n\t\tif (!rel.startsWith('worktrees/')) return undefined;\n\n\t\tconst id = rel.substring(10);\n\t\treturn id.length > 0 && !id.includes('/') ? id : undefined;\n\t}\n\n\tprivate ensureCommonWatcher(): void {\n\t\tif (this._commonWatcher != null || this._disposed) return;\n\n\t\t// The watcher can report its reliability before `createWatcher` returns\n\t\tlet watcher: FileWatcher | undefined;\n\t\tlet initialReliability: FileWatcherReliability = 'reliable';\n\t\tconst options = this.getWatcherOptions(state => {\n\t\t\tif (watcher == null) {\n\t\t\t\tinitialReliability = state;\n\t\t\t} else if (this._commonWatcher === watcher) {\n\t\t\t\tthis.setCommonReliability(state);\n\t\t\t}\n\t\t});\n\n\t\twatcher = this.provider.createWatcher(\n\t\t\tthis.commonGitDir,\n\t\t\tdotGitGlobCommon,\n\t\t\t(event: FileWatchEvent) => {\n\t\t\t\tif (event.reason !== 'overflow') {\n\t\t\t\t\tthis._commonTarget?.forget(event.path);\n\t\t\t\t}\n\t\t\t\tthis.onCommonEvent(event);\n\t\t\t},\n\t\t\toptions,\n\t\t);\n\t\tthis._commonWatcher = watcher;\n\t\tif (initialReliability !== 'reliable') {\n\t\t\tthis.setCommonReliability(initialReliability, true);\n\t\t}\n\t}\n\n\tprivate disposeCommonWatcher(): void {\n\t\tthis._commonWatcher?.dispose();\n\t\tthis._commonWatcher = undefined;\n\t\tthis._commonReliability = 'reliable';\n\t\tif (this._commonTarget != null) {\n\t\t\tthis.poller?.delete(this._commonTarget);\n\t\t\tthis._commonTarget = undefined;\n\t\t}\n\t}\n\n\t/** Hosts that didn't opt into polling get exactly the options they always had */\n\tprivate getWatcherOptions(onDidChangeReliability: (state: FileWatcherReliability) => void): FileWatcherOptions {\n\t\treturn this.poller != null\n\t\t\t? { excludes: gitDirExcludes, onDidChangeReliability: onDidChangeReliability }\n\t\t\t: { excludes: gitDirExcludes };\n\t}\n\n\tprivate setRootReliability(entry: SessionEntry, state: FileWatcherReliability, initial?: boolean): void {\n\t\tconst previous = entry.reliability;\n\t\tif (previous === state || this.poller == null) return;\n\n\t\tentry.reliability = state;\n\t\tif (state === 'reliable') {\n\t\t\tthis.stopPolling(entry);\n\t\t} else if (entry.target == null) {\n\t\t\tconst target = new GitDirProbeTarget(\n\t\t\t\tentry.gitDirPath,\n\t\t\t\tthis.poller,\n\t\t\t\t() => this.getRootProbes(entry),\n\t\t\t\tgitDirProbesByEmittedPath,\n\t\t\t\t(event, probe) => {\n\t\t\t\t\tif (entry.target !== target) return;\n\n\t\t\t\t\tthis.onRootEvent(event, entry);\n\t\t\t\t\tif (probe.path === 'logs/HEAD' && !entry.coversCommon) {\n\t\t\t\t\t\tthis.onLinkedHeadMoved();\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t);\n\t\t\tentry.target = target;\n\t\t\tthis.poller.add(target);\n\t\t}\n\n\t\tif ((state === 'unwatchable') === (previous === 'unwatchable')) return;\n\n\t\t// The watch went down or came back: whatever changed meanwhile went unseen\n\t\tif (!initial) {\n\t\t\tthis.deliverOverflow(entry);\n\t\t}\n\t\tentry.hooks.onUnwatchableChanged?.(entry.repoPath);\n\t}\n\n\tprivate setCommonReliability(state: FileWatcherReliability, initial?: boolean): void {\n\t\tconst previous = this._commonReliability;\n\t\tif (previous === state || this.poller == null) return;\n\n\t\tthis._commonReliability = state;\n\t\tif (state === 'reliable') {\n\t\t\tif (this._commonTarget != null) {\n\t\t\t\tthis.poller.delete(this._commonTarget);\n\t\t\t\tthis._commonTarget = undefined;\n\t\t\t}\n\t\t} else if (this._commonTarget == null) {\n\t\t\tconst target = new GitDirProbeTarget(\n\t\t\t\tthis.commonGitDir,\n\t\t\t\tthis.poller,\n\t\t\t\t() => this.getCommonProbes(),\n\t\t\t\tgitDirProbesByEmittedPath,\n\t\t\t\tevent => {\n\t\t\t\t\tif (this._commonTarget === target) {\n\t\t\t\t\t\tthis.onCommonEvent(event);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t);\n\t\t\tthis._commonTarget = target;\n\t\t\tthis.poller.add(target);\n\t\t}\n\n\t\tif ((state === 'unwatchable') === (previous === 'unwatchable')) return;\n\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\tif (entry.coversCommon) continue;\n\n\t\t\tif (!initial) {\n\t\t\t\tthis.deliverOverflow(entry);\n\t\t\t}\n\t\t\tentry.hooks.onUnwatchableChanged?.(entry.repoPath);\n\t\t}\n\t}\n\n\tprivate stopPolling(entry: SessionEntry): void {\n\t\tif (entry.target == null) return;\n\n\t\tthis.poller?.delete(entry.target);\n\t\tentry.target = undefined;\n\t}\n\n\t/**\n\t * A session rooted at the common dir polls the common files too, with the refs of every session in the group: its\n\t * combined watcher would hear them all. While the common dir is polled too, the poller stats each shared file once\n\t * for both, and each keeps its own readings, so what one watcher reported is held back from its own sessions only\n\t */\n\tprivate getRootProbes(entry: SessionEntry): readonly GitDirProbe[] {\n\t\tif (!entry.coversCommon) return gitDirRootProbes;\n\n\t\tconst refs = new Set<string>();\n\t\tfor (const e of this._sessions.values()) {\n\t\t\tfor (const ref of e.hooks.getProbeRefs?.() ?? []) {\n\t\t\t\trefs.add(ref);\n\t\t\t}\n\t\t}\n\t\treturn [...gitDirRootProbes, ...gitDirCommonProbes, ...getGitDirRefProbes(refs)];\n\t}\n\n\t/**\n\t * A linked worktree's `logs/HEAD` moved — a commit or reset there, on a branch a polled session rooted at the common\n\t * dir may not probe — so each such session hears a `heads` change its combined watcher would have heard\n\t */\n\tprivate onLinkedHeadMoved(): void {\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\tif (!entry.coversCommon || entry.target == null) continue;\n\n\t\t\tentry.hooks.onRepoChanged(entry.repoPath, ['heads']);\n\t\t}\n\t}\n\n\t/** The common files, with the refs of every linked session (the only ones the common watcher delivers to) */\n\tprivate getCommonProbes(): GitDirProbe[] {\n\t\tconst refs = new Set<string>();\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\tif (entry.coversCommon) continue;\n\n\t\t\tfor (const ref of entry.hooks.getProbeRefs?.() ?? []) {\n\t\t\t\trefs.add(ref);\n\t\t\t}\n\t\t}\n\t\treturn [...gitDirCommonProbes, ...getGitDirRefProbes(refs)];\n\t}\n\n\tprivate onCommonEvent(event: FileWatchEvent): void {\n\t\tif (event.reason === 'overflow') {\n\t\t\tfor (const entry of this._sessions.values()) {\n\t\t\t\tif (entry.coversCommon) continue;\n\n\t\t\t\tthis.deliverOverflow(entry);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Compute relative path from common git dir\n\t\tif (!isDescendant(event.path, this.commonGitDir)) return;\n\n\t\tconst relativePath = relative(this.commonGitDir, event.path);\n\t\tif (shouldIgnoreRepoPath(relativePath)) return;\n\n\t\tif (this.readFile != null && isConfigFile(relativePath)) {\n\t\t\tthis.onConfigReported();\n\t\t\treturn;\n\t\t}\n\n\t\tconst isFetchHead = relativePath === 'FETCH_HEAD';\n\t\tconst isExclude = relativePath === 'info/exclude';\n\t\tconst changes = isFetchHead ? undefined : classifyGitDirChange(relativePath);\n\t\tif (changes == null && !isFetchHead && !isExclude) return;\n\n\t\tconst ref = changes != null ? getChangedRef(relativePath) : undefined;\n\n\t\t// `worktrees/<n>/HEAD`: the owner hears its own HEAD from its root watcher. Its `locked` and `gitdir` aren't in\n\t\t// the root glob, so the owner hears those only from here\n\t\tlet ownerId: string | undefined;\n\t\tif (changes != null && relativePath.length > 10 && relativePath.startsWith('worktrees/')) {\n\t\t\tconst idEnd = relativePath.indexOf('/', 10);\n\t\t\tif (idEnd !== -1 && relativePath.substring(idEnd + 1) === 'HEAD') {\n\t\t\t\townerId = relativePath.substring(10, idEnd);\n\t\t\t}\n\t\t}\n\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\t// A session rooted at the common dir sees every common file (hooks included) through its own\n\t\t\t// combined root watcher, so delivering here too would duplicate it\n\t\t\tif (entry.coversCommon) continue;\n\n\t\t\tif (isFetchHead) {\n\t\t\t\t// FETCH_HEAD lives in the main .git dir; each worktree updates its last-fetched state\n\t\t\t\tentry.hooks.onFetchHeadChanged?.(entry.repoPath);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (changes != null && (ownerId == null || entry.worktreeId !== ownerId)) {\n\t\t\t\tentry.hooks.onRepoChanged(entry.repoPath, changes, ref);\n\t\t\t}\n\n\t\t\tif (isExclude) {\n\t\t\t\tentry.hooks.onIgnoresChanged?.(entry.repoPath);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate deliverOverflow(entry: SessionEntry): void {\n\t\t// Every kind, no ref (ref kinds stay unnamed). Never `'unknown'` — the cache would unregister the path\n\t\tentry.hooks.onRepoChanged(entry.repoPath, [...allWatchedRepositoryChanges], undefined, true);\n\t\tentry.hooks.onFetchHeadChanged?.(entry.repoPath);\n\t\tentry.hooks.onIgnoresChanged?.(entry.repoPath);\n\t}\n\n\tprivate onRootEvent(event: FileWatchEvent, entry: SessionEntry): void {\n\t\tif (event.reason === 'overflow') {\n\t\t\tthis.deliverOverflow(entry);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!isDescendant(event.path, entry.gitDirPath)) return;\n\n\t\tconst relativePath = relative(entry.gitDirPath, event.path);\n\t\tif (shouldIgnoreRepoPath(relativePath)) return;\n\n\t\t// Check for FETCH_HEAD before interpretation\n\t\tif (relativePath === 'FETCH_HEAD') {\n\t\t\tentry.hooks.onFetchHeadChanged?.(entry.repoPath);\n\t\t\treturn;\n\t\t}\n\n\t\t// A linked worktree's git dir isn't the common one, so no file there is the shared config\n\t\tif (entry.coversCommon && this.readFile != null && isConfigFile(relativePath)) {\n\t\t\tthis.onConfigReported();\n\t\t\treturn;\n\t\t}\n\n\t\tconst changes = classifyGitDirChange(relativePath);\n\t\tif (changes != null) {\n\t\t\tentry.hooks.onRepoChanged(entry.repoPath, changes, getChangedRef(relativePath));\n\t\t}\n\n\t\tif (relativePath === 'info/exclude') {\n\t\t\tentry.hooks.onIgnoresChanged?.(entry.repoPath);\n\t\t}\n\t}\n\n\tprivate onConfigReported(): void {\n\t\t// The baseline may already hold the change reported, which comparing with it would lose\n\t\tif (this._configBaselining) {\n\t\t\tthis.deliverConfigChange(undefined);\n\t\t\treturn;\n\t\t}\n\t\tif (this._configReading) {\n\t\t\tthis._configRereadQueued = true;\n\t\t\treturn;\n\t\t}\n\n\t\tvoid this.readConfigChanges();\n\t}\n\n\tprivate async readConfigBaseline(): Promise<void> {\n\t\tthis._configBaselining = true;\n\t\tthis._configReading = true;\n\t\ttry {\n\t\t\tconst config = await this.readConfig();\n\t\t\tif (this._disposed) return;\n\n\t\t\tthis._config = config;\n\t\t} finally {\n\t\t\tthis._configBaselining = false;\n\t\t\tthis._configReading = false;\n\t\t}\n\t}\n\n\t/** Reads and compares until no report came in during the last read: it may describe a change the read missed */\n\tprivate async readConfigChanges(): Promise<void> {\n\t\tthis._configReading = true;\n\t\ttry {\n\t\t\tdo {\n\t\t\t\tthis._configRereadQueued = false;\n\t\t\t\tconst config = await this.readConfig();\n\t\t\t\tif (this._disposed) return;\n\n\t\t\t\tconst previous = this._config;\n\t\t\t\tif (config == null || previous == null) {\n\t\t\t\t\t// Unreadable now, or never readable before: the change may be anywhere. A failed read keeps the last\n\t\t\t\t\t// snapshot, so the next read still compares with what every session was told\n\t\t\t\t\tthis._config ??= config;\n\t\t\t\t\tthis.deliverConfigChange(undefined);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tthis._config = config;\n\t\t\t\tconst change = diffGitConfigSections(previous, config);\n\t\t\t\tif (change != null) {\n\t\t\t\t\tthis.deliverConfigChange(change);\n\t\t\t\t}\n\t\t\t} while (this._configRereadQueued);\n\t\t} finally {\n\t\t\tthis._configReading = false;\n\t\t}\n\t}\n\n\t/** `undefined` when the file couldn't be read or parsed, or the read didn't settle in time */\n\tprivate async readConfig(): Promise<GitConfigSections | undefined> {\n\t\tconst readFile = this.readFile;\n\t\tif (readFile == null) return undefined;\n\n\t\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\t\ttry {\n\t\t\tconst data = await Promise.race([\n\t\t\t\treadFile(joinPaths(this.commonGitDir, 'config')),\n\t\t\t\tnew Promise<undefined>(resolve => {\n\t\t\t\t\ttimer = setTimeout(resolve, configReadTimeoutMs, undefined);\n\t\t\t\t}),\n\t\t\t]);\n\t\t\treturn data != null ? parseGitConfigBytes(data) : undefined;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t} finally {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t}\n\n\t/**\n\t * Tells every session of a change to the shared `.git/config`: confined to some branches' sections, or (`undefined`,\n\t * or a change outside them) anywhere, which may have changed a remote's settings too. Either way no ref moved\n\t */\n\tprivate deliverConfigChange(change: GitConfigSectionsChange | undefined): void {\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\tif (change?.confined) {\n\t\t\t\tentry.hooks.onRepoChanged(entry.repoPath, ['config'], false, false, change.branches);\n\t\t\t} else {\n\t\t\t\tentry.hooks.onRepoChanged(entry.repoPath, ['config', 'remotes'], false);\n\t\t\t}\n\t\t}\n\t}\n}\n\n/** The shared `.git/config` (or the lock git writes it through), relative to the common git dir */\nfunction isConfigFile(relativePath: string): boolean {\n\treturn relativePath === 'config' || relativePath === 'config.lock';\n}\n\n/**\n * The ref a `.git`-relative path names: a loose ref (or ref directory) under `refs/`, or the `HEAD`/`ORIG_HEAD`\n * pseudo-refs. Git writes a ref as `<name>.lock` and renames it over `<name>`, so the lock file names the same ref.\n */\nfunction getChangedRef(relativePath: string): string | false | undefined {\n\tconst ref = relativePath.endsWith('.lock') ? relativePath.substring(0, relativePath.length - 5) : relativePath;\n\t// Remote config still invalidates remote and branch caches, but doesn't itself move any ref.\n\tif (ref === 'config' || ref === 'config.worktree') return false;\n\n\treturn ref.startsWith('refs/') || ref === 'HEAD' || ref === 'ORIG_HEAD' ? ref : undefined;\n}\n"],"mappings":"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"}
|
|
@@ -63,6 +63,13 @@ export interface WatchServiceOptions {
|
|
|
63
63
|
readonly getIgnoredDirectories?: (repoPath: string) => Promise<readonly string[]>;
|
|
64
64
|
/** Opts into polling the watchers that report they can't be trusted. When absent, no watcher is asked. */
|
|
65
65
|
readonly polling?: WatchPollingOptions;
|
|
66
|
+
/**
|
|
67
|
+
* Reads a file's bytes; a rejection means it couldn't be read. With it, each report of a repository's shared
|
|
68
|
+
* `.git/config` is classified by what changed in the file: a change confined to some branches' `branch.<name>`
|
|
69
|
+
* sections reaches only the subscribers following those branches, and carries no `remotes`. Without it, every
|
|
70
|
+
* config change concerns everyone, as before.
|
|
71
|
+
*/
|
|
72
|
+
readonly readFile?: (path: string) => Promise<Uint8Array>;
|
|
66
73
|
}
|
|
67
74
|
export interface WatchHooks {
|
|
68
75
|
/** Called when FETCH_HEAD changed (for last-fetched tracking) */
|
|
@@ -120,6 +127,7 @@ export declare class RepositoryWatchService implements UnifiedDisposable {
|
|
|
120
127
|
private readonly getIgnoredDirectories?;
|
|
121
128
|
private readonly provider;
|
|
122
129
|
private readonly poller;
|
|
130
|
+
private readonly readFile;
|
|
123
131
|
private readonly sessionMap;
|
|
124
132
|
private readonly watchGroups;
|
|
125
133
|
private readonly wtWatchers;
|
|
@@ -144,10 +152,13 @@ export declare class RepositoryWatchService implements UnifiedDisposable {
|
|
|
144
152
|
watch(repoPath: string, gitDir: GitDir, watchHooks?: WatchHooks): WatchHandle | undefined;
|
|
145
153
|
/**
|
|
146
154
|
* Core announced one of its own writes for `repoPath`; the session watching it fires the announcement in place of
|
|
147
|
-
* the write's own `.git` events (see {@link RepositoryWatchSession.noteSelfWrite})
|
|
148
|
-
*
|
|
155
|
+
* the write's own `.git` events (see {@link RepositoryWatchSession.noteSelfWrite}); `refs` are the full ref names it
|
|
156
|
+
* moved, when it knows them, and `options.configConfinedToRefs` confines its `config` change to the sections of the
|
|
157
|
+
* local branches among them (see `GitServiceHooks.repository.onChanged`). A path no session watches is ignored.
|
|
149
158
|
*/
|
|
150
|
-
noteSelfWrite(repoPath: string, changes: readonly RepositoryChange[]
|
|
159
|
+
noteSelfWrite(repoPath: string, changes: readonly RepositoryChange[], refs?: readonly string[], options?: {
|
|
160
|
+
readonly configConfinedToRefs?: boolean;
|
|
161
|
+
}): void;
|
|
151
162
|
/** Get an existing session by repo path */
|
|
152
163
|
getSession(repoPath: string): RepositoryWatchSession | undefined;
|
|
153
164
|
/**
|