Skip to content

Commit b3ae12f

Browse files
authored
git real time (#18323)
1 parent dda04f5 commit b3ae12f

37 files changed

+1333
-293
lines changed

components/common-go/experiments/flags.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,10 @@ func SupervisorPersistServerAPIChannelWhenStart(ctx context.Context, client Clie
2828
func SupervisorUsePublicAPI(ctx context.Context, client Client, attributes Attributes) bool {
2929
return client.GetBoolValue(ctx, SupervisorUsePublicAPIFlag, false, attributes)
3030
}
31+
32+
func SupervisorLiveGitStatus(ctx context.Context, client Client, attributes Attributes) bool {
33+
if client == nil {
34+
return false
35+
}
36+
return client.GetBoolValue(ctx, "supervisor_live_git_status", false, attributes)
37+
}

components/dashboard/src/components/PendingChangesDropdown.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,19 @@
44
* See License.AGPL.txt in the project root for license information.
55
*/
66

7-
import { WorkspaceInstance } from "@gitpod/gitpod-protocol";
7+
import { WorkspaceInstance, WorkspaceInstanceRepoStatus } from "@gitpod/gitpod-protocol";
88
import ContextMenu, { ContextMenuEntry } from "./ContextMenu";
99
import CaretDown from "../icons/CaretDown.svg";
10+
import { useFeatureFlag } from "../data/featureflag-query";
1011

1112
export default function PendingChangesDropdown(props: { workspaceInstance?: WorkspaceInstance }) {
12-
const repo = props.workspaceInstance?.status?.repo;
13+
const liveGitStatus = useFeatureFlag("supervisor_live_git_status");
14+
let repo: WorkspaceInstanceRepoStatus | undefined;
15+
if (liveGitStatus) {
16+
repo = props.workspaceInstance?.gitStatus;
17+
} else {
18+
repo = props.workspaceInstance?.status?.repo;
19+
}
1320
const headingStyle = "text-gray-500 dark:text-gray-400 text-left";
1421
const itemStyle = "text-gray-400 dark:text-gray-500 text-left -mt-5";
1522
const menuEntries: ContextMenuEntry[] = [];

components/dashboard/src/data/featureflag-query.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const featureFlags = {
2626
doRetryUserLoader: true,
2727
// Local SSH feature of VS Code Desktop Extension
2828
gitpod_desktop_use_local_ssh_proxy: false,
29+
supervisor_live_git_status: false,
2930
};
3031

3132
export const useFeatureFlag = (featureFlag: keyof typeof featureFlags) => {

components/dashboard/src/workspaces/WorkspaceEntry.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@
44
* See License.AGPL.txt in the project root for license information.
55
*/
66

7-
import { CommitContext, Workspace, WorkspaceInfo, ContextURL } from "@gitpod/gitpod-protocol";
7+
import {
8+
CommitContext,
9+
Workspace,
10+
WorkspaceInfo,
11+
ContextURL,
12+
WorkspaceInstanceRepoStatus,
13+
} from "@gitpod/gitpod-protocol";
814
import { GitpodHostUrl } from "@gitpod/gitpod-protocol/lib/util/gitpod-host-url";
915
import { FunctionComponent, useMemo, useState } from "react";
1016
import { Item, ItemField, ItemFieldIcon } from "../components/ItemsList";
@@ -13,6 +19,7 @@ import Tooltip from "../components/Tooltip";
1319
import dayjs from "dayjs";
1420
import { WorkspaceEntryOverflowMenu } from "./WorkspaceOverflowMenu";
1521
import { WorkspaceStatusIndicator } from "./WorkspaceStatusIndicator";
22+
import { useFeatureFlag } from "../data/featureflag-query";
1623

1724
type Props = {
1825
info: WorkspaceInfo;
@@ -22,9 +29,16 @@ type Props = {
2229
export const WorkspaceEntry: FunctionComponent<Props> = ({ info, shortVersion }) => {
2330
const [menuActive, setMenuActive] = useState(false);
2431

32+
const liveGitStatus = useFeatureFlag("supervisor_live_git_status");
33+
let repo: WorkspaceInstanceRepoStatus | undefined;
34+
if (liveGitStatus) {
35+
repo = info.latestInstance?.gitStatus;
36+
} else {
37+
repo = info.latestInstance?.status.repo;
38+
}
39+
2540
const workspace = info.workspace;
26-
const currentBranch =
27-
info.latestInstance?.status.repo?.branch || Workspace.getBranchName(info.workspace) || "<unknown>";
41+
const currentBranch = repo?.branch || Workspace.getBranchName(info.workspace) || "<unknown>";
2842
const project = getProjectPath(workspace);
2943
const normalizedContextUrl = ContextURL.getNormalizedURL(workspace)?.toString();
3044
const normalizedContextUrlDescription = normalizedContextUrl || workspace.contextURL; // Instead of showing nothing, we prefer to show the raw content instead

components/gitpod-db/src/typeorm/entity/db-workspace-instance.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
WorkspaceInstancePhase,
1313
WorkspaceInstanceConfiguration,
1414
ImageBuildInfo,
15+
WorkspaceInstanceRepoStatus,
1516
} from "@gitpod/gitpod-protocol";
1617
import { TypeORM } from "../typeorm";
1718
import { Transformer } from "../transformer";
@@ -76,6 +77,9 @@ export class DBWorkspaceInstance implements WorkspaceInstance {
7677
@Column("json")
7778
status: WorkspaceInstanceStatus;
7879

80+
@Column("simple-json", { nullable: true })
81+
gitStatus?: WorkspaceInstanceRepoStatus;
82+
7983
/**
8084
* This field is a databse-only copy of status.phase for the sole purpose of creating indexes on it.
8185
* Is replicated inside workspace-db-impl.ts/storeInstance.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License.AGPL.txt in the project root for license information.
5+
*/
6+
7+
import { MigrationInterface, QueryRunner } from "typeorm";
8+
import { columnExists } from "./helper/helper";
9+
10+
export class AddGitStatusColumnToWorkspaceInstance1690915807191 implements MigrationInterface {
11+
public async up(queryRunner: QueryRunner): Promise<void> {
12+
if (!(await columnExists(queryRunner, "d_b_workspace_instance", "gitStatus"))) {
13+
await queryRunner.query("ALTER TABLE d_b_workspace_instance ADD COLUMN `gitStatus` text NULL");
14+
}
15+
}
16+
17+
public async down(queryRunner: QueryRunner): Promise<void> {}
18+
}

components/gitpod-protocol/go/gitpod-service.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ type APIInterface interface {
5959
GetOpenPorts(ctx context.Context, workspaceID string) (res []*WorkspaceInstancePort, err error)
6060
OpenPort(ctx context.Context, workspaceID string, port *WorkspaceInstancePort) (res *WorkspaceInstancePort, err error)
6161
ClosePort(ctx context.Context, workspaceID string, port float32) (err error)
62+
UpdateGitStatus(ctx context.Context, workspaceID string, status *WorkspaceInstanceRepoStatus) (err error)
6263
GetWorkspaceEnvVars(ctx context.Context, workspaceID string) (res []*EnvVar, err error)
6364
GetEnvVars(ctx context.Context) (res []*EnvVar, err error)
6465
SetEnvVar(ctx context.Context, variable *UserEnvVarValue) (err error)
@@ -1019,6 +1020,24 @@ func (gp *APIoverJSONRPC) ClosePort(ctx context.Context, workspaceID string, por
10191020
return
10201021
}
10211022

1023+
// UpdateGitStatus calls UpdateGitStatus on the server
1024+
func (gp *APIoverJSONRPC) UpdateGitStatus(ctx context.Context, workspaceID string, status *WorkspaceInstanceRepoStatus) (err error) {
1025+
if gp == nil {
1026+
err = errNotConnected
1027+
return
1028+
}
1029+
var _params []interface{}
1030+
_params = append(_params, workspaceID)
1031+
_params = append(_params, status)
1032+
1033+
err = gp.C.Call(ctx, "updateGitStatus", _params, nil)
1034+
if err != nil {
1035+
return
1036+
}
1037+
1038+
return
1039+
}
1040+
10221041
// GetWorkspaceEnvVars calls GetWorkspaceEnvVars on the server
10231042
func (gp *APIoverJSONRPC) GetWorkspaceEnvVars(ctx context.Context, workspaceID string) (res []*EnvVar, err error) {
10241043
if gp == nil {
@@ -1707,6 +1726,7 @@ type WorkspaceInstance struct {
17071726
Region string `json:"region,omitempty"`
17081727
StartedTime string `json:"startedTime,omitempty"`
17091728
Status *WorkspaceInstanceStatus `json:"status,omitempty"`
1729+
GitStatus *WorkspaceInstanceRepoStatus `json:"gitStatus,omitempty"`
17101730
StoppedTime string `json:"stoppedTime,omitempty"`
17111731
WorkspaceID string `json:"workspaceId,omitempty"`
17121732
WorkspaceImage string `json:"workspaceImage,omitempty"`

components/gitpod-protocol/go/mock.go

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/gitpod-protocol/src/gitpod-service.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,12 @@ import {
4646
import { JsonRpcProxy, JsonRpcServer } from "./messaging/proxy-factory";
4747
import { Disposable, CancellationTokenSource } from "vscode-jsonrpc";
4848
import { HeadlessLogUrls } from "./headless-workspace-log";
49-
import { WorkspaceInstance, WorkspaceInstancePort, WorkspaceInstancePhase } from "./workspace-instance";
49+
import {
50+
WorkspaceInstance,
51+
WorkspaceInstancePort,
52+
WorkspaceInstancePhase,
53+
WorkspaceInstanceRepoStatus,
54+
} from "./workspace-instance";
5055
import { AdminServer } from "./admin-protocol";
5156
import { GitpodHostUrl } from "./util/gitpod-host-url";
5257
import { WebSocketConnectionProvider } from "./messaging/browser/connection";
@@ -131,6 +136,8 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
131136
openPort(workspaceId: string, port: WorkspaceInstancePort): Promise<WorkspaceInstancePort | undefined>;
132137
closePort(workspaceId: string, port: number): Promise<void>;
133138

139+
updateGitStatus(workspaceId: string, status: Required<WorkspaceInstanceRepoStatus> | undefined): Promise<void>;
140+
134141
// Workspace env vars
135142
getWorkspaceEnvVars(workspaceId: string): Promise<EnvVarWithValue[]>;
136143

components/gitpod-protocol/src/workspace-instance.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ export interface WorkspaceInstance {
4444
// status is the latest status of the instance that we're aware of
4545
status: WorkspaceInstanceStatus;
4646

47+
// repo contains information about the Git working copy inside the workspace
48+
gitStatus?: WorkspaceInstanceRepoStatus;
49+
4750
// configuration captures the per-instance configuration variance of a workspace
4851
// Beware: this field was added retroactively and not all instances have valid
4952
// values here.
@@ -89,7 +92,12 @@ export interface WorkspaceInstanceStatus {
8992
// exposedPorts is the list of currently exposed ports
9093
exposedPorts?: WorkspaceInstancePort[];
9194

92-
// repo contains information about the Git working copy inside the workspace
95+
/**
96+
* repo contains information about the Git working copy inside the workspace
97+
* @deprecated use WorkspaceInstance.gitStatus instead if supervisor_live_git_status feature flag is enabled
98+
*
99+
* TODO(ak) remove after migration to live git status
100+
*/
93101
repo?: WorkspaceInstanceRepoStatus;
94102

95103
// timeout is a non-default timeout value configured for a workspace
@@ -229,6 +237,42 @@ export interface WorkspaceInstanceRepoStatus {
229237
// the total number of unpushed changes
230238
totalUnpushedCommits?: number;
231239
}
240+
export namespace WorkspaceInstanceRepoStatus {
241+
export function equals(
242+
a: WorkspaceInstanceRepoStatus | undefined,
243+
b: WorkspaceInstanceRepoStatus | undefined,
244+
): boolean {
245+
if (!a && !b) {
246+
return true;
247+
}
248+
if (!a || !b) {
249+
return false;
250+
}
251+
return (
252+
a.branch === b.branch &&
253+
a.latestCommit === b.latestCommit &&
254+
a.totalUncommitedFiles === b.totalUncommitedFiles &&
255+
a.totalUnpushedCommits === b.totalUnpushedCommits &&
256+
a.totalUntrackedFiles === b.totalUntrackedFiles &&
257+
stringArrayEquals(a.uncommitedFiles, b.uncommitedFiles) &&
258+
stringArrayEquals(a.untrackedFiles, b.untrackedFiles) &&
259+
stringArrayEquals(a.unpushedCommits, b.unpushedCommits)
260+
);
261+
}
262+
function stringArrayEquals(a: string[] | undefined, b: string[] | undefined): boolean {
263+
if (a === undefined && b === undefined) return true;
264+
265+
if (a === undefined || b === undefined) return false;
266+
267+
if (a.length !== b.length) return false;
268+
269+
for (let i = 0; i < a.length; i++) {
270+
if (a[i] !== b[i]) return false;
271+
}
272+
273+
return true;
274+
}
275+
}
232276

233277
// ConfigurationIdeConfig ide config of WorkspaceInstanceConfiguration
234278
export interface ConfigurationIdeConfig {

0 commit comments

Comments
 (0)