如何完整的将AOSP源码汇入本地gerrit

972 阅读7分钟

0. 文章背景

在日常工作中,有时候我们需要将完整的AOSP源码汇入私有gerrit中,以方便各类开发人员拉取,由于AOSP源码涉及的repo仓库多(截止笔者发稿,AOSP关联仓库已达到1100多个),一次性汇入gerrit耗时长,难度大,尤其是在源码汇入gerrit中,遇到了诸多问题,为了解决这些问题,我参考前人的相关文档《将安卓代码导入gerrit》,修改了汇入gerrit的shell脚本,在此分享给大家,以便后人汇入gerrit时少走弯路。

为了方便AOSP 开发者初次使用,我将aosp相关源码写成了shell工具,读者可以自行取用,该工具还在迭代中,欢迎各位读者PR。
仓库地址:github.com/zyf41919431…

clone该项目后执行bash main.sh,按照提示操作,示例:
2023-07-03_23-16.png

0.1 先期准备

执行gerrit汇入您至少需要先具备如下环境

gerrit AOSP仓库

【图】gerrit AOSP仓库一揽

1. gerrit汇入AOSP流程

1.1 创建repo仓库群

原始AOSP仓库具有1100多个仓库,这些仓库需要在私有gerrit中创建,除此之外,我们还需做如下操作:

  • 新建一个manifests仓库以保存我们的manifests文件
  • 新建一个AOSP仓库作为父仓库,用于gerrit权限管理

您可以通过如下脚本进行创建:

注意

  1. 请在AOSP源码根目录执行该文件
  2. 请对<>标识的内容按实际情况替换
  3. 该脚本执行结果幂等,可以重复执行而不影响最终结果
LOCAL_PATH=`pwd`
MANIFEST_XML_FILE=$LOCAL_PATH/.repo/manifests/default.xml
 
USER_NAME="<Your gerrit username>"
SERVER_IP="<1.1.1.1>"
SERVER_PORT="<29418>"
 
OUTPUT_PROJECT_LIST_FILE_NAME=$LOCAL_PATH/project_list_name
OUTPUT_PROJECT_LIST_FILE_PATH=$LOCAL_PATH/project_list_path
 
