@gitkraken/core-gitlens 0.16.0 → 0.16.1

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 (34) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/dist/git/cache.js +2 -2
  3. package/dist/git/cache.js.map +1 -1
  4. package/dist/git/run.types.d.ts +3 -0
  5. package/dist/git/watching/classifyChange.js +3 -0
  6. package/dist/git/watching/classifyChange.js.map +1 -1
  7. package/dist/git/watching/watchSession.d.ts +2 -0
  8. package/dist/git/watching/watchSession.js +9 -1
  9. package/dist/git/watching/watchSession.js.map +1 -1
  10. package/dist/git-cli/cliGitProvider.d.ts +1 -1
  11. package/dist/git-cli/cliGitProvider.js +7 -4
  12. package/dist/git-cli/cliGitProvider.js.map +1 -1
  13. package/dist/git-cli/exec/commandClassifier.d.ts +18 -3
  14. package/dist/git-cli/exec/commandClassifier.js +562 -16
  15. package/dist/git-cli/exec/commandClassifier.js.map +1 -1
  16. package/dist/git-cli/exec/exec.d.ts +2 -2
  17. package/dist/git-cli/exec/git.d.ts +31 -4
  18. package/dist/git-cli/exec/git.js +105 -43
  19. package/dist/git-cli/exec/git.js.map +1 -1
  20. package/dist/git-cli/exec/headState.d.ts +32 -0
  21. package/dist/git-cli/exec/headState.js +78 -0
  22. package/dist/git-cli/exec/headState.js.map +1 -0
  23. package/dist/git-cli/providers/branches.js +14 -4
  24. package/dist/git-cli/providers/branches.js.map +1 -1
  25. package/dist/git-cli/providers/operations.d.ts +6 -7
  26. package/dist/git-cli/providers/operations.js +46 -64
  27. package/dist/git-cli/providers/operations.js.map +1 -1
  28. package/dist/git-cli/providers/refs.d.ts +4 -1
  29. package/dist/git-cli/providers/refs.js +13 -9
  30. package/dist/git-cli/providers/refs.js.map +1 -1
  31. package/dist/utils/env/node/exec.d.ts +38 -1
  32. package/dist/utils/env/node/exec.js +14 -6
  33. package/dist/utils/env/node/exec.js.map +1 -1
  34. package/package.json +1 -1
