github-reaper 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org/'
2
+
3
+ gem 'octokit', '~> 3.0'
4
+ gem 'slop', '~>3.6.0'
5
+ gem 'colorize'
6
+ gem 'github-reaper', :path => '.'
@@ -0,0 +1,28 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ github-reaper (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ addressable (2.3.6)
10
+ colorize (0.7.3)
11
+ faraday (0.9.0)
12
+ multipart-post (>= 1.2, < 3)
13
+ multipart-post (2.0.0)
14
+ octokit (3.4.2)
15
+ sawyer (~> 0.5.3)
16
+ sawyer (0.5.5)
17
+ addressable (~> 2.3.5)
18
+ faraday (~> 0.8, < 0.10)
19
+ slop (3.6.0)
20
+
21
+ PLATFORMS
22
+ ruby
23
+
24
+ DEPENDENCIES
25
+ colorize
26
+ github-reaper!
27
+ octokit (~> 3.0)
28
+ slop (~> 3.6.0)
@@ -0,0 +1,2 @@
1
+ reaper
2
+ ======
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'github-reaper'
4
+ require 'slop'
5
+
6
+ command = nil
7
+ opts = Slop.parse(ARGV, strict: true, help: true) do
8
+ on 'u', 'user=', 'Username for Github login credentials.'
9
+ on 'p', 'password=', 'Password for Github login credentials.'
10
+ on 'r', 'repository=', 'Repository to access, e.g "amfeng/reaper"'
11
+ on 't', 'threshold=', 'Number of months to set as reap threshold.', as: Integer, default: 3
12
+ on 's', 'skip', 'Skip confirmation prompts.'
13
+
14
+ on 'version', 'Print the version.' do
15
+ puts Reaper::VERSION
16
+ exit
17
+ end
18
+ end
19
+
20
+
21
+ Reaper::CLI.new(opts).run!
@@ -0,0 +1,16 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), 'lib'))
2
+ require 'reaper/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'github-reaper'
6
+ s.version = Reaper::VERSION
7
+ s.summary = 'Reaper'
8
+ s.description = "Reaper helps find and close stale Github issues."
9
+ s.authors = ["Amber Feng"]
10
+ s.email = 'amber.feng@gmail.com'
11
+
12
+ s.files = `git ls-files`.split("\n")
13
+ s.test_files = `git ls-files -- test/test_*.rb`.split("\n")
14
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
15
+ s.require_paths = ['lib']
16
+ end
@@ -0,0 +1,119 @@
1
+ #!/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require 'colorize'
5
+ require 'time'
6
+
7
+ require 'reaper/version'
8
+ require 'reaper/issue'
9
+ require 'reaper/client'
10
+
11
+ module Reaper
12
+ REAPER_WARNING = <<-eos
13
+ Hi! This is a friendly (automated) warning from the reaper that this
14
+ issue hasn't been updated in 3 months and will be automatically closed
15
+ in 1 week.
16
+
17
+ If you don't want this to be closed yet, you should remove the `to-reap`
18
+ label and the 3 month timeout will reset. If you never want this to be
19
+ reaped, add the label `do-not-reap`.
20
+
21
+ Thanks! (:
22
+ eos
23
+
24
+ class CLI
25
+ def initialize(opts)
26
+ @client = Reaper::Client.instance
27
+ @client.set_repo(opts[:repository])
28
+
29
+ @stale_threshold = 3600 * 24 * 30 * opts[:threshold]
30
+ @skip_confirm = opts[:skip]
31
+ end
32
+
33
+ def run!
34
+ now = Time.now
35
+
36
+ puts "Welcome to Reaper! ⟝⦆ (fetching from `#{@client.repo}`)".white.bold
37
+ issues_reaped = false
38
+
39
+ # Fetch to-reap issues.
40
+
41
+ options = {
42
+ labels: 'to-reap'
43
+ }
44
+
45
+ puts "Finding issues to reap..."
46
+ issues = @client.list_issues(options)
47
+ issues.each do |issue|
48
+ issues_reaped = true
49
+ issue = Reaper::Issue.new(issue)
50
+ # TODO: Add force-all flag.
51
+
52
+ issue_action(issue, "Close issue?", @skip_confirm) do |issue|
53
+ issue.reap
54
+ puts "Issue was reaped.".green
55
+ end
56
+ end
57
+
58
+ # Fetch issues in ascending updated order.
59
+ options = {
60
+ sort: :updated,
61
+ direction: :asc
62
+ }
63
+
64
+ puts "Finding next reapable issues..."
65
+ issues = @client.list_issues(options)
66
+ issues.each do |issue|
67
+ issue = Reaper::Issue.new(issue)
68
+
69
+ # Skip if this task has already been closed or has a do-not-reap tag.
70
+ next if issue.closed?
71
+ next if issue.labels.include?('do-not-reap')
72
+
73
+ # If there's a to-reap tag, close it. Else, add a to-reap tag and
74
+ # a warning comment.
75
+ # FIXME: The timeout for closing an issue after a warning should be
76
+ # customizable and not based on run-time of the reaper (e.g. running
77
+ # reaper should be idempotent).
78
+
79
+ puts "\n"
80
+
81
+ # Break out of the whole loop if the issue's updated date is outside
82
+ # the range.
83
+ break if issue.updated_at > now - @stale_threshold
84
+
85
+ issues_reaped = true
86
+ issue_action(issue, "Add warning?", @skip_confirm) do |issue|
87
+ issue.warn(Reaper::REAPER_WARNING)
88
+ puts "Added `to-reap` to #{issue.number}"
89
+ end
90
+ end
91
+
92
+ if issues_reaped
93
+ puts "Nice, you're done!".green
94
+ else
95
+ puts "No reap-able issues, woohoo!".green
96
+ end
97
+ end
98
+
99
+ def issue_action(issue, action_label, skip_confirm=false, show_title=true, &blk)
100
+ return yield issue if skip_confirm
101
+
102
+ puts "= Issue ##{issue.number}: #{issue.title}".white if show_title
103
+ print "#{action_label} [Y]es, [N]o, or n[E]ver: ".yellow
104
+ input = $stdin.gets.chomp.downcase
105
+
106
+ case input
107
+ when 'y'
108
+ yield issue
109
+ when 'n'
110
+ puts "OK, skipping.".red
111
+ when 'e'
112
+ issue.protect
113
+ puts "OK, added `do-not-reap`.".green
114
+ else
115
+ issue_action(issue, action_label, false, &blk)
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,30 @@
1
+ require 'octokit'
2
+ require 'singleton'
3
+
4
+ module Reaper
5
+ class Client
6
+ include Singleton
7
+
8
+ attr_accessor :client, :repo
9
+
10
+ def initialize
11
+ # Look for .netrc credentials first; if they don't exist then prompt
12
+ # for login.
13
+ # TODO
14
+ @client = Octokit::Client.new(access_token: ENV['GITHUB_ACCESS_TOKEN'])
15
+ @repo = nil
16
+
17
+ # TODO: Add things other than Github.
18
+ end
19
+
20
+ def set_repo(repo)
21
+ @repo = repo
22
+ end
23
+
24
+ def method_missing(meth, *args, &block)
25
+ raise "Must provide a Github repository." unless @repo
26
+
27
+ @client.send(meth, *([@repo] + args), &block)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,60 @@
1
+ module Reaper
2
+ class Issue
3
+ attr_accessor :state, :title, :body, :labels
4
+
5
+ def initialize(issue)
6
+ @issue = issue
7
+
8
+ @title = @issue.title
9
+ @body = @issue.body
10
+ @state = @issue.state
11
+ @labels = issue.labels.map(&:name)
12
+ @buffered_comments = []
13
+
14
+ @client = Reaper::Client.instance
15
+ end
16
+
17
+ # Action methods
18
+
19
+ def reap
20
+ @labels << 'reaped'
21
+ @labels -= ['to-reap']
22
+ @client.close_issue(@issue.number, labels: @labels)
23
+ end
24
+
25
+ def warn(warning)
26
+ @labels << 'to-reap'
27
+ comment(warning)
28
+ save
29
+ end
30
+
31
+ def protect
32
+ @labels << 'do-not-reap'
33
+ @labels -= ['to-reap']
34
+ save
35
+ end
36
+
37
+ def closed?
38
+ @state == 'closed'
39
+ end
40
+
41
+ private
42
+
43
+ def comment(comment)
44
+ @buffered_comments << comment
45
+ end
46
+
47
+ def save
48
+ @client.update_issue(@issue.number, @title, @body, labels: @labels)
49
+
50
+ @buffered_comments.each do |comment|
51
+ @client.add_comment(@issue.number, comment)
52
+ end
53
+ @buffered_comments = []
54
+ end
55
+
56
+ def method_missing(meth, *args, &block)
57
+ @issue.send(meth, *args, &block)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,3 @@
1
+ module Reaper
2
+ VERSION = '0.0.1'
3
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: github-reaper
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Amber Feng
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-05-22 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Reaper helps find and close stale Github issues.
15
+ email: amber.feng@gmail.com
16
+ executables:
17
+ - reaper
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - Gemfile
23
+ - Gemfile.lock
24
+ - README.md
25
+ - bin/reaper
26
+ - github-reaper.gemspec
27
+ - lib/github-reaper.rb
28
+ - lib/reaper/client.rb
29
+ - lib/reaper/issue.rb
30
+ - lib/reaper/version.rb
31
+ homepage:
32
+ licenses: []
33
+ post_install_message:
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ! '>='
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ! '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubyforge_project:
51
+ rubygems_version: 1.8.23
52
+ signing_key:
53
+ specification_version: 3
54
+ summary: Reaper
55
+ test_files: []
56
+ has_rdoc: