flux 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,2 @@
1
1
  use :pivotal_tracker, project_id: 128808
2
- use :git
3
2
  use :github, repo_user: 'mojotech', repo_name: 'flux'
4
- use :mojotech
@@ -0,0 +1,69 @@
1
+ [WIP]
2
+
3
+ Developer workflow
4
+ ------------------
5
+
6
+ I'll start by listing scheduled (current + backlog) stories:
7
+
8
+ $ flux pt
9
+ ID STATE ASSIGNEE EST STORY
10
+ 12345679 finished David Leal 1 A developer can link a branch to a story
11
+ 12345680 finished David Leal 1 A developer can mark a story as started
12
+ 12345678 unstarted A developer can grab a story
13
+ ...
14
+
15
+ `12345678` is next in line, so I'll just pick that one:
16
+
17
+ $ flux feature:start 12345678 dl/grab_a_story -e 1
18
+
19
+ This does a few things:
20
+
21
+ * Assigns `12345678` to me.
22
+ * Sets `1` as `12345678`'s estimate (you can't start a story unless it's estimated).
23
+ * Marks `12345678` as `started`.
24
+ * Creates dl/grab_a_story and origin/dl/grab_a_story.
25
+ * Sets origin/dl/grab_a_story as dl/grab_a_story's upstream.
26
+
27
+ [Time passes.]
28
+
29
+ I'm done with the feature, so I'll push it and ask for a review [TODO: automate this]
30
+
31
+ $ git push
32
+ $ flux review:request
33
+
34
+ Reviewer workflow
35
+ -----------------
36
+
37
+ Studies have found that, to properly review code, you need to look at small
38
+ changes at a time. This allows you to reason about each change, and
39
+ increases your chances of finding bugs. However, Github pull requests
40
+ encourage you to look at changes as a whole. If you add a comment to a
41
+ commit and then rewrite the branch, the comment will be wiped out from the
42
+ pull request view.
43
+
44
+ To work around this, we create a new pull request whenever we need to review
45
+ changes to a branch.
46
+
47
+ The first time we get a pull request, we just review it on Github. We add
48
+ comments to each commit, and leave the pull request open.
49
+
50
+ The branch owner sees the comments, makes the necessary changes, force-pushes
51
+ the branch again and asks for a new review, using
52
+
53
+ $ flux review:request
54
+
55
+ This will close the old pull request and open a new one.
56
+
57
+ The reviewer can check if a new branch was changed using
58
+
59
+ $ flux review:history <branch name>
60
+ NUM STORY AUTHOR DATE TITLE
61
+ 34 19692783 david 2012-02-21 15:15 A developer can only submit for...
62
+ 33 19692783 david 2012-02-21 13:02 A developer can only submit for...
63
+
64
+ The reviewer can also see what exactly changed between the 2 branches using
65
+
66
+ $ flux review:diff
67
+ ...
68
+
69
+ Without arguments, this will show an interdiff between the last 2 revisions.
data/README.md CHANGED
@@ -1,108 +1,42 @@
1
1
  Flux
2
2
  ====
3
3
 
4
- Flux is a command-line tool that tries to eliminate the drudgery from your
4
+ Flux is a command-line tool we use at MojoTech to automate some parts of our
5
5
  workflow.
6
6
 
7
7
  It provides hooks for interacting with the following:
8
8
 
9
- * Tracker (currently only Pivotal Tracker is supported)
10
- * RCS (currently, git only)
11
- * Anything else you can think of, as long as someone implements it.
12
-
13
- Some examples
14
- -------------
15
-
16
- Let's list all stories that have been scheduled (i.e., moved out of the icebox
17
- and into some iteration):
18
-
19
- $ flux stories
20
- ID STATE ASSIGNEE STORY
21
- 12345678 unstarted A developer can grab a story
22
- 12345679 finished David Leal A developer can link a branch to a story
23
- 12345680 finished David Leal A developer can mark a story as started
24
- ...
25
-
26
- [TODO Complete workflow. For now try `flux` to see a list of implemented tasks.]
9
+ * Pivotal Tracker
10
+ * Git
11
+ * Github pull requests
27
12
 
28
13
  Configuration
29
14
  -------------
30
15
 
31
- A project is configured by placing a file named `.flux` in the project root.
32
- Each top level key represents a functional domain.
16
+ A project is configured by placing a file named `.flux.rb` in the project root.
33
17
 
34
18
  This project's `.flux` file is as follows:
35
19
 
