UNPKG

gitaura

Version:

Tool to create backdated git commits for GitHub contribution graph

337 lines (286 loc) 9.7 kB
#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); const os = require('os'); const inquirer = require('inquirer'); function isGitHubCLIAvailable() { try { execSync('gh --version', { stdio: 'ignore' }); return true; } catch (error) { return false; } } function isGitHubAuthenticated() { try { execSync('gh auth status', { stdio: 'ignore' }); return true; } catch (error) { return false; } } function isGitRepo() { try { execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); return true; } catch (error) { return false; } } function hasGitOrigin() { try { const output = execSync('git remote get-url origin', { stdio: 'pipe' }); return true; } catch (error) { return false; } } function getGitOriginUrl() { try { return execSync('git remote get-url origin', { encoding: 'utf8' }).trim(); } catch (error) { return null; } } function distributeCommits(startDate, endDate, totalCommits) { const daysBetween = Math.floor((endDate - startDate) / (1000 * 60 * 60 * 24)) + 1; if (daysBetween <= 0) { throw new Error('End date must be after start date'); } const dateArray = []; const currentDate = new Date(startDate); while (currentDate <= endDate) { dateArray.push(new Date(currentDate)); currentDate.setDate(currentDate.getDate() + 1); } const commitMap = {}; dateArray.forEach(date => { commitMap[date.toDateString()] = 0; }); for (let i = 0; i < totalCommits; i++) { const randomIndex = Math.floor(Math.random() * dateArray.length); const dateKey = dateArray[randomIndex].toDateString(); commitMap[dateKey]++; } return commitMap; } async function setupGitRepo() { let repoUrl = null; const isRepo = isGitRepo(); const hasOrigin = isRepo && hasGitOrigin(); if (hasOrigin) { console.log('Git repository with origin already exists.'); repoUrl = getGitOriginUrl(); console.log(`Using existing origin: ${repoUrl}`); return repoUrl; } const ghAvailable = isGitHubCLIAvailable(); const ghAuthenticated = ghAvailable && isGitHubAuthenticated(); if (ghAuthenticated) { console.log('GitHub CLI detected and authenticated!'); let repoCreated = false; while (!repoCreated) { const repoAnswer = await inquirer.prompt([ { type: 'input', name: 'repoName', message: 'Enter a name for your new GitHub repository:', default: path.basename(process.cwd()), }, { type: 'confirm', name: 'isPrivate', message: 'Should the repository be private?', default: true, } ]); try { const repoExists = execSync(`gh repo view ${repoAnswer.repoName}`, { stdio: 'pipe' }); console.log(`Repository ${repoAnswer.repoName} already exists. Using the existing repository.`); repoCreated = true; repoUrl = execSync(`gh repo view ${repoAnswer.repoName} --json url -q .url`, { encoding: 'utf8' }).trim() + '.git'; } catch (error) { console.log(`Creating GitHub repository: ${repoAnswer.repoName}...`); const visibility = repoAnswer.isPrivate ? '--private' : '--public'; try { execSync(`gh repo create ${repoAnswer.repoName} ${visibility}`, { stdio: 'inherit' }); repoCreated = true; repoUrl = execSync(`gh repo view ${repoAnswer.repoName} --json url -q .url`, { encoding: 'utf8' }).trim() + '.git'; } catch (error) { console.error('Failed to create repository:', error.message); if (error.message.includes('Name already exists on this account')) { console.log('A repository with that name already exists. Please try a different name.'); } else { throw error; } } } } } else { if (ghAvailable) { console.log('GitHub CLI detected but not authenticated. Please run "gh auth login" separately and try again or create a Github repo and enter the URL.'); } else { console.log('GitHub CLI not detected. Using manual repository URL entry.'); } const repoAnswer = await inquirer.prompt([ { type: 'input', name: 'githubRepo', message: 'Enter your GitHub repository URL (e.g., https://github.com/username/repo):', validate: input => { if (!input || !input.match(/github\.com\/[\w-]+\/[\w-]+/)) { return 'Please enter a valid GitHub repository URL'; } return true; }, filter: input => { if (!input.endsWith('.git')) { return input.endsWith('/') ? `${input.slice(0, -1)}.git` : `${input}.git`; } return input; } } ]); repoUrl = repoAnswer.githubRepo; } if (!isRepo) { console.log('Initializing git repository...'); execSync('git init', { stdio: 'inherit' }); } console.log('Adding GitHub remote repository...'); if (hasOrigin) { execSync(`git remote set-url origin ${repoUrl}`, { stdio: 'inherit' }); } else { execSync(`git remote add origin ${repoUrl}`, { stdio: 'inherit' }); } console.log('Git repository initialized successfully!'); console.log(`GitHub remote added: ${repoUrl}`); return repoUrl; } async function createBackdatedCommits() { const dateFormat = /^\d{4}-\d{2}-\d{2}$/; const answers = await inquirer.prompt([ { type: 'input', name: 'startDate', message: 'Enter the start date for your commits (YYYY-MM-DD):', validate: input => { if (!dateFormat.test(input) || isNaN(new Date(input).getTime())) { return 'Please enter a valid date in YYYY-MM-DD format'; } return true; } }, { type: 'input', name: 'endDate', message: 'Enter the end date for your commits (YYYY-MM-DD):', validate: input => { if (!dateFormat.test(input) || isNaN(new Date(input).getTime())) { return 'Please enter a valid date in YYYY-MM-DD format'; } return true; } }, { type: 'number', name: 'commitCount', message: 'How many commits do you want to create?', validate: input => { if (isNaN(input) || input <= 0 || !Number.isInteger(Number(input))) { return 'Please enter a positive integer'; } return true; } } ]); const startDate = new Date(answers.startDate); const endDate = new Date(answers.endDate); const commitCount = parseInt(answers.commitCount); console.log('Distributing commits across date range...'); const commitMap = distributeCommits(startDate, endDate, commitCount); // Create a single file for all commits const commitFile = `commit_history.txt`; fs.writeFileSync(commitFile, 'Commit history file\n'); execSync(`git add ${commitFile}`, { stdio: 'ignore' }); console.log(`Creating ${commitCount} commits...`); // Prepare all commits data in advance const commits = []; for (const dateStr in commitMap) { const numCommits = commitMap[dateStr]; if (numCommits > 0) { const date = new Date(dateStr); for (let i = 0; i < numCommits; i++) { commits.push({ date: date, timestamp: date.toISOString() }); } } } // Sort commits by date commits.sort((a, b) => a.date - b.date); // Create all commits in one go for (let i = 0; i < commits.length; i++) { const commit = commits[i]; // Create the backdated commit const commitEnv = { GIT_AUTHOR_DATE: commit.timestamp, GIT_COMMITTER_DATE: commit.timestamp }; fs.appendFileSync(commitFile, `Commit ${i+1}/${commits.length} for ${commit.date.toDateString()}\n`); execSync(`git add ${commitFile}`, { stdio: 'ignore' }); execSync(`git commit -m "Commit for ${commit.date.toDateString()}"`, { env: { ...process.env, ...commitEnv }, stdio: 'ignore' }); // Show progress only occasionally if ((i+1) % Math.max(10, Math.floor(commits.length / 10)) === 0 || i === commits.length - 1) { console.log(`Progress: ${i+1}/${commits.length} commits (${Math.round((i+1)/commits.length*100)}%)`); } } console.log('All commits created successfully!'); const shouldPush = await inquirer.prompt([ { type: 'confirm', name: 'push', message: 'Push commits to GitHub?', default: true, } ]); if (shouldPush.push) { console.log('Pushing commits to GitHub...'); execSync('git push -u origin HEAD', { stdio: 'inherit' }); console.log('Commits pushed successfully!'); } } async function main() { try { const homeDir = os.homedir(); const defaultDir = path.join(homeDir, 'gitaura'); const dirAnswer = await inquirer.prompt([ { type: 'input', name: 'directory', message: 'Where would you like to create your repository?', default: defaultDir, } ]); const targetDir = dirAnswer.directory; if (!fs.existsSync(targetDir)) { console.log(`Creating directory: ${targetDir}`); fs.mkdirSync(targetDir, { recursive: true }); } else { console.log(`Directory already exists: ${targetDir}`); } process.chdir(targetDir); console.log(`Changed to directory: ${targetDir}`); await setupGitRepo(); await createBackdatedCommits(); } catch (error) { console.error('An error occurred:', error.message); process.exit(1); } } main();