Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions beautify_git_hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,14 @@ def load_git_commit(commit_id):
return subprocess_check_output(['git', 'cat-file', 'commit', commit_id])


def git_commit_hash(commit):
def git_commit_hash(commit, object_format='sha1'):
object = 'commit %i\x00%s' % (len(commit), commit)
return hashlib.sha1(object).hexdigest()
if object_format == 'sha1':
return hashlib.sha1(object).hexdigest()
elif object_format == 'sha256':
return hashlib.sha256(object).hexdigest()
else:
raise Exception('Unsupported object format: ' + object_format)


def commit_line_to_format(line, aggregate_values):
Expand All @@ -117,15 +122,24 @@ def commit_to_format(commit):
aggregate_values = {}
commit_format = '\n'.join(commit_line_to_format(line, aggregate_values)
for line in str(commit).split('\n'))
return commit_format, aggregate_values

tree = commit_format.split('\n', 1)[0].split(' ')[1]
object_format = get_hash_type(tree)
return commit_format, aggregate_values, object_format

def get_hash_type(hash):
if len(hash) == 40:
return 'sha1'
elif len(hash) == 64:
return 'sha256'
else:
raise Exception('Unsupported hash length: ' + str(len(hash)))

def find_beautiful_git_hash(old_commit, prefix, max_minutes=120):
ALLOWED_PREFIX_CHARACTERS = '0123456789abcdef'
if prefix.lstrip(ALLOWED_PREFIX_CHARACTERS) != '':
raise Exception(
'Invalid prefix! Only lower case hex digits are allowed: ' + ALLOWED_PREFIX_CHARACTERS)
commit_format, old_values = commit_to_format(old_commit)
commit_format, old_values, object_format = commit_to_format(old_commit)
bound = max_minutes*60
for committer_date_offset in range(bound + 1):
for author_date_offset in range(committer_date_offset + 1):
Expand All @@ -134,7 +148,7 @@ def find_beautiful_git_hash(old_commit, prefix, max_minutes=120):
'committer_date_timestamp': old_values['committer_date_timestamp'] + committer_date_offset,
}
commit = commit_format % new_values
if git_commit_hash(commit).startswith(prefix):
if git_commit_hash(commit, object_format=object_format).startswith(prefix):
if author_date_offset == committer_date_offset == 0:
return None
else:
Expand Down
24 changes: 16 additions & 8 deletions git_commit_chooser_react/src/hash.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SHA1, enc } from 'crypto-js';
import { SHA1, SHA256 } from 'crypto-js';