36
- trackers:
37
- adapter: pivotal_tracker
38
- project_id: 128808
39
- rcs:
40
- adapter: git
41
- workflows:
42
- adapter: mojotech
20
+ use :pivotal_tracker, project_id: 128808
21
+ use :github, repo_user: 'mojotech', repo_name: 'flux'
43
22
 
44
23
  ### Local configuration
45
24
 
46
- The `.flux` file should be used for project-wide configuration and kept under
25
+ The `.flux.rb` file should be used for project-wide configuration and kept under
47
26
  version control. Obviously you aren't going to store your account credentials
48
27
  there, so you need a different place where you can store them. That place is
49
- `.flux.local`. If you're using Git (and really, why wouldn't you?), this file
50
- should be added to the project's `.gitignore`.
51
-
52
- Here's an example of a `.flux.local` file.
53
-
54
- trackers:
55
- token: abcdef1234567890
56
- email: david@mojotech.com
57
-
58
- Note for Pivotal Tracker: Your API token is at https://www.pivotaltracker.com/profile
59
-
60
-
61
- Code Reviews Workflow
62
- ---------------------
63
-
64
- # start your feature on a branch
65
- $ git checkout feature-branch
66
-
67
- # code your feature
68
- $ git commit # rinse, repeat
69
-
70
- # make sure your code to review is available
71
- $ git push
72
-
73
- # request a review
74
- $ flux branches:review feature-branch [ --parent master ]
75
- # Note: currently, you must explicitly list your feature branch name
76
- # This feature branch name will be used to keep track of multiple rounds
77
- # of the review.
78
-
79
- # flux will generate a pull request and print its URL
80
-
81
- # Github takes sends out notices.
82
- # other devs review and comment
83
-
84
- # You fix up your commits.
85
- $ git rebase -i origin/master
86
- # I'm in your branch... rewriting your history.
87
-
88
- # Time to resubmit for review
89
- $ git push
90
- $ flux request-review feature-branch [ --parent master ] [ --close ]
91
-
92
- # flux closes old review request for feature-branch (optionally)
93
- # flux opens a new review request for feature-branch
94
- # The process repeats.
95
-
96
- # Now the reviewers want to see if you addressed their concerns.
97
- # They run:
98
- $ flux branches:all_reviews
99
- # (TODO: should be able to limit this report to one feature branch)
28
+ `.flux.local.rb`. This file should be added to the project's `.gitignore`.
100
29
 
101
- # They see: the most recent review request for each branch, but also links
102
- # to the diffs between each previous review request for that branch.
30
+ Here's an example of a `.flux.local.rb` file.
103
31
 
104
- # So, instead of repeating the entire review, they can look at just the "inter-diff"
32
+ use :pivotal_tracker, token: 'abcdef1234567890', email: 'david@mojotech.com'
33
+ use :github, username: 'david', password: 'IL0vePandas!'
105
34
 
35
+ Your Pivotal Tracker API token is at https://www.pivotaltracker.com/profile
106
36
 
37
+ Use
38
+ ---
107
39
 
40
+ $ flux # show a list of available tasks
108
41
 
42
+ For a complete description of our workflow, see MOJOTECH.md.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.6
1
+ 0.0.7
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "flux"
8
- s.version = "0.0.6"
8
+ s.version = "0.0.7"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["David Leal"]
12
- s.date = "2012-02-18"
12
+ s.date = "2012-02-22"
13
13
  s.email = "david@mojotech.com"
14
14
  s.executables = ["flux"]
