git_survey 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9027ae943fb7beb7a9032eaa5c83e2398de62c33
4
+ data.tar.gz: e80051924dbca0d3f09b6e84bc39fd300517d9c3
5
+ SHA512:
6
+ metadata.gz: 6e2ba6525da6fdd24d8792dad2ed375ad10a1c7405a22212a03c02c7def9b40bdb9088550f283c31d7d2f6e1550b711e513c0dbef0395937b6c3661bde11e244
7
+ data.tar.gz: ef3d4018695e307988fc3b768cf29a4cf9ae62d6c76a5e5f00d0687e5723381086d9acc4b676e3cbfcdadf003805ad4ad8f3d3e81344e8143d4a60aab1981387
data/bin/git-survey ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'git_survey'
5
+
6
+ ::GitSurvey.main(ARGV)
data/lib/git_survey.rb ADDED
@@ -0,0 +1,57 @@
1
+ require 'time'
2
+ require 'json'
3
+ require 'digest'
4
+
5
+ require 'git_survey/version'
6
+ require 'git_survey/arguments_parser.rb'
7
+ require 'git_survey/shell_adapter.rb'
8
+
9
+ EXECUTABLE_NAME = 'git-survey'.freeze
10
+
11
+ # Generates a report on a git project's history
12
+ module GitSurvey
13
+ def self.executable_hash(options)
14
+ {
15
+ 'date' => options.scan_date,
16
+ 'anonymized' => options.anonymize,
17
+ 'branch' => options.git_branch,
18
+ 'hotFiles' => options.number_of_hotfiles
19
+ }
20
+ end
21
+ private_class_method :executable_hash
22
+
23
+ def self.repo_hash(options)
24
+ {
25
+ 'name' => ShellAdapter.repo_name(options.anonymize, options.input_directory),
26
+ 'dateOfInit' => ShellAdapter.init_date(options.input_directory),
27
+ 'dateOfLastCommit' => ShellAdapter.latest_activity_date(options.input_directory),
28
+ 'yearsWorkedOn' => ShellAdapter.activity_interval(options.input_directory),
29
+ 'numberOfFiles' => ShellAdapter.number_of_files(options.input_directory),
30
+ 'numberOfCommits' => ShellAdapter.number_of_commits(options.input_directory,
31
+ options.git_branch),
32
+ 'numberOfBranches' => ShellAdapter.number_of_branches(options.input_directory)
33
+ }
34
+ end
35
+ private_class_method :repo_hash
36
+
37
+ def self.main(_argv)
38
+ options = Parser.parse(ARGV)
39
+
40
+ # Google JSON Style Guide (camel case for keys)
41
+ # https://google.github.io/styleguide/jsoncstyleguide.xml
42
+
43
+ result = {
44
+ EXECUTABLE_NAME => executable_hash(options),
45
+ 'repo' => repo_hash(options),
46
+ 'hotFiles' => ShellAdapter.hot_files(options.anonymize,
47
+ options.input_directory,
48
+ options.number_of_hotfiles),
49
+ 'authors' => ShellAdapter.authors(options.anonymize,
50
+ options.input_directory)
51
+ }
52
+
53
+ puts JSON.pretty_generate(result)
54
+
55
+ exit 0
56
+ end
57
+ end
@@ -0,0 +1,81 @@
1
+ require 'optparse'
2
+
3
+ # https://docs.ruby-lang.org/en/2.1.0/OptionParser.html
4
+ Options = Struct.new(:anonymize,
5
+ :git_branch,
6
+ :input_directory,
7
+ :number_of_hotfiles,
8
+ :scan_date)
9
+
10
+ # Parses command line arguments
11
+ class Parser
12
+ def self.default_options
13
+ result = Options.new
14
+ result.anonymize = false
15
+ result.git_branch = 'master'
16
+ result.number_of_hotfiles = 10
17
+ result.scan_date = Time.now.iso8601(3)
18
+ result.input_directory = '.'
19
+ result
20
+ end
21
+ private_class_method :default_options
22
+
23
+ def self.parse(argv)
24
+ # If no arguments supplied, print help
25
+ argv << '-h' if argv.empty?
26
+
27
+ result = default_options
28
+
29
+ options_parser = OptionParser.new do |o|
30
+ o.banner = 'Usage: git-survey.rb [options] [input directory]'
31
+
32
+ o.on('-a',
33
+ '--anonymize',
34
+ "Anonymizes the output (#{result.anonymize})") do |v|
35
+ result.anonymize = v
36
+ end
37
+
38
+ o.on('-bBRANCH',
39
+ '--branch=BRANCH',
40
+ "Git branch (#{result.git_branch})") do |v|
41
+ result.git_branch = v
42
+ end
43
+
44
+ o.on('-nNUMBER',
45
+ '--number=NUMBER',
46
+ "Number of hot files to display (#{result.number_of_hotfiles})") do |v|
47
+ result.number_of_hotfiles = v
48
+ end
49
+
50
+ o.on('-tTODAY',
51
+ '--today=TODAY',
52
+ "Today's date for testing purposes (string)") do |v|
53
+ result.scan_date = v
54
+ end
55
+
56
+ o.on('-h',
57
+ '--help',
58
+ 'Prints this help') do
59
+ puts options_parser
60
+ exit 0
61
+ end
62
+ end
63
+
64
+ begin
65
+ options_parser.parse!(argv)
66
+ rescue StandardError => exception
67
+ puts exception
68
+ puts options_parser
69
+ exit 1
70
+ end
71
+
72
+ result.input_directory = argv.pop
73
+ if result.input_directory.nil? || !Dir.exist?(result.input_directory)
74
+ puts 'Can\'t find directory ' + result.input_directory
75
+ Parser.parse %w[--help]
76
+ exit 0
77
+ end
78
+
79
+ result
80
+ end
81
+ end
@@ -0,0 +1,10 @@
1
+ # Convenience utilities.
2
+ class Helpers
3
+ def self.anonymized(anonymize, string)
4
+ if anonymize
5
+ Digest::MD5.hexdigest(string)[0...10]
6
+ else
7
+ string
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,101 @@
1
+ require_relative 'helpers'
2
+
3
+ COMMAND_REPO_NAME = 'basename `git rev-parse --show-toplevel`'.freeze
4
+ COMMAND_REPO_INIT_DATE = 'git log --date=iso8601-strict --reverse |head -3 ' +
5
+ '|grep "Date"'.freeze
6
+ COMMAND_LATEST_ACTIVITY_DATE = 'git log --date=iso8601-strict |head -3 ' +
7
+ '|grep "Date"'.freeze
8
+ COMMAND_COMMITS_NUMBER = 'git rev-list --count '.freeze
9
+ COMMAND_FILES_NUMBER = 'git ls-files | wc -l'.freeze
10
+ COMMAND_BRANCHES_NUMBER = 'git branch | wc -l'.freeze
11
+ COMMAND_AUTHORS = 'git shortlog -s -n'.freeze
12
+ COMMAND_FILES_HOT = 'git log --pretty=format: --name-only | sort | uniq -c | ' +
13
+ 'sort -rg | head -'.freeze
14
+
15
+ # Adapter which handles shell access.
16
+ class ShellAdapter
17
+ def self.run_shell_command(directory, cmd)
18
+ result = `cd #{directory}; #{cmd}`
19
+ result
20
+ end
21
+ private_class_method :run_shell_command
22
+
23
+ def self.date_at_the_end_of_line(string)
24
+ string.split(' ').last
25
+ end
26
+ private_class_method :date_at_the_end_of_line
27
+
28
+ def self.hash_from_line_with_delimiter(anonymized,
29
+ directory,
30
+ command,
31
+ delimiter)
32
+
33
+ list = run_shell_command(directory, command)
34
+ .split("\n")
35
+ empty = 'empty' # git outputs empty lines too, they should be removed.
36
+
37
+ result = {}
38
+ list.each do |line|
39
+ parts = line.split(delimiter)
40
+ value = parts.first
41
+ key = parts.last == value ? empty : Helpers.anonymized(anonymized, parts.last)
42
+
43
+ result[key] = value.to_i
44
+ end
45
+ result.reject { |key, _value| key.to_s.match(empty) }
46
+ end
47
+ private_class_method :hash_from_line_with_delimiter
48
+
49
+ # basename `git rev-parse --show-toplevel`
50
+ def self.repo_name(anonimize, directory)
51
+ name = run_shell_command(directory, COMMAND_REPO_NAME).strip
52
+ Helpers.anonymized(anonimize, name)
53
+ end
54
+
55
+ # git log --date=iso8601-strict --reverse |head -3 |grep "Date"
56
+ def self.init_date(directory)
57
+ string = run_shell_command(directory, COMMAND_REPO_INIT_DATE)
58
+ Time.iso8601(date_at_the_end_of_line(string))
59
+ end
60
+
61
+ # git log --date=iso8601-strict |head -3 |grep "Date"
62
+ def self.latest_activity_date(directory)
63
+ string = run_shell_command(directory, COMMAND_LATEST_ACTIVITY_DATE)
64
+ Time.iso8601(date_at_the_end_of_line(string))
65
+ end
66
+
67
+ def self.activity_interval(directory)
68
+ earliest = init_date(directory).to_f
69
+ latest = latest_activity_date(directory).to_f
70
+
71
+ diff = latest - earliest
72
+ (diff / (365 * 24 * 60 * 60)).round(2)
73
+ end
74
+
75
+ # git rev-list --count <revision>
76
+ def self.number_of_commits(directory, branch)
77
+ run_shell_command(directory, COMMAND_COMMITS_NUMBER + branch).to_i
78
+ end
79
+
80
+ # git ls-files | wc -l
81
+ def self.number_of_files(directory)
82
+ run_shell_command(directory, COMMAND_FILES_NUMBER).to_i
83
+ end
84
+
85
+ # git branch | wc -l
86
+ def self.number_of_branches(directory)
87
+ run_shell_command(directory, COMMAND_BRANCHES_NUMBER).to_i
88
+ end
89
+
90
+ # git shortlog -s -n
91
+ def self.authors(anonymized, directory)
92
+ hash_from_line_with_delimiter(anonymized, directory, COMMAND_AUTHORS, "\t")
93
+ end
94
+
95
+ # git log --pretty=format: --name-only | sort | uniq -c | sort -rg | head -10
96
+ def self.hot_files(anonymized, directory, number)
97
+ number_with_empty_lines = number.to_i + 1
98
+ command = COMMAND_FILES_HOT + number_with_empty_lines.to_s
99
+ hash_from_line_with_delimiter(anonymized, directory, command, ' ')
100
+ end
101
+ end
@@ -0,0 +1,3 @@
1
+ module GitSurvey
2
+ VERSION = '0.1.0'.freeze
3
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: git_survey
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrei Nagy
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-02-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ description: Analyzes the git history and outputs to console.
56
+ email:
57
+ - nagy.andrei@gmail.com
58
+ executables:
59
+ - git-survey
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - bin/git-survey
64
+ - lib/git_survey.rb
65
+ - lib/git_survey/arguments_parser.rb
66
+ - lib/git_survey/helpers.rb
67
+ - lib/git_survey/shell_adapter.rb
68
+ - lib/git_survey/version.rb
69
+ homepage: https://github.com/andreinagy/git-survey
70
+ licenses:
71
+ - MIT
72
+ metadata:
73
+ allowed_push_host: https://rubygems.org
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubyforge_project:
90
+ rubygems_version: 2.5.2.3
91
+ signing_key:
92
+ specification_version: 4
93
+ summary: Script that displays a project's git history.
94
+ test_files: []