/**
* Git Operations
*
* Low-level Git operations executed via GitHub API including:
* - Reference operations
* - Tree operations
* - Commit operations
* - Blob operations
*/
import { logger } from '../utils/logger.js';
import { retryHandler } from '../utils/retry-handler.js';
/**
* Git Operations Class
*/
export class GitOperations {
/**
* Get a reference (branch, tag)
* @param {string} githubToken - GitHub access token
* @param {string} owner - Repository owner
* @param {string} repo - Repository name
* @param {string} ref - Reference name (e.g., heads/main)
* @param {string} correlationId - Request correlation ID
* @returns {Promise<Object>} Reference data
*/
async getRef(githubToken, owner, repo, ref, correlationId) {
const url = `https://api.github.com/repos/${owner}/${repo}/git/refs/${ref}`;
const response = await retryHandler.execute(
() => fetch(url, {
headers: {
'Authorization': `token ${githubToken}`,
'User-Agent': 'github-mcp-control-plane',
'Accept': 'application/vnd.github.v3+json'
}
}),
{ correlationId, operation: 'get_ref' }
);
if (!response.ok) {
if (response.status === 404) {
throw new Error(`Reference '${ref}' not found`);
}
throw new Error(`Failed to get reference: ${response.status} ${response.statusText}`);
}
return await response.json();
}
/**
* Create a reference (branch)
* @param {string} githubToken - GitHub access token
* @param {string} owner - Repository owner
* @param {string} repo - Repository name
* @param {string} ref - Reference name (e.g., refs/heads/feature-branch)
* @param {string} sha - SHA to point reference to
* @param {string} correlationId - Request correlation ID
* @returns {Promise<Object>} Created reference data
*/
async createRef(githubToken, owner, repo, ref, sha, correlationId) {
const url = `https://api.github.com/repos/${owner}/${repo}/git/refs`;
const response = await retryHandler.execute(
() => fetch(url, {
method: 'POST',
headers: {
'Authorization': `token ${githubToken}`,
'User-Agent': 'github-mcp-control-plane',
'Accept': 'application/vnd.github.v3+json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
ref,
sha
})
}),
{ correlationId, operation: 'create_ref' }
);
if (!response.ok) {
const error = await response.json();
if (response.status === 422 && error.message && error.message.includes('already exists')) {
throw new Error(`Reference '${ref}' already exists`);
}
throw new Error(`Failed to create reference: ${response.status} ${error.message}`);
}
return await response.json();
}
/**
* Update a reference
* @param {string} githubToken - GitHub access token
* @param {string} owner - Repository owner
* @param {string} repo - Repository name
* @param {string} ref - Reference name (e.g., heads/main)
* @param {string} sha - New SHA to point reference to
* @param {boolean} force - Force update (dangerous)
* @param {string} correlationId - Request correlation ID
* @returns {Promise<Object>} Updated reference data
*/
async updateRef(githubToken, owner, repo, ref, sha, force = false, correlationId) {
const url = `https://api.github.com/repos/${owner}/${repo}/git/refs/${ref}`;
const response = await retryHandler.execute(
() => fetch(url, {
method: 'PATCH',
headers: {
'Authorization': `token ${githubToken}`,
'User-Agent': 'github-mcp-control-plane',
'Accept': 'application/vnd.github.v3+json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sha,
force
})
}),
{ correlationId, operation: 'update_ref' }
);
if (!response.ok) {
const error = await response.json();
throw new Error(`Failed to update reference: ${response.status} ${error.message}`);
}
return await response.json();
}
/**
* Delete a reference
* @param {string} githubToken - GitHub access token
* @param {string} owner - Repository owner
* @param {string} repo - Repository name
* @param {string} ref - Reference name (e.g., heads/feature-branch)
* @param {string} correlationId - Request correlation ID
* @returns {Promise<void>}
*/
async deleteRef(githubToken, owner, repo, ref, correlationId) {
const url = `https://api.github.com/repos/${owner}/${repo}/git/refs/${ref}`;
const response = await retryHandler.execute(
() => fetch(url, {
method: 'DELETE',
headers: {
'Authorization': `token ${githubToken}`,
'User-Agent': 'github-mcp-control-plane',
'Accept': 'application/vnd.github.v3+json'
}
}),
{ correlationId, operation: 'delete_ref' }
);
if (!response.ok) {
throw new Error(`Failed to delete reference: ${response.status} ${response.statusText}`);
}
}
/**
* Get a blob (file content)
* @param {string} githubToken - GitHub access token
* @param {string} owner - Repository owner
* @param {string} repo - Repository name
* @param {string} sha - Blob SHA
* @param {string} correlationId - Request correlation ID
* @returns {Promise<Object>} Blob data
*/
async getBlob(githubToken, owner, repo, sha, correlationId) {
const url = `https://api.github.com/repos/${owner}/${repo}/git/blobs/${sha}`;
const response = await retryHandler.execute(
() => fetch(url, {
headers: {
'Authorization': `token ${githubToken}`,
'User-Agent': 'github-mcp-control-plane',
'Accept': 'application/vnd.github.v3+json'
}
}),
{ correlationId, operation: 'get_blob' }
);
if (!response.ok) {
throw new Error(`Failed to get blob: ${response.status} ${response.statusText}`);
}
return await response.json();
}
/**
* Create a blob
* @param {string} githubToken - GitHub access token
* @param {string} owner - Repository owner
* @param {string} repo - Repository name
* @param {string} content - File content
* @param {string} encoding - Content encoding (utf-8 or base64)
* @param {string} correlationId - Request correlation ID
* @returns {Promise<Object>} Created blob data
*/
async createBlob(githubToken, owner, repo, content, encoding = 'utf-8', correlationId) {
const url = `https://api.github.com/repos/${owner}/${repo}/git/blobs`;
const response = await retryHandler.execute(
() => fetch(url, {
method: 'POST',
headers: {
'Authorization': `token ${githubToken}`,
'User-Agent': 'github-mcp-control-plane',
'Accept': 'application/vnd.github.v3+json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
content,
encoding
})
}),
{ correlationId, operation: 'create_blob' }
);
if (!response.ok) {
const error = await response.json();
throw new Error(`Failed to create blob: ${response.status} ${error.message}`);
}
return await response.json();
}
/**
* Get a tree (directory)
* @param {string} githubToken - GitHub access token
* @param {string} owner - Repository owner
* @param {string} repo - Repository name
* @param {string} sha - Tree SHA
* @param {boolean} recursive - Get tree recursively
* @param {string} correlationId - Request correlation ID
* @returns {Promise<Object>} Tree data
*/
async getTree(githubToken, owner, repo, sha, recursive = false, correlationId) {
const url = new URL(`https://api.github.com/repos/${owner}/${repo}/git/trees/${sha}`);
if (recursive) {
url.searchParams.append('recursive', '1');
}
const response = await retryHandler.execute(
() => fetch(url.toString(), {
headers: {
'Authorization': `token ${githubToken}`,
'User-Agent': 'github-mcp-control-plane',
'Accept': 'application/vnd.github.v3+json'
}
}),
{ correlationId, operation: 'get_tree' }
);
if (!response.ok) {
throw new Error(`Failed to get tree: ${response.status} ${response.statusText}`);
}
return await response.json();
}
/**
* Create a tree
* @param {string} githubToken - GitHub access token
* @param {string} owner - Repository owner
* @param {string} repo - Repository name
* @param {Array} tree - Tree entries
* @param {string} baseTree - Base tree SHA (optional)
* @param {string} correlationId - Request correlation ID
* @returns {Promise<Object>} Created tree data
*/
async createTree(githubToken, owner, repo, tree, baseTree = null, correlationId) {
const url = `https://api.github.com/repos/${owner}/${repo}/git/trees`;
const body = { tree };
if (baseTree) {
body.base_tree = baseTree;
}
const response = await retryHandler.execute(
() => fetch(url, {
method: 'POST',
headers: {
'Authorization': `token ${githubToken}`,
'User-Agent': 'github-mcp-control-plane',
'Accept': 'application/vnd.github.v3+json',
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
}),
{ correlationId, operation: 'create_tree' }
);
if (!response.ok) {
const error = await response.json();
throw new Error(`Failed to create tree: ${response.status} ${error.message}`);
}
return await response.json();
}
/**
* Get a 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<Object>} Commit data
*/
async getCommit(githubToken, owner, repo, sha, correlationId) {
const url = `https://api.github.com/repos/${owner}/${repo}/git/commits/${sha}`;
const response = await retryHandler.execute(
() => fetch(url, {
headers: {
'Authorization': `token ${githubToken}`,
'User-Agent': 'github-mcp-control-plane',
'Accept': 'application/vnd.github.v3+json'
}
}),
{ correlationId, operation: 'get_commit' }
);
if (!response.ok) {
throw new Error(`Failed to get commit: ${response.status} ${response.statusText}`);
}
return await response.json();
}
/**
* Create a commit
* @param {string} githubToken - GitHub access token
* @param {string} owner - Repository owner
* @param {string} repo - Repository name
* @param {string} message - Commit message
* @param {string} tree - Tree SHA
* @param {Array<string>} parents - Parent commit SHAs
* @param {string} correlationId - Request correlation ID
* @returns {Promise<Object>} Created commit data
*/
async createCommit(githubToken, owner, repo, message, tree, parents, correlationId) {
const url = `https://api.github.com/repos/${owner}/${repo}/git/commits`;
const response = await retryHandler.execute(
() => fetch(url, {
method: 'POST',
headers: {
'Authorization': `token ${githubToken}`,
'User-Agent': 'github-mcp-control-plane',
'Accept': 'application/vnd.github.v3+json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
message,
tree,
parents
})
}),
{ correlationId, operation: 'create_commit' }
);
if (!response.ok) {
const error = await response.json();
throw new Error(`Failed to create commit: ${response.status} ${error.message}`);
}
return await response.json();
}
}
// Export singleton instance
export const gitOperations = new GitOperations();