15
15
  s.extra_rdoc_files = [
@@ -23,6 +23,7 @@ Gem::Specification.new do |s|
23
23
  ".rspec",
24
24
  "Gemfile",
25
25
  "LICENSE.txt",
26
+ "MOJOTECH.md",
26
27
  "README.md",
27
28
  "Rakefile",
28
29
  "VERSION",
@@ -31,17 +32,16 @@ Gem::Specification.new do |s|
31
32
  "lib/flux.rb",
32
33
  "lib/flux/cli.rb",
33
34
  "lib/flux/cli/feature.rb",
35
+ "lib/flux/cli/git.rb",
34
36
  "lib/flux/cli/pivotal_tracker.rb",
35
37
  "lib/flux/cli/review.rb",
36
38
  "lib/flux/ext/pivotal-tracker.rb",
37
39
  "lib/flux/git.rb",
38
40
  "lib/flux/pivotal_tracker.rb",
39
- "lib/flux/rcs.rb",
40
- "lib/flux/rcs/git.rb",
41
41
  "lib/flux/util.rb",
42
42
  "lib/flux/util/output.rb",
43
43
  "lib/flux/util/table.rb",
44
- "lib/flux/workflows/mojotech.rb",
44
+ "spec/flux/git_spec.rb",
45
45
  "spec/flux/util/table_spec.rb",
46
46
  "spec/flux_spec.rb",
47
47
  "spec/spec_helper.rb",
@@ -1,7 +1,7 @@
1
1
  require 'ostruct'
2
2
  require 'pathname'
3
3
  require 'thor'
4
- require 'yaml'
4
+ require 'forwardable'
5
5
 
6
6
  require 'flux/util'
7
7
 
@@ -22,7 +22,6 @@ module Flux
22
22
  load rc
23
23
  load rc_local if File.exist?(rc_local)
24
24
 
25
- require 'flux/rcs'
26
25
  require 'flux/cli'
27
26
  end
28
27
 
@@ -1,3 +1,4 @@
1
1
  require 'flux/cli/feature'
2
2
  require 'flux/cli/pivotal_tracker'
3
3
  require 'flux/cli/review'
4
+ require 'flux/cli/git'
@@ -3,6 +3,8 @@ require 'flux/git'
3
3
  module Flux
4
4
  module CLI
5
5
  class Feature < Thor
6
+ B = Flux::Git::Branch
7
+
6
8
  namespace :feature
7
9
 
8
10
  desc "start STORY_ID BRANCH", "start working on a story"
@@ -10,29 +12,42 @@ module Flux
10
12
  method_option :parent_branch, :type => :string,
11
13
  :default => 'master',
12
14
  :aliases => '-b'
13
- def start(story_id, branch_id)
15
+ def start(story_id, branch_id = branch_id_from_story(story_id))
14
16
  invoke 'pt:grab', [story_id], :estimate => options[:estimate]
15
17
  invoke 'pt:start', [story_id]
18
+ invoke 'branch:create',
19
+ [branch_id],
20
+ :parent_branch => options[:parent_branch]
16
21
 
17
- create_branch branch_id, story_id, options
22
+ link story_id, branch_id
18
23
  end
19
24
 
20
- private
21
-
22
- def create_branch(branch_id, story_id, options = {})
23
- up = Git::Branch.remote('origin', branch_id)
24
- parent = Git::Branch.local(options[:parent_branch])
25
- branch = Git::Branch.local(branch_id).
26
- create(parent).
27
- publish(up).
28
- track(up).
29
- checkout
25
+ desc "link STORY_ID BRANCH", "link a story to a branch"
26
+ def link(story_id, branch_id)
27
+ B.local(branch_id).config('story_id', story_id)
28
+ end
30
29
 
31
- link_branch_to_story branch_id, story_id
30
+ desc "finish", "finish the current feature"
31
+ def finish(branch_id = B.current.name)
32
+ invoke "pt:finish", [B.local(branch_id).config('story_id')]
33
+ invoke "review:request"
32
34
  end
33
35
 
34
- def link_branch_to_story(branch_id, story_id)
35
- Git::Branch.local(branch_id).config('story_id', story_id)
36
+ private
37
+
38
+ def branch_id_from_story(story_id)
39
+ project = Flux::PT::Project.current
40
+ story = project.stories.find(story_id)
41
+ words = story.
42
+ name.
43
+ strip.
44
+ downcase.
45
+ split(/\s+/).
46
+ select { |w| w.size > 2 }.
47
+ last(7)
48
+ initials = project.members.me.initials.downcase
49
+
50
+ "#{initials}/#{story.id}-#{words.join('_')}"
36
51
  end
37
52
  end
38
53
  end
@@ -0,0 +1,66 @@
1
+ require 'flux/git'
2
+
3
+ module Flux
4
+ module CLI
5
+ class Git < Thor
6
+ G = Flux::Git
7
+
8
+ namespace :git
9
+
10
+ desc "fetch", "fetch changes from remote repos"
11
+ def fetch
12
+ G.git.fetch
13
+ end
14
+ end
15
+
16
+ class Branch < Thor
17
+ B = Flux::Git::Branch
18
+
19
+ namespace :branch
20
+
21
+ desc "create BRANCH", "create a branch as a public, tracked branch"
22
+ method_option :parent_branch, :type => :string,
23
+ :default => 'master',
24
+ :aliases => '-p'
25
+ def create(branch_id, options = {})
26
+ up = B.remote(branch_id, 'origin')
27
+ parent = B.local(options[:parent_branch])
28
+ branch = B.local(branch_id).
29
+ create(parent).
30
+ publish(up).
31
+ track(up).
32
+ checkout
33
+ end
34
+
35
+ class Checks < Thor
36
+ G = Flux::Git
37
+ B = G::Branch
38
+
39
+ namespace 'branch:checks'
40
+
41
+ desc "synced [BRANCH]", "check if branch is synced with upstream"
42
+ def synced(branch = B.current)
43
+ branch = G::Branch(branch)
44
+
45
+ unless branch.synced?
46
+ raise Error, "Local branch `#{branch.name}' is not synced."
47
+ end
48
+ end
49
+
50
+ desc "rebased [BRANCH]", "check if branch is rebased with other branch"
51
+ method_option :parent, :type => :string,
52
+ :default => 'master',
53
+ :aliases => '-p'
54
+ def rebased(branch = B.current)
55
+ branch = G::Branch(branch)
56
+ parent = G::Branch(options[:parent])
57
+
58
+ unless branch.includes?(parent)
59
+ raise Error,
60
+ "`#{branch.fqdn}' is not rebased onto `#{parent.fqdn}'."
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -1,9 +1,6 @@
1
1
  require 'pivotal-tracker'
2
- require 'hirb'
3
2
  require 'flux/pivotal_tracker'
4
3
  require 'flux/ext/pivotal-tracker'
5
- require 'flux/util/table'
6
- require 'forwardable'
7
4
 
8
5
  module Flux
9
6
  module CLI
@@ -18,12 +15,8 @@ module Flux
18
15
  default_task :list
19
16
 
20
17
  class << self
21
- def config
22
- Flux.environment['pivotal_tracker']
23
- end
24
-
25
18
  def login
26
- ::PivotalTracker::Client.token = config['token']
19
+ ::PivotalTracker::Client.token = Flux::PT.config['token']
27
20
  end
28
21
 
29
22
  def story_update(name, attrs = nil, &get_attrs)
@@ -43,7 +36,7 @@ module Flux
43
36
  def_delegator self, :config
44
37
  }
