Skip to content

Commit

Permalink
Open-source json-git
Browse files Browse the repository at this point in the history
  • Loading branch information
RobinBressan committed Feb 7, 2017
0 parents commit 0261ac1
Show file tree
Hide file tree
Showing 36 changed files with 4,885 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"presets": [
"es2015",
"stage-0"
]
}
13 changes: 13 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"env": {
"node": true,
"mocha": true,
},
"extends": "airbnb-base",
"rules": {
"import/no-extraneous-dependencies": ["error", {
"devDependencies": ["**/*Spec.js"]
}],
"indent": [2, 4],
}
}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
lib
node_modules
4 changes: 4 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
src
.babelrc
.eslintrc
Makefile
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
language: node_js
node_js:
- '7.5.0'
- '6.9.5'
sudo: false
19 changes: 19 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright (c) 2017 Robin Bressan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
329 changes: 329 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
# json-git

A pure JS local Git to versionnize any JSON.

## Installation

It is avaible through npm:

```sh
npm install json-git
```

## Usage

### Create a repository

json-git exports only one method `createRepository`:

```js
import createRepository from 'json-git';

const repository = createRepository();
```

It's that simple!

### Your first commit

You have now a JSON git, it's time to do your first commit:

```js
const tree = {
foo: 'bar',
};

repository.commit('robin', 'first commit', tree);
```

`robin` is my author name for that commit, and of course `first commit` is the commit's message.

### Read the current tree

At anytime you can read your current tree by getting the `tree` property :

```js
console.log(repository.tree);
```

This will display:

```json
{
"foo": "bar",
}
```

### Create your first branch

In json-git you always have a `master` branch but you can create some others as many as you want:

```js
repository.checkout('dev', true); // true means the branch is new
```

You can now commit on that branch:

```js
const tree = {
foo: 'bar',
hello: 'you',
bar: true,
};

repository.commit('robin', 'second commit', tree);
```

If you display the tree you will get:

```json
{
"foo": "bar",
"hello": "you",
"bar": true,
}
```

### Change the current branch

Well, we would like to come back to master:

```js
repository.checkout('master');
```

This time, if you display the tree you will get:

```json
{
"foo": "bar",
}
```

Our `"hello": "world"` is missing because it was commited on `dev` branch not `master`.

*Tip: Use the `branch` property of your repository to know the current branch.*

### Merge a branch into the current branch

#### No conflicts

The better situation for a merge is when there aren't any conflicts. So if we merge our `dev` branch into `master` it should be ok:

```js
repository.merge('robin', 'dev');
```

This will automaticaly create a merge commit with `robin` as author.

#### With conflicts

Forget about our last merge, let's talk about conflicts. First let's commit a new tree on `master`:

```js
const tree = {
foo: 'lorem',
hello: 'me',
};

repository.commit('robin', 'an evil commit', tree);
```

We've changed the `foo` value and added an `hello` key too! Let's try to merge `dev` into `master`:

```js
repository.merge('robin', 'dev');
```

Well... it still works. Don't worry it is a normal behaviour. As we are dealing with simple JSON and not a full tree of files and folders, by default json-git will always give priority to the new version .

To review conflicts you must provide a resolver to the merge method:

```js
function resolver(targetPatch /* dev branch */, localPatch /* master branch */, reject) {
// this resolver will be called on each conflict
// you receive the two patches which overlap
// if you do nothing, the targetPatch wins
// if you call reject, the localPatch wins
}

repository.merge('robin', 'dev', resolver);
```

So if we don't call `reject()`, the tree will look like this:

```json
{
"foo": "lorem",
"hello": "you",
"bar": true,
}
```

But if we call `reject()` to reject the `hello` conflict, the tree will look like this:

```json
{
"foo": "lorem",
"hello": "me",
"bar": true,
}
```

*Tip: a merge won't delete the branch you've just merged. You must use `repository.deleteBranch()` for that.*

### Revert a commit

You can revert at anytime the changes introduced by a commit. It will generate a patch representing the diff between this commit and its parent, and then it will apply and commit it:

```js
repository.revert('robin', '9acf3199dc573910e7f8ed6aaf9ae3d50a174bc9');
```

This will automaticaly create a revert commit with `robin` as author.

As for the merge, the conflict policy is the same (always works until you provide a resolver):

```js
function resolver(patch, localValue, reject) {
// this resolver will be called on each conflict
// you receive the patch that json-git want to apply and the current value of the target node
// if you do nothing, the patch wins
// if you call reject, the current value wins

// a conflict on revert can happen for example when the revert want to remove an already removed node.
}

repository.revert('robin', '9acf3199dc573910e7f8ed6aaf9ae3d50a174bc9', resolver);
```

