Jenkins + Git: только строить, если PR внес изменения в подкаталог - PullRequest
4 голосов
/ 05 октября 2019

У нас есть большой монорепо с несколькими проектами (A и B) внутри. В настоящее время у меня есть Jenkins, настроенный как проект Multipranch Pipelines, который отслеживает монорепо PR. Если создается PR, Дженкинс строит как A, так и B.

Теперь я хочу, чтобы Jenkins был умнее и строил проект A только в том случае, если любое изменение в PR привело к изменению в каталоге 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 и т. Д. (Я пробовал их все)? Есть ли более простой способ выполнить то, что я пытаюсь сделать?


Я использую 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: 7ef64efeb0fb19d8931a684f147666ae681b4ddf и 47600816c0dca3e5555e417085ab2052453a39b2!

1 Ответ

2 голосов
/ 15 октября 2019

Хорошо, я наконец-то решил это.

Похоже (поправьте меня, если у меня неверная терминология), что Дженкинс делает то, что называется голым клоном, что означает, что у вас не будетдоступ к любым ссылкам, если вы специально не получите их. Таким образом, у вас не будет доступа к именам веток, локальным или удаленным.

Ключ находится в этих двух строках журнала:

> 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-9 и upstream/master.

Удобно, что переменные среды Jenkins BRANCH_NAME и CHANGE_TARGET содержат PR-9 и master соответственно.

Таким образом, 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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...