diff --git a/app.cfg.example b/app.cfg.example
index 7a6a1073..1888e499 100644
--- a/app.cfg.example
+++ b/app.cfg.example
@@ -219,3 +219,13 @@ no_matching_tarball = No tarball matching `{tarball_pattern}` found in job dir.
multiple_tarballs = Found {num_tarballs} tarballs in job dir - only 1 matching `{tarball_pattern}` expected.
job_result_unknown_fmt = :shrug: UNKNOWN _(click triangle for detailed information)_
- Job results file `{filename}` does not exist in job directory, or parsing it failed.
- No artefacts were found/reported.
job_test_unknown_fmt = :shrug: UNKNOWN _(click triangle for detailed information)_
- Job test file `{filename}` does not exist in job directory, or parsing it failed.
+
+[download_pr_comments]
+git_clone_failure = Unable to clone the target repository.
+git_clone_tip = _Tip: This could be a connection failure. Try again and if the issue remains check if the address is correct_.
+git_checkout_failure = Unable to checkout to the correct branch.
+git_checkout_tip = _Tip: Ensure that the branch name is correct and the target branch is available._
+curl_failure = Unable to download the `.diff` file.
+curl_tip = _Tip: This could be a connection failure. Try again and if the issue remains check if the address is correct_
+git_apply_failure = Unable to download or merge changes between the source branch and the destination branch.
+git_apply_tip = _Tip: This can usually be resolved by syncing your branch and resolving any merge conflicts._
diff --git a/tasks/build.py b/tasks/build.py
index 1ce8e956..473599b6 100644
--- a/tasks/build.py
+++ b/tasks/build.py
@@ -40,9 +40,22 @@
BUILD_PERMISSION = "build_permission"
CFG_DIRNAME = "cfg"
CONTAINER_CACHEDIR = "container_cachedir"
+CURL_FAILURE = "curl_failure"
+CURL_TIP = "curl_tip"
CVMFS_CUSTOMIZATIONS = "cvmfs_customizations"
DEFAULT_JOB_TIME_LIMIT = "24:00:00"
+DOWNLOAD_PR_COMMENTS = "download_pr_comments"
+ERROR_CURL = "curl"
+ERROR_GIT_APPLY = "git apply"
+ERROR_GIT_CHECKOUT = "git checkout"
+ERROR_GIT_CLONE = "curl"
GITHUB = "github"
+GIT_CLONE_FAILURE = "git_clone_failure"
+GIT_CLONE_TIP = "git_clone_tip"
+GIT_CHECKOUT_FAILURE = "git_checkout_failure"
+GIT_CHECKOUT_TIP = "git_checkout_tip"
+GIT_APPLY_FAILURE = "git_apply_failure"
+GIT_APPLY_TIP = "git_apply_tip"
HTTPS_PROXY = "https_proxy"
HTTP_PROXY = "http_proxy"
INITIAL_COMMENT = "initial_comment"
@@ -337,7 +350,9 @@ def download_pr(repo_name, branch_name, pr, arch_job_dir):
arch_job_dir (string): working directory of the job to be submitted
Returns:
- None (implicitly)
+ None (implicitly), in case an error is caught in the git clone, git checkout, curl,
+ or git apply commands, returns the output, stderror, exit code and a string
+ stating which of these commands failed.
"""
# download pull request to arch_job_dir
# - 'git clone' repository into arch_job_dir (NOTE 'git clone' requires that
@@ -346,20 +361,91 @@ def download_pr(repo_name, branch_name, pr, arch_job_dir):
# - 'curl' diff for pull request
# - 'git apply' diff file
git_clone_cmd = ' '.join(['git clone', f'https://github.com/{repo_name}', arch_job_dir])
- clone_output, clone_error, clone_exit_code = run_cmd(git_clone_cmd, "Clone repo", arch_job_dir)
+ log(f'cloning with command {git_clone_cmd}')
+ clone_output, clone_error, clone_exit_code = run_cmd(
+ git_clone_cmd, "Clone repo", arch_job_dir, raise_on_error=False
+ )
+ if clone_exit_code != 0:
+ error_stage = ERROR_GIT_CLONE
+ return clone_output, clone_error, clone_exit_code, error_stage
git_checkout_cmd = ' '.join([
'git checkout',
branch_name,
])
- checkout_output, checkout_err, checkout_exit_code = run_cmd(git_checkout_cmd,
- "checkout branch '%s'" % branch_name, arch_job_dir)
+ log(f'checking out with command {git_checkout_cmd}')
+ checkout_output, checkout_err, checkout_exit_code = run_cmd(
+ git_checkout_cmd, "checkout branch '%s'" % branch_name, arch_job_dir, raise_on_error=False
+ )
+ if checkout_exit_code != 0:
+ error_stage = ERROR_GIT_CHECKOUT
+ return checkout_output, checkout_err, checkout_exit_code, error_stage
curl_cmd = f'curl -L https://github.com/{repo_name}/pull/{pr.number}.diff > {pr.number}.diff'
- curl_output, curl_error, curl_exit_code = run_cmd(curl_cmd, "Obtain patch", arch_job_dir)
+ log(f'curl with command {curl_cmd}')
+ curl_output, curl_error, curl_exit_code = run_cmd(
+ curl_cmd, "Obtain patch", arch_job_dir, raise_on_error=False
+ )
+ if curl_exit_code != 0:
+ error_stage = ERROR_CURL
+ return curl_output, curl_error, curl_exit_code, error_stage
git_apply_cmd = f'git apply {pr.number}.diff'
- git_apply_output, git_apply_error, git_apply_exit_code = run_cmd(git_apply_cmd, "Apply patch", arch_job_dir)
+ log(f'git apply with command {git_apply_cmd}')
+ git_apply_output, git_apply_error, git_apply_exit_code = run_cmd(
+ git_apply_cmd, "Apply patch", arch_job_dir, raise_on_error=False
+ )
+ if git_apply_exit_code != 0:
+ error_stage = ERROR_GIT_APPLY
+ return git_apply_output, git_apply_error, git_apply_exit_code, error_stage
+
+
+def comment_download_pr(base_repo_name, pr, download_pr_exit_code, download_pr_error, error_stage):
+ """
+ Handle download_pr() exit code and write helpful comment to PR in case of failure
+
+ Args:
+ base_repo_name (string): name of the repository (format USER_OR_ORGANISATION/REPOSITORY)
+ pr (github.PullRequest.PullRequest): instance representing the pull request
+ download_pr_exit_code (int): exit code from download_pr(). 0 if all tasks were successful,
+ otherwise it corresponds to the error codes of git clone, git checkout, git apply, or curl.
+ download_pr_error (string): none, or the output of stderr from git clone, git checkout, git apply or curl.
+ error_stage (string): a string informing the stage where download_pr() failed. Can be 'git clone',
+ 'git checkout', 'curl', or 'git apply'.
+
+ Return:
+ None (implicitly). A comment is created in the appropriate PR.
+
+ """
+ if download_pr_exit_code != 0:
+ fn = sys._getframe().f_code.co_name
+
+ download_pr_comments_cfg = config.read_config()[DOWNLOAD_PR_COMMENTS]
+ if error_stage == ERROR_GIT_CLONE:
+ download_comment = (f"`{download_pr_error}`"
+ f"{download_pr_comments_cfg[GIT_CLONE_FAILURE]}"
+ f"{download_pr_comments_cfg[GIT_CLONE_TIP]}")
+ elif error_stage == ERROR_GIT_CHECKOUT:
+ download_comment = (f"`{download_pr_error}`"
+ f"{download_pr_comments_cfg[GIT_CHECKOUT_FAILURE]}"
+ f"{download_pr_comments_cfg[GIT_CHECKOUT_TIP]}")
+ elif error_stage == ERROR_CURL:
+ download_comment = (f"`{download_pr_error}`"
+ f"{download_pr_comments_cfg[CURL_FAILURE]}"
+ f"{download_pr_comments_cfg[CURL_TIP]}")
+ elif error_stage == ERROR_GIT_APPLY:
+ download_comment = (f"`{download_pr_error}`"
+ f"{download_pr_comments_cfg[GIT_APPLY_FAILURE]}"
+ f"{download_pr_comments_cfg[GIT_APPLY_TIP]}")
+
+ download_comment = pr_comments.create_comment(
+ repo_name=base_repo_name, pr_number=pr.number, comment=download_comment
+ )
+ if download_comment:
+ log(f"{fn}(): created PR issue comment with id {download_comment.id}")
+ else:
+ log(f"{fn}(): failed to create PR issue comment")
+ raise ValueError("Unable to download PR and/or sync changes")
def apply_cvmfs_customizations(cvmfs_customizations, arch_job_dir):
@@ -454,8 +540,10 @@ def prepare_jobs(pr, cfg, event_info, action_filter):
log(f"{fn}(): job_dir '{job_dir}'")
# TODO optimisation? download once, copy and cleanup initial copy?
- download_pr(base_repo_name, base_branch_name, pr, job_dir)
-
+ download_pr_output, download_pr_error, download_pr_exit_code, error_stage = download_pr(
+ base_repo_name, base_branch_name, pr, job_dir
+ )
+ comment_download_pr(base_repo_name, pr, download_pr_exit_code, download_pr_error, error_stage)
# prepare job configuration file 'job.cfg' in directory /cfg
cpu_target = '/'.join(arch.split('/')[1:])
os_type = arch.split('/')[0]