45
38
 
46
- login if config
39
+ login if Flux::PT.config
47
40
 
48
41
  desc "list", "list stories, excluding icebox by default"
49
42
  def list
@@ -57,17 +50,13 @@ module Flux
57
50
  end
58
51
 
59
52
  story_update :finish, :current_state => 'finished'
60
- story_update(:grab) {{:owned_by => me.name}}
53
+ story_update(:grab) {{:owned_by => current_project.members.me.name}}
61
54
  story_update :start, :current_state => 'started'
62
55
 
63
56
  private
64
57
 
65
58
  def current_project
66
- Flux::PT::Project.new(config['project_id'])
67
- end
68
-
69
- def me
70
- current_project.members.find { |m| m.email == config['email'] }
59
+ Flux::PT::Project.current
71
60
  end
72
61
 
73
62
  def story(id)
@@ -5,6 +5,9 @@ require 'github_api'
5
5
  module Flux
6
6
  module CLI
7
7
  class Review < Thor
8
+ G = Flux::Git
9
+ B = G::Branch
10
+
8
11
  PULL_REQUEST_HEADERS = %w(>NUM >STORY AUTHOR DATE TITLE)
9
12
 
10
13
  include Flux::Util
@@ -15,7 +18,7 @@ module Flux
15
18
  method_option :from, :type => :numeric, :aliases => '-f'
16
19
  method_option :to, :type => :numeric, :aliases => '-t'
17
20
  method_option :color, :type => :boolean, :aliases => '-c'
18
- def diff(branch_name = Git::Branch.current.name)
21
+ def diff(branch_name = B.current.name)
19
22
  reqs = Github::PullRequest.all(:branch_name => branch_name)
20
23
 
21
24
  if options[:from] && options[:to]
@@ -41,10 +44,16 @@ module Flux
41
44
  end
42
45
  end
43
46
 
44
- desc "request", "request a review for the current feature"
45
- def request
46
- branch = Git::Branch.current
47
- story = current_project.stories.find(branch.config('story_id'))
47
+ desc "request [BRANCH]", "request a review"
48
+ def request(branch = B.current)
49
+ branch = G::Branch(branch)
50
+
51
+ invoke 'git:fetch'
52
+ invoke 'branch:checks:synced', [branch]
53
+ invoke 'branch:checks:rebased', [branch], :parent => 'origin/master'
54
+
55
+ story = Flux::PT::Project.
56
+ current.stories.find(branch.config('story_id'))
48
57
  old = Github::PullRequest.all(:branch_name => branch.name)
49
58
  last = old.first
50
59
 
@@ -54,7 +63,7 @@ module Flux
54
63
  :story_url => story.url,
