Merge "Allows slave to use authenticated Git/HTTP protocol"
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 40873fe..ef2cdd9 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -1121,6 +1121,38 @@
   HTTP/1.1 204 No Content
 ----
 
+[[delete-branches]]
+=== Delete Branches
+--
+'POST /projects/link:#project-name[\{project-name\}]/branches:delete'
+--
+
+Delete one or more branches.
+
+The branches to be deleted must be provided in the request body as a
+link:#delete-branches-input[DeleteBranchesInput] entity.
+
+.Request
+----
+  POST /projects/MyProject/branches:delete HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+
+  {
+    "branches": [
+      "stable-1.0",
+      "stable-2.0"
+    ]
+  }
+----
+
+.Response
+----
+  HTTP/1.1 204 No Content
+----
+
+If some branches could not be deleted, the response is "`409 Conflict`" and the
+error message is contained in the response body.
+
 [[get-content]]
 === Get Content
 --
@@ -2060,6 +2092,18 @@
 Tokens such as `${project}` are not resolved.
 |===========================
 
+[[delete-branches-input]]
+=== DeleteBranchesInput
+The `DeleteBranchesInput` entity contains information about branches that should
+be deleted.
+
+[options="header",width="50%",cols="1,6"]
+|==========================
+|Field Name   |Description
+|`branches`   |A list of branch names that identify the branches that should be
+deleted.
+|==========================
+
 [[gc-input]]
 === GCInput
 The `GCInput` entity contains information to run the Git garbage
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index ff2944f..a177d00 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -36,6 +36,7 @@
 import com.google.gerrit.extensions.api.projects.BranchInput;
 import com.google.gerrit.extensions.client.SubmitType;
 import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ChangeMessageInfo;
 import com.google.gerrit.extensions.common.CommentInfo;
 import com.google.gerrit.extensions.common.DiffInfo;
 import com.google.gerrit.extensions.common.MergeableInfo;
@@ -55,6 +56,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
@@ -157,6 +159,21 @@
         .cherryPick(in);
     assertThat((Iterable<?>)orig.get().messages).hasSize(2);
 
+    String cherryPickedRevision = cherry.get().currentRevision;
+    String expectedMessage = String.format(
+        "Patch Set 1: Cherry Picked\n\n" +
+        "This patchset was cherry picked to branch %s as commit %s",
+        in.destination, cherryPickedRevision);
+
+    Iterator<ChangeMessageInfo> origIt = orig.get().messages.iterator();
+    origIt.next();
+    assertThat(origIt.next().message).isEqualTo(expectedMessage);
+
+    assertThat((Iterable<?>)cherry.get().messages).hasSize(1);
+    Iterator<ChangeMessageInfo> cherryIt = cherry.get().messages.iterator();
+    expectedMessage = "Patch Set 1: Cherry Picked from branch master.";
+    assertThat(cherryIt.next().message).isEqualTo(expectedMessage);
+
     assertThat(cherry.get().subject).contains(in.message);
     assertThat(cherry.get().topic).isEqualTo("someTopic");
     cherry.current().review(ReviewInput.approve());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
index ee8ea69..f4f7087 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
@@ -15,7 +15,6 @@
 
 import com.google.gerrit.client.VoidResult;
 import com.google.gerrit.client.projects.ConfigInfo.ConfigParameterValue;
-import com.google.gerrit.client.rpc.CallbackGroup;
 import com.google.gerrit.client.rpc.NativeMap;
 import com.google.gerrit.client.rpc.NativeString;
 import com.google.gerrit.client.rpc.RestApi;
