123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121 |
- #!/bin/bash
- readonly BE_VERBOSE=0
- readonly USAGE="git-branch-diffs [-b <branch>] [-d] [-p <path>]* [<\"search-regex\">]
- Report which files differ per-branch compared to the master branch.
- OPTIONS:
- -b <branch>
- Consider only the specified branch.
- -d
- Display verbose diffs (like \`git diff\`)
- -p <path>
- Consider only the specified filesystem path.
- The argument may be absolute, or relative to the present working directory.
- This option may be given multiple times.
- <\"search-regex\">
- Filter returned results (like piping to \`grep\`).
- "
- readonly AMBIGUOUS_RX="warning: refname '.*' is ambiguous."
- ShowDiffs=0 # HhandleCliArgs()
- Branches=() # HandleCliArgs()
- Paths=() # HandleCliArgs()
- SearchRegex='' # HandleCliArgs()
- LOG() { (( BE_VERBOSE )) && echo -e "${@}" >&2 ; }
- HandleCliArgs()
- {
- ShowDiffs=0
- local filter_branch='*'
- while getopts 'b:dp:' arg
- do case "${arg}" in
- b) filter_branch=${OPTARG} ;;
- d) ShowDiffs=1 ;;
- p) Paths+=("${OPTARG}") ;;
- *) echo "${USAGE}" >&2 ; exit 1 ;;
- esac
- done
- shift $((OPTIND - 1))
- [[ "${filter_branch}" != master ]] || ! echo "branch may not be 'master'" || exit 1
- SearchRegex=$( [[ -n "$1" ]] && echo "$1" || echo '.*' )
- Branches=( $(git branch --list "${filter_branch}" | grep -v master | sed 's|^\* | |') )
- }
- IsUnambiguousRef() # (branch)
- {
- local branch=$1
- local ambiguous_warning="$(git log -1 ${branch} 2>&1 1>/dev/null | grep "${AMBIGUOUS_RX}")"
- [[ -n "${ambiguous_warning}" ]] && echo "${ambiguous_warning}" >&2
- [[ -z "${ambiguous_warning}" ]]
- }
- GitCommonAncestor() # ([-s] ref_a ref_b)
- {
- local is_short=$( [[ "$1" == '-s' ]] ; echo $(( ! $? )) ) ; (( is_short )) && shift ;
- local fmt=$( (( is_short )) && echo "%h" || echo "%h %ad %an [%G?] %s %d" )
- local ref_a=$1
- local ref_b=$( [[ -n "$2" ]] && echo $2 || echo HEAD )
- [[ -n "${ref_a}" ]] || ! echo "no ref specified" || return 1
- git log -n1 --format="${fmt}" --date=short $(git merge-base ${ref_a} ${ref_b})
- }
- Main()
- {
- local branch
- local common_ancestor
- local changed_files
- local dt
- local changed_file
- local change_msgs=()
- LOG "\n=== processing (${#Branches[*]}) branches ===\n"
- for branch in ${Branches[*]}
- do IsUnambiguousRef ${branch} || continue
- LOG "scanning branch: ${branch}"
- # detect diffs
- common_ancestor=$(GitCommonAncestor -s master ${branch})
- if (( ${#Paths[@]} ))
- then changed_files=( $(git diff --name-only ${common_ancestor} ${branch} -- "${Paths[@]}") )
- else changed_files=( $(git diff --name-only ${common_ancestor} ${branch} ) )
- fi
- (( ${#changed_files[@]} )) || continue
- # present results
- if (( ShowDiffs ))
- then dt=$(git log -1 --format='%cs' ${branch})
- echo '---' ; printf "[${branch}]: %s\n" "${dt}" ;
- git diff ${common_ancestor} ${branch} "${Paths[@]}"
- else for changed_file in "${changed_files[@]}"
- do if [[ "${changed_file}" =~ ${SearchRegex} ]]
- then dt=$(git log -1 --format='%cs' ${branch} -- "${changed_file}")
- change_msgs+=( "[${branch}]: ${dt} ${changed_file}" )
- fi
- done
- fi
- done
- LOG "\n\n=== per-branch file diffs ===\n"
- (( ${#change_msgs[@]} )) && printf "%s\n" "${change_msgs[@]}" || echo "none"
- }
- HandleCliArgs "$@"
- if (( ShowDiffs ))
- then Main "$@"
- else Main "$@" | column -t
- fi
|