march-audit 0.1.0
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.
- checksums.yaml +7 -0
- data/bin/march +48 -0
- data/lib/march.rb +3 -0
- data/lib/march/audit.rb +69 -0
- data/lib/march/client.rb +39 -0
- data/lib/march/repo.rb +75 -0
- metadata +134 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a2421a60347f0ca6c971068bdf1d724c2038fb3e
|
4
|
+
data.tar.gz: b3a873b83c16750b97cc68e1b7e348b4d5b559f7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cfbfe0e315383109415b7b23636648c2916eaf8325a5b896a62f69422ff014e0e015fe4eb7911bdaff2b804e578040c8199d6eccf5061a0f2bccae987732d145
|
7
|
+
data.tar.gz: b00118106ea8306132a43315f3c8c16a3ce050d291c84264563771e1e0415be2e719803cfa22672a66362af351a9d6ea56caaa37bdfd4b93cb821cf0273d490c
|
data/bin/march
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'march'
|
3
|
+
require 'thor'
|
4
|
+
|
5
|
+
module March
|
6
|
+
class Command < Thor
|
7
|
+
class_option :interactive, default: true, type: :boolean
|
8
|
+
class_option :destructive, default: false, type: :boolean
|
9
|
+
|
10
|
+
desc 'audit NAMESPACE REPO', 'audit of NAMESPACE/REPO branch health'
|
11
|
+
def audit(namespace, repo)
|
12
|
+
March::Audit.complete(namespace, repo, options[:interactive], options[:destructive])
|
13
|
+
rescue Octokit::NotFound
|
14
|
+
puts "Cannot find repository #{namespace}/#{repo}"
|
15
|
+
end
|
16
|
+
|
17
|
+
desc 'audit_merged NAMESPACE REPO', 'only audit merged branches'
|
18
|
+
def audit_merged(namespace, repo)
|
19
|
+
repo = March::Repo.new(namespace, repo)
|
20
|
+
March::Audit.merged(repo, options[:interactive], options[:destructive])
|
21
|
+
end
|
22
|
+
|
23
|
+
option :max_age, default: 3600*24*14, type: :numeric
|
24
|
+
desc 'audit_age NAMESPACE REPO', 'only audit branches for age'
|
25
|
+
def audit_age(namespace, repo)
|
26
|
+
ENV.store('MAX_AGE', options[:max_age])
|
27
|
+
repo = March::Repo.new(namespace, repo)
|
28
|
+
March::Audit.age(repo, options[:interactive], options[:destructive])
|
29
|
+
end
|
30
|
+
|
31
|
+
desc 'user', 'print the current username'
|
32
|
+
def user
|
33
|
+
puts March::Github.client.user.login
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
begin
|
39
|
+
March::Command.start
|
40
|
+
rescue Faraday::SSLError => e
|
41
|
+
puts e
|
42
|
+
puts 'Cannot verify SSL certificate, consider setting VERIFY_SSL=false'
|
43
|
+
rescue Interrupt
|
44
|
+
puts
|
45
|
+
puts 'Quitting...'
|
46
|
+
end
|
47
|
+
|
48
|
+
# vi:ft=ruby
|
data/lib/march.rb
ADDED
data/lib/march/audit.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'chronic_duration'
|
2
|
+
|
3
|
+
module March
|
4
|
+
class Audit
|
5
|
+
def self.complete(namespace, repository, interactive, destructive)
|
6
|
+
repo = March::Repo.new(namespace, repository)
|
7
|
+
|
8
|
+
merged(repo, interactive, destructive)
|
9
|
+
puts
|
10
|
+
age(repo, interactive, destructive)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.merged(repo, interactive, destructive)
|
14
|
+
puts 'Auditing merged branches...'
|
15
|
+
|
16
|
+
puts 'No merged branches.' if repo.merged_branches.empty?
|
17
|
+
return if repo.merged_branches.empty?
|
18
|
+
|
19
|
+
print 'These branches have been merged: '
|
20
|
+
puts repo.merged_branches.join(', ')
|
21
|
+
|
22
|
+
if interactive && destructive
|
23
|
+
print 'Delete merged branches? (y/N) '
|
24
|
+
bool = $stdin.gets
|
25
|
+
|
26
|
+
case bool.chomp
|
27
|
+
when 'y' then repo.delete_branches(repo.merged_branches)
|
28
|
+
else puts 'Canceled action'
|
29
|
+
end
|
30
|
+
elsif destructive
|
31
|
+
repo.delete_branches(repo.merged_branches)
|
32
|
+
else
|
33
|
+
puts 'Taking no action (destructive is disabled)'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.age(repo, interactive, destructive)
|
38
|
+
puts 'Auditing old branches...'
|
39
|
+
msgs = repo.branch_age.each_with_object({}) do |(name, dates), acc|
|
40
|
+
created = ChronicDuration.output(Time.now - dates[:oldest], weeks: true, units: 2)
|
41
|
+
updated = ChronicDuration.output(Time.now - dates[:newest], weeks: true, units: 2)
|
42
|
+
if (Time.now - dates[:newest]) > (ENV['MAX_AGE'] || 3600*24*14)
|
43
|
+
acc[name] = "Branch #{name} was created #{created} ago and updated #{updated} ago and is probably owned by #{repo.branch_owners[name].join(', ')}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
puts 'No old branches' if msgs.empty?
|
48
|
+
return if msgs.empty?
|
49
|
+
|
50
|
+
msgs.each { |_name, msg| puts msg }
|
51
|
+
|
52
|
+
old_branches = msgs.map(&:first)
|
53
|
+
|
54
|
+
if interactive && destructive
|
55
|
+
print 'Delete old branches? (y/N) '
|
56
|
+
delete = $stdin.gets
|
57
|
+
|
58
|
+
case delete.chomp
|
59
|
+
when 'y' then repo.delete_branches(old_branches)
|
60
|
+
else puts 'Canceled action'
|
61
|
+
end
|
62
|
+
elsif destructive
|
63
|
+
repo.delete_branches(old_branches)
|
64
|
+
else
|
65
|
+
puts 'Taking no action (destructive is disabled)'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/march/client.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'octokit'
|
2
|
+
require 'singleton'
|
3
|
+
require 'dotenv'
|
4
|
+
Dotenv.load
|
5
|
+
|
6
|
+
module March
|
7
|
+
module Github
|
8
|
+
class Configuration
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
attr_accessor :username, :password
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.configure
|
15
|
+
yield(configuration) if block_given?
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.configuration
|
19
|
+
Configuration.instance
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.client
|
23
|
+
return @client if @client
|
24
|
+
|
25
|
+
Octokit.api_endpoint = ENV['GITHUB_API'] if ENV['GITHUB_API']
|
26
|
+
opts = { :access_token => ENV.fetch('GITHUB_TOKEN') }
|
27
|
+
@client = Octokit::Client.new(opts)
|
28
|
+
verify =
|
29
|
+
case ENV['VERIFY_SSL'] || 'true'
|
30
|
+
when 'false', 'no' then false
|
31
|
+
else true
|
32
|
+
end
|
33
|
+
|
34
|
+
@client.connection_options[:ssl] = { verify: verify }
|
35
|
+
|
36
|
+
@client
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/march/repo.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'chronic_duration'
|
2
|
+
|
3
|
+
module March
|
4
|
+
class Repo
|
5
|
+
def initialize(namespace, repo)
|
6
|
+
@string = "#{namespace}/#{repo}"
|
7
|
+
@repo = client.repo(@string)
|
8
|
+
end
|
9
|
+
|
10
|
+
def client
|
11
|
+
March::Github.client
|
12
|
+
end
|
13
|
+
|
14
|
+
def id
|
15
|
+
@repo.id
|
16
|
+
end
|
17
|
+
|
18
|
+
def default_branch_name
|
19
|
+
@repo.default_branch
|
20
|
+
end
|
21
|
+
|
22
|
+
def default_branch
|
23
|
+
branches[branches.find_index { |b| b.name == default_branch_name }]
|
24
|
+
end
|
25
|
+
|
26
|
+
def default_tip
|
27
|
+
default_branch.commit.sha
|
28
|
+
end
|
29
|
+
|
30
|
+
def branches
|
31
|
+
@branches ||= @repo.rels[:branches].get.data
|
32
|
+
end
|
33
|
+
|
34
|
+
def compare_branches
|
35
|
+
branches.each_with_object({}) do |branch, acc|
|
36
|
+
acc[branch.name] = client.compare(id, default_tip, branch.commit.sha)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def merged_branches
|
41
|
+
@merged_branches ||= compare_branches.select do |_name, diff|
|
42
|
+
diff.commits.empty?
|
43
|
+
end.map(&:first).reject { |name| name == default_branch.name }
|
44
|
+
end
|
45
|
+
|
46
|
+
def branch_owners
|
47
|
+
@branch_owners ||=
|
48
|
+
compare_branches.each_with_object({}) do |(name, diff), acc|
|
49
|
+
author_ary = diff.commits.map { |c| c.commit.author }
|
50
|
+
authors =
|
51
|
+
author_ary.map do |h|
|
52
|
+
h.map { |k, v| v if k == :email }.compact
|
53
|
+
end.uniq
|
54
|
+
|
55
|
+
acc[name] = authors.flatten
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def branch_age
|
60
|
+
compare_branches.each_with_object({}) do |(name, diff), acc|
|
61
|
+
oldest = diff.merge_base_commit.commit.author.date
|
62
|
+
newest = diff.commits.map { |c| c.commit.author.date }.sort.last
|
63
|
+
res = { oldest: oldest, newest: newest }
|
64
|
+
acc[name] = res unless name == default_branch_name || newest.nil?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def delete_branches(branch_names)
|
69
|
+
branch_names.each do |name|
|
70
|
+
puts 'Deleting ' + name
|
71
|
+
client.delete_branch(id, name)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
metadata
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: march-audit
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David Gwilliam
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-11-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: octokit
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: dotenv
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: thor
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.19'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.19'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: chronic_duration
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.10'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.10'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.10'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.10'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '11.3'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '11.3'
|
97
|
+
description: tiny application that helps identify and cull branches that shouldn't
|
98
|
+
exist
|
99
|
+
email: david.gwilliam@slalom.com
|
100
|
+
executables:
|
101
|
+
- march
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- bin/march
|
106
|
+
- lib/march.rb
|
107
|
+
- lib/march/audit.rb
|
108
|
+
- lib/march/client.rb
|
109
|
+
- lib/march/repo.rb
|
110
|
+
homepage: https://github.com/slalompdx/march-audit
|
111
|
+
licenses:
|
112
|
+
- MIT
|
113
|
+
metadata: {}
|
114
|
+
post_install_message:
|
115
|
+
rdoc_options: []
|
116
|
+
require_paths:
|
117
|
+
- lib
|
118
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
requirements: []
|
129
|
+
rubyforge_project:
|
130
|
+
rubygems_version: 2.5.1
|
131
|
+
signing_key:
|
132
|
+
specification_version: 4
|
133
|
+
summary: automate audit of repository branches
|
134
|
+
test_files: []
|