diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java index c4c56d80e4..9aaec7f120 100644 --- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java +++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java @@ -41,6 +41,7 @@ public class ChangeDetail { protected List messages; protected PatchSet.Id currentPatchSetId; protected PatchSetDetail currentDetail; + protected boolean canEdit; public ChangeDetail() { } @@ -190,4 +191,12 @@ public class ChangeDetail { public String getDescription() { return currentDetail != null ? currentDetail.getInfo().getMessage() : ""; } + + public void setCanEdit(boolean a) { + canEdit = a; + } + + public boolean canEdit() { + return canEdit; + } } diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectDetail.java index 02aaf808a7..b6e4fad612 100644 --- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectDetail.java +++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectDetail.java @@ -22,6 +22,7 @@ public class ProjectDetail { public boolean canModifyMergeType; public boolean canModifyAgreements; public boolean canModifyAccess; + public boolean canModifyState; public ProjectDetail() { } @@ -38,6 +39,10 @@ public class ProjectDetail { canModifyMergeType = cmmt; } + public void setCanModifyState(final boolean cms) { + canModifyState = cms; + } + public void setCanModifyAgreements(final boolean cma) { canModifyAgreements = cma; } 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 adad511f77..e3b7ca4649 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 @@ -63,6 +63,10 @@ public interface AdminConstants extends Constants { String projectSubmitType_MERGE_IF_NECESSARY(); String projectSubmitType_CHERRY_PICK(); + String projectState_ACTIVE(); + String projectState_READ_ONLY(); + String projectState_HIDDEN(); + String groupType_SYSTEM(); String groupType_INTERNAL(); String groupType_LDAP(); 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 f782a6327d..d52fa4188d 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 @@ -43,6 +43,10 @@ projectSubmitType_MERGE_IF_NECESSARY = Merge If Necessary projectSubmitType_MERGE_ALWAYS = Always Merge projectSubmitType_CHERRY_PICK = Cherry Pick +projectState_ACTIVE = Active +projectState_READ_ONLY = Read Only +projectState_HIDDEN = Hidden + groupType_SYSTEM = System Group groupType_INTERNAL = Internal Group groupType_LDAP = LDAP Group diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java index fb35855c6f..a85fe0ec2f 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java @@ -39,6 +39,7 @@ public class ProjectInfoScreen extends ProjectScreen { private Panel projectOptionsPanel; private CheckBox requireChangeID; private ListBox submitType; + private ListBox state; private CheckBox useContentMerge; private Panel agreementsPanel; @@ -79,19 +80,22 @@ public class ProjectInfoScreen extends ProjectScreen { new ScreenLoadCallback(this) { public void preDisplay(final ProjectDetail result) { enableForm(result.canModifyAgreements, - result.canModifyDescription, result.canModifyMergeType); + result.canModifyDescription, result.canModifyMergeType, result.canModifyState); saveProject.setVisible( result.canModifyAgreements || result.canModifyDescription || - result.canModifyMergeType); + result.canModifyMergeType || + result.canModifyState); display(result); } }); } private void enableForm(final boolean canModifyAgreements, - final boolean canModifyDescription, final boolean canModifyMergeType) { + final boolean canModifyDescription, final boolean canModifyMergeType, + final boolean canModifyState) { submitType.setEnabled(canModifyMergeType); + state.setEnabled(canModifyState); useContentMerge.setEnabled(canModifyMergeType); descTxt.setEnabled(canModifyDescription); useContributorAgreements.setEnabled(canModifyAgreements); @@ -130,6 +134,14 @@ public class ProjectInfoScreen extends ProjectScreen { saveEnabler.listenTo(submitType); projectOptionsPanel.add(submitType); + state = new ListBox(); + for (final Project.State stateValue : Project.State.values()) { + state.addItem(Util.toLongString(stateValue), stateValue.name()); + } + + saveEnabler.listenTo(state); + projectOptionsPanel.add(state); + useContentMerge = new CheckBox(Util.C.useContentMerge(), true); saveEnabler.listenTo(useContentMerge); projectOptionsPanel.add(useContentMerge); @@ -186,6 +198,17 @@ public class ProjectInfoScreen extends ProjectScreen { } } + private void setState(final Project.State newState) { + if (state != null) { + for (int i = 0; i < state.getItemCount(); i++) { + if (newState.name().equals(state.getValue(i))) { + state.setSelectedIndex(i); + break; + } + } + } + } + void display(final ProjectDetail result) { project = result.project; @@ -202,6 +225,7 @@ public class ProjectInfoScreen extends ProjectScreen { useContentMerge.setValue(project.isUseContentMerge()); requireChangeID.setValue(project.isRequireChangeID()); setSubmitType(project.getSubmitType()); + setState(project.getState()); saveProject.setEnabled(false); } @@ -216,14 +240,18 @@ public class ProjectInfoScreen extends ProjectScreen { project.setSubmitType(Project.SubmitType.valueOf(submitType .getValue(submitType.getSelectedIndex()))); } + if (state.getSelectedIndex() >= 0) { + project.setState(Project.State.valueOf(state + .getValue(state.getSelectedIndex()))); + } - enableForm(false, false, false); + enableForm(false, false, false, false); Util.PROJECT_SVC.changeProjectSettings(project, new GerritCallback() { public void onSuccess(final ProjectDetail result) { enableForm(result.canModifyAgreements, - result.canModifyDescription, result.canModifyMergeType); + result.canModifyDescription, result.canModifyMergeType, result.canModifyState); display(result); } }); diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java index c599ee93aa..e3bcab1b4b 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java @@ -53,4 +53,20 @@ public class Util { return type.name(); } } + + public static String toLongString(final Project.State type) { + if (type == null) { + return ""; + } + switch (type) { + case ACTIVE: + return C.projectState_ACTIVE(); + case READ_ONLY: + return C.projectState_READ_ONLY(); + case HIDDEN: + return C.projectState_HIDDEN(); + default: + return type.name(); + } + } } diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java index 138a01c039..23547bd608 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java @@ -164,9 +164,11 @@ class PatchSetComplexDisclosurePanel extends ComplexDisclosurePanel implements O actionsPanel.setStyleName(Gerrit.RESOURCES.css().patchSetActions()); body.add(actionsPanel); if (Gerrit.isSignedIn()) { - populateReviewAction(); - if (changeDetail.isCurrentPatchSet(detail)) { - populateActions(detail); + if (changeDetail.canEdit()) { + populateReviewAction(); + if (changeDetail.isCurrentPatchSet(detail)) { + populateActions(detail); + } } } populateDiffAllActions(detail); diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java index 248515d48d..c94c0e32e6 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java @@ -125,6 +125,8 @@ public class ChangeDetailFactory extends Handler { detail.setCanRevert(change.getStatus() == Change.Status.MERGED && control.canAddPatchSet()); + detail.setCanEdit(control.getRefControl().canWrite()); + if (detail.getChange().getStatus().isOpen()) { List submitRecords = control.canSubmit(db, patch.getId()); for (SubmitRecord rec : submitRecords) { diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java index 1eb940b281..7dfce4eb36 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java @@ -57,6 +57,7 @@ class ProjectDetailFactory extends Handler { detail.setCanModifyAgreements(userIsOwner); detail.setCanModifyDescription(userIsOwner); detail.setCanModifyMergeType(userIsOwner); + detail.setCanModifyState(userIsOwner); return detail; } } diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Project.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Project.java index 13df386c8e..33c6d31d87 100644 --- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Project.java +++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Project.java @@ -75,6 +75,14 @@ public final class Project { CHERRY_PICK; } + public static enum State { + ACTIVE, + + READ_ONLY, + + HIDDEN; + } + protected NameKey name; protected String description; @@ -85,6 +93,8 @@ public final class Project { protected SubmitType submitType; + protected State state; + protected NameKey parent; protected boolean requireChangeID; @@ -97,6 +107,7 @@ public final class Project { public Project(Project.NameKey nameKey) { name = nameKey; submitType = SubmitType.MERGE_IF_NECESSARY; + state = State.ACTIVE; } public Project.NameKey getNameKey() { @@ -155,6 +166,14 @@ public final class Project { submitType = type; } + public State getState() { + return state; + } + + public void setState(final State newState) { + state = newState; + } + public void copySettingsFrom(final Project update) { description = update.description; useContributorAgreements = update.useContributorAgreements; @@ -162,6 +181,7 @@ public final class Project { useContentMerge = update.useContentMerge; requireChangeID = update.requireChangeID; submitType = update.submitType; + state = update.state; } public Project.NameKey getParent() { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java index 164fb05b95..7131012c9a 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java @@ -24,6 +24,7 @@ import com.google.gerrit.common.data.Permission; import com.google.gerrit.common.data.PermissionRule; import com.google.gerrit.reviewdb.AccountGroup; import com.google.gerrit.reviewdb.Project; +import com.google.gerrit.reviewdb.Project.State; import com.google.gerrit.reviewdb.Project.SubmitType; import com.google.gerrit.server.account.GroupCache; @@ -66,9 +67,12 @@ public class ProjectConfig extends VersionedMetaData { private static final String SUBMIT = "submit"; private static final String KEY_ACTION = "action"; private static final String KEY_MERGE_CONTENT = "mergeContent"; + private static final String KEY_STATE = "state"; private static final SubmitType defaultSubmitAction = SubmitType.MERGE_IF_NECESSARY; + private static final State defaultStateValue = + State.ACTIVE; private Project.NameKey projectName; private Project project; @@ -217,6 +221,7 @@ public class ProjectConfig extends VersionedMetaData { p.setSubmitType(getEnum(rc, SUBMIT, null, KEY_ACTION, defaultSubmitAction)); p.setUseContentMerge(getBoolean(rc, SUBMIT, KEY_MERGE_CONTENT, false)); + p.setState(getEnum(rc, PROJECT, null, KEY_STATE, defaultStateValue)); accessSections = new HashMap(); for (String refName : rc.getSubsections(ACCESS)) { @@ -341,6 +346,8 @@ public class ProjectConfig extends VersionedMetaData { set(rc, SUBMIT, null, KEY_ACTION, p.getSubmitType(), defaultSubmitAction); set(rc, SUBMIT, null, KEY_MERGE_CONTENT, p.isUseContentMerge()); + set(rc, PROJECT, null, KEY_STATE, p.getState(), null); + Set keepGroups = new HashSet(); AccessSection capability = accessSections.get(AccessSection.GLOBAL_CAPABILITIES); if (capability != null) { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java index b6fb074488..d08f8e36eb 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java @@ -192,10 +192,14 @@ public class ProjectControl { return state.getProject(); } + private boolean isHidden() { + return getProject().getState().equals(Project.State.HIDDEN); + } + /** Can this user see this project exists? */ public boolean isVisible() { - return visibleForReplication() - || canPerformOnAnyRef(Permission.READ); + return (visibleForReplication() + || canPerformOnAnyRef(Permission.READ)) && !isHidden(); } public boolean canAddRefs() { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java index 9c60133bc2..e1341da261 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java @@ -18,6 +18,7 @@ import com.google.gerrit.common.data.AccessSection; import com.google.gerrit.common.data.Permission; import com.google.gerrit.common.data.PermissionRange; import com.google.gerrit.common.data.PermissionRule; +import com.google.gerrit.reviewdb.Project; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.git.GitRepositoryManager; @@ -97,8 +98,8 @@ public class RefControl { /** Can this user see this reference exists? */ public boolean isVisible() { - return projectControl.visibleForReplication() - || canPerform(Permission.READ); + return (projectControl.visibleForReplication() || canPerform(Permission.READ)) + && canRead(); } /** @@ -109,16 +110,16 @@ public class RefControl { * ref */ public boolean canUpload() { - return projectControl - .controlForRef("refs/for/" + getRefName()) - .canPerform(Permission.PUSH); + return projectControl.controlForRef("refs/for/" + getRefName()) + .canPerform(Permission.PUSH) + && canWrite(); } /** @return true if this user can submit merge patch sets to this ref */ public boolean canUploadMerges() { - return projectControl - .controlForRef("refs/for/" + getRefName()) - .canPerform(Permission.PUSH_MERGE); + return projectControl.controlForRef("refs/for/" + getRefName()) + .canPerform(Permission.PUSH_MERGE) + && canWrite(); } /** @return true if this user can submit patch sets to this ref */ @@ -131,7 +132,8 @@ public class RefControl { // granting of powers beyond submitting to the configuration. return projectControl.isOwner(); } - return canPerform(Permission.SUBMIT); + return canPerform(Permission.SUBMIT) + && canWrite(); } /** @return true if the user can update the reference as a fast-forward. */ @@ -145,17 +147,28 @@ public class RefControl { // granting of powers beyond pushing to the configuration. return false; } - return canPerform(Permission.PUSH); + return canPerform(Permission.PUSH) + && canWrite(); } /** @return true if the user can rewind (force push) the reference. */ public boolean canForceUpdate() { - return canPushWithForce() || canDelete(); + return (canPushWithForce() || canDelete()) && canWrite(); + } + + public boolean canWrite() { + return getProjectControl().getProject().getState().equals( + Project.State.ACTIVE); + } + + public boolean canRead() { + return getProjectControl().getProject().getState().equals( + Project.State.READ_ONLY) || canWrite(); } private boolean canPushWithForce() { - if (GitRepositoryManager.REF_CONFIG.equals(refName) - && !projectControl.isOwner()) { + if (!canWrite() || (GitRepositoryManager.REF_CONFIG.equals(refName) + && !projectControl.isOwner())) { // Pushing requires being at least project owner, in addition to push. // Pushing configuration changes modifies the access control // rules. Allowing this to be done by a non-project-owner opens @@ -183,6 +196,9 @@ public class RefControl { * @return {@code true} if the user specified can create a new Git ref */ public boolean canCreate(RevWalk rw, RevObject object) { + if (!canWrite()) { + return false; + } boolean owner; switch (getCurrentUser().getAccessPath()) { case WEB_UI: @@ -242,7 +258,7 @@ public class RefControl { * @return {@code true} if the user specified can delete a Git ref. */ public boolean canDelete() { - if (GitRepositoryManager.REF_CONFIG.equals(refName)) { + if (!canWrite() || (GitRepositoryManager.REF_CONFIG.equals(refName))) { // Never allow removal of the refs/meta/config branch. // Deleting the branch would destroy all Gerrit specific // metadata about the project, including its access rules. diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java index e8a235f62d..c5b5164717 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java @@ -113,7 +113,9 @@ public class ProjectConfigTest extends LocalDiskRepositoryTestCase { + " submit = group Developers\n" // + "\tsubmit = group Staff\n" // + " upload = group Developers\n" // - + " read = group Developers\n", text(rev, "project.config")); + + " read = group Developers\n"// + + "[project]\n"// + + "\tstate = active\n", text(rev, "project.config")); } @Test @@ -140,7 +142,9 @@ public class ProjectConfigTest extends LocalDiskRepositoryTestCase { + " submit = group People Who Can Submit\n" // + "\tsubmit = group Staff\n" // + " upload = group Developers\n" // - + " read = group Developers\n", text(rev, "project.config")); + + " read = group Developers\n"// + + "[project]\n"// + + "\tstate = active\n", text(rev, "project.config")); } private ProjectConfig read(RevCommit rev) throws IOException,