我在公司经常面临这种情况,我们找到了两个主要解决方案来解决这个问题。它们都利用了git的能力,在某些情况下保留提交历史记录。顺便说一句,好的做法是永远不要推送指向可删除分支(即功能分支)的子模块引用。
我假设,像我一样,你不想推送feature_branch,以免污染你的远程仓库,并且对于清除所有旧的已推送的feature_branch的好奇工作伙伴而言,具有鲁棒性。
问题在于当你进行rebase时,提交会被重新应用到master历史的顶部。它们是新的提交(假设为X'、Y'和Z'),具有新的哈希值,并且分支头移动到Z'的顶部。因此,X、Y、Z将停留在一个死分支上,并将随着时间(默认设置为90天)而被删除。除此之外,当你推送你的rebased分支时,默认情况下它们不会被推送。
1. 经典、干净的方法:使用合并而不是rebase
进行合并操作。E、F、G将链接到合并提交。这样,即使分支被删除(在这种情况下,只有分支的引用),X、Y、Z也会永久保留并且不会改变,但代价是主分支上出现了非线性历史记录。当您推送父分支时,X、Y、Z也将被推送。
- 使用标签保留历史记录中的提交
保留X、Y、Z提交历史记录的一种解决方案是对Z打标签(在rebase之前(标签将幸存于此),或之后(但您必须在reflog中重新找到它),都可以)。如果随后推送此标签,则X、Y、Z将不会被清除。这相当于一种比较脏的方式,并且有副作用,但是可以完成任务。当然,您必须推送该标签!(说实话,这与推送分支相当等效。唯一的区别是您可以具体命名,例如DONT_DELETE_THIS_FOOL ;))
以下是一个小脚本,您可以运行它以确保不会丢失任何子模块依赖项(通过为每个子模块打标签并推送它们)(使用-h获取帮助):
#!/bin/bash
check_tags () {
slength=${#1}
slength=$((slength+1))
refmajor=`echo $2`
refminor=`echo $3`
refrevision=`echo $4`
echo "Check for anterior tags in $5..."
for tag in $(git tag -l -n "$1*.*.*" | cut -d" " -f1 | cut -c "$slength"- ); do
temptagMajor=`echo $tag | cut -d. -f1`
temptagMinor=`echo $tag | cut -d. -f2`
temptagRevision=`echo $tag | cut -d. -f3`
if [[ temptagMajor -gt tagMajor ]] || [[ temptagMajor -eq tagMajor && temptagMinor -gt tagMinor ]] || [[ temptagMajor -eq tagMajor && temptagMinor -eq tagMinor && temptagRevision -gt tagRevision ]]; then
tagMajor=$temptagMajor
tagMinor=$temptagMinor
tagRevision=$temptagRevision
fi
done
echo "Latest versioning tag found : $1$tagMajor.$tagMinor.$tagRevision (want to apply $1$refmajor.$refminor.$refrevision)"
if [[ tagMajor -gt refmajor ]]; then
echo "Cannot tag with $1$refmajor.$refminor.$refrevision : anterior tag with greater major revision number (found $tagMajor, wanted $refmajor). Use -f to ignore."
return 1
elif [[ tagMajor -eq refmajor ]] && [[ tagMinor -gt refminor ]]; then
echo "Cannot tag with $1$refmajor.$refminor.$refrevision : anterior tag with equal major revision number but greater minor revision number (found $tagMinor, wanted $refminor). Use -f to ignore."
return 1
elif [[ tagMajor -eq refmajor ]] && [[ tagMinor -eq refminor ]] && [[ tagRevision -gt refrevision ]]; then
echo "Cannot tag with $1$refmajor.$refminor.$refrevision : anterior tag with equal major and minor revision number, but greater revision number (found $tagRevision, wanted $refrevision). Use -f to ignore."
return 1
elif [[ tagMajor -eq refmajor ]] && [[ tagMinor -eq refminor ]] && [[ tagRevision -eq refrevision ]]; then
echo "Cannot tag with $1$refmajor.$refminor.$refrevision : anterior tag with equal major, minor, and revision number (found $1$tagMajor.$tagMinor.$tagRevision, wanted $1$refmajor.$refminor.$refrevision). Use -f to ignore."
return 1
else
return 0
fi
}
function subcheck() {
if [[ ! -d .git ]]; then
echo "not a git repo"
return 1
fi;
if ! [[ `git submodule status` ]]; then
echo 'no submodule'
return 1
fi
submodules=($(git config --file .gitmodules --get-regexp path | awk '{ print $2 }'))
currentDirectory=$(pwd)
for submodule in "${submodules[@]}"
do
printf "\n\nEntering '$submodule'\n"
cd "$currentDirectory/$submodule"
check_tags $1 $2 $3 $4 $submodule
if [[ $? -eq 1 ]]; then
cd "$currentDirectory"
return 1
fi
done
cd "$currentDirectory"
}
while getopts v:hfti: option
do
case "${option}"
in
v) VERSION=${OPTARG};;
h) HELP='help';;
f) FORCE='force';;
t) TEST='test';;
i) INDEX=${OPTARG};;
esac
done
printf "Tag release script v0.1\n"
if [ "$HELP" != "" ]; then
echo 'GIT Release Script'
echo "Options :"
echo 'Use -v to specify version (mandatory). Ex : "-v 1.0.2"'
echo 'Use -t to run unit test of -v inputs'
echo 'Use -f to force tagging / skip anterior tag versions check'
echo 'Use -i to specify index (optional). Ex : "-v 1.0.2 -i A" for a indA + v1.0.2 double tag.'
exit 1
fi
if [ "$HELP" != "" ]; then
array=( ".2.3" "1..3" "1.2." "A.2.3" "1.A.3" "1.2.A" "1A3.123.123" "123.1D3.123" "123.123.1A3" "nougatine" "1.3" )
arr=(${array[*]})
echo "Tested valudes : ${#arr[*]}"
for ix in ${!arr[*]}
do
printf " %s\n" "${arr[$ix]}"
. release_script.sh -v ${arr[$ix]}
done
fi
if [ "$VERSION" == "" ]; then
echo "Argument missing"
echo "Run -h for help"
exit 1
fi
major=`echo $VERSION | cut -d. -f1`
minor=`echo $VERSION | cut -d. -f2`
revision=`echo $VERSION | cut -d. -f3`
if [ -n "$(printf '%s\n' "$major" | sed 's/[0-9]//g')" ] || [ "$major" == "" ]; then
echo "Invalid major version argument (was \"$major\")"
echo "Run -h for help"
exit 1
fi
if [ -n "$(printf '%s\n' "$minor" | sed 's/[0-9]//g')" ] || [ "$minor" == "" ] ; then
echo "Invalid minor version argument (was \"$minor\")"
echo "Run -h for help"
exit 1
fi
if [ -n "$(printf '%s\n' "$revision" | sed 's/[0-9]//g')" ] || [ "$revision" == "" ]; then
echo "Invalid revision version argument (was \"$revision\")"
echo "Run -h for help"
exit 1
fi
git fetch --all
echo "Fetching tags"
git fetch --tag
tagMajor=0
tagMinor=0
tagRevision=0
versionLabel=v$VERSION
url=$(git config --get remote.origin.url)
basename=$(basename "$url" .git)
if [ "$FORCE" == "" ]; then
if [ "$INDEX" != "" ]; then
echo "Check for anterior index tag in $basename..."
for tag in $(git tag -l -n "ind$INDEX" ); do
if [[ "ind$INDEX" == $tag ]] ; then
echo "\"ind$INDEX\" tag already exists in $basename. Use -f to ignore."
exit 1
fi
done
echo "No conflict found for "ind$INDEX" index tag"
fi
check_tags v "$major" "$minor" "$revision" "$basename"
if [[ $? -eq 1 ]]; then
exit 1
fi
subcheck ${basename}_v $major $minor $revision
if [[ $? -eq 1 ]]; then
exit 1
fi
fi
versionLabel=v$VERSION
url=$(git config --get remote.origin.url)
basename=$(basename "$url" .git)
echo "Tagging project $basename (\"$versionLabel\")"
git tag $versionLabel
if [ "$INDEX" != "" ]; then
echo "Tagging index $INDEX"
git tag "ind$INDEX"
fi
echo "Tagging submodules (\"${basename}_$versionLabel\")"
git submodule foreach "git tag ${basename}_$versionLabel || :"
echo "Pushing project tag"
git push --tags
echo "Pushing submodules tags"
git submodule foreach 'git push --tags || :'
git branch old-feature feature@{last.monday}
或其他方法。 - jthill