Skip to content

Instantly share code, notes, and snippets.

@jcoglan
Created December 19, 2017 14:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jcoglan/1d673b59cac3c43789282ce4d641f0dd to your computer and use it in GitHub Desktop.
Save jcoglan/1d673b59cac3c43789282ce4d641f0dd to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby
require "digest/sha1"
require "fileutils"
require "zlib"
GIT_DIR = ".git"
Commit = Struct.new(:id, :data) do
def self.path(id)
File.join(GIT_DIR, "objects", id[0..1], id[2..-1])
end
def self.load(id)
path = Commit.path(id)
blob = Zlib::Inflate.inflate(File.read(path))
data = blob.split("\0", 2)[1]
new(id, data)
end
def self.store(data)
blob = "commit #{ data.bytesize }\0#{ data }"
id = Digest::SHA1.hexdigest(blob)
path = Commit.path(id)
unless File.readable?(path)
FileUtils.mkdir_p(File.dirname(path))
File.open(path, "w", 0444) do |f|
f.write Zlib::Deflate.deflate(blob, Zlib::BEST_SPEED)
end
end
new(id, data)
end
def parent
parent_id = data.lines.grep(/^parent /).first.split(/\s+/)[1]
Commit.load(parent_id)
end
end
target_id = ARGV.first
head_name = "HEAD"
head_id = nil
until head_id
head_data = File.read(File.join(GIT_DIR, head_name)).strip
case head_data
when /^ref: (.*)$/
head_name = $1
when /^[0-9a-f]{40}$/
head_id = head_data
end
end
commit = Commit.load(head_id)
chain = []
until commit.id == target_id
chain.unshift(commit)
commit = commit.parent
end
msg_path = File.join(GIT_DIR, "COMMIT_EDITMSG")
File.open(msg_path, "w") { |f| f.write(commit.data) }
exit unless system("vim", msg_path)
new_data = File.read(msg_path)
new_commit = Commit.store(new_data)
exit if new_commit.id == commit.id
new_head = chain.inject(new_commit) do |parent, cmt|
data = cmt.data.gsub(/^parent.*$/, "parent #{ parent.id }")
Commit.store(data)
end
File.open(File.join(GIT_DIR, head_name), "w") do |f|
f.puts new_head.id
end
old_ids = (chain + [commit]).map(&:id)
old_ids.each do |id|
path = Commit.path(id)
dir = File.dirname(path)
File.unlink(path)
begin
Dir.rmdir(dir)
rescue Errno::ENOTEMPTY
end
puts "dropped: #{ path }"
end
puts "HEAD is now: #{ new_head.id }"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment