diff --git a/.config/shell/git b/.config/shell/git index f3f754a..9453c4d 100644 --- a/.config/shell/git +++ b/.config/shell/git @@ -69,98 +69,5 @@ gitretag() { git push origin refs/tags/${1}:refs/tags/${2} :refs/tags/$1 && git tag -d $1 } -# Rewriting history {{{ - -# gets the AuthorDate of a given committish -git-authordate() { - local date=$(git log --pretty=fuller --date=raw -1 $1 | grep AuthorDate) - echo ${date##*: } -} -# executes a git command (usually commit) with the date of a given committish -git-withdate() { - local date=$(git-authordate $1) - GIT_AUTHOR_DATE="$date" GIT_COMMITTER_DATE="$date" git "${@:2}" -} - -# takes all changes in the current working tree and amends them to the given commit -gitedit() { - git stash - gitcommits -q $1 - git reset --keep $1 - git stash pop - git-withdate $1 commit --all --amend "${@:2}" - gitcommits -} - -# takes two committishs and squashes them with all commits between them into a single commit -# this will rewrite the full history from then on, but should not create any conflicts -gitsquash() { - local -a options - while [ $# -gt 0 ]; do - case $1 in - -i) local ignore=true; shift 1;; - -f|--force) local force=true; shift 1;; - -*) options+=($1); exit 1;; - *) break;; - esac - done - - ((#!=2)) && echo "Usage: [options] " && return 1 - [[ -n $(git status -s) ]] && [ ! $force ] && echo -e "Tree is dirty, commit or stash your changes first!\nIf you want to execute the command regardless, run again with --force" && return 1 - - local 1=$(git rev-parse $1) - local 2=$(git rev-parse $2) - [ $(git rev-list $1 --count) -lt $(git rev-list $2 --count) ] && t=$1 && 1=$2 && 2=$t - - gitcommits -q $1 - git reset --hard $1 - if [ $(git rev-list $2 --count) = 1 ]; then - git update-ref -d HEAD - git add . - git-withdate $1 commit -c $1 - else - git reset -q $2 - git add . - git commit --amend - fi - gitcommits -} - -# given a committish, this command saves a list of commits between the HEAD and the given committish into the .git directory -# when ran without parameters it applies the saved list of commits onto the current HEAD -gitcommits() { - local verbosity=1 - while [[ $# -gt 0 ]]; do - case $1 in - -v) verbosity=2; shift 1;; - -q|--quiet) verbosity=0; shift 1;; - --theirs) local params=(-X theirs); shift 1;; - *) break;; - esac - done - - local stashed="$(git rev-parse --git-path stashed-commits)" - if [ $1 ]; then - if [ $verbosity -eq 0 ] - then git rev-list --reverse HEAD...$1 >$stashed - else git rev-list --reverse HEAD...$1 | tee $stashed - fi - else - local aborted - for commit in $(cat $stashed); do - [ $aborted ] && rest+=($commit) && continue - [ $verbosity -gt 0 ] && git --no-pager log --oneline -1 $commit - git-withdate $commit cherry-pick $commit ${params:0} >/dev/null - local last=$? - [ $last -gt 0 ] && local aborted=true && typeset -a rest && continue - [ $verbosity -gt 0 ] && echo -e "\e[1A$(git log --color=always --pretty=format:"%C(yellow)$(git rev-parse --short HEAD^^)%C(bold) -> %Creset%C(yellow)%h%Creset %s" -1)" - [ $verbosity -gt 1 ] && git status -s - done - echo $rest >$stashed - [ $aborted ] && echo "A problem was encountered. Fix it and run 'gitcommits' again to apply the remaining ${#rest} commits." - fi -} -# }}} - # Testing gittestcommit() { touch file$((++i)) && git add 'file*' && git commit -m "Create file$i"; } diff --git a/.local/bin/scripts/git-cash b/.local/bin/scripts/git-cash new file mode 100755 index 0000000..92a7c2e --- /dev/null +++ b/.local/bin/scripts/git-cash @@ -0,0 +1,36 @@ +#!/bin/sh -e +# The git [c]ommit st[ash] +# Given a committish, this command saves a list of commits between the HEAD and the given committish into the .git directory. +# Without parameters it applies the saved list of commits onto the current HEAD. +# NOTE: You should prefer rebase -i to this brewery. +local verbosity=1 +while test $# -gt 0; do + case $1 in + (-v) verbosity=2; shift 1;; + (-q|--quiet) verbosity=0; shift 1;; + (--theirs) local params=(-X theirs); shift 1;; + (*) break;; + esac +done + +local stashed="$(git rev-parse --git-path stashed-commits)" +if [ $1 ]; then + if [ $verbosity -eq 0 ] + then git rev-list --reverse "HEAD...$1" >$stashed + else git rev-list --reverse "HEAD...$1" | tee $stashed + fi +else + local aborted + for commit in $(cat $stashed); do + [ $aborted ] && rest+=($commit) && continue + [ $verbosity -gt 0 ] && git --no-pager log --oneline -1 $commit + git-withdate $commit cherry-pick $commit ${params:0} >/dev/null + local last=$? + [ $last -gt 0 ] && local aborted=true && typeset -a rest && continue + [ $verbosity -gt 0 ] && echo -e "\e[1A$(git log --color=always --pretty=format:"%C(yellow)$(git rev-parse --short 'HEAD^^')%C(bold) -> %Creset%C(yellow)%h%Creset %s" -1)" + [ $verbosity -gt 1 ] && git status -s + done + echo $rest >$stashed + [ $aborted ] && echo "A problem was encountered. Fix it and run '$0' again to apply the remaining ${#rest} commits." +fi + diff --git a/.local/bin/scripts/git-edit b/.local/bin/scripts/git-edit new file mode 100755 index 0000000..408a5bf --- /dev/null +++ b/.local/bin/scripts/git-edit @@ -0,0 +1,11 @@ +#!/bin/sh -e +# Takes all changes in the current working tree and amends them to the given commit. +# NOTE: You should prefer rebase -i to this brewery. +# The only potential advantage is the preservation of commit time, +# but that is a questionable ambition... +git stash push +git-cash -q "$1" +git reset --keep "$1" +git stash pop +git-withdate "$1" commit --all --amend "${@:2}" +git-cash diff --git a/.local/bin/scripts/git-squash b/.local/bin/scripts/git-squash new file mode 100755 index 0000000..964dbe2 --- /dev/null +++ b/.local/bin/scripts/git-squash @@ -0,0 +1,33 @@ +#!/bin/sh -e +# Takes two committishs and squashes them with all commits inbetween into a single commit. +# This will rewrite the full history from then on, but should not create any conflicts. +# NOTE: You should prefer rebase -i to this brewery. +local -a options +while [ $# -gt 0 ]; do + case $1 in + -i) local ignore=true; shift 1;; + -f|--force) local force=true; shift 1;; + -*) options+=($1); exit 1;; + *) break;; + esac +done + +test $# != 2 && echo "Usage: [options] " && return 1 +[[ -n $(git status -s) ]] && [ ! $force ] && echo -e "Tree is dirty, commit or stash your changes first!\nIf you want to execute the command regardless, run again with --force" && return 1 + +local 1=$(git rev-parse $1) +local 2=$(git rev-parse $2) +[ $(git rev-list $1 --count) -lt $(git rev-list $2 --count) ] && t=$1 && 1=$2 && 2=$t + +git-cash -q $1 +git reset --hard $1 +if [ $(git rev-list $2 --count) = 1 ]; then + git update-ref -d HEAD + git add . + git-withdate $1 commit -c $1 +else + git reset -q $2 + git add . + git commit --amend +fi +git-cash diff --git a/.local/bin/scripts/git-withdate b/.local/bin/scripts/git-withdate new file mode 100755 index 0000000..9975c76 --- /dev/null +++ b/.local/bin/scripts/git-withdate @@ -0,0 +1,4 @@ +#!/bin/sh -e +# Execute a git command (usually commit) using the AuthorDate of a given committish +local date=$(git log --pretty=format:%at -1 "$1") +GIT_AUTHOR_DATE="$date" GIT_COMMITTER_DATE="$date" git "${@:2}"