diff --git a/git-subsplit.sh b/git-subsplit.sh index 5c84c3b..1c823ab 100755 --- a/git-subsplit.sh +++ b/git-subsplit.sh @@ -6,7 +6,7 @@ # Copyright (C) 2012 Dragonfly Development Inc. # if [ $# -eq 0 ]; then - set -- -h + set -- -h fi OPTS_SPEC="\ git subsplit init url @@ -26,6 +26,8 @@ tags= only publish for listed tags instead of all tags no-tags do not publish any tags update fetch updates from repository before publishing rebuild-tags rebuild all tags (as opposed to skipping tags that are already synced) +use-splitsh use the splitsh-lite binary instead of git git-subtree +splitsh-bin path of the splitsh-lite binary. Will try to guess it " eval "$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)" @@ -39,7 +41,7 @@ PATH=$PATH:$(git --exec-path) if [ "$(hash git-subtree &>/dev/null && echo OK)" = "" ] then - die "Git subplit needs git subtree; install git subtree or upgrade git to >=1.7.11" + die "Git subplit needs git subtree; install git subtree or upgrade git to >=1.7.11" fi ANNOTATE= @@ -55,293 +57,311 @@ NO_TAGS= REBUILD_TAGS= DRY_RUN= VERBOSE= +SPLITSH_BIN=$(which splitsh-lite) +USE_SPLITSH=0 subsplit_main() { - while [ $# -gt 0 ]; do - opt="$1" - shift - case "$opt" in - -q) QUIET=1 ;; - --debug) VERBOSE=1 ;; - --heads) HEADS="$1"; shift ;; - --no-heads) NO_HEADS=1 ;; - --tags) TAGS="$1"; shift ;; - --no-tags) NO_TAGS=1 ;; - --update) UPDATE=1 ;; - -n) DRY_RUN="--dry-run" ;; - --dry-run) DRY_RUN="--dry-run" ;; - --rebuild-tags) REBUILD_TAGS=1 ;; - --) break ;; - *) die "Unexpected option: $opt" ;; - esac - done - - COMMAND="$1" - shift - - case "$COMMAND" in - init) - if [ $# -lt 1 ]; then die "init command requires url to be passed as first argument"; fi - REPO_URL="$1" - shift - subsplit_init - ;; - publish) - if [ $# -lt 1 ]; then die "publish command requires splits to be passed as first argument"; fi - SPLITS="$1" - shift - subsplit_publish - ;; - update) - subsplit_update - ;; - *) die "Unknown command '$COMMAND'" ;; - esac + while [ $# -gt 0 ]; do + opt="$1" + shift + case "$opt" in + -q) QUIET=1 ;; + --debug) VERBOSE=1 ;; + --heads) HEADS="$1"; shift ;; + --no-heads) NO_HEADS=1 ;; + --tags) TAGS="$1"; shift ;; + --no-tags) NO_TAGS=1 ;; + --update) UPDATE=1 ;; + -n) DRY_RUN="--dry-run" ;; + --dry-run) DRY_RUN="--dry-run" ;; + --rebuild-tags) REBUILD_TAGS=1 ;; + --use-splitsh) USE_SPLITSH=1 ;; + --) break ;; + *) die "Unexpected option: $opt" ;; + esac + done + + COMMAND="$1" + shift + + case "$COMMAND" in + init) + if [ $# -lt 1 ]; then die "init command requires url to be passed as first argument"; fi + REPO_URL="$1" + shift + subsplit_init + ;; + publish) + if [ $# -lt 1 ]; then die "publish command requires splits to be passed as first argument"; fi + SPLITS="$1" + shift + subsplit_publish + ;; + update) + subsplit_update + ;; + *) die "Unknown command '$COMMAND'" ;; + esac } say() { - if [ -z "$QUIET" ]; - then - echo "$@" >&2 - fi + if [ -z "$QUIET" ]; + then + echo "$@" >&2 + fi } subsplit_require_work_dir() { - if [ ! -e "$WORK_DIR" ] - then - die "Working directory not found at ${WORK_DIR}; please run init first" - fi + if [ ! -e "$WORK_DIR" ] + then + die "Working directory not found at ${WORK_DIR}; please run init first" + fi - if [ -n "$VERBOSE" ]; - then - echo "${DEBUG} pushd \"${WORK_DIR}\" >/dev/null" - fi + if [ -n "$VERBOSE" ]; + then + echo "${DEBUG} pushd \"${WORK_DIR}\" >/dev/null" + fi - pushd "$WORK_DIR" >/dev/null + pushd "$WORK_DIR" >/dev/null } subsplit_init() { - if [ -e "$WORK_DIR" ] - then - die "Working directory already found at ${WORK_DIR}; please remove or run update" - fi + if [ -e "$WORK_DIR" ] + then + die "Working directory already found at ${WORK_DIR}; please remove or run update" + fi - say "Initializing subsplit from origin (${REPO_URL})" + say "Initializing subsplit from origin (${REPO_URL})" - if [ -n "$VERBOSE" ]; - then - echo "${DEBUG} git clone -q \"${REPO_URL}\" \"${WORK_DIR}\"" - fi + if [ -n "$VERBOSE" ]; + then + echo "${DEBUG} git clone -q \"${REPO_URL}\" \"${WORK_DIR}\"" + fi - git clone -q "$REPO_URL" "$WORK_DIR" || die "Could not clone repository" + git clone -q "$REPO_URL" "$WORK_DIR" || die "Could not clone repository" } subsplit_publish() { - subsplit_require_work_dir - - if [ -n "$UPDATE" ]; - then - subsplit_update - fi - - if [ -z "$HEADS" ] && [ -z "$NO_HEADS" ] - then - # If heads are not specified and we want heads, discover them. - HEADS="$(git ls-remote origin 2>/dev/null | grep "refs/heads/" | cut -f3- -d/)" - - if [ -n "$VERBOSE" ]; - then - echo "${DEBUG} HEADS=\"${HEADS}\"" - fi - fi - - if [ -z "$TAGS" ] && [ -z "$NO_TAGS" ] - then - # If tags are not specified and we want tags, discover them. - TAGS="$(git ls-remote origin 2>/dev/null | grep -v "\^{}" | grep "refs/tags/" | cut -f3 -d/)" - - if [ -n "$VERBOSE" ]; - then - echo "${DEBUG} TAGS=\"${TAGS}\"" - fi - fi - - for SPLIT in $SPLITS - do - SUBPATH=$(echo "$SPLIT" | cut -f1 -d:) - REMOTE_URL=$(echo "$SPLIT" | cut -f2- -d:) - REMOTE_NAME=$(echo "$SPLIT" | git hash-object --stdin) - - if [ -n "$VERBOSE" ]; - then - echo "${DEBUG} SUBPATH=${SUBPATH}" - echo "${DEBUG} REMOTE_URL=${REMOTE_URL}" - echo "${DEBUG} REMOTE_NAME=${REMOTE_NAME}" - fi - - if ! git remote | grep "^${REMOTE_NAME}$" >/dev/null - then - git remote add "$REMOTE_NAME" "$REMOTE_URL" - - if [ -n "$VERBOSE" ]; - then - echo "${DEBUG} git remote add \"${REMOTE_NAME}\" \"${REMOTE_URL}\"" - fi - fi - - - say "Syncing ${SUBPATH} -> ${REMOTE_URL}" - - for HEAD in $HEADS - do - if [ -n "$VERBOSE" ]; - then - echo "${DEBUG} git show-ref --quiet --verify -- \"refs/remotes/origin/${HEAD}\"" - fi - - if ! git show-ref --quiet --verify -- "refs/remotes/origin/${HEAD}" - then - say " - skipping head '${HEAD}' (does not exist)" - continue - fi - LOCAL_BRANCH="${REMOTE_NAME}-branch-${HEAD}" - - if [ -n "$VERBOSE" ]; - then - echo "${DEBUG} LOCAL_BRANCH=\"${LOCAL_BRANCH}\"" - fi - - say " - syncing branch '${HEAD}'" - - git checkout master >/dev/null 2>&1 - git branch -D "$LOCAL_BRANCH" >/dev/null 2>&1 - git branch -D "${LOCAL_BRANCH}-checkout" >/dev/null 2>&1 - git checkout -b "${LOCAL_BRANCH}-checkout" "origin/${HEAD}" >/dev/null 2>&1 - git subtree split -q --prefix="$SUBPATH" --branch="$LOCAL_BRANCH" "origin/${HEAD}" >/dev/null - RETURNCODE=$? - - if [ -n "$VERBOSE" ]; - then - echo "${DEBUG} git checkout master >/dev/null 2>&1" - echo "${DEBUG} git branch -D \"$LOCAL_BRANCH\" >/dev/null 2>&1" - echo "${DEBUG} git branch -D \"${LOCAL_BRANCH}-checkout\" >/dev/null 2>&1" - echo "${DEBUG} git checkout -b \"${LOCAL_BRANCH}-checkout\" \"origin/${HEAD}\" >/dev/null 2>&1" - echo "${DEBUG} git subtree split -q --prefix=\"$SUBPATH\" --branch=\"$LOCAL_BRANCH\" \"origin/${HEAD}\" >/dev/null" - fi - - if [ $RETURNCODE -eq 0 ] - then - PUSH_CMD="git push -q ${DRY_RUN} --force $REMOTE_NAME ${LOCAL_BRANCH}:${HEAD}" - - if [ -n "$VERBOSE" ]; - then - echo "${DEBUG} $PUSH_CMD" - fi - - if [ -n "$DRY_RUN" ] - then - echo \# $PUSH_CMD - $PUSH_CMD - else - $PUSH_CMD - fi - fi - done - - for TAG in $TAGS - do - if [ -n "$VERBOSE" ]; - then - echo "${DEBUG} git show-ref --quiet --verify -- \"refs/tags/${TAG}\"" - fi - - if ! git show-ref --quiet --verify -- "refs/tags/${TAG}" - then - say " - skipping tag '${TAG}' (does not exist)" - continue - fi - LOCAL_TAG="${REMOTE_NAME}-tag-${TAG}" - - if [ -n "$VERBOSE" ]; - then - echo "${DEBUG} LOCAL_TAG="${LOCAL_TAG}"" - fi - - if git branch | grep "${LOCAL_TAG}$" >/dev/null && [ -z "$REBUILD_TAGS" ] - then - say " - skipping tag '${TAG}' (already synced)" - continue - fi - - if [ -n "$VERBOSE" ]; - then - echo "${DEBUG} git branch | grep \"${LOCAL_TAG}$\" >/dev/null && [ -z \"${REBUILD_TAGS}\" ]" - fi - - say " - syncing tag '${TAG}'" - say " - deleting '${LOCAL_TAG}'" - git branch -D "$LOCAL_TAG" >/dev/null 2>&1 - - if [ -n "$VERBOSE" ]; - then - echo "${DEBUG} git branch -D \"${LOCAL_TAG}\" >/dev/null 2>&1" - fi - - say " - subtree split for '${TAG}'" - git subtree split -q --annotate="${ANNOTATE}" --prefix="$SUBPATH" --branch="$LOCAL_TAG" "$TAG" >/dev/null - RETURNCODE=$? - - if [ -n "$VERBOSE" ]; - then - echo "${DEBUG} git subtree split -q --annotate=\"${ANNOTATE}\" --prefix=\"$SUBPATH\" --branch=\"$LOCAL_TAG\" \"$TAG\" >/dev/null" - fi - - say " - subtree split for '${TAG}' [DONE]" - if [ $RETURNCODE -eq 0 ] - then - PUSH_CMD="git push -q ${DRY_RUN} --force ${REMOTE_NAME} ${LOCAL_TAG}:refs/tags/${TAG}" - - if [ -n "$VERBOSE" ]; - then - echo "${DEBUG} PUSH_CMD=\"${PUSH_CMD}\"" - fi - - if [ -n "$DRY_RUN" ] - then - echo \# $PUSH_CMD - $PUSH_CMD - else - $PUSH_CMD - fi - fi - done - done - - popd >/dev/null + subsplit_require_work_dir + + if [ -n "$UPDATE" ]; + then + subsplit_update + fi + + if [ -z "$HEADS" ] && [ -z "$NO_HEADS" ] + then + # If heads are not specified and we want heads, discover them. + HEADS="$(git ls-remote origin 2>/dev/null | grep "refs/heads/" | cut -f3- -d/)" + + if [ -n "$VERBOSE" ]; + then + echo "${DEBUG} HEADS=\"${HEADS}\"" + fi + fi + + if [ -z "$TAGS" ] && [ -z "$NO_TAGS" ] + then + # If tags are not specified and we want tags, discover them. + TAGS="$(git ls-remote origin 2>/dev/null | grep -v "\^{}" | grep "refs/tags/" | cut -f3 -d/)" + + if [ -n "$VERBOSE" ]; + then + echo "${DEBUG} TAGS=\"${TAGS}\"" + fi + fi + + for SPLIT in $SPLITS + do + SUBPATH=$(echo "$SPLIT" | cut -f1 -d:) + REMOTE_URL=$(echo "$SPLIT" | cut -f2- -d:) + REMOTE_NAME=$(echo "$SPLIT" | git hash-object --stdin) + + if [ -n "$VERBOSE" ]; + then + echo "${DEBUG} SUBPATH=${SUBPATH}" + echo "${DEBUG} REMOTE_URL=${REMOTE_URL}" + echo "${DEBUG} REMOTE_NAME=${REMOTE_NAME}" + fi + + if ! git remote | grep "^${REMOTE_NAME}$" >/dev/null + then + git remote add "$REMOTE_NAME" "$REMOTE_URL" + + if [ -n "$VERBOSE" ]; + then + echo "${DEBUG} git remote add \"${REMOTE_NAME}\" \"${REMOTE_URL}\"" + fi + fi + + + say "Syncing ${SUBPATH} -> ${REMOTE_URL}" + + for HEAD in $HEADS + do + if [ -n "$VERBOSE" ]; + then + echo "${DEBUG} git show-ref --quiet --verify -- \"refs/remotes/origin/${HEAD}\"" + fi + + if ! git show-ref --quiet --verify -- "refs/remotes/origin/${HEAD}" + then + say " - skipping head '${HEAD}' (does not exist)" + continue + fi + LOCAL_BRANCH="${REMOTE_NAME}-branch-${HEAD}" + + if [ -n "$VERBOSE" ]; + then + echo "${DEBUG} LOCAL_BRANCH=\"${LOCAL_BRANCH}\"" + fi + + say " - syncing branch '${HEAD}'" + + git checkout master >/dev/null 2>&1 + git branch -D "${LOCAL_BRANCH}" >/dev/null 2>&1 + git branch -D "${LOCAL_BRANCH}-checkout" >/dev/null 2>&1 + git checkout -b "${LOCAL_BRANCH}-checkout" "origin/${HEAD}" >/dev/null 2>&1 + if [ $USE_SPLITSH -eq 0 ] + then + git subtree split -q --prefix="$SUBPATH" --branch="$LOCAL_BRANCH" "origin/${HEAD}" >/dev/null + else + $SPLITSH_BIN -prefix="${SUBPATH}" -origin="refs/heads/${LOCAL_BRANCH}-checkout" -target="refs/heads/${LOCAL_BRANCH}" + fi + RETURNCODE=$? + + if [ -n "$VERBOSE" ]; + then + echo "${DEBUG} git checkout master >/dev/null 2>&1" + echo "${DEBUG} git branch -D \"${LOCAL_BRANCH}\" >/dev/null 2>&1" + echo "${DEBUG} git branch -D \"${LOCAL_BRANCH}-checkout\" >/dev/null 2>&1" + echo "${DEBUG} git checkout -b \"${LOCAL_BRANCH}-checkout\" \"origin/${HEAD}\" >/dev/null 2>&1" + if [ $USE_SPLITSH -eq 0 ] + then + echo "${DEBUG} git subtree split -q --prefix=\"$SUBPATH\" --branch=\"$LOCAL_BRANCH\" \"origin/${HEAD}\" >/dev/null" + else + echo "${DEBUG} ${SPLITSH_BIN} -prefix=\"${SUBPATH}\" -origin=\"refs/heads/${LOCAL_BRANCH}-checkout\" -target=\"refs/heads/${LOCAL_BRANCH}\"" + fi + fi + + if [ $RETURNCODE -eq 0 ] + then + PUSH_CMD="git push -q ${DRY_RUN} --force $REMOTE_NAME ${LOCAL_BRANCH}:${HEAD}" + + if [ -n "$VERBOSE" ]; + then + echo "${DEBUG} $PUSH_CMD" + fi + + if [ -n "$DRY_RUN" ] + then + echo \# $PUSH_CMD + $PUSH_CMD + else + $PUSH_CMD + fi + fi + done + + if [ "$USE_SPLITSH" -eq 0 ] + then + for TAG in $TAGS + do + if [ -n "$VERBOSE" ]; + then + echo "${DEBUG} git show-ref --quiet --verify -- \"refs/tags/${TAG}\"" + fi + + if ! git show-ref --quiet --verify -- "refs/tags/${TAG}" + then + say " - skipping tag '${TAG}' (does not exist)" + continue + fi + LOCAL_TAG="${REMOTE_NAME}-tag-${TAG}" + + if [ -n "$VERBOSE" ]; + then + echo "${DEBUG} LOCAL_TAG="${LOCAL_TAG}"" + fi + + if git branch | grep "${LOCAL_TAG}$" >/dev/null && [ -z "$REBUILD_TAGS" ] + then + say " - skipping tag '${TAG}' (already synced)" + continue + fi + + if [ -n "$VERBOSE" ]; + then + echo "${DEBUG} git branch | grep \"${LOCAL_TAG}$\" >/dev/null && [ -z \"${REBUILD_TAGS}\" ]" + fi + + say " - syncing tag '${TAG}'" + say " - deleting '${LOCAL_TAG}'" + git branch -D "$LOCAL_TAG" >/dev/null 2>&1 + + if [ -n "$VERBOSE" ]; + then + echo "${DEBUG} git branch -D \"${LOCAL_TAG}\" >/dev/null 2>&1" + fi + + say " - subtree split for '${TAG}'" + git subtree split -q --annotate="${ANNOTATE}" --prefix="$SUBPATH" --branch="$LOCAL_TAG" "$TAG" >/dev/null + RETURNCODE=$? + + if [ -n "$VERBOSE" ]; + then + echo "${DEBUG} git subtree split -q --annotate=\"${ANNOTATE}\" --prefix=\"$SUBPATH\" --branch=\"$LOCAL_TAG\" \"$TAG\" >/dev/null" + fi + + say " - subtree split for '${TAG}' [DONE]" + if [ $RETURNCODE -eq 0 ] + then + PUSH_CMD="git push -q ${DRY_RUN} --force ${REMOTE_NAME} ${LOCAL_TAG}:refs/tags/${TAG}" + + if [ -n "$VERBOSE" ]; + then + echo "${DEBUG} PUSH_CMD=\"${PUSH_CMD}\"" + fi + + if [ -n "$DRY_RUN" ] + then + echo \# $PUSH_CMD + $PUSH_CMD + else + $PUSH_CMD + fi + fi + done + else + echo "${DEBUG} tags are not supported with Splitsh-lite - skipping" + fi + done + + popd >/dev/null } subsplit_update() { - subsplit_require_work_dir + subsplit_require_work_dir - say "Updating subsplit from origin" + say "Updating subsplit from origin" - git fetch -q -t origin - git checkout master - git reset --hard origin/master + git fetch -q -t origin + git checkout master + git reset --hard origin/master - if [ -n "$VERBOSE" ]; - then - echo "${DEBUG} git fetch -q -t origin" - echo "${DEBUG} git checkout master" - echo "${DEBUG} git reset --hard origin/master" - fi + if [ -n "$VERBOSE" ]; + then + echo "${DEBUG} git fetch -q -t origin" + echo "${DEBUG} git checkout master" + echo "${DEBUG} git reset --hard origin/master" + fi - popd >/dev/null + popd >/dev/null } subsplit_main "$@"