@@ -0,0 +1,78 @@
1
+ import { fileUri, joinUriPath } from "../../utils/uri.js";
2
+ import { FileType } from "../../git/context.js";
3
+ import { resolve } from "node:path";
4
+ //#region ../git-cli/src/exec/headState.ts
5
+ const headBranchRegex = /^ref:\s*(refs\/heads\/\S+)\s*$/;
6
+ const headShaRegex = /^(?:[0-9a-f]{40}|[0-9a-f]{64})\s*$/;
7
+ const gitFileRegex = /^gitdir:\s*(.+?)\s*$/m;
8
+ /**
9
+ * Reads `repoPath`'s HEAD from the worktree's own `HEAD` file, without running git: read around a write, it says which
10
+ * branch that write moved, whatever a switch outside core left in the caches. The git dir is the cached one, else the
11
+ * one `<repoPath>/.git` is or points to, so a worktree core never registered is read without spawning git either.
12
+ * A reftable repository's `HEAD` file holds the placeholder `refs/heads/.invalid` (its real HEAD is in the tables),
13
+ * which is no branch name git allows, so it reads as unknown, as does a branch that is a symbolic ref to another (a
14
+ * `master` kept pointing at `main`), since git follows it and a write moves the target. An object id is detached only
15
+ * when the git dir's listing doesn't flag `HEAD` as a symlink (a host's `stat` may follow the link): read as detached,
16
+ * a symlinked HEAD would say a write on its branch moved no branch. Never rejects.
17
+ */
18
+ async function readHeadState(repoPath, fs, cache) {
19
+ try {
20
+ const cached = cache.gitDir.get(repoPath);
21
+ const gitDir = cached != null ? {
22
+ uri: cached.uri,
23
+ commonUri: cached.commonUri
24
+ } : await findGitDir(repoPath, fs);
25
+ if (gitDir == null) return void 0;
26
+ const decoder = new TextDecoder();
27
+ const head = decoder.decode(await fs.readFile(joinUriPath(gitDir.uri, "HEAD")));
28
+ if (headShaRegex.test(head)) return (await fs.readDirectory(gitDir.uri)).some(([name, type]) => name === "HEAD" && (type & FileType.SymbolicLink) !== 0) ? void 0 : "detached";
29
+ const ref = headBranchRegex.exec(head)?.[1];
30
+ if (ref == null || ref.split("/").some((part) => part.startsWith("."))) return void 0;
31
+ let target;
32
+ try {
33
+ target = decoder.decode(await fs.readFile(joinUriPath(gitDir.commonUri ?? gitDir.uri, ref)));
34
+ } catch {}
35
+ return target?.startsWith("ref:") ? void 0 : { branch: ref };
36
+ } catch {
37
+ return;
38
+ }
39
+ }
40
+ /**
41
+ * The git dir `<repoPath>/.git` is: the folder itself, or the one a linked worktree's `gitdir:` file names, whose
42
+ * `commondir` file names the repository's shared one
43
+ */
44
+ async function findGitDir(repoPath, fs) {
45
+ const dotGit = joinUriPath(fileUri(repoPath), ".git");
46
+ const stat = await fs.stat(dotGit);
47
+ if (stat == null) return void 0;
48
+ if (stat.type & FileType.Directory) return {
49
+ uri: dotGit,
50
+ commonUri: void 0
51
+ };
52
+ const dir = gitFileRegex.exec(new TextDecoder().decode(await fs.readFile(dotGit)))?.[1];
53
+ if (dir == null) return void 0;
54
+ const gitDir = resolve(repoPath, dir);
55
+ let commonDir;
56
+ try {
57
+ commonDir = new TextDecoder().decode(await fs.readFile(joinUriPath(fileUri(gitDir), "commondir"))).trim();
58
+ } catch {}
59
+ return {
60
+ uri: fileUri(gitDir),
61
+ commonUri: commonDir ? fileUri(resolve(gitDir, commonDir)) : void 0
62
+ };
63
+ }
64
+ /**
65
+ * Which branch a write that moves only HEAD's branch moved, from HEAD read before and after it: that branch when HEAD
66
+ * was on the same one both times, `'detached'` (no branch) when it was detached both times, otherwise `undefined` — a
67
+ * hook or an outside switch moved HEAD meanwhile, or a read failed, so the moved branch is unknown.
68
+ */
69
+ function getMovedHead(before, after) {
70
+ if (before == null || after == null) return void 0;
71
+ if (before === "detached") return after === "detached" ? "detached" : void 0;
72
+ if (after === "detached") return void 0;
73
+ return before.branch === after.branch ? before : void 0;
74
+ }
75
+ //#endregion
76
+ export { getMovedHead, readHeadState };
77
+
78
+ //# sourceMappingURL=headState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headState.js","names":["resolvePath"],"sources":["../../../../git-cli/src/exec/headState.ts"],"sourcesContent":["import { resolve as resolvePath } from 'node:path';\nimport type { Cache } from '@gitlens/git/cache.js';\nimport type { FileSystemProvider } from '@gitlens/git/context.js';\nimport { FileType } from '@gitlens/git/context.js';\nimport type { Uri } from '@gitlens/utils/uri.js';\nimport { fileUri, joinUriPath } from '@gitlens/utils/uri.js';\n\n/**\n * What a worktree's HEAD is on: the full name of its branch (`refs/heads/main`), `'detached'`, or `undefined` when\n * unknown — unreadable, not a branch name git allows, or a HEAD kept as a symlink to its branch\n * (`core.preferSymlinkRefs`). Reading such a HEAD follows the link to the branch's object id, as a detached HEAD\n * reads, so it is unknown when the git dir's listing flags it as a link, and `'detached'` on a host whose listing\n * doesn't.\n */\nexport type HeadState = { readonly branch: string } | 'detached' | undefined;\n\nconst headBranchRegex = /^ref:\\s*(refs\\/heads\\/\\S+)\\s*$/;\nconst headShaRegex = /^(?:[0-9a-f]{40}|[0-9a-f]{64})\\s*$/;\nconst gitFileRegex = /^gitdir:\\s*(.+?)\\s*$/m;\n\n/**\n * Reads `repoPath`'s HEAD from the worktree's own `HEAD` file, without running git: read around a write, it says which\n * branch that write moved, whatever a switch outside core left in the caches. The git dir is the cached one, else the\n * one `<repoPath>/.git` is or points to, so a worktree core never registered is read without spawning git either.\n * A reftable repository's `HEAD` file holds the placeholder `refs/heads/.invalid` (its real HEAD is in the tables),\n * which is no branch name git allows, so it reads as unknown, as does a branch that is a symbolic ref to another (a\n * `master` kept pointing at `main`), since git follows it and a write moves the target. An object id is detached only\n * when the git dir's listing doesn't flag `HEAD` as a symlink (a host's `stat` may follow the link): read as detached,\n * a symlinked HEAD would say a write on its branch moved no branch. Never rejects.\n */\nexport async function readHeadState(repoPath: string, fs: FileSystemProvider, cache: Cache): Promise<HeadState> {\n\ttry {\n\t\tconst cached = cache.gitDir.get(repoPath);\n\t\tconst gitDir =\n\t\t\tcached != null ? { uri: cached.uri, commonUri: cached.commonUri } : await findGitDir(repoPath, fs);\n\t\tif (gitDir == null) return undefined;\n\n\t\tconst decoder = new TextDecoder();\n\t\tconst head = decoder.decode(await fs.readFile(joinUriPath(gitDir.uri, 'HEAD')));\n\t\tif (headShaRegex.test(head)) {\n\t\t\tconst entries = await fs.readDirectory(gitDir.uri);\n\t\t\tconst linked = entries.some(([name, type]) => name === 'HEAD' && (type & FileType.SymbolicLink) !== 0);\n\t\t\treturn linked ? undefined : 'detached';\n\t\t}\n\n\t\tconst ref = headBranchRegex.exec(head)?.[1];\n\t\tif (ref == null || ref.split('/').some(part => part.startsWith('.'))) return undefined;\n\n\t\t// Packed refs can't hold a symbolic ref, so a missing loose file is a plain branch\n\t\tlet target: string | undefined;\n\t\ttry {\n\t\t\ttarget = decoder.decode(await fs.readFile(joinUriPath(gitDir.commonUri ?? gitDir.uri, ref)));\n\t\t} catch {}\n\t\treturn target?.startsWith('ref:') ? undefined : { branch: ref };\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * The git dir `<repoPath>/.git` is: the folder itself, or the one a linked worktree's `gitdir:` file names, whose\n * `commondir` file names the repository's shared one\n */\nasync function findGitDir(\n\trepoPath: string,\n\tfs: FileSystemProvider,\n): Promise<{ readonly uri: Uri; readonly commonUri: Uri | undefined } | undefined> {\n\tconst dotGit = joinUriPath(fileUri(repoPath), '.git');\n\tconst stat = await fs.stat(dotGit);\n\tif (stat == null) return undefined;\n\tif (stat.type & FileType.Directory) return { uri: dotGit, commonUri: undefined };\n\n\tconst dir = gitFileRegex.exec(new TextDecoder().decode(await fs.readFile(dotGit)))?.[1];\n\tif (dir == null) return undefined;\n\n\tconst gitDir = resolvePath(repoPath, dir);\n\tlet commonDir: string | undefined;\n\ttry {\n\t\tcommonDir = new TextDecoder().decode(await fs.readFile(joinUriPath(fileUri(gitDir), 'commondir'))).trim();\n\t} catch {}\n\treturn { uri: fileUri(gitDir), commonUri: commonDir ? fileUri(resolvePath(gitDir, commonDir)) : undefined };\n}\n\n/**\n * Which branch a write that moves only HEAD's branch moved, from HEAD read before and after it: that branch when HEAD\n * was on the same one both times, `'detached'` (no branch) when it was detached both times, otherwise `undefined` — a\n * hook or an outside switch moved HEAD meanwhile, or a read failed, so the moved branch is unknown.\n */\nexport function getMovedHead(before: HeadState, after: HeadState): HeadState {\n\tif (before == null || after == null) return undefined;\n\tif (before === 'detached') return after === 'detached' ? 'detached' : undefined;\n\tif (after === 'detached') return undefined;\n\n\treturn before.branch === after.branch ? before : undefined;\n}\n"],"mappings":";;;;AAgBA,MAAM,kBAAkB;AACxB,MAAM,eAAe;AACrB,MAAM,eAAe;;;;;;;;;;;AAYrB,eAAsB,cAAc,UAAkB,IAAwB,OAAkC;CAC/G,IAAI;EACH,MAAM,SAAS,MAAM,OAAO,IAAI,QAAQ;EACxC,MAAM,SACL,UAAU,OAAO;GAAE,KAAK,OAAO;GAAK,WAAW,OAAO;EAAU,IAAI,MAAM,WAAW,UAAU,EAAE;EAClG,IAAI,UAAU,MAAM,OAAO,KAAA;EAE3B,MAAM,UAAU,IAAI,YAAY;EAChC,MAAM,OAAO,QAAQ,OAAO,MAAM,GAAG,SAAS,YAAY,OAAO,KAAK,MAAM,CAAC,CAAC;EAC9E,IAAI,aAAa,KAAK,IAAI,GAGzB,QADe,MADO,GAAG,cAAc,OAAO,GAAG,EAAA,CAC1B,MAAM,CAAC,MAAM,UAAU,SAAS,WAAW,OAAO,SAAS,kBAAkB,CACxF,IAAI,KAAA,IAAY;EAG7B,MAAM,MAAM,gBAAgB,KAAK,IAAI,CAAC,GAAG;EACzC,IAAI,OAAO,QAAQ,IAAI,MAAM,GAAG,CAAC,CAAC,MAAK,SAAQ,KAAK,WAAW,GAAG,CAAC,GAAG,OAAO,KAAA;EAG7E,IAAI;EACJ,IAAI;GACH,SAAS,QAAQ,OAAO,MAAM,GAAG,SAAS,YAAY,OAAO,aAAa,OAAO,KAAK,GAAG,CAAC,CAAC;EAC5F,QAAQ,CAAC;EACT,OAAO,QAAQ,WAAW,MAAM,IAAI,KAAA,IAAY,EAAE,QAAQ,IAAI;CAC/D,QAAQ;EACP;CACD;AACD;;;;;AAMA,eAAe,WACd,UACA,IACkF;CAClF,MAAM,SAAS,YAAY,QAAQ,QAAQ,GAAG,MAAM;CACpD,MAAM,OAAO,MAAM,GAAG,KAAK,MAAM;CACjC,IAAI,QAAQ,MAAM,OAAO,KAAA;CACzB,IAAI,KAAK,OAAO,SAAS,WAAW,OAAO;EAAE,KAAK;EAAQ,WAAW,KAAA;CAAU;CAE/E,MAAM,MAAM,aAAa,KAAK,IAAI,YAAY,CAAC,CAAC,OAAO,MAAM,GAAG,SAAS,MAAM,CAAC,CAAC,CAAC,GAAG;CACrF,IAAI,OAAO,MAAM,OAAO,KAAA;CAExB,MAAM,SAASA,QAAY,UAAU,GAAG;CACxC,IAAI;CACJ,IAAI;EACH,YAAY,IAAI,YAAY,CAAC,CAAC,OAAO,MAAM,GAAG,SAAS,YAAY,QAAQ,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,KAAK;CACzG,QAAQ,CAAC;CACT,OAAO;EAAE,KAAK,QAAQ,MAAM;EAAG,WAAW,YAAY,QAAQA,QAAY,QAAQ,SAAS,CAAC,IAAI,KAAA;CAAU;AAC3G;;;;;;AAOA,SAAgB,aAAa,QAAmB,OAA6B;CAC5E,IAAI,UAAU,QAAQ,SAAS,MAAM,OAAO,KAAA;CAC5C,IAAI,WAAW,YAAY,OAAO,UAAU,aAAa,aAAa,KAAA;CACtE,IAAI,UAAU,YAAY,OAAO,KAAA;CAEjC,OAAO,OAAO,WAAW,MAAM,SAAS,SAAS,KAAA;AAClD"}
@@ -14,6 +14,7 @@ import { createReference } from "../../git/utils/reference.utils.js";
14
14
  import __decorate from "../../_virtual/@oxc-project+runtime/helpers/esm/decorate.js";
