Jenkins + Git:仅在PR引入子目录更改时构建

10
我们有一个大型的monorepo,里面包含多个项目(A和B)。我目前已经将Jenkins设置为Multibranch Pipelines项目,它会监视PR来自monorepo。如果创建了一个PR,则Jenkins构建A和B两个项目。
现在,我希望Jenkins更加智能化,只有当PR中引入A/目录的更改时,才构建项目A。这证明非常困难。 when { changeset "A/" }似乎只检查最后提交的文件是否更改了A/,而不是PR是否更改了A/中的文件。
因此,我使用https://issues.jenkins-ci.org/browse/JENKINS-54285使其更智能化,并执行以下操作: when { expression { return sourceChanged("A/") } } 其中,sourceChanged定义为:
def boolean sourceChanged(String module) {
    if (env.CHANGE_TARGET == null)
        return true;

    def MASTER = sh(returnStdout: true, script: "git rev-parse origin/${env.CHANGE_TARGET}").trim()
    def HEAD = sh(returnStdout: true, script: "git show -s --no-abbrev-commit --pretty=format:%P%n%H%n HEAD | tr ' ' '\n' | grep -v ${MASTER} | head -n 1").trim()

    return sh(returnStatus: true, script: "git diff --exit-code --name-only ${MASTER}...${HEAD} {module}") == 1;
}

无论我尝试什么方法,都无法获得CHANGE_TARGET的提交哈希值,总是会出现以下错误:

git rev-parse origin/master
fatal: ambiguous argument 'origin/master': unknown revision or path not in the working tree.

为什么Git找不到`master`、`origin/master`和`refs/head/master`等(我已经尝试了它们所有的版本)?有没有更简单的方法来完成我想做的事情?
我正在使用docker hub上的`jenkins/jenkins:lts`,以及BitBucket Branch Source插件。
下面是相关的Jenkins日志序列,如果有帮助:
Fetching changes from 2 remote Git repositories
 > git config remote.origin.url http://bitbucket.ccm.com:7990/scm/JUP/jt.git # timeout=10
Fetching without tags
Fetching upstream changes from http://bitbucket.ccm.com:7990/scm/JUP/jt.git
 > git --version # timeout=10
using GIT_ASKPASS to set credentials 
 > git fetch --no-tags --progress -- http://bitbucket.ccm.com:7990/scm/JUP/jt.git +refs/pull-requests/9/from:refs/remotes/origin/PR-9
 > git config remote.upstream.url http://bitbucket.ccm.com:7990/scm/JUP/jt.git # timeout=10
Fetching without tags
Fetching upstream changes from http://bitbucket.ccm.com:7990/scm/JUP/jt.git
using GIT_ASKPASS to set credentials 
 > git fetch --no-tags --progress -- http://bitbucket.ccm.com:7990/scm/JUP/jt.git +refs/heads/master:refs/remotes/upstream/master
Merging remotes/upstream/master commit 7ef64efeb0fb19d8931a684f147666ae681b4ddf into PR head commit 47600816c0dca3e5555e417085ab2052453a39b2
Enabling Git LFS pull
 > git config core.sparsecheckout # timeout=10
 > git checkout -f 47600816c0dca3e5555e417085ab2052453a39b2
 > git config --get remote.origin.url # timeout=10
using GIT_ASKPASS to set credentials 
 > git lfs pull origin
 > git merge 7ef64efeb0fb19d8931a684f147666ae681b4ddf # timeout=10
 > git rev-parse HEAD^{commit} # timeout=10
Merge succeeded, producing 47600816c0dca3e5555e417085ab2052453a39b2
Checking out Revision 47600816c0dca3e5555e417085ab2052453a39b2 (PR-9)
Enabling Git LFS pull
 > git config core.sparsecheckout # timeout=10
 > git checkout -f 47600816c0dca3e5555e417085ab2052453a39b2
 > git config --get remote.origin.url # timeout=10
using GIT_ASKPASS to set credentials 
 > git lfs pull origin