function getNameAndPath()
{
    echo > $OUTPUT_PROJECT_LIST_FILE_NAME
    echo > $OUTPUT_PROJECT_LIST_FILE_PATH
 
    while read LINE
    do
        command_line=`echo $LINE | grep "<project"`
        if [ "$command_line" ]
        then
            #echo $LINE
 
            reposity_name_sec=${LINE#*name=\"}
            reposity_path_sec=${LINE#*path=\"}
 
            if [ "$reposity_name_sec" ] && [ "$reposity_path_sec" ]
            then
                reposity_name=${reposity_name_sec%%\"*}
                reposity_path=${reposity_path_sec%%\"*}
                echo "$reposity_name" >> $OUTPUT_PROJECT_LIST_FILE_NAME
                echo "$reposity_path" >> $OUTPUT_PROJECT_LIST_FILE_PATH
            fi
        fi
    done  < $MANIFEST_XML_FILE
}
 
function creatEmptyGerritProject()
{
    # 建立父仓库 AOSP,方便gerrita权限管理
    echo "ssh -p $SERVER_PORT $USER_NAME@$SERVER_IP gerrit create-project --permissions-only AOSP"
    ssh -p $SERVER_PORT $USER_NAME@$SERVER_IP gerrit create-project --permissions-only AOSP

    # 建立单独的 manifests 仓库,风格与AOSP保持一致
    echo "ssh -p $SERVER_PORT $USER_NAME@$SERVER_IP gerrit create-project platform/manifests"
    ssh -p $SERVER_PORT $USER_NAME@$SERVER_IP gerrit create-project platform/manifests
    echo "ssh -p $SERVER_PORT $USER_NAME@$SERVER_IP gerrit set-project-parent --parent AOSP platform/manifests"
    ssh -p $SERVER_PORT $USER_NAME@$SERVER_IP gerrit set-project-parent --parent AOSP platform/manifests

    for i in `cat $OUTPUT_PROJECT_LIST_FILE_NAME`;
    do
        echo $i
        echo "ssh -p $SERVER_PORT $USER_NAME@$SERVER_IP gerrit create-project $i"
        ssh -p $SERVER_PORT $USER_NAME@$SERVER_IP gerrit create-project $i
        echo "ssh -p $SERVER_PORT $USER_NAME@$SERVER_IP gerrit set-project-parent --parent AOSP $i"
        ssh -p $SERVER_PORT $USER_NAME@$SERVER_IP gerrit set-project-parent --parent AOSP $i
    done
}
 
function removeFiles()
{
    rm -rf $LOCAL_PATH/project_list_name
    rm -rf $LOCAL_PATH/project_list_path
}
 
getNameAndPath
creatEmptyGerritProject
removeFiles

1.2 汇入AOSP源码

本步骤耗时较长,笔者实测需要6h+,请耐心等待,另外该步骤存在的坑很多,详见 2.常见问题及解决方法

注意

  1. 请在AOSP源码根目录执行该文件
  2. 请对<>标识的内容按实际情况替换
  3. 该脚本执行结果幂等,可以重复执行而不影响最终结果
LOCAL_PATH=`pwd`
MANIFEST_XML_FILE=$LOCAL_PATH/.repo/manifests/default.xml
 
USER_NAME="<Your gerrit username>"
SERVER_IP="<1.1.1.1>"
SERVER_PORT="<29418>"

function prePush()
{
    git config --global http.postBuffer 4000000000
    git config --global https.postBuffer 4000000000
    git config --global ssh.postBuffer 4000000000

    while read LINE
    do
        branch=`echo $LINE | grep "<superproject"`
        if [ "$branch" ]
        then
            branch_sec=${LINE#*revision=\"}
            DEFAULT_BRANCH=${branch_sec%%\"*}
            echo "当前repo主分支被设置为: $DEFAULT_BRANCH"
            break
        fi
    done  < $MANIFEST_XML_FILE
}
 
function pushLocalToRemote()
{
    # 首先向platform/manifests仓库推送文件
    cd $MANIFEST
    git push --no-thin ssh://$USER_NAME@$SERVER_IP:$SERVER_PORT/platform/manifests.git
    cd -

    # 按照xml文件汇入
    while read LINE
    do
        cd $LOCAL_PATH
        command_line=`echo $LINE | grep "<project"`
        if [ "$command_line" ]
        then
            # echo $LINE
            reposity_name_sec=${LINE#*name=\"}
            reposity_path_sec=${LINE#*path=\"}
 
            if [ "$reposity_name_sec" ] && [ "$reposity_path_sec" ]
            then
                reposity_name=${reposity_name_sec%%\"*}
                reposity_path=${reposity_path_sec%%\"*}
 
                src_path=$LOCAL_PATH/$reposity_path
 
                if [ -d "$src_path" ]; then
                    cd $src_path
                    echo `pwd`

                    git push --no-thin ssh://$USER_NAME@$SERVER_IP:$SERVER_PORT/$reposity_name 2>&1 | tee git_push_error.log
                    
                    if grep -q "Unpack error" git_push_error.log 
                    then
                        echo "发现Unpack error错误,请尝试修改 gerrit.config 文件中的 receive.maxBatchCommits 字段为更大值, 并重启gerrit, 之后重新运行该脚本"
                    elif grep -q "unexpected disconnect" git_push_error.log 
                    then
                        rm -rf git_push_error.log
                        echo "发现unexpected disconnect错误,请尝试修改 gerrit.config 文件中的 sshd.TCPKeepAlive = yes, 并重启gerrit, 之后重新运行该脚本"
                        exit 1
                    else
                        rm -rf git_push_error.log
                    fi
                    cd -
                fi
            fi
        fi

    done  < $MANIFEST_XML_FILE
}

prePush
pushLocalToRemote

1.3 [附] 如果想删除所有AOSP仓库怎么办

在汇入过程中,有时候在遇到大量错误或者脚本配置有误的情况下,您有可能选择回滚所有提交并删除仓库重来,此时1100多个仓库如果靠手动删除无疑压力巨大,在此一并提供删除仓库脚本。

注意

  1. 该脚本将删除当前所有仓库,请务必谨慎操作
LOCAL_PATH=`pwd`
MANIFEST_XML_FILE=$LOCAL_PATH/.repo/manifests/default.xml
 
USER_NAME="admin"
SERVER_IP="192.168.0.100"
SERVER_PORT="29418"
 
OUTPUT_PROJECT_LIST_FILE_NAME=$LOCAL_PATH/project_list_name
OUTPUT_PROJECT_LIST_FILE_PATH=$LOCAL_PATH/project_list_path
 
function getNameAndPath()
{
    echo > $OUTPUT_PROJECT_LIST_FILE_NAME
    echo > $OUTPUT_PROJECT_LIST_FILE_PATH
 
    while read LINE
    do
        command_line=`echo $LINE | grep "<project"`
        if [ "$command_line" ]
        then
            #echo $LINE
 
            reposity_name_sec=${LINE#*name=\"}
            reposity_path_sec=${LINE#*path=\"}
 
            if [ "$reposity_name_sec" ] && [ "$reposity_path_sec" ]
            then
                reposity_name=${reposity_name_sec%%\"*}
                reposity_path=${reposity_path_sec%%\"*}
                echo "$reposity_name" >> $OUTPUT_PROJECT_LIST_FILE_NAME
                echo "$reposity_path" >> $OUTPUT_PROJECT_LIST_FILE_PATH
            fi
        fi
    done  < $MANIFEST_XML_FILE
}
 
function deleteGerritProject()
{
    for i in `cat $OUTPUT_PROJECT_LIST_FILE_NAME`;
    do
        echo $i
        echo "ssh -p $SERVER_PORT $USER_NAME@$SERVER_IP delete-project delete --yes-really-delete $i"
        ssh -p $SERVER_PORT $USER_NAME@$SERVER_IP delete-project delete --yes-really-delete $i
    done
}
 
function removeFiles()
{
    rm -rf $LOCAL_PATH/project_list_name
    rm -rf $LOCAL_PATH/project_list_path
}
 
getNameAndPath
deleteGerritProject
removeFiles

2. 常见问题及解决方法

2.1 error: 远程解包失败:error Missing commit xxx

2.1.1 问题原因

当源码汇入gerrit过程中,部分仓库可能会有类似如下报错:

/home/xxx/Project/Android/aosp/kernel/prebuilts/4.19/arm64
枚举对象中: 15, 完成.
对象计数中: 100% (15/15), 完成.
使用 16 个线程进行压缩
压缩对象中: 100% (14/14), 完成.
写入对象中: 100% (15/15), 37.12 MiB | 10.63 MiB/s, 完成.
总共 15(差异 0),复用 0(差异 0),包复用 0
error: 远程解包失败:error Missing commit a51c5febf3fb291d9258fa41176972e55b942f3c
fatal: Unpack error, check server log
To ssh://192.168.1.1:29418/kernel/prebuilts/4.19/arm64
 ! [remote rejected] android-13.0.0_r52 -> android-13.0.0_r52 (n/a (unpacker error))
error: 无法推送一些引用到 'ssh://192.168.0.100:29418/kernel/prebuilts/4.19/arm64'

检索stackoverflow,发现有开发者报告过这个问题git-unpack-error-on-push-to-gerrit,根据该网站建议,我尝试使用--no-thin参数进行源码汇入,但发现该问题仍旧不能解决,经过笔者调查分析,该问题是提交时gerrit限制了maxBatchCommits参数,导致部分仓库由于commit过多被拒绝。

2024.9.21 更:感谢@蔡树伟先生给出的进一步问题分析,现将蔡先生的问题分析更新如下:
应该是部分repo源克隆项目的时候为了节省空间加快速度,使用浅克隆,所以本地git项目只有最新的提交记录,并且有他的父级commit id,这也是导致missing commit的原因。

2.2.2 解决方案

按如下示例修改 gerrit.config 文件中的 receive.maxBatchCommits 字段为更大值, 并重启gerrit, 之后重新汇入。

[receive]
        enableSignedPush = false
        maxBatchCommits = 999999999 # 这个地方按实际情况尽可能增大
        timeout = 120min            # 这个地方推荐120min

然后,再次运行原始命令。

2024.9.21 更: @蔡树伟给出该问题的解决方案:
当发生如2.1.1的问题时,如果还能访问之前的远程仓库可以参考这个文章(blog.csdn.net)取回完整的提交历史记录。
如果旧的服务器已经没法访问了,可以在git项目下执行git filter-branch -f -- --all,这个命令会重置所有分支,使得那些持有无效commit的提交作为分支的根,也就不再有父级提交了。

2.3 fatal: 远端意外挂断了

2.3.1 问题原因

当源码汇入gerrit过程中,部分仓库可能会有类似如下报错:

/home/xxx/Project/Android/aosp/prebuilts/clang-tools
枚举对象中: 274, 完成.
对象计数中: 100% (274/274), 完成.
使用 16 个线程进行压缩
压缩对象中: 100% (210/210), 完成.
Connection to 192.168.1.1 closed by remote host.s
send-pack: unexpected disconnect while reading sideband packet
fatal: 远端意外挂断了

该问题笔者在网上找到了相似问题git-push-fails-fatal-the-remote-end-hung-up-unexpectedly,但给出的解决方案未能解决该问题,最后通过分析,笔者经过对gerrit报错分析,考虑是gerrit自身sshd没有开启ssh连接保活导致的。

2.3.2 解决方案

按如下示例修改 gerrit.config 文件中的 sshd.TCPKeepAlive = yes, 并重启gerrit, 之后重新汇入。

执行如下命令:

[sshd]
        listenAddress = *:29418
        TCPKeepAlive =  yes # 增加这个字段以启用ssh保活

参考文档

  1. 将AOSP完整导入Gerrit Server (笔者注:截至笔者发稿,该文档地址已不支持访问,但该文档中给出的方式方法具有重要参考价值,特此保留)
  2. 将安卓代码导入gerrit
  3. 安卓代码(repo管理)完整导入Gerrit服务器和Gerrit代码迁移备份


以上就是我将aosp源码汇入gerrit的全流程与遇到的问题,有很多大坑小坑都没能及时的记录下来,如果您在汇入过程中遇到了其他错误,也非常欢迎您在下方留言,如果我正巧遇到过,就可以为您提供一些参考,同时也可以把这篇文章的排错指南丰富一下。