├── .gitignore ├── Gemfile ├── Gemfile.lock ├── README.md ├── bin └── git-wayback-machine ├── git-wayback-machine.gemspec ├── lib ├── git-wayback-machine.rb ├── git_wayback_machine.rb └── git_wayback_machine │ ├── controls.rb │ ├── engine.rb │ ├── history.rb │ ├── navigator.rb │ └── version.rb └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | *.gem 10 | Gemfile.lock 11 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in git-wayback-machine.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | git-wayback-machine (0.1.0) 5 | 6 | GEM 7 | remote: https://rubygems.org/ 8 | specs: 9 | 10 | PLATFORMS 11 | ruby 12 | 13 | DEPENDENCIES 14 | bundler (~> 1.9) 15 | git-wayback-machine! 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Git Wayback Machine 2 | 3 | A handy dandy tool to quickly navigate a project's state through it's GIT history 4 | 5 | ![Screenshot](./screenshot.png) 6 | 7 | 8 | ## Installation 9 | 10 | Add this line to your application's Gemfile: 11 | 12 | ```ruby 13 | gem 'git-wayback-machine' 14 | ``` 15 | 16 | And then execute: 17 | 18 | $ bundle 19 | 20 | Or install it yourself as: 21 | 22 | $ gem install git-wayback-machine 23 | 24 | ## Development 25 | 26 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment. 27 | 28 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 29 | 30 | ## Contributing 31 | 32 | 1. Fork it ( https://github.com/MadRabbit/git-wayback-machine/fork ) 33 | 2. Create your feature branch (`git checkout -b my-new-feature`) 34 | 3. Commit your changes (`git commit -am 'Add some feature'`) 35 | 4. Push to the branch (`git push origin my-new-feature`) 36 | 5. Create a new Pull Request 37 | 38 | ## Copyright & License 39 | 40 | All code in this repository is released under the terms of the MIT License 41 | 42 | Copyright (C) 2015 Nikolay Nemshilov 43 | -------------------------------------------------------------------------------- /bin/git-wayback-machine: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require_relative "../lib/git-wayback-machine" 4 | 5 | GitWaybackMachine.boot 6 | -------------------------------------------------------------------------------- /git-wayback-machine.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'git_wayback_machine/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "git-wayback-machine" 8 | spec.version = GitWaybackMachine::VERSION 9 | spec.authors = ["Nikolay Nemshilov"] 10 | spec.email = ["nemshilov@gmail.com"] 11 | spec.licenses = ["MIT"] 12 | 13 | spec.summary = "Wayback machine to navigate GIT log" 14 | spec.description = "Wayback machine to navigate GIT log, for real!" 15 | spec.homepage = "https://github.com/MadRabbit/git-wayback-machine" 16 | 17 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 18 | spec.bindir = "bin" 19 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 20 | spec.require_paths = ["lib"] 21 | 22 | spec.add_development_dependency "bundler", "~> 1.9" 23 | end 24 | -------------------------------------------------------------------------------- /lib/git-wayback-machine.rb: -------------------------------------------------------------------------------- 1 | require_relative "./git_wayback_machine" 2 | -------------------------------------------------------------------------------- /lib/git_wayback_machine.rb: -------------------------------------------------------------------------------- 1 | module GitWaybackMachine 2 | 3 | def self.boot 4 | Engine.new.tap { |e| e.start! } 5 | end 6 | 7 | end 8 | 9 | require_relative "./git_wayback_machine/version" 10 | require_relative "./git_wayback_machine/engine" 11 | require_relative "./git_wayback_machine/history" 12 | require_relative "./git_wayback_machine/navigator" 13 | require_relative "./git_wayback_machine/controls" 14 | -------------------------------------------------------------------------------- /lib/git_wayback_machine/controls.rb: -------------------------------------------------------------------------------- 1 | require "io/console" 2 | 3 | module GitWaybackMachine 4 | 5 | class Controls 6 | 7 | def initialize 8 | end 9 | 10 | def on_event(&callback) 11 | while true 12 | case read_key 13 | when "\e[A" then callback.call(:up) # UP 14 | when "\e[B" then callback.call(:down) # DOWN 15 | when "\r", "\n" then callback.call(:enter) # ENTER 16 | when "\u0003", "\e" # Ctrl+C, ESC 17 | exit(0) 18 | end 19 | end 20 | end 21 | 22 | private 23 | 24 | def read_key 25 | STDIN.raw do |io| 26 | key = io.getc.chr 27 | 28 | if key == "\e" 29 | extra_thread = Thread.new do 30 | key += io.getc.chr + io.getc.chr 31 | end 32 | extra_thread.join(0.001) 33 | extra_thread.kill 34 | end 35 | 36 | key 37 | end 38 | end 39 | end 40 | 41 | end 42 | -------------------------------------------------------------------------------- /lib/git_wayback_machine/engine.rb: -------------------------------------------------------------------------------- 1 | module GitWaybackMachine 2 | 3 | class Engine 4 | def initialize 5 | @history = GitWaybackMachine::History.new 6 | @navigator = GitWaybackMachine::Navigator.new(@history) 7 | end 8 | 9 | def start! 10 | stash! 11 | 12 | @navigator.on_change do |commit| 13 | move_to commit 14 | end 15 | 16 | rescue Interrupt => e 17 | nil # that's cool 18 | ensure 19 | rollback! 20 | end 21 | 22 | def move_to(entry) 23 | `git reset --hard #{entry.sha}` 24 | end 25 | 26 | def stash! 27 | @stash = `git stash -u` 28 | end 29 | 30 | def rollback! 31 | puts "\rJumping back to the reality!" 32 | move_to @history[0] 33 | `git stash pop` unless @stash.include?("No local changes to save") 34 | end 35 | 36 | end 37 | 38 | end 39 | -------------------------------------------------------------------------------- /lib/git_wayback_machine/history.rb: -------------------------------------------------------------------------------- 1 | module GitWaybackMachine 2 | 3 | class History < Array 4 | SIZE = 200 5 | 6 | def initialize 7 | super raw_entries.map { |entry| Entry.new *entry } 8 | end 9 | 10 | class Entry < Struct.new(:sha, :name, :time, :comment) 11 | def to_s 12 | meta = "\e[33m#{sha}\e[37m | \e[35m#{name.ljust(17)} \e[36m(#{time})\e[37m - " 13 | size_so_far = meta.gsub(/\e\[\d+m/, "").size 14 | terminal_width = `tput cols`.to_i 15 | cut_comment = comment.slice(0, terminal_width - size_so_far - 3) 16 | cut_comment << "…" if comment.size > cut_comment.size 17 | 18 | "#{meta}#{cut_comment}\e[0m" 19 | end 20 | end 21 | 22 | private 23 | 24 | def raw_entries 25 | `git log --pretty=format:'%h|%an|%cr|%s' -#{SIZE}`.split("\n").map do |entry| 26 | entry.sub(/\A\s*\*\s*/, "").split("|") 27 | end 28 | end 29 | 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /lib/git_wayback_machine/navigator.rb: -------------------------------------------------------------------------------- 1 | module GitWaybackMachine 2 | 3 | class Navigator 4 | WINDOW_SIZE = 10 # the number of entries to show 5 | 6 | def initialize(history) 7 | @history = history 8 | @current_entry = @history[0] 9 | @controls = GitWaybackMachine::Controls.new 10 | end 11 | 12 | def on_change(&callback) 13 | render 14 | 15 | @controls.on_event do |event| 16 | case event 17 | when :up then @current_entry = prev_entry 18 | when :down then @current_entry = next_entry 19 | end 20 | 21 | callback.call(@current_entry) 22 | cleanup 23 | render 24 | end 25 | 26 | ensure 27 | cleanup 28 | end 29 | 30 | def render 31 | puts intro_text 32 | 33 | entries_slice.each do |entry| 34 | if entry == @current_entry 35 | puts " \e[37;1m#{entry}\e[0m" 36 | else 37 | puts " \e[37;2m#{entry}\e[0m" 38 | end 39 | end 40 | end 41 | 42 | def cleanup 43 | navigator_size = entries_slice.size + 2 44 | terminal_size = `tput cols`.to_i 45 | print "\r\e[#{navigator_size}A" 46 | print (" " * terminal_size + "\n") * navigator_size 47 | print "\r\e[#{navigator_size}A" 48 | end 49 | 50 | private 51 | 52 | def intro_text 53 | " \e[37;2mUse \e[0m\e[37;0mUP\e[0m\e[37;2m and \e[0m\e[37;1mDOWN\e[0m\e[37;2m keys to switch between commits:\n\e[0m" 54 | end 55 | 56 | def prev_entry 57 | index = @history.index(@current_entry) 58 | index == 0 ? @current_entry : @history[index-1] 59 | end 60 | 61 | def next_entry 62 | index = @history.index(@current_entry) 63 | index == @history.size-1 ? @current_entry : @history[index+1] 64 | end 65 | 66 | def entries_slice 67 | return [] if @history.size == 0 68 | 69 | index = @history.index(@current_entry) 70 | start = index - WINDOW_SIZE / 2 71 | start = @history.size - WINDOW_SIZE if start > @history.size - WINDOW_SIZE 72 | start = 0 if start < 0 73 | 74 | @history.slice(start, WINDOW_SIZE) 75 | end 76 | 77 | end 78 | 79 | end 80 | -------------------------------------------------------------------------------- /lib/git_wayback_machine/version.rb: -------------------------------------------------------------------------------- 1 | module GitWaybackMachine 2 | VERSION = "0.1.2" 3 | end 4 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaievns/git-wayback-machine/cffe74e958361b4ca5a56046e19dec86895bdfd0/screenshot.png --------------------------------------------------------------------------------