flux 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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