├── .gitignore ├── LICENSE ├── git-extract-branch ├── git-fix-branch ├── git-prune-all ├── git-push-branch ├── git-push-each ├── git-sync-mtime ├── hooks └── post-checkout └── strict-mode.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw[op] 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Dan Kubb 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 | -------------------------------------------------------------------------------- /git-extract-branch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Extract N commits of the current branch to a new branch 4 | 5 | set -o errexit -o pipefail -o noglob -o noclobber -o nounset 6 | IFS=$'\n\t' 7 | 8 | main() { 9 | local -r \ 10 | new_branch=$1 \ 11 | n_commits=$2 \ 12 | parent=${3-master} \ 13 | master=${4-master} \ 14 | remote=${5-origin} 15 | 16 | local -r branch=$(git rev-parse --abbrev-ref HEAD) 17 | local -r new_branch_head=$( 18 | git rev-list "$parent".."$branch" | tail -n "$n_commits" | head -n 1 19 | ) 20 | 21 | echo "Extracting ${new_branch}: ${parent}..${new_branch_head}" 22 | 23 | git fetch "$remote" "${master}:${master}" 24 | git fix-branch "$master" 25 | git checkout -b "$new_branch" "$master" 26 | git cherry-pick "${parent}..${new_branch_head}" 27 | git fix-branch "$master" 28 | 29 | if [ "$parent" = "$master" ]; then 30 | git checkout "$branch" 31 | git rebase "$new_branch" 32 | git fix-branch 33 | git checkout "$new_branch" 34 | fi 35 | } 36 | 37 | main "$@" 38 | -------------------------------------------------------------------------------- /git-fix-branch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Fix branch commit data to be the same as author data 4 | 5 | # shellcheck source=/dev/null 6 | source "${BASH_SOURCE%/*}/strict-mode.sh" 7 | 8 | export FILTER_BRANCH_SQUELCH_WARNING=1 9 | 10 | parent=${1-$(git config init.defaultBranch)} 11 | 12 | # The command to execute for each commit 13 | read -r -d '' command <<-'COMMAND' || true 14 | export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME" 15 | export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL" 16 | export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" 17 | COMMAND 18 | 19 | # Rebase the current branch on top of the parent 20 | git rebase --rebase-merges -- "$parent" 21 | 22 | # Rewrite all commits to use the author data 23 | git filter-branch --force --env-filter "$command" -- "$parent..HEAD" 24 | -------------------------------------------------------------------------------- /git-prune-all: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Prune all merged local and remote branches 4 | 5 | # shellcheck source=/dev/null 6 | source "${BASH_SOURCE%/*}/strict-mode.sh" 7 | 8 | branch=$(git rev-parse --abbrev-ref HEAD) 9 | remote=$(git config -- "branch.${branch}.remote") 10 | 11 | # Remove remote references 12 | git remote update --prune > /dev/null 13 | 14 | # Remove remote branches 15 | git branch --remotes --merged "$remote/$branch" \ 16 | | perl -lne "print \$1 if m!(?<=^ $remote/)(\S+)\$!" \ 17 | | (grep --invert-match --extended-regexp "^(release/|pr/|$(git config init.defaultBranch)$|${branch}$)" || true) \ 18 | | xargs --no-run-if-empty git push --delete -- "$remote" || true 19 | 20 | # Remove local branches 21 | # shellcheck disable=SC2063 22 | git branch --merged "$branch" \ 23 | | grep --invert-match --fixed-strings --line-regexp "* $branch" \ 24 | | xargs --no-run-if-empty git branch --delete -- || true 25 | -------------------------------------------------------------------------------- /git-push-branch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Push a local branch to a newly created remote using push-each 4 | 5 | set -o errexit -o pipefail -o noglob -o noclobber -o nounset 6 | IFS=$'\n\t' 7 | 8 | main() { 9 | local -r remote=${1-origin} parent=${2-master} 10 | local -r branch=$(git rev-parse --abbrev-ref HEAD) 11 | 12 | echo "Pushing ${branch} to ${remote}/${branch} (parent=$parent)" 13 | 14 | git fetch "$remote" "${parent}:${parent}" 15 | git fix-branch "$parent" 16 | git push "$remote" "${parent}:${branch}" 17 | git branch --set-upstream-to "${remote}/${branch}" 18 | git push-each "$parent" 19 | } 20 | 21 | main "$@" 22 | -------------------------------------------------------------------------------- /git-push-each: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Push commits individually to trigger CI builds for each 4 | 5 | # shellcheck source=/dev/null 6 | source "${BASH_SOURCE%/*}/strict-mode.sh" 7 | 8 | parent=${1-$(git config init.defaultBranch)} 9 | branch=$(git rev-parse --abbrev-ref HEAD) 10 | remote=$(git config -- "branch.${branch}.remote") 11 | 12 | # Commits in the local branch not in upstream 13 | commits=$(git rev-list HEAD "^$remote/$branch" "^$remote/$parent" --reverse) 14 | 15 | # TODO: use git push --force on the first commit, and then --ff-only all others 16 | # TODO: allow --set-upstream to be passed-through 17 | 18 | # Push all commits not upstream 19 | for commit in $commits 20 | do 21 | # Trigger CI for each commit by pushing them individually 22 | git push --force-with-lease -- "$remote" "$commit:$branch" 23 | done 24 | -------------------------------------------------------------------------------- /git-sync-mtime: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Fix file mtime to match last git commit date 4 | 5 | # shellcheck source=/dev/null 6 | source "${BASH_SOURCE%/*}/strict-mode.sh" 7 | 8 | # shellcheck disable=SC2016 9 | git ls-tree -z -r --name-only HEAD | parallel --gnu --null --quote sh -c ' 10 | mtime=$(git log --date=iso -1 --format=%cd "{}"); 11 | touch --date "$mtime" "{}"; 12 | echo "$mtime - {}" 13 | ' 14 | -------------------------------------------------------------------------------- /hooks/post-checkout: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Pull upstream master branch when checking out master locally 4 | 5 | # shellcheck source=/dev/null 6 | source "$(dirname -- "$(readlink -f -- "${BASH_SOURCE[0]}")")/../strict-mode.sh" 7 | 8 | branch=$(git rev-parse --abbrev-ref HEAD) 9 | 10 | if [ "$branch" = 'master' ]; then 11 | git pull --rebase 12 | git prune-all 13 | fi 14 | -------------------------------------------------------------------------------- /strict-mode.sh: -------------------------------------------------------------------------------- 1 | # Reference: 2 | # http://www.davidpashley.com/articles/writing-robust-shell-scripts/ 3 | # http://kvz.io/blog/2013/11/21/bash-best-practices/ 4 | # http://redsymbol.net/articles/unofficial-bash-strict-mode/ 5 | 6 | set -o errexit # Exit when an expression fails 7 | set -o pipefail # Exit when a command in a pipeline fails 8 | set -o nounset # Exit when an undefined variable is used 9 | set -o noglob # Disable shell globbing 10 | set -o noclobber # Disable automatic file overwriting 11 | set -o posix # Ensure posix semantics 12 | 13 | IFS=$'\n\t' # Set default field separator to not split on spaces 14 | 15 | umask 0077 16 | --------------------------------------------------------------------------------