# Puter Git Client This is a JavaScript in-browser git client running on the Puter filesystem, using [isomorphic-git](https://isomorphic-git.org). It aims to match `git`'s interface, so that running a command behaves the same way as it would there. Of course, `git` has a large variety of subcommands and options, some of which are obscure, so many will be missing. Consider adding them if you are interested! :^) While this is built for Puter, the only Puter-specific parts are: - `src/filesystem.js` which integrates with Puter's filesystem. - `src/main.js` which uses the Puter SDK to connect to the parent Phoenix shell. By modifying these, and the values in `config/`, you should be able to port it to other environments. ## Subcommand structure The `git` CLI is structured as a series of sub-commands - `git branch` has nothing in common with `git version`, for example. Each of these is modelled as its own file in `src/subcommands`, and should export a default object with the following structure: ```js export default { // The subcommand's name name: 'help', // A string or array of strings, for how the subcommand is run. Shown in the help. usage: [ 'git help [-a|--all]', 'git help ', ], // A description, shown in the help. description: `Display help information for git itself, or a subcommand.`, // Arguments object, passed to `parseArgs()` args: { allowPositionals: true, options: { all: { // The one deviation from parseArgs() is that each option gets a description, // which is shown in the help output. description: 'List all available subcommands.', type: 'boolean', } }, }, // Function to actually run the subcommand. // The return value can be a posix-style exit code, or a plain `return` as a shorthand for 0. // Throwing errors is allowed. // Throwing the special `SHOW_USAGE` value defined in `src/help.js` can be used to print the command usage text. execute: async (ctx) => { // ctx has the following structure: ctx = { io: { stdout, // Function that takes a string, and prints it to stdout stderr, // Function that takes a string, and prints it to stderr }, fs, // A filesystem implementation, for isomorphic-git args, // An object returned from `parseArgs()`. env, // Object containing environment variables, such as PWD for the current working directory. } } } ``` These are them listed in `src/subcommands/__exports__.js`, which can be automatically generated by running `packages/phoenix/tools/gen.js packages/git/src/subcommands` from the Puter repo root. But it's not hard to modify manually. ## Common patterns ### Shared options It's common in a few places that options are shared between commands, for example options related to formatting commits are shared by `git log` and `git show`. In these cases, those options are defined in an object in `src/format.js` with an accompanying function to process their results into a more convenient format. For example, `diff_formatting_options` and `process_diff_formatting_options(options)`: Some of the options are shorthands for others; some "imply" another; and some are set by default under certain conditions. All this is handled in one place instead of in each subcommand that needs them. ### Repo root Since the `git` command may be run from a repository, a subdirectory, or a non-repository, you can locate the git directory like so: ```js const { dir, gitdir } = await find_repo_root(fs, env.PWD); ``` The `dir` and `gitdir` variables can then be passed to isomorphic-git methods that expect them. If no repository is found, this will throw an error, which is then printed to stderr. (isomorphic-git's `git.findRoot()` does not implement checks for a `.git` text file that points to an alternative directory that git's metadata is stored in. We should maybe upstream this.) ## Isomorphic-git The library we use for most interaction with git's files is [isomorphic-git](https://isomorphic-git.org). It handles most of the basics, but because we want to do everything that [git](https://git-scm.com) does, a lot has to be implemented manually. For example, it has a `git.add()` method for adding files, but whereas `git add deleted_file` will stage that deletion, `git.add({ filepath: 'deleted_file', ... })` will complain that the file does not exist. So our implementation of `git add` manually iterates the changed files and either calls `git.add()` or `git.remove()` as appropriate. ### Troubleshooting tips: - File paths given to isomorphic-git need to be relative to the repo root. Absolute paths often silently do nothing!