{"thread":{"id":"7027","subject":"Updated version of git-hunk-commit.bash","startedAt":"2007-03-01T02:52:39Z","lastAt":"2007-03-01T02:52:39Z","messageCount":1,"participants":["Johannes Schindelin"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"35913","messageId":"Pine.LNX.4.63.0703010350010.22628@wbgn013.biozentrum.uni-wuerzburg.de","threadId":"7027","inReplyTo":null,"subject":"Updated version of git-hunk-commit.bash","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-03-01T02:52:39Z","receivedAt":"2007-03-01T02:52:39Z","isPatch":false,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\non IRC, \"cehteh\" requested that I send an updated version of the script. \nIt was submitted when a short-lived option of git-diff was used, so the \nfirst publicised version no longer works.\n\nThis has seen a tremendous amount of work by Jason Chu, which made it work \nmore like darcs when called as \"bash git-hunk-commit.bash --darcs\".\n\nIt still relies heavily on bash arrays.\n\nCiao,\nDscho\n\n\n#!/bin/bash\n\n# Copyright (C) 2006 Johannes E. Schindelin\n# Distributed under the same license as git.\n\n# Use this command to commit just a few hunks of the current output\n# of \"git diff\". For your security, it only works when the index matches\n# HEAD.\n\n# ensure that this is a git repository\n. git-sh-setup\n\nTMP_FILE=$GIT_DIR/tmp.$$.txt\n\ntrap \"rm -f $TMP_FILE; exit\" 0 1 2 3 15\n\n# the index must match the HEAD\nindex_check=\"$(git diff --index --name-only HEAD 2>&1)\"\nif [ $? -ne 0 ]; then\n\tindex_check=\"$(git diff --cached --name-only HEAD 2>&1)\"\nfi\nif [ -n \"$index_check\" ]; then\n\techo \"The staging area (AKA index) is already dirty.\"\n\texit 1\nfi\n\n# read the names of all modified files into the array \"modified\"\n\ndeclare -a modified\nfilenr=1\ngit ls-files --modified -z > $TMP_FILE\nwhile read -d $'\\0' file; do\n\tmodified[$filenr]=\"$file\"\n\tfilenr=$(($filenr+1))\ndone < $TMP_FILE\n\nif [ ${#modified[*]} = 0 ]; then\n\techo \"No modified files.\"\n\texit 1\nfi\n\ndeclare -a hunks\n\ndeclare -a darcs_hunk_state\n\n# interactively show the hunks of a file and ask if they should be committed.\n# 1st parameter is the index into the modified file list.\n#\tDarcs mode means that all hunks are presented one after another.\n#\tNormal mode means user can specify hunks interactively.\n\nselect_hunks () {\n\tlocal index=$1\n\tlocal filename=${modified[$index]}\n\tlocal -a diff\n\tlocal -a hunk_start\n\tlocal current_hunks=${hunks[$index]}\n\tlocal lineno\n\tlocal hunkno\n\tlocal action\n\tlocal i\n\tlocal active\n\n\tlineno=1\n\thunkno=0\n\tgit diff \"$filename\" > $TMP_FILE\n\twhile read -r line; do\n\t\tdiff[$lineno]=\"$line\"\n\t\tcase \"$line\" in\n\t\t@@*)\n\t\t\thunk_start[$hunkno]=$lineno\n\t\t\thunkno=$(($hunkno+1))\n\t\t\t;;\n\t\tesac\n\t\tlineno=$(($lineno+1))\n\tdone < $TMP_FILE\n\n\thunk_start[$hunkno]=$lineno\n\n\taction=\"\"\n\twhile [ \"$action\" != commit -a \"$action\" != abort ]; do\n\t\techo\n\t\techo \"Current hunks: ($current_hunks) of $hunkno hunks\"\n\t\techo \"To show (and decide on) a hunk type in the number.\"\n\t\techo \"To commit the current hunks, say 'commit', else 'abort'.\"\n\t\techo\n\t\techo -n \"Your choice? \"\n\t\tread action\n\t\tcase \"$action\" in\n\t\tc) action=commit;;\n\t\tq|a) action=abort;;\n\t\tcommit|abort) ;;\n\t\t[1-9]*)\n\t\t\techo\n\t\t\tfor ((i=${hunk_start[$(($action-1))]}; i<${hunk_start[$action]}; i++)); do\n\t\t\t\techo ${diff[$i]}\n\t\t\tdone | less -FS\n\t\t\tactive=$(echo $current_hunks,$action | tr , '\\n' | sort | uniq -u | tr '\\n' , | sed -e \"s/^,//\" -e \"s/,$//\")\n\t\t\tif [ ${#active} -lt ${#current_hunks} ]; then\n\t\t\t\ti=yes\n\t\t\telse\n\t\t\t\ti=no\n\t\t\tfi\n\t\t\techo\n\t\t\twhile [ -n \"$action\" -a \"$action\" != yes -a \"$action\" != no -a -n \"$action\" ]; do\n\t\t\t\techo -n \"Commit this hunk (default is $i)? \"\n\t\t\t\tread action\n\t\t\t\tcase \"$action\" in\n\t\t\t\ty) action=yes;;\n\t\t\t\tn) action=no;;\n\t\t\t\tesac\n\t\t\tdone\n\t\t\tif [ -n \"$action\" -a $i != \"$action\" ]; then\n\t\t\t\tcurrent_hunks=$active\n\t\t\tfi\n\t\t\t;;\n\t\t*) echo \"Unknown command: $action\";;\n\t\tesac\n\tdone\n\n\tif [ \"$action\" = commit ]; then\n\t\thunks[$index]=$current_hunks\n\tfi\n}\n\ndarcs_select_hunks () {\n\tlocal index=1\n\tlocal filename=${modified[$index]}\n\tlocal -a diff\n\tlocal -a hunk_start\n\tlocal lineno\n\tlocal hunkno\n\tlocal i\n\tlocal current_hunkno=1\n\tlocal current_hunk_state\n\tlocal input=''\n\tlocal done=0\n\n\t# Hunk number over all files\n\tlocal absolute_hunkno=1\n\n\t# Was the input we just had something that would finish recording\n\t# if we hit the end of the patches?\n\tlocal quiter\n\n\tlineno=1\n\thunkno=0\n\tgit diff \"$filename\" > $TMP_FILE\n\twhile read -r line; do\n\t\tdiff[$lineno]=\"$line\"\n\t\tcase \"$line\" in\n\t\t@@*)\n\t\t\thunk_start[$hunkno]=$lineno\n\t\t\thunkno=$(($hunkno+1))\n\t\t\t;;\n\t\tesac\n\t\tlineno=$(($lineno+1))\n\tdone < $TMP_FILE\n\n\thunk_start[$hunkno]=$lineno\n\n\twhile [ $done -eq 0 ]; do\n\t\tquiter=0\n\t\t# Darcs hunk preamble\n\t\techo \"hunk $filename\"\n\t\t# Display hunk\n\t\tfor ((i=${hunk_start[$(($current_hunkno-1))]}; i<${hunk_start[$current_hunkno]}; i++)); do\n\t\t\techo ${diff[$i]}\n\t\tdone\n\t\tcurrent_hunk_state=$(get_hunk_state $index $current_hunkno)\n\t\techo -n \"Shall I record this change? ($absolute_hunkno/?)  [\"\n\t\tcase \"$current_hunk_state\" in\n\t\ty) echo -n Ynwsfqadjkc ;;\n\t\tn) echo -n yNwsfqadjkc ;;\n\t\tw) echo -n ynWsfqadjkc ;;\n\t\tesac\n\t\techo -n \"], or ? for help: \"\n\t\tread -n 1 input\n\t\techo\n\t\tcase \"$input\" in\n\t\tn|y|w) set_hunk_state $index $current_hunkno $input\n\t\t       current_hunkno=$((current_hunkno+1))\n\t\t       absolute_hunkno=$((absolute_hunkno+1))\n\t\t       quiter=1 ;;\n\t\tj) current_hunkno=$((current_hunkno+1))\n\t\t   absolute_hunkno=$((absolute_hunkno+1)) ;;\n\t\tk) current_hunkno=$((current_hunkno-1))\n\t\t   absolute_hunkno=$((absolute_hunkno-1)) ;;\n\t\tq) done=2\n\t\t   continue ;;\n\t\t' ') current_hunkno=$((current_hunkno+1))\n\t\t     absolute_hunkno=$((absolute_hunkno+1))\n\t\t     quiter=1 ;;\n\t\td) done=1\n\t\t   continue ;;\n\t\t?|h) print_darcs_help\n\t\t   continue ;;\n\t\tesac\n\t\tif [ $((current_hunkno+1)) -gt ${#hunk_start[@]} ]; then\n\t\t\tindex=$(($index+1))\n\t\t\tif [ $index -gt ${#modified[@]} ]; then\n\t\t\t\tif [ $quiter -eq 1 ]; then\n\t\t\t\t\tdone=1\n\t\t\t\telse\n\t\t\t\t\tcurrent_hunkno=$((current_hunkno-1))\n\t\t\t\t\tabsolute_hunkno=$((absolute_hunkno-1))\n\t\t\t\t\tindex=$(($index-1))\n\t\t\t\tfi\n\t\t\t\tcontinue\n\t\t\tfi\n\t\t\tfilename=${modified[$index]}\n\n\t\t\tunset hunk_start\n\t\t\tlineno=1\n\t\t\thunkno=0\n\t\t\tgit diff \"$filename\" > $TMP_FILE\n\t\t\twhile read -r line; do\n\t\t\t\tdiff[$lineno]=\"$line\"\n\t\t\t\tcase \"$line\" in\n\t\t\t\t@@*)\n\t\t\t\t\thunk_start[$hunkno]=$lineno\n\t\t\t\t\thunkno=$(($hunkno+1))\n\t\t\t\t\t;;\n\t\t\t\tesac\n\t\t\t\tlineno=$(($lineno+1))\n\t\t\tdone < $TMP_FILE\n\n\t\t\thunk_start[$hunkno]=$lineno\n\n\t\t\tcurrent_hunkno=1\n\t\telif [ $current_hunkno -lt 1 ]; then\n\t\t\tindex=$(($index-1))\n\t\t\tif [ $index -lt 1 ]; then\n\t\t\t\tindex=1\n\t\t\t\tcurrent_hunkno=1\n\t\t\t\tabsolute_hunkno=1\n\t\t\t\tcontinue\n\t\t\tfi\n\t\t\tfilename=${modified[$index]}\n\n\t\t\tunset hunk_start\n\t\t\tlineno=1\n\t\t\thunkno=0\n\t\t\tgit diff \"$filename\" > $TMP_FILE\n\t\t\twhile read -r line; do\n\t\t\t\tdiff[$lineno]=\"$line\"\n\t\t\t\tcase \"$line\" in\n\t\t\t\t@@*)\n\t\t\t\t\thunk_start[$hunkno]=$lineno\n\t\t\t\t\thunkno=$(($hunkno+1))\n\t\t\t\t\t;;\n\t\t\t\tesac\n\t\t\t\tlineno=$(($lineno+1))\n\t\t\tdone < $TMP_FILE\n\n\t\t\thunk_start[$hunkno]=$lineno\n\n\t\t\tcurrent_hunkno=$((hunkno))\n\t\tfi\n\tdone\n\n\tcase $done in\n\t# q -- don't do anything\n\t2) echo \"Commit cancelled.\"\n\t   exit 0 ;;\n\t# d -- commit everything that was a y\n\t1) set_darcs_hunks\n\t   return ;;\n\tesac\n}\n\nset_darcs_hunks () {\n\tlocal i\n\tlocal active\n\n\tfor ((i=1; i<$filenr; i++)); do\n\t\tactive=$(echo ${darcs_hunk_state[$i]} | tr , '\\n' | grep :y$ | sed -e 's/:y$//' | sort | tr '\\n' , | sed -e \"s/^,//\" -e \"s/,$//\")\n\t\thunks[$i]=$active\n\tdone\n}\n\nprint_darcs_help () {\ncat << EOF\nHow to use git-hunk-commit...\ny: record this patch\nn: don't record it\nw: wait and decide later, defaulting to no\n\ns: don't record the rest of the changes to this file\nf: record the rest of the changes to this file\n\nd: record selected patches, skipping all the remaining patches\na: record all the remaining patches\nq: cancel record\n\nj: skip to next patch\nk: back up to previous patch\nc: calculate number of patches\nh or ?: show this help\n\n<Space>: accept the current default (which is capitalized)\nEOF\n}\n\n# Given the index ($1) and hunkno ($2) return the current darcs choice\n\nget_hunk_state () {\n\tlocal index=$1\n\tlocal hunkno=$2\n\tlocal current_darcs_state=${darcs_hunk_state[$index]}\n\tlocal value=$(echo $current_darcs_state | sed -e \"s/.*\\(^\\|,\\)$hunkno:\\([^,]*\\)\\(,\\|$\\).*/\\2/\")\n\n\tif [ \"$value\" = \"$current_darcs_state\" ]; then\n\t\techo w\n\telse\n\t\techo $value\n\tfi\n}\n\n# Given the index ($1), hunkno ($2), and new state ($3) set the entry in darcs_hunk_state\nset_hunk_state () {\n\tlocal index=$1\n\tlocal hunkno=$2\n\tlocal state=$3\n\tlocal current_darcs_state=${darcs_hunk_state[$index]}\n\tlocal newstates=''\n\n\tnewstates=$(echo $current_darcs_state | sed -e \"s/\\(^\\|,\\)$hunkno:[^,]*\\(,\\|$\\)//\")\n\tnewstates=\"$newstates,$hunkno:$state\"\n\tdarcs_hunk_state[$index]=$(echo $newstates | sed -e \"s/^,//\" -e \"s/,$//\")\n}\n\n# Apply the hunks saved in the array hunks for the specified file.\n# This means that the diff is rewritten to skip the unwanted hunks.\n\napply_hunks () {\n\tlocal index=$1\n\tlocal filename=${modified[$index]}\n\tlocal -a current_hunks\n\tlocal lineno\n\tlocal lineno2\n\tlocal linediff\n\tlocal hunkno\n\tlocal i\n\tlocal active\n\n\ti=0\n\techo ${hunks[$index]} | tr , '\\n' > $TMP_FILE\n\twhile read hunkno; do\n\t\tcurrent_hunks[$i]=$hunkno\n\t\ti=$(($i+1))\n\tdone < $TMP_FILE\n\n\tlinediff=0\n\thunkno=0\n\ti=0\n\tactive=true\n\tgit diff \"$filename\" > $TMP_FILE\n\twhile read -r line\n\tdo\n\t\tcase \"$line\" in\n\t\t@@*)\n\t\t\thunkno=$(($hunkno+1))\n\t\t\tif [ $hunkno = \"${current_hunks[$i]}\" ]; then\n\t\t\t\tactive=true\n\t\t\t\ti=$(($i+1))\n\t\t\t\tif [ $linediff -ne 0 ]; then\n\t\t\t\t\tlineno=$(echo \"$line\" | sed \"s/^.*+\\([0-9]*\\)[, ].*$/\\1/\")\n\t\t\t\t\tlineno2=$(($lineno+$linediff))\n\t\t\t\t\tline=\"$(echo \"$line\" | sed \"s/+$lineno/+$lineno2/\")\"\n\t\t\t\tfi\n\t\t\telse\n\t\t\t\tactive=\n\t\t\t\tlineno=$(echo \"$line\" | sed -n \"s/^.*-[0-9]*,\\([0-9]*\\) .*$/\\1/p\")\n\t\t\t\tif [ -z \"$lineno\" ]; then\n\t\t\t\t\tlineno=1\n\t\t\t\tfi\n\t\t\t\tlineno2=$(echo \"$line\" | sed -n \"s/^.*+[0-9]*,\\([0-9]*\\) .*$/\\1/p\")\n\t\t\t\tif [ -z \"$lineno2\" ]; then\n\t\t\t\t\tlineno2=1\n\t\t\t\tfi\n\t\t\t\tlinediff=$(($linediff+$lineno-$lineno2))\n\t\t\tfi\n\t\t\t;;\n\t\tesac\n\t\tif [ -n \"$active\" ]; then\n\t\t\techo \"$line\"\n\t\tfi\n\tdone < $TMP_FILE\n}\n\ndarcs_mode=\ncase \"$1\" in\n--darcs) darcs_mode=true;;\nesac\n\nIFS=''\naction=\ni=\nif [ ! -n \"$darcs_mode\" ]; then\n\twhile [ \"$action\" != commit -a \"$action\" != abort ]; do\n\t\techo\n\t\tfor ((i=1; i<$filenr; i++)); do\n\t\t\techo -n \"$i ${modified[$i]}\"\n\t\t\tif [ -n \"${hunks[$i]}\" ]; then\n\t\t\t\techo \" (${hunks[$i]})\"\n\t\t\telse\n\t\t\t\techo\n\t\t\tfi\n\t\tdone | less -FS\n\t\techo\n\t\techo \"To put one or more hunks of a file into the staging area (AKA\"\n\t\techo \"index), type in the number of the file.\"\n\t\techo \"To commit, say 'commit', to abort, say 'abort'.\"\n\t\techo\n\t\techo -n \"Your choice? \"\n\t\tread action\n\t\tcase \"$action\" in\n\t\tc) action=commit;;\n\t\tq|a) action=abort;;\n\t\tcommit|abort) ;;\n\t\t[0-9]*) select_hunks \"$action\";;\n\t\t*) echo \"Unknown command.\" ;;\n\t\tesac\n\tdone\nelse\n\tdarcs_select_hunks\n\taction=\"commit\"\nfi\n\nif [ \"$action\" = commit ]; then\n\tfor ((i=1; i<$filenr; i++)); do\n\t\tif [ -n \"${hunks[$i]}\" ]; then\n\t\t\tapply_hunks $i\n\t\tfi\n\tdone | tee a123 | git apply --cached || exit\n\techo git commit\nfi\n\n"}]}