Loading git-supertree 0 → 100755 +290 −0 Original line number Diff line number Diff line #!/bin/bash set -eu shopt -s nullglob shopt -s dotglob SCRIPT=$(basename $0) USAGE="Usage: $SCRIPT [-h] {init|clone|convert|add} [OPTION [OPTION ...]] Create 'super working trees', which are a trees of working trees. This is useful for projects with a great deal of work-churn (constant changing of development focus). Each piece of work can have it's own working tree with in-development changes without having to stash and unstash constantly. Working trees are a core part of git, but normally the repository is stored under the main (first) working tree, and new working trees must be created from there. In 'super working trees' the repository is stored in the top-level directory and each working tree (including the main one) is a subdirectory. New working trees can be created from the top-level with the \`git worktree add\` command. $SCRIPT init [REPOPATH] [[MAIN-TREE] BRANCH-NAME] Create a new super working tree at NAME (default: current directory) $SCRIPT clone URL [REPOPATH] [[MAIN-TREE] BRANCH-NAME] Clone a remote repository to NAME (default: current directory) as a super working tree $SCRIPT convert [REPOPATH] [MAIN-TREE] [NOT IMPLEMENTED YET] Convert a regular git working tree + repository into a super working tree. $SCRIPT add [--from FROM] NAME Add a new worktree named NAME. If a matching branch does not exist it is created from FROM. $SCRIPT rm NAME Remove a worktree name NAME. Options: -h --help Show this message and exit. Arguments: REPOPATH The path to the top-level directory where a super-tree is to be created. URL A URL to a repository using one of the schemes known to git. BRANCH-NAME The initial branch to check out in the main working tree. MAIN-TREE The name of the main working tree, defaults to the initial branch name. NAME The name of a worktree. FROM A commit/branch to create a branch at. " declare COMMAND declare -a ARGUMENTS help() { echo "$USAGE" exit } die() { local exit_code=${EXIT_CODE:-$?} [ $exit_code -gt 0 ] || exit_code=1 echo "$SCRIPT [CRITICAL]: $*" exit $exit_code } >&2 arg_error() { declare -a termcodes=( "" "" ) if [ -t 1 ]; then termcodes=( "\e[31;1m" "\e[m" ) fi echo "$USAGE" echo "Error:" printf " ${termcodes[0]}%s${termcodes[1]}\n" "$*" exit 2 } >&2 info() { fmt -w $(tput cols); } warn() { { printf "WARNING: "; cat; } | info >&2; } is_empty() { declare -a files=( $1/* ) [[ ${#files[*]} -eq 1 ]] && [[ ${files[0]} -ef $1/.backup.tar ]] || [[ ${#files[*]} -eq 0 ]] } is_subdirectory() { local CDIR=$PWD pushd "$1" >/dev/null while [[ ! $PWD -ef / ]]; do cd .. if [[ $PWD -ef $CDIR ]]; then popd >/dev/null return 0 fi done popd >/dev/null return 1 } branch_exists() { git rev-parse --verify $1 >/dev/null 2>&1 } mkcommit() { local tree=$(git mktree </dev/null) if [ $# -gt 0 ]; then printf "%s\n" "$@" | git commit-tree $tree else git commit-tree $tree fi } prepare_init() { case $# in 1) REPOPATH=$1 ;; 2) REPOPATH=$2 ;; 3) REPOPATH=$2 BRANCH_NAME=$3 ;; 4) REPOPATH=$2 MAIN_TREE=$3 BRANCH_NAME=$4 ;; *) arg_error "Too many arguments: $*" ;; esac REPOPATH=${REPOPATH:-.} BRANCH_NAME=${BRANCH_NAME:-master} MAIN_TREE=${MAIN_TREE:-$BRANCH_NAME} if [[ -d $REPOPATH ]]; then is_empty "$REPOPATH" || die "Cannot initialise a repository as the" \ "directory is not empty: $REPOPATH" else mkdir -p -- "$REPOPATH" fi } action_init() { local REPOPATH MAIN_TREE BRANCH_NAME prepare_init . "$@" # sets REPOPATH, MAIN_TREE, BRANCH_NAME mkdir -p "$REPOPATH/.git" git init --bare "$REPOPATH"/.git # -- "$REPOPATH"/"$MAIN_TREE" cd "$REPOPATH" mkcommit "Supertree commit" | tee .git/refs/heads/supertree__ > .git/refs/heads/$BRANCH_NAME echo "ref: refs/heads/supertree__" > .git/HEAD git symbolic-ref SUPERTREE_DEFAULT refs/heads/$BRANCH_NAME action_add $MAIN_TREE mv .git/refs/heads/supertree__ .git/HEAD rm .git/refs/heads/$BRANCH_NAME } action_clone() { local URL=$1; shift local BASE_NAME=$(basename "$URL") local REPOPATH MAIN_TREE BRANCH_NAME prepare_init ${BASE_NAME%.git} "$@" # sets REPOPATH, MAIN_TREE, BRANCH_NAME mkdir "$REPOPATH/.git" git clone --bare -- "$URL" "$REPOPATH/.git" cd "$REPOPATH" mkcommit "Supertree commit" > .git/HEAD git symbolic-ref SUPERTREE_DEFAULT $BRANCH_NAME action_add $MAIN_TREE --from $BRANCH_NAME } action_add() { local BRANCH= FROM= while [ $# -gt 0 ]; do case $1 in --from) FROM=$2; shift ;; *) case '' in $BRANCH) BRANCH=$1 ;; *) arg_error "Unknown argument $1 to add action" ;; esac esac shift done test -n "$BRANCH" || arg_error "A branch name is required" if branch_exists $BRANCH; then git worktree add $BRANCH $BRANCH elif branch_exists ${FROM:-SUPERTREE_DEFAULT}; then git worktree add -b $BRANCH $BRANCH ${FROM:-SUPERTREE_DEFAULT} elif [ -z "$FROM" ]; then warn <<-MSG You cannot create new worktrees without a valid branch. Either use '--from BRANCH', commit something to the default branch (`git symbolic-ref --short SUPERTREE_DEFAULT`) or change the default branch with 'git symbolic-ref SUPERTREE_DEFAULT <BRANCH-NAME>' MSG else warn <<-MSG The branch $FROM does not exist. MSG fi } action_rm() { local BRANCH="$1" test -n "$BRANCH" || arg_error "A worktree name is required" if is_subdirectory "$BRANCH"; then rm -r "$BRANCH" git worktree prune else arg_error "The first argument must be a worktree name" fi } action_convert() { local REPOPATH=${1:-${GIT_DIR:-.}} local BRANCH_REF=$(git rev-parse --symbolic-full-name HEAD) local BRANCH=${BRANCH_REF#refs/heads/} cd $REPOPATH local is_super=yes for f in ./*; do if [ "$f" -ef .git ] || [ -e "$f/.git" ]; then continue else is_super=no fi done test $is_super = no || die "$REPOPATH/ looks like it is already a supertree" # make a backup of the repo & worktree if [ -e .backup.tar ]; then die "There is already a $REPOPATH/.backup.tar present. A previous" \ "attempt at conversion may have been interrupted." fi tar -cf .backup.tar ./* STAGE=$(mktemp -d) find -mindepth 1 -maxdepth 1 \ -name .backup.tar \ -o -exec mv '{}' "$STAGE"/ \; action_clone "$STAGE/.git" . git worktree add $BRANCH $BRANCH_REF local git_file=$(<"$BRANCH"/.git) rm -rf "$STAGE"/.git "$BRANCH"/* mv "$STAGE"/* "$BRANCH"/ rm .backup.tar } # Options & command while [ $# -gt 0 ]; do case $1 in -h|--help) help ;; init|clone|convert|add|rm) COMMAND=$1 ;; *) break ;; esac shift done [[ -v COMMAND ]] || arg_error "'init', 'clone' or 'convert' is required" action_$COMMAND "$@" Loading
git-supertree 0 → 100755 +290 −0 Original line number Diff line number Diff line #!/bin/bash set -eu shopt -s nullglob shopt -s dotglob SCRIPT=$(basename $0) USAGE="Usage: $SCRIPT [-h] {init|clone|convert|add} [OPTION [OPTION ...]] Create 'super working trees', which are a trees of working trees. This is useful for projects with a great deal of work-churn (constant changing of development focus). Each piece of work can have it's own working tree with in-development changes without having to stash and unstash constantly. Working trees are a core part of git, but normally the repository is stored under the main (first) working tree, and new working trees must be created from there. In 'super working trees' the repository is stored in the top-level directory and each working tree (including the main one) is a subdirectory. New working trees can be created from the top-level with the \`git worktree add\` command. $SCRIPT init [REPOPATH] [[MAIN-TREE] BRANCH-NAME] Create a new super working tree at NAME (default: current directory) $SCRIPT clone URL [REPOPATH] [[MAIN-TREE] BRANCH-NAME] Clone a remote repository to NAME (default: current directory) as a super working tree $SCRIPT convert [REPOPATH] [MAIN-TREE] [NOT IMPLEMENTED YET] Convert a regular git working tree + repository into a super working tree. $SCRIPT add [--from FROM] NAME Add a new worktree named NAME. If a matching branch does not exist it is created from FROM. $SCRIPT rm NAME Remove a worktree name NAME. Options: -h --help Show this message and exit. Arguments: REPOPATH The path to the top-level directory where a super-tree is to be created. URL A URL to a repository using one of the schemes known to git. BRANCH-NAME The initial branch to check out in the main working tree. MAIN-TREE The name of the main working tree, defaults to the initial branch name. NAME The name of a worktree. FROM A commit/branch to create a branch at. " declare COMMAND declare -a ARGUMENTS help() { echo "$USAGE" exit } die() { local exit_code=${EXIT_CODE:-$?} [ $exit_code -gt 0 ] || exit_code=1 echo "$SCRIPT [CRITICAL]: $*" exit $exit_code } >&2 arg_error() { declare -a termcodes=( "" "" ) if [ -t 1 ]; then termcodes=( "\e[31;1m" "\e[m" ) fi echo "$USAGE" echo "Error:" printf " ${termcodes[0]}%s${termcodes[1]}\n" "$*" exit 2 } >&2 info() { fmt -w $(tput cols); } warn() { { printf "WARNING: "; cat; } | info >&2; } is_empty() { declare -a files=( $1/* ) [[ ${#files[*]} -eq 1 ]] && [[ ${files[0]} -ef $1/.backup.tar ]] || [[ ${#files[*]} -eq 0 ]] } is_subdirectory() { local CDIR=$PWD pushd "$1" >/dev/null while [[ ! $PWD -ef / ]]; do cd .. if [[ $PWD -ef $CDIR ]]; then popd >/dev/null return 0 fi done popd >/dev/null return 1 } branch_exists() { git rev-parse --verify $1 >/dev/null 2>&1 } mkcommit() { local tree=$(git mktree </dev/null) if [ $# -gt 0 ]; then printf "%s\n" "$@" | git commit-tree $tree else git commit-tree $tree fi } prepare_init() { case $# in 1) REPOPATH=$1 ;; 2) REPOPATH=$2 ;; 3) REPOPATH=$2 BRANCH_NAME=$3 ;; 4) REPOPATH=$2 MAIN_TREE=$3 BRANCH_NAME=$4 ;; *) arg_error "Too many arguments: $*" ;; esac REPOPATH=${REPOPATH:-.} BRANCH_NAME=${BRANCH_NAME:-master} MAIN_TREE=${MAIN_TREE:-$BRANCH_NAME} if [[ -d $REPOPATH ]]; then is_empty "$REPOPATH" || die "Cannot initialise a repository as the" \ "directory is not empty: $REPOPATH" else mkdir -p -- "$REPOPATH" fi } action_init() { local REPOPATH MAIN_TREE BRANCH_NAME prepare_init . "$@" # sets REPOPATH, MAIN_TREE, BRANCH_NAME mkdir -p "$REPOPATH/.git" git init --bare "$REPOPATH"/.git # -- "$REPOPATH"/"$MAIN_TREE" cd "$REPOPATH" mkcommit "Supertree commit" | tee .git/refs/heads/supertree__ > .git/refs/heads/$BRANCH_NAME echo "ref: refs/heads/supertree__" > .git/HEAD git symbolic-ref SUPERTREE_DEFAULT refs/heads/$BRANCH_NAME action_add $MAIN_TREE mv .git/refs/heads/supertree__ .git/HEAD rm .git/refs/heads/$BRANCH_NAME } action_clone() { local URL=$1; shift local BASE_NAME=$(basename "$URL") local REPOPATH MAIN_TREE BRANCH_NAME prepare_init ${BASE_NAME%.git} "$@" # sets REPOPATH, MAIN_TREE, BRANCH_NAME mkdir "$REPOPATH/.git" git clone --bare -- "$URL" "$REPOPATH/.git" cd "$REPOPATH" mkcommit "Supertree commit" > .git/HEAD git symbolic-ref SUPERTREE_DEFAULT $BRANCH_NAME action_add $MAIN_TREE --from $BRANCH_NAME } action_add() { local BRANCH= FROM= while [ $# -gt 0 ]; do case $1 in --from) FROM=$2; shift ;; *) case '' in $BRANCH) BRANCH=$1 ;; *) arg_error "Unknown argument $1 to add action" ;; esac esac shift done test -n "$BRANCH" || arg_error "A branch name is required" if branch_exists $BRANCH; then git worktree add $BRANCH $BRANCH elif branch_exists ${FROM:-SUPERTREE_DEFAULT}; then git worktree add -b $BRANCH $BRANCH ${FROM:-SUPERTREE_DEFAULT} elif [ -z "$FROM" ]; then warn <<-MSG You cannot create new worktrees without a valid branch. Either use '--from BRANCH', commit something to the default branch (`git symbolic-ref --short SUPERTREE_DEFAULT`) or change the default branch with 'git symbolic-ref SUPERTREE_DEFAULT <BRANCH-NAME>' MSG else warn <<-MSG The branch $FROM does not exist. MSG fi } action_rm() { local BRANCH="$1" test -n "$BRANCH" || arg_error "A worktree name is required" if is_subdirectory "$BRANCH"; then rm -r "$BRANCH" git worktree prune else arg_error "The first argument must be a worktree name" fi } action_convert() { local REPOPATH=${1:-${GIT_DIR:-.}} local BRANCH_REF=$(git rev-parse --symbolic-full-name HEAD) local BRANCH=${BRANCH_REF#refs/heads/} cd $REPOPATH local is_super=yes for f in ./*; do if [ "$f" -ef .git ] || [ -e "$f/.git" ]; then continue else is_super=no fi done test $is_super = no || die "$REPOPATH/ looks like it is already a supertree" # make a backup of the repo & worktree if [ -e .backup.tar ]; then die "There is already a $REPOPATH/.backup.tar present. A previous" \ "attempt at conversion may have been interrupted." fi tar -cf .backup.tar ./* STAGE=$(mktemp -d) find -mindepth 1 -maxdepth 1 \ -name .backup.tar \ -o -exec mv '{}' "$STAGE"/ \; action_clone "$STAGE/.git" . git worktree add $BRANCH $BRANCH_REF local git_file=$(<"$BRANCH"/.git) rm -rf "$STAGE"/.git "$BRANCH"/* mv "$STAGE"/* "$BRANCH"/ rm .backup.tar } # Options & command while [ $# -gt 0 ]; do case $1 in -h|--help) help ;; init|clone|convert|add|rm) COMMAND=$1 ;; *) break ;; esac shift done [[ -v COMMAND ]] || arg_error "'init', 'clone' or 'convert' is required" action_$COMMAND "$@"