Skip to content

Instantly share code, notes, and snippets.

@burke
Last active Dec 15, 2021
Embed
What would you like to do?
# fragment added to ~/.zshrc
zle-gasdf() { gasdf; zle .beginning-of-line; zle .kill-line; zle .accept-line }
zle -N zle-gasdf
bindkey 'å' zle-gasdf # Alt-A Canadian English
#!ruby --disable-gems
autoload(:FileUtils, 'fileutils')
autoload(:Open3, 'open3')
class GASDF
def initialize(toplevel)
@m = Mutex.new
@cv = ConditionVariable.new
@ticker = 0
@toplevel = toplevel
@status = {
commit: false,
fetch: false,
rebase: false,
push: false,
}
render(first: true)
end
TICKS = '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'.each_char.to_a
def run
threads = @status
.keys
.map { |k| Thread.new { send(k) } }
threads << Thread.new do
loop do
@m.synchronize { @cv.wait(@m) }
render
break if done?
end
end
# don't wait on this one, just let it get torn down on exit
Thread.new do
loop do
@ticker += 1
sleep(0.1)
@m.synchronize { @cv.broadcast }
end
end
threads.map(&:join)
end
private
def done?
@status.values.all? { |v| v == :done }
end
def commit
out = git!('status', '--porcelain')
return if out.empty?
git!('add', '.')
git!('commit', '-q', '-m', '[automatic commit]')
ensure
complete(:commit)
end
def fetch
wait(:fetch, :commit)
git!('fetch', '-q', 'origin', branch)
ensure
complete(:fetch)
end
def rebase
wait(:rebase, :fetch)
git!('rebase', '-X', 'theirs', '-q', 'FETCH_HEAD')
ensure
complete(:rebase)
end
def push
wait(:push, :commit)
_, _, stat = git('push', '-q')
return if stat.success?
stall(:push)
wait(:push, :rebase)
git!('push', '-q')
ensure
complete(:push)
end
def branch
git!('rev-parse', '--abbrev-ref', 'HEAD').chomp
end
def stall(x)
@m.synchronize do
@status[x] = :stall
render
@cv.broadcast
end
end
def complete(x)
@m.synchronize do
@status[x] = :done
render
@cv.broadcast
end
end
def wait(from, x)
@status[from] = :wait
loop do
@m.synchronize do
@cv.wait(@m)
return if @status[x] == :done
end
end
ensure
@status[from] = false
end
def git(*args)
Open3.capture3('git', '-C', @toplevel, *args)
end
def git!(*args)
out, err, stat = git(*args)
unless stat.success?
abort("git #{args.first} failed: #{err}")
end
out
end
def render(first: false)
o = +''
tick = TICKS[@ticker % TICKS.size]
[:commit, :fetch, :rebase, :push].each do |f|
color = case @status[f]
when false then "\x1b[1;33m#{tick}"
when :done then "\x1b[1;32m✓"
when :stall then "\x1b[1;31m#{tick}"
when :wait then "\x1b[1;35m⧖"
else "\x1b[1;31m#{tick}"
end
o += color + f.to_s[0].upcase
end
if first
print(o + "\x1b[0m")
else
print("\x1b[8D" + o + "\x1b[0m")
end
end
end
toplevel = File.expand_path(Dir.pwd)
loop do
toplevel = Dir.pwd
break if Dir.exist?(File.join(toplevel, '.git'))
toplevel = File.dirname(toplevel)
abort('not in a git repo') if toplevel == '/'
end
case ARGV.shift
when '-e'
FileUtils.touch(File.join(toplevel, '.git', 'gasdf'))
puts('enabled')
when nil
unless File.exist?(File.join(toplevel, '.git', 'gasdf'))
abort('run with -e to enable')
end
GASDF.new(toplevel).run
else
abort('invalid usage')
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment