GitHub pull request builder plugin

Skip to end of metadata
Go to start of metadata

This plugin builds pull requests in github and report results.

Plugin Information

Plugin ID ghprb Changes In Latest Release
Since Latest Release
Latest Release
Latest Release Date
Required Core
Dependencies
1.24.7 (archives)
Jul 07, 2015
1.532
credentials (version:1.21)
plain-credentials (version:1.1)
token-macro (version:1.10, optional)
git (version:2.2.4)
build-flow-plugin (version:0.12, optional)
github-api (version:1.66)
ssh-agent (version:1.3)
github (version:1.9.1)
Source Code
Issue Tracking
Pull Requests
Maintainer(s)
GitHub
Open Issues
Pull Requests
Honza Br��zdil (id: janinko)
Valdis Rigdon (id: valdisrigdon)
Usage Installations 2014-Jul 3091
2014-Aug 3206
2014-Sep 3366
2014-Oct 3665
2014-Nov 3751
2014-Dec 3823
2015-Jan 4033
2015-Feb 4191
2015-Mar 4553
2015-Apr 4616
2015-May 4767
2015-Jun 5044

Commands: 

When a new pull request is opened in the project and the author of the pull request isn't white-listed, builder will ask "Can one of the admins verify this patch?".

  • "ok to test" to accept this pull request for testing
  • "test this please" for a one time test run
  • "add to whitelist" to add the author to the whitelist

If the build fails for other various reasons you can rebuild.

  • "retest this please" to start a new build

Installation:

  • I recommend to create GitHub 'bot' user that will be used for communication with GitHub (however you can use your own account if you want).
  • The user needs to have push rights for your repository (must be collaborator (user repo) or must have Push & Pull rights (organization repo)).
  • If you want to use GitHub hooks have them set automatically the user needs to have administrator rights for your repository (must be owner (user repo) or must have Push, Pull & Administrative rights (organization repo))
  • Install the plugin.
  • Go to ``Manage Jenkins`` -> ``Configure System`` -> ``GitHub pull requests builder`` section.
  • Add GitHub usernames of admins (these usernames will be used as defaults in new jobs).
  • Under Advanced, you can modify:
    • The phrase for adding users to the whitelist via comment. (Java regexp)
    • The phrase for accepting a pull request for testing. (Java regexp)
    • The phrase for starting a new build. (Java regexp)
    • The crontab line. This specify default setting for new jobs.
  • Under Application Setup
    • There are global and job default extensions that can be configured for things like:
      • Commit status updates
      • Build status messages
      • Adding lines from the build log to the build result message
      • etc.

Credentials

  • If you are using Enterprise GitHub set the server api URL in ``GitHub server api URL``. Otherwise leave there ``https://api.github.com``.
  • A GitHub API token or username password can be used for access to the GitHub API
  • To setup credentials for a given GitHub Server API URL:
    • Click Add next to the ``Credentials`` drop down
      • For a token select ``Kind`` -> ``Secret text``
        • If you haven't generated an access token you can generate one in ``Test Credentials...``.
          • Set your 'bot' user's GitHub username and password.
          • Press the ``Create Access Token`` button
          • Jenkins will create a token credential, and give you the id of the newly created credentials. The default description is: ``serverAPIUrl + " GitHub auto generated token credentials"``.
      • For username/password us ``Kind`` -> ``Username with password``
        • The scope determines what has access to the credentials you are about to create
      • The first part of the description is used to show different credentials in the drop down, so use something semi-descriptive
      • Click ``Add``
    • Credentials will automatically be created in the domain given by the ``GitHub Server API URL`` field.
    • Select the credentials you just created in the drop down.
    • The first fifty characters in the Description are used to differentiate credentials per job, so again use something semi-descriptive
  • Add as many GitHub auth sections as you need, even duplicate server URLs
  • Save to preserve your changes.

