Skip to content

Commit cfea6e2

Browse files
feat: implement branch reuse logic and add tests for existing branches
1 parent 384ea09 commit cfea6e2

File tree

3 files changed

+42
-2
lines changed

3 files changed

+42
-2
lines changed

docs/tasks.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ Use Context7 MCP for up to date documentation.
233233
Validate `track` as `/^\d+\.\d+$/`.
234234
Verify: Bad inputs fail fast.
235235

236-
38. [ ] **Concurrency control**
236+
38. [x] **Concurrency control**
237237
Check existing ref before branch create. Document workflow `concurrency`.
238238
Verify: Parallel runs yield one PR.
239239

src/git/branch.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ export interface BranchCommitResult {
2020
filesCommitted: string[];
2121
}
2222

23+
async function branchExists(branch: string, repoPath: string): Promise<boolean> {
24+
try {
25+
await runGit(['show-ref', '--verify', `refs/heads/${branch}`], repoPath);
26+
return true;
27+
} catch {
28+
return false;
29+
}
30+
}
31+
2332
async function runGit(
2433
args: string[],
2534
repoPath: string,
@@ -67,7 +76,11 @@ export async function createBranchAndCommit(
6776

6877
const branch = `${branchPrefix}${track}`;
6978

70-
await runGit(['checkout', '-B', branch], repoPath);
79+
if (await branchExists(branch, repoPath)) {
80+
await runGit(['checkout', branch], repoPath);
81+
} else {
82+
await runGit(['checkout', '-B', branch], repoPath);
83+
}
7184
await stageFiles(files, repoPath);
7285
const stagedFiles = await getStagedFiles(repoPath);
7386

tests/git-branch.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,31 @@ describe('createBranchAndCommit', () => {
7474
const status = await runGit(['status', '--short'], repoDir);
7575
expect(status).toBe('');
7676
});
77+
78+
it('reuses existing branch when present', async () => {
79+
const filePath = path.join(repoDir, 'Dockerfile');
80+
await writeFile(filePath, 'FROM python:3.11.8-slim\n');
81+
82+
// First run creates the branch and commit.
83+
await createBranchAndCommit({
84+
repoPath: repoDir,
85+
track: '3.11',
86+
files: ['Dockerfile'],
87+
commitMessage: 'chore: bump python to 3.11.9',
88+
});
89+
90+
// Modify file again and ensure second run keeps branch without recreation errors.
91+
await writeFile(filePath, 'FROM python:3.11.9-slim\n');
92+
93+
const result = await createBranchAndCommit({
94+
repoPath: repoDir,
95+
track: '3.11',
96+
files: ['Dockerfile'],
97+
commitMessage: 'chore: bump python to 3.11.10',
98+
});
99+
100+
expect(result.branch).toBe('chore/bump-python-3.11');
101+
const commitCount = await runGit(['rev-list', '--count', 'chore/bump-python-3.11'], repoDir);
102+
expect(Number.parseInt(commitCount, 10)).toBeGreaterThanOrEqual(2);
103+
});
77104
});

0 commit comments

Comments
 (0)