blob: 74571ebace1646083b6a9978ed7ff15e0f1b9c5e [file] [log] [blame]
Sergey Kolekonovba203982016-12-21 18:32:17 +04001package com.mirantis.mk
2
3/**
4 *
5 * Git functions
6 *
7 */
8
9/**
10 * Checkout single git repository
11 *
12 * @param path Directory to checkout repository to
13 * @param url Source Git repository URL
14 * @param branch Source Git repository branch
15 * @param credentialsId Credentials ID to use for source Git
Jakub Josef7dccebe2017-03-06 18:08:32 +010016 * @param poll Enable git polling (default true)
17 * @param timeout Set checkout timeout (default 10)
Jakub Josef61f29e62017-03-08 16:42:06 +010018 * @param depth Git depth param (default 0 means no depth)
Alexandr Lovtsove818e102019-07-29 14:45:01 +030019 * @param reference Git reference param to checkout (default empyt, i.e. no reference)
Viktor Karpochevcd42bbc2023-03-03 14:51:58 +030020 * @param withWipeOut Enable workspace wipe before checkout
Sergey Kolekonovba203982016-12-21 18:32:17 +040021 */
Viktor Karpochevcd42bbc2023-03-03 14:51:58 +030022def checkoutGitRepository(path, url, branch, credentialsId = null, poll = true, timeout = 10, depth = 0, reference = '', withWipeOut = false){
Alexandr Lovtsov73786142019-09-02 17:45:12 +030023 def branch_name = reference ? 'FETCH_HEAD' : "*/${branch}"
Viktor Karpochevcd42bbc2023-03-03 14:51:58 +030024 def scmExtensions = [
25 [$class: 'CheckoutOption', timeout: timeout],
26 [$class: 'CloneOption', depth: depth, noTags: false, shallow: depth > 0, timeout: timeout]
27 ]
28
29 // wipe workspace before checkout
30 if (withWipeOut) {
31 scmExtensions.add([$class: 'WipeWorkspace'])
32 }
33
Sergey Kolekonovba203982016-12-21 18:32:17 +040034 dir(path) {
Jakub Josef6fa8cb12017-03-06 18:20:08 +010035 checkout(
36 changelog:true,
37 poll: poll,
38 scm: [
39 $class: 'GitSCM',
Alexandr Lovtsov73786142019-09-02 17:45:12 +030040 branches: [[name: branch_name]],
Jakub Josef6fa8cb12017-03-06 18:20:08 +010041 doGenerateSubmoduleConfigurations: false,
Viktor Karpochevcd42bbc2023-03-03 14:51:58 +030042 extensions: scmExtensions,
Jakub Josef6fa8cb12017-03-06 18:20:08 +010043 submoduleCfg: [],
Alexandr Lovtsov73786142019-09-02 17:45:12 +030044 userRemoteConfigs: [[url: url, credentialsId: credentialsId, refspec: reference]]]
Jakub Josef6fa8cb12017-03-06 18:20:08 +010045 )
Sergey Kolekonovba203982016-12-21 18:32:17 +040046 }
47}
48
49/**
50 * Parse HEAD of current directory and return commit hash
51 */
52def getGitCommit() {
53 git_commit = sh (
54 script: 'git rev-parse HEAD',
55 returnStdout: true
56 ).trim()
57 return git_commit
58}
59
60/**
Ales Komarekfb7cbcb2017-02-24 14:02:03 +010061 * Change actual working branch of repo
62 *
63 * @param path Path to the git repository
64 * @param branch Branch desired to switch to
65 */
66def changeGitBranch(path, branch) {
piudin335f1bc2025-09-18 22:37:13 +040067 def git_cmd
Ales Komarekfb7cbcb2017-02-24 14:02:03 +010068 dir(path) {
69 git_cmd = sh (
Leontii Istominb4f4ae12018-02-27 20:25:43 +010070 script: "git checkout ${branch}",
Ales Komarekfb7cbcb2017-02-24 14:02:03 +010071 returnStdout: true
72 ).trim()
73 }
74 return git_cmd
75}
76
77/**
Ales Komarekc3a8b972017-03-24 13:57:25 +010078 * Get remote URL
79 *
80 * @param name Name of remote (default any)
81 * @param type Type (fetch or push, default fetch)
82 */
83def getGitRemote(name = '', type = 'fetch') {
84 gitRemote = sh (
85 script: "git remote -v | grep '${name}' | grep ${type} | awk '{print \$2}' | head -1",
86 returnStdout: true
87 ).trim()
88 return gitRemote
89}
90
91/**
Alexandr Lovtsovd1540612020-05-07 14:10:37 +030092 * Get commit message for given commit reference
93 */
94def getGitCommitMessage(String path, String commitRef = 'HEAD') {
95 dir(path) {
96 commitMsg = sh (
97 script: "git log --format=%B -n 1 ${commitRef}",
98 returnStdout: true
99 ).trim()
100 }
101 return commitMsg
102}
103
104/**
Ales Komarekc3a8b972017-03-24 13:57:25 +0100105 * Create new working branch for repo
106 *
107 * @param path Path to the git repository
108 * @param branch Branch desired to switch to
109 */
110def createGitBranch(path, branch) {
111 def git_cmd
112 dir(path) {
113 git_cmd = sh (
114 script: "git checkout -b ${branch}",
115 returnStdout: true
116 ).trim()
117 }
118 return git_cmd
119}
120
121/**
Ales Komarekfb7cbcb2017-02-24 14:02:03 +0100122 * Commit changes to the git repo
123 *
124 * @param path Path to the git repository
125 * @param message A commit message
Denis Egorenkof4c45512019-03-04 15:53:36 +0400126 * @param global Use global config
Oleksii Grudeva64e5b22019-06-11 11:21:02 +0300127 * @param amend Whether to use "--amend" in commit command
Ales Komarekfb7cbcb2017-02-24 14:02:03 +0100128 */
Oleksii Grudeva64e5b22019-06-11 11:21:02 +0300129def commitGitChanges(path, message, gitEmail='jenkins@localhost', gitName='jenkins-slave', global=false, amend=false) {
Ales Komarekc3a8b972017-03-24 13:57:25 +0100130 def git_cmd
Oleksii Grudeva64e5b22019-06-11 11:21:02 +0300131 def gitOpts
Denis Egorenkof4c45512019-03-04 15:53:36 +0400132 def global_arg = ''
133 if (global) {
134 global_arg = '--global'
135 }
Oleksii Grudeva64e5b22019-06-11 11:21:02 +0300136 if (amend) {
137 gitOpts = '--amend'
138 } else {
139 gitOpts = ''
140 }
Alexandr Lovtsov2fb93482020-06-16 14:39:43 +0300141 def gitEnv = [
Maxim Rasskazovbf095032023-11-13 22:38:58 +0400142 "GIT_AUTHOR_NAME='${gitName}'",
143 "GIT_AUTHOR_EMAIL='${gitEmail}'",
144 "GIT_COMMITTER_NAME='${gitName}'",
145 "GIT_COMMITTER_EMAIL='${gitEmail}'",
Alexandr Lovtsov2fb93482020-06-16 14:39:43 +0300146 ]
Ales Komarekfb7cbcb2017-02-24 14:02:03 +0100147 dir(path) {
Denis Egorenkof4c45512019-03-04 15:53:36 +0400148 sh "git config ${global_arg} user.email '${gitEmail}'"
149 sh "git config ${global_arg} user.name '${gitName}'"
Tomáš Kukráldf7bebc2017-03-27 15:12:43 +0200150
Ales Komarekfb7cbcb2017-02-24 14:02:03 +0100151 sh(
152 script: 'git add -A',
153 returnStdout: true
154 ).trim()
Alexandr Lovtsov2fb93482020-06-16 14:39:43 +0300155 withEnv(gitEnv) {
156 git_cmd = sh(
157 script: "git commit ${gitOpts} -m '${message}'",
158 returnStdout: true
159 ).trim()
160 }
Ales Komarekfb7cbcb2017-02-24 14:02:03 +0100161 }
162 return git_cmd
163}
164
Ales Komarekfb7cbcb2017-02-24 14:02:03 +0100165/**
166 * Push git changes to remote repo
167 *
Ales Komarekc3a8b972017-03-24 13:57:25 +0100168 * @param path Path to the local git repository
Ales Komarekfb7cbcb2017-02-24 14:02:03 +0100169 * @param branch Branch on the remote git repository
170 * @param remote Name of the remote repository
Ales Komarekc3a8b972017-03-24 13:57:25 +0100171 * @param credentialsId Credentials with write permissions
Ales Komarekfb7cbcb2017-02-24 14:02:03 +0100172 */
Ales Komarekc3a8b972017-03-24 13:57:25 +0100173def pushGitChanges(path, branch = 'master', remote = 'origin', credentialsId = null) {
174 def ssh = new com.mirantis.mk.Ssh()
Ales Komarekfb7cbcb2017-02-24 14:02:03 +0100175 dir(path) {
Ales Komarekc3a8b972017-03-24 13:57:25 +0100176 if (credentialsId == null) {
177 sh script: "git push ${remote} ${branch}"
178 }
179 else {
180 ssh.prepareSshAgentKey(credentialsId)
181 ssh.runSshAgentCommand("git push ${remote} ${branch}")
182 }
Ales Komarekfb7cbcb2017-02-24 14:02:03 +0100183 }
Ales Komarekfb7cbcb2017-02-24 14:02:03 +0100184}
185
Ales Komarekc3a8b972017-03-24 13:57:25 +0100186
Sergey Kolekonovba203982016-12-21 18:32:17 +0400187/**
Filip Pytloun49d66302017-03-06 10:26:22 +0100188 * Mirror git repository, merge target changes (downstream) on top of source
189 * (upstream) and push target or both if pushSource is true
190 *
191 * @param sourceUrl Source git repository
192 * @param targetUrl Target git repository
Ivan Berezovskiycf269442019-07-18 16:15:26 +0400193 * @param credentialsId Credentials id to use for accessing target repositories
Filip Pytloun49d66302017-03-06 10:26:22 +0100194 * @param branches List or comma-separated string of branches to sync
195 * @param followTags Mirror tags
196 * @param pushSource Push back into source branch, resulting in 2-way sync
197 * @param pushSourceTags Push target tags into source or skip pushing tags
198 * @param gitEmail Email for creation of merge commits
199 * @param gitName Name for creation of merge commits
Sergey Kolekonovba203982016-12-21 18:32:17 +0400200 */
Ivan Berezovskiycf269442019-07-18 16:15:26 +0400201def mirrorGit(sourceUrl, targetUrl, credentialsId, branches, followTags = false, pushSource = false, pushSourceTags = false, gitEmail = 'jenkins@localhost', gitName = 'Jenkins', sourceRemote = 'origin') {
Jakub Josef668dc2b2017-06-19 16:55:26 +0200202 def common = new com.mirantis.mk.Common()
203 def ssh = new com.mirantis.mk.Ssh()
Sergey Kolekonovba203982016-12-21 18:32:17 +0400204 if (branches instanceof String) {
205 branches = branches.tokenize(',')
206 }
Ivan Berezovskiycf269442019-07-18 16:15:26 +0400207 // If both source and target repos are secured and accessible via http/https,
208 // we need to switch GIT_ASKPASS value when running git commands
209 def sourceAskPass
210 def targetAskPass
Sergey Kolekonovba203982016-12-21 18:32:17 +0400211
Ivan Berezovskiycf269442019-07-18 16:15:26 +0400212 def sshCreds = common.getCredentialsById(credentialsId, 'sshKey') // True if found
213 if (sshCreds) {
214 ssh.prepareSshAgentKey(credentialsId)
215 ssh.ensureKnownHosts(targetUrl)
216 sh "git config user.name '${gitName}'"
217 } else {
218 withCredentials([[$class : 'UsernamePasswordMultiBinding',
219 credentialsId : credentialsId,
220 passwordVariable: 'GIT_PASSWORD',
221 usernameVariable: 'GIT_USERNAME']]) {
222 sh """
223 set +x
224 git config --global credential.${targetUrl}.username \${GIT_USERNAME}
225 echo "echo \${GIT_PASSWORD}" > ${WORKSPACE}/${credentialsId}_askpass.sh
226 chmod +x ${WORKSPACE}/${credentialsId}_askpass.sh
227 git config user.name \${GIT_USERNAME}
228 """
229 sourceAskPass = env.GIT_ASKPASS ?: ''
230 targetAskPass = "${WORKSPACE}/${credentialsId}_askpass.sh"
231 }
232 }
Filip Pytloun49d66302017-03-06 10:26:22 +0100233 sh "git config user.email '${gitEmail}'"
Filip Pytloun49d66302017-03-06 10:26:22 +0100234
Jakub Josef1caa7ae2017-08-21 16:39:00 +0200235 def remoteExistence = sh(script: "git remote -v | grep ${TARGET_URL} | grep target", returnStatus: true)
Ivan Berezovskiycf269442019-07-18 16:15:26 +0400236 if(remoteExistence == 0) {
237 // silently try to remove target
238 sh(script: "git remote remove target", returnStatus: true)
Jakub Josef1caa7ae2017-08-21 16:39:00 +0200239 }
Ivan Berezovskiycf269442019-07-18 16:15:26 +0400240 sh("git remote add target ${TARGET_URL}")
241 if (sshCreds) {
242 ssh.agentSh "git remote update --prune"
243 } else {
244 env.GIT_ASKPASS = sourceAskPass
245 sh "git remote update ${sourceRemote} --prune"
246 env.GIT_ASKPASS = targetAskPass
247 sh "git remote update target --prune"
248 }
Filip Pytloun49d66302017-03-06 10:26:22 +0100249
Sergey Kolekonovba203982016-12-21 18:32:17 +0400250 for (i=0; i < branches.size; i++) {
251 branch = branches[i]
Jakub Josef668dc2b2017-06-19 16:55:26 +0200252 sh "git branch | grep ${branch} || git checkout -b ${branch}"
253 def resetResult = sh(script: "git checkout ${branch} && git reset --hard origin/${branch}", returnStatus: true)
254 if(resetResult != 0){
255 common.warningMsg("Cannot reset to origin/${branch} for perform git mirror, trying to reset from target/${branch}")
256 resetResult = sh(script: "git checkout ${branch} && git reset --hard target/${branch}", returnStatus: true)
257 if(resetResult != 0){
258 throw new Exception("Cannot reset even to target/${branch}, git mirroring failed!")
259 }
260 }
Sergey Kolekonovba203982016-12-21 18:32:17 +0400261
Sergey Kolekonovba203982016-12-21 18:32:17 +0400262 sh "git ls-tree target/${branch} && git merge --no-edit --ff target/${branch} || echo 'Target repository is empty, skipping merge'"
263 followTagsArg = followTags ? "--follow-tags" : ""
Ivan Berezovskiycf269442019-07-18 16:15:26 +0400264 if (sshCreds) {
265 ssh.agentSh "git push ${followTagsArg} target HEAD:${branch}"
266 } else {
267 sh "git push ${followTagsArg} target HEAD:${branch}"
268 }
Filip Pytloun49d66302017-03-06 10:26:22 +0100269
270 if (pushSource == true) {
271 followTagsArg = followTags && pushSourceTags ? "--follow-tags" : ""
Ivan Berezovskiycf269442019-07-18 16:15:26 +0400272 if (sshCreds) {
273 ssh.agentSh "git push ${followTagsArg} origin HEAD:${branch}"
274 } else {
275 sh "git push ${followTagsArg} origin HEAD:${branch}"
276 }
Filip Pytloun49d66302017-03-06 10:26:22 +0100277 }
278 }
Filip Pytloun49d66302017-03-06 10:26:22 +0100279 if (followTags == true) {
Ivan Berezovskiycf269442019-07-18 16:15:26 +0400280 if (sshCreds) {
281 ssh.agentSh "git push -f target --tags"
282 } else {
283 sh "git push -f target --tags"
284 }
Filip Pytloun49d66302017-03-06 10:26:22 +0100285
286 if (pushSourceTags == true) {
Ivan Berezovskiycf269442019-07-18 16:15:26 +0400287 if (sshCreds) {
288 ssh.agentSh "git push -f origin --tags"
289 } else {
290 sh "git push -f origin --tags"
291 }
Filip Pytloun49d66302017-03-06 10:26:22 +0100292 }
Sergey Kolekonovba203982016-12-21 18:32:17 +0400293 }
Jakub Josefecf8b452017-04-20 13:34:29 +0200294 sh "git remote rm target"
Ivan Berezovskiycf269442019-07-18 16:15:26 +0400295 if (!sshCreds) {
296 sh "set +x; rm -f ${targetAskPass}"
297 sh "git config --global --unset credential.${targetUrl}.username"
298 }
Sergey Kolekonovba203982016-12-21 18:32:17 +0400299}
Martin Polreich765f7ba2019-03-12 16:39:25 +0100300
301
302/**
303 * Return all branches for the defined git repository that match the matcher.
304 *
305 * @param repoUrl URL of git repository
306 * @param branchMatcher matcher to filter out the branches (If '' or '*', returns all branches without filtering)
307 * @return branchesList list of branches
308 */
309
310def getBranchesForGitRepo(repoUrl, branchMatcher = ''){
311
312 if (branchMatcher.equals("*")) {
313 branchMatcher = ''
314 }
315 branchesList = sh (
316 script: "git ls-remote --heads ${repoUrl} | cut -f2 | grep -e '${branchMatcher}' | sed 's/refs\\/heads\\///g'",
317 returnStdout: true
318 ).trim()
319 return branchesList.tokenize('\n')
Oleksii Grudeva64e5b22019-06-11 11:21:02 +0300320}
321
Mykyta Karpin82437932019-06-06 14:08:18 +0300322/**
323 * Method for preparing a tag to be SemVer 2 compatible, and can handle next cases:
324 * - length of tag splitted by dots is more than 3
325 * - first part of splitted tag starts not from digit
326 * - length of tag is lower than 3
327 *
328 * @param tag String which contains a git tag from repository
329 * @return HashMap HashMap in the form: ['version': 'x.x.x', 'extra': 'x.x.x'], extra
330 * is added only if size of original tag splitted by dots is more than 3
331 */
332
333def prepareTag(tag){
334 def parts = tag.tokenize('.')
335 def res = [:]
336 // Handle case with tags like v1.1.1
337 parts[0] = parts[0].replaceFirst("[^\\d.]", '')
338 // handle different sizes of tags - 1.1.1.1 or 1.1.1.1rc1
339 if (parts.size() > 3){
340 res['extra'] = parts[3..-1].join('.')
341 } else if (parts.size() < 3){
342 (parts.size()..2).each {
343 parts[it] = '0'
344 }
345 }
346 res['version'] = "${parts[0]}.${parts[1]}.${parts[2]}"
347 return res
348}
349
350/**
351 * Method for incrementing SemVer 2 compatible version
352 *
353 * @param version String which contains main part of SemVer2 version - '2.1.0'
354 * @return string String conaining version with Patch part of version incremented by 1
355 */
356
357def incrementVersion(version){
358 def parts = checkVersion(version)
359 return "${parts[0]}.${parts[1]}.${parts[2].toInteger() + 1}"
360}
361
362/**
363 * Method for checking whether version is compatible with Sem Ver 2
364 *
365 * @param version String which contains main part of SemVer2 version - '2.1.0'
366 * @return list With 3 strings as result of splitting version by dots
367 */
368
369def checkVersion(version) {
370 def parts = version.tokenize('.')
371 if (parts.size() != 3 || !(parts[0] ==~ /^\d+/)) {
372 error "Bad version ${version}"
373 }
374 return parts
375}
376
377/**
378 * Method for constructing SemVer2 compatible version from tag in Git repository:
379 * - if current commit matches the last tag, last tag will be returned as version
380 * - if no tag found assuming no release was done, version will be 0.0.1 with pre release metadata
381 * - if tag found - patch part of version will be incremented and pre-release metadata will be added
382 *
383 *
384 * @param repoDir String which contains path to directory with git repository
385 * @param allowNonSemVer2 Bool whether to allow working with tags which aren't compatible
386 * with Sem Ver 2 (not in form X.Y.Z). if set to true tag will be
387* converted to Sem Ver 2 version e.g tag 1.1.1.1rc1 -> version 1.1.1-1rc1
388 * @return version String
389 */
390def getVersion(repoDir, allowNonSemVer2 = false) {
391 def common = new com.mirantis.mk.Common()
392 dir(repoDir){
393 def cmd = common.shCmdStatus('git describe --tags --first-parent --abbrev=0')
394 def tag_data = [:]
395 def last_tag = cmd['stdout'].trim()
396 def commits_since_tag
397 if (cmd['status'] != 0){
398 if (cmd['stderr'].contains('fatal: No names found, cannot describe anything')){
399 common.warningMsg('No parent tag found, using initial version 0.0.0')
400 tag_data['version'] = '0.0.0'
401 commits_since_tag = sh(script: 'git rev-list --count HEAD', returnStdout: true).trim()
402 } else {
403 error("Something went wrong, cannot find git information ${cmd['stderr']}")
404 }
405 } else {
406 tag_data['version'] = last_tag
407 commits_since_tag = sh(script: "git rev-list --count ${last_tag}..HEAD", returnStdout: true).trim()
408 }
409 try {
410 checkVersion(tag_data['version'])
411 } catch (Exception e) {
412 if (allowNonSemVer2){
413 common.errorMsg(
414 """Git tag isn't compatible with SemVer2, but allowNonSemVer2 is set.
415 Trying to convert git tag to Sem Ver 2 compatible version
416 ${e.message}""")
417 tag_data = prepareTag(tag_data['version'])
418 } else {
419 error("Git tag isn't compatible with SemVer2\n${e.message}")
420 }
421 }
422 // If current commit is exact match to the first parent tag than return it
423 def pre_release_meta = []
424 if (tag_data.get('extra')){
425 pre_release_meta.add(tag_data['extra'])
426 }
427 if (common.shCmdStatus('git describe --tags --first-parent --exact-match')['status'] == 0){
428 if (pre_release_meta){
429 return "${tag_data['version']}-${pre_release_meta[0]}"
430 } else {
431 return tag_data['version']
432 }
433 }
434 // If we away from last tag for some number of commits - add additional metadata and increment version
435 pre_release_meta.add(commits_since_tag)
436 def next_version = incrementVersion(tag_data['version'])
437 def commit_sha = sh(script: 'git rev-parse --short=7 HEAD', returnStdout: true).trim()
438 return "${next_version}-${pre_release_meta.join('.')}-${commit_sha}"
439 }
440}
Mykyta Karpinf5b6c162019-08-08 14:28:15 +0300441
442
443/**
444 * Method for uploading a change request
445 *
446 * @param repo String which contains path to directory with git repository
447 * @param credentialsId Credentials id to use for accessing target repositories
448 * @param commit Id of commit which should be uploaded
449 * @param branch Name of the branch for uploading
450 * @param topic Topic of the change
451 *
452 */
453def pushForReview(repo, credentialsId, commit, branch, topic='', remote='origin') {
454 def common = new com.mirantis.mk.Common()
455 def ssh = new com.mirantis.mk.Ssh()
456 common.infoMsg("Uploading commit ${commit} to ${branch} for review...")
457
458 def pushArg = "${commit}:refs/for/${branch}"
459 def process = [:]
460 if (topic){
461 pushArg += '%topic=' + topic
462 }
463 dir(repo){
464 ssh.prepareSshAgentKey(credentialsId)
465 ssh.runSshAgentCommand("git push ${remote} ${pushArg}")
466 }
467}
468
469/**
470 * Generates a commit message with predefined or auto generate change id. If change
471 * id isn't provided, changeIdSeed and current sha of git head will be used in
472 * generation of commit change id.
473 *
474 * @param repo String which contains path to directory with git repository
475 * @param message Commit message main part
476 * @param changeId User defined change-id usually sha1 hash
477 * @param changeIdSeed Custom part of change id which can be added during change id generation
478 *
479 *
480 * @return commitMessage Multiline String with generated commit message
481 */
482def genCommitMessage(repo, message, changeId = '', changeIdSeed = ''){
483 def git = new com.mirantis.mk.Git()
484 def common = new com.mirantis.mk.Common()
485 def commitMessage
486 def id = changeId
487 def seed = changeIdSeed
488 if (!id) {
489 if (!seed){
490 seed = common.generateRandomHashString(32)
491 }
492 def head_sha
493 dir(repo){
494 head_sha = git.getGitCommit()
495 }
496 id = 'I' + sh(script: 'echo -n ' + seed + head_sha + ' | sha1sum | awk \'{print $1}\'', returnStdout: true)
497 }
498 commitMessage =
499 """${message}
500
501 |Change-Id: ${id}
502 """.stripMargin()
503
504 return commitMessage
505}
Alexandr Lovtsov4fdafd92019-09-09 17:52:16 +0200506
507/**
508 * Update (or create if cannot find) gerrit change request
509 *
510 * @param params Map of parameters to customize commit
511 * - gerritAuth A map containing information about Gerrit. Should include HOST, PORT and USER
512 * - credentialsId Jenkins credentials id for gerrit
513 * - repo Local directory with repository
514 * - comment Commit comment
515 * - change_id_seed Custom part of change id which can be added during change id generation
516 * - branch Name of the branch for uploading
517 * - topic Topic of the change
518 * - project Gerrit project to search in for gerrit change request
519 * - status Change request's status to search for
520 * - changeAuthorEmail Author's email of the change
521 * - changeAuthorName Author's name of the change
Mykyta Karpincfbbbd82021-11-03 16:58:07 +0000522 * - forceUpdate Whether to update change if no diff between local state and remote
Mykyta Karpin28695382022-04-22 16:16:44 +0300523 * - gerritPatch Maps with patch information (result of gerrit.findGerritChange)
524 * - amend Do amend current patch
525 */
Alexandr Lovtsov4fdafd92019-09-09 17:52:16 +0200526def updateChangeRequest(Map params) {
527 def gerrit = new com.mirantis.mk.Gerrit()
Mykyta Karpincfbbbd82021-11-03 16:58:07 +0000528 def common = new com.mirantis.mk.Common()
Alexandr Lovtsov4fdafd92019-09-09 17:52:16 +0200529
530 def commitMessage
Alexandr Lovtsov4fdafd92019-09-09 17:52:16 +0200531 def creds = params['credentialsId']
532 def repo = params['repo']
533 def comment = params['comment']
534 def change_id_seed = params.get('change_id_seed', JOB_NAME)
535 def branch = params['branch']
536 def topic = params['topic']
Alexandr Lovtsov4fdafd92019-09-09 17:52:16 +0200537 def changeAuthorEmail = params['changeAuthorEmail']
538 def changeAuthorName = params['changeAuthorName']
Mykyta Karpincfbbbd82021-11-03 16:58:07 +0000539 def forceUpdate = params.get('forceUpdate', true)
Mykyta Karpin28695382022-04-22 16:16:44 +0300540 def amend = params.get('amend', false)
541 def jsonChange = params.get('gerritPatch', [:])
Maxim Rasskazov427435f2020-09-16 15:38:56 +0400542 def changeId = params.get('changeId', '')
Sergey Kolekonov02d70022023-03-28 14:22:36 +0600543 def remote = params.get('remote', 'origin')
Alexandr Lovtsov4fdafd92019-09-09 17:52:16 +0200544 def commit
Mykyta Karpin28695382022-04-22 16:16:44 +0300545
546 if (!jsonChange) {
547 def auth = params['gerritAuth']
548 def status = params.get('status', 'open')
549 def project = params['project']
550 def changeParams = ['owner': auth['USER'], 'status': status, 'project': project, 'branch': branch, 'topic': topic]
551 def gerritChange = gerrit.findGerritChange(creds, auth, changeParams, '--current-patch-set')
552 if (gerritChange) {
553 jsonChange = readJSON text: gerritChange
554 }
555 }
556 if (jsonChange) {
Alexandr Lovtsov4fdafd92019-09-09 17:52:16 +0200557 changeId = jsonChange['id']
Mykyta Karpincfbbbd82021-11-03 16:58:07 +0000558 if(!forceUpdate){
559 def ref = jsonChange['currentPatchSet']['ref']
560 def res
561 dir(repo){
562 sshagent (credentials: [creds]){
563 res = common.shCmdStatus("git fetch origin ${ref} && git diff --quiet --exit-code FETCH_HEAD")["status"]
564 }
565 }
566 if (res == 0){
567 common.infoMsg("Current patch set ${ref} is up to date, no need to update")
568 return
569 }
570 }
Alexandr Lovtsov4fdafd92019-09-09 17:52:16 +0200571 }
572 commitMessage = genCommitMessage(repo, comment, changeId, change_id_seed)
Mykyta Karpin28695382022-04-22 16:16:44 +0300573 commitGitChanges(repo, commitMessage, changeAuthorEmail, changeAuthorName, false, amend)
Alexandr Lovtsov4fdafd92019-09-09 17:52:16 +0200574 dir(repo){
575 commit = getGitCommit()
576 }
Sergey Kolekonov02d70022023-03-28 14:22:36 +0600577 pushForReview(repo, creds, commit, branch, topic, remote)
Alexandr Lovtsov4fdafd92019-09-09 17:52:16 +0200578}
Mykyta Karpin28695382022-04-22 16:16:44 +0300579
580/**
581 * Create new working branch for repo from patch if it exists
582 *
583 * @param params Map of parameters to customize commit
584 * - gerritAuth A map containing information about Gerrit. Should include HOST, PORT and USER
585 * - credentialsId Jenkins credentials id for gerrit
586 * - repo Local directory with repository
587 * - branch Name of the branch for uploading
588 * - topic Topic of the change
589 * - project Gerrit project to search in for gerrit change request
590 * - status Change request's status to search for
591 */
592def createGitBranchFromRef(Map params) {
593 def gerrit = new com.mirantis.mk.Gerrit()
594 def common = new com.mirantis.mk.Common()
595
596 def auth = params['gerritAuth']
597 def creds = params['credentialsId']
598 def repo = params['repo']
599 def branch = params['branch']
600 def topic = params['topic']
601 def project = params['project']
602 def status = params.get('status', 'open')
603 def localBranch = "branch_${topic}"
604 def jsonChange = [:]
605
606 def changeParams = ['owner': auth['USER'], 'status': status, 'project': project, 'branch': branch, 'topic': topic]
607 def gerritChange = gerrit.findGerritChange(creds, auth, changeParams, '--current-patch-set')
608 if (gerritChange) {
609 jsonChange = readJSON text: gerritChange
610 def ref = jsonChange['currentPatchSet']['ref']
611 changeId = jsonChange['id']
612 dir(repo){
613 sshagent (credentials: [creds]){
614 common.shCmdStatus("git fetch origin ${ref} && git checkout -b ${localBranch} FETCH_HEAD")
615 }
616 }
617 }
618 else {
619 createGitBranch(repo, localBranch)
620 }
621 return jsonChange
622}
Oleksandr Kononenkof133a0e2024-02-21 17:04:41 +0200623
624/**
Vasyl Saienkof3df2d32024-07-01 17:34:11 +0300625 * Return array with branches that contains specific commit ref
Oleksandr Kononenkof133a0e2024-02-21 17:04:41 +0200626 *
627 * @param path Path to the git repository
Vasyl Saienkof3df2d32024-07-01 17:34:11 +0300628 * @param ref search ref
Oleksandr Kononenkof133a0e2024-02-21 17:04:41 +0200629 */
Vasyl Saienkof3df2d32024-07-01 17:34:11 +0300630def getBranchesContainsRef(String path, String ref) {
Oleksandr Kononenkof133a0e2024-02-21 17:04:41 +0200631 List result = []
632 dir(path) {
633 def gitResult = sh (
Vasyl Saienkof3df2d32024-07-01 17:34:11 +0300634 script: "git branch --all --contains ${ref}",
Oleksandr Kononenkof133a0e2024-02-21 17:04:41 +0200635 returnStdout: true
636 )
637 for (line in gitResult.split('\n')) {
638 if (! line.contains("HEAD detached")) {
639 result.add(line.trim().replaceAll('remotes/origin/', ''))
640 }
641 }
642 }
643 return result
644}
Vasyl Saienkof3df2d32024-07-01 17:34:11 +0300645
646/**
647 * Return array with branches that contains specific tag
648 *
649 * @param path Path to the git repository
650 * @param tag search tag
651 */
652def getBranchesContainsTag(String path, String tag) {
653 return getBranchesContainsRef(path, "tags/${tag}")
654}