flux 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/.flux.rb.sample +0 -2
- data/MOJOTECH.md +69 -0
- data/README.md +17 -83
- data/VERSION +1 -1
- data/flux.gemspec +5 -5
- data/lib/flux.rb +1 -2
- data/lib/flux/cli.rb +1 -0
- data/lib/flux/cli/feature.rb +30 -15
- data/lib/flux/cli/git.rb +66 -0
- data/lib/flux/cli/pivotal_tracker.rb +4 -15
- data/lib/flux/cli/review.rb +16 -15
- data/lib/flux/git.rb +45 -15
- data/lib/flux/pivotal_tracker.rb +15 -1
- data/lib/flux/util.rb +1 -1
- data/spec/flux/git_spec.rb +51 -0
- metadata +24 -24
- data/lib/flux/rcs.rb +0 -1
- data/lib/flux/rcs/git.rb +0 -162
- data/lib/flux/workflows/mojotech.rb +0 -40
data/.flux.rb.sample
CHANGED
data/MOJOTECH.md
ADDED
@@ -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
|
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
|
-
*
|
10
|
-
*
|
11
|
-
*
|
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
|
-
|
37
|
-
|
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`.
|
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
|
-
|
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
|
-
|
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.
|
1
|
+
0.0.7
|
data/flux.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "flux"
|
8
|
-
s.version = "0.0.
|
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-
|
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
|
-
"
|
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",
|
data/lib/flux.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'ostruct'
|
2
2
|
require 'pathname'
|
3
3
|
require 'thor'
|
4
|
-
require '
|
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
|
|
data/lib/flux/cli.rb
CHANGED
data/lib/flux/cli/feature.rb
CHANGED
@@ -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
|
-
|
22
|
+
link story_id, branch_id
|
18
23
|
end
|
19
24
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
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
|
-
|
35
|
-
|
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
|
data/lib/flux/cli/git.rb
ADDED
@@ -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.
|
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)
|
data/lib/flux/cli/review.rb
CHANGED
@@ -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 =
|
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
|
45
|
-
def request
|
46
|
-
branch =
|
47
|
-
|
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.
|
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 =
|
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] +
|
data/lib/flux/git.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
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
|
-
|
19
|
-
|
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(
|
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
|
-
|
39
|
-
|
40
|
-
@
|
41
|
-
@repo
|
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
|
-
|
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
|
-
|
107
|
+
Branch.remote(name, remote, repo_path)
|
108
|
+
end
|
79
109
|
end
|
80
110
|
end
|
81
111
|
end
|
data/lib/flux/pivotal_tracker.rb
CHANGED
@@ -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
|
data/lib/flux/util.rb
CHANGED
@@ -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.
|
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-
|
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: &
|
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: *
|
24
|
+
version_requirements: *86582250
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: pivotal-tracker
|
27
|
-
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: *
|
35
|
+
version_requirements: *86581640
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: grit
|
38
|
-
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: *
|
46
|
+
version_requirements: *86581140
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: github_api
|
49
|
-
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: *
|
57
|
+
version_requirements: *86580730
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: rspec
|
60
|
-
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: *
|
68
|
+
version_requirements: *86580390
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: bundler
|
71
|
-
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: *
|
79
|
+
version_requirements: *86579740
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: jeweler
|
82
|
-
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: *
|
90
|
+
version_requirements: *86577840
|
91
91
|
- !ruby/object:Gem::Dependency
|
92
92
|
name: rcov
|
93
|
-
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: *
|
101
|
+
version_requirements: *86577180
|
102
102
|
- !ruby/object:Gem::Dependency
|
103
103
|
name: rr
|
104
|
-
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: *
|
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
|
-
-
|
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: -
|
166
|
+
hash: -283853007
|
167
167
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
168
168
|
none: false
|
169
169
|
requirements:
|
data/lib/flux/rcs.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
require 'flux/rcs/git'
|
data/lib/flux/rcs/git.rb
DELETED
@@ -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
|