git_miner 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0aa67e6c72b1527778f786fb604807437789516188c2b0aac1d6520701571762
4
+ data.tar.gz: 9323e1fcd8d7ab220b10ef465baf8a158252b7bf6f317366707f7954a69f11e3
5
+ SHA512:
6
+ metadata.gz: 6d6e3c36b03808d24f4e8ae8dafd7cec12da24d586cabc637d667ed76a5c9c446f017764a80f57a1cefd2e0ca349c61bc2eb3948cb76998c97992aaa4497218b
7
+ data.tar.gz: aaeef8f54d352eb93649de28f2d54f107b1ac7e5d856505660f9a7a649aa8c436062216ffaf2ca5da04771e3cd35292b9963fa620fb91213bdcd804c904e8ca6
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ *.gem
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.5.5
7
+ before_install: gem install bundler -v 2.0.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in git_miner.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,42 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ git_miner (0.1.0)
5
+ concurrent-ruby (~> 1.1)
6
+ parallel (~> 1.17)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ concurrent-ruby (1.1.5)
12
+ diff-lcs (1.3)
13
+ parallel (1.17.0)
14
+ rake (10.5.0)
15
+ rake-compiler (1.0.7)
16
+ rake
17
+ rspec (3.8.0)
18
+ rspec-core (~> 3.8.0)
19
+ rspec-expectations (~> 3.8.0)
20
+ rspec-mocks (~> 3.8.0)
21
+ rspec-core (3.8.0)
22
+ rspec-support (~> 3.8.0)
23
+ rspec-expectations (3.8.3)
24
+ diff-lcs (>= 1.2.0, < 2.0)
25
+ rspec-support (~> 3.8.0)
26
+ rspec-mocks (3.8.0)
27
+ diff-lcs (>= 1.2.0, < 2.0)
28
+ rspec-support (~> 3.8.0)
29
+ rspec-support (3.8.0)
30
+
31
+ PLATFORMS
32
+ ruby
33
+
34
+ DEPENDENCIES
35
+ bundler (~> 2.0)
36
+ git_miner!
37
+ rake (~> 10.0)
38
+ rake-compiler (~> 1.0)
39
+ rspec (~> 3.0)
40
+
41
+ BUNDLED WITH
42
+ 2.0.1
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # GitMiner
2
+
3
+ Pet project I build experimenting with different concepts.
4
+
5
+ GitMiner allow for "mining" of vanity git prefixes.
6
+
7
+ This gem is a work in progress (core functionality is working).
8
+
9
+
10
+ ## Installation
11
+
12
+ Installation will add the `git-mine` binary.
13
+
14
+ ```ruby
15
+ gem 'git_miner'
16
+ ```
17
+
18
+
19
+ ## Usage
20
+
21
+ Though `git`, using `git mine [DESIRED_PREFIX]` will invoke the required logic.
22
+
23
+ Eg.:
24
+ ```
25
+ git mine c0ffee
26
+ ```
27
+
28
+ Some extra options are available. `git-mine -h` will display the help section (default options are optimised for performance).
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ require "rake/extensiontask"
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
8
+
9
+ Rake::ExtensionTask.new "git_miner_ext" do |ext|
10
+ ext.lib_dir = "lib/git_miner"
11
+ end
12
+
13
+ Rake::Task[:test].prerequisites << :compile
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "git_miner"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/git-mine ADDED
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ STDOUT.sync = true
4
+
5
+ require 'git_miner'
6
+
7
+
8
+ require 'optparse'
9
+
10
+ options = {
11
+ engine: :ruby,
12
+ dispatch: :parallel,
13
+ }
14
+
15
+ OptionParser.new do |opts|
16
+ opts.banner = "Usage: example.rb [options]"
17
+
18
+ opts.on("--engine [ruby|c]", [:ruby, :c], "Desired engine (default: ruby)") do |x|
19
+ options[:engine] = x
20
+ end
21
+
22
+ opts.on("--dispatch [simple|parallel]", [:simple, :parallel], "Desired dispatch (default: parallel)") do |x|
23
+ options[:dispatch] = x
24
+ end
25
+
26
+ opts.on_tail("-h", "--help", "Show this message") do
27
+ puts opts
28
+ exit
29
+ end
30
+ end.parse!
31
+
32
+ unless ARGV.size == 1
33
+ puts opts
34
+ exit 1
35
+ end
36
+
37
+ prefix = ARGV.first
38
+
39
+ engine = case(options[:engine])
40
+ when :ruby
41
+ GitMiner::Engine::RubyEngine
42
+ when :c
43
+ GitMiner::Engine::CExtensionEngine
44
+ else
45
+ raise ArgumentError, "unexpected engine"
46
+ end
47
+
48
+ dispatch = case(options[:dispatch])
49
+ when :simple
50
+ GitMiner::Dispatch::SimpleDispatch
51
+ when :parallel
52
+ GitMiner::Dispatch::ParallelDispatch
53
+ else
54
+ raise ArgumentError, "unexpected engine"
55
+ end
56
+
57
+ miner = GitMiner::Core.new(
58
+ engine: engine,
59
+ dispatch: dispatch,
60
+ prefix: prefix,
61
+ )
62
+ miner.validate
63
+ miner.mine
64
+ miner.report
65
+ miner.alter
data/bin/setup ADDED
@@ -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,116 @@
1
+ #include "ruby.h"
2
+ #include "openssl/sha.h"
3
+ #include <stdio.h>
4
+ #include <stdbool.h>
5
+
6
+ VALUE GitMinerExt = Qnil;
7
+
8
+ void Init_git_miner_ext();
9
+
10
+ VALUE rb_sha1_hexdigest(VALUE self, VALUE rbString);
11
+ VALUE rb_sha1_mine(VALUE self, int authorOffset, int committerOffset, int qty);
12
+
13
+ void Init_git_miner_ext() {
14
+ GitMinerExt = rb_define_module("GitMinerExt");
15
+ rb_define_method(GitMinerExt, "c_sha1_hexdigest", rb_sha1_hexdigest, 1);
16
+ rb_define_method(GitMinerExt, "c_sha1_mine", rb_sha1_mine, 3);
17
+ }
18
+
19
+ VALUE rb_sha1_hexdigest(VALUE self, VALUE rbString) {
20
+ unsigned char result[SHA_DIGEST_LENGTH];
21
+ const char *string = StringValueCStr(rbString);
22
+
23
+ SHA1(string, strlen(string), result);
24
+
25
+ unsigned char resultHex[SHA_DIGEST_LENGTH*2 + 1];
26
+ int i = 0;
27
+ for (i = 0; i < SHA_DIGEST_LENGTH; i++) {
28
+ sprintf((char*)&(resultHex[i*2]), "%02x", result[i]);
29
+ }
30
+
31
+ printf("ShaGenDebug: %s\n", resultHex);
32
+ return rb_str_new_cstr(resultHex);
33
+ }
34
+
35
+ VALUE rb_sha1_mine(VALUE self, int authorOffset, int committerOffset, int qty) {
36
+ VALUE rbPrefix = rb_iv_get(self, "@prefix");
37
+ char* prefix = StringValueCStr(rbPrefix);
38
+
39
+ VALUE rbTimestamp = rb_iv_get(self, "@timestamp");
40
+ long timestamp = FIX2LONG(rbTimestamp);
41
+
42
+ VALUE rbGitHeadTree = rb_iv_get(self, "@git_head_tree");
43
+ char* gitHeadTree = StringValueCStr(rbGitHeadTree);
44
+
45
+ VALUE rbGitHeadParent = rb_iv_get(self, "@git_head_parent");
46
+ char* gitHeadParent = StringValueCStr(rbGitHeadParent);
47
+
48
+ VALUE rbGitHeadAuthorPrefix = rb_iv_get(self, "@git_head_author_prefix");
49
+ char* gitHeadAuthorPrefix = StringValueCStr(rbGitHeadAuthorPrefix);
50
+
51
+ VALUE rbGitHeadCommitterPrefix = rb_iv_get(self, "@git_head_committer_prefix");
52
+ char* gitHeadCommitterPrefix = StringValueCStr(rbGitHeadCommitterPrefix);
53
+
54
+ VALUE rbGitHeadMessage = rb_iv_get(self, "@git_head_message");
55
+ char* gitHeadMessage = StringValueCStr(rbGitHeadMessage);
56
+
57
+ // TODO: init required or not?
58
+ int space = 10000;
59
+ char currentHead[space];
60
+ char commitPrefix[space];
61
+ char currentCommit[space];
62
+ for(int i = 0; i < space; i++) {
63
+ currentHead[i] = '\0';
64
+ commitPrefix[i] = '\0';
65
+ currentCommit[i] = '\0';
66
+ }
67
+
68
+ unsigned char currentSha[SHA_DIGEST_LENGTH];
69
+ unsigned char currentHex[SHA_DIGEST_LENGTH*2 + 1];
70
+
71
+ int shaLen;
72
+
73
+ for(int i = 0; i < qty; i++) {
74
+ sprintf(currentHead, "%s%s%s%d +0000\n%s%d +0000\n%s",
75
+ gitHeadTree,
76
+ gitHeadParent,
77
+ gitHeadAuthorPrefix,
78
+ timestamp - authorOffset,
79
+ gitHeadCommitterPrefix,
80
+ timestamp - committerOffset,
81
+ gitHeadMessage
82
+ );
83
+
84
+ sprintf(commitPrefix, "commit %d", strlen(currentHead));
85
+ sprintf(currentCommit, "%s_%s", commitPrefix, currentHead);
86
+ currentCommit[strlen(commitPrefix)] = '\0';
87
+
88
+ shaLen = strlen(commitPrefix) + 1 + strlen(currentHead);
89
+ SHA1(currentCommit, shaLen, currentSha);
90
+
91
+ for (int j = 0; j < SHA_DIGEST_LENGTH; j++) {
92
+ sprintf((char*)&(currentHex[j*2]), "%02x", currentSha[j]);
93
+ }
94
+
95
+ int prefixLen = strlen(prefix);
96
+ bool match = true;
97
+ for(int j = 0; j < prefixLen; j++) {
98
+ match = match && (prefix[j] == currentHex[j]);
99
+ }
100
+
101
+ if (match) {
102
+ rb_iv_set(self, "@result_current_sha", rb_str_new_cstr(currentHex));
103
+ rb_iv_set(self, "@result_author_offset", LONG2FIX(authorOffset));
104
+ rb_iv_set(self, "@result_committer_offset", LONG2FIX(committerOffset));
105
+ return Qnil;
106
+ }
107
+
108
+ committerOffset++;
109
+ if(committerOffset > authorOffset) {
110
+ authorOffset++;
111
+ committerOffset = 0;
112
+ }
113
+ }
114
+
115
+ return Qnil;
116
+ }
@@ -0,0 +1,11 @@
1
+ # Loads mkmf which is used to make makefiles for Ruby extensions
2
+ require 'mkmf'
3
+
4
+ # Give it a name
5
+ extension_name = 'git_miner_ext'
6
+
7
+ # The destination
8
+ dir_config(extension_name)
9
+
10
+ # Do the work
11
+ create_makefile(extension_name)
data/git_miner.gemspec ADDED
@@ -0,0 +1,45 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "git_miner/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "git_miner"
8
+ spec.version = GitMiner::VERSION
9
+ spec.authors = ["Thierry Joyal"]
10
+ spec.email = ["thierry.joyal@gmail.com"]
11
+
12
+ spec.summary = "git-mine shell executable"
13
+ spec.description = "git-mine provide mining logic for Git sha prefix"
14
+ spec.homepage = "https://github.com/tjoyal/git_miner"
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ if spec.respond_to?(:metadata)
19
+ # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
20
+
21
+ spec.metadata["homepage_uri"] = spec.homepage
22
+ spec.metadata["source_code_uri"] = "https://github.com/tjoyal/git_miner"
23
+ # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
24
+ else
25
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
26
+ end
27
+
28
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
29
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
30
+ end
31
+
32
+ spec.bindir = "bin"
33
+ spec.executables = ["git-mine"]
34
+ spec.require_paths = ["lib"]
35
+
36
+ spec.extensions = %w[ext/git_miner_ext/extconf.rb]
37
+
38
+ spec.add_runtime_dependency "parallel", "~> 1.17"
39
+ spec.add_runtime_dependency "concurrent-ruby", "~> 1.1"
40
+
41
+ spec.add_development_dependency "bundler", "~> 2.0"
42
+ spec.add_development_dependency "rake", "~> 10.0"
43
+ spec.add_development_dependency "rspec", "~> 3.0"
44
+ spec.add_development_dependency "rake-compiler", "~> 1.0"
45
+ end
data/lib/git_miner.rb ADDED
@@ -0,0 +1,8 @@
1
+ # Require all. Not ideal, only what is required based on the parameters should get to be required.
2
+ Dir[File.join(__dir__, 'git_miner', '**', '*.rb')].each do |file|
3
+ require file
4
+ end
5
+
6
+ module GitMiner
7
+ class Error < StandardError; end
8
+ end
@@ -0,0 +1,68 @@
1
+ module GitMiner
2
+ class Core
3
+ def initialize(engine:, dispatch:, prefix:)
4
+ puts "Initializing with engine '#{engine}'. dispatch '#{dispatch}' and prefix '#{prefix}'"
5
+
6
+ @prefix = prefix
7
+
8
+ @now = Time.now.utc
9
+
10
+ @engine = engine.new(
11
+ prefix: @prefix,
12
+ now: @now,
13
+ )
14
+
15
+ @dispatch = dispatch.new(
16
+ prefix: @prefix,
17
+ engine: @engine
18
+ )
19
+ end
20
+
21
+ def validate
22
+ str = "Test content"
23
+ expected = "bca20547e94049e1ffea27223581c567022a5774"
24
+
25
+ current = @engine.sha1(str)
26
+
27
+ unless expected == current
28
+ message = <<~ERROR
29
+ Invalid sha computation logic
30
+ Expected: #{expected} (#{expected.size})
31
+ Generated: #{current} (#{current.size})
32
+ ERROR
33
+
34
+ raise(message)
35
+ end
36
+
37
+ unless @prefix[/^[0-9a-f]{1,32}$/]
38
+ raise "Invalid prefix, expected '^[0-9a-f]{1,32}$'"
39
+ end
40
+
41
+ puts "Validations: Successful"
42
+ end
43
+
44
+ def mine
45
+ puts "Mining for sha"
46
+
47
+ @mining_result = @dispatch.execute
48
+ end
49
+
50
+ def report
51
+ raise "Prerequisite: Require mining to be completed first" unless @mining_result
52
+
53
+ puts "Mining results:"
54
+ puts "- New sha: #{@mining_result.sha}"
55
+ puts "- Author offset: #{@mining_result.author_offset}"
56
+ puts "- Committer offset: #{@mining_result.committer_offset}"
57
+ end
58
+
59
+ def alter
60
+ raise "Prerequisite: Require mining to be completed first" unless @mining_result
61
+
62
+ author_date = @now - @mining_result.author_offset
63
+ committer_date = @now - @mining_result.committer_offset
64
+
65
+ GitUtil.update_current_ref(author_date, committer_date)
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,34 @@
1
+ module GitMiner
2
+ module Dispatch
3
+ class AbstractDispatch
4
+ BATCH_SIZE = 50_000
5
+
6
+ def initialize(prefix:, engine:)
7
+ @prefix = prefix
8
+ @engine = engine
9
+
10
+ @group_manager = GroupManager.new(batch_size: BATCH_SIZE)
11
+ end
12
+
13
+ def execute
14
+ thread = Thread.new do
15
+ progress = Progress.new(@prefix.length)
16
+ loop do
17
+ progress.tick(@group_manager.batch, @group_manager.count)
18
+ sleep 1
19
+ end
20
+ end
21
+
22
+ perform
23
+ ensure
24
+ thread.kill
25
+ end
26
+
27
+ private
28
+
29
+ def perform
30
+ raise NotImplementedError
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,48 @@
1
+ require 'parallel'
2
+ require 'concurrent'
3
+
4
+ module GitMiner
5
+ module Dispatch
6
+ class ParallelDispatch < AbstractDispatch
7
+ class ShaFound < StandardError
8
+ attr_reader :result
9
+
10
+ def initialize(result)
11
+ @result = result
12
+ end
13
+ end
14
+
15
+ def perform
16
+ Parallel.each(parallel_groups, in_processes: Concurrent.processor_count, preserve_results: false) do |group|
17
+ author_offset, committer_offset = group
18
+
19
+ result = @engine.mine(
20
+ author_offset: author_offset,
21
+ committer_offset: committer_offset,
22
+ qty: @group_manager.batch_size
23
+ )
24
+
25
+ if result
26
+ raise(ShaFound, result)
27
+ end
28
+ end
29
+ rescue ShaFound => e
30
+ e.result
31
+ end
32
+
33
+ def parallel_groups
34
+ -> do
35
+ author_offset = @group_manager.author_offset
36
+ committer_offset = @group_manager.committer_offset
37
+
38
+ @group_manager.advance!
39
+
40
+ [
41
+ author_offset,
42
+ committer_offset
43
+ ]
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,22 @@
1
+ module GitMiner
2
+ module Dispatch
3
+ class SimpleDispatch < AbstractDispatch
4
+ def perform
5
+ loop do
6
+ result = @engine.mine(
7
+ author_offset: @group_manager.author_offset,
8
+ committer_offset: @group_manager.committer_offset,
9
+ qty: @group_manager.batch_size
10
+ )
11
+
12
+ unless result
13
+ @group_manager.advance!
14
+ next
15
+ end
16
+
17
+ return result
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,30 @@
1
+ module GitMiner
2
+ module Engine
3
+ class AbstractEngine
4
+
5
+ def sha1(_str)
6
+ raise NotImplementedError
7
+ end
8
+
9
+ def initialize(prefix:, now:)
10
+ @prefix = prefix
11
+
12
+ @timestamp = now.to_i
13
+
14
+ git_head = GitUtil.commit_head
15
+ @git_head_tree ||= git_head[/^(tree.+?\n)/]
16
+ @git_head_parent ||= git_head[/^(parent.+?\n)/]
17
+ @git_head_author ||= git_head[/^(author.+?\n)/]
18
+ @git_head_committer ||= git_head[/^(committer.+?\n)/]
19
+ @git_head_message ||= git_head.gsub(/^(tree|parent|author|committer).+\n/, '')
20
+
21
+ @git_head_author_prefix = @git_head_author.sub(/\d{10}.+?\n/, '')
22
+ @git_head_committer_prefix = @git_head_committer.sub(/\d{10}.+?\n/, '')
23
+ end
24
+
25
+ def mine(author_offset:, committer_offset:, qty:)
26
+ raise NotImplementedError
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,28 @@
1
+ require 'git_miner_ext'
2
+
3
+ module GitMiner
4
+ module Engine
5
+ class CExtensionEngine < AbstractEngine
6
+ include GitMinerExt
7
+
8
+ def sha1(str)
9
+ c_sha1_hexdigest(str)
10
+ end
11
+
12
+ def mine(author_offset:, committer_offset:, qty:)
13
+ c_sha1_mine(author_offset, committer_offset, qty)
14
+
15
+ # From C mutations
16
+ if @result_current_sha
17
+ MiningResult.new(
18
+ sha: @result_current_sha,
19
+ author_offset: @result_author_offset,
20
+ committer_offset: @result_committer_offset,
21
+ )
22
+ else
23
+ nil
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,47 @@
1
+ require 'digest'
2
+
3
+ module GitMiner
4
+ module Engine
5
+ class RubyEngine < AbstractEngine
6
+ def sha1(str)
7
+ Digest::SHA1.hexdigest(str)
8
+ end
9
+
10
+ def mine(author_offset:, committer_offset:, qty:)
11
+ qty.times do
12
+ current_sha = generate_sha(author_offset, committer_offset)
13
+
14
+ if current_sha.start_with?(@prefix)
15
+ return MiningResult.new(
16
+ sha: current_sha,
17
+ author_offset: author_offset,
18
+ committer_offset: committer_offset,
19
+ )
20
+ end
21
+
22
+ committer_offset += 1
23
+ if committer_offset > author_offset
24
+ author_offset += 1
25
+ committer_offset = 0
26
+ end
27
+ end
28
+
29
+ return
30
+ end
31
+
32
+ private
33
+
34
+ # TODO: handle multiple author/committer
35
+ def generate_sha(author_offset, committer_offset)
36
+ commit_head = @git_head_tree
37
+ commit_head += @git_head_parent
38
+ commit_head += @git_head_author_prefix + (@timestamp - author_offset).to_s + " +0000\n"
39
+ commit_head += @git_head_committer_prefix + (@timestamp - committer_offset).to_s + " +0000\n"
40
+ commit_head += @git_head_message
41
+
42
+ str = "commit #{commit_head.size}\0#{commit_head}"
43
+ Digest::SHA1.hexdigest(str)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,53 @@
1
+ require 'open3'
2
+ require 'time'
3
+
4
+ module GitMiner
5
+ class GitUtil
6
+ class << self
7
+ def commit_head
8
+ # tree 2f18732a3837319fc7def49360366e435ca19700
9
+ # parent 94d9a9d715847d1fed730ef74a664a6773989a1b
10
+ # author Thierry Joyal <thierry.joyal@gmail.com> 1558198927 +0000
11
+ # committer Thierry Joyal <thierry.joyal@gmail.com> 1558198927 +0000
12
+ #
13
+ # This is the commit message
14
+ shell('git cat-file commit HEAD')
15
+ end
16
+
17
+ def update_current_ref(author_date, committer_date)
18
+ commit_object = shell("git cat-file -p HEAD | head -n 2").chomp
19
+ tree = shell("echo \"#{commit_object}\" | head -n 1 | cut -c 6-46").chomp
20
+ head = shell("echo \"#{commit_object}\" | tail -n 1 | cut -c 8-48").chomp
21
+ branch = shell("git branch | sed -n -e 's/^\\* \\(.*\\)/\\1/p'").chomp
22
+
23
+ environment = {
24
+ "GIT_AUTHOR_DATE" => author_date.iso8601,
25
+ "GIT_COMMITTER_DATE" => committer_date.iso8601,
26
+ }
27
+ cmd = "git commit-tree #{tree} -p #{head} -F -"
28
+ git_commit_message = shell('git log -1 --pretty=%B').chomp
29
+ new_sha = shell(cmd, environment: environment, stdin_data: git_commit_message).chomp
30
+
31
+ shell("git update-ref refs/heads/#{branch} #{new_sha}").chomp
32
+ end
33
+
34
+ private
35
+
36
+ def shell(cmd, environment: {}, stdin_data: nil)
37
+ puts "System call: #{cmd}"
38
+ puts "environment: #{environment.inspect}" unless environment.empty?
39
+ puts "stdin_data: #{stdin_data}" if stdin_data
40
+
41
+ output, status = Open3.capture2(environment, cmd, stdin_data: stdin_data) #, chdir: @working_directory
42
+
43
+ unless status.success?
44
+ raise "Error on system call: #{output}, #{status}"
45
+ end
46
+
47
+ puts "result: #{output}"
48
+
49
+ output
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,28 @@
1
+ module GitMiner
2
+ class GroupManager
3
+ attr_reader :author_offset, :batch_size, :committer_offset, :count, :batch
4
+
5
+ def initialize(batch_size:)
6
+ @batch_size = batch_size
7
+
8
+ @author_offset = 0
9
+ @committer_offset = 0
10
+ @batch = 0
11
+ @count = 0
12
+ end
13
+
14
+ def advance!
15
+ @batch_size.times do
16
+ @committer_offset += 1
17
+
18
+ if @committer_offset > @author_offset
19
+ @author_offset += 1
20
+ @committer_offset = 0
21
+ end
22
+ end
23
+
24
+ @count += @batch_size
25
+ @batch += 1
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,11 @@
1
+ module GitMiner
2
+ class MiningResult
3
+ attr_reader :sha, :author_offset, :committer_offset
4
+
5
+ def initialize(sha:, author_offset:, committer_offset:)
6
+ @sha = sha
7
+ @author_offset = author_offset
8
+ @committer_offset = committer_offset
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,49 @@
1
+ module GitMiner
2
+ class Progress
3
+ HISTORIC_SIZE = 10
4
+
5
+ def initialize(prefix_length)
6
+ @combinations = 16 ** prefix_length
7
+
8
+ @historic = []
9
+ end
10
+
11
+ def tick(batch, count)
12
+ update_historic(batch, count)
13
+ report(count)
14
+ end
15
+
16
+ private
17
+
18
+ def update_historic(batch, count)
19
+ @historic << {
20
+ batch: batch,
21
+ count: count,
22
+ timestamp: Time.now
23
+ }
24
+
25
+ if @historic.size > HISTORIC_SIZE
26
+ @historic.shift
27
+ end
28
+ end
29
+
30
+ def report(count)
31
+ info = []
32
+
33
+ percentage = count * 100 / @combinations.to_f
34
+ info << "#{percentage.round(2)}%"
35
+
36
+ historic_count = @historic.last[:count] - @historic.first[:count]
37
+ historic_delay = @historic.last[:timestamp] - @historic.first[:timestamp]
38
+ if historic_delay > 0
39
+ per_sec = historic_count / historic_delay
40
+ info << "hash: #{per_sec.round(2)}/s"
41
+
42
+ per_sec = (@historic.last[:batch] - @historic.first[:batch]) / historic_delay
43
+ info << "batches: #{per_sec.round(2)}/s"
44
+ end
45
+
46
+ puts info.join(', ')
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,3 @@
1
+ module GitMiner
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: git_miner
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Thierry Joyal
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-05-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: parallel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.17'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: concurrent-ruby
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake-compiler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.0'
97
+ description: git-mine provide mining logic for Git sha prefix
98
+ email:
99
+ - thierry.joyal@gmail.com
100
+ executables:
101
+ - git-mine
102
+ extensions:
103
+ - ext/git_miner_ext/extconf.rb
104
+ extra_rdoc_files: []
105
+ files:
106
+ - ".gitignore"
107
+ - ".rspec"
108
+ - ".travis.yml"
109
+ - Gemfile
110
+ - Gemfile.lock
111
+ - README.md
112
+ - Rakefile
113
+ - bin/console
114
+ - bin/git-mine
115
+ - bin/setup
116
+ - ext/git_miner_ext/GitMinerExt.c
117
+ - ext/git_miner_ext/extconf.rb
118
+ - git_miner.gemspec
119
+ - lib/git_miner.rb
120
+ - lib/git_miner/core.rb
121
+ - lib/git_miner/dispatch/abstract_dispatch.rb
122
+ - lib/git_miner/dispatch/parallel_dispatch.rb
123
+ - lib/git_miner/dispatch/simple_dispatch.rb
124
+ - lib/git_miner/engine/abstract_engine.rb
125
+ - lib/git_miner/engine/c_extension_engine.rb
126
+ - lib/git_miner/engine/ruby_engine.rb
127
+ - lib/git_miner/git_util.rb
128
+ - lib/git_miner/group_manager.rb
129
+ - lib/git_miner/mining_result.rb
130
+ - lib/git_miner/progress.rb
131
+ - lib/git_miner/version.rb
132
+ homepage: https://github.com/tjoyal/git_miner
133
+ licenses: []
134
+ metadata:
135
+ homepage_uri: https://github.com/tjoyal/git_miner
136
+ source_code_uri: https://github.com/tjoyal/git_miner
137
+ post_install_message:
138
+ rdoc_options: []
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ requirements: []
152
+ rubygems_version: 3.0.3
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: git-mine shell executable
156
+ test_files: []