@@ -60,21 +59,20 @@
   }
 
   /**
-   * Delete branches. For each branch to be deleted a separate DELETE request is
-   * fired to the server. The {@code onSuccess} method of the provided callback
-   * is invoked once after all requests succeeded. If any request fails the
-   * callbacks' {@code onFailure} method is invoked. In a failure case it can be
-   * that still some of the branches were successfully deleted.
+   * Delete branches. One call is fired to the server to delete all the
+   * branches.
    */
   public static void deleteBranches(Project.NameKey name,
       Set<String> refs, AsyncCallback<VoidResult> cb) {
-    CallbackGroup group = new CallbackGroup();
-    for (String ref : refs) {
-      project(name).view("branches").id(ref)
-          .delete(group.add(cb));
-      cb = CallbackGroup.emptyCallback();
+    if (refs.size() == 1) {
+      project(name).view("branches").id(refs.iterator().next()).delete(cb);
+    } else {
+      DeleteBranchesInput d = DeleteBranchesInput.create();
+      for (String ref : refs) {
+        d.add_branch(ref);
+      }
+      project(name).view("branches:delete").post(d, cb);
     }
-    group.done();
   }
 
   public static void getConfig(Project.NameKey name,
@@ -292,4 +290,18 @@
 
     final native void setRef(String r) /*-{ if(r)this.ref=r; }-*/;
   }
+
+  private static class DeleteBranchesInput extends JavaScriptObject {
+    static DeleteBranchesInput create() {
+      DeleteBranchesInput d = createObject().cast();
+      d.init();
+      return d;
+    }
+
+    protected DeleteBranchesInput() {
+    }
+
+    final native void init() /*-{ this.branches = []; }-*/;
+    final native void add_branch(String b) /*-{ this.branches.push(b); }-*/;
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
index 374cde3..9606397 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.GerritPersonIdent;
@@ -34,6 +35,7 @@
 import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.git.validators.CommitValidationException;
 import com.google.gerrit.server.git.validators.CommitValidators;
+import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.NoSuchChangeException;
@@ -76,7 +78,9 @@
   private final CommitValidators.Factory commitValidatorsFactory;
   private final ChangeInserter.Factory changeInserterFactory;
   private final PatchSetInserter.Factory patchSetInserterFactory;
-  final MergeUtil.Factory mergeUtilFactory;
+  private final MergeUtil.Factory mergeUtilFactory;
+  private final ChangeMessagesUtil changeMessagesUtil;
+  private final ChangeUpdate.Factory updateFactory;
 
   @Inject
   CherryPickChange(Provider<ReviewDb> db,
@@ -87,7 +91,9 @@
       CommitValidators.Factory commitValidatorsFactory,
       ChangeInserter.Factory changeInserterFactory,
       PatchSetInserter.Factory patchSetInserterFactory,
-      MergeUtil.Factory mergeUtilFactory) {
+      MergeUtil.Factory mergeUtilFactory,
+      ChangeMessagesUtil changeMessagesUtil,
+      ChangeUpdate.Factory updateFactory) {
     this.db = db;
     this.queryProvider = queryProvider;
     this.gitManager = gitManager;
@@ -97,6 +103,8 @@
     this.changeInserterFactory = changeInserterFactory;
     this.patchSetInserterFactory = patchSetInserterFactory;
     this.mergeUtilFactory = mergeUtilFactory;
+    this.changeMessagesUtil = changeMessagesUtil;
+    this.updateFactory = updateFactory;
   }
 
   public Change.Id cherryPick(Change change, PatchSet patch,
@@ -185,9 +193,17 @@
         } else {
           // Change key not found on destination branch. We can create a new
           // change.
-          return createNewChange(git, revWalk, changeKey, project,
-              patch.getId(), destRef, cherryPickCommit, refControl,
+          Change newChange = createNewChange(git, revWalk, changeKey, project,
+              destRef, cherryPickCommit, refControl,
               identifiedUser, change.getTopic());
+
+          addMessageToSourceChange(change, patch.getId(), destinationBranch,
+              cherryPickCommit, identifiedUser, refControl);
+
+          addMessageToDestinationChange(newChange, change.getDest().getShortName(),
+              identifiedUser, refControl);
+
+          return newChange.getId();
         }
       } finally {
         revWalk.release();
@@ -216,8 +232,8 @@
     return change.getId();
   }
 
-  private Change.Id createNewChange(Repository git, RevWalk revWalk,
-      Change.Key changeKey, Project.NameKey project, PatchSet.Id patchSetId,
+  private Change createNewChange(Repository git, RevWalk revWalk,
+      Change.Key changeKey, Project.NameKey project,
       Ref destRef, RevCommit cherryPickCommit, RefControl refControl,
       IdentifiedUser identifiedUser, String topic)
       throws OrmException, InvalidChangeOperationException, IOException {
@@ -254,31 +270,51 @@
           change.getDest().getParentKey().get(), ru.getResult()));
     }
 
-    ins.setMessage(buildChangeMessage(patchSetId, change, cherryPickCommit,
-        identifiedUser))
-        .insert();
+    ins.insert();
 
-    return change.getId();
+    return change;
   }
 
