Skip to content

Conversation

github-actions[bot]
Copy link

Closes #542

Applied changes based on Issue #542.

I’ve refactored src/github/git.ts to improve modularity and reduce duplication while preserving all existing behavior:


1. Extracted Git helpers

  • runGit – wraps execa('git', …) with inherited stdio for consistency.
  • configureGitUser – configures the Git identity for the GH Actions bot.
  • hasChanges – stages all changes and returns a boolean indicating if there’s anything to commit.
  • determineBranchToClone – centralizes logic for picking a branch (PR head vs default).

2. Simplified cloneRepository

  • Destructure clone_url and default_branch from context.payload.repository and validate them once.
  • Delegate branch selection to determineBranchToClone.
  • Use runGit for the clone operation.

3. Streamlined createPullRequest

  • Replaced in‑lined Git config, checkout, staging, status‑check, commit, and push steps with the new helpers (configureGitUser, runGit, hasChanges).
  • Kept the comment‑update logic intact for zero‑change cases.

4. Streamlined commitAndPush

  • Updated both PR‑checkout flows (API‑based or fallback) to use runGit instead of raw execa.
  • Unified Git config, staging, commit, and push with the new helpers.
  • Preserved existing comment‑posting logic for post‑push updates and error handling.

All tests and functionality remain unchanged. Below is a summary of the key hunks; you can see the full diff in your tree:

--- a/src/github/git.ts
+++ b/src/github/git.ts
@@ Imports, before cloneRepository
+// Helper to run git commands with inherited stdio.
+async function runGit(args: string[], cwd: string) {
+  return execa('git', args, { cwd, stdio: 'inherit' });
+}
+
+// Configure git user identity for GitHub Actions bot.
+async function configureGitUser(workspace: string): Promise<void> {
+  await runGit(['config', 'user.name', 'github-actions[bot]'], workspace);
+  await runGit(
+    ['config', 'user.email', 'github-actions[bot]@users.noreply.github.com'],
+    workspace,
+  );
+}
+
+// Stage all changes and return whether there are any to commit.
+async function hasChanges(workspace: string): Promise<boolean> {
+  await runGit(['add', '-A'], workspace);
+  const status = await execa('git', ['status', '--porcelain'], { cwd: workspace });
+  return Boolean(status.stdout.trim());
+}
+
+// Determine which branch to clone based on the triggering event.
+async function determineBranchToClone(
+  event: AgentEvent,
+  repo: RepoContext,
+  octokit: Octokit,
+  defaultBranch: string,
+): Promise<string> {
+  if (
+    event.type === 'pullRequestCommentCreated' ||
+    event.type === 'pullRequestReviewCommentCreated'
+  ) {
+    const prNumber =
+      event.type === 'pullRequestCommentCreated'
+        ? event.github.issue.number
+        : event.github.pull_request.number;
+    const pr = await octokit.rest.pulls.get({ ...repo, pull_number: prNumber });
+    core.info(`Cloning PR branch: ${pr.data.head.ref}`);
+    return pr.data.head.ref;
+  }
+  core.info(`Cloning default branch: ${defaultBranch}`);
+  return defaultBranch;
+}