Creating job:

  • Create a new job.
  • Add the project's GitHub URL to the "GitHub project" field (the one you can enter into browser. eg: "https://github.com/janinko/ghprb")
  • Select Git SCM.
  • Add your GitHub "Repository URL".
  • Under Advanced, set "refspec" to
    +refs/pull/*:refs/remotes/origin/pr/*
  • In "Branch Specifier", enter
    ${sha1}

    or if you want to use the actual commit in the pull request, use

    ${ghprbActualCommit}
  • Under "Build Triggers", check "Github pull requests builder".
    • Add admins for this specific job.
    • If you want to use GitHub hooks for automatic testing, read the help for "Use github hooks for build triggering" in job configuration. Then you can check the checkbox.
    • In Advanced, you can modify:
      • The crontab line for this specific job. This schedules polling to GitHub for new changes in Pull Requests.
      • The whitelisted users for this specific job.
      • The organisation names whose members are considered whitelisted for this specific job.
  • Save to preserve your changes.
    Make sure you DON'T have "Prune remote branches before build" advanced option selected, since it will prune the branch created to test this build.

If you want to manually build the job, in the job setting check "This build is parameterized" and add string parameter named "sha1". When starting build give the "sha1" parameter commit id you want to build or refname (eg: "origin/pr/9/head").

Environment Variables

The plugin makes some very useful environment variables available.

  • ghprbActualCommit
  • ghprbActualCommitAuthor
  • ghprbActualCommitAuthorEmail
  • ghprbPullDescription
  • ghprbPullId
  • ghprbPullLink
  • ghprbPullTitle
  • ghprbSourceBranch
  • ghprbTargetBranch
  • sha1

Changelog

1.24

Use signature checking for webhooks if desired.
Add custom messages to status updates and a custom url field.

1.23

Use credentials plugin for username/password combinations and tokens.
Support multiple GitHub endpoints.

1.22.x

Fix issue where if a project was disabled the Jenkins Trigger process would crash
Fix commit status context
Add one line test results for downstream builds
Miscellaneous bug fixes

1.22

Move commit status over to extension form. It is now configurable, satisfying #81, #73, and #19 at least.

1.21

Move all commenting logic out into extensions.

1.20.1

Null Pointer fix for trigger.
Added clarity to error message when access is forbidden.

1.20

PullRequestMerger now notifies the taskListener of failures.
AutoCloseFailedPullRequest has been extracted from the published URL check.

1.19

More work for disabled builds.
Unified tabs to spaces.
Updates to the tests, and added some tests.

1.18

Add support for folder projects.
Correcting issue with default credentials.
Correcting issue with ghRepository being null when it shouldn't be.
Ignoring case when matching repo to url.
Changing the wording for pull requests that are mergeable.
Change requestForTesting phrase to a textArea.
Check if project is disabled, if it is then don't do anything.

1.14

A comment file can be created during the build and added to any comment made to the pull request. podarok#33
Added a [skip ci] setting, that can be changed. Adding the skip statement to the pull request body will cause the job not to run. sathiya-mit#29
Escaping single quotes in log statements tIGO#38
Fixed owner name deduction from url on github hook handling nikicat#40
Removed unused Test field from the config

1.13-1

Replacing deprecated Github.connect method. tIGO#39
Added a merge plugin for post build. If the build is successful, the job can specify conditions under which the pull request "button" will be pressed.

1.12

Fixes issue with checking rate limits for GitHub Enterprise
Internal refactorings
Fixes checking of TriggerTimer (JENKINS-22550)

1.11.2

Major reduction in the number of requests made to GitHub to avoid rate limit issues
Fixed changelog for a build so it reports differences between Pull Request build
Fixes bug on getting author email address from GitHub APIs
Updates warning message is URL isn't set on the GitHub project plugin
Adds some additional parameters to the build, such as the source branch, pull request link, etc.
Success/failure message now put into the comment on Pull Requests

1.10 - 1.11.1

Not publicly released.

1.9

Added job specific triggers.
Added more informations as job parameters.
Fix support for gh-ent token generation. JENKINS-17334
Lookup PR detailed information for each PR. JENKINS-17852
Fix closing PR.
various bugfixes

1.8

Support auto-close at build level
add pull id and target branch to environment variables
fix GitHub Hooks when authentication is required on Jenkins
various bug fixes and improvements

1.7

implemented support for GitHub hooks
added button for generating API token

1.6

option for specifying how to mark unstable builds in GitHub
Close failed pull requests automatically if plugin admin configured it
customizeable pass/fail comments
Refactoring of code
help description in Jenkins configuration

1.5

checkbox enabling sending comments when update of comment status fails
option to enter organisations which members are whitelisted
support for Enterprise GitHub
link to pull request into build description
fixes

1.4

Changed comment commands - phrases for testing once, pull request or add user to whitelist
"test this please" starts new build
"ok to test" allow pull request for building
"add to whitelist" add user to whitelist

1.3

Add published URL feature.
Enable connection to GitHub Enterprise instances.
Enable authentication via a token.
When automatic merge is possible, build the merge.

1.2

Use commit status API instead of comments for reporting results.

Labels

plugin-trigger plugin-trigger Delete
credentials-consumer credentials-consumer Delete
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. Sep 28, 2012

    Marco de Jong says:

    Hi there, I don't know if I should report this as actual bug, but as soon as I...
    Hi there,
    
    I don't know if I should report this as actual bug, but as soon as I try to configure the job as described as above,
    I get a "Exception: java.lang.NullPointerException". After this the job will never actually try and fetch data as configured in crontab.
    Posting some ugly data after this...
    
    
    Status Code: 500
    Exception: java.lang.NullPointerException
    Stacktrace:
    javax.servlet.ServletException: java.lang.NullPointerException
    	at org.kohsuke.stapler.Stapler.tryInvoke(Stapler.java:616)
    	at org.kohsuke.stapler.Stapler.invoke(Stapler.java:659)
    	at org.kohsuke.stapler.MetaClass$6.doDispatch(MetaClass.java:241)
    	at org.kohsuke.stapler.NameBasedDispatcher.dispatch(NameBasedDispatcher.java:53)
    	at org.kohsuke.stapler.Stapler.tryInvoke(Stapler.java:574)
    	at org.kohsuke.stapler.Stapler.invoke(Stapler.java:659)
    	at org.kohsuke.stapler.Stapler.invoke(Stapler.java:488)
    	at org.kohsuke.stapler.Stapler.service(Stapler.java:162)
    	at javax.servlet.http.HttpServlet.service(HttpServlet.java:45)
    	at winstone.ServletConfiguration.execute(ServletConfiguration.java:248)
    	at winstone.RequestDispatcher.forward(RequestDispatcher.java:333)
    	at winstone.RequestDispatcher.doFilter(RequestDispatcher.java:376)
    	at hudson.util.PluginServletFilter$1.doFilter(PluginServletFilter.java:95)
    	at hudson.plugins.greenballs.GreenBallFilter.doFilter(GreenBallFilter.java:58)
    	at hudson.util.PluginServletFilter$1.doFilter(PluginServletFilter.java:98)
    	at hudson.util.PluginServletFilter.doFilter(PluginServletFilter.java:87)
    	at winstone.FilterConfiguration.execute(FilterConfiguration.java:194)
    	at winstone.RequestDispatcher.doFilter(RequestDispatcher.java:366)
    	at hudson.security.csrf.CrumbFilter.doFilter(CrumbFilter.java:47)
    	at winstone.FilterConfiguration.execute(FilterConfiguration.java:194)
    	at winstone.RequestDispatcher.doFilter(RequestDispatcher.java:366)
    	at hudson.security.ChainedServletFilter$1.doFilter(ChainedServletFilter.java:84)
    	at hudson.security.UnwrapSecurityExceptionFilter.doFilter(UnwrapSecurityExceptionFilter.java:51)
    	at hudson.security.ChainedServletFilter$1.doFilter(ChainedServletFilter.java:87)
    	at org.acegisecurity.ui.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:166)
    	at hudson.security.ChainedServletFilter$1.doFilter(ChainedServletFilter.java:87)
    	at org.acegisecurity.providers.anonymous.AnonymousProcessingFilter.doFilter(AnonymousProcessingFilter.java:125)
    	at hudson.security.ChainedServletFilter$1.doFilter(ChainedServletFilter.java:87)
    	at org.acegisecurity.ui.rememberme.RememberMeProcessingFilter.doFilter(RememberMeProcessingFilter.java:142)
    	at hudson.security.ChainedServletFilter$1.doFilter(ChainedServletFilter.java:87)
    	at org.acegisecurity.ui.AbstractProcessingFilter.doFilter(AbstractProcessingFilter.java:271)
    	at hudson.security.ChainedServletFilter$1.doFilter(ChainedServletFilter.java:87)
    	at org.acegisecurity.ui.basicauth.BasicProcessingFilter.doFilter(BasicProcessingFilter.java:173)
    	at hudson.security.ChainedServletFilter$1.doFilter(ChainedServletFilter.java:87)
    	at jenkins.security.ApiTokenFilter.doFilter(ApiTokenFilter.java:63)
    	at hudson.security.ChainedServletFilter$1.doFilter(ChainedServletFilter.java:87)
    	at org.acegisecurity.context.HttpSessionContextIntegrationFilter.doFilter(HttpSessionContextIntegrationFilter.java:249)
    	at hudson.security.HttpSessionContextIntegrationFilter2.doFilter(HttpSessionContextIntegrationFilter2.java:66)
    	at hudson.security.ChainedServletFilter$1.doFilter(ChainedServletFilter.java:87)
    	at hudson.security.ChainedServletFilter.doFilter(ChainedServletFilter.java:76)
    	at hudson.security.HudsonFilter.doFilter(HudsonFilter.java:164)
    	at winstone.FilterConfiguration.execute(FilterConfiguration.java:194)
    	at winstone.RequestDispatcher.doFilter(RequestDispatcher.java:366)
    	at org.kohsuke.stapler.compression.CompressionFilter.doFilter(CompressionFilter.java:50)
    	at winstone.FilterConfiguration.execute(FilterConfiguration.java:194)
    	at winstone.RequestDispatcher.doFilter(RequestDispatcher.java:366)
    	at hudson.util.CharacterEncodingFilter.doFilter(CharacterEncodingFilter.java:81)
    	at winstone.FilterConfiguration.execute(FilterConfiguration.java:194)
    	at winstone.RequestDispatcher.doFilter(RequestDispatcher.java:366)
    	at winstone.RequestDispatcher.forward(RequestDispatcher.java:331)
    	at winstone.RequestHandlerThread.processRequest(RequestHandlerThread.java:215)
    	at winstone.RequestHandlerThread.run(RequestHandlerThread.java:138)
    	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
    	at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
    	at java.util.concurrent.FutureTask.run(FutureTask.java:166)
    	at winstone.BoundedExecutorService$1.run(BoundedExecutorService.java:77)
    	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
    	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
    	at java.lang.Thread.run(Thread.java:636)
    Caused by: java.lang.NullPointerException
    	at org.jenkinsci.plugins.ghprb.GhprbTrigger.start(GhprbTrigger.java:65)
    	at org.jenkinsci.plugins.ghprb.GhprbTrigger.start(GhprbTrigger.java:33)
    	at hudson.model.AbstractProject.submit(AbstractProject.java:1796)
    	at hudson.model.Project.submit(Project.java:197)
    	at hudson.model.Job.doConfigSubmit(Job.java:991)
    	at hudson.model.AbstractProject.doConfigSubmit(AbstractProject.java:699)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:616)
    	at org.kohsuke.stapler.Function$InstanceFunction.invoke(Function.java:288)
    	at org.kohsuke.stapler.Function.bindAndInvoke(Function.java:151)
    	at org.kohsuke.stapler.Function.bindAndInvokeAndServeResponse(Function.java:90)
    	at org.kohsuke.stapler.MetaClass$1.doDispatch(MetaClass.java:111)
    	at org.kohsuke.stapler.NameBasedDispatcher.dispatch(NameBasedDispatcher.java:53)
    	at org.kohsuke.stapler.Stapler.tryInvoke(Stapler.java:574)
    	... 58 more
    
    
    
  2. Oct 01, 2012

    recampbell Campbell says:

    That's the most obscure plugin name I've ever seen. Can you rename it to github-...

    That's the most obscure plugin name I've ever seen. Can you rename it to github-pull-request-builder ?

  3. Oct 17, 2012

    Mike Dougherty says:

    This should use a github API token instead of username and password, and it shou...

    This should use a github API token instead of username and password, and it should also use the API made explicitly for this feature.

    https://github.com/blog/1227-commit-status-api

    1. Oct 18, 2012

      Chuck LeDuc Díaz says:

      Agreed 100%. The password in clear is particularly frightening.

      Agreed 100%. The password in clear is particularly frightening.

  4. Jan 16, 2013

    Manuel de la Peña says:

    Hi all! Could you explain how pull request status is changed in Github? And...

    Hi all!

    Could you explain how pull request status is changed in Github? And which configuration of this plugin (related to users: repos' admin, whitelisted, etc) is necessary to mark that status in every Jenkins build?

    Thanks!

  5. Jan 16, 2013

    Manuel de la Peña says:

    It also would be nice to configure the message automatically written in the pull...

    It also would be nice to configure the message automatically written in the pull request: "Can one of the admins verify this patch?"

    Thanks!

  6. Apr 07, 2014

    Marc Abramowitz says:

    I just upgraded from 1.9 to 1.11.2 and our pull requests stopped building. Downg...

    I just upgraded from 1.9 to 1.11.2 and our pull requests stopped building. Downgraded and they work again.

    If you email me, I can try to get you more info to debug the issue. Could even hope on IM or IRC if you want.

    1. Apr 08, 2014

      Marc Abramowitz says:

      OK, I did a little more digging and eventually discovered that 1.11.2 has a know...

      OK, I did a little more digging and eventually discovered that 1.11.2 has a known issue with the rate limit API of GitHub Enterprise installations. It is fixed in the following pull request, which has been merged:

      https://github.com/janinko/ghprb/pull/127

      but there is not yet an official release of the plugin with this change.

      Can we get a new release please?

  7. Jun 10, 2014

    Jens Brejner says:

    It seems I have difficulties making this plugin work with multiple repositories ...

    It seems I have difficulties making this plugin work with multiple repositories ? I want this plugin to look for PR's on a private repository on github, and that works nice - but for testing the contents of the github repo, I need the contents of another repository (locally hosted on our inside). So I use the "Mulitple SCM's" plugin, to clone both repositories, and it appears that this plugin attempts to find the PR SHA in both repositories - which naturally fails.

    Am I correct in assuming that multiple repositories is not supported by the plugin ?

    Can you suggest a way to achieve my goal in some way ?

    1. Jul 28, 2014

      Tomoe Sugihara says:

      I think I'm getting the same. I get the following error on the 2nd github projec...

      I think I'm getting the same. I get the following error on the 2nd github project, which is not related to the one that pull request builder is listening on.  

      using .gitcredentials to set credentials
       > git config --local credential.helper store --file=/tmp/git4437896140776317333.credentials
       > git fetch --tags --progress https://github.com/accountX/projectX +refs/heads/*:refs/remotes/origin/*
       > git config --local --remove-section credential
       > git rev-parse origin/pr/220/merge^{commit}
      FATAL: Command "git rev-parse origin/pr/220/merge^{commit}" returned status code 128:
      stdout: origin/pr/220/merge^{commit}
      
      stderr: fatal: ambiguous argument 'origin/pr/220/merge^{commit}': unknown revision or path not in the working tree.
      Use '--' to separate paths from revisions
      
      hudson.plugins.git.GitException: Command "git rev-parse origin/pr/220/merge^{commit}" returned status code 128:
      stdout: origin/pr/220/merge^{commit}
      
      stderr: fatal: ambiguous argument 'origin/pr/220/merge^{commit}': unknown revision or path not in the working tree.
      Use '--' to separate paths from revisions
      
      	at org.jenkinsci.plugins.gitclient.CliGitAPIImpl.launchCommandIn(CliGitAPIImpl.java:1406)
      	at org.jenkinsci.plugins.gitclient.CliGitAPIImpl.launchCommandIn(CliGitAPIImpl.java:1382)
      	at org.jenkinsci.plugins.gitclient.CliGitAPIImpl.launchCommandIn(CliGitAPIImpl.java:1378)
      	at org.jenkinsci.plugins.gitclient.CliGitAPIImpl.launchCommand(CliGitAPIImpl.java:1092)
      	at org.jenkinsci.plugins.gitclient.CliGitAPIImpl.launchCommand(CliGitAPIImpl.java:1102)
      	at org.jenkinsci.plugins.gitclient.CliGitAPIImpl.revParse(CliGitAPIImpl.java:498)
      	at hudson.plugins.git.GitAPI.revParse(GitAPI.java:257)
      	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
      	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      	at java.lang.reflect.Method.invoke(Method.java:606)
      	at hudson.remoting.RemoteInvocationHandler$RPCRequest.perform(RemoteInvocationHandler.java:309)
      	at hudson.remoting.RemoteInvocationHandler$RPCRequest.call(RemoteInvocationHandler.java:290)
      	at hudson.remoting.RemoteInvocationHandler$RPCRequest.call(RemoteInvocationHandler.java:249)
      	at hudson.remoting.UserRequest.perform(UserRequest.java:118)
      	at hudson.remoting.UserRequest.perform(UserRequest.java:48)
      	at hudson.remoting.Request$2.run(Request.java:328)
      	at hudson.remoting.InterceptingExecutorService$1.call(InterceptingExecutorService.java:72)
      	at java.util.concurrent.FutureTask.run(FutureTask.java:262)
      	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
      	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
      	at java.lang.Thread.run(Thread.java:744)
      

      I'm now trying to workaround by checking out repos manually by shell executor at the beginning of the job. But it'd be great if this use case is supported.

  8. Oct 21, 2014

    Eric Lordahl says:

    Can you guys update the ChangeLog?

    Can you guys update the ChangeLog?

  9. Oct 21, 2014

    Eric Lordahl says:

    Can you guys update the ChangeLog?

    Can you guys update the ChangeLog?

  10. May 26

    gruberro - says:

    Hi guys, I've ran several times into the problem that double quotes or an apost...

    Hi guys,

    I've ran several times into the problem that double quotes or an apostrophe in a commit (e.g. for a revert commit) breaks the build pipeline. The build itself is done with Ant and the non-escaped auto-attached ghprb-params are breaking.

    [build] $ ant -file build.xml -DghprbTriggerAuthorEmail= -DghprbTargetBranch=master -DghprbSourceBranch=fix-wsod "-DghprbActualCommitAuthor=Foo Bar" -Dsha1=origin/pr/1337/merge -DghprbPullLink=https://github.com/xxx/xxx/pull/1337 -DghprbActualCommit=d4668b1798373a99e0f845bbbc3e9f01337dfda7 -DGIT_BRANCH=fix-wsod -DghprbPullAuthorEmail= -DghprbActualCommitAuthorEmail=github@example.com -DghprbTriggerAuthor= -DghprbPullId=1337 "-DghprbPullTitle=Reverts "Bad commit"" "-DghprbPullDescription=GitHub pull request #1337 of commit d4668b1798373a99e0f845bbbc3e9f01337dfda7 automatically merged."
    /usr/bin/ant: 1: eval: Syntax error: end of file unexpected

    Any suggestion of how to work around or where to write a feature request (gpprb vs. Ant-builder)?

  11. Jun 18

    stanley shen says:

    Hello, all I am using the latest one, but in configuration page I cannot find a...

    Hello, all

    I am using the latest one, but in configuration page I cannot find a place to use existed git access token.

    What should I do if I want to use an existed access token.

    1. Jun 19

      David Tanner says:

      Use the Add button to add a set of credentials.  Use Secret Text to add the...

      Use the Add button to add a set of credentials.  Use Secret Text to add the token, and Username Password for user setup.

      1. Jun 22

        stanley shen says:

        yes, I tried that way, but it always failed for the message like attached. Fina...

        yes, I tried that way, but it always failed for the message like attached.

        Finally I turn to 1.22.4 version, and it works for me then.