55
64
  :iterations => old.map(&:html_url)}
56
65
  data = {:base => 'master',
57
- :head => branch.commit.sha,
66
+ :head => branch.top.sha,
58
67
  :title => story.name,
59
68
  :body => body}
60
69
 
@@ -67,7 +76,7 @@ module Flux
67
76
  end
68
77
 
69
78
  desc "history [BRANCH]", "show review history for branch"
70
- def history(branch_id = Git::Branch.current.name)
79
+ def history(branch_id = B.current.name)
71
80
  reqs = Github::PullRequest.all(:branch_name => branch_id)
72
81
 
73
82
  list_pull_requests reqs
@@ -79,14 +88,6 @@ module Flux
79
88
  Flux.environment
80
89
  end
81
90
 
82
- def current_project
83
- Flux::PT::Project.new(pt_config['project_id'])
84
- end
85
-
86
- def pt_config
87
- config['pivotal_tracker']
88
- end
89
-
90
91
  def list_pull_requests(pull_requests)
91
92
  table =
92
93
  [PULL_REQUEST_HEADERS] +
@@ -1,4 +1,4 @@
1
- require 'forwardable'
1
+ require 'grit'
2
2
 
3
3
  module Flux
4
4
  module Git
@@ -15,14 +15,31 @@ module Flux
15
15
  raise RCSError, "Couldn't find git repo starting at #{start_dir}."
16
16
  end
17
17
 
18
- class Branch
19
- extend Forwardable
18
+ def self.Branch(branch, repo_path = Git.repo_path)
19
+ case branch
20
+ when Branch
21
+ branch
22
+ when String
23
+ remotes = Git.repo(repo_path).remote_list
20
24
 
