Skip to content

Commit

Permalink
Merge branch 'master' into shallow-clone-ui
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkEWaite committed Apr 11, 2020
2 parents 5315553 + 106376e commit dfdfc7e
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 23 deletions.
8 changes: 8 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,14 @@ Excluded Messages::
Removes remote tracking branches from the local workspace if they no longer exist on the remote.
See `link:https://git-scm.com/docs/git-remote#Documentation/git-remote.txt-empruneem[git remote prune]` and `link:https://git-scm.com/docs/git-fetch#_pruning[git fetch --prune]` for more details.

[#prune-stale-tags]
==== Prune stale tags

Removes tags from the local workspace before fetch if they no longer exist on the remote.
If stale tags are not pruned, deletion of a remote tag will not remove the local tag in the workspace.
If the local tag already exists in the workspace, git correctly refuses to create the tag again.
Pruning stale tags allows the local workspace to create a tag with the same name as a tag which was removed from the remote.

[#sparse-checkout-paths]
==== Sparse Checkout paths

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,22 @@
import hudson.plugins.git.GitSCM;
import hudson.plugins.git.extensions.GitSCMExtension;
import hudson.plugins.git.extensions.GitSCMExtensionDescriptor;
import net.sf.json.JSONObject;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Map.Entry;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.URIish;
import org.jenkinsci.Symbol;
import org.jenkinsci.plugins.gitclient.FetchCommand;
import org.jenkinsci.plugins.gitclient.GitClient;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;

/**
* Prune stale local tags that do not exist on any remote.
Expand All @@ -68,6 +73,15 @@ public PruneStaleTag(boolean pruneTags) {
this.pruneTags = pruneTags;
}

/**
* Needed for pipeline syntax generator.
*
* @return {@code true} if this extension is enable, {@code false} otherwise.
*/
public boolean getPruneTags() {
return pruneTags;
}

/**
* {@inheritDoc}
*/
Expand All @@ -94,7 +108,7 @@ public void decorateFetchCommand(GitSCM scm,

List<RemoteConfig> remoteRepos = run == null ? scm.getRepositories() : scm.getParamExpandedRepos(run, listener);
for (RemoteConfig remote : remoteRepos) {
for(URIish url : remote.getURIs()) {
for (URIish url : remote.getURIs()) {
Map<String, ObjectId> refs = git.getRemoteReferences(url.toASCIIString(), null, false, true);
for (Entry<String, ObjectId> ref : refs.entrySet()) {
String remoteTagName = ref.getKey();
Expand Down Expand Up @@ -125,15 +139,18 @@ public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}
return o instanceof PruneStaleTag;

PruneStaleTag that = (PruneStaleTag) o;

return pruneTags == that.pruneTags;
}

/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return PruneStaleTag.class.hashCode();
return Objects.hashCode(pruneTags);
}

/**
Expand All @@ -144,8 +161,15 @@ public String toString() {
return "PruneStaleTag {}";
}

@Symbol("pruneTags")
@Extension
public static class DescriptorImpl extends GitSCMExtensionDescriptor {

@Override
public GitSCMExtension newInstance(StaplerRequest req, JSONObject formData) throws FormException {
return new PruneStaleTag(true);
}

/**
* {@inheritDoc}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public void checkoutWithValidCredentials() throws Exception {
+ " )"
+ "}", true));
WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
r.assertLogContains("using credential github", b);
r.waitForMessage("using credential github", b);
}

@Issue("JENKINS-30515")
Expand All @@ -78,7 +78,7 @@ public void checkoutWithDifferentCredentials() throws Exception {
+ " )"
+ "}", true));
WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
r.assertLogContains("Warning: CredentialId \"github\" could not be found", b);
r.waitForMessage("Warning: CredentialId \"github\" could not be found", b);
}

@Issue("JENKINS-30515")
Expand All @@ -97,7 +97,7 @@ public void checkoutWithInvalidCredentials() throws Exception {
+ " )"
+ "}", true));
WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
r.assertLogContains("Warning: CredentialId \"github\" could not be found", b);
r.waitForMessage("Warning: CredentialId \"github\" could not be found", b);
}

@Issue("JENKINS-30515")
Expand All @@ -114,7 +114,7 @@ public void checkoutWithNoCredentialsStoredButUsed() throws Exception {
+ " )"
+ "}", true));
WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
r.assertLogContains("Warning: CredentialId \"github\" could not be found", b);
r.waitForMessage("Warning: CredentialId \"github\" could not be found", b);
}

@Issue("JENKINS-30515")
Expand All @@ -131,7 +131,7 @@ public void checkoutWithNoCredentialsSpecified() throws Exception {
+ " )"
+ "}", true));
WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
r.assertLogContains("No credentials specified", b);
r.waitForMessage("No credentials specified", b);
}


Expand Down
10 changes: 5 additions & 5 deletions src/test/java/hudson/plugins/git/GitSCMTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1119,19 +1119,19 @@ public void testEnvVarsAvailable() throws Exception {
FreeStyleBuild build1 = build(project, Result.SUCCESS, commitFile1);

assertEquals("origin/master", getEnvVars(project).get(GitSCM.GIT_BRANCH));
rule.assertLogContains(getEnvVars(project).get(GitSCM.GIT_BRANCH), build1);
rule.waitForMessage(getEnvVars(project).get(GitSCM.GIT_BRANCH), build1);

rule.assertLogContains(checkoutString(project, GitSCM.GIT_COMMIT), build1);
rule.waitForMessage(checkoutString(project, GitSCM.GIT_COMMIT), build1);

final String commitFile2 = "commitFile2";
commit(commitFile2, johnDoe, "Commit number 2");
FreeStyleBuild build2 = build(project, Result.SUCCESS, commitFile2);

rule.assertLogNotContains(checkoutString(project, GitSCM.GIT_PREVIOUS_COMMIT), build2);
rule.assertLogContains(checkoutString(project, GitSCM.GIT_PREVIOUS_COMMIT), build1);
rule.waitForMessage(checkoutString(project, GitSCM.GIT_PREVIOUS_COMMIT), build1);

rule.assertLogNotContains(checkoutString(project, GitSCM.GIT_PREVIOUS_SUCCESSFUL_COMMIT), build2);
rule.assertLogContains(checkoutString(project, GitSCM.GIT_PREVIOUS_SUCCESSFUL_COMMIT), build1);
rule.waitForMessage(checkoutString(project, GitSCM.GIT_PREVIOUS_SUCCESSFUL_COMMIT), build1);
}

@Issue("HUDSON-7411")
Expand Down Expand Up @@ -2140,7 +2140,7 @@ public void testCheckoutFailureIsRetryable() throws Exception {
try {
FileUtils.touch(lock);
final FreeStyleBuild build2 = build(project, Result.FAILURE);
rule.assertLogContains("java.io.IOException: Could not checkout", build2);
rule.waitForMessage("java.io.IOException: Could not checkout", build2);
} finally {
lock.delete();
}
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/hudson/plugins/git/GitStatusTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,7 @@ private void doNotifyCommitWithDefaultParameter(final boolean allowed, String sa

FreeStyleBuild build = project.scheduleBuild2(0, new Cause.UserCause()).get();

jenkins.assertLogContains("aaa aaaccc ccc", build);
jenkins.waitForMessage("aaa aaaccc ccc", build);

String extraValue = "An-extra-value";
when(requestWithParameter.getParameterMap()).thenReturn(setupParameterMap(extraValue));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.junit.Assert;
import org.junit.Test;

import java.lang.Thread;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
Expand Down Expand Up @@ -80,7 +81,7 @@ public void testSingleRevision() throws Exception {
rule.assertBuildStatusSuccess(build);
boolean result = build.getLog(100).contains(
String.format("Scheduling another build to catch up with %s", project.getName()));
Assert.assertFalse(result);
Assert.assertFalse("Single revision scheduling did not prevent a build of a different revision", result);
}

@Test
Expand Down Expand Up @@ -110,6 +111,14 @@ public void testMultiRevision() throws Exception {

rule.assertBuildStatusSuccess(build);
rule.waitForMessage(String.format("Scheduling another build to catch up with %s", project.getName()), build);

// Wait briefly for newly scheduled job to start.
// Once job has started, waitForAllJobsToComplete will hold the test until job completes.
// Windows can remove log files once job completes.
// Wait on non-Windows reduces log file InterruptedException from rule teardown before job completion.
// Wait on non-Windows not strictly required but gives one less exception in the test log.
java.util.Random random = new java.util.Random();
Thread.sleep(500L + random.nextInt(300));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ public void checkoutTimeout() throws Exception {
+ " )"
+ "}", true));
WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
r.assertLogContains("# timeout=1234", b);
r.waitForMessage("# timeout=1234", b);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* The MIT License
*
* Copyright (c) 2020 Nikolas Falco
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package hudson.plugins.git.extensions.impl;

import java.io.File;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.io.FileUtils;
import org.jenkinsci.plugins.gitclient.GitClient;
import org.jenkinsci.plugins.gitclient.TestCliGitAPIImpl;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;

import hudson.EnvVars;
import hudson.FilePath;
import hudson.Functions;
import hudson.model.Result;
import hudson.model.TaskListener;
import hudson.util.LogTaskListener;

public class PruneStaleTagPipelineTest {

@Rule
public TemporaryFolder fileRule = new TemporaryFolder();
@Rule
public JenkinsRule j = new JenkinsRule();

private TaskListener listener;

@Before
public void setup() throws Exception {
listener = new LogTaskListener(Logger.getLogger("prune tags"), Level.FINEST);
}

@Issue("JENKINS-61869")
@Test
public void verify_that_local_tag_is_pruned_when_not_exist_on_remote_using_pipeline() throws Exception {
File remoteRepo = fileRule.newFolder("remote");

// create a remote repository without one tag
GitClient remoteClient = initRepository(remoteRepo);
String tagName = "tag";
String tagComment = "tag comment";
remoteClient.tag(tagName, tagComment);

WorkflowJob job = j.jenkins.createProject(WorkflowJob.class, "pruneTags");

FilePath workspace = j.jenkins.getWorkspaceFor(job);
String remoteURL = "file://" + remoteRepo.toURI().getPath();

job.setDefinition(new CpsFlowDefinition(""
+ " node {\n"
+ " checkout([$class: 'GitSCM',"
+ " branches: [[name: '*/master']],"
+ " doGenerateSubmoduleConfigurations: false,"
+ " extensions: [pruneTags(true)],"
+ " submoduleCfg: [],"
+ " userRemoteConfigs: [[url: '" + remoteURL + "']]"
+ " ])"
+ " }\n", true));

// first run clone the repository
WorkflowRun r = job.scheduleBuild2(0).waitForStart();
j.assertBuildStatus(Result.SUCCESS, j.waitForCompletion(r));

// remove tag on remote, tag remains on local cloned repository
remoteClient.deleteTag(tagName);

// second run it should remove stale tags
r = job.scheduleBuild2(0).waitForStart();
j.assertBuildStatus(Result.SUCCESS, j.waitForCompletion(r));

GitClient localClient = newGitClient(new File(workspace.getRemote()));
Assert.assertFalse("local tag has not been pruned", localClient.tagExists(tagName));
}

private GitClient newGitClient(File localRepo) {
String gitExe = Functions.isWindows() ? "git.exe" : "git";
GitClient localClient = new TestCliGitAPIImpl(gitExe, localRepo, listener, new EnvVars());
return localClient;
}

private GitClient initRepository(File workspace) throws Exception {
GitClient remoteClient = newGitClient(workspace);
remoteClient.init();

FileUtils.touch(new File(workspace, "test"));
remoteClient.add("test");

remoteClient.commit("initial commit");
return remoteClient;
}

}
10 changes: 5 additions & 5 deletions src/test/java/jenkins/plugins/git/GitStepTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,13 @@ public void basicCloneAndUpdate() throws Exception {
" }\n" +
"}", true));
WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
r.assertLogContains("Cloning the remote Git repository", b); // GitSCM.retrieveChanges
r.waitForMessage("Cloning the remote Git repository", b); // GitSCM.retrieveChanges
assertTrue(b.getArtifactManager().root().child("file").isFile());
sampleRepo.write("nextfile", "");
sampleRepo.git("add", "nextfile");
sampleRepo.git("commit", "--message=next");
b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
r.assertLogContains("Fetching changes from the remote Git repository", b); // GitSCM.retrieveChanges
r.waitForMessage("Fetching changes from the remote Git repository", b); // GitSCM.retrieveChanges
assertTrue(b.getArtifactManager().root().child("nextfile").isFile());
}

Expand All @@ -123,14 +123,14 @@ public void changelogAndPolling() throws Exception {
" }\n" +
"}", true));
WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
r.assertLogContains("Cloning the remote Git repository", b);
r.waitForMessage("Cloning the remote Git repository", b);
sampleRepo.write("nextfile", "");
sampleRepo.git("add", "nextfile");
sampleRepo.git("commit", "--message=next");
sampleRepo.notifyCommit(r);
b = p.getLastBuild();
assertEquals(2, b.number);
r.assertLogContains("Fetching changes from the remote Git repository", b);
r.waitForMessage("Fetching changes from the remote Git repository", b);
List<ChangeLogSet<? extends ChangeLogSet.Entry>> changeSets = b.getChangeSets();
assertEquals(1, changeSets.size());
ChangeLogSet<? extends ChangeLogSet.Entry> changeSet = changeSets.get(0);
Expand Down Expand Up @@ -254,7 +254,7 @@ public void commitToWorkspace() throws Exception {
" rungit 'show master'\n" +
"}", true));
WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
r.assertLogContains("+edited by build", b);
r.waitForMessage("+edited by build", b);
}

}
Loading

0 comments on commit dfdfc7e

Please sign in to comment.