gistory 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.
@@ -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: []