@relbns/github-cloner
Version:
Github Cloner is a CLI tool for cloning Github repositories with interactive lists in the terminal. the tool lists your repositories and sorts them by owners (users and organizations) and by name. choosing the repo will clone it to the current directory i
249 lines (221 loc) • 7.61 kB
JavaScript
import simpleGit from 'simple-git';
import chalk from 'chalk';
import axios from 'axios';
export const isGitRepository = async () => {
const git = simpleGit();
try {
await git.revparse(['--git-dir']);
return true;
} catch (error) {
return false;
}
};
export const getCurrentBranch = async () => {
const git = simpleGit();
try {
const status = await git.status();
return status.current;
} catch (error) {
throw new Error(`Failed to get current branch: ${error.message}`);
}
};
export const checkoutAndPull = async (branchName) => {
const git = simpleGit();
try {
await git.checkout(branchName);
const pullResult = await git.pull('origin', branchName);
return { success: true, message: pullResult.summary };
} catch (error) {
return { success: false, message: error.message };
}
};
export const hasMergeConflicts = async () => {
const git = simpleGit();
try {
const status = await git.status();
return status.conflicted.length > 0;
} catch (error) {
return false;
}
};
export const abortMerge = async () => {
const git = simpleGit();
try {
await git.merge(['--abort']);
return { success: true, message: 'Merge aborted successfully' };
} catch (error) {
return { success: false, message: error.message };
}
};
export const getAllRemoteBranches = async () => {
const git = simpleGit();
try {
await git.fetch(); // Ensure we have latest remote info
const branches = await git.branch(['-r']);
return branches.all
.filter(branch => !branch.includes('HEAD'))
.map(branch => ({
name: branch.replace('origin/', ''),
fullName: branch
}));
} catch (error) {
console.error(chalk.red('Error fetching remote branches:', error.message));
return [];
}
};
export const getLocalBranches = async () => {
const git = simpleGit();
try {
const branches = await git.branchLocal();
return branches.all;
} catch (error) {
console.error(chalk.red('Error fetching local branches:', error.message));
return [];
}
};
export const getOpenPullRequests = async (token) => {
try {
const { owner, repo } = await getRemoteInfo();
console.log(chalk.dim(`Fetching PRs for ${owner}/${repo}...`));
const response = await axios.get(
`https://api.github.com/repos/${owner}/${repo}/pulls`,
{
headers: {
Authorization: `token ${token}`,
Accept: 'application/vnd.github.v3+json'
}
}
);
return response.data.map(pr => ({
branch: pr.head.ref,
title: pr.title,
url: pr.html_url
}));
} catch (error) {
if (error.response?.status === 404) {
throw new Error('Repository not found or no access. Please check your permissions.');
}
throw error;
}
};
const extractRepoInfo = (remoteUrl) => {
try {
// Handle various URL formats
// SSH format: git@github.com:owner/repo.git
const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/([^.]+)(\.git)?/);
if (sshMatch) {
return { owner: sshMatch[1], repo: sshMatch[2] };
}
// HTTPS format: https://github.com/owner/repo.git
const httpsMatch = remoteUrl.match(/github\.com\/([^/]+)\/([^/.]+)(\.git)?/);
if (httpsMatch) {
return { owner: httpsMatch[1], repo: httpsMatch[2] };
}
// Handle URL without .git extension
const simpleMatch = remoteUrl.match(/([^/:]+)\/([^/.]+)$/);
if (simpleMatch) {
return { owner: simpleMatch[1], repo: simpleMatch[2] };
}
throw new Error(`Unrecognized remote URL format: ${remoteUrl}`);
} catch (error) {
console.error(chalk.dim('Debug - Remote URL:'), remoteUrl);
throw error;
}
};
const getRemoteInfo = async () => {
const git = simpleGit();
try {
// Get all remotes
const remotes = await git.getRemotes(true);
if (!remotes.length) {
throw new Error('No remotes configured for this repository');
}
// Prefer 'origin', but fall back to first remote
const remote = remotes.find(r => r.name === 'origin') || remotes[0];
const remoteUrl = remote.refs.fetch || remote.refs.push;
if (!remoteUrl) {
throw new Error('No valid URL found for remote');
}
return extractRepoInfo(remoteUrl);
} catch (error) {
console.error(chalk.red('Error getting repository information:'), error.message);
throw new Error('Failed to get repository information from Git remote');
}
};
export const hasUncommittedChanges = async () => {
const git = simpleGit();
try {
const status = await git.status();
return status.files.length > 0;
} catch (error) {
console.error(chalk.red('Error checking for uncommitted changes:', error.message));
return false;
}
};
export const stashChanges = async () => {
const git = simpleGit();
try {
await git.stash(['push', '-u', '-m', "Auto-stashed by github-cloner"]);
return { success: true, message: 'Changes stashed successfully' };
} catch (error) {
return { success: false, message: error.message };
}
};
export const checkoutBranch = async (branchName) => {
const git = simpleGit();
try {
await git.fetch();
const localBranches = await getLocalBranches();
if (localBranches.includes(branchName)) {
await git.checkout(branchName);
await git.pull('origin', branchName);
return { success: true, message: `Updated local branch ${branchName}` };
} else {
await git.checkout(['-b', branchName, `origin/${branchName}`]);
return { success: true, message: `Created and checked out ${branchName}` };
}
} catch (error) {
if (error.message.includes('would be overwritten by checkout')) {
return {
success: false,
message: 'Cannot checkout: You have uncommitted changes that would be overwritten'
};
}
return { success: false, message: error.message };
}
};
export const pullLatestChanges = async (branchName) => {
const git = simpleGit();
try {
const currentBranch = await getCurrentBranch();
if (currentBranch !== branchName) {
return {
success: false,
needsCheckout: true,
message: `You are on branch '${currentBranch}'. Need to checkout '${branchName}' first.`
};
}
await git.fetch('origin', branchName);
try {
await git.pull('origin', branchName);
return {
success: true,
message: `Successfully pulled latest changes for ${branchName}`
};
} catch (error) {
if (await hasMergeConflicts()) {
return {
success: false,
hasConflicts: true,
message: 'Merge conflicts detected'
};
}
throw error;
}
} catch (error) {
return {
success: false,
message: error.message
};
}
};