Visualize in which revisions a merged change is included

Introduced a new "Included in" panel on merged changes. The panel
will produce sorted lists all branches and tags which contains the
merged change.

Provides a simple way for users to determine in which revisions of
the project the change is included.

Change-Id: If94b2604607f53a2e45330b56d55cc5de8288054
This commit is contained in:
Goran Lungberg 2010-05-25 08:49:00 +02:00 committed by Shawn O. Pearce
parent 04132a143f
commit 56f76b2bd0
9 changed files with 276 additions and 0 deletions

View File

@ -26,6 +26,8 @@ import com.google.gwtjsonrpc.client.RpcImpl.Version;
public interface ChangeDetailService extends RemoteJsonService {
void changeDetail(Change.Id id, AsyncCallback<ChangeDetail> callback);
void includedInDetail(Change.Id id, AsyncCallback<IncludedInDetail> callback);
void patchSetDetail(PatchSet.Id key, AsyncCallback<PatchSetDetail> callback);
@SignInRequired

View File

@ -0,0 +1,44 @@
// Copyright (C) 2010 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.common.data;
import java.util.Collections;
import java.util.List;
public class IncludedInDetail {
private List<String> branches;
private List<String> tags;
public IncludedInDetail() {
}
public void setBranches(final List<String> b) {
Collections.sort(b);
branches = b;
}
public List<String> getBranches() {
return branches;
}
public void setTags(final List<String> t) {
Collections.sort(t);
tags = t;
}
public List<String> getTags() {
return tags;
}
}

View File

@ -63,6 +63,7 @@ public interface ChangeConstants extends Constants {
String prevPatchLinkIcon();
String nextPatchLinkIcon();
String changeScreenIncludedIn();
String changeScreenDependencies();
String changeScreenDependsOn();
String changeScreenNeededBy();
@ -79,6 +80,9 @@ public interface ChangeConstants extends Constants {
String changeInfoBlockStatus();
String changePermalink();
String includedInTableBranch();
String includedInTableTag();
String messageNoAuthor();
String messageExpandRecent();
String messageExpandAll();

View File

@ -40,6 +40,7 @@ patchTablePrev = Previous file
patchTableNext = Next file
patchTableOpen = Open file
changeScreenIncludedIn = Included in
changeScreenDependencies = Dependencies
changeScreenDependsOn = Depends On
changeScreenNeededBy = Needed By
@ -56,6 +57,9 @@ changeInfoBlockUpdated = Updated
changeInfoBlockStatus = Status
changePermalink = Permalink
includedInTableBranch = Branch Name
includedInTableTag = Tag Name
messageNoAuthor = Gerrit Code Review
messageExpandRecent = Expand Recent
messageExpandAll = Expand All

View File

@ -34,6 +34,7 @@ import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ChangeMessage;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.Change.Status;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
@ -64,6 +65,8 @@ public class ChangeScreen extends Screen {
private ChangeDescriptionBlock descriptionBlock;
private ApprovalTable approvals;
private IncludedInTable includedInTable;
private DisclosurePanel includedInPanel;
private DisclosurePanel dependenciesPanel;
private ChangeTable dependencies;
private ChangeTable.Section dependsOn;
@ -171,6 +174,12 @@ public class ChangeScreen extends Screen {
approvals = new ApprovalTable();
add(approvals);
includedInPanel = new DisclosurePanel(Util.C.changeScreenIncludedIn());
includedInTable = new IncludedInTable(changeId);
includedInPanel.setContent(includedInTable);
add(includedInPanel);
dependencies = new ChangeTable() {
{
table.setWidth("98%");
@ -219,6 +228,13 @@ public class ChangeScreen extends Screen {
setStarred(detail.isStarred());
}
if (Status.MERGED == detail.getChange().getStatus()) {
includedInPanel.setVisible(true);
includedInPanel.addOpenHandler(includedInTable);
} else {
includedInPanel.setVisible(false);
}
dependencies.setAccountInfoCache(detail.getAccounts());
approvals.setAccountInfoCache(detail.getAccounts());

View File

@ -0,0 +1,85 @@
// Copyright (C) 2010 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.client.changes;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.common.data.IncludedInDetail;
import com.google.gerrit.reviewdb.Change;
import com.google.gwt.event.logical.shared.OpenEvent;
import com.google.gwt.event.logical.shared.OpenHandler;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DisclosurePanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
/** Displays a table of Branches and Tags containing the change record. */
public class IncludedInTable extends Composite implements
OpenHandler<DisclosurePanel> {
private final Grid table;
private final Change.Id changeId;
private boolean loaded = false;
public IncludedInTable(final Change.Id chId) {
changeId = chId;
table = new Grid(1, 1);
initWidget(table);
}
public void loadTable(final IncludedInDetail detail) {
int row = 0;
table.resizeRows(detail.getBranches().size() + 1);
table.addStyleName(Gerrit.RESOURCES.css().changeTable());
final CellFormatter fmt = table.getCellFormatter();
fmt.addStyleName(row, 0, Gerrit.RESOURCES.css().dataHeader());
table.setText(row, 0, Util.C.includedInTableBranch());
for (final String branch : detail.getBranches()) {
fmt.addStyleName(++row, 0, Gerrit.RESOURCES.css().dataCell());
fmt.addStyleName(row, 0, Gerrit.RESOURCES.css().leftMostCell());
table.setText(row, 0, branch);
}
if (!detail.getTags().isEmpty()) {
table.resizeRows(table.getRowCount() + 2 + detail.getTags().size());
row++;
fmt.addStyleName(++row, 0, Gerrit.RESOURCES.css().dataHeader());
table.setText(row, 0, Util.C.includedInTableTag());
for (final String tag : detail.getTags()) {
fmt.addStyleName(++row, 0, Gerrit.RESOURCES.css().dataCell());
fmt.addStyleName(row, 0, Gerrit.RESOURCES.css().leftMostCell());
table.setText(row, 0, tag);
}
}
table.setVisible(true);
loaded = true;
}
@Override
public void onOpen(OpenEvent<DisclosurePanel> event) {
if (!loaded) {
Util.DETAIL_SVC.includedInDetail(changeId,
new GerritCallback<IncludedInDetail>() {
@Override
public void onSuccess(final IncludedInDetail result) {
loadTable(result);
}
});
}
}
}

View File

@ -16,6 +16,7 @@ package com.google.gerrit.httpd.rpc.changedetail;
import com.google.gerrit.common.data.ChangeDetail;
import com.google.gerrit.common.data.ChangeDetailService;
import com.google.gerrit.common.data.IncludedInDetail;
import com.google.gerrit.common.data.PatchSetDetail;
import com.google.gerrit.common.data.PatchSetPublishDetail;
import com.google.gerrit.reviewdb.Change;
@ -25,14 +26,17 @@ import com.google.inject.Inject;
class ChangeDetailServiceImpl implements ChangeDetailService {
private final ChangeDetailFactory.Factory changeDetail;
private final IncludedInDetailFactory.Factory includedInDetail;
private final PatchSetDetailFactory.Factory patchSetDetail;
private final PatchSetPublishDetailFactory.Factory patchSetPublishDetail;
@Inject
ChangeDetailServiceImpl(final ChangeDetailFactory.Factory changeDetail,
final IncludedInDetailFactory.Factory includedInDetail,
final PatchSetDetailFactory.Factory patchSetDetail,
final PatchSetPublishDetailFactory.Factory patchSetPublishDetail) {
this.changeDetail = changeDetail;
this.includedInDetail = includedInDetail;
this.patchSetDetail = patchSetDetail;
this.patchSetPublishDetail = patchSetPublishDetail;
}
@ -42,6 +46,11 @@ class ChangeDetailServiceImpl implements ChangeDetailService {
changeDetail.create(id).to(callback);
}
public void includedInDetail(final Change.Id id,
final AsyncCallback<IncludedInDetail> callback) {
includedInDetail.create(id).to(callback);
}
public void patchSetDetail(final PatchSet.Id id,
final AsyncCallback<PatchSetDetail> callback) {
patchSetDetail.create(id).to(callback);

View File

@ -30,6 +30,7 @@ public class ChangeModule extends RpcServletModule {
protected void configure() {
factory(AbandonChange.Factory.class);
factory(ChangeDetailFactory.Factory.class);
factory(IncludedInDetailFactory.Factory.class);
factory(PatchSetDetailFactory.Factory.class);
factory(PatchSetPublishDetailFactory.Factory.class);
factory(SubmitAction.Factory.class);

View File

@ -0,0 +1,111 @@
// Copyright (C) 2010 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.httpd.rpc.changedetail;
import com.google.gerrit.common.data.IncludedInDetail;
import com.google.gerrit.common.errors.InvalidRevisionException;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/** Creates a {@link IncludedInDetail} of a {@link Change}. */
class IncludedInDetailFactory extends Handler<IncludedInDetail> {
interface Factory {
IncludedInDetailFactory create(Change.Id id);
}
private final ReviewDb db;
private final ChangeControl.Factory changeControlFactory;
private final GitRepositoryManager repoManager;
private final Change.Id changeId;
private IncludedInDetail detail;
private ChangeControl control;
@Inject
IncludedInDetailFactory(final ReviewDb db,
final ChangeControl.Factory changeControlFactory,
final GitRepositoryManager repoManager, @Assisted final Change.Id changeId) {
this.changeControlFactory = changeControlFactory;
this.repoManager = repoManager;
this.changeId = changeId;
this.db = db;
}
@Override
public IncludedInDetail call() throws OrmException, NoSuchChangeException,
NoSuchEntityException, IOException, InvalidRevisionException {
control = changeControlFactory.validateFor(changeId);
final PatchSet patch =
db.patchSets().get(control.getChange().currentPatchSetId());
final Repository repo =
repoManager.openRepository(control.getProject().getName());
final Map<String, Ref> refsHeads =
repo.getRefDatabase().getRefs(Constants.R_HEADS);
final Map<String, Ref> refsTags =
repo.getRefDatabase().getRefs(Constants.R_TAGS);
RevWalk rw = new RevWalk(repo);
try {
final RevCommit rev =
rw.parseCommit(ObjectId.fromString(patch.getRevision().get()));
final List<String> branches = new ArrayList<String>();
for (final String branch : refsHeads.keySet()) {
if (rw.isMergedInto(rev, rw.parseCommit(refsHeads.get(branch)
.getObjectId()))) {
branches.add(branch);
}
}
final List<String> tags = new ArrayList<String>();
for (final String tag : refsTags.keySet()) {
if (rw.isMergedInto(rev, rw
.parseCommit(refsTags.get(tag).getObjectId()))) {
tags.add(tag);
}
}
detail = new IncludedInDetail();
detail.setBranches(branches);
detail.setTags(tags);
return detail;
} catch (IncorrectObjectTypeException err) {
throw new InvalidRevisionException();
} catch (MissingObjectException err) {
throw new InvalidRevisionException();
}
}
}