Skip to content

Commit 4f328b1

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/terminal-right-dock
2 parents f4f29ed + 226ed99 commit 4f328b1

43 files changed

Lines changed: 2663 additions & 433 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/server/src/git/Layers/GitCore.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -949,11 +949,12 @@ it.layer(TestLayer)("git integration", (it) => {
949949
yield* git(source, ["checkout", defaultBranch]);
950950
yield* git(source, ["branch", "-D", featureBranch]);
951951

952-
yield* (yield* GitCore).checkoutBranch({
952+
const checkoutResult = yield* (yield* GitCore).checkoutBranch({
953953
cwd: source,
954954
branch: `${remoteName}/${featureBranch}`,
955955
});
956956

957+
expect(checkoutResult.branch).toBe("upstream/feature");
957958
expect(yield* git(source, ["branch", "--show-current"])).toBe("upstream/feature");
958959
const realGitCore = yield* GitCore;
959960
let fetchArgs: readonly string[] | null = null;

apps/server/src/git/Layers/GitCore.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,9 +1177,7 @@ export const makeGitCore = Effect.fn("makeGitCore")(function* (options?: {
11771177
return branchLastCommit;
11781178
});
11791179

1180-
const statusDetails: GitCoreShape["statusDetails"] = Effect.fn("statusDetails")(function* (cwd) {
1181-
yield* refreshStatusUpstreamIfStale(cwd).pipe(Effect.ignoreCause({ log: true }));
1182-
1180+
const readStatusDetailsLocal = Effect.fn("readStatusDetailsLocal")(function* (cwd: string) {
11831181
const statusResult = yield* executeGit(
11841182
"GitCore.statusDetails.status",
11851183
cwd,
@@ -1312,6 +1310,17 @@ export const makeGitCore = Effect.fn("makeGitCore")(function* (options?: {
13121310
};
13131311
});
13141312

1313+
const statusDetailsLocal: GitCoreShape["statusDetailsLocal"] = Effect.fn("statusDetailsLocal")(
1314+
function* (cwd) {
1315+
return yield* readStatusDetailsLocal(cwd);
1316+
},
1317+
);
1318+
1319+
const statusDetails: GitCoreShape["statusDetails"] = Effect.fn("statusDetails")(function* (cwd) {
1320+
yield* refreshStatusUpstreamIfStale(cwd).pipe(Effect.ignoreCause({ log: true }));
1321+
return yield* readStatusDetailsLocal(cwd);
1322+
});
1323+
13151324
const status: GitCoreShape["status"] = (input) =>
13161325
statusDetails(input.cwd).pipe(
13171326
Effect.map((details) => ({
@@ -2000,12 +2009,6 @@ export const makeGitCore = Effect.fn("makeGitCore")(function* (options?: {
20002009
return { branch: targetBranch };
20012010
});
20022011

2003-
const createBranch: GitCoreShape["createBranch"] = (input) =>
2004-
executeGit("GitCore.createBranch", input.cwd, ["branch", input.branch], {
2005-
timeoutMs: 10_000,
2006-
fallbackErrorMessage: "git branch create failed",
2007-
}).pipe(Effect.asVoid);
2008-
20092012
const checkoutBranch: GitCoreShape["checkoutBranch"] = Effect.fn("checkoutBranch")(
20102013
function* (input) {
20112014
const [localInputExists, remoteExists] = yield* Effect.all(
@@ -2078,9 +2081,28 @@ export const makeGitCore = Effect.fn("makeGitCore")(function* (options?: {
20782081
timeoutMs: 10_000,
20792082
fallbackErrorMessage: "git checkout failed",
20802083
});
2084+
2085+
const branch = yield* runGitStdout("GitCore.checkoutBranch.currentBranch", input.cwd, [
2086+
"branch",
2087+
"--show-current",
2088+
]).pipe(Effect.map((stdout) => stdout.trim() || null));
2089+
2090+
return { branch };
20812091
},
20822092
);
20832093

2094+
const createBranch: GitCoreShape["createBranch"] = Effect.fn("createBranch")(function* (input) {
2095+
yield* executeGit("GitCore.createBranch", input.cwd, ["branch", input.branch], {
2096+
timeoutMs: 10_000,
2097+
fallbackErrorMessage: "git branch create failed",
2098+
});
2099+
if (input.checkout) {
2100+
yield* checkoutBranch({ cwd: input.cwd, branch: input.branch });
2101+
}
2102+
2103+
return { branch: input.branch };
2104+
});
2105+
20842106
const initRepo: GitCoreShape["initRepo"] = (input) =>
20852107
executeGit("GitCore.initRepo", input.cwd, ["init"], {
20862108
timeoutMs: 10_000,
@@ -2106,6 +2128,7 @@ export const makeGitCore = Effect.fn("makeGitCore")(function* (options?: {
21062128
execute,
21072129
status,
21082130
statusDetails,
2131+
statusDetailsLocal,
21092132
prepareCommitContext,
21102133
commit,
21112134
pushCurrentBranch,

apps/server/src/git/Layers/GitManager.ts

Lines changed: 104 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@ import {
88
GitCommandError,
99
GitRunStackedActionResult,
1010
GitStackedAction,
11+
type GitStatusLocalResult,
12+
type GitStatusRemoteResult,
1113
ModelSelection,
1214
} from "@t3tools/contracts";
1315
import {
16+
detectGitHostingProviderFromRemoteUrl,
17+
mergeGitStatusParts,
1418
resolveAutoFeatureBranchName,
1519
sanitizeBranchFragment,
1620
sanitizeFeatureBranchName,
@@ -695,26 +699,55 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () {
695699

696700
const tempDir = process.env.TMPDIR ?? process.env.TEMP ?? process.env.TMP ?? "/tmp";
697701
const normalizeStatusCacheKey = (cwd: string) => canonicalizeExistingPath(cwd);
698-
const readStatus = Effect.fn("readStatus")(function* (cwd: string) {
699-
const details = yield* gitCore.statusDetails(cwd).pipe(
700-
Effect.catchIf(isNotGitRepositoryError, () =>
701-
Effect.succeed({
702-
isRepo: false,
703-
hasOriginRemote: false,
704-
isDefaultBranch: false,
705-
branch: null,
706-
upstreamRef: null,
707-
hasWorkingTreeChanges: false,
708-
workingTree: { files: [], insertions: 0, deletions: 0 },
709-
hasUpstream: false,
710-
aheadCount: 0,
711-
behindCount: 0,
712-
} satisfies GitStatusDetails),
713-
),
714-
);
702+
const nonRepositoryStatusDetails = {
703+
isRepo: false,
704+
hasOriginRemote: false,
705+
isDefaultBranch: false,
706+
branch: null,
707+
upstreamRef: null,
708+
hasWorkingTreeChanges: false,
709+
workingTree: { files: [], insertions: 0, deletions: 0 },
710+
hasUpstream: false,
711+
aheadCount: 0,
712+
behindCount: 0,
713+
} satisfies GitStatusDetails;
714+
const readLocalStatus = Effect.fn("readLocalStatus")(function* (cwd: string) {
715+
const details = yield* gitCore
716+
.statusDetailsLocal(cwd)
717+
.pipe(
718+
Effect.catchIf(isNotGitRepositoryError, () => Effect.succeed(nonRepositoryStatusDetails)),
719+
);
720+
const hostingProvider = details.isRepo
721+
? yield* resolveHostingProvider(cwd, details.branch)
722+
: null;
723+
724+
return {
725+
isRepo: details.isRepo,
726+
...(hostingProvider ? { hostingProvider } : {}),
727+
hasOriginRemote: details.hasOriginRemote,
728+
isDefaultBranch: details.isDefaultBranch,
729+
branch: details.branch,
730+
hasWorkingTreeChanges: details.hasWorkingTreeChanges,
731+
workingTree: details.workingTree,
732+
} satisfies GitStatusLocalResult;
733+
});
734+
const localStatusResultCache = yield* Cache.makeWith({
735+
capacity: STATUS_RESULT_CACHE_CAPACITY,
736+
lookup: readLocalStatus,
737+
timeToLive: (exit) => (Exit.isSuccess(exit) ? STATUS_RESULT_CACHE_TTL : Duration.zero),
738+
});
739+
const invalidateLocalStatusResultCache = (cwd: string) =>
740+
Cache.invalidate(localStatusResultCache, normalizeStatusCacheKey(cwd));
741+
const readRemoteStatus = Effect.fn("readRemoteStatus")(function* (cwd: string) {
742+
const details = yield* gitCore
743+
.statusDetails(cwd)
744+
.pipe(Effect.catchIf(isNotGitRepositoryError, () => Effect.succeed(null)));
745+
if (details === null || !details.isRepo) {
746+
return null;
747+
}
715748

716749
const pr =
717-
details.isRepo && details.branch !== null
750+
details.branch !== null
718751
? yield* findLatestPr(cwd, {
719752
branch: details.branch,
720753
upstreamRef: details.upstreamRef,
@@ -725,29 +758,38 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () {
725758
: null;
726759

727760
return {
728-
isRepo: details.isRepo,
729-
hasOriginRemote: details.hasOriginRemote,
730-
isDefaultBranch: details.isDefaultBranch,
731-
branch: details.branch,
732-
hasWorkingTreeChanges: details.hasWorkingTreeChanges,
733-
workingTree: details.workingTree,
734761
hasUpstream: details.hasUpstream,
735762
aheadCount: details.aheadCount,
736763
behindCount: details.behindCount,
737764
pr,
738-
};
765+
} satisfies GitStatusRemoteResult;
739766
});
740-
const statusResultCache = yield* Cache.makeWith({
767+
const remoteStatusResultCache = yield* Cache.makeWith({
741768
capacity: STATUS_RESULT_CACHE_CAPACITY,
742-
lookup: readStatus,
769+
lookup: readRemoteStatus,
743770
timeToLive: (exit) => (Exit.isSuccess(exit) ? STATUS_RESULT_CACHE_TTL : Duration.zero),
744771
});
745-
const invalidateStatusResultCache = (cwd: string) =>
746-
Cache.invalidate(statusResultCache, normalizeStatusCacheKey(cwd));
772+
const invalidateRemoteStatusResultCache = (cwd: string) =>
773+
Cache.invalidate(remoteStatusResultCache, normalizeStatusCacheKey(cwd));
747774

748775
const readConfigValueNullable = (cwd: string, key: string) =>
749776
gitCore.readConfigValue(cwd, key).pipe(Effect.catch(() => Effect.succeed(null)));
750777

778+
const resolveHostingProvider = Effect.fn("resolveHostingProvider")(function* (
779+
cwd: string,
780+
branch: string | null,
781+
) {
782+
const preferredRemoteName =
783+
branch === null
784+
? "origin"
785+
: ((yield* readConfigValueNullable(cwd, `branch.${branch}.remote`)) ?? "origin");
786+
const remoteUrl =
787+
(yield* readConfigValueNullable(cwd, `remote.${preferredRemoteName}.url`)) ??
788+
(yield* readConfigValueNullable(cwd, "remote.origin.url"));
789+
790+
return remoteUrl ? detectGitHostingProviderFromRemoteUrl(remoteUrl) : null;
791+
});
792+
751793
const resolveRemoteRepositoryContext = Effect.fn("resolveRemoteRepositoryContext")(function* (
752794
cwd: string,
753795
remoteName: string | null,
@@ -1311,9 +1353,34 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () {
13111353
};
13121354
});
13131355

1356+
const localStatus: GitManagerShape["localStatus"] = Effect.fn("localStatus")(function* (input) {
1357+
return yield* Cache.get(localStatusResultCache, normalizeStatusCacheKey(input.cwd));
1358+
});
1359+
const remoteStatus: GitManagerShape["remoteStatus"] = Effect.fn("remoteStatus")(
1360+
function* (input) {
1361+
return yield* Cache.get(remoteStatusResultCache, normalizeStatusCacheKey(input.cwd));
1362+
},
1363+
);
13141364
const status: GitManagerShape["status"] = Effect.fn("status")(function* (input) {
1315-
return yield* Cache.get(statusResultCache, normalizeStatusCacheKey(input.cwd));
1365+
const [local, remote] = yield* Effect.all([localStatus(input), remoteStatus(input)]);
1366+
return mergeGitStatusParts(local, remote);
13161367
});
1368+
const invalidateLocalStatus: GitManagerShape["invalidateLocalStatus"] = Effect.fn(
1369+
"invalidateLocalStatus",
1370+
)(function* (cwd) {
1371+
yield* invalidateLocalStatusResultCache(cwd);
1372+
});
1373+
const invalidateRemoteStatus: GitManagerShape["invalidateRemoteStatus"] = Effect.fn(
1374+
"invalidateRemoteStatus",
1375+
)(function* (cwd) {
1376+
yield* invalidateRemoteStatusResultCache(cwd);
1377+
});
1378+
const invalidateStatus: GitManagerShape["invalidateStatus"] = Effect.fn("invalidateStatus")(
1379+
function* (cwd) {
1380+
yield* invalidateLocalStatusResultCache(cwd);
1381+
yield* invalidateRemoteStatusResultCache(cwd);
1382+
},
1383+
);
13171384

13181385
const resolvePullRequest: GitManagerShape["resolvePullRequest"] = Effect.fn("resolvePullRequest")(
13191386
function* (input) {
@@ -1488,7 +1555,7 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () {
14881555
branch: worktree.worktree.branch,
14891556
worktreePath: worktree.worktree.path,
14901557
};
1491-
}).pipe(Effect.ensuring(invalidateStatusResultCache(input.cwd)));
1558+
}).pipe(Effect.ensuring(invalidateStatus(input.cwd)));
14921559
});
14931560

14941561
const runFeatureBranchStep = Effect.fn("runFeatureBranchStep")(function* (
@@ -1692,7 +1759,7 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () {
16921759
});
16931760

16941761
return yield* runAction().pipe(
1695-
Effect.ensuring(invalidateStatusResultCache(input.cwd)),
1762+
Effect.ensuring(invalidateStatus(input.cwd)),
16961763
Effect.tapError((error) =>
16971764
Effect.flatMap(Ref.get(currentPhase), (phase) =>
16981765
progress.emit({
@@ -1707,7 +1774,12 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () {
17071774
);
17081775

17091776
return {
1777+
localStatus,
1778+
remoteStatus,
17101779
status,
1780+
invalidateLocalStatus,
1781+
invalidateRemoteStatus,
1782+
invalidateStatus,
17111783
resolvePullRequest,
17121784
preparePullRequestThread,
17131785
runStackedAction,

0 commit comments

Comments
 (0)