├── README.md ├── bin └── git-slides ├── demo.gif └── plugin └── git-slides.vim /README.md: -------------------------------------------------------------------------------- 1 | git-slides 2 | ========== 3 | 4 | Text-based slides using vim and git. 5 | 6 | ![Screencast](demo.gif) 7 | 8 | Overview 9 | -------- 10 | 11 | Abuse git's history rewriting mechanism by creating one commit for each slide. 12 | 13 | Within vim, press Space and Backspace (in normal mode) to move forwards and backwards. 14 | 15 | Especially useful for live demos with a lot of canned code, which you want to present and run incrementally. 16 | 17 | 18 | Installation 19 | ------------ 20 | 21 | Copy `plugin/git-slides.vim` to your `~/.vim/plugin` 22 | folder, and put `bin/git-slides` somewhere on your path. 23 | -------------------------------------------------------------------------------- /bin/git-slides: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | function usage { 5 | echo "usage:" 6 | echo " $0 next [-f|--force]" 7 | echo " $0 prev [-f|--force]" 8 | echo " $0 save [msg]" 9 | echo " $0 insert [msg]" 10 | echo " $0 insert-before [msg]" 11 | echo " $0 delete" 12 | echo " $0 transform" 13 | echo "" 14 | echo "Move forwards or backwards through the git history." 15 | echo "Use --force to do so even if the files have been changed." 16 | echo "" 17 | echo "To modify a slide, simply move to that slide and edit it." 18 | echo "Then use 'save' to commit the state of the slide into git." 19 | echo "'insert' is a variant which inserts the slide after the one" 20 | echo "it was modified from, while 'delete' removes the current" 21 | echo "slide. All commands rewrite the history of all the slides" 22 | echo "which come afterwards." 23 | echo "" 24 | echo "'transform' is a special version of 'save' which tries to" 25 | echo "apply the change to all future slides, stopping at the" 26 | echo "first conflict." 27 | } 28 | 29 | # "commit 12345" => "12345" 30 | function cut_commit { 31 | cut -d' ' -f2 32 | } 33 | 34 | function current_commit { 35 | git log HEAD | head -n1 | cut_commit 36 | } 37 | 38 | function branches { 39 | git branch | grep -v '(no branch)' | grep -v '(detached from' | grep -v '(HEAD detached' | cut -c3- 40 | } 41 | 42 | function current_branch { 43 | local CURRENT="$(current_commit)" 44 | for BRANCH in `branches`; do 45 | if [ "$(git log "$BRANCH" | grep "^commit $CURRENT")" ]; then 46 | echo "$BRANCH" 47 | break 48 | fi 49 | done 50 | } 51 | 52 | function commits { 53 | git log "$(current_branch)" | grep '^commit' 54 | } 55 | 56 | function current_tip { 57 | commits | head -n1 | cut_commit 58 | } 59 | 60 | function next_commit { 61 | local CURRENT="$(current_commit)" 62 | commits | grep -B1 "^commit $CURRENT" | head -n1 | cut_commit 63 | } 64 | 65 | function prev_commit { 66 | local CURRENT="$(current_commit)" 67 | commits | grep -A1 "^commit $CURRENT" | tail -n1 | cut_commit 68 | } 69 | 70 | function move_to { 71 | if [ "$2" = "-f" ] || [ "$2" = "--force" ]; then 72 | git clean -df 73 | git reset --hard 74 | fi 75 | if [ "$1" = "$(current_tip)" ]; then 76 | git checkout "$(current_branch)" 77 | else 78 | git checkout "$1" 79 | fi 80 | } 81 | 82 | function next { 83 | move_to "$(next_commit)" 84 | } 85 | 86 | function prev { 87 | move_to "$(prev_commit)" 88 | } 89 | 90 | function duplicate { 91 | local MSG="$1" 92 | local BRANCH="$(current_branch)" 93 | local CURRENT="$(current_commit)" 94 | 95 | # duplicate $CURRENT 96 | git commit --allow-empty -m "$MSG" 97 | local DUP="$(current_commit)" 98 | 99 | # rewrite history to include $DUP. 100 | git rebase "$DUP" "$BRANCH" 101 | 102 | # move to this new commit (whose SHA1 is no longer $DUP) 103 | git checkout -f "$CURRENT" 104 | next 105 | } 106 | 107 | function commit_message { 108 | local COMMIT="$1" 109 | git show -s --format=%B "$COMMIT" 110 | } 111 | 112 | # rewrite history so that the current commit is followed by $FUTURE, 113 | # then the rest of $BRANCH. $FUTURE must already belong to $BRANCH. 114 | function rewrite_history { 115 | local FUTURE="$1" 116 | local BRANCH="$2" 117 | 118 | # force rebase to succeed by duplicating $FUTURE. 119 | # note that the syntax is COMMIT^{tree}, so 120 | # "$FUTURE^{tree}" means the tree from $FUTURE, 121 | # not from $FUTURE^. 122 | local MSG="$(commit_message "$FUTURE")" 123 | local TREE="$FUTURE^{tree}" 124 | local PARENT="$(current_commit)" 125 | local DUP="$(echo "$MSG" | git commit-tree "$TREE" -p "$PARENT")" 126 | git checkout "$DUP" 127 | 128 | # rewrite history to include $DUP instead of $FUTURE. 129 | git rebase --onto "$DUP" "$FUTURE" "$BRANCH" 130 | } 131 | 132 | function save { 133 | local MSG="$1" 134 | local BRANCH="$(current_branch)" 135 | local OLD="$(current_commit)" 136 | local NEXT="$(next_commit)" 137 | if [ "$MSG" ]; then 138 | git commit -a --amend --allow-empty -m"$MSG" 139 | else 140 | git commit -a --amend --allow-empty --reuse-message="$OLD" 141 | fi 142 | local NEW="$(current_commit)" 143 | 144 | if [ "$NEXT" != "$OLD" ]; then 145 | # rewrite history to include $NEW instead of $OLD. 146 | rewrite_history "$NEXT" "$BRANCH" 147 | 148 | # move to the saved commit. 149 | git checkout -f "$NEW" 150 | fi 151 | } 152 | 153 | function insert_before { 154 | local MSG="$1" 155 | local BRANCH="$(current_branch)" 156 | local PREV="$(current_commit)" 157 | 158 | # detach from branch, so we don't accidentally add 159 | # commits to it. rewrite_history will do that. 160 | git checkout "$PREV" 161 | git commit -a --amend --allow-empty -m "$MSG" 162 | local NEW="$(current_commit)" 163 | 164 | # rewrite history to include $NEW. 165 | rewrite_history "$PREV" "$BRANCH" 166 | 167 | # move to the inserted commit. 168 | git checkout -f "$NEW" 169 | } 170 | 171 | function insert_after { 172 | local MSG="$1" 173 | local BRANCH="$(current_branch)" 174 | local PREV="$(current_commit)" 175 | local NEXT="$(next_commit)" 176 | git commit -a --allow-empty -m "$MSG" 177 | local NEW="$(current_commit)" 178 | 179 | if [ "$NEXT" != "$PREV" ]; then 180 | # rewrite history to include $NEW. 181 | rewrite_history "$NEXT" "$BRANCH" 182 | 183 | # move to the inserted commit. 184 | git checkout -f "$NEW" 185 | fi 186 | } 187 | 188 | function delete { 189 | if [ "$(current_commit)" = "$(current_tip)" ]; then 190 | git reset --hard HEAD^ 191 | else 192 | local BRANCH="$(current_branch)" 193 | local PREV="$(prev_commit)" 194 | local NEXT="$(next_commit)" 195 | 196 | # rewrite history to include $PREV and $NEXT, but not the current commit. 197 | git checkout -f "$PREV" 198 | rewrite_history "$NEXT" "$BRANCH" 199 | 200 | # move to the commit before the one we deleted. 201 | git checkout -f "$PREV" 202 | fi 203 | } 204 | 205 | function transform { 206 | local BRANCH="$(current_branch)" 207 | local OLD="$(current_commit)" 208 | git commit -a --amend --allow-empty --reuse-message="$OLD" 209 | local NEW="$(current_commit)" 210 | 211 | # unsafe version of rewrite_history, which will 212 | # propagate the changes forward and probably cause conflicts. 213 | if ! git rebase --onto "$NEW" "$OLD" "$BRANCH"; then 214 | # resolve conflicts by ignoring "our" changes, 215 | # thereby stopping their propagation. 216 | local DELETED_FILES="$(git status --porcelain | grep "^UD " | cut -d' ' -f2)" 217 | local ADDED_FILES="$(git status --porcelain | grep "^AU " | cut -d' ' -f2)" 218 | if [ "$DELETED_FILES" ]; then 219 | echo "$DELETED_FILES" | xargs git rm -f 220 | fi 221 | if [ "$ADDED_FILES" ]; then 222 | echo "$ADDED_FILES" | xargs git rm -f 223 | fi 224 | git checkout --theirs . 225 | git add -u 226 | local STOP="$(current_commit)" 227 | EDITOR=true git rebase --continue 228 | 229 | # move to the last successfully-modified commit. 230 | git checkout -f "$STOP" 231 | fi 232 | } 233 | 234 | if [ "$1" = "next" ]; then 235 | move_to "$(next_commit)" "$2" 236 | elif [ "$1" = "prev" ]; then 237 | move_to "$(prev_commit)" "$2" 238 | elif [ "$1" = "save" ]; then 239 | save "$2" 240 | elif [ "$1" = "insert" ] || [ "$1" = "insert-after" ]; then 241 | if [ "$2" ]; then 242 | insert_after "$2" 243 | else 244 | insert_after "new slide" 245 | fi 246 | elif [ "$1" = "insert-before" ]; then 247 | if [ "$2" ]; then 248 | insert_before "$2" 249 | else 250 | insert_before "new slide" 251 | fi 252 | elif [ "$1" = "delete" ]; then 253 | delete 254 | elif [ "$1" = "transform" ]; then 255 | transform 256 | else 257 | usage 258 | exit 1 259 | fi 260 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gelisam/git-slides/19581f5913978bd312edc2f7b8f32b3eed143bfd/demo.gif -------------------------------------------------------------------------------- /plugin/git-slides.vim: -------------------------------------------------------------------------------- 1 | :set nofoldenable 2 | 3 | :nmap :wa:call system("git-slides next"):e! 4 | :nmap :wa:call system("git-slides prev"):e! 5 | :nmap sl :wa:call system("git-slides next"):e! 6 | :nmap sh :wa:call system("git-slides prev"):e! 7 | :nmap sL :call system("git-slides next -f"):e! 8 | :nmap sH :call system("git-slides prev -f"):e! 9 | :nmap ss :wa:call system("git-slides save"):e! 10 | :nmap sb :wa:call system("git-slides insert-before"):e! 11 | :nmap sa :wa:call system("git-slides insert-after"):e! 12 | :nmap st :wa:call system("git-slides transform"):e! 13 | :nmap sd :wa:call system("git-slides delete"):e! 14 | :nmap sr :!./% 15 | --------------------------------------------------------------------------------