mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 14:10:37 -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:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|||||||
2
.github/workflows/e2e.yml
vendored
2
.github/workflows/e2e.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: 'E2E Tests'
|
name: 'Testing: E2E'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|||||||
@@ -164,20 +164,40 @@ function getPatchVersion(patchFrom) {
|
|||||||
const distTag = patchFrom === 'stable' ? 'latest' : 'preview';
|
const distTag = patchFrom === 'stable' ? 'latest' : 'preview';
|
||||||
const pattern = distTag === 'latest' ? 'v[0-9].[0-9].[0-9]' : 'v*-preview*';
|
const pattern = distTag === 'latest' ? 'v[0-9].[0-9].[0-9]' : 'v*-preview*';
|
||||||
const { latestVersion, latestTag } = getAndVerifyTags(distTag, pattern);
|
const { latestVersion, latestTag } = getAndVerifyTags(distTag, pattern);
|
||||||
const [version, ...prereleaseParts] = latestVersion.split('-');
|
|
||||||
const prerelease = prereleaseParts.join('-');
|
if (patchFrom === 'stable') {
|
||||||
const versionParts = version.split('.');
|
// For stable versions, increment the patch number: 0.5.4 -> 0.5.5
|
||||||
const major = versionParts[0];
|
const versionParts = latestVersion.split('.');
|
||||||
const minor = versionParts[1];
|
const major = versionParts[0];
|
||||||
const patch = versionParts[2] ? parseInt(versionParts[2]) : 0;
|
const minor = versionParts[1];
|
||||||
const releaseVersion = prerelease
|
const patch = versionParts[2] ? parseInt(versionParts[2]) : 0;
|
||||||
? `${major}.${minor}.${patch + 1}-${prerelease}`
|
const releaseVersion = `${major}.${minor}.${patch + 1}`;
|
||||||
: `${major}.${minor}.${patch + 1}`;
|
return {
|
||||||
return {
|
releaseVersion,
|
||||||
releaseVersion,
|
npmTag: distTag,
|
||||||
npmTag: distTag,
|
previousReleaseTag: latestTag,
|
||||||
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 = {}) {
|
export function getVersion(options = {}) {
|
||||||
|
|||||||
@@ -97,7 +97,53 @@ async function main() {
|
|||||||
|
|
||||||
// Cherry-pick the commit.
|
// Cherry-pick the commit.
|
||||||
console.log(`Cherry-picking commit ${commit} into ${hotfixBranch}...`);
|
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.
|
// Push the hotfix branch.
|
||||||
console.log(`Pushing hotfix branch ${hotfixBranch} to origin...`);
|
console.log(`Pushing hotfix branch ${hotfixBranch} to origin...`);
|
||||||
@@ -107,15 +153,45 @@ async function main() {
|
|||||||
console.log(
|
console.log(
|
||||||
`Creating pull request from ${hotfixBranch} to ${releaseBranch}...`,
|
`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.`;
|
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) {
|
if (dryRun) {
|
||||||
prBody += '\n\n**[DRY RUN]**';
|
prBody += '\n\n**[DRY RUN]**';
|
||||||
}
|
}
|
||||||
|
|
||||||
const prCommand = `gh pr create --base ${releaseBranch} --head ${hotfixBranch} --title "${prTitle}" --body "${prBody}"`;
|
const prCommand = `gh pr create --base ${releaseBranch} --head ${hotfixBranch} --title "${prTitle}" --body "${prBody}"`;
|
||||||
run(prCommand, dryRun);
|
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) {
|
if (dryRun) {
|
||||||
console.log('\n--- Dry Run Summary ---');
|
console.log('\n--- Dry Run Summary ---');
|
||||||
@@ -125,7 +201,7 @@ async function main() {
|
|||||||
console.log('---------------------');
|
console.log('---------------------');
|
||||||
}
|
}
|
||||||
|
|
||||||
return { newBranch: hotfixBranch, created: true };
|
return { newBranch: hotfixBranch, created: true, hasConflicts };
|
||||||
}
|
}
|
||||||
|
|
||||||
function run(command, dryRun = false, throwOnError = true) {
|
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 mockPrNumber = Math.floor(Math.random() * 1000) + 8000;
|
||||||
const mockPrUrl = `https://github.com/${repository}/pull/${mockPrNumber}`;
|
const mockPrUrl = `https://github.com/${repository}/pull/${mockPrNumber}`;
|
||||||
|
|
||||||
|
const hasConflicts =
|
||||||
|
logContent.includes('Cherry-pick has conflicts') ||
|
||||||
|
logContent.includes('[CONFLICTS]');
|
||||||
|
|
||||||
commentBody = `🚀 **Patch PR Created!**
|
commentBody = `🚀 **Patch PR Created!**
|
||||||
|
|
||||||
**📋 Patch Details:**
|
**📋 Patch Details:**
|
||||||
- **Channel**: \`${channel}\` → will publish to npm tag \`${npmTag}\`
|
- **Channel**: \`${channel}\` → will publish to npm tag \`${npmTag}\`
|
||||||
- **Commit**: \`${commit}\`
|
- **Commit**: \`${commit}\`
|
||||||
- **Hotfix Branch**: [\`${branch}\`](https://github.com/${repository}/tree/${branch})
|
- **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:**
|
**📝 Next Steps:**
|
||||||
1. Review and approve the hotfix PR: [#${mockPrNumber}](${mockPrUrl})
|
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' : ''}
|
||||||
2. Once merged, the patch release will automatically trigger
|
${hasConflicts ? '3' : '2'}. Once merged, the patch release will automatically trigger
|
||||||
3. You'll receive updates here when the release completes
|
${hasConflicts ? '4' : '3'}. You'll receive updates here when the release completes
|
||||||
|
|
||||||
**🔗 Track Progress:**
|
**🔗 Track Progress:**
|
||||||
- [View hotfix PR #${mockPrNumber}](${mockPrUrl})`;
|
- [View hotfix PR #${mockPrNumber}](${mockPrUrl})`;
|
||||||
@@ -209,18 +213,22 @@ A patch branch [\`${branch}\`](https://github.com/${repository}/tree/${branch})
|
|||||||
|
|
||||||
if (prList.data.length > 0) {
|
if (prList.data.length > 0) {
|
||||||
const pr = prList.data[0];
|
const pr = prList.data[0];
|
||||||
|
const hasConflicts =
|
||||||
|
logContent.includes('Cherry-pick has conflicts') ||
|
||||||
|
pr.title.includes('[CONFLICTS]');
|
||||||
|
|
||||||
commentBody = `🚀 **Patch PR Created!**
|
commentBody = `🚀 **Patch PR Created!**
|
||||||
|
|
||||||
**📋 Patch Details:**
|
**📋 Patch Details:**
|
||||||
- **Channel**: \`${channel}\` → will publish to npm tag \`${npmTag}\`
|
- **Channel**: \`${channel}\` → will publish to npm tag \`${npmTag}\`
|
||||||
- **Commit**: \`${commit}\`
|
- **Commit**: \`${commit}\`
|
||||||
- **Hotfix Branch**: [\`${branch}\`](https://github.com/${repository}/tree/${branch})
|
- **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:**
|
**📝 Next Steps:**
|
||||||
1. Review and approve the hotfix PR: [#${pr.number}](${pr.html_url})
|
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' : ''}
|
||||||
2. Once merged, the patch release will automatically trigger
|
${hasConflicts ? '3' : '2'}. Once merged, the patch release will automatically trigger
|
||||||
3. You'll receive updates here when the release completes
|
${hasConflicts ? '4' : '3'}. You'll receive updates here when the release completes
|
||||||
|
|
||||||
**🔗 Track Progress:**
|
**🔗 Track Progress:**
|
||||||
- [View hotfix PR #${pr.number}](${pr.html_url})`;
|
- [View hotfix PR #${pr.number}](${pr.html_url})`;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ describe('getVersion', () => {
|
|||||||
if (command.includes('npm view') && command.includes('--tag=latest'))
|
if (command.includes('npm view') && command.includes('--tag=latest'))
|
||||||
return '0.4.1';
|
return '0.4.1';
|
||||||
if (command.includes('npm view') && command.includes('--tag=preview'))
|
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'))
|
if (command.includes('npm view') && command.includes('--tag=nightly'))
|
||||||
return '0.6.0-nightly.20250910.a31830a3';
|
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]'"))
|
if (command.includes("git tag --sort=-creatordate -l 'v[0-9].[0-9].[0-9]'"))
|
||||||
return 'v0.4.1';
|
return 'v0.4.1';
|
||||||
if (command.includes("git tag --sort=-creatordate -l 'v*-preview*'"))
|
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*'"))
|
if (command.includes("git tag --sort=-creatordate -l 'v*-nightly*'"))
|
||||||
return 'v0.6.0-nightly.20250910.a31830a3';
|
return 'v0.6.0-nightly.20250910.a31830a3';
|
||||||
|
|
||||||
// GitHub Release Mocks
|
// GitHub Release Mocks
|
||||||
if (command.includes('gh release view "v0.4.1"')) return 'v0.4.1';
|
if (command.includes('gh release view "v0.4.1"')) return 'v0.4.1';
|
||||||
if (command.includes('gh release view "v0.5.0-preview-2"'))
|
if (command.includes('gh release view "v0.5.0-preview.2"'))
|
||||||
return 'v0.5.0-preview-2';
|
return 'v0.5.0-preview.2';
|
||||||
if (command.includes('gh release view "v0.6.0-nightly.20250910.a31830a3"'))
|
if (command.includes('gh release view "v0.6.0-nightly.20250910.a31830a3"'))
|
||||||
return 'v0.6.0-nightly.20250910.a31830a3';
|
return 'v0.6.0-nightly.20250910.a31830a3';
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ describe('getVersion', () => {
|
|||||||
const result = getVersion({ type: 'preview' });
|
const result = getVersion({ type: 'preview' });
|
||||||
expect(result.releaseVersion).toBe('0.6.0-preview.0');
|
expect(result.releaseVersion).toBe('0.6.0-preview.0');
|
||||||
expect(result.npmTag).toBe('preview');
|
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', () => {
|
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.releaseVersion).toBe('4.5.6-preview.0');
|
||||||
expect(result.npmTag).toBe('preview');
|
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', () => {
|
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', () => {
|
it('should calculate the next patch version for a preview release', () => {
|
||||||
vi.mocked(execSync).mockImplementation(mockExecSync);
|
vi.mocked(execSync).mockImplementation(mockExecSync);
|
||||||
const result = getVersion({ type: 'patch', 'patch-from': 'preview' });
|
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.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);
|
vi.mocked(execSync).mockImplementation(mockWithMismatchGitTag);
|
||||||
|
|
||||||
expect(() => getVersion({ type: 'stable' })).toThrow(
|
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', () => {
|
it('should throw an error if the GitHub release is missing', () => {
|
||||||
const mockWithMissingRelease = (command) => {
|
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
|
throw new Error('gh command failed'); // Simulate gh failure
|
||||||
}
|
}
|
||||||
return mockExecSync(command);
|
return mockExecSync(command);
|
||||||
@@ -174,7 +174,7 @@ describe('getVersion', () => {
|
|||||||
vi.mocked(execSync).mockImplementation(mockWithMissingRelease);
|
vi.mocked(execSync).mockImplementation(mockWithMissingRelease);
|
||||||
|
|
||||||
expect(() => getVersion({ type: 'stable' })).toThrow(
|
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