| // Copyright (C) 2013 The Android Open Source Project | |
| // | |
| // 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. | |
| package com.google.gerrit.server.restapi.project; | |
| import static com.google.common.base.Preconditions.checkState; | |
| import static java.nio.charset.StandardCharsets.UTF_8; | |
| import com.google.gerrit.common.data.GarbageCollectionResult; | |
| import com.google.gerrit.common.data.GarbageCollectionResult.GcError; | |
| import com.google.gerrit.common.data.GlobalCapability; | |
| import com.google.gerrit.entities.Project; | |
| import com.google.gerrit.extensions.annotations.RequiresCapability; | |
| import com.google.gerrit.extensions.registration.DynamicItem; | |
| import com.google.gerrit.extensions.restapi.BinaryResult; | |
| import com.google.gerrit.extensions.restapi.Response; | |
| import com.google.gerrit.extensions.restapi.RestModifyView; | |
| import com.google.gerrit.extensions.webui.UiAction; | |
| import com.google.gerrit.server.config.UrlFormatter; | |
| import com.google.gerrit.server.git.GarbageCollection; | |
| import com.google.gerrit.server.git.GitRepositoryManager; | |
| import com.google.gerrit.server.git.WorkQueue; | |
| import com.google.gerrit.server.ioutil.HexFormat; | |
| import com.google.gerrit.server.project.ProjectResource; | |
| import com.google.gerrit.server.restapi.project.GarbageCollect.Input; | |
| import com.google.inject.Inject; | |
| import com.google.inject.Singleton; | |
| import java.io.IOException; | |
| import java.io.OutputStream; | |
| import java.io.OutputStreamWriter; | |
| import java.io.PrintWriter; | |
| import java.util.Collections; | |
| import java.util.Optional; | |
| /** REST endpoint that executes GC on a project. */ | |
| @RequiresCapability(GlobalCapability.RUN_GC) | |
| @Singleton | |
| public class GarbageCollect | |
| implements RestModifyView<ProjectResource, Input>, UiAction<ProjectResource> { | |
| public static class Input { | |
| public boolean showProgress; | |
| public boolean aggressive; | |
| public boolean async; | |
| } | |
| private final boolean canGC; | |
| private final GarbageCollection.Factory garbageCollectionFactory; | |
| private final WorkQueue workQueue; | |
| private final DynamicItem<UrlFormatter> urlFormatter; | |
| @Inject | |
| GarbageCollect( | |
| GitRepositoryManager repoManager, | |
| GarbageCollection.Factory garbageCollectionFactory, | |
| WorkQueue workQueue, | |
| DynamicItem<UrlFormatter> urlFormatter) { | |
| this.workQueue = workQueue; | |
| this.urlFormatter = urlFormatter; | |
| this.canGC = repoManager.canPerformGC(); | |
| this.garbageCollectionFactory = garbageCollectionFactory; | |
| } | |
| @Override | |
| public Response<?> apply(ProjectResource rsrc, Input input) { | |
| Project.NameKey project = rsrc.getNameKey(); | |
| if (input.async) { | |
| return applyAsync(project, input); | |
| } | |
| return Response.ok(applySync(project, input)); | |
| } | |
| private Response.Accepted applyAsync(Project.NameKey project, Input input) { | |
| Runnable job = | |
| new Runnable() { | |
| @Override | |
| public void run() { | |
| runGC(project, input, null); | |
| } | |
| @Override | |
| public String toString() { | |
| return "Run " | |
| + (input.aggressive ? "aggressive " : "") | |
| + "garbage collection on project " | |
| + project.get(); | |
| } | |
| }; | |
| @SuppressWarnings("unchecked") | |
| WorkQueue.Task<Void> task = (WorkQueue.Task<Void>) workQueue.getDefaultQueue().submit(job); | |
| Optional<String> url = | |
| urlFormatter | |
| .get() | |
| .getRestUrl("a/config/server/tasks/" + HexFormat.fromInt(task.getTaskId())); | |
| // We're in a HTTP handler, so must be present. | |
| checkState(url.isPresent()); | |
| return Response.accepted(url.get()); | |
| } | |
| @SuppressWarnings("resource") | |
| private BinaryResult applySync(Project.NameKey project, Input input) { | |
| return new BinaryResult() { | |
| @Override | |
| public void writeTo(OutputStream out) throws IOException { | |
| PrintWriter writer = | |
| new PrintWriter(new OutputStreamWriter(out, UTF_8)) { | |
| @Override | |
| public void println() { | |
| write('\n'); | |
| } | |
| }; | |
| try { | |
| PrintWriter progressWriter = input.showProgress ? writer : null; | |
| GarbageCollectionResult result = runGC(project, input, progressWriter); | |
| String msg = "Garbage collection completed successfully."; | |
| if (result.hasErrors()) { | |
| for (GcError e : result.getErrors()) { | |
| switch (e.getType()) { | |
| case REPOSITORY_NOT_FOUND: | |
| msg = "Error: project \"" + e.getProjectName() + "\" not found."; | |
| break; | |
| case GC_ALREADY_SCHEDULED: | |
| msg = | |
| "Error: garbage collection for project \"" | |
| + e.getProjectName() | |
| + "\" was already scheduled."; | |
| break; | |
| case GC_FAILED: | |
| msg = | |
| "Error: garbage collection for project \"" | |
| + e.getProjectName() | |
| + "\" failed."; | |
| break; | |
| default: | |
| msg = | |
| "Error: garbage collection for project \"" | |
| + e.getProjectName() | |
| + "\" failed: " | |
| + e.getType() | |
| + "."; | |
| } | |
| } | |
| } | |
| writer.println(msg); | |
| } finally { | |
| writer.flush(); | |
| } | |
| } | |
| }.setContentType("text/plain").setCharacterEncoding(UTF_8).disableGzip(); | |
| } | |
| GarbageCollectionResult runGC(Project.NameKey project, Input input, PrintWriter progressWriter) { | |
| return garbageCollectionFactory | |
| .create() | |
| .run(Collections.singletonList(project), input.aggressive, progressWriter); | |
| } | |
| @Override | |
| public UiAction.Description getDescription(ProjectResource rsrc) { | |
| return new UiAction.Description() | |
| .setLabel("Run GC") | |
| .setTitle("Triggers the Git Garbage Collection for this project.") | |
| .setVisible(canGC); | |
| } | |
| } |