### Getting the full log of the repository

As you've seen, we've been using the commit hash for our `revert()` method. There are two ways to retrieve a commit hash :

* The `commit()` method returns the hash of the new commit
* The `log` property return the full history of your repository indexed by commit hashes

For example if we revert the merge commit, the log could look like this:
```json
{
"2de9314077d9074bb0ca57cb94e7f455b198db5e": {
"author": "robin",
"date": "2017-02-07T11:58:15.592Z",
"message": "first commit",
"treeHash": "a5e744d0164540d33b1d7ea616c28f2fa97e754a",
"parent": "0000000000000000000000000000000000000000",
},
"ddfa215a540b0a43e6ae67b0b3893e355b8c06f7":
{
"author": "robin",
"date": "2017-02-07T11:58:15.623Z",
"message": "second commit",
"treeHash": "3420a96c38d2a469cf4b029a8a39edd927976d86",
"parent": "2de9314077d9074bb0ca57cb94e7f455b198db5e"
},
"f46b2822b2c7e3f88139789f7c14c83d8a85843a": {
"author": "robin",
"date": "2017-02-07T11:58:15.625Z",
"message": "an evil commit",
"treeHash": "e81607575aa673052e6cba65d14fee88ae7504ca",
"parent": "2de9314077d9074bb0ca57cb94e7f455b198db5e"
},
"9acf3199dc573910e7f8ed6aaf9ae3d50a174bc9": {
"author": "robin",
"date": "2017-02-07T11:58:15.628Z",
"message": "Merge of dev into master",
"treeHash": "932b670bb8ccdc53606e51ef5d71ea85748b9d86",
"parent": "f46b2822b2c7e3f88139789f7c14c83d8a85843a"
},
"9f866f45395ce63425113f50165d3b9371e04a8f": {
"author": "robin",
"date": "2017-02-07T11:58:15.630Z",
"message": "Revert of commit 9acf3199dc573910e7f8ed6aaf9ae3d50a174bc9",
"treeHash": "1c1713b5f507d9b70e50dcff649d5aa4574b6da7",
"parent": "9acf3199dc573910e7f8ed6aaf9ae3d50a174bc9"
}
}
```

### Generate a diff

You can generate a diff between two branches or commits by using the `diff()` command:

```js
repository.diff('master', 'dev'); // understand: Give me the patch I need to apply to master if I want to get dev state
```

The output is a JSON Patch, and looks like this:

```json
[
{ "op": "add", "path": "/bar", "value": true },
{ "op": "replace", "path": "/foo", "value": "bar" },
{ "op": "replace", "path": "/hello", "value": "you" }
]
```

### Apply a patch

You can apply a JSON Patch to the current tree by using the `apply()` method:

```js
repository.apply(patch);
```

*Tip: It will return the result but won't make a new commit. If you want to keep the result, it's up to you to commit it.*

If you want to deal with the conflicts that may have occured, it works the same way as the `revert()` method above.

### Delete a branch

When you're done with a branch, you can delete it. It is impossible to delete `master` branch for obvious reasons. The deletion only removes the head pointer, it doesn't remove the commits from the history. We should have a garbage collector for that (in future updates):

```js
repository.deleteBranch('dev');
```

### List of branches

To retrieve the list of branches, use the `branches` property: `console.log(repository.branches)`.

### Save and load your repository

Of course you can always export your repository with its `toJSON()` method in order to save it. Give the json snapshot to `createRepository()` to reload it:

```js
const snapshot = repository.toJSON();
const newRepository = createRepository(snapshot);
```

### API

* `repository.branch` returns the current branch
* `repository.branches` returns the list of branches
* `repository.head` returns the head hash of the current branch
* `repository.log` returns the full history of the repository
* `repository.tree` returns the current tree of the repository
* `repository.apply(patch [, resolver])` applies a patch to the current tree and returns the result
* `repository.commit(author, message, tree)` creates a new commit on the current branch
* `repository.checkout(branch [, create=false])` creates and/or changes the current branch
* `repository.deleteBranch(branch)` removes a branch
* `repository.diff(left, right)` generates a JSON Patch between two branches or commits
* `repository.merge(author, branch [, resolver])` merges a branch into the current branch
* `repository.revert(author, commitHash, [, resolver])` reverts the changes introduced by a commit
* `repository.toJSON()` exports a snapshot of the repository
* `createRepository([snapshot])` creates a new repository

## Development

Install dependencies with [yarn](https://yarnpkg.com/). You're then good to go.

To run the tests, just do `npm test`.

## Contribute

All contributions are welcome and must pass the tests. If you add a new feature, please write tests for it.

## License

This application is available under the MIT License.
Loading

0 comments on commit 0261ac1

Please sign in to comment.