-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add feature to delete, and publish the contribution
Signed-off-by: Anil Vishnoi <[email protected]>
- Loading branch information
1 parent
a8f5fad
commit 4f5189d
Showing
6 changed files
with
425 additions
and
71 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,10 +5,14 @@ import fs from 'fs'; | |
import path from 'path'; | ||
|
||
// Get the repository path from the environment variable | ||
const TAXONOMY_ROOT_DIR = process.env.NEXT_PUBLIC_TAXONOMY_ROOT_DIR || './.instructlab-ui'; | ||
const LOCAL_TAXONOMY_ROOT_DIR = process.env.NEXT_PUBLIC_LOCAL_TAXONOMY_ROOT_DIR || './.instructlab-ui'; | ||
interface Diffs { | ||
file: string; | ||
status: string; | ||
} | ||
|
||
export async function GET() { | ||
const REPO_DIR = path.join(TAXONOMY_ROOT_DIR, '/taxonomy'); | ||
const REPO_DIR = path.join(LOCAL_TAXONOMY_ROOT_DIR, '/taxonomy'); | ||
try { | ||
// Ensure the repository path exists | ||
if (!fs.existsSync(REPO_DIR)) { | ||
|
@@ -41,91 +45,234 @@ export async function GET() { | |
|
||
// Handle POST requests for merge or branch comparison | ||
export async function POST(req: NextRequest) { | ||
const REPO_DIR = path.join(TAXONOMY_ROOT_DIR, '/taxonomy'); | ||
const { branchName, action } = await req.json(); | ||
const LOCAL_TAXONOMY_DIR = path.join(LOCAL_TAXONOMY_ROOT_DIR, '/taxonomy'); | ||
const { branchName, action, remoteTaxonomyRepoDir } = await req.json(); | ||
console.log('Received POST request:', { branchName, action, remoteTaxonomyRepoDir }); | ||
|
||
if (action === 'delete') { | ||
return handleDelete(branchName, LOCAL_TAXONOMY_DIR); | ||
} | ||
|
||
if (action === 'diff') { | ||
return handleDiff(branchName, LOCAL_TAXONOMY_DIR); | ||
} | ||
|
||
if (action === 'publish') { | ||
return handlePublish(branchName, LOCAL_TAXONOMY_DIR, remoteTaxonomyRepoDir); | ||
} | ||
return NextResponse.json({ error: 'Invalid action specified' }, { status: 400 }); | ||
} | ||
|
||
async function handleDelete(branchName: string, localTaxonomyDir: string) { | ||
try { | ||
if (action === 'merge') { | ||
// Ensure valid branch name | ||
if (!branchName || branchName === 'main') { | ||
return NextResponse.json({ error: 'Invalid branch name for merge' }, { status: 400 }); | ||
} | ||
if (!branchName || branchName === 'main') { | ||
return NextResponse.json({ error: 'Invalid branch name for deletion' }, { status: 400 }); | ||
} | ||
|
||
// Initialize the repository and checkout main branch | ||
await git.init({ fs, dir: REPO_DIR }); | ||
await git.checkout({ fs, dir: REPO_DIR, ref: 'main' }); | ||
// Delete the target branch | ||
await git.deleteBranch({ fs, dir: localTaxonomyDir, ref: branchName }); | ||
|
||
// Perform the merge | ||
await git.merge({ | ||
fs, | ||
dir: REPO_DIR, | ||
ours: 'main', | ||
theirs: branchName, | ||
author: { | ||
name: 'Instruct Lab Local', | ||
email: '[email protected]' | ||
} | ||
}); | ||
return NextResponse.json({ message: `Successfully deleted branch ${branchName}.` }, { status: 200 }); | ||
} catch (error) { | ||
console.error(`Failed to delete contribution ${branchName}:`, error); | ||
return NextResponse.json( | ||
{ | ||
error: `Failed to delete contribution ${branchName}` | ||
}, | ||
{ status: 500 } | ||
); | ||
} finally { | ||
// Ensure switching back to 'main' branch after any operation | ||
try { | ||
await git.checkout({ fs, dir: localTaxonomyDir, ref: 'main' }); | ||
} catch (checkoutError) { | ||
console.error('Failed to switch back to main branch:', checkoutError); | ||
} | ||
} | ||
} | ||
|
||
async function handleDiff(branchName: string, localTaxonomyDir: string) { | ||
try { | ||
// Ensure valid branch name | ||
if (!branchName || branchName === 'main') { | ||
return NextResponse.json({ error: 'Invalid branch name for comparison' }, { status: 400 }); | ||
} | ||
|
||
const changes = await findDiff(branchName, localTaxonomyDir); | ||
return NextResponse.json({ changes }, { status: 200 }); | ||
} catch (error) { | ||
console.error(`Failed to show contribution changes ${branchName}:`, error); | ||
return NextResponse.json( | ||
{ | ||
error: `Failed to show contribution changes for ${branchName}` | ||
}, | ||
{ status: 500 } | ||
); | ||
} finally { | ||
// Ensure switching back to 'main' branch after any operation | ||
try { | ||
await git.checkout({ fs, dir: localTaxonomyDir, ref: 'main' }); | ||
} catch (checkoutError) { | ||
console.error('Failed to switch back to main branch:', checkoutError); | ||
} | ||
} | ||
} | ||
|
||
async function findDiff(branchName: string, localTaxonomyDir: string): Promise<Diffs[]> { | ||
// Fetch the commit SHA for `main` and the target branch | ||
const mainCommit = await git.resolveRef({ fs, dir: localTaxonomyDir, ref: 'main' }); | ||
const branchCommit = await git.resolveRef({ fs, dir: localTaxonomyDir, ref: branchName }); | ||
|
||
return NextResponse.json({ message: `Successfully merged ${branchName} into main.` }, { status: 200 }); | ||
} else if (action === 'diff') { | ||
// Ensure valid branch name | ||
if (!branchName || branchName === 'main') { | ||
return NextResponse.json({ error: 'Invalid branch name for comparison' }, { status: 400 }); | ||
const mainFiles = await getFilesFromTree(mainCommit); | ||
const branchFiles = await getFilesFromTree(branchCommit); | ||
|
||
// Create an array of Diffs to store changes | ||
const changes: Diffs[] = []; | ||
// Identify modified and deleted files | ||
for (const file in mainFiles) { | ||
if (branchFiles[file]) { | ||
if (mainFiles[file] !== branchFiles[file]) { | ||
changes.push({ file, status: 'modified' }); | ||
} | ||
} else { | ||
changes.push({ file, status: 'deleted' }); | ||
} | ||
} | ||
|
||
// Identify added files | ||
for (const file in branchFiles) { | ||
if (!mainFiles[file]) { | ||
changes.push({ file, status: 'added' }); | ||
} | ||
} | ||
return changes; | ||
} | ||
|
||
// Fetch the commit SHA for `main` and the target branch | ||
const mainCommit = await git.resolveRef({ fs, dir: REPO_DIR, ref: 'main' }); | ||
const branchCommit = await git.resolveRef({ fs, dir: REPO_DIR, ref: branchName }); | ||
async function getTopCommitDetails(dir: string, ref: string = 'HEAD') { | ||
try { | ||
// Fetch the top commit (latest commit on the branch) | ||
const [topCommit] = await git.log({ | ||
fs, | ||
dir, | ||
ref, | ||
depth: 1 // Only fetch the latest commit | ||
}); | ||
|
||
const mainFiles = await getFilesFromTree(mainCommit); | ||
const branchFiles = await getFilesFromTree(branchCommit); | ||
if (!topCommit) { | ||
throw new Error('No commits found in the repository.'); | ||
} | ||
|
||
const changes = []; | ||
// Extract commit message | ||
const commitMessage = topCommit.commit.message; | ||
|
||
// Identify modified and deleted files | ||
for (const file in mainFiles) { | ||
if (branchFiles[file]) { | ||
if (mainFiles[file] !== branchFiles[file]) { | ||
changes.push({ file, status: 'modified' }); | ||
} | ||
} else { | ||
changes.push({ file, status: 'deleted' }); | ||
} | ||
// Check for Signed-off-by line | ||
const signoffMatch = commitMessage.match(/^Signed-off-by: (.+)$/m); | ||
const signoff = signoffMatch ? signoffMatch[1] : null; | ||
|
||
return { | ||
message: commitMessage, | ||
signoff | ||
}; | ||
} catch (error) { | ||
console.error('Error reading top commit details:', error); | ||
throw error; | ||
} | ||
} | ||
async function handlePublish(branchName: string, localTaxonomyDir: string, remoteTaxonomyDir: string) { | ||
try { | ||
if (!branchName || branchName === 'main') { | ||
return NextResponse.json({ error: 'Invalid branch name for publish' }, { status: 400 }); | ||
} | ||
|
||
console.log(`Publishing contribution from ${branchName} to remote taxonomy repo at ${remoteTaxonomyDir}`); | ||
const changes = await findDiff(branchName, localTaxonomyDir); | ||
|
||
// Check if there are any changes to publish, create a new branch at remoteTaxonomyDir and copy all the files listed in the changes array to the new branch and create a commit | ||
if (changes.length > 0) { | ||
const remoteBranchName = branchName; | ||
await git.checkout({ fs, dir: localTaxonomyDir, ref: branchName }); | ||
// Read the commit message of the top commit from the branch | ||
const details = await getTopCommitDetails(localTaxonomyDir); | ||
|
||
// Check if the remote branch exists, if not create it | ||
const remoteBranchExists = await git.listBranches({ fs, dir: remoteTaxonomyDir }); | ||
if (remoteBranchExists.includes(remoteBranchName)) { | ||
console.log(`Branch ${remoteBranchName} exist in remote taxonomy, deleting it.`); | ||
// Delete the remote branch if it exists, we will recreate it | ||
await git.deleteBranch({ fs, dir: remoteTaxonomyDir, ref: remoteBranchName }); | ||
} else { | ||
console.log(`Branch ${remoteBranchName} does not exist in remote taxonomy, creating a new branch.`); | ||
} | ||
|
||
// Identify added files | ||
for (const file in branchFiles) { | ||
if (!mainFiles[file]) { | ||
changes.push({ file, status: 'added' }); | ||
await git.checkout({ fs, dir: remoteTaxonomyDir, ref: 'main' }); | ||
await git.branch({ fs, dir: remoteTaxonomyDir, ref: remoteBranchName }); | ||
await git.checkout({ fs, dir: remoteTaxonomyDir, ref: remoteBranchName }); | ||
|
||
// Copy the files listed in the changes array to the remote branch and if the directories do not exist, create them | ||
for (const change of changes) { | ||
console.log(`Copying ${change.file} to remote branch`); | ||
const filePath = path.join(localTaxonomyDir, change.file); | ||
const remoteFilePath = path.join(remoteTaxonomyDir, change.file); | ||
const remoteFileDir = path.dirname(remoteFilePath); | ||
if (!fs.existsSync(remoteFileDir)) { | ||
fs.mkdirSync(remoteFileDir, { recursive: true }); | ||
} | ||
fs.copyFileSync(filePath, remoteFilePath); | ||
} | ||
|
||
return NextResponse.json({ changes }, { status: 200 }); | ||
await git.add({ fs, dir: remoteTaxonomyDir, filepath: '.' }); | ||
|
||
const authorInfo = details.signoff!.match(/(.*?) <(.*?)>/); | ||
let authorName = ''; | ||
let authorEmail = ''; | ||
if (authorInfo) { | ||
console.log(`Author information found in signoff: ${authorInfo}`); | ||
authorName = authorInfo[1]; | ||
authorEmail = authorInfo[2]; | ||
} else { | ||
return NextResponse.json({ message: `Author information is not present in the contribution ${branchName}.` }, { status: 500 }); | ||
} | ||
// Create a commit with the same message and signoff as the top commit from the local branch | ||
await git.commit({ | ||
fs, | ||
dir: remoteTaxonomyDir, | ||
message: details.message, | ||
author: { | ||
name: authorName, | ||
email: authorEmail | ||
} | ||
}); | ||
console.log(`Successfully published contribution from ${branchName} to remote taxonomy repo at ${remoteTaxonomyDir}`); | ||
return NextResponse.json({ message: `Successfully published contribution to ${remoteTaxonomyDir}.` }, { status: 200 }); | ||
} else { | ||
return NextResponse.json({ error: 'Invalid action specified' }, { status: 400 }); | ||
return NextResponse.json({ message: `No changes to publish from ${branchName}.` }, { status: 200 }); | ||
} | ||
} catch (error) { | ||
console.error(`Failed to ${action === 'merge' ? 'merge branch' : 'compare branches'}:`, error); | ||
console.error(`Failed to publish contribution from ${branchName}:`, error); | ||
return NextResponse.json( | ||
{ | ||
error: `Failed to ${action === 'merge' ? 'merge branch' : 'compare branches'}` | ||
error: `Failed to publish contribution from ${branchName}` | ||
}, | ||
{ status: 500 } | ||
); | ||
} finally { | ||
// Ensure switching back to 'main' branch after any operation | ||
try { | ||
await git.checkout({ fs, dir: REPO_DIR, ref: 'main' }); | ||
await git.checkout({ fs, dir: localTaxonomyDir, ref: 'main' }); | ||
} catch (checkoutError) { | ||
console.error('Failed to switch back to main branch:', checkoutError); | ||
console.error('Failed to switch back to main branch in local taxonomy repo:', checkoutError); | ||
} | ||
try { | ||
await git.checkout({ fs, dir: remoteTaxonomyDir, ref: 'main' }); | ||
} catch (checkoutError) { | ||
console.error('Failed to switch back to main branch in remote taxonomy repo:', checkoutError); | ||
} | ||
} | ||
} | ||
|
||
// Helper function to recursively gather file paths and their oids from a tree | ||
async function getFilesFromTree(commitOid: string) { | ||
const REPO_DIR = path.join(TAXONOMY_ROOT_DIR, '/taxonomy'); | ||
const REPO_DIR = path.join(LOCAL_TAXONOMY_ROOT_DIR, '/taxonomy'); | ||
const fileMap: Record<string, string> = {}; | ||
|
||
async function walkTree(dir: string) { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.