25
+ if remotes.find { |r| branch =~ %r{^#{r}/} }
26
+ args = branch.split('/', 2).reverse << repo_path
27
+
28
+ Branch.remote(*args)
29
+ else
30
+ Branch.local(branch, repo_path)
31
+ end
32
+ else
33
+ raise "Unable to convert object to branch: #{branch.inspect}"
34
+ end
35
+ end
36
+
37
+ class Branch < Struct.new(:repo_path, :remote, :name)
21
38
  def self.current(repo_path = Git.repo_path)
22
39
  Branch.local(Git.repo(repo_path).head.name, repo_path)
23
40
  end
24
41
 
25
- def self.remote(remote, name, repo_path = Git.repo_path)
42
+ def self.remote(name, remote = 'origin', repo_path = Git.repo_path)
26
43
  new(repo_path, remote, name)
27
44
  end
28
45
 
@@ -30,15 +47,11 @@ module Flux
30
47
  new(repo_path, nil, name)
31
48
  end
32
49
 
33
- def_delegator :head, :commit
34
-
35
- attr_reader :name, :remote
36
-
37
50
  def initialize(repo_path, remote, name)
38
- @git = Git.git(repo_path)
39
- @remote = remote
40
- @name = name
41
- @repo = Git.repo(repo_path)
51
+ super
52
+
53
+ @git = Git.git(repo_path)
54
+ @repo = Git.repo(repo_path)
42
55
  end
43
56
 
44
57
  def config(var, value = nil)
@@ -60,7 +73,17 @@ module Flux
60
73
  end
61
74
 
62
75
  def fqdn
63
- "#{remote}/#{name}"
76
+ remote ? "#{remote}/#{name}" : name
77
+ end
78
+
79
+ def include?(branch)
80
+ @repo.commits_between(fqdn, branch.fqdn).empty?
81
+ end
82
+
83
+ alias_method :includes?, :include?
84
+
85
+ def top
86
+ @repo.commits(fqdn, 1).first
64
87
  end
65
88
 
66
89
  def publish(upstream)
@@ -73,9 +96,16 @@ module Flux
73
96
  tap { @git.branch({:set_upstream => true}, @name, upstream.fqdn) }
74
97
  end
75
98
 
76
- private
99
+ def synced?
100
+ upstream.top.sha == top.sha
101
+ end
102
+
103
+ def upstream
104
+ remote = config('remote')
105
+ name = config('merge').sub('refs/heads/', '')
77
106
 
78
- def_delegator :@repo, :head
107
+ Branch.remote(name, remote, repo_path)
108
+ end
79
109
  end
80
110
  end
81
111
  end
@@ -1,14 +1,28 @@
1
1
  module Flux
2
2
  module PT
3
+ def self.config
4
+ Flux.environment['pivotal_tracker']
5
+ end
6
+
3
7
  class Project
4
8
  attr_reader :id
5
9
 
10
+ def self.current
11
+ Project.new(PT.config['project_id'])
12
+ end
13
+
6
14
  def initialize(id)
7
15
  @id = id
8
16
  end
9
17
 
10
18
  def members
11
- ::PivotalTracker::Membership.all(self)
19
+ ::PivotalTracker::Membership.all(self).tap { |members|
20
+ class << members
21
+ def me
22
+ find { |m| m.email == PT.config['email'] }
23
+ end
24
+ end
25
+ }
12
26
  end
13
27
 
14
28
  def stories
@@ -1,2 +1,2 @@
1
1
  require 'flux/util/output'
2
-
2
+ require 'flux/util/table'
@@ -0,0 +1,51 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ require 'flux/git'
4
+
5
+ describe "Flux::Git::Branch()" do
6
+ G = Flux::Git
7
+ B = G::Branch
8
+
9
+ it "when passed a branch, returns the same branch" do
10
+ G::Branch(B.current).should == B.current
11
+ end
12
+
13
+ it "converts a remote/branch/name string to a remote branch" do
14
+ stub(G).repo('blah').stub!.remote_list { ['origin'] }
15
+
16
+ a = B.remote('dl/something', 'origin', 'blah')
17
+ b = G::Branch('origin/dl/something', 'blah')
18
+
19
+ b.should == a
20
+ end
21
+
22
+ it "converts a branch/name string to a local branch" do
23
+ stub(G).repo('blah').stub!.remote_list { ['origin'] }
24
+
25
+ a = B.local('dl/something', 'blah')
26
+ b = G::Branch('dl/something', 'blah')
27
+
28
+ b.should == a
29
+ end
30
+ end
31
+
32
+ describe Flux::Git::Branch do
33
+ self::G = Flux::Git
34
+ self::B = G::Branch
35
+
36
+ it "determines whether it is rebased with another branch" do
37
+ stub(G).repo('.').stub! {
38
+ commits_between('my_branch', 'master') { [] }
39
+ commits_between('my_branch', 'origin/my_branch') {
40
+ ["commit_in_origin_my_branch"]
41
+ }
42
+ }
43
+
44
+ a = B.local('my_branch', '.')
45
+ b = B.remote('my_branch', 'origin', '.')
46
+ c = B.local('master', '.')
47
+
48
+ a.should include(c)
49
+ a.should_not include(b)
50
+ end
51
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flux
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-02-18 00:00:00.000000000 Z
12
+ date: 2012-02-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: thor
16
- requirement: &82116730 !ruby/object:Gem::Requirement
16
+ requirement: &86582250 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 0.14.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *82116730
24
+ version_requirements: *86582250
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: pivotal-tracker
27
- requirement: &82116220 !ruby/object:Gem::Requirement
27
+ requirement: &86581640 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 0.4.0
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *82116220
35
+ version_requirements: *86581640
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: grit
38
- requirement: &82115540 !ruby/object:Gem::Requirement
38
+ requirement: &86581140 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 2.4.0
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *82115540
46
+ version_requirements: *86581140
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: github_api
49
- requirement: &82114430 !ruby/object:Gem::Requirement
49
+ requirement: &86580730 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *82114430
57
+ version_requirements: *86580730
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: rspec
60
- requirement: &82111670 !ruby/object:Gem::Requirement
60
+ requirement: &86580390 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ~>
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '2.0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *82111670
68
+ version_requirements: *86580390
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: bundler
71
- requirement: &82109050 !ruby/object:Gem::Requirement
71
+ requirement: &86579740 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ~>
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: 1.0.0
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *82109050
79
+ version_requirements: *86579740
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: jeweler
82
- requirement: &82123730 !ruby/object:Gem::Requirement
82
+ requirement: &86577840 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ~>
@@ -87,10 +87,10 @@ dependencies:
87
87
  version: 1.6.2
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *82123730
90
+ version_requirements: *86577840
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: rcov
93
- requirement: &82122310 !ruby/object:Gem::Requirement
93
+ requirement: &86577180 !ruby/object:Gem::Requirement
94
94
  none: false
95
95
  requirements:
96
96
  - - ! '>='
@@ -98,10 +98,10 @@ dependencies:
98
98
  version: '0'
99
99
  type: :development
100
100
  prerelease: false
101
- version_requirements: *82122310
101
+ version_requirements: *86577180
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: rr
104
- requirement: &82120780 !ruby/object:Gem::Requirement
104
+ requirement: &86602150 !ruby/object:Gem::Requirement
105
105
  none: false
106
106
  requirements:
107
107
  - - ~>
@@ -109,7 +109,7 @@ dependencies:
109
109
  version: 1.0.0
110
110
  type: :development
111
111
  prerelease: false
112
- version_requirements: *82120780
112
+ version_requirements: *86602150
113
113
  description:
114
114
  email: david@mojotech.com
115
115
  executables:
@@ -125,6 +125,7 @@ files:
125
125
  - .rspec
126
126
  - Gemfile
127
127
  - LICENSE.txt
128
+ - MOJOTECH.md
128
129
  - README.md
129
130
  - Rakefile
130
131
  - VERSION
@@ -133,17 +134,16 @@ files:
133
134
  - lib/flux.rb
134
135
  - lib/flux/cli.rb
135
136
  - lib/flux/cli/feature.rb
137
+ - lib/flux/cli/git.rb
136
138
  - lib/flux/cli/pivotal_tracker.rb
137
139
  - lib/flux/cli/review.rb
138
140
  - lib/flux/ext/pivotal-tracker.rb
139
141
  - lib/flux/git.rb
140
142
  - lib/flux/pivotal_tracker.rb
141
- - lib/flux/rcs.rb
142
- - lib/flux/rcs/git.rb
143
143
  - lib/flux/util.rb
144
144
  - lib/flux/util/output.rb
145
145
  - lib/flux/util/table.rb
146
- - lib/flux/workflows/mojotech.rb
146
+ - spec/flux/git_spec.rb
147
147
  - spec/flux/util/table_spec.rb
148
148
  - spec/flux_spec.rb
149
149
  - spec/spec_helper.rb
@@ -163,7 +163,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
163
163
  version: '0'
164
164
  segments:
165
165
  - 0
166
- hash: -114953261
166
+ hash: -283853007
167
167
  required_rubygems_version: !ruby/object:Gem::Requirement
168
168
  none: false
169
169
  requirements:
@@ -1 +0,0 @@
1
- require 'flux/rcs/git'
@@ -1,162 +0,0 @@
1
- require 'grit'
2
- require 'json'
3
-
4
- module Flux
5
- module RCS
6
- class Branches < Thor
7
- namespace :branches
8
-
9
- desc "review BRANCH_NAME", "review a branch"
10
- method_option :parent, :type => :string, :default => "master", :desc => "upstream"
11
- method_option :close, :type => :boolean, :default => false, :desc => "whether or not to close previous review requests for this branch", :aliases => '-c'
12
- def review(branch_name=nil)
13
- # note: we ignore any closed review requests for this branch
14
- # so, if there are no open review requests, this one will start out at version 1.
15
- related = open_reviews_for(branch_name)
16
-
17
- if branch_name
18
- # we use the sha instead of the branch name in order to prevent GH from
19
- # updating the pull-request when someone pushed to the branch again.
20
- head = repo.get_head(branch_name).commit.sha
21
- else
22
- head = repo.head.commit.sha
23
- end
24
-
25
- if related.last
26
- previous_title = related.last["title"]
27
- previous_version = previous_title.match(/Please review .+ \(v(\d+)\)/)[1].to_i
28
- new_version = previous_version + 1
29
- else
30
- new_version = 1
31
- end
32
- history = related.map {|pr| pr["title"] }.join("\n")
33
- history = "PRIOR VERSIONS: \n" + history unless history.empty?
34
- data = {
35
- :title => "Please review #{branch_name} (v#{new_version}) (#{head[0..8]})",
36
- :head => head,
37
- :base => options[:parent],
38
- :body => history
39
- }.to_json
40
- cmd = "curl -s #{auth_string} #{pulls_url} -d '#{data}'"
41
-
42
- puts cmd if @debug
43
- new_pr = JSON.parse(`#{cmd}`)
44
- if new_pr["number"].to_i > 0
45
- puts "Created a new review request: #{new_pr["html_url"]}"
46
- else
47
- puts "Error: "
48
- puts new_pr
49
- return
50
- end
51
-
52
- if options.close?
53
- # close previous pull requests
54
- related.each {|pr| close_pull_request(pr) }
55
- end
56
- new_pr
57
- end
58
-
59
- desc "all_reviews", "list all pending reviews"
60
- def all_reviews
61
- reviews("open").group_by {|pr|
62
- pr["title"].match(/Please review (.+) \(v.+\)/)[1]
63
- }.each_pair {|branch_name, prs|
64
- puts "\nReviews for #{branch_name}:"
65
- prior_head = nil
66
- pr = nil
67
- our_reviews = reviews_for(branch_name)
68
- our_reviews.each {|pr|
69
- head = pr["title"].match(/Please review .+ \(v.+\) \((.+)\)/)[1]
70
- title = pr["title"].sub("Please review #{branch_name}", "")
71
- line = " #{title} : #{pulls_url}/#{pr["number"]}"
72
- if prior_head
73
- line << " : #{compare_cmd(prior_head, head)}"
74
- end
75
- puts line
76
- prior_head = head
77
- }
78
- puts " Latest review request: #{our_reviews.last["html_url"]}"
79
- }
80
- end
81
-
82
- private
83
-
84
- def compare_cmd(prior_head, head)
85
- "git diff #{prior_head}..#{head}"
86
- end
87
-
88
- def reviews(state = "open")
89
- @cached ||= {}
90
- return @cached[state] if @cached[state]
91
- cmd = "curl -s #{auth_string} #{pulls_url}?state=#{state}"
92
- puts cmd if @debug
93
- pull_requests = JSON.parse(`#{cmd}`)
94
- @cached[state] = pull_requests.select {|pr|
95
- pr["title"] =~ /Please review .+ \(v.+\)/
96
- }
97
- end
98
-
99
- def filter_for_branch(pull_requests, branch_name)
100
- pull_requests.select {|pr|
101
- pr["title"] =~ /Please review #{branch_name} \(v.+\)/
102
- }.sort_by {|pr| pr["title"] }
103
- end
104
-
105
- def open_reviews_for(branch_name)
106
- filter_for_branch(reviews("open"), branch_name)
107
- end
108
-
109
- def closed_reviews_for(branch_name)
110
- filter_for_branch(reviews("closed"), branch_name)
111
- end
112
-
113
- def reviews_for(branch_name)
114
- filter_for_branch(reviews("closed"), branch_name) + filter_for_branch(reviews("open"), branch_name)
115
- end
116
-
117
- def close_pull_request(pr)
118
- puts "closing review request: #{pr["title"]}"
119
- data = {
120
- "state" => "closed",
121
- "body" => "This review request has been replaced by: \n" + pr["body"]
122
- }.to_json
123
- cmd = "curl -s -X PATCH #{auth_string} #{pulls_url}/#{pr["number"]} -d '#{data}'"
124
- puts cmd if @debug
125
- `#{cmd}`
126
- end
127
-
128
- def api_host
129
- "https://api.github.com"
130
- end
131
-
132
- def pulls_url
133
- repo_user = config['repo_user']
134
- repo_name = config['repo_name']
135
- "#{api_host}/repos/#{repo_user}/#{repo_name}/pulls"
136
- end
137
-
138
- def auth_string
139
- username = config['username']
140
- password = config['password']
141
- "-u \"#{username}:#{password}\""
142
- end
143
-
144
- def config
145
- Flux.environment['github']
146
- end
147
-
148
- def git
149
- @git ||= Grit::Git.new(repo_path)
150
- end
151
-
152
- def repo
153
- @repo ||= Grit::Repo.new(repo_path)
154
- end
155
-
156
- def repo_path
157
- Flux.find_upwards('.git', Dir.pwd) or
158
- raise RCSError, "Couldn't find git repo starting at #{Dir.pwd}."
159
- end
160
- end
161
- end
162
- end
@@ -1,40 +0,0 @@
1
- require 'flux/git'
2
-
3
- module Flux
4
- module Workflows
5
- module MojoTech
6
- class Feature < Thor
7
- namespace :feature
8
-
9
- desc "start STORY_ID BRANCH_ID", "start working on a story"
10
- method_option :estimate, :type => :numeric
11
- method_option :parent_branch, :type => :string,
12
- :default => 'master',
13
- :aliases => '-b'
14
- def start(story_id, branch_id)
15
- invoke 'stories:grab', [story_id], :estimate => options[:estimate]
16
- invoke 'stories:start', [story_id]
17
-
18
- create_branch branch_id, options
19
- end
20
-
21
- private
22
-
23
- def create_branch(branch_id, options = {})
24
- up = Branch.remote(repo_path, 'origin', branch_id)
25
- parent = Branch.local(repo_path, options[:parent_branch])
26
- branch = Branch.local(repo_path, branch_id).
27
- create(parent).
28
- publish(up).
29
- track(up).
30
- checkout
31
- end
32
-
33
- def repo_path
34
- Flux.find_upwards('.git', Dir.pwd) or
35
- raise RCSError, "Couldn't find git repo starting at #{Dir.pwd}."
36
- end
37
- end
38
- end
39
- end
40
- end