Created
August 10, 2018 23:25
-
-
Save awreece/a00a4bd5cc73b333dc67c07355b93d4b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'digest' | |
module VanitySha | |
extend self | |
COMMITTER_PATTERN = /^committer .* \<.*\> (?<timestamp>\d+)(?<timezone>.*$)/ | |
AUTHOR_PATTERN = /^author .* \<.*\> (?<timestamp>\d+)(?<timezone>.*$)/ | |
TIMESTAMP_DELTA_MAX = 10 * 24 * 60 * 60 | |
DELTAS = Enumerator.new do |enumerator| | |
for size in 1..2*TIMESTAMP_DELTA_MAX | |
for split in 0..size | |
enumerator << [split, size-split] | |
end | |
end | |
end | |
def get_commit_info() | |
`git cat-file commit HEAD` | |
end | |
def extract_committer(commit_info) | |
committer = commit_info | |
.split("\n") | |
.map {|line| line.match(COMMITTER_PATTERN)} | |
.compact | |
.first | |
[committer.string, committer[:timestamp].to_i, committer[:timezone]] | |
end | |
def extract_author(commit_info) | |
author = commit_info | |
.split("\n") | |
.map {|line| line.match(AUTHOR_PATTERN)} | |
.compact | |
.first | |
[author.string, author[:timestamp].to_i, author[:timezone]] | |
end | |
def search(commit_info, hex_prefix) | |
committer_line, commit_ts = extract_committer(commit_info) | |
author_line, author_ts = extract_author(commit_info) | |
DELTAS.each do |commit_delta, author_delta| | |
new_committer_line = committer_line.sub(commit_ts.to_s, (commit_ts - commit_delta).to_s) | |
new_author_line = author_line.sub(author_ts.to_s, (author_ts - author_delta).to_s) | |
new_commit = commit_info.sub(committer_line, new_committer_line).sub(author_line, new_author_line) | |
new_sha = get_sha(new_commit) | |
puts new_sha, new_commit if new_sha.start_with?(hex_prefix) | |
return [commit_delta, author_delta, new_sha] if new_sha.start_with?(hex_prefix) | |
end | |
nil | |
end | |
def get_sha(commit_info) | |
Digest::SHA1.hexdigest "#{commit_header(commit_info)}#{commit_info}" | |
end | |
def amend_commit(new_commit_ts, original_commit_tz, new_author_ts, original_author_tz) | |
puts "LC_ALL=C GIT_COMMITTER_DATE=\"#{new_commit_ts}#{original_commit_tz}\" git commit --amend --date \"#{new_author_ts}#{original_author_tz}\" --no-edit" | |
`LC_ALL=C GIT_COMMITTER_DATE=\"#{new_commit_ts}#{original_commit_tz}\" git commit --amend --date \"#{new_author_ts}#{original_author_tz}\" --no-edit` | |
end | |
def commit_header(commit_info) | |
"commit #{commit_info.length}\0" | |
end | |
end | |
if ARGV.length != 1 || ARGV[0].length < 1 | |
puts "usage: git vanity-sha prefix" | |
puts "example: git vanity-sha CAFE" | |
exit(1) | |
end | |
target_prefix = ARGV[0].downcase | |
original_commit_info = VanitySha.get_commit_info() | |
_, original_commit_ts, original_commit_tz = VanitySha.extract_committer(original_commit_info) | |
_, original_author_ts, original_author_tz = VanitySha.extract_author(original_commit_info) | |
puts "Searching for new SHA...\n" | |
result = VanitySha.search(original_commit_info, target_prefix) | |
if result | |
commit_delta, author_delta, sha = result | |
new_commit_ts = original_commit_ts - commit_delta | |
new_author_ts = original_author_ts - author_delta | |
puts "SHA found: #{sha.sub(target_prefix, "\e[32m#{target_prefix}\e[0m")}" | |
puts "Change committer timestamp to #{Time.at(new_commit_ts)}? This will amend your commit." | |
puts "Change author timestamp to #{Time.at(new_author_ts)}? This will amend your commit." | |
print "(y/n): " | |
if STDIN.gets.chomp == "y" | |
VanitySha.amend_commit(new_commit_ts, original_commit_tz, new_author_ts, original_author_tz) | |
puts "\n" + "-" * 47 | |
puts `git show --quiet --format=short HEAD` | |
else | |
puts "Aborting." | |
end | |
else | |
puts "Failed to generate a sha with prefix #{target_prefix}." | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment