Allow updating the parent project from the WebUI
This change adds support for updating the parent project of a child project from the WebUI. This functionality is offered in the ProjectAccessScreen. When the access rights are changed the user is also able to change the parent project (if the user has the needed privileges, means if he is administrator). When the user saves the new settings only one RPC to the server is done that updates both the access rights and the parent project property. Technically both updates are modifications of the 'project.config' file in the 'refs/meta/config' branch. There will be only one commit for this file that does both updates. In the UI only valid parent projects are suggested as new parent project (all projects that would cause a cycle in the line of parent projects are not suggested). Bug: issue 1298 Change-Id: Ic63bdb039ea5057a0551138f8fef9ede280b2be3 Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
		| @@ -28,6 +28,7 @@ public class ProjectAccess { | |||||||
|   protected Set<String> ownerOf; |   protected Set<String> ownerOf; | ||||||
|   protected boolean isConfigVisible; |   protected boolean isConfigVisible; | ||||||
|   protected boolean canUpload; |   protected boolean canUpload; | ||||||
|  |   protected boolean canChangeParent; | ||||||
|   protected LabelTypes labelTypes; |   protected LabelTypes labelTypes; | ||||||
|   protected Map<String, String> capabilities; |   protected Map<String, String> capabilities; | ||||||
|  |  | ||||||
| @@ -107,6 +108,14 @@ public class ProjectAccess { | |||||||
|     this.canUpload = canUpload; |     this.canUpload = canUpload; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   public boolean canChangeParent() { | ||||||
|  |     return canChangeParent; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public void setCanChangeParent(boolean canChangeParent) { | ||||||
|  |     this.canChangeParent = canChangeParent; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   public LabelTypes getLabelTypes() { |   public LabelTypes getLabelTypes() { | ||||||
|     return labelTypes; |     return labelTypes; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -33,11 +33,11 @@ public interface ProjectAdminService extends RemoteJsonService { | |||||||
|   @Audit |   @Audit | ||||||
|   @SignInRequired |   @SignInRequired | ||||||
|   void changeProjectAccess(Project.NameKey projectName, String baseRevision, |   void changeProjectAccess(Project.NameKey projectName, String baseRevision, | ||||||
|       String message, List<AccessSection> sections, |       String message, List<AccessSection> sections, Project.NameKey parentProjectName, | ||||||
|       AsyncCallback<ProjectAccess> callback); |       AsyncCallback<ProjectAccess> callback); | ||||||
|  |  | ||||||
|   @SignInRequired |   @SignInRequired | ||||||
|   void reviewProjectAccess(Project.NameKey projectName, String baseRevision, |   void reviewProjectAccess(Project.NameKey projectName, String baseRevision, | ||||||
|       String message, List<AccessSection> sections, |       String message, List<AccessSection> sections, Project.NameKey parentProjectName, | ||||||
|       AsyncCallback<Change.Id> callback); |       AsyncCallback<Change.Id> callback); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,27 @@ | |||||||
|  | // Copyright (C) 2013 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.errors; | ||||||
|  |  | ||||||
|  | /** Error indicating that updating a parent project failed. */ | ||||||
|  | public class UpdateParentFailedException extends Exception { | ||||||
|  |   private static final long serialVersionUID = 1L; | ||||||
|  |  | ||||||
|  |   public static final String MESSAGE = "Update Parent Project Failed: "; | ||||||
|  |  | ||||||
|  |   public UpdateParentFailedException(final String message, | ||||||
|  |       final Throwable why) { | ||||||
|  |     super(MESSAGE + ": " + message, why); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -34,4 +34,6 @@ public interface GerritMessages extends Messages { | |||||||
|  |  | ||||||
|   String pluginFailed(String scriptPath); |   String pluginFailed(String scriptPath); | ||||||
|   String cannotDownloadPlugin(String scriptPath); |   String cannotDownloadPlugin(String scriptPath); | ||||||
|  |  | ||||||
|  |   String parentUpdateFailed(String message); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -15,3 +15,5 @@ branchCreationConflict = Cannot create branch {0} since it conflicts with branch | |||||||
|  |  | ||||||
| pluginFailed = Plugin JavaScript {0} failed to load | pluginFailed = Plugin JavaScript {0} failed to load | ||||||
| cannotDownloadPlugin = Cannot download JavaScript plugin from: {0}. | cannotDownloadPlugin = Cannot download JavaScript plugin from: {0}. | ||||||
|  |  | ||||||
|  | parentUpdateFailed = Setting parent project failed: {0} | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ import com.google.gerrit.client.Dispatcher; | |||||||
| import com.google.gerrit.client.Gerrit; | import com.google.gerrit.client.Gerrit; | ||||||
| import com.google.gerrit.client.GitwebLink; | import com.google.gerrit.client.GitwebLink; | ||||||
| import com.google.gerrit.client.ui.Hyperlink; | import com.google.gerrit.client.ui.Hyperlink; | ||||||
|  | import com.google.gerrit.client.ui.ParentProjectBox; | ||||||
| import com.google.gerrit.common.data.AccessSection; | import com.google.gerrit.common.data.AccessSection; | ||||||
| import com.google.gerrit.common.data.ProjectAccess; | import com.google.gerrit.common.data.ProjectAccess; | ||||||
| import com.google.gerrit.reviewdb.client.Branch; | import com.google.gerrit.reviewdb.client.Branch; | ||||||
| @@ -55,6 +56,10 @@ public class ProjectAccessEditor extends Composite implements | |||||||
|   @UiField |   @UiField | ||||||
|   Hyperlink parentProject; |   Hyperlink parentProject; | ||||||
|  |  | ||||||
|  |   @UiField | ||||||
|  |   @Editor.Ignore | ||||||
|  |   ParentProjectBox parentProjectBox; | ||||||
|  |  | ||||||
|   @UiField |   @UiField | ||||||
|   DivElement history; |   DivElement history; | ||||||
|  |  | ||||||
| @@ -106,6 +111,11 @@ public class ProjectAccessEditor extends Composite implements | |||||||
|       parentProject.setText(parent.get()); |       parentProject.setText(parent.get()); | ||||||
|       parentProject.setTargetHistoryToken( // |       parentProject.setTargetHistoryToken( // | ||||||
|           Dispatcher.toProjectAdmin(parent, ProjectScreen.ACCESS)); |           Dispatcher.toProjectAdmin(parent, ProjectScreen.ACCESS)); | ||||||
|  |  | ||||||
|  |       parentProjectBox.setVisible(editing && value.canChangeParent()); | ||||||
|  |       parentProjectBox.setProject(value.getProjectName()); | ||||||
|  |       parentProjectBox.setParentProject(value.getInheritsFrom()); | ||||||
|  |       parentProject.setVisible(!parentProjectBox.isVisible()); | ||||||
|     } else { |     } else { | ||||||
|       inheritsFrom.getStyle().setDisplay(Display.NONE); |       inheritsFrom.getStyle().setDisplay(Display.NONE); | ||||||
|     } |     } | ||||||
| @@ -135,6 +145,7 @@ public class ProjectAccessEditor extends Composite implements | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     value.setLocal(keep); |     value.setLocal(keep); | ||||||
|  |     value.setInheritsFrom(parentProjectBox.getParentProjectName()); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @Override |   @Override | ||||||
|   | |||||||
| @@ -56,6 +56,9 @@ limitations under the License. | |||||||
|   <div ui:field='inheritsFrom' class='{style.inheritsFrom}'> |   <div ui:field='inheritsFrom' class='{style.inheritsFrom}'> | ||||||
|     <span class='{style.parentTitle}'><ui:msg>Rights Inherit From:</ui:msg></span> |     <span class='{style.parentTitle}'><ui:msg>Rights Inherit From:</ui:msg></span> | ||||||
|     <q:Hyperlink ui:field='parentProject' styleName='{style.parentLink}'/> |     <q:Hyperlink ui:field='parentProject' styleName='{style.parentLink}'/> | ||||||
|  |     <q:ParentProjectBox | ||||||
|  |       ui:field='parentProjectBox' | ||||||
|  |       visible='false'/> | ||||||
|   </div> |   </div> | ||||||
|   <div ui:field='history' class='{style.history}'> |   <div ui:field='history' class='{style.history}'> | ||||||
|     <span class='{style.historyTitle}'><ui:msg>History:</ui:msg></span> |     <span class='{style.historyTitle}'><ui:msg>History:</ui:msg></span> | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ package com.google.gerrit.client.admin; | |||||||
| import static com.google.gerrit.common.ProjectAccessUtil.mergeSections; | import static com.google.gerrit.common.ProjectAccessUtil.mergeSections; | ||||||
| import static com.google.gerrit.common.ProjectAccessUtil.removeEmptyPermissionsAndSections; | import static com.google.gerrit.common.ProjectAccessUtil.removeEmptyPermissionsAndSections; | ||||||
|  |  | ||||||
|  | import com.google.gerrit.client.ErrorDialog; | ||||||
| import com.google.gerrit.client.Gerrit; | import com.google.gerrit.client.Gerrit; | ||||||
| import com.google.gerrit.client.config.CapabilityInfo; | import com.google.gerrit.client.config.CapabilityInfo; | ||||||
| import com.google.gerrit.client.config.ConfigServerApi; | import com.google.gerrit.client.config.ConfigServerApi; | ||||||
| @@ -28,6 +29,7 @@ import com.google.gerrit.client.rpc.ScreenLoadCallback; | |||||||
| import com.google.gerrit.common.PageLinks; | import com.google.gerrit.common.PageLinks; | ||||||
| import com.google.gerrit.common.data.AccessSection; | import com.google.gerrit.common.data.AccessSection; | ||||||
| import com.google.gerrit.common.data.ProjectAccess; | import com.google.gerrit.common.data.ProjectAccess; | ||||||
|  | import com.google.gerrit.common.errors.UpdateParentFailedException; | ||||||
| import com.google.gerrit.reviewdb.client.Change; | import com.google.gerrit.reviewdb.client.Change; | ||||||
| import com.google.gerrit.reviewdb.client.Project; | import com.google.gerrit.reviewdb.client.Project; | ||||||
| import com.google.gwt.core.client.GWT; | import com.google.gwt.core.client.GWT; | ||||||
| @@ -45,6 +47,7 @@ import com.google.gwt.user.client.ui.Label; | |||||||
| import com.google.gwt.user.client.ui.UIObject; | import com.google.gwt.user.client.ui.UIObject; | ||||||
| import com.google.gwt.user.client.ui.VerticalPanel; | import com.google.gwt.user.client.ui.VerticalPanel; | ||||||
| import com.google.gwtexpui.globalkey.client.NpTextArea; | import com.google.gwtexpui.globalkey.client.NpTextArea; | ||||||
|  | import com.google.gwtjsonrpc.client.RemoteJsonException; | ||||||
|  |  | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| @@ -205,6 +208,7 @@ public class ProjectAccessScreen extends ProjectScreen { | |||||||
|         access.getRevision(), // |         access.getRevision(), // | ||||||
|         message, // |         message, // | ||||||
|         access.getLocal(), // |         access.getLocal(), // | ||||||
|  |         access.getInheritsFrom(), // | ||||||
|         new GerritCallback<ProjectAccess>() { |         new GerritCallback<ProjectAccess>() { | ||||||
|           @Override |           @Override | ||||||
|           public void onSuccess(ProjectAccess newAccess) { |           public void onSuccess(ProjectAccess newAccess) { | ||||||
| @@ -250,8 +254,16 @@ public class ProjectAccessScreen extends ProjectScreen { | |||||||
|           public void onFailure(Throwable caught) { |           public void onFailure(Throwable caught) { | ||||||
|             error.clear(); |             error.clear(); | ||||||
|             enable(true); |             enable(true); | ||||||
|  |             if (caught instanceof RemoteJsonException | ||||||
|  |                 && caught.getMessage().startsWith( | ||||||
|  |                     UpdateParentFailedException.MESSAGE)) { | ||||||
|  |               new ErrorDialog(Gerrit.M.parentUpdateFailed(caught.getMessage() | ||||||
|  |                   .substring(UpdateParentFailedException.MESSAGE.length() + 1))) | ||||||
|  |                   .center(); | ||||||
|  |             } else { | ||||||
|               super.onFailure(caught); |               super.onFailure(caught); | ||||||
|             } |             } | ||||||
|  |           } | ||||||
|         }); |         }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -275,6 +287,7 @@ public class ProjectAccessScreen extends ProjectScreen { | |||||||
|         access.getRevision(), // |         access.getRevision(), // | ||||||
|         message, // |         message, // | ||||||
|         access.getLocal(), // |         access.getLocal(), // | ||||||
|  |         access.getInheritsFrom(), // | ||||||
|         new GerritCallback<Change.Id>() { |         new GerritCallback<Change.Id>() { | ||||||
|           @Override |           @Override | ||||||
|           public void onSuccess(Change.Id changeId) { |           public void onSuccess(Change.Id changeId) { | ||||||
|   | |||||||
| @@ -110,6 +110,15 @@ public class ProjectApi { | |||||||
|         }); |         }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   public static void getChildren(Project.NameKey name, boolean recursive, | ||||||
|  |       AsyncCallback<JsArray<ProjectInfo>> cb) { | ||||||
|  |     RestApi view = project(name).view("children"); | ||||||
|  |     if (recursive) { | ||||||
|  |       view.addParameterTrue("recursive"); | ||||||
|  |     } | ||||||
|  |     view.get(cb); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   public static void getDescription(Project.NameKey name, |   public static void getDescription(Project.NameKey name, | ||||||
|       AsyncCallback<NativeString> cb) { |       AsyncCallback<NativeString> cb) { | ||||||
|     project(name).view("description").get(cb); |     project(name).view("description").get(cb); | ||||||
|   | |||||||
| @@ -0,0 +1,101 @@ | |||||||
|  | // Copyright (C) 2013 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.ui; | ||||||
|  |  | ||||||
|  | import com.google.gerrit.client.projects.ProjectApi; | ||||||
|  | import com.google.gerrit.client.projects.ProjectInfo; | ||||||
|  | import com.google.gerrit.client.rpc.Natives; | ||||||
|  | import com.google.gerrit.reviewdb.client.Project; | ||||||
|  | import com.google.gwt.core.client.JsArray; | ||||||
|  | import com.google.gwt.user.client.rpc.AsyncCallback; | ||||||
|  | import com.google.gwt.user.client.ui.Composite; | ||||||
|  | import com.google.gwt.user.client.ui.SuggestBox; | ||||||
|  | import com.google.gwtexpui.globalkey.client.NpTextBox; | ||||||
|  |  | ||||||
|  | import java.util.HashSet; | ||||||
|  | import java.util.Set; | ||||||
|  |  | ||||||
|  | public class ParentProjectBox extends Composite { | ||||||
|  |   private final NpTextBox textBox; | ||||||
|  |   private final SuggestBox suggestBox; | ||||||
|  |   private final ParentProjectNameSuggestOracle suggestOracle; | ||||||
|  |  | ||||||
|  |   public ParentProjectBox() { | ||||||
|  |     textBox = new NpTextBox(); | ||||||
|  |     suggestOracle = new ParentProjectNameSuggestOracle(); | ||||||
|  |     suggestBox = new SuggestBox(suggestOracle, textBox); | ||||||
|  |     initWidget(suggestBox); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public void setVisibleLength(int len) { | ||||||
|  |     textBox.setVisibleLength(len); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public void setProject(final Project.NameKey project) { | ||||||
|  |     suggestOracle.setProject(project); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public void setParentProject(final Project.NameKey parent) { | ||||||
|  |     suggestBox.setText(parent != null ? parent.get() : ""); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public Project.NameKey getParentProjectName() { | ||||||
|  |     final String projectName = suggestBox.getText().trim(); | ||||||
|  |     if (projectName.isEmpty()) { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |     return new Project.NameKey(projectName); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private static class ParentProjectNameSuggestOracle extends ProjectNameSuggestOracle { | ||||||
|  |     private Set<String> exclude = new HashSet<String>(); | ||||||
|  |  | ||||||
|  |     public void setProject(Project.NameKey project) { | ||||||
|  |       exclude.clear(); | ||||||
|  |       exclude.add(project.get()); | ||||||
|  |       ProjectApi.getChildren(project, true, new AsyncCallback<JsArray<ProjectInfo>>() { | ||||||
|  |         @Override | ||||||
|  |         public void onSuccess(JsArray<ProjectInfo> result) { | ||||||
|  |           for (ProjectInfo p : Natives.asList(result)) { | ||||||
|  |             exclude.add(p.name()); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Override | ||||||
|  |         public void onFailure(Throwable caught) { | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void _onRequestSuggestions(Request req, final Callback callback) { | ||||||
|  |       super._onRequestSuggestions(req, new Callback() { | ||||||
|  |         public void onSuggestionsReady(Request request, Response response) { | ||||||
|  |           if (exclude.size() > 0) { | ||||||
|  |             Set<Suggestion> filteredSuggestions = | ||||||
|  |                 new HashSet<Suggestion>(response.getSuggestions()); | ||||||
|  |             for (Suggestion s : response.getSuggestions()) { | ||||||
|  |               if (exclude.contains(s.getReplacementString())) { | ||||||
|  |                 filteredSuggestions.remove(s); | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |             response.setSuggestions(filteredSuggestions); | ||||||
|  |           } | ||||||
|  |           callback.onSuggestionsReady(request, response); | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -19,12 +19,15 @@ import com.google.gerrit.common.data.AccessSection; | |||||||
| import com.google.gerrit.common.data.ProjectAccess; | import com.google.gerrit.common.data.ProjectAccess; | ||||||
| import com.google.gerrit.reviewdb.client.Project; | import com.google.gerrit.reviewdb.client.Project; | ||||||
| import com.google.gerrit.server.account.GroupBackend; | import com.google.gerrit.server.account.GroupBackend; | ||||||
|  | import com.google.gerrit.server.config.AllProjectsNameProvider; | ||||||
| import com.google.gerrit.server.git.MetaDataUpdate; | import com.google.gerrit.server.git.MetaDataUpdate; | ||||||
| import com.google.gerrit.server.git.ProjectConfig; | import com.google.gerrit.server.git.ProjectConfig; | ||||||
| import com.google.gerrit.server.project.NoSuchProjectException; | import com.google.gerrit.server.project.NoSuchProjectException; | ||||||
| import com.google.gerrit.server.project.ProjectCache; | import com.google.gerrit.server.project.ProjectCache; | ||||||
| import com.google.gerrit.server.project.ProjectControl; | import com.google.gerrit.server.project.ProjectControl; | ||||||
|  | import com.google.gerrit.server.project.SetParent; | ||||||
| import com.google.inject.Inject; | import com.google.inject.Inject; | ||||||
|  | import com.google.inject.Provider; | ||||||
| import com.google.inject.assistedinject.Assisted; | import com.google.inject.assistedinject.Assisted; | ||||||
|  |  | ||||||
| import org.eclipse.jgit.errors.ConfigInvalidException; | import org.eclipse.jgit.errors.ConfigInvalidException; | ||||||
| @@ -35,9 +38,11 @@ import java.util.List; | |||||||
|  |  | ||||||
| class ChangeProjectAccess extends ProjectAccessHandler<ProjectAccess> { | class ChangeProjectAccess extends ProjectAccessHandler<ProjectAccess> { | ||||||
|   interface Factory { |   interface Factory { | ||||||
|     ChangeProjectAccess create(@Assisted Project.NameKey projectName, |     ChangeProjectAccess create( | ||||||
|  |         @Assisted("projectName") Project.NameKey projectName, | ||||||
|         @Nullable @Assisted ObjectId base, |         @Nullable @Assisted ObjectId base, | ||||||
|         @Assisted List<AccessSection> sectionList, |         @Assisted List<AccessSection> sectionList, | ||||||
|  |         @Nullable @Assisted("parentProjectName") Project.NameKey parentProjectName, | ||||||
|         @Nullable @Assisted String message); |         @Nullable @Assisted String message); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -45,17 +50,21 @@ class ChangeProjectAccess extends ProjectAccessHandler<ProjectAccess> { | |||||||
|   private final ProjectCache projectCache; |   private final ProjectCache projectCache; | ||||||
|  |  | ||||||
|   @Inject |   @Inject | ||||||
|   ChangeProjectAccess(final ProjectAccessFactory.Factory projectAccessFactory, |   ChangeProjectAccess(ProjectAccessFactory.Factory projectAccessFactory, | ||||||
|       final ProjectControl.Factory projectControlFactory, |       ProjectControl.Factory projectControlFactory, | ||||||
|       final ProjectCache projectCache, final GroupBackend groupBackend, |       ProjectCache projectCache, GroupBackend groupBackend, | ||||||
|       final MetaDataUpdate.User metaDataUpdateFactory, |       MetaDataUpdate.User metaDataUpdateFactory, | ||||||
|  |       AllProjectsNameProvider allProjects, | ||||||
|  |       Provider<SetParent> setParent, | ||||||
|  |  | ||||||
|       @Assisted final Project.NameKey projectName, |       @Assisted("projectName") Project.NameKey projectName, | ||||||
|       @Nullable @Assisted final ObjectId base, |       @Nullable @Assisted ObjectId base, | ||||||
|       @Assisted List<AccessSection> sectionList, |       @Assisted List<AccessSection> sectionList, | ||||||
|  |       @Nullable @Assisted("parentProjectName") Project.NameKey parentProjectName, | ||||||
|       @Nullable @Assisted String message) { |       @Nullable @Assisted String message) { | ||||||
|     super(projectControlFactory, groupBackend, metaDataUpdateFactory, |     super(projectControlFactory, groupBackend, metaDataUpdateFactory, | ||||||
|         projectName, base, sectionList, message, true); |         allProjects, setParent, projectName, base, sectionList, | ||||||
|  |         parentProjectName, message, true); | ||||||
|     this.projectAccessFactory = projectAccessFactory; |     this.projectAccessFactory = projectAccessFactory; | ||||||
|     this.projectCache = projectCache; |     this.projectCache = projectCache; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -203,6 +203,8 @@ class ProjectAccessFactory extends Handler<ProjectAccess> { | |||||||
|     detail.setOwnerOf(ownerOf); |     detail.setOwnerOf(ownerOf); | ||||||
|     detail.setCanUpload(pc.isOwner() |     detail.setCanUpload(pc.isOwner() | ||||||
|         || (metaConfigControl.isVisible() && metaConfigControl.canUpload())); |         || (metaConfigControl.isVisible() && metaConfigControl.canUpload())); | ||||||
|  |     detail.setCanChangeParent(pc.getCurrentUser().getCapabilities() | ||||||
|  |         .canAdministrateServer()); | ||||||
|     detail.setConfigVisible(pc.isOwner() || metaConfigControl.isVisible()); |     detail.setConfigVisible(pc.isOwner() || metaConfigControl.isVisible()); | ||||||
|     detail.setLabelTypes(pc.getLabelTypes()); |     detail.setLabelTypes(pc.getLabelTypes()); | ||||||
|     return detail; |     return detail; | ||||||
|   | |||||||
| @@ -22,16 +22,23 @@ import com.google.gerrit.common.data.Permission; | |||||||
| import com.google.gerrit.common.data.PermissionRule; | import com.google.gerrit.common.data.PermissionRule; | ||||||
| import com.google.gerrit.common.errors.InvalidNameException; | import com.google.gerrit.common.errors.InvalidNameException; | ||||||
| import com.google.gerrit.common.errors.NoSuchGroupException; | import com.google.gerrit.common.errors.NoSuchGroupException; | ||||||
|  | import com.google.gerrit.common.errors.UpdateParentFailedException; | ||||||
|  | import com.google.gerrit.extensions.restapi.AuthException; | ||||||
|  | import com.google.gerrit.extensions.restapi.ResourceConflictException; | ||||||
|  | import com.google.gerrit.extensions.restapi.UnprocessableEntityException; | ||||||
| import com.google.gerrit.httpd.rpc.Handler; | import com.google.gerrit.httpd.rpc.Handler; | ||||||
| import com.google.gerrit.reviewdb.client.Project; | import com.google.gerrit.reviewdb.client.Project; | ||||||
| import com.google.gerrit.server.account.GroupBackend; | import com.google.gerrit.server.account.GroupBackend; | ||||||
| import com.google.gerrit.server.account.GroupBackends; | import com.google.gerrit.server.account.GroupBackends; | ||||||
|  | import com.google.gerrit.server.config.AllProjectsNameProvider; | ||||||
| import com.google.gerrit.server.git.MetaDataUpdate; | import com.google.gerrit.server.git.MetaDataUpdate; | ||||||
| import com.google.gerrit.server.git.ProjectConfig; | import com.google.gerrit.server.git.ProjectConfig; | ||||||
| import com.google.gerrit.server.project.NoSuchProjectException; | import com.google.gerrit.server.project.NoSuchProjectException; | ||||||
| import com.google.gerrit.server.project.ProjectControl; | import com.google.gerrit.server.project.ProjectControl; | ||||||
| import com.google.gerrit.server.project.RefControl; | import com.google.gerrit.server.project.RefControl; | ||||||
|  | import com.google.gerrit.server.project.SetParent; | ||||||
| import com.google.gwtorm.server.OrmException; | import com.google.gwtorm.server.OrmException; | ||||||
|  | import com.google.inject.Provider; | ||||||
|  |  | ||||||
| import org.eclipse.jgit.errors.ConfigInvalidException; | import org.eclipse.jgit.errors.ConfigInvalidException; | ||||||
| import org.eclipse.jgit.errors.RepositoryNotFoundException; | import org.eclipse.jgit.errors.RepositoryNotFoundException; | ||||||
| @@ -47,27 +54,32 @@ public abstract class ProjectAccessHandler<T> extends Handler<T> { | |||||||
|   private final ProjectControl.Factory projectControlFactory; |   private final ProjectControl.Factory projectControlFactory; | ||||||
|   protected final GroupBackend groupBackend; |   protected final GroupBackend groupBackend; | ||||||
|   private final MetaDataUpdate.User metaDataUpdateFactory; |   private final MetaDataUpdate.User metaDataUpdateFactory; | ||||||
|  |   private final AllProjectsNameProvider allProjects; | ||||||
|  |   private final Provider<SetParent> setParent; | ||||||
|  |  | ||||||
|   protected final Project.NameKey projectName; |   protected final Project.NameKey projectName; | ||||||
|   protected final ObjectId base; |   protected final ObjectId base; | ||||||
|   private List<AccessSection> sectionList; |   private List<AccessSection> sectionList; | ||||||
|  |   private final Project.NameKey parentProjectName; | ||||||
|   protected String message; |   protected String message; | ||||||
|   private boolean checkIfOwner; |   private boolean checkIfOwner; | ||||||
|  |  | ||||||
|   protected ProjectAccessHandler( |   protected ProjectAccessHandler(ProjectControl.Factory projectControlFactory, | ||||||
|       final ProjectControl.Factory projectControlFactory, |       GroupBackend groupBackend, MetaDataUpdate.User metaDataUpdateFactory, | ||||||
|       final GroupBackend groupBackend, |       AllProjectsNameProvider allProjects, Provider<SetParent> setParent, | ||||||
|       final MetaDataUpdate.User metaDataUpdateFactory, |       Project.NameKey projectName, ObjectId base, | ||||||
|       final Project.NameKey projectName, final ObjectId base, |       List<AccessSection> sectionList, Project.NameKey parentProjectName, | ||||||
|       final List<AccessSection> sectionList, final String message, |       String message, boolean checkIfOwner) { | ||||||
|       final boolean checkIfOwner) { |  | ||||||
|     this.projectControlFactory = projectControlFactory; |     this.projectControlFactory = projectControlFactory; | ||||||
|     this.groupBackend = groupBackend; |     this.groupBackend = groupBackend; | ||||||
|     this.metaDataUpdateFactory = metaDataUpdateFactory; |     this.metaDataUpdateFactory = metaDataUpdateFactory; | ||||||
|  |     this.allProjects = allProjects; | ||||||
|  |     this.setParent = setParent; | ||||||
|  |  | ||||||
|     this.projectName = projectName; |     this.projectName = projectName; | ||||||
|     this.base = base; |     this.base = base; | ||||||
|     this.sectionList = sectionList; |     this.sectionList = sectionList; | ||||||
|  |     this.parentProjectName = parentProjectName; | ||||||
|     this.message = message; |     this.message = message; | ||||||
|     this.checkIfOwner = checkIfOwner; |     this.checkIfOwner = checkIfOwner; | ||||||
|   } |   } | ||||||
| @@ -75,7 +87,7 @@ public abstract class ProjectAccessHandler<T> extends Handler<T> { | |||||||
|   @Override |   @Override | ||||||
|   public final T call() throws NoSuchProjectException, IOException, |   public final T call() throws NoSuchProjectException, IOException, | ||||||
|       ConfigInvalidException, InvalidNameException, NoSuchGroupException, |       ConfigInvalidException, InvalidNameException, NoSuchGroupException, | ||||||
|       OrmException { |       OrmException, UpdateParentFailedException { | ||||||
|     final ProjectControl projectControl = |     final ProjectControl projectControl = | ||||||
|         projectControlFactory.controlFor(projectName); |         projectControlFactory.controlFor(projectName); | ||||||
|  |  | ||||||
| @@ -120,6 +132,20 @@ public abstract class ProjectAccessHandler<T> extends Handler<T> { | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  |       if (!config.getProject().getNameKey().equals(allProjects.get()) && | ||||||
|  |           !config.getProject().getParent(allProjects.get()).equals(parentProjectName)) { | ||||||
|  |         try { | ||||||
|  |           setParent.get().validateParentUpdate(projectControl, parentProjectName.get()); | ||||||
|  |         } catch (AuthException e) { | ||||||
|  |           throw new UpdateParentFailedException(e.getMessage(), e); | ||||||
|  |         } catch (ResourceConflictException e) { | ||||||
|  |           throw new UpdateParentFailedException(e.getMessage(), e); | ||||||
|  |         } catch (UnprocessableEntityException e) { | ||||||
|  |           throw new UpdateParentFailedException(e.getMessage(), e); | ||||||
|  |         } | ||||||
|  |         config.getProject().setParentName(parentProjectName); | ||||||
|  |       } | ||||||
|  |  | ||||||
|       if (message != null && !message.isEmpty()) { |       if (message != null && !message.isEmpty()) { | ||||||
|         if (!message.endsWith("\n")) { |         if (!message.endsWith("\n")) { | ||||||
|           message += "\n"; |           message += "\n"; | ||||||
|   | |||||||
| @@ -56,14 +56,16 @@ class ProjectAdminServiceImpl implements ProjectAdminService { | |||||||
|   @Override |   @Override | ||||||
|   public void changeProjectAccess(Project.NameKey projectName, |   public void changeProjectAccess(Project.NameKey projectName, | ||||||
|       String baseRevision, String msg, List<AccessSection> sections, |       String baseRevision, String msg, List<AccessSection> sections, | ||||||
|       AsyncCallback<ProjectAccess> cb) { |       Project.NameKey parentProjectName, AsyncCallback<ProjectAccess> cb) { | ||||||
|     changeProjectAccessFactory.create(projectName, getBase(baseRevision), sections, msg).to(cb); |     changeProjectAccessFactory.create(projectName, getBase(baseRevision), | ||||||
|  |         sections, parentProjectName, msg).to(cb); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @Override |   @Override | ||||||
|   public void reviewProjectAccess(Project.NameKey projectName, |   public void reviewProjectAccess(Project.NameKey projectName, | ||||||
|       String baseRevision, String msg, List<AccessSection> sections, |       String baseRevision, String msg, List<AccessSection> sections, | ||||||
|       AsyncCallback<Change.Id> cb) { |       Project.NameKey parentProjectName, AsyncCallback<Change.Id> cb) { | ||||||
|     reviewProjectAccessFactory.create(projectName, getBase(baseRevision), sections, msg).to(cb); |     reviewProjectAccessFactory.create(projectName, getBase(baseRevision), | ||||||
|  |         sections, parentProjectName, msg).to(cb); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -31,6 +31,7 @@ import com.google.gerrit.server.IdentifiedUser; | |||||||
| import com.google.gerrit.server.account.GroupBackend; | import com.google.gerrit.server.account.GroupBackend; | ||||||
| import com.google.gerrit.server.change.ChangeResource; | import com.google.gerrit.server.change.ChangeResource; | ||||||
| import com.google.gerrit.server.change.PostReviewers; | import com.google.gerrit.server.change.PostReviewers; | ||||||
|  | import com.google.gerrit.server.config.AllProjectsNameProvider; | ||||||
| import com.google.gerrit.server.git.GitRepositoryManager; | import com.google.gerrit.server.git.GitRepositoryManager; | ||||||
| import com.google.gerrit.server.git.MetaDataUpdate; | import com.google.gerrit.server.git.MetaDataUpdate; | ||||||
| import com.google.gerrit.server.git.ProjectConfig; | import com.google.gerrit.server.git.ProjectConfig; | ||||||
| @@ -39,6 +40,7 @@ import com.google.gerrit.server.mail.CreateChangeSender; | |||||||
| import com.google.gerrit.server.patch.PatchSetInfoFactory; | import com.google.gerrit.server.patch.PatchSetInfoFactory; | ||||||
| import com.google.gerrit.server.project.ChangeControl; | import com.google.gerrit.server.project.ChangeControl; | ||||||
| import com.google.gerrit.server.project.ProjectControl; | import com.google.gerrit.server.project.ProjectControl; | ||||||
|  | import com.google.gerrit.server.project.SetParent; | ||||||
| import com.google.gerrit.server.util.TimeUtil; | import com.google.gerrit.server.util.TimeUtil; | ||||||
| import com.google.gwtorm.server.OrmException; | import com.google.gwtorm.server.OrmException; | ||||||
| import com.google.inject.Inject; | import com.google.inject.Inject; | ||||||
| @@ -60,9 +62,11 @@ public class ReviewProjectAccess extends ProjectAccessHandler<Change.Id> { | |||||||
|       LoggerFactory.getLogger(ReviewProjectAccess.class); |       LoggerFactory.getLogger(ReviewProjectAccess.class); | ||||||
|  |  | ||||||
|   interface Factory { |   interface Factory { | ||||||
|     ReviewProjectAccess create(@Assisted Project.NameKey projectName, |     ReviewProjectAccess create( | ||||||
|  |         @Assisted("projectName") Project.NameKey projectName, | ||||||
|         @Nullable @Assisted ObjectId base, |         @Nullable @Assisted ObjectId base, | ||||||
|         @Assisted List<AccessSection> sectionList, |         @Assisted List<AccessSection> sectionList, | ||||||
|  |         @Nullable @Assisted("parentProjectName") Project.NameKey parentProjectName, | ||||||
|         @Nullable @Assisted String message); |         @Nullable @Assisted String message); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -84,13 +88,17 @@ public class ReviewProjectAccess extends ProjectAccessHandler<Change.Id> { | |||||||
|       ChangeControl.GenericFactory changeFactory, |       ChangeControl.GenericFactory changeFactory, | ||||||
|       ChangeIndexer indexer, ChangeHooks hooks, |       ChangeIndexer indexer, ChangeHooks hooks, | ||||||
|       CreateChangeSender.Factory createChangeSenderFactory, |       CreateChangeSender.Factory createChangeSenderFactory, | ||||||
|  |       AllProjectsNameProvider allProjects, | ||||||
|  |       Provider<SetParent> setParent, | ||||||
|  |  | ||||||
|       @Assisted Project.NameKey projectName, |       @Assisted("projectName") Project.NameKey projectName, | ||||||
|       @Nullable @Assisted ObjectId base, |       @Nullable @Assisted ObjectId base, | ||||||
|       @Assisted List<AccessSection> sectionList, |       @Assisted List<AccessSection> sectionList, | ||||||
|  |       @Nullable @Assisted("parentProjectName") Project.NameKey parentProjectName, | ||||||
|       @Nullable @Assisted String message) { |       @Nullable @Assisted String message) { | ||||||
|     super(projectControlFactory, groupBackend, metaDataUpdateFactory, |     super(projectControlFactory, groupBackend, metaDataUpdateFactory, | ||||||
|         projectName, base, sectionList, message, false); |         allProjects, setParent, projectName, base, sectionList, | ||||||
|  |         parentProjectName, message, false); | ||||||
|     this.db = db; |     this.db = db; | ||||||
|     this.user = user; |     this.user = user; | ||||||
|     this.patchSetInfoFactory = patchSetInfoFactory; |     this.patchSetInfoFactory = patchSetInfoFactory; | ||||||
|   | |||||||
| @@ -38,7 +38,7 @@ import org.eclipse.jgit.errors.RepositoryNotFoundException; | |||||||
|  |  | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
|  |  | ||||||
| class SetParent implements RestModifyView<ProjectResource, Input> { | public class SetParent implements RestModifyView<ProjectResource, Input> { | ||||||
|   static class Input { |   static class Input { | ||||||
|     @DefaultInput |     @DefaultInput | ||||||
|     String parent; |     String parent; | ||||||
| @@ -63,41 +63,14 @@ class SetParent implements RestModifyView<ProjectResource, Input> { | |||||||
|       BadRequestException, ResourceConflictException, |       BadRequestException, ResourceConflictException, | ||||||
|       ResourceNotFoundException, UnprocessableEntityException, IOException { |       ResourceNotFoundException, UnprocessableEntityException, IOException { | ||||||
|     ProjectControl ctl = rsrc.getControl(); |     ProjectControl ctl = rsrc.getControl(); | ||||||
|  |     validateParentUpdate(ctl, input.parent); | ||||||
|     IdentifiedUser user = (IdentifiedUser) ctl.getCurrentUser(); |     IdentifiedUser user = (IdentifiedUser) ctl.getCurrentUser(); | ||||||
|     if (!user.getCapabilities().canAdministrateServer()) { |  | ||||||
|       throw new AuthException("not administrator"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (rsrc.getNameKey().equals(allProjects)) { |  | ||||||
|       throw new ResourceConflictException("cannot set parent of " |  | ||||||
|           + allProjects.get()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     input.parent = Strings.emptyToNull(input.parent); |  | ||||||
|     if (input.parent != null) { |  | ||||||
|       ProjectState parent = cache.get(new Project.NameKey(input.parent)); |  | ||||||
|       if (parent == null) { |  | ||||||
|         throw new UnprocessableEntityException("parent project " + input.parent |  | ||||||
|             + " not found"); |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       if (Iterables.tryFind(parent.tree(), new Predicate<ProjectState>() { |  | ||||||
|         @Override |  | ||||||
|         public boolean apply(ProjectState input) { |  | ||||||
|           return input.getProject().getNameKey().equals(rsrc.getNameKey()); |  | ||||||
|         } |  | ||||||
|       }).isPresent()) { |  | ||||||
|         throw new ResourceConflictException("cycle exists between " |  | ||||||
|             + rsrc.getName() + " and " + parent.getProject().getName()); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     try { |     try { | ||||||
|       MetaDataUpdate md = updateFactory.create(rsrc.getNameKey()); |       MetaDataUpdate md = updateFactory.create(rsrc.getNameKey()); | ||||||
|       try { |       try { | ||||||
|         ProjectConfig config = ProjectConfig.read(md); |         ProjectConfig config = ProjectConfig.read(md); | ||||||
|         Project project = config.getProject(); |         Project project = config.getProject(); | ||||||
|         project.setParentName(input.parent); |         project.setParentName(Strings.emptyToNull(input.parent)); | ||||||
|  |  | ||||||
|         String msg = Strings.emptyToNull(input.commitMessage); |         String msg = Strings.emptyToNull(input.commitMessage); | ||||||
|         if (msg == null) { |         if (msg == null) { | ||||||
| @@ -124,4 +97,39 @@ class SetParent implements RestModifyView<ProjectResource, Input> { | |||||||
|           "invalid project.config: %s", e.getMessage())); |           "invalid project.config: %s", e.getMessage())); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   public void validateParentUpdate(final ProjectControl ctl, String newParent) | ||||||
|  |       throws AuthException, ResourceConflictException, | ||||||
|  |       UnprocessableEntityException { | ||||||
|  |     IdentifiedUser user = (IdentifiedUser) ctl.getCurrentUser(); | ||||||
|  |     if (!user.getCapabilities().canAdministrateServer()) { | ||||||
|  |       throw new AuthException("not administrator"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (ctl.getProject().getNameKey().equals(allProjects)) { | ||||||
|  |       throw new ResourceConflictException("cannot set parent of " | ||||||
|  |           + allProjects.get()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     newParent = Strings.emptyToNull(newParent); | ||||||
|  |     if (newParent != null) { | ||||||
|  |       ProjectState parent = cache.get(new Project.NameKey(newParent)); | ||||||
|  |       if (parent == null) { | ||||||
|  |         throw new UnprocessableEntityException("parent project " + newParent | ||||||
|  |             + " not found"); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (Iterables.tryFind(parent.tree(), new Predicate<ProjectState>() { | ||||||
|  |         @Override | ||||||
|  |         public boolean apply(ProjectState input) { | ||||||
|  |           return input.getProject().getNameKey() | ||||||
|  |               .equals(ctl.getProject().getNameKey()); | ||||||
|  |         } | ||||||
|  |       }).isPresent()) { | ||||||
|  |         throw new ResourceConflictException("cycle exists between " | ||||||
|  |             + ctl.getProject().getName() + " and " | ||||||
|  |             + parent.getProject().getName()); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Edwin Kempin
					Edwin Kempin