Skip to content

Instantly share code, notes, and snippets.

@varjmes
Last active March 31, 2019 10:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save varjmes/fcf88d69cffcaa5b8d4ed0cb877cb18f to your computer and use it in GitHub Desktop.
Save varjmes/fcf88d69cffcaa5b8d4ed0cb877cb18f to your computer and use it in GitHub Desktop.
Creating the tree!
class Blob {
constructor(data) {
this.data = data
}
type() {
return 'blob'
}
toString() {
return this.data
}
}
module.exports = Blob
const crypto = require('crypto')
const fs = require('fs')
const path = require('path')
const zlib = require('zlib')
class Database {
constructor(pathname) {
this.pathname = pathname
this.tempChars = [
...Array.from({ length: 26 }, (_, i) =>
String.fromCharCode('A'.charCodeAt(0) + i)
),
...Array.from({ length: 26 }, (_, i) =>
String.fromCharCode('a'.charCodeAt(0) + i)
),
...[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
]
}
hashIt(string) {
return crypto
.createHash('sha1')
.update(string, 'utf-8')
.digest('hex')
}
writeObject(oid, content) {
const dirPath = path.join(this.pathname, oid.slice(0, 2))
const objectPath = path.join(dirPath, oid.slice(2, oid.length))
const tempPath = path.join(this.pathname, this.generateTempName())
const compressed = zlib.deflateSync(content)
fs.writeFileSync(tempPath, compressed, { flag: 'wx+' })
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath)
}
fs.renameSync(tempPath, objectPath)
}
generateTempName() {
const randomString = Array.from(
{ length: 6 },
(_, i) =>
this.tempChars[Math.floor(Math.random() * this.tempChars.length)]
).join('')
return `tmp_obj_${randomString}`
}
store(object) {
const string = object.toString()
const type = Buffer.from(object.type())
const typeByte = Buffer.from(`${type} ${string.byteLength.toString()}\x00`)
const content = Buffer.concat([typeByte, string])
object.oid = this.hashIt(content)
this.writeObject(object.oid, content)
}
}
module.exports = Database
class Entry {
constructor(name, oid) {
this.name = name
this.oid = oid
}
}
module.exports = Entry
class Tree {
constructor(entries) {
this.entryFormat = 'A7Z*H40'
this.mode = '100644'
this.entries = entries
}
type() {
return 'tree'
}
toString() {
const entries = this.entries.sort((a, b) => {
return -(a.name < b.name) || +(a.name > b.name)
})
const packed = entries.map(entry => {
let mode = Buffer.from(this.mode.replace(/^0/, ''))
let space = Buffer.from(' ')
let name = Buffer.from(entry.name, 'utf8')
let nullChar = Buffer.from([0])
let oid = Buffer.from(entry.oid.match(/../g).map(n => parseInt(n, 16)))
return Buffer.concat([mode, space, name, nullChar, oid])
})
return Buffer.concat(packed)
}
}
module.exports = Tree
const fs = require('fs')
const path = require('path')
const Blob = require('./blob')
const Database = require('./database')
const Entry = require('./entry')
const Tree = require('./tree')
const Workspace = require('./workspace')
const directories = ['objects', 'refs']
const command = process.argv[2]
switch (command) {
case 'init': {
const repoPath = process.argv[3] || ''
const gitPath = path.resolve(repoPath, '.git')
directories.map(dir => {
const dirPath = path.join(gitPath, dir)
fs.mkdirSync(dirPath, { recursive: true })
})
console.info(`Initialised empty vrs repository in ${gitPath}`)
process.exit(0)
}
case 'commit': {
const gitPath = path.resolve('.git')
const dbPath = path.join(gitPath, 'objects')
const workspace = new Workspace(process.cwd())
const database = new Database(dbPath)
const entries = []
workspace.listFiles().forEach(filePath => {
const data = workspace.readFile(filePath)
const blob = new Blob(data)
database.store(blob)
entries.push(new Entry(filePath, blob.oid))
})
const tree = new Tree(entries)
database.store(tree)
console.info(`tree: ${tree.oid}`)
process.exit(0)
}
default:
console.error(`vrs: '${command}' is not a valid vrs command`)
process.exit(1)
}
const fs = require('fs')
const path = require('path')
class Workspace {
constructor(pathname) {
this.ignore = ['.', '..', '.git']
this.pathname = pathname
}
listFiles() {
return fs
.readdirSync(this.pathname)
.filter(item => !this.ignore.includes(item))
}
readFile(filePath) {
return fs.readFileSync(path.join(this.pathname, filePath))
}
}
module.exports = Workspace
@billiegoose
Copy link

billiegoose commented Mar 31, 2019

My guess if the tree hashes aren't coming out right would be to double check this sort function:

const entries = this.entries.sort((a, b) => {
  return a.name - b.name
})

I'm pretty sure that subtracting two strings results in NaN. In isomorphic-git I use this crazy function I found on stack overflow.

https://github.com/isomorphic-git/isomorphic-git/blob/741013320582a6c80a70b23d691ae621df2e6c1a/src/utils/compareStrings.js

@varjmes
Copy link
Author

varjmes commented Mar 31, 2019

My guess if the tree hashes aren't coming out right would be to double check this sort function:

const entries = this.entries.sort((a, b) => {
  return a.name - b.name
})

I'm pretty sure that subtracting two strings results in NaN. In isomorphic-git I use this crazy function I found on stack overflow.

https://github.com/isomorphic-git/isomorphic-git/blob/741013320582a6c80a70b23d691ae621df2e6c1a/src/utils/compareStrings.js

Good to know, I'm now using that, thanks!

I think my problem is encoding on the tree file.

blob file

~/projects/vrs
❯ cat .git/objects/16/213a7070f837cc149955ab811cbf2850169b88 | inflate 
blob 160class Blob {
  constructor(data) {
    this.data = data
  }

  type() {
    return 'blob'
  }

  toString() {
    return this.data
  }
}

module.exports = Blob

tree file

❯ cat .git/objects/3b/2504a6517df796a255a8acccf6e1cbee3b8915 | inflate 
tree 219100644 blob.js!:pp�7��U���(P��100644 database.js(���}�3�d�ҋ�����100644 entry.js8�j����+�d�HFn
                   V�d�100644 tree.js;�ĮR���3l�j���?��
]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment