This is an automated email from the ASF dual-hosted git repository.

thiagoelg pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-tools.git


The following commit(s) were added to refs/heads/main by this push:
     new ec273dc401f kie-issues#2385: Add commit signing support for Git 
operations (#3690)
ec273dc401f is described below

commit ec273dc401f09763815e2c7fb2eb9a1ef07fffdf
Author: Adarsh vk <[email protected]>
AuthorDate: Thu Aug 6 18:22:40 2026 +0530

    kie-issues#2385: Add commit signing support for Git operations (#3690)
---
 .../src/commitSigner/CommitHistorySigner.ts        | 192 +++++++++++++++++++++
 .../src/commitSigner/CommitSignerApi.ts            |  29 ++++
 .../src/commitSigner/SigningArgs.ts                |  35 ++++
 .../src/context/WorkspacesContext.tsx              |   8 +
 .../src/context/WorkspacesContextProvider.tsx      |   7 +
 .../workspaces-git-fs/src/services/GitService.tsx  |   5 +
 .../src/worker/WorkspacesWorkerApiImpl.ts          |  37 +++-
 .../src/worker/api/WorkspacesWorkerGitApi.ts       |   7 +
 8 files changed, 319 insertions(+), 1 deletion(-)

diff --git a/packages/workspaces-git-fs/src/commitSigner/CommitHistorySigner.ts 
b/packages/workspaces-git-fs/src/commitSigner/CommitHistorySigner.ts
new file mode 100644
index 00000000000..e41bb56d32e
--- /dev/null
+++ b/packages/workspaces-git-fs/src/commitSigner/CommitHistorySigner.ts
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import git from "isomorphic-git";
+import { KieSandboxWorkspacesFs } from "../services/KieSandboxWorkspaceFs";
+import { SigningArgs, buildSignOptions } from "./SigningArgs";
+
+type ReadCommitResult = Awaited<ReturnType<typeof git.readCommit>>;
+
+/**
+ * Signs existing commits by recreating them with a signature attached. 
Recreating a commit changes
+ * its id, so every commit after it is recreated too.
+ */
+export class CommitHistorySigner {
+  public constructor(private readonly args: { fs: KieSandboxWorkspacesFs; dir: 
string }) {}
+
+  /**
+   * Signs the commits on `ref` that are not reachable from `until`, and moves 
`ref` to the new tip.
+   * It makes no changes when every one of them is already signed, or when 
`ref` moved while the
+   * signatures were being produced.
+   */
+  public async signUnpushed(args: {
+    ref: string;
+    author: { name: string; email: string };
+    signing: SigningArgs;
+    until?: string;
+  }): Promise<void> {
+    const { fs, dir } = this.args;
+    const tipAtStart = await git.resolveRef({ fs, dir, ref: args.ref });
+
+    const unpushed = await this.listUnpushedCommits({ ref: args.ref, until: 
args.until });
+    if (unpushed === undefined) {
+      return;
+    }
+
+    let oldestUnsignedIndex = -1;
+    for (let i = 0; i < unpushed.length; i++) {
+      if (!unpushed[i].commit.gpgsig) {
+        oldestUnsignedIndex = i;
+      }
+    }
+    if (oldestUnsignedIndex === -1) {
+      return;
+    }
+
+    // `unpushed` is newest first, `recreateCommits` takes oldest first.
+    const newHeadOid = await this.recreateCommits({
+      commits: unpushed.slice(0, oldestUnsignedIndex + 1).reverse(),
+      author: args.author,
+      signing: args.signing,
+    });
+
+    // Stop if `ref` moved while the signatures were being produced.
+    if ((await git.resolveRef({ fs, dir, ref: args.ref })) !== tipAtStart) {
+      return;
+    }
+
+    // `ref` can expand to a ref other than `refs/heads/<branch>`.
+    const pushRef = await git.expandRef({ fs, dir, ref: args.ref });
+    await git.writeRef({ fs, dir, ref: pushRef, value: newHeadOid, force: true 
});
+
+    // `depth: 2` yields the branch a symbolic HEAD points at, or an oid when 
HEAD is detached.
+    const headRef = await git.resolveRef({ fs, dir, ref: "HEAD", depth: 2 });
+    if (headRef !== pushRef) {
+      const ref = headRef.startsWith("refs/") ? headRef : "HEAD";
+      await git.writeRef({ fs, dir, ref, value: newHeadOid, force: true });
+    }
+  }
+
+  /**
+   * The commits on `ref` that `until` does not already reach, newest first. 
Undefined when the point
+   * where the two histories meet cannot be found.
+   */
+  private async listUnpushedCommits(args: { ref: string; until?: string }): 
Promise<ReadCommitResult[] | undefined> {
+    const chain = await this.walkFirstParents({ ref: args.ref, stopAt: 
args.until });
+    if (args.until === undefined || chain.stoppedAtTarget) {
+      return chain.commits;
+    }
+
+    // The chain missed `until`. The commits it reaches are a suffix of the 
chain, so the first one
+    // found searching back from `until` is where the two meet.
+    const boundary = await this.findFirstReachable({
+      from: args.until,
+      targets: new Set(chain.commits.map((entry) => entry.oid)),
+    });
+    if (boundary === undefined) {
+      return undefined;
+    }
+    return chain.commits.slice(
+      0,
+      chain.commits.findIndex((entry) => entry.oid === boundary)
+    );
+  }
+
+  /**
+   * Walks first parents from `ref`, newest first. `stoppedAtTarget` is true 
only when `stopAt` ended
+   * the walk, not when the history ran out.
+   */
+  private async walkFirstParents(args: {
+    ref: string;
+    stopAt?: string;
+  }): Promise<{ commits: ReadCommitResult[]; stoppedAtTarget: boolean }> {
+    const { fs, dir } = this.args;
+    const commits: ReadCommitResult[] = [];
+    let oid: string | undefined = await git.resolveRef({ fs, dir, ref: 
args.ref });
+    while (oid !== undefined && oid !== args.stopAt) {
+      let entry: ReadCommitResult;
+      try {
+        entry = await git.readCommit({ fs, dir, oid });
+      } catch {
+        // Not in the local object store, as at the boundary of a shallow 
clone.
+        return { commits, stoppedAtTarget: false };
+      }
+      commits.push(entry);
+      oid = entry.commit.parent[0];
+    }
+    return { commits, stoppedAtTarget: oid !== undefined };
+  }
+
+  /**
+   * The first commit reachable from `from` that is in `targets`, searched 
breadth first. Undefined
+   * when `from` is missing locally, or when it reaches nothing in `targets`.
+   */
+  private async findFirstReachable(args: { from: string; targets: Set<string> 
}): Promise<string | undefined> {
+    const { fs, dir } = this.args;
+    const visited = new Set<string>();
+    const queue: string[] = [args.from];
+    for (let i = 0; i < queue.length; i++) {
+      const oid = queue[i];
+      if (visited.has(oid)) {
+        continue;
+      }
+      visited.add(oid);
+      if (args.targets.has(oid)) {
+        return oid;
+      }
+      try {
+        const entry = await git.readCommit({ fs, dir, oid });
+        queue.push(...entry.commit.parent);
+      } catch {
+        // Not in the local object store, so its parents are unreachable.
+      }
+    }
+    return undefined;
+  }
+
+  /**
+   * Recreates `commits`, ordered oldest first, each keeping its tree, message 
and author. A commit
+   * whose author has no email is attributed to `author`. Returns the oid of 
the last one.
+   */
+  private async recreateCommits(args: {
+    commits: ReadCommitResult[];
+    author: { name: string; email: string };
+    signing: SigningArgs;
+  }): Promise<string> {
+    const { fs, dir } = this.args;
+    const newOidByOldOid = new Map<string, string>();
+    let newHeadOid = "";
+    for (const entry of args.commits) {
+      const commit = entry.commit;
+      newHeadOid = await git.commit({
+        fs,
+        dir,
+        message: commit.message,
+        tree: commit.tree,
+        parent: commit.parent.map((oid) => newOidByOldOid.get(oid) ?? oid),
+        author: commit.author.email ? commit.author : { ...commit.author, 
...args.author },
+        committer: { ...commit.committer, ...args.author },
+        noUpdateBranch: true,
+        ...buildSignOptions(args.signing),
+      });
+      newOidByOldOid.set(entry.oid, newHeadOid);
+    }
+    return newHeadOid;
+  }
+}
diff --git a/packages/workspaces-git-fs/src/commitSigner/CommitSignerApi.ts 
b/packages/workspaces-git-fs/src/commitSigner/CommitSignerApi.ts
new file mode 100644
index 00000000000..12a61aa9f36
--- /dev/null
+++ b/packages/workspaces-git-fs/src/commitSigner/CommitSignerApi.ts
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/** The private key used to sign a commit, and the passphrase that unlocks it 
when the key is protected. */
+export interface SigningKeyConfig {
+  privateKey: string;
+  passphrase?: string;
+}
+
+/** Signs a commit payload and returns its signature. */
+export interface CommitSigner {
+  sign(args: { payload: string } & SigningKeyConfig): Promise<{ signature: 
string }>;
+}
diff --git a/packages/workspaces-git-fs/src/commitSigner/SigningArgs.ts 
b/packages/workspaces-git-fs/src/commitSigner/SigningArgs.ts
new file mode 100644
index 00000000000..58c91851f1a
--- /dev/null
+++ b/packages/workspaces-git-fs/src/commitSigner/SigningArgs.ts
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { CommitSigner, SigningKeyConfig } from "./CommitSignerApi";
+
+export interface SigningArgs {
+  signer: CommitSigner;
+  config: SigningKeyConfig;
+}
+
+export function buildSignOptions(signing?: SigningArgs) {
+  if (!signing) {
+    return {};
+  }
+  return {
+    signingKey: signing.config.privateKey,
+    onSign: ({ payload }: { payload: string }) => signing.signer.sign({ 
payload, ...signing.config }),
+  };
+}
diff --git a/packages/workspaces-git-fs/src/context/WorkspacesContext.tsx 
b/packages/workspaces-git-fs/src/context/WorkspacesContext.tsx
index 26a8071b2a4..4430835c0f8 100644
--- a/packages/workspaces-git-fs/src/context/WorkspacesContext.tsx
+++ b/packages/workspaces-git-fs/src/context/WorkspacesContext.tsx
@@ -41,6 +41,7 @@ import { WorkspacesSharedWorker } from 
"../worker/WorkspacesSharedWorker";
 import { GitServerRef } from "../worker/api/GitServerRef";
 import { FetchResult } from "isomorphic-git";
 import { UnstagedModifiedFilesStatusEntryType } from "../services/GitService";
+import { SigningKeyConfig } from "../commitSigner/CommitSignerApi";
 
 export class WorkspaceFile {
   private readonly parsedRelativePath;
@@ -104,6 +105,8 @@ export interface WorkspacesContextType {
     localFiles: LocalFile[];
     preferredName?: string;
     gitAuthSessionId?: string;
+    gitConfig?: { email: string; name: string };
+    signingConfig?: SigningKeyConfig;
   }) => Promise<{
     workspace: WorkspaceDescriptor;
     suggestedFirstFile?: WorkspaceFile;
@@ -130,6 +133,7 @@ export interface WorkspacesContextType {
     };
     insecurelyDisableTlsCertificateValidation?: boolean;
     disableEncoding?: boolean;
+    signingConfig?: SigningKeyConfig;
   }): Promise<void>;
 
   push(args: {
@@ -144,6 +148,8 @@ export interface WorkspacesContextType {
     };
     insecurelyDisableTlsCertificateValidation?: boolean;
     disableEncoding?: boolean;
+    gitConfig?: { email: string; name: string };
+    signingConfig?: SigningKeyConfig;
   }): Promise<void>;
 
   deleteBranch(args: { workspaceId: string; ref: string }): Promise<void>;
