e-pick 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.js +81 -0
- package/lib/pick-commit.js +162 -0
- package/package.json +17 -0
package/cli.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import meow from 'meow';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import pickCommit from './lib/pick-commit.js';
|
|
5
|
+
|
|
6
|
+
const cli = meow(`
|
|
7
|
+
Usage
|
|
8
|
+
$ epick [options] <file_path>
|
|
9
|
+
|
|
10
|
+
<file_path> - Path to the CSV file containing commit and repository information.
|
|
11
|
+
|
|
12
|
+
Options
|
|
13
|
+
--commit-index, -c Index of the commit ID column in the CSV file. Default is 5.
|
|
14
|
+
--repo-index, -r Index of the repository name column in the CSV file. Default is 1.
|
|
15
|
+
--repo-name, -n Name of the repository to filter commits by. If empty, the first valid repository name found will be used.
|
|
16
|
+
--execute, -x Execute the cherry-pick command. Without this flag, the command will only simulate execution.
|
|
17
|
+
--allow-empty-commit, -a Allows empty commits. Default is false.
|
|
18
|
+
--verbose, -v Print detailed logs.
|
|
19
|
+
|
|
20
|
+
Examples
|
|
21
|
+
$ epick --commit-index 3 --repo-index 2 --repo-name "your-repo" --execute ./commits.csv
|
|
22
|
+
Executes the cherry-pick command on the commit of the repository named "your-repo".
|
|
23
|
+
|
|
24
|
+
$ epick -c 4 -r 1 -n "example-repo" ./commits.csv
|
|
25
|
+
Simulates the cherry-pick command for "example-repo".
|
|
26
|
+
`, {
|
|
27
|
+
importMeta: import.meta,
|
|
28
|
+
flags: {
|
|
29
|
+
commitIndex: {
|
|
30
|
+
type: 'number',
|
|
31
|
+
shortFlag: 'c',
|
|
32
|
+
default: 5,
|
|
33
|
+
},
|
|
34
|
+
repoIndex: {
|
|
35
|
+
type: 'number',
|
|
36
|
+
shortFlag: 'r',
|
|
37
|
+
default: 1,
|
|
38
|
+
},
|
|
39
|
+
repoName: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
shortFlag: 'n',
|
|
42
|
+
default: ''
|
|
43
|
+
},
|
|
44
|
+
execute: {
|
|
45
|
+
type: 'boolean',
|
|
46
|
+
shortFlag: 'x'
|
|
47
|
+
},
|
|
48
|
+
allowEmptyCommit: {
|
|
49
|
+
type: 'boolean',
|
|
50
|
+
shortFlag: 'a',
|
|
51
|
+
default: false
|
|
52
|
+
},
|
|
53
|
+
verbose: {
|
|
54
|
+
type: 'boolean',
|
|
55
|
+
shortFlag: 'v',
|
|
56
|
+
default: false
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
allowUnknownFlags: false
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (cli.input.length === 0) {
|
|
63
|
+
console.error("Error: No file path provided. Please specify the path to the CSV file.");
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const filePath = cli.input[0];
|
|
68
|
+
|
|
69
|
+
if (!fs.existsSync(filePath)) {
|
|
70
|
+
console.error(`Error: The file "${filePath}" does not exist.`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const options = cli.flags;
|
|
75
|
+
|
|
76
|
+
if (options.verbose) {
|
|
77
|
+
console.log("Options:", options);
|
|
78
|
+
console.log("Processing file:", filePath);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
await pickCommit(filePath, options);
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
|
|
4
|
+
const COMMIT_HASH_REGEX = /^[0-9a-f]{40}$/;
|
|
5
|
+
const isValidCommit = (commit_id) => COMMIT_HASH_REGEX.test(commit_id);
|
|
6
|
+
|
|
7
|
+
const uniqueArray = (array) => [...new Set(array)];
|
|
8
|
+
|
|
9
|
+
function isExistCommit(commitId, repoPath) {
|
|
10
|
+
try {
|
|
11
|
+
// Change the current working directory to the repository path
|
|
12
|
+
const originalCwd = process.cwd();
|
|
13
|
+
process.chdir(repoPath);
|
|
14
|
+
|
|
15
|
+
// Execute the git command to check the commit type
|
|
16
|
+
const result = execSync(`git cat-file -t ${commitId}`, { stdio: 'pipe' }).toString().trim();
|
|
17
|
+
|
|
18
|
+
// Change back to the original working directory
|
|
19
|
+
process.chdir(originalCwd);
|
|
20
|
+
|
|
21
|
+
// If the command's output starts with "commit", the ID is valid
|
|
22
|
+
return result === 'commit';
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (error.message.includes('fatal: git cat-file: could not get object info')) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
console.error(error.message);
|
|
28
|
+
process.exit(error.status || 128);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getSheetData({ file_path, commit_index, repo_index }) {
|
|
33
|
+
// Read the CSV file
|
|
34
|
+
let csv;
|
|
35
|
+
try {
|
|
36
|
+
csv = fs.readFileSync(file_path, 'utf8');
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.error(`Failed to read file ${file_path}`, err);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Split the CSV data into an array of arrays
|
|
43
|
+
const rawData = csv
|
|
44
|
+
.split('\n')
|
|
45
|
+
.map((line) => line.split('\t').map((item) => item?.trim()))
|
|
46
|
+
.filter((line) => line.filter(Boolean).length > 2);
|
|
47
|
+
|
|
48
|
+
const rows = rawData.map((line) => ({
|
|
49
|
+
commit_id: line[commit_index],
|
|
50
|
+
repo_name: line[repo_index],
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
if (!isValidCommit(rows[0]?.commit_id)) {
|
|
54
|
+
rows.shift(); // Remove header row
|
|
55
|
+
}
|
|
56
|
+
return rows;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isMatchRepoName(row, selected_repo) {
|
|
60
|
+
return row?.repo_name?.toLowerCase() === selected_repo.toLowerCase();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Extracts and returns the repository name from the provided object.
|
|
65
|
+
*
|
|
66
|
+
* @param {Object} params - The parameters object.
|
|
67
|
+
* @param {string} params.repo_name - The name of the repository to be extracted.
|
|
68
|
+
* @param {Array.<{commit_id: string, repo_name: string}>} params.data - An array of objects,
|
|
69
|
+
* each containing a `commit_id` and a `repo_name`. This parameter is not used in the current implementation.
|
|
70
|
+
*
|
|
71
|
+
* @returns {string} The repository name extracted from the `repo_name` parameter.
|
|
72
|
+
*/
|
|
73
|
+
function getRepoName({ repo_name, data }) {
|
|
74
|
+
if (repo_name) {
|
|
75
|
+
return repo_name;
|
|
76
|
+
}
|
|
77
|
+
const allRepos = uniqueArray(data.map((row) => row.repo_name).filter(Boolean));
|
|
78
|
+
|
|
79
|
+
const matchRepos = allRepos.map((name) => {
|
|
80
|
+
const commitId = data.find((row) => isMatchRepoName(row, name) && row?.commit_id)?.commit_id;
|
|
81
|
+
|
|
82
|
+
const isExist = isExistCommit(commitId, process.cwd());
|
|
83
|
+
return isExist ? name : null;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const validRepoName = matchRepos.find(Boolean);
|
|
87
|
+
|
|
88
|
+
if (!validRepoName) {
|
|
89
|
+
console.error('Invalid repo name', 'Please check repoName', allRepos);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return validRepoName;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Processes a CSV file containing commit and repository information to generate and optionally execute a git cherry-pick command.
|
|
98
|
+
*
|
|
99
|
+
* This function reads a CSV file specified by the `filePath` parameter, filters the data based on the provided options,
|
|
100
|
+
* and generates a git cherry-pick command for the commits in the selected repository. If the `execute` option is true,
|
|
101
|
+
* the cherry-pick command is executed in the current repository.
|
|
102
|
+
*
|
|
103
|
+
* @param {string} filePath - The path to the CSV file containing commit and repository information.
|
|
104
|
+
* @param {Object} options - An object containing options for processing the CSV file.
|
|
105
|
+
* @param {number} options.commitIndex - The index of the commit ID column in the CSV file.
|
|
106
|
+
* @param {number} options.repoIndex - The index of the repository name column in the CSV file.
|
|
107
|
+
* @param {string} options.repoName - The name of the repository to filter commits by. If empty, the first valid repository name found will be used.
|
|
108
|
+
* @param {boolean} options.execute - If true, the generated cherry-pick command will be executed.
|
|
109
|
+
* @param {boolean} options.allowEmptyCommit - If true, allows do not throw error empty commits.
|
|
110
|
+
*/
|
|
111
|
+
export default async function pickCommit(filePath, options) {
|
|
112
|
+
const { commitIndex, repoIndex, repoName, execute, allowEmptyCommit } = options;
|
|
113
|
+
// Read the CSV file
|
|
114
|
+
const data = getSheetData({ file_path: filePath, commit_index: commitIndex, repo_index: repoIndex });
|
|
115
|
+
|
|
116
|
+
const selectedRepoName = getRepoName({ repo_name: repoName, data });
|
|
117
|
+
|
|
118
|
+
// Check for invalid repo index
|
|
119
|
+
const dataOfRepo = data.filter((line) => isMatchRepoName(line, selectedRepoName));
|
|
120
|
+
|
|
121
|
+
// Check for invalid commit hashes
|
|
122
|
+
const invalidCommit = dataOfRepo.find((line) => {
|
|
123
|
+
const commitId = line.commit_id;
|
|
124
|
+
return !isValidCommit(commitId) && (!allowEmptyCommit || commitId);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (invalidCommit) {
|
|
128
|
+
console.error('Invalid commit', 'Please check commitIndex', invalidCommit);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Extract commit hashes
|
|
133
|
+
const commits = dataOfRepo.map((line) => line.commit_id);
|
|
134
|
+
|
|
135
|
+
// Run git show command for each commit
|
|
136
|
+
const command = `git show -s --format="%ci %H" ${commits.join(' ')}`;
|
|
137
|
+
|
|
138
|
+
let output;
|
|
139
|
+
try {
|
|
140
|
+
output = execSync(command, { encoding: 'utf8' }).trim();
|
|
141
|
+
} catch (err) {
|
|
142
|
+
console.error(`Failed to execute command ${command}`, err);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Sort the output data by commit date and hash
|
|
147
|
+
const outputData = output
|
|
148
|
+
.split('\n')
|
|
149
|
+
.sort()
|
|
150
|
+
.map((line) => line.split(' '));
|
|
151
|
+
|
|
152
|
+
// Extract sorted commit hashes
|
|
153
|
+
const sortedCommits = outputData.map((line) => line[3]);
|
|
154
|
+
|
|
155
|
+
// Generate cherry-pick command
|
|
156
|
+
const pickCommand = `git cherry-pick ${sortedCommits.join(' ')}`;
|
|
157
|
+
console.log(pickCommand);
|
|
158
|
+
|
|
159
|
+
if (execute) {
|
|
160
|
+
execSync(pickCommand, { stdio: 'inherit' });
|
|
161
|
+
}
|
|
162
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "e-pick",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"scripts": {
|
|
6
|
+
},
|
|
7
|
+
"bin": {
|
|
8
|
+
"epick": "./cli.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [],
|
|
11
|
+
"author": "",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"meow": "^13.2.0"
|
|
16
|
+
}
|
|
17
|
+
}
|