Commit message: "l"
[Pipeline] withEnv
[Pipeline] {
[Pipeline] sh
+ docker inspect -f . registry.ccm.com:7991/jt:1.0
.
[Pipeline] withDockerContainer
Jenkins seems to be running inside container fdc7e8eec5ea708e59cebe4682651bc5192478b95de803b5981edd222f39af97
$ docker run -t -d -u 1000:979 -v $PWD:/build_env -v $HOME/.ssh:/home/docker_user/.ssh -w /build_env --add-host civm3:10.33.67.183 -e UNIX_USER=jtbuild -w /var/jenkins_home/workspace/jt_PR-9@2 --volumes-from fdc7e8eec5ea708e59cebe4682651bc5192478b95de803b5981edd222f39af97 -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** registry.ccm.com:7991/jt:1.0 cat
$ docker top c7bb23bbc91119c2b1875ab2a9186ae34da1754f2b8ae42f758594227ff77137 -eo pid,comm
[Pipeline] {
[Pipeline] sh
+ git rev-parse origin/master
fatal: ambiguous argument 'origin/master': unknown revision or path not in the working tree.

我希望在Jenkinsfile中获得两个相关提交ID的访问权限: 7ef64efeb0fb19d8931a684f147666ae681b4ddf47600816c0dca3e5555e417085ab2052453a39b2!


1
Jenkins 克隆存储库的方式很奇怪(不是克隆分支或标签,而是只使用 SHA)。此外,据我所见,它会将您的 PR 合并到主分支中 (git merge 7ef64efeb0fb19d8931a684f147666ae681b4ddf)。因此,您需要进行一些实验,但您可能可以通过 HEAD^2 获取 master,并通过 HEAD^1 获取您的 PR head(请参阅 refnames)。 - AlexisBRENON
你是否考虑将你的代码仓库拆分成多个仓库,其中每个子文件夹都是一个独立的GIT仓库,而根仓库则将其作为子模块包含进来? - yorammi
2个回答

7

好的,我终于解决了。

看起来Jenkins执行的是所谓的“裸”克隆(如果术语不正确,请纠正我),这意味着除非你专门获取它们,否则你将无法访问任何引用。因此,你将无法访问你的分支名称,无论是本地还是远程的。

关键在于日志中的这两行:

> git fetch --no-tags --progress -- http://bitbucket.ccm.com:7990/scm/JUP/jt.git +refs/pull-requests/9/from:refs/remotes/origin/PR-9 
> git fetch --no-tags --progress -- http://bitbucket.ccm.com:7990/scm/JUP/jt.git +refs/heads/master:refs/remotes/upstream/master

以下是上述两个命令的缩短、注释版本:
> git fetch the PR ref, store it as 'origin/PR-9'
> git fetch master ref, store it as 'upstream/master'

因此,需要关注的两个提交被存储在 origin/PR-9upstream/master 中。
方便地,Jenkins 环境变量 BRANCH_NAMECHANGE_TARGET 分别包含 PR-9master
因此,Jenkinsfile 应该使用以下内容:
def boolean sourceChanged(String module) {
    def target_branch = env.CHANGE_TARGET;
    def pr_ref        = env.BRANCH_NAME;

    if (target_branch == null) {
        echo "No target branch defined...";
        return true;
    }

    def TARGET = sh(returnStdout: true, script: "git rev-parse upstream/${target_branch}").trim()
    def HEAD   = sh(returnStdout: true, script: "git rev-parse origin/${pr_ref}").trim()

    echo "Checking for source changes between ${TARGET} (${target_branch}) and ${HEAD} (${pr_ref})...";
    return sh(returnStatus: true, script: "git diff --exit-code --name-only ${TARGET}...${HEAD} {module}") == 1;
}

与...一起使用,即:

when { expression { return sourceChanged("A/") } }

检查多个目录的差异可以按如下方式完成:
def SOURCE_DIRS = [
    "A/",
    "X/"
];
...
when { expression { return sourceChanged(SOURCE_DIRS) } }
...
def sourceChanged(ArrayList<String> source_dirs) {
    def target_branch = env.CHANGE_TARGET;
    def pr_ref        = env.BRANCH_NAME;

    if (target_branch == null) {
        echo "No target branch defined...";
        return true;
    }

    def TARGET = sh(returnStdout: true, script: "git rev-parse upstream/${target_branch}").trim()
    def HEAD   = sh(returnStdout: true, script: "git rev-parse origin/${pr_ref}").trim()

    echo "Checking for source changes between ${TARGET} (${target_branch}) and ${HEAD} (${pr_ref})...";
    for (String dir : source_dirs) {
        def rc = sh(returnStatus: true, script: "git diff --name-only --exit-code ${TARGET}...${HEAD} ${dir}");
        if (rc == 1) {
            echo "Changes detected in ${dir}!";
            return true;
        }
    }

    echo "No changes detected.";
    return false;
}

1
如果您愿意使用标准的Jenkins 'checkout'步骤与Git一起使用,那么有一个选项可以允许从PR生成更改集。您可以使用Jenkins的Pipeline Syntax Snippet Generator,选择名为“checkout:Check out from version control”的示例步骤,在“Additional Behaviours”下单击“Add”,并添加名为“Calculate changelog against a specific branch”的行为。当您填写字段并单击“Generate Pipeline Script”时,将为您配置“ChangelogToBranch”(它将适当地填充更改集)。然后,您可以使用when { changeset ... - dlauzon

1

您还可以使用 配置 > 分支来源 > 行为 为您的多分支项目指定其他 refspecs。使用 添加 > 指定 ref specs,这将使您需要的任何分支名称可用。

有关屏幕截图,请参见此答案:https://stackoverflow.com/a/63267947/64505


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接