├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── _fiddle_seq_editor ├── appveyor.yml ├── git-fiddle ├── man1 └── git-fiddle.1 └── tests ├── fiddle.bats └── support.bash /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw[a-z] 2 | *.un~ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | - osx 4 | 5 | before_install: 6 | - | 7 | if [[ "$TRAVIS_OS_NAME" == osx ]]; then 8 | brew install bats 9 | else 10 | sudo add-apt-repository ppa:duggan/bats --yes 11 | sudo apt-get update -qq 12 | sudo apt-get install -qq -y bats 13 | fi 14 | 15 | script: 16 | - bats -p tests/ 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Felix Schlitter 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT: test docs 2 | 3 | SHELL=/bin/bash 4 | 5 | test: 6 | bats -p tests 7 | 8 | docs: 9 | source ./git-fiddle;\ 10 | awk '\ 11 | BEGIN { x = 0; f = 0; };\ 12 | FNR == 1 { f++; };\ 13 | f == 1 && help_text == "" { help_text = $$0; next };\ 14 | f == 1 { help_text = help_text "\n" $$0; next };\ 15 | f == 2 && /\`\`\`usage/ { x = 1; print; next };\ 16 | f == 2 && x == 0 { print; next; };\ 17 | f == 2 && /\`\`\`/ && x == 1 { x = 0; print help_text; print };\ 18 | ' <(\ 19 | echo "\$$ git fiddle -h";\ 20 | echo "$$(derive_help ./git-fiddle)";\ 21 | ) README.md > README.md.tmp;\ 22 | mv README.md{.tmp,};\ 23 | 24 | watch: 25 | fswatch -x git-fiddle _fiddle_seq_editor tests/*.bats tests/*.bash 2>/dev/null | \ 26 | while read -r f attr; do \ 27 | if ! [ -z "$$f" ] && [[ "$$attr" =~ "Updated" ]]; then \ 28 | echo "file changed: $$f ($$attr)"; \ 29 | make test; \ 30 | fi; \ 31 | done 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # git-fiddle (1) [![OSX/Linux Build Status](https://travis-ci.org/felixSchl/git-fiddle.svg?branch=master)](https://travis-ci.org/felixSchl/git-fiddle) [![Windows Build status](https://ci.appveyor.com/api/projects/status/vncu5ft2xji5sq78/branch/master?svg=true)](https://ci.appveyor.com/project/felixSchl/git-fiddle/branch/master) 2 | 3 | `git-fiddle` is a wrapper around `git-rebase(1)` that allows editing of commit 4 | information straight from the `git-rebase` editor, such as the author date, 5 | author name and commit message. This makes it trivial to edit a whole range 6 | of commits, shift them through time or alter ownership. 7 | 8 | ## Installation 9 | 10 | Simply ensure `git-fiddle` is in your `$PATH`. The `git fiddle` alias will 11 | become available automatically. 12 | 13 | ## Usage 14 | 15 | Usage is almost identical to that of `git-rebase`. 16 | 17 | ```usage 18 | $ git fiddle -h 19 | git-fiddle - edit commit meta information during an *interactive* rebase. 20 | 21 | `git-fiddle(1)' is a lightweight wrapper around `git-rebase(1)' that 22 | annotates each commit with its *author* date, the author name, as well 23 | as the commit message. Changes to any of these will then be applied 24 | using an 'exec' script during the git-rebase sequence. 25 | 26 | Usage: 27 | git fiddle [options] [args...] 28 | 29 | Options: 30 | --[no-]fiddle 31 | Turn ON/OFF fiddeling. Useful to turn off all options and selectively 32 | enable some. 33 | --[no-]fiddle-author 34 | Do (not) edit author names. Note that the author *can* still be edited, 35 | but it is not pre-populated. [git config: fiddle.author] 36 | --[no-]fiddle-author-email 37 | Do not pre-populate the author's email. Refer to --[no-]fiddle-author. 38 | [git config: fiddle.author.email] 39 | --[no-]fiddle-author-date 40 | Do (not) edit author dates. Note that the author date *can* still be 41 | edited, but it is not pre-populated. [git config: fiddle.author.date] 42 | --[no-]fiddle-subject 43 | Do (not) commit subject lines. Note that the commit message *can* still 44 | be edited, but it is not pre-populated. [git config: fiddle.subject] 45 | --[no-]fiddle-body 46 | Do (not) the commit body. Note that the commit message *CANNOT* be edit 47 | if this option is turned OFF and might case `git-rebase` errors. 48 | [git config: fiddle.body] 49 | [args...] These arguments are passed verbatim to git-rebase. 50 | ``` 51 | 52 | ## License 53 | 54 | **git-fiddle** is released under the **MIT LICENSE**. 55 | See file `LICENSE` for a more detailed description of its terms. 56 | -------------------------------------------------------------------------------- /_fiddle_seq_editor: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Enhace the git-rebase(1) todo file with additional information about each 4 | # commit, such as the author date, the author name and e-mail, as well as the 5 | # commit message. The user then has the chance to edit any of this information 6 | # when 'GIT_SEQUENCE_EDITOR' is invoked on the decorated git-rebase(1) todo 7 | # file. Upon successful execution of 'GIT_SEQUENCE_EDITOR', parse the user's 8 | # changes and prepare a 'git commit --amend' 'exec' step to be called after each 9 | # 'pick'ed commit. 10 | 11 | GIT_TODO=$1 12 | GIT_FIDDLE_TODO=${1}.fiddle 13 | GIT_FIDDLE_WORK=${1}.fiddle.work 14 | 15 | trap '{ 16 | exit_code=$? 17 | rm -f "$GIT_FIDDLE_TODO" || : 18 | exit $exit_code 19 | }' EXIT 20 | 21 | FIDDLE_MESSAGE="$(cat <<-'EOF' 22 | # 23 | # GIT-FIDDLE(1) HELP: 24 | # ------------------- 25 | # 26 | # The format employed by git-fiddle is: 27 | # 28 | # [] [@ ] [> ] 29 | # [] 30 | # 31 | # where '' needs to be 'fiddle' for anything to happen. 32 | EOF 33 | )" 34 | 35 | function trim { 36 | echo "$1" | sed -e ' 37 | s/^[[:space:]]*// 38 | s/[[:space:]]*$// 39 | ' 40 | } 41 | 42 | function create_git_pretty_format { 43 | local format= 44 | 45 | # --[no-]fiddle-author 46 | if ! [ "$GIT_FIDDLE_AUTHOR" = false ]; then 47 | format="$(trim "$format %an")" 48 | if ! [ "$GIT_FIDDLE_AUTHOR_EMAIL" = false ]; then 49 | format="$format <%ae>" 50 | fi 51 | fi 52 | 53 | # --[no-]fiddle-date 54 | if ! [ "$GIT_FIDDLE_AUTHOR_DATE" = false ]; then 55 | format="$(trim "$format @ %ad")" 56 | fi 57 | 58 | # --[no-]fiddle-subject 59 | # note: always show this, only apply if 'GIT_FIDDLE_SUBJECT' 60 | format="$(trim "$format> %s")" 61 | 62 | # --[no-]fiddle-body 63 | if ! [ "$GIT_FIDDLE_BODY" = false ]; then 64 | if [ "$GIT_FIDDLE_SUBJECT" = false ]; then 65 | format="$(trim "$format"$'%s\n%b')" 66 | else 67 | format="$(trim "$format"$'\n%b')" 68 | fi 69 | fi 70 | 71 | echo "$format" 72 | } 73 | 74 | function prepare { 75 | 76 | if [ "$GIT_FIDDLE_SUBJECT" = false ]; then 77 | echo '# git-fiddle: subject lines are *read-only*' 78 | echo '' 79 | fi > "$GIT_FIDDLE_TODO" 80 | 81 | local -r git_pretty_format="--format=$(create_git_pretty_format)" 82 | while read -r line; do 83 | if [ -z "$line" ] || [[ $line =~ ^[[:space:]]?# ]]; then 84 | printf "%s\n" "$line" 85 | else 86 | read -r action sha rest <<< "$line" 87 | if [[ "$action" == pick ]]; then 88 | action=fiddle 89 | fi 90 | printf "%s %s %s\n" "$action" "$sha" \ 91 | "$(git --no-pager show -s "$sha" "$git_pretty_format" | \ 92 | git stripspace)" 93 | fi 94 | done < "$GIT_TODO" >> "$GIT_FIDDLE_TODO" 95 | 96 | echo "$FIDDLE_MESSAGE" >> "$GIT_FIDDLE_TODO" 97 | } 98 | 99 | function fiddle_sequence_editor { 100 | if [ -z "$GIT_FIDDLE_SEQUENCE_EDITOR" ]; then 101 | GIT_FIDDLE_SEQUENCE_EDITOR="$(git config sequence.editor)" 102 | if [ -z "$GIT_FIDDLE_SEQUENCE_EDITOR" ]; then 103 | GIT_FIDDLE_SEQUENCE_EDITOR="$(git var GIT_EDITOR)" || return $? 104 | fi 105 | fi 106 | eval "$GIT_FIDDLE_SEQUENCE_EDITOR" "$GIT_FIDDLE_TODO" 107 | } 108 | 109 | function esc { echo "$1" | sed -e "s/'/'\"'\"$'/g"; } 110 | 111 | function emit { 112 | local -r action="$1" 113 | local -r sha="$2" 114 | local -r author="$3" 115 | local -r date="$4" 116 | local subject="$5" 117 | local message="$6" 118 | local has_message=false 119 | local -r message_file=${GIT_FIDDLE_WORK}/${sha}.message 120 | 121 | if [[ "$action" == fiddle ]]; then 122 | echo "pick $sha" 123 | 124 | if [ -z "$message" ]; then 125 | if [ -n "$subject" ] && ! [ "$GIT_FIDDLE_SUBJECT" = false ]; then 126 | if [ "$GIT_FIDDLE_BODY" = false ]; then 127 | has_message=true 128 | echo "$subject" 129 | echo 130 | git --no-pager show -s "$sha" --format=$'%b' 131 | else 132 | has_message=true 133 | echo "$subject" 134 | fi 135 | fi 136 | else 137 | if [ -n "$subject" ] && ! [ "$GIT_FIDDLE_SUBJECT" = false ]; then 138 | has_message=true 139 | echo "$subject" 140 | echo 141 | printf "$message" 142 | fi 143 | fi > "$message_file" 144 | 145 | local c= 146 | if [ -n "$date" ]; then 147 | c="GIT_AUTHOR_DATE='$date'" 148 | fi 149 | 150 | c="$c git commit --amend --no-edit" 151 | 152 | if [ -n "$date" ]; then 153 | c="$c --date='$(esc "$date")'" 154 | fi 155 | 156 | if [ -n "$author" ]; then 157 | c="$c --author='$(esc "$author")'" 158 | fi 159 | 160 | if [ "$has_message" = true ]; then 161 | c="$c --file='$message_file'" 162 | fi 163 | 164 | echo "exec bash -c \"$(echo "$c" | sed \ 165 | -e 's/\\/\\\\/g' \ 166 | -e 's/"/\\"/g' \ 167 | -e 's/`/\\`/g' \ 168 | -e ':a;N;$!ba;s/\n/\\n/g' 169 | )\"" 170 | else 171 | echo "$action $sha" 172 | fi 173 | } 174 | 175 | function apply { 176 | set -eo pipefail 177 | 178 | local cur_sha= 179 | local cur_action= 180 | local scanning_body=false 181 | local _author _date _subject _message 182 | 183 | while read -r line; do 184 | read -r action sha rest <<< "$line" 185 | if echo "$action" \ 186 | | grep -E -q '^(fiddle|pick|p|drop|d|reword|r|edit|e|squash|s|fixup|f)$' 187 | then 188 | if [[ "$sha" =~ ^[0-9a-z]{6,}$ ]]; then 189 | 190 | # flush the current scan 191 | if $scanning_body; then 192 | emit "$cur_action" "$cur_sha" "$_author" "$_date" "$_subject" "$_message" 193 | fi 194 | 195 | # reset state 196 | scanning_body=false 197 | _author= 198 | _date= 199 | _subject= 200 | _message= 201 | declare -a output 202 | 203 | # begin a fresh scan for 'fiddle' actions 204 | cur_sha="$sha" 205 | cur_action="$action" 206 | OLD_IFS="$IFS" 207 | IFS=$'\n' output=($(awk -v FS='' '{ 208 | # constants 209 | FALSE = 0 210 | TRUE = 1 211 | STATE_AUTHOR = 1 212 | STATE_DATE = 2 213 | STATE_SUBJECT = 3 214 | 215 | # intermediate/output state 216 | author = "" 217 | date = "" 218 | subject = "" 219 | in_parens = FALSE 220 | in_email_angles = FALSE 221 | state = STATE_AUTHOR 222 | 223 | # [author []] [@ ] [>] 224 | # | STATE_AUTHOR | | STATE_DATE | STATE_SUBJECT 225 | 226 | for (i = 1; i <= NF; i++) { 227 | # process state transitions 228 | if ($i == "@" && in_email_angles == FALSE && state < STATE_DATE) { 229 | state = STATE_DATE 230 | } else if ($i == ">" && in_email_angles == FALSE && state < STATE_SUBJECT) { 231 | state = STATE_SUBJECT 232 | } else if (state == STATE_AUTHOR) { 233 | if ($i == "<") { 234 | in_email_angles = TRUE 235 | } else if ($i == ">") { 236 | in_email_angles = FALSE 237 | } 238 | author = author $i 239 | } else if (state == STATE_DATE) { 240 | date = date $i 241 | } else if (state == STATE_SUBJECT) { 242 | subject = subject $i 243 | } 244 | } 245 | 246 | # avoid field collapsing in bash, by printing a blank 247 | # which is not present in $IFS 248 | if (length(author) == 0) { author = " "; } 249 | if (length(date) == 0) { date = " "; } 250 | if (length(subject) == 0) { subject = " "; } 251 | 252 | print author 253 | print date 254 | print subject 255 | }' <<< "$(trim "$rest")")) 256 | IFS="$OLD_IFS" 257 | 258 | _author="$(trim "${output[0]}")" 259 | _date="$(trim "${output[1]}")" 260 | _subject="$(trim "${output[2]}")" 261 | 262 | scanning_body=true 263 | continue 264 | fi 265 | fi 266 | 267 | if $scanning_body; then 268 | _message="$_message\n$line" 269 | else 270 | echo "$line" # not our issue! let git-rebase fail 271 | fi 272 | done <<< "$(git stripspace --strip-comments < "$GIT_FIDDLE_TODO")" 273 | 274 | # flush the current scan 275 | if $scanning_body; then 276 | emit "$cur_action" "$cur_sha" "$_author" "$_date" "$_subject" "$_message" 277 | fi 278 | } 279 | 280 | # kick off 281 | rm -rf "$GIT_FIDDLE_WORK" && \ 282 | mkdir -p "$GIT_FIDDLE_WORK" && \ 283 | prepare && \ 284 | fiddle_sequence_editor && \ 285 | apply > "$GIT_TODO" || exit $?; 286 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - MSYSTEM: MINGW64 4 | PATH: C:\msys64\usr\bin;C:\msys64\mingw64\bin;C:\Windows\System32;C:\Windows;%PATH% 5 | - MSYSTEM: MINGW32 6 | PATH: C:\msys64\usr\bin;C:\msys64\mingw32\bin;C:\Windows\System32;C:\Windows;%PATH% 7 | 8 | install: 9 | - bash -c 'curl -L https://github.com/sstephenson/bats/archive/v0.4.0.tar.gz | tar -xvz' 10 | 11 | build_script: 12 | - bash -c 'PATH="./bats-0.4.0/bin:./bats-0.4.0/libexec:$PATH" bats tests/fiddle.bats' 13 | -------------------------------------------------------------------------------- /git-fiddle: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # git-fiddle - edit commit meta information during an *interactive* rebase. 4 | # 5 | # `git-fiddle(1)' is a lightweight wrapper around `git-rebase(1)' that 6 | # annotates each commit with its *author* date, the author name, as well 7 | # as the commit message. Changes to any of these will then be applied 8 | # using an 'exec' script during the git-rebase sequence. 9 | # 10 | # Usage: 11 | # git fiddle [options] [args...] 12 | # 13 | # Options: 14 | # --[no-]fiddle 15 | # Turn ON/OFF fiddeling. Useful to turn off all options and selectively 16 | # enable some. 17 | # --[no-]fiddle-author 18 | # Do (not) edit author names. Note that the author *can* still be edited, 19 | # but it is not pre-populated. [git config: fiddle.author] 20 | # --[no-]fiddle-author-email 21 | # Do not pre-populate the author's email. Refer to --[no-]fiddle-author. 22 | # [git config: fiddle.author.email] 23 | # --[no-]fiddle-author-date 24 | # Do (not) edit author dates. Note that the author date *can* still be 25 | # edited, but it is not pre-populated. [git config: fiddle.author.date] 26 | # --[no-]fiddle-subject 27 | # Do (not) commit subject lines. Note that the commit message *can* still 28 | # be edited, but it is not pre-populated. [git config: fiddle.subject] 29 | # --[no-]fiddle-body 30 | # Do (not) the commit body. Note that the commit message *CANNOT* be edit 31 | # if this option is turned OFF and might case `git-rebase` errors. 32 | # [git config: fiddle.body] 33 | # [args...] These arguments are passed verbatim to git-rebase. 34 | 35 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 36 | ORIGINAL_GIT_SEQUENCE_EDITOR="$GIT_SEQUENCE_EDITOR" 37 | 38 | # derive the help text from the first comment in a shell script 39 | function derive_help { 40 | awk ' 41 | f && !NF { exit } 42 | /^#/ && NR>2 { f=1; sub("^# ?", "", $0); print $0 } 43 | ' < "$1" 44 | } 45 | 46 | # print the help text, derived from this file 47 | function help { derive_help "${BASH_SOURCE[0]}"; } 48 | 49 | # stop here, if 'source'd 50 | if [ "$0" = "${BASH_SOURCE[0]}" ]; then 51 | 52 | # only capture '-h|--help' if it's first arg. let git-rebase handle it 53 | # otherwise. 54 | case "${1---help}" in 55 | -h|--help) help; exit 0;; 56 | esac 57 | 58 | git_fiddle_author_date= 59 | git_fiddle_author= 60 | git_fiddle_author_email= 61 | git_fiddle_subject= 62 | git_fiddle_body= 63 | 64 | # check for a given key in `git-config`. 65 | # assigns the result to the given var name "$1" 66 | function check_config { 67 | local -r var="$1" 68 | local -r key="$2" 69 | if eval "[ -z \"\$$var\" ]"; then 70 | eval "$var=\"$(git config --get fiddle."$key")\"" 71 | if eval "[ -z \"\$$var\" ]"; then 72 | eval "$var=true" 73 | fi 74 | fi 75 | } 76 | 77 | check_config git_fiddle_author author 78 | check_config git_fiddle_author_date author.date 79 | check_config git_fiddle_author_email author.email 80 | check_config git_fiddle_subject subject 81 | check_config git_fiddle_body body 82 | 83 | # strip args to `git-fiddle' from argv, so git-rebase won't reject them. 84 | declare -a git_rebase_args 85 | while [ $# -gt 0 ]; do 86 | case "$1" in 87 | --no-fiddle) 88 | git_fiddle_author_date=false 89 | git_fiddle_author=false 90 | git_fiddle_author_email=false 91 | git_fiddle_subject=false 92 | git_fiddle_body=false 93 | ;; 94 | --fiddle-author-date) git_fiddle_author_date=true; ;; 95 | --no-fiddle-author-date) git_fiddle_author_date=false; ;; 96 | --fiddle-author) git_fiddle_author=true; ;; 97 | --no-fiddle-author) git_fiddle_author=false; ;; 98 | --fiddle-author-email) git_fiddle_author_email=true; ;; 99 | --no-fiddle-author-email) git_fiddle_author_email=false; ;; 100 | --fiddle-subject) git_fiddle_subject=true; ;; 101 | --no-fiddle-subject) git_fiddle_subject=false; ;; 102 | --fiddle-body) git_fiddle_body=true; ;; 103 | --no-fiddle-body) git_fiddle_body=false; ;; 104 | *) git_rebase_args+=("$1"); ;; 105 | esac 106 | shift 107 | done 108 | 109 | # invoke `git-rebase' using a custom editor, effectively wrapping the user's 110 | # actual editor choice, which will be invoked from the `git-fiddle` editor 111 | # itself. 112 | GIT_SEQUENCE_EDITOR="'${SCRIPT_DIR}/_fiddle_seq_editor'" \ 113 | GIT_FIDDLE_AUTHOR_DATE="$git_fiddle_author_date" \ 114 | GIT_FIDDLE_AUTHOR="$git_fiddle_author" \ 115 | GIT_FIDDLE_AUTHOR_EMAIL="$git_fiddle_author_email" \ 116 | GIT_FIDDLE_SUBJECT="$git_fiddle_subject" \ 117 | GIT_FIDDLE_BODY="$git_fiddle_body" \ 118 | GIT_FIDDLE_SEQUENCE_EDITOR="$ORIGINAL_GIT_SEQUENCE_EDITOR" \ 119 | git rebase -i "${git_rebase_args[@]}" 120 | 121 | fi 122 | -------------------------------------------------------------------------------- /man1/git-fiddle.1: -------------------------------------------------------------------------------- 1 | .\" Manpage for git\-fiddle. 2 | .\" Contact felixschlitter@gmail.com to correct errors or typos. 3 | .TH man 1 "04 Sep 2016" "1.0" "git-fiddle man page" 4 | .SH NAME 5 | git\-fiddle \- Edit commit messages, authors and timestamps during git\-rebase 6 | .SH SYNOPSIS 7 | .B fiddle 8 | [\fBoptions\fR] [\fBargs...\fR] 9 | .SH OPTIONS 10 | 11 | .TP 12 | .BR \-\-[no\-]fiddle 13 | Turn ON/OFF fiddeling. Useful to turn off all options and selectively 14 | 15 | .TP 16 | .BR \-\-[no\-]fiddle\-author 17 | Do (not) edit author names. Note that the author *can* still be edited, 18 | .br 19 | but it is not pre-populated. [git config: \fIfiddle.author\fR] 20 | 21 | .TP 22 | .BR \-\-[no\-]fiddle\-author\-email 23 | Do not pre-populate the author's email. Refer to --[no-]fiddle-author. 24 | .br 25 | [git config: \fIfiddle.author.email\fR] 26 | 27 | .TP 28 | .BR \-\-[no\-]fiddle\-author\-date 29 | Do (not) edit author dates. Note that the author date *can* still be 30 | .br 31 | edited, but it is not pre-populated. [git config: \fIfiddle.author.date\fR] 32 | 33 | .TP 34 | .BR \-\-[no\-]fiddle\-subject 35 | Do (not) commit subject lines. Note that the commit message *can* still 36 | .br 37 | be edited, but it is not pre-populated. [git config: \fIfiddle.subject\fR] 38 | 39 | .TP 40 | .BR \-\-[no\-]fiddle\-body 41 | Do (not) the commit body. Note that the commit message *CANNOT* be edit 42 | .br 43 | if this option is turned OFF and might case `git-rebase` errors. 44 | .br 45 | [git config: \fIfiddle.body\fR] 46 | 47 | .TP 48 | .BR [args...] 49 | These arguments are passed verbatim to git-rebase. 50 | .SH BUGS 51 | No known bugs. 52 | .SH AUTHOR 53 | Felix Schlitter 54 | -------------------------------------------------------------------------------- /tests/fiddle.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load support 4 | 5 | function init_repo () { 6 | git init 7 | git config user.name 'Foo Bar' 8 | git config user.email 'foo@bar.com' 9 | touch .gitgnore && git add -A && git commit -m 'Initial commit' 10 | } 11 | 12 | @test 'fiddle: change commit messages' { 13 | init_repo &> /dev/null 14 | 15 | touch A && git add -A && git commit -m "$(cat <<-'EOF' 16 | Commit 'A' 17 | 18 | This is the first commit. 19 | In a series of commits 20 | EOF 21 | )" &> /dev/null 22 | 23 | GIT_SEQUENCE_EDITOR="$(mk_script <<-'EOF' 24 | #!/bin/sh 25 | sed -i.bak -e "s/Commit 'A'/Commit \`'B'/g" "$1" 26 | EOF 27 | )" run git_fiddle HEAD~ 28 | [ $status -eq 0 ] 29 | 30 | run git show -s HEAD $'--format=%s\n\n%b' 31 | [ $status -eq 0 ] 32 | 33 | expected="$( 34 | echo "Commit \`'B'"; 35 | cat <<-'EOF' 36 | 37 | This is the first commit. 38 | In a series of commits 39 | EOF 40 | )" 41 | 42 | [[ $output == "$expected" ]] 43 | } 44 | 45 | @test 'fiddle: no-opt if action isnt "fiddle"' { 46 | init_repo &> /dev/null 47 | 48 | touch A && git add -A && git commit -m "$(cat <<-'EOF' 49 | Commit A 50 | 51 | This is the first commit. 52 | In a series of commits 53 | EOF 54 | )" &> /dev/null 55 | 56 | GIT_SEQUENCE_EDITOR="$(mk_script <<-'EOF' 57 | #!/bin/sh 58 | sed -i.bak -e 's/^fiddle /pick /g' "$1" 59 | EOF 60 | )" run git_fiddle HEAD~ 61 | [ $status -eq 0 ] 62 | 63 | run git show -s HEAD $'--format=%s\n\n%b' 64 | [ $status -eq 0 ] 65 | 66 | expected="$( 67 | cat <<-'EOF' 68 | Commit A 69 | 70 | This is the first commit. 71 | In a series of commits 72 | EOF 73 | )" 74 | 75 | [[ $output == "$expected" ]] 76 | } 77 | 78 | @test 'fiddle: parse commit messages that looks similar to action' { 79 | init_repo &> /dev/null 80 | 81 | touch A && git add -A && git commit -m "$(cat <<-'EOF' 82 | Commit A 83 | 84 | use 12fbac 85 | EOF 86 | )" &> /dev/null 87 | 88 | GIT_SEQUENCE_EDITOR="$(mk_script <<-'EOF' 89 | #!/bin/sh 90 | sed -i.bak -e 's/Commit A/Commit B/g' "$1" 91 | EOF 92 | )" run git_fiddle HEAD~ 93 | [ $status -eq 0 ] 94 | 95 | run git show -s HEAD $'--format=%s\n\n%b' 96 | [ $status -eq 0 ] 97 | [[ $output == "$(cat <<-'EOF' 98 | Commit B 99 | 100 | use 12fbac 101 | EOF 102 | )" ]] 103 | } 104 | 105 | @test 'fiddle: abort editing' { 106 | init_repo &> /dev/null 107 | 108 | touch A && git add -A && git commit -m 'Commit A' &> /dev/null 109 | 110 | GIT_SEQUENCE_EDITOR="$(mk_script <<-'EOF' 111 | #!/bin/sh 112 | sed -i.bak -e 's/Commit A/Commit B/g' "$1" 113 | echo "$1" > "$PWD/todo-location" 114 | exit 1 # abort 115 | EOF 116 | )" run git_fiddle HEAD~ 117 | [ $status -eq 1 ] 118 | 119 | # directory shouldn't exist (rebase aborted) 120 | [ ! -f "$(cat todo-location)" ] 121 | 122 | run git show -s HEAD $'--format=%s' 123 | [ $status -eq 0 ] 124 | [[ $output == $'Commit A' ]] 125 | } 126 | 127 | @test 'fiddle: change multiple commit messages' { 128 | init_repo &> /dev/null 129 | 130 | touch A && git add -A && git commit -m "$(cat <<-'EOF' 131 | Commit A 132 | 133 | This is the first commit. 134 | In a series of commits 135 | EOF 136 | )" &> /dev/null 137 | 138 | touch B && git add -A && git commit -m "$(cat <<-'EOF' 139 | Commit B 140 | 141 | This is the second commit. 142 | EOF 143 | )" &> /dev/null 144 | 145 | GIT_SEQUENCE_EDITOR="$(mk_script <<-'EOF' 146 | #!/bin/sh 147 | sed -i.bak \ 148 | -e 's/Commit A/Commit Z/g' \ 149 | -e 's/Commit B/Commit Y/g' \ 150 | "$1" 151 | EOF 152 | )" run git_fiddle HEAD~2 153 | [ $status -eq 0 ] 154 | 155 | run git show -s HEAD~ $'--format=%s\n\n%b' 156 | [ $status -eq 0 ] 157 | [[ $output == "$(cat <<-'EOF' 158 | Commit Z 159 | 160 | This is the first commit. 161 | In a series of commits 162 | EOF 163 | )" ]] 164 | 165 | run git show -s HEAD $'--format=%s\n\n%b' 166 | [ $status -eq 0 ] 167 | [[ $output == "$(cat <<-'EOF' 168 | Commit Y 169 | 170 | This is the second commit. 171 | EOF 172 | )" ]] 173 | } 174 | 175 | @test 'fiddle: git config fiddle.subject (off)' { 176 | init_repo &> /dev/null 177 | 178 | git config fiddle.subject false 179 | 180 | touch A && git add -A && git commit -m 'Commit A' &> /dev/null 181 | 182 | GIT_SEQUENCE_EDITOR="$(mk_script <<-EOF 183 | #!/bin/sh 184 | sed -i.bak -e 's/Commit A/Commit B/g' "\$1" 185 | EOF 186 | )" run git_fiddle HEAD~ 187 | [ $status -eq 0 ] 188 | 189 | run git show -s HEAD --format='%s' 190 | 191 | [ $status -eq 0 ] 192 | [[ $output == 'Commit A' ]] 193 | } 194 | 195 | @test 'fiddle: --no-fiddle-subject' { 196 | init_repo &> /dev/null 197 | 198 | git config fiddle.subject true # should be overwritten by flag 199 | 200 | touch A && git add -A && git commit -m 'Commit A' &> /dev/null 201 | 202 | GIT_SEQUENCE_EDITOR="$(mk_script <<-EOF 203 | #!/bin/sh 204 | sed -i.bak -e 's/Commit A/Commit B/g' "\$1" 205 | EOF 206 | )" run git_fiddle --no-fiddle-subject HEAD~ 207 | [ $status -eq 0 ] 208 | 209 | run git show -s HEAD --format='%s' 210 | [ $status -eq 0 ] 211 | [[ $output == 'Commit A' ]] 212 | } 213 | 214 | 215 | @test 'fiddle: change commit author' { 216 | init_repo &> /dev/null 217 | 218 | touch A && git add -A && git commit -m 'Commit A' &> /dev/null 219 | 220 | GIT_SEQUENCE_EDITOR="$(mk_script <<-'EOF' 221 | #!/bin/sh 222 | sed -i.bak -e 's/Foo Bar /Quz Baz /g' "$1" 223 | EOF 224 | )" run git_fiddle HEAD~ 225 | [ $status -eq 0 ] 226 | 227 | run git show -s HEAD --format='%an <%ae>' 228 | [ $status -eq 0 ] 229 | [[ "$output" == "Quz Baz " ]] 230 | } 231 | 232 | @test 'fiddle: git config fiddle.author.email (off)' { 233 | init_repo &> /dev/null 234 | 235 | git config fiddle.author.email false 236 | 237 | touch A && git add -A && git commit -m 'Commit A' &> /dev/null 238 | 239 | GIT_SEQUENCE_EDITOR="$(mk_script <<-'EOF' 240 | #!/bin/sh 241 | sed -i.bak -e 's///g' "$1" 242 | EOF 243 | )" run git_fiddle HEAD~ 244 | [ $status -eq 0 ] 245 | 246 | run git show -s HEAD --format='<%ae>' 247 | [ $status -eq 0 ] 248 | 249 | # it shouldn've have changed, since the string wasn't found 250 | [[ "$output" == "" ]] 251 | } 252 | 253 | @test 'fiddle: --no-fiddle-author-email' { 254 | init_repo &> /dev/null 255 | 256 | git config fiddle.author.email true # should be overwritten by flag 257 | 258 | touch A && git add -A && git commit -m 'Commit A' &> /dev/null 259 | 260 | GIT_SEQUENCE_EDITOR="$(mk_script <<-'EOF' 261 | #!/bin/sh 262 | sed -i.bak -e 's///g' "$1" 263 | EOF 264 | )" run git_fiddle --no-fiddle-author-email HEAD~ 265 | [ $status -eq 0 ] 266 | 267 | run git show -s HEAD --format='<%ae>' 268 | [ $status -eq 0 ] 269 | 270 | # it shouldn've have changed, since the string wasn't found 271 | [[ "$output" == "" ]] 272 | } 273 | 274 | @test 'fiddle: git config fiddle.author (off)' { 275 | init_repo &> /dev/null 276 | 277 | git config fiddle.author false 278 | 279 | touch A && git add -A && git commit -m 'Commit A' &> /dev/null 280 | 281 | GIT_SEQUENCE_EDITOR="$(mk_script <<-'EOF' 282 | #!/bin/sh 283 | sed -i.bak -e 's///g' "$1" 284 | EOF 285 | )" run git_fiddle HEAD~ 286 | [ $status -eq 0 ] 287 | 288 | run git show -s HEAD --format='%an' 289 | [ $status -eq 0 ] 290 | 291 | # it shouldn've have changed, since the string wasn't found 292 | [[ "$output" == "Foo Bar" ]] 293 | } 294 | 295 | @test 'fiddle: --no-fiddle-author' { 296 | init_repo &> /dev/null 297 | 298 | git config fiddle.author true # should be overwritten by flag 299 | 300 | touch A && git add -A && git commit -m 'Commit A' &> /dev/null 301 | 302 | GIT_SEQUENCE_EDITOR="$(mk_script <<-'EOF' 303 | #!/bin/sh 304 | sed -i.bak -e 's///g' "$1" 305 | EOF 306 | )" run git_fiddle --no-fiddle-author HEAD~ 307 | [ $status -eq 0 ] 308 | 309 | run git show -s HEAD --format='%an' 310 | [ $status -eq 0 ] 311 | 312 | # it shouldn've have changed, since the string wasn't found 313 | [[ "$output" == "Foo Bar" ]] 314 | } 315 | 316 | @test 'fiddle: should escape quotes' { 317 | init_repo &> /dev/null 318 | 319 | touch A && git add -A && git commit -m 'Commit A' &> /dev/null 320 | 321 | GIT_SEQUENCE_EDITOR="$(mk_script <<-'EOF' 322 | #!/bin/sh 323 | sed -i.bak -e "s/Foo Bar /' Quz \"Baz \\"\\\\"\" /g" "$1" 324 | EOF 325 | )" run git_fiddle HEAD~ 326 | [ $status -eq 0 ] 327 | 328 | run git show -s HEAD --format='%an <%ae>' 329 | [ $status -eq 0 ] 330 | [[ "$output" == 'Quz "Baz ' ]] 331 | } 332 | 333 | @test 'fiddle: change commit author date' { 334 | init_repo &> /dev/null 335 | 336 | local -r before='Wed Aug 17 15:47:08 2016 +1200' 337 | local -r expected='Sun Dec 24 12:00:08 1989 +1200' 338 | 339 | touch A && git add -A && { 340 | GIT_AUTHOR_DATE="$before" git commit -m 'Commit A' --date="$before" 341 | } &> /dev/null 342 | 343 | run git show -s HEAD --format='%ad' 344 | [ $status -eq 0 ] 345 | [[ $output == "$before" ]] 346 | 347 | GIT_SEQUENCE_EDITOR="$(mk_script <<-EOF 348 | #!/bin/sh 349 | sed -i.bak -e 's/$before/$expected/g' "\$1" 350 | EOF 351 | )" run git_fiddle HEAD~ 352 | [ $status -eq 0 ] 353 | 354 | run git show -s HEAD --format='%ad' 355 | [ $status -eq 0 ] 356 | [[ "$output" == "$expected" ]] 357 | } 358 | -------------------------------------------------------------------------------- /tests/support.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | setup () { 4 | set -eo pipefail 5 | export SCRIPT_DIR="$PWD" 6 | export TMP_DIR="$BATS_TMPDIR/git-fiddle-tests" 7 | [ -d "$TMP_DIR" ] && rm -rf "$TMP_DIR" 8 | mkdir -p "$TMP_DIR" 9 | cd "$TMP_DIR" 10 | export GIT_CEILING_DIRECTORY="$TMP_DIR" 11 | } 12 | 13 | teardown () { 14 | set -eo pipefail 15 | [ -d "$TMP_DIR" ] && rm -rf "$TMP_DIR" 16 | } 17 | 18 | # create a temporary directory in a cross-platform way 19 | function x_mktemp () { 20 | prefix=git-fiddle 21 | mktemp -d 2>/dev/null || mktemp -d -t "$prefix" 22 | } 23 | 24 | # create an executable script either from the first argument or from stdin. 25 | function mk_script () { 26 | local -r script="$1" 27 | local -r tmp_dir="$(x_mktemp)" 28 | local -r tmp_editor="$tmp_dir"/editor 29 | if [ -z "$script" ]; then 30 | cat > "$tmp_editor" 31 | else 32 | echo "$1" > "$tmp_editor" 33 | fi 34 | chmod +x "$tmp_editor" 35 | echo "$tmp_editor" 36 | } 37 | 38 | function git_fiddle () { "$SCRIPT_DIR"/git-fiddle "$@"; } 39 | 40 | export git_fiddle 41 | --------------------------------------------------------------------------------