Skip to content

Commit 2dc6da2

Browse files
committed
some cleanUp and more modular codes
1 parent 5344a8d commit 2dc6da2

File tree

4 files changed

+164
-37
lines changed

4 files changed

+164
-37
lines changed

index.js renamed to lib/index.js

Lines changed: 85 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ const chalk = require("chalk");
77
const objChange = require("on-change");
88
const cliProgress = require("cli-progress");
99
const argv = require("yargs");
10+
const Utils = require("./utils");
1011

12+
/**
13+
* define script options.
14+
*/
1115
argv
1216
.usage("Backup gitlab repo in local machine")
1317
.option("token", {
@@ -36,10 +40,13 @@ if (!argv.argv.token) {
3640
process.exit(0);
3741
}
3842

43+
/**
44+
* defining our needs.
45+
*/
3946
const token = argv.argv.token || "";
4047
const baseUrl = argv.argv.url || "https://gitlab.com";
4148
const output = argv.argv.output || "./repos";
42-
const pagination = 100;
49+
const pagination = 100; // currently maximum gitlab pagination supports.
4350
const defaultAddress = `/api/v4/projects?simple=true&membership=true&pagination=keyset&order_by=id&sort=asc&per_page=${pagination}`;
4451
const bar = new cliProgress.SingleBar(
4552
{
@@ -48,8 +55,19 @@ const bar = new cliProgress.SingleBar(
4855
cliProgress.Presets.shades_classic
4956
);
5057

58+
/**
59+
* for saving next paginated data url.
60+
*/
5161
let next;
62+
63+
/**
64+
* total repository for handling progress bar.
65+
*/
5266
let total = 0;
67+
68+
/**
69+
* to indicate when first cloning starts.
70+
*/
5371
let firstStart = true;
5472

5573
axios.defaults.baseURL = baseUrl;
@@ -59,12 +77,18 @@ if (!fs.existsSync(output)) {
5977
fs.mkdirSync(output);
6078
}
6179

80+
/**
81+
* listen for changes on cloned repos and we reached end of pagination or not.
82+
*/
6283
const observer = objChange(
6384
{
6485
cloned: 0,
6586
hasNext: true,
6687
},
6788
() => {
89+
/**
90+
* if we cloned all repos based on total and cloned and also there is no link for next paginated data, so we cloned all repos and should stop progress bar and cleanup message.
91+
*/
6892
if (observer.cloned === total && !observer.hasNext) {
6993
bar.stop();
7094
console.clear();
@@ -73,32 +97,48 @@ const observer = objChange(
7397
}
7498
);
7599

76-
function isRepoExist(repoName) {
77-
return fs.existsSync(`${output}/${repoName}/.git`);
78-
}
79-
80-
function cloneCompleted(repoName, showMessage = true) {
81-
if (showMessage) {
82-
console.log(chalk.green(`\n clone completed ${repoName} \n`));
83-
}
84-
85-
observer.cloned += 1;
86-
bar.increment(1);
87-
}
88-
100+
/**
101+
* generate our uitls.
102+
*/
103+
const utils = new Utils({
104+
token,
105+
url: baseUrl,
106+
output,
107+
bar,
108+
observer,
109+
});
110+
111+
/**
112+
* main function:
113+
* we make a request to gitlab for fetching available repos in this particular request.
114+
* at the beginning we don't have next url so we fetch from defaultAddress, in next recursive call it should be next url
115+
* this mechanism guarantee that we can take all repos if they are paginated.
116+
*/
89117
function main() {
90118
axios
91119
.get(next || defaultAddress)
92120
.then((res) => {
93121
const repos = res.data;
94122
const { length } = repos;
95123
total += length;
124+
125+
/**
126+
* based on gitlab pagination document, the next url to be called come here, so we grab it and save it
127+
* to handle our pagination process.
128+
*/
96129
const { link } = res.headers;
97130

98131
if (firstStart) {
132+
/**
133+
* so at the beginning of cloning we start our cli progress.
134+
*/
99135
bar.start(total, 0);
136+
100137
firstStart = false;
101138
} else {
139+
/**
140+
* we update our total repos because of in new paginated data we should update total repos.
141+
*/
102142
bar.setTotal(total);
103143
}
104144

@@ -108,35 +148,54 @@ function main() {
108148
name_with_namespace: nameWithNameSpace,
109149
http_url_to_repo: httpUrlToRepo,
110150
} = repo;
111-
const repoName = nameWithNameSpace.replace(/\//g, "-");
112-
const repoNameColor = chalk.cyan(repoName);
113-
const repoUrl = httpUrlToRepo.replace(
114-
"https://",
115-
`https://gitlab-ci-token:${token}@`
116-
);
117-
118-
if (isRepoExist(repoName)) {
119-
cloneCompleted(repoNameColor, false);
151+
152+
const repoName = Utils.generateRepoName(nameWithNameSpace);
153+
const repoNameColor = Utils.generateRepoNameColorized(repoName);
154+
const repoUrl = utils.generateRepoUrl(httpUrlToRepo);
155+
156+
/**
157+
* if we had already cloned this repo, we do action after cloning a repo without showing any message to update our process.
158+
*/
159+
if (utils.isRepoExist(repoName)) {
160+
utils.cloneCompleted(repoNameColor, false);
120161
} else {
162+
/**
163+
* we start cloning the repo
164+
*/
121165
console.log(chalk.yellow(`\n cloning ${repoNameColor} ... \n`));
122166

123167
clone(`${repoUrl}`, `${output}/${repoName}`, undefined, () => {
124-
if (isRepoExist(repoName)) {
125-
cloneCompleted(repoNameColor);
168+
/**
169+
* here again, check if clone completed, do action after completing clone.
170+
*/
171+
if (utils.isRepoExist(repoName)) {
172+
utils.cloneCompleted(repoNameColor);
126173
}
127174
});
128175
}
129176
}
130177

178+
/**
179+
* handle pagination.
180+
* based on gitlab document, if our array length is 0 and link also is no absence then we have all of our repos data.
181+
*/
131182
if (length !== 0 && link) {
132183
next = link.replace("<http", "http").replace(`>; rel="next"`, "");
184+
185+
/**
186+
* after saving next url that should be called, we call again our main function.
187+
*/
133188
main();
134189
} else {
190+
/**
191+
* if we don't have any other repos, so we stop our pagination process.
192+
*/
135193
observer.hasNext = false;
136194
}
137195
})
138196
.catch((err) => {
139-
console.log("err:", err.message);
197+
console.log(`\n ${chalk.red(`trace: ${err.stack}`)} \n`);
198+
process.exit(0);
140199
});
141200
}
142201

lib/utils.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
const fs = require("fs-extra");
2+
const chalk = require("chalk");
3+
4+
class Utils {
5+
/**
6+
* generate repo directory based on name space and remove all `/` in the name because don't create directory per `/`.
7+
* @param {string} name - name of repo with name space.
8+
* TODO: this should be configurable by passing an option.
9+
*/
10+
static generateRepoName(name) {
11+
return name.replace(/\//g, "-");
12+
}
13+
14+
/**
15+
* make repoName colorable. :)
16+
*
17+
* @param {string} name - name of repo after generated by generateRepoName
18+
*/
19+
static generateRepoNameColorized(name) {
20+
return chalk.cyan(name);
21+
}
22+
23+
constructor(scriptState) {
24+
const { token, url, output, bar, observer } = scriptState;
25+
this.token = token;
26+
this.url = url;
27+
this.output = output;
28+
this.bar = bar;
29+
this.observer = observer;
30+
}
31+
32+
/**
33+
* a utility function for detect given repo is cloned or not. return true or false.
34+
* @param {string} repoName - the name of repo for checking directory.
35+
*/
36+
isRepoExist(repoName) {
37+
return fs.existsSync(`${this.output}/${repoName}/.git`);
38+
}
39+
40+
/**
41+
* a utility function should called after a repo has been successfully cloned.
42+
* @param {string} repoName - the name of repo to be displayed.
43+
* @param {boolean} showMessage - show completed message or not.
44+
*/
45+
cloneCompleted(repoName, showMessage = true) {
46+
if (showMessage) {
47+
console.log(chalk.green(`\n clone completed ${repoName} \n`));
48+
}
49+
50+
/**
51+
* we increase cloned repo to keep tracking how much cloned.
52+
*/
53+
// this.observer.cloned += 1;
54+
55+
/**
56+
* increase cli progress bar by 1 when a repo cloned.
57+
*/
58+
this.bar.increment(1);
59+
}
60+
61+
/**
62+
* generate url of a repo based on Personal Access Token.
63+
* by this method there is no need to satisfy cloning in `https` or `ssh` mode.
64+
* @param {string} httpUrlToRepo http url of repo
65+
*/
66+
generateRepoUrl(httpUrlToRepo) {
67+
return httpUrlToRepo.replace(
68+
"https://",
69+
`https://gitlab-ci-token:${this.token}@`
70+
);
71+
}
72+
}
73+
74+
module.exports = Utils;

package-lock.json

Lines changed: 1 addition & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
11
{
22
"name": "mmd-gitlab-backuper",
33
"description": "a package to backup from all projects that you have on gitlab",
4-
"version": "1.0.1",
5-
"main": "index.js",
4+
"version": "1.2.0",
5+
"main": "lib/index.js",
66
"license": "MIT",
77
"author": {
88
"name": "Mohammad Toosi",
99
"email": "[email protected]",
1010
"url": "http://mammad2c.github.io/"
1111
},
1212
"bin": {
13-
"mmd-gitlab-backuper": "index.js"
13+
"mmd-gitlab-backuper": "lib/index.js"
1414
},
1515
"scripts": {
16-
"start": "node index.js"
16+
"start": "node lib/index.js"
1717
},
1818
"dependencies": {
19-
"add": "^2.0.6",
2019
"axios": "^0.20.0",
2120
"chalk": "^4.1.0",
2221
"cli-progress": "^3.8.2",

0 commit comments

Comments
 (0)