github-reaper 0.0.1
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/.gitignore +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +28 -0
- data/README.md +2 -0
- data/bin/reaper +21 -0
- data/github-reaper.gemspec +16 -0
- data/lib/github-reaper.rb +119 -0
- data/lib/reaper/client.rb +30 -0
- data/lib/reaper/issue.rb +60 -0
- data/lib/reaper/version.rb +3 -0
- metadata +56 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.gem
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -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)
|
data/README.md
ADDED
data/bin/reaper
ADDED
@@ -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
|
data/lib/reaper/issue.rb
ADDED
@@ -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
|
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:
|