diff --git a/lib/bindings/bindings.js b/lib/bindings/bindings.js index 1d86fcc..6ebf237 100644 --- a/lib/bindings/bindings.js +++ b/lib/bindings/bindings.js @@ -186,10 +186,9 @@ Gitteh.error.GIT_ENOTIMPLEMENTED = undefined; Repository.prototype.getCommit = function(sha1, callback) {}; /** - * Creates a new Commit and immediately stores it in the Repository. - * @param {Object} data The data for the commit. Should be the same properties that a {@link Commit} contains. - * @param {Function} [callback] If provided, the Commit will be created asynchronously, and the newly created Commit object returned to the callback. - * @throws If Commit couldn't be created. + * Creates a new {@link Commit} and immediately stores it in the Repository. + * @param {Object} data The data for the commit. Should be the same properties that a {@link Commit} contains, but without id. + * @param {Function} [callback] The id of the newly created Commit is returned to the callback. */ Repository.prototype.createCommit = function(data, callback) {}; @@ -203,12 +202,11 @@ Repository.prototype.createCommit = function(data, callback) {}; Repository.prototype.getTree = function(sha1, callback) {}; /** - * Creates a new {@link Tree} in the Repository with the given data. NOTE: - * this is currently unimplemented. - * @param {Object} data - * @param {Function} [callback] + * Creates a new {@link Tree} in the Repository with the given entries. + * @param {Array} entries A list of file-entries from which to create the tree. Each entry should have a name, id (of the blob object) and mode. The mode should be any of the values 0040000, 0100644, 0100755, 0120000 or 0160000. + * @param {Function} [callback] The id of the newly created Tree is returned to the callback. */ -Repository.prototype.createTree = function(data, callback) {}; +Repository.prototype.createTree = function(entries, callback) {}; /** * Retrieves a {@link Reference} from the Repository with the given name. @@ -307,6 +305,13 @@ Repository.prototype.getTag = function(sha1, callback) {}; */ Repository.prototype.createBlob = function(data, callback) {}; +/** + * Creates a new {@link Blob} in the Repository. + * @param {String} path The path to a file from which data will be made into the Blob. + * @param {Function} [callback] The id of the newly created Blob is returned to the callback. + */ +Repository.prototype.createBlobFromDisk = function(path, callback) {}; + /** * Retrieves a Blob from the Repository with given id. * @param {String} sha1 The sha1 hash id of the blob. diff --git a/src/args.coffee b/src/args.coffee index 8ebe3aa..0eeaa6c 100644 --- a/src/args.coffee +++ b/src/args.coffee @@ -71,4 +71,10 @@ fn.validators = return objectTypes.indexOf val > -1 remoteDir: (val) -> - return remoteDirs.indexOf val > -1 \ No newline at end of file + return remoteDirs.indexOf val > -1 + + array: (val) -> + return val instanceof Array + + object: (val) -> + return typeof val is "object" \ No newline at end of file diff --git a/src/gitteh.coffee b/src/gitteh.coffee index fb8bf41..41b5173 100644 --- a/src/gitteh.coffee +++ b/src/gitteh.coffee @@ -188,7 +188,7 @@ Gitteh.Tree = class Tree * **id**: *(String)* OID this entry points to. * **name**: *(String)* file name of this entry. * **type**: *(String)* kind of object pointed to by this entry - * **attributes**: *(Number)* UNIX file attributes for this entry. + * **filemode**: *(Number)* UNIX file filemode for this entry. ### constructor: (@repository, obj) -> @@ -200,7 +200,7 @@ Gitteh.Tree = class Tree .set("id") .set("name") .set("type") - .set("attributes") + .set("filemode") _immutable(@, obj) .set("id") .set("entries") @@ -580,6 +580,70 @@ Gitteh.Repository = class Repository _priv.native.createRemote name, url, _wrapCallback cb, (remote) => return cb null, new Remote @, remote + createBlobFromDisk: -> + ### + Creates a new Blob for this repository from the file-content loaded + from `path`. Calls `cb` when the operation has completed. + ### + _priv = _getPrivate @ + [path, cb] = args + path: type: "string" + cb: type: "function" + _priv.native.createBlobFromDisk path, _wrapCallback cb, (blob) => + return cb null, blob + + createBlobFromBuffer: -> + ### + Creates a new Blob for this repository from the data supplied + as `buffer`. Calls `cb` when the operation has completed. + ### + _priv = _getPrivate @ + [buffer, cb] = args + buffer: type: "object" + cb: type: "function" + _priv.native.createBlobFromBuffer buffer, _wrapCallback cb, (blob) => + return cb null, blob + + createTree: -> + ### + Creates a new Tree for this repository from the `entities` + that contain the content (as a blob), the name and the filemode + of the tree-entities that will be in the new Tree. + Calls `cb` when the operation has completed. + ### + _priv = _getPrivate @ + [entities, cb] = args + entities: type: "array" + cb: type: "function" + _priv.native.createTree entities, _wrapCallback cb, (tree) => + return cb null, tree + + createCommit: -> + ### + Creates a new Commit for this repository from `data`. + `data` should contain the following keys: + + * **updateref**: updates this reference to the new commit. (optional, default: do not update any refs) + * **author**: a signature of the author (optional, default: committer is used) + * **committer**: a signature of the committer + * **message**: the message of the commit + * **tree**: the id of a tree object + * **parents**: an array of parents. + + A signature has the following keys: + + * **name**: the name of the author/committer + * **email**: the email of the author/committer + * **time**: the time of the commit (optional) + * **offset**: timezone offset of the commit-time (optional) + ### + _priv = _getPrivate @ + [data, cb] = args + data: type: "object" + cb: type: "function" + _priv.native.createCommit data, _wrapCallback cb, (commit) => + return cb null, commit + ###* * Alias of {@link #reference}. * @param {String} oid id of reference to be fetched. @@ -691,7 +755,7 @@ Gitteh.clone = => else if entry.type is "blob" repo.blob entry.id, _wrapCallback cb, (blob) -> file = fs.createWriteStream _path.join(dest, entry.name), - mode: entry.attributes + mode: entry.filemode file.write blob.data file.end() cb() diff --git a/src/repository.cc b/src/repository.cc index 86f1a39..f969b0f 100644 --- a/src/repository.cc +++ b/src/repository.cc @@ -52,6 +52,22 @@ static Persistent ref_target_symbol; static Persistent object_id_symbol; static Persistent object_type_symbol; +static Persistent tree_entry_id_symbol; +static Persistent tree_entry_name_symbol; +static Persistent tree_entry_filemode_symbol; + +static Persistent signature_name_symbol; +static Persistent signature_email_symbol; +static Persistent signature_time_symbol; +static Persistent signature_offset_symbol; + +static Persistent commit_updateref_symbol; +static Persistent commit_author_symbol; +static Persistent commit_committer_symbol; +static Persistent commit_message_symbol; +static Persistent commit_parents_symbol; +static Persistent commit_tree_symbol; + class OpenRepoBaton : public Baton { public: string path ; @@ -171,6 +187,53 @@ class CreateRemoteBaton : public RepositoryBaton { CreateRemoteBaton(Repository *r) : RepositoryBaton(r) { } }; +class CreateBlobFromDiskBaton : public RepositoryBaton { +public: + string path; + git_oid id; + CreateBlobFromDiskBaton(Repository *r) : RepositoryBaton(r) { } +}; + +class CreateBlobFromBufferBaton : public RepositoryBaton { +public: + void *data; + int length; + git_oid id; + CreateBlobFromBufferBaton(Repository *r) : RepositoryBaton(r) { } +}; + +class TreeEntry { +public: + git_oid id; + string name; + git_filemode_t filemode; +}; +class CreateTreeBaton : public RepositoryBaton { +public: + list entries; + git_oid treeid; + CreateTreeBaton(Repository *r) : RepositoryBaton(r), entries() { } +}; + +class CommitSignature { +public: + string name; + string email; + git_time_t time; + int offset; +}; +class CreateCommitBaton : public RepositoryBaton { +public: + string update_ref; + CommitSignature author; + CommitSignature committer; + string message; + list parents; + git_oid treeid; + git_oid commitid; + CreateCommitBaton(Repository *r) : RepositoryBaton(r) { } +}; + Persistent Repository::constructor_template; Repository::Repository() { @@ -230,6 +293,25 @@ void Repository::Init(Handle target) { object_id_symbol = NODE_PSYMBOL("id"); object_type_symbol = NODE_PSYMBOL("_type"); + // Tree Entry symbols + tree_entry_id_symbol = NODE_PSYMBOL("id"); + tree_entry_name_symbol = NODE_PSYMBOL("name"); + tree_entry_filemode_symbol = NODE_PSYMBOL("filemode"); + + // Signature symbols + signature_name_symbol = NODE_PSYMBOL("name"); + signature_email_symbol = NODE_PSYMBOL("email"); + signature_time_symbol = NODE_PSYMBOL("time"); + signature_offset_symbol = NODE_PSYMBOL("offset"); + + // Commit symbols + commit_updateref_symbol = NODE_PSYMBOL("updateref"); + commit_author_symbol = NODE_PSYMBOL("author"); + commit_committer_symbol = NODE_PSYMBOL("committer"); + commit_message_symbol = NODE_PSYMBOL("message"); + commit_parents_symbol = NODE_PSYMBOL("parents"); + commit_tree_symbol = NODE_PSYMBOL("tree"); + Local t = FunctionTemplate::New(New); constructor_template = Persistent::New(t); constructor_template->SetClassName(repo_class_symbol); @@ -242,6 +324,10 @@ void Repository::Init(Handle target) { NODE_SET_PROTOTYPE_METHOD(t, "createSymReference", CreateSymReference); NODE_SET_PROTOTYPE_METHOD(t, "remote", GetRemote); NODE_SET_PROTOTYPE_METHOD(t, "createRemote", CreateRemote); + NODE_SET_PROTOTYPE_METHOD(t, "createBlobFromDisk", CreateBlobFromDisk); + NODE_SET_PROTOTYPE_METHOD(t, "createBlobFromBuffer", CreateBlobFromBuffer); + NODE_SET_PROTOTYPE_METHOD(t, "createTree", CreateTree); + NODE_SET_PROTOTYPE_METHOD(t, "createCommit", CreateCommit); NODE_SET_METHOD(target, "openRepository", OpenRepository); NODE_SET_METHOD(target, "initRepository", InitRepository); @@ -696,6 +782,249 @@ void Repository::AsyncAfterCreateRemote(uv_work_t *req) { delete baton; } +Handle Repository::CreateBlobFromDisk(const Arguments &args) { + HandleScope scope; + Repository *repository = ObjectWrap::Unwrap(args.This()); + + CreateBlobFromDiskBaton *baton = new CreateBlobFromDiskBaton(repository); + baton->path = CastFromJS(args[0]); + baton->setCallback(args[1]); + + uv_queue_work(uv_default_loop(), &baton->req, AsyncCreateBlobFromDisk, + NODE_094_UV_AFTER_WORK_CAST(AsyncAfterCreateBlobFromDisk)); + + return Undefined(); +} + +void Repository::AsyncCreateBlobFromDisk(uv_work_t *req) { + CreateBlobFromDiskBaton *baton = GetBaton(req); + + if(AsyncLibCall(git_blob_create_fromdisk(&baton->id, baton->repo->repo_, + baton->path.c_str()), baton)) { + } +} + +void Repository::AsyncAfterCreateBlobFromDisk(uv_work_t *req) { + HandleScope scope; + CreateBlobFromDiskBaton *baton = GetBaton(req); + + if(baton->isErrored()) { + Handle argv[] = { baton->createV8Error() }; + FireCallback(baton->callback, 1, argv); + } + else { + Handle id = CastToJS(baton->id); + Handle argv[] = { Null(), id }; + FireCallback(baton->callback, 2, argv); + } + + delete baton; +} + +Handle Repository::CreateBlobFromBuffer(const Arguments &args) { + HandleScope scope; + Repository *repository = ObjectWrap::Unwrap(args.This()); + + CreateBlobFromBufferBaton *baton = new CreateBlobFromBufferBaton(repository); + baton->data = Buffer::Data(args[0]); + baton->length = Buffer::Length(args[0]); + baton->setCallback(args[1]); + + uv_queue_work(uv_default_loop(), &baton->req, AsyncCreateBlobFromBuffer, + NODE_094_UV_AFTER_WORK_CAST(AsyncAfterCreateBlobFromBuffer)); + + return Undefined(); +} + +void Repository::AsyncCreateBlobFromBuffer(uv_work_t *req) { + CreateBlobFromBufferBaton *baton = GetBaton(req); + + if(AsyncLibCall(git_blob_create_frombuffer(&baton->id, baton->repo->repo_, + baton->data, baton->length), baton)) { + } +} + +void Repository::AsyncAfterCreateBlobFromBuffer(uv_work_t *req) { + HandleScope scope; + CreateBlobFromBufferBaton *baton = GetBaton(req); + + if(baton->isErrored()) { + Handle argv[] = { baton->createV8Error() }; + FireCallback(baton->callback, 1, argv); + } + else { + Handle id = CastToJS(baton->id); + Handle argv[] = { Null(), id }; + FireCallback(baton->callback, 2, argv); + } + + delete baton; +} + +Handle Repository::CreateTree(const Arguments &args) { + HandleScope scope; + Repository *repository = ObjectWrap::Unwrap(args.This()); + + CreateTreeBaton *baton = new CreateTreeBaton(repository); + Handle jsentries = args[0].As(); + for(unsigned int i=0;iLength();i++) { + TreeEntry entry; + Handle jsentry = jsentries->Get(i)->ToObject(); + entry.id = CastFromJS(jsentry->Get(tree_entry_id_symbol)); + entry.name = CastFromJS(jsentry->Get(tree_entry_name_symbol)); + entry.filemode = (git_filemode_t)CastFromJS(jsentry->Get(tree_entry_filemode_symbol)); + baton->entries.push_back(entry); + } + baton->setCallback(args[1]); + + uv_queue_work(uv_default_loop(), &baton->req, AsyncCreateTree, + NODE_094_UV_AFTER_WORK_CAST(AsyncAfterCreateTree)); + + return Undefined(); +} + +void Repository::AsyncCreateTree(uv_work_t *req) { + CreateTreeBaton *baton = GetBaton(req); + git_treebuilder *treebuilder; + if(AsyncLibCall(git_treebuilder_create(&treebuilder, NULL), baton)) { + for(list::iterator i = baton->entries.begin(); + i != baton->entries.end(); ++i) { + if(!AsyncLibCall(git_treebuilder_insert(NULL, treebuilder, + i->name.c_str(), &i->id, i->filemode), baton)) { + break; + } + } + if (git_treebuilder_entrycount(treebuilder) == baton->entries.size()) { + AsyncLibCall(git_treebuilder_write(&baton->treeid,baton->repo->repo_, + treebuilder), baton); + } + + } + + git_treebuilder_free(treebuilder); +} + +void Repository::AsyncAfterCreateTree(uv_work_t *req) { + HandleScope scope; + CreateTreeBaton *baton = GetBaton(req); + + if(baton->isErrored()) { + Handle argv[] = { baton->createV8Error() }; + FireCallback(baton->callback, 1, argv); + } + else { + Handle treeid = CastToJS(baton->treeid); + Handle argv[] = { Null(), treeid }; + FireCallback(baton->callback, 2, argv); + } + + delete baton; +} + +Handle Repository::CreateCommit(const Arguments &args) { + HandleScope scope; + Repository *repository = ObjectWrap::Unwrap(args.This()); + + CreateCommitBaton *baton = new CreateCommitBaton(repository); + Handle options = args[0].As(); + + baton->update_ref = options->Has(commit_updateref_symbol) + ? CastFromJS(options->Get(commit_updateref_symbol)) + : ""; + + Handle committer = options->Get(commit_committer_symbol).As(); + baton->committer.name = CastFromJS(committer->Get(signature_name_symbol)); + baton->committer.email = CastFromJS(committer->Get(signature_email_symbol)); + baton->committer.time = CastFromJS(committer->Get(signature_time_symbol)); + baton->committer.offset = CastFromJS(committer->Get(signature_offset_symbol)); + + if (options->Has(commit_author_symbol)) { + Handle author = options->Get(commit_author_symbol).As(); + baton->author.name = CastFromJS(author->Get(signature_name_symbol)); + baton->author.email = CastFromJS(author->Get(signature_email_symbol)); + baton->author.time = CastFromJS(author->Get(signature_time_symbol)); + baton->author.offset = CastFromJS(author->Get(signature_offset_symbol)); + } else { + baton->author = baton->committer; + } + + baton->message = CastFromJS(options->Get(commit_message_symbol)); + if (options->Has(commit_parents_symbol)) { + baton->parents = CastFromJS< list >(options->Get(commit_parents_symbol)); + } + baton->treeid = CastFromJS(options->Get(commit_tree_symbol)); + baton->setCallback(args[1]); + + uv_queue_work(uv_default_loop(), &baton->req, AsyncCreateCommit, + NODE_094_UV_AFTER_WORK_CAST(AsyncAfterCreateCommit)); + + return Undefined(); +} + +void Repository::AsyncCreateCommit(uv_work_t *req) { + CreateCommitBaton *baton = GetBaton(req); + + git_tree *tree; + std::vector parents; + for(list::iterator i = baton->parents.begin(); + i != baton->parents.end(); ++i) { + git_commit *commit; + if (AsyncLibCall(git_commit_lookup(&commit, baton->repo->repo_, + &(*i)), baton)) { + parents.push_back(commit); + } else { + break; + } + } + + if(parents.size() == baton->parents.size()) { + git_signature *committer; + if (AsyncLibCall(baton->committer.time == 0 + ? git_signature_now(&committer, baton->committer.name.c_str(), baton->committer.email.c_str()) + : git_signature_new(&committer, baton->committer.name.c_str(), baton->committer.email.c_str(), + baton->committer.time, baton->committer.offset), baton)) { + git_signature *author; + if (AsyncLibCall(baton->author.time == 0 + ? git_signature_now(&author, baton->author.name.c_str(), baton->author.email.c_str()) + : git_signature_new(&author, baton->author.name.c_str(), baton->author.email.c_str(), + baton->author.time, baton->author.offset), baton)) { + if(AsyncLibCall(git_tree_lookup(&tree,baton->repo->repo_, + &baton->treeid), baton)) { + if(AsyncLibCall(git_commit_create(&baton->commitid,baton->repo->repo_, + baton->update_ref.empty() ? NULL : baton->update_ref.c_str(), + author, committer, NULL, baton->message.c_str(), tree, + parents.size(), (const git_commit**)&(parents[0])), baton)) { + + } + } + git_signature_free(author); + } + git_signature_free(committer); + } + } + + for(std::vector::iterator i = parents.begin(); i != parents.end(); ++i) { + git_commit_free(*i); + } +} + +void Repository::AsyncAfterCreateCommit(uv_work_t *req) { + HandleScope scope; + CreateCommitBaton *baton = GetBaton(req); + + if(baton->isErrored()) { + Handle argv[] = { baton->createV8Error() }; + FireCallback(baton->callback, 1, argv); + } + else { + Handle commitid = CastToJS(baton->commitid); + Handle argv[] = { Null(), commitid }; + FireCallback(baton->callback, 2, argv); + } + + delete baton; +} + Handle Repository::Exists(const Arguments& args) { HandleScope scope; Repository *repo = ObjectWrap::Unwrap(args.This()); diff --git a/src/repository.h b/src/repository.h index d94f002..36d8940 100644 --- a/src/repository.h +++ b/src/repository.h @@ -40,6 +40,10 @@ class Repository : public ObjectWrap { static Handle GetRemote(const Arguments&); static Handle Exists(const Arguments&); static Handle CreateRemote(const Arguments&); + static Handle CreateBlobFromDisk(const Arguments&); + static Handle CreateBlobFromBuffer(const Arguments&); + static Handle CreateTree(const Arguments&); + static Handle CreateCommit(const Arguments&); void close(); @@ -61,6 +65,14 @@ class Repository : public ObjectWrap { static void AsyncAfterGetRemote(uv_work_t*); static void AsyncCreateRemote(uv_work_t*); static void AsyncAfterCreateRemote(uv_work_t*); + static void AsyncCreateBlobFromDisk(uv_work_t*); + static void AsyncAfterCreateBlobFromDisk(uv_work_t*); + static void AsyncCreateBlobFromBuffer(uv_work_t*); + static void AsyncAfterCreateBlobFromBuffer(uv_work_t*); + static void AsyncCreateTree(uv_work_t*); + static void AsyncAfterCreateTree(uv_work_t*); + static void AsyncCreateCommit(uv_work_t*); + static void AsyncAfterCreateCommit(uv_work_t*); static Handle CreateReferenceObject(git_reference*);