Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.

Implement composite Git strategy - #538

Merged
BinaryMuse merged 35 commits into
masterfrom
mkt-composite-git-strategy
Feb 23, 2017
Merged

BinaryMuse merged 35 commits into
masterfrom
mkt-composite-git-strategy

Conversation

@BinaryMuse

Copy link
Copy Markdown
Contributor

⚠️ Here there be 🐲s (and even scarier: metaprogramming! 😱) ⚠️


#524 experiments with utilizing nodegit for some operations; in order to more easily modify and switch between implementations (perhaps even dynamically at runtime), it'd be nice to have both implementations in the codebase in a fully complete state at all times. This PR reverts GitShellOutStrategy to use pure shelling out and adds NodegitStrategy which implements some operations using nodegit.

Since the nodegit strategy doesn't implement all the Git operations, this PR also introduces a new helper called firstImplementer which takes an array of receivers and returns an object that intercepts property accessors and returns the property value from the first receiver that contains that property. An example may help:

class A {
  one()   { return 'a-one'; }
  two()   { return 'a-two'; }
}

class B {
  two()   { return 'b-two'; }
  three() { return 'b-three'; }
}

const target = firstImplementer(new A(), new B());
target.one()   // a-one
target.two()   // a-two
target.three() // b-three

A new strategy called CompositeGitStrategy uses this helper to automatically create both the underlying Git strategies, creates a shared async queue for both of them, and routes method calls to the appropriate strategy as approprite.

The GitShellOutStrategy tests have been converted to GitStrategiesTest, which tests all strategies, and which uses even more magic to only test the methods that are implemented on the strategy under test. There is some complexity here because even when we're testing the nodegit strategy, we often need methods the strategy doesn't implement for supporting calls (like calling exec to set local config). So, the tests are implemented using a special version of the composite strategy which ensures that the method under test is called on the strategy under test, but lets other method calls be fulfilled by either strategy. This code is under heavy construction. 🚧

The other downside to this... er, strategy, is that it's implemented via a Proxy, and since sinon does it's own metaprogrammy stuff to create stubs and mocks, they're not playing super well together. I'm still working on this as well.

@smashwilson smashwilson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is super cool and a big validation of the Strategy pattern for git operations 🎉

The metaprogramming isn't nearly as scary as advertised, either; I've done worse myself in production code 😉 I'd recommend maybe a few more comments to document it maybe, especially around the tests?

Comment thread lib/git-shell-out-strategy.js Outdated

import {GitProcess} from 'git-kitchen-sink';
import {parse as parseDiff} from 'what-the-diff';
import Git from 'nodegit';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was actually going to comment on the inappropriateness of the name GitShellOutStrategy in #524, by the way 😆

Comment thread lib/helpers.js
* Takes an array of targets and returns a proxy. The proxy intercepts property accessor calls and
* returns the value of that property on the first object in `targets` where the target implements that property.
*/
export function firstImplementer(...targets) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't nearly as scary as you made it sound 😉

Comment thread lib/nodegit-strategy.js Outdated
const STATUS = Git.Status.STATUS;
const index = await repo.index();

/* eslint-disable no-bitwise */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's kind of a weird eslint rule. I guess bitwise operations are rare enough that they assume it's a typo for boolean operations?

Comment thread test/git-strategies.test.js Outdated
function createTestStrategyForPropertyUnderTest(...args) {
const compositeStrategy = CompositeGitStrategy.create(...args);
const originalStrategy = new Strategy(...args);
return new Proxy(compositeStrategy, {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, this bit gets a little hairy 😆 I think I still follow, though.

Comment thread test/git-strategies.test.js Outdated
operations.forEach(op => {
it(`temporarily overrides gpg.program when ${op.progressiveTense}`, async () => {
// if (op !== 'pulling') { return; }
console.log('========================');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes — the pull tests were failing (still are IIRC) and I was trying to figure out why

@BinaryMuse
BinaryMuse force-pushed the mkt-composite-git-strategy branch from 8463f26 to 57887e3 Compare February 22, 2017 19:36

@smashwilson smashwilson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I definitely prefer the chained methods to the single method with four parameters 👍 Much more readable.

Comment thread test/git-strategies.test.js Outdated
});
} else {
return describeMethod(description, function() {
it('is not implemented', function() {});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm yeah I guess it was misleading to have these as "pending" tests, considering that we wouldn't actually intend to implement many of them anyway.

Comment thread test/git-strategies.test.js Outdated

[GitShellOutStrategy, NodeGitStrategy].forEach(function(Strategy) {
forStrategy(Strategy, function(forCommand) {
describe.only(`Git commands for ${Strategy.name}`, function() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 .only() 😉

});

it('displays copied files as an added file', async function() {
// This repo is carefully constructed after much experimentation

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation looks a touch off on these lines 🎨

@BinaryMuse
BinaryMuse changed the base branch from mkt-ku-nodegit2-the-revenge-of-nodegit to master February 23, 2017 01:19
@BinaryMuse

Copy link
Copy Markdown
Contributor Author

Since #524 is closed, I've set this PR's base to master. I'm keeping CompositeGitStrategy as it's a great way to experiment with new implementations of some features, and the testing story here is relatively complete.

Michelle Tilley added 2 commits February 22, 2017 17:50
Comment thread lib/helpers.js
getPrototypeOf(target) {
return targets.reduce((acc, t) => {
return Object.create(acc, descriptorsFromProto(Object.getPrototypeOf(t)));
}, Object.prototype);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was surprisingly tricky to work out! Huge thanks to @kuychaco who rocked this.

].forEach(function(strategies) {
const createTestStrategy = (...args) => {
return CompositeGitStrategy.withStrategies(strategies)(...args);
};

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smashwilson As I was reviewing this with @kuychaco, she recommended this much simpler (and much improved, IMO) strategy. Thoughts?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm! Definitely much cleaner.

Let's see. We lose the ability to have the test suite identify which methods are implemented on each strategy... but we gain higher fidelity to the way the strategies will actually be used by the package. 👍

To go even further - in what situation would we want to run the suite with different combinations of strategies in one run? Presumably the package will use a fixed strategy configuration at any given time, so as long as we stay consistent with that, these tests will exercise what they're supposed to exercise. Maybe we don't even need the outer loop!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smashwilson That's a really good question! And you're right, in the end we'll only use a specific order for the production strategy. The point of these tests was specifically to unit test each individual strategy's methods as much as possible; however, to do so, we may need to use methods that only exist on the other strategy (such as git.exec to set configs or do other test setup). That's why each isn't tested in complete isolation, but as part of a composite strategy.

action: () => git.pull('some-branch'),
configureStub: (stub, gitStrategy) => {
sinon.stub(gitStrategy, 'getRemoteForBranch').returns(Promise.resolve('origin'));
},

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smashwilson We also changed this up a bit.

@smashwilson smashwilson added this to the Public Release milestone Feb 23, 2017
@BinaryMuse
BinaryMuse merged commit 6c0565f into master Feb 23, 2017
@BinaryMuse
BinaryMuse deleted the mkt-composite-git-strategy branch February 23, 2017 19:42
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants