Skip to content

Commit

Permalink
cli - snob is now self hosting
Browse files Browse the repository at this point in the history
  • Loading branch information
dominictarr committed Mar 18, 2012
1 parent 9fc30eb commit cf293e9
Show file tree
Hide file tree
Showing 11 changed files with 355 additions and 8 deletions.
Binary file removed .readme.markdown.swp
Binary file not shown.
2 changes: 2 additions & 0 deletions .snob/commits

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions .snob/state
@@ -0,0 +1 @@
{"branches":{"master":"4f63d2637ae35e9313fd4f23ea6cf4e8e527ba3c"},"tags":{},"current":"master"}
181 changes: 181 additions & 0 deletions cli.js
@@ -0,0 +1,181 @@
var fs = require('fs')
var join = require('path').join
var Repo = require('./snob')

// just for a joke, lets add a CLI so that snob can be self hosting.

/*
COMMANDS
init // create a repo and persist it.
save commits in an append only log
save state in a json file
just needs branches,
and current checkout commit
commit file... save the current file in a new commit
checkout commitish (commitish = commit/tag/branch)
tag name commitish
merge commitish1 commitish2 || current_branch
branch branchname
whereami show current branch
*/

function Snob (dir) {
this.dir = dir
this.repo = new Repo()
}

Snob.prototype = {
read: function (file, ready) {
fs.readFile(join(this.dir, file), 'utf-8', ready)
},
load: function (callback) {
var repo = this.repo, self = this
var n = 2, err
function ready (e) {
err = err || e
if(--n) return
callback(err)
}

fs.readFile(join(this.dir, '.snob', 'commits'), 'utf-8',
function (err, commits) {
commits.split('\n').map(function (e) {
if(!e) return
var commit = JSON.parse(e)
repo.commits[commit.id] = commit
})
ready(err)
})

fs.readFile(join(this.dir, '.snob', 'state'), 'utf-8',
function (err, data) {
var state = JSON.parse(data)
repo.branches = state.branches || {}
repo.tags = state.tags || {}
self.current = state.current
console.log(state)
ready(err)
})
},
save: function (commit, callback) {
var n = 2, err
function ready (e) {
err = err || e
if(!--n) return
callback(err)
}
append(join(this.dir, '.snob', 'commits')
, JSON.stringify(commit) + '\n', ready)

fs.writeFile(join(this.dir, '.snob', 'state')
, JSON.stringify({
branches: this.repo.branches,
tags: this.repo.tags,
current: this.current || 'master' ,
}), ready)
}
}

function append (file, text, callback) {
var s = fs.createWriteStream(file, {flags: 'a'})
s.on('end', callback)
s.on('error', callback)
s.write(text)
s.end()
}

function findDir (dir) {
if(dir === '/')
throw new Error('is not a SNOB repo')
dir = dir || process.cwd()
var dotsnob = join(dir, '.snob')
try {
fs.readdirSync(dotsnob)
return new Snob(dir)
} catch (err) {
throw err // currently must use snob from root of repo
if(err.code === 'ENOENT')
return findDir(join(dir, '..'))
}
}

var commands = {
init: function (dir) {
dir = dir || process.cwd()
var dotsnob = join(dir, '.snob')
try {
fs.readdirSync(dotsnob)
} catch (err) {
//
if(err.code === 'ENOENT') {
// create
fs.mkdirSync(dotsnob)
fs.writeFileSync(join(dotsnob, 'commits'), '')
fs.writeFileSync(join(dotsnob, 'state'),
JSON.stringify({current: 'master'})
)
}
}
},
commit: function () {
var state = findDir()
var files = [].slice.call(arguments)
var n = files.length + 1// extra one is the repo commits file
var repo = state.repo
state.load (done)
if(!files.length)
throw new Error('expected args: files to commit')
console.log(files)
//read each file, and
var world = {}

files.map(function (f) {
state.read(f, function (err, text) {
world[f] = text.split('\n')
done(err)
})
})

function done (err, text) {
if(err) throw err
if(--n) return
var commit = repo.commit(world, {parent: state.current || 'master'})
console.log(commit)
state.save(commit, console.log)
}
},
heads: function () {
var state = findDir()
state.load(function () {
console.log(state.repo.heads())
})
},
branch: function () {
var state = findDir()
state.load(function (err) {
if(err) throw err
console.log(state.repo.branches, 'current:', state.current)
})
},
log: function () {
var state = findDir()
state.load(function () {
state.repo.revlist('master') // add changing branch
.map(function (id) {

var commit = state.repo.get(id)
console.log(commit.id, commit.parent, new Date(commit.timestamp))
})
})
}
}

var args = process.argv
args.splice(0, 2)

console.log(args)
var cmd = args.shift()
if(commands[cmd])
commands[cmd].apply(null, args)
3 changes: 3 additions & 0 deletions notes
@@ -0,0 +1,3 @@
[ZS97, Cha99, p. 17]. "Even simple variants of the unordered tree problem have been shown to be NP-hard
ordered tree more computationally feasible, apparently!

23 changes: 21 additions & 2 deletions readme.markdown
Expand Up @@ -8,7 +8,6 @@ one of the best uses is to learn you exactly how git works.
if you want to know what is the difference between two files, you must first know what is the same.
this is called the Longest Common Subsequence problem. if you have two sequences `x = "ABDCEF" and `y = "ABCXYZF"` then `LCS(x,y)` is clearly "ABCF".


## lcs

```
Expand All @@ -23,7 +22,7 @@ function lcs (a,b)
this is very simple, but with exponential time complexity.
however, it can easily be made sufficantly performant by cacheing the return value of each call to lcs().

