-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from Debajyati/dev
Major Release: Merging dev into main
- Loading branch information
Showing
27 changed files
with
2,902 additions
and
775 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,60 +1,43 @@ | ||
# gitFM | ||
|
||
gitFM is a command-line tool based on Node.js that searches the GitHub repository, retrieves repository file structures, and creates repository files. | ||
gitFM is a command-line tool that can search GitHub/GitLab repositories, retrieve repository file contents and clone them normally or partially depending on your choice directly from the terminal(never leave the terminal). Your best friend for fetching repository as filesystem and easy cloning. | ||
|
||
## Installation | ||
|
||
First, make sure you have installed Node.js. Then install gitFM globally by running the following command: | ||
First, make sure you have Node.js and git pre-installed in your system. Then install gitFM globally by running the following command: | ||
|
||
```bash | ||
npm install -g gitfm | ||
gitfm | ||
``` | ||
|
||
or, run it without locally installing it - | ||
## Usage | ||
|
||
```bash | ||
npx gitfm | ||
``` | ||
|
||
## How to use | ||
|
||
gitFM has a simple command line interface that guides you through the process of searching, selecting, and operating the repository. | ||
|
||
### Search Repositories | ||
|
||
When you run gitFM, it will prompt you to enter a search keyword. For example, if you want to search for a repository with the name "express", you can enter: | ||
|
||
```bash | ||
express | ||
``` | ||
|
||
in the search input. | ||
|
||
### Select Repository | ||
### Helptexts | ||
|
||
gitFM returns a list of search results. You can select one from the Repository list. | ||
Run `gitfm --help` or `gitfm -h` or `gitfm help` to know get the helptext. It will look like this - | ||
![screenshot of the helptext](./assets/img/helptext.png) | ||
|
||
**NOTE:** There's no reverse motion support for now. Means once you enter in the list of repos after the search input you can't go back to the search input. | ||
To get more info about any individual command of gitfm, run - `gitfm help <command-name>`. For example, - if you want to know about the `clone` command in detail, you would run - `gitfm help clone`. | ||
|
||
### View Repositories contents | ||
![screenshot of the clone command helptext](./assets/img/clone-helptext.png) | ||
|
||
After selecting a Repo, gitFM will display detailed information about the Repo, including the Repositories name, description, and GitHub link. You can continue viewing the contents of . | ||
> [!NOTE] | ||
> In the helptexts, - an argument enclosed within `[]` signify that the arg is optional and within `<>` signify that the arg is mandatory. | ||
### Clone Repositories | ||
### Descriptions of the Individual Commands | ||
|
||
If you select a Repository, gitFM will prompt you to agree cloning the Repository locally. For example, if you want to create a repository in the "my-repo" directory, you can specify: | ||
|
||
```bash | ||
my-repo | ||
``` | ||
in the next input prompt where it will ask you if want to clone into a specific directory. | ||
- **`gitfm clone [options]`** :- This command has four options, - `--blobless`, `--treeless`, `--shallow` and `--sparse`. When none of them are provided, it proceeds to perform a normal(cloning the whole repository as usual) git clone of the provided repository URL. This command doesn't require any authentication. This is going to be your most helpful/used command of this application (most likely). `--shallow` option should be used when you want to clone only the latest commit of the repository. `--blobless` and `--treeless` options should be used when you want to clone a repository in a [blobless or treeless format](https://github.blog/open-source/git/get-up-to-speed-with-partial-clone-and-shallow-clone/#). The `--sparse` option should be used when you want to clone only the specified files or directories of the repository. The beauty of this command is that you can run it without installing the CLI (`npx gitfm clone <repourl> [options]`). For more info about the options, run `gitfm clone --help` or `gitfm help clone`. | ||
- **`gitfm ghauth [options]`** :- This command has three options, - `--login`, `--logout` and `--refresh`. When none of them are provided, it will first check if you are already authenticated with GitHub. If you are, it will say so and exit. If you are not, it will ask you to login with your GitHub account(basically, the same as `gitfm ghauth --login`). If you want to logout, run `gitfm ghauth --logout`. If you want to refresh your token, run `gitfm ghauth --refresh`. In case of browser login, a browser window/tab will be automatically opened with the verification URL if and only if your default browser is anything between **chrome**, **edge** or **firefox**. If your default browser is something else, the verification URL will be shown in the console so that you can manually open it in the browser to log in. After a successful login, the token is stored in the `$HOME` directory of your system in a config file named `.gitfmrc.json`. Your token is revoked by gitfm while logging out only if it is an **oauth token** (you authenticated using Browser with passcodes). If it is a **PAT**, gitfm will just reset the config file. **TOKEN WON'T BE DELETED**. To delete your token, you should directly do that from your GitHub developer settings. For more info about the options, run `gitfm ghauth --help` or `gitfm help ghauth`. | ||
- **`gitfm ghprofile`** :- Shows a minimal view of your GitHub profile. This command takes no arguments. Requires you to be authenticated. If not, it will autostart interactive authentication mode. When authentication process will complete, it will show your profile info. | ||
- **`gitfm glauth [options]`** :- This command also contains 3 options - `--login`, `--logout`, `--rotate`. You can authenticate gitfm with your Gitlab account only using a personal access token. Browser login is not supported and won't be implemented in future. The token is stored in a JSON file(`.gl.gitfmrc.json`) inside the `$HOME` directory of your system. For more info, run - `gitfm glauth -h` or `gitfm help glauth`. | ||
- **`gitfm icl [options]`** :- This command has three options - `--gh`, `--gl` and `--unauthenticated`. When none of them are provided, it will do nothing.. If you want to interactively search and clone a GitHub repository, run `gitfm icl --gh`. If you want to interactively search and clone a GitLab repository, run `gitfm icl --gl`. If you want to run the legacy version of the GitHub repo interactive clone command(without partial cloning support but doesn't require authentication), run `gitfm icl --unauthenticated`. For more info about the options, run `gitfm icl --help` or `gitfm help icl`. | ||
|
||
## Summary: | ||
gitFM is a convenient command-line tool that can be used to search, view and clone GitHub Repositories. Through this tool, you can easily browse and explore the code repository on GitHub from you fav commandline. | ||
## Feedback | ||
|
||
## Feedback and contributions | ||
If you encounter any problems or have any suggestions when using gitFM, you are welcome to submit an issue in the [GitHub repository](https://github.com/Debajyati/gitFM). | ||
|
||
If you encounter any problems or have any suggestions when using gitFM, you are welcome to submit an issue or pull request in the GitHub repository. | ||
## Star 🌟 This repo | ||
Show some love to this project by starring it. It will help in increasing the visibility of this project and also in motivating me to work on this project more often. More features will be added to this project in the future. :) | ||
|
||
GitHub repository: https://github.com/Debajyati/gitFM | ||
## License | ||
Internet Standard Consortium License (ISC) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
const cloningOptions = [ | ||
{ | ||
name: "Normal Cloning", | ||
value: "normal", | ||
description: "Clones the entire repository, including all branches and history.", | ||
}, | ||
{ | ||
name: "Partial Cloning", | ||
value: "partial", | ||
description: "Clones only specific parts of the repository, such as a single branch or a subset of files.", | ||
} | ||
]; | ||
|
||
const partialCloningOptions = [ | ||
{ | ||
name: "Shallow Cloning", | ||
value: "shallow", | ||
description: "shallow clone only the latest commit", | ||
}, | ||
{ | ||
name: "Sparse Cloning", | ||
value: "sparse", | ||
description: "clone only a specific folder", | ||
}, | ||
{ | ||
name: "Blobless Cloning", | ||
value: "blobless", | ||
description: ` | ||
Clones the repository by fetching only the repository metadata and history. | ||
file contents (blobs) fetched on-demand when accessed.`, | ||
}, | ||
{ | ||
name: "Treeless Cloning", | ||
value: "treeless", | ||
description: | ||
"Clones the repository without checking out the working directory tree,\n including only the repository metadata and history.", | ||
}, | ||
]; | ||
|
||
export { cloningOptions, partialCloningOptions }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import { execFile } from 'child_process'; | ||
import { promisify } from 'util'; | ||
|
||
const execFileAsync = promisify(execFile); | ||
|
||
async function executeCommand(command, args = []) { | ||
try { | ||
const { stdout, stderr } = await execFileAsync(command, args); | ||
if (stderr) { | ||
const { default: chalk } = await import('chalk'); | ||
console.log(chalk.yellowBright(stderr)); | ||
} | ||
// console.log(`stdout: ${stdout}`); | ||
return stdout; | ||
} catch (error) { | ||
console.error(`Error: ${error.message}`); | ||
throw error; | ||
} | ||
} | ||
|
||
async function runShallowClone(repoUrl, dirName='', branch='') { | ||
try { | ||
if (!dirName) { | ||
dirName = repoUrl.split('/').pop().replace(/\.git$/, '') || 'default-repo'; | ||
} | ||
if (!branch) { | ||
await executeCommand('git', ['clone', '--depth', '1', repoUrl, dirName]); | ||
} else { | ||
await executeCommand('git', ['clone', '--depth', '1', '--single-branch', '-b', branch, repoUrl, dirName]); | ||
} | ||
console.log('Shallow cloning completed successfully!'); | ||
} catch (error) { | ||
console.error(`Error during shallow clone process: ${error.message}`); | ||
process.exit(1); | ||
} | ||
} | ||
|
||
async function runBloblessClone(repoUrl, dirName='', branch='') { | ||
try { | ||
if (!dirName) { | ||
dirName = repoUrl.split('/').pop().replace(/\.git$/, '') || 'default-repo'; | ||
} | ||
if (!branch) { | ||
await executeCommand('git', ['clone', '--filter=blob:none', repoUrl, dirName]); | ||
} else { | ||
await executeCommand('git', ['clone', '--filter=blob:none', '--single-branch', '-b', branch, repoUrl, dirName]); | ||
} | ||
console.log('Blobless cloning completed successfully!'); | ||
} catch (error) { | ||
console.error(`Error during blobless clone process: ${error.message}`); | ||
process.exit(1); | ||
} | ||
} | ||
|
||
async function runTreelessClone(repoUrl, dirName = '', branch = '') { | ||
try { | ||
if (!dirName) { | ||
dirName = repoUrl.split('/').pop().replace(/\.git$/, '') || 'default-repo'; | ||
} | ||
if (!branch) { | ||
await executeCommand('git', ['clone', '--no-checkout', '--filter=tree:0', repoUrl, dirName]); | ||
} else { | ||
await executeCommand('git', ['clone', '--no-checkout', '--filter=tree:0', '--single-branch', '-b', branch, repoUrl, dirName]); | ||
} | ||
console.log('Treeless cloning completed successfully!'); | ||
} catch (error) { | ||
console.error(`Error during treeless clone process: ${error.message}`); | ||
process.exit(1); | ||
} | ||
} | ||
|
||
async function runSparseCheckout(repoUrl, dirName = '', branch = '', pathToDirectory, noCone=true) { | ||
try { | ||
// Validate inputs | ||
if (!pathToDirectory) { | ||
throw new Error('Path to directory for sparse checkout cannot be empty.'); | ||
} | ||
|
||
if (!dirName) { | ||
dirName = repoUrl.split('/').pop().replace(/\.git$/, '') || 'default-repo'; | ||
} | ||
|
||
const cloneArgs = ['clone', '--no-checkout', '--filter=blob:none']; | ||
// if (branch) cloneArgs.push('-b', branch); | ||
cloneArgs.push(repoUrl, dirName); | ||
|
||
await executeCommand('git', cloneArgs); | ||
|
||
// Change to cloned directory | ||
process.chdir(dirName); | ||
|
||
// Initialize sparse-checkout | ||
const sparseAddDirArgs = ['sparse-checkout', 'add']; | ||
if (noCone) { | ||
await executeCommand('git', ['sparse-checkout', 'set', '--no-cone']); | ||
sparseAddDirArgs.push('!/*'); | ||
} else { | ||
await executeCommand('git', ['sparse-checkout', 'set', '--cone']); | ||
} | ||
|
||
if (pathToDirectory.constructor === Array) { | ||
await executeCommand('git', [...sparseAddDirArgs, ...pathToDirectory]); | ||
} else { | ||
await executeCommand('git', [...sparseAddDirArgs, pathToDirectory]); | ||
} | ||
|
||
// Determine default branch if not provided | ||
const branchList = (await executeCommand('git', ['ls-remote', '--sort=-committerdate', '--heads', 'origin'])) | ||
.split('\n') | ||
.map(line => line.split('\t').pop().replace('refs/heads/', '').trim()); | ||
const defaultLocalBranch = branch || branchList[0] || 'main'; | ||
|
||
// Checkout branch | ||
await executeCommand('git', ['checkout', defaultLocalBranch]); | ||
console.log('Cloning portion of the repo completed successfully!'); | ||
} catch (error) { | ||
console.error('Error during sparse checkout process:', error.message || error); | ||
process.exit(1); | ||
} | ||
} | ||
|
||
async function normalClone(repoUrl, dirName='', branch='') { | ||
try { | ||
const cloneArgs = ['clone', repoUrl]; | ||
if (branch) cloneArgs.push('--single-branch', '--branch', branch); | ||
if (dirName) cloneArgs.push(dirName); | ||
await executeCommand('git', cloneArgs); | ||
console.log('Cloning completed successfully!'); | ||
} catch (error) { | ||
console.error(`Error during cloning process: ${error.message}`); | ||
process.exit(1); | ||
} | ||
} | ||
|
||
export { | ||
executeCommand, | ||
runSparseCheckout, | ||
normalClone, | ||
runShallowClone, | ||
runBloblessClone, | ||
runTreelessClone, | ||
} |
Oops, something went wrong.