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 +7 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +42 -0
- data/README.md +28 -0
- data/Rakefile +13 -0
- data/bin/console +14 -0
- data/bin/git-mine +65 -0
- data/bin/setup +8 -0
- data/ext/git_miner_ext/GitMinerExt.c +116 -0
- data/ext/git_miner_ext/extconf.rb +11 -0
- data/git_miner.gemspec +45 -0
- data/lib/git_miner.rb +8 -0
- data/lib/git_miner/core.rb +68 -0
- data/lib/git_miner/dispatch/abstract_dispatch.rb +34 -0
- data/lib/git_miner/dispatch/parallel_dispatch.rb +48 -0
- data/lib/git_miner/dispatch/simple_dispatch.rb +22 -0
- data/lib/git_miner/engine/abstract_engine.rb +30 -0
- data/lib/git_miner/engine/c_extension_engine.rb +28 -0
- data/lib/git_miner/engine/ruby_engine.rb +47 -0
- data/lib/git_miner/git_util.rb +53 -0
- data/lib/git_miner/group_manager.rb +28 -0
- data/lib/git_miner/mining_result.rb +11 -0
- data/lib/git_miner/progress.rb +49 -0
- data/lib/git_miner/version.rb +3 -0
- metadata +156 -0
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
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
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,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
|
+
}
|
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,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,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
|
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: []
|