see js implementation, [index.js#L64-94](index.js#L64-94)
see js implementation, [index.js#L64-94](https://github.com/dominictarr/adiff/blob/master/index.js#L63-94)

## chunking

Expand Down Expand Up @@ -76,3 +75,23 @@ this is only a slightly more complicated problem. given a string `"ABDCEF"`, If
and meanwhile you changed it to "AXBCEFG". we must compare each of our changes to the original string, the [Concestor](http://en.wikipedia.org/wiki/Concestor)

TODO: worked example with chunks, resolve.

## self hosting!

snob became self hosting

```
4f63d2637ae35e9313fd4f23ea6cf4e8e527ba3c null Sun, 18 Mar 2012 08:20:34 GMT
```


## what next?

refactor snob out of adiff.
still thinking about how I will manage diffs on objects,
- refactor vu, with what I know now.
giving snob a cli, and in particular, push/pullable remotes, is the first step to being distributed.

- improve support for injection with merge resolution.
- get running in the browser
- make Repository an EventEmitter
23 changes: 17 additions & 6 deletions snob.js
Expand Up @@ -47,13 +47,16 @@ Repository.prototype = {
commit.timestamp = Date.now()
commit.id = hash(commit)

// XXX make an error if the commits are empty !!! XXX

this.commits[commit.id] = commit
this.branch(branch, commit.id)
console.log('new commit',branch, commit.id, this.getId(commit.id))
return commit
// emit the new commit
},
get: function (commitish) {
return this.commits[commitish] || this.branches[commitish] || this.tags[commitish]
return this.commits[commitish] || this.commits[this.branches[commitish] || this.tags[commitish]]
},
getId: function (commitish) {
return (this.get(commitish) || {id:null}).id
Expand All @@ -63,18 +66,24 @@ Repository.prototype = {
},
branch: function (name, commitish) {
// do not save this as a branch if it's actually a commit, or a tag.
if(this.commits[name] || this.tags[name]) return
this.branches[name] = this.getId(commitish)
if(this.commits[name] || this.tags[name]) {
console.log('!!!!!!!!!!!!!!!!!!!!!')
return this.getId(commitish)
}
console.log('BRANCH', name, commitish)
return this.branches[name] = this.getId(commitish)
},
diff: function (parent, world) {
var head = this.checkout(parent)
if('object' !== typeof world)
world = this.checkout(world)
return map(world, function (b, f) {
return a.diff(head[f] || [], b)
})
},
revlist: function (id) {
if(!id) return []
var commit = this.commits[id]
var commit = this.get(id)
if(!commit) return []
return this.revlist(commit.parent).concat(id)
},
concestor: function (heads) { //a list of commits you want to merge
Expand Down Expand Up @@ -129,9 +138,11 @@ Repository.prototype = {

commit.merged = branches
commit.parent = branches[0]
commit.depth = this.commits[branches[0]].depth + 1
commit.timestamp = Date.now()

commit.id = hash(commit)
commit.depth = this.commits[branches[0]].depth + 1

this.commits[commit.id] = commit
this.branch(mine, commit.id) // if this was merge( ['master', ...], ...) update the branch
return commit
Expand Down
63 changes: 63 additions & 0 deletions test/snob-test-branches.js
@@ -0,0 +1,63 @@

var assert = require('assert')
var Repo = require('../snob')

var snob = new Repo()
var world
var init = snob.commit(world = {
hello: ['who', 'what', 'when','why']
},
{ message: 'init', parent: 'master'})

assert.equal(init.depth, 1)

assert.equal(snob.getId('master'), init.id)

snob.branch('underdog', init.id)

var _world = snob.checkout(init.id)

assert.deepEqual(_world, world)

world.hello.splice(2, 0, 'how')

var second = snob.commit(world, {
message: 'second',
parent: 'master'
})

console.log(second)
console.log(snob.revlist(second.id))

assert.deepEqual(
snob.revlist(second.id),
[ init.id, second.id])

assert.deepEqual(second.depth, 2)

//a branch off init, so can test merge

_world.hello.push('WTF!?')
var branch = snob.commit(_world, {
message: 'branch',
parent: 'underdog'
})

assert.equal(branch.depth, 2)
console.log(branch)

var concestor = snob.concestor(branch.id, second.id)

assert.equal(concestor, init.id)
console.log(concestor)

var merged = snob.merge([branch.id, second.id], {message: 'merge1'})

console.log(merged)
assert.equal(3, merged.depth)

var world3 = snob.checkout(merged.id)

console.log(world3)


4 changes: 4 additions & 0 deletions test/snob-test.js
Expand Up @@ -40,9 +40,12 @@ var branch = snob.commit(_world, {

assert.equal(branch.depth, 2)
console.log(branch)

var concestor = snob.concestor(branch.id, second.id)

assert.equal(concestor, init.id)
console.log(concestor)

var merged = snob.merge([branch.id, second.id], {message: 'merge1'})

console.log(merged)
Expand All @@ -52,3 +55,4 @@ var world3 = snob.checkout(merged.id)

console.log(world3)


2 changes: 2 additions & 0 deletions test/test.js
Expand Up @@ -62,4 +62,6 @@ if(!module.parent) {
//note, it's possible for this case to occur in a
//n-way merge where there is a delete and a false conflict.
//most merges will be only 3 ways, so lets leave that for now.

console.log(d.diff3(split("ABCXYZF"), split("ABDCEF"), split("AXBCEFG")))
}

0 comments on commit cf293e9

Please sign in to comment.