/*************************** GO-LICENSE-START*********************************
* Copyright 2016 ThoughtWorks, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ************************GO-LICENSE-END***********************************/
package com.thoughtworks.go.domain.materials.git;
import com.thoughtworks.go.config.materials.git.GitMaterial;
import com.thoughtworks.go.domain.BuildCommand;
import com.thoughtworks.go.domain.materials.Revision;
import com.thoughtworks.go.domain.materials.RevisionContext;
import com.thoughtworks.go.util.command.UrlArgument;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import static com.thoughtworks.go.domain.BuildCommand.*;
import static java.lang.String.format;
public class GitMaterialUpdater {
private GitMaterial material;
public GitMaterialUpdater(GitMaterial material) {
this.material = material;
}
public BuildCommand updateTo(String baseDir, RevisionContext revisionContext) {
Revision revision = revisionContext.getLatestRevision();
String workingDir = material.workingdir(new File(baseDir)).getPath();
UrlArgument url = material.getUrlArgument();
return compose(
echoWithPrefix("Start updating %s at revision %s from %s", material.updatingTarget(), revision.getRevision(), url.forDisplay()),
secret(url.forCommandline(), url.forDisplay()),
cloneIfNeeded(workingDir, revisionContext.numberOfModifications() + 1),
fetchRemote(workingDir),
unshallowIfNeeded(workingDir, revision, new Integer[]{GitMaterial.UNSHALLOW_TRYOUT_STEP, Integer.MAX_VALUE}),
resetWorkingCopy(workingDir, revision),
echoWithPrefix("Done.\n"));
}
private BuildCommand fetchRemote(String workingDir) {
return compose(
echo("[GIT] Fetching changes"),
exec("git", "fetch", "origin", "--prune").setWorkingDirectory(workingDir),
exec("git", "gc", "--auto").setWorkingDirectory(workingDir));
}
private BuildCommand resetWorkingCopy(String workingDir, Revision revision) {
return compose(
echo("[GIT] Reset working directory %s", workingDir),
echo("[GIT] Updating working copy to revision %s", revision.getRevision()),
cleanupUnversionedFiles(workingDir),
resetHard(workingDir, revision),
updateSubmodules(workingDir),
cleanupUnversionedFiles(workingDir));
}
private BuildCommand updateSubmodules(String workingDir) {
return compose(
echo("[GIT] Removing modified files in submodules"),
exec("git", "submodule", "foreach", "--recursive", "git", "checkout", "."),
echo("[GIT] Updating git sub-modules"),
exec("git", "submodule", "init"),
exec("git", "submodule", "sync"),
exec("git", "submodule", "foreach", "--recursive", "git", "submodule", "sync"),
exec("git", "submodule", "update"),
echo("[GIT] Git sub-module status"),
exec("git", "submodule", "status"))
.setTest(hasSubmodules(workingDir))
.setWorkingDirectoryRecursively(workingDir);
}
private BuildCommand resetHard(String workingDir, Revision revision) {
return exec("git", "reset", "--hard", revision.getRevision())
.setWorkingDirectory(workingDir);
}
private BuildCommand cleanupUnversionedFiles(String workingDir) {
return compose(
echo("[GIT] Cleaning all unversioned files in working copy"),
exec("git", "submodule", "foreach", "--recursive", "git", "clean", "-fdd")
.setTest(hasSubmodules(workingDir)),
exec("git", "clean", "-dff"))
.setWorkingDirectoryRecursively(workingDir);
}
private BuildCommand hasSubmodules(String workingDir) {
return test("-f", new File(workingDir, ".gitmodules").getPath());
}
private BuildCommand unshallowIfNeeded(String workingDir, Revision revision, Integer[] steps) {
if (steps.length == 0) {
return noop();
}
int depth = steps[0];
return compose(
compose(
echo("[GIT] Unshallowing repository with depth %d", depth),
exec("git", "fetch", "origin", format("--depth=%d", depth)).setWorkingDirectory(workingDir),
unshallowIfNeeded(workingDir, revision, Arrays.copyOfRange(steps, 1, steps.length))
).setTest(revisionNotExists(workingDir, revision))
).setTest(isShallow(workingDir));
}
private BuildCommand revisionNotExists(String workingDir, Revision revision) {
return test("-neq", "commit",
exec("git", "cat-file", "-t", revision.getRevision()).setWorkingDirectory(workingDir));
}
private BuildCommand isShallow(String workingDir) {
return test("-f", new File(workingDir, ".git/shallow").getPath());
}
private BuildCommand cloneIfNeeded(String workDir, int cloneDepth) {
return compose(
mkdirs(workDir).setTest(test("-nd", workDir)),
cleanWorkingDir(workDir),
cmdClone(workDir, cloneDepth));
}
private BuildCommand cleanWorkingDir(String workDir) {
return compose(
cleandir(workDir).setTest(isNotRepository(workDir)),
cleandir(workDir).setTest(isRepoUrlChanged(workDir)),
cleandir(workDir).setTest(isBranchChanged(workDir)),
material.isShallowClone() ? noop() : cleandir(workDir).setTest(isShallow(workDir))
).setTest(test("-d", workDir));
}
private BuildCommand isRepoUrlChanged(String workDir) {
return test("-neq",
material.getUrlArgument().forCommandline(),
exec("git", "config", "remote.origin.url").setWorkingDirectory(workDir));
}
private BuildCommand isBranchChanged(String workDir) {
return test("-neq",
material.branchWithDefault(),
exec("git", "rev-parse", "--abbrev-ref", "HEAD").setWorkingDirectory(workDir));
}
private BuildCommand cmdClone(String workDir, int cloneDepth) {
ArrayList<String> cloneArgs = new ArrayList<>();
cloneArgs.add("clone");
cloneArgs.add("--no-checkout");
cloneArgs.add(format("--branch=%s", material.branchWithDefault()));
if (material.isShallowClone()) {
cloneArgs.add(format("--depth=%s", String.valueOf(cloneDepth)));
}
cloneArgs.add(material.getUrlArgument().forCommandline());
cloneArgs.add(workDir);
return exec("git", cloneArgs.toArray(new String[cloneArgs.size()]))
.setTest(isNotRepository(workDir));
}
private BuildCommand isNotRepository(String workDir) {
return test("-nd", new File(workDir, ".git").getPath());
}
}