@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.
Files changed (61) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/dist/git/cache.d.ts +9 -12
  3. package/dist/git/cache.js +34 -16
  4. package/dist/git/cache.js.map +1 -1
  5. package/dist/git/context.d.ts +19 -2
  6. package/dist/git/context.js.map +1 -1
  7. package/dist/git/providers/provider.d.ts +7 -0
  8. package/dist/git/service.d.ts +5 -1
  9. package/dist/git/service.js +8 -4
  10. package/dist/git/service.js.map +1 -1
  11. package/dist/git/watching/changeEvent.d.ts +25 -1
  12. package/dist/git/watching/changeEvent.js +36 -2
  13. package/dist/git/watching/changeEvent.js.map +1 -1
  14. package/dist/git/watching/configSections.d.ts +35 -0
  15. package/dist/git/watching/configSections.js +210 -0
  16. package/dist/git/watching/configSections.js.map +1 -0
  17. package/dist/git/watching/watchGroup.d.ts +36 -3
  18. package/dist/git/watching/watchGroup.js +107 -2
  19. package/dist/git/watching/watchGroup.js.map +1 -1
  20. package/dist/git/watching/watchService.d.ts +14 -3
  21. package/dist/git/watching/watchService.js +10 -7
  22. package/dist/git/watching/watchService.js.map +1 -1
  23. package/dist/git/watching/watchSession.d.ts +52 -21
  24. package/dist/git/watching/watchSession.js +164 -42
  25. package/dist/git/watching/watchSession.js.map +1 -1
  26. package/dist/git/watching/worktreeSummary.d.ts +9 -7
  27. package/dist/git/watching/worktreeSummary.js +21 -10
  28. package/dist/git/watching/worktreeSummary.js.map +1 -1
  29. package/dist/git-cli/cliGitProvider.d.ts +6 -1
  30. package/dist/git-cli/cliGitProvider.js +45 -5
  31. package/dist/git-cli/cliGitProvider.js.map +1 -1
  32. package/dist/git-cli/providers/branches.d.ts +2 -2
  33. package/dist/git-cli/providers/branches.js +17 -15
  34. package/dist/git-cli/providers/branches.js.map +1 -1
  35. package/dist/git-cli/providers/config.d.ts +13 -0
  36. package/dist/git-cli/providers/config.js +38 -1
  37. package/dist/git-cli/providers/config.js.map +1 -1
  38. package/dist/git-cli/providers/operations.d.ts +35 -1
  39. package/dist/git-cli/providers/operations.js +175 -16
  40. package/dist/git-cli/providers/operations.js.map +1 -1
  41. package/dist/git-cli/providers/stash.d.ts +1 -0
  42. package/dist/git-cli/providers/stash.js +9 -3
  43. package/dist/git-cli/providers/stash.js.map +1 -1
  44. package/dist/git-cli/providers/worktrees.js +11 -4
  45. package/dist/git-cli/providers/worktrees.js.map +1 -1
  46. package/dist/git-cli/service.js +5 -1
  47. package/dist/git-cli/service.js.map +1 -1
  48. package/dist/plus/integrations/collectionMetadata.js +4 -2
  49. package/dist/plus/integrations/collectionMetadata.js.map +1 -1
  50. package/dist/plus/integrations/providers/issueSorts.d.ts +0 -1
  51. package/dist/plus/integrations/providers/providerErrors.d.ts +22 -0
  52. package/dist/plus/integrations/providers/providerErrors.js +43 -2
  53. package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
  54. package/dist/plus/integrations/providers/providersApi.js +12 -8
  55. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  56. package/dist/plus/integrations/reads/drains.d.ts +0 -1
  57. package/dist/plus/integrations/results.d.ts +13 -3
  58. package/dist/plus/integrations/results.js +12 -2
  59. package/dist/plus/integrations/results.js.map +1 -1
  60. package/docs/integrations.md +14 -0
  61. 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
- constructor(commonGitDir, provider, poller) {
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}). A path no session watches is
148
- * ignored.
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[]): void;
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
  /**