-  private ChangeMessage buildChangeMessage(PatchSet.Id patchSetId, Change dest,
-      RevCommit cherryPickCommit, IdentifiedUser identifiedUser)
-      throws OrmException {
-    ChangeMessage cmsg = new ChangeMessage(
+  private void addMessageToSourceChange(Change change, PatchSet.Id patchSetId,
+      String destinationBranch, RevCommit cherryPickCommit,
+      IdentifiedUser identifiedUser, RefControl refControl) throws OrmException {
+    ChangeMessage changeMessage = new ChangeMessage(
         new ChangeMessage.Key(
             patchSetId.getParentKey(), ChangeUtil.messageUUID(db.get())),
             identifiedUser.getAccountId(), TimeUtil.nowTs(), patchSetId);
-    String destBranchName = dest.getDest().get();
-    StringBuilder msgBuf = new StringBuilder("Patch Set ")
+    StringBuilder sb = new StringBuilder("Patch Set ")
         .append(patchSetId.get())
         .append(": Cherry Picked")
         .append("\n\n")
         .append("This patchset was cherry picked to branch ")
-        .append(destBranchName.substring(
-            destBranchName.indexOf("refs/heads/") + "refs/heads/".length()))
+        .append(destinationBranch)
         .append(" as commit ")
         .append(cherryPickCommit.getId().getName());
-    cmsg.setMessage(msgBuf.toString());
-    return cmsg;
+    changeMessage.setMessage(sb.toString());
+
+    ChangeControl ctl = refControl.getProjectControl().controlFor(change);
+    ChangeUpdate update = updateFactory.create(ctl, change.getCreatedOn());
+    changeMessagesUtil.addChangeMessage(db.get(), update, changeMessage);
+  }
+
+  private void addMessageToDestinationChange(Change change, String sourceBranch,
+      IdentifiedUser identifiedUser, RefControl refControl) throws OrmException {
+    PatchSet.Id patchSetId =
+        db.get().patchSets().get(change.currentPatchSetId()).getId();
+    ChangeMessage changeMessage = new ChangeMessage(
+        new ChangeMessage.Key(
+            patchSetId.getParentKey(), ChangeUtil.messageUUID(db.get())),
+            identifiedUser.getAccountId(), TimeUtil.nowTs(), patchSetId);
+
+    StringBuilder sb = new StringBuilder("Patch Set ")
+      .append(patchSetId.get())
+      .append(": Cherry Picked from branch ")
+      .append(sourceBranch)
+      .append(".");
+    changeMessage.setMessage(sb.toString());
+
+    ChangeControl ctl = refControl.getProjectControl().controlFor(change);
+    ChangeUpdate update = updateFactory.create(ctl, change.getCreatedOn());
+    changeMessagesUtil.addChangeMessage(db.get(), update, changeMessage);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
index 9a714f8..4aba333 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
@@ -32,6 +32,7 @@
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
+import org.eclipse.jgit.errors.LockFailedException;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
 import org.slf4j.Logger;
@@ -42,6 +43,8 @@
 @Singleton
 public class DeleteBranch implements RestModifyView<BranchResource, Input>{
   private static final Logger log = LoggerFactory.getLogger(DeleteBranch.class);
+  private static final int MAX_LOCK_FAILURE_CALLS = 10;
+  private static final long SLEEP_ON_LOCK_FAILURE_MS = 15;
 
   static class Input {
   }
@@ -81,14 +84,28 @@
     Repository r = repoManager.openRepository(rsrc.getNameKey());
     try {
       RefUpdate.Result result;
-      RefUpdate u;
-      try {
-        u = r.updateRef(rsrc.getRef());
-        u.setForceUpdate(true);
-        result = u.delete();
-      } catch (IOException e) {
-        log.error("Cannot delete " + rsrc.getBranchKey(), e);
-        throw e;
+      RefUpdate u = r.updateRef(rsrc.getRef());
+      u.setForceUpdate(true);
+      int remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
+      for (;;) {
+        try {
+          result = u.delete();
+        } catch (LockFailedException e) {
+          result = RefUpdate.Result.LOCK_FAILURE;
+        } catch (IOException e) {
+          log.error("Cannot delete " + rsrc.getBranchKey(), e);
+          throw e;
+        }
+        if (result == RefUpdate.Result.LOCK_FAILURE
+            && --remainingLockFailureCalls > 0) {
+          try {
+            Thread.sleep(SLEEP_ON_LOCK_FAILURE_MS);
+          } catch (InterruptedException ie) {
+            // ignore
+          }
+        } else {
+          break;
+        }
       }
 
       switch (result) {
@@ -104,7 +121,7 @@
           break;
 
         case REJECTED_CURRENT_BRANCH:
-          log.warn("Cannot delete " + rsrc.getBranchKey() + ": " + result.name());
+          log.error("Cannot delete " + rsrc.getBranchKey() + ": " + result.name());
           throw new ResourceConflictException("cannot delete current branch");
 
         default:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java
new file mode 100644
index 0000000..bdc67ac
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java
@@ -0,0 +1,181 @@
+// Copyright (C) 2015 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.project;
+
+import static java.lang.String.format;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.DeleteBranches.Input;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.transport.ReceiveCommand.Result;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.List;
+
+@Singleton
+class DeleteBranches implements RestModifyView<ProjectResource, Input> {
+  private static final Logger log = LoggerFactory.getLogger(DeleteBranches.class);
+
+  static class Input {
+    List<String> branches;
+
+    static Input init(Input in) {
+      if (in == null) {
+        in = new Input();
+      }
+      if (in.branches == null) {
+        in.branches = Lists.newArrayListWithCapacity(1);
+      }
+      return in;
+    }
+  }
+
+  private final Provider<IdentifiedUser> identifiedUser;
+  private final GitRepositoryManager repoManager;
+  private final Provider<ReviewDb> dbProvider;
+  private final Provider<InternalChangeQuery> queryProvider;
+  private final GitReferenceUpdated referenceUpdated;
+  private final ChangeHooks hooks;
+
+  @Inject
+  DeleteBranches(Provider<IdentifiedUser> identifiedUser,
+      GitRepositoryManager repoManager,
+      Provider<ReviewDb> dbProvider,
+      Provider<InternalChangeQuery> queryProvider,
+      GitReferenceUpdated referenceUpdated,
+      ChangeHooks hooks) {
+    this.identifiedUser = identifiedUser;
+    this.repoManager = repoManager;
+    this.dbProvider = dbProvider;
+    this.queryProvider = queryProvider;
+    this.referenceUpdated = referenceUpdated;
+    this.hooks = hooks;
+  }
+
+  @Override
+  public Response<?> apply(ProjectResource project, Input input)
+      throws OrmException, IOException, ResourceConflictException {
+    input = Input.init(input);
+    Repository r = repoManager.openRepository(project.getNameKey());
+    try {
+      BatchRefUpdate batchUpdate = r.getRefDatabase().newBatchUpdate();
+      for (String branch : input.branches) {
+        batchUpdate.addCommand(createDeleteCommand(project, r, branch));
+      }
+      RevWalk rw = new RevWalk(r);
+      try {
+        batchUpdate.execute(rw, NullProgressMonitor.INSTANCE);
+      } finally {
+        rw.release();
+      }
+      StringBuilder errorMessages = new StringBuilder();
+      for (ReceiveCommand command : batchUpdate.getCommands()) {
+        if (command.getResult() == Result.OK) {
+          postDeletion(project, command);
+        } else {
+          appendAndLogErrorMessage(errorMessages, command);
+        }
+      }
+      if (errorMessages.length() > 0) {
+        throw new ResourceConflictException(errorMessages.toString());
+      }
+    } finally {
+      r.close();
+    }
+    return Response.none();
+  }
+
+  private ReceiveCommand createDeleteCommand(ProjectResource project,
+      Repository r, String branch) throws OrmException, IOException {
+    Ref ref = r.getRefDatabase().getRef(branch);
+    ReceiveCommand command;
+    if (ref == null) {
+      command = new ReceiveCommand(ObjectId.zeroId(), ObjectId.zeroId(), branch);
+      command.setResult(Result.REJECTED_OTHER_REASON,
+          "it doesn't exist or you do not have permission to delete it");
+      return command;
+    }
+    command =
+        new ReceiveCommand(ref.getObjectId(), ObjectId.zeroId(), ref.getName());
+    Branch.NameKey branchKey =
+        new Branch.NameKey(project.getNameKey(), ref.getName());
+    if (!project.getControl().controlForRef(branchKey).canDelete()) {
+      command.setResult(Result.REJECTED_OTHER_REASON,
+          "it doesn't exist or you do not have permission to delete it");
+    }
+    if (!queryProvider.get().setLimit(1).byBranchOpen(branchKey).isEmpty()) {
+      command.setResult(Result.REJECTED_OTHER_REASON, "it has open changes");
+    }
+    return command;
+  }
+
+  private void appendAndLogErrorMessage(StringBuilder errorMessages,
+      ReceiveCommand cmd) {
+    String msg = null;
+    switch (cmd.getResult()) {
+      case REJECTED_CURRENT_BRANCH:
+        msg = format("Cannot delete %s: it is the current branch",
+            cmd.getRefName());
+        break;
+      case REJECTED_OTHER_REASON:
+        msg = format("Cannot delete %s: %s", cmd.getRefName(), cmd.getMessage());
+        break;
+      default:
+        msg = format("Cannot delete %s: %s", cmd.getRefName(), cmd.getResult());
+        break;
+    }
+    log.error(msg);
+    errorMessages.append(msg);
+    errorMessages.append("\n");
+  }
+
+  private void postDeletion(ProjectResource project, ReceiveCommand cmd)
+      throws OrmException {
+    referenceUpdated.fire(project.getNameKey(), cmd.getRefName(),
+        cmd.getOldId(), cmd.getNewId());
+    Branch.NameKey branchKey =
+        new Branch.NameKey(project.getNameKey(), cmd.getRefName());
+    hooks.doRefUpdatedHook(branchKey, cmd.getOldId(), cmd.getNewId(),
+        identifiedUser.get().getAccount());
+    ResultSet<SubmoduleSubscription> submoduleSubscriptions =
+        dbProvider.get().submoduleSubscriptions().bySuperProject(branchKey);
+    dbProvider.get().submoduleSubscriptions().delete(submoduleSubscriptions);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
index ace221d..430d8f5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
@@ -64,6 +64,7 @@
     put(BRANCH_KIND).to(PutBranch.class);
     get(BRANCH_KIND).to(GetBranch.class);
     delete(BRANCH_KIND).to(DeleteBranch.class);
+    post(PROJECT_KIND, "branches:delete").to(DeleteBranches.class);
     install(new FactoryModuleBuilder().build(CreateBranch.Factory.class));
     get(BRANCH_KIND, "reflog").to(GetReflog.class);
     child(BRANCH_KIND, "files").to(FilesCollection.class);