血泪史

最近在使用Jenkins拉取Git工程编译代码时候遇到一个很奇怪的的问题:Jenkins的GitPlugin下载代码的版本不对(commitId不对)。由于线上部署和线下部署的编译产物是同一版本,导致最后发布到生产环境的代码版本也不对。这个问题在线上验证阶段才最终被发现,回顾整个job构建过程,控制台没有报错,也成功编译出来了上线包,那到底是哪里出了问题?

初步定位

我最开始怀疑是本地Git工程残留的问题,于是尝试删除jenkins对应job所运行的机器节点上的WORKSPACE目录,保证下次触发Jenkins构建能拉取到最新代码。

删除方式:

1. 登陆到运行这个job的节点的机器(在控制台中查看这个job运行的节点,在第一行有打印)。

2. 查看$WORKSPACE(在job的控制台中查看;如果找不到,直接在shell executor中加一行echo $WORKSPACE,重新执行job)

3. 删除对应的WORKSPACE信息。请谨慎操作,先看清楚WORKSPACE的值对不对(echo $WORKSPACE),别搞错导致删除了根目录!!!

[ -d "$WORKSPACE" ] && rm -rf ${WORKSPACE}

4.删除后重新触发job

这么操作后,最终打包出来的编译产物还是版本不对。

既然不是本地代码缓存问题,那有没有可能在GIt拉取代码的时候版本就不对呢?

二次排错

打开Jenkins上job的配置详情,我在BranchSpecifier这里只填写了"*/qa",我的预期是匹配git特定工程(Repository URL)中的qa分支。

 

然后看看之前有问题那次构建的记录(打开job当次构建的详情页),点击Git Build Data(左侧):

这里显示了两个remote的branch,我在GitPlugin里面只配置了一个qa分支,这里应该只有一个叫做qa的分支被下载才对,怎么会有两个分支呢?

我开始怀疑可能是在配置Git Plugin中BranchName的规则匹配问题,导致在这里匹配了两个分支(一个叫做qa,一个叫origin/qa)。

再次打开Jenkins上对应job的配置,我们看到在BranchSpecifier这里填的是"*/qa",*是一个通配符,会不会在这个工程下正好有两个branch,如上图中的(origin/qa和qa),正好匹配到了*/qa呢?

 这么一想就有点解释得通了,于是我点击了Branches Specifier这一栏右侧的问号查看帮助,可以看到使用帮助信息。

详情分析

//下面这句话的意思是:如果你不填写branch specifier(留空或者写的是any),那么任意分支都会被匹配,也就是你无法工程使用的是哪个分支。一般不建议这么用。

Specify the branches if you'd like to track a specific branch in a repository. If left blank, all branches will be examined for changes and built.

//下面这句话的意思是:最安全的方式是使用refs/heads/<branchName>这种语法

The safest way is to use the refs/heads/<branchName> syntax. This way the expected branch is unambiguous. 

//如果你的分支里面包含‘/’(例如叫origin/qa),最好就用上面提到的语法:refs/heads/<branchName>

If your branch name has a / in it make sure to use the full reference above. When not presented with a full path the plugin will only use the part of the string right of the last slash. Meaning foo/bar will actually match bar.

 

If you use a wildcard branch specifier, with a slash (e.g. release/), you'll need to specify the origin repository in the branch names to make sure changes are picked up. So e.g. origin/release/

Possible options:  

  • <branchName>         //branchName只是个通配符,比如realese可以匹配release分支,也可能匹配origin/release,一般不要这么写(否则会和我一样踩同样的坑)

//由于指定<branchName>存在二义性,建议使用refs/heads/<branchName>来明确指定具体的远程分支。

Tracks/checks out the specified branch. If ambiguous the first result is taken, which is not necessarily the expected one. Better use refs/heads/<branchName>.

E.g. master, feature1,...    

  • refs/heads/<branchName>

 

Tracks/checks out the specified branch.        //checkout远程的某个分支

E.g. refs/heads/master, refs/heads/feature1/master,...

  • <remoteRepoName>/<branchName>

 

Tracks/checks out the specified branch. If ambiguous the first result is taken, which is not necessarily the expected one.

Better use refs/heads/<branchName>.       //这种指定远程具体分支的方法可能存在二义性,建议使用refs/heads/<branchName>

E.g. origin/master

  • remotes/<remoteRepoName>/<branchName>

 

Tracks/checks out the specified branch.  //这种指定方式也是比较精确的。

E.g. remotes/origin/master

  • refs/remotes/<remoteRepoName>/<branchName>

 

Tracks/checks out the specified branch.

E.g. refs/remotes/origin/master            //checkout指定的tag,实际上这里tagName不会被当做tag,不建议这么写。

  • <tagName>

This does not work since the tag will not be recognized as tag.

Use refs/tags/<tagName> instead. ..     //checkout指定的tag,这才是正确的语法。

E.g. git-2.3.0

  • refs/tags/<tagName>

Tracks/checks out the specified tag.

E.g. refs/tags/git-2.3.0

 

  • <commitId>

Checks out the specified commit.       //checkout指定的commitid

E.g. 5062ac843f2b947733e6a3b105977056821bd352, 5062ac84, ...

  • ${ENV_VARIABLE}

It is also possible to use environment variables. In this case the variables are evaluated and the result is used as described above.

E.g. ${TREEISH}, refs/tags/${TAGNAME},...

  • <Wildcards>

The syntax is of the form: REPOSITORYNAME/BRANCH. In addition, BRANCH is recognized as a shorthand of */BRANCH, '*' is recognized as a wildcard, and '**' is recognized as wildcard that includes the separator '/'. Therefore, origin/branches* would match origin/branches-foo but not origin/branches/foo, while origin/branches** would match both origin/branches-foo and origin/branches/foo.

  • :<regular expression>

The syntax is of the form: :regexp. Regular expression syntax in branches to build will only build those branches whose names match the regular expression.

Examples:

  • :^(?!(origin/prefix)).*

  • matches: origin or origin/master or origin/feature

  • does not match: origin/prefix or origin/prefix_123 or origin/prefix-abc

  • :origin/release-\d{8}

  • matches: origin/release-20150101

  • does not match: origin/release-2015010 or origin/release-201501011 or origin/release-20150101-something

  • :^(?!origin/master$|origin/develop$).*

  • matches: origin/branch1 or origin/branch-2 or origin/master123 or origin/develop-123

  • does not match: origin/master or origin/develop

 所以对于想要指定特定分支进行拉取,最好的四种没有二义性的写法是:

  • refs/heads/<branchName>

  • refs/remotes/<remoteRepoName>/<branchName>

  • refs/tags/<tagName>

  • <commitId>

到这里基本问题就真相大白了,我去gitlab上看了下这个工程的分支列表,发现还真有一个叫做origin/qa的分支,经过和其他同事的确认,有人手抖创建了这个origin/qa的分支。

我把原来的*/qa换成refs/heads/qa,这样就精确地匹配到了具体的分支。尝试再次触发job构建,这次下载的Git代码版本也正确了。

 

本文为原创踩坑史,如果对你有帮助,辛苦手下留赞,谢谢! 

博主:测试生财

座右铭:通过测试生涯完成原始积累,通过投资奔向财务自由

csdn:https://blog.csdn.net/ccgshigao

博客园:https://www.cnblogs.com/qa-freeroad/

51cto:https://blog.51cto.com/14900374