gitpick 4.20.0-canary.5 ā 4.20.0-canary.7
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/README.md +22 -20
- package/dist/index.mjs +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
|
|
16
16
|
Unlike other tools that force you to tweak URLs or follow strict formats to clone files, folders, branches or commits GitPick works seamlessly with any URL.
|
|
17
17
|
|
|
18
|
+
> š„ **New in v5.0** ā [Interactive mode](#-interactive-mode): browse any repo's file tree in your terminal and cherry-pick exactly what you need with `gitpick owner/repo -i`
|
|
19
|
+
|
|
18
20
|
<img width="400" alt="GitPick Meme" src="https://github.com/user-attachments/assets/180c3e5b-320c-48d7-aaf9-a7402e74c882" />
|
|
19
21
|
|
|
20
22
|
---
|
|
@@ -24,8 +26,8 @@ Unlike other tools that force you to tweak URLs or follow strict formats to clon
|
|
|
24
26
|
- [Some Examples](#-some-examples)
|
|
25
27
|
- [Features](#-features)
|
|
26
28
|
- [Quick Usage](#-quick-usage)
|
|
27
|
-
- [Interactive Mode](#-interactive-mode)
|
|
28
29
|
- [Options](#-options)
|
|
30
|
+
- [Interactive Mode](#-interactive-mode)
|
|
29
31
|
- [Private Repos](#-private-repos)
|
|
30
32
|
- [Config File](#-config-file)
|
|
31
33
|
- [Install Globally](#-install-globally-optional)
|
|
@@ -126,6 +128,25 @@ npx gitpick https://bitbucket.org/owner/repo # Bitbucket
|
|
|
126
128
|
|
|
127
129
|
---
|
|
128
130
|
|
|
131
|
+
## š§ Options
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
-b, --branch Branch/SHA to clone
|
|
135
|
+
-i, --interactive Browse and pick files/folders interactively
|
|
136
|
+
-n, --dry-run Show what would be cloned without cloning
|
|
137
|
+
-o, --overwrite Skip overwrite prompt
|
|
138
|
+
-r, --recursive Clone submodules
|
|
139
|
+
-w, --watch [time] Watch the repository and sync every [time]
|
|
140
|
+
(e.g. 1h, 30m, 15s)
|
|
141
|
+
--tree List copied files as a tree
|
|
142
|
+
-q, --quiet Suppress all output except errors
|
|
143
|
+
--verbose Show detailed clone information
|
|
144
|
+
-h, --help display help for command
|
|
145
|
+
-v, --version display the version number
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
129
150
|
## š„ Interactive Mode
|
|
130
151
|
|
|
131
152
|
> **New in v5.0** ā Browse any repository's file tree in your terminal and cherry-pick exactly the files and folders you want.
|
|
@@ -177,25 +198,6 @@ Works with GitHub, GitLab, Bitbucket, public and private repos.
|
|
|
177
198
|
|
|
178
199
|
---
|
|
179
200
|
|
|
180
|
-
## š§ Options
|
|
181
|
-
|
|
182
|
-
```
|
|
183
|
-
-b, --branch Branch/SHA to clone
|
|
184
|
-
-i, --interactive Browse and pick files/folders interactively
|
|
185
|
-
-n, --dry-run Show what would be cloned without cloning
|
|
186
|
-
-o, --overwrite Skip overwrite prompt
|
|
187
|
-
-r, --recursive Clone submodules
|
|
188
|
-
-w, --watch [time] Watch the repository and sync every [time]
|
|
189
|
-
(e.g. 1h, 30m, 15s)
|
|
190
|
-
--tree List copied files as a tree
|
|
191
|
-
-q, --quiet Suppress all output except errors
|
|
192
|
-
--verbose Show detailed clone information
|
|
193
|
-
-h, --help display help for command
|
|
194
|
-
-v, --version display the version number
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
---
|
|
198
|
-
|
|
199
201
|
## š Private Repos
|
|
200
202
|
|
|
201
203
|
Use a personal access token with read-only contents permission. Works with GitHub, GitLab and Bitbucket:
|
package/dist/index.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import e from"node:fs";import t from"node:os";import n from"node:path";import{pa
|
|
|
10
10
|
`;let C=E(`āā:navigate enter:expand space:select c:confirm q:quit`);m+=` ${C}\n`,s.write(m)}function m(e){let t=ie(r),i=t.length+1,o=e.toString();if(o===``||o===`q`||o===`Q`){u(),process.removeListener(`exit`,d),process.removeListener(`SIGINT`,f),n([]);return}if(o===`c`||o===`C`){u(),process.removeListener(`exit`,d),process.removeListener(`SIGINT`,f),n(ae(r));return}if((o===`\x1B[A`||o===`k`)&&a>0&&a--,(o===`\x1B[B`||o===`j`)&&a<i-1&&a++,a===0&&(o===` `||o===`\r`)){let e=r.every(e=>e.selected);for(let t of r)z(t,!e)}if(o===` `&&a>0){let e=t[a-1];e&&z(e.node,!e.node.selected)}if(o===`\r`&&a>0){let e=t[a-1];e&&e.node.type===`tree`&&(e.node.expanded=!e.node.expanded)}if((o===`\x1B[C`||o===`l`)&&a>0){let e=t[a-1];e&&e.node.type===`tree`&&(e.node.expanded=!0)}if((o===`\x1B[D`||o===`h`)&&a>0){let e=t[a-1];e&&e.node.type===`tree`&&(e.node.expanded=!1)}p()}c.on(`data`,m),p()})}function B(e){if(typeof e==`number`||/^\d+$/.test(e))return typeof e==`number`?e:parseInt(e,10);let t=/(\d+)([hms])/g,n=0,r;for(;(r=t.exec(e))!==null;){let e=parseInt(r[1],10);switch(r[2]){case`h`:n+=e*36e5;break;case`m`:n+=e*6e4;break;case`s`:n+=e*1e3;break}}return n}const V=async e=>{let t=(await S(`git`,[`ls-remote`,e])).stdout,n=t.match(/(.+)\s+HEAD/)?.[1],r=t.match(RegExp(`${n}\\s+refs/heads/(.+)`))?.[1];if(!r)throw Error(`Could not determine default branch!`);return r},H=[{prefix:`git@github.com:`,host:`github.com`},{prefix:`https://github.com/`,host:`github.com`},{prefix:`https://raw.githubusercontent.com/`,host:`github.com`},{prefix:`git@gitlab.com:`,host:`gitlab.com`},{prefix:`https://gitlab.com/`,host:`gitlab.com`},{prefix:`git@bitbucket.org:`,host:`bitbucket.org`},{prefix:`https://bitbucket.org/`,host:`bitbucket.org`}];async function le(e,{branch:t,target:n}){let r=e.match(/^https:\/\/([^@]+)@(github\.com|gitlab\.com|bitbucket\.org)/),i=``;if(r)i=r[1],e=e.replace(`${r[1]}@`,``);else{let t={"github.com":process.env.GITHUB_TOKEN||process.env.GH_TOKEN||``,"gitlab.com":process.env.GITLAB_TOKEN||``,"bitbucket.org":process.env.BITBUCKET_TOKEN||``};for(let{prefix:n,host:r}of H)if(e.startsWith(n)){i=t[r];break}!i&&!e.startsWith(`https://`)&&!e.startsWith(`git@`)&&(i=t[`github.com`])}let a=`github.com`;for(let{prefix:t,host:n}of H)if(e.startsWith(t)){a=n,e=e.replace(t,``);break}let o=e.split(`/`),s=o[0],c=o[1]?.endsWith(`.git`)?o[1].slice(0,-4):o[1],l=`https://${i&&i+`@`}${a}/${s}/${c}`,u,d,f;a===`github.com`?o[2]===`refs`&&[`heads`,`tags`].includes(o[3])?(u=`raw`,d=t||o[4],f=o.slice(5).join(`/`)):o[2]===`refs`&&o[3]===`remotes`?(u=`raw`,d=t||`${o[4]}/${o[5]}`,f=o.slice(6).join(`/`)):o[2]===`blob`?(u=`blob`,d=t||o[3],f=o.slice(4).join(`/`)):o[2]===`tree`?(u=`tree`,d=t||o[3],f=o.slice(4).join(`/`)):o[2]===`commit`?(u=`repository`,d=t||o[3],f=``):(u=`repository`,d=t||await V(l),f=``):a===`gitlab.com`?o[2]===`-`&&o[3]===`blob`?(u=`blob`,d=t||o[4],f=o.slice(5).join(`/`)):o[2]===`-`&&o[3]===`tree`?(u=`tree`,d=t||o[4],f=o.slice(5).join(`/`)):(u=`repository`,d=t||await V(l),f=``):o[2]===`src`?(u=`tree`,d=t||o[3],f=o.slice(4).join(`/`)):(u=`repository`,d=t||await V(l),f=``);let p=n||(u===`blob`?`.`:f.split(`/`).pop()||c);return{token:i,host:a,owner:s,repository:c,type:u,branch:d,path:f,target:p}}const U=n.join(t.homedir(),`.cache`,`gitpick`),W=n.join(U,`update-check.json`);function G(){try{return JSON.parse(e.readFileSync(W,`utf-8`))}catch{return null}}function ue(t){try{e.mkdirSync(U,{recursive:!0}),e.writeFileSync(W,JSON.stringify(t))}catch{}}function de(){return new Promise(e=>{let t=d.get(`https://registry.npmjs.org/gitpick/latest`,{headers:{Accept:`application/json`},timeout:3e3},t=>{if(t.statusCode!==200)return t.resume(),e(null);let n=``;t.on(`data`,e=>n+=e),t.on(`end`,()=>{try{e(JSON.parse(n).version||null)}catch{e(null)}})});t.on(`error`,()=>e(null)),t.on(`timeout`,()=>{t.destroy(),e(null)})})}function fe(e,t){let n=e.split(`.`).map(Number),r=t.split(`.`).map(Number);for(let e=0;e<3;e++){if((n[e]||0)>(r[e]||0))return!0;if((n[e]||0)<(r[e]||0))return!1}return!1}function K(e,t){if(t)return;let n=G();n&&fe(n.latestVersion,e)&&console.log(E(`\n Update available: ${k(e)} ā ${A(T(n.latestVersion))}\n Run ${A(`npm i -g gitpick`)} to update\n`))}function pe(){let e=G();e&&Date.now()-e.lastCheck<864e5||setTimeout(async()=>{let e=await de();e&&ue({lastCheck:Date.now(),latestVersion:e})},0)}const q=Symbol(`singleComment`),J=Symbol(`multiComment`),Y=(e,t,n)=>e.slice(t,n).replace(/[^ \t\r\n]/g,` `),me=(e,t)=>{let n=t-1,r=0;for(;e[n]===`\\`;)--n,r+=1;return!!(r%2)};function he(e){if(typeof e!=`string`)throw TypeError(`Expected argument \`jsonString\` to be a \`string\`, got \`${typeof e}\``);let t=!1,n=!1,r=0,i=``,a=``,o=-1;for(let s=0;s<e.length;s++){let c=e[s],l=e[s+1];if(!n&&c===`"`&&(me(e,s)||(t=!t)),!t)if(!n&&c+l===`//`)i+=e.slice(r,s),r=s,n=q,s++;else if(n===q&&c+l===`\r
|
|
11
11
|
`){s++,n=!1,i+=Y(e,r,s),r=s;continue}else if(n===q&&c===`
|
|
12
12
|
`)n=!1,i+=Y(e,r,s),r=s;else if(!n&&c+l===`/*`){i+=e.slice(r,s),r=s,n=J,s++;continue}else if(n===J&&c+l===`*/`){s++,n=!1,i+=Y(e,r,s+1),r=s+1;continue}else n||(o===-1?c===`,`&&(a+=i+e.slice(r,s),i=``,r=s,o=s):c===`}`||c===`]`?(i+=e.slice(r,s),a+=Y(i,0,1)+i.slice(1),i=``,r=s,o=-1):c!==` `&&c!==` `&&c!==`\r`&&c!==`
|
|
13
|
-
`&&(i+=e.slice(r,s),r=s,o=-1))}let s=n===q?Y(e,r):e.slice(r);return a+i+s}const ge=[`.gitpick.json`,`.gitpick.jsonc`],_e=async()=>{let t;for(let r of ge){let i=n.resolve(r);if(e.existsSync(i)){t=i;break}}if(!t)return!1;let r=await e.promises.readFile(t,`utf-8`),i=JSON.parse(he(r));if(!Array.isArray(i)||!i.every(e=>typeof e==`string`))throw Error(`${n.basename(t)} must be an array of strings`);for(let e of i)await S(process.argv[0],[...process.argv.slice(1),...e.split(/\s+/),`-o`],{stdio:`inherit`});return!0};var ve=`gitpick`,X=`4.20.0-canary.
|
|
13
|
+
`&&(i+=e.slice(r,s),r=s,o=-1))}let s=n===q?Y(e,r):e.slice(r);return a+i+s}const ge=[`.gitpick.json`,`.gitpick.jsonc`],_e=async()=>{let t;for(let r of ge){let i=n.resolve(r);if(e.existsSync(i)){t=i;break}}if(!t)return!1;let r=await e.promises.readFile(t,`utf-8`),i=JSON.parse(he(r));if(!Array.isArray(i)||!i.every(e=>typeof e==`string`))throw Error(`${n.basename(t)} must be an array of strings`);for(let e of i)await S(process.argv[0],[...process.argv.slice(1),...e.split(/\s+/),`-o`],{stdio:`inherit`});return!0};var ve=`gitpick`,X=`4.20.0-canary.7`;const Z=(e,t)=>`\x1b]8;;${t}\x07${e}\x1b]8;;\x07`,ye=`
|
|
14
14
|
With ${T(`${Z(`GitPick`,`https://github.com/nrjdalal/gitpick`)}`)} clone specific directories or files from GitHub, GitLab and Bitbucket!
|
|
15
15
|
|
|
16
16
|
$ gitpick ${k(`<url>`)} ${O(`[target]`)} ${A(`[options]`)}
|
|
@@ -48,8 +48,8 @@ ${T(`Examples:`)}
|
|
|
48
48
|
$ gitpick https://bitbucket.org/owner/repo
|
|
49
49
|
|
|
50
50
|
š More awesome tools at ${A(`https://github.com/nrjdalal`)}`,Q=e=>{let r=process.cwd(),i=t.homedir(),a=n.sep;return e===r?`.`:e.startsWith(r+a)?`./`+n.relative(r,e).replaceAll(a,`/`):e.startsWith(i+a)?`~/`+n.relative(i,e).replaceAll(a,`/`):e},$=async(t,r=``)=>{let i=(await e.promises.readdir(t,{withFileTypes:!0})).filter(e=>e.name!==`.git`).sort((e,t)=>e.name.localeCompare(t.name,void 0,{sensitivity:`base`}));for(let a=0;a<i.length;a++){let o=i[a],s=a===i.length-1,c=s?`āāā `:`āāā `,l=n.join(t,o.name);if(o.isSymbolicLink()){let t=await e.promises.readlink(l),n=!1;try{n=e.statSync(l).isDirectory()}catch{}process.stdout.write(`${r}${c}${k(o.name)} -> ${n?A(t):t}\n`)}else o.isDirectory()?(process.stdout.write(`${r}${c}${A(o.name)}\n`),await $(l,`${r}${s?` `:`ā `}`)):process.stdout.write(`${r}${c}${o.name}\n`)}},be=e=>{try{return r(e)}catch(e){throw Error(`Error parsing arguments: ${e.message}`)}};(async()=>{pe();try{let{positionals:r,values:i}=be({allowPositionals:!0,options:{branch:{type:`string`,short:`b`},"dry-run":{type:`boolean`,short:`n`},force:{type:`boolean`,short:`f`},help:{type:`boolean`,short:`h`},interactive:{type:`boolean`,short:`i`},quiet:{type:`boolean`,short:`q`},tree:{type:`boolean`},verbose:{type:`boolean`},overwrite:{type:`boolean`,short:`o`},recursive:{type:`boolean`,short:`r`},version:{type:`boolean`,short:`v`},watch:{type:`string`,short:`w`}}});r.length||(i.version&&(console.log(`\n${ve}@${X}`),process.exit(0)),await _e()&&process.exit(0),console.log(ye),process.exit(0)),r[0]===`clone`&&r.shift();let[a,o]=r,s={branch:i.branch,dryRun:i[`dry-run`],force:i.force,interactive:i.interactive,quiet:i.quiet,tree:i.tree,verbose:i.verbose,overwrite:i.overwrite,recursive:i.recursive,watch:i.watch},c=s.tree||s.quiet;c||console.log(`\nWith ${T(`${Z(`GitPick`,`https://github.com/nrjdalal/gitpick`)}`)} clone specific files, folders, branches,\ncommits and much more from GitHub, GitLab and Bitbucket!`);let l=await le(a,{branch:s.branch,target:o});if(l.type===`blob`){let e=l.target.split(/[/\\]/).filter(e=>e!==``),t=e[e.length-1];t!==`.`&&t!==`..`&&t.includes(`.`)?e.pop():t=l.path.split(`/`).pop()||t,l.target=[...e,t].join(`/`)}c||console.info(`\n${O(`ā`)} ${l.owner}/${l.repository} ${A(l.type+`:`+l.branch)} ${l.type===`repository`?`> ${O(l.target)}`:`${l.path.length?k(l.path)+` >`:`>`} ${O(l.target)}`}`);let u=n.resolve(l.target);if(s.interactive){if(!process.stdout.isTTY)throw Error(`Interactive mode requires a TTY`);let r=n.resolve(t.tmpdir(),`gitpick-interactive-${Date.now()}${Math.random().toString(16).slice(2,6)}`),i=`https://${l.token?l.token+`@`:``}${l.host}/${l.owner}/${l.repository}.git`,a=N();a.start(`Fetching ${l.owner}/${l.repository}...`);try{await S(`git`,[`clone`,i,r,`--branch`,l.branch,`--depth`,`1`,`--single-branch`,...s.recursive?[`--recursive`]:[]])}catch{await S(`git`,[`clone`,i,r,...s.recursive?[`--recursive`]:[]]),await S(`git`,[`checkout`,l.branch],{cwd:r})}let o=[];async function c(t,r){let i=await e.promises.readdir(t,{withFileTypes:!0});for(let a of i){if(a.name===`.git`)continue;let i=r?`${r}/${a.name}`:a.name,s=n.join(t,a.name);if(a.isDirectory())o.push({path:i,type:`tree`}),await c(s,i);else{let t=await e.promises.stat(s);o.push({path:i,type:`blob`,size:t.size})}}}await c(r,``),a.success(`Fetched ${l.owner}/${l.repository} (${o.length} entries)`),o.length||(await e.promises.rm(r,{recursive:!0,force:!0}),console.log(k(`
|
|
51
|
-
Repository has no files.`)),process.exit(0));let d=await ce(o,`${l.owner}/${l.repository} ${A(`repository:`+l.branch)} > ${O(l.target)}`);d.length||(await e.promises.rm(r,{recursive:!0,force:!0}),console.log(`
|
|
52
|
-
No files selected.`),process.exit(0)),console.log(`\n${O(`ā`)} Picking ${d.length} selected path${d.length===1?``:`s`}...`),e.existsSync(u)&&!s.overwrite&&(await e.promises.readdir(u)).length&&(await e.promises.rm(r,{recursive:!0,force:!0}),console.log(`${k(`\nWarning: The target directory exists at ${O(l.target)} and is not empty. Use ${A(`-f`)} or ${A(`-o`)} to overwrite.`)}`),process.exit(1)),await e.promises.mkdir(u,{recursive:!0});let f=0;for(let t of d){let i=n.join(r,t),a=n.join(u,t),o=await e.promises.stat(i).catch(()=>null);if(o)if(o.isDirectory()){await e.promises.mkdir(a,{recursive:!0});let t=await P(i,a);f+=t.length}else await e.promises.mkdir(n.dirname(a),{recursive:!0}),await e.promises.copyFile(i,a),f++}await e.promises.rm(r,{recursive:!0,force:!0}),console.log(O(`ā Copied ${f} file${f===1?``:`s`} to ${Q(u)}`)),s.tree&&(process.stdout.write(`\n${T(A(Q(u)))}\n`),await $(u),process.stdout.write(`
|
|
51
|
+
Repository has no files.`)),process.exit(0));let d=await ce(o,`${l.owner}/${l.repository} ${A(`repository:`+l.branch)} > ${O(l.target)}`);if(d.length||(await e.promises.rm(r,{recursive:!0,force:!0}),console.log(`
|
|
52
|
+
No files selected.`),process.exit(0)),s.dryRun){console.log(`\n${O(`ā`)} Would pick ${d.length} path${d.length===1?``:`s`}:`);for(let e of d)console.log(` ${e}`);await e.promises.rm(r,{recursive:!0,force:!0}),console.log(),K(X,!1),process.exit(0)}console.log(`\n${O(`ā`)} Picking ${d.length} selected path${d.length===1?``:`s`}...`),e.existsSync(u)&&!s.overwrite&&(await e.promises.readdir(u)).length&&(await e.promises.rm(r,{recursive:!0,force:!0}),console.log(`${k(`\nWarning: The target directory exists at ${O(l.target)} and is not empty. Use ${A(`-f`)} or ${A(`-o`)} to overwrite.`)}`),process.exit(1)),await e.promises.mkdir(u,{recursive:!0});let f=0;for(let t of d){let i=n.join(r,t),a=n.join(u,t),o=await e.promises.stat(i).catch(()=>null);if(o)if(o.isDirectory()){await e.promises.mkdir(a,{recursive:!0});let t=await P(i,a);f+=t.length}else await e.promises.mkdir(n.dirname(a),{recursive:!0}),await e.promises.copyFile(i,a),f++}await e.promises.rm(r,{recursive:!0,force:!0}),console.log(O(`ā Copied ${f} file${f===1?``:`s`} to ${Q(u)}`)),s.tree&&(process.stdout.write(`\n${T(A(Q(u)))}\n`),await $(u),process.stdout.write(`
|
|
53
53
|
`)),K(X,!1),process.exit(0)}let d=async t=>{e.statSync(t).isDirectory()?(process.stdout.write(`${T(A(Q(u)))}\n`),await $(t)):(process.stdout.write(`${T(A(Q(n.dirname(u))))}\n`),process.stdout.write(`āāā ${n.basename(u)}\n`)),process.stdout.write(`
|
|
54
54
|
`)};if(s.dryRun){if(s.tree){let r=n.resolve(t.tmpdir(),`gitpick-dry-${Date.now()}${Math.random().toString(16).slice(2,6)}`);try{await L(l,s,r),await d(r)}finally{await e.promises.rm(r,{recursive:!0,force:!0})}}c||console.log(),K(X,c),process.exit(0)}if(s.overwrite=s.overwrite||s.force,s.watch&&(s.overwrite=!0),e.existsSync(u)&&!s.overwrite&&(l.type===`blob`&&(console.log(`${k(`\nWarning: The target file exists at ${O(l.target)}. Use ${A(`-f`)} or ${A(`-o`)} to overwrite.`)}`),process.exit(1)),(await e.promises.readdir(u)).length&&(console.log(`${k(`\nWarning: The target directory exists at ${O(l.target)} and is not empty. Use ${A(`-f`)} or ${A(`-o`)} to overwrite.`)}`),process.exit(1))),s.watch){c||console.log(`\nš Watching every ${B(s.watch)/1e3+`s`}\n`),await L(l,s,u),s.tree&&await d(u);let e=B(s.watch);setInterval(async()=>{await L(l,s,u),s.tree&&await d(u)},e)}else await L(l,s,u),s.tree&&await d(u),K(X,c),process.exit(0)}catch(e){e instanceof Error?console.log(T(`\n${D(`Error: `)}`)+e.message):console.log(T(`${D(`
|
|
55
55
|
Unexpected Error: `)}`)+JSON.stringify(e,null,2)),process.exit(1)}})();export{};
|