Skip to content

Commit

Permalink
Merge pull request #43 from cloudogu/feature/push_and_pull_on_failure
Browse files Browse the repository at this point in the history
Feature/push and pull on failure
  • Loading branch information
schnatterer authored May 29, 2020
2 parents 4882fa1 + f5f05db commit 89f0532
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 13 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,8 @@ gitWithCreds 'https://your.repo' // Implicitly passed credentials
### Changes to remote repository

* `git.push('master')` - pushes origin
* `git.pushAndPullOnFailure('refspec')` - pushes and pulls if push failed e.g. because local and remote have diverged,
then tries pushing again
* `pushGitHubPagesBranch('folderToPush', 'commit Message')` - Commits and pushes a folder to the `gh-pages` branch of
the current repo. Can be used to conveniently deliver websites. See https://pages.github.com. Note:
* Uses the name and email of the last committer as author and committer.
Expand Down
57 changes: 46 additions & 11 deletions src/com/cloudogu/ces/cesbuildlib/Git.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -291,21 +291,54 @@ class Git implements Serializable {
*
* @param refSpec branch or tag name
*/
void push(String refSpec) {
executeGitWithCredentials "push origin ${refSpec}"
void push(String refSpec = '') {
refSpec = addOriginWhenMissing(refSpec)
executeGitWithCredentials "push ${refSpec}"
}

/**
* Pulls to local from remote repo.
*
* @param refSpec branch or tag name
* @param authorName
* @param authorEmail
*/
void pull(String refSpec = '', String authorName = commitAuthorName, String authorEmail = commitAuthorEmail) {
refSpec = addOriginWhenMissing(refSpec)
withAuthorAndEmail(authorName, authorEmail) {
executeGitWithCredentials "pull ${refSpec}"
}
}

/**
* Pushes local to remote repo. Additionally pulls, if push has failed.
*
* @param refSpec branch or tag name
* @param authorName
* @param authorEmail
*/
void pushAndPullOnFailure(String refSpec = '', String authorName = commitAuthorName, String authorEmail = commitAuthorEmail) {
refSpec = addOriginWhenMissing(refSpec)
executeGitWithCredentials("push ${refSpec}") {
script.echo "Got error, trying to pull first"
pull(refSpec, authorName, authorEmail)
}
}

/**
* Method exists purely because of downward compatibility. Adding remote to pushes and pulls is preferred,
* but historically git push always added `origin` implicitly.
*
*/
private static String addOriginWhenMissing(String refSpec) {
// if refspec contains more than 1 argument e.g. `upstream master`
if(!refSpec || refSpec.trim().split(' ').length > 1 || refSpec.trim() == 'origin') {
return refSpec
}

return 'origin ' + refSpec
}

/**
* Commits and pushes a folder to the <code>gh-pages</code> branch of the current repo.
* Can be used to conveniently deliver websites. See https://pages.github.com/
Expand Down Expand Up @@ -338,28 +371,30 @@ class Git implements Serializable {
* This method executes the git command with a bash function as credential helper,
* which return username and password from jenkins credentials.
*
* If the script failes with exit code 128, this will retry the call up to the
* If the script failes with exit code > 0, this will retry the call up to the
* configured max retries before failing.
*
* @param args git arguments
* @param closure closure to execute after first retry
*/
protected void executeGitWithCredentials(String args) {
protected void executeGitWithCredentials(String args, Closure executeBeforeRetry = {}) {
if (credentials) {
script.withCredentials([script.usernamePassword(credentialsId: credentials,
passwordVariable: 'GIT_AUTH_PSW', usernameVariable: 'GIT_AUTH_USR')]) {
def pushResultCode = 128
def gitResultCode = 1
def retryCount = 0
while (pushResultCode == 128 && retryCount < maxRetries) {
while (gitResultCode > 0 && retryCount < maxRetries) {
if (retryCount > 0) {
script.echo "Got error code ${pushResultCode} - retrying in ${retryTimeout} ms ..."
script.echo "Got error code ${gitResultCode} - retrying in ${retryTimeout} ms ..."
sleep(retryTimeout)
executeBeforeRetry.call()
}
++retryCount
pushResultCode = script.sh returnStatus: true, script: "git -c credential.helper=\"!f() { echo username='\$GIT_AUTH_USR'; echo password='\$GIT_AUTH_PSW'; }; f\" ${args}"
pushResultCode = pushResultCode as int
gitResultCode = script.sh returnStatus: true, script: "git -c credential.helper=\"!f() { echo username='\$GIT_AUTH_USR'; echo password='\$GIT_AUTH_PSW'; }; f\" ${args}"
gitResultCode = gitResultCode as int
}
if (pushResultCode != 0) {
script.error "Unable to execute git call. Retried ${retryCount} times. Last error code: ${pushResultCode}"
if (gitResultCode != 0) {
script.error "Unable to execute git call. Retried ${retryCount} times. Last error code: ${gitResultCode}"
}
}
} else {
Expand Down
185 changes: 183 additions & 2 deletions test/com/cloudogu/ces/cesbuildlib/GitTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ class GitTest {
}

@Test
void pull() {
void "pull with empty refspec"() {
def expectedGitCommandWithCredentials = 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull'
scriptMock.expectedShRetValueForScript.put(expectedGitCommandWithCredentials, 0)
scriptMock.expectedShRetValueForScript.put('git --no-pager show -s --format=\'%an <%ae>\' HEAD', 'User Name <[email protected]>')
Expand All @@ -240,7 +240,45 @@ class GitTest {
}

@Test
void 'pull with refspec'() {
void "pull origin"() {
def expectedGitCommandWithCredentials = 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull origin'
scriptMock.expectedShRetValueForScript.put(expectedGitCommandWithCredentials, 0)
scriptMock.expectedShRetValueForScript.put('git --no-pager show -s --format=\'%an <%ae>\' HEAD', 'User Name <[email protected]>')
git = new Git(scriptMock, 'creds')

git.pull('origin')

def actualWithEnv = scriptMock.actualWithEnvAsMap()
assert actualWithEnv['GIT_AUTHOR_NAME'] == 'User Name'
assert actualWithEnv['GIT_COMMITTER_NAME'] == 'User Name'
assert actualWithEnv['GIT_AUTHOR_EMAIL'] == '[email protected]'
assert actualWithEnv['GIT_COMMITTER_EMAIL'] == '[email protected]'

assert scriptMock.actualShMapArgs.size() == 3
assert scriptMock.actualShMapArgs.get(2).trim() == expectedGitCommandWithCredentials
}

@Test
void 'pull master'() {
def expectedGitCommandWithCredentials = 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull origin master'
scriptMock.expectedShRetValueForScript.put(expectedGitCommandWithCredentials, 0)
scriptMock.expectedShRetValueForScript.put('git --no-pager show -s --format=\'%an <%ae>\' HEAD', 'User Name <[email protected]>')
git = new Git(scriptMock, 'creds')

git.pull 'master'

def actualWithEnv = scriptMock.actualWithEnvAsMap()
assert actualWithEnv['GIT_AUTHOR_NAME'] == 'User Name'
assert actualWithEnv['GIT_COMMITTER_NAME'] == 'User Name'
assert actualWithEnv['GIT_AUTHOR_EMAIL'] == '[email protected]'
assert actualWithEnv['GIT_COMMITTER_EMAIL'] == '[email protected]'

assert scriptMock.actualShMapArgs.size() == 3
assert scriptMock.actualShMapArgs.get(2).trim() == expectedGitCommandWithCredentials
}

@Test
void 'pull origin master'() {
def expectedGitCommandWithCredentials = 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull origin master'
scriptMock.expectedShRetValueForScript.put(expectedGitCommandWithCredentials, 0)
scriptMock.expectedShRetValueForScript.put('git --no-pager show -s --format=\'%an <%ae>\' HEAD', 'User Name <[email protected]>')
Expand All @@ -258,6 +296,25 @@ class GitTest {
assert scriptMock.actualShMapArgs.get(2).trim() == expectedGitCommandWithCredentials
}

@Test
void 'pull upstream master'() {
def expectedGitCommandWithCredentials = 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull upstream master'
scriptMock.expectedShRetValueForScript.put(expectedGitCommandWithCredentials, 0)
scriptMock.expectedShRetValueForScript.put('git --no-pager show -s --format=\'%an <%ae>\' HEAD', 'User Name <[email protected]>')
git = new Git(scriptMock, 'creds')

git.pull 'upstream master'

def actualWithEnv = scriptMock.actualWithEnvAsMap()
assert actualWithEnv['GIT_AUTHOR_NAME'] == 'User Name'
assert actualWithEnv['GIT_COMMITTER_NAME'] == 'User Name'
assert actualWithEnv['GIT_AUTHOR_EMAIL'] == '[email protected]'
assert actualWithEnv['GIT_COMMITTER_EMAIL'] == '[email protected]'

assert scriptMock.actualShMapArgs.size() == 3
assert scriptMock.actualShMapArgs.get(2).trim() == expectedGitCommandWithCredentials
}

@Test
void checkout() {
git.checkout("master")
Expand Down Expand Up @@ -310,6 +367,38 @@ class GitTest {
git.push('master')
}

@Test
void "push with empty refspec"() {
git.push()

assert scriptMock.actualShStringArgs.size() == 1
assert scriptMock.actualShStringArgs.get(0).trim() == 'git push'
}

@Test
void "push origin"() {
git.push('origin')

assert scriptMock.actualShStringArgs.size() == 1
assert scriptMock.actualShStringArgs.get(0) == 'git push origin'
}

@Test
void "push origin master"() {
git.push('origin master')

assert scriptMock.actualShStringArgs.size() == 1
assert scriptMock.actualShStringArgs.get(0) == 'git push origin master'
}

@Test
void "push upstream master"() {
git.push('upstream master')

assert scriptMock.actualShStringArgs.size() == 1
assert scriptMock.actualShStringArgs.get(0) == 'git push upstream master'
}

@Test
void pushNonHttps() {
scriptMock.expectedShRetValueForScript.put('git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push origin master', 0)
Expand All @@ -333,6 +422,98 @@ class GitTest {
assert scriptMock.actualShMapArgs.get(2) == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push origin master'
}

@Test
void "pushAndPullOnFailure with empty refspec"() {
def expectedGitCommandWithCredentials = 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push'
scriptMock.expectedShRetValueForScript.put(expectedGitCommandWithCredentials, [1, 0])
scriptMock.expectedShRetValueForScript.put('git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull', 0)
scriptMock.expectedShRetValueForScript.put('git --no-pager show -s --format=\'%an <%ae>\' HEAD', 'User Name <[email protected]>')
git = new Git(scriptMock, 'creds')

git.retryTimeout = 1
git.pushAndPullOnFailure()

def actualWithEnv = scriptMock.actualWithEnvAsMap()
assert actualWithEnv['GIT_AUTHOR_NAME'] == 'User Name'
assert actualWithEnv['GIT_COMMITTER_NAME'] == 'User Name'
assert actualWithEnv['GIT_AUTHOR_EMAIL'] == '[email protected]'
assert actualWithEnv['GIT_COMMITTER_EMAIL'] == '[email protected]'

assert scriptMock.actualShMapArgs.size() == 5
assert scriptMock.actualShMapArgs.get(2).trim() == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push'
assert scriptMock.actualShMapArgs.get(3).trim() == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull'
assert scriptMock.actualShMapArgs.get(4).trim() == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push'
}

@Test
void "pushAndPullOnFailure master"() {
def expectedGitCommandWithCredentials = 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push origin master'
scriptMock.expectedShRetValueForScript.put(expectedGitCommandWithCredentials, [1, 0])
scriptMock.expectedShRetValueForScript.put('git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull origin master', 0)
scriptMock.expectedShRetValueForScript.put('git --no-pager show -s --format=\'%an <%ae>\' HEAD', 'User Name <[email protected]>')
git = new Git(scriptMock, 'creds')

git.retryTimeout = 1
git.pushAndPullOnFailure('master')

def actualWithEnv = scriptMock.actualWithEnvAsMap()
assert actualWithEnv['GIT_AUTHOR_NAME'] == 'User Name'
assert actualWithEnv['GIT_COMMITTER_NAME'] == 'User Name'
assert actualWithEnv['GIT_AUTHOR_EMAIL'] == '[email protected]'
assert actualWithEnv['GIT_COMMITTER_EMAIL'] == '[email protected]'

assert scriptMock.actualShMapArgs.size() == 5
assert scriptMock.actualShMapArgs.get(2) == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push origin master'
assert scriptMock.actualShMapArgs.get(3) == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull origin master'
assert scriptMock.actualShMapArgs.get(4) == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push origin master'
}

@Test
void "pushAndPullOnFailure origin master"() {
def expectedGitCommandWithCredentials = 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push origin master'
scriptMock.expectedShRetValueForScript.put(expectedGitCommandWithCredentials, [1, 0])
scriptMock.expectedShRetValueForScript.put('git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull origin master', 0)
scriptMock.expectedShRetValueForScript.put('git --no-pager show -s --format=\'%an <%ae>\' HEAD', 'User Name <[email protected]>')
git = new Git(scriptMock, 'creds')

git.retryTimeout = 1
git.pushAndPullOnFailure('origin master')

def actualWithEnv = scriptMock.actualWithEnvAsMap()
assert actualWithEnv['GIT_AUTHOR_NAME'] == 'User Name'
assert actualWithEnv['GIT_COMMITTER_NAME'] == 'User Name'
assert actualWithEnv['GIT_AUTHOR_EMAIL'] == '[email protected]'
assert actualWithEnv['GIT_COMMITTER_EMAIL'] == '[email protected]'

assert scriptMock.actualShMapArgs.size() == 5
assert scriptMock.actualShMapArgs.get(2) == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push origin master'
assert scriptMock.actualShMapArgs.get(3) == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull origin master'
assert scriptMock.actualShMapArgs.get(4) == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push origin master'
}

@Test
void "pushAndPullOnFailure upstream master"() {
def expectedGitCommandWithCredentials = 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push upstream master'
scriptMock.expectedShRetValueForScript.put(expectedGitCommandWithCredentials, [1, 0])
scriptMock.expectedShRetValueForScript.put('git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull upstream master', 0)
scriptMock.expectedShRetValueForScript.put('git --no-pager show -s --format=\'%an <%ae>\' HEAD', 'User Name <[email protected]>')
git = new Git(scriptMock, 'creds')

git.retryTimeout = 1
git.pushAndPullOnFailure('upstream master')

def actualWithEnv = scriptMock.actualWithEnvAsMap()
assert actualWithEnv['GIT_AUTHOR_NAME'] == 'User Name'
assert actualWithEnv['GIT_COMMITTER_NAME'] == 'User Name'
assert actualWithEnv['GIT_AUTHOR_EMAIL'] == '[email protected]'
assert actualWithEnv['GIT_COMMITTER_EMAIL'] == '[email protected]'

assert scriptMock.actualShMapArgs.size() == 5
assert scriptMock.actualShMapArgs.get(2) == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push upstream master'
assert scriptMock.actualShMapArgs.get(3) == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull upstream master'
assert scriptMock.actualShMapArgs.get(4) == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push upstream master'
}

@Test
void pushNoCredentials() {
git.push('master')
Expand Down

0 comments on commit 89f0532

Please sign in to comment.