diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt index ef653cc214..90212fb51f 100644 --- a/Documentation/cmd-index.txt +++ b/Documentation/cmd-index.txt @@ -120,7 +120,7 @@ link:cmd-gc.html[gerrit gc]:: link:cmd-gsql.html[gerrit gsql]:: Administrative interface to active database. -link:cmd-index-index.html[gerrit index activate]:: +link:cmd-index-activate.html[gerrit index activate]:: Activate the latest index version available. link:cmd-index-start.html[gerrit index start]:: diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt index ce9b1b30f6..a38f9fcbee 100644 --- a/Documentation/config-gerrit.txt +++ b/Documentation/config-gerrit.txt @@ -1394,21 +1394,37 @@ Type of database server to connect to. If set this value will be used to automatically create correct database.driver and database.url values to open the connection. + -* `POSTGRESQL` +* `DB2` + -Connect to a PostgreSQL database server. +Connect to a DB2 database server. ++ +* `DERBY` ++ +Connect to an Apache Derby database server. + * `H2` + Connect to a local embedded H2 database. + +* `JDBC` ++ +Connect using a JDBC driver class name and URL. ++ +* `MAXDB` ++ +Connect to an SAP MaxDb database server. ++ * `MYSQL` + Connect to a MySQL database server. + -* `JDBC` +* `ORACLE` + -Connect using a JDBC driver class name and URL. +Connect to an Oracle database server. ++ +* `POSTGRESQL` ++ +Connect to a PostgreSQL database server. + If not specified, database.driver and database.url are used as-is, diff --git a/ReleaseNotes/ReleaseNotes-2.12.1.txt b/ReleaseNotes/ReleaseNotes-2.12.1.txt new file mode 100644 index 0000000000..379572edec --- /dev/null +++ b/ReleaseNotes/ReleaseNotes-2.12.1.txt @@ -0,0 +1,189 @@ +Release notes for Gerrit 2.12.1 +=============================== + +Gerrit 2.12.1 is now available: + +link:https://gerrit-releases.storage.googleapis.com/gerrit-2.12.1.war[ +https://gerrit-releases.storage.googleapis.com/gerrit-2.12.1.war] + +Gerrit 2.12.1 includes the bug fixes done with +link:ReleaseNotes-2.11.6.html[Gerrit 2.11.6] and +link:ReleaseNotes-2.11.7.html[Gerrit 2.11.7]. These bug fixes are *not* +listed in these release notes. + +*WARNING:* This version includes a manual schema upgrade when upgrading +from 2.12. ++ +If you have already upgraded to 2.12, you need to issue this SQL statement +manually (e.g. using the `gerrit gsql` SSH command or the `gqsl` site +program): ++ + alter table patch_sets modify push_certficate clob; ++ +Or with this command if the site is configured to use PostgreSQL: ++ + alter table patch_sets alter column push_certficate type text; ++ +Note that the misspelled `push_certficate` is the actual name of the +column. ++ +If you are upgrading from a version earlier than 2.12, this manual step is +not necessary and should be omitted. + + +Bug Fixes +--------- + +General +^^^^^^^ + +* Fix column type for signed push certificates. ++ +The column type `VARCHAR(255)` was too small, preventing some PGP push +certificates from being stored. + +* Add the `DRAFT_COMMENTS` option to the list changes REST API endpoint +and mark it as deprecated. ++ +It was removed in version 2.12 because it's not needed any more by the UI, +but this caused failures for clients that still use it. ++ +Now it is added back, although it does not do anything and is marked as +deprecated. + +* link:https://code.google.com/p/gerrit/issues/detail?id=3669[Issue 3669]: +Fix schema migration when migrating to 2.12.x directly from a version +earlier than 2.11. + +* link:https://code.google.com/p/gerrit/issues/detail?id=3733[Issue 3733]: +Correctly detect symlinked log directory on startup. ++ +If `$site_path/logs` was a symlink, the server would not start. + +* link:https://code.google.com/p/gerrit/issues/detail?id=3871[Issue 3871]: +Throw an explicit exception when failing to load a change from the database. ++ +If a change could not be loaded from the database, for example if it was +manually removed from the changes table but references to it were remaining +in other tables, a null change was returned which would then lead to an +'Internal Server Error' that was difficult to track down. Now an error is +raised earlier which will help administrators to find the root cause. + +* https://code.google.com/p/gerrit/issues/detail?id=3743[Issue 3743]: +Use submitter identity as committer when using 'Rebase if Necessary' merge +strategy. ++ +When submitting a change that required rebase, the committer was being +set to 'Gerrit Code Review' instead of the name of the submitter. + +* link:https://code.google.com/p/gerrit/issues/detail?id=3758[Issue 3758]: +Fix serving of static resources when deployed in application container. ++ +When deployed in a container, for example Tomcat, it was not possible to +load the UI because static content could not be loaded from the WAR file. + +* link:https://code.google.com/p/gerrit/issues/detail?id=3790[Issue 3790]: +When deployed in a container, for example Tomcat, the 'Documentation' menu +was missing. + +* link:https://code.google.com/p/gerrit/issues/detail?id=3786[Issue 3786]: +Fix SQL statement syntax in schema migration. ++ +An extra semicolon was preventing migration from 2.11.x to 2.12 when using +an Oracle database. + +* Send email using email queue instead of the default queue. ++ +Some emails sent asynchronously were already being sent using that queue +but some were not. This was confusing for a gerrit administrator because +if there is a build up of `send-email` tasks in the queue, he would +think that increasing `sendemail.threadPoolSize` would help but it did not +because some of the email were sent using the default queue which is +configurable using `execution.defaultThreadPoolSize`. + +* Fix XSRF token cookie to honor `auth.cookieSecure` setting. + +* link:https://code.google.com/p/gerrit/issues/detail?id=3767[Issue 3767]: +Fix replication of first patch set for new changes. ++ +When new changes were pushed from the command line, the first patch +set did not get replicated to destinations. + +* link:https://code.google.com/p/gerrit/issues/detail?id=3771[Issue 3771]: +Remove `index.defaultMaxClauseCount` configuration option. ++ +When `index.maxTerms` was either not set (thus no limit) or set to a value +higher than `index.defaultMaxClauseCount` it was possible that viewing the +related changes tab could cause a 'Too many clauses' error for changes that +have a lot of related changes. ++ +The `index.defaultMaxClauseCount` configuration option is removed, and the +existing `index.maxTerms` is reused. The default value of `index.maxTerms` +is reduced from 'no limit' to 1024. + +* link:https://code.google.com/p/gerrit/issues/detail?id=3919[Issue 3919]: +Explicitly set parent project to 'All-Projects' when a project is created +without giving the parent. + +UI +^^ + +* link:https://code.google.com/p/gerrit/issues/detail?id=3071[Issue 3071]: +Fix display of submodule differences in side-by-side view. + +* link:https://code.google.com/p/gerrit/issues/detail?id=3718[Issue 3718]: +Hide avatar images when no avatars are available. ++ +The UI was showing a transparent empty image with a border. + +* link:https://code.google.com/p/gerrit/issues/detail?id=3731[Issue 3731]: +Fix syntax higlighting of tcl files. + +* link:https://code.google.com/p/gerrit/issues/detail?id=3863[Issue 3863]: +Fix display of active row marker in tag list. ++ +Clicking on one of the rows would cause the tag name to disappear. + +* link:https://code.google.com/p/gerrit/issues/detail?id=1207[Issue 1207]: +Fix keyboard shortcuts for non-US keyboards on side-by-side diff screen. ++ +The forward/backward navigation keys `[` and `]` only worked on keyboards where +these characters could be typed without using any modifier key (like CTRL, ALT, +etc..). ++ +Note that the problem still exists on the unified diff screen. + +Plugins +^^^^^^^ + +* Allow plugins to get the caller in merge validation requests. ++ +Plugins that implement the `MergeValidationListener` interface now get the +caller (the user who initiated the merge) in the `onPreMerge` method. ++ +Existing plugins that implement this interface must be adapted to the new +method signature. + +* link:https://code.google.com/p/gerrit/issues/detail?id=3741[Issue 3741]: +Fix handling of merge validation exceptions emitted by plugins. ++ +If a plugin raised an exception, it was reported to the user as 'Change is +new', rather than 'Missing dependency'. + +* link:https://code.google.com/p/gerrit/issues/detail?id=3821[Issue 3821]: +Fix repeated reloading of plugins when running on OpenJDK 8. ++ +OpenJDK 8 uses nanotime precision for file modification time on systems that +are POSIX 2008 compatible. This leads to precision incompatibility when +comparing the plugin's JAR file timestamp, resulting in the plugin being +reloaded every minute. + +Documentation +^^^^^^^^^^^^^ + +* Update documentation of `commentlink` to reflect changed search URL. + +Upgrades +-------- + +* Upgrade JGit to 4.1.2.201602141800-r. diff --git a/ReleaseNotes/index.txt b/ReleaseNotes/index.txt index fa57dab590..af33d7a24c 100644 --- a/ReleaseNotes/index.txt +++ b/ReleaseNotes/index.txt @@ -9,6 +9,7 @@ Version 2.13.x [[2_12]] Version 2.12.x -------------- +* link:ReleaseNotes-2.12.1.html[2.12.1] * link:ReleaseNotes-2.12.html[2.12] [[2_11]] diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java index 35e86c5b55..291b953205 100644 --- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java +++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java @@ -43,6 +43,8 @@ import org.eclipse.jgit.transport.PushResult; import org.eclipse.jgit.transport.RemoteRefUpdate; import org.eclipse.jgit.transport.RemoteRefUpdate.Status; +import java.util.List; + public class PushOneCommit { public static final String SUBJECT = "test commit"; public static final String FILE_NAME = "a.txt"; @@ -179,6 +181,13 @@ public class PushOneCommit { .committer(new PersonIdent(i, testRepo.getDate())); } + public void setParents(List parents) throws Exception { + commitBuilder.noParents(); + for (RevCommit p : parents) { + commitBuilder.parent(p); + } + } + public Result to(String ref) throws Exception { commitBuilder.add(fileName, content); return execute(ref); @@ -189,7 +198,7 @@ public class PushOneCommit { return execute(ref); } - private Result execute(String ref) throws Exception { + public Result execute(String ref) throws Exception { RevCommit c = commitBuilder.create(); if (changeId == null) { changeId = GitUtil.getChangeId(testRepo, c).get(); diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java index d6c8dacb00..e5281b1568 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java @@ -103,7 +103,7 @@ public class ActionsIT extends AbstractDaemonTest { assertThat(info.label).isEqualTo("Submit whole topic"); assertThat(info.method).isEqualTo("POST"); assertThat(info.title).isEqualTo( - "Clicking the button would fail for other changes"); + "See the \"Submitted Together\" tab for problems"); } else { noSubmitWholeTopicAssertions(actions, 1); } diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java index e09c63acc4..b77fb01ed2 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java @@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertThat; import com.google.gerrit.acceptance.AbstractDaemonTest; import com.google.gerrit.acceptance.RestResponse; import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.server.config.AllProjectsNameProvider; import com.google.gerrit.server.project.SetParent; import org.junit.Test; @@ -49,6 +50,19 @@ public class SetParentIT extends AbstractDaemonTest { newGson().fromJson(r.getReader(), String.class); assertThat(newParent).isEqualTo(parent); r.consume(); + + // When the parent name is not explicitly set, it should be + // set to "All-Projects". + r = adminSession.put("/projects/" + project.get() + "/parent", + newParentInput(null)); + r.assertOK(); + r.consume(); + + r = adminSession.get("/projects/" + project.get() + "/parent"); + r.assertOK(); + newParent = newGson().fromJson(r.getReader(), String.class); + assertThat(newParent).isEqualTo(AllProjectsNameProvider.DEFAULT); + r.consume(); } @Test diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommandSet.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommandSet.java index e2fec27a45..26252226d7 100644 --- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommandSet.java +++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommandSet.java @@ -136,15 +136,6 @@ public class KeyCommandSet implements KeyPressHandler { if (mask == 0) { mask = event.getNativeEvent().getKeyCode(); } - if (event.isAltKeyDown()) { - mask |= KeyCommand.M_ALT; - } - if (event.isControlKeyDown()) { - mask |= KeyCommand.M_CTRL; - } - if (event.isMetaKeyDown()) { - mask |= KeyCommand.M_META; - } return mask; } } diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.java index c652cb2c0c..2996c07238 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.java @@ -368,8 +368,8 @@ public class SideBySide extends Screen { KeyMap keyMap = KeyMap.create() .on("A", upToChange(true)) .on("U", upToChange(false)) - .on("[", header.navigate(Direction.PREV)) - .on("]", header.navigate(Direction.NEXT)) + .on("'['", header.navigate(Direction.PREV)) + .on("']'", header.navigate(Direction.NEXT)) .on("R", header.toggleReviewed()) .on("O", commentManager.toggleOpenBox(cm)) .on("Enter", commentManager.toggleOpenBox(cm)) diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java index 154f8d963d..ff5bae1416 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java @@ -85,8 +85,8 @@ public class Submit implements RestModifyView, "This change depends on other hidden changes which are not ready"; private static final String CLICK_FAILURE_TOOLTIP = "Clicking the button would fail"; - private static final String CLICK_FAILURE_OTHER_TOOLTIP = - "Clicking the button would fail for other changes"; + private static final String CHANGES_NOT_MERGEABLE = + "See the \"Submitted Together\" tab for problems"; public static class Output { transient Change change; @@ -258,7 +258,7 @@ public class Submit implements RestModifyView, return CLICK_FAILURE_TOOLTIP; } if (!mergeable) { - return CLICK_FAILURE_OTHER_TOOLTIP; + return CHANGES_NOT_MERGEABLE; } MergeOp.checkSubmitRule(c); } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java index bc9b2a1212..f54e071082 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java @@ -20,11 +20,8 @@ import com.google.gerrit.extensions.registration.DynamicMap.Entry; import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.reviewdb.client.Branch; import com.google.gerrit.reviewdb.client.PatchSet; -import com.google.gerrit.reviewdb.client.PatchSetApproval; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.RefNames; -import com.google.gerrit.reviewdb.server.ReviewDb; -import com.google.gerrit.server.ApprovalsUtil; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.config.AllProjectsName; import com.google.gerrit.server.config.PluginConfig; @@ -97,9 +94,7 @@ public class MergeValidators { + "The change must be submitted by a Gerrit administrator."; private final AllProjectsName allProjectsName; - private final ReviewDb db; private final ProjectCache projectCache; - private final ApprovalsUtil approvalsUtil; private final DynamicMap pluginConfigEntries; public interface Factory { @@ -108,13 +103,10 @@ public class MergeValidators { @Inject public ProjectConfigValidator(AllProjectsName allProjectsName, - ReviewDb db, ProjectCache projectCache, - ApprovalsUtil approvalsUtil, + ProjectCache projectCache, DynamicMap pluginConfigEntries) { this.allProjectsName = allProjectsName; - this.db = db; this.projectCache = projectCache; - this.approvalsUtil = approvalsUtil; this.pluginConfigEntries = pluginConfigEntries; } @@ -142,11 +134,6 @@ public class MergeValidators { } } else { if (!oldParent.equals(newParent)) { - PatchSetApproval psa = - approvalsUtil.getSubmitter(db, commit.notes(), patchSetId); - if (psa == null) { - throw new MergeValidationException(SET_BY_ADMIN); - } if (!caller.getCapabilities().canAdministrateServer()) { throw new MergeValidationException(SET_BY_ADMIN); } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java index e479ba9880..2704be8746 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java @@ -150,9 +150,13 @@ public class PatchFile { if (tw == null) { return Text.EMPTY; } - if (tw.getFileMode(0).getObjectType() != Constants.OBJ_BLOB) { + if (tw.getFileMode(0).getObjectType() == Constants.OBJ_BLOB) { + return new Text(repo.open(tw.getObjectId(0), Constants.OBJ_BLOB)); + } else if (tw.getFileMode(0).getObjectType() == Constants.OBJ_COMMIT) { + String str = "Subproject commit " + ObjectId.toString(tw.getObjectId(0)); + return new Text(str.getBytes()); + } else { return Text.EMPTY; } - return new Text(repo.open(tw.getObjectId(0), Constants.OBJ_BLOB)); } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java index 2caa937345..2b5e2354c0 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java @@ -32,7 +32,6 @@ import com.google.gerrit.reviewdb.client.PatchSet; import org.eclipse.jgit.diff.Edit; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.patch.CombinedFileHeader; import org.eclipse.jgit.patch.FileHeader; import org.eclipse.jgit.util.IntList; @@ -97,10 +96,7 @@ public class PatchListEntry { header = compact(hdr); - if (hdr instanceof CombinedFileHeader - || hdr.getHunks().isEmpty() // - || hdr.getOldMode() == FileMode.GITLINK - || hdr.getNewMode() == FileMode.GITLINK) { + if (hdr instanceof CombinedFileHeader || hdr.getHunks().isEmpty()) { edits = Collections.emptyList(); } else { edits = Collections.unmodifiableList(editList); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java index 10ea61cf1c..63693e6bc1 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java @@ -312,14 +312,6 @@ public class PatchListLoader implements Callable { private PatchListEntry newEntry(RevTree aTree, FileHeader fileHeader, long size, long sizeDelta) { - final FileMode oldMode = fileHeader.getOldMode(); - final FileMode newMode = fileHeader.getNewMode(); - - if (oldMode == FileMode.GITLINK || newMode == FileMode.GITLINK) { - return new PatchListEntry(fileHeader, Collections. emptyList(), - size, sizeDelta); - } - if (aTree == null // want combined diff || fileHeader.getPatchType() != PatchType.UNIFIED || fileHeader.getHunks().isEmpty()) { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java index 9072c2aa8a..51c70f5db7 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java @@ -179,9 +179,7 @@ class PatchScriptBuilder { } boolean hugeFile = false; - if (a.mode == FileMode.GITLINK || b.mode == FileMode.GITLINK) { - // Do nothing - } else if (a.src == b.src && a.size() <= context + if (a.src == b.src && a.size() <= context && content.getEdits().isEmpty()) { // Odd special case; the files are identical (100% rename or copy) // and the user has asked for context that is larger than the file. @@ -471,6 +469,10 @@ class PatchScriptBuilder { } else if (mode.getObjectType() == Constants.OBJ_BLOB) { srcContent = Text.asByteArray(db.open(id, Constants.OBJ_BLOB)); + } else if (mode.getObjectType() == Constants.OBJ_COMMIT) { + String strContent = "Subproject commit " + ObjectId.toString(id); + srcContent = strContent.getBytes(); + } else { srcContent = Text.NO_BYTES; } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java index 445c8c5407..fec858a6c4 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java @@ -44,6 +44,7 @@ import com.google.gerrit.reviewdb.client.RefNames; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.GerritPersonIdent; import com.google.gerrit.server.account.GroupBackend; +import com.google.gerrit.server.config.AllProjectsName; import com.google.gerrit.server.config.ProjectOwnerGroupsProvider; import com.google.gerrit.server.config.RepositoryConfig; import com.google.gerrit.server.extensions.events.GitReferenceUpdated; @@ -102,6 +103,7 @@ public class CreateProject implements RestModifyView currentUser; private final Provider putConfig; + private final AllProjectsName allProjects; private final String name; @Inject @@ -120,6 +122,7 @@ public class CreateProject implements RestModifyView currentUser, Provider putConfig, + AllProjectsName allProjects, @Assisted String name) { this.projectsCollection = projectsCollection; this.groupsCollection = groupsCollection; @@ -137,6 +140,7 @@ public class CreateProject implements RestModifyView { throws AuthException, ResourceConflictException, ResourceNotFoundException, UnprocessableEntityException, IOException { ProjectControl ctl = rsrc.getControl(); - validateParentUpdate(ctl, input.parent, checkIfAdmin); + String parentName = MoreObjects.firstNonNull( + Strings.emptyToNull(input.parent), allProjects.get()); + validateParentUpdate(ctl, parentName, checkIfAdmin); try (MetaDataUpdate md = updateFactory.create(rsrc.getNameKey())) { ProjectConfig config = ProjectConfig.read(md); Project project = config.getProject(); - project.setParentName(Strings.emptyToNull(input.parent)); + project.setParentName(parentName); String msg = Strings.emptyToNull(input.commitMessage); if (msg == null) { msg = String.format( - "Changed parent to %s.\n", - MoreObjects.firstNonNull(project.getParentName(), - allProjects.get())); + "Changed parent to %s.\n", parentName); } else if (!msg.endsWith("\n")) { msg += "\n"; } @@ -90,8 +92,9 @@ public class SetParent implements RestModifyView { config.commit(md); cache.evict(ctl.getProject()); - Project.NameKey parentName = project.getParent(allProjects); - return parentName != null ? parentName.get() : ""; + Project.NameKey parent = project.getParent(allProjects); + checkNotNull(parent); + return parent.get(); } catch (RepositoryNotFoundException notFound) { throw new ResourceNotFoundException(rsrc.getName()); } catch (ConfigInvalidException e) {