├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── index.coffee ├── lib ├── dao │ ├── blobs.dao.coffee │ ├── commits.dao.coffee │ ├── objects.dao.coffee │ ├── repos.dao.coffee │ ├── trees.dao.coffee │ └── users.dao.coffee ├── massive.git.coffee ├── objects │ ├── blob.coffee │ ├── commit.coffee │ ├── git.object.coffee │ ├── repo.coffee │ ├── tag.coffee │ ├── tree.coffee │ ├── tree.entry.coffee │ └── user.coffee └── validators │ └── input.validators.coffee ├── package.json └── test ├── blob.coffee ├── commit.coffee ├── error.handling.coffee ├── fetch.repos.coffee ├── fixtures └── test.png ├── helper └── helper.coffee ├── input.validators.coffee ├── massive.git.utils.coffee ├── repo.coffee ├── repo.commit.coffee ├── repo.delete.coffee ├── repo.fork.coffee ├── repo.history.coffee ├── repo.init.coffee ├── tree.coffee ├── user.coffee └── user.new.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.*~ 3 | Makefile~ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2011 CircuitHub., http://circuithub.com/ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TESTS = test/*.coffee 2 | 3 | test: 4 | @NODE_ENV=test ./node_modules/.bin/mocha \ 5 | --require coffee-script \ 6 | --reporter list \ 7 | $(TESTS) 8 | 9 | test-ci: 10 | @NODE_ENV=test ./node_modules/.bin/mocha \ 11 | --require coffee-script \ 12 | --reporter tap \ 13 | --timeout 4000 \ 14 | $(TESTS) >> result.tap 15 | 16 | 17 | .PHONY: test test-ci 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MassiveGit 2 | 3 | MassiveGit - revision controlled database. 4 | 5 | ## Implementation 6 | 7 | MassiveGit implements Git Object Model on top of Riak [http://basho.com/products/riak-overview/]. 8 | In future other backends may be implemented. 9 | 10 | ## Use cases 11 | 12 | There are variety of use cases where it can be used: 13 | 14 | 1. GitHub 15 | 2. CircuitHub 16 | 3. WikiPedia 17 | 4. shapesmith (MCAD) 18 | 19 | Basically any case where you need to store revisioned versions of your data. With MassiveGit you get nice abstraction layer which allows you to deal with data in terms of Git: Commit, Tree, Blob, Tag. 20 | 21 | ## Additional materials 22 | 23 | We have video and slide from London Node.js User Group: http://lanyrd.com/2011/lnug-october/skgpw/. 24 | 25 | Check them for initial introduction. 26 | 27 | ## Tips 28 | 29 | Remove riak data from this directory: 30 | 31 | `/var/lib/riak/leveldb` 32 | 33 | 34 | ## Configurations 35 | 36 | Please updated `js_max_vm_mem` and `js_thread_stack` to 512 MB on your app.config. 37 | 38 | See [http://wiki.basho.com/MapReduce.html] for details how to do this and why. 39 | 40 | Since we are using secondary indexes please make following change in the riak app.config: 41 | `change the storage backend to riak_kv_eleveldb_backend`. 42 | 43 | ## Contributions 44 | 45 | MassiveGit is currently is under development. If you need any feature tell us or fork project and implement it by yourself. 46 | 47 | We appreciate feedback! 48 | 49 | 50 | ## License 51 | 52 | (The MIT License) 53 | 54 | Copyright (c) 2011 CircuitHub., http://circuithub.com/ 55 | 56 | Permission is hereby granted, free of charge, to any person obtaining 57 | a copy of this software and associated documentation files (the 58 | 'Software'), to deal in the Software without restriction, including 59 | without limitation the rights to use, copy, modify, merge, publish, 60 | distribute, sublicense, and/or sell copies of the Software, and to 61 | permit persons to whom the Software is furnished to do so, subject to 62 | the following conditions: 63 | 64 | The above copyright notice and this permission notice shall be 65 | included in all copies or substantial portions of the Software. 66 | 67 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 68 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 69 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 70 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 71 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 72 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 73 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 74 | 75 | -------------------------------------------------------------------------------- /index.coffee: -------------------------------------------------------------------------------- 1 | module.exports = require "./lib/massive.git" 2 | 3 | -------------------------------------------------------------------------------- /lib/dao/blobs.dao.coffee: -------------------------------------------------------------------------------- 1 | Blob = require("../objects/blob").Blob 2 | ObjectsDao = require("./objects.dao").ObjectsDao 3 | 4 | class BlobsDao extends ObjectsDao 5 | 6 | populateEntity: (meta, attributes) => 7 | if attributes? 8 | new Blob(attributes, @getRepository(meta.links), meta.key, meta.contentType) 9 | 10 | exports.newInstance = (log) -> new BlobsDao(log) 11 | -------------------------------------------------------------------------------- /lib/dao/commits.dao.coffee: -------------------------------------------------------------------------------- 1 | Commit = require("../objects/commit").Commit 2 | ObjectsDao = require("./objects.dao").ObjectsDao 3 | 4 | class CommitsDao extends ObjectsDao 5 | 6 | populateEntity: (meta, attributes) => 7 | if attributes? 8 | tree = @getLink meta.links, "tree" 9 | parent = @getLink meta.links, "parent" 10 | author = @getLink meta.links, "author" 11 | committer = @getLink meta.links, "committer" 12 | repository = @getLink meta.links, "repository" 13 | authorEmail = attributes.authorEmail 14 | committerEmail = attributes.committerEmail 15 | authoredDate = attributes.authoredDate 16 | commitedDate = attributes.commitedDate 17 | new Commit(tree, parent, author, authorEmail, authoredDate, committer, committerEmail, commitedDate, attributes.message, repository, meta.key) 18 | 19 | getParents: (commitId, callback) => 20 | @links commitId, [["objects", "parent", 1],["objects", "parent", 1],["objects", "parent", 1],["objects", "parent", 1],["objects", "parent", 1],["objects", "parent", 1],["objects", "parent", 1],["objects", "parent", 1],["objects", "parent", 1],["objects", "parent", 1]], (err, docs) => 21 | if err 22 | callback err 23 | else 24 | commits = [] 25 | for doc in docs 26 | data = doc.data 27 | commit = @populateEntity(doc.meta, data) 28 | commits.push commit if commit? 29 | # fetching initial commit by specified i. NOTE (anton) refactor it. We fetching this list in 2 requests here 30 | @get commitId, (err, commit) -> 31 | if err 32 | callback undefined, commits 33 | else 34 | commits.unshift commit 35 | callback undefined, commits 36 | 37 | exports.newInstance = (log) -> new CommitsDao(log) 38 | -------------------------------------------------------------------------------- /lib/dao/objects.dao.coffee: -------------------------------------------------------------------------------- 1 | Dao = require("riak-entity").Dao 2 | 3 | ## ObjectsDao 4 | ## ------------- 5 | ## Base Dao for all Git Objects: Blob, Commit, Tree, Tag. 6 | class exports.ObjectsDao extends Dao 7 | 8 | constructor: (log) -> super "objects", log 9 | 10 | # Get link to repository. Can be `null`. 11 | getRepository: (links) => @getLink links, "repository" 12 | 13 | 14 | -------------------------------------------------------------------------------- /lib/dao/repos.dao.coffee: -------------------------------------------------------------------------------- 1 | Dao = require("riak-entity").Dao 2 | Repo = require("../objects/repo").Repo 3 | 4 | class ReposDao extends Dao 5 | 6 | constructor: (log) -> super "repositories", log 7 | 8 | populateEntity: (meta, attributes) => 9 | if attributes? 10 | author = @getLink meta.links, "author" 11 | forkedFrom = @getLink meta.links, "forked_from" 12 | commit = @getLink meta.links, "commit" 13 | new Repo(attributes.name, author, attributes.type, attributes.public, commit, forkedFrom) 14 | 15 | getNewestRepos: (callback) => 16 | reduceDescending = ( v , args ) -> 17 | v.sort ( (a,b) -> return b.meta.lastModified - a.meta.lastModified ) 18 | return v 19 | @db.add(@bucket).map(@_map).reduce(reduceDescending).run (err, docs) => 20 | if err 21 | callback err 22 | else 23 | console.log "newest repos", docs.length if @log 24 | repos = (@populateEntity doc.meta, doc.attributes for doc in docs when doc.meta?) 25 | callback undefined, repos 26 | 27 | search: (name, author, callback) => 28 | key_filters = [] 29 | if name and author 30 | key_filters = ["and", [["matches", name]], [["starts_with", author]]] 31 | if name and !author 32 | key_filters = [["matches", name]] 33 | if !name and author 34 | key_filters = [["starts_with", author]] 35 | @db.add({bucket: @bucket, key_filters: key_filters }).map(@_map).run (err, docs) => 36 | if err 37 | console.log "cannot find repos", err, "for filters", key_filters if @log 38 | callback undefined, [] 39 | else 40 | console.log "found repos", docs.length, docs if @log 41 | repos = (@populateEntity doc.meta, doc.attributes for doc in docs when doc.meta?) 42 | callback undefined, repos 43 | 44 | exports.newInstance = (log) -> new ReposDao(log) 45 | -------------------------------------------------------------------------------- /lib/dao/trees.dao.coffee: -------------------------------------------------------------------------------- 1 | Tree = require("../objects/tree").Tree 2 | blobsDao = require("./blobs.dao").newInstance() 3 | ObjectsDao = require("./objects.dao").ObjectsDao 4 | 5 | class TreesDao extends ObjectsDao 6 | 7 | populateEntity: (meta, attributes) => 8 | repo = @getRepository(meta.links) 9 | new Tree(attributes.entries, repo, meta.key) if attributes? 10 | 11 | getBlobs: (treeId, callback) => 12 | @walk treeId, [[@bucket, "blob"]], (err, docs) => 13 | if err 14 | callback err 15 | else 16 | blobs = (blobsDao.populateEntity doc.meta, doc.attributes for doc in docs when doc? and doc.attributes?) 17 | callback undefined, blobs 18 | 19 | exports.newInstance = (log) -> new TreesDao(log) 20 | 21 | -------------------------------------------------------------------------------- /lib/dao/users.dao.coffee: -------------------------------------------------------------------------------- 1 | _ = require "underscore" 2 | Dao = require("riak-entity").Dao 3 | reposDao = require("./repos.dao").newInstance() 4 | blobsDao = require("./blobs.dao").newInstance() 5 | commitsDao = require("./commits.dao").newInstance() 6 | treesDao = require("./trees.dao").newInstance() 7 | User = require("../objects/user").User 8 | 9 | class UsersDao extends Dao 10 | 11 | constructor: (log) -> super "users", log 12 | 13 | populateEntity: (meta, attributes) -> 14 | new User(meta.key, attributes.email, meta.links) if attributes? 15 | 16 | fetchAllRepos: (username, callback) => 17 | @get username, (err, user) => 18 | console.log "user", user.links() 19 | if err 20 | callback err 21 | else 22 | callback undefined, user.getLinks "repositories" 23 | 24 | addRepo: (user, repoId, type, callback) => 25 | @get user, (err, user) => 26 | if err 27 | callback err 28 | else 29 | user.addLink "repositories", repoId, type 30 | @save user, callback 31 | 32 | removeRepo: (user, repoId, callback) => 33 | @get user, (err, user) => 34 | if err 35 | callback err 36 | else 37 | user.removeLink "repositories", repoId 38 | @save user, callback 39 | 40 | 41 | watchRepo: (user, repoId, type, callback) => 42 | 43 | unwatchRepo: (user, repoId, callback) => 44 | 45 | 46 | followUser: (user, userToFollow, callback) => 47 | 48 | unfollowUser: (user, userToUnfollow, callback) => 49 | 50 | exports.newInstance = (log) -> new UsersDao(log) 51 | -------------------------------------------------------------------------------- /lib/massive.git.coffee: -------------------------------------------------------------------------------- 1 | async = require "async" 2 | _ = require "underscore" 3 | validators = require "./validators/input.validators" 4 | 5 | # Dao objects 6 | ReposDao = require "./dao/repos.dao" 7 | UsersDao = require "./dao/users.dao" 8 | CommitsDao = require "./dao/commits.dao" 9 | TreesDao = require "./dao/trees.dao" 10 | BlobsDao = require "./dao/blobs.dao" 11 | 12 | # Re-export objects. 13 | User = exports.User = require("./objects/user").User 14 | Repo = exports.Repo = require("./objects/repo").Repo 15 | Tree = exports.Tree = require("./objects/tree").Tree 16 | Blob = exports.Blob = require("./objects/blob").Blob 17 | Commit = exports.Commit = require("./objects/commit").Commit 18 | TreeEntry = exports.TreeEntry = require("./objects/tree.entry").TreeEntry 19 | 20 | # Utility method for merging two arrays into one. 21 | mergeArrays = (first, second) -> Array::push.apply first, second 22 | 23 | MassiveGit = exports.MassiveGit = class MassiveGit 24 | 25 | constructor: (@log = false) -> 26 | @reposDao = ReposDao.newInstance() 27 | @usersDao = UsersDao.newInstance() 28 | @commitsDao = CommitsDao.newInstance() 29 | @treesDao = TreesDao.newInstance() 30 | @blobsDao = BlobsDao.newInstance() 31 | 32 | newUser: (username, email, callback) => 33 | validationResult = validators.validateUser(username, email) 34 | if !validationResult.isValid() 35 | callback {statusCode: 422, message: validationResult.errorMessage} 36 | else 37 | # create user object and save it 38 | [username, email] = validationResult.sanitizedParameters 39 | user = new User username, email 40 | console.log "user to be created", user, validationResult 41 | @usersDao.exists user.id(), (err, exists) => 42 | if err 43 | callback {statusCode: 400, message: "Internal error"} 44 | if exists 45 | callback {statusCode: 422, message: "User already exists"} 46 | else 47 | @usersDao.save user, callback 48 | 49 | initRepo: (name, author, type, callback) => 50 | validationResult = validators.validateRepo(name, author) 51 | if !validationResult.isValid() 52 | callback {statusCode: 422, message: validationResult.errorMessage} 53 | else 54 | # create repo object and save it 55 | [name, author] = validationResult.sanitizedParameters 56 | repo = new Repo(name, author, type) 57 | @reposDao.exists repo.id(), (err, exists) => 58 | if err 59 | callback {statusCode: 400, message: "Internal error"} 60 | if exists 61 | callback {statusCode: 422, message: "Repo already exists"} 62 | else 63 | @_saveRepo repo, callback 64 | 65 | forkRepo: (repoId, name, author, callback) => 66 | @getRepo repoId, (err, repo) => 67 | if err 68 | callback err 69 | else 70 | forkedRepo = repo.fork name, author 71 | @_saveRepo forkedRepo, callback 72 | 73 | _saveRepo: (repo, callback) => 74 | @reposDao.save repo, (err, ok) => 75 | if err 76 | err.message = "Repo wasn't found" 77 | callback err 78 | else 79 | @usersDao.addRepo repo.author, repo.id(), repo.type, (err, ok) -> 80 | if err 81 | callback {statusCode: 400, message: "User wasn't found"} 82 | else 83 | callback undefined, repo 84 | 85 | deleteRepo: (repoId, author, callback) => 86 | @reposDao.remove repoId, (err, ok) => 87 | if err 88 | callback {statusCode: 400, message: "Repo wasn't found"} 89 | else 90 | @usersDao.removeRepo author, repoId, (err, ok) -> 91 | if err 92 | err.message = "User wasn't found" 93 | callback err 94 | else 95 | callback undefined, ok 96 | 97 | getUserRepos: (user, callback) => @usersDao.fetchAllRepos user, callback 98 | 99 | getNewestRepos: (callback) => @reposDao.getNewestRepos callback 100 | 101 | searchRepos: (name, author, callback) => @reposDao.search name, author, callback 102 | 103 | commit: (entries, repoId, author, message = "initial commit", parentCommit = undefined, callback) => 104 | preparedEntries = @_prepareEntries entries, repoId 105 | tasks = preparedEntries.tasks 106 | treeEntries = preparedEntries.treeEntries 107 | async.series tasks, (err, results) => 108 | if err 109 | callback err 110 | else 111 | @_prepareTreeAndCommit treeEntries, repoId, parentCommit, author, message, callback 112 | 113 | addToIndex: (entries, repoId, author, message = "update", callback) => 114 | @getHeadTree repoId, (err, tree, commitId) => 115 | if err 116 | callback err 117 | else 118 | preparedEntries = @_prepareEntries entries, repoId 119 | newEntries = preparedEntries.treeEntries 120 | tasks = preparedEntries.tasks 121 | date = new Date().getTime() 122 | mergedEntries = tree.entries 123 | newEntriesNames = (entry.name for entry in newEntries) 124 | mergedEntries = _.reject mergedEntries, (entry) -> _.include newEntriesNames, entry.name 125 | mergeArrays mergedEntries, newEntries 126 | async.series tasks, (err, results) => 127 | if err 128 | callback err 129 | else 130 | @_prepareTreeAndCommit mergedEntries, repoId, commitId, author, message, callback 131 | 132 | _prepareTreeAndCommit: (treeEntries, repoId, parentCommit, author, message, callback) => 133 | date = new Date().getTime() 134 | root = new Tree(treeEntries, repoId) 135 | @treesDao.save root, (err, ok) => 136 | if err 137 | callback err 138 | else 139 | @_prepareAndSaveCommit root, parentCommit, author, date, message, repoId, callback 140 | 141 | _prepareAndSaveCommit: (root, parentCommit, author, date, message, repoId, callback) => 142 | @usersDao.get author, (err, user) => 143 | if err 144 | callback err 145 | else 146 | commit = new Commit(root.id(), parentCommit, author, user.email, date, author, user.email, date, message, repoId) 147 | @commitsDao.save commit, (err, commit) => 148 | if err 149 | callback err 150 | else 151 | @_updateRepoCommitRef repoId, commit.id(), (err, resp) -> 152 | if err 153 | callback err 154 | else 155 | callback undefined, commit.id() 156 | 157 | _updateRepoCommitRef: (repoId, commitId, callback) => 158 | @getRepo repoId, (err, repo) => 159 | if err 160 | callback err 161 | else 162 | repo.commit = commitId 163 | @reposDao.save repo, callback 164 | 165 | # Callback takes 3 parameters: err, entries and repo instance. 166 | fetchRepoRootEntriesById: (repoId, callback) => 167 | @getHead repoId, (err, commitId, repo) => 168 | if err 169 | callback err 170 | else 171 | @fetchRootEntriesForCommit commitId, (err, entries) -> 172 | if err 173 | callback err 174 | else 175 | callback undefined, entries, repo 176 | 177 | # Callback takes 3 parameters: err, commit id and repo instance. 178 | getHead: (repoId, callback) => 179 | @getRepo repoId, (err, repo) -> 180 | if err 181 | callback err 182 | else 183 | callback undefined, repo.commit, repo 184 | 185 | # Callback accepts 3 parameters: error, tree and commit id. 186 | getHeadTree: (repoId, callback) => 187 | @getHead repoId, (err, commitId) => 188 | if err 189 | callback err 190 | else 191 | @getHeadTreeFromCommit commitId, callback 192 | 193 | # Callback accepts 3 parameters: error, tree and commit id. 194 | getHeadTreeFromCommit: (commitId, callback) => 195 | @getCommit commitId, (err, commit) => 196 | if err 197 | callback err 198 | else 199 | treeId = commit.tree 200 | @getTree treeId, (err, tree) -> 201 | if err 202 | callback err 203 | else 204 | callback undefined, tree, commitId 205 | 206 | fetchRootEntriesForCommit: (commitId, callback) => 207 | @getHeadTreeFromCommit commitId, (err, tree) => 208 | if err then callback(err) else @getTreeEntries tree, callback 209 | 210 | _prepareEntries: (entries, repoId) => 211 | plainEntries = [] 212 | tasks = [] 213 | for entry in entries 214 | plainEntries.push entry.attributes() 215 | # todo (anton) trees can be added later 216 | if entry.entry.type == "blob" 217 | blob = entry.entry 218 | blob.repo = repoId 219 | # todo (anton) we can use dao.exists() before saving each blob. 220 | task = async.apply @blobsDao.save, blob 221 | tasks.push task 222 | {tasks: tasks, treeEntries: plainEntries} 223 | 224 | # @TODO write test 225 | getHistoryForRepo: (repoId, callback) => 226 | @getHead repoId, (err, commitId) => 227 | if err 228 | callback err 229 | else 230 | @getHistoryForCommit commitId, callback 231 | 232 | # @TODO write test 233 | getHistoryForCommit: (commitId, callback) => @commitsDao.getParents commitId, callback 234 | 235 | # @TODO write test 236 | getTreeEntries: (tree, callback) => 237 | @getBlobs tree.id(), (err, blobs) -> 238 | if err 239 | callback err 240 | else 241 | entries = tree.entries 242 | treeEntries = [] 243 | for blob in blobs 244 | name = entry.name for entry in entries when entry.id == blob.id() 245 | treeEntries.push new TreeEntry name, blob 246 | callback err, treeEntries 247 | 248 | getBlobs: (id, callback) => 249 | if !id 250 | callback {statusCode: 422, message: "Invalid parameters"} 251 | else 252 | @treesDao.getBlobs id, (err, blobs) -> 253 | if err 254 | err.message = "Cannot retrive blobs" 255 | callback err 256 | else 257 | callback undefined, blobs 258 | 259 | getRepo: (id, callback) => 260 | if !id 261 | callback {statusCode: 422, message: "Invalid parameters"} 262 | else 263 | @reposDao.get id, (err, repo) -> 264 | if err 265 | callback {statusCode: 400, message: "Repo wasn't found"} 266 | else 267 | callback undefined, repo 268 | 269 | getBlob: (id, callback) => 270 | if !id 271 | callback {statusCode: 422, message: "Invalid parameters"} 272 | else 273 | @blobsDao.get id, (err, blob) -> 274 | if err 275 | callback {statusCode: 400, message: "Blob wasn't found"} 276 | else 277 | callback undefined, blob 278 | 279 | getCommit: (id, callback) => 280 | if !id 281 | callback {statusCode: 422, message: "Invalid parameters"} 282 | else 283 | @commitsDao.get id, (err, commit) -> 284 | if err 285 | callback {statusCode: 400, message: "Commit wasn't found"} 286 | else 287 | callback undefined, commit 288 | 289 | getTree: (id, callback) => 290 | if !id 291 | callback {statusCode: 422, message: "Invalid parameters"} 292 | else 293 | @treesDao.get id, (err, commit) -> 294 | if err 295 | callback {statusCode: 400, message: "Tree wasn't found"} 296 | else 297 | callback undefined, commit 298 | 299 | # Factory method to deal with Git Objects 300 | # -------------- 301 | # create new tree entry. 302 | createTreeEntry: (name, blob) -> new TreeEntry name, blob 303 | 304 | # Create new with provided data. 305 | createBlob: (data, repo, contentType = "application/json") -> new Blob(data, repo, null, contentType) 306 | 307 | -------------------------------------------------------------------------------- /lib/objects/blob.coffee: -------------------------------------------------------------------------------- 1 | GitObject = require("./git.object").GitObject 2 | 3 | # Blob 4 | # ----------- 5 | # Class representing Git `blob`. Blob can store data of any type. 6 | # User can additionaly specify content type for the blob by `contentType` property. 7 | Blob = exports.Blob = class Blob extends GitObject 8 | 9 | # Constructor takes `data` and optionally `repo` id and blob's `id`. 10 | constructor: (@data, @repo = null, @_id = null, @contentType) -> 11 | super "blob", @repo, @_id 12 | 13 | content: => @data 14 | 15 | # Dao related methods. 16 | # --------- 17 | 18 | # Method for getting plain `attributes` of the GitObject. 19 | attributes: => @data 20 | 21 | -------------------------------------------------------------------------------- /lib/objects/commit.coffee: -------------------------------------------------------------------------------- 1 | GitObject = require("./git.object").GitObject 2 | 3 | Commit = exports.Commit = class Commit extends GitObject 4 | 5 | # Constructor takes mandatory `Commit` properties and optionally `repo` id and commit's `id`. 6 | constructor: (@tree, @parent, @author, @authorEmail, @authoredDate, @committer, @committerEmail, @commitedDate, @message, @repo = null, @_id = null) -> 7 | super "commit", @repo, @_id 8 | 9 | # todo (anton) author and commiter here should keep authored and commited date. Only in this way sha will be Git compatible 10 | content: => 11 | parentToken = "" 12 | parentToken += "parent" + char for char in @parent if @parent # todo (anton) check what to do if parent is null. In case of first commit 13 | "tree " + @tree + "\n" + parentToken + "author " + @author + "<" + @authorEmail + ">" + "\ncommitter " + @committer + "<" + @committerEmail + ">" + "\n\n" + @message 14 | 15 | # Dao related methods. 16 | # --------- 17 | 18 | # Method for getting plain `attributes` of the GitObject. 19 | attributes: => 20 | attributes = super() 21 | attributes.message = @message 22 | attributes.authorEmail = @authorEmail 23 | attributes.committerEmail = @committerEmail 24 | attributes.authoredDate = @authoredDate 25 | attributes.commitedDate = @commitedDate 26 | attributes 27 | 28 | # Method for getting `links` that connect this GitObject with another GitObjects, users or repositories. 29 | links: => 30 | links = super() 31 | links.push @buildLink "objects", @tree, "tree" 32 | links.push @buildLink "objects", @parent, "parent" if @parent 33 | links.push @buildLink "users", @author, "author" 34 | links.push @buildLink "users", @committer, "committer" 35 | links 36 | 37 | -------------------------------------------------------------------------------- /lib/objects/git.object.coffee: -------------------------------------------------------------------------------- 1 | crypto = require "crypto" 2 | GitEntity = require("riak-entity").Entity 3 | 4 | sha1 = (data) -> crypto.createHash("sha1").update(data).digest("hex") 5 | 6 | # GitObject 7 | # ------------ 8 | # Every git object has following attributes: 9 | # id - SHA as unique identifier. 10 | # type - type of the object: blob, commit, tree or tag. 11 | # content - raw content of the object. 12 | # repo - reference to the repo. 13 | GitObject = exports.GitObject = class GitObject extends GitEntity 14 | 15 | # Constructor takes `type` of object and optionally `repo` id and internal `_id` of the object. 16 | constructor: (@type, @repo = null, @_id) -> 17 | 18 | id: => 19 | if @_id # return @_id if we have it. 20 | return @_id 21 | content = @content() 22 | header = "#" + @type + " " + content.length + "\0" # type(space)size(null byte) 23 | store = header + content 24 | sha1 store 25 | 26 | content: -> throw new Error("Should be implemented in every subclass!") 27 | 28 | # Dao related methods. 29 | # --------- 30 | 31 | # Method for getting plain `attributes` of the GitObject. 32 | attributes: -> {} 33 | 34 | # Method for getting `index`es of the GitObject. 35 | index: => 36 | type: @type 37 | repo: @repo 38 | 39 | # Method for getting `links` that connect this GitObject with another GitObjects, users or repositories. 40 | links: => 41 | links = [] 42 | if @repo 43 | links.push @buildLink "repositories", @repo, "repository" 44 | links 45 | 46 | # Get repository for this object. 47 | getRepository: => @getLink "repository" 48 | 49 | -------------------------------------------------------------------------------- /lib/objects/repo.coffee: -------------------------------------------------------------------------------- 1 | Entity = require("riak-entity").Entity 2 | 3 | # Repo 4 | # --------- 5 | # Class representing repository. 6 | # `id` - unique repository id. Id calculated from `owner`, `type` and `name` of the repository. 7 | # `author` - repository's author. 8 | # `type` - type of the repository. Meta information. 9 | # `public` - flag that indicated whether repo is public or private. Default to `true`. 10 | # `commit` - last commit for this repository. Can be `null` if repository wasn't commited previously. 11 | # `forkedFrom` - id of the repository from which this was cloned. Default to `null`. 12 | Repo = exports.Repo = class Repo extends Entity 13 | 14 | constructor: (@name, @author, @type, @public = true, @commit, @forkedFrom = null) -> 15 | 16 | id: => @author + "$" + @name 17 | 18 | # Create `forked` copy of the repo. 19 | fork: (name, author) => new Repo(name, author, @type, @public, @commit, @id()) 20 | 21 | # Dao related methods. 22 | # --------- 23 | 24 | # Method for getting plain `attributes` of the GitObject. 25 | attributes: => 26 | name : @name 27 | type : @type 28 | public: @public 29 | 30 | # Method for getting `index`es of the GitObject. 31 | index: => 32 | author: @author 33 | type : @type 34 | public: @public 35 | 36 | # Method for getting `links` that connect this GitObject with other Git Objects, users or repositories. 37 | links: => 38 | links = [] 39 | links.push @buildLink "users", @author, "author" 40 | links.push @buildLink "repositories", @forkedFrom, "forked_from" if @forkedFrom 41 | links.push @buildLink "objects", @commit, "commit" if @commit 42 | links 43 | 44 | -------------------------------------------------------------------------------- /lib/objects/tag.coffee: -------------------------------------------------------------------------------- 1 | # todo (anton) no need to implement at this stage. Can be implemented later 2 | class Tag extends GitObject 3 | 4 | -------------------------------------------------------------------------------- /lib/objects/tree.coffee: -------------------------------------------------------------------------------- 1 | _ = require "underscore" 2 | GitObject = require("./git.object").GitObject 3 | 4 | # Tree 5 | # --------- 6 | # Tree is one of the 4 core Git Objects. Tree stores `entries` which are links to blobs and other trees. 7 | Tree = exports.Tree = class Tree extends GitObject 8 | 9 | # Constructor takes Tree entries and optionally `repo` id and tree's `id`. 10 | # Entry object has `name`, `id` and `type`. 11 | constructor: (entries, @repo = null, @_id = null) -> 12 | super "tree", @repo, @_id 13 | # Entries are ordered by name as in native Git implementation. 14 | @entries = _.sortBy entries, (entry) -> entry.name 15 | 16 | content: => 17 | stringFromEntry = (id, name, type, mode) -> mode + " " + type + " " + id + "\t" + name + "\n" 18 | (stringFromEntry entry.id, entry.name, entry.type, entry.mode for entry in @entries).join("") 19 | 20 | # Dao related methods. 21 | # --------- 22 | 23 | # Method for getting plain `attributes` of the GitObject. 24 | attributes: => 25 | attributes = super() 26 | # @NOTE (anton) we need to store entities also here. Since they cannot be fully repopulated from links (mode and name of entry). 27 | # However for now it's not clear do we need these 'full' entities... 28 | attributes.entries = @entries 29 | attributes 30 | 31 | # Method for getting `links` that connect this GitObject with another GitObjects, users or repositories. 32 | links: => 33 | links = super() 34 | links.push @buildLink "objects", entry.id, entry.type for entry in @entries 35 | links 36 | 37 | -------------------------------------------------------------------------------- /lib/objects/tree.entry.coffee: -------------------------------------------------------------------------------- 1 | # TreeEntry 2 | # ---------- 3 | # Class represents entry for the `tree`. Each entry has: 4 | # `name` - name of the file or directory 5 | # `entry` - entry itself. Each entry has `type` and `id`. 6 | # `mode` - mode for file or directory. Used for tree's `id` claculation. 7 | TreeEntry = exports.TreeEntry = class TreeEntry 8 | 9 | # Constructor takes `data` and optionally `repo` id and blob's `id`. 10 | # todo (anton) we can hardcode `mode` right now 11 | constructor: (@name, @entry, @mode = 100644) -> 12 | 13 | attributes: => 14 | id : @entry.id() 15 | name: @name 16 | type: @entry.type 17 | mode: @mode 18 | 19 | -------------------------------------------------------------------------------- /lib/objects/user.coffee: -------------------------------------------------------------------------------- 1 | _ = require "underscore" 2 | Entity = require("riak-entity").Entity 3 | 4 | # User 5 | # ------------ 6 | # Class represent information about user in the system. 7 | # User has `id` (which is username) and `email`. 8 | User = exports.User = class User extends Entity 9 | 10 | constructor: (@_id, @email, @_links = []) -> 11 | 12 | addLink: (bucket, key, tag) => 13 | @_links.push @buildLink bucket, key, tag 14 | 15 | removeLink: (bucket, key) => 16 | @_links = _.select @_links, (link) -> link.bucket != bucket or link.key != key 17 | 18 | # Dao related methods. 19 | # --------- 20 | 21 | # Method for getting plain `attributes` of the GitObject. 22 | attributes: => {email: @email} 23 | 24 | # Method for getting `index`es of the GitObject. 25 | index: => 26 | email: @email 27 | 28 | # Method for getting `links` that connect this user with another GitObjects, users or repositories. 29 | links: => @_links 30 | 31 | -------------------------------------------------------------------------------- /lib/validators/input.validators.coffee: -------------------------------------------------------------------------------- 1 | check = require("validator").check 2 | sanitize = require("validator").sanitize 3 | 4 | class ValidationResult 5 | 6 | constructor: (@errorMessage, @sanitizedParameters...) -> 7 | 8 | isValid: => !@errorMessage? 9 | 10 | # Validate user's data. `username` and `email` are both mandatory. 11 | exports.validateUser = (username, email) -> 12 | # sanitize input params before further validation 13 | username = if username then sanitize(username).trim() else null 14 | email = if email then sanitize(email).trim() else null 15 | try 16 | # check that name and author were specified 17 | check(username, "Invalid parameters").notNull() 18 | check(email, "Invalid parameters").notNull() 19 | # check mimimum and maximum length 20 | check(username, "Username is out of range").len(3, 20) 21 | # check email 22 | check(email, "Email address is invalid").isEmail() 23 | return new ValidationResult(null, username, email) 24 | catch e 25 | return new ValidationResult e.message 26 | 27 | # Validate repo's data. `name` and `author` are both mandatory. 28 | exports.validateRepo = (name, author) -> 29 | # sanitize input params before further validation 30 | name = if name then sanitize(name).trim() else null 31 | author = if author then sanitize(author).trim() else null 32 | try 33 | # check that name and author were specified 34 | check(name, "Invalid parameters").notNull() 35 | check(author, "Invalid parameters").notNull() 36 | # check mimimum and maximum length 37 | check(name, "Repository name is out of range").len(3, 20) 38 | return new ValidationResult(null, name, author) 39 | catch e 40 | return new ValidationResult e.message 41 | 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "CircuitHub (circuithub.com)", 3 | "name": "massive-git", 4 | "description": "Revision controlled database that implements Git Object Model on top of Riak", 5 | "version": "0.5.2", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/circuithub/massive-git.git" 9 | }, 10 | "main": "./index.coffee", 11 | "engines": { 12 | "node": "~v0.4.10" 13 | }, 14 | "dependencies": { 15 | "riak-entity": ">=0.1.0", 16 | "coffee-script": ">=1.1.2", 17 | "underscore": ">=1.2.0", 18 | "validator": ">=0.3.0" 19 | }, 20 | "devDependencies": { 21 | "mocha": ">=0.0.8", 22 | "should": ">=0.3.1", 23 | "async": ">=0.1.10" 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /test/blob.coffee: -------------------------------------------------------------------------------- 1 | should = require "should" 2 | Blob = require("../lib/objects/blob").Blob 3 | 4 | describe "new Blob", -> 5 | blob = new Blob("test-content", "anton$project1") 6 | it "should have correct properties", -> 7 | blob.id().should.equal "0535cbee7fa4e0fef31389c68336ec6bcb5422b3" 8 | blob.should.have.property "type", "blob" 9 | blob.should.have.property "repo", "anton$project1" 10 | blob.should.have.property "data", "test-content" 11 | blob.content().should.equal blob.data 12 | blob.index().should.have.property "type", "blob" 13 | blob.index().should.have.property "repo", "anton$project1" 14 | it "should have correct links", -> 15 | blob.links().should.have.length(1) 16 | repoLink = blob.links()[0] 17 | repoLink.should.have.property "bucket", "repositories" 18 | repoLink.should.have.property "key", "anton$project1" 19 | repoLink.should.have.property "tag", "repository" 20 | blob.getLink("repository").should.equal "anton$project1" 21 | 22 | -------------------------------------------------------------------------------- /test/commit.coffee: -------------------------------------------------------------------------------- 1 | should = require "should" 2 | Commit = require("../lib/objects/commit").Commit 3 | 4 | describe "new Commit", -> 5 | authoredDate = new Date().getTime() 6 | commitedDate = new Date().getTime() 7 | commit = new Commit("tree-id", "parent-id", "anton", "anton@circuithub.com", authoredDate, "andrew","andrew@circuithub.com", commitedDate, "initial commit", "anton$project1") 8 | it "should have correct properties", -> 9 | commit.id().should.equal "bdd1caa1de76e21a5e8afe0676057a3a6685738d" 10 | commit.should.have.property "type", "commit" 11 | commit.should.have.property "tree", "tree-id" 12 | commit.should.have.property "parent", "parent-id" 13 | commit.should.have.property "author", "anton" 14 | commit.should.have.property "authorEmail", "anton@circuithub.com" 15 | commit.should.have.property "authoredDate", authoredDate 16 | commit.should.have.property "committer", "andrew" 17 | commit.should.have.property "committerEmail", "andrew@circuithub.com" 18 | commit.should.have.property "commitedDate", commitedDate 19 | commit.should.have.property "message", "initial commit" 20 | commit.should.have.property "repo", "anton$project1" 21 | commit.index().should.have.property "type", "commit" 22 | commit.index().should.have.property "repo", "anton$project1" 23 | it "should have correct links", -> 24 | commit.links().should.have.length(5) 25 | # repo link 26 | repoLink = commit.links()[0] 27 | repoLink.should.have.property "bucket", "repositories" 28 | repoLink.should.have.property "key", "anton$project1" 29 | repoLink.should.have.property "tag", "repository" 30 | commit.getLink("repository").should.equal "anton$project1" 31 | # tree link 32 | treeLink = commit.links()[1] 33 | treeLink.should.have.property "bucket", "objects" 34 | treeLink.should.have.property "key", "tree-id" 35 | treeLink.should.have.property "tag", "tree" 36 | commit.getLink("tree").should.equal "tree-id" 37 | # parent link 38 | parentLink = commit.links()[2] 39 | parentLink.should.have.property "bucket", "objects" 40 | parentLink.should.have.property "key", "parent-id" 41 | parentLink.should.have.property "tag", "parent" 42 | commit.getLink("parent").should.equal "parent-id", 43 | # author link 44 | authorLink = commit.links()[3] 45 | authorLink.should.have.property "bucket", "users" 46 | authorLink.should.have.property "key", "anton" 47 | authorLink.should.have.property "tag", "author" 48 | commit.getLink("author").should.equal "anton" 49 | # committer link 50 | committerLink = commit.links()[4] 51 | committerLink.should.have.property "bucket", "users" 52 | committerLink.should.have.property "key", "andrew" 53 | committerLink.should.have.property "tag", "committer" 54 | commit.getLink("committer").should.equal "andrew" 55 | 56 | 57 | -------------------------------------------------------------------------------- /test/error.handling.coffee: -------------------------------------------------------------------------------- 1 | should = require "should" 2 | MassiveGit = new (require("../lib/massive.git").MassiveGit)() 3 | 4 | describe "MassiveGit", -> 5 | describe "#getRepo('fake-repo-id')", -> 6 | it "return 'Repo wasn't found' error", (done) -> 7 | MassiveGit.getRepo "fake-repo-id", (err, repo) -> 8 | err.should.exist 9 | err.should.have.property "message", "Repo wasn't found" 10 | err.should.have.property "statusCode", 400 11 | should.not.exist repo 12 | done() 13 | describe "#getBlob('fake-blob-id')", -> 14 | it "return 'Blob wasn't found' error", (done) -> 15 | MassiveGit.getBlob "fake-blob-id", (err, blob) -> 16 | err.should.exist 17 | err.should.have.property "message", "Blob wasn't found" 18 | err.should.have.property "statusCode", 400 19 | should.not.exist blob 20 | done() 21 | describe "#getCommit('fake-commit-id')", -> 22 | it "return 'Commit wasn't found' error", (done) -> 23 | MassiveGit.getCommit "fake-commit-id", (err, commit) -> 24 | err.should.exist 25 | err.should.have.property "message", "Commit wasn't found" 26 | err.should.have.property "statusCode", 400 27 | should.not.exist commit 28 | done() 29 | describe "#getTree('fake-tree-id')", -> 30 | it "return 'Tree wasn't found' error", (done) -> 31 | MassiveGit.getTree "fake-tree-id", (err, tree) -> 32 | err.should.exist 33 | err.should.have.property "message", "Tree wasn't found" 34 | err.should.have.property "statusCode", 400 35 | should.not.exist tree 36 | done() 37 | describe "#getBlobs('fake-tree-id')", -> 38 | it "return 'Cannot retrive blobs' error", (done) -> 39 | MassiveGit.getBlobs "fake-tree-id", (err, blobs) -> 40 | blobs.length.should.equal 0 41 | done err 42 | describe "#getRepo(null)", -> 43 | it "return 'Invalid parameters' error", (done) -> 44 | MassiveGit.getRepo null, (err, repo) -> 45 | err.should.exist 46 | err.should.have.property "message", "Invalid parameters" 47 | err.should.have.property "statusCode", 422 48 | should.not.exist repo 49 | done() 50 | describe "#getBlob(null)", -> 51 | it "return 'Invalid parameters' error", (done) -> 52 | MassiveGit.getBlob null, (err, blob) -> 53 | err.should.exist 54 | err.should.have.property "message", "Invalid parameters" 55 | err.should.have.property "statusCode", 422 56 | should.not.exist blob 57 | done() 58 | describe "#getCommit(null)", -> 59 | it "return 'Invalid parameters' error", -> 60 | MassiveGit.getCommit null, (err, commit) -> 61 | err.should.exist 62 | err.should.have.property "message", "Invalid parameters" 63 | err.should.have.property "statusCode", 422 64 | should.not.exist commit 65 | describe "#getTree(null)", -> 66 | it "return 'Invalid parameters' error", (done) -> 67 | MassiveGit.getTree null, (err, tree) -> 68 | err.should.exist 69 | err.should.have.property "message", "Invalid parameters" 70 | err.should.have.property "statusCode", 422 71 | should.not.exist tree 72 | done() 73 | describe "#getBlobs(null)", -> 74 | it "return 'Invalid parameters' error", (done) -> 75 | MassiveGit.getBlobs null, (err, blobs) -> 76 | err.should.exist 77 | err.should.have.property "message", "Invalid parameters" 78 | err.should.have.property "statusCode", 422 79 | should.not.exist blobs 80 | done() 81 | describe "#getRepo(undefined)", -> 82 | it "return 'Invalid parameters' error", (done) -> 83 | MassiveGit.getRepo undefined, (err, repo) -> 84 | err.should.exist 85 | err.should.have.property "message", "Invalid parameters" 86 | err.should.have.property "statusCode", 422 87 | should.not.exist repo 88 | done() 89 | describe "#getBlob(undefined)", -> 90 | it "return 'Invalid parameters' error", (done) -> 91 | MassiveGit.getBlob undefined, (err, blob) -> 92 | err.should.exist 93 | err.should.have.property "message", "Invalid parameters" 94 | err.should.have.property "statusCode", 422 95 | should.not.exist blob 96 | done() 97 | describe "#getCommit(undefined)", -> 98 | it "return 'Invalid parameters' error", (done) -> 99 | MassiveGit.getCommit undefined, (err, commit) -> 100 | err.should.exist 101 | err.should.have.property "message", "Invalid parameters" 102 | err.should.have.property "statusCode", 422 103 | should.not.exist commit 104 | done() 105 | describe "#getTree(undefined)", -> 106 | it "return 'Invalid parameters' error", (done) -> 107 | MassiveGit.getTree undefined, (err, tree) -> 108 | err.should.exist 109 | err.should.have.property "message", "Invalid parameters" 110 | err.should.have.property "statusCode", 422 111 | should.not.exist tree 112 | done() 113 | describe "#getBlobs(undefined)", -> 114 | it "return 'Invalid parameters' error", (done) -> 115 | MassiveGit.getBlobs undefined, (err, blobs) -> 116 | err.should.exist 117 | err.should.have.property "message", "Invalid parameters" 118 | err.should.have.property "statusCode", 422 119 | should.not.exist blobs 120 | done() 121 | 122 | 123 | -------------------------------------------------------------------------------- /test/fetch.repos.coffee: -------------------------------------------------------------------------------- 1 | should = require "should" 2 | Blob = require("../lib/objects/blob").Blob 3 | TreeEntry = require("../lib/objects/tree.entry").TreeEntry 4 | MassiveGit = new (require("../lib/massive.git").MassiveGit)() 5 | helper = require "./helper/helper" 6 | 7 | describe "MassiveGit#getUserRepos()", -> 8 | repoName = "repo" + Math.floor(1000 * Math.random()) 9 | userName = "anton" + Math.floor(1000 * Math.random()) 10 | repoId = userName + "$" + repoName 11 | before (done) -> 12 | helper.createUserWithRepo userName, repoName, "repo-type", done 13 | it "should return 1 repo for provided real user", (done) -> 14 | MassiveGit.getUserRepos userName, (err, entries) -> 15 | should.not.exist err 16 | entries.should.have.length 1 17 | entries[0].id().should.equal repoId 18 | done err 19 | it "should return 0 repos for fake user", (done) -> 20 | MassiveGit.getUserRepos "fake-user", (err, entries) -> 21 | should.not.exist err 22 | entries.should.have.length 0 23 | done err 24 | after (done) -> 25 | helper.deleteUserWithRepo userName, repoId, done 26 | 27 | -------------------------------------------------------------------------------- /test/fixtures/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circuithub/massive-git/a30fd74d3f43d64f3af18950cf20dda0e19b3187/test/fixtures/test.png -------------------------------------------------------------------------------- /test/helper/helper.coffee: -------------------------------------------------------------------------------- 1 | should = require "should" 2 | async = require "async" 3 | reposDao = require("../../lib/dao/repos.dao").newInstance() 4 | commitsDao = require("../../lib/dao/commits.dao").newInstance() 5 | blobsDao = require("../../lib/dao/blobs.dao").newInstance() 6 | treesDao = require("../../lib/dao/trees.dao").newInstance() 7 | usersDao = require("../../lib/dao/users.dao").newInstance() 8 | MassiveGit = new (require("../../lib/massive.git").MassiveGit)() 9 | 10 | helper = exports 11 | 12 | helper.createUser = (username, callback) -> 13 | MassiveGit.newUser username, "some-email@circuithub.com", (err, user) -> 14 | should.not.exist err 15 | callback err, user 16 | 17 | helper.createUserWithRepo = (username, reponame, repotype, mainCallback) -> 18 | # create user 19 | step1 = (callback) -> 20 | helper.createUser username, callback 21 | # create repo 22 | step2 = (user, callback) -> 23 | MassiveGit.initRepo reponame, username, repotype, (err, repo) -> 24 | if(err) 25 | callback err 26 | else 27 | repo.id().should.equal username + "$" + reponame 28 | callback undefined, repo 29 | async.waterfall [step1, step2], (err, results) -> 30 | mainCallback err, results 31 | 32 | 33 | helper.deleteUser = (username, callback) -> usersDao.remove username, callback 34 | 35 | helper.deleteRepo = (repoId, callback) -> reposDao.remove repoId, callback 36 | 37 | helper.deleteUserWithRepo = (username, repoId, callback) -> 38 | helper.deleteRepo repoId, (err) -> 39 | if err 40 | callback err 41 | else 42 | helper.deleteUser username, callback 43 | 44 | helper.deleteUserWithRepos = (username, repoOneId, repoTwoId, callback) -> 45 | helper.deleteRepo repoOneId, (err) -> 46 | if err 47 | callback err 48 | else 49 | helper.deleteUserWithRepo username, repoTwoId, callback 50 | 51 | helper.createUserWithRepos = (username, firstReponame, firstRepotype, secondReponame, secondRepotype, mainCallback) -> 52 | # create user 53 | step1 = (callback) -> 54 | helper.createUser username, callback 55 | # create first repo 56 | step2 = (user, callback) -> 57 | MassiveGit.initRepo firstReponame, username, firstRepotype, (err, repo) -> 58 | should.not.exist err 59 | repo.id().should.equal username + "$" + firstReponame 60 | callback err, repo 61 | # create second 62 | step3 = (firstRepo, callback) -> 63 | MassiveGit.initRepo secondReponame, username, secondRepotype, (err, repo) -> 64 | should.not.exist err 65 | repo.id().should.equal username + "$" + secondReponame 66 | callback err, [firstRepo, repo] 67 | 68 | async.waterfall [step1, step2, step3], (err, results) -> 69 | mainCallback err, results 70 | 71 | 72 | helper.deleteAll = -> 73 | usersDao.removeAll() 74 | reposDao.removeAll() 75 | commitsDao.removeAll() 76 | treesDao.removeAll() 77 | blobsDao.removeAll() 78 | 79 | -------------------------------------------------------------------------------- /test/input.validators.coffee: -------------------------------------------------------------------------------- 1 | should = require "should" 2 | validators = require "../lib/validators/input.validators" 3 | 4 | describe "user validation", -> 5 | describe "with valid user", -> 6 | it "should just work", -> 7 | validators.validateUser("anton", "anton@circuithub.com").isValid().should.be.true 8 | describe "with null username", -> 9 | it "should return 'Invalid parameters' error", -> 10 | validators.validateUser(null, "anton@circuithub.com").errorMessage.should.equal "Invalid parameters" 11 | describe "with undefined username", -> 12 | it "should return 'Invalid parameters' error", -> 13 | validators.validateUser(undefined, "anton@circuithub.com").errorMessage.should.equal "Invalid parameters" 14 | describe "with empty username", -> 15 | it "should return 'Invalid parameters' error", -> 16 | validators.validateUser("", "anton@circuithub.com").errorMessage.should.equal "Invalid parameters" 17 | describe "with blank username", -> 18 | it "should return 'Invalid parameters' error", -> 19 | validators.validateUser(" ", "anton@circuithub.com").errorMessage.should.equal "Invalid parameters" 20 | describe "with short username", -> 21 | it "should return 'Invalid parameters' error", -> 22 | validators.validateUser("bc", "anton@circuithub.com").errorMessage.should.equal "Username is out of range" 23 | describe "with long username", -> 24 | it "should return 'Invalid parameters' error", -> 25 | validators.validateUser("123456789azwsxedcrfcrfvtgb", "anton@circuithub.com").errorMessage.should.equal "Username is out of range" 26 | describe "with null email", -> 27 | it "should return 'Invalid parameters' error", -> 28 | validators.validateUser("anton", null).errorMessage.should.equal "Invalid parameters" 29 | describe "with undefined email", -> 30 | it "should return 'Invalid parameters' error", -> 31 | validators.validateUser("anton", undefined).errorMessage.should.equal "Invalid parameters" 32 | describe "with empty email", -> 33 | it "should return 'Invalid parameters' error", -> 34 | validators.validateUser("anton", "").errorMessage.should.equal "Invalid parameters" 35 | describe "with blank email", -> 36 | it "should return 'Invalid parameters' error", -> 37 | validators.validateUser("anton", " ").errorMessage.should.equal "Invalid parameters" 38 | describe "with invalid email", -> 39 | it "should return 'Email address is invalid' error", -> 40 | validators.validateUser("anton", "anton@com").errorMessage.should.equal "Email address is invalid" 41 | 42 | describe "repo validation", -> 43 | describe "with valid repo", -> 44 | it "should work fine", -> 45 | validators.validateRepo("project-one", "anton").isValid().should.be.true 46 | describe "with null repo name", -> 47 | it "should return 'Invalid parameters' error", -> 48 | validators.validateRepo(null, "anton").errorMessage.should.equal "Invalid parameters" 49 | describe "with undefined repo name", -> 50 | it "should return 'Invalid parameters' error", -> 51 | validators.validateRepo(undefined, "anton").errorMessage.should.equal "Invalid parameters" 52 | describe "with empty repo name", -> 53 | it "should return 'Invalid parameters' error", -> 54 | validators.validateRepo("", "anton").errorMessage.should.equal "Invalid parameters" 55 | describe "with blank repo name", -> 56 | it "should return 'Invalid parameters' error", -> 57 | validators.validateRepo(" ", "anton").errorMessage.should.equal "Invalid parameters" 58 | describe "with short repo name", -> 59 | it "should return 'Repository name is out of range' error", -> 60 | validators.validateRepo("bc", "anton").errorMessage.should.equal "Repository name is out of range" 61 | describe "with long repo name", -> 62 | it "should return 'Repository name is out of range' error", -> 63 | validators.validateRepo("123456789azwsxedcrfcrfvtgb", "anton").errorMessage.should.equal "Repository name is out of range" 64 | describe "with null author name", -> 65 | it "should return 'Invalid parameters' error", -> 66 | validators.validateRepo("project-one", null).errorMessage.should.equal "Invalid parameters" 67 | describe "with undefined author name", -> 68 | it "should return 'Invalid parameters' error", -> 69 | validators.validateRepo("project-one", undefined).errorMessage.should.equal "Invalid parameters" 70 | describe "with empty author name", -> 71 | it "should return 'Invalid parameters' error", -> 72 | validators.validateRepo("project-one", "").errorMessage.should.equal "Invalid parameters" 73 | describe "with blank author name", -> 74 | it "should return 'Invalid parameters' error", -> 75 | validators.validateRepo("project-one", " ").errorMessage.should.equal "Invalid parameters" 76 | 77 | -------------------------------------------------------------------------------- /test/massive.git.utils.coffee: -------------------------------------------------------------------------------- 1 | should = require "should" 2 | _ = require "underscore" 3 | Blob = require("../lib/objects/blob").Blob 4 | TreeEntry = require("../lib/objects/tree.entry").TreeEntry 5 | MassiveGit = new (require("../lib/massive.git").MassiveGit)() 6 | 7 | 8 | describe "create blob from massive git", -> 9 | blob1 = new Blob "blob-content" 10 | blob2 = MassiveGit.createBlob "blob-content" 11 | it "should works in the same way as invoking contructor", -> 12 | blob1.equals(blob2).should.be.ok 13 | 14 | describe "create tree entry from massive git", -> 15 | blob1 = new Blob "blob-content" 16 | treeEntry1 = new TreeEntry "entry.json", blob1 17 | treeEntry2 = MassiveGit.createTreeEntry "entry.json", new Blob("blob-content") 18 | it "should works in the same way as invoking contructor", -> 19 | _.isEqual(treeEntry1.attributes(), treeEntry2.attributes()).should.be.ok 20 | _.isEqual(treeEntry1.name, treeEntry2.name).should.be.ok 21 | 22 | -------------------------------------------------------------------------------- /test/repo.coffee: -------------------------------------------------------------------------------- 1 | should = require "should" 2 | Repo = require("../lib/objects/repo").Repo 3 | 4 | describe "new Repo", -> 5 | describe "with regular parameters", -> 6 | repo = new Repo("project1", "anton", "project") 7 | it "should have correct properties", -> 8 | repo.id().should.equal "anton$project1" 9 | repo.should.have.property "type", "project" 10 | repo.should.have.property "author", "anton" 11 | repo.should.have.property "name", "project1" 12 | repo.public.should.be.ok 13 | should.not.exist repo.forkedFrom 14 | repo.attributes().should.have.property "type", "project" 15 | repo.attributes().public.should.be.ok 16 | repo.links().should.have.length(1) 17 | repo.getLink("author").should.equal "anton" 18 | describe "with private flag", -> 19 | repo = new Repo("project1", "anton", "project", false) 20 | it "should have correct properties", -> 21 | repo.id().should.equal "anton$project1" 22 | repo.should.have.property "type", "project" 23 | repo.should.have.property "author", "anton" 24 | repo.should.have.property "name", "project1" 25 | repo.public.should.be.not.ok 26 | should.not.exist repo.forkedFrom 27 | describe "forked from another repo", -> 28 | repo = new Repo("project1", "anton", "project", false, null, "andrew$project1") 29 | it "should have correct properties", -> 30 | repo.id().should.equal "anton$project1" 31 | repo.should.have.property "type", "project" 32 | repo.should.have.property "author", "anton" 33 | repo.should.have.property "name", "project1" 34 | repo.public.should.be.not.ok 35 | repo.should.have.property "forkedFrom", "andrew$project1" 36 | repo.links().should.have.length(2) 37 | repo.getLink("author").should.equal "anton" 38 | repo.getLink("forked_from").should.equal "andrew$project1" 39 | describe "created from another repo", -> 40 | repo = new Repo("project1", "anton", "project", false, "4ca68e7f293e0b7445beda64f0f8fe854682a0ac") 41 | it "should have correct properties", -> 42 | repo.id().should.equal "anton$project1" 43 | repo.should.have.property "type", "project" 44 | repo.should.have.property "author", "anton" 45 | repo.should.have.property "name", "project1" 46 | repo.public.should.be.not.ok 47 | repo.should.have.property "commit", "4ca68e7f293e0b7445beda64f0f8fe854682a0ac" 48 | repo.links().should.have.length(2) 49 | repo.getLink("author").should.equal "anton" 50 | repo.getLink("commit").should.equal "4ca68e7f293e0b7445beda64f0f8fe854682a0ac" 51 | 52 | describe "forked repo", -> 53 | repo = new Repo("project1", "anton", "project", false, null, "andrew$project1") 54 | forked = repo.fork("new-project-name", "peter") 55 | it "should have same properties from original and forked field", -> 56 | exports.testForkRepo = -> 57 | forked.id().should.equal "peter$new-project-name" 58 | forked.should.have.property "name", "new-project-name" 59 | forked.should.have.property "author", "peter" 60 | forked.public.should.be.not.ok 61 | should.not.exist forked.commit 62 | forked.should.have.property "forkedFrom", "anton$project1" 63 | 64 | -------------------------------------------------------------------------------- /test/repo.commit.coffee: -------------------------------------------------------------------------------- 1 | should = require "should" 2 | _ = require "underscore" 3 | Repo = require("../lib/objects/repo").Repo 4 | Blob = require("../lib/objects/blob").Blob 5 | TreeEntry = require("../lib/objects/tree.entry").TreeEntry 6 | MassiveGit = new (require("../lib/massive.git").MassiveGit)() 7 | helper = require "./helper/helper" 8 | 9 | describe "MassiveGit", -> 10 | blob1 = new Blob "test-content" 11 | blob2 = new Blob "1111" 12 | repo1Name = "repo1" + Math.floor(1000 * Math.random()) 13 | username = "random-user" + Math.floor(100000 * Math.random()) 14 | repo1Id = username + "$" + repo1Name 15 | before (done) -> 16 | helper.createUserWithRepo username, repo1Name, "part", done 17 | describe "#commit() two entries", -> 18 | entries = [new TreeEntry("symbol.json", blob2), new TreeEntry("datasheet.json", blob1)] 19 | it "should just normaly commit them", (done) -> 20 | MassiveGit.commit entries, repo1Id, username, "first commit", undefined, done 21 | describe "and then MassiveGit#getRepo() and MassiveGit#fetchRootEntries()", -> 22 | it "should return two files", (done) -> 23 | MassiveGit.getRepo repo1Id, (err, repo) -> 24 | commit = repo.getLink("commit") 25 | MassiveGit.fetchRootEntriesForCommit commit, (err, entries) -> 26 | console.log "CC>>", commit, entries, repo, err 27 | should.not.exist err 28 | console.log "fetching entries for commit", commit, entries 29 | entries.should.have.length 2 30 | blob1Copy = (entry.entry for entry in entries when entry.name == "datasheet.json")[0] 31 | blob2Copy = (entry.entry for entry in entries when entry.name == "symbol.json")[0] 32 | blob1Copy.equals(blob1).should.be.ok 33 | blob2Copy.equals(blob2).should.be.ok 34 | done err 35 | describe "and then MassiveGit#getRepo() and MassiveGit#getCommit()", -> 36 | it "should return two files", (done) -> 37 | MassiveGit.getRepo repo1Id, (err, repo) -> 38 | commit = repo.getLink("commit") 39 | MassiveGit.getCommit commit, (err, commit) -> 40 | should.not.exist err 41 | commit.should.have.property "author", username 42 | commit.should.have.property "committer", username 43 | commit.should.have.property "message", "first commit" 44 | commit.should.have.property "repo", repo1Id 45 | commit.authoredDate.should.exist 46 | commit.commitedDate.should.exist 47 | console.log "COMMIT", commit 48 | should.not.exist commit.parent 49 | should.not.exist commit.getLink "parent" 50 | done err 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /test/repo.delete.coffee: -------------------------------------------------------------------------------- 1 | should = require "should" 2 | _ = require "underscore" 3 | MassiveGit = new (require("../lib/massive.git").MassiveGit)() 4 | helper = require "./helper/helper" 5 | 6 | describe "MassiveGit", -> 7 | repo1Name = "part-1-delete" + Math.floor(1000 * Math.random()) 8 | repo2Name = "part-2-delete" + Math.floor(1000 * Math.random()) 9 | username = "random-user" + Math.floor(1000 * Math.random()) 10 | repo1Id = username + "$" + repo1Name 11 | repo2Id = username + "$" + repo2Name 12 | before (done) -> 13 | helper.createUserWithRepos username, repo1Name, "part", repo2Name, "part", done 14 | describe "#deleteRepo()", -> 15 | describe "fake repo", -> 16 | it "should return error 'Repo wasn't found'", (done) -> 17 | MassiveGit.deleteRepo "fake-repo-name$fake-username", username, (err, user) -> 18 | err.should.have.property "message", "Repo wasn't found" 19 | err.should.have.property "statusCode", 400 20 | done() 21 | describe "existent repo", -> 22 | it "should return user with update link property", (done) -> 23 | MassiveGit.deleteRepo repo1Id, username, (err, user) -> 24 | should.not.exist err 25 | user.links().should.have.length 1 26 | done err 27 | describe "MassiveGit#getUserRepos()", -> 28 | it "should return correct array of repos", (done) -> 29 | MassiveGit.getUserRepos username, (err, repos) -> 30 | repos.should.have.length 1 31 | done err 32 | after (done) -> 33 | helper.deleteUserWithRepo username, repo2Id, done 34 | 35 | -------------------------------------------------------------------------------- /test/repo.fork.coffee: -------------------------------------------------------------------------------- 1 | should = require "should" 2 | MassiveGit = new (require("../lib/massive.git").MassiveGit)() 3 | helper = require "./helper/helper" 4 | 5 | describe "MassiveGit#forkRepo()", -> 6 | partName = "part" + Math.floor(1000 * Math.random()) 7 | forkedPartName = "forked-part" + Math.floor(1000 * Math.random()) 8 | username = "random-user" + Math.floor(1000 * Math.random()) 9 | repoId = username + "$" + partName 10 | forkedRepoId = username + "$" + forkedPartName 11 | before (done) -> 12 | helper.createUserWithRepo username, partName, "part", done 13 | describe "with same user", -> 14 | it "should return forked repo object", (done) -> 15 | MassiveGit.forkRepo repoId, forkedPartName, username, (err, forkedRepo) -> 16 | should.not.exist err 17 | forkedRepo.forkedFrom.should.equal repoId 18 | forkedRepo.id().should.equal forkedRepoId 19 | forkedRepo.should.have.property "name", forkedPartName 20 | forkedRepo.should.have.property "author", username 21 | forkedRepo.public.should.be.ok 22 | should.not.exist forkedRepo.commit 23 | done err 24 | describe "with another user", -> 25 | forkedUsername = "another-user" + Math.floor(1000 * Math.random()) 26 | before (done) -> 27 | helper.createUser forkedUsername, done 28 | it "should return forked repo object", (done) -> 29 | MassiveGit.forkRepo repoId, forkedPartName, forkedUsername, (err, forkedRepo) -> 30 | should.not.exist err 31 | forkedRepo.forkedFrom.should.equal repoId 32 | forkedRepo.id().should.equal forkedUsername + "$" + forkedPartName 33 | forkedRepo.should.have.property "name", forkedPartName 34 | forkedRepo.should.have.property "author", forkedUsername 35 | forkedRepo.public.should.be.ok 36 | should.not.exist forkedRepo.commit 37 | done err 38 | after (done) -> 39 | helper.deleteUser forkedUsername, done 40 | after (done) -> 41 | helper.deleteUserWithRepos username, repoId, forkedRepoId, done 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /test/repo.history.coffee: -------------------------------------------------------------------------------- 1 | should = require "should" 2 | _ = require "underscore" 3 | Repo = require("../lib/objects/repo").Repo 4 | Blob = require("../lib/objects/blob").Blob 5 | TreeEntry = require("../lib/objects/tree.entry").TreeEntry 6 | MassiveGit = new (require("../lib/massive.git").MassiveGit)() 7 | helper = require "./helper/helper" 8 | 9 | # 10 | testCommitUpdate = (beforeExit) -> 11 | blob1 = new Blob JSON.stringify {one:1, two: 2} 12 | blob2 = new Blob "some-string" 13 | randomPartName = "part" + Math.floor(1000 * Math.random()) 14 | repoId = "anton$" + randomPartName 15 | username = "un-for-history" + Math.floor(1000 * Math.random()) 16 | # create user with repo 17 | step1 = (callback) -> 18 | helper.createUserWithRepo username, randomPartName, "part", callback 19 | # commit 1 file 20 | step2 = (repo, callback) -> 21 | blob = MassiveGit.createBlob(JSON.stringify({one:1, two: 2}), repo.id()) 22 | entries = [MassiveGit.createTreeEntry "datasheet.json", blob] 23 | MassiveGit.commit entries, repo.id(), username, "first commit", undefined, callback 24 | # get entries from commit 25 | step3 = (commitId, callback) -> 26 | MassiveGit.fetchRootEntriesForCommit commitId, (err, entries) -> 27 | should.not.exist err 28 | entries.should.have.length 1 29 | blob1Copy = entries[0].entry 30 | _.isEqual(blob1Copy.data, blob1.data).should.be.ok 31 | callback undefined, commitId 32 | # get commit and check it 33 | step4 = (commitId, callback) -> 34 | MassiveGit.getCommit commitId, (err, commit) -> 35 | should.not.exist err 36 | commit.should.have.property "author", username 37 | commit.should.have.property "committer", username 38 | commit.should.have.property "message", "first commit" 39 | commit.should.have.property "repo", repoId 40 | commit.authoredDate.should.exist 41 | commit.commitedDate.should.exist 42 | commit.tree.should.exist 43 | callback err, commit 44 | # get blobs from tree 45 | step5 = (commit, callback) -> 46 | MassiveGit.getBlobs commit.tree, (err, blobs) -> 47 | should.not.exist err 48 | blobs.should.have.length 1 49 | blob1Copy = blobs[0] 50 | _.isEqual(blob1Copy.data, blob1.data).should.be.ok 51 | callback err, commit 52 | # get tree and check it 53 | step6 = (commit, callback) -> 54 | MassiveGit.getTree commit.tree, (err, tree) -> 55 | should.not.exist err 56 | tree.should.have.property "repo", repoId 57 | tree.getLinks("blob").should.have.length 1 58 | tree.getLinks("blob")[0].should.equal blob1.id() 59 | callback err, commit 60 | # commit new blob 61 | step7 = (commit, callback) -> 62 | entries = [MassiveGit.createTreeEntry "symbol.json", "some-string"] 63 | MassiveGit.addToIndex entries, repoId, "andrew", "add new file symbol.json", (err, commitId) -> 64 | should.not.exist err 65 | console.log "Commit>>", commit.id(), "updated to", commitId 66 | callback err, commitId 67 | # get commit and check it 68 | step8 = (commitId, callback) -> 69 | MassiveGit.getCommit commitId, (err, commit) -> 70 | should.not.exist err 71 | commit.should.have.property "author", "andrew" 72 | commit.should.have.property "committer", "andrew" 73 | commit.should.have.property "message", "add new file symbol.json" 74 | commit.should.have.property "repo", repoId 75 | commit.authoredDate.should.exist 76 | commit.commitedDate.should.exist 77 | commit.tree.should.exist 78 | callback err, commit 79 | # get entries for commit 80 | step9 = (commit, callback) -> 81 | MassiveGit.fetchRootEntriesForCommit commit.id(), (err, entries) -> 82 | should.not.exist err 83 | entries.should.have.length 2 # todo (anton) why we have 1 here??? 84 | 85 | 86 | -------------------------------------------------------------------------------- /test/repo.init.coffee: -------------------------------------------------------------------------------- 1 | should = require "should" 2 | MassiveGit = new (require("../lib/massive.git").MassiveGit)() 3 | helper = require "./helper/helper" 4 | 5 | describe "init repo", -> 6 | describe "with fake user", -> 7 | randomProjectName = "project" + Math.floor(1000000 * Math.random()) 8 | username = "unique-fake-user-name" + Math.floor(1000000 * Math.random()) 9 | it "should return 'User wasn't found' error", (done) -> 10 | MassiveGit.initRepo randomProjectName, username, "project", (err, repo) -> 11 | err.should.have.property "message", "User wasn't found" 12 | err.should.have.property "statusCode", 400 13 | done() 14 | describe "with undefined repo name", -> 15 | username = "fake-user-name" + Math.floor(1000 * Math.random()) 16 | it "should return 'Invalid parameters' error", (done) -> 17 | MassiveGit.initRepo undefined, username, "project", (err, repo) -> 18 | should.exist err 19 | err.should.have.property "message", "Invalid parameters" 20 | err.should.have.property "statusCode", 422 21 | should.not.exist repo 22 | done() 23 | describe "with null repo name", -> 24 | username = "fake-user-name" + Math.floor(1000 * Math.random()) 25 | it "should return 'Invalid parameters' error", (done) -> 26 | MassiveGit.initRepo null, username, "project", (err, repo) -> 27 | should.exist err 28 | err.should.have.property "message", "Invalid parameters" 29 | err.should.have.property "statusCode", 422 30 | should.not.exist repo 31 | done() 32 | describe "with undefined user name", -> 33 | it "should return 'Invalid parameters' error", (done) -> 34 | MassiveGit.initRepo "repo1", undefined, "project", (err, repo) -> 35 | should.exist err 36 | err.should.have.property "message", "Invalid parameters" 37 | err.should.have.property "statusCode", 422 38 | should.not.exist repo 39 | done() 40 | describe "with null user name", -> 41 | it "should return 'Invalid parameters' error", (done) -> 42 | MassiveGit.initRepo "repo1", null, "project", (err, repo) -> 43 | should.exist err 44 | err.should.have.property "message", "Invalid parameters" 45 | err.should.have.property "statusCode", 422 46 | should.not.exist repo 47 | done() 48 | describe "with existent repo name", -> 49 | repoName = "project" + Math.floor(1000 * Math.random()) 50 | username = "some-user-name" + Math.floor(1000 * Math.random()) 51 | before (done) -> 52 | helper.createUserWithRepo username, repoName, "project", done 53 | it "should return 'Repo already exists' error", (done) -> 54 | MassiveGit.initRepo repoName, username, "project", (err, repo) -> 55 | should.exist err 56 | err.should.have.property "message", "Repo already exists" 57 | err.should.have.property "statusCode", 422 58 | should.not.exist repo 59 | done() 60 | after (done) -> 61 | helper.deleteUser username, done 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /test/tree.coffee: -------------------------------------------------------------------------------- 1 | should = require "should" 2 | Blob = require("../lib/objects/blob").Blob 3 | Tree = require("../lib/objects/tree").Tree 4 | TreeEntry = require("../lib/objects/tree.entry").TreeEntry 5 | 6 | describe "new Tree", -> 7 | describe "with plain structure", -> 8 | blob1 = new Blob("test-content", "anton$project1") 9 | blob2 = new Blob("1111", "anton-project1") 10 | entries = [] 11 | entries.push new TreeEntry("symbol.json", blob2).attributes() 12 | entries.push new TreeEntry("datasheet.json", blob1).attributes() 13 | tree = new Tree(entries, "anton$project1") 14 | it "should have correct properties", -> 15 | tree.id().should.equal "7a8b327d8ec3e00838b350a59887c4ae6c183928" 16 | tree.entries.should.have.length 2 17 | tree.entries.should.equal tree.attributes().entries 18 | describe "with hierarchical structure", -> 19 | blob1 = new Blob("test-content", "anton$project1") 20 | blob2 = new Blob("1111", "anton-project1") 21 | subtreeEntries = [] 22 | subtreeEntries.push new TreeEntry("symbol.json", blob2).attributes() 23 | subtree = new Tree(subtreeEntries, "anton-project1") 24 | subtree.id().should.equal "b1c610ed5d646a401e710d3a110d04431cfd231e" 25 | subtree.entries.should.have.length 1 26 | entries = [] 27 | entries.push new TreeEntry("datasheet.json", blob1).attributes() 28 | entries.push new TreeEntry("js", subtree).attributes() 29 | tree = new Tree(entries, "anton$project1") 30 | it "should have correct properties", -> 31 | tree.id().should.equal "260f756862c7fc027f275aaec07ba4fa2139b0e9" 32 | tree.entries.should.have.length 2 33 | tree.entries.should.equal tree.attributes().entries 34 | 35 | -------------------------------------------------------------------------------- /test/user.coffee: -------------------------------------------------------------------------------- 1 | should = require "should" 2 | User = require("../lib/objects/user").User 3 | 4 | describe "new User", -> 5 | user = new User("anton", "anton@circuithub.com") 6 | it "should have correct proeprties", -> 7 | user.id().should.equal "anton" 8 | user.should.have.property "email", "anton@circuithub.com" 9 | user.attributes().should.have.property "email", "anton@circuithub.com" 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/user.new.coffee: -------------------------------------------------------------------------------- 1 | should = require "should" 2 | MassiveGit = new (require("../lib/massive.git").MassiveGit)() 3 | helper = require "./helper/helper" 4 | 5 | describe "new user", -> 6 | describe "with correct parameters", -> 7 | username = "new-user-name" + Math.floor(100000 * Math.random()) 8 | it "should return user object with properties", (done) -> 9 | MassiveGit.newUser username, "hello@circuithub.com", (err, user) -> 10 | should.not.exist err 11 | should.exist user 12 | user.id().should.equal username 13 | user.should.have.property "email", "hello@circuithub.com" 14 | done err 15 | describe "with invalid email", -> 16 | username = "new-user-name" + Math.floor(1000 * Math.random()) 17 | it "should return error 'Email address is invalid'", (done) -> 18 | MassiveGit.newUser username, "circuithub.com", (err, user) -> 19 | should.exist err 20 | err.should.have.property "statusCode", 422 21 | err.should.have.property "message", "Email address is invalid" 22 | should.not.exist user 23 | done() 24 | describe "with null email", -> 25 | username = "new-user-name" + Math.floor(1000 * Math.random()) 26 | it "should return error 'Invalid parameters'", (done) -> 27 | MassiveGit.newUser username, null, (err, user) -> 28 | should.exist err 29 | err.should.have.property "statusCode", 422 30 | err.should.have.property "message", "Invalid parameters" 31 | done() 32 | describe "with undefined email", -> 33 | username = "new-user-name" + Math.floor(1000 * Math.random()) 34 | it "should return error 'Invalid parameters'", (done) -> 35 | MassiveGit.newUser username, undefined, (err, user) -> 36 | should.exist err 37 | err.should.have.property "statusCode", 422 38 | err.should.have.property "message", "Invalid parameters" 39 | should.not.exist user 40 | done() 41 | describe "with null username", -> 42 | it "should return error 'Invalid parameters'", (done) -> 43 | MassiveGit.newUser null, "anton@circuithub.com", (err, user) -> 44 | should.exist err 45 | err.should.have.property "statusCode", 422 46 | err.should.have.property "message", "Invalid parameters" 47 | should.not.exist user 48 | done() 49 | describe "with undefined username", -> 50 | it "should return error 'Invalid parameters'", (done) -> 51 | MassiveGit.newUser undefined, "hello@circuithub.com", (err, user) -> 52 | should.exist err 53 | err.should.have.property "statusCode", 422 54 | err.should.have.property "message", "Invalid parameters" 55 | should.not.exist user 56 | done() 57 | describe "with empty username", -> 58 | it "should return error 'Invalid parameters'", (done) -> 59 | MassiveGit.newUser "", "hello@circuithub.com", (err, user) -> 60 | should.exist err 61 | err.should.have.property "statusCode", 422 62 | err.should.have.property "message", "Invalid parameters" 63 | should.not.exist user 64 | done() 65 | describe "with blank username", -> 66 | it "should return error 'Invalid parameters'", (done) -> 67 | MassiveGit.newUser " ", "hello@circuithub.com", (err, user) -> 68 | should.exist err 69 | err.should.have.property "statusCode", 422 70 | err.should.have.property "message", "Invalid parameters" 71 | should.not.exist user 72 | done() 73 | describe "with short username", -> 74 | randomName = "" + Math.floor(100 * Math.random()) 75 | it "should return error 'Username is out of range'", (done) -> 76 | MassiveGit.newUser randomName, "hello@circuithub.com", (err, user) -> 77 | should.exist err 78 | err.should.have.property "statusCode", 422 79 | err.should.have.property "message", "Username is out of range" 80 | should.not.exist user 81 | done() 82 | describe "with long username", -> 83 | randomName = "aaaaaaaaaaaaaaaaaaaaa" + Math.floor(1000000000 * Math.random()) 84 | it "should return error 'Username is out of range'", (done) -> 85 | MassiveGit.newUser randomName, "hello@circuithub.com", (err, user) -> 86 | should.exist err 87 | err.should.have.property "statusCode", 422 88 | err.should.have.property "message", "Username is out of range" 89 | should.not.exist user 90 | done() 91 | 92 | 93 | --------------------------------------------------------------------------------