@@ -184,6 +190,7 @@ export interface WorkspacesContextType {
       name: string;
     };
     commitMessage: string;
+    signingConfig?: SigningKeyConfig;
   }): Promise<void>;
 
   createSavePoint(args: {
@@ -194,6 +201,7 @@ export interface WorkspacesContextType {
     };
     commitMessage?: string;
     forceHasChanges?: boolean;
+    signingConfig?: SigningKeyConfig;
   }): Promise<void>;
 
   stageFile: (args: { workspaceId: string; relativePath: string }) => 
Promise<void>;
diff --git 
a/packages/workspaces-git-fs/src/context/WorkspacesContextProvider.tsx 
b/packages/workspaces-git-fs/src/context/WorkspacesContextProvider.tsx
index 665b89d7065..0b721461586 100644
--- a/packages/workspaces-git-fs/src/context/WorkspacesContextProvider.tsx
+++ b/packages/workspaces-git-fs/src/context/WorkspacesContextProvider.tsx
@@ -32,6 +32,7 @@ import {
 } from "../worker/api/WorkspaceOrigin";
 import { WorkspaceWorkerFileDescriptor } from 
"../worker/api/WorkspaceWorkerFileDescriptor";
 import { WorkspacesSharedWorker } from "../worker/WorkspacesSharedWorker";
+import { SigningKeyConfig } from "../commitSigner/CommitSignerApi";
 
 type Props = {
   children: React.ReactNode;
@@ -113,6 +114,7 @@ export function WorkspacesContextProvider(props: Props) {
       authInfo?: { username: string; password: string };
       insecurelyDisableTlsCertificateValidation?: boolean;
       disableEncoding?: boolean;
+      signingConfig?: SigningKeyConfig;
     }) =>
       workspacesSharedWorker.withBus((workspacesWorkerBus) =>
         
workspacesWorkerBus.clientApi.requests.kieSandboxWorkspacesGit_pull(args)
@@ -133,6 +135,8 @@ export function WorkspacesContextProvider(props: Props) {
       };
       insecurelyDisableTlsCertificateValidation?: boolean;
       disableEncoding?: boolean;
+      gitConfig?: { email: string; name: string };
+      signingConfig?: SigningKeyConfig;
     }) =>
       workspacesSharedWorker.withBus((workspacesWorkerBus) => {
         return 
workspacesWorkerBus.clientApi.requests.kieSandboxWorkspacesGit_push(args);
@@ -217,6 +221,7 @@ export function WorkspacesContextProvider(props: Props) {
       targetBranch: string;
       commitMessage: string;
       gitConfig?: { email: string; name: string };
+      signingConfig?: SigningKeyConfig;
     }) => {
       return workspacesSharedWorker.withBus((workspacesWorkerBus) =>
         
workspacesWorkerBus.clientApi.requests.kieSandboxWorkspacesGit_commit(args)
@@ -231,6 +236,7 @@ export function WorkspacesContextProvider(props: Props) {
       gitConfig?: { email: string; name: string };
       commitMessage?: string;
       forceHasChanges?: boolean;
+      signingConfig?: SigningKeyConfig;
     }) => {
       if (!args.forceHasChanges && !(await hasLocalChanges(args))) {
         return;
@@ -295,6 +301,7 @@ export function WorkspacesContextProvider(props: Props) {
       preferredName?: string;
       gitAuthSessionId: string | undefined;
       gitConfig?: { email: string; name: string };
+      signingConfig?: SigningKeyConfig;
     }) => {
       const workspaceInit = await 
workspacesSharedWorker.withBus((workspacesWorkerBus) =>
         
workspacesWorkerBus.clientApi.requests.kieSandboxWorkspacesGit_init(args)
diff --git a/packages/workspaces-git-fs/src/services/GitService.tsx 
b/packages/workspaces-git-fs/src/services/GitService.tsx
index f23dca523f1..a116e2217ce 100644
--- a/packages/workspaces-git-fs/src/services/GitService.tsx
+++ b/packages/workspaces-git-fs/src/services/GitService.tsx
@@ -21,6 +21,7 @@ import git, { FetchResult, STAGE, WORKDIR } from 
"isomorphic-git";
 import http from "isomorphic-git/http/web";
 import { GIT_DEFAULT_BRANCH } from "../constants/GitConstants";
 import { KieSandboxWorkspacesFs } from "./KieSandboxWorkspaceFs";
+import { SigningArgs, buildSignOptions } from "../commitSigner/SigningArgs";
 import { CorsProxyHeaderKeys } from "@kie-tools/cors-proxy-api/dist";
 
 export interface CloneArgs {
@@ -49,6 +50,7 @@ export interface CommitArgs {
     name: string;
     email: string;
   };
+  signing?: SigningArgs;
 }
 
 export interface PushArgs {
@@ -243,6 +245,7 @@ export class GitService {
         email: args.author.email,
       },
       ref: args.targetBranch,
+      ...buildSignOptions(args.signing),
     });
 
     await git.writeRef({
@@ -268,6 +271,7 @@ export class GitService {
     };
     insecurelyDisableTlsCertificateValidation?: boolean;
     disableEncoding?: boolean;
+    signing?: SigningArgs;
   }) {
     await git.pull({
       fs: args.fs,
@@ -279,6 +283,7 @@ export class GitService {
       singleBranch: true,
       author: args.author,
       onAuth: () => args.authInfo,
+      ...buildSignOptions(args.signing),
     });
   }
 
diff --git a/packages/workspaces-git-fs/src/worker/WorkspacesWorkerApiImpl.ts 
b/packages/workspaces-git-fs/src/worker/WorkspacesWorkerApiImpl.ts
index 455ecdd935a..8cf37efc42e 100644
--- a/packages/workspaces-git-fs/src/worker/WorkspacesWorkerApiImpl.ts
+++ b/packages/workspaces-git-fs/src/worker/WorkspacesWorkerApiImpl.ts
@@ -28,6 +28,9 @@ import { GIT_DEFAULT_BRANCH } from 
"../constants/GitConstants";
 import { decoder, encoder } from "../encoderdecoder/EncoderDecoder";
 import { FsSchema } from "../services/FsCache";
 import { FileModificationStatus, UnstagedModifiedFilesStatusEntryType } from 
"../services/GitService";
+import { SigningArgs } from "../commitSigner/SigningArgs";
+import { CommitHistorySigner } from "../commitSigner/CommitHistorySigner";
+import { CommitSigner, SigningKeyConfig } from 
"../commitSigner/CommitSignerApi";
 import { KieSandboxWorkspacesFs } from "../services/KieSandboxWorkspaceFs";
 import { StorageFile } from "../services/StorageService";
 import { GitServerRef } from "./api/GitServerRef";
@@ -71,9 +74,15 @@ export class WorkspacesWorkerApiImpl implements 
WorkspacesWorkerApi {
       appName: string;
       fileFilter: FileFilter;
       services: WorkspaceServices;
+      commitSigner?: CommitSigner;
     }
   ) {}
 
+  private buildSigningArgs(config?: SigningKeyConfig): SigningArgs | undefined 
{
+    const signer = this.args.commitSigner;
+    return signer && config ? { signer, config } : undefined;
+  }
+
   public async kieSandboxWorkspacesGit_changeGitAuthSessionId(args: {
     workspaceId: string;
     gitAuthSessionId: string | undefined;
@@ -601,6 +610,7 @@ export class WorkspacesWorkerApiImpl implements 
WorkspacesWorkerApi {
     gitConfig?: { email: string; name: string };
     commitMessage: string;
     targetBranch: string;
+    signingConfig?: SigningKeyConfig;
   }): Promise<void> {
     const workspaceRootDirPath = 
this.args.services.workspaceService.getAbsolutePath({ workspaceId: 
args.workspaceId });
 
@@ -614,6 +624,7 @@ export class WorkspacesWorkerApiImpl implements 
WorkspacesWorkerApi {
           name: args.gitConfig?.name ?? this.GIT_DEFAULT_USER.name,
           email: args.gitConfig?.email ?? this.GIT_DEFAULT_USER.email,
         },
+        signing: this.buildSigningArgs(args.signingConfig),
       });
     });
   }
@@ -622,6 +633,7 @@ export class WorkspacesWorkerApiImpl implements 
WorkspacesWorkerApi {
     workspaceId: string;
     gitConfig?: { email: string; name: string };
     commitMessage?: string;
+    signingConfig?: SigningKeyConfig;
   }): Promise<void> {
     const descriptor = await 
this.args.services.descriptorsFsService.withReadWriteInMemoryFs(({ fs }) => {
       return this.args.services.descriptorService.get(fs, args.workspaceId);
@@ -665,6 +677,7 @@ export class WorkspacesWorkerApiImpl implements 
WorkspacesWorkerApi {
             name: args.gitConfig?.name ?? this.GIT_DEFAULT_USER.name,
             email: args.gitConfig?.email ?? this.GIT_DEFAULT_USER.email,
           },
+          signing: this.buildSigningArgs(args.signingConfig),
         });
 
         broadcaster.broadcast({
@@ -704,6 +717,7 @@ export class WorkspacesWorkerApiImpl implements 
WorkspacesWorkerApi {
     gitConfig?: { email: string; name: string };
     gitInsecurelyDisableTlsCertificateValidation?: boolean;
     disableEncoding?: boolean;
+    signingConfig?: SigningKeyConfig;
   }): Promise<{ workspace: WorkspaceDescriptor; suggestedFirstFile?: 
WorkspaceWorkerFileDescriptor }> {
     return this.createWorkspace({
       preferredName: args.preferredName,
@@ -775,6 +789,7 @@ export class WorkspacesWorkerApiImpl implements 
WorkspacesWorkerApi {
             name: args.gitConfig?.name ?? this.GIT_DEFAULT_USER.name,
             email: args.gitConfig?.email ?? this.GIT_DEFAULT_USER.email,
           },
+          signing: this.buildSigningArgs(args.signingConfig),
         });
 
         return 
this.args.services.workspaceService.getFilteredWorkspaceFileDescriptors(schema, 
workspace.workspaceId);
@@ -788,6 +803,7 @@ export class WorkspacesWorkerApiImpl implements 
WorkspacesWorkerApi {
     authInfo?: { username: string; password: string };
     insecurelyDisableTlsCertificateValidation?: boolean;
     disableEncoding?: boolean;
+    signingConfig?: SigningKeyConfig;
   }): Promise<void> {
     const workspace = await 
this.args.services.descriptorsFsService.withReadWriteInMemoryFs(({ fs }) => {
       return this.args.services.descriptorService.get(fs, args.workspaceId);
@@ -807,6 +823,7 @@ export class WorkspacesWorkerApiImpl implements 
WorkspacesWorkerApi {
           authInfo: args.authInfo,
           insecurelyDisableTlsCertificateValidation: 
args.insecurelyDisableTlsCertificateValidation,
           disableEncoding: args.disableEncoding,
+          signing: this.buildSigningArgs(args.signingConfig),
         });
 
         broadcaster.broadcast({
@@ -832,13 +849,31 @@ export class WorkspacesWorkerApiImpl implements 
WorkspacesWorkerApi {
     };
     insecurelyDisableTlsCertificateValidation?: boolean;
     disableEncoding?: boolean;
+    gitConfig?: { email: string; name: string };
+    signingConfig?: SigningKeyConfig;
   }): Promise<void> {
     return this.args.services.workspaceFsService.withReadWriteInMemoryFs(
       args.workspaceId,
       async ({ fs, broadcaster }) => {
+        const dir = this.args.services.workspaceService.getAbsolutePath({ 
workspaceId: args.workspaceId });
+
+        const signing = this.buildSigningArgs(args.signingConfig);
+        if (signing && args.gitConfig) {
+          const until = await this.args.services.gitService
+            .resolveRef({ fs, dir, ref: 
`refs/remotes/${args.remote}/${args.ref}` })
+            .catch(() => undefined);
+
+          await new CommitHistorySigner({ fs, dir }).signUnpushed({
+            ref: args.ref,
+            author: args.gitConfig,
+            signing,
+            until,
+          });
+        }
+
         return this.args.services.gitService.push({
           fs: fs,
-          dir: this.args.services.workspaceService.getAbsolutePath({ 
workspaceId: args.workspaceId }),
+          dir,
           ...args,
         });
       }
diff --git 
a/packages/workspaces-git-fs/src/worker/api/WorkspacesWorkerGitApi.ts 
b/packages/workspaces-git-fs/src/worker/api/WorkspacesWorkerGitApi.ts
index 31b5ed4450c..1b65f768bfb 100644
--- a/packages/workspaces-git-fs/src/worker/api/WorkspacesWorkerGitApi.ts
+++ b/packages/workspaces-git-fs/src/worker/api/WorkspacesWorkerGitApi.ts
@@ -31,6 +31,7 @@ import { WorkspaceWorkerFileDescriptor } from 
"./WorkspaceWorkerFileDescriptor";
 import { GitServerRef } from "./GitServerRef";
 import { FetchResult } from "isomorphic-git";
 import { UnstagedModifiedFilesStatusEntryType } from 
"../../services/GitService";
+import { SigningKeyConfig } from "../../commitSigner/CommitSignerApi";
 
 export interface WorkspacesWorkerGitApi {
   kieSandboxWorkspacesGit_getGitServerRefs(args: {
@@ -58,6 +59,7 @@ export interface WorkspacesWorkerGitApi {
       name: string;
     };
     gitInsecurelyDisableTlsCertificateValidation?: boolean;
+    signingConfig?: SigningKeyConfig;
   }): Promise<{
     workspace: WorkspaceDescriptor;
     suggestedFirstFile?: WorkspaceWorkerFileDescriptor;
@@ -93,6 +95,7 @@ export interface WorkspacesWorkerGitApi {
     };
     insecurelyDisableTlsCertificateValidation?: boolean;
     disableEncoding?: boolean;
+    signingConfig?: SigningKeyConfig;
   }): Promise<void>;
 
   kieSandboxWorkspacesGit_push(args: {
@@ -107,6 +110,8 @@ export interface WorkspacesWorkerGitApi {
     };
     insecurelyDisableTlsCertificateValidation?: boolean;
     disableEncoding?: boolean;
+    gitConfig?: { email: string; name: string };
+    signingConfig?: SigningKeyConfig;
   }): Promise<void>;
 
   kieSandboxWorkspacesGit_deleteBranch(args: { workspaceId: string; ref: 
string }): Promise<void>;
@@ -144,12 +149,14 @@ export interface WorkspacesWorkerGitApi {
       name: string;
     };
     commitMessage?: string;
+    signingConfig?: SigningKeyConfig;
   }): Promise<void>;
 
   kieSandboxWorkspacesGit_createSavePoint(args: {
     workspaceId: string;
     gitConfig?: { email: string; name: string };
     commitMessage?: string;
+    signingConfig?: SigningKeyConfig;
   }): Promise<void>;
 
   kieSandboxWorkspacesGit_fetch(args: {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to