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.
- 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
|