15
15
  import { BranchError, GitWarnings } from "../../git/errors.js";
16
16
  import { mayWriteTracking } from "../exec/commandClassifier.js";
17
+ import { readHeadState } from "../exec/headState.js";
17
18
  import { KeyedSequencer } from "../exec/indexLock.js";
18
19
  import { GitError, GitErrors, getGitCommandError, gitConfigsBranch, gitConfigsLog } from "../exec/git.js";
19
20
  import { maybeStopWatch } from "../../utils/stopwatch.js";
@@ -763,14 +764,23 @@ var BranchesGitSubProvider = class {
763
764
  const records = await this.provider.refs.getRefs(repoPath, cancellation);
764
765
  if (!records.length) return { known: false };
765
766
  if (branch == null) {
766
- const current = await this.getCurrentBranchReference(repoPath, cancellation);
767
- if (current == null) return { known: false };
768
- if (current.remote || isDetachedHead(current.name)) return {
767
+ const head = await readHeadState(repoPath, this.context.fs, this.cache);
768
+ if (head === "detached") return {
769
769
  known: true,
770
770
  push: void 0,
771
771
  upstream: void 0
772
772
  };
773
- branch = current.name;
773
+ if (head != null) branch = head.branch;
774
+ else {
775
+ const current = await this.getCurrentBranchReference(repoPath, cancellation);
776
+ if (current == null) return { known: false };
777
+ if (current.remote || isDetachedHead(current.name)) return {
778
+ known: true,
779
+ push: void 0,
780
+ upstream: void 0
781
+ };
782
+ branch = current.name;
783
+ }
774
784
  }
775
785
  const record = findRefRecord(records, branch.startsWith("refs/heads/") ? branch : `refs/heads/${branch}`);
776
786
  return {