| #!/bin/bash |
| # |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| # |
| |
| # displays a summary list of commits in your current repo vs upstream branch and upstream master, |
| # and of working tree changes Added, Modified, Deleted, or not in index ("??") |
| |
| THIS_COMMAND="$0 $@" |
| |
| while true; do |
| case $1 in |
| -o|--offline) |
| OFFLINE=true |
| ;; |
| -r|--recurse|--recursive) |
| RECURSE=true |
| ;; |
| -m|--master) |
| shift |
| UPSTREAM_MASTER=$1 |
| MASTER_UPSTREAM_REPO=`echo $1 | cut -d / -f 1` |
| ;; |
| -h|--help) |
| cat <<EOF |
| Usage: git-summary [--help] [-o|--offline] [-r|--recursive] [-m|--master REPO/BRANCH] [<path>]" |
| |
| This will display a one-line git log for differences between the local branch, the upstream |
| pull source of the local branch, and the push target of the local branch. With the "central" |
| (one-repo) workflow the latter two are the same (and you won't see < or > below). |
| In a triangle workflow, these might be `master`, `upstream/master`, and `origin/master` |
| (or `your_github_id/master` instead of `origin`). See `man git-config` pages on `push.default` |
| and `remote.pushDefault` for help configuring triangle workflow. Or use these names and |
| the script will do the right thing! |
| |
| The following prefixes are shown on commits which are in some but not all locations: |
| |
| * local change, not in either the pull or push upstream (do a git push, then open a pull request) |
| > changes not yet upstream, in your push upstream but not your pull upstream (open a pull request) |
| ^ someone else's change, in your pull upstream but not local (do a git pull to update) |
| < someone else's change, local but not in the push target (do a git push to update) |
| |
| The optional <path> can be used to specify one or more files or paths to look at. |
| |
| The following options are supported: |
| |
| -o to operate offline, skipping the fetch done by default (aka --offline) |
| -r to recurse (aka --recurs{e,ive}) |
| -m to specify a master "repo/branch" to compare upstream with, or "" for none (aka --master); |
| defaults to "upstream/master" or "origin/master" (the first existing) |
| |
| |
| EOF |
| exit 1 |
| ;; |
| *) |
| break |
| ;; |
| esac |
| shift || break |
| done |
| |
| # find pull and push sources using canonical git commands |
| [ -n "${PULL_BRANCH}" ] || \ |
| PULL_BRANCH=$(git for-each-ref --format='%(upstream:short)' $(git symbolic-ref -q HEAD)) && \ |
| git show $PULL_BRANCH > /dev/null 2> /dev/null || \ |
| unset PULL_BRANCH && true |
| |
| [ -n "${PUSH_BRANCH}" ] || \ |
| PUSH_BRANCH=$(git for-each-ref --format='%(push:short)' $(git symbolic-ref -q HEAD)) && \ |
| git show $PUSH_BRANCH > /dev/null 2> /dev/null || \ |
| unset PUSH_BRANCH && true |
| |
| # attempt some inferencing if the above didn't work |
| # or if pulling and pushing from same repo, see if others are available |
| # (remove this if you normally use central workflow) |
| |
| if [ "${PULL_BRANCH}" == "${PUSH_BRANCH}" ] ; then |
| [ "${PULL_BRANCH#upstream/}" == "${PULL_BRANCH}" ] || unset PUSH_BRANCH |
| [ "${PULL_BRANCH#origin/}" == "${PULL_BRANCH}" ] || unset PULL_BRANCH |
| fi |
| |
| [ -n "${PULL_REPO}" ] || \ |
| { [ -n "${PULL_BRANCH}" ] && PULL_REPO=$(echo $PULL_BRANCH | cut -f 1 -d '/') ; } || \ |
| PULL_REPO=upstream && git config remote.${PULL_REPO}.url > /dev/null || \ |
| PULL_REPO=origin && git config remote.${PULL_REPO}.url > /dev/null || \ |
| unset PULL_REPO |
| |
| # set default PULL BRANCH if needed |
| [ -z "${PULL_REPO}" ] || [ -n "${PULL_BRANCH}" ] || \ |
| PULL_BRANCH=${PULL_REPO}/master |
| |
| [ -z "${PULL_REPO}" ] || [ -n "${OFFLINE}" ] || \ |
| git fetch ${PULL_REPO} |
| |
| [ -n "${PUSH_REPO}" ] || \ |
| { [ -n "${PUSH_BRANCH}" ] && PUSH_REPO=$(echo $PUSH_BRANCH | cut -f 1 -d '/') ; } || \ |
| PUSH_REPO=origin && git config remote.${PUSH_REPO}.url > /dev/null || \ |
| unset PUSH_REPO |
| |
| # set default PUSH BRANCH if needed |
| [ -z "${PUSH_REPO}" ] || [ -n "${PUSH_BRANCH}" ] || \ |
| PUSH_BRANCH=${PUSH_REPO}/master |
| |
| |
| THIS_BRANCH_DISPLAY_NAME=$(git symbolic-ref --short -q HEAD) |
| |
| if [ -z "${THIS_BRANCH_DISPLAY_NAME}" ] ; then |
| THIS_BRANCH_DISPLAY_NAME="(DETACHED)" |
| unset PUSH_BRANCH |
| else |
| PUSH_REPO=$(echo $PUSH_BRANCH | cut -f 1 -d '/') |
| # fetch the branch's upstream repo if different to the master |
| [ -n "${OFFLINE}" ] || [ -z "${PUSH_REPO}" ] || [ "${PUSH_REPO}" == "${PULL_REPO}" ] || git fetch ${PUSH_REPO} |
| fi |
| [ -n "${PUSH_BRANCH}" ] && PUSH_BRANCH_DISPLAY_NAME="${PUSH_BRANCH}" || PUSH_BRANCH_DISPLAY_NAME="(no upstream)" |
| [ -n "$PUSH_BRANCH}" ] || PUSH_BRANCH=${PULL_BRANCH} |
| [ -n "$PULL_BRANCH}" ] || PULL_BRANCH=${PUSH_BRANCH} |
| |
| TMP=/tmp/git-summary-$(uuidgen) |
| rm -f ${TMP}-* |
| touch ${TMP}-{1-pull-source-ahead,2-up,3-push-target-ahead,4-local} |
| |
| # basically there are 6 modes where a commit is not in all three repos |
| # * pull ahead - show ^ - IMPORTANT means new commits we must get |
| # * pull behind - show > - IMPORTANT means PR not yet merged |
| # * push ahead - show ?> - WEIRD means someone else pushed to our origin |
| # * push behind - show < - means push needed to bring origin back into sync |
| # * local ahead - show * - IMPORTANT means new commits we must push |
| # * local behind - show ?^ - WEIRD means someone else pushed to our origin |
| |
| if [ -z "${PULL_BRANCH}" ] ; then |
| true # nothing to do |
| else |
| if [ "${PULL_BRANCH}" == "${PUSH_BRANCH}" ] ; then |
| git log --pretty=" ^ %h %aN, %ar: %s" ..${PULL_BRANCH} "$@" >> ${TMP}-2-up |
| git log --pretty=" * %h %aN, %ar: %s" ${PULL_BRANCH}.. "$@" >> ${TMP}-4-local |
| else |
| # items in pull source not yet in our push target, will be correlated with items not in local |
| git log --pretty=" < %h %aN, %ar: %s" ${PUSH_BRANCH}..${PULL_BRANCH} "$@" >> ${TMP}-1-pull-source-ahead |
| git log --pretty="?^ %h %aN, %ar: %s" ..${PULL_BRANCH} "$@" >> ${TMP}-1-pull-source-ahead-of-local |
| |
| # now filter items from 1-pull-source-ahead-of-local correcting items in 1-pull-source-ahead |
| cat ${TMP}-1-pull-source-ahead-of-local | while read line ; do |
| word=$(echo "$line" | awk '{print $2}') |
| if grep $word ${TMP}-1-pull-source-ahead > /dev/null 2> /dev/null ; then |
| # if missing from both, show ^ |
| sed -i .bak "s/ < $word/ ^ $word/" ${TMP}-1-pull-source-ahead |
| else |
| # in pull and push but not local, show ? |
| echo " $line" >> ${TMP}-2-up |
| fi |
| done |
| |
| git log --pretty=" > %h %aN, %ar: %s" ${PULL_BRANCH}..${PUSH_BRANCH} "$@" >> ${TMP}-3-push-target-ahead |
| git log --pretty="%h %aN, %ar: %s" ..${PUSH_BRANCH} "$@" >> ${TMP}-3-push-target-ahead-of-local |
| cat ${TMP}-3-push-target-ahead | while read line ; do |
| word=$(echo "$line" | awk '{print $3}') |
| if grep $word ${TMP}-3-push-target-ahead-of-local > /dev/null 2> /dev/null ; then |
| # if missing from both, show ?> |
| sed -i .bak "s/ > $word/?> $word/" ${TMP}-3-push-target-ahead |
| fi |
| done |
| |
| |
| git log --pretty="%h %aN, %ar: %s" ${PUSH_BRANCH}.. "$@" >> ${TMP}-4-local-ahead-of-push |
| git log --pretty="%h %aN, %ar: %s" ${PULL_BRANCH}.. "$@" >> ${TMP}-4-local-ahead-of-pull |
| cat ${TMP}-4-local-ahead-of-push | while read line ; do |
| word=$(echo "$line" | awk '{print $1}') |
| if grep $word ${TMP}-4-local-ahead-of-pull > /dev/null 2> /dev/null ; then |
| # in local but not push or pull |
| echo " * $line" >> ${TMP}-4-local |
| # else ignore, we reported it above (in local and pull target, but not in push) |
| fi |
| done |
| |
| rm -f ${TMP}-{1,2,3,4}-*{-of-*,.bak} |
| fi |
| fi |
| |
| git status --porcelain --ignore-submodules "$@" > ${TMP}-5-commits |
| cat ${TMP}-* > ${TMP} |
| |
| # TODO unreliable way to get project name |
| SUMMARY=$(basename $(pwd)) |
| |
| # append files if needed |
| [ -z "$1" ] || SUMMARY="${SUMMARY} ($@)" |
| |
| SUMMARY="${SUMMARY}: ${THIS_BRANCH_DISPLAY_NAME} -> ${PUSH_BRANCH_DISPLAY_NAME}" |
| [ -z ${PULL_BRANCH} ] || [ "${PULL_BRANCH}" == "${PUSH_BRANCH}" ] || |
| SUMMARY=${SUMMARY}" -> ${PULL_BRANCH}" |
| |
| if [ -s ${TMP} ] ; then |
| AHEAD=$(wc ${TMP}-1-* | awk '{print $1}') |
| BEHIND=$(wc ${TMP}-3-* | awk '{print $1}') |
| UP=$(wc ${TMP}-2-* | awk '{print $1}') |
| LOCAL=$(wc ${TMP}-4-* | awk '{print $1}') |
| [ "${AHEAD}" == "0" ] || COUNTS="push target ${AHEAD} behind upstream" |
| [ "${BEHIND}" == "0" ] || COUNTS="${COUNTS}${COUNTS:+, }upstream ${BEHIND} behind push target" |
| [ "${UP}" == "0" ] || COUNTS="${COUNTS}${COUNTS:+, }local ${UP} behind" |
| [ "${LOCAL}" == "0" ] || COUNTS="${COUNTS}${COUNTS:+, }local ${LOCAL} unpushed" |
| echo "${SUMMARY} (${COUNTS:-uncommitted changes only})" |
| cat ${TMP} | sed 's/^/ /' |
| else |
| echo "${SUMMARY} (all up to date)" |
| fi |
| rm -f ${TMP} ${TMP}-* |
| |
| # submodules ignored; if using them, set up |
| |
| if [ "$RECURSE" ] ; then |
| echo |
| git submodule --quiet foreach --recursive "${THIS_COMMAND}" |
| fi |
| |