gistory 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6f0db94a0ca0b970909c4f8118e5343ecd8bb9dd
4
+ data.tar.gz: f8d219bce6020ff7c737f3dcc70e0fe2ff08390b
5
+ SHA512:
6
+ metadata.gz: 01dd9e2a1ddfb4e136f6efbe8360b676ebf5c221c1903b10daf1eb8555a77a7bba97b8b3e6cb65c0131c30e8e9c166a54a3c17763094e980428ad3e50590aed5
7
+ data.tar.gz: 12b412a19b76a0a8a4d4f0159ccb77ea2bfa25f6df209aee24d91dedb99905ae186b0889f79f36f62c406410ac4ca7be7364911545c54f29ba6bbf0957646732
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ NOTES.txt
@@ -0,0 +1,15 @@
1
+ Metrics/LineLength:
2
+ Max: 120
3
+
4
+ Documentation:
5
+ Enabled: false
6
+
7
+ Metrics/AbcSize:
8
+ # The ABC size is a calculated magnitude, so this number can be a Fixnum or a Float.
9
+ # http://c2.com/cgi/wiki?AbcMetric
10
+ Max: 20
11
+ Exclude:
12
+ - 'test/**/*_test.rb' # ignore tests which typically have high B count due to many asserts
13
+
14
+ Metrics/MethodLength:
15
+ Max: 15
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.3
5
+ before_install: gem install bundler -v 1.14.3
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development, :test do
6
+ gem 'byebug' # debugger
7
+ gem 'pry' # better console
8
+ gem 'pry-byebug' # pry integration for byebug
9
+ end
10
+
11
+ group :test do
12
+ gem 'flexmock' # mock object
13
+ gem 'simplecov', require: false # code coverage
14
+ end
@@ -0,0 +1,40 @@
1
+ # Gistory
2
+
3
+ If you use bundler and git, and want to know when a gem you are using was updated, `gistory` comes to your rescue, simply:
4
+
5
+ ```shell
6
+ gem install gistory
7
+ cd /path/to/repo
8
+ gistory sidekiq
9
+ ```
10
+
11
+ and you'll see something like:
12
+ ```
13
+ Gem: sidekiq
14
+ Current version: 4.2.7
15
+
16
+ Change history:
17
+ 4.2.7 on Tue, 7 Feb 2017 16:05 +01:00 (commit c6edf321)
18
+ 4.2.6 on Wed, 30 Nov 2016 13:47 +01:00 (commit bf6a0d17)
19
+ 4.2.5 on Tue, 22 Nov 2016 14:48 -05:00 (commit 20ff5148)
20
+ 4.1.4 on Wed, 9 Nov 2016 14:31 +01:00 (commit 05a3c549)
21
+ ```
22
+
23
+ By default `gistory` only looks at the 100 last changes to Gemfile.lock
24
+ if you want to see farther in the past run:
25
+
26
+ ```shell
27
+ gistory sidekiq -m10000
28
+ ```
29
+
30
+ Note that if the gem was added, then removed, and then added again, `gistory` will
31
+ only show the latest version changes up until it was removed.
32
+
33
+ ## Roadmap
34
+
35
+ - use red for changes in the major version, blue for changes in the minor version
36
+ - support other VCSs like subversion, mercurial, etc.
37
+ - detect if the gem was added, then removed and then added again
38
+ - use a libgit2 binding instead of the git cli, how much faster it is?
39
+ - remove bundler dep
40
+ - add yard doc
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'test'
6
+ t.libs << 'lib'
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task default: :test
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'gistory'
5
+ require 'pry'
6
+
7
+ root_path = "#{File.dirname(__FILE__)}/.."
8
+
9
+ # require all files in lib for quick access to them in the console
10
+ Dir["#{root_path}/lib/**/*.rb"].each { |file| require file }
11
+
12
+ def reload!
13
+ verbosity = $VERBOSE
14
+ begin
15
+ $VERBOSE = nil
16
+ files = $LOADED_FEATURES.select { |feat| feat =~ %r{/gistory/} }
17
+ files.each { |file| load(file) }
18
+ ensure
19
+ $VERBOSE = verbosity
20
+ end
21
+ true
22
+ end
23
+
24
+ Pry.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path('../../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'gistory'
7
+
8
+ Gistory::Cli::Main.new(repo_path: Dir.getwd, args: ARGV).run
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'gistory/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'gistory'
8
+ spec.version = Gistory::VERSION
9
+ spec.authors = ['Sergio Medina']
10
+ spec.email = ['medinasergio@gmail.com']
11
+
12
+ spec.summary = 'Gistory: Know exactly when a gem was updated in your Gemfile.lock'
13
+ spec.description = 'Gistory: Know exactly when a gem was updated in your Gemfile.lock'
14
+ spec.homepage = 'https://www.github.com/serch/gistory'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = 'exe'
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_dependency 'bundler', '~> 1.14' # FIXME: what is the minimum bundler version I need?
24
+ spec.add_dependency 'colorize'
25
+
26
+ spec.add_development_dependency 'rake', '~> 10.0'
27
+ spec.add_development_dependency 'minitest', '~> 5.0'
28
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ require 'gistory/version'
3
+
4
+ require 'gistory/cli/main'
5
+ require 'gistory/cli/arg_parser'
6
+ require 'gistory/cli/io'
7
+ require 'gistory/configuration'
8
+
9
+ require 'gistory/errors'
10
+ require 'gistory/commit'
11
+ require 'gistory/version_change'
12
+ require 'gistory/git_repo'
13
+ require 'gistory/change_log'
14
+
15
+ module Gistory
16
+ class << self
17
+ def config
18
+ @config ||= Gistory::Configuration.new
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: truew
2
+ require 'bundler'
3
+
4
+ module Gistory
5
+ class ChangeLog
6
+ LOCKFILE = 'Gemfile.lock'.freeze
7
+
8
+ def initialize(repo:)
9
+ @repo = repo
10
+ end
11
+
12
+ def changelog_for_gem(gem_name)
13
+ version_changes = []
14
+ previous_version = nil
15
+ lockfile_changes = @repo.changes_to_file(LOCKFILE)
16
+
17
+ lockfile_changes.each do |commit|
18
+ gem_spec = gem_spec_at_commit_hash(commit.short_hash, gem_name)
19
+
20
+ # we reached the end, the gem didn't exist back then
21
+ # TODO: what if it was added then removed and then added again?
22
+ break if gem_spec.nil?
23
+
24
+ # only store version changes of this gem
25
+ unless gem_spec.version.to_s == previous_version
26
+ version_changes << VersionChange.new(commit: commit, version: gem_spec.version)
27
+ previous_version = gem_spec.version.to_s
28
+ end
29
+ end
30
+
31
+ version_changes
32
+ end
33
+
34
+ private
35
+
36
+ def gem_spec_at_commit_hash(commit_hash, gem_name)
37
+ lockfile_content = @repo.file_content_at_commit(commit_hash, LOCKFILE)
38
+ lockfile = Bundler::LockfileParser.new(lockfile_content)
39
+ gem_spec = lockfile.specs.find { |spec| spec.name == gem_name }
40
+ gem_spec
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+ require 'optparse'
3
+
4
+ module Gistory
5
+ module Cli
6
+ class ArgParser
7
+ def initialize(args:, io: Gistory::Cli::Io.new)
8
+ @args = args
9
+ @config = Gistory.config
10
+ @parser = create_parser(@config)
11
+ @io = io
12
+ end
13
+
14
+ def parse
15
+ @parser.parse!(@args)
16
+
17
+ parse_gem_name
18
+ @io.error("extra parameters ignored: #{@args}") unless @args.count.zero?
19
+ @config
20
+ rescue OptionParser::InvalidOption => err
21
+ raise(Gistory::ParserError, err.message)
22
+ end
23
+
24
+ def to_s
25
+ @parser.to_s
26
+ end
27
+
28
+ private
29
+
30
+ def parse_gem_name
31
+ gem_name = @args.shift
32
+ raise(Gistory::ParserError, 'No gem specified') unless gem_name
33
+ @config.gem_name = gem_name
34
+ end
35
+
36
+ def create_parser(config)
37
+ parser = OptionParser.new
38
+ parser.banner = 'Usage: gistory <gem_name> [options]'
39
+
40
+ add_specific_options(parser, config)
41
+ add_common_options(parser)
42
+
43
+ parser
44
+ end
45
+
46
+ def add_specific_options(parser, config)
47
+ parser.separator ''
48
+ parser.separator 'Specific options:'
49
+
50
+ add_max_lockfile_changes(parser, config)
51
+ end
52
+
53
+ def add_common_options(parser)
54
+ parser.separator ''
55
+ parser.separator 'Common options:'
56
+
57
+ add_help(parser)
58
+ add_version(parser)
59
+ end
60
+
61
+ def add_max_lockfile_changes(parser, config)
62
+ default = config.max_lockfile_changes
63
+ description = "max number of changes to the lock file (default #{default})"
64
+ parser.on('-m', '--max-lockfile-changes [INTEGER]', Integer, description) do |m|
65
+ raise(Gistory::ParserError, 'argument --max-lockfile-changes must be an integer') if m.nil?
66
+ config.max_lockfile_changes = m
67
+ end
68
+ end
69
+
70
+ def add_help(parser)
71
+ parser.on_tail('-h', '--help', 'Show this message') do
72
+ @io.puts parser
73
+ exit
74
+ end
75
+ end
76
+
77
+ def add_version(parser)
78
+ parser.on_tail('--version', 'Show version') do
79
+ @io.puts "gistory version #{Gistory::VERSION}"
80
+ exit
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ require 'colorize'
3
+
4
+ module Gistory
5
+ module Cli
6
+ class Io
7
+ def initialize(out: $stdout, err: $stderr)
8
+ @out = out
9
+ @err = err
10
+ end
11
+
12
+ def puts(msg)
13
+ @out.puts(msg)
14
+ end
15
+
16
+ def error(msg)
17
+ @err.puts(msg.red)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+ module Gistory
3
+ module Cli
4
+ class Main
5
+ def initialize(repo_path:, args:, io: Gistory::Cli::Io.new)
6
+ @repo_path = repo_path
7
+ @args = args
8
+ @io = io
9
+ end
10
+
11
+ def run
12
+ repo = GitRepo.new(path: @repo_path)
13
+ parser = Cli::ArgParser.new(args: @args, io: @io)
14
+ config = parser.parse
15
+ history(repo, config.gem_name)
16
+ rescue Gistory::ParserError => error
17
+ @io.error error.message
18
+ @io.puts parser
19
+ rescue Gistory::Error => error
20
+ @io.error error.message
21
+ end
22
+
23
+ private
24
+
25
+ def history(repo, gem_name)
26
+ changes = ChangeLog.new(repo: repo).changelog_for_gem(gem_name)
27
+
28
+ if changes.empty?
29
+ raise(Gistory::Error, "Gem '#{gem_name}' not found in lock file, maybe a typo?")
30
+ end
31
+
32
+ @io.puts "Gem: #{gem_name}"
33
+ @io.puts "Current version: #{changes.first.version}"
34
+ @io.puts ''
35
+
36
+ @io.puts 'Change history:'
37
+ changes.each do |change|
38
+ @io.puts "#{change.version} on #{change.date.strftime('%a, %e %b %Y %H:%M %Z')} (commit #{change.short_hash})"
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ require 'date'
3
+
4
+ module Gistory
5
+ class Commit
6
+ attr_reader :short_hash, :date
7
+
8
+ def initialize(short_hash:, date:)
9
+ @short_hash = short_hash
10
+ @date = DateTime.parse(date.to_s)
11
+ freeze
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ module Gistory
3
+ class Configuration
4
+ attr_accessor :gem_name, :max_lockfile_changes
5
+
6
+ def initialize
7
+ @max_lockfile_changes = 100
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+ module Gistory
3
+ class Error < StandardError; end
4
+
5
+ class ParserError < Error; end
6
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ require 'english'
3
+
4
+ module Gistory
5
+ class GitRepo
6
+ def initialize(path:)
7
+ raise(Gistory::Error, 'This is not a valid git repository') unless Dir.exist?(File.join(path, '.git'))
8
+ raise(Gistory::Error, 'git is not available, please install it') unless git_cli_available?
9
+ end
10
+
11
+ def changes_to_file(filename)
12
+ max_count = Gistory.config.max_lockfile_changes
13
+ hashes_and_dates = git("log --pretty=format:'%h|%cD' --max-count=#{max_count} --follow #{filename}")
14
+ to_commits(hashes_and_dates.split("\n"))
15
+ end
16
+
17
+ def file_content_at_commit(commit_hash, filename)
18
+ git("show #{commit_hash}:#{filename}")
19
+ end
20
+
21
+ private
22
+
23
+ def git_cli_available?
24
+ system('which git > /dev/null 2>&1')
25
+ end
26
+
27
+ def to_commits(hashes_and_dates)
28
+ hashes_and_dates.map do |hash_and_date|
29
+ commit_hash, date = hash_and_date.split('|')
30
+ Commit.new(short_hash: commit_hash, date: date)
31
+ end
32
+ end
33
+
34
+ def git(command)
35
+ out = `git #{command}`
36
+ raise('Git CLI command failed') unless $CHILD_STATUS.success?
37
+ out
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ module Gistory
3
+ VERSION = '0.1.0'.freeze
4
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ require 'forwardable'
3
+
4
+ module Gistory
5
+ class VersionChange
6
+ extend Forwardable
7
+ def_delegators :@commit, :short_hash, :date
8
+
9
+ attr_reader :commit, :version
10
+
11
+ def initialize(commit:, version:)
12
+ @commit = commit
13
+ @version = version
14
+ freeze
15
+ end
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gistory
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sergio Medina
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-02-09 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.14'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: colorize
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '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
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.0'
69
+ description: 'Gistory: Know exactly when a gem was updated in your Gemfile.lock'
70
+ email:
71
+ - medinasergio@gmail.com
72
+ executables:
73
+ - gistory
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - ".rubocop.yml"
79
+ - ".travis.yml"
80
+ - Gemfile
81
+ - README.md
82
+ - Rakefile
83
+ - bin/console
84
+ - bin/setup
85
+ - exe/gistory
86
+ - gistory.gemspec
87
+ - lib/gistory.rb
88
+ - lib/gistory/change_log.rb
89
+ - lib/gistory/cli/arg_parser.rb
90
+ - lib/gistory/cli/io.rb
91
+ - lib/gistory/cli/main.rb
92
+ - lib/gistory/commit.rb
93
+ - lib/gistory/configuration.rb
94
+ - lib/gistory/errors.rb
95
+ - lib/gistory/git_repo.rb
96
+ - lib/gistory/version.rb
97
+ - lib/gistory/version_change.rb
98
+ homepage: https://www.github.com/serch/gistory
99
+ licenses: []
100
+ metadata: {}
101
+ post_install_message:
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ requirements: []
116
+ rubyforge_project:
117
+ rubygems_version: 2.6.8
118
+ signing_key:
119
+ specification_version: 4
120
+ summary: 'Gistory: Know exactly when a gem was updated in your Gemfile.lock'
121
+ test_files: []