vcs2json 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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.