Skip to content

Commit d4a504b

Browse files
mattKorwelgiraffechen
authored andcommitted
dealing with conflicts (google-gemini#8772)
1 parent 0550485 commit d4a504b

File tree

6 files changed

+143
-39
lines changed

6 files changed

+143
-39
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: 'Gemini CLI CI'
1+
name: 'Testing: CI'
22

33
on:
44
push:

.github/workflows/e2e.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: 'E2E Tests'
1+
name: 'Testing: E2E'
22

33
on:
44
push:

scripts/get-release-version.js

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -164,20 +164,40 @@ function getPatchVersion(patchFrom) {
164164
const distTag = patchFrom === 'stable' ? 'latest' : 'preview';
165165
const pattern = distTag === 'latest' ? 'v[0-9].[0-9].[0-9]' : 'v*-preview*';
166166
const { latestVersion, latestTag } = getAndVerifyTags(distTag, pattern);
167-
const [version, ...prereleaseParts] = latestVersion.split('-');
168-
const prerelease = prereleaseParts.join('-');
169-
const versionParts = version.split('.');
170-
const major = versionParts[0];
171-
const minor = versionParts[1];
172-
const patch = versionParts[2] ? parseInt(versionParts[2]) : 0;
173-
const releaseVersion = prerelease
174-
? `${major}.${minor}.${patch + 1}-${prerelease}`
175-
: `${major}.${minor}.${patch + 1}`;
176-
return {
177-
releaseVersion,
178-
npmTag: distTag,
179-
previousReleaseTag: latestTag,
180-
};
167+
168+
if (patchFrom === 'stable') {
169+
// For stable versions, increment the patch number: 0.5.4 -> 0.5.5
170+
const versionParts = latestVersion.split('.');
171+
const major = versionParts[0];
172+
const minor = versionParts[1];
173+
const patch = versionParts[2] ? parseInt(versionParts[2]) : 0;
174+
const releaseVersion = `${major}.${minor}.${patch + 1}`;
175+
return {
176+
releaseVersion,
177+
npmTag: distTag,
178+
previousReleaseTag: latestTag,
179+
};
180+
} else {
181+
// For preview versions, increment the preview number: 0.6.0-preview.2 -> 0.6.0-preview.3
182+
const [version, prereleasePart] = latestVersion.split('-');
183+
if (!prereleasePart || !prereleasePart.startsWith('preview.')) {
184+
throw new Error(
185+
`Invalid preview version format: ${latestVersion}. Expected format like "0.6.0-preview.2"`,
186+
);
187+
}
188+
189+
const previewNumber = parseInt(prereleasePart.split('.')[1]);
190+
if (isNaN(previewNumber)) {
191+
throw new Error(`Could not parse preview number from: ${prereleasePart}`);
192+
}
193+
194+
const releaseVersion = `${version}-preview.${previewNumber + 1}`;
195+
return {
196+
releaseVersion,
197+
npmTag: distTag,
198+
previousReleaseTag: latestTag,
199+
};
200+
}
181201
}
182202

183203
export function getVersion(options = {}) {

scripts/releasing/create-patch-pr.js

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,53 @@ async function main() {
9797

9898
// Cherry-pick the commit.
9999
console.log(`Cherry-picking commit ${commit} into ${hotfixBranch}...`);
100-
run(`git cherry-pick ${commit}`, dryRun);
100+
let hasConflicts = false;
101+
if (!dryRun) {
102+
try {
103+
execSync(`git cherry-pick ${commit}`, { stdio: 'pipe' });
104+
console.log(`✅ Cherry-pick successful - no conflicts detected`);
105+
} catch (error) {
106+
// Check if this is a cherry-pick conflict
107+
try {
108+
const status = execSync('git status --porcelain', { encoding: 'utf8' });
109+
const conflictFiles = status
110+
.split('\n')
111+
.filter(
112+
(line) =>
113+
line.startsWith('UU ') ||
114+
line.startsWith('AA ') ||
115+
line.startsWith('DU ') ||
116+
line.startsWith('UD '),
117+
);
118+
119+
if (conflictFiles.length > 0) {
120+
hasConflicts = true;
121+
console.log(
122+
`⚠️ Cherry-pick has conflicts in ${conflictFiles.length} file(s):`,
123+
);
124+
conflictFiles.forEach((file) =>
125+
console.log(` - ${file.substring(3)}`),
126+
);
127+
128+
// Add all files (including conflict markers) and commit
129+
console.log(
130+
`📝 Creating commit with conflict markers for manual resolution...`,
131+
);
132+
execSync('git add .');
133+
execSync(`git commit --no-edit`);
134+
console.log(`✅ Committed cherry-pick with conflict markers`);
135+
} else {
136+
// Re-throw if it's not a conflict error
137+
throw error;
138+
}
139+
} catch (_statusError) {
140+
// Re-throw original error if we can't determine the status
141+
throw error;
142+
}
143+
}
144+
} else {
145+
console.log(`[DRY RUN] Would cherry-pick ${commit}`);
146+
}
101147

102148
// Push the hotfix branch.
103149
console.log(`Pushing hotfix branch ${hotfixBranch} to origin...`);
@@ -107,15 +153,45 @@ async function main() {
107153
console.log(
108154
`Creating pull request from ${hotfixBranch} to ${releaseBranch}...`,
109155
);
110-
const prTitle = `fix(patch): cherry-pick ${commit.substring(0, 7)} to ${releaseBranch}`;
156+
let prTitle = `fix(patch): cherry-pick ${commit.substring(0, 7)} to ${releaseBranch}`;
111157
let prBody = `This PR automatically cherry-picks commit ${commit} to patch the ${channel} release.`;
158+
159+
if (hasConflicts) {
160+
prTitle = `fix(patch): cherry-pick ${commit.substring(0, 7)} to ${releaseBranch} [CONFLICTS]`;
161+
prBody += `
162+
163+
## ⚠️ Merge Conflicts Detected
164+
165+
This cherry-pick resulted in merge conflicts that need manual resolution.
166+
167+
### 🔧 Next Steps:
168+
1. **Review the conflicts**: Check out this branch and review the conflict markers
169+
2. **Resolve conflicts**: Edit the affected files to resolve the conflicts
170+
3. **Test the changes**: Ensure the patch works correctly after resolution
171+
4. **Update this PR**: Push your conflict resolution
172+
173+
### 📋 Files with conflicts:
174+
The commit has been created with conflict markers for easier manual resolution.
175+
176+
### 🚨 Important:
177+
- Do not merge this PR until conflicts are resolved
178+
- The automated patch release will trigger once this PR is merged`;
179+
}
180+
112181
if (dryRun) {
113182
prBody += '\n\n**[DRY RUN]**';
114183
}
184+
115185
const prCommand = `gh pr create --base ${releaseBranch} --head ${hotfixBranch} --title "${prTitle}" --body "${prBody}"`;
116186
run(prCommand, dryRun);
117187

118-
console.log('Patch process completed successfully!');
188+
if (hasConflicts) {
189+
console.log(
190+
'⚠️ Patch process completed with conflicts - manual resolution required!',
191+
);
192+
} else {
193+
console.log('✅ Patch process completed successfully!');
194+
}
119195

120196
if (dryRun) {
121197
console.log('\n--- Dry Run Summary ---');
@@ -125,7 +201,7 @@ async function main() {
125201
console.log('---------------------');
126202
}
127203

128-
return { newBranch: hotfixBranch, created: true };
204+
return { newBranch: hotfixBranch, created: true, hasConflicts };
129205
}
130206

131207
function run(command, dryRun = false, throwOnError = true) {

scripts/releasing/patch-create-comment.js

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -182,18 +182,22 @@ A patch branch [\`${branch}\`](https://github.com/${repository}/tree/${branch})
182182
const mockPrNumber = Math.floor(Math.random() * 1000) + 8000;
183183
const mockPrUrl = `https://github.com/${repository}/pull/${mockPrNumber}`;
184184

185+
const hasConflicts =
186+
logContent.includes('Cherry-pick has conflicts') ||
187+
logContent.includes('[CONFLICTS]');
188+
185189
commentBody = `🚀 **Patch PR Created!**
186190
187191
**📋 Patch Details:**
188192
- **Channel**: \`${channel}\` → will publish to npm tag \`${npmTag}\`
189193
- **Commit**: \`${commit}\`
190194
- **Hotfix Branch**: [\`${branch}\`](https://github.com/${repository}/tree/${branch})
191-
- **Hotfix PR**: [#${mockPrNumber}](${mockPrUrl})
195+
- **Hotfix PR**: [#${mockPrNumber}](${mockPrUrl})${hasConflicts ? '\n- **⚠️ Status**: Cherry-pick conflicts detected - manual resolution required' : ''}
192196
193197
**📝 Next Steps:**
194-
1. Review and approve the hotfix PR: [#${mockPrNumber}](${mockPrUrl})
195-
2. Once merged, the patch release will automatically trigger
196-
3. You'll receive updates here when the release completes
198+
1. ${hasConflicts ? '⚠️ **Resolve conflicts** in the hotfix PR first' : 'Review and approve the hotfix PR'}: [#${mockPrNumber}](${mockPrUrl})${hasConflicts ? '\n2. **Test your changes** after resolving conflicts' : ''}
199+
${hasConflicts ? '3' : '2'}. Once merged, the patch release will automatically trigger
200+
${hasConflicts ? '4' : '3'}. You'll receive updates here when the release completes
197201
198202
**🔗 Track Progress:**
199203
- [View hotfix PR #${mockPrNumber}](${mockPrUrl})`;
@@ -209,18 +213,22 @@ A patch branch [\`${branch}\`](https://github.com/${repository}/tree/${branch})
209213

210214
if (prList.data.length > 0) {
211215
const pr = prList.data[0];
216+
const hasConflicts =
217+
logContent.includes('Cherry-pick has conflicts') ||
218+
pr.title.includes('[CONFLICTS]');
219+
212220
commentBody = `🚀 **Patch PR Created!**
213221
214222
**📋 Patch Details:**
215223
- **Channel**: \`${channel}\` → will publish to npm tag \`${npmTag}\`
216224
- **Commit**: \`${commit}\`
217225
- **Hotfix Branch**: [\`${branch}\`](https://github.com/${repository}/tree/${branch})
218-
- **Hotfix PR**: [#${pr.number}](${pr.html_url})
226+
- **Hotfix PR**: [#${pr.number}](${pr.html_url})${hasConflicts ? '\n- **⚠️ Status**: Cherry-pick conflicts detected - manual resolution required' : ''}
219227
220228
**📝 Next Steps:**
221-
1. Review and approve the hotfix PR: [#${pr.number}](${pr.html_url})
222-
2. Once merged, the patch release will automatically trigger
223-
3. You'll receive updates here when the release completes
229+
1. ${hasConflicts ? '⚠️ **Resolve conflicts** in the hotfix PR first' : 'Review and approve the hotfix PR'}: [#${pr.number}](${pr.html_url})${hasConflicts ? '\n2. **Test your changes** after resolving conflicts' : ''}
230+
${hasConflicts ? '3' : '2'}. Once merged, the patch release will automatically trigger
231+
${hasConflicts ? '4' : '3'}. You'll receive updates here when the release completes
224232
225233
**🔗 Track Progress:**
226234
- [View hotfix PR #${pr.number}](${pr.html_url})`;

scripts/tests/get-release-version.test.js

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,22 @@ describe('getVersion', () => {
2121
if (command.includes('npm view') && command.includes('--tag=latest'))
2222
return '0.4.1';
2323
if (command.includes('npm view') && command.includes('--tag=preview'))
24-
return '0.5.0-preview-2';
24+
return '0.5.0-preview.2';
2525
if (command.includes('npm view') && command.includes('--tag=nightly'))
2626
return '0.6.0-nightly.20250910.a31830a3';
2727

2828
// Git Tag Mocks
2929
if (command.includes("git tag --sort=-creatordate -l 'v[0-9].[0-9].[0-9]'"))
3030
return 'v0.4.1';
3131
if (command.includes("git tag --sort=-creatordate -l 'v*-preview*'"))
32-
return 'v0.5.0-preview-2';
32+
return 'v0.5.0-preview.2';
3333
if (command.includes("git tag --sort=-creatordate -l 'v*-nightly*'"))
3434
return 'v0.6.0-nightly.20250910.a31830a3';
3535

3636
// GitHub Release Mocks
3737
if (command.includes('gh release view "v0.4.1"')) return 'v0.4.1';
38-
if (command.includes('gh release view "v0.5.0-preview-2"'))
39-
return 'v0.5.0-preview-2';
38+
if (command.includes('gh release view "v0.5.0-preview.2"'))
39+
return 'v0.5.0-preview.2';
4040
if (command.includes('gh release view "v0.6.0-nightly.20250910.a31830a3"'))
4141
return 'v0.6.0-nightly.20250910.a31830a3';
4242

@@ -71,7 +71,7 @@ describe('getVersion', () => {
7171
const result = getVersion({ type: 'preview' });
7272
expect(result.releaseVersion).toBe('0.6.0-preview.0');
7373
expect(result.npmTag).toBe('preview');
74-
expect(result.previousReleaseTag).toBe('v0.5.0-preview-2');
74+
expect(result.previousReleaseTag).toBe('v0.5.0-preview.2');
7575
});
7676

7777
it('should use the override version for preview if provided', () => {
@@ -82,7 +82,7 @@ describe('getVersion', () => {
8282
});
8383
expect(result.releaseVersion).toBe('4.5.6-preview.0');
8484
expect(result.npmTag).toBe('preview');
85-
expect(result.previousReleaseTag).toBe('v0.5.0-preview-2');
85+
expect(result.previousReleaseTag).toBe('v0.5.0-preview.2');
8686
});
8787

8888
it('should calculate the next nightly version from the latest nightly', () => {
@@ -106,9 +106,9 @@ describe('getVersion', () => {
106106
it('should calculate the next patch version for a preview release', () => {
107107
vi.mocked(execSync).mockImplementation(mockExecSync);
108108
const result = getVersion({ type: 'patch', 'patch-from': 'preview' });
109-
expect(result.releaseVersion).toBe('0.5.1-preview-2');
109+
expect(result.releaseVersion).toBe('0.5.0-preview.3');
110110
expect(result.npmTag).toBe('preview');
111-
expect(result.previousReleaseTag).toBe('v0.5.0-preview-2');
111+
expect(result.previousReleaseTag).toBe('v0.5.0-preview.2');
112112
});
113113
});
114114

@@ -160,21 +160,21 @@ describe('getVersion', () => {
160160
vi.mocked(execSync).mockImplementation(mockWithMismatchGitTag);
161161

162162
expect(() => getVersion({ type: 'stable' })).toThrow(
163-
'Discrepancy found! NPM preview tag (0.5.0-preview-2) does not match latest git preview tag (v0.4.0-preview-99).',
163+
'Discrepancy found! NPM preview tag (0.5.0-preview.2) does not match latest git preview tag (v0.4.0-preview-99).',
164164
);
165165
});
166166

167167
it('should throw an error if the GitHub release is missing', () => {
168168
const mockWithMissingRelease = (command) => {
169-
if (command.includes('gh release view "v0.5.0-preview-2"')) {
169+
if (command.includes('gh release view "v0.5.0-preview.2"')) {
170170
throw new Error('gh command failed'); // Simulate gh failure
171171
}
172172
return mockExecSync(command);
173173
};
174174
vi.mocked(execSync).mockImplementation(mockWithMissingRelease);
175175

176176
expect(() => getVersion({ type: 'stable' })).toThrow(
177-
'Discrepancy found! Failed to verify GitHub release for v0.5.0-preview-2.',
177+
'Discrepancy found! Failed to verify GitHub release for v0.5.0-preview.2.',
178178
);
179179
});
180180
});

0 commit comments

Comments
 (0)