-
-
Save rsitze/6a9d0ac280bca850e2471634a3f83204 to your computer and use it in GitHub Desktop.
Find nearest (in commit history) parent branch of a given base branch using regex patterns for likely parent branch names. See https://stackoverflow.com/a/68673744/1448212
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/local/bin/bash | |
# For a walk through of what this does, how it works, | |
# see: https://stackoverflow.com/a/68673744/1448212 | |
# git show-branch supports 29 branches; reserve 1 for current branch | |
GIT_SHOW_BRANCH_MAX=28 | |
CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD)" | |
if (( $? != 0 )); then | |
echo "Failed to determine git branch; is this a git repo?" >&2 | |
exit 1 | |
fi | |
## | |
# Given Params: | |
# EXCEPT : $1 | |
# VALUES : $2..N | |
# | |
# Return all values except EXCEPT, in order. | |
# | |
function valuesExcept() { | |
local except=$1 ; shift | |
for value in "$@"; do | |
if [[ "$value" != "$except" ]]; then | |
echo $value | |
fi | |
done | |
} | |
## | |
# Given Params: | |
# BASE_BRANCH : $1 : base branch; default is current branch | |
# BRANCHES : [ $2 .. $N ] : list of unique branch names (no duplicates); | |
# perhaps possible parents. | |
# Default is all branches except base branch. | |
# | |
# For the most recent commit in the commit history for BASE_BRANCH that is | |
# also in the commit history of at least one branch in BRANCHES: output all | |
# BRANCHES that share that commit in their commit history. | |
# | |
function nearestCommonBranches() { | |
local BASE_BRANCH | |
if [[ -z "${1+x}" || "$1" == '.' ]]; then | |
BASE_BRANCH="$CURRENT_BRANCH" | |
else | |
BASE_BRANCH="$1" | |
fi | |
shift | |
local -a CANDIDATES | |
if [[ -z "${1+x}" ]]; then | |
CANDIDATES=( $(git rev-parse --symbolic --branches) ) | |
else | |
CANDIDATES=("$@") | |
fi | |
local BRANCHES=( $(valuesExcept "$BASE_BRANCH" "${CANDIDATES[@]}") ) | |
local BRANCH_COUNT=${#BRANCHES[@]} | |
if (( $BRANCH_COUNT > $GIT_SHOW_BRANCH_MAX )); then | |
echo "Too many branches: limit $GIT_SHOW_BRANCH_MAX" >&2 | |
exit 1 | |
fi | |
local MAP=( $(git show-branch --topo-order "${BRANCHES[@]}" "$BASE_BRANCH" \ | |
| tail -n +$(($BRANCH_COUNT+3)) \ | |
| sed "s/ \[.*$//" \ | |
| sed "s/ /_/g" \ | |
| sed "s/*/+/g" \ | |
| egrep '^_*[^_].*[^_]$' \ | |
| head -n1 \ | |
| sed 's/\(.\)/\1\n/g' | |
) ) | |
for idx in "${!BRANCHES[@]}"; do | |
## to include "merge", symbolized by '-', use | |
## ALT: if [[ "${MAP[$idx]}" != "_" ]] | |
if [[ "${MAP[$idx]}" == "+" ]]; then | |
echo "${BRANCHES[$idx]}" | |
fi | |
done | |
} | |
# Usage: gitr [ baseBranch [branchToConsider]* ] | |
# baseBranch: '.' (no quotes needed) corresponds to default current branch | |
# branchToConsider* : list of unique branch names (no duplicates); | |
# perhaps possible (bias?) parents. | |
# Default is all branches except base branch. | |
#nearestCommonBranches "${@}"~ | |
## | |
# Given: | |
# BASE_BRANCH : $1 : first param on every batch | |
# BRANCHES : [ $2 .. $N ] : list of unique branch names (no duplicates); | |
# perhaps possible parents | |
# Default is all branches except base branch. | |
# | |
# Output all BRANCHES that share that commit in their commit history. | |
# | |
function repeatBatchingUntilStableResults() { | |
local BASE_BRANCH="$1" | |
shift | |
local -a CANDIDATES | |
if [[ -z "${1+x}" ]]; then | |
CANDIDATES=( $(git rev-parse --symbolic --branches) ) | |
else | |
CANDIDATES=("$@") | |
fi | |
local BRANCHES=( $(valuesExcept "$BASE_BRANCH" "${CANDIDATES[@]}") ) | |
local SIZE=$GIT_SHOW_BRANCH_MAX | |
local COUNT=${#BRANCHES[@]} | |
local LAST_COUNT=$(( $COUNT + 1 )) | |
local NOT_DONE=1 | |
while (( $NOT_DONE && $COUNT < $LAST_COUNT )); do | |
NOT_DONE=$(( $SIZE < $COUNT )) | |
LAST_COUNT=$COUNT | |
local -a BRANCHES_TO_BATCH=( "${BRANCHES[@]}" ) | |
local -a AGGREGATE=() | |
while (( ${#BRANCHES_TO_BATCH[@]} > 0 )); do | |
local -a BATCH=( "${BRANCHES_TO_BATCH[@]:0:$SIZE}" ) | |
AGGREGATE+=( $(nearestCommonBranches "$BASE_BRANCH" "${BATCH[@]}") ) | |
BRANCHES_TO_BATCH=( "${BRANCHES_TO_BATCH[@]:$SIZE}" ) | |
done | |
BRANCHES=( "${AGGREGATE[@]}" ) | |
COUNT=${#BRANCHES[@]} | |
done | |
if (( ${#BRANCHES[@]} > $SIZE )); then | |
echo "Unable to reduce candidate branches below MAX for git-show-branch" >&2 | |
echo " Base Branch : $BASE_BRANCH" >&2 | |
echo " MAX Branches: $SIZE" >&2 | |
echo " Candidates : ${BRANCHES[@]}" >&2 | |
exit 1 | |
fi | |
echo "${BRANCHES[@]}" | |
} | |
#repeatBatchingUntilStableResults "$@" | |
## | |
# Given Params: | |
# BASE_BRANCH : $1 : base branch | |
# REGEXs : $2 [ .. $N ] : regex(s) | |
# | |
# Output: | |
# - git branches matching at least one of the regex params | |
# - base branch is excluded from result | |
# - order: branches matching the Nth regex will appear before | |
# branches matching the (N+1)th regex. | |
# - no duplicates in output | |
# | |
function expandUniqGitBranches() { | |
local -A BSET[$1]=1 | |
shift | |
local ALL_BRANCHES=$(git rev-parse --symbolic --branches) | |
for regex in "$@"; do | |
for branch in $ALL_BRANCHES; do | |
## RE: -z ${BSET[$branch]+x ... ; presumes ENV 'x' is not defined | |
if [[ $branch =~ $regex && -z "${BSET[$branch]+x}" ]]; then | |
echo "$branch" | |
BSET[$branch]=1 | |
fi | |
done | |
done | |
} | |
## | |
# Params: | |
# BASE_BRANCH: $1 : "." equates to the current branch; | |
# REGEXS : $2..N : regex(es) corresponding to other to include | |
# | |
function findBranchesSharingFirstCommonCommit() { | |
if [[ -z "$1" ]]; then | |
echo "Usage: findBranchesSharingFirstCommonCommit ( . | baseBranch ) [ regex [ ... ] ]" >&2 | |
exit 1 | |
fi | |
local BASE_BRANCH | |
if [[ -z "${1+x}" || "$1" == '.' ]]; then | |
BASE_BRANCH="$CURRENT_BRANCH" | |
else | |
BASE_BRANCH="$1" | |
fi | |
shift | |
local REGEXS | |
if [[ -z "$1" ]]; then | |
REGEXS=(".*") | |
else | |
REGEXS=("$@") | |
fi | |
local BRANCHES=( $(expandUniqGitBranches "$BASE_BRANCH" "${REGEXS[@]}") ) | |
## nearestCommonBranches can also be used here, if batching not used. | |
repeatBatchingUntilStableResults "$BASE_BRANCH" "${BRANCHES[@]}" | |
} | |
#findBranchesSharingFirstCommonCommit "$@" | |
# bash associative arrays maintain key/entry order. | |
# So, use two maps, values correlated by index: | |
declare -a MAP_BASE_BRANCH_REGEX=( "^master$" \ | |
"^support/.*$" \ | |
"^hotfix/.*$" \ | |
"^release/.*$" \ | |
"^bugfix/.*$" \ | |
"^feature/.*$" \ | |
"^.*$" ) | |
declare -a MAP_BRANCHES_REGEXS=("" \ | |
"^master$" \ | |
"^support/.*$ ^master$" \ | |
"^support/.*$ ^master$" \ | |
"^release/.*$" \ | |
"^release/.*$ ^support/.*$ ^master$" \ | |
"^release/.*$ ^support/.*$ ^master$" ) | |
function findBranchesByBaseBranch() { | |
local BASE_BRANCH | |
if [[ -z "${1+x}" || "$1" == '.' ]]; then | |
BASE_BRANCH="$CURRENT_BRANCH" | |
else | |
BASE_BRANCH="$1" | |
fi | |
for idx in "${!MAP_BASE_BRANCH_REGEX[@]}"; do | |
local BASE_BRANCH_REGEX=${MAP_BASE_BRANCH_REGEX[$idx]} | |
if [[ "$BASE_BRANCH" =~ $BASE_BRANCH_REGEX ]]; then | |
local BRANCHES_REGEXS=( ${MAP_BRANCHES_REGEXS[$idx]} ) | |
if (( ${#BRANCHES_REGEXS[@]} > 0 )); then | |
findBranchesSharingFirstCommonCommit $BASE_BRANCH "${BRANCHES_REGEXS[@]}" | |
fi | |
break | |
fi | |
done | |
} | |
## | |
# usage: gitr [ <basebranch> ] | |
# | |
# where default basebranch defaults to current branch. | |
# | |
findBranchesByBaseBranch "$1" |
I have the same problem as above
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@rsitze How to run this on a windows machine?
I have copied the above content in a file and named it a parent.sh. then I opened git bash and typed sh parent.sh. I am getting this error.
error: no matching refs with ^release/.*$