Skip to content

Commit fa7628a

Browse files
committed
Imrove base commit detection logic
The previous iteration was imperfect, the question is a lot more complicated than simply executing git log. The new solution first tries to see if head matches (as it often will for recent patches), then if not it looks at the git log of any files changed in the patch series, and tries to find the latest commit where the hash of all files matched the expected before values.
1 parent ecfed3f commit fa7628a

File tree

2 files changed

+56
-34
lines changed

2 files changed

+56
-34
lines changed

public/scripts/hackorum-patch

Lines changed: 54 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -425,51 +425,72 @@ class HackorumPatch
425425
return base_commit
426426
end
427427

428-
# Fall back to index hash detection
429-
puts "Detecting base commit from patch index entries..."
430-
431-
first_patch = all_patch_files.first
432-
index_hashes = []
433-
new_file_count = 0
434-
content = File.read(first_patch)
435-
content.scan(/^index ([0-9a-f]+)\.\.([0-9a-f]+)/) do |before_hash, after_hash|
436-
if before_hash =~ /^0+$/
437-
new_file_count += 1
438-
else
439-
index_hashes << before_hash
428+
# Fall back to index hash detection using file paths
429+
puts "Detecting base commit from patch file paths..."
430+
431+
# Extract file paths and their "before" blob hashes from all patches
432+
# Only record the FIRST occurrence of each file - later patches in a series
433+
# will have "before" hashes that are the result of earlier patches, not the original state
434+
file_info = {} # path => before_hash
435+
all_patch_files.each do |patch_file|
436+
content = File.read(patch_file)
437+
# Format: diff --git a/path b/path followed by index abc123..def456
438+
content.scan(/^diff --git a\/(.+?) b\/\1\n(?:.*?\n)*?index ([0-9a-f]+)\.\.([0-9a-f]+)/m) do |path, before_hash, after_hash|
439+
next if before_hash =~ /^0+$/ # Skip new files
440+
next if file_info.key?(path) # Only use first occurrence
441+
file_info[path] = before_hash
440442
end
441443
end
442444

443-
if index_hashes.empty?
444-
puts " No index entries found in first patch (#{new_file_count} new files)"
445+
if file_info.empty?
446+
puts " No modified files found in patches (all new files?)"
445447
return detect_default_branch_head
446448
end
447449

448-
puts " Found #{index_hashes.size} index entries in first patch (skipped #{new_file_count} new files)"
449-
puts " Searching for base commit (this may take a moment)..."
450+
puts " Found #{file_info.size} modified files across #{all_patch_files.size} patches"
450451

451-
candidates = []
452-
index_hashes.each_with_index do |hash, idx|
453-
puts " Checking index #{idx + 1}/#{index_hashes.size}..."
452+
default_branch = detect_default_branch_head
454453

455-
output = `git log --all --pretty=format:%H --find-object=#{hash} -n 1 2>/dev/null`.strip
456-
if !output.empty? && output =~ /^[0-9a-f]{40}$/
457-
candidates << output
458-
puts " Found commit: #{output[0..7]}..."
459-
end
454+
# Check if the "before" hashes match what's at the default branch HEAD
455+
# This confirms HEAD is the correct base
456+
default_head = `git rev-parse #{default_branch} 2>/dev/null`.strip
457+
all_match = file_info.all? do |path, before_hash|
458+
current_hash = `git rev-parse #{default_head}:#{path} 2>/dev/null`.strip
459+
current_hash == before_hash || current_hash.start_with?(before_hash) || before_hash.start_with?(current_hash)
460460
end
461461

462-
if candidates.empty?
463-
puts " Could not detect base commit from index entries"
464-
return detect_default_branch_head
462+
if all_match
463+
puts " All file hashes match #{default_branch} HEAD"
464+
puts "[OK] Base commit: #{default_head} (#{default_branch})"
465+
puts ""
466+
return default_head
465467
end
466468

467-
base_commit = candidates.first
468-
puts " Using commit: #{base_commit[0..7]}... as base"
469-
puts "[OK] Base commit: #{base_commit}"
470-
puts ""
469+
# Hashes don't match HEAD, search history for a matching commit
470+
puts " File hashes don't match HEAD, searching history..."
471+
472+
# Find commits on the default branch that touched these files
473+
paths = file_info.keys.map { |p| Shellwords.escape(p) }.join(" ")
474+
commits = `git log #{default_branch} --pretty=format:%H -n 50 -- #{paths} 2>/dev/null`.strip.split("\n")
475+
476+
commits.each do |commit|
477+
next if commit.empty?
478+
479+
matches = file_info.all? do |path, before_hash|
480+
blob_hash = `git rev-parse #{commit}:#{path} 2>/dev/null`.strip
481+
blob_hash == before_hash || blob_hash.start_with?(before_hash) || before_hash.start_with?(blob_hash)
482+
end
483+
484+
if matches
485+
puts " Found matching commit: #{commit[0..11]}..."
486+
puts "[OK] Base commit: #{commit}"
487+
puts ""
488+
return commit
489+
end
490+
end
471491

472-
base_commit
492+
puts " Could not find matching commit in history"
493+
detect_default_branch_head
473494
end
474495

475496
def detect_base_commit_from_base_line(patch_files)
@@ -488,7 +509,7 @@ class HackorumPatch
488509
end
489510

490511
def detect_default_branch_head
491-
%w[origin/master master].each do |ref|
512+
%w[master origin/master].each do |ref|
492513
if system("git rev-parse --verify #{ref} > /dev/null 2>&1")
493514
return ref
494515
end

public/scripts/hackorum-patch.changelog.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"changes": [
88
"Added version check to notify users of updates",
99
"Added support for base-commit line from git format-patch --base",
10-
"Added support for .diff files (applied with git apply and committed individually)"
10+
"Added support for .diff files (applied with git apply and committed individually)",
11+
"Fixed base commit detection for --force re-application and multi-patch series"
1112
]
1213
},
1314
{

0 commit comments

Comments
 (0)