RemoveDrafts: disallow creation of new drafts
Draft workflow in Gerrit will be deprecated and slated for removal. This change is the first step in this direction. However, the repo-tool, mainly used by Andorid developers, is still depending on drafts and has no support for private or wip changes. We've added '--private' and '--wip' options to the repo-tool so that users could have alternatives after pushing drafts is rejected. But, unfortunately, it has proved hard for us to create a new repo-tool release containing those two new options. That means we have to find some workarounds instead of rejecting requests for creating drafts directly. The most important feature of drafts is privacy (draft changes and patch sets are only visible to the change owners and reviewers). Luckily, we can achieve this with the help of private changes and change edits: 1- If a user tries to create a draft change, we will create a private change instead. 2- If a user tries to create a draft patch set, we will create a change edit instead. The above behaviors will be a little confusing to our users because the change or the patch set state is inconsistent with the push option. But they get almost the same feature. And they can use similar operations to public their changes/edits. Before this, they click 'Publish drafts', now they click 'Unmark private' or 'Publish edit'. After the repo-tool supports private/wip changes, we will reject those requests with draft option completely. With this change, users can't create new drafts through the git push or the 'Create Change' REST API. After this change is released, draft changes and draft patch sets can still exist in Gerrit sites. The existing draft changes and draft patch sets can still be published and deleted. Some operations like cherry-pick may create a new draft patch set if the current patch set of the target change is draft. But after migrating existing drafts, there is no way to create a new draft any more. Given that Change.Status enum still contains DRAFT value and given that patch set class still has draft flag, the draft state of changes and patch sets can still be toggled programmatically. Thus we can keep most drafts related unit tests until existing draft changes and draft patch sets are migrated to non-draft changes and patch sets. Migration path for draft changes and patch sets (will be done in follow-up changes): * Draft changes will be migrated to private or work-in-progress changes * Draft patch sets will be published During this migration, the DRAFT value will be removed from the Change.Status enum and the draft flag will be removed from the patch set class. Bug: Issue 6881 Change-Id: I8663f0580b90d7ce10a597b42abecf8734d3f9b2
This commit is contained in:
parent
03077adb37
commit
4b44bdc702
@ -413,20 +413,14 @@ and patch sets.
|
||||
|
||||
==== refs/drafts/*
|
||||
|
||||
Push to `+refs/drafts/*+` creates a change like push to `+refs/for/*+`, except the
|
||||
resulting change remains hidden from public review. You then have the option
|
||||
of adding individual reviewers before making the change public to all. The
|
||||
change page will have a 'Publish' button which allows you to convert individual
|
||||
draft patch sets of a change into public patch sets for review.
|
||||
|
||||
To block push permission to `+refs/drafts/*+` the following permission rule can
|
||||
be configured:
|
||||
|
||||
----
|
||||
[access "refs/drafts/*"]
|
||||
push = block group Anonymous Users
|
||||
----
|
||||
Draft workflow is slated for removal. Consider using
|
||||
link:intro-user.html#private-changes[private changes] or
|
||||
link:user-upload.html#wip[work-in-progress changes] instead.
|
||||
|
||||
Before the remove process is complete, pushing with `+refs/drafts/*+` to create a new
|
||||
draft change will get a link:intro-user.html#private-changes[private change] instead.
|
||||
Pushing with `+refs/drafts/*+` to an existing change to create a draft patch set will
|
||||
get a link:user-upload.html#[change_edit][change edit] instead.
|
||||
|
||||
[[access_categories]]
|
||||
== Access Categories
|
||||
|
@ -539,25 +539,16 @@ until a new patch set is uploaded.
|
||||
[[drafts]]
|
||||
== Working with Drafts
|
||||
|
||||
Drafts is a deprecated feature and will be removed soon. Consider using
|
||||
private changes instead.
|
||||
Drafts will be deprecated and removed soon. Consider using
|
||||
link:#private-changes[private changes] or
|
||||
link:user-upload.html#wip[work-in-progress changes] instead.
|
||||
|
||||
Changes can be uploaded as drafts. By default draft changes are only
|
||||
visible to the change owner. This gives you the possibility to have
|
||||
some staging before making your changes visible to the reviewers. Draft
|
||||
changes can also be used to backup unfinished changes.
|
||||
Changes cannot be uploaded as drafts any more, but existing draft changes
|
||||
and patch sets can still be published and deleted.
|
||||
|
||||
A draft change is created by pushing to the magic
|
||||
`refs/drafts/<target-branch>` ref, or by pushing with the 'draft'
|
||||
option to `refs/for/<target-branch>%draft`.
|
||||
|
||||
.Push a Draft Change
|
||||
----
|
||||
$ git commit
|
||||
$ git push origin HEAD:refs/drafts/master
|
||||
# or
|
||||
$ git push origin HEAD:refs/for/master%draft
|
||||
----
|
||||
By default draft changes are only visible to the change owner. This gives
|
||||
you the possibility to have some staging before making your changes visible
|
||||
to the reviewers. Draft changes can also be used to backup unfinished changes.
|
||||
|
||||
Draft changes have the state link:user-review-ui.html#draft[Draft] and
|
||||
can be link:user-review-ui.html#publish[published] or
|
||||
@ -567,12 +558,6 @@ By link:user-review-ui.html#reviewers[adding reviewers] to a draft
|
||||
change the change is made visible to these users. This way you can
|
||||
collaborate with other users in privacy.
|
||||
|
||||
By pushing to `refs/drafts/<target-branch>` you can also upload draft
|
||||
patch sets to non-draft changes. Draft patch sets are immediately
|
||||
visible to all reviewers of the change, but other users cannot see the
|
||||
draft patch set. A draft patch set can be published and deleted in the
|
||||
same way as a draft change.
|
||||
|
||||
[[inline-edit]]
|
||||
== Inline Edit
|
||||
|
||||
|
@ -5810,7 +5810,7 @@ The `refs/heads/` prefix is omitted.
|
||||
The subject of the change (header line of the commit message).
|
||||
|`topic` |optional|The topic to which this change belongs.
|
||||
|`status` |optional, default to `NEW`|
|
||||
The status of the change (only `NEW` and `DRAFT` accepted here).
|
||||
The status of the change (only `NEW` accepted here).
|
||||
|`is_private` |optional, default to `false`|
|
||||
Whether the new change should be marked as private.
|
||||
|`work_in_progress` |optional, default to `false`|
|
||||
|
@ -10,9 +10,8 @@ browser.
|
||||
A new change can be created directly in the browser, meaning it is not necessary
|
||||
to clone the whole repository to make trivial changes.
|
||||
|
||||
The new change is created as a draft change, unless
|
||||
link:config-gerrit.html#change.allowDrafts[change.allowDrafts] is set to false,
|
||||
in which case the change is created as a normal new change.
|
||||
The new change is created as a public
|
||||
link:user-upload.html#wip[work-in-progress change].
|
||||
|
||||
There are two different ways to create a new change:
|
||||
|
||||
|
@ -67,6 +67,7 @@ import com.google.gerrit.extensions.restapi.RestApiException;
|
||||
import com.google.gerrit.reviewdb.client.AccountGroup;
|
||||
import com.google.gerrit.reviewdb.client.Branch;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.Change.Status;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.reviewdb.client.RefNames;
|
||||
@ -606,18 +607,62 @@ public abstract class AbstractDaemonTest {
|
||||
return result;
|
||||
}
|
||||
|
||||
protected PushOneCommit.Result createDraftChange() throws Exception {
|
||||
return pushTo("refs/drafts/master");
|
||||
protected PushOneCommit.Result createCommitAndPush(
|
||||
TestRepository<InMemoryRepository> repo,
|
||||
String ref,
|
||||
String commitMsg,
|
||||
String fileName,
|
||||
String content)
|
||||
throws Exception {
|
||||
PushOneCommit.Result result =
|
||||
pushFactory.create(db, admin.getIdent(), repo, commitMsg, fileName, content).to(ref);
|
||||
result.assertOkStatus();
|
||||
return result;
|
||||
}
|
||||
|
||||
protected PushOneCommit.Result forceDraftChange() throws Exception {
|
||||
PushOneCommit.Result pushTo = pushTo("refs/for/master");
|
||||
markChangeAsDraft(pushTo.getChange().change().getId());
|
||||
setDraftStatusOfPatchSetsOfChange(pushTo.getChange().change().getId(), true);
|
||||
return pushTo;
|
||||
protected PushOneCommit.Result createChangeWithTopic() throws Exception {
|
||||
return createChangeWithTopic(testRepo, "topic", "message", "a.txt", "content\n");
|
||||
}
|
||||
|
||||
protected PushOneCommit.Result createChangeWithTopic(
|
||||
TestRepository<InMemoryRepository> repo,
|
||||
String topic,
|
||||
String commitMsg,
|
||||
String fileName,
|
||||
String content)
|
||||
throws Exception {
|
||||
assertThat(topic).isNotEmpty();
|
||||
return createCommitAndPush(
|
||||
repo, "refs/for/master/" + name(topic), commitMsg, fileName, content);
|
||||
}
|
||||
|
||||
protected PushOneCommit.Result createDraftChange() throws Exception {
|
||||
return createDraftChange("");
|
||||
}
|
||||
|
||||
protected PushOneCommit.Result createDraftChange(String topic) throws Exception {
|
||||
PushOneCommit.Result r =
|
||||
Strings.isNullOrEmpty(topic)
|
||||
? createChange()
|
||||
: createChangeWithTopic(testRepo, topic, "message", "a.txt", "content");
|
||||
markChangeAsDraft(r.getChange().change().getId());
|
||||
setCurrentPatchSetAsDraft(r.getChange().change().getId());
|
||||
ChangeData cd = Iterables.getOnlyElement(queryProvider.get().byKeyPrefix(r.getChangeId()));
|
||||
assertThat(cd.change().getStatus()).isEqualTo(Status.DRAFT);
|
||||
return r;
|
||||
}
|
||||
|
||||
protected String createDraftChangeWith2PS() throws Exception {
|
||||
PushOneCommit.Result r = createDraftChange();
|
||||
amendChangeAndMarkChangeAndPatchSetsAsDraft(r.getChangeId());
|
||||
return r.getChangeId();
|
||||
}
|
||||
|
||||
protected void markChangeAsDraft(Change.Id id) throws Exception {
|
||||
markChangeAsDraft(project, id);
|
||||
}
|
||||
|
||||
protected void markChangeAsDraft(Project.NameKey project, Change.Id id) throws Exception {
|
||||
try (BatchUpdate batchUpdate =
|
||||
batchUpdateFactory.create(db, project, atrScope.get().getUser(), TimeUtil.nowTs())) {
|
||||
batchUpdate.addOp(id, new MarkChangeAsDraftUpdateOp());
|
||||
@ -628,8 +673,20 @@ public abstract class AbstractDaemonTest {
|
||||
assertThat(changeStatus).isEqualTo(ChangeStatus.DRAFT);
|
||||
}
|
||||
|
||||
protected void setDraftStatusOfPatchSetsOfChange(Change.Id id, boolean draftStatus)
|
||||
throws Exception {
|
||||
protected void setCurrentPatchSetAsDraft(Change.Id id) throws Exception {
|
||||
try (BatchUpdate batchUpdate =
|
||||
batchUpdateFactory.create(db, project, atrScope.get().getUser(), TimeUtil.nowTs())) {
|
||||
batchUpdate.addOp(id, new SetDraftStatusOfCurrentPatchSetOp(true));
|
||||
batchUpdate.execute();
|
||||
}
|
||||
|
||||
ChangeInfo changeInfo =
|
||||
gApi.changes().id(id.get()).get(EnumSet.of(ListChangesOption.ALL_REVISIONS));
|
||||
RevisionInfo revisionInfo = changeInfo.revisions.get(changeInfo.currentRevision);
|
||||
assertThat(revisionInfo.draft).isEqualTo(Boolean.TRUE);
|
||||
}
|
||||
|
||||
protected void setDraftStatusOfPatchSets(Change.Id id, boolean draftStatus) throws Exception {
|
||||
try (BatchUpdate batchUpdate =
|
||||
batchUpdateFactory.create(db, project, atrScope.get().getUser(), TimeUtil.nowTs())) {
|
||||
batchUpdate.addOp(id, new DraftStatusOfPatchSetsUpdateOp(draftStatus));
|
||||
@ -663,6 +720,28 @@ public abstract class AbstractDaemonTest {
|
||||
}
|
||||
}
|
||||
|
||||
private class SetDraftStatusOfCurrentPatchSetOp implements BatchUpdateOp {
|
||||
private final boolean draftStatus;
|
||||
|
||||
SetDraftStatusOfCurrentPatchSetOp(boolean draftStatus) {
|
||||
this.draftStatus = draftStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateChange(ChangeContext ctx) throws Exception {
|
||||
PatchSet currentPatchSet = psUtil.current(db, ctx.getNotes());
|
||||
|
||||
// Change status in ReviewDb.
|
||||
currentPatchSet.setDraft(draftStatus);
|
||||
db.patchSets().update(Collections.singleton(currentPatchSet));
|
||||
|
||||
// Change status in NoteDb.
|
||||
PatchSetState patchSetState = draftStatus ? PatchSetState.DRAFT : PatchSetState.PUBLISHED;
|
||||
ctx.getUpdate(currentPatchSet.getId()).setPatchSetState(patchSetState);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private class DraftStatusOfPatchSetsUpdateOp implements BatchUpdateOp {
|
||||
private final boolean draftStatus;
|
||||
|
||||
@ -783,8 +862,21 @@ public abstract class AbstractDaemonTest {
|
||||
revision(r).submit();
|
||||
}
|
||||
|
||||
protected PushOneCommit.Result amendChangeAsDraft(String changeId) throws Exception {
|
||||
return amendChange(changeId, "refs/drafts/master");
|
||||
protected PushOneCommit.Result amendChangeAndMarkPatchSetAsDraft(String changeId)
|
||||
throws Exception {
|
||||
PushOneCommit.Result r = amendChange(changeId, "refs/for/master");
|
||||
r.assertOkStatus();
|
||||
setCurrentPatchSetAsDraft(r.getChange().getId());
|
||||
return r;
|
||||
}
|
||||
|
||||
protected PushOneCommit.Result amendChangeAndMarkChangeAndPatchSetsAsDraft(String changeId)
|
||||
throws Exception {
|
||||
PushOneCommit.Result r = amendChange(changeId, "refs/for/master");
|
||||
r.assertOkStatus();
|
||||
markChangeAsDraft(r.getChange().getId());
|
||||
setCurrentPatchSetAsDraft(r.getChange().change().getId());
|
||||
return r;
|
||||
}
|
||||
|
||||
protected ChangeInfo info(String id) throws RestApiException {
|
||||
|
@ -834,7 +834,7 @@ public class ChangeIT extends AbstractDaemonTest {
|
||||
|
||||
@Test
|
||||
public void publish() throws Exception {
|
||||
PushOneCommit.Result r = createChange("refs/drafts/master");
|
||||
PushOneCommit.Result r = createDraftChange();
|
||||
assertThat(info(r.getChangeId()).status).isEqualTo(ChangeStatus.DRAFT);
|
||||
gApi.changes().id(r.getChangeId()).publish();
|
||||
assertThat(info(r.getChangeId()).status).isEqualTo(ChangeStatus.NEW);
|
||||
@ -842,7 +842,7 @@ public class ChangeIT extends AbstractDaemonTest {
|
||||
|
||||
@Test
|
||||
public void deleteDraftChange() throws Exception {
|
||||
PushOneCommit.Result r = createChange("refs/drafts/master");
|
||||
PushOneCommit.Result r = createDraftChange();
|
||||
assertThat(query(r.getChangeId())).hasSize(1);
|
||||
assertThat(info(r.getChangeId()).status).isEqualTo(ChangeStatus.DRAFT);
|
||||
gApi.changes().id(r.getChangeId()).delete();
|
||||
@ -2472,8 +2472,9 @@ public class ChangeIT extends AbstractDaemonTest {
|
||||
|
||||
// Amend draft as admin
|
||||
PushOneCommit.Result r2 =
|
||||
amendChange(r1.getChangeId(), "refs/drafts/master", admin, adminTestRepo);
|
||||
amendChange(r1.getChangeId(), "refs/for/master", admin, adminTestRepo);
|
||||
r2.assertOkStatus();
|
||||
setCurrentPatchSetAsDraft(r2.getChange().getId());
|
||||
|
||||
// Add user as reviewer to make this patch set visible
|
||||
AddReviewerInput in = new AddReviewerInput();
|
||||
@ -2485,9 +2486,9 @@ public class ChangeIT extends AbstractDaemonTest {
|
||||
userTestRepo.reset("ps");
|
||||
|
||||
// Amend change as user
|
||||
PushOneCommit.Result r3 =
|
||||
amendChange(r2.getChangeId(), "refs/drafts/master", user, userTestRepo);
|
||||
PushOneCommit.Result r3 = amendChange(r2.getChangeId(), "refs/for/master", user, userTestRepo);
|
||||
r3.assertOkStatus();
|
||||
setCurrentPatchSetAsDraft(r3.getChange().getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -2503,8 +2504,9 @@ public class ChangeIT extends AbstractDaemonTest {
|
||||
|
||||
// Amend draft as admin
|
||||
PushOneCommit.Result r2 =
|
||||
amendChange(r1.getChangeId(), "refs/drafts/master", admin, adminTestRepo);
|
||||
amendChange(r1.getChangeId(), "refs/for/master", admin, adminTestRepo);
|
||||
r2.assertOkStatus();
|
||||
setCurrentPatchSetAsDraft(r2.getChange().getId());
|
||||
|
||||
// Fetch change
|
||||
GitUtil.fetch(userTestRepo, r1.getPatchSet().getRefName() + ":ps");
|
||||
@ -2594,8 +2596,9 @@ public class ChangeIT extends AbstractDaemonTest {
|
||||
|
||||
// Create change as admin
|
||||
PushOneCommit push = pushFactory.create(db, admin.getIdent(), adminTestRepo);
|
||||
PushOneCommit.Result r1 = push.to("refs/drafts/master");
|
||||
PushOneCommit.Result r1 = push.to("refs/for/master");
|
||||
r1.assertOkStatus();
|
||||
markChangeAsDraft(r1.getChange().getId());
|
||||
|
||||
// Add user as reviewer
|
||||
AddReviewerInput in = new AddReviewerInput();
|
||||
@ -2624,8 +2627,9 @@ public class ChangeIT extends AbstractDaemonTest {
|
||||
|
||||
// Create change as admin
|
||||
PushOneCommit push = pushFactory.create(db, admin.getIdent(), adminTestRepo);
|
||||
PushOneCommit.Result r1 = push.to("refs/drafts/master");
|
||||
PushOneCommit.Result r1 = push.to("refs/for/master");
|
||||
r1.assertOkStatus();
|
||||
markChangeAsDraft(p, r1.getChange().getId());
|
||||
|
||||
// Add user as reviewer
|
||||
AddReviewerInput in = new AddReviewerInput();
|
||||
@ -2637,8 +2641,7 @@ public class ChangeIT extends AbstractDaemonTest {
|
||||
userTestRepo.reset("ps");
|
||||
|
||||
// Amend change as user
|
||||
PushOneCommit.Result r2 =
|
||||
amendChange(r1.getChangeId(), "refs/drafts/master", user, userTestRepo);
|
||||
PushOneCommit.Result r2 = amendChange(r1.getChangeId(), "refs/for/master", user, userTestRepo);
|
||||
r2.assertErrorStatus("cannot add patch set to " + r1.getChange().getId().id + ".");
|
||||
}
|
||||
|
||||
|
@ -303,7 +303,7 @@ public class RevisionIT extends AbstractDaemonTest {
|
||||
|
||||
@Test
|
||||
public void deleteDraft() throws Exception {
|
||||
PushOneCommit.Result r = createDraft();
|
||||
PushOneCommit.Result r = createDraftChange();
|
||||
gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).delete();
|
||||
}
|
||||
|
||||
@ -1305,11 +1305,6 @@ public class RevisionIT extends AbstractDaemonTest {
|
||||
return push.to("refs/for/master");
|
||||
}
|
||||
|
||||
private PushOneCommit.Result createDraft() throws Exception {
|
||||
PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo);
|
||||
return push.to("refs/drafts/master");
|
||||
}
|
||||
|
||||
private RevisionApi current(PushOneCommit.Result r) throws Exception {
|
||||
return gApi.changes().id(r.getChangeId()).current();
|
||||
}
|
||||
|
@ -622,21 +622,23 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
|
||||
|
||||
@Test
|
||||
public void pushForMasterAsDraft() throws Exception {
|
||||
// create draft by pushing to 'refs/drafts/'
|
||||
// create draft by pushing to 'refs/drafts/' will get a private change.
|
||||
PushOneCommit.Result r = pushTo("refs/drafts/master");
|
||||
r.assertOkStatus();
|
||||
r.assertChange(Change.Status.DRAFT, null);
|
||||
r.assertChange(Change.Status.NEW, null);
|
||||
|
||||
// create draft by using 'draft' option
|
||||
assertThat(gApi.changes().id(r.getChangeId()).get().isPrivate).isTrue();
|
||||
|
||||
// create draft by using 'draft' option will get a private change, too.
|
||||
r = pushTo("refs/for/master%draft");
|
||||
r.assertOkStatus();
|
||||
r.assertChange(Change.Status.DRAFT, null);
|
||||
r.assertChange(Change.Status.NEW, null);
|
||||
assertThat(gApi.changes().id(r.getChangeId()).get().isPrivate).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publishDraftChangeByPushingNonDraftPatchSet() throws Exception {
|
||||
// create draft change
|
||||
PushOneCommit.Result r = pushTo("refs/drafts/master");
|
||||
PushOneCommit.Result r = createDraftChange();
|
||||
r.assertOkStatus();
|
||||
r.assertChange(Change.Status.DRAFT, null);
|
||||
|
||||
|
@ -323,8 +323,7 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
|
||||
public void uploadPackDraftRefs() throws Exception {
|
||||
allow("refs/heads/*", Permission.READ, REGISTERED_USERS);
|
||||
|
||||
PushOneCommit.Result br =
|
||||
pushFactory.create(db, admin.getIdent(), testRepo).to("refs/drafts/master");
|
||||
PushOneCommit.Result br = createDraftChange();
|
||||
br.assertOkStatus();
|
||||
Change.Id c5 = br.getChange().getId();
|
||||
String r5 = changeRefPrefix(c5);
|
||||
|
@ -172,7 +172,7 @@ public class SubmitOnPushIT extends AbstractDaemonTest {
|
||||
@Test
|
||||
public void submitOnPushingDraft_Error() throws Exception {
|
||||
PushOneCommit.Result r = pushTo("refs/for/master%draft,submit");
|
||||
r.assertErrorStatus("cannot submit draft");
|
||||
r.assertErrorStatus();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -551,7 +551,7 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
|
||||
@Test
|
||||
public void submitDraftPatchSet() throws Exception {
|
||||
PushOneCommit.Result change = createChange();
|
||||
PushOneCommit.Result draft = amendChangeAsDraft(change.getChangeId());
|
||||
PushOneCommit.Result draft = amendChangeAndMarkPatchSetAsDraft(change.getChangeId());
|
||||
Change.Id num = draft.getChange().getId();
|
||||
|
||||
submitWithConflict(
|
||||
|
@ -22,7 +22,6 @@ import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_REVI
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||
import com.google.gerrit.acceptance.PushOneCommit;
|
||||
import com.google.gerrit.acceptance.TestProjectInput;
|
||||
import com.google.gerrit.extensions.api.changes.ActionVisitor;
|
||||
import com.google.gerrit.extensions.api.changes.ReviewInput;
|
||||
@ -43,8 +42,6 @@ import java.util.EnumSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
|
||||
import org.eclipse.jgit.junit.TestRepository;
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
@ -151,7 +148,7 @@ public class ActionsIT extends AbstractDaemonTest {
|
||||
String etag1 = getETag(change);
|
||||
|
||||
setApiUser(admin);
|
||||
String draft = createDraftWithTopic().getChangeId();
|
||||
String draft = createDraftChange("topic").getChangeId();
|
||||
approve(draft);
|
||||
|
||||
setApiUser(user);
|
||||
@ -225,7 +222,7 @@ public class ActionsIT extends AbstractDaemonTest {
|
||||
|
||||
// create another change with the same topic
|
||||
String changeId2 =
|
||||
createChangeWithTopic(testRepo, "foo2", "touching b", "b.txt", "real content")
|
||||
createChangeWithTopic(testRepo, "topic", "touching b", "b.txt", "real content")
|
||||
.getChangeId();
|
||||
int changeNum2 = gApi.changes().id(changeId2).info()._number;
|
||||
approve(changeId2);
|
||||
@ -477,35 +474,4 @@ public class ActionsIT extends AbstractDaemonTest {
|
||||
assertThat(actions).containsKey("description");
|
||||
assertThat(actions).containsKey("rebase");
|
||||
}
|
||||
|
||||
private PushOneCommit.Result createCommitAndPush(
|
||||
TestRepository<InMemoryRepository> repo,
|
||||
String ref,
|
||||
String commitMsg,
|
||||
String fileName,
|
||||
String content)
|
||||
throws Exception {
|
||||
return pushFactory.create(db, admin.getIdent(), repo, commitMsg, fileName, content).to(ref);
|
||||
}
|
||||
|
||||
private PushOneCommit.Result createChangeWithTopic(
|
||||
TestRepository<InMemoryRepository> repo,
|
||||
String topic,
|
||||
String commitMsg,
|
||||
String fileName,
|
||||
String content)
|
||||
throws Exception {
|
||||
assertThat(topic).isNotEmpty();
|
||||
return createCommitAndPush(
|
||||
repo, "refs/for/master/" + name(topic), commitMsg, fileName, content);
|
||||
}
|
||||
|
||||
private PushOneCommit.Result createChangeWithTopic() throws Exception {
|
||||
return createChangeWithTopic(testRepo, "foo2", "a message", "a.txt", "content\n");
|
||||
}
|
||||
|
||||
private PushOneCommit.Result createDraftWithTopic() throws Exception {
|
||||
return createCommitAndPush(
|
||||
testRepo, "refs/drafts/master/" + name("foo2"), "a message", "a.txt", "content\n");
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||
import com.google.gerrit.acceptance.GerritConfig;
|
||||
import com.google.gerrit.acceptance.PushOneCommit;
|
||||
import com.google.gerrit.acceptance.PushOneCommit.Result;
|
||||
import com.google.gerrit.acceptance.RestResponse;
|
||||
@ -43,7 +44,6 @@ import com.google.gerrit.extensions.common.CommitInfo;
|
||||
import com.google.gerrit.extensions.common.MergeInput;
|
||||
import com.google.gerrit.extensions.common.RevisionInfo;
|
||||
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
|
||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
||||
@ -51,13 +51,11 @@ import com.google.gerrit.reviewdb.client.Branch;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.server.config.AnonymousCowardNameProvider;
|
||||
import com.google.gerrit.server.git.ChangeAlreadyMergedException;
|
||||
import com.google.gerrit.testutil.ConfigSuite;
|
||||
import com.google.gerrit.testutil.FakeEmailSender.Message;
|
||||
import com.google.gerrit.testutil.TestTimeUtil;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.PersonIdent;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
@ -69,11 +67,6 @@ import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
public class CreateChangeIT extends AbstractDaemonTest {
|
||||
@ConfigSuite.Config
|
||||
public static Config allowDraftsDisabled() {
|
||||
return allowDraftsDisabledConfig();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void setTimeForTesting() {
|
||||
TestTimeUtil.resetWithClockStep(1, SECONDS);
|
||||
@ -135,8 +128,8 @@ public class CreateChangeIT extends AbstractDaemonTest {
|
||||
|
||||
@Test
|
||||
public void createNewChangeSignedOffByFooter() throws Exception {
|
||||
assume().that(isAllowDrafts()).isTrue();
|
||||
setSignedOffByFooter();
|
||||
|
||||
ChangeInfo info = assertCreateSucceeds(newChangeInput(ChangeStatus.NEW));
|
||||
String message = info.revisions.get(info.currentRevision).commit.message;
|
||||
assertThat(message)
|
||||
@ -146,16 +139,10 @@ public class CreateChangeIT extends AbstractDaemonTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createNewDraftChange() throws Exception {
|
||||
assume().that(isAllowDrafts()).isTrue();
|
||||
assertCreateSucceeds(newChangeInput(ChangeStatus.DRAFT));
|
||||
}
|
||||
|
||||
@Test
|
||||
@GerritConfig(name = "change.allowDrafts", value = "true")
|
||||
public void createNewDraftChangeNotAllowed() throws Exception {
|
||||
assume().that(isAllowDrafts()).isFalse();
|
||||
ChangeInput ci = newChangeInput(ChangeStatus.DRAFT);
|
||||
assertCreateFails(ci, MethodNotAllowedException.class, "draft workflow is disabled");
|
||||
assertCreateFails(ci, BadRequestException.class, "unsupported change status");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -21,7 +21,6 @@ import com.google.common.collect.Iterables;
|
||||
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||
import com.google.gerrit.acceptance.NoHttpd;
|
||||
import com.google.gerrit.acceptance.PushOneCommit;
|
||||
import com.google.gerrit.acceptance.PushOneCommit.Result;
|
||||
import com.google.gerrit.acceptance.TestAccount;
|
||||
import com.google.gerrit.extensions.api.changes.DraftInput;
|
||||
import com.google.gerrit.extensions.api.changes.ReviewInput;
|
||||
@ -40,8 +39,6 @@ import com.google.gerrit.server.config.AllUsersName;
|
||||
import com.google.gerrit.server.query.change.ChangeData;
|
||||
import com.google.inject.Inject;
|
||||
import java.util.HashMap;
|
||||
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
|
||||
import org.eclipse.jgit.junit.TestRepository;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.junit.Test;
|
||||
@ -189,14 +186,9 @@ public class DeleteDraftPatchSetIT extends AbstractDaemonTest {
|
||||
|
||||
@Test
|
||||
public void deleteCurrentDraftPatchSetWhenPreviousPatchSetDoesNotExist() throws Exception {
|
||||
PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo);
|
||||
String changeId = push.to("refs/for/master").getChangeId();
|
||||
pushFactory
|
||||
.create(db, admin.getIdent(), testRepo, PushOneCommit.SUBJECT, "b.txt", "foo", changeId)
|
||||
.to("refs/drafts/master");
|
||||
pushFactory
|
||||
.create(db, admin.getIdent(), testRepo, PushOneCommit.SUBJECT, "b.txt", "bar", changeId)
|
||||
.to("refs/drafts/master");
|
||||
String changeId = createChange().getChangeId();
|
||||
amendChangeAndMarkPatchSetAsDraft(changeId);
|
||||
amendChangeAndMarkPatchSetAsDraft(changeId);
|
||||
|
||||
deletePatchSet(changeId, 2);
|
||||
deletePatchSet(changeId, 3);
|
||||
@ -209,20 +201,13 @@ public class DeleteDraftPatchSetIT extends AbstractDaemonTest {
|
||||
|
||||
@Test
|
||||
public void deleteDraftPatchSetAndPushNewDraftPatchSet() throws Exception {
|
||||
String ref = "refs/drafts/master";
|
||||
|
||||
// Clone repository
|
||||
TestRepository<InMemoryRepository> testRepo = cloneProject(project, admin);
|
||||
|
||||
// Create change
|
||||
PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo);
|
||||
PushOneCommit.Result r1 = push.to(ref);
|
||||
r1.assertOkStatus();
|
||||
PushOneCommit.Result r1 = createDraftChange();
|
||||
String changeId = r1.getChangeId();
|
||||
String revPs1 = r1.getChange().currentPatchSet().getRevision().get();
|
||||
|
||||
// Push draft patch set
|
||||
PushOneCommit.Result r2 = amendChange(r1.getChangeId(), ref, admin, testRepo);
|
||||
r2.assertOkStatus();
|
||||
PushOneCommit.Result r2 = amendChangeAndMarkPatchSetAsDraft(changeId);
|
||||
String revPs2 = r2.getChange().currentPatchSet().getRevision().get();
|
||||
|
||||
assertThat(gApi.changes().id(r1.getChange().getId().get()).get().currentRevision)
|
||||
@ -235,8 +220,7 @@ public class DeleteDraftPatchSetIT extends AbstractDaemonTest {
|
||||
.isEqualTo(revPs1);
|
||||
|
||||
// Push new draft patch set
|
||||
PushOneCommit.Result r3 = amendChange(r1.getChangeId(), ref, admin, testRepo);
|
||||
r3.assertOkStatus();
|
||||
amendChangeAndMarkPatchSetAsDraft(changeId);
|
||||
String revPs3 = r2.getChange().currentPatchSet().getRevision().get();
|
||||
|
||||
assertThat(gApi.changes().id(r1.getChange().getId().get()).get().currentRevision)
|
||||
@ -259,21 +243,6 @@ public class DeleteDraftPatchSetIT extends AbstractDaemonTest {
|
||||
}
|
||||
}
|
||||
|
||||
private String createDraftChangeWith2PS() throws Exception {
|
||||
PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo);
|
||||
Result result = push.to("refs/drafts/master");
|
||||
push =
|
||||
pushFactory.create(
|
||||
db,
|
||||
admin.getIdent(),
|
||||
testRepo,
|
||||
PushOneCommit.SUBJECT,
|
||||
"b.txt",
|
||||
"4711",
|
||||
result.getChangeId());
|
||||
return push.to("refs/drafts/master").getChangeId();
|
||||
}
|
||||
|
||||
private PatchSet getCurrentPatchSet(String changeId) throws Exception {
|
||||
return getChange(changeId).currentPatchSet();
|
||||
}
|
||||
|
@ -29,6 +29,8 @@ import com.google.gerrit.extensions.client.ChangeStatus;
|
||||
import com.google.gerrit.extensions.client.ReviewerState;
|
||||
import com.google.gerrit.extensions.common.AccountInfo;
|
||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||
import com.google.gerrit.extensions.common.EditInfo;
|
||||
import com.google.gerrit.extensions.common.EditInfoSubject;
|
||||
import com.google.gerrit.extensions.common.LabelInfo;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
|
||||
@ -38,6 +40,7 @@ import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.testutil.ConfigSuite;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -49,7 +52,7 @@ public class DraftChangeIT extends AbstractDaemonTest {
|
||||
|
||||
@Test
|
||||
public void forceCreateAndPublishDraftChangeWhenAllowDraftsDisabled() throws Exception {
|
||||
PushOneCommit.Result result = forceDraftChange();
|
||||
PushOneCommit.Result result = createDraftChange();
|
||||
result.assertOkStatus();
|
||||
String changeId = result.getChangeId();
|
||||
String triplet = project.get() + "~master~" + changeId;
|
||||
@ -105,7 +108,7 @@ public class DraftChangeIT extends AbstractDaemonTest {
|
||||
pushFactory.create(db, user.getIdent(), testRepo).to("refs/for/master");
|
||||
Change.Id id = changeResult.getChange().getId();
|
||||
markChangeAsDraft(id);
|
||||
setDraftStatusOfPatchSetsOfChange(id, true);
|
||||
setDraftStatusOfPatchSets(id, true);
|
||||
|
||||
String changeId = changeResult.getChangeId();
|
||||
exception.expect(MethodNotAllowedException.class);
|
||||
@ -125,7 +128,7 @@ public class DraftChangeIT extends AbstractDaemonTest {
|
||||
pushFactory.create(db, user.getIdent(), testRepo).to("refs/for/master");
|
||||
Change.Id id = changeResult.getChange().getId();
|
||||
markChangeAsDraft(id);
|
||||
setDraftStatusOfPatchSetsOfChange(id, true);
|
||||
setDraftStatusOfPatchSets(id, true);
|
||||
|
||||
String changeId = changeResult.getChangeId();
|
||||
|
||||
@ -151,7 +154,7 @@ public class DraftChangeIT extends AbstractDaemonTest {
|
||||
|
||||
PushOneCommit.Result changeResult = createDraftChange();
|
||||
Change.Id id = changeResult.getChange().getId();
|
||||
setDraftStatusOfPatchSetsOfChange(id, false);
|
||||
setDraftStatusOfPatchSets(id, false);
|
||||
|
||||
String changeId = changeResult.getChangeId();
|
||||
exception.expect(ResourceConflictException.class);
|
||||
@ -194,8 +197,8 @@ public class DraftChangeIT extends AbstractDaemonTest {
|
||||
@Test
|
||||
public void createDraftChangeWhenDraftsNotAllowed() throws Exception {
|
||||
assume().that(isAllowDrafts()).isFalse();
|
||||
PushOneCommit.Result r = createDraftChange();
|
||||
r.assertErrorStatus("draft workflow is disabled");
|
||||
PushOneCommit.Result r = pushTo("refs/drafts/master");
|
||||
r.assertErrorStatus();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -228,6 +231,53 @@ public class DraftChangeIT extends AbstractDaemonTest {
|
||||
assertThat(label.all.get(0).value).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pushWithDraftOptionGetsPrivateChange() throws Exception {
|
||||
assume().that(isAllowDrafts()).isTrue();
|
||||
PushOneCommit.Result result = createChange("refs/drafts/master");
|
||||
String changeId = result.getChangeId();
|
||||
ChangeInfo changeInfo = gApi.changes().id(changeId).get();
|
||||
|
||||
assertThat(changeInfo.status).isEqualTo(ChangeStatus.NEW);
|
||||
assertThat(changeInfo.isPrivate).isEqualTo(true);
|
||||
assertThat(changeInfo.revisions).hasSize(1);
|
||||
assertThat(Iterables.getOnlyElement(changeInfo.revisions.values()).draft).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pushWithDraftOptionToExistingNewChangeGetsChangeEdit() throws Exception {
|
||||
assume().that(isAllowDrafts()).isTrue();
|
||||
pushWithDraftOptionToExistingChangeGetsChangeEdit(createChange().getChangeId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pushWithDraftOptionToExistingDraftChangeGetsChangeEdit() throws Exception {
|
||||
assume().that(isAllowDrafts()).isTrue();
|
||||
pushWithDraftOptionToExistingChangeGetsChangeEdit(createDraftChange().getChangeId());
|
||||
}
|
||||
|
||||
private void pushWithDraftOptionToExistingChangeGetsChangeEdit(String changeId) throws Exception {
|
||||
Optional<EditInfo> edit = getEdit(changeId);
|
||||
EditInfoSubject.assertThat(edit).isAbsent();
|
||||
|
||||
ChangeInfo changeInfo = gApi.changes().id(changeId).get();
|
||||
ChangeStatus originalChangeStatus = changeInfo.status;
|
||||
Boolean originalPatchSetStatus = Iterables.getOnlyElement(changeInfo.revisions.values()).draft;
|
||||
|
||||
PushOneCommit.Result result = amendChange(changeId, "refs/drafts/master");
|
||||
result.assertOkStatus();
|
||||
|
||||
changeInfo = gApi.changes().id(changeId).get();
|
||||
assertThat(changeInfo.status).isEqualTo(originalChangeStatus);
|
||||
assertThat(changeInfo.isPrivate).isNull();
|
||||
assertThat(changeInfo.revisions).hasSize(1);
|
||||
assertThat(Iterables.getOnlyElement(changeInfo.revisions.values()).draft)
|
||||
.isEqualTo(originalPatchSetStatus);
|
||||
|
||||
edit = getEdit(changeId);
|
||||
EditInfoSubject.assertThat(edit).isPresent();
|
||||
}
|
||||
|
||||
private static RestResponse deleteChange(String changeId, RestSession s) throws Exception {
|
||||
return s.delete("/changes/" + changeId);
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import com.google.gerrit.extensions.common.ChangeInfo;
|
||||
import com.google.gerrit.extensions.common.FileInfo;
|
||||
import com.google.gerrit.extensions.common.RevisionInfo;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.testutil.ConfigSuite;
|
||||
import java.util.EnumSet;
|
||||
@ -156,14 +157,8 @@ public class SubmittedTogetherIT extends AbstractDaemonTest {
|
||||
|
||||
@Test
|
||||
public void hiddenDraftInTopic() throws Exception {
|
||||
RevCommit initialHead = getRemoteHead();
|
||||
RevCommit a = commitBuilder().add("a", "1").message("change 1").create();
|
||||
pushHead(testRepo, "refs/for/master/" + name("topic"), false);
|
||||
String id1 = getChangeId(a);
|
||||
|
||||
testRepo.reset(initialHead);
|
||||
commitBuilder().add("b", "2").message("invisible change").create();
|
||||
pushHead(testRepo, "refs/drafts/master/" + name("topic"), false);
|
||||
String id1 = createChange("subject", "a", "1", "topic").getChangeId();
|
||||
createDraftChange("topic");
|
||||
|
||||
setApiUser(user);
|
||||
SubmittedTogetherInfo result =
|
||||
@ -181,14 +176,8 @@ public class SubmittedTogetherIT extends AbstractDaemonTest {
|
||||
|
||||
@Test
|
||||
public void hiddenDraftInTopicOldApi() throws Exception {
|
||||
RevCommit initialHead = getRemoteHead();
|
||||
RevCommit a = commitBuilder().add("a", "1").message("change 1").create();
|
||||
pushHead(testRepo, "refs/for/master/" + name("topic"), false);
|
||||
String id1 = getChangeId(a);
|
||||
|
||||
testRepo.reset(initialHead);
|
||||
commitBuilder().add("b", "2").message("invisible change").create();
|
||||
pushHead(testRepo, "refs/drafts/master/" + name("topic"), false);
|
||||
String id1 = createChange("subject", "a", "1", "topic").getChangeId();
|
||||
createDraftChange("topic");
|
||||
|
||||
setApiUser(user);
|
||||
if (isSubmitWholeTopicEnabled()) {
|
||||
@ -209,18 +198,16 @@ public class SubmittedTogetherIT extends AbstractDaemonTest {
|
||||
String id1 = getChangeId(a1);
|
||||
|
||||
testRepo.reset(initialHead);
|
||||
RevCommit parent = commitBuilder().message("parent").create();
|
||||
pushHead(testRepo, "refs/for/master", false);
|
||||
String parentId = getChangeId(parent);
|
||||
String parentId = createChange().getChangeId();
|
||||
|
||||
// TODO(jrn): use insertChangeId(id1) once jgit TestRepository accepts
|
||||
// the leading "I".
|
||||
// TODO(jrn): use insertChangeId(id1) once jgit TestRepository accepts the leading "I".
|
||||
commitBuilder()
|
||||
.insertChangeId(id1.substring(1))
|
||||
.add("a", "2")
|
||||
.message("draft patch set on change 1")
|
||||
.create();
|
||||
pushHead(testRepo, "refs/drafts/master/" + name("topic"), false);
|
||||
pushHead(testRepo, "refs/for/master/" + name("topic"), false);
|
||||
setCurrentPatchSetAsDraft(new Change.Id(gApi.changes().id(id1).get()._number));
|
||||
|
||||
testRepo.reset(initialHead);
|
||||
RevCommit b = commitBuilder().message("change with same topic").create();
|
||||
@ -243,16 +230,12 @@ public class SubmittedTogetherIT extends AbstractDaemonTest {
|
||||
@Test
|
||||
public void doNotRevealVisibleAncestorOfHiddenDraft() throws Exception {
|
||||
RevCommit initialHead = getRemoteHead();
|
||||
commitBuilder().message("parent").create();
|
||||
pushHead(testRepo, "refs/for/master", false);
|
||||
createChange().getChangeId();
|
||||
|
||||
commitBuilder().message("draft").create();
|
||||
pushHead(testRepo, "refs/drafts/master/" + name("topic"), false);
|
||||
createDraftChange("topic");
|
||||
|
||||
testRepo.reset(initialHead);
|
||||
RevCommit change = commitBuilder().message("same topic").create();
|
||||
pushHead(testRepo, "refs/for/master/" + name("topic"), false);
|
||||
String id = getChangeId(change);
|
||||
String id = createChange("subject", "b", "1", "topic").getChangeId();
|
||||
|
||||
setApiUser(user);
|
||||
SubmittedTogetherInfo result =
|
||||
|
@ -42,6 +42,7 @@ import com.google.gerrit.extensions.api.changes.ReviewInput;
|
||||
import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
|
||||
import com.google.gerrit.extensions.api.changes.ReviewInput.DraftHandling;
|
||||
import com.google.gerrit.extensions.client.Side;
|
||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||
import com.google.gerrit.extensions.common.CommentInfo;
|
||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||
@ -485,23 +486,23 @@ public class ChangeRebuilderIT extends AbstractDaemonTest {
|
||||
|
||||
// TODO(dborowitz): Re-enable these assertions once we fix auto-rebuilding
|
||||
// in the BatchUpdate path.
|
||||
//// As an implementation detail, change wasn't actually rebuilt inside the
|
||||
//// BatchUpdate transaction, but it was rebuilt during read for the
|
||||
//// subsequent reindex. Thus it's impossible to actually observe an
|
||||
//// out-of-date state in the caller.
|
||||
//assertChangeUpToDate(true, id);
|
||||
// As an implementation detail, change wasn't actually rebuilt inside the
|
||||
// BatchUpdate transaction, but it was rebuilt during read for the
|
||||
// subsequent reindex. Thus it's impossible to actually observe an
|
||||
// out-of-date state in the caller.
|
||||
// assertChangeUpToDate(true, id);
|
||||
|
||||
//// Check that the bundles are equal.
|
||||
//ChangeNotes notes = notesFactory.create(dbProvider.get(), project, id);
|
||||
//ChangeBundle actual = ChangeBundle.fromNotes(commentsUtil, notes);
|
||||
//ChangeBundle expected = bundleReader.fromReviewDb(getUnwrappedDb(), id);
|
||||
//assertThat(actual.differencesFrom(expected)).isEmpty();
|
||||
//assertThat(
|
||||
// Check that the bundles are equal.
|
||||
// ChangeNotes notes = notesFactory.create(dbProvider.get(), project, id);
|
||||
// ChangeBundle actual = ChangeBundle.fromNotes(commentsUtil, notes);
|
||||
// ChangeBundle expected = bundleReader.fromReviewDb(getUnwrappedDb(), id);
|
||||
// assertThat(actual.differencesFrom(expected)).isEmpty();
|
||||
// assertThat(
|
||||
// Iterables.transform(
|
||||
// notes.getChangeMessages(),
|
||||
// ChangeMessage::getMessage))
|
||||
// .contains(msg);
|
||||
//assertThat(actual.getChange().getTopic()).isEqualTo(topic);
|
||||
// assertThat(actual.getChange().getTopic()).isEqualTo(topic);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -817,28 +818,16 @@ public class ChangeRebuilderIT extends AbstractDaemonTest {
|
||||
|
||||
@Test
|
||||
public void deleteDraftPS1WithNoOtherEntities() throws Exception {
|
||||
PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo);
|
||||
PushOneCommit.Result r = push.to("refs/drafts/master");
|
||||
push =
|
||||
pushFactory.create(
|
||||
db,
|
||||
admin.getIdent(),
|
||||
testRepo,
|
||||
PushOneCommit.SUBJECT,
|
||||
"b.txt",
|
||||
"4711",
|
||||
r.getChangeId());
|
||||
r = push.to("refs/drafts/master");
|
||||
PatchSet.Id psId = r.getPatchSetId();
|
||||
Change.Id id = psId.getParentKey();
|
||||
|
||||
gApi.changes().id(r.getChangeId()).revision(1).delete();
|
||||
String r = createDraftChangeWith2PS();
|
||||
gApi.changes().id(r).revision(1).delete();
|
||||
ChangeInfo changeInfo = get(r);
|
||||
|
||||
Change.Id id = new Change.Id(changeInfo._number);
|
||||
checker.rebuildAndCheckChanges(id);
|
||||
|
||||
setNotesMigration(true, true);
|
||||
ChangeNotes notes = notesFactory.create(db, project, id);
|
||||
assertThat(notes.getPatchSets().keySet()).containsExactly(psId);
|
||||
assertThat(notes.getPatchSets().keySet()).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -93,72 +93,6 @@ public class ProjectWatchIT extends AbstractDaemonTest {
|
||||
assertThat(m.body()).contains("Gerrit-PatchSet: 2\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noNotificationForDraftChangesForWatchersInNotifyConfig() throws Exception {
|
||||
Address addr = new Address("Watcher", "watcher@example.com");
|
||||
NotifyConfig nc = new NotifyConfig();
|
||||
nc.addEmail(addr);
|
||||
nc.setName("team");
|
||||
nc.setHeader(NotifyConfig.Header.TO);
|
||||
nc.setTypes(EnumSet.of(NotifyType.NEW_CHANGES, NotifyType.ALL_COMMENTS));
|
||||
|
||||
ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
|
||||
cfg.putNotifyConfig("team", nc);
|
||||
saveProjectConfig(project, cfg);
|
||||
|
||||
PushOneCommit.Result r =
|
||||
pushFactory
|
||||
.create(db, admin.getIdent(), testRepo, "draft change", "a", "a1")
|
||||
.to("refs/for/master%draft");
|
||||
r.assertOkStatus();
|
||||
|
||||
assertThat(sender.getMessages()).isEmpty();
|
||||
|
||||
setApiUser(admin);
|
||||
ReviewInput in = new ReviewInput();
|
||||
in.message = "comment";
|
||||
gApi.changes().id(r.getChangeId()).current().review(in);
|
||||
|
||||
assertThat(sender.getMessages()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noNotificationForDraftPatchSetsForWatchersInNotifyConfig() throws Exception {
|
||||
Address addr = new Address("Watcher", "watcher@example.com");
|
||||
NotifyConfig nc = new NotifyConfig();
|
||||
nc.addEmail(addr);
|
||||
nc.setName("team");
|
||||
nc.setHeader(NotifyConfig.Header.TO);
|
||||
nc.setTypes(EnumSet.of(NotifyType.NEW_PATCHSETS, NotifyType.ALL_COMMENTS));
|
||||
|
||||
ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
|
||||
cfg.putNotifyConfig("team", nc);
|
||||
saveProjectConfig(project, cfg);
|
||||
|
||||
PushOneCommit.Result r =
|
||||
pushFactory
|
||||
.create(db, admin.getIdent(), testRepo, "subject", "a", "a1")
|
||||
.to("refs/for/master");
|
||||
r.assertOkStatus();
|
||||
|
||||
sender.clear();
|
||||
|
||||
r =
|
||||
pushFactory
|
||||
.create(db, admin.getIdent(), testRepo, "subject", "a", "a2", r.getChangeId())
|
||||
.to("refs/for/master%draft");
|
||||
r.assertOkStatus();
|
||||
|
||||
assertThat(sender.getMessages()).isEmpty();
|
||||
|
||||
setApiUser(admin);
|
||||
ReviewInput in = new ReviewInput();
|
||||
in.message = "comment";
|
||||
gApi.changes().id(r.getChangeId()).current().review(in);
|
||||
|
||||
assertThat(sender.getMessages()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noNotificationForPrivateChangesForWatchersInNotifyConfig() throws Exception {
|
||||
Address addr = new Address("Watcher", "watcher@example.com");
|
||||
@ -528,70 +462,6 @@ public class ProjectWatchIT extends AbstractDaemonTest {
|
||||
assertThat(sender.getMessages()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void watchProjectNoNotificationForDraftChange() throws Exception {
|
||||
// watch project
|
||||
String watchedProject = createProject("watchedProject").get();
|
||||
setApiUser(user);
|
||||
watch(watchedProject);
|
||||
|
||||
// push a draft change to watched project -> should not trigger email notification
|
||||
setApiUser(admin);
|
||||
TestRepository<InMemoryRepository> watchedRepo =
|
||||
cloneProject(new Project.NameKey(watchedProject), admin);
|
||||
PushOneCommit.Result r =
|
||||
pushFactory
|
||||
.create(db, admin.getIdent(), watchedRepo, "draft change", "a", "a1")
|
||||
.to("refs/for/master%draft");
|
||||
r.assertOkStatus();
|
||||
|
||||
// assert email notification
|
||||
assertThat(sender.getMessages()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void watchProjectNotifyOnDraftChange() throws Exception {
|
||||
String watchedProject = createProject("watchedProject").get();
|
||||
|
||||
// create group that can view all drafts
|
||||
GroupInfo groupThatCanViewDrafts = gApi.groups().create("groupThatCanViewDrafts").get();
|
||||
grant(
|
||||
new Project.NameKey(watchedProject),
|
||||
"refs/*",
|
||||
Permission.VIEW_DRAFTS,
|
||||
false,
|
||||
new AccountGroup.UUID(groupThatCanViewDrafts.id));
|
||||
|
||||
// watch project as user that can't view drafts
|
||||
setApiUser(user);
|
||||
watch(watchedProject);
|
||||
|
||||
// watch project as user that can view all drafts
|
||||
TestAccount userThatCanViewDrafts =
|
||||
accountCreator.create("user2", "user2@test.com", "User2", groupThatCanViewDrafts.name);
|
||||
setApiUser(userThatCanViewDrafts);
|
||||
watch(watchedProject);
|
||||
|
||||
// push a draft change to watched project -> should trigger email notification for
|
||||
// userThatCanViewDrafts, but not for user
|
||||
setApiUser(admin);
|
||||
TestRepository<InMemoryRepository> watchedRepo =
|
||||
cloneProject(new Project.NameKey(watchedProject), admin);
|
||||
PushOneCommit.Result r =
|
||||
pushFactory
|
||||
.create(db, admin.getIdent(), watchedRepo, "TRIGGER", "a", "a1")
|
||||
.to("refs/for/master%draft");
|
||||
r.assertOkStatus();
|
||||
|
||||
// assert email notification
|
||||
List<Message> messages = sender.getMessages();
|
||||
assertThat(messages).hasSize(1);
|
||||
Message m = messages.get(0);
|
||||
assertThat(m.rcpt()).containsExactly(userThatCanViewDrafts.emailAddress);
|
||||
assertThat(m.body()).contains("Change subject: TRIGGER\n");
|
||||
assertThat(m.body()).contains("Gerrit-PatchSet: 1\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void watchProjectNoNotificationForIgnoredChange() throws Exception {
|
||||
// watch project
|
||||
|
@ -292,7 +292,7 @@ public class QueryIT extends AbstractDaemonTest {
|
||||
@Test
|
||||
public void queryWithNonVisibleCurrentPatchSet() throws Exception {
|
||||
String changeId = createChange().getChangeId();
|
||||
amendChangeAsDraft(changeId);
|
||||
amendChangeAndMarkPatchSetAsDraft(changeId);
|
||||
String query = "--current-patch-set --patch-sets " + changeId;
|
||||
List<ChangeAttribute> changes = executeSuccessfulQuery(query);
|
||||
assertThat(changes.size()).isEqualTo(1);
|
||||
|
@ -14,7 +14,6 @@
|
||||
|
||||
package com.google.gerrit.client.changes;
|
||||
|
||||
import com.google.gerrit.client.Gerrit;
|
||||
import com.google.gerrit.client.info.AccountInfo;
|
||||
import com.google.gerrit.client.info.ChangeInfo;
|
||||
import com.google.gerrit.client.info.ChangeInfo.CommitInfo;
|
||||
@ -24,7 +23,6 @@ import com.google.gerrit.client.rpc.CallbackGroup.Callback;
|
||||
import com.google.gerrit.client.rpc.NativeString;
|
||||
import com.google.gerrit.client.rpc.RestApi;
|
||||
import com.google.gerrit.common.Nullable;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gwt.core.client.JavaScriptObject;
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
@ -39,13 +37,7 @@ public class ChangeApi {
|
||||
call(project, id, "abandon").post(input, cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new change.
|
||||
*
|
||||
* <p>The new change is created as DRAFT unless the draft workflow is disabled by
|
||||
* `change.allowDrafts = false` in the configuration, in which case the new change is created as
|
||||
* NEW.
|
||||
*/
|
||||
/** Create a new work-in-progress change. */
|
||||
public static void createChange(
|
||||
String project,
|
||||
String branch,
|
||||
@ -59,10 +51,7 @@ public class ChangeApi {
|
||||
input.topic(emptyToNull(topic));
|
||||
input.subject(emptyToNull(subject));
|
||||
input.baseChange(emptyToNull(base));
|
||||
|
||||
if (Gerrit.info().change().allowDrafts()) {
|
||||
input.status(Change.Status.DRAFT.toString());
|
||||
}
|
||||
input.workInProgress(true);
|
||||
|
||||
new RestApi("/changes/").post(input, cb);
|
||||
}
|
||||
@ -350,6 +339,8 @@ public class ChangeApi {
|
||||
|
||||
public final native void baseChange(String b) /*-{ if(b)this.base_change=b; }-*/;
|
||||
|
||||
public final native void workInProgress(Boolean b) /*-{ if(b)this.work_in_progress=b; }-*/;
|
||||
|
||||
protected CreateChangeInput() {}
|
||||
}
|
||||
|
||||
|
@ -281,11 +281,6 @@ public class ChangeInserter implements InsertChangeOp {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChangeInserter setDraft(boolean draft) {
|
||||
checkState(change == null, "setDraft(boolean) only valid before creating change");
|
||||
return setStatus(draft ? Change.Status.DRAFT : Change.Status.NEW);
|
||||
}
|
||||
|
||||
public ChangeInserter setWorkInProgress(boolean workInProgress) {
|
||||
this.workInProgress = workInProgress;
|
||||
return this;
|
||||
@ -399,7 +394,6 @@ public class ChangeInserter implements InsertChangeOp {
|
||||
update.setRevertOf(revertOf.get());
|
||||
}
|
||||
|
||||
boolean draft = status == Change.Status.DRAFT;
|
||||
List<String> newGroups = groups;
|
||||
if (newGroups.isEmpty()) {
|
||||
newGroups = GroupCollector.getDefaultGroups(commitId);
|
||||
@ -411,7 +405,7 @@ public class ChangeInserter implements InsertChangeOp {
|
||||
update,
|
||||
psId,
|
||||
commitId,
|
||||
draft,
|
||||
false,
|
||||
newGroups,
|
||||
pushCert,
|
||||
patchSetDescription);
|
||||
|
@ -29,7 +29,6 @@ import com.google.gerrit.extensions.common.ChangeInput;
|
||||
import com.google.gerrit.extensions.common.MergeInput;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
|
||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||
import com.google.gerrit.extensions.restapi.Response;
|
||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||
@ -108,7 +107,6 @@ public class CreateChange
|
||||
private final ChangeJson.Factory jsonFactory;
|
||||
private final ChangeFinder changeFinder;
|
||||
private final PatchSetUtil psUtil;
|
||||
private final boolean allowDrafts;
|
||||
private final MergeUtil.Factory mergeUtilFactory;
|
||||
private final SubmitType submitType;
|
||||
private final NotifyUtil notifyUtil;
|
||||
@ -148,7 +146,6 @@ public class CreateChange
|
||||
this.jsonFactory = json;
|
||||
this.changeFinder = changeFinder;
|
||||
this.psUtil = psUtil;
|
||||
this.allowDrafts = config.getBoolean("change", "allowDrafts", true);
|
||||
this.submitType = config.getEnum("project", null, "submitType", SubmitType.MERGE_IF_NECESSARY);
|
||||
this.mergeUtilFactory = mergeUtilFactory;
|
||||
this.notifyUtil = notifyUtil;
|
||||
@ -172,12 +169,9 @@ public class CreateChange
|
||||
}
|
||||
|
||||
if (input.status != null) {
|
||||
if (input.status != ChangeStatus.NEW && input.status != ChangeStatus.DRAFT) {
|
||||
if (input.status != ChangeStatus.NEW) {
|
||||
throw new BadRequestException("unsupported change status");
|
||||
}
|
||||
if (!allowDrafts && input.status == ChangeStatus.DRAFT) {
|
||||
throw new MethodNotAllowedException("draft workflow is disabled");
|
||||
}
|
||||
}
|
||||
|
||||
ProjectResource rsrc = projectsCollection.parse(input.project);
|
||||
@ -264,7 +258,6 @@ public class CreateChange
|
||||
topic = Strings.emptyToNull(topic.trim());
|
||||
}
|
||||
ins.setTopic(topic);
|
||||
ins.setDraft(input.status == ChangeStatus.DRAFT);
|
||||
ins.setPrivate(input.isPrivate == null ? privateByDefault : input.isPrivate);
|
||||
ins.setWorkInProgress(input.workInProgress != null && input.workInProgress);
|
||||
ins.setGroups(groups);
|
||||
|
@ -622,7 +622,7 @@ class ReceiveCommits {
|
||||
if (!updated.isEmpty()) {
|
||||
addMessage("");
|
||||
addMessage("Updated Changes:");
|
||||
boolean edit = magicBranch != null && magicBranch.edit;
|
||||
boolean edit = magicBranch != null && (magicBranch.edit || magicBranch.draft);
|
||||
Boolean isPrivate = null;
|
||||
Boolean wip = null;
|
||||
if (magicBranch != null) {
|
||||
@ -1159,7 +1159,12 @@ class ReceiveCommits {
|
||||
@Option(name = "--topic", metaVar = "NAME", usage = "attach topic to changes")
|
||||
String topic;
|
||||
|
||||
@Option(name = "--draft", usage = "mark new/updated changes as draft")
|
||||
@Option(
|
||||
name = "--draft",
|
||||
usage =
|
||||
"Will be removed. Before that, this option will be mapped to '--private'"
|
||||
+ "for new changes and '--edit' for existing changes"
|
||||
)
|
||||
boolean draft;
|
||||
|
||||
@Option(name = "--private", usage = "mark new/updated change as private")
|
||||
@ -1463,6 +1468,7 @@ class ReceiveCommits {
|
||||
}
|
||||
|
||||
if (magicBranch.draft) {
|
||||
// TODO(xchangcheng): reject all after repo-tool supports private and wip changes.
|
||||
if (!receiveConfig.allowDrafts) {
|
||||
errors.put(ReceiveError.CODE_REVIEW, ref);
|
||||
reject(cmd, "draft workflow is disabled");
|
||||
@ -2115,14 +2121,15 @@ class ReceiveCommits {
|
||||
changeInserterFactory
|
||||
.create(changeId, commit, refName)
|
||||
.setTopic(magicBranch.topic)
|
||||
.setPrivate(magicBranch.isPrivate || (privateByDefault && !magicBranch.removePrivate))
|
||||
.setPrivate(
|
||||
magicBranch.draft
|
||||
|| magicBranch.isPrivate
|
||||
|| (privateByDefault && !magicBranch.removePrivate))
|
||||
.setWorkInProgress(magicBranch.workInProgress)
|
||||
// Changes already validated in validateNewCommits.
|
||||
.setValidate(false);
|
||||
|
||||
if (magicBranch.draft) {
|
||||
ins.setDraft(magicBranch.draft);
|
||||
} else if (magicBranch.merged) {
|
||||
if (magicBranch.merged) {
|
||||
ins.setStatus(Change.Status.MERGED);
|
||||
}
|
||||
cmd = new ReceiveCommand(ObjectId.zeroId(), commit, ins.getPatchSetId().toRefName());
|
||||
@ -2144,8 +2151,7 @@ class ReceiveCommits {
|
||||
checkNotNull(magicBranch);
|
||||
recipients.add(magicBranch.getMailRecipients());
|
||||
approvals = magicBranch.labels;
|
||||
recipients.add(
|
||||
getRecipientsFromFooters(db, accountResolver, magicBranch.draft, footerLines));
|
||||
recipients.add(getRecipientsFromFooters(db, accountResolver, false, footerLines));
|
||||
recipients.remove(me);
|
||||
StringBuilder msg =
|
||||
new StringBuilder(
|
||||
@ -2429,7 +2435,7 @@ class ReceiveCommits {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (magicBranch != null && magicBranch.edit) {
|
||||
if (magicBranch != null && (magicBranch.edit || magicBranch.draft)) {
|
||||
return newEdit();
|
||||
}
|
||||
|
||||
@ -2485,7 +2491,7 @@ class ReceiveCommits {
|
||||
}
|
||||
|
||||
void addOps(BatchUpdate bu, @Nullable Task progress) throws IOException {
|
||||
if (magicBranch != null && magicBranch.edit) {
|
||||
if (magicBranch != null && (magicBranch.edit || magicBranch.draft)) {
|
||||
bu.addOp(notes.getChangeId(), new ReindexOnlyOp());
|
||||
if (prev != null) {
|
||||
bu.addRepoOnlyOp(new UpdateOneRefOp(prev));
|
||||
|
Loading…
Reference in New Issue
Block a user