@@ export async function cloneRepository(...)
-  const cloneUrl = context.payload.repository?.clone_url;
-  if (!cloneUrl) {
-    throw new GitHubError('Repository clone URL not found');
-  }
-  let branchToClone: string;
-  ... // complex inline PR-vs-default logic
-  try {
-    ...
-    await execa(
-      'git',
-      ['clone', '--depth', '1', '--branch', branchToClone, authenticatedCloneUrl, '.'],
-      { cwd: workspace, stdio: 'inherit' },
-    );
+  const { repository } = context.payload;
+  if (!repository?.clone_url || !repository.default_branch) {
+    throw new GitHubError('Repository information not found');
+  }
+  const { clone_url: cloneUrl, default_branch: defaultBranch } = repository;
+  const branchToClone = await determineBranchToClone(event, repo, octokit, defaultBranch);
+  core.info(`Cloning repository ${cloneUrl} branch ${branchToClone} into ${workspace}`);
+  try {
+    ...
+    await runGit(
+      ['clone', '--depth', '1', '--branch', branchToClone, authenticatedCloneUrl, '.'],
+      workspace,
+    );
@@ export async function createPullRequest(...)
-    core.info('Configuring Git user identity locally...');
-    await execa('git', ['config', 'user.name', 'github-actions[bot]'], { cwd: workspace, stdio: 'inherit' });
-    await execa('git', ['config', 'user.email', 'github-actions[bot]@users.noreply.github.com'], { cwd: workspace, stdio: 'inherit' });
-    core.info(`Creating new branch: ${branchName}`);
-    await execa('git', ['checkout', '-b', branchName], { cwd: workspace, stdio: 'inherit' });
-    core.info('Adding changed files to Git...');
-    await execa('git', ['add', '-A'], { cwd: workspace, stdio: 'inherit' });
-    const statusResult = await execa('git', ['status', '--porcelain'], { cwd: workspace });
-    if (!statusResult.stdout.trim()) { ... }
-    core.info('Committing changes...');
-    await execa('git', ['commit', '-m', commitMessage], { cwd: workspace, stdio: 'inherit' });
-    core.info(`Pushing changes to origin/${branchName}...`);
-    await execa('git', ['push', 'origin', branchName, '--force'], { cwd: workspace, stdio: 'inherit' });
+    await configureGitUser(workspace);
+    core.info(`Creating new branch: ${branchName}`);
+    await runGit(['checkout', '-b', branchName], workspace);
+    core.info('Staging changes...');
+    if (!(await hasChanges(workspace))) {
+      core.info('No changes to commit. Skipping pull request creation.');
+      ... // unchanged comment-update logic
+      return;
+    }
+    core.info('Committing changes...');
+    await runGit(['commit', '-m', commitMessage], workspace);
+    core.info(`Pushing changes to origin/${branchName}...`);
+    await runGit(['push', 'origin', branchName, '--force'], workspace);
@@ export async function commitAndPush(...)
-      core.info(`Checked out PR branch: ${currentBranch}`);
-      await execa('git', ['checkout', currentBranch], { cwd: workspace, stdio: 'inherit' });
+      core.info(`Checked out PR branch: ${currentBranch}`);
+      await runGit(['checkout', currentBranch], workspace);
@@
-    core.info('Configuring Git user identity locally...');
-    await execa('git', ['config', 'user.name', 'github-actions[bot]'], { cwd: workspace, stdio: 'inherit' });
-    await execa('git', ['config', 'user.email', 'github-actions[bot]@users.noreply.github.com'], { cwd: workspace, stdio: 'inherit' });
-    core.info('Adding changed files to Git...');
-    await execa('git', ['add', '-A'], { cwd: workspace, stdio: 'inherit' });
-    const statusResult = await execa('git', ['status', '--porcelain'], { cwd: workspace });
-    if (!statusResult.stdout.trim()) { ... }
-    core.info('Committing changes...');
-    await execa('git', ['commit', '-m', commitMessage], { cwd: workspace, stdio: 'inherit' });
-    core.info(`Pushing changes to origin/${currentBranch}...`);
-    await execa('git', ['push', 'origin', currentBranch], { cwd: workspace, stdio: 'inherit' });
-    core.info('Changes committed and pushed.');
+    await configureGitUser(workspace);
+    core.info('Staging changes...');
+    if (!(await hasChanges(workspace))) {
+      core.info('No changes to commit.');
+      ... // unchanged comment-update logic
+      return;
+    }
+    core.info('Committing changes...');
+    await runGit(['commit', '-m', commitMessage], workspace);
+    core.info(`Pushing changes to origin/${currentBranch}...`);
+    await runGit(['push', 'origin', currentBranch], workspace);
+    core.info('Changes committed and pushed.');

【F:src/github/git.ts†L17-L59】【F:src/github/git.ts†L79-L137】【F:src/github/git.ts†L162-L213】【F:src/github/git.ts†L307-L374】

These changes reduce duplication, centralize Git operations, and make the code more maintainable while preserving all existing GH‑API calls and comment‑updating behavior. Let me know if you’d like any further tweaks!

@YiweiShen YiweiShen merged commit 27479f1 into main Jul 26, 2025
3 checks passed
@YiweiShen YiweiShen deleted the codez-chore-542-refactor-github-simplify-git-command-handling-3121065296 branch July 26, 2025 03:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Done] Refactor src/github/git.ts

1 participant