Check for open changes on branch deletion

Check for open changes when deleting a branch in the Gerrit WebUI.
Delete a branch only if there are no open changes for this branch.

The motivation for this change is to make the user aware of open
changes when deleting a branch.

Change-Id: Ifa5068dc5f93a48ced69b171465193ee35d5ab3b
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java
index 74a2678..13bba12 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java
@@ -25,6 +25,7 @@
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.safehtml.client.SafeHtml;
 import com.google.gwtexpui.user.client.PluginSafePopupPanel;
 import com.google.gwtjsonrpc.client.RemoteJsonException;
@@ -94,6 +95,11 @@
     body.add(message.toBlockWidget());
   }
 
+  public ErrorDialog(final Widget w) {
+    this();
+    body.add(w);
+  }
+
   /** Create a dialog box to nicely format an exception. */
   public ErrorDialog(final Throwable what) {
     this();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
index 5801c8e..a265c46 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
@@ -104,6 +104,7 @@
   String errorDialogTitle();
   String errorDialogButtons();
   String errorDialogErrorType();
+  String errorDialogText();
   String fileColumnHeader();
   String fileLine();
   String fileLineCONTEXT();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
index c928eb9..ec09c7d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
@@ -91,6 +91,7 @@
   String initialRevision();
   String buttonAddBranch();
   String buttonDeleteBranch();
+  String branchDeletionOpenChanges();
 
   String groupListPrev();
   String groupListNext();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index f2a8ad6..6d0a677 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -71,6 +71,8 @@
 initialRevision = Initial Revision
 buttonAddBranch = Create Branch
 buttonDeleteBranch = Delete
+branchDeletionOpenChanges = The following branches were not deleted \
+because they have open changes:
 
 groupListPrev = Previous group
 groupListNext = Next group
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
index f0d076f..e9f0288 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
@@ -16,9 +16,11 @@
 
 import com.google.gerrit.client.ConfirmationCallback;
 import com.google.gerrit.client.ConfirmationDialog;
+import com.google.gerrit.client.ErrorDialog;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
+import com.google.gerrit.client.ui.BranchLink;
 import com.google.gerrit.client.ui.FancyFlexTable;
 import com.google.gerrit.client.ui.HintTextBox;
 import com.google.gerrit.common.data.GitwebLink;
@@ -26,6 +28,7 @@
 import com.google.gerrit.common.errors.InvalidNameException;
 import com.google.gerrit.common.errors.InvalidRevisionException;
 import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.core.client.Scheduler;
 import com.google.gwt.core.client.Scheduler.ScheduledCommand;
@@ -41,6 +44,7 @@
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.Grid;
 import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.VerticalPanel;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
 import com.google.gwtjsonrpc.client.RemoteJsonException;
 
@@ -260,24 +264,52 @@
               b.toSafeHtml(), new ConfirmationCallback() {
         @Override
         public void onOk() {
-          Util.PROJECT_SVC.deleteBranch(getProjectKey(), ids,
-              new GerritCallback<Set<Branch.NameKey>>() {
-                public void onSuccess(final Set<Branch.NameKey> deleted) {
-                  for (int row = 1; row < table.getRowCount();) {
-                    final Branch k = getRowItem(row);
-                    if (k != null && deleted.contains(k.getNameKey())) {
-                      table.removeRow(row);
-                    } else {
-                      row++;
-                    }
-                  }
-                }
-              });
+          deleteBranches(ids);
         }
       });
       confirmationDialog.center();
     }
 
+    private void deleteBranches(final Set<Branch.NameKey> branchIds) {
+      Util.PROJECT_SVC.deleteBranch(getProjectKey(), branchIds,
+          new GerritCallback<Set<Branch.NameKey>>() {
+            public void onSuccess(final Set<Branch.NameKey> deleted) {
+              if (!deleted.isEmpty()) {
+                for (int row = 1; row < table.getRowCount();) {
+                  final Branch k = getRowItem(row);
+                  if (k != null && deleted.contains(k.getNameKey())) {
+                    table.removeRow(row);
+                  } else {
+                    row++;
+                  }
+                }
+              }
+
+              branchIds.removeAll(deleted);
+              if (!branchIds.isEmpty()) {
+                final VerticalPanel p = new VerticalPanel();
+                final ErrorDialog errorDialog = new ErrorDialog(p);
+                final Label l = new Label(Util.C.branchDeletionOpenChanges());
+                l.setStyleName(Gerrit.RESOURCES.css().errorDialogText());
+                p.add(l);
+                for (final Branch.NameKey branch : branchIds) {
+                  final BranchLink link =
+                      new BranchLink(branch.getParentKey(), Change.Status.NEW,
+                          branch.get(), null) {
+                    @Override
+                    public void go() {
+                      errorDialog.hide();
+                      super.go();
+                    };
+                  };
+                  p.add(link);
+                }
+                errorDialog.center();
+              }
+            }
+          });
+    }
+
     void display(final List<Branch> result) {
       canDelete = false;
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
index 3510820..b287870 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
@@ -375,6 +375,18 @@
   width: 100%;
   margin-top: 15px;
 }
+.errorDialogText {
+  font-size: 15px;
+  font-family: verdana;
+}
+.errorDialog a,
+.errorDialog a:visited,
+.errorDialog a:hover {
+  color: white;
+  font-weight: bold;
+  font-size: 15px;
+  font-family: verdana;
+}
 
 
 /** Screen **/
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java
index 073e2d7..a3a1c25 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java
@@ -18,11 +18,13 @@
 import com.google.gerrit.httpd.rpc.Handler;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.ReplicationQueue;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectControl;
+import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
@@ -34,6 +36,7 @@
 
 import java.io.IOException;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Set;
 
 class DeleteBranches extends Handler<Set<Branch.NameKey>> {
@@ -50,6 +53,7 @@
   private final ReplicationQueue replication;
   private final IdentifiedUser identifiedUser;
   private final ChangeHooks hooks;
+  private final ReviewDb db;
 
   private final Project.NameKey projectName;
   private final Set<Branch.NameKey> toRemove;
@@ -60,6 +64,7 @@
       final ReplicationQueue replication,
       final IdentifiedUser identifiedUser,
       final ChangeHooks hooks,
+      final ReviewDb db,
 
       @Assisted Project.NameKey name, @Assisted Set<Branch.NameKey> toRemove) {
     this.projectControlFactory = projectControlFactory;
@@ -67,6 +72,7 @@
     this.replication = replication;
     this.identifiedUser = identifiedUser;
     this.hooks = hooks;
+    this.db = db;
 
     this.projectName = name;
     this.toRemove = toRemove;
@@ -74,17 +80,23 @@
 
   @Override
   public Set<Branch.NameKey> call() throws NoSuchProjectException,
-      RepositoryNotFoundException {
+      RepositoryNotFoundException, OrmException {
     final ProjectControl projectControl =
         projectControlFactory.controlFor(projectName);
 
-    for (Branch.NameKey k : toRemove) {
+    final Iterator<Branch.NameKey> branchIt = toRemove.iterator();
+    while (branchIt.hasNext()) {
+      final Branch.NameKey k = branchIt.next();
       if (!projectName.equals(k.getParentKey())) {
         throw new IllegalArgumentException("All keys must be from same project");
       }
       if (!projectControl.controlForRef(k).canDelete()) {
         throw new IllegalStateException("Cannot delete " + k.getShortName());
       }
+
+      if (db.changes().byBranchOpenAll(k).iterator().hasNext()) {
+        branchIt.remove();
+      }
     }
 
     final Set<Branch.NameKey> deleted = new HashSet<Branch.NameKey>();