puter/packages/git/README.md

94 lines
4.7 KiB
Markdown
Raw Normal View History

# Puter Git Client
2024-06-11 13:40:57 +00:00
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 <command>',
],
// 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!