├── .gitignore ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── _DIFFSHOTS.md ├── _DIFFSHOTS ├── accepts-range-of-commits.diffshot.rb.png ├── added-border-and-color-options.diffshot.rb.png ├── added-configyml.config.yml.png ├── added-configyml.diffshot.rb.png ├── better-image-names.diffshot.rb.png ├── diffs-files.diffshot.rb.png ├── generates-markdown-file.diffshot.rb.png ├── gets-github-url.diffshot.rb.png ├── prints-diffed-files.diffshot.rb.png ├── prints-line-color.diffshot.rb.png ├── shows-all-commits.diffshot.rb.png ├── shows-files-in-initial-commit.diffshot.rb.png └── successfully-generates-the-images.diffshot.rb.png └── bin └── diffshot /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in diffshot.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 RobertAKARobin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Diffshot 2 | 3 | ## Description 4 | This script takes screenshots of every file diff through a Git repo's commit history, and outputs a markdown file containing the commit history with its images, annotations, and table of contents. 5 | 6 | **[See this example, created with this repo.](_DIFFSHOTS.md)** 7 | 8 | > Note: The links in the table of contents don't take into account the possibility of multiple commits having the same commit message. So be a good developer and make each commit message unique! 9 | 10 | ## Installation 11 | 12 | #### 1. Install ImageMagick 13 | 14 | ```bash 15 | $ brew install imagemagick 16 | ``` 17 | 18 | #### 2. Install system fonts in ImageMagick 19 | 20 | ```bash 21 | $ cd $(dirname $(which convert)) 22 | $ cd $(dirname $(readlink $(which convert))) 23 | $ cd ../etc/ImageMagick-6/ 24 | $ curl http://www.imagemagick.org/Usage/scripts/imagick_type_gen > find_fonts.sh 25 | $ perl find_fonts.sh > type.xml 26 | ``` 27 | 28 | > This downloads a script that scans your system for fonts and compiles them into `type.xml`, which ImageMagick can parse. The end result should be `type.xml` exists on a path like `/usr/local/Cellar/imagemagick/6.9.3-0_2/etc/ImageMagick-6` 29 | 30 | #### 3. Install Diffshot 31 | 32 | ``` 33 | $ gem install diffshot 34 | ``` 35 | 36 | #### 4. Try it out 37 | 38 | Go to some Github repo, type `diffshot`, and you should see it print out the commits and files as it goes through them. 39 | 40 | At the end, you'll have a [_DIFFSHOTS](/_DIFFSHOTS) folder with a bunch of images inside it, and a [_DIFFSHOTS.md](/_DIFFSHOTS.md)! Each image is named with this convention: 41 | 42 | ``` 43 | commit-message.file-name.png 44 | ``` 45 | 46 | (Non-alphanumeric characters are removed or replaced with `-`.) 47 | 48 | #### Options 49 | 50 | `$ diffshot hash..hash` 51 | 52 | As with `git diff` and `git log`, you can pass a range of hashes to `diffshot` and it will iterate only over that range. 53 | 54 | ## Contributing 55 | 56 | Yes, please! 57 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | task :default => :spec 3 | -------------------------------------------------------------------------------- /_DIFFSHOTS.md: -------------------------------------------------------------------------------- 1 | # https://www.github.com/RobertAKARobin/diffshot 2 | 3 | > This commit history created using [Diffshot](https://github.com/RobertAKARobin/diffshot) 4 | 5 | ## Table of Contents 6 | 7 | - [c2b152e: Added config.yml](#added-configyml) 8 | - [config.yml](#added-configyml-configyml) 9 | - [diffshot.rb](#added-configyml-diffshotrb) 10 | - [ae9a1aa: Shows all commits](#shows-all-commits) 11 | - [diffshot.rb](#shows-all-commits-diffshotrb) 12 | - [33705a5: Gets Github URL](#gets-github-url) 13 | - [diffshot.rb](#gets-github-url-diffshotrb) 14 | - [b5138ed: Prints diffed files](#prints-diffed-files) 15 | - [diffshot.rb](#prints-diffed-files-diffshotrb) 16 | - [a4ffcde: Diffs files](#diffs-files) 17 | - [diffshot.rb](#diffs-files-diffshotrb) 18 | - [456d406: Prints line color](#prints-line-color) 19 | - [diffshot.rb](#prints-line-color-diffshotrb) 20 | - [e887de7: Successfully generates the images](#successfully-generates-the-images) 21 | - [diffshot.rb](#successfully-generates-the-images-diffshotrb) 22 | - [cafd7cd: Accepts range of commits](#accepts-range-of-commits) 23 | - [diffshot.rb](#accepts-range-of-commits-diffshotrb) 24 | - [02aac3a: Added border and color options](#added-border-and-color-options) 25 | - [diffshot.rb](#added-border-and-color-options-diffshotrb) 26 | - [a5a0ea1: Better image names](#better-image-names) 27 | - [diffshot.rb](#better-image-names-diffshotrb) 28 | - [b4f9f34: Generates markdown file](#generates-markdown-file) 29 | - [diffshot.rb](#generates-markdown-file-diffshotrb) 30 | - [e4f371a: Shows files in initial commit](#shows-files-in-initial-commit) 31 | - [diffshot.rb](#shows-files-in-initial-commit-diffshotrb) 32 | 33 | 34 | # Added config.yml 35 | > [c2b152e](https://www.github.com/RobertAKARobin/diffshot/commit/c2b152e) 36 | 37 | ### [Added config.yml: `config.yml`](https://www.github.com/RobertAKARobin/diffshot/blob/c2b152e/config.yml) 38 | 39 | ![Added config.yml, config.yml](_DIFFSHOTS/added-configyml.config.yml.png) 40 | ### [Added config.yml: `diffshot.rb`](https://www.github.com/RobertAKARobin/diffshot/blob/c2b152e/diffshot.rb) 41 | 42 | ![Added config.yml, diffshot.rb](_DIFFSHOTS/added-configyml.diffshot.rb.png) 43 | # Shows all commits 44 | > [ae9a1aa](https://www.github.com/RobertAKARobin/diffshot/commit/ae9a1aa) 45 | 46 | ### [Shows all commits: `diffshot.rb`](https://www.github.com/RobertAKARobin/diffshot/blob/ae9a1aa/diffshot.rb) 47 | 48 | ![Shows all commits, diffshot.rb](_DIFFSHOTS/shows-all-commits.diffshot.rb.png) 49 | # Gets Github URL 50 | > [33705a5](https://www.github.com/RobertAKARobin/diffshot/commit/33705a5) 51 | 52 | ### [Gets Github URL: `diffshot.rb`](https://www.github.com/RobertAKARobin/diffshot/blob/33705a5/diffshot.rb) 53 | 54 | ![Gets Github URL, diffshot.rb](_DIFFSHOTS/gets-github-url.diffshot.rb.png) 55 | # Prints diffed files 56 | > [b5138ed](https://www.github.com/RobertAKARobin/diffshot/commit/b5138ed) 57 | 58 | ### [Prints diffed files: `diffshot.rb`](https://www.github.com/RobertAKARobin/diffshot/blob/b5138ed/diffshot.rb) 59 | 60 | ![Prints diffed files, diffshot.rb](_DIFFSHOTS/prints-diffed-files.diffshot.rb.png) 61 | # Diffs files 62 | > [a4ffcde](https://www.github.com/RobertAKARobin/diffshot/commit/a4ffcde) 63 | 64 | ### [Diffs files: `diffshot.rb`](https://www.github.com/RobertAKARobin/diffshot/blob/a4ffcde/diffshot.rb) 65 | 66 | ![Diffs files, diffshot.rb](_DIFFSHOTS/diffs-files.diffshot.rb.png) 67 | # Prints line color 68 | > [456d406](https://www.github.com/RobertAKARobin/diffshot/commit/456d406) 69 | 70 | ### [Prints line color: `diffshot.rb`](https://www.github.com/RobertAKARobin/diffshot/blob/456d406/diffshot.rb) 71 | 72 | ![Prints line color, diffshot.rb](_DIFFSHOTS/prints-line-color.diffshot.rb.png) 73 | # Successfully generates the images 74 | > [e887de7](https://www.github.com/RobertAKARobin/diffshot/commit/e887de7) 75 | 76 | ### [Successfully generates the images: `diffshot.rb`](https://www.github.com/RobertAKARobin/diffshot/blob/e887de7/diffshot.rb) 77 | 78 | ![Successfully generates the images, diffshot.rb](_DIFFSHOTS/successfully-generates-the-images.diffshot.rb.png) 79 | # Accepts range of commits 80 | > [cafd7cd](https://www.github.com/RobertAKARobin/diffshot/commit/cafd7cd) 81 | 82 | ### [Accepts range of commits: `diffshot.rb`](https://www.github.com/RobertAKARobin/diffshot/blob/cafd7cd/diffshot.rb) 83 | 84 | ![Accepts range of commits, diffshot.rb](_DIFFSHOTS/accepts-range-of-commits.diffshot.rb.png) 85 | # Added border and color options 86 | > [02aac3a](https://www.github.com/RobertAKARobin/diffshot/commit/02aac3a) 87 | 88 | ### [Added border and color options: `diffshot.rb`](https://www.github.com/RobertAKARobin/diffshot/blob/02aac3a/diffshot.rb) 89 | 90 | ![Added border and color options, diffshot.rb](_DIFFSHOTS/added-border-and-color-options.diffshot.rb.png) 91 | # Better image names 92 | > [a5a0ea1](https://www.github.com/RobertAKARobin/diffshot/commit/a5a0ea1) 93 | 94 | ### [Better image names: `diffshot.rb`](https://www.github.com/RobertAKARobin/diffshot/blob/a5a0ea1/diffshot.rb) 95 | 96 | ![Better image names, diffshot.rb](_DIFFSHOTS/better-image-names.diffshot.rb.png) 97 | # Generates markdown file 98 | > [b4f9f34](https://www.github.com/RobertAKARobin/diffshot/commit/b4f9f34) 99 | 100 | ### [Generates markdown file: `diffshot.rb`](https://www.github.com/RobertAKARobin/diffshot/blob/b4f9f34/diffshot.rb) 101 | 102 | ![Generates markdown file, diffshot.rb](_DIFFSHOTS/generates-markdown-file.diffshot.rb.png) 103 | # Shows files in initial commit 104 | > [e4f371a](https://www.github.com/RobertAKARobin/diffshot/commit/e4f371a) 105 | 106 | ### [Shows files in initial commit: `diffshot.rb`](https://www.github.com/RobertAKARobin/diffshot/blob/e4f371a/diffshot.rb) 107 | 108 | ![Shows files in initial commit, diffshot.rb](_DIFFSHOTS/shows-files-in-initial-commit.diffshot.rb.png) 109 | 110 | -------------------------------------------------------------------------------- /_DIFFSHOTS/accepts-range-of-commits.diffshot.rb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertAKARobin/diffshot-rb/c71da1484364627d3c9b2ba87a8058a74dfe3365/_DIFFSHOTS/accepts-range-of-commits.diffshot.rb.png -------------------------------------------------------------------------------- /_DIFFSHOTS/added-border-and-color-options.diffshot.rb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertAKARobin/diffshot-rb/c71da1484364627d3c9b2ba87a8058a74dfe3365/_DIFFSHOTS/added-border-and-color-options.diffshot.rb.png -------------------------------------------------------------------------------- /_DIFFSHOTS/added-configyml.config.yml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertAKARobin/diffshot-rb/c71da1484364627d3c9b2ba87a8058a74dfe3365/_DIFFSHOTS/added-configyml.config.yml.png -------------------------------------------------------------------------------- /_DIFFSHOTS/added-configyml.diffshot.rb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertAKARobin/diffshot-rb/c71da1484364627d3c9b2ba87a8058a74dfe3365/_DIFFSHOTS/added-configyml.diffshot.rb.png -------------------------------------------------------------------------------- /_DIFFSHOTS/better-image-names.diffshot.rb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertAKARobin/diffshot-rb/c71da1484364627d3c9b2ba87a8058a74dfe3365/_DIFFSHOTS/better-image-names.diffshot.rb.png -------------------------------------------------------------------------------- /_DIFFSHOTS/diffs-files.diffshot.rb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertAKARobin/diffshot-rb/c71da1484364627d3c9b2ba87a8058a74dfe3365/_DIFFSHOTS/diffs-files.diffshot.rb.png -------------------------------------------------------------------------------- /_DIFFSHOTS/generates-markdown-file.diffshot.rb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertAKARobin/diffshot-rb/c71da1484364627d3c9b2ba87a8058a74dfe3365/_DIFFSHOTS/generates-markdown-file.diffshot.rb.png -------------------------------------------------------------------------------- /_DIFFSHOTS/gets-github-url.diffshot.rb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertAKARobin/diffshot-rb/c71da1484364627d3c9b2ba87a8058a74dfe3365/_DIFFSHOTS/gets-github-url.diffshot.rb.png -------------------------------------------------------------------------------- /_DIFFSHOTS/prints-diffed-files.diffshot.rb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertAKARobin/diffshot-rb/c71da1484364627d3c9b2ba87a8058a74dfe3365/_DIFFSHOTS/prints-diffed-files.diffshot.rb.png -------------------------------------------------------------------------------- /_DIFFSHOTS/prints-line-color.diffshot.rb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertAKARobin/diffshot-rb/c71da1484364627d3c9b2ba87a8058a74dfe3365/_DIFFSHOTS/prints-line-color.diffshot.rb.png -------------------------------------------------------------------------------- /_DIFFSHOTS/shows-all-commits.diffshot.rb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertAKARobin/diffshot-rb/c71da1484364627d3c9b2ba87a8058a74dfe3365/_DIFFSHOTS/shows-all-commits.diffshot.rb.png -------------------------------------------------------------------------------- /_DIFFSHOTS/shows-files-in-initial-commit.diffshot.rb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertAKARobin/diffshot-rb/c71da1484364627d3c9b2ba87a8058a74dfe3365/_DIFFSHOTS/shows-files-in-initial-commit.diffshot.rb.png -------------------------------------------------------------------------------- /_DIFFSHOTS/successfully-generates-the-images.diffshot.rb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertAKARobin/diffshot-rb/c71da1484364627d3c9b2ba87a8058a74dfe3365/_DIFFSHOTS/successfully-generates-the-images.diffshot.rb.png -------------------------------------------------------------------------------- /bin/diffshot: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | 3 | require "fileutils" 4 | require "shellwords" 5 | 6 | COMMIT_RANGE = (ARGV[0] || "") 7 | 8 | C = { 9 | file: { 10 | img_dir: "_DIFFSHOTS", 11 | markdown: "_DIFFSHOTS.md", 12 | }, 13 | font: { 14 | family: "Consolas", 15 | size: "16", 16 | height: "20" 17 | }, 18 | color: { 19 | hide: "#aaaaaa", 20 | delete: "#ff0000", 21 | add: "#00ff00", 22 | normal: "#ffffff", 23 | background: "#000000" 24 | }, 25 | image: { 26 | width: "800", 27 | quality: "0" 28 | } 29 | } 30 | 31 | def first_commit 32 | raw = `git rev-list --max-parents=0 HEAD` 33 | return raw[0..6] 34 | end 35 | 36 | def all_commits 37 | output = [] 38 | raw = `git log --pretty=format:"%h%n%s" #{COMMIT_RANGE}`.split("\n") 39 | hash = "" 40 | raw.each_with_index do |line, linenum| 41 | if linenum.even? 42 | hash = line 43 | else 44 | output.unshift({hash: hash, message: line}) 45 | end 46 | end 47 | return output 48 | end 49 | 50 | def github_url 51 | remote =`git remote get-url origin` 52 | remote.sub!("git@github.com:", "https://www.github.com/") 53 | remote.sub!(/\.git$/, "") 54 | return remote.strip 55 | end 56 | 57 | def has_origin? 58 | return !(`git remote -v | grep origin`.empty?) 59 | end 60 | 61 | def all_files(hash) 62 | return `git show --pretty=format: --numstat #{hash}` 63 | end 64 | 65 | def changed_files(hash) 66 | # A = added, M = modified, R = removed 67 | return `git diff --diff-filter=AMR --numstat #{hash}~..#{hash}` 68 | end 69 | 70 | def file_list(filelist) 71 | output = [] 72 | filelist.split("\n").each_with_index do |file, index| 73 | # A numstat entry beginning with `-` is binary 74 | next if file =~ /^-/ 75 | # Diff file lines start with tabs 76 | output.push(file.sub(/^[0-9]{1,}\t[0-9]{1,}\t/, "")) 77 | end 78 | return output 79 | end 80 | 81 | def file_diff(hash, file) 82 | diff = `git show --format=format: \ 83 | --no-prefix \ 84 | --no-color \ 85 | --ignore-all-space \ 86 | --ignore-space-change \ 87 | --ignore-space-at-eol \ 88 | --ignore-blank-lines \ 89 | #{hash} -- #{file}` 90 | return diff 91 | end 92 | 93 | def color_of(line) 94 | if line =~ /^(@@|---|\+\+\+)/ 95 | return C[:color][:hide] 96 | elsif line =~ /^-/ 97 | return C[:color][:delete] 98 | elsif line =~ /^\+/ 99 | return C[:color][:add] 100 | else 101 | return C[:color][:normal] 102 | end 103 | end 104 | 105 | def spine_case(string) 106 | output = string.downcase 107 | output.gsub!(/[^a-zA-Z0-9 \-\.]/, "") 108 | output.gsub!(/ /, "-") 109 | output.gsub!(/-{2,}/, "-") 110 | return output 111 | end 112 | 113 | def anchor(string) 114 | output = string.downcase 115 | output.gsub!(/ /, "-") 116 | output.gsub!(/[^a-zA-Z0-9\-_]/, "") 117 | return "##{output}" 118 | end 119 | 120 | def q(string) 121 | return "\"#{string}\"" 122 | end 123 | 124 | def annotate(string, options = {}) 125 | return [ 126 | "-splice", "0x#{options[:height] || C[:font][:height]}", 127 | "-fill", q(options[:color] || color_of(string)), 128 | # Necessary to escape `\n`. Sigh. 129 | "-annotate", "0 #{q(" " + Shellwords.escape(string).gsub("\\\\", "\\\\\\\\\\\\\\\\"))}" 130 | ] 131 | end 132 | 133 | # ===== 134 | # MAIN PROCESS BEGINS 135 | # ===== 136 | 137 | FileUtils.rm_rf(C[:file][:img_dir]) 138 | FileUtils.mkdir(C[:file][:img_dir]) 139 | 140 | cTABLE = "" 141 | cOMMITS = "" 142 | 143 | all_commits.each_with_index do |commit, index| 144 | puts "#{commit[:hash]}: #{commit[:message]}" 145 | if index == 0 && commit[:hash] == first_commit 146 | filenames = all_files(commit[:hash]) 147 | else 148 | filenames = changed_files(commit[:hash]) 149 | end 150 | filenames = file_list(filenames) 151 | 152 | cTABLE += <<-____ 153 | - [#{commit[:hash]}: #{commit[:message]}](#{anchor commit[:message]}) 154 | ____ 155 | 156 | cOMMITS += <<-____ 157 | # #{commit[:message]} 158 | > #{has_origin? ? "[#{commit[:hash]}](#{github_url}/commit/#{commit[:hash]})" : commit[:hash]} 159 | 160 | ____ 161 | 162 | filenames.each do |filename| 163 | puts " #{filename}" 164 | header = "#{commit[:message]}: \`#{filename}\`" 165 | imgname = C[:file][:img_dir] + "/" + [spine_case(commit[:message]), spine_case(filename), "png"].join(".") 166 | 167 | cTABLE += <<-____ 168 | - [#{filename}](#{anchor header}) 169 | ____ 170 | 171 | cOMMITS += <<-____ 172 | ### #{has_origin? ? "[#{header}](#{github_url}/blob/#{commit[:hash]}/#{filename})" : header} 173 | 174 | ![#{commit[:message]}, #{filename}](#{imgname}) 175 | ____ 176 | 177 | command = [ 178 | "convert", 179 | "-font", q(C[:font][:family]), 180 | "-pointsize", q(C[:font][:size]), 181 | "-extent", q(C[:image][:width]), 182 | "-quality", q(C[:image][:quality]), 183 | "-background", q(C[:color][:background]), 184 | "-gravity", q("SouthWest"), 185 | "-fill", q(C[:color][:normal]), 186 | "label:\" \"" 187 | ] 188 | command.concat annotate("#{commit[:hash]}: #{commit[:message]}", {color: "#FFFF00", height: 8}) 189 | file_diff(commit[:hash], filename).split("\n").each do |line| 190 | command.concat annotate(line) 191 | end 192 | command.concat annotate(" ", {height: 8}) 193 | command.push q(imgname) 194 | command = command.join(" ") 195 | system(command) 196 | end 197 | end 198 | 199 | File.write C[:file][:markdown], <<-____ 200 | # #{has_origin? ? github_url : "Commit History"} 201 | 202 | > This commit history created using [Diffshot](https://github.com/RobertAKARobin/diffshot) 203 | 204 | ## Table of Contents 205 | 206 | #{cTABLE} 207 | 208 | #{cOMMITS} 209 | ____ 210 | --------------------------------------------------------------------------------