/**
* Rollback Handler
*
* Handles rollback operations for failed commits and operations.
* Ensures data consistency by reverting to previous states.
*/
import { logger } from '../utils/logger.js';
import { gitOperations } from './git-operations.js';
/**
* Rollback Handler Class
*/
export class RollbackHandler {
constructor() {
this.rollbackHistory = new Map();
this.maxHistorySize = 100;
}
/**
* Rollback a commit by resetting branch to previous state
* @param {string} githubToken - GitHub access token
* @param {string} owner - Repository owner
* @param {string} repo - Repository name
* @param {string} branch - Branch name
* @param {string} targetSha - SHA to rollback to
* @param {string} correlationId - Request correlation ID
* @returns {Promise<Object>} Rollback result
*/
async rollbackCommit(githubToken, owner, repo, branch, targetSha, correlationId) {
logger.info('Initiating commit rollback', {
correlationId,
owner,
repo,
branch,
targetSha
});
try {
// Get current branch state before rollback
const currentRef = await gitOperations.getRef(
githubToken,
owner,
repo,
`heads/${branch}`,
correlationId
);
const currentSha = currentRef.object.sha;
// Store rollback history
this.recordRollback(owner, repo, branch, currentSha, targetSha);
// Update branch reference to target SHA
await gitOperations.updateRef(
githubToken,
owner,
repo,
`heads/${branch}`,
targetSha,
true, // Force update
correlationId
);
logger.info('Rollback successful', {
correlationId,
fromSha: currentSha,
toSha: targetSha
});
return {
success: true,
fromSha: currentSha,
toSha: targetSha,
branch,
message: 'Successfully rolled back to previous state'
};
} catch (error) {
logger.error('Rollback failed', {
correlationId,
error: error.message
});
return {
success: false,
error: error.message,
branch,
message: 'Failed to rollback: ' + error.message
};
}
}
/**
* Rollback multiple operations
* @param {string} githubToken - GitHub access token
* @param {Array} operations - Operations to rollback
* @param {string} correlationId - Request correlation ID
* @returns {Promise<Object>} Rollback result
*/
async rollbackMultiple(githubToken, operations, correlationId) {
logger.info('Initiating multiple rollback operations', {
correlationId,
operationCount: operations.length
});
const results = {
total: operations.length,
successful: 0,
failed: 0,
details: []
};
for (const operation of operations) {
try {
const result = await this.rollbackCommit(
githubToken,
operation.owner,
operation.repo,
operation.branch,
operation.targetSha,
correlationId
);
if (result.success) {
results.successful++;
} else {
results.failed++;
}
results.details.push(result);
} catch (error) {
results.failed++;
results.details.push({
success: false,
error: error.message,
operation
});
logger.error('Rollback operation failed', {
correlationId,
operation,
error: error.message
});
}
}
logger.info('Multiple rollback operations completed', {
correlationId,
successful: results.successful,
failed: results.failed
});
return results;
}
/**
* Create a rollback branch from current state
* @param {string} githubToken - GitHub access token
* @param {string} owner - Repository owner
* @param {string} repo - Repository name
* @param {string} sourceBranch - Source branch name
* @param {string} rollbackBranchName - Name for rollback branch
* @param {string} correlationId - Request correlation ID
* @returns {Promise<Object>} Rollback branch result
*/
async createRollbackBranch(githubToken, owner, repo, sourceBranch, rollbackBranchName, correlationId) {
logger.info('Creating rollback branch', {
correlationId,
owner,
repo,
sourceBranch,
rollbackBranchName
});
try {
// Get source branch SHA
const sourceRef = await gitOperations.getRef(
githubToken,
owner,
repo,
`heads/${sourceBranch}`,
correlationId
);
const sourceSha = sourceRef.object.sha;
// Create new branch from current state
await gitOperations.createRef(
githubToken,
owner,
repo,
`refs/heads/${rollbackBranchName}`,
sourceSha,
correlationId
);
logger.info('Rollback branch created successfully', {
correlationId,
rollbackBranchName,
fromSha: sourceSha
});
return {
success: true,
branch: rollbackBranchName,
fromSha: sourceSha,
message: 'Rollback branch created successfully'
};
} catch (error) {
logger.error('Failed to create rollback branch', {
correlationId,
error: error.message
});
return {
success: false,
error: error.message,
message: 'Failed to create rollback branch: ' + error.message
};
}
}
/**
* Get previous commit SHA before a specific commit
* @param {string} githubToken - GitHub access token
* @param {string} owner - Repository owner
* @param {string} repo - Repository name
* @param {string} sha - Commit SHA
* @param {string} correlationId - Request correlation ID
* @returns {Promise<string|null>} Previous commit SHA or null
*/
async getPreviousSha(githubToken, owner, repo, sha, correlationId) {
logger.debug('Getting previous commit SHA', {
correlationId,
owner,
repo,
sha
});
try {
const commitData = await gitOperations.getCommit(
githubToken,
owner,
repo,
sha,
correlationId
);
if (commitData.parents && commitData.parents.length > 0) {
return commitData.parents[0].sha;
}
return null;
} catch (error) {
logger.error('Failed to get previous commit SHA', {
correlationId,
sha,
error: error.message
});
return null;
}
}
/**
* Verify rollback integrity
* @param {string} githubToken - GitHub access token
* @param {string} owner - Repository owner
* @param {string} repo - Repository name
* @param {string} branch - Branch name
* @param {string} expectedSha - Expected SHA after rollback
* @param {string} correlationId - Request correlation ID
* @returns {Promise<Object>} Verification result
*/
async verifyRollback(githubToken, owner, repo, branch, expectedSha, correlationId) {
logger.info('Verifying rollback integrity', {
correlationId,
owner,
repo,
branch,
expectedSha
});
try {
const currentRef = await gitOperations.getRef(
githubToken,
owner,
repo,
`heads/${branch}`,
correlationId
);
const currentSha = currentRef.object.sha;
const isValid = currentSha === expectedSha;
logger.info('Rollback verification completed', {
correlationId,
isValid,
currentSha,
expectedSha
});
return {
valid: isValid,
currentSha,
expectedSha,
message: isValid ? 'Rollback verified successfully' : 'Rollback verification failed'
};
} catch (error) {
logger.error('Rollback verification failed', {
correlationId,
error: error.message
});
return {
valid: false,
error: error.message,
message: 'Verification failed: ' + error.message
};
}
}
/**
* Record rollback in history
* @param {string} owner - Repository owner
* @param {string} repo - Repository name
* @param {string} branch - Branch name
* @param {string} fromSha - Original SHA
* @param {string} toSha - Target SHA
*/
recordRollback(owner, repo, branch, fromSha, toSha) {
const key = `${owner}/${repo}/${branch}`;
const history = this.rollbackHistory.get(key) || [];
history.push({
timestamp: Date.now(),
fromSha,
toSha
});
// Keep only last N entries
if (history.length > this.maxHistorySize) {
history.shift();
}
this.rollbackHistory.set(key, history);
logger.debug('Rollback recorded', {
key,
fromSha,
toSha
});
}
/**
* Get rollback history for a branch
* @param {string} owner - Repository owner
* @param {string} repo - Repository name
* @param {string} branch - Branch name
* @returns {Array} Rollback history
*/
getRollbackHistory(owner, repo, branch) {
const key = `${owner}/${repo}/${branch}`;
return this.rollbackHistory.get(key) || [];
}
/**
* Clear rollback history
*/
clearHistory() {
this.rollbackHistory.clear();
logger.info('Rollback history cleared');
}
}
// Export singleton instance
export const rollbackHandler = new RollbackHandler();