isomorphic-git
Version:
Node library for interacting with git repositories, circa 2017
214 lines (166 loc) • 6.89 kB
Markdown
Node library for interacting with git repositories, circa 2017
(Originally I was going to call it `esgit` but the name is too similar to another
project called [`es-git`](https://github.com/es-git/es-git).)
Porcelain:
- [x] git init
- [x] git clone
- [x] Github API protocol (only signed commits)
- [ ] HTTP smart protocol + cors-buster
- [x] git checkout
- [ ] update index correctly when checking out
- [x] git list (ls-files)
- [x] git add
- [x] git remove
- [ ] git status
- [x] git commit
- [x] git push (due to CORS, use https://github-cors.now.sh instead of https://github.com)
- [ ] git pull
- [ ] git diff
- [ ] git merge
- [x] `esgit` CLI
Plumbing:
- [ ] read-tree
- [x] git listCommits (rev-list)
- [x] git pack (pack-objects)
- [ ] git list packed objects (verify-pack)
- [ ] git unpack-objects
Note: There appears to be no a way to *push* signed commits back to Github using their API (v3 or v4), so I think we will have to use smart HTTP, packfiles, and an anti-CORS proxy.
## High-level API (unstable)
This is analogous to the "porcelain" git commands. There is a single function `git()` that serves as a fluent command builder.
Examples:
```js
import git from 'esgit'
// Create a new empty repo
git('test').init()
// Clone from a Github repository to the current working directory.
// Just like it's counterpart, clone is really just shorthand for git.init(); git.fetch(); git.checkout();
git('.').githubToken(process.env.GITHUB_TOKEN).clone('https://github.com/wmhilton/esgit')
// Checkout a commitish
git('.').checkout('master')
// List files in the index
git('.').list()
// Add files to the index
git('.').add('README.md')
// Remove files from the index
git('.').remove('.env')
// Create a new commit (there's actually several more options for date, committer)
git('.')
.add('a.txt')
.author('Mr. Test')
.email('mrtest@example.com')
.signingKey('-----BEGIN PGP PRIVATE KEY BLOCK-----...')
.commit('Added the a.txt file')
// Manually add a remote (git clone should be doing this but it doesn't (yet))
git('.')
.setConfig('remote "origin".url', 'https://github-cors.now.sh/wmhilton/test.empty')
// Push a branch back to Github
git('.')
.githubToken(process.env.GITHUB_TOKEN)
.remote('origin')
.push('refs/heads/master')
// TODO: git.merge(), git.pull(), git.status(), git.diff(), git.tag(), git.branch(), etc
// And if you need to work with bare repos there are
// equivalents to the `--git-dir` and `--work-tree` options
git().gitdir('my-bare-repo').workdir('/var/www/website')
```
### CLI
I realized I could "translate" command line options into JavaScript chained commands
without hard-coding any knowledge of the API if I kept the chained commands very predictable.
I built a purely a generic translator and it worked surprisingly well.
So you can do *any* current or future esgit commands using the included `esgit` CLI.
It always starts with an implicit `git('.')` so it defaults to working in the
current working directory.
```
// Create a new empty repo
esgit --gitdir=test init
// Clone from a Github repository to the current working directory.
// Just like it's counterpart, clone is really just shorthand for git.init(); git.fetch(); git.checkout();
esgit --githubToken=$GITHUB_TOKEN clone https://github.com/wmhilton/esgit
// Checkout a commitish
esgit checkout master
// List files in the index
esgit list
// Add files to the index
esgit add README.md
// Remove files from the index
esgit remove .env
// Create a new commit (there's actually several more options for date, committer)
esgit add a.txt
esgit --author='Mr. Test' --email=mrtest@example.com --signingKey="$(cat private.key)" commit 'Added the a.txt file'
// And if you need to work with bare repos there are
// equivalents to the `--git-dir` and `--work-tree` options
esgit --gitdir=my-bare-repo --workdir=/var/www/website
```
The high-level makes some assumptions (like you have a file-system and network access) that might not be well suited
to your embedded git-based concept thingy. Fear not! I am
purposefully building this library as a series of small modules
so you can pick and choose features as you need them.
### Individual commands
Each command is available as its own file, so hopefully with
a bit of finagling you will be able to do:
```js
const add = require('esgit/lib/commands/add.js')
const checkout = require('esgit/lib/commands/checkout.js')
const config = require('esgit/lib/commands/config.js')
const fetch = require('esgit/lib/commands/fetch.js')
const init = require('esgit/lib/commands/init.js')
const list = require('esgit/lib/commands/list.js')
const remove = require('esgit/lib/commands/remove.js')
// Example
checkout({
workdir: '.',
gitdir: '.git',
ref: 'master',
remote: 'origin'
})
.then()
```
Only interested in commit objects? Import just the GitCommit class. And so on:
```js
const GitBlob = require('esgit/lib/models/GitBlob.js')
const GitCommit = require('esgit/lib/models/GitCommit.js')
const GitTree = require('esgit/lib/models/GitTree.js')
const GitObject = require('esgit/lib/models/GitObject.js')
// Example
const commit = GitCommit.from(`tree 0370383003493b845c73fb4316355a18f0e346fe
parent c9efafe888804a58e333ec1b64ca58f9c4c92414
author Will Hilton <wmhilton@gmail.com> 1503898987 -0400
committer Will Hilton <wmhilton@gmail.com> 1503899426 -0400
gpgsig -----BEGIN PGP SIGNATURE-----
Version: GnuPG v1
iQIcBAABAgAGBQJZo68iAAoJEJYJuKWSi6a5WesP/31Ie7uNJcLU1+yEch1ubuhS
J9PfAna2iUflQANNq8BCyufW8Nq3ZA+nyRvaA499VqLkiuEI6nMUwWVPdhuYUG4s
P7QP85NXn1w1DRNfbb2K6USz40P3a108GqhOpDCLP55kRfcxyJZIymD/WtHKji7T
3ubE0meMYHOsx9a76Roo9DOHiqy3gAZsiwqvvqToD/IrmPmRv3qCqFBPrKKMW7la
DZGPFHV9bhJfaGTT42lu0vTjv5itdLwfCLReh3LuQ1TAq5b6hhvwguE+M+X8qA11
KXxxyQf06wt3+o8pjxxUHttVsuiS68yxzeTQOWe8pk/E5oaEqOx6O9stDvSfZatm
2LvFR++0axDMANluuStAoDhTMrqUqLQ3iWeay+xpivrP7mWnLM/bzrPShQVSkFPP
VkKedKiaaCGKTEY4xa/RBQvQ3WqDnNogY5t6bOolPvC5/D5n3grkzOpI7WQqh8EN
+6eYOOYTTPShi5gnBTSwFdSBKG4aWJI5moQtQJ1wQtc4Zhm4jKbp7OLPn1GJEET6
QwnURymv4mkDuvyPUki5nsP5U+xzWObp8PlqE4SCbSiV4D5FqF5uVmeh4E15WsTP
y2l3fT2JUVecDXOLot3MSceiakrgoKNsYQlM+e/+XHwuZ41nZ/OuSCBGjhRn0UDI
1qMqffk5gWiTsZFwLjBV
=gsxy
-----END PGP SIGNATURE-----
Add a .git/index parserializer`)
commit.parseHeaders()
/*
{ tree: '0370383003493b845c73fb4316355a18f0e346fe',
parent: [ 'c9efafe888804a58e333ec1b64ca58f9c4c92414' ],
author:
{ name: 'Will Hilton',
email: 'wmhilton@gmail.com',
timestamp: 1503898987,
timezoneOffset: 240 },
committer:
{ name: 'Will Hilton',
email: 'wmhilton@gmail.com',
timestamp: 1503899426,
timezoneOffset: 240 },
gpgsig: '-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v1' }
*/
```