blob: 30eaeca034b2246e5782f66ce8dd607b057b899e [file] [log] [blame]
/*
* The MIT License
*
* Copyright 2015 Sony Mobile Communications AB. All rights reserved.
*
* 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 com.googlesource.gerrit.plugins.refprotection;
import static org.eclipse.jgit.lib.Constants.R_HEADS;
import static org.eclipse.jgit.lib.Constants.R_REFS;
import static org.eclipse.jgit.lib.Constants.R_TAGS;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.gerrit.server.data.AccountAttribute;
import com.google.gerrit.server.data.RefUpdateAttribute;
import com.google.gerrit.server.events.RefUpdatedEvent;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.CreateBranch;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectResource;
import com.google.inject.Inject;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.TagBuilder;
import org.eclipse.jgit.revwalk.RevTag;
import org.eclipse.jgit.revwalk.RevWalk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BackupRef {
public static final String R_BACKUPS = R_REFS + "backups/";
private static final Logger log = LoggerFactory.getLogger(BackupRef.class);
private final CreateBranch.Factory createBranchFactory;
@Inject private static PluginConfigFactory cfg;
@Inject private static GitRepositoryManager repoManager;
@Inject @PluginName private static String pluginName;
@Inject
BackupRef(CreateBranch.Factory createBranchFactory) {
this.createBranchFactory = createBranchFactory;
}
public void createBackup(RefUpdatedEvent event, ProjectResource project) {
String refName = event.getRefName();
try (Repository git = repoManager.openRepository(project.getNameKey())) {
String backupRef = get(project, refName);
// No-op if the backup branch name is same as the original
if (backupRef.equals(refName)) {
return;
}
try (RevWalk revWalk = new RevWalk(git)) {
RefUpdateAttribute refUpdate = event.refUpdate.get();
if (cfg.getFromGerritConfig(pluginName).getBoolean("createTag", false)) {
TagBuilder tag = new TagBuilder();
AccountAttribute submitter = event.submitter.get();
tag.setTagger(new PersonIdent(submitter.name, submitter.email));
tag.setObjectId(revWalk.parseCommit(ObjectId.fromString(refUpdate.oldRev)));
String update = "Non-fast-forward update to";
if (refUpdate.newRev.equals(ObjectId.zeroId().getName())) {
update = "Deleted";
}
String type = "branch";
String fullMessage = "";
if (refUpdate.refName.startsWith(R_TAGS)) {
type = "tag";
try {
RevTag origTag = revWalk.parseTag(ObjectId.fromString(refUpdate.oldRev));
SimpleDateFormat format = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy ZZZZ");
PersonIdent taggerIdent = origTag.getTaggerIdent();
String tagger =
String.format(
"Tagger: %s <%s>\nDate: %s",
taggerIdent.getName(),
taggerIdent.getEmailAddress(),
format.format(taggerIdent.getWhen()));
fullMessage = "\n\nOriginal tag:\n" + tagger + "\n\n" + origTag.getFullMessage();
} catch (MissingObjectException e) {
log.warn("Original tag does not exist", e);
} catch (IncorrectObjectTypeException e) {
log.warn("Original tag was not a tag", e);
} catch (IOException e) {
log.warn("Unable to read original tag details", e);
}
}
tag.setMessage(update + " " + type + " " + refUpdate.refName + fullMessage);
tag.setTag(backupRef);
ObjectInserter inserter = git.newObjectInserter();
ObjectId tagId = inserter.insert(tag);
inserter.flush();
RefUpdate tagRef = git.updateRef(tag.getTag());
tagRef.setNewObjectId(tagId);
tagRef.setRefLogMessage("tagged deleted branch/tag " + tag.getTag(), false);
tagRef.setForceUpdate(false);
Result result = tagRef.update();
switch (result) {
case NEW:
case FORCED:
log.debug("Successfully created backup tag");
break;
case LOCK_FAILURE:
log.error("Failed to lock repository while creating backup tag");
break;
case REJECTED:
log.error("Tag already exists while creating backup tag");
break;
case FAST_FORWARD:
case IO_FAILURE:
case NOT_ATTEMPTED:
case NO_CHANGE:
case REJECTED_CURRENT_BRANCH:
case RENAMED:
case REJECTED_MISSING_OBJECT:
case REJECTED_OTHER_REASON:
default:
log.error("Unexpected result while creating backup tag: {}", result);
}
} else {
BranchInput input = new BranchInput();
input.ref = backupRef;
// We need to parse the commit to ensure if it's a tag, we get the
// commit the tag points to!
input.revision =
ObjectId.toString(revWalk.parseCommit(ObjectId.fromString(refUpdate.oldRev)).getId());
try {
createBranchFactory.create(backupRef).apply(project, input);
} catch (BadRequestException
| AuthException
| ResourceConflictException
| IOException
| PermissionBackendException
| NoSuchProjectException e) {
log.error(e.getMessage(), e);
}
}
}
} catch (RepositoryNotFoundException e) {
log.error("Repository does not exist", e);
} catch (IOException e) {
log.error("Could not open repository", e);
}
}
static String get(ProjectResource project, String refName) {
if (cfg.getFromGerritConfig(pluginName).getBoolean("useTimestamp", true)) {
return getTimestampBranch(refName);
}
return getSequentialBranch(project, refName);
}
static String getTimestampBranch(String refName) {
if (refName.startsWith(R_HEADS) || refName.startsWith(R_TAGS)) {
return String.format(
"%s-%s",
R_BACKUPS + refName.replaceFirst(R_REFS, ""),
new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()));
}
return refName;
}
private static String getSequentialBranch(ProjectResource project, String branchName) {
Integer rev = 1;
String deletedName = branchName.replaceFirst(R_REFS, "");
try (Repository git = repoManager.openRepository(project.getNameKey())) {
for (Ref ref : git.getAllRefs().values()) {
String name = ref.getName();
if (name.startsWith(R_BACKUPS + deletedName + "/")) {
Integer thisNum = Integer.parseInt(name.substring(name.lastIndexOf('/') + 1));
if (thisNum >= rev) {
rev = thisNum + 1;
}
}
}
} catch (RepositoryNotFoundException e) {
log.error("Repository does not exist", e);
} catch (IOException e) {
log.error("Could not determine latest revision of deleted branch", e);
}
return R_BACKUPS + deletedName + "/" + rev;
}
}