mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 22:21:22 -07:00
dealing with conflicts (#8772)
This commit is contained in:
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: 'Gemini CLI CI'
|
||||
name: 'Testing: CI'
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
2
.github/workflows/e2e.yml
vendored
2
.github/workflows/e2e.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: 'E2E Tests'
|
||||
name: 'Testing: E2E'
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
@@ -164,20 +164,40 @@ function getPatchVersion(patchFrom) {
|
||||
const distTag = patchFrom === 'stable' ? 'latest' : 'preview';
|
||||
const pattern = distTag === 'latest' ? 'v[0-9].[0-9].[0-9]' : 'v*-preview*';
|
||||
const { latestVersion, latestTag } = getAndVerifyTags(distTag, pattern);
|
||||
const [version, ...prereleaseParts] = latestVersion.split('-');
|
||||
const prerelease = prereleaseParts.join('-');
|
||||
const versionParts = version.split('.');
|
||||
const major = versionParts[0];
|
||||
const minor = versionParts[1];
|
||||
const patch = versionParts[2] ? parseInt(versionParts[2]) : 0;
|
||||
const releaseVersion = prerelease
|
||||
? `${major}.${minor}.${patch + 1}-${prerelease}`
|
||||
: `${major}.${minor}.${patch + 1}`;
|
||||
return {
|
||||
releaseVersion,
|
||||
npmTag: distTag,
|
||||
previousReleaseTag: latestTag,
|
||||
};
|
||||
|
||||
if (patchFrom === 'stable') {
|
||||
// For stable versions, increment the patch number: 0.5.4 -> 0.5.5
|
||||
const versionParts = latestVersion.split('.');
|
||||
const major = versionParts[0];
|
||||
const minor = versionParts[1];
|
||||
const patch = versionParts[2] ? parseInt(versionParts[2]) : 0;
|
||||
const releaseVersion = `${major}.${minor}.${patch + 1}`;
|
||||
return {
|
||||
releaseVersion,
|
||||
npmTag: distTag,
|
||||
previousReleaseTag: latestTag,
|
||||
};
|
||||
} else {
|
||||
// For preview versions, increment the preview number: 0.6.0-preview.2 -> 0.6.0-preview.3
|
||||
const [version, prereleasePart] = latestVersion.split('-');
|
||||
if (!prereleasePart || !prereleasePart.startsWith('preview.')) {
|
||||
throw new Error(
|
||||
`Invalid preview version format: ${latestVersion}. Expected format like "0.6.0-preview.2"`,
|
||||
);
|
||||
}
|
||||
|
||||
const previewNumber = parseInt(prereleasePart.split('.')[1]);
|
||||
if (isNaN(previewNumber)) {
|
||||
throw new Error(`Could not parse preview number from: ${prereleasePart}`);
|
||||
}
|
||||
|
||||
const releaseVersion = `${version}-preview.${previewNumber + 1}`;
|
||||
return {
|
||||
releaseVersion,
|
||||
npmTag: distTag,
|
||||
previousReleaseTag: latestTag,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function getVersion(options = {}) {
|
||||
|
||||
@@ -97,7 +97,53 @@ async function main() {
|
||||
|
||||
// Cherry-pick the commit.
|
||||
console.log(`Cherry-picking commit ${commit} into ${hotfixBranch}...`);
|
||||
run(`git cherry-pick ${commit}`, dryRun);
|
||||
let hasConflicts = false;
|
||||
if (!dryRun) {
|
||||
try {
|
||||
execSync(`git cherry-pick ${commit}`, { stdio: 'pipe' });
|
||||
console.log(`✅ Cherry-pick successful - no conflicts detected`);
|
||||
} catch (error) {
|
||||
// Check if this is a cherry-pick conflict
|
||||
try {
|
||||
const status = execSync('git status --porcelain', { encoding: 'utf8' });
|
||||
const conflictFiles = status
|
||||
.split('\n')
|
||||
.filter(
|
||||
(line) =>
|
||||
line.startsWith('UU ') ||
|
||||
line.startsWith('AA ') ||
|
||||
line.startsWith('DU ') ||
|
||||
line.startsWith('UD '),
|
||||
);
|
||||
|
||||
if (conflictFiles.length > 0) {
|
||||
hasConflicts = true;
|
||||
console.log(
|
||||
`⚠️ Cherry-pick has conflicts in ${conflictFiles.length} file(s):`,
|
||||
);
|
||||
conflictFiles.forEach((file) =>
|
||||
console.log(` - ${file.substring(3)}`),
|
||||
);
|
||||
|
||||
// Add all files (including conflict markers) and commit
|
||||
console.log(
|
||||
`📝 Creating commit with conflict markers for manual resolution...`,
|
||||
);
|
||||
execSync('git add .');
|
||||
execSync(`git commit --no-edit`);
|
||||
console.log(`✅ Committed cherry-pick with conflict markers`);
|
||||
} else {
|
||||
// Re-throw if it's not a conflict error
|
||||
throw error;
|
||||
}
|
||||
} catch (_statusError) {
|
||||
// Re-throw original error if we can't determine the status
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(`[DRY RUN] Would cherry-pick ${commit}`);
|
||||
}
|
||||
|
||||
// Push the hotfix branch.
|
||||
console.log(`Pushing hotfix branch ${hotfixBranch} to origin...`);
|
||||
@@ -107,15 +153,45 @@ async function main() {
|
||||
console.log(
|
||||
`Creating pull request from ${hotfixBranch} to ${releaseBranch}...`,
|
||||
);
|
||||
const prTitle = `fix(patch): cherry-pick ${commit.substring(0, 7)} to ${releaseBranch}`;
|
||||
let prTitle = `fix(patch): cherry-pick ${commit.substring(0, 7)} to ${releaseBranch}`;
|
||||
let prBody = `This PR automatically cherry-picks commit ${commit} to patch the ${channel} release.`;
|
||||
|
||||
if (hasConflicts) {
|
||||
prTitle = `fix(patch): cherry-pick ${commit.substring(0, 7)} to ${releaseBranch} [CONFLICTS]`;
|
||||
prBody += `
|
||||
|
||||
## ⚠️ Merge Conflicts Detected
|
||||
|
||||
This cherry-pick resulted in merge conflicts that need manual resolution.
|
||||
|
||||
### 🔧 Next Steps:
|
||||
1. **Review the conflicts**: Check out this branch and review the conflict markers
|
||||
2. **Resolve conflicts**: Edit the affected files to resolve the conflicts
|
||||
3. **Test the changes**: Ensure the patch works correctly after resolution
|
||||
4. **Update this PR**: Push your conflict resolution
|
||||
|
||||
### 📋 Files with conflicts:
|
||||
The commit has been created with conflict markers for easier manual resolution.
|
||||
|
||||
### 🚨 Important:
|
||||
- Do not merge this PR until conflicts are resolved
|
||||
- The automated patch release will trigger once this PR is merged`;
|
||||
}
|
||||
|
||||
if (dryRun) {
|
||||
prBody += '\n\n**[DRY RUN]**';
|
||||
}
|
||||
|
||||
const prCommand = `gh pr create --base ${releaseBranch} --head ${hotfixBranch} --title "${prTitle}" --body "${prBody}"`;
|
||||
run(prCommand, dryRun);
|
||||
|
||||
console.log('Patch process completed successfully!');
|
||||
if (hasConflicts) {
|
||||
console.log(
|
||||
'⚠️ Patch process completed with conflicts - manual resolution required!',
|
||||
);
|
||||
} else {
|
||||
console.log('✅ Patch process completed successfully!');
|
||||
}
|
||||
|
||||
if (dryRun) {
|
||||
console.log('\n--- Dry Run Summary ---');
|
||||
@@ -125,7 +201,7 @@ async function main() {
|
||||
console.log('---------------------');
|
||||
}
|
||||
|
||||
return { newBranch: hotfixBranch, created: true };
|
||||
return { newBranch: hotfixBranch, created: true, hasConflicts };
|
||||
}
|
||||
|
||||
function run(command, dryRun = false, throwOnError = true) {
|
||||
|
||||
@@ -182,18 +182,22 @@ A patch branch [\`${branch}\`](https://github.com/${repository}/tree/${branch})
|
||||
const mockPrNumber = Math.floor(Math.random() * 1000) + 8000;
|
||||
const mockPrUrl = `https://github.com/${repository}/pull/${mockPrNumber}`;
|
||||
|
||||
const hasConflicts =
|
||||
logContent.includes('Cherry-pick has conflicts') ||
|
||||
logContent.includes('[CONFLICTS]');
|
||||
|
||||
commentBody = `🚀 **Patch PR Created!**
|
||||
|
||||
**📋 Patch Details:**
|
||||
- **Channel**: \`${channel}\` → will publish to npm tag \`${npmTag}\`
|
||||
- **Commit**: \`${commit}\`
|
||||
- **Hotfix Branch**: [\`${branch}\`](https://github.com/${repository}/tree/${branch})
|
||||
- **Hotfix PR**: [#${mockPrNumber}](${mockPrUrl})
|
||||
- **Hotfix PR**: [#${mockPrNumber}](${mockPrUrl})${hasConflicts ? '\n- **⚠️ Status**: Cherry-pick conflicts detected - manual resolution required' : ''}
|
||||
|
||||
**📝 Next Steps:**
|
||||
1. Review and approve the hotfix PR: [#${mockPrNumber}](${mockPrUrl})
|
||||
2. Once merged, the patch release will automatically trigger
|
||||
3. You'll receive updates here when the release completes
|
||||
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' : ''}
|
||||
${hasConflicts ? '3' : '2'}. Once merged, the patch release will automatically trigger
|
||||
${hasConflicts ? '4' : '3'}. You'll receive updates here when the release completes
|
||||
|
||||
**🔗 Track Progress:**
|
||||
- [View hotfix PR #${mockPrNumber}](${mockPrUrl})`;
|
||||
@@ -209,18 +213,22 @@ A patch branch [\`${branch}\`](https://github.com/${repository}/tree/${branch})
|
||||
|
||||
if (prList.data.length > 0) {
|
||||
const pr = prList.data[0];
|
||||
const hasConflicts =
|
||||
logContent.includes('Cherry-pick has conflicts') ||
|
||||
pr.title.includes('[CONFLICTS]');
|
||||
|
||||
commentBody = `🚀 **Patch PR Created!**
|
||||
|
||||
**📋 Patch Details:**
|
||||
- **Channel**: \`${channel}\` → will publish to npm tag \`${npmTag}\`
|
||||
- **Commit**: \`${commit}\`
|
||||
- **Hotfix Branch**: [\`${branch}\`](https://github.com/${repository}/tree/${branch})
|
||||
- **Hotfix PR**: [#${pr.number}](${pr.html_url})
|
||||
- **Hotfix PR**: [#${pr.number}](${pr.html_url})${hasConflicts ? '\n- **⚠️ Status**: Cherry-pick conflicts detected - manual resolution required' : ''}
|
||||
|
||||
**📝 Next Steps:**
|
||||
1. Review and approve the hotfix PR: [#${pr.number}](${pr.html_url})
|
||||
2. Once merged, the patch release will automatically trigger
|
||||
3. You'll receive updates here when the release completes
|
||||
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' : ''}
|
||||
${hasConflicts ? '3' : '2'}. Once merged, the patch release will automatically trigger
|
||||
${hasConflicts ? '4' : '3'}. You'll receive updates here when the release completes
|
||||
|
||||
**🔗 Track Progress:**
|
||||
- [View hotfix PR #${pr.number}](${pr.html_url})`;
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('getVersion', () => {
|
||||
if (command.includes('npm view') && command.includes('--tag=latest'))
|
||||
return '0.4.1';
|
||||
if (command.includes('npm view') && command.includes('--tag=preview'))
|
||||
return '0.5.0-preview-2';
|
||||
return '0.5.0-preview.2';
|
||||
if (command.includes('npm view') && command.includes('--tag=nightly'))
|
||||
return '0.6.0-nightly.20250910.a31830a3';
|
||||
|
||||
@@ -29,14 +29,14 @@ describe('getVersion', () => {
|
||||
if (command.includes("git tag --sort=-creatordate -l 'v[0-9].[0-9].[0-9]'"))
|
||||
return 'v0.4.1';
|
||||
if (command.includes("git tag --sort=-creatordate -l 'v*-preview*'"))
|
||||
return 'v0.5.0-preview-2';
|
||||
return 'v0.5.0-preview.2';
|
||||
if (command.includes("git tag --sort=-creatordate -l 'v*-nightly*'"))
|
||||
return 'v0.6.0-nightly.20250910.a31830a3';
|
||||
|
||||
// GitHub Release Mocks
|
||||
if (command.includes('gh release view "v0.4.1"')) return 'v0.4.1';
|
||||
if (command.includes('gh release view "v0.5.0-preview-2"'))
|
||||
return 'v0.5.0-preview-2';
|
||||
if (command.includes('gh release view "v0.5.0-preview.2"'))
|
||||
return 'v0.5.0-preview.2';
|
||||
if (command.includes('gh release view "v0.6.0-nightly.20250910.a31830a3"'))
|
||||
return 'v0.6.0-nightly.20250910.a31830a3';
|
||||
|
||||
@@ -71,7 +71,7 @@ describe('getVersion', () => {
|
||||
const result = getVersion({ type: 'preview' });
|
||||
expect(result.releaseVersion).toBe('0.6.0-preview.0');
|
||||
expect(result.npmTag).toBe('preview');
|
||||
expect(result.previousReleaseTag).toBe('v0.5.0-preview-2');
|
||||
expect(result.previousReleaseTag).toBe('v0.5.0-preview.2');
|
||||
});
|
||||
|
||||
it('should use the override version for preview if provided', () => {
|
||||
@@ -82,7 +82,7 @@ describe('getVersion', () => {
|
||||
});
|
||||
expect(result.releaseVersion).toBe('4.5.6-preview.0');
|
||||
expect(result.npmTag).toBe('preview');
|
||||
expect(result.previousReleaseTag).toBe('v0.5.0-preview-2');
|
||||
expect(result.previousReleaseTag).toBe('v0.5.0-preview.2');
|
||||
});
|
||||
|
||||
it('should calculate the next nightly version from the latest nightly', () => {
|
||||
@@ -106,9 +106,9 @@ describe('getVersion', () => {
|
||||
it('should calculate the next patch version for a preview release', () => {
|
||||
vi.mocked(execSync).mockImplementation(mockExecSync);
|
||||
const result = getVersion({ type: 'patch', 'patch-from': 'preview' });
|
||||
expect(result.releaseVersion).toBe('0.5.1-preview-2');
|
||||
expect(result.releaseVersion).toBe('0.5.0-preview.3');
|
||||
expect(result.npmTag).toBe('preview');
|
||||
expect(result.previousReleaseTag).toBe('v0.5.0-preview-2');
|
||||
expect(result.previousReleaseTag).toBe('v0.5.0-preview.2');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -160,13 +160,13 @@ describe('getVersion', () => {
|
||||
vi.mocked(execSync).mockImplementation(mockWithMismatchGitTag);
|
||||
|
||||
expect(() => getVersion({ type: 'stable' })).toThrow(
|
||||
'Discrepancy found! NPM preview tag (0.5.0-preview-2) does not match latest git preview tag (v0.4.0-preview-99).',
|
||||
'Discrepancy found! NPM preview tag (0.5.0-preview.2) does not match latest git preview tag (v0.4.0-preview-99).',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the GitHub release is missing', () => {
|
||||
const mockWithMissingRelease = (command) => {
|
||||
if (command.includes('gh release view "v0.5.0-preview-2"')) {
|
||||
if (command.includes('gh release view "v0.5.0-preview.2"')) {
|
||||
throw new Error('gh command failed'); // Simulate gh failure
|
||||
}
|
||||
return mockExecSync(command);
|
||||
@@ -174,7 +174,7 @@ describe('getVersion', () => {
|
||||
vi.mocked(execSync).mockImplementation(mockWithMissingRelease);
|
||||
|
||||
expect(() => getVersion({ type: 'stable' })).toThrow(
|
||||
'Discrepancy found! Failed to verify GitHub release for v0.5.0-preview-2.',
|
||||
'Discrepancy found! Failed to verify GitHub release for v0.5.0-preview.2.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user