@@ -7,7 +7,11 @@ const chalk = require("chalk");
77const objChange = require ( "on-change" ) ;
88const cliProgress = require ( "cli-progress" ) ;
99const argv = require ( "yargs" ) ;
10+ const Utils = require ( "./utils" ) ;
1011
12+ /**
13+ * define script options.
14+ */
1115argv
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+ */
3946const token = argv . argv . token || "" ;
4047const baseUrl = argv . argv . url || "https://gitlab.com" ;
4148const output = argv . argv . output || "./repos" ;
42- const pagination = 100 ;
49+ const pagination = 100 ; // currently maximum gitlab pagination supports.
4350const defaultAddress = `/api/v4/projects?simple=true&membership=true&pagination=keyset&order_by=id&sort=asc&per_page=${ pagination } ` ;
4451const 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+ */
5161let next ;
62+
63+ /**
64+ * total repository for handling progress bar.
65+ */
5266let total = 0 ;
67+
68+ /**
69+ * to indicate when first cloning starts.
70+ */
5371let firstStart = true ;
5472
5573axios . 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+ */
6283const 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+ */
89117function 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
0 commit comments