bin/git-vanity-sha (view raw)
1#!/usr/bin/env ruby
2require 'digest'
3
4module VanitySha
5 extend self
6
7 COMMITTER_PATTERN = /^committer .* \<.*\> (?<timestamp>\d+)(?<timezone>.*$)/
8
9 TIMESTAMP_DELTA_MAX = 10 * 24 * 60 * 60
10
11 DELTAS = Enumerator.new do |enumerator|
12 for i in 0..TIMESTAMP_DELTA_MAX
13 enumerator << i
14 enumerator << -i
15 end
16 end
17
18 def get_commit_info()
19 `git cat-file commit HEAD`
20 end
21
22 def extract_committer(commit_info)
23 committer = commit_info
24 .split("\n")
25 .map {|line| line.match(COMMITTER_PATTERN)}
26 .compact
27 .first
28
29 [committer.string, committer[:timestamp].to_i, committer[:timezone]]
30 end
31
32 def search(commit_info, hex_prefix)
33 committer_line, commit_ts = extract_committer(commit_info)
34
35 DELTAS.each do |delta|
36 new_committer_line = committer_line.sub(commit_ts.to_s, (commit_ts + delta).to_s)
37 new_commit = commit_info.sub(committer_line, new_committer_line)
38 new_sha = get_sha(new_commit)
39 return [delta, new_sha] if new_sha.start_with?(hex_prefix)
40 end
41 nil
42 end
43
44 def get_sha(commit_info)
45 Digest::SHA1.hexdigest "#{commit_header(commit_info)}#{commit_info}"
46 end
47
48 def amend_commit(new_ts, original_tz)
49 `LC_ALL=C GIT_COMMITTER_DATE=\"#{new_ts}#{original_tz}\" git commit --amend --no-edit`
50 end
51
52 def commit_header(commit_info)
53 "commit #{commit_info.length}\0"
54 end
55end
56
57if ARGV.length != 1 || ARGV[0].length < 1
58 puts "usage: git vanity-sha prefix"
59 puts "example: git vanity-sha CAFE"
60 exit(1)
61end
62
63target_prefix = ARGV[0].downcase
64original_commit_info = VanitySha.get_commit_info()
65_, original_ts, original_tz = VanitySha.extract_committer(original_commit_info)
66
67puts "Searching for new SHA...\n"
68
69result = VanitySha.search(original_commit_info, target_prefix)
70
71if result
72 delta, sha = result
73 new_ts = original_ts + delta
74
75 puts "SHA found: #{sha.sub(target_prefix, "\e[32m#{target_prefix}\e[0m")}"
76 puts "Change committer timestamp to #{Time.at(new_ts)}? This will amend your commit."
77 print "(y/n): "
78
79 if STDIN.gets.chomp == "y"
80 VanitySha.amend_commit(new_ts, original_tz)
81 puts "\n" + "-" * 47
82 puts `git show --quiet --format=short HEAD`
83 else
84 puts "Aborting."
85 end
86else
87 puts "Failed to generate a sha with prefix #{target_prefix}."
88end
89