├── .gitignore ├── emacs ├── git-wip.el └── git-wip-mode.el ├── sublime └── gitwip.py ├── vim └── plugin │ └── git-wip.vim ├── test-git-wip.sh ├── README.markdown └── git-wip /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *~ 3 | -------------------------------------------------------------------------------- /emacs/git-wip.el: -------------------------------------------------------------------------------- 1 | (defun git-wip-wrapper () 2 | (interactive) 3 | (let ((file-arg (shell-quote-argument (buffer-file-name)))) 4 | (shell-command (concat "git-wip save \"WIP from emacs: " (buffer-file-name) "\" --editor -- " file-arg)) 5 | (message (concat "Wrote and git-wip'd " (buffer-file-name))))) 6 | 7 | (defun git-wip-if-git () 8 | (interactive) 9 | (when (string= (vc-backend (buffer-file-name)) "Git") 10 | (git-wip-wrapper))) 11 | 12 | (add-hook 'after-save-hook 'git-wip-if-git) 13 | -------------------------------------------------------------------------------- /sublime/gitwip.py: -------------------------------------------------------------------------------- 1 | import sublime_plugin 2 | from subprocess import Popen, PIPE, STDOUT 3 | import os 4 | import sublime 5 | import copy 6 | 7 | class GitWipAutoCommand(sublime_plugin.EventListener): 8 | 9 | def on_post_save_async(self, view): 10 | dirname, fname = os.path.split(view.file_name()) 11 | 12 | p = Popen(["git", "wip", "save", 13 | "WIP from ST3: saving %s" % fname, 14 | "--editor", "--", fname], 15 | cwd=dirname, universal_newlines=True, 16 | bufsize=8096, stdout=PIPE, stderr=STDOUT) 17 | 18 | def finish_callback(): 19 | rcode = p.poll() 20 | 21 | if rcode is None: # not terminated yet 22 | sublime.set_timeout_async(finish_callback, 20) 23 | return 24 | 25 | if rcode != 0: 26 | print ("git command returned code {}".format(rcode)) 27 | 28 | for line in p.stdout: 29 | print(line) 30 | 31 | finish_callback() 32 | 33 | -------------------------------------------------------------------------------- /vim/plugin/git-wip.vim: -------------------------------------------------------------------------------- 1 | " --------------------------------------------------------------------------- 2 | " git wip stuff 3 | 4 | if !exists('g:git_wip_verbose') 5 | let g:git_wip_verbose = 0 6 | endif 7 | if !exists('g:git_wip_disable_signing') 8 | let g:git_wip_disable_signing = 0 9 | endif 10 | 11 | let g:git_wip_status = 0 " 0 = unchecked, 1 = good, 2 = failed 12 | 13 | function! GitWipSave() 14 | if expand("%") == ".git/COMMIT_EDITMSG" 15 | return 16 | endif 17 | if g:git_wip_status == 2 18 | augroup git-wip 19 | autocmd! 20 | augroup END 21 | return 22 | endif 23 | if g:git_wip_status == 0 24 | silent! !git wip -h >/dev/null 2>&1 25 | if v:shell_error 26 | let g:git_wip_status = 2 27 | return 28 | else 29 | let g:git_wip_status = 1 30 | endif 31 | endif 32 | let wip_opts = '--editor' 33 | if g:git_wip_disable_signing 34 | let wip_opts .= ' --no-gpg-sign' 35 | endif 36 | let out = system('git rev-parse 2>&1') 37 | if v:shell_error 38 | return 39 | endif 40 | let dir = expand("%:p:h") 41 | let show_cdup = system('cd "' . dir . '" && git rev-parse --show-cdup 2>/dev/null') 42 | if v:shell_error 43 | " We're not editing a file anywhere near a .git repository, so abort 44 | return 45 | endif 46 | let show_cdup_len = len( show_cdup ) 47 | if show_cdup_len == 0 48 | " We're editing a file in the .git directory 49 | " (.git/EDIT_COMMITMSG, .git/config, etc.), so abort 50 | return 51 | endif 52 | let file = expand("%:t") 53 | let out = system('cd "' . dir . '" && git wip save "WIP from vim (' . file . ')" ' . wip_opts . ' -- "' . file . '" 2>&1') 54 | let err = v:shell_error 55 | if err 56 | redraw 57 | echohl Error 58 | echo "git-wip: " . out 59 | echohl None 60 | elseif g:git_wip_verbose 61 | redraw 62 | echo "git-wip: " . out 63 | endif 64 | endf 65 | 66 | augroup git-wip 67 | autocmd! 68 | autocmd BufWritePost * :call GitWipSave() 69 | augroup END 70 | -------------------------------------------------------------------------------- /emacs/git-wip-mode.el: -------------------------------------------------------------------------------- 1 | ;;; git-wip-mode.el --- Use git-wip to record every buffer save 2 | 3 | ;; Copyright (C) 2013 Jerome Baum 4 | 5 | ;; Author: Jerome Baum 6 | ;; Version: 0.1 7 | ;; Keywords: vc 8 | 9 | ;; This program is free software; you can redistribute it and/or modify 10 | ;; it under the terms of the GNU General Public License as published by 11 | ;; the Free Software Foundation, either version 3 of the License, or 12 | ;; (at your option) any later version. 13 | 14 | ;; This program is distributed in the hope that it will be useful, 15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | ;; GNU General Public License for more details. 18 | 19 | ;; You should have received a copy of the GNU General Public License 20 | ;; along with this program. If not, see . 21 | 22 | ;;; Commentary: 23 | 24 | ;;; Code: 25 | 26 | (eval-when-compile 27 | (require 'cl)) 28 | 29 | (require 'vc) 30 | 31 | (defvar git-wip-buffer-name " *git-wip*" 32 | "Name of the buffer to which git-wip's output will be echoed") 33 | 34 | (defvar git-wip-path 35 | (or 36 | ;; Internal copy of git-wip; preferred because it will be 37 | ;; version-matched 38 | (expand-file-name 39 | "../git-wip" 40 | (file-name-directory 41 | (or load-file-name 42 | (locate-library "git-wip-mode")))) 43 | ;; Look in $PATH and git exec-path 44 | (let ((exec-path 45 | (append 46 | exec-path 47 | (parse-colon-path 48 | (replace-regexp-in-string 49 | "[ \t\n\r]+\\'" "" 50 | (shell-command-to-string "git --exec-path")))))) 51 | (executable-find "git-wip")))) 52 | 53 | (defun git-wip-after-save () 54 | (when (and (string= (vc-backend (buffer-file-name)) "Git") 55 | git-wip-path) 56 | (start-process "git-wip" git-wip-buffer-name 57 | git-wip-path "save" (concat "WIP from emacs: " 58 | (file-name-nondirectory 59 | buffer-file-name)) 60 | "--editor" "--" 61 | (file-name-nondirectory buffer-file-name)) 62 | (message (concat "Wrote and git-wip'd " (buffer-file-name))))) 63 | 64 | ;;;###autoload 65 | (define-minor-mode git-wip-mode 66 | "Toggle git-wip mode. 67 | With no argument, this command toggles the mode. 68 | Non-null prefix argument turns on the mode. 69 | Null prefix argument turns off the mode. 70 | 71 | When git-wip mode is enabled, git-wip will be called every time 72 | you save a buffer." 73 | ;; The initial value. 74 | nil 75 | ;; The indicator for the mode line. 76 | " WIP" 77 | :group 'git-wip 78 | 79 | ;; (de-)register our hook 80 | (if git-wip-mode 81 | (add-hook 'after-save-hook 'git-wip-after-save nil t) 82 | (remove-hook 'after-save-hook 'git-wip-after-save t))) 83 | 84 | (defun git-wip-mode-if-git () 85 | (when (string= (vc-backend (buffer-file-name)) "Git") 86 | (git-wip-mode t))) 87 | 88 | (add-hook 'find-file-hook 'git-wip-mode-if-git) 89 | 90 | (provide 'git-wip-mode) 91 | ;;; git-wip-mode.el ends here 92 | -------------------------------------------------------------------------------- /test-git-wip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # split the script name into BASE directory, and the name it calls itSELF 6 | BASE=$(realpath ${0%/*}) 7 | SELF=${0##*/} 8 | 9 | # this tells git where to find our copy of git-wip script 10 | export PATH="$BASE:$PATH" 11 | 12 | # keep state for each test in this temporary tree 13 | TEST_TREE=/tmp/git-wip-test-$$ 14 | REPO="$TEST_TREE/repo" 15 | CMD="$TEST_TREE/cmd" 16 | OUT="$TEST_TREE/out" 17 | RC="$TEST_TREE/rc" 18 | 19 | # ------------------------------------------------------------------------ 20 | # some helpful utility functions 21 | 22 | die() { echo >&2 "ERROR: $@" ; exit 1 ; } 23 | warn() { echo >&2 "WARNING: $@" ; } 24 | comment() { echo >&2 "# $@" ; } 25 | 26 | _RUN() { 27 | comment "$@" 28 | [ `pwd` = "$REPO" ] || die "expecting to be in $REPO, not `pwd`" 29 | 30 | set +e 31 | echo "$@" >"$CMD" 32 | eval "$@" >"$OUT" 2>&1 33 | echo "$?" >"$RC" 34 | set -e 35 | } 36 | 37 | RUN() { 38 | _RUN "$@" 39 | local rc="`cat $RC`" 40 | [ "$rc" = 0 ] || handle_error 41 | } 42 | 43 | EXP_none() { 44 | local out="$(head -n1 "$OUT")" 45 | if [ -n "$out" ] ; then 46 | warn "out: $out" 47 | handle_error 48 | fi 49 | } 50 | 51 | EXP_text() { 52 | local exp="$1" 53 | local out="$(head -n1 "$OUT")" 54 | if [ "$out" != "$exp" ] ; then 55 | warn "exp: $exp" 56 | warn "out: $out" 57 | handle_error 58 | fi 59 | } 60 | 61 | EXP_grep() { 62 | if ! grep -q "$@" < "$OUT" ; then 63 | warn "grep: $@" 64 | handle_error 65 | fi 66 | } 67 | 68 | create_test_tree() { 69 | rm -rf "$REPO" 70 | mkdir -p "$REPO" 71 | cd "$REPO" 72 | RUN git init 73 | } 74 | 75 | cleanup_test_tree() { 76 | rm -rf "$TEST_TREE" 77 | } 78 | 79 | handle_error() { 80 | set +e 81 | warn "CMD='`cat $CMD`' RC=`cat $RC`" 82 | cat >&1 "$OUT" 83 | cleanup_test_tree 84 | exit 1 85 | } 86 | 87 | trap cleanup_test_tree EXIT 88 | trap handle_error INT TERM ERR 89 | 90 | # ------------------------------------------------------------------------ 91 | # tests 92 | 93 | test_general() { 94 | # init 95 | 96 | create_test_tree 97 | RUN "echo 1 >README" 98 | RUN git add README 99 | RUN git commit -m README 100 | 101 | # run wip w/o changes 102 | 103 | _RUN git wip save 104 | EXP_text "no changes" 105 | 106 | RUN git wip save --editor 107 | EXP_none 108 | 109 | RUN git wip save -e 110 | EXP_none 111 | 112 | # expecting a master branch 113 | 114 | RUN git branch 115 | EXP_grep "^\* master$" 116 | EXP_grep -v "wip" 117 | 118 | # not expecting wip ref at this time 119 | 120 | RUN git for-each-ref 121 | EXP_grep -v "commit.refs/wip/master$" 122 | 123 | # make changes, store wip 124 | 125 | RUN "echo 2 >README" 126 | RUN git wip save --editor 127 | EXP_none 128 | 129 | # expecting a wip ref 130 | 131 | RUN git for-each-ref 132 | EXP_grep "commit.refs/wip/master$" 133 | 134 | # expecting a log entry 135 | 136 | RUN git wip log 137 | EXP_grep "^commit " 138 | EXP_grep "^\s\+WIP$" 139 | 140 | # there should be no wip branch 141 | 142 | RUN git branch 143 | EXP_grep -v "wip" 144 | 145 | # make changes, store wip 146 | 147 | RUN "echo 3 >README" 148 | RUN git wip save "\"message2\"" 149 | EXP_none 150 | 151 | # expecting both log entries 152 | 153 | RUN git wip log 154 | EXP_grep "^commit " 155 | EXP_grep "^\s\+WIP$" 156 | EXP_grep "^\s\+message2$" 157 | 158 | # make a commit 159 | 160 | RUN git add -u README 161 | RUN git commit -m README.2 162 | 163 | # make changes, store wip 164 | 165 | RUN "echo 4 >UNTRACKED" 166 | RUN "echo 4 >README" 167 | RUN git wip save "\"message3\"" 168 | EXP_none 169 | 170 | # expecting message3, not message2 or original WIP 171 | 172 | RUN git wip log 173 | EXP_grep "^commit " 174 | EXP_grep -v "^\s\+WIP$" 175 | EXP_grep -v "^\s\+message2$" 176 | EXP_grep "^\s\+message3$" 177 | 178 | # expecting file changes to README, not UNTRACKED 179 | 180 | RUN git wip log --stat 181 | EXP_grep "^commit " 182 | EXP_grep "^ README | 2" 183 | EXP_grep -v "UNTRACKED" 184 | 185 | # need to be able to extract latest data from git wip branch 186 | 187 | RUN git show HEAD:README 188 | EXP_grep '^3$' 189 | EXP_grep -v '^4$' 190 | 191 | RUN git show wip/master:README 192 | EXP_grep -v '^3$' 193 | EXP_grep '^4$' 194 | } 195 | 196 | test_spaces() { 197 | # init 198 | 199 | create_test_tree 200 | RUN "echo 1 >\"s p a c e s\"" 201 | RUN git add "\"s p a c e s\"" 202 | RUN git commit -m "\"s p a c e s\"" 203 | 204 | # make changes, store wip 205 | 206 | RUN "echo 2 >\"s p a c e s\"" 207 | RUN git wip save "\"message with spaces\"" 208 | EXP_none 209 | 210 | # expecting a wip ref 211 | 212 | RUN git for-each-ref 213 | EXP_grep "commit.refs/wip/master$" 214 | 215 | # expecting a log entry 216 | 217 | RUN git wip log 218 | EXP_grep "^commit " 219 | EXP_grep "^\s\+message with spaces$" 220 | } 221 | 222 | # ------------------------------------------------------------------------ 223 | # run tests 224 | 225 | TESTS=( test_general test_spaces ) 226 | 227 | for TEST in "${TESTS[@]}" ; do 228 | echo "-- $TEST" 229 | $TEST 230 | echo "OK" 231 | done 232 | 233 | trap - INT TERM ERR 234 | cleanup_test_tree 235 | echo "DONE" 236 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | git-wip is a script that will manage Work In Progress (or WIP) branches. 4 | WIP branches are mostly throw away but identify points of development 5 | between commits. The intent is to tie this script into your editor so 6 | that each time you save your file, the git-wip script captures that 7 | state in git. git-wip also helps you return back to a previous state of 8 | development. 9 | 10 | Latest git-wip can be obtained from [github.com](http://github.com/bartman/git-wip). 11 | git-wip was written by [Bart Trojanowski](mailto:bart@jukie.net). 12 | You can find out more from the original [blog post](http://www.jukie.net/bart/blog/save-everything-with-git-wip). 13 | 14 | # WIP branches 15 | 16 | Wip branches are named after the branch that is being worked on, but are 17 | prefixed with 'wip/'. For example if you are working on a branch named 18 | 'feature' then the git-wip script will only manipulate the 'wip/feature' 19 | branch. 20 | 21 | When you run git-wip for the first time, it will capture all changes to 22 | tracked files and all untracked (but not ignored) files, create a 23 | commit, and make a new wip/*topic* branch point to it. 24 | 25 | --- * --- * --- * <-- topic 26 | \ 27 | * <-- wip/topic 28 | 29 | The next invocation of git-wip after a commit is made will continue to 30 | evolve the work from the last wip/*topic* point. 31 | 32 | --- * --- * --- * <-- topic 33 | \ 34 | * 35 | \ 36 | * <-- wip/topic 37 | 38 | When git-wip is invoked after a commit is made, the state of the 39 | wip/*topic* branch will be reset back to your *topic* branch and the new 40 | changes to the working tree will be caputred on a new commit. 41 | 42 | --- * --- * --- * --- * <-- topic 43 | \ \ 44 | * * <-- wip/topic 45 | \ 46 | * 47 | 48 | While the old wip/*topic* work is no longer accessible directly, it can 49 | always be recovered from git-reflog. In the above example you could use 50 | `wip/topic@{1}` to access the dangling references. 51 | 52 | # git-wip command 53 | 54 | The git-wip command can be invoked in several different ways. 55 | 56 | * `git wip` 57 | 58 | In this mode, git-wip will create a new commit on the wip/*topic* 59 | branch (creating it if needed) as described above. 60 | 61 | * `git wip save "description"` 62 | 63 | Similar to `git wip`, but allows for a custom commit message. 64 | 65 | * `git wip log` 66 | 67 | Show the list of the work that leads upto the last WIP commit. This 68 | is similar to invoking: 69 | 70 | `git log --stat wip/$branch...$(git merge-base wip/$branch $branch)` 71 | 72 | # Installation 73 | 74 | Download the script from the GitHub page: 75 | 76 | git clone git://github.com/bartman/git-wip.git 77 | 78 | Add `git-wip` to your `$PATH`: 79 | 80 | mkdir -p ~/bin 81 | cp git-wip/git-wip ~/bin/ 82 | 83 | # editor hooking 84 | 85 | To use git-wip effectively, you should tie it into your editor so you 86 | don't have to remember to run git-wip manually. 87 | 88 | ## vim 89 | 90 | To add git-wip support to vim you can install the provided vim plugin. There 91 | are a few ways to do this. 92 | 93 | **(1)** If you're using [Vundle](https://github.com/gmarik/Vundle.vim), you 94 | just need to include the following line in your `.vimrc`. 95 | 96 | Bundle 'bartman/git-wip', {'rtp': 'vim/'} 97 | 98 | **(2)** You can slo copy the `git-wip.vim` into your vim runtime: 99 | 100 | cp vim/plugin/git-wip ~/.vim/plugin/git-wip 101 | 102 | **(3)** Alternatively, you can add the following to your `.vimrc`. Doing so 103 | will make it be invoked after every `:w` operation. 104 | 105 | augroup git-wip 106 | autocmd! 107 | autocmd BufWritePost * :silent !cd "`dirname "%"`" && git wip save "WIP from vim" --editor -- "`basename "%"`" 108 | augroup END 109 | 110 | The `--editor` option puts git-wip into a special mode that will make it 111 | more quiet and not report errors if there were no changes made to the 112 | file. 113 | 114 | ## emacs 115 | 116 | To add git-wip support to emacs add the following to your `.emacs`. Doing 117 | so will make it be invoked after every `save-buffer` operation. 118 | 119 | (load "/{path_to_git-wip}/emacs/git-wip.el") 120 | 121 | Or you may also copy the content of git-wip.el in your `.emacs`. 122 | 123 | ## sublime 124 | 125 | A sublime plugin was contributed as well. You will find it in the `sublime` 126 | directory. 127 | 128 | # recovery 129 | 130 | Should you discover that you made some really bad changes in your code, 131 | from which you want to recover, here is what to do. 132 | 133 | First we need to find the commit we are interested in. If it's the most recent 134 | then it can be referenced with `wip/master` (assuming your branch is `master`), 135 | otherwise you may need to find the one you want using: 136 | 137 | git reflog show wip/master 138 | 139 | I personally prefer to inspect the reflog with `git log -g`, and sometimes 140 | with `-p` also: 141 | 142 | git log -g -p wip/master 143 | 144 | Once you've picked a commit, you need to checkout the files, note that we are not 145 | switching the commit that your branch points to (HEAD will continue to reference 146 | the last real commit on the branch). We are just checking out the files: 147 | 148 | git checkout ref -- . 149 | 150 | Here `ref` could be a SHA1 or `wip/master`. If you only want to recover one file, 151 | then use it's path instead of the *dot*. 152 | 153 | The changes will be staged in the index and checked out into the working tree, to 154 | review what the differences are between the last commit, use: 155 | 156 | git diff --cached 157 | 158 | If you want, you can unstage all or some with `git reset`, optionally specifying a 159 | filename to unstage. You can then stage them again using `git add` or `git add -p`. 160 | Finally, when you're happy with the changes, commit them. 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /git-wip: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Copyright Bart Trojanowski 4 | # 5 | # git-wip is a script that will manage Work In Progress (or WIP) branches. 6 | # WIP branches are mostly throw away but identify points of development 7 | # between commits. The intent is to tie this script into your editor so 8 | # that each time you save your file, the git-wip script captures that 9 | # state in git. git-wip also helps you return back to a previous state of 10 | # development. 11 | # 12 | # See also http://github.com/bartman/git-wip 13 | # 14 | # The code is licensed as GPL v2 or, at your option, any later version. 15 | # Please see http://www.gnu.org/licenses/gpl-2.0.txt 16 | # 17 | 18 | USAGE='[ info | save [ --editor | --untracked | --no-gpg-sign ] | log [ --pretty ] | delete ] [ [--] ... ]' 19 | LONG_USAGE="Manage Work In Progress branches 20 | 21 | Commands: 22 | git wip - create a new WIP commit 23 | git wip save - create a new WIP commit with custom message 24 | git wip info [] - brief WIP info 25 | git wip log [] - show changes on the WIP branch 26 | git wip delete [] - delete a WIP branch 27 | 28 | Options for save: 29 | -e --editor - be less verbose, assume called from an editor 30 | -u --untracked - capture also untracked files 31 | -i --ignored - capture also ignored files 32 | --no-gpg-sign - do not sign commit; that is, countermand 33 | 'commit.gpgSign = true' 34 | 35 | Options for log: 36 | -p --pretty - show a pretty graph 37 | -r --reflog - show changes in reflog 38 | -s --stat - show diffstat 39 | " 40 | 41 | SUBDIRECTORY_OK=Yes 42 | OPTIONS_SPEC= 43 | 44 | . "$(git --exec-path)/git-sh-setup" 45 | 46 | require_work_tree 47 | 48 | TMP="$GIT_DIR/.git-wip.$$" 49 | trap 'rm -f "$TMP-*"' 0 50 | 51 | WIP_INDEX="$TMP-INDEX" 52 | 53 | WIP_PREFIX=refs/wip/ 54 | WIP_COMMAND= 55 | WIP_MESSAGE=WIP 56 | EDITOR_MODE=false 57 | 58 | dbg() { 59 | if test -n "$WIP_DEBUG" 60 | then 61 | printf '# %s\n' "$*" 62 | fi 63 | } 64 | 65 | # some errors are not worth reporting in --editor mode 66 | report_soft_error () { 67 | $EDITOR_MODE && exit 0 68 | die "$@" 69 | } 70 | 71 | cleanup () { 72 | rm -f "$TMP-*" 73 | } 74 | 75 | get_work_branch () { 76 | ref=$(git symbolic-ref -q HEAD) \ 77 | || report_soft_error "git-wip requires a branch" 78 | 79 | 80 | branch=${ref#refs/heads/} 81 | if [ $branch = $ref ] ; then 82 | die "git-wip requires a local branch" 83 | fi 84 | 85 | echo $branch 86 | } 87 | 88 | get_wip_branch () { 89 | return 0 90 | } 91 | 92 | check_files () { 93 | local -a files=( "$@" ) 94 | 95 | for f in "${files[@]}" 96 | do 97 | [ -f "$f" -o -d "$f" ] || die "$f: No such file or directory." 98 | done 99 | } 100 | 101 | build_new_tree () { 102 | local untracked=$1 ; shift 103 | local ignored=$1 ; shift 104 | local -a files=( "$@" ) 105 | 106 | ( 107 | set -e 108 | rm -f "$WIP_INDEX" 109 | cp -p "$GIT_DIR/index" "$WIP_INDEX" 110 | export GIT_INDEX_FILE="$WIP_INDEX" 111 | git read-tree $wip_parent 112 | if test "${#files[@]}" -gt 0 113 | then 114 | git add -f "${files[@]}" 115 | else 116 | git add --update . 117 | fi 118 | [ -n "$untracked" ] && git add . 119 | [ -n "$ignored" ] && git add -f -A . 120 | git write-tree 121 | rm -f "$WIP_INDEX" 122 | ) 123 | } 124 | 125 | do_save () { 126 | local msg="$1" ; shift 127 | local add_untracked= 128 | local add_ignored= 129 | local no_sign= 130 | 131 | while test $# != 0 132 | do 133 | case "$1" in 134 | -e|--editor) 135 | EDITOR_MODE=true 136 | ;; 137 | -u|--untracked) 138 | add_untracked=t 139 | ;; 140 | -i|--ignored) 141 | add_ignored=t 142 | ;; 143 | --no-gpg-sign) 144 | no_sign=--no-gpg-sign 145 | ;; 146 | --) 147 | shift 148 | break 149 | ;; 150 | *) 151 | [ -f "$1" ] && break 152 | die "Unknown option '$1'." 153 | ;; 154 | esac 155 | shift 156 | done 157 | local -a files=( "$@" ) 158 | local "add_untracked=$add_untracked" 159 | local "add_ignored=$add_ignored" 160 | 161 | if test "${#files[@]}" -gt 0 162 | then 163 | dbg "check_files ${files[@]}" 164 | check_files "${files[@]}" 165 | fi 166 | 167 | dbg "msg=$msg" 168 | dbg "files=$files" 169 | 170 | local work_branch=$(get_work_branch) 171 | local wip_branch="$WIP_PREFIX$work_branch" 172 | 173 | dbg "work_branch=$work_branch" 174 | dbg "wip_branch=$wip_branch" 175 | 176 | # enable reflog 177 | local wip_branch_reflog="$GIT_DIR/logs/$wip_branch" 178 | dbg "wip_branch_reflog=$wip_branch_reflog" 179 | mkdir -p "$(dirname "$wip_branch_reflog")" 180 | : >>"$wip_branch_reflog" 181 | 182 | if ! work_last=$(git rev-parse --verify $work_branch) 183 | then 184 | report_soft_error "'$work_branch' branch has no commits." 185 | fi 186 | 187 | dbg "work_last=$work_last" 188 | 189 | if wip_last=$(git rev-parse --quiet --verify $wip_branch) 190 | then 191 | local base=$(git merge-base $wip_last $work_last) \ 192 | || die "'work_branch' and '$wip_branch' are unrelated." 193 | 194 | if [ $base = $work_last ] ; then 195 | wip_parent=$wip_last 196 | else 197 | wip_parent=$work_last 198 | fi 199 | else 200 | # remove empty/corrupt wip branch file 201 | local wip_branch_file="$GIT_DIR/$wip_branch" 202 | if test -e "$wip_branch_file" 203 | then 204 | dbg "removing $wip_branch_file" 205 | rm -f "$wip_branch_file" 206 | fi 207 | # use the working branch for parent 208 | wip_parent=$work_last 209 | fi 210 | 211 | dbg "wip_parent=$wip_parent" 212 | 213 | new_tree=$( build_new_tree "$add_untracked" "$add_ignored" "${files[@]}" ) \ 214 | || die "Cannot save the current worktree state." 215 | 216 | dbg "new_tree=$new_tree" 217 | 218 | if git diff-tree --exit-code --quiet $new_tree $wip_parent ; then 219 | report_soft_error "no changes" 220 | fi 221 | 222 | dbg "... has changes" 223 | 224 | new_wip=$(printf '%s\n' "$msg" | git commit-tree $no_sign $new_tree -p $wip_parent 2>/dev/null) \ 225 | || die "Cannot record working tree state" 226 | 227 | dbg "new_wip=$new_wip" 228 | 229 | msg1=$(printf '%s\n' "$msg" | sed -e 1q) 230 | git update-ref -m "git-wip: $msg1" $wip_branch $new_wip $wip_last 231 | 232 | dbg "SUCCESS" 233 | } 234 | 235 | do_info () { 236 | local branch=$1 237 | 238 | die "info not implemented" 239 | } 240 | 241 | do_log () { 242 | local work_branch=$1 243 | [ -z $branch ] && work_branch=$(get_work_branch) 244 | local wip_branch="$WIP_PREFIX$work_branch" 245 | 246 | local log_cmd="log" 247 | local graph="" 248 | local pretty="" 249 | local stat="" 250 | while [ -n "$1" ] 251 | do 252 | case "$1" in 253 | -p|--pretty) 254 | graph="--graph" 255 | pretty="--pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset' --abbrev-commit --date=relative" 256 | ;; 257 | -s|--stat) 258 | stat="--stat" 259 | ;; 260 | -r|--reflog) 261 | log_cmd="reflog" 262 | ;; 263 | *) 264 | break 265 | ;; 266 | esac 267 | shift 268 | done 269 | 270 | if [ $log_cmd = reflog ] 271 | then 272 | echo git reflog $stat $pretty $wip_branch | sh 273 | return $? 274 | fi 275 | 276 | if ! work_last=$(git rev-parse --verify $work_branch) 277 | then 278 | die "'$work_branch' branch has no commits." 279 | fi 280 | 281 | dbg work_last=$work_last 282 | 283 | if ! wip_last=$(git rev-parse --quiet --verify $wip_branch) 284 | then 285 | die "'$work_branch' branch has no commits." 286 | fi 287 | 288 | dbg wip_last=$wip_last 289 | 290 | local base=$(git merge-base $wip_last $work_last) 291 | 292 | dbg base=$base 293 | 294 | local stop=$base 295 | if git cat-file commit $base | grep -q '^parent' ; then 296 | stop="$base~1" 297 | fi 298 | 299 | dbg stop=$stop 300 | 301 | echo git log $graph $stat $pretty "$@" $wip_last $work_last "^$stop" | sh 302 | } 303 | 304 | do_delete () { 305 | local branch=$1 306 | 307 | die "delete not implemented" 308 | } 309 | 310 | do_help () { 311 | local rc=$1 312 | 313 | cat <