octopolo 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +21 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +6 -0
  6. data/Gemfile +3 -0
  7. data/Guardfile +5 -0
  8. data/MIT-LICENSE +20 -0
  9. data/README.markdown +55 -0
  10. data/Rakefile +38 -0
  11. data/bash_completion.sh +13 -0
  12. data/bin/octopolo +21 -0
  13. data/bin/op +21 -0
  14. data/lib/octopolo.rb +15 -0
  15. data/lib/octopolo/changelog.rb +27 -0
  16. data/lib/octopolo/cli.rb +210 -0
  17. data/lib/octopolo/commands/accept_pull.rb +8 -0
  18. data/lib/octopolo/commands/compare_release.rb +9 -0
  19. data/lib/octopolo/commands/deployable.rb +8 -0
  20. data/lib/octopolo/commands/github_auth.rb +5 -0
  21. data/lib/octopolo/commands/new_branch.rb +9 -0
  22. data/lib/octopolo/commands/new_deployable.rb +8 -0
  23. data/lib/octopolo/commands/new_staging.rb +8 -0
  24. data/lib/octopolo/commands/octopolo_setup.rb +5 -0
  25. data/lib/octopolo/commands/pivotal_auth.rb +5 -0
  26. data/lib/octopolo/commands/pull_request.rb +13 -0
  27. data/lib/octopolo/commands/signoff.rb +10 -0
  28. data/lib/octopolo/commands/stage_up.rb +8 -0
  29. data/lib/octopolo/commands/stale_branches.rb +11 -0
  30. data/lib/octopolo/commands/sync_branch.rb +11 -0
  31. data/lib/octopolo/commands/tag_release.rb +13 -0
  32. data/lib/octopolo/config.rb +146 -0
  33. data/lib/octopolo/convenience_wrappers.rb +46 -0
  34. data/lib/octopolo/dated_branch_creator.rb +81 -0
  35. data/lib/octopolo/git.rb +262 -0
  36. data/lib/octopolo/github.rb +95 -0
  37. data/lib/octopolo/github/commit.rb +45 -0
  38. data/lib/octopolo/github/pull_request.rb +126 -0
  39. data/lib/octopolo/github/pull_request_creator.rb +127 -0
  40. data/lib/octopolo/github/user.rb +40 -0
  41. data/lib/octopolo/jira/story_commenter.rb +26 -0
  42. data/lib/octopolo/pivotal.rb +44 -0
  43. data/lib/octopolo/pivotal/story_commenter.rb +19 -0
  44. data/lib/octopolo/pull_request_merger.rb +99 -0
  45. data/lib/octopolo/renderer.rb +37 -0
  46. data/lib/octopolo/reports.rb +18 -0
  47. data/lib/octopolo/scripts.rb +23 -0
  48. data/lib/octopolo/scripts/accept_pull.rb +67 -0
  49. data/lib/octopolo/scripts/compare_release.rb +52 -0
  50. data/lib/octopolo/scripts/deployable.rb +27 -0
  51. data/lib/octopolo/scripts/github_auth.rb +87 -0
  52. data/lib/octopolo/scripts/new_branch.rb +34 -0
  53. data/lib/octopolo/scripts/new_deployable.rb +14 -0
  54. data/lib/octopolo/scripts/new_staging.rb +15 -0
  55. data/lib/octopolo/scripts/octopolo_setup.rb +55 -0
  56. data/lib/octopolo/scripts/pivotal_auth.rb +44 -0
  57. data/lib/octopolo/scripts/pull_request.rb +127 -0
  58. data/lib/octopolo/scripts/signoff.rb +85 -0
  59. data/lib/octopolo/scripts/stage_up.rb +26 -0
  60. data/lib/octopolo/scripts/stale_branches.rb +54 -0
  61. data/lib/octopolo/scripts/sync_branch.rb +37 -0
  62. data/lib/octopolo/scripts/tag_release.rb +70 -0
  63. data/lib/octopolo/templates/pull_request_body.erb +24 -0
  64. data/lib/octopolo/user_config.rb +112 -0
  65. data/lib/octopolo/version.rb +3 -0
  66. data/lib/octopolo/week.rb +130 -0
  67. data/octopolo.gemspec +31 -0
  68. data/spec/.DS_Store +0 -0
  69. data/spec/octopolo/cli_spec.rb +310 -0
  70. data/spec/octopolo/config_spec.rb +344 -0
  71. data/spec/octopolo/convenience_wrappers_spec.rb +80 -0
  72. data/spec/octopolo/dated_branch_creator_spec.rb +143 -0
  73. data/spec/octopolo/git_spec.rb +419 -0
  74. data/spec/octopolo/github/commit_spec.rb +59 -0
  75. data/spec/octopolo/github/pull_request_creator_spec.rb +174 -0
  76. data/spec/octopolo/github/pull_request_spec.rb +291 -0
  77. data/spec/octopolo/github/user_spec.rb +65 -0
  78. data/spec/octopolo/github_spec.rb +169 -0
  79. data/spec/octopolo/jira/stor_commenter_spec.rb +30 -0
  80. data/spec/octopolo/pivotal/story_commenter_spec.rb +34 -0
  81. data/spec/octopolo/pivotal_spec.rb +61 -0
  82. data/spec/octopolo/pull_request_merger_spec.rb +144 -0
  83. data/spec/octopolo/renderer_spec.rb +35 -0
  84. data/spec/octopolo/scripts/accept_pull_spec.rb +76 -0
  85. data/spec/octopolo/scripts/compare_release_spec.rb +115 -0
  86. data/spec/octopolo/scripts/deployable_spec.rb +52 -0
  87. data/spec/octopolo/scripts/github_auth_spec.rb +156 -0
  88. data/spec/octopolo/scripts/new_branch_spec.rb +41 -0
  89. data/spec/octopolo/scripts/new_deployable_spec.rb +18 -0
  90. data/spec/octopolo/scripts/new_staging_spec.rb +18 -0
  91. data/spec/octopolo/scripts/octopolo_setup_spec.rb +120 -0
  92. data/spec/octopolo/scripts/pivotal_auth_spec.rb +77 -0
  93. data/spec/octopolo/scripts/pull_request_spec.rb +217 -0
  94. data/spec/octopolo/scripts/signoff_spec.rb +139 -0
  95. data/spec/octopolo/scripts/stage_up_spec.rb +52 -0
  96. data/spec/octopolo/scripts/stale_branches_spec.rb +81 -0
  97. data/spec/octopolo/scripts/sync_branch_spec.rb +57 -0
  98. data/spec/octopolo/scripts/tag_release_spec.rb +108 -0
  99. data/spec/octopolo/user_config_spec.rb +167 -0
  100. data/spec/octopolo_spec.rb +7 -0
  101. data/spec/spec_helper.rb +29 -0
  102. data/spec/support/engine_yard.cache +0 -0
  103. data/spec/support/sample_octopolo.yml +2 -0
  104. data/spec/support/sample_user.yml +2 -0
  105. data/templates/lib.erb +23 -0
  106. data/templates/script.erb +7 -0
  107. data/templates/spec.erb +29 -0
  108. metadata +344 -0
@@ -0,0 +1,95 @@
1
+ require "octokit"
2
+ # TODO this needs to get moved out of scripts and into its own new module
3
+ require_relative "scripts/github_auth"
4
+
5
+ module Octopolo
6
+ module GitHub
7
+ extend UserConfigWrapper
8
+
9
+ # Used as the name of a user if we can't find it on GitHub's API
10
+ UNKNOWN_USER = "Unknown User"
11
+
12
+ # Public: Perform the given block if the user has valid GitHub credentials
13
+ #
14
+ # When performing anything that connects to GitHub, wrap with
15
+ # GitHub.connect to ensure that the user's credentials are all set up
16
+ # before running anything.
17
+ #
18
+ # Example:
19
+ #
20
+ # GitHub.connect do
21
+ # # PullRequest.create or whatever
22
+ # end
23
+ def self.connect &block
24
+ GitHub.check_connection
25
+
26
+ yield
27
+ rescue GitHub::BadCredentials, GitHub::TryAgain => error
28
+ CLI.say error.message
29
+ end
30
+
31
+ # Public: A GitHub client object
32
+ def self.client(options = {})
33
+ Octokit::Client.new(options.merge(login: user_config.github_user, access_token: user_config.github_token))
34
+ rescue UserConfig::MissingGitHubAuth
35
+ raise TryAgain, "No GitHub API token stored. Please run `bundle exec github-auth` to generate your token."
36
+ end
37
+
38
+ # Public: A GitHub client configured to crawl through pages
39
+ def self.crawling_client
40
+ client(auto_traversal: true)
41
+ end
42
+
43
+ # Public: Check that GitHub credentials have been properly set up
44
+ def self.check_connection
45
+ # we don't care about the output, just try to hit the API
46
+ client.user && nil
47
+ rescue Octokit::Unauthorized
48
+ raise BadCredentials, "Your stored credentials were rejected by GitHub. Run `bundle exec github-auth` to generate a new token."
49
+ end
50
+
51
+ def self.pull_request *args
52
+ client.pull_request *args
53
+ end
54
+
55
+ def self.pull_request_commits *args
56
+ client.pull_request_commits *args
57
+ end
58
+
59
+ def self.issue_comments *args
60
+ client.issue_comments *args
61
+ end
62
+
63
+ def self.pull_requests *args
64
+ crawling_client.pull_requests *args
65
+ end
66
+
67
+ def self.create_pull_request *args
68
+ client.create_pull_request *args
69
+ end
70
+
71
+ def self.add_comment *args
72
+ client.add_comment *args
73
+ end
74
+
75
+ def self.user username
76
+ client.user(username)
77
+ rescue
78
+ Hashie::Mash.new(name: UNKNOWN_USER)
79
+ end
80
+
81
+ def self.org_repos org_name="sportngin"
82
+ crawling_client.organization_repositories org_name
83
+ end
84
+
85
+ def self.excluded_users
86
+ ["tst-octopolo"]
87
+ end
88
+
89
+ # now that you've set up your credentials, try again
90
+ TryAgain = Class.new(StandardError)
91
+ # the credentials you've entered are bad
92
+ BadCredentials = Class.new(StandardError)
93
+ end
94
+ end
95
+
@@ -0,0 +1,45 @@
1
+ require_relative "../github"
2
+ require_relative "user"
3
+
4
+ module Octopolo
5
+ module GitHub
6
+ class Commit
7
+ attr_accessor :commit_data
8
+
9
+ # Public: Instantiate a new Commit wrapper object
10
+ #
11
+ # commit_data - The GitHub API data about the commit
12
+ def initialize commit_data
13
+ self.commit_data = commit_data
14
+ end
15
+
16
+ # Public: Find commits for a given pull request
17
+ #
18
+ # pull_request - A PullRequest or other object responding to #repo_name
19
+ # and #number
20
+ #
21
+ # Returns an Array of Commit objects
22
+ def self.for_pull_request pull_request
23
+ GitHub.pull_request_commits(pull_request.repo_name, pull_request.number).map do |c|
24
+ Commit.new c
25
+ end
26
+ end
27
+
28
+ # Public: GitHub User that is the author of the commit
29
+ #
30
+ # Returns a Hashie::Mash object of the GitHub User
31
+ def author
32
+ GitHub::User.new(commit_data.author.login)
33
+ rescue NoMethodError
34
+ GitHub::User.new(GitHub::UNKNOWN_USER)
35
+ end
36
+
37
+ # Public: The name of the author of the commit
38
+ #
39
+ # Returns a String containing the author's name
40
+ def author_name
41
+ author.author_name
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,126 @@
1
+ require_relative "../github"
2
+ require_relative "commit"
3
+ require_relative "pull_request_creator"
4
+ require_relative "user"
5
+ require_relative "../week"
6
+ require "octokit"
7
+
8
+ module Octopolo
9
+ module GitHub
10
+ class PullRequest
11
+ attr_accessor :pull_request_data
12
+ attr_accessor :repo_name
13
+ attr_accessor :number
14
+
15
+ def initialize repo_name, number, pull_request_data = nil
16
+ raise MissingParameter if repo_name.nil? or number.nil?
17
+
18
+ self.repo_name = repo_name
19
+ self.number = number
20
+ self.pull_request_data = pull_request_data
21
+ end
22
+
23
+ # Public: All closed pull requests for a given repo
24
+ #
25
+ # repo_name - Full name ("account/repo") of the repo in question
26
+ #
27
+ # Returns an Array of PullRequest objects
28
+ def self.closed repo_name
29
+ GitHub.pull_requests(repo_name, "closed").map do |data|
30
+ new repo_name, data.number, data
31
+ end
32
+ end
33
+
34
+ # Public: Create a pull request for the given repo
35
+ #
36
+ # repo_name - Full name ("account/repo") of the repo in question
37
+ # options - Hash of pull request information
38
+ # title: Title of the pull request
39
+ # description: Brief description of the pull request
40
+ # release: Boolean indicating if the pull request is for Release
41
+ # destination_branch: Which branch to merge into
42
+ # source_branch: Which branch to be merged
43
+ #
44
+ # Returns a PullRequest instance
45
+ def self.create repo_name, options
46
+ # create via the API
47
+ creator = PullRequestCreator.perform(repo_name, options)
48
+ # wrap in our class
49
+ new repo_name, creator.number, creator.pull_request_data
50
+ end
51
+
52
+ def pull_request_data
53
+ @pull_request_data ||= GitHub.pull_request(repo_name, number)
54
+ rescue Octokit::NotFound
55
+ raise NotFound
56
+ end
57
+
58
+ def title
59
+ pull_request_data.title
60
+ end
61
+
62
+ def url
63
+ pull_request_data.html_url
64
+ end
65
+
66
+ def branch
67
+ pull_request_data.head.ref
68
+ end
69
+
70
+ def mergeable?
71
+ pull_request_data.mergeable
72
+ end
73
+
74
+ def week
75
+ Week.parse pull_request_data.closed_at
76
+ end
77
+
78
+ def commenter_names
79
+ exlude_octopolo_user (comments.map{ |comment| GitHub::User.new(comment.user.login).author_name }.uniq - author_names)
80
+ end
81
+
82
+ def author_names
83
+ exlude_octopolo_user commits.map(&:author_name).uniq
84
+ end
85
+
86
+ def exlude_octopolo_user(user_list)
87
+ user_list.reject{|u| GitHub.excluded_users.include?(u) }
88
+ end
89
+
90
+ def body
91
+ pull_request_data.body || ""
92
+ end
93
+
94
+ def external_urls
95
+ # extract http and https URLs from the body
96
+ URI.extract body, %w(http https)
97
+ end
98
+
99
+ def human_app_name
100
+ repo = repo_name.split("/").last
101
+ repo.split("_").map(&:capitalize).join(" ")
102
+ end
103
+
104
+ def commits
105
+ @commits ||= Commit.for_pull_request self
106
+ end
107
+
108
+ def comments
109
+ @comments ||= GitHub.issue_comments(repo_name, number)
110
+ end
111
+
112
+ # Public: Add a comment to the pull request
113
+ #
114
+ # message - A String containing the desired comment body
115
+ def write_comment(message)
116
+ GitHub.add_comment repo_name, number, ":octocat: #{message}"
117
+ rescue Octokit::UnprocessableEntity => error
118
+ raise CommentFailed, "Unable to write the comment: '#{error.message}'"
119
+ end
120
+
121
+ MissingParameter = Class.new StandardError
122
+ NotFound = Class.new StandardError
123
+ CommentFailed = Class.new StandardError
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,127 @@
1
+ require_relative "../renderer"
2
+
3
+ module Octopolo
4
+ module GitHub
5
+ class PullRequestCreator
6
+ include ConfigWrapper
7
+ # for instantiating the pull request creator
8
+ attr_accessor :repo_name
9
+ attr_accessor :options
10
+ # for caputuring the created pull request information
11
+ attr_accessor :number
12
+ attr_accessor :pull_request_data
13
+
14
+ # Public: Create a pull request for the given repo with the given options
15
+ #
16
+ # repo_name - Full name ("account/repo") of the repo in question
17
+ # options - Hash of pull request information
18
+ # title: Title of the pull request
19
+ # destination_branch: Which branch to merge into
20
+ # source_branch: Which branch to be merged
21
+ def initialize repo_name, options
22
+ self.repo_name = repo_name
23
+ self.options = options
24
+ end
25
+
26
+ # Public: Create a pull request for the given repo with the given options
27
+ #
28
+ # repo_name - Full name ("account/repo") of the repo in question
29
+ # options - Hash of pull request information
30
+ # title: Title of the pull request
31
+ # destination_branch: Which branch to merge into
32
+ # source_branch: Which branch to be merged
33
+ #
34
+ # Returns the PullRequestCreator instance
35
+ def self.perform repo_name, options
36
+ new(repo_name, options).tap do |creator|
37
+ creator.perform
38
+ end
39
+ end
40
+
41
+ # Public: Create the pull request
42
+ #
43
+ # Returns an array with the first element being the pull request's
44
+ # number, the second being a Mash of the response from GitHub's API
45
+ def perform
46
+ result = GitHub.create_pull_request(repo_name, destination_branch, source_branch, title, body)
47
+ # capture the information
48
+ self.number = result.number
49
+ self.pull_request_data = result
50
+ rescue => e
51
+ raise CannotCreate, e.message
52
+ end
53
+
54
+ # Public: The created pull request's details
55
+ def pull_request_data
56
+ @pull_request_data || raise(NotYetCreated)
57
+ end
58
+
59
+ # Public: The created pull request's number
60
+ def number
61
+ @number || raise(NotYetCreated)
62
+ end
63
+
64
+ # Public: Branch to merge the pull request into
65
+ #
66
+ # Returns a String with the branch name
67
+ def destination_branch
68
+ options[:destination_branch] || raise(MissingAttribute)
69
+ end
70
+
71
+ # Public: Branch to merge into the destination branch
72
+ #
73
+ # Returns a String with the branch name
74
+ def source_branch
75
+ options[:source_branch] || raise(MissingAttribute)
76
+ end
77
+
78
+ # Public: Title of the pull request
79
+ #
80
+ # Returns a String with the title
81
+ def title
82
+ options[:title] || raise(MissingAttribute)
83
+ end
84
+
85
+ # Public: The Pivotal Tracker story IDs associated with the pull request
86
+ #
87
+ # Returns an Array of Strings
88
+ def pivotal_ids
89
+ options[:pivotal_ids] || []
90
+ end
91
+
92
+ # Public: Jira Issue IDs associated with the pull request
93
+ #
94
+ # Returns an Array of Strings
95
+ def jira_ids
96
+ options[:jira_ids] || []
97
+ end
98
+
99
+ # Public: Jira Url associated with the pull request
100
+ #
101
+ # Returns Jira Url
102
+ def jira_url
103
+ config.jira_url
104
+ end
105
+
106
+ # Public: The body (primary copy) of the pull request
107
+ #
108
+ # Returns a String
109
+ def body
110
+ Renderer.render Renderer::PULL_REQUEST_BODY, body_locals
111
+ end
112
+
113
+ # Public: The local variables to pass into the template
114
+ def body_locals
115
+ {
116
+ pivotal_ids: pivotal_ids,
117
+ jira_ids: jira_ids,
118
+ jira_url: jira_url,
119
+ }
120
+ end
121
+
122
+ MissingAttribute = Class.new(StandardError)
123
+ NotYetCreated = Class.new(StandardError)
124
+ CannotCreate = Class.new(StandardError)
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,40 @@
1
+ module Octopolo
2
+ module GitHub
3
+ class User
4
+ @cache ||= {}
5
+
6
+ attr_accessor :login
7
+
8
+ # Public: Instantiate a new User
9
+ #
10
+ # login - The login (username) of the given user
11
+ def initialize login
12
+ self.login = login
13
+ end
14
+
15
+ # Public: The real name of the author
16
+ #
17
+ # If the user has a name in GitHub, #author_name returns this. Otherwise
18
+ # returns the given login.
19
+ #
20
+ # Returns a String containing the name
21
+ def author_name
22
+ user_data.name || login
23
+ end
24
+
25
+ # Private: The raw GitHub API data for the user
26
+ #
27
+ # Returns a Hashie::Mash containing the data
28
+ def user_data
29
+ User.user_data login
30
+ end
31
+
32
+ # Private: The raw GitHub API data for the given login
33
+ #
34
+ # Returns a Hashie::Mash containing the data
35
+ def self.user_data login
36
+ @cache[login] ||= GitHub.user login
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,26 @@
1
+ require 'jiralicious'
2
+
3
+ module Octopolo
4
+ module Jira
5
+ class StoryCommenter
6
+ include ConfigWrapper
7
+
8
+ attr_accessor :issue
9
+ attr_accessor :comment
10
+
11
+ def initialize(issue_id, comment)
12
+ Jiralicious.configure do |jira_config|
13
+ jira_config.username = config.jira_user
14
+ jira_config.password = config.jira_password
15
+ jira_config.uri = config.jira_url
16
+ end
17
+ self.issue = Jiralicious::Issue.find issue_id
18
+ self.comment = comment
19
+ end
20
+
21
+ def perform
22
+ issue.comments.add(comment)
23
+ end
24
+ end
25
+ end
26
+ end