summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn O. Pearce <sop@google.com>2009-02-10 20:07:56 -0800
committerShawn O. Pearce <sop@google.com>2009-02-10 20:07:56 -0800
commit3a8874b34ec2a722b01853846d166927c1a774c9 (patch)
tree45d533fa32dad0419404f4c67b0517831a6a91ee
parent99bab581a0d1ebf411e792dbac0a2c32c328c4be (diff)
Add a Branches tab to the project admin screen
This tab allows project owners to view branches, delete branches, and create new branches from any Git commit SHA-1 expression we can recognize in JGit (which is most of the expressions, except reflog queries). Bug: GERRIT-20 Signed-off-by: Shawn O. Pearce <sop@google.com>
-rw-r--r--src/main/java/com/google/gerrit/client/admin/AdminConstants.java8
-rw-r--r--src/main/java/com/google/gerrit/client/admin/AdminConstants.properties8
-rw-r--r--src/main/java/com/google/gerrit/client/admin/ProjectAdminScreen.java12
-rw-r--r--src/main/java/com/google/gerrit/client/admin/ProjectAdminService.java12
-rw-r--r--src/main/java/com/google/gerrit/client/admin/ProjectBranchesPanel.java316
-rw-r--r--src/main/java/com/google/gerrit/client/rpc/InvalidNameException.java24
-rw-r--r--src/main/java/com/google/gerrit/client/rpc/InvalidRevisionException.java24
-rw-r--r--src/main/java/com/google/gerrit/server/ProjectAdminServiceImpl.java198
8 files changed, 602 insertions, 0 deletions
diff --git a/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
index 03d0197d48..41c8df4c3f 100644
--- a/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
+++ b/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
@@ -19,6 +19,8 @@ import com.google.gwt.i18n.client.Constants;
public interface AdminConstants extends Constants {
String defaultAccountName();
String defaultAccountGroupName();
+ String defaultBranchName();
+ String defaultRevisionSpec();
String buttonDeleteGroupMembers();
String buttonAddGroupMember();
@@ -43,8 +45,14 @@ public interface AdminConstants extends Constants {
String columnApprovalCategory();
String columnRightRange();
+ String columnBranchName();
+ String initialRevision();
+ String buttonAddBranch();
+ String buttonDeleteBranch();
+
String groupListTitle();
String projectListTitle();
String projectAdminTabGeneral();
+ String projectAdminTabBranches();
String projectAdminTabAccess();
}
diff --git a/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index dca7ce07b1..94bff9eb3c 100644
--- a/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -1,5 +1,7 @@
defaultAccountName = Name or Email
defaultAccountGroupName = Group Name
+defaultBranchName = Branch Name
+defaultRevisionSpec = Revision (Branch or SHA-1)
buttonDeleteGroupMembers = Delete
buttonAddGroupMember = Add
@@ -24,7 +26,13 @@ columnProjectDescription = Description
columnApprovalCategory = Category
columnRightRange = Permitted Range
+columnBranchName = Branch Name
+initialRevision = Initial Revision
+buttonAddBranch = Create Branch
+buttonDeleteBranch = Delete
+
groupListTitle = Groups
projectListTitle = Projects
projectAdminTabGeneral = General
+projectAdminTabBranches = Branches
projectAdminTabAccess = Access
diff --git a/src/main/java/com/google/gerrit/client/admin/ProjectAdminScreen.java b/src/main/java/com/google/gerrit/client/admin/ProjectAdminScreen.java
index d6509ffc46..f6d40563c3 100644
--- a/src/main/java/com/google/gerrit/client/admin/ProjectAdminScreen.java
+++ b/src/main/java/com/google/gerrit/client/admin/ProjectAdminScreen.java
@@ -17,6 +17,7 @@ package com.google.gerrit.client.admin;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.Link;
import com.google.gerrit.client.reviewdb.Project;
+import com.google.gerrit.client.reviewdb.ProjectRight;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.AccountScreen;
import com.google.gerrit.client.ui.LazyTabChild;
@@ -29,6 +30,7 @@ import java.util.List;
public class ProjectAdminScreen extends AccountScreen {
static final String INFO_TAB = "info";
+ static final String BRANCH_TAB = "branches";
static final String ACCESS_TAB = "access";
private String initialTabToken;
@@ -73,6 +75,16 @@ public class ProjectAdminScreen extends AccountScreen {
}, Util.C.projectAdminTabGeneral());
tabTokens.add(Link.toProjectAdmin(projectId, INFO_TAB));
+ if (!ProjectRight.WILD_PROJECT.equals(projectId)) {
+ tabs.add(new LazyTabChild<ProjectBranchesPanel>() {
+ @Override
+ protected ProjectBranchesPanel create() {
+ return new ProjectBranchesPanel(projectId);
+ }
+ }, Util.C.projectAdminTabBranches());
+ tabTokens.add(Link.toProjectAdmin(projectId, BRANCH_TAB));
+ }
+
tabs.add(new LazyTabChild<ProjectRightsPanel>() {
@Override
protected ProjectRightsPanel create() {
diff --git a/src/main/java/com/google/gerrit/client/admin/ProjectAdminService.java b/src/main/java/com/google/gerrit/client/admin/ProjectAdminService.java
index 85308622b4..21da108bbf 100644
--- a/src/main/java/com/google/gerrit/client/admin/ProjectAdminService.java
+++ b/src/main/java/com/google/gerrit/client/admin/ProjectAdminService.java
@@ -15,6 +15,7 @@
package com.google.gerrit.client.admin;
import com.google.gerrit.client.reviewdb.ApprovalCategory;
+import com.google.gerrit.client.reviewdb.Branch;
import com.google.gerrit.client.reviewdb.Project;
import com.google.gerrit.client.reviewdb.ProjectRight;
import com.google.gerrit.client.rpc.SignInRequired;
@@ -47,4 +48,15 @@ public interface ProjectAdminService extends RemoteJsonService {
void addRight(Project.Id projectId, ApprovalCategory.Id categoryId,
String groupName, short min, short max,
AsyncCallback<ProjectDetail> callback);
+
+ @SignInRequired
+ void listBranches(Project.Id project, AsyncCallback<List<Branch>> callback);
+
+ @SignInRequired
+ void addBranch(Project.Id project, String branchName,
+ String startingRevision, AsyncCallback<List<Branch>> callback);
+
+ @SignInRequired
+ void deleteBranch(Set<Branch.NameKey> ids,
+ AsyncCallback<Set<Branch.NameKey>> callback);
}
diff --git a/src/main/java/com/google/gerrit/client/admin/ProjectBranchesPanel.java b/src/main/java/com/google/gerrit/client/admin/ProjectBranchesPanel.java
new file mode 100644
index 0000000000..0253fa88ee
--- /dev/null
+++ b/src/main/java/com/google/gerrit/client/admin/ProjectBranchesPanel.java
@@ -0,0 +1,316 @@
+// Copyright 2009 Google Inc.
+//
+// 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.client.admin;
+
+import com.google.gerrit.client.data.GitwebLink;
+import com.google.gerrit.client.reviewdb.Branch;
+import com.google.gerrit.client.reviewdb.Project;
+import com.google.gerrit.client.rpc.Common;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.InvalidNameException;
+import com.google.gerrit.client.rpc.InvalidRevisionException;
+import com.google.gerrit.client.ui.FancyFlexTable;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.ClickListener;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.FocusListenerAdapter;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.SourcesTableEvents;
+import com.google.gwt.user.client.ui.TableListener;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
+import com.google.gwtjsonrpc.client.RemoteJsonException;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class ProjectBranchesPanel extends Composite {
+ private Project.Id projectId;
+
+ private BranchesTable branches;
+ private Button delBranch;
+ private Button addBranch;
+ private TextBox nameTxtBox;
+ private TextBox irevTxtBox;
+
+ public ProjectBranchesPanel(final Project.Id toShow) {
+ final FlowPanel body = new FlowPanel();
+ initBranches(body);
+ initWidget(body);
+
+ projectId = toShow;
+ }
+
+ @Override
+ public void onLoad() {
+ enableForm(false);
+ super.onLoad();
+
+ Util.PROJECT_SVC.listBranches(projectId,
+ new GerritCallback<List<Branch>>() {
+ public void onSuccess(final List<Branch> result) {
+ enableForm(true);
+ branches.display(result);
+ branches.finishDisplay(true);
+ }
+ });
+ }
+
+ private void enableForm(final boolean on) {
+ delBranch.setEnabled(on);
+ addBranch.setEnabled(on);
+ nameTxtBox.setEnabled(on);
+ irevTxtBox.setEnabled(on);
+ }
+
+ private void initBranches(final Panel body) {
+ final FlowPanel addPanel = new FlowPanel();
+ addPanel.setStyleName("gerrit-AddSshKeyPanel");
+
+ final Grid addGrid = new Grid(2, 2);
+
+ nameTxtBox = new TextBox();
+ nameTxtBox.setVisibleLength(50);
+ nameTxtBox.setText(Util.C.defaultBranchName());
+ nameTxtBox.addStyleName("gerrit-InputFieldTypeHint");
+ nameTxtBox.addFocusListener(new FocusListenerAdapter() {
+ @Override
+ public void onFocus(Widget sender) {
+ if (Util.C.defaultBranchName().equals(nameTxtBox.getText())) {
+ nameTxtBox.setText("");
+ nameTxtBox.removeStyleName("gerrit-InputFieldTypeHint");
+ }
+ }
+
+ @Override
+ public void onLostFocus(Widget sender) {
+ if ("".equals(nameTxtBox.getText())) {
+ nameTxtBox.setText(Util.C.defaultBranchName());
+ nameTxtBox.addStyleName("gerrit-InputFieldTypeHint");
+ }
+ }
+ });
+ addGrid.setText(0, 0, Util.C.columnBranchName() + ":");
+ addGrid.setWidget(0, 1, nameTxtBox);
+
+ irevTxtBox = new TextBox();
+ irevTxtBox.setVisibleLength(50);
+ irevTxtBox.setText(Util.C.defaultRevisionSpec());
+ irevTxtBox.addStyleName("gerrit-InputFieldTypeHint");
+ irevTxtBox.addFocusListener(new FocusListenerAdapter() {
+ @Override
+ public void onFocus(Widget sender) {
+ if (Util.C.defaultRevisionSpec().equals(irevTxtBox.getText())) {
+ irevTxtBox.setText("");
+ irevTxtBox.removeStyleName("gerrit-InputFieldTypeHint");
+ }
+ }
+
+ @Override
+ public void onLostFocus(Widget sender) {
+ if ("".equals(irevTxtBox.getText())) {
+ irevTxtBox.setText(Util.C.defaultRevisionSpec());
+ irevTxtBox.addStyleName("gerrit-InputFieldTypeHint");
+ }
+ }
+ });
+ addGrid.setText(1, 0, Util.C.initialRevision() + ":");
+ addGrid.setWidget(1, 1, irevTxtBox);
+
+ addBranch = new Button(Util.C.buttonAddBranch());
+ addBranch.addClickListener(new ClickListener() {
+ public void onClick(final Widget sender) {
+ doAddNewBranch();
+ }
+ });
+ addPanel.add(addGrid);
+ addPanel.add(addBranch);
+
+ branches = new BranchesTable();
+
+ delBranch = new Button(Util.C.buttonDeleteBranch());
+ delBranch.addClickListener(new ClickListener() {
+ public void onClick(final Widget sender) {
+ branches.deleteChecked();
+ }
+ });
+
+ body.add(branches);
+ body.add(delBranch);
+ body.add(addPanel);
+ }
+
+ private void doAddNewBranch() {
+ String branchName = nameTxtBox.getText();
+ if ("".equals(branchName) || Util.C.defaultBranchName().equals(branchName)) {
+ return;
+ }
+
+ String rev = irevTxtBox.getText();
+ if ("".equals(rev) || Util.C.defaultRevisionSpec().equals(rev)) {
+ return;
+ }
+
+ if (!branchName.startsWith(Branch.R_REFS)) {
+ branchName = Branch.R_HEADS + branchName;
+ }
+
+ addBranch.setEnabled(false);
+ Util.PROJECT_SVC.addBranch(projectId, branchName, rev,
+ new GerritCallback<List<Branch>>() {
+ public void onSuccess(final List<Branch> result) {
+ addBranch.setEnabled(true);
+ nameTxtBox.setText("");
+ irevTxtBox.setText("");
+ branches.display(result);
+ }
+
+ @Override
+ public void onFailure(final Throwable caught) {
+ if (caught instanceof InvalidNameException
+ || caught instanceof RemoteJsonException
+ && caught.getMessage().equals(InvalidNameException.MESSAGE)) {
+ nameTxtBox.selectAll();
+ nameTxtBox.setFocus(true);
+
+ } else if (caught instanceof InvalidRevisionException
+ || caught instanceof RemoteJsonException
+ && caught.getMessage().equals(InvalidRevisionException.MESSAGE)) {
+ irevTxtBox.selectAll();
+ irevTxtBox.setFocus(true);
+ }
+
+ addBranch.setEnabled(true);
+ super.onFailure(caught);
+ }
+ });
+ }
+
+ private class BranchesTable extends FancyFlexTable<Branch> {
+ BranchesTable() {
+ table.setText(0, 2, Util.C.columnBranchName());
+ table.setHTML(0, 3, "&nbsp;");
+ table.addTableListener(new TableListener() {
+ public void onCellClicked(SourcesTableEvents sender, int row, int cell) {
+ if (cell != 1 && getRowItem(row) != null) {
+ movePointerTo(row);
+ }
+ }
+ });
+
+ final FlexCellFormatter fmt = table.getFlexCellFormatter();
+ fmt.addStyleName(0, 1, S_ICON_HEADER);
+ fmt.addStyleName(0, 2, S_DATA_HEADER);
+ fmt.addStyleName(0, 3, S_DATA_HEADER);
+ }
+
+ @Override
+ protected Object getRowItemKey(final Branch item) {
+ return item.getId();
+ }
+
+ @Override
+ protected boolean onKeyPress(final char keyCode, final int modifiers) {
+ if (super.onKeyPress(keyCode, modifiers)) {
+ return true;
+ }
+ if (modifiers == 0) {
+ switch (keyCode) {
+ case 's':
+ case 'c':
+ toggleCurrentRow();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected void onOpenItem(final Branch item) {
+ toggleCurrentRow();
+ }
+
+ private void toggleCurrentRow() {
+ final CheckBox cb = (CheckBox) table.getWidget(getCurrentRow(), 1);
+ cb.setChecked(!cb.isChecked());
+ }
+
+ void deleteChecked() {
+ final HashSet<Branch.NameKey> ids = new HashSet<Branch.NameKey>();
+ for (int row = 1; row < table.getRowCount(); row++) {
+ final Branch k = getRowItem(row);
+ if (k != null && table.getWidget(row, 1) instanceof CheckBox
+ && ((CheckBox) table.getWidget(row, 1)).isChecked()) {
+ ids.add(k.getNameKey());
+ }
+ }
+ if (ids.isEmpty()) {
+ return;
+ }
+
+ Util.PROJECT_SVC.deleteBranch(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++;
+ }
+ }
+ }
+ });
+ }
+
+ void display(final List<Branch> result) {
+ while (1 < table.getRowCount())
+ table.removeRow(table.getRowCount() - 1);
+
+ for (final Branch k : result) {
+ final int row = table.getRowCount();
+ table.insertRow(row);
+ applyDataRowStyle(row);
+ populate(row, k);
+ }
+ }
+
+ void populate(final int row, final Branch k) {
+ final GitwebLink c = Common.getGerritConfig().getGitwebLink();
+
+ table.setWidget(row, 1, new CheckBox());
+ table.setText(row, 2, k.getShortName());
+ if (c != null) {
+ table.setWidget(row, 3, new Anchor("(gitweb)", false, c.toBranch(k
+ .getNameKey())));
+ } else {
+ table.setHTML(row, 3, "&nbsp;");
+ }
+
+ final FlexCellFormatter fmt = table.getFlexCellFormatter();
+ fmt.addStyleName(row, 1, S_ICON_CELL);
+ fmt.addStyleName(row, 2, S_DATA_CELL);
+ fmt.addStyleName(row, 3, S_DATA_CELL);
+
+ setRowItem(row, k);
+ }
+ }
+}
diff --git a/src/main/java/com/google/gerrit/client/rpc/InvalidNameException.java b/src/main/java/com/google/gerrit/client/rpc/InvalidNameException.java
new file mode 100644
index 0000000000..c32eeebd2d
--- /dev/null
+++ b/src/main/java/com/google/gerrit/client/rpc/InvalidNameException.java
@@ -0,0 +1,24 @@
+// Copyright 2009 Google Inc.
+//
+// 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.client.rpc;
+
+/** Error indicating the entity name is invalid as supplied. */
+public class InvalidNameException extends Exception {
+ public static final String MESSAGE = "Invalid Name";
+
+ public InvalidNameException() {
+ super(MESSAGE);
+ }
+}
diff --git a/src/main/java/com/google/gerrit/client/rpc/InvalidRevisionException.java b/src/main/java/com/google/gerrit/client/rpc/InvalidRevisionException.java
new file mode 100644
index 0000000000..801c6d24fc
--- /dev/null
+++ b/src/main/java/com/google/gerrit/client/rpc/InvalidRevisionException.java
@@ -0,0 +1,24 @@
+// Copyright 2009 Google Inc.
+//
+// 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.client.rpc;
+
+/** Error indicating the revision is invalid as supplied. */
+public class InvalidRevisionException extends Exception {
+ public static final String MESSAGE = "Invalid Revision";
+
+ public InvalidRevisionException() {
+ super(MESSAGE);
+ }
+}
diff --git a/src/main/java/com/google/gerrit/server/ProjectAdminServiceImpl.java b/src/main/java/com/google/gerrit/server/ProjectAdminServiceImpl.java
index 7b9adf90fa..356f1a7990 100644
--- a/src/main/java/com/google/gerrit/server/ProjectAdminServiceImpl.java
+++ b/src/main/java/com/google/gerrit/server/ProjectAdminServiceImpl.java
@@ -21,11 +21,14 @@ import com.google.gerrit.client.reviewdb.Account;
import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.client.reviewdb.ApprovalCategory;
import com.google.gerrit.client.reviewdb.ApprovalCategoryValue;
+import com.google.gerrit.client.reviewdb.Branch;
import com.google.gerrit.client.reviewdb.Project;
import com.google.gerrit.client.reviewdb.ProjectRight;
import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gerrit.client.rpc.BaseServiceImplementation;
import com.google.gerrit.client.rpc.Common;
+import com.google.gerrit.client.rpc.InvalidNameException;
+import com.google.gerrit.client.rpc.InvalidRevisionException;
import com.google.gerrit.client.rpc.NoSuchEntityException;
import com.google.gerrit.git.InvalidRepositoryException;
import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -34,9 +37,16 @@ import com.google.gwtorm.client.OrmException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.spearce.jgit.errors.IncorrectObjectTypeException;
+import org.spearce.jgit.errors.MissingObjectException;
import org.spearce.jgit.lib.Constants;
import org.spearce.jgit.lib.LockFile;
+import org.spearce.jgit.lib.ObjectId;
+import org.spearce.jgit.lib.Ref;
+import org.spearce.jgit.lib.RefUpdate;
import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.revwalk.ObjectWalk;
+import org.spearce.jgit.revwalk.RevCommit;
import java.io.File;
import java.io.IOException;
@@ -250,6 +260,194 @@ public class ProjectAdminServiceImpl extends BaseServiceImplementation
});
}
+ public void listBranches(final Project.Id project,
+ final AsyncCallback<List<Branch>> callback) {
+ run(callback, new Action<List<Branch>>() {
+ public List<Branch> run(ReviewDb db) throws OrmException, Failure {
+ final ProjectCache.Entry e = Common.getProjectCache().get(project);
+ if (e == null) {
+ throw new Failure(new NoSuchEntityException());
+ }
+ assertCanRead(e.getProject().getNameKey());
+ return db.branches().byProject(e.getProject().getNameKey()).toList();
+ }
+ });
+ }
+
+ public void deleteBranch(final Set<Branch.NameKey> ids,
+ final AsyncCallback<Set<Branch.NameKey>> callback) {
+ run(callback, new Action<Set<Branch.NameKey>>() {
+ public Set<Branch.NameKey> run(ReviewDb db) throws OrmException, Failure {
+ final Set<Branch.NameKey> deleted = new HashSet<Branch.NameKey>();
+ final Set<Project.Id> owned = ids(myOwnedProjects(db));
+ Boolean amAdmin = null;
+ for (final Branch.NameKey k : ids) {
+ final ProjectCache.Entry e;
+
+ e = Common.getProjectCache().get(k.getParentKey());
+ if (e == null) {
+ throw new Failure(new NoSuchEntityException());
+ }
+ if (!owned.contains(e.getProject().getId())) {
+ if (amAdmin == null) {
+ amAdmin =
+ Common.getGroupCache().isAdministrator(Common.getAccountId());
+ }
+ if (!amAdmin) {
+ throw new Failure(new NoSuchEntityException());
+ }
+ }
+ }
+ for (final Branch.NameKey k : ids) {
+ final Branch m = db.branches().get(k);
+ if (m == null) {
+ continue;
+ }
+ final Repository r;
+
+ try {
+ r = server.getRepositoryCache().get(k.getParentKey().get());
+ } catch (InvalidRepositoryException e) {
+ throw new Failure(new NoSuchEntityException());
+ }
+
+ final RefUpdate.Result result;
+ try {
+ final RefUpdate u = r.updateRef(m.getName());
+ u.setForceUpdate(true);
+ result = u.delete();
+ } catch (IOException e) {
+ log.error("Cannot delete " + k, e);
+ continue;
+ }
+
+ switch (result) {
+ case NEW:
+ case NO_CHANGE:
+ case FAST_FORWARD:
+ case FORCED:
+ db.branches().delete(Collections.singleton(m));
+ deleted.add(m.getNameKey());
+ break;
+
+ case REJECTED_CURRENT_BRANCH:
+ log.warn("Cannot delete " + k + ": " + result.name());
+ break;
+
+ default:
+ log.error("Cannot delete " + k + ": " + result.name());
+ break;
+ }
+ }
+ return deleted;
+ }
+ });
+ }
+
+ public void addBranch(final Project.Id projectId, final String branchName,
+ final String startingRevision, final AsyncCallback<List<Branch>> callback) {
+ run(callback, new Action<List<Branch>>() {
+ public List<Branch> run(ReviewDb db) throws OrmException, Failure {
+ String refname = branchName;
+ if (!refname.startsWith(Constants.R_REFS)) {
+ refname = Constants.R_HEADS + refname;
+ }
+ if (!Repository.isValidRefName(refname)) {
+ throw new Failure(new InvalidNameException());
+ }
+
+ final Account me = Common.getAccountCache().get(Common.getAccountId());
+ if (me == null) {
+ throw new Failure(new NoSuchEntityException());
+ }
+ final ProjectCache.Entry pce = Common.getProjectCache().get(projectId);
+ if (pce == null) {
+ throw new Failure(new NoSuchEntityException());
+ }
+ assertAmProjectOwner(db, projectId);
+
+ final String repoName = pce.getProject().getName();
+ final Repository repo;
+ try {
+ repo = server.getRepositoryCache().get(repoName);
+ } catch (InvalidRepositoryException e1) {
+ throw new Failure(new NoSuchEntityException());
+ }
+
+ // Convert the name given by the user into a valid object.
+ //
+ final ObjectId revid;
+ try {
+ revid = repo.resolve(startingRevision);
+ if (revid == null) {
+ throw new Failure(new InvalidRevisionException());
+ }
+ } catch (IOException err) {
+ log.error("Cannot resolve \"" + startingRevision + "\" in "
+ + repoName, err);
+ throw new Failure(new InvalidRevisionException());
+ }
+
+ // Ensure it is fully connected in this repository. If not,
+ // we can't safely create a ref to it as objects are missing
+ //
+ final RevCommit revcommit;
+ final ObjectWalk rw = new ObjectWalk(repo);
+ try {
+ try {
+ revcommit = rw.parseCommit(revid);
+ rw.markStart(revcommit);
+ } catch (IncorrectObjectTypeException err) {
+ throw new Failure(new InvalidRevisionException());
+ }
+ for (final Ref r : repo.getAllRefs().values()) {
+ try {
+ rw.markUninteresting(rw.parseAny(r.getObjectId()));
+ } catch (MissingObjectException err) {
+ continue;
+ }
+ }
+ rw.checkConnectivity();
+ } catch (IncorrectObjectTypeException err) {
+ throw new Failure(new InvalidRevisionException());
+ } catch (MissingObjectException err) {
+ throw new Failure(new InvalidRevisionException());
+ } catch (IOException err) {
+ log.error("Repository " + repoName + " possibly corrupt", err);
+ throw new Failure(new InvalidRevisionException());
+ }
+
+ final Branch.NameKey name =
+ new Branch.NameKey(pce.getProject().getNameKey(), refname);
+ try {
+ final RefUpdate u = repo.updateRef(refname);
+ u.setExpectedOldObjectId(ObjectId.zeroId());
+ u.setNewObjectId(revid);
+ u.setRefLogIdent(ChangeUtil.toPersonIdent(me));
+ u.setRefLogMessage("created via web", true);
+ final RefUpdate.Result result = u.update(rw);
+ switch (result) {
+ case FAST_FORWARD:
+ case NEW:
+ case NO_CHANGE:
+ break;
+ default:
+ log.error("Cannot create branch " + name + ": " + result.name());
+ throw new Failure(new IOException(result.name()));
+ }
+ } catch (IOException err) {
+ log.error("Cannot create branch " + name, err);
+ throw new Failure(err);
+ }
+
+ final Branch.Id id = new Branch.Id(db.nextBranchId());
+ final Branch newBranch = new Branch(name, id);
+ db.branches().insert(Collections.singleton(newBranch));
+ return db.branches().byProject(pce.getProject().getNameKey()).toList();
+ }
+ });
+ }
+
private void assertAmProjectOwner(final ReviewDb db,
final Project.Id projectId) throws Failure {
final ProjectCache.Entry p = Common.getProjectCache().get(projectId);