interface CommitValues {
author_date_timestamp: number;
Expand All @@ -7,10 +7,16 @@ interface CommitValues {
committer_date_tz: string;
}

function git_commit_hash(commit: string): string {
function git_commit_hash(commit: string, object_format: 'sha1' | 'sha256' = 'sha1'): string {
const object = `commit ${commit.length}\x00${commit}`;
const sha = SHA1(object).toString();
return sha;
switch (object_format) {
case 'sha1':
return SHA1(object).toString();
case 'sha256':
return SHA256(object).toString();
default:
throw new Error('Unsupported object format: ' + object_format);
}
}

function commit_line_to_format(line: string, aggregate_values: CommitValues): string {
Expand All @@ -34,7 +40,7 @@ function commit_line_to_format(line: string, aggregate_values: CommitValues): st
return format_words.join(' ');
}

function commit_to_format(commit: string): [string, CommitValues] {
function commit_to_format(commit: string): [string, CommitValues, 'sha1' | 'sha256'] {
const aggregate_values: CommitValues = {
author_date_timestamp: 0,
author_date_tz: '',
Expand All @@ -45,7 +51,9 @@ function commit_to_format(commit: string): [string, CommitValues] {
const commit_format = commit_lines
.map((line) => commit_line_to_format(line, aggregate_values))
.join('\n');
return [commit_format, aggregate_values];
const tree = commit_lines.find((line) => line.startsWith('tree '))?.split(' ')[1];
const object_format: 'sha1' | 'sha256' = tree?.length === 64 ? 'sha256' : 'sha1';
return [commit_format, aggregate_values, object_format];
}

interface CommitTimes {
Expand All @@ -63,7 +71,7 @@ export async function find_beautiful_git_hash(
if (![...prefix].every((c) => allowed_prefix_chars.includes(c))) {
throw new Error('Invalid prefix! Only lowercase hex digits are allowed');
}
const [commit_format, old_values] = commit_to_format(old_commit);
const [commit_format, old_values, object_format] = commit_to_format(old_commit);

const bound = max_minutes * 60;
const possibilities = (bound + 1) * (bound + 2) / 2;
Expand Down Expand Up @@ -104,7 +112,7 @@ export async function find_beautiful_git_hash(
const commit = commit_format
.replace('%(author_date_timestamp)i', new_values.author_date_timestamp.toString())
.replace('%(committer_date_timestamp)i', new_values.committer_date_timestamp.toString());
const sha = git_commit_hash(commit);
const sha = git_commit_hash(commit, object_format);
if (sha.startsWith(prefix)) {
if (author_date_offset === 0 && committer_date_offset === 0) {
return null;
Expand Down
2 changes: 2 additions & 0 deletions git_hash_chooser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ edition = "2021"

[dependencies]
clap = {version = "4.3.0", features = ["derive"]}
digest = "0.10.7"
hex = "0.4.3"
kdam = "0.3.0"
md5 = "0.7.0"
rayon = "1.7.0"
sha1 = "0.10.5"
sha2 = "0.10.9"
43 changes: 35 additions & 8 deletions git_hash_chooser/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use clap::Parser;
use kdam::{tqdm, BarExt};
use rayon::prelude::*;
use sha1::Digest;
use digest::Digest;
use std::error::Error;
use std::process::Command;
use std::sync::{Arc, Mutex};
Expand All @@ -13,6 +13,13 @@ struct CommitValues {
committer_date_tz: String,
}

#[derive(Default, Clone, Copy)]
enum HashType {
#[default]
Sha1,
Sha256,
}

fn subprocess_check_output(cmd: &str) -> Result<String, Box<dyn Error>> {
let output = Command::new("sh").arg("-c").arg(cmd).output()?;
let stdout = String::from_utf8_lossy(&output.stdout).into_owned();
Expand All @@ -28,10 +35,18 @@ fn load_git_commit(commit_id: &str) -> Result<String, Box<dyn Error>> {
subprocess_check_output(&format!("git cat-file commit {}", commit_id))
}

fn git_commit_hash(commit: &str) -> String {
fn git_commit_hash(commit: &str, hash_type: HashType) -> String {
let object = format!("commit {}\x00{}", commit.len(), commit);
let sha = sha1::Sha1::digest(object.as_bytes());
format!("{:x}", sha)
match hash_type {
HashType::Sha1 => {
let sha = sha1::Sha1::digest(object.as_bytes());
format!("{:x}", sha)
}
HashType::Sha256 => {
let sha = sha2::Sha256::digest(object.as_bytes());
format!("{:x}", sha)
}
}
}

fn commit_line_to_format(line: &str, aggregate_values: &mut CommitValues) -> String {
Expand All @@ -58,7 +73,7 @@ fn commit_line_to_format(line: &str, aggregate_values: &mut CommitValues) -> Str
format_words.join(" ")
}

fn commit_to_format(commit: &str) -> Result<(String, CommitValues), Box<dyn Error>> {
fn commit_to_format(commit: &str) -> Result<(String, CommitValues, HashType), Box<dyn Error>> {
let mut aggregate_values = CommitValues {
author_date_timestamp: 0,
author_date_tz: String::new(),
Expand All @@ -71,7 +86,19 @@ fn commit_to_format(commit: &str) -> Result<(String, CommitValues), Box<dyn Erro
.map(|line| commit_line_to_format(line, &mut aggregate_values))
.collect::<Vec<String>>()
.join("\n");
Ok((commit_format, aggregate_values))
let hash_format = commit
.lines()
.find(|line| line.starts_with("tree "))
.and_then(|line| {
let tree_hash = line.split_whitespace().nth(1)?;
match tree_hash.len() {
40 => Some(HashType::Sha1),
64 => Some(HashType::Sha256),
_ => None,
}
})
.unwrap_or_default();
Ok((commit_format, aggregate_values, hash_format))
}

fn find_beautiful_git_hash(
Expand All @@ -84,7 +111,7 @@ fn find_beautiful_git_hash(
if !prefix.chars().all(|c| allowed_prefix_chars.contains(c)) {
return Err("Invalid prefix! Only lower case hex digits are allowed".into());
}
let (commit_format, old_values) = commit_to_format(old_commit)?;
let (commit_format, old_values, hash_type) = commit_to_format(old_commit)?;

let lower_bound = min_minutes * 60;
let upper_bound = max_minutes * 60;
Expand Down Expand Up @@ -132,7 +159,7 @@ fn find_beautiful_git_hash(
"%(committer_date_timestamp)i",
&new_values.committer_date_timestamp.to_string(),
);
if git_commit_hash(&commit).starts_with(prefix) {
if git_commit_hash(&commit, hash_type).starts_with(prefix) {
if author_date_offset == 0 && committer_date_offset == 0 {
return Some(None);
} else {
Expand Down