Merge changes from topic 'check'
* changes: ChangeJson: Catch RuntimeException in fallback check handlers Support fixing corrupt changes in ConsistencyChecker Add API method for checking changes Check consistency with a ListChangesOption Embed consistency check results in ChangeInfo result ChangeData: Handle null PatchSet in isMergeable
This commit is contained in:
		| @@ -300,6 +300,11 @@ default. Optional fields are: | ||||
| * `WEB_LINKS`: include the `web_links` field. | ||||
| -- | ||||
|  | ||||
| [[check]] | ||||
| -- | ||||
| * `CHECK`: include potential problems with the change. | ||||
| -- | ||||
|  | ||||
| .Request | ||||
| ---- | ||||
|   GET /changes/?q=97&o=CURRENT_REVISION&o=CURRENT_COMMIT&o=CURRENT_FILES&o=DOWNLOAD_COMMANDS HTTP/1.0 | ||||
| @@ -1143,7 +1148,12 @@ Adds or updates the change in the secondary index. | ||||
| -- | ||||
|  | ||||
| Performs consistency checks on the change, and returns a | ||||
| link:#check-result[CheckResult] entity. | ||||
| link:#change-info[ChangeInfo] entity with the `problems` field set to a | ||||
| list of link:#problem-info[ProblemInfo] entities. | ||||
|  | ||||
| Depending on the type of problem, some fields not marked optional may be | ||||
| missing from the result. At least `id`, `project`, `branch`, and | ||||
| `_number` will be present. | ||||
|  | ||||
| .Request | ||||
| ---- | ||||
| @@ -1158,26 +1168,80 @@ link:#check-result[CheckResult] entity. | ||||
|  | ||||
|   )]}' | ||||
|   { | ||||
|     "change": { | ||||
|       "id": "myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940", | ||||
|       "project": "myProject", | ||||
|       "branch": "master", | ||||
|       "change_id": "I8473b95934b5732ac55d26311a706c9c2bde9940", | ||||
|       "subject": "Implementing Feature X", | ||||
|       "status": "NEW", | ||||
|       "created": "2013-02-01 09:59:32.126000000", | ||||
|       "updated": "2013-02-21 11:16:36.775000000", | ||||
|       "mergeable": true, | ||||
|       "insertions": 34, | ||||
|       "deletions": 101, | ||||
|       "_sortkey": "0023412400000f7d", | ||||
|       "_number": 3965, | ||||
|       "owner": { | ||||
|         "name": "John Doe" | ||||
|       } | ||||
|     "id": "myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940", | ||||
|     "project": "myProject", | ||||
|     "branch": "master", | ||||
|     "change_id": "I8473b95934b5732ac55d26311a706c9c2bde9940", | ||||
|     "subject": "Implementing Feature X", | ||||
|     "status": "NEW", | ||||
|     "created": "2013-02-01 09:59:32.126000000", | ||||
|     "updated": "2013-02-21 11:16:36.775000000", | ||||
|     "mergeable": true, | ||||
|     "insertions": 34, | ||||
|     "deletions": 101, | ||||
|     "_sortkey": "0023412400000f7d", | ||||
|     "_number": 3965, | ||||
|     "owner": { | ||||
|       "name": "John Doe" | ||||
|     }, | ||||
|     "messages": [ | ||||
|       "Current patch set 1 not found" | ||||
|     "problems": [ | ||||
|       { | ||||
|         "message": "Current patch set 1 not found" | ||||
|       } | ||||
|     ] | ||||
|   } | ||||
| ---- | ||||
|  | ||||
| [[fix-change]] | ||||
| === Fix change | ||||
| -- | ||||
| 'POST /changes/link:#change-id[\{change-id\}]/check' | ||||
| -- | ||||
|  | ||||
| Performs consistency checks on the change as with link:#check-change[GET | ||||
| /check], and additionally fixes any problems that can be fixed | ||||
| automatically. The returned field values reflect any fixes. | ||||
|  | ||||
| Only the change owner, a project owner, or an administrator may fix changes. | ||||
|  | ||||
| .Request | ||||
| ---- | ||||
|   POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/check HTTP/1.0 | ||||
| ---- | ||||
|  | ||||
| .Response | ||||
| ---- | ||||
|   HTTP/1.1 200 OK | ||||
|   Content-Disposition: attachment | ||||
|   Content-Type: application/json;charset=UTF-8 | ||||
|  | ||||
|   )]}' | ||||
|   { | ||||
|     "id": "myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940", | ||||
|     "project": "myProject", | ||||
|     "branch": "master", | ||||
|     "change_id": "I8473b95934b5732ac55d26311a706c9c2bde9940", | ||||
|     "subject": "Implementing Feature X", | ||||
|     "status": "MERGED", | ||||
|     "created": "2013-02-01 09:59:32.126000000", | ||||
|     "updated": "2013-02-21 11:16:36.775000000", | ||||
|     "mergeable": true, | ||||
|     "insertions": 34, | ||||
|     "deletions": 101, | ||||
|     "_sortkey": "0023412400000f7d", | ||||
|     "_number": 3965, | ||||
|     "owner": { | ||||
|       "name": "John Doe" | ||||
|     }, | ||||
|     "problems": [ | ||||
|       { | ||||
|         "message": "Current patch set 2 not found" | ||||
|       }, | ||||
|       { | ||||
|         "message": "Patch set 1 (1eee2c9d8f352483781e772f35dc586a69ff5646) is merged into destination ref master (1eee2c9d8f352483781e772f35dc586a69ff5646), but change status is NEW", | ||||
|         "status": FIXED, | ||||
|         "outcome": "Marked change as merged" | ||||
|       } | ||||
|     ] | ||||
|   } | ||||
| ---- | ||||
| @@ -3259,6 +3323,9 @@ if link:#all-revisions[all revisions] are requested. | ||||
| |`_more_changes`      |optional, not set if `false`| | ||||
| Whether the query would deliver more results if not limited. + | ||||
| Only set on either the last or the first change that is returned. | ||||
| |`problems`           |optional| | ||||
| A list of link:#problem-info[ProblemInfo] entities describing potential | ||||
| problems with this change. Only set if link:#check[CHECK] is set. | ||||
| |================================== | ||||
|  | ||||
| [[related-changes-info]] | ||||
| @@ -3978,22 +4045,23 @@ the commit message within a change edit. | ||||
| |`message`     ||New commit message. | ||||
| |=========================== | ||||
|  | ||||
| [[check-result]] | ||||
| === CheckResult | ||||
| The `CheckResult` entity contains the results of a consistency check on | ||||
| a change. | ||||
| [[problem-info]] | ||||
| === ProblemInfo | ||||
| The `ProblemInfo` entity contains a description of a potential consistency problem | ||||
| with a change. These are not related to the code review process, but rather | ||||
| indicate some inconsistency in Gerrit's database or repository metadata related | ||||
| to the enclosing change. | ||||
|  | ||||
| [options="header",cols="1,6"] | ||||
| [options="header",cols="1,^1,5"] | ||||
| |=========================== | ||||
| |Field Name|Description | ||||
| |`change`| | ||||
| link:#change-info[ChangeInfo] entity containing information about the change, | ||||
| as in link:#get-change[Get Change] with no options. Some fields not marked | ||||
| optional may be missing if a consistency check failed, but at least | ||||
| `id`, `project`, `branch`, and `_number` will be present. | ||||
| |`messages`| | ||||
| List of messages describing potential problems with the change. May be | ||||
| empty if no problems were found. | ||||
| |Field Name||Description | ||||
| |`message` ||Plaintext message describing the problem with the change. | ||||
| |`status`  |optional| | ||||
| The status of fixing the problem (`FIXED`, `FIX_FAILED`). Only set if a | ||||
| fix was attempted. | ||||
| |`outcome` |optional| | ||||
| If `status` is set, an additional plaintext message describing the | ||||
| outcome of the fix. | ||||
| |=========================== | ||||
|  | ||||
| GERRIT | ||||
|   | ||||
| @@ -301,4 +301,17 @@ public class ChangeIT extends AbstractDaemonTest { | ||||
|         .id(r.getChangeId()) | ||||
|         .topic()).isEqualTo(""); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void check() throws Exception { | ||||
|     PushOneCommit.Result r = createChange(); | ||||
|     assertThat(gApi.changes() | ||||
|         .id(r.getChangeId()) | ||||
|         .get() | ||||
|         .problems).isNull(); | ||||
|     assertThat(gApi.changes() | ||||
|         .id(r.getChangeId()) | ||||
|         .get(EnumSet.of(ListChangesOption.CHECK)) | ||||
|         .problems).isEmpty(); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,88 @@ | ||||
| // Copyright (C) 2014 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.acceptance.api.change; | ||||
|  | ||||
| import static com.google.common.truth.Truth.assertThat; | ||||
|  | ||||
| import com.google.gerrit.acceptance.AbstractDaemonTest; | ||||
| import com.google.gerrit.acceptance.NoHttpd; | ||||
| import com.google.gerrit.acceptance.PushOneCommit; | ||||
| import com.google.gerrit.extensions.api.changes.FixInput; | ||||
| import com.google.gerrit.extensions.api.changes.ReviewInput; | ||||
| import com.google.gerrit.extensions.common.ChangeInfo; | ||||
| import com.google.gerrit.extensions.common.ChangeStatus; | ||||
| import com.google.gerrit.extensions.common.ProblemInfo; | ||||
| import com.google.gerrit.reviewdb.client.Change; | ||||
|  | ||||
| import org.junit.Test; | ||||
|  | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
|  | ||||
| @NoHttpd | ||||
| public class CheckIT extends AbstractDaemonTest { | ||||
|   // Most types of tests belong in ConsistencyCheckerTest; these mostly just | ||||
|   // test paths outside of ConsistencyChecker, like API wiring. | ||||
|   @Test | ||||
|   public void currentPatchSetMissing() throws Exception { | ||||
|     PushOneCommit.Result r = createChange(); | ||||
|     Change c = getChange(r); | ||||
|     db.patchSets().deleteKeys(Collections.singleton(c.currentPatchSetId())); | ||||
|  | ||||
|     List<ProblemInfo> problems = gApi.changes() | ||||
|         .id(r.getChangeId()) | ||||
|         .check() | ||||
|         .problems; | ||||
|     assertThat(problems).hasSize(1); | ||||
|     assertThat(problems.get(0).message) | ||||
|         .isEqualTo("Current patch set 1 not found"); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void fixReturnsUpdatedValue() throws Exception { | ||||
|     PushOneCommit.Result r = createChange(); | ||||
|     gApi.changes() | ||||
|         .id(r.getChangeId()) | ||||
|         .revision(r.getCommit().name()) | ||||
|         .review(ReviewInput.approve()); | ||||
|     gApi.changes() | ||||
|         .id(r.getChangeId()) | ||||
|         .revision(r.getCommit().name()) | ||||
|         .submit(); | ||||
|  | ||||
|     Change c = getChange(r); | ||||
|     c.setStatus(Change.Status.NEW); | ||||
|     db.changes().update(Collections.singleton(c)); | ||||
|  | ||||
|     ChangeInfo info = gApi.changes() | ||||
|         .id(r.getChangeId()) | ||||
|         .check(); | ||||
|     assertThat(info.problems).hasSize(1); | ||||
|     assertThat(info.problems.get(0).status).isNull(); | ||||
|     assertThat(info.status).isEqualTo(ChangeStatus.NEW); | ||||
|  | ||||
|     info = gApi.changes() | ||||
|         .id(r.getChangeId()) | ||||
|         .check(new FixInput()); | ||||
|     assertThat(info.problems).hasSize(1); | ||||
|     assertThat(info.problems.get(0).status).isEqualTo(ProblemInfo.Status.FIXED); | ||||
|     assertThat(info.status).isEqualTo(ChangeStatus.MERGED); | ||||
|   } | ||||
|  | ||||
|   private Change getChange(PushOneCommit.Result r) throws Exception { | ||||
|     return db.changes().get(new Change.Id( | ||||
|         gApi.changes().id(r.getChangeId()).get()._number)); | ||||
|   } | ||||
| } | ||||
| @@ -81,9 +81,9 @@ public interface ChangeApi { | ||||
|  | ||||
|   ChangeInfo get(EnumSet<ListChangesOption> options) throws RestApiException; | ||||
|  | ||||
|   /** {@code get} with {@link ListChangesOption} set to ALL. */ | ||||
|   /** {@code get} with {@link ListChangesOption} set to all except CHECK. */ | ||||
|   ChangeInfo get() throws RestApiException; | ||||
|   /** {@code get} with {@link ListChangesOption} set to NONE. */ | ||||
|   /** {@code get} with {@link ListChangesOption} set to none. */ | ||||
|   ChangeInfo info() throws RestApiException; | ||||
|  | ||||
|   /** | ||||
| @@ -98,6 +98,9 @@ public interface ChangeApi { | ||||
|    */ | ||||
|   Set<String> getHashtags() throws RestApiException; | ||||
|  | ||||
|   ChangeInfo check() throws RestApiException; | ||||
|   ChangeInfo check(FixInput fix) throws RestApiException; | ||||
|  | ||||
|   /** | ||||
|    * A default implementation which allows source compatibility | ||||
|    * when adding new methods to the interface. | ||||
| @@ -197,5 +200,15 @@ public interface ChangeApi { | ||||
|     public Set<String> getHashtags() throws RestApiException { | ||||
|       throw new NotImplementedException(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public ChangeInfo check() throws RestApiException { | ||||
|       throw new NotImplementedException(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public ChangeInfo check(FixInput fix) throws RestApiException { | ||||
|       throw new NotImplementedException(); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,18 @@ | ||||
| // Copyright (C) 2014 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.extensions.api.changes; | ||||
|  | ||||
| public class FixInput { | ||||
| } | ||||
| @@ -16,6 +16,7 @@ package com.google.gerrit.extensions.common; | ||||
|  | ||||
| import java.sql.Timestamp; | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| public class ChangeInfo { | ||||
| @@ -50,4 +51,6 @@ public class ChangeInfo { | ||||
|   public String currentRevision; | ||||
|   public Map<String, RevisionInfo> revisions; | ||||
|   public Boolean _moreChanges; | ||||
|  | ||||
|   public List<ProblemInfo> problems; | ||||
| } | ||||
|   | ||||
| @@ -52,7 +52,10 @@ public enum ListChangesOption { | ||||
|   DOWNLOAD_COMMANDS(13), | ||||
|  | ||||
|   /** Include patch set weblinks. */ | ||||
|   WEB_LINKS(14); | ||||
|   WEB_LINKS(14), | ||||
|  | ||||
|   /** Include consistency check results. */ | ||||
|   CHECK(15); | ||||
|  | ||||
|   private final int value; | ||||
|  | ||||
|   | ||||
| @@ -12,13 +12,14 @@ | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
| 
 | ||||
| package com.google.gerrit.server.change; | ||||
| package com.google.gerrit.extensions.common; | ||||
| 
 | ||||
| import com.google.gerrit.extensions.common.ChangeInfo; | ||||
| public class ProblemInfo { | ||||
|   public static enum Status { | ||||
|     FIXED, FIX_FAILED; | ||||
|   } | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| public class CheckResult { | ||||
|   public ChangeInfo change; | ||||
|   public List<String> messages; | ||||
|   public String message; | ||||
|   public Status status; | ||||
|   public String outcome; | ||||
| } | ||||
| @@ -19,6 +19,7 @@ import com.google.gerrit.extensions.api.changes.AbandonInput; | ||||
| import com.google.gerrit.extensions.api.changes.AddReviewerInput; | ||||
| import com.google.gerrit.extensions.api.changes.ChangeApi; | ||||
| import com.google.gerrit.extensions.api.changes.Changes; | ||||
| import com.google.gerrit.extensions.api.changes.FixInput; | ||||
| import com.google.gerrit.extensions.api.changes.HashtagsInput; | ||||
| import com.google.gerrit.extensions.api.changes.RestoreInput; | ||||
| import com.google.gerrit.extensions.api.changes.RevertInput; | ||||
| @@ -30,6 +31,7 @@ import com.google.gerrit.extensions.restapi.RestApiException; | ||||
| import com.google.gerrit.server.change.Abandon; | ||||
| import com.google.gerrit.server.change.ChangeJson; | ||||
| import com.google.gerrit.server.change.ChangeResource; | ||||
| import com.google.gerrit.server.change.Check; | ||||
| import com.google.gerrit.server.change.GetHashtags; | ||||
| import com.google.gerrit.server.change.GetTopic; | ||||
| import com.google.gerrit.server.change.PostHashtags; | ||||
| @@ -65,6 +67,7 @@ class ChangeApiImpl extends ChangeApi.NotImplemented implements ChangeApi { | ||||
|   private final Provider<ChangeJson> changeJson; | ||||
|   private final PostHashtags postHashtags; | ||||
|   private final GetHashtags getHashtags; | ||||
|   private final Check check; | ||||
|  | ||||
|   @Inject | ||||
|   ChangeApiImpl(Changes changeApi, | ||||
| @@ -79,6 +82,7 @@ class ChangeApiImpl extends ChangeApi.NotImplemented implements ChangeApi { | ||||
|       Provider<ChangeJson> changeJson, | ||||
|       PostHashtags postHashtags, | ||||
|       GetHashtags getHashtags, | ||||
|       Check check, | ||||
|       @Assisted ChangeResource change) { | ||||
|     this.changeApi = changeApi; | ||||
|     this.revert = revert; | ||||
| @@ -92,6 +96,7 @@ class ChangeApiImpl extends ChangeApi.NotImplemented implements ChangeApi { | ||||
|     this.changeJson = changeJson; | ||||
|     this.postHashtags = postHashtags; | ||||
|     this.getHashtags = getHashtags; | ||||
|     this.check = check; | ||||
|     this.change = change; | ||||
|   } | ||||
|  | ||||
| @@ -206,7 +211,7 @@ class ChangeApiImpl extends ChangeApi.NotImplemented implements ChangeApi { | ||||
|  | ||||
|   @Override | ||||
|   public ChangeInfo get() throws RestApiException { | ||||
|     return get(EnumSet.allOf(ListChangesOption.class)); | ||||
|     return get(EnumSet.complementOf(EnumSet.of(ListChangesOption.CHECK))); | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
| @@ -231,4 +236,22 @@ class ChangeApiImpl extends ChangeApi.NotImplemented implements ChangeApi { | ||||
|       throw new RestApiException("Cannot get hashtags", e); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public ChangeInfo check() throws RestApiException { | ||||
|     try { | ||||
|       return check.apply(change).value(); | ||||
|     } catch (OrmException e) { | ||||
|       throw new RestApiException("Cannot check change", e); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public ChangeInfo check(FixInput fix) throws RestApiException { | ||||
|     try { | ||||
|       return check.apply(change, fix).value(); | ||||
|     } catch (OrmException e) { | ||||
|       throw new RestApiException("Cannot check change", e); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -17,6 +17,7 @@ package com.google.gerrit.server.change; | ||||
| import static com.google.gerrit.extensions.common.ListChangesOption.ALL_COMMITS; | ||||
| import static com.google.gerrit.extensions.common.ListChangesOption.ALL_FILES; | ||||
| import static com.google.gerrit.extensions.common.ListChangesOption.ALL_REVISIONS; | ||||
| import static com.google.gerrit.extensions.common.ListChangesOption.CHECK; | ||||
| import static com.google.gerrit.extensions.common.ListChangesOption.CURRENT_ACTIONS; | ||||
| import static com.google.gerrit.extensions.common.ListChangesOption.CURRENT_COMMIT; | ||||
| import static com.google.gerrit.extensions.common.ListChangesOption.CURRENT_FILES; | ||||
| @@ -55,6 +56,7 @@ import com.google.gerrit.common.data.LabelValue; | ||||
| import com.google.gerrit.common.data.Permission; | ||||
| import com.google.gerrit.common.data.PermissionRange; | ||||
| import com.google.gerrit.common.data.SubmitRecord; | ||||
| import com.google.gerrit.extensions.api.changes.FixInput; | ||||
| import com.google.gerrit.extensions.common.AccountInfo; | ||||
| import com.google.gerrit.extensions.common.ActionInfo; | ||||
| import com.google.gerrit.extensions.common.ApprovalInfo; | ||||
| @@ -65,6 +67,7 @@ import com.google.gerrit.extensions.common.FetchInfo; | ||||
| import com.google.gerrit.extensions.common.GitPerson; | ||||
| import com.google.gerrit.extensions.common.LabelInfo; | ||||
| import com.google.gerrit.extensions.common.ListChangesOption; | ||||
| import com.google.gerrit.extensions.common.ProblemInfo; | ||||
| import com.google.gerrit.extensions.common.RevisionInfo; | ||||
| import com.google.gerrit.extensions.common.WebLinkInfo; | ||||
| import com.google.gerrit.extensions.config.DownloadCommand; | ||||
| @@ -141,8 +144,10 @@ public class ChangeJson { | ||||
|   private final EnumSet<ListChangesOption> options; | ||||
|   private final ChangeMessagesUtil cmUtil; | ||||
|   private final PatchLineCommentsUtil plcUtil; | ||||
|   private final Provider<ConsistencyChecker> checkerProvider; | ||||
|  | ||||
|   private AccountLoader accountLoader; | ||||
|   private FixInput fix; | ||||
|  | ||||
|   @Inject | ||||
|   ChangeJson( | ||||
| @@ -161,7 +166,8 @@ public class ChangeJson { | ||||
|       Revisions revisions, | ||||
|       WebLinks webLinks, | ||||
|       ChangeMessagesUtil cmUtil, | ||||
|       PatchLineCommentsUtil plcUtil) { | ||||
|       PatchLineCommentsUtil plcUtil, | ||||
|       Provider<ConsistencyChecker> checkerProvider) { | ||||
|     this.db = db; | ||||
|     this.labelNormalizer = ln; | ||||
|     this.userProvider = user; | ||||
| @@ -178,6 +184,7 @@ public class ChangeJson { | ||||
|     this.webLinks = webLinks; | ||||
|     this.cmUtil = cmUtil; | ||||
|     this.plcUtil = plcUtil; | ||||
|     this.checkerProvider = checkerProvider; | ||||
|     options = EnumSet.noneOf(ListChangesOption.class); | ||||
|   } | ||||
|  | ||||
| @@ -191,6 +198,11 @@ public class ChangeJson { | ||||
|     return this; | ||||
|   } | ||||
|  | ||||
|   public ChangeJson fix(FixInput fix) { | ||||
|     this.fix = fix; | ||||
|     return this; | ||||
|   } | ||||
|  | ||||
|   public ChangeInfo format(ChangeResource rsrc) throws OrmException { | ||||
|     return format(changeDataFactory.create(db.get(), rsrc.getControl())); | ||||
|   } | ||||
| @@ -200,7 +212,16 @@ public class ChangeJson { | ||||
|   } | ||||
|  | ||||
|   public ChangeInfo format(Change.Id id) throws OrmException { | ||||
|     return format(changeDataFactory.create(db.get(), id)); | ||||
|     Change c; | ||||
|     try { | ||||
|       c = db.get().changes().get(id); | ||||
|     } catch (OrmException e) { | ||||
|       if (!has(CHECK)) { | ||||
|         throw e; | ||||
|       } | ||||
|       return checkOnly(changeDataFactory.create(db.get(), id)); | ||||
|     } | ||||
|     return format(changeDataFactory.create(db.get(), c)); | ||||
|   } | ||||
|  | ||||
|   public ChangeInfo format(ChangeData cd) throws OrmException { | ||||
| @@ -209,14 +230,21 @@ public class ChangeJson { | ||||
|  | ||||
|   private ChangeInfo format(ChangeData cd, Optional<PatchSet.Id> limitToPsId) | ||||
|       throws OrmException { | ||||
|     accountLoader = accountLoaderFactory.create(has(DETAILED_ACCOUNTS)); | ||||
|     Set<Change.Id> reviewed = Sets.newHashSet(); | ||||
|     if (has(REVIEWED)) { | ||||
|       reviewed = loadReviewed(Collections.singleton(cd)); | ||||
|     try { | ||||
|       accountLoader = accountLoaderFactory.create(has(DETAILED_ACCOUNTS)); | ||||
|       Set<Change.Id> reviewed = Sets.newHashSet(); | ||||
|       if (has(REVIEWED)) { | ||||
|         reviewed = loadReviewed(Collections.singleton(cd)); | ||||
|       } | ||||
|       ChangeInfo res = toChangeInfo(cd, reviewed, limitToPsId); | ||||
|       accountLoader.fill(); | ||||
|       return res; | ||||
|     } catch (OrmException | RuntimeException e) { | ||||
|       if (!has(CHECK)) { | ||||
|         throw e; | ||||
|       } | ||||
|       return checkOnly(cd); | ||||
|     } | ||||
|     ChangeInfo res = toChangeInfo(cd, reviewed, limitToPsId); | ||||
|     accountLoader.fill(); | ||||
|     return res; | ||||
|   } | ||||
|  | ||||
|   public ChangeInfo format(RevisionResource rsrc) throws OrmException { | ||||
| @@ -261,10 +289,14 @@ public class ChangeJson { | ||||
|       if (i == null) { | ||||
|         try { | ||||
|           i = toChangeInfo(cd, reviewed, Optional.<PatchSet.Id> absent()); | ||||
|         } catch (OrmException e) { | ||||
|           log.warn( | ||||
|               "Omitting corrupt change " + cd.getId() + " from results", e); | ||||
|           continue; | ||||
|         } catch (OrmException | RuntimeException e) { | ||||
|           if (has(CHECK)) { | ||||
|             i = checkOnly(cd); | ||||
|           } else { | ||||
|             log.warn( | ||||
|                 "Omitting corrupt change " + cd.getId() + " from results", e); | ||||
|             continue; | ||||
|           } | ||||
|         } | ||||
|         out.put(cd.getId(), i); | ||||
|       } | ||||
| @@ -273,11 +305,49 @@ public class ChangeJson { | ||||
|     return info; | ||||
|   } | ||||
|  | ||||
|   private ChangeInfo checkOnly(ChangeData cd) { | ||||
|     ConsistencyChecker.Result result = checkerProvider.get().check(cd, fix); | ||||
|     ChangeInfo info; | ||||
|     Change c = result.change(); | ||||
|     if (c != null) { | ||||
|       info = new ChangeInfo(); | ||||
|       info.project = c.getProject().get(); | ||||
|       info.branch = c.getDest().getShortName(); | ||||
|       info.topic = c.getTopic(); | ||||
|       info.changeId = c.getKey().get(); | ||||
|       info.subject = c.getSubject(); | ||||
|       info.status = c.getStatus().asChangeStatus(); | ||||
|       info.owner = new AccountInfo(c.getOwner().get()); | ||||
|       info.created = c.getCreatedOn(); | ||||
|       info.updated = c.getLastUpdatedOn(); | ||||
|       info._number = c.getId().get(); | ||||
|       info.problems = result.problems(); | ||||
|       finish(info); | ||||
|     } else { | ||||
|       info = new ChangeInfo(); | ||||
|       info._number = result.id().get(); | ||||
|       info.problems = result.problems(); | ||||
|     } | ||||
|     return info; | ||||
|   } | ||||
|  | ||||
|   private ChangeInfo toChangeInfo(ChangeData cd, Set<Change.Id> reviewed, | ||||
|       Optional<PatchSet.Id> limitToPsId) throws OrmException { | ||||
|     ChangeControl ctl = cd.changeControl().forUser(userProvider.get()); | ||||
|     ChangeInfo out = new ChangeInfo(); | ||||
|  | ||||
|     if (has(CHECK)) { | ||||
|       out.problems = checkerProvider.get().check(cd.change(), fix).problems(); | ||||
|       // If any problems were fixed, the ChangeData needs to be reloaded. | ||||
|       for (ProblemInfo p : out.problems) { | ||||
|         if (p.status == ProblemInfo.Status.FIXED) { | ||||
|           cd = changeDataFactory.create(cd.db(), cd.getId()); | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     Change in = cd.change(); | ||||
|     ChangeControl ctl = cd.changeControl().forUser(userProvider.get()); | ||||
|     out.project = in.getProject().get(); | ||||
|     out.branch = in.getDest().getShortName(); | ||||
|     out.topic = in.getTopic(); | ||||
| @@ -355,6 +425,7 @@ public class ChangeJson { | ||||
|         out.actions.put(descr.getId(), new ActionInfo(descr)); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return out; | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -14,63 +14,41 @@ | ||||
|  | ||||
| package com.google.gerrit.server.change; | ||||
|  | ||||
| import com.google.gerrit.extensions.common.AccountInfo; | ||||
| import com.google.gerrit.extensions.api.changes.FixInput; | ||||
| import com.google.gerrit.extensions.common.ChangeInfo; | ||||
| import com.google.gerrit.extensions.common.ListChangesOption; | ||||
| import com.google.gerrit.extensions.restapi.AuthException; | ||||
| import com.google.gerrit.extensions.restapi.Response; | ||||
| import com.google.gerrit.extensions.restapi.RestModifyView; | ||||
| import com.google.gerrit.extensions.restapi.RestReadView; | ||||
| import com.google.gerrit.reviewdb.client.Change; | ||||
| import com.google.gerrit.server.project.ChangeControl; | ||||
| import com.google.gwtorm.server.OrmException; | ||||
| import com.google.inject.Inject; | ||||
| import com.google.inject.Provider; | ||||
| import com.google.inject.Singleton; | ||||
|  | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| @Singleton | ||||
| public class Check implements RestReadView<ChangeResource> { | ||||
|   private static final Logger log = LoggerFactory.getLogger(Check.class); | ||||
|  | ||||
|   private final Provider<ConsistencyChecker> checkerProvider; | ||||
| public class Check implements RestReadView<ChangeResource>, | ||||
|     RestModifyView<ChangeResource, FixInput> { | ||||
|   private final ChangeJson json; | ||||
|  | ||||
|   @Inject | ||||
|   Check(Provider<ConsistencyChecker> checkerProvider, | ||||
|       ChangeJson json) { | ||||
|     this.checkerProvider = checkerProvider; | ||||
|   Check(ChangeJson json) { | ||||
|     this.json = json; | ||||
|     json.addOption(ListChangesOption.CHECK); | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public CheckResult apply(ChangeResource rsrc) { | ||||
|     CheckResult result = new CheckResult(); | ||||
|     result.messages = checkerProvider.get().check(rsrc.getChange()); | ||||
|     try { | ||||
|       result.change = json.format(rsrc); | ||||
|     } catch (OrmException e) { | ||||
|       // Even with no options there are a surprising number of dependencies in | ||||
|       // ChangeJson. Fall back to a very basic implementation with no | ||||
|       // dependencies if this fails. | ||||
|       String msg = "Error rendering final ChangeInfo"; | ||||
|       log.warn(msg, e); | ||||
|       result.messages.add(msg); | ||||
|       result.change = basicChangeInfo(rsrc.getChange()); | ||||
|     } | ||||
|     return result; | ||||
|   public Response<ChangeInfo> apply(ChangeResource rsrc) throws OrmException { | ||||
|     return GetChange.cache(json.format(rsrc)); | ||||
|   } | ||||
|  | ||||
|   private static ChangeInfo basicChangeInfo(Change c) { | ||||
|     ChangeInfo info = new ChangeInfo(); | ||||
|     info.project = c.getProject().get(); | ||||
|     info.branch = c.getDest().getShortName(); | ||||
|     info.topic = c.getTopic(); | ||||
|     info.changeId = c.getKey().get(); | ||||
|     info.subject = c.getSubject(); | ||||
|     info.status = c.getStatus().asChangeStatus(); | ||||
|     info.owner = new AccountInfo(c.getOwner().get()); | ||||
|     info.created = c.getCreatedOn(); | ||||
|     info.updated = c.getLastUpdatedOn(); | ||||
|     info._number = c.getId().get(); | ||||
|     ChangeJson.finish(info); | ||||
|     return info; | ||||
|   @Override | ||||
|   public Response<ChangeInfo> apply(ChangeResource rsrc, FixInput input) | ||||
|       throws AuthException, OrmException { | ||||
|     ChangeControl ctl = rsrc.getControl(); | ||||
|     if (!ctl.isOwner() | ||||
|         && !ctl.getProjectControl().isOwner() | ||||
|         && !ctl.getCurrentUser().getCapabilities().canAdministrateServer()) { | ||||
|       throw new AuthException("Not owner"); | ||||
|     } | ||||
|     return GetChange.cache(json.fix(input).format(rsrc)); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -14,16 +14,23 @@ | ||||
|  | ||||
| package com.google.gerrit.server.change; | ||||
|  | ||||
| import com.google.auto.value.AutoValue; | ||||
| import com.google.common.base.Function; | ||||
| import com.google.common.collect.Collections2; | ||||
| import com.google.common.collect.Multimap; | ||||
| import com.google.common.collect.MultimapBuilder; | ||||
| import com.google.common.collect.Ordering; | ||||
| import com.google.gerrit.common.Nullable; | ||||
| import com.google.gerrit.extensions.api.changes.FixInput; | ||||
| import com.google.gerrit.extensions.common.ProblemInfo; | ||||
| import com.google.gerrit.extensions.common.ProblemInfo.Status; | ||||
| import com.google.gerrit.reviewdb.client.Change; | ||||
| import com.google.gerrit.reviewdb.client.PatchSet; | ||||
| import com.google.gerrit.reviewdb.client.Project; | ||||
| import com.google.gerrit.reviewdb.server.ReviewDb; | ||||
| import com.google.gerrit.server.git.GitRepositoryManager; | ||||
| import com.google.gerrit.server.query.change.ChangeData; | ||||
| import com.google.gwtorm.server.AtomicUpdate; | ||||
| import com.google.gwtorm.server.OrmException; | ||||
| import com.google.inject.Inject; | ||||
| import com.google.inject.Provider; | ||||
| @@ -55,9 +62,28 @@ public class ConsistencyChecker { | ||||
|   private static final Logger log = | ||||
|       LoggerFactory.getLogger(ConsistencyChecker.class); | ||||
|  | ||||
|   @AutoValue | ||||
|   public static abstract class Result { | ||||
|     private static Result create(Change.Id id, List<ProblemInfo> problems) { | ||||
|       return new AutoValue_ConsistencyChecker_Result(id, null, problems); | ||||
|     } | ||||
|  | ||||
|     private static Result create(Change c, List<ProblemInfo> problems) { | ||||
|       return new AutoValue_ConsistencyChecker_Result(c.getId(), c, problems); | ||||
|     } | ||||
|  | ||||
|     public abstract Change.Id id(); | ||||
|  | ||||
|     @Nullable | ||||
|     public abstract Change change(); | ||||
|  | ||||
|     public abstract List<ProblemInfo> problems(); | ||||
|   } | ||||
|  | ||||
|   private final Provider<ReviewDb> db; | ||||
|   private final GitRepositoryManager repoManager; | ||||
|  | ||||
|   private FixInput fix; | ||||
|   private Change change; | ||||
|   private Repository repo; | ||||
|   private RevWalk rw; | ||||
| @@ -65,7 +91,7 @@ public class ConsistencyChecker { | ||||
|   private PatchSet currPs; | ||||
|   private RevCommit currPsCommit; | ||||
|  | ||||
|   private List<String> messages; | ||||
|   private List<ProblemInfo> problems; | ||||
|  | ||||
|   @Inject | ||||
|   ConsistencyChecker(Provider<ReviewDb> db, | ||||
| @@ -79,15 +105,34 @@ public class ConsistencyChecker { | ||||
|     change = null; | ||||
|     repo = null; | ||||
|     rw = null; | ||||
|     messages = new ArrayList<>(); | ||||
|     problems = new ArrayList<>(); | ||||
|   } | ||||
|  | ||||
|   public List<String> check(Change c) { | ||||
|   public Result check(ChangeData cd) { | ||||
|     return check(cd, null); | ||||
|   } | ||||
|  | ||||
|   public Result check(ChangeData cd, @Nullable FixInput f) { | ||||
|     reset(); | ||||
|     try { | ||||
|       return check(cd.change(), f); | ||||
|     } catch (OrmException e) { | ||||
|       error("Error looking up change", e); | ||||
|       return Result.create(cd.getId(), problems); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   public Result check(Change c) { | ||||
|     return check(c, null); | ||||
|   } | ||||
|  | ||||
|   public Result check(Change c, @Nullable FixInput f) { | ||||
|     reset(); | ||||
|     fix = f; | ||||
|     change = c; | ||||
|     try { | ||||
|       checkImpl(); | ||||
|       return messages; | ||||
|       return Result.create(c, problems); | ||||
|     } finally { | ||||
|       if (rw != null) { | ||||
|         rw.release(); | ||||
| @@ -115,7 +160,7 @@ public class ConsistencyChecker { | ||||
|   private void checkOwner() { | ||||
|     try { | ||||
|       if (db.get().accounts().get(change.getOwner()) == null) { | ||||
|         messages.add("Missing change owner: " + change.getOwner()); | ||||
|         problem("Missing change owner: " + change.getOwner()); | ||||
|       } | ||||
|     } catch (OrmException e) { | ||||
|       error("Failed to look up owner", e); | ||||
| @@ -127,7 +172,7 @@ public class ConsistencyChecker { | ||||
|       PatchSet.Id psId = change.currentPatchSetId(); | ||||
|       currPs = db.get().patchSets().get(psId); | ||||
|       if (currPs == null) { | ||||
|         messages.add(String.format("Current patch set %d not found", psId.get())); | ||||
|         problem(String.format("Current patch set %d not found", psId.get())); | ||||
|       } | ||||
|     } catch (OrmException e) { | ||||
|       error("Failed to look up current patch set", e); | ||||
| @@ -189,7 +234,7 @@ public class ConsistencyChecker { | ||||
|     for (Map.Entry<ObjectId, Collection<PatchSet>> e | ||||
|         : bySha.asMap().entrySet()) { | ||||
|       if (e.getValue().size() > 1) { | ||||
|         messages.add(String.format("Multiple patch sets pointing to %s: %s", | ||||
|         problem(String.format("Multiple patch sets pointing to %s: %s", | ||||
|             e.getKey().name(), | ||||
|             Collections2.transform(e.getValue(), toPsId))); | ||||
|       } | ||||
| @@ -204,11 +249,11 @@ public class ConsistencyChecker { | ||||
|     try { | ||||
|       dest = repo.getRef(refName); | ||||
|     } catch (IOException e) { | ||||
|       messages.add("Failed to look up destination ref: " + refName); | ||||
|       problem("Failed to look up destination ref: " + refName); | ||||
|       return; | ||||
|     } | ||||
|     if (dest == null) { | ||||
|       messages.add("Destination ref not found (may be new branch): " | ||||
|       problem("Destination ref not found (may be new branch): " | ||||
|           + change.getDest().get()); | ||||
|       return; | ||||
|     } | ||||
| @@ -221,38 +266,67 @@ public class ConsistencyChecker { | ||||
|     try { | ||||
|       merged = rw.isMergedInto(currPsCommit, tip); | ||||
|     } catch (IOException e) { | ||||
|       messages.add("Error checking whether patch set " + currPs.getId().get() | ||||
|       problem("Error checking whether patch set " + currPs.getId().get() | ||||
|           + " is merged"); | ||||
|       return; | ||||
|     } | ||||
|     if (merged && change.getStatus() != Change.Status.MERGED) { | ||||
|       messages.add(String.format("Patch set %d (%s) is merged into destination" | ||||
|             + " ref %s (%s), but change status is %s", currPs.getId().get(), | ||||
|             currPsCommit.name(), refName, tip.name(), change.getStatus())); | ||||
|       // TODO(dborowitz): Just fix it. | ||||
|       ProblemInfo p = problem(String.format( | ||||
|           "Patch set %d (%s) is merged into destination ref %s (%s), but change" | ||||
|           + " status is %s", currPs.getId().get(), currPsCommit.name(), | ||||
|           refName, tip.name(), change.getStatus())); | ||||
|       if (fix != null) { | ||||
|         fixMerged(p); | ||||
|       } | ||||
|     } else if (!merged && change.getStatus() == Change.Status.MERGED) { | ||||
|       messages.add(String.format("Patch set %d (%s) is not merged into" | ||||
|       problem(String.format("Patch set %d (%s) is not merged into" | ||||
|             + " destination ref %s (%s), but change status is %s", | ||||
|             currPs.getId().get(), currPsCommit.name(), refName, tip.name(), | ||||
|             change.getStatus())); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private void fixMerged(ProblemInfo p) { | ||||
|     try { | ||||
|       change = db.get().changes().atomicUpdate(change.getId(), | ||||
|           new AtomicUpdate<Change>() { | ||||
|             @Override | ||||
|             public Change update(Change c) { | ||||
|               c.setStatus(Change.Status.MERGED); | ||||
|               return c; | ||||
|             } | ||||
|           }); | ||||
|       p.status = Status.FIXED; | ||||
|       p.outcome = "Marked change as merged"; | ||||
|     } catch (OrmException e) { | ||||
|       log.warn("Error marking " + change.getId() + "as merged", e); | ||||
|       p.status = Status.FIX_FAILED; | ||||
|       p.outcome = "Error updating status to merged"; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private RevCommit parseCommit(ObjectId objId, String desc) { | ||||
|     try { | ||||
|       return rw.parseCommit(objId); | ||||
|     } catch (MissingObjectException e) { | ||||
|       messages.add(String.format("Object missing: %s: %s", desc, objId.name())); | ||||
|       problem(String.format("Object missing: %s: %s", desc, objId.name())); | ||||
|     } catch (IncorrectObjectTypeException e) { | ||||
|       messages.add(String.format("Not a commit: %s: %s", desc, objId.name())); | ||||
|       problem(String.format("Not a commit: %s: %s", desc, objId.name())); | ||||
|     } catch (IOException e) { | ||||
|       messages.add(String.format("Failed to look up: %s: %s", desc, objId.name())); | ||||
|       problem(String.format("Failed to look up: %s: %s", desc, objId.name())); | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   private ProblemInfo problem(String msg) { | ||||
|     ProblemInfo p = new ProblemInfo(); | ||||
|     p.message = msg; | ||||
|     problems.add(p); | ||||
|     return p; | ||||
|   } | ||||
|  | ||||
|   private boolean error(String msg, Throwable t) { | ||||
|     messages.add(msg); | ||||
|     problem(msg); | ||||
|     // TODO(dborowitz): Expose stack trace to administrators. | ||||
|     log.warn("Error in consistency check of change " + change.getId(), t); | ||||
|     return false; | ||||
|   | ||||
| @@ -53,7 +53,7 @@ public class GetChange implements RestReadView<ChangeResource> { | ||||
|     return cache(json.format(rsrc)); | ||||
|   } | ||||
|  | ||||
|   private Response<ChangeInfo> cache(ChangeInfo res) { | ||||
|   static Response<ChangeInfo> cache(ChangeInfo res) { | ||||
|     return Response.ok(res) | ||||
|         .caching(CacheControl.PRIVATE(0, TimeUnit.SECONDS).setMustRevalidate()); | ||||
|   } | ||||
|   | ||||
| @@ -53,6 +53,7 @@ public class Module extends RestApiModule { | ||||
|     get(CHANGE_KIND, "in").to(IncludedIn.class); | ||||
|     get(CHANGE_KIND, "hashtags").to(GetHashtags.class); | ||||
|     get(CHANGE_KIND, "check").to(Check.class); | ||||
|     post(CHANGE_KIND, "check").to(Check.class); | ||||
|     put(CHANGE_KIND, "topic").to(PutTopic.class); | ||||
|     delete(CHANGE_KIND, "topic").to(PutTopic.class); | ||||
|     delete(CHANGE_KIND).to(DeleteDraftChange.class); | ||||
|   | ||||
| @@ -594,6 +594,9 @@ public class ChangeData { | ||||
|         mergeable = true; | ||||
|       } else { | ||||
|         PatchSet ps = currentPatchSet(); | ||||
|         if (ps == null) { | ||||
|           throw new OrmException("Missing patch set for mergeability check"); | ||||
|         } | ||||
|         Repository repo = null; | ||||
|         try { | ||||
|           repo = repoManager.openRepository(c.getProject()); | ||||
|   | ||||
| @@ -20,7 +20,11 @@ import static com.google.gerrit.testutil.TestChanges.newChange; | ||||
| import static com.google.gerrit.testutil.TestChanges.newPatchSet; | ||||
| import static java.util.Collections.singleton; | ||||
|  | ||||
| import com.google.common.base.Function; | ||||
| import com.google.common.collect.Lists; | ||||
| import com.google.gerrit.common.TimeUtil; | ||||
| import com.google.gerrit.extensions.api.changes.FixInput; | ||||
| import com.google.gerrit.extensions.common.ProblemInfo; | ||||
| import com.google.gerrit.reviewdb.client.Account; | ||||
| import com.google.gerrit.reviewdb.client.Change; | ||||
| import com.google.gerrit.reviewdb.client.PatchSet; | ||||
| @@ -40,6 +44,8 @@ import org.junit.After; | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| public class ConsistencyCheckerTest { | ||||
|   private InMemoryDatabase schemaFactory; | ||||
|   private ReviewDb db; | ||||
| @@ -90,7 +96,7 @@ public class ConsistencyCheckerTest { | ||||
|     PatchSet ps2 = newPatchSet(c.currentPatchSetId(), commit2, userId); | ||||
|     db.patchSets().insert(singleton(ps2)); | ||||
|  | ||||
|     assertThat(checker.check(c)).isEmpty(); | ||||
|     assertProblems(c); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
| @@ -110,7 +116,7 @@ public class ConsistencyCheckerTest { | ||||
|     db.patchSets().insert(singleton(ps2)); | ||||
|  | ||||
|     repo.branch(c.getDest().get()).update(commit2); | ||||
|     assertThat(checker.check(c)).isEmpty(); | ||||
|     assertProblems(c); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
| @@ -122,7 +128,7 @@ public class ConsistencyCheckerTest { | ||||
|     PatchSet ps = newPatchSet(c.currentPatchSetId(), commit, userId); | ||||
|     db.patchSets().insert(singleton(ps)); | ||||
|  | ||||
|     assertThat(checker.check(c)).containsExactly("Missing change owner: 2"); | ||||
|     assertProblems(c, "Missing change owner: 2"); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
| @@ -132,8 +138,7 @@ public class ConsistencyCheckerTest { | ||||
|     PatchSet ps = newPatchSet(c.currentPatchSetId(), | ||||
|         ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), userId); | ||||
|     db.patchSets().insert(singleton(ps)); | ||||
|     assertThat(checker.check(c)) | ||||
|         .containsExactly("Destination repository not found: otherproject"); | ||||
|     assertProblems(c, "Destination repository not found: otherproject"); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
| @@ -153,7 +158,7 @@ public class ConsistencyCheckerTest { | ||||
|     PatchSet ps2 = newPatchSet(c.currentPatchSetId(), commit2, userId); | ||||
|     db.patchSets().insert(singleton(ps2)); | ||||
|  | ||||
|     assertThat(checker.check(c)).containsExactly( | ||||
|     assertProblems(c, | ||||
|         "Invalid revision on patch set 1:" | ||||
|         + " fooooooooooooooooooooooooooooooooooooooo"); | ||||
|   } | ||||
| @@ -166,7 +171,7 @@ public class ConsistencyCheckerTest { | ||||
|         ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), userId); | ||||
|     db.patchSets().insert(singleton(ps)); | ||||
|  | ||||
|     assertThat(checker.check(c)).containsExactly( | ||||
|     assertProblems(c, | ||||
|         "Object missing: patch set 1: deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); | ||||
|   } | ||||
|  | ||||
| @@ -174,8 +179,7 @@ public class ConsistencyCheckerTest { | ||||
|   public void currentPatchSetMissing() throws Exception { | ||||
|     Change c = newChange(project, userId); | ||||
|     db.changes().insert(singleton(c)); | ||||
|     assertThat(checker.check(c)) | ||||
|       .containsExactly("Current patch set 1 not found"); | ||||
|     assertProblems(c, "Current patch set 1 not found"); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
| @@ -191,8 +195,8 @@ public class ConsistencyCheckerTest { | ||||
|     PatchSet ps2 = newPatchSet(c.currentPatchSetId(), commit1, userId); | ||||
|     db.patchSets().insert(singleton(ps2)); | ||||
|  | ||||
|     assertThat(checker.check(c)).containsExactly("Multiple patch sets pointing to " | ||||
|         + commit1.name() + ": [1, 2]"); | ||||
|     assertProblems(c, | ||||
|         "Multiple patch sets pointing to " + commit1.name() + ": [1, 2]"); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
| @@ -206,8 +210,7 @@ public class ConsistencyCheckerTest { | ||||
|     PatchSet ps = newPatchSet(c.currentPatchSetId(), commit, userId); | ||||
|     db.patchSets().insert(singleton(ps)); | ||||
|  | ||||
|     assertThat(checker.check(c)).containsExactly( | ||||
|         "Destination ref not found (may be new branch): master"); | ||||
|     assertProblems(c, "Destination ref not found (may be new branch): master"); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
| @@ -220,7 +223,7 @@ public class ConsistencyCheckerTest { | ||||
|     PatchSet ps = newPatchSet(c.currentPatchSetId(), commit, userId); | ||||
|     db.patchSets().insert(singleton(ps)); | ||||
|  | ||||
|     assertThat(checker.check(c)).containsExactly( | ||||
|     assertProblems(c, | ||||
|         "Patch set 1 (" + commit.name() + ") is not merged into destination ref" | ||||
|         + " master (" + tip.name() + "), but change status is MERGED"); | ||||
|   } | ||||
| @@ -235,8 +238,42 @@ public class ConsistencyCheckerTest { | ||||
|     db.patchSets().insert(singleton(ps)); | ||||
|     repo.branch(c.getDest().get()).update(commit); | ||||
|  | ||||
|     assertThat(checker.check(c)).containsExactly( | ||||
|     assertProblems(c, | ||||
|         "Patch set 1 (" + commit.name() + ") is merged into destination ref" | ||||
|         + " master (" + commit.name() + "), but change status is NEW"); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void newChangeIsMergedWithFix() throws Exception { | ||||
|     Change c = newChange(project, userId); | ||||
|     db.changes().insert(singleton(c)); | ||||
|     RevCommit commit = repo.branch(c.currentPatchSetId().toRefName()).commit() | ||||
|         .parent(tip).create(); | ||||
|     PatchSet ps = newPatchSet(c.currentPatchSetId(), commit, userId); | ||||
|     db.patchSets().insert(singleton(ps)); | ||||
|     repo.branch(c.getDest().get()).update(commit); | ||||
|  | ||||
|     List<ProblemInfo> problems = checker.check(c, new FixInput()).problems(); | ||||
|     assertThat(problems).hasSize(1); | ||||
|     ProblemInfo p = problems.get(0); | ||||
|     assertThat(p.message).isEqualTo( | ||||
|         "Patch set 1 (" + commit.name() + ") is merged into destination ref" | ||||
|         + " master (" + commit.name() + "), but change status is NEW"); | ||||
|     assertThat(p.status).isEqualTo(ProblemInfo.Status.FIXED); | ||||
|     assertThat(p.outcome).isEqualTo("Marked change as merged"); | ||||
|  | ||||
|     c = db.changes().get(c.getId()); | ||||
|     assertThat(c.getStatus()).isEqualTo(Change.Status.MERGED); | ||||
|     assertProblems(c); | ||||
|   } | ||||
|  | ||||
|   private void assertProblems(Change c, String... expected) { | ||||
|     assertThat(Lists.transform(checker.check(c).problems(), | ||||
|           new Function<ProblemInfo, String>() { | ||||
|             @Override | ||||
|             public String apply(ProblemInfo in) { | ||||
|               return in.message; | ||||
|             } | ||||
|           })).containsExactly((Object[]) expected); | ||||
|   } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Dave Borowitz
					Dave Borowitz