diff --git a/generate/input/descriptor.json b/generate/input/descriptor.json index 05938ae73..4b431ad84 100644 --- a/generate/input/descriptor.json +++ b/generate/input/descriptor.json @@ -1278,6 +1278,53 @@ } } }, + "rebase": { + "functions": { + "git_rebase_commit": { + "args": { + "id": { + "isReturn": true, + "shouldAlloc": true + }, + "author": { + "isOptional": true + }, + "message_encoding": { + "isOptional": true + }, + "message": { + "isOptional": true + } + } + }, + "git_rebase_finish": { + "args": { + "signature": { + "isOptional": true + } + } + }, + "git_rebase_free": { + "ignore": true + }, + "git_rebase_init": { + "args": { + "upstream": { + "isOptional": true + }, + "onto": { + "isOptional": true + }, + "signature": { + "isOptional": true + }, + "opts": { + "isOptional": true + } + } + } + } + }, "refdb": { "functions": { "git_refdb_backend_fs": { diff --git a/generate/input/libgit2-supplement.json b/generate/input/libgit2-supplement.json index 3940797b1..0641d4a45 100644 --- a/generate/input/libgit2-supplement.json +++ b/generate/input/libgit2-supplement.json @@ -108,6 +108,28 @@ }, "new" : { "functions": { + "git_rebase_next": { + "type": "function", + "file": "rebase.h", + "args": [ + { + "name": "out", + "type": "git_rebase_operation **" + }, + { + "name": "rebase", + "type": "git_rebase *" + }, + { + "name": "checkout_opts", + "type": "git_checkout_options *" + } + ], + "return": { + "type": "int" + }, + "group": "rebase" + }, "git_reset": { "type": "function", "file": "reset.h", @@ -175,7 +197,7 @@ }, "groups": [ [ - "annotated", + "annotated_commit", [ "git_annotated_commit_free", "git_annotated_commit_from_fetchhead", @@ -498,6 +520,9 @@ } }, "groups": { + "rebase": [ + "git_rebase_next" + ], "reset": [ "git_reset" ], diff --git a/lib/rebase.js b/lib/rebase.js new file mode 100644 index 000000000..8caa5891f --- /dev/null +++ b/lib/rebase.js @@ -0,0 +1,23 @@ +var NodeGit = require("../"); +var Rebase = NodeGit.Rebase; +var normalizeOptions = NodeGit.Utils.normalizeOptions; + +// Override Rebase.prototype.finish to normalize opts +var finish = Rebase.prototype.finish; +Rebase.prototype.finish = function(signature, opts) { + opts = normalizeOptions(opts || {}, NodeGit.RebaseOptions); + return finish.call(this, signature, opts); +}; + +// Override Rebase.prototype.next to normalize opts and provide good defaults +var next = Rebase.prototype.next; +Rebase.prototype.next = function(checkoutOpts) { + if (!checkoutOpts) { + checkoutOpts = { + checkoutStrategy: NodeGit.Checkout.STRATEGY.SAFE_CREATE + }; + } + + checkoutOpts = normalizeOptions(checkoutOpts, NodeGit.CheckoutOptions); + return next.call(this, checkoutOpts); +}; diff --git a/lib/repository.js b/lib/repository.js index 2c9058269..5e75f4752 100644 --- a/lib/repository.js +++ b/lib/repository.js @@ -747,6 +747,117 @@ Repository.prototype.mergeBranches = function(to, from, signature) { }); }; +/** + * Goes through a rebase's rebase operations and commits them if there are + * no merge conflicts + * + * @param {Repository} repository The repository that the rebase is being + * performed in + * @param {Rebase} rebase The current rebase being performed + * @param {Signature} signature Identity of the one performing the rebase + * @return {Int|Index} An error code for an unsuccesful rebase or an index for + * a rebase with conflicts + */ +function performRebase(repository, rebase, signature) { + return rebase.next() + .then(function(rebaseOperation) { + return repository.openIndex() + .then(function(index) { + if (index.hasConflicts()) { + throw index; + } + + if (rebaseOperation) { + rebase.commit(null, signature); + + return performRebase(repository, rebase, signature); + } + + return rebase.finish(signature); + }); + }); +} + +/** + * Rebases a branch onto another branch + * + * @param {String} branch + * @param {String} upstream + * @param {String} onto + * @param {Signature} signature Identity of the one performing the rebase + * @return {Oid|Index} A commit id for a succesful merge or an index for a + * rebase with conflicts + */ +Repository.prototype.rebaseBranches = function( + branch, + upstream, + onto, + signature) +{ + var repo = this; + + signature = signature || repo.defaultSignature(); + + return Promise.all([ + repo.getReference(branch), + upstream ? repo.getReference(upstream) : null, + onto ? repo.getReference(upstream) : null + ]) + .then(function(refs) { + return Promise.all([ + NodeGit.AnnotatedCommit.fromRef(repo, refs[0]), + upstream ? NodeGit.AnnotatedCommit.fromRef(repo, refs[1]) : null, + onto ? NodeGit.AnnotatedCommit.fromRef(repo, refs[2]) : null + ]); + }) + .then(function(annotatedCommits) { + return NodeGit.Rebase.init(repo, annotatedCommits[0], annotatedCommits[1], + annotatedCommits[2], signature, null); + }) + .then(function(rebase) { + return performRebase(repo, rebase, signature); + }) + .then(function(error) { + if (error) { + throw error; + } + + return repo.getBranchCommit("HEAD"); + }); +}; + +/** + * Continues an existing rebase + * + * @param {Signature} signature Identity of the one performing the rebase + * @return {Oid|Index} A commit id for a succesful merge or an index for a + * rebase with conflicts + */ +Repository.prototype.continueRebase = function(signature) { + var repo = this; + + return repo.openIndex() + .then(function(index) { + if (index.hasConflicts()) { + throw index; + } + + return NodeGit.Rebase.open(repo); + }) + .then(function(rebase) { + rebase.commit(null, signature); + + return performRebase(repo, rebase, signature); + }) + .then(function(error) { + if (error) { + throw error; + } + + return repo.getBranchCommit("HEAD"); + }); +}; + // Override Repository.initExt to normalize initoptions var initExt = Repository.initExt; Repository.initExt = function(repo_path, opts) { diff --git a/lib/utils/normalize_options.js b/lib/utils/normalize_options.js index a7b73cb5e..2aac5658f 100644 --- a/lib/utils/normalize_options.js +++ b/lib/utils/normalize_options.js @@ -7,12 +7,16 @@ var NodeGit = require("../../"); * @return {Object} An Oid instance. */ function normalizeOptions(options, Ctor) { - var instance = options instanceof Ctor ? options : new Ctor(); - if (!options) { return null; } + if (options instanceof Ctor) { + return options; + } + + var instance = new Ctor(); + Object.keys(options).forEach(function(key) { instance[key] = options[key]; }); diff --git a/test/tests/rebase.js b/test/tests/rebase.js new file mode 100644 index 000000000..b05b8cfe8 --- /dev/null +++ b/test/tests/rebase.js @@ -0,0 +1,967 @@ +var assert = require("assert"); +var path = require("path"); +var local = path.join.bind(path, __dirname); +var Promise = require("nodegit-promise"); +var promisify = require("promisify-node"); +var fse = promisify(require("fs-extra")); + +describe("Rebase", function() { + var NodeGit = require("../../"); + + var repoPath = local("../repos/rebase"); + var ourBranchName = "ours"; + var theirBranchName = "theirs"; + + var addFileToIndex = function(repository, fileName) { + return repository.openIndex() + .then(function(index) { + index.read(1); + index.addByPath(fileName); + index.write(); + + return index.writeTree(); + }); + }; + + var removeFileFromIndex = function(repository, fileName) { + return repository.openIndex() + .then(function(index) { + index.read(1); + index.removeByPath(fileName); + index.write(); + + return index.writeTree(); + }); + }; + + beforeEach(function() { + var test = this; + return fse.remove(repoPath) + .then(function() { + return fse.ensureDir(repoPath); + }) + .then(function() { + return NodeGit.Repository.init(repoPath, 0); + }) + .then(function(repo) { + test.repository = repo; + }); + }); + + after(function() { + return fse.remove(repoPath); + }); + + it("can cleanly fast-forward via rebase", function() { + var ourFileName = "ourNewFile.txt"; + var theirFileName = "theirNewFile.txt"; + + var ourFileContent = "I like Toll Roads. I have an EZ-Pass!"; + var theirFileContent = "I'm skeptical about Toll Roads"; + + var ourSignature = NodeGit.Signature.create + ("Ron Paul", "RonPaul@TollRoadsRBest.info", 123456789, 60); + var theirSignature = NodeGit.Signature.create + ("Greg Abbott", "Gregggg@IllTollYourFace.us", 123456789, 60); + + var repository = this.repository; + var ourCommit; + var theirBranch; + + return fse.writeFile(path.join(repository.workdir(), ourFileName), + ourFileContent) + // Load up the repository index and make our initial commit to HEAD + .then(function() { + return addFileToIndex(repository, ourFileName); + }) + .then(function(oid) { + assert.equal(oid.toString(), + "11ead82b1135b8e240fb5d61e703312fb9cc3d6a"); + + return repository.createCommit("HEAD", ourSignature, + ourSignature, "we made a commit", oid, []); + }) + .then(function(commitOid) { + assert.equal(commitOid.toString(), + "91a183f87842ebb7a9b08dad8bc2473985796844"); + + return repository.getCommit(commitOid).then(function(commit) { + ourCommit = commit; + }).then(function() { + return repository.createBranch(ourBranchName, commitOid) + .then(function(branch) { + return repository.createBranch(theirBranchName, commitOid); + }); + }); + }) + .then(function(branch) { + theirBranch = branch; + return fse.writeFile(path.join(repository.workdir(), theirFileName), + theirFileContent); + }) + .then(function() { + return addFileToIndex(repository, theirFileName); + }) + .then(function(oid) { + assert.equal(oid.toString(), + "76631cb5a290dafe2959152626bb90f2a6d8ec94"); + + return repository.createCommit(theirBranch.name(), theirSignature, + theirSignature, "they made a commit", oid, [ourCommit]); + }) + .then(function(commitOid) { + assert.equal(commitOid.toString(), + "0e9231d489b3f4303635fc4b0397830da095e7e7"); + }) + .then(function() { + // unstage changes so that we can begin a rebase + return removeFileFromIndex(repository, theirFileName); + }) + .then(function() { + return Promise.all([ + repository.getReference(ourBranchName), + repository.getReference(theirBranchName) + ]); + }) + .then(function(refs) { + assert.equal(refs.length, 2); + + return Promise.all([ + NodeGit.AnnotatedCommit.fromRef(repository, refs[0]), + NodeGit.AnnotatedCommit.fromRef(repository, refs[1]) + ]); + }) + .then(function(annotatedCommits) { + assert.equal(annotatedCommits.length, 2); + + var ourAnnotatedCommit = annotatedCommits[0]; + var theirAnnotatedCommit = annotatedCommits[1]; + + assert.equal(ourAnnotatedCommit.id().toString(), + "91a183f87842ebb7a9b08dad8bc2473985796844"); + assert.equal(theirAnnotatedCommit.id().toString(), + "0e9231d489b3f4303635fc4b0397830da095e7e7"); + + return NodeGit.Rebase.init(repository, ourAnnotatedCommit, + theirAnnotatedCommit, theirAnnotatedCommit, ourSignature, + new NodeGit.RebaseOptions()); + }) + .then(function(rebase) { + assert.equal(rebase.operationEntrycount(), 0); + assert.equal(rebase.operationCurrent(), 0); + + return rebase.finish(ourSignature, new NodeGit.RebaseOptions()); + }) + .then(function() { + return repository.getBranchCommit(ourBranchName); + }) + .then(function(commit) { + assert.equal(commit.id().toString(), + "0e9231d489b3f4303635fc4b0397830da095e7e7"); + }); + }); + + it("can cleanly rebase a branch onto another branch", function() { + var baseFileName = "baseNewFile.txt"; + var ourFileName = "ourNewFile.txt"; + var theirFileName = "theirNewFile.txt"; + + var baseFileContent = "How do you feel about Toll Roads?"; + var ourFileContent = "I like Toll Roads. I have an EZ-Pass!"; + var theirFileContent = "I'm skeptical about Toll Roads"; + + var ourSignature = NodeGit.Signature.create + ("Ron Paul", "RonPaul@TollRoadsRBest.info", 123456789, 60); + var theirSignature = NodeGit.Signature.create + ("Greg Abbott", "Gregggg@IllTollYourFace.us", 123456789, 60); + + var repository = this.repository; + var ourCommit; + var ourBranch; + var theirBranch; + var rebase; + + return fse.writeFile(path.join(repository.workdir(), baseFileName), + baseFileContent) + // Load up the repository index and make our initial commit to HEAD + .then(function() { + return addFileToIndex(repository, baseFileName); + }) + .then(function(oid) { + assert.equal(oid.toString(), + "b5cdc109d437c4541a13fb7509116b5f03d5039a"); + + return repository.createCommit("HEAD", ourSignature, + ourSignature, "initial commit", oid, []); + }) + .then(function(commitOid) { + assert.equal(commitOid.toString(), + "be03abdf0353d05924c53bebeb0e5bb129cda44a"); + + return repository.getCommit(commitOid).then(function(commit) { + ourCommit = commit; + }).then(function() { + return repository.createBranch(ourBranchName, commitOid) + .then(function(branch) { + ourBranch = branch; + return repository.createBranch(theirBranchName, commitOid); + }); + }); + }) + .then(function(branch) { + theirBranch = branch; + return fse.writeFile(path.join(repository.workdir(), theirFileName), + theirFileContent); + }) + .then(function() { + return addFileToIndex(repository, theirFileName); + }) + .then(function(oid) { + assert.equal(oid.toString(), + "be5f0fd38a39a67135ad68921c93cd5c17fefb3d"); + + return repository.createCommit(theirBranch.name(), theirSignature, + theirSignature, "they made a commit", oid, [ourCommit]); + }) + .then(function(commitOid) { + assert.equal(commitOid.toString(), + "e9ebd92f2f4778baf6fa8e92f0c68642f931a554"); + + return removeFileFromIndex(repository, theirFileName); + }) + .then(function() { + return fse.remove(path.join(repository.workdir(), theirFileName)); + }) + .then(function() { + return fse.writeFile(path.join(repository.workdir(), ourFileName), + ourFileContent); + }) + .then(function() { + return addFileToIndex(repository, ourFileName); + }) + .then(function(oid) { + assert.equal(oid.toString(), + "77867fc0bfeb3f80ab18a78c8d53aa3a06207047"); + + return repository.createCommit(ourBranch.name(), ourSignature, + ourSignature, "we made a commit", oid, [ourCommit]); + }) + .then(function(commitOid) { + assert.equal(commitOid.toString(), + "e7f37ee070837052937e24ad8ba66f6d83ae7941"); + + return removeFileFromIndex(repository, ourFileName); + }) + .then(function() { + return fse.remove(path.join(repository.workdir(), ourFileName)); + }) + .then(function() { + return repository.checkoutBranch(ourBranchName); + }) + .then(function() { + return Promise.all([ + repository.getReference(ourBranchName), + repository.getReference(theirBranchName) + ]); + }) + .then(function(refs) { + assert.equal(refs.length, 2); + + return Promise.all([ + NodeGit.AnnotatedCommit.fromRef(repository, refs[0]), + NodeGit.AnnotatedCommit.fromRef(repository, refs[1]) + ]); + }) + .then(function(annotatedCommits) { + assert.equal(annotatedCommits.length, 2); + + var ourAnnotatedCommit = annotatedCommits[0]; + var theirAnnotatedCommit = annotatedCommits[1]; + + assert.equal(ourAnnotatedCommit.id().toString(), + "e7f37ee070837052937e24ad8ba66f6d83ae7941"); + assert.equal(theirAnnotatedCommit.id().toString(), + "e9ebd92f2f4778baf6fa8e92f0c68642f931a554"); + + return NodeGit.Rebase.init(repository, ourAnnotatedCommit, + theirAnnotatedCommit, null, ourSignature, null); + }) + .then(function(newRebase) { + rebase = newRebase; + + // there should only be 1 rebase operation to perform + assert.equal(rebase.operationEntrycount(), 1); + + var opts = new NodeGit.CheckoutOptions(); + opts.checkoutStrategy = NodeGit.Checkout.STRATEGY.SAFE_CREATE; + + return rebase.next(opts); + }) + .then(function(rebaseOperation) { + assert.equal(rebaseOperation.type(), + NodeGit.RebaseOperation.REBASE_OPERATION.PICK); + assert.equal(rebaseOperation.id().toString(), + "e7f37ee070837052937e24ad8ba66f6d83ae7941"); + + return rebase.commit(null, ourSignature); + }) + .then(function(commitOid) { + assert.equal(commitOid.toString(), + "b937100ee0ea17ef20525306763505a7fe2be29e"); + + // git_rebase_operation_current returns the index of the rebase + // operation that was last applied, so after the first operation, it + // should be 0. + assert.equal(rebase.operationCurrent(), 0); + + return rebase.finish(ourSignature, {}); + }) + .then(function(result) { + assert.equal(result, 0); + + return repository.getBranchCommit(ourBranchName); + }) + .then(function(commit) { + // verify that the "ours" branch has moved to the correct place + assert.equal(commit.id().toString(), + "b937100ee0ea17ef20525306763505a7fe2be29e"); + + return commit.parent(0); + }) + .then(function(commit) { + // verify that we are on top of "their commit" + assert.equal(commit.id().toString(), + "e9ebd92f2f4778baf6fa8e92f0c68642f931a554"); + }); + }); + + it("can rebase 2 branches with conflicts on a single file", function() { + var fileName = "everyonesFile.txt"; + + var baseFileContent = "How do you feel about Toll Roads?\n"; + var ourFileContent = "I like Toll Roads. I have an EZ-Pass!\n"; + var theirFileContent = "I'm skeptical about Toll Roads\n"; + + var expectedConflictedFileContent = + "How do you feel about Toll Roads?\n" + + "<<<<<<< theirs\n" + + "I'm skeptical about Toll Roads\n" + + "=======\n" + + "I like Toll Roads. I have an EZ-Pass!\n" + + ">>>>>>> we made a commit\n"; + + var conflictSolvedFileContent = + "How do you feel about Toll Roads?\n" + + "He's skeptical about Toll Roads,\n" + + "but I like Toll Roads. I have an EZ-Pass!\n"; + + var ourSignature = NodeGit.Signature.create + ("Ron Paul", "RonPaul@TollRoadsRBest.info", 123456789, 60); + var theirSignature = NodeGit.Signature.create + ("Greg Abbott", "Gregggg@IllTollYourFace.us", 123456789, 60); + + var repository = this.repository; + var ourCommit; + var ourBranch; + var theirBranch; + var rebase; + + return fse.writeFile(path.join(repository.workdir(), fileName), + baseFileContent) + .then(function() { + return addFileToIndex(repository, fileName); + }) + .then(function(oid) { + assert.equal(oid.toString(), + "044704f62399fecbe22da6d7d47b14e52625630e"); + + return repository.createCommit("HEAD", ourSignature, + ourSignature, "initial commit", oid, []); + }) + .then(function(commitOid) { + assert.equal(commitOid.toString(), + "80111c46ac73b857a3493b24c81df08639b5de99"); + + return repository.getCommit(commitOid).then(function(commit) { + ourCommit = commit; + }).then(function() { + return repository.createBranch(ourBranchName, commitOid) + .then(function(branch) { + ourBranch = branch; + return repository.createBranch(theirBranchName, commitOid); + }); + }); + }) + .then(function(branch) { + theirBranch = branch; + return fse.writeFile(path.join(repository.workdir(), fileName), + baseFileContent + theirFileContent); + }) + .then(function() { + return addFileToIndex(repository, fileName); + }) + .then(function(oid) { + assert.equal(oid.toString(), + "b826e989aca7647bea64810f0a2a38acbbdd4c1a"); + + return repository.createCommit(theirBranch.name(), theirSignature, + theirSignature, "they made a commit", oid, [ourCommit]); + }) + .then(function(commitOid) { + assert.equal(commitOid.toString(), + "b3c355bb606ec7da87174dfa1a0b0c0e3dc97bc0"); + + return fse.writeFile(path.join(repository.workdir(), fileName), + baseFileContent + ourFileContent); + }) + .then(function() { + return addFileToIndex(repository, fileName); + }) + .then(function(oid) { + assert.equal(oid.toString(), + "e7fe41bf7c0c28766887a63ffe2f03f624276fbe"); + + return repository.createCommit(ourBranch.name(), ourSignature, + ourSignature, "we made a commit", oid, [ourCommit]); + }) + .then(function(commitOid) { + assert.equal(commitOid.toString(), + "28cfeb17f66132edb3c4dacb7ff38e8dd48a1844"); + + var opts = { + checkoutStrategy: NodeGit.Checkout.STRATEGY.FORCE + }; + + return NodeGit.Checkout.head(repository, opts); + }) + .then(function() { + return Promise.all([ + repository.getReference(ourBranchName), + repository.getReference(theirBranchName) + ]); + }) + .then(function(refs) { + assert.equal(refs.length, 2); + + return Promise.all([ + NodeGit.AnnotatedCommit.fromRef(repository, refs[0]), + NodeGit.AnnotatedCommit.fromRef(repository, refs[1]) + ]); + }) + .then(function(annotatedCommits) { + assert.equal(annotatedCommits.length, 2); + + var ourAnnotatedCommit = annotatedCommits[0]; + var theirAnnotatedCommit = annotatedCommits[1]; + + assert.equal(ourAnnotatedCommit.id().toString(), + "28cfeb17f66132edb3c4dacb7ff38e8dd48a1844"); + assert.equal(theirAnnotatedCommit.id().toString(), + "b3c355bb606ec7da87174dfa1a0b0c0e3dc97bc0"); + + return NodeGit.Rebase.init(repository, ourAnnotatedCommit, + theirAnnotatedCommit, null, ourSignature, null); + }) + .then(function(newRebase) { + rebase = newRebase; + + // there should only be 1 rebase operation to perform + assert.equal(rebase.operationEntrycount(), 1); + + return rebase.next(); + }) + .then(function(rebaseOperation) { + assert.equal(rebaseOperation.type(), + NodeGit.RebaseOperation.REBASE_OPERATION.PICK); + assert.equal(rebaseOperation.id().toString(), + "28cfeb17f66132edb3c4dacb7ff38e8dd48a1844"); + + return repository.openIndex() + .then(function(index) { + assert.ok(index.hasConflicts()); + }); + }) + .then(function() { + return fse.readFile(path.join(repository.workdir(), fileName), "utf8") + .then(function(fileContent) { + assert.equal(fileContent, expectedConflictedFileContent); + + return fse.writeFile(path.join(repository.workdir(), fileName), + conflictSolvedFileContent); + }); + }) + .then(function() { + return addFileToIndex(repository, fileName); + }) + .then(function(oid) { + return repository.openIndex() + .then(function(index) { + assert.ok(!index.hasConflicts()); + + return rebase.commit(null, ourSignature); + }); + }) + .then(function(commitOid) { + assert.equal(commitOid.toString(), + "ef6d0e95167435b3d58f51ab165948c72f6f94b6"); + + return rebase.finish(ourSignature); + }) + .then(function(result) { + assert.equal(result, 0); + + return repository.getBranchCommit(ourBranchName); + }) + .then(function(commit) { + // verify that the "ours" branch has moved to the correct place + assert.equal(commit.id().toString(), + "ef6d0e95167435b3d58f51ab165948c72f6f94b6"); + + return commit.parent(0); + }) + .then(function(commit) { + // verify that we are on top of "their commit" + assert.equal(commit.id().toString(), + "b3c355bb606ec7da87174dfa1a0b0c0e3dc97bc0"); + }); + }); + + it("can abort an in-progress rebase", function() { + var baseFileName = "baseNewFile.txt"; + var ourFileName = "ourNewFile.txt"; + var theirFileName = "theirNewFile.txt"; + + var baseFileContent = "How do you feel about Toll Roads?"; + var ourFileContent = "I like Toll Roads. I have an EZ-Pass!"; + var theirFileContent = "I'm skeptical about Toll Roads"; + + var ourSignature = NodeGit.Signature.create + ("Ron Paul", "RonPaul@TollRoadsRBest.info", 123456789, 60); + var theirSignature = NodeGit.Signature.create + ("Greg Abbott", "Gregggg@IllTollYourFace.us", 123456789, 60); + + var repository = this.repository; + var ourCommit; + var ourBranch; + var theirBranch; + var rebase; + + return fse.writeFile(path.join(repository.workdir(), baseFileName), + baseFileContent) + // Load up the repository index and make our initial commit to HEAD + .then(function() { + return addFileToIndex(repository, baseFileName); + }) + .then(function(oid) { + assert.equal(oid.toString(), + "b5cdc109d437c4541a13fb7509116b5f03d5039a"); + + return repository.createCommit("HEAD", ourSignature, + ourSignature, "initial commit", oid, []); + }) + .then(function(commitOid) { + assert.equal(commitOid.toString(), + "be03abdf0353d05924c53bebeb0e5bb129cda44a"); + + return repository.getCommit(commitOid).then(function(commit) { + ourCommit = commit; + }).then(function() { + return repository.createBranch(ourBranchName, commitOid) + .then(function(branch) { + ourBranch = branch; + return repository.createBranch(theirBranchName, commitOid); + }); + }); + }) + .then(function(branch) { + theirBranch = branch; + return fse.writeFile(path.join(repository.workdir(), theirFileName), + theirFileContent); + }) + .then(function() { + return addFileToIndex(repository, theirFileName); + }) + .then(function(oid) { + assert.equal(oid.toString(), + "be5f0fd38a39a67135ad68921c93cd5c17fefb3d"); + + return repository.createCommit(theirBranch.name(), theirSignature, + theirSignature, "they made a commit", oid, [ourCommit]); + }) + .then(function(commitOid) { + assert.equal(commitOid.toString(), + "e9ebd92f2f4778baf6fa8e92f0c68642f931a554"); + + return removeFileFromIndex(repository, theirFileName); + }) + .then(function() { + return fse.remove(path.join(repository.workdir(), theirFileName)); + }) + .then(function() { + return fse.writeFile(path.join(repository.workdir(), ourFileName), + ourFileContent); + }) + .then(function() { + return addFileToIndex(repository, ourFileName); + }) + .then(function(oid) { + assert.equal(oid.toString(), + "77867fc0bfeb3f80ab18a78c8d53aa3a06207047"); + + return repository.createCommit(ourBranch.name(), ourSignature, + ourSignature, "we made a commit", oid, [ourCommit]); + }) + .then(function(commitOid) { + assert.equal(commitOid.toString(), + "e7f37ee070837052937e24ad8ba66f6d83ae7941"); + + return removeFileFromIndex(repository, ourFileName); + }) + .then(function() { + return fse.remove(path.join(repository.workdir(), ourFileName)); + }) + .then(function() { + return repository.checkoutBranch(ourBranchName); + }) + .then(function() { + return Promise.all([ + repository.getReference(ourBranchName), + repository.getReference(theirBranchName) + ]); + }) + .then(function(refs) { + assert.equal(refs.length, 2); + + return Promise.all([ + NodeGit.AnnotatedCommit.fromRef(repository, refs[0]), + NodeGit.AnnotatedCommit.fromRef(repository, refs[1]) + ]); + }) + .then(function(annotatedCommits) { + assert.equal(annotatedCommits.length, 2); + + var ourAnnotatedCommit = annotatedCommits[0]; + var theirAnnotatedCommit = annotatedCommits[1]; + + assert.equal(ourAnnotatedCommit.id().toString(), + "e7f37ee070837052937e24ad8ba66f6d83ae7941"); + assert.equal(theirAnnotatedCommit.id().toString(), + "e9ebd92f2f4778baf6fa8e92f0c68642f931a554"); + + return NodeGit.Rebase.init(repository, ourAnnotatedCommit, + theirAnnotatedCommit, null, ourSignature, null); + }) + .then(function(newRebase) { + rebase = newRebase; + + // there should only be 1 rebase operation to perform + assert.equal(rebase.operationEntrycount(), 1); + + return rebase.next(); + }) + .then(function(rebaseOperation) { + assert.equal(rebaseOperation.type(), + NodeGit.RebaseOperation.REBASE_OPERATION.PICK); + assert.equal(rebaseOperation.id().toString(), + "e7f37ee070837052937e24ad8ba66f6d83ae7941"); + + return rebase.commit(null, ourSignature); + }) + .then(function(commitOid) { + assert.equal(commitOid.toString(), + "b937100ee0ea17ef20525306763505a7fe2be29e"); + + return repository.getBranchCommit("HEAD") + .then(function(commit) { + // verify that HEAD is on the rebased commit + assert.equal(commit.id().toString(), commitOid.toString()); + }); + }) + .then(function() { + return rebase.abort(ourSignature); + }) + .then(function() { + return NodeGit.Rebase.open(repository) + .then(function(existingRebase) { + assert.fail(existingRebase, undefined, + "There should not be a rebase in progress"); + }) + .catch(function(e) { + assert.equal(e.message, "There is no rebase in progress"); + }); + }) + .then(function() { + return Promise.all([ + repository.getBranchCommit("HEAD"), + repository.getBranchCommit(ourBranchName) + ]); + }) + .then(function(commits) { + assert.equal(commits.length, 2); + + // verify that 'HEAD' and 'ours' are back to their pre-rebase state + assert.equal(commits[0].id().toString(), + "e7f37ee070837052937e24ad8ba66f6d83ae7941"); + assert.equal(commits[1].id().toString(), + "e7f37ee070837052937e24ad8ba66f6d83ae7941"); + }); + }); + + it("can rebase using the convenience method", function() { + var baseFileName = "baseNewFile.txt"; + var ourFileName = "ourNewFile.txt"; + var theirFileName = "theirNewFile.txt"; + + var baseFileContent = "How do you feel about Toll Roads?"; + var ourFileContent = "I like Toll Roads. I have an EZ-Pass!"; + var theirFileContent = "I'm skeptical about Toll Roads"; + + var ourSignature = NodeGit.Signature.create + ("Ron Paul", "RonPaul@TollRoadsRBest.info", 123456789, 60); + var theirSignature = NodeGit.Signature.create + ("Greg Abbott", "Gregggg@IllTollYourFace.us", 123456789, 60); + + var repository = this.repository; + var ourCommit; + var ourBranch; + var theirBranch; + + return fse.writeFile(path.join(repository.workdir(), baseFileName), + baseFileContent) + // Load up the repository index and make our initial commit to HEAD + .then(function() { + return addFileToIndex(repository, baseFileName); + }) + .then(function(oid) { + assert.equal(oid.toString(), + "b5cdc109d437c4541a13fb7509116b5f03d5039a"); + + return repository.createCommit("HEAD", ourSignature, + ourSignature, "initial commit", oid, []); + }) + .then(function(commitOid) { + assert.equal(commitOid.toString(), + "be03abdf0353d05924c53bebeb0e5bb129cda44a"); + + return repository.getCommit(commitOid).then(function(commit) { + ourCommit = commit; + }).then(function() { + return repository.createBranch(ourBranchName, commitOid) + .then(function(branch) { + ourBranch = branch; + return repository.createBranch(theirBranchName, commitOid); + }); + }); + }) + .then(function(branch) { + theirBranch = branch; + return fse.writeFile(path.join(repository.workdir(), theirFileName), + theirFileContent); + }) + .then(function() { + return addFileToIndex(repository, theirFileName); + }) + .then(function(oid) { + assert.equal(oid.toString(), + "be5f0fd38a39a67135ad68921c93cd5c17fefb3d"); + + return repository.createCommit(theirBranch.name(), theirSignature, + theirSignature, "they made a commit", oid, [ourCommit]); + }) + .then(function(commitOid) { + assert.equal(commitOid.toString(), + "e9ebd92f2f4778baf6fa8e92f0c68642f931a554"); + + return removeFileFromIndex(repository, theirFileName); + }) + .then(function() { + return fse.remove(path.join(repository.workdir(), theirFileName)); + }) + .then(function() { + return fse.writeFile(path.join(repository.workdir(), ourFileName), + ourFileContent); + }) + .then(function() { + return addFileToIndex(repository, ourFileName); + }) + .then(function(oid) { + assert.equal(oid.toString(), + "77867fc0bfeb3f80ab18a78c8d53aa3a06207047"); + + return repository.createCommit(ourBranch.name(), ourSignature, + ourSignature, "we made a commit", oid, [ourCommit]); + }) + .then(function(commitOid) { + assert.equal(commitOid.toString(), + "e7f37ee070837052937e24ad8ba66f6d83ae7941"); + + return removeFileFromIndex(repository, ourFileName); + }) + .then(function() { + return fse.remove(path.join(repository.workdir(), ourFileName)); + }) + .then(function() { + return repository.checkoutBranch(ourBranchName); + }) + .then(function() { + return repository.rebaseBranches(ourBranchName, theirBranchName, + null, ourSignature); + }) + .then(function(commit) { + // verify that the "ours" branch has moved to the correct place + assert.equal(commit.id().toString(), + "b937100ee0ea17ef20525306763505a7fe2be29e"); + + return commit.parent(0); + }) + .then(function(commit) { + // verify that we are on top of "their commit" + assert.equal(commit.id().toString(), + "e9ebd92f2f4778baf6fa8e92f0c68642f931a554"); + }); + }); + + it("can rebase with conflicts using the convenience methods", function() { + var fileName = "everyonesFile.txt"; + + var baseFileContent = "How do you feel about Toll Roads?\n"; + var ourFileContent = "I like Toll Roads. I have an EZ-Pass!\n"; + var theirFileContent = "I'm skeptical about Toll Roads\n"; + + var expectedConflictedFileContent = + "How do you feel about Toll Roads?\n" + + "<<<<<<< theirs\n" + + "I'm skeptical about Toll Roads\n" + + "=======\n" + + "I like Toll Roads. I have an EZ-Pass!\n" + + ">>>>>>> we made a commit\n"; + + var conflictSolvedFileContent = + "How do you feel about Toll Roads?\n" + + "He's skeptical about Toll Roads,\n" + + "but I like Toll Roads. I have an EZ-Pass!\n"; + + var ourSignature = NodeGit.Signature.create + ("Ron Paul", "RonPaul@TollRoadsRBest.info", 123456789, 60); + var theirSignature = NodeGit.Signature.create + ("Greg Abbott", "Gregggg@IllTollYourFace.us", 123456789, 60); + + var repository = this.repository; + var ourCommit; + var ourBranch; + var theirBranch; + + return fse.writeFile(path.join(repository.workdir(), fileName), + baseFileContent) + .then(function() { + return addFileToIndex(repository, fileName); + }) + .then(function(oid) { + assert.equal(oid.toString(), + "044704f62399fecbe22da6d7d47b14e52625630e"); + + return repository.createCommit("HEAD", ourSignature, + ourSignature, "initial commit", oid, []); + }) + .then(function(commitOid) { + assert.equal(commitOid.toString(), + "80111c46ac73b857a3493b24c81df08639b5de99"); + + return repository.getCommit(commitOid).then(function(commit) { + ourCommit = commit; + }).then(function() { + return repository.createBranch(ourBranchName, commitOid) + .then(function(branch) { + ourBranch = branch; + return repository.createBranch(theirBranchName, commitOid); + }); + }); + }) + .then(function(branch) { + theirBranch = branch; + return fse.writeFile(path.join(repository.workdir(), fileName), + baseFileContent + theirFileContent); + }) + .then(function() { + return addFileToIndex(repository, fileName); + }) + .then(function(oid) { + assert.equal(oid.toString(), + "b826e989aca7647bea64810f0a2a38acbbdd4c1a"); + + return repository.createCommit(theirBranch.name(), theirSignature, + theirSignature, "they made a commit", oid, [ourCommit]); + }) + .then(function(commitOid) { + assert.equal(commitOid.toString(), + "b3c355bb606ec7da87174dfa1a0b0c0e3dc97bc0"); + + return fse.writeFile(path.join(repository.workdir(), fileName), + baseFileContent + ourFileContent); + }) + .then(function() { + return addFileToIndex(repository, fileName); + }) + .then(function(oid) { + assert.equal(oid.toString(), + "e7fe41bf7c0c28766887a63ffe2f03f624276fbe"); + + return repository.createCommit(ourBranch.name(), ourSignature, + ourSignature, "we made a commit", oid, [ourCommit]); + }) + .then(function(commitOid) { + assert.equal(commitOid.toString(), + "28cfeb17f66132edb3c4dacb7ff38e8dd48a1844"); + + var opts = { + checkoutStrategy: NodeGit.Checkout.STRATEGY.FORCE + }; + + return NodeGit.Checkout.head(repository, opts); + }) + .then(function() { + return repository.rebaseBranches(ourBranchName, theirBranchName, + null, ourSignature) + .then(function(commit) { + assert.fail(commit, undefined, + "The index should have been thrown due to merge conflicts"); + }) + .catch(function(index) { + assert.ok(index); + assert.ok(index.hasConflicts()); + }); + }) + .then(function() { + return fse.readFile(path.join(repository.workdir(), fileName), "utf8") + .then(function(fileContent) { + assert.equal(fileContent, expectedConflictedFileContent); + + return fse.writeFile(path.join(repository.workdir(), fileName), + conflictSolvedFileContent); + }); + }) + .then(function() { + return addFileToIndex(repository, fileName); + }) + .then(function(oid) { + return repository.openIndex() + .then(function(index) { + assert.ok(!index.hasConflicts()); + + return repository.continueRebase(ourSignature); + }); + }) + .then(function(commit) { + // verify that the "ours" branch has moved to the correct place + assert.equal(commit.id().toString(), + "ef6d0e95167435b3d58f51ab165948c72f6f94b6"); + + return commit.parent(0); + }) + .then(function(commit) { + // verify that we are on top of "their commit" + assert.equal(commit.id().toString(), + "b3c355bb606ec7da87174dfa1a0b0c0e3dc97bc0"); + }); + }); +});