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:
David Ostrovsky 2017-07-27 08:05:43 +02:00
parent 03077adb37
commit 4b44bdc702
23 changed files with 268 additions and 401 deletions

View File

@ -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

View File

@ -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

View File

@ -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`|

View File

@ -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:

View File

@ -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 {

View File

@ -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 + ".");
}

View File

@ -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();
}

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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(

View File

@ -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");
}
}

View File

@ -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

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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 =

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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() {}
}

View File

@ -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);

View File

@ -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);

View File

@ -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));