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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 15d7ae8091e66e7a08873392edccfcbb5a97fdd2
4
- data.tar.gz: d0b611ebafc4ced7993371f8836e1f9aa81e6cbd
3
+ metadata.gz: 7320d73c75a5f2989baaf2789bd5809d1262da6e
4
+ data.tar.gz: 4e2a78f7370a848f1bf6d590d3e3b31516dab4d4
5
5
  SHA512:
6
- metadata.gz: 74b531c87495036b5f7622fc58310d9a9cb81ebe1eee55e3aa2d293cd15702ec7dc46cfdbebf97c8b2843009491d78d6f1b5b5e6e16aa1ec3b69ede98bf2f18c
7
- data.tar.gz: e3fa836d920cc77295a10de443419fdd11e8ddef8e901b94acf85343cb5daf6de41934df344b14ce507865952d87d78281e3b10e04206243ade963b6df3b6c7a
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
- method_option :issue, :aliases => '-i', :type => :boolean, :default => false, :desc => "Attempt to extract issue ids from commit messages"
6
- method_option :after, :aliases => '-a', :desc => "Only include commits after this date"
7
- method_option :before, :aliases => '-b', :desc => "Only include commits before this date"
8
- method_option :number, :aliases => '-n', :desc => "The number of commits to dump"
9
- desc "git [options]","Make a dump of the change-history of system using git, output on stdout"
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
- $stdout.puts Vcs2Json::Git.new(options).execute
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
- class Git
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
- @opts = opts
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
- # simply un-abbreviates the status code given by --name-status
11
- def parse_status(abbreviated_status)
12
- case abbreviated_status
13
- when "A"
14
- "added"
15
- when "M"
16
- "modified"
17
- when "D"
18
- "deleted"
19
- end
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
- # attempts to parse an issue/bug id from the given commit message
24
- def parse_issue(message)
25
- if match = /(bug|issue) (?<id>\d+)/i.match(message)
26
- return match[:id]
27
- else
28
- return ""
29
- end
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 execute
34
- before = @opts[:before].nil? ? '' : "--before=\"#{@opts[:before]}\""
35
- after = @opts[:after].nil? ? '' : "--after=\"#{@opts[:after]}\""
36
- number = @opts[:number].nil? ? '' : "-n #{@opts[:number]}"
37
- options = "#{before} #{after} #{number} --no-merges"
38
-
39
- # Generate separators between fields and commits
40
- field_sep = Digest::SHA256.hexdigest Time.new.to_s + "field_sep"
41
- commit_sep = Digest::SHA256.hexdigest Time.new.to_s + "commit_sep"
42
-
43
- # Create a new hash that defaults to creating new hashes given hash[:key]
44
- # so we can do 'commit[:commit][:author][:name] = .. ' without creating the :commit and :author hashes first
45
- commits = Hash.new {|h,k| h[k] = Hash.new(&h.default_proc) }
46
-
47
- #TODO: log this => print "Getting metadata on the commits..\r"
48
- raw_commits = `git log #{options} --pretty=format:'%H#{field_sep}%cn#{field_sep}%ce#{field_sep}%cd#{field_sep}%ad#{field_sep}%B#{commit_sep}'`
49
- commits_info = raw_commits.encode('UTF-8', :invalid => :replace).split(commit_sep)
50
-
51
- commits_info.each do |commit|
52
- fields = commit.split(field_sep)
53
- sha = fields[0].delete("\n") #remove astray newlines
54
- commits[sha][:sha] = sha
55
- commits[sha][:name] = fields[1]
56
- commits[sha][:email] = fields[2]
57
- commits[sha][:date] = Time.parse fields[3]
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
- end
66
-
67
-
68
- commits_changes_type = `git log --pretty=format:'#{field_sep}%H' --name-status #{options}`.split(field_sep)
69
- commits_changes_type.each do |commit|
70
- if !commit.empty?
71
- lines = commit.split("\n")
72
- sha = lines[0]
73
- commits[sha][:changes][:all] = []
74
- if lines.size > 1
75
- lines[1..-1].each do |line|
76
- if !line.empty?
77
- file_info = line.split("\t")
78
- file_name = file_info[1]
79
- status = file_info[0]
80
- commits[sha][:changes][:all] << file_name
81
- commits[sha][:changes][:details][file_name][:filename] = file_name
82
- commits[sha][:changes][:details][file_name][:status] = parse_status(status)
83
- end
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
- end
88
-
89
- # create file_name -> integer mapping
90
- mapping = Hash.new
91
- index_counter = 0
92
- commits.each do |sha,info|
93
- integer_representation = []
94
- info[:changes][:all].each do |file|
95
- if mapping[file].nil?
96
- mapping[file] = index_counter
97
- index_counter += 1
98
- end
99
- integer_representation << mapping[file]
100
- info[:changes][:details][file][:id] = mapping[file]
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
- info[:changes][:all].clear
103
- info[:changes][:all] = integer_representation
104
- end
185
+ end
105
186
 
106
- JSON.pretty_generate(commits.sort_by {|id,commit| commit[:date]}.reverse.map {|(sha,commit)| commit})
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
- end
208
+ end
109
209
  end
@@ -1,3 +1,3 @@
1
1
  module Vcs2json
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
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.2.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-04-24 00:00:00.000000000 Z
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.4.6
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.