読者です 読者をやめる 読者になる 読者になる

デモなどで使えるGitコミットを簡単にresetし切替えるツールを作りました

私は仕事がら、人前でコード作成のデモを行う事がよくあります。その場でバリバリとライブ・コーデイング出来るとカッコ良いですが、間違えたり、コードを書くことに気を取られ説明がおろそかになったりすることも起こりがちです。

f:id:yuum3:20160506094523p:plain

そこで説明のストーリに沿ってコード作成の過程をgitにコミットしておき、コマンドで簡単に git reset を行い、その時点でのコードを説明したり、動かしたりできるツールを作りました。

上の画像のように、このコマンドを起動するとgit logが表示され、現在のコミットが赤い色で表示されます、ここで n キーを押すと次のコミットに git reset できます。また p キーで前のコミットに git reset したり、カーソル上下キーでコミットを移動して RETURNキーでそこにgit reset できます。

このコマンドを実行すると git reset されてしまうので、 -c オプション指定で 予めファイルに全git logを書いておきます。

rubyで作ってあります、 gem 等のインストールは不要です。

#!/usr/bin/env ruby

require 'io/console'

GIT_LIST_PATH = "#{ENV['HOME']}/tmp/git-demo.lst"

RED = "\e[31m"
YELLOW = "\e[33m"
BLACK = "\e[30m"
FIN = "\e[0m"

UP = "\e[A"
DOWN = "\e[B"
RETURN = "\r"


def show_log_item(item, current)
  if current
    print "#{RED}#{item[0]} #{BLACK}#{item[1]}#{FIN}#{RETURN}"
  else
    print "#{YELLOW}#{item[0]} #{BLACK}#{item[1]}#{FIN}#{RETURN}"
  end
end

def show_git_log(log, current_index)
  log.each_with_index do |item, ix|
    show_log_item(item, ix == current_index)
    puts
  end
end

def get_current_hash
  IO.read("|git log --pretty=format:'%h' -1")
end

def find_current_hash(log, current_hash)
  log.find_index {|item| item[0] == current_hash}
end

def wait_key
  key = STDIN.getch
  if key == "\e" && STDIN.getch == "["
    case STDIN.getch
    when "A" then ""
    when "B" then ""
    else ""
    end
  else
    key
  end
end

def cursor_up
  print UP
  STDOUT.flush
end

def cursor_down
  print DOWN
  STDOUT.flush
end

def set_cursor(log, current_index)
  (log.size - current_index).times { cursor_up }
end

def reset_cursor(log, current_index)
 (log.size - current_index).times { cursor_down }
end

def move_current_postion(log, index, delta)
  show_log_item(log[index], false)

  if delta > 0
    if index < log.size - 1
      cursor_down
      index += 1
    end
  else
    if index > 0
      cursor_up
      index -= 1
    end
  end

  show_log_item(log[index], true)
  index
end

def get_git_log
  IO.readlines(GIT_LIST_PATH).map {|s| s.scan(/^(\w+) (.*)\n?/)[0]}
end

def put_git_log
  system "git log --pretty=format:'%h %s' > #{GIT_LIST_PATH}"
end

def git_reset_hard(log, index)
  system "git reset --hard #{log[index][0]}"
end

def main
  log = get_git_log
  current_index = find_current_hash(log, get_current_hash)
  show_git_log(log, current_index)
  set_cursor(log, current_index)
  do_reset = false

  while true
    case wait_key
    when 'n'
      current_index = move_current_postion(log, current_index,  - 1)
      do_reset = true
      break
    when 'p'
      current_index = move_current_postion(log, current_index,  + 1)
      do_reset = true
      break
    when "\r", "\n"
      do_reset = true
      break
    when "\C-c", "q"
      break
    when ""
      current_index = move_current_postion(log, current_index,  - 1)
    when ""
      current_index = move_current_postion(log, current_index,  + 1)
    end
  end
  reset_cursor(log, current_index)
  git_reset_hard(log, current_index)  if do_reset

end

if ARGV.size > 0
  if ARGV[0] == "-c"
    put_git_log
    puts "#{GIT_LIST_PATH} created."
  else
    puts "#{$0} -c"
    puts "     or  "
    puts "#{$0} "
    puts "  n: git reset --hard Next commit"
    puts "  p: git reset --hard Pervious commit"
    puts "  ↑ ↓: seek commit"
    puts "  RETURN: git reset --hard Current commit"
    puts "  q: quit"
  end
else
  main
end

Gistにも置きました