vcs2json 0.2.0 → 0.3.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 +4 -4
- data/lib/cli/main.rb +8 -6
- data/lib/vcs2json/git.rb +189 -89
- data/lib/vcs2json/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7320d73c75a5f2989baaf2789bd5809d1262da6e
|
4
|
+
data.tar.gz: 4e2a78f7370a848f1bf6d590d3e3b31516dab4d4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c85a8e3d1aa5177349eeb3dfb0e72f0bf155d4fd9202ffdb39c630f99701d97cad527611ad1d8357e9db9cf1af6cf654b187f5a805ef5ee23f76a7a9485cbf3a
|
7
|
+
data.tar.gz: cb97b86a43a3006d229c8221aaabd4930c0dd9b650335926b523b313b2b2f958427f14bb2d3d3d0a5ac257fc0c409b91a4f9de41d23667b3f9094bba3015a107
|
data/lib/cli/main.rb
CHANGED
@@ -2,13 +2,15 @@ require_relative 'cli_helper'
|
|
2
2
|
|
3
3
|
module Vcs2JsonCLI
|
4
4
|
class Main < Thor
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
method_option :ignore, type: :string, desc: "Specify location of .evocignore file"
|
6
|
+
method_option :case_id, type: :string, desc: "Specify case identifier. Used by .evocignore etc"
|
7
|
+
method_option :issue, :aliases => '-i', :type => :boolean, :default => false, :desc => "Attempt to extract issue ids from commit messages"
|
8
|
+
method_option :after, :aliases => '-a', :desc => "Only include commits after this date"
|
9
|
+
method_option :before, :aliases => '-b', :desc => "Only include commits before this date"
|
10
|
+
method_option :number, :aliases => '-n', type: :numeric, default: 10000, :desc => "The number of commits to dump"
|
11
|
+
desc "git [options]","Make a dump of the change-history of system using git, output on stdout"
|
10
12
|
def git
|
11
|
-
|
13
|
+
Vcs2Json::Git.new(options).execute
|
12
14
|
end
|
13
15
|
end
|
14
16
|
end
|
data/lib/vcs2json/git.rb
CHANGED
@@ -1,109 +1,209 @@
|
|
1
1
|
require_relative '../vcs2json_helper'
|
2
2
|
|
3
3
|
module Vcs2Json
|
4
|
-
|
4
|
+
class Git
|
5
|
+
# Generate separators between fields and commits
|
6
|
+
FIELD_SEP = Digest::SHA256.hexdigest Time.new.to_s + "field_sep"
|
7
|
+
COMMIT_SEP = Digest::SHA256.hexdigest Time.new.to_s + "commit_sep"
|
8
|
+
|
5
9
|
def initialize(opts)
|
6
|
-
|
10
|
+
@opts = opts
|
11
|
+
self.ignore = @opts[:ignore]
|
12
|
+
# Create a commit hash that defaults to creating new hashes given hash[:key]
|
13
|
+
# so we can do 'commit[:commit][:author][:name] = .. ' without creating the :commit and :author hashes first
|
14
|
+
@commits = Hash.new {|h,k| h[k] = Hash.new(&h.default_proc) }
|
15
|
+
# place to stare empty commit ids if they are encountered
|
16
|
+
@empty_commits = []
|
17
|
+
# used to decide if we should try to search for more commits
|
18
|
+
@oldest_commit_in_previous_search = Time.now
|
7
19
|
end
|
8
20
|
|
21
|
+
def execute
|
22
|
+
# recursively add commits as long as we have less than :number and there are still more commits to search
|
23
|
+
add_commits(@opts)
|
24
|
+
add_integer_mapping
|
9
25
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
26
|
+
# sort on date and prune excessive commits
|
27
|
+
sorted_and_pruned = @commits.sort_by {|id,commit| commit[:date]}.reverse.map {|(_,commit)| commit}.first(@opts[:number])
|
28
|
+
|
29
|
+
# print commits to stdout as json
|
30
|
+
$stdout.puts JSON.pretty_generate(sorted_and_pruned)
|
31
|
+
|
32
|
+
# print ids of empty commits to stderr
|
33
|
+
if !@empty_commits.empty?
|
34
|
+
STDERR.puts "EMPTY COMMITS"
|
35
|
+
STDERR.puts @empty_commits
|
36
|
+
end
|
37
|
+
# print additional info to stderr
|
38
|
+
STDERR.puts "\n\nExtracted #{sorted_and_pruned.size} commits."
|
20
39
|
end
|
21
40
|
|
41
|
+
def ignore
|
42
|
+
@ignore
|
43
|
+
end
|
22
44
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
45
|
+
def ignore= path
|
46
|
+
default_locations = ["#{Dir.pwd}/.evocignore","~/.evocignore"]
|
47
|
+
paths = (path.nil? ? default_locations : [path] + default_locations)
|
48
|
+
file = nil
|
49
|
+
ignore = []
|
50
|
+
paths.each do |p|
|
51
|
+
if File.exist?(p)
|
52
|
+
file = File.open(p)
|
53
|
+
STDERR.puts "Loading files to ignore from #{file.path}"
|
54
|
+
# return first match
|
55
|
+
break
|
56
|
+
end
|
57
|
+
end
|
58
|
+
if file.nil?
|
59
|
+
STDERR.puts ".evocignore not found. Tried #{paths}. All files will be used."
|
60
|
+
else
|
61
|
+
if @opts[:case_id].nil?
|
62
|
+
STDERR.puts "Id in .evocignore not specified, not ignoring any files."
|
63
|
+
else
|
64
|
+
ignore_file = YAML.load(file)
|
65
|
+
if ignore_file.key?(@opts[:case_id])
|
66
|
+
ignore = ignore_file[@opts[:case_id]]
|
67
|
+
if !ignore.nil?
|
68
|
+
STDERR.puts "Ignoring #{ignore.size} files"
|
69
|
+
end
|
70
|
+
else
|
71
|
+
STDERR.puts "The id: '#{@opts[:case_id]}' not found in #{file.path}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
@ignore = (ignore.nil? ? [] : ignore)
|
76
|
+
return @ignore
|
77
|
+
end
|
78
|
+
private
|
79
|
+
|
80
|
+
def add_commits(opts)
|
81
|
+
add_meta_information(opts)
|
82
|
+
add_change_information(opts)
|
83
|
+
|
84
|
+
if @commits.size < @opts[:number]
|
85
|
+
oldest_commit_in_this_search = get_oldest_commit
|
86
|
+
if oldest_commit_in_this_search != @oldest_commit_in_previous_search
|
87
|
+
# we found new commits in this search but still need more
|
88
|
+
@oldest_commit_in_previous_search = oldest_commit_in_this_search
|
89
|
+
add_commits(before: oldest_commit_in_this_search, number: (@opts[:number] - @commits.size)*2)
|
90
|
+
else
|
91
|
+
STDERR.puts "\nAsked for #{@opts[:number]} commits, only found #{@commits.size} non-empty commits. Searched all the way back to #{oldest_commit_in_this_search}."
|
92
|
+
end
|
93
|
+
end
|
30
94
|
end
|
31
95
|
|
96
|
+
def get_oldest_commit
|
97
|
+
oldest = nil
|
98
|
+
if !@commits.empty?
|
99
|
+
oldest = @commits.first[1][:date]
|
100
|
+
@commits.each do |sha,info|
|
101
|
+
if info[:date] < oldest
|
102
|
+
oldest = info[:date]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
return oldest
|
107
|
+
end
|
32
108
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
commits[sha][:author_date] = Time.parse fields[4]
|
59
|
-
commits[sha][:message] = fields[5]
|
60
|
-
|
61
|
-
# attempt to parse an issue id from the commit message
|
62
|
-
if @opts.issue?
|
63
|
-
commits[commit[0]][:issue] = parse_issue(commits[sha][:message])
|
109
|
+
def hash_2_gitoptions(opts)
|
110
|
+
before = opts[:before].nil? ? '' : "--before=\"#{opts[:before]}\""
|
111
|
+
after = opts[:after].nil? ? '' : "--after=\"#{opts[:after]}\""
|
112
|
+
number = opts[:number].nil? ? '' : "-n #{opts[:number]}"
|
113
|
+
return "#{before} #{after} #{number} --no-merges"
|
114
|
+
end
|
115
|
+
|
116
|
+
def add_meta_information(opts)
|
117
|
+
raw_commits = `git log #{hash_2_gitoptions(opts)} --pretty=format:'%H#{FIELD_SEP}%cn#{FIELD_SEP}%ce#{FIELD_SEP}%cd#{FIELD_SEP}%ad#{FIELD_SEP}%B#{COMMIT_SEP}'`
|
118
|
+
commits_info = raw_commits.encode('UTF-8', :invalid => :replace).split(COMMIT_SEP)
|
119
|
+
|
120
|
+
commits_info.each do |commit|
|
121
|
+
fields = commit.split(FIELD_SEP)
|
122
|
+
sha = fields[0].delete("\n") #remove astray newlines
|
123
|
+
@commits[sha][:sha] = sha
|
124
|
+
@commits[sha][:name] = fields[1]
|
125
|
+
@commits[sha][:email] = fields[2]
|
126
|
+
@commits[sha][:date] = Time.parse fields[3]
|
127
|
+
@commits[sha][:author_date] = Time.parse fields[4]
|
128
|
+
@commits[sha][:message] = fields[5]
|
129
|
+
|
130
|
+
# attempt to parse an issue id from the commit message
|
131
|
+
if @opts[:issue]
|
132
|
+
@commits[commit[0]][:issue] = parse_issue(@commits[sha][:message])
|
133
|
+
end
|
64
134
|
end
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
135
|
+
end
|
136
|
+
|
137
|
+
def add_change_information(opts)
|
138
|
+
commits_changes_type = `git log --pretty=format:'#{FIELD_SEP}%H' --name-status #{hash_2_gitoptions(opts)}`.split(FIELD_SEP)
|
139
|
+
commits_changes_type.each do |commit|
|
140
|
+
if !commit.empty?
|
141
|
+
lines = commit.split("\n")
|
142
|
+
sha = lines[0]
|
143
|
+
@commits[sha][:changes][:all] = []
|
144
|
+
if lines.size > 1
|
145
|
+
lines[1..-1].each do |line|
|
146
|
+
if !line.empty?
|
147
|
+
file_info = line.split("\t")
|
148
|
+
file_name = file_info[1]
|
149
|
+
status = file_info[0]
|
150
|
+
@commits[sha][:changes][:all] << file_name
|
151
|
+
@commits[sha][:changes][:details][file_name][:filename] = file_name
|
152
|
+
@commits[sha][:changes][:details][file_name][:status] = parse_status(status)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
# filter out ignored files
|
157
|
+
if !self.ignore.nil?
|
158
|
+
@commits[sha][:changes][:all].reject! {|i| self.ignore.include?(i)}
|
159
|
+
end
|
160
|
+
if @commits[sha][:changes][:all].empty?
|
161
|
+
@empty_commits << sha
|
162
|
+
@commits.delete(sha)
|
163
|
+
end
|
84
164
|
end
|
85
|
-
end
|
86
165
|
end
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
166
|
+
end
|
167
|
+
|
168
|
+
def add_integer_mapping
|
169
|
+
# create file_name -> integer mapping
|
170
|
+
mapping = Hash.new
|
171
|
+
index_counter = 0
|
172
|
+
@commits.each do |sha,info|
|
173
|
+
integer_representation = []
|
174
|
+
info[:changes][:all].each do |file|
|
175
|
+
if mapping[file].nil?
|
176
|
+
mapping[file] = index_counter
|
177
|
+
index_counter += 1
|
178
|
+
end
|
179
|
+
integer_representation << mapping[file]
|
180
|
+
info[:changes][:details][file][:id] = mapping[file]
|
181
|
+
end
|
182
|
+
info[:changes][:all].clear
|
183
|
+
info[:changes][:all] = integer_representation
|
101
184
|
end
|
102
|
-
|
103
|
-
info[:changes][:all] = integer_representation
|
104
|
-
end
|
185
|
+
end
|
105
186
|
|
106
|
-
|
187
|
+
# simply un-abbreviates the status code given by --name-status
|
188
|
+
def parse_status(abbreviated_status)
|
189
|
+
case abbreviated_status
|
190
|
+
when "A"
|
191
|
+
"added"
|
192
|
+
when "M"
|
193
|
+
"modified"
|
194
|
+
when "D"
|
195
|
+
"deleted"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
# attempts to parse an issue/bug id from the given commit message
|
201
|
+
def parse_issue(message)
|
202
|
+
if match = /(bug|issue) (?<id>\d+)/i.match(message)
|
203
|
+
return match[:id]
|
204
|
+
else
|
205
|
+
return ""
|
206
|
+
end
|
107
207
|
end
|
108
|
-
|
208
|
+
end
|
109
209
|
end
|
data/lib/vcs2json/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vcs2json
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Rolfsnes
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-06-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -159,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
159
159
|
version: '0'
|
160
160
|
requirements: []
|
161
161
|
rubyforge_project:
|
162
|
-
rubygems_version: 2.
|
162
|
+
rubygems_version: 2.5.1
|
163
163
|
signing_key:
|
164
164
|
specification_version: 4
|
165
165
|
summary: Create JSON dumps of change history from git etc.
|