\+$ J <- feature/b
/
H
/ \
/ I <- feature/a
/
D---E <- master
/ \
/ F---G <- hotfix
/
A---B---C <- feature/f, release/2, release/3
\ \
\ W--X <- feature/g
\
\ M <- support/1
\ /
K---L <- release/4
\
\ T---U---V <- feature/e
\ /
N---O
\
P <- feature/c
\
Q---R---S <- feature/d
对示例图的无偏结果
假设脚本在可执行文件gitr
中,则运行以下命令:
gitr <baseBranch>
针对不同的分支B
,我们得到了以下结果:
GIVEN B | 共享提交C | 历史中包含C的分支!B? |
---|
feature/a | H | feature/b |
feature/b | H | feature/a |
feature/c | P | feature/d |
feature/d | P | feature/c |
feature/e | O | feature/c,feature/d |
feature/f | C | feature/a,feature/b,feature/g,hotfix,master,release/2,release/3 |
feature/g | C | feature/a,feature/b,feature/f,hotfix,master,release/2,release/3 |
hotfix | D | feature/a,feature/b,master |
master | D | feature/a,feature/b,hotfix |
release/2 | C | feature/a,feature/b,feature/f,feature/g,hotfix,master,release/3 |
release/3 | C | feature/a,feature/b,feature/f,feature/g,hotfix,master,release/2 |
release/4 | L | feature/c,feature/d,feature/e,support/1 |
support/1 | L | feature/c,feature/d,feature/e,release/4 |
批量分支
[由于它最适合在此时呈现,因此呈现在最终脚本中。 此部分不是必需的,请随意跳过。]
git show-branch
限制了29个分支。这可能会成为某些人(没有评判,只是说!)的阻碍。
在某些情况下,我们可以通过将分支分组成批次来改进结果。
- 每个分支都必须提交
BASE_BRANCH
。 - 如果一个存储库中有大量分支,则这可能具有有限的价值。
- 如果您发现其他限制分支(将被分批的分支)的方法,则可以提供更多价值。
- 上一个观点适用于我的用例,所以继续前进!
此机制不是完美的,随着结果大小接近最大值(29),请预期它会失败。详见下文
批处理解决方案
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 "$@"
exit 0
它的工作原理
重复操作直到结果稳定
- 将
BRANCHES
拆分为每个批次包含GIT_SHOW_BRANCH_MAX
(也称为SIZE
)个元素
- 调用
nearestCommonBranches BASE_BRANCH BATCH
- 将结果聚合成一个新的(更小的?)分支集
可能会出现的问题
如果聚合分支的数量超过了最大值SIZE
,
且进一步的拆分/处理无法减少该数量,则可能:
- 已经聚合的分支是解决方案,
但
git show-branch
无法验证,或者
- 每个批处理未减少;
可能需要从一个批处理中提取分支来帮助减少另一个
(diff merge base);当前算法放弃并失败。
考虑替代方案
将一个基本分支与所有其他感兴趣的分支进行配对,为每个配对确定一个提交节点(合并基础);按提交历史顺序排序合并基础集,获取最接近的节点,并确定与该节点相关联的所有分支。
我从后见之明的角度提出这个方案。
这可能真的是正确的方法。
我正在前进;
也许在当前主题之外有价值。
一个有偏见的问题
您可能已经注意到,早期脚本中的核心函数nearestCommonBranches
回答了不止一个问题Q1。
实际上,该函数回答了一个更普遍的问题:
Q2
给定一个分支B
和
一组有序的(无重复)分支P
(B
不在P
中):
考虑最接近B'HEAD
(C
可以是B'HEAD
)
且被P
中的分支共享的提交C
:
按照P的顺序,P中哪些分支在其提交历史中具有C?
选择P
会带来偏见或描述(有限的)惯例。
为了匹配您的所有偏见/惯例特征,可能需要额外的工具,超出了此讨论的范围。
建模简单的偏见/惯例
偏见因不同的组织和惯例而异,
以下内容可能不适合于您的组织。
如果没有其他,也许这里的一些想法可以帮助
您找到适合您需求的解决方案。
有偏见的解决方案;基于分支命名约定的偏见
可能可以将偏见映射到使用的命名约定中并从中提取偏见。
基于P
的偏见(其他分支名称)
我们将需要它用于下一步,
因此让我们看看通过正则表达式过滤分支名称可以做什么。
上面的代码和下面的新代码组合起来可在gist: gitr中
#
# Remove/comment-out the function call at the end of script,
# and append this to the end.
##
##
# 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 "$@"
示例图表的偏向结果
让我们考虑有序集合
P = { ^release/.*$ ^support/.*$ ^master$ }
假设脚本(所有部分)在可执行文件gitr
中,则运行:
gitr <baseBranch> '^release/.*$' '^support/.*$' '^master$'
对于不同的分支B
,我们得到以下结果:
GIVEN B | Shared Commit C | Branches P with C in their history (in order) |
---|
feature/a | D | master |
feature/b | D | master |
feature/c | L | release/4、support/1 |
feature/d | L | release/4、support/1 |
feature/e | L | release/4、support/1 |
feature/f | C | release/2、release/3、master |
feature/g | C | release/2、release/3、master |
hotfix | D | master |
master | C | release/2、release/3 |
release/2 | C | release/3、master |
release/3 | C | release/2、master |
release/4 | L | support/1 |
support/1 | L | release/4 |
这让我们离得出一个明确的答案更近了一步。对于发布分支的响应还不理想。让我们再深入一步。
以BASE_NAME
和P
为偏好
可以采取的一种方法是为不同的基本名称使用不同的P
。 让我们设计一个方案。
约定
免责声明:我不是git流派的纯粹主义者,请谅解
- 支持分支应从master分支中分支出。
- 热修复分支应从支持分支或master分支分支出。
- 发布分支应从支持分支或master分支分支出。
- 可能会有多个发布分支共用一个公共提交;也就是在相同的时间从master分支分支出来。
- 错误修复分支应从发布分支中分支出。
- 特性分支可以从特性、发布、支持或master分支中分支出:
- 为了“父级”这一目的,一个特性分支不能被确定为另一个特性分支的父级(请参见初始讨论)。
- 因此:跳过特性分支,寻找“父级”在发布、支持和/或master分支中。
- 将任何其他分支名称视为工作分支,其约定与特性分支相同。
让我们看看我们用git
能走多远:
基础分支模式 |
父分支,按顺序排列 |
注释 |
^master$ |
n/a |
没有父分支 |
^support/.*$ |
^master$ |
|
^hotfix/.*$ |
^support/.*$ ^master$ |
优先使用支持分支而不是主分支(排序) |
^release/.*$ |
^support/.*$ ^master$ |
优先使用支持分支而不是主分支(排序) |
^bugfix/.*$ |
^release/.*$ |
|
^feature/.*$ |
^release/.*$ ^support/.*$ ^master$ |
|
^.*$ |
^release/.*$ ^support/.*$ ^master$ |
冗余的,但保持设计上的独立性 |
脚本
组合先前的代码和下面的新代码可在gist: gitp中获得
#
# Remove/comment-out the function call at the end of script,
# and append this to the end.
##
# 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
}
findBranchesByBaseBranch "$1"
示例图的偏倚结果
假设所有部分的脚本都在可执行文件gitr
中,则运行:
gitr <baseBranch>
对于不同的分支B
,我们得到了以下结果:
GIVEN B | Shared Commit C | Branches P with C in their history (in order) |
---|
feature/a | D | master |
feature/b | D | master |
feature/c | L | release/4, support/1 |
feature/d | L | release/4, support/1 |
feature/e | L | release/4, support/1 |
feature/f | C | release/2, release/3, master |
feature/g | C | release/2, release/3, master |
hotfix | D | master |
master | | (空白,没有值) |
release/2 | C | master |
release/3 | C | master |
release/4 | L | support/1 |
support/1 | L | master |
为胜利进行重构!
机遇!
在这个例子中,发布分支与多个其它分支共享一个公共提交:发布、支持或主分支。
让我们重新评估已使用的惯例,并稍微收紧它们。
考虑以下git
使用惯例:
创建新的发布分支时:立即创建新的提交;也许更新一个版本或README文件。这可以确保发布的特征/工作分支(从发布中分支出)将与发布分支共享提交,而不是支持或主分支的提交之前。
例如:
G
/
E <- release/1
/
A
\
F <- release/2
从发布/1分支创建的功能分支不能有一个包含发布/1(其父级)和master或release/2的公共提交。
按照这些约定,对于每个分支都提供了一个结果,即父级。
使用工具和约定完成后,我可以生活在一个OCF友好的结构化git世界中。
你的情况可能会有所不同!
别离的思考
- 要点
首先:我得出结论,
除了这里介绍的内容之外,
某个时间点上可能需要接受可能存在多个分支的现实。
- 也许所有潜在分支都可以执行验证;
可以应用“至少一个”或“全部”或其他规则。
就像这样的一周,我真的认为是时候学习Python了。
git log --first-parent
命令,它会显示当前分支的所有提交记录,然后显示父分支及其提交记录。 - Vipul Patil