From f485a0d5b0a818577236ea03dccf80b2b53ae331 Mon Sep 17 00:00:00 2001 From: Edwin Kempin Date: Fri, 30 Mar 2012 08:11:47 +0200 Subject: [PATCH] Add SSH command to ban commits Change-Id: Iacd242547dfb2221c85959bf7ec3690134cb5d85 Signed-off-by: Edwin Kempin --- Documentation/cmd-ban-commit.txt | 60 ++++ Documentation/cmd-index.txt | 3 + .../server/config/GerritRequestModule.java | 2 + .../google/gerrit/server/git/BanCommit.java | 273 ++++++++++++++++++ .../gerrit/server/git/BanCommitResult.java | 54 ++++ .../git/IncompleteUserInfoException.java | 23 ++ .../gerrit/server/git/MergeException.java | 2 +- .../com/google/gerrit/sshd/SshModule.java | 3 + .../gerrit/sshd/args4j/ObjectIdHandler.java | 47 +++ .../sshd/commands/BanCommitCommand.java | 118 ++++++++ .../sshd/commands/DefaultCommandModule.java | 1 + 11 files changed, 585 insertions(+), 1 deletion(-) create mode 100644 Documentation/cmd-ban-commit.txt create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommitResult.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java create mode 100644 gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ObjectIdHandler.java create mode 100644 gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java diff --git a/Documentation/cmd-ban-commit.txt b/Documentation/cmd-ban-commit.txt new file mode 100644 index 0000000000..fb4a2ac97c --- /dev/null +++ b/Documentation/cmd-ban-commit.txt @@ -0,0 +1,60 @@ +gerrit ban-commit +================= + +NAME +---- +gerrit ban-commit - Bans a commit from a project's repository. + +SYNOPSIS +-------- +[verse] +'ssh' -p 'gerrit ban-commit' + [--reason ] + + ... + +DESCRIPTION +----------- +Marks a commit as banned for the specified repository. If a commit is +banned Gerrit rejects every push that includes this commit with +link:error-contains-banned-commit.html[contains banned commit ...]. + +[NOTE] +This command just marks the commit as banned, but it does not remove +the commit from the history of any central branch. This needs to be +done manually. + +ACCESS +------ +Caller must be owner of the project or be a member of the privileged +'Administrators' group. + +SCRIPTING +--------- +This command is intended to be used in scripts. + +OPTIONS +------- +:: + Required; name of the project for which the commit should be + banned. + +:: + Required; commit(s) that should be banned. + +--reason:: + Reason for banning the commit. + +EXAMPLES +-------- +Ban commit `421919d015c062fd28901fe144a78a555d0b5984` from project +`myproject`: + +==== + $ ssh -p 29418 review.example.com gerrit ban-commit myproject \ + 421919d015c062fd28901fe144a78a555d0b5984 +==== + +GERRIT +------ +Part of link:index.html[Gerrit Code Review] diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt index b09c3b363e..e7d59fb7bc 100644 --- a/Documentation/cmd-index.txt +++ b/Documentation/cmd-index.txt @@ -54,6 +54,9 @@ see link:user-upload.html#test_ssh[Testing Your SSH Connection]. 'gerrit approve':: 'Deprecated alias for `gerrit review`.' +link:cmd-ban-commit.html[gerrit ban-commit]:: + Bans a commit from a project's repository. + link:cmd-ls-groups.html[gerrit ls-groups]:: List groups visible to the caller. diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java index 00562b0bd5..a39fe6125e 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java @@ -34,6 +34,7 @@ import com.google.gerrit.server.changedetail.PublishDraft; import com.google.gerrit.server.changedetail.RestoreChange; import com.google.gerrit.server.changedetail.Submit; import com.google.gerrit.server.git.AsyncReceiveCommits; +import com.google.gerrit.server.git.BanCommit; import com.google.gerrit.server.git.CreateCodeReviewNotes; import com.google.gerrit.server.git.MergeOp; import com.google.gerrit.server.git.MetaDataUpdate; @@ -113,5 +114,6 @@ public class GerritRequestModule extends FactoryModule { factory(CreateProject.Factory.class); factory(Submit.Factory.class); factory(SuggestParentCandidates.Factory.class); + factory(BanCommit.Factory.class); } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java new file mode 100644 index 0000000000..c9c9753a2a --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java @@ -0,0 +1,273 @@ +// Copyright (C) 2012 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.git; + +import static com.google.gerrit.server.git.GitRepositoryManager.REF_REJECT_COMMITS; + +import com.google.gerrit.common.errors.PermissionDeniedException; +import com.google.gerrit.reviewdb.client.Account; +import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.GerritPersonIdent; +import com.google.gerrit.server.account.AccountCache; +import com.google.gerrit.server.project.ProjectControl; +import com.google.inject.Inject; +import com.google.inject.Provider; + +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.Constants; +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.notes.Note; +import org.eclipse.jgit.notes.NoteMap; +import org.eclipse.jgit.notes.NoteMapMerger; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; + +import java.io.IOException; +import java.util.List; + +public class BanCommit { + + private static final int MAX_LOCK_FAILURE_CALLS = 10; + private static final int SLEEP_ON_LOCK_FAILURE_MS = 25; + + public interface Factory { + BanCommit create(); + } + + private final Provider currentUser; + private final GitRepositoryManager repoManager; + private final AccountCache accountCache; + private final PersonIdent gerritIdent; + + @Inject + BanCommit(final Provider currentUser, + final GitRepositoryManager repoManager, final AccountCache accountCache, + @GerritPersonIdent final PersonIdent gerritIdent) { + this.currentUser = currentUser; + this.repoManager = repoManager; + this.accountCache = accountCache; + this.gerritIdent = gerritIdent; + } + + public BanCommitResult ban(final ProjectControl projectControl, + final List commitsToBan, final String reason) + throws PermissionDeniedException, IOException, + IncompleteUserInfoException, InterruptedException, MergeException { + if (!projectControl.isOwner()) { + throw new PermissionDeniedException( + "No project owner: not permitted to ban commits"); + } + + final BanCommitResult result = new BanCommitResult(); + + final PersonIdent currentUserIdent = createPersonIdent(); + final Repository repo = + repoManager.openRepository(projectControl.getProject().getNameKey()); + try { + final RevWalk revWalk = new RevWalk(repo); + final ObjectInserter inserter = repo.newObjectInserter(); + try { + NoteMap baseNoteMap = null; + RevCommit baseCommit = null; + final Ref notesBranch = repo.getRef(REF_REJECT_COMMITS); + if (notesBranch != null) { + baseCommit = revWalk.parseCommit(notesBranch.getObjectId()); + baseNoteMap = NoteMap.read(revWalk.getObjectReader(), baseCommit); + } + + final NoteMap ourNoteMap; + if (baseCommit != null) { + ourNoteMap = NoteMap.read(repo.newObjectReader(), baseCommit); + } else { + ourNoteMap = NoteMap.newEmptyMap(); + } + + for (final ObjectId commitToBan : commitsToBan) { + try { + revWalk.parseCommit(commitToBan); + } catch (MissingObjectException e) { + // ignore exception, also not existing commits can be banned + } catch (IncorrectObjectTypeException e) { + result.notACommit(commitToBan, e.getMessage()); + continue; + } + + final Note note = ourNoteMap.getNote(commitToBan); + if (note != null) { + result.commitAlreadyBanned(commitToBan); + continue; + } + + final String noteContent = reason != null ? reason : ""; + final ObjectId noteContentId = + inserter + .insert(Constants.OBJ_BLOB, noteContent.getBytes("UTF-8")); + ourNoteMap.set(commitToBan, noteContentId); + result.commitBanned(commitToBan); + } + + if (result.getNewlyBannedCommits().isEmpty()) { + return result; + } + + final ObjectId ourCommit = + commit(ourNoteMap, inserter, currentUserIdent, baseCommit, result, + reason); + + updateRef(repo, revWalk, inserter, ourNoteMap, ourCommit, baseNoteMap, + baseCommit); + } finally { + revWalk.release(); + inserter.release(); + } + } finally { + repo.close(); + } + + return result; + } + + private PersonIdent createPersonIdent() throws IncompleteUserInfoException { + final String userName = currentUser.get().getUserName(); + final Account account = accountCache.getByUsername(userName).getAccount(); + if (account.getFullName() == null) { + throw new IncompleteUserInfoException(userName, "full name"); + } + if (account.getPreferredEmail() == null) { + throw new IncompleteUserInfoException(userName, "preferred email"); + } + return new PersonIdent(account.getFullName(), account.getPreferredEmail()); + } + + private static ObjectId commit(final NoteMap noteMap, + final ObjectInserter inserter, final PersonIdent personIdent, + final ObjectId baseCommit, final BanCommitResult result, + final String reason) throws IOException { + final String commitMsg = + buildCommitMessage(result.getNewlyBannedCommits(), reason); + if (baseCommit != null) { + return createCommit(noteMap, inserter, personIdent, commitMsg, baseCommit); + } else { + return createCommit(noteMap, inserter, personIdent, commitMsg); + } + } + + private static ObjectId createCommit(final NoteMap noteMap, + final ObjectInserter inserter, final PersonIdent personIdent, + final String message, final ObjectId... parents) throws IOException { + final CommitBuilder b = new CommitBuilder(); + b.setTreeId(noteMap.writeTree(inserter)); + b.setAuthor(personIdent); + b.setCommitter(personIdent); + if (parents.length > 0) { + b.setParentIds(parents); + } + b.setMessage(message); + final ObjectId commitId = inserter.insert(b); + inserter.flush(); + return commitId; + } + + private static String buildCommitMessage(final List bannedCommits, + final String reason) { + final StringBuilder commitMsg = new StringBuilder(); + commitMsg.append("Banning "); + commitMsg.append(bannedCommits.size()); + commitMsg.append(" "); + commitMsg.append(bannedCommits.size() == 1 ? "commit" : "commits"); + commitMsg.append("\n\n"); + if (reason != null) { + commitMsg.append("Reason: "); + commitMsg.append(reason); + commitMsg.append("\n\n"); + } + commitMsg.append("The following commits are banned:\n"); + final StringBuilder commitList = new StringBuilder(); + for (final ObjectId c : bannedCommits) { + if (commitList.length() > 0) { + commitList.append(",\n"); + } + commitList.append(c.getName()); + } + commitMsg.append(commitList); + return commitMsg.toString(); + } + + public void updateRef(final Repository repo, final RevWalk revWalk, + final ObjectInserter inserter, final NoteMap ourNoteMap, + final ObjectId oursCommit, final NoteMap baseNoteMap, + final ObjectId baseCommit) throws IOException, InterruptedException, + MissingObjectException, IncorrectObjectTypeException, + CorruptObjectException, MergeException { + + int remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS; + RefUpdate refUpdate = createRefUpdate(repo, oursCommit, baseCommit); + + for (;;) { + final Result result = refUpdate.update(); + + if (result == Result.LOCK_FAILURE) { + if (--remainingLockFailureCalls > 0) { + Thread.sleep(SLEEP_ON_LOCK_FAILURE_MS); + } else { + throw new MergeException("Failed to lock the ref: " + + REF_REJECT_COMMITS); + } + + } else if (result == Result.REJECTED) { + final RevCommit theirsCommit = + revWalk.parseCommit(refUpdate.getOldObjectId()); + final NoteMap theirNoteMap = + NoteMap.read(revWalk.getObjectReader(), theirsCommit); + final NoteMapMerger merger = new NoteMapMerger(repo); + final NoteMap merged = + merger.merge(baseNoteMap, ourNoteMap, theirNoteMap); + final ObjectId mergeCommit = + createCommit(merged, inserter, gerritIdent, + "Merged note commits\n", oursCommit, theirsCommit); + refUpdate = createRefUpdate(repo, mergeCommit, theirsCommit); + remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS; + + } else if (result == Result.IO_FAILURE) { + throw new IOException( + "Couldn't create commit reject notes because of IO_FAILURE"); + } else { + break; + } + } + } + + private static RefUpdate createRefUpdate(final Repository repo, + final ObjectId newObjectId, final ObjectId expectedOldObjectId) + throws IOException { + RefUpdate refUpdate = repo.updateRef(REF_REJECT_COMMITS); + refUpdate.setNewObjectId(newObjectId); + if (expectedOldObjectId == null) { + refUpdate.setExpectedOldObjectId(ObjectId.zeroId()); + } else { + refUpdate.setExpectedOldObjectId(expectedOldObjectId); + } + return refUpdate; + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommitResult.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommitResult.java new file mode 100644 index 0000000000..1b4845502f --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommitResult.java @@ -0,0 +1,54 @@ +// Copyright (C) 2012 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.git; + +import org.eclipse.jgit.lib.ObjectId; + +import java.util.LinkedList; +import java.util.List; + +public class BanCommitResult { + + private final List newlyBannedCommits = new LinkedList(); + private final List alreadyBannedCommits = new LinkedList(); + private final List ignoredObjectIds = new LinkedList(); + + public BanCommitResult() { + } + + public void commitBanned(final ObjectId commitId) { + newlyBannedCommits.add(commitId); + } + + public void commitAlreadyBanned(final ObjectId commitId) { + alreadyBannedCommits.add(commitId); + } + + public void notACommit(final ObjectId id, final String message) { + ignoredObjectIds.add(id); + } + + public List getNewlyBannedCommits() { + return newlyBannedCommits; + } + + public List getAlreadyBannedCommits() { + return alreadyBannedCommits; + } + + public List getIgnoredObjectIds() { + return ignoredObjectIds; + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java new file mode 100644 index 0000000000..204d777137 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java @@ -0,0 +1,23 @@ +// Copyright (C) 2012 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.git; + +public class IncompleteUserInfoException extends Exception { + private static final long serialVersionUID = 1L; + + public IncompleteUserInfoException(final String userName, final String missingInfo) { + super("For the user \"" + userName + "\" " + missingInfo + " is not set."); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeException.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeException.java index 44becb525b..1997c13d2e 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeException.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeException.java @@ -15,7 +15,7 @@ package com.google.gerrit.server.git; /** Indicates the current branch's queue cannot be processed at this time. */ -class MergeException extends Exception { +public class MergeException extends Exception { private static final long serialVersionUID = 1L; MergeException(final String msg) { diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java index 00281b848f..e59c014eeb 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java @@ -36,6 +36,7 @@ import com.google.gerrit.server.util.RequestScopePropagator; import com.google.gerrit.sshd.args4j.AccountGroupIdHandler; import com.google.gerrit.sshd.args4j.AccountGroupUUIDHandler; import com.google.gerrit.sshd.args4j.AccountIdHandler; +import com.google.gerrit.sshd.args4j.ObjectIdHandler; import com.google.gerrit.sshd.args4j.PatchSetIdHandler; import com.google.gerrit.sshd.args4j.ProjectControlHandler; import com.google.gerrit.sshd.args4j.SocketAddressHandler; @@ -49,6 +50,7 @@ import com.google.inject.servlet.RequestScoped; import org.apache.sshd.common.KeyPairProvider; import org.apache.sshd.server.CommandFactory; import org.apache.sshd.server.PublickeyAuthenticator; +import org.eclipse.jgit.lib.ObjectId; import org.kohsuke.args4j.spi.OptionHandler; import java.net.SocketAddress; @@ -120,6 +122,7 @@ public class SshModule extends FactoryModule { registerOptionHandler(Account.Id.class, AccountIdHandler.class); registerOptionHandler(AccountGroup.Id.class, AccountGroupIdHandler.class); registerOptionHandler(AccountGroup.UUID.class, AccountGroupUUIDHandler.class); + registerOptionHandler(ObjectId.class, ObjectIdHandler.class); registerOptionHandler(PatchSet.Id.class, PatchSetIdHandler.class); registerOptionHandler(ProjectControl.class, ProjectControlHandler.class); registerOptionHandler(SocketAddress.class, SocketAddressHandler.class); diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ObjectIdHandler.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ObjectIdHandler.java new file mode 100644 index 0000000000..adb5ad6616 --- /dev/null +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ObjectIdHandler.java @@ -0,0 +1,47 @@ +// Copyright (C) 2012 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.sshd.args4j; + +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; + +import org.eclipse.jgit.lib.ObjectId; +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; +import org.kohsuke.args4j.OptionDef; +import org.kohsuke.args4j.spi.OptionHandler; +import org.kohsuke.args4j.spi.Parameters; +import org.kohsuke.args4j.spi.Setter; + +public class ObjectIdHandler extends OptionHandler { + + @Inject + public ObjectIdHandler(@Assisted final CmdLineParser parser, + @Assisted final OptionDef option, @Assisted final Setter setter) { + super(parser, option, setter); + } + + @Override + public int parseArguments(Parameters params) throws CmdLineException { + final String n = params.getParameter(0); + setter.addValue(ObjectId.fromString(n)); + return 1; + } + + @Override + public String getDefaultMetaVariable() { + return "COMMIT"; + } +} diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java new file mode 100644 index 0000000000..fd58221e28 --- /dev/null +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java @@ -0,0 +1,118 @@ +// Copyright (C) 2012 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.sshd.commands; + +import com.google.gerrit.common.errors.PermissionDeniedException; +import com.google.gerrit.server.git.BanCommit; +import com.google.gerrit.server.git.BanCommitResult; +import com.google.gerrit.server.git.IncompleteUserInfoException; +import com.google.gerrit.server.git.MergeException; +import com.google.gerrit.server.project.ProjectControl; +import com.google.gerrit.sshd.BaseCommand; +import com.google.inject.Inject; + +import org.apache.sshd.server.Environment; +import org.eclipse.jgit.lib.ObjectId; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +public class BanCommitCommand extends BaseCommand { + + @Option(name = "--reason", aliases = {"-r"}, metaVar = "REASON", usage = "reason for banning the commit") + private String reason; + + @Argument(index = 0, required = true, metaVar = "PROJECT", + usage = "name of the project for which the commit should be banned") + private ProjectControl projectControl; + + @Argument(index = 1, required = true, multiValued = true, metaVar = "COMMIT", + usage = "commit(s) that should be banned") + private List commitsToBan = new ArrayList(); + + @Inject + private BanCommit.Factory banCommitFactory; + + @Override + public void start(final Environment env) throws IOException { + startThread(new CommandRunnable() { + @Override + public void run() throws Exception { + parseCommandLine(); + BanCommitCommand.this.display(); + } + }); + } + + private void display() throws Failure { + try { + final BanCommitResult result = + banCommitFactory.create().ban(projectControl, commitsToBan, reason); + + final PrintWriter stdout = toPrintWriter(out); + try { + final List newlyBannedCommits = + result.getNewlyBannedCommits(); + if (!newlyBannedCommits.isEmpty()) { + stdout.print("The following commits were banned:\n"); + printCommits(stdout, newlyBannedCommits); + } + + final List alreadyBannedCommits = + result.getAlreadyBannedCommits(); + if (!alreadyBannedCommits.isEmpty()) { + stdout.print("The following commits were already banned:\n"); + printCommits(stdout, alreadyBannedCommits); + } + + final List ignoredIds = result.getIgnoredObjectIds(); + if (!ignoredIds.isEmpty()) { + stdout.print("The following ids do not represent commits" + + " and were ignored:\n"); + printCommits(stdout, ignoredIds); + } + } finally { + stdout.flush(); + } + } catch (PermissionDeniedException e) { + throw die(e); + } catch (IOException e) { + throw die(e); + } catch (IncompleteUserInfoException e) { + throw die(e); + } catch (MergeException e) { + throw die(e); + } catch (InterruptedException e) { + throw die(e); + } + } + + private static void printCommits(final PrintWriter stdout, + final List commits) { + boolean first = true; + for (final ObjectId c : commits) { + if (!first) { + stdout.print(",\n"); + } + stdout.print(c.getName()); + first = false; + } + stdout.print("\n\n"); + } +} diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java index 5cee06ee96..437d4dba40 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java @@ -35,6 +35,7 @@ public class DefaultCommandModule extends CommandModule { // SlaveCommandModule. command(gerrit).toProvider(new DispatchCommandProvider(gerrit)); + command(gerrit, "ban-commit").to(BanCommitCommand.class); command(gerrit, "flush-caches").to(FlushCaches.class); command(gerrit, "ls-projects").to(ListProjects.class); command(gerrit, "ls-groups").to(ListGroupsCommand.class);