Discontinue draft workflow

Migration is implemented to either replace draft changes with private
(default) or work-in-progress changes.

We bump the change index version to trigger online reindexing.
That's needed because we don't support reindexing during init step, and
we need to reindex all changes, because DRAFT is removed from the
Change.Status enum and index still contains the old references to the
changes that were migrated to private or work-in-progress changes. To
make online reindexing work, draft status is replaced with the new
change status.

PolyGerrit UI is not addressed in this change and will be done in a
follow-up change.

User branches are not updated in this change to clean-up My-Menu entry
in user preferences. This will be done in a follow-up change.

refs/meta/config is not updated in this change to clean-up draft
related permissions. This will be done in a follow-up change.

ChangeControl#isPatchVisible() call sites are migrated to the
ChangeControl#isVisible() and isVisible() is made public again. This and
other temporary changes to ChangeControl class will be cleaned up in
follow up changes.

Bug: Issue 6880
Change-Id: Icfcb34efe1ff0ea1d39e94ed500db776f5770d8f
This commit is contained in:
David Ostrovsky 2017-02-13 21:16:58 +01:00 committed by Changcheng Xiao
parent 80522c3e22
commit 6ffb7d9358
129 changed files with 237 additions and 2908 deletions

View File

@ -405,23 +405,6 @@ Further documentation on how to push can be found on the
link:user-upload.html#push_create[Upload changes] page.
==== refs/publish/*
`+refs/publish/*+` is an alternative name to `+refs/for/*+` when pushing new changes
and patch sets.
==== refs/drafts/*
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
@ -429,7 +412,6 @@ Gerrit has several permission categories that can be granted to groups
within projects, enabling functionality for that group's members.
[[category_abandon]]
=== Abandon
@ -852,36 +834,6 @@ The change owner and any explicitly added reviewers can always see
private changes (even without having the `View Private Changes` access
right assigned).
[[category_view_drafts]]
=== View Drafts
This category permits users to view draft changes uploaded by other
users.
The change owner and any explicitly added reviewers can always see
draft changes (even without having the `View Drafts` access right
assigned).
[[category_publish_drafts]]
=== Publish Drafts
This category permits users to publish draft changes uploaded by other
users.
The change owner can always publish draft changes (even without having
the `Publish Drafts` access right assigned).
[[category_delete_drafts]]
=== Delete Drafts
This category permits users to delete draft changes uploaded by other
users.
The change owner can always delete draft changes (even without having
the `Delete Drafts` access right assigned).
[[category_delete_own_changes]]
=== Delete Own Changes
@ -949,13 +901,7 @@ Suggested access rights to grant:
If it's desired to have the possibility to upload temporarily hidden
changes there's a specific permission for that. This enables someone
to add specific reviewers for early feedback before making the change
publicly visible. If you want to allow others than the owners to
publish a draft you also need to grant them `Publish Drafts`.
Optional access rights to grant:
* xref:category_push[`Push`] to 'refs/drafts/*'
* xref:category_publish_drafts[`Publish Drafts`] to 'refs/heads/*'
publicly visible.
[[examples_developer]]

View File

@ -109,16 +109,6 @@ branch.
(option is mutually exclusive with --abandon, --publish --delete, --rebase
and --json)
--publish::
Publish the specified draft patch set(s).
(option is mutually exclusive with --submit, --restore, --abandon, --delete
and --json)
--delete::
Delete the specified draft patch set(s).
(option is mutually exclusive with --submit, --restore, --abandon, --publish,
--rebase and --json)
--code-review::
--verified::
Set the label to the value 'N'. The exact option names

View File

@ -46,7 +46,7 @@ Only subscribe to specific event types:
----
$ ssh -p 29418 review.example.com gerrit stream-events \
-s draft-published -s patchset-created -s ref-replicated
-s patchset-created -s ref-replicated
----
== SCHEMA
@ -143,21 +143,6 @@ comment:: Review comment cover message.
eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
created.
=== Draft Published
Sent when a draft change has been published.
type:: "draft-published"
change:: link:json.html#change[change attribute]
patchSet:: link:json.html#patchSet[patchSet attribute]
uploader:: link:json.html#account[account attribute]
eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
created.
=== Dropped Output
Sent to notify a client that events have been dropped.
@ -201,11 +186,6 @@ created.
Sent when a new change has been uploaded, or a new patch set has been uploaded
to an existing change.
Note that this event is also sent for changes or patch sets uploaded as draft,
but is only visible to the change owner, any existing reviewers, and users who
belong to a group that is granted the
link:access-control.html#category_view_drafts[View Drafts] capability.
type:: "patchset-created"
change:: link:json.html#change[change attribute]

View File

@ -3491,10 +3491,9 @@ operations when multiple changes are impacted at once.
[[receive.checkMagicRefs]]receive.checkMagicRefs::
+
If true, Gerrit will verify the destination repository has
no references under the magic 'refs/drafts', 'refs/for', or
'refs/publish' branch namespaces. Names under these locations
confuse clients when trying to upload code reviews so Gerrit
requires them to be empty.
no references under the magic 'refs/for' branch namespace. Names under
these locations confuse clients when trying to upload code reviews so
Gerrit requires them to be empty.
+
If false Gerrit skips the sanity check and assumes administrators
have ensured the repository does not contain any magic references.

View File

@ -247,16 +247,6 @@ Documentation] |
link:https://gerrit.googlesource.com/plugins/emoticons/+doc/master/src/main/resources/Documentation/config.md[
Configuration]
[[force-draft]]
=== force-draft
Provides an ssh command to force a change or patch set to draft status.
This is useful for administrators to be able to easily completely
delete a change or patch set from the server.
link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/force-draft[
Project]
[[gitblit]]
=== gitblit
@ -668,24 +658,6 @@ Documentation] |
link:https://gerrit.googlesource.com/plugins/websession-flatfile/+doc/master/src/main/resources/Documentation/config.md[
Configuration]
[[wip]]
=== wip
This plugin adds a new button that allows a change owner to set a
change to Work In Progress, and a button to change from WIP back to a
"Ready For Review" state.
Any change in the WIP state will not show up in anyone's Review
Requests. Pushing a new patchset will reset the change to Review In
Progress.
link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/wip[
Project] |
link:https://gerrit.googlesource.com/plugins/wip/+doc/master/src/main/resources/Documentation/about.md[
Documentation] |
link:https://gerrit.googlesource.com/plugins/wip/+doc/master/src/main/resources/Documentation/config.md[
Configuration]
[[x-docs]]
=== x-docs

View File

@ -536,28 +536,6 @@ Alternatively, rather than completely ignoring the change, it can be muted.
Muting a change means it will always be marked as "reviewed" in dashboards,
until a new patch set is uploaded.
[[drafts]]
== Working with Drafts
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 cannot be uploaded as drafts any more, but existing draft changes
and patch sets can still be published and deleted.
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
link:user-review-ui.html#delete[deleted] from the change screen.
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.
[[inline-edit]]
== Inline Edit

View File

@ -41,8 +41,6 @@ status:: Current state of this change.
NEW;; Change is still being reviewed.
DRAFT;; Change is a draft change that only consists of draft patchsets.
MERGED;; Change has been merged to its branch.
ABANDONED;; Change was abandoned by its owner or administrator.
@ -108,8 +106,6 @@ author:: Author of this patchset in <<account,account attribute>>.
createdOn:: Time in seconds since the UNIX epoch when this patchset
was created.
isDraft:: Whether or not the patch set is a draft patch set.
kind:: Kind of change uploaded.
REWORK;; Nontrivial content changes.

View File

@ -1255,10 +1255,6 @@ any account.
"url": "#/dashboard/self",
"name": "Changes"
},
{
"url": "#/q/owner:self+is:draft",
"name": "Drafts"
},
{
"url": "#/q/has:draft",
"name": "Draft Comments"
@ -1313,10 +1309,6 @@ link:#preferences-input[PreferencesInput] entity.
"url": "#/dashboard/self",
"name": "Changes"
},
{
"url": "#/q/owner:self+is:draft",
"name": "Drafts"
},
{
"url": "#/q/has:draft",
"name": "Draft Comments"
@ -1369,10 +1361,6 @@ link:#preferences-info[PreferencesInfo] entity.
"url": "#/dashboard/self",
"name": "Changes"
},
{
"url": "#/q/owner:self+is:draft",
"name": "Drafts"
},
{
"url": "#/q/has:draft",
"name": "Draft Comments"

View File

@ -26,7 +26,7 @@ request body.
"subject" : "Let's support 100% Gerrit workflow direct in browser",
"branch" : "master",
"topic" : "create-change-in-browser",
"status" : "DRAFT"
"status" : "NEW"
}
----
@ -47,7 +47,7 @@ the resulting change.
"topic": "create-change-in-browser",
"change_id": "I8473b95934b5732ac55d26311a706c9c2bde9941",
"subject": "Let's support 100% Gerrit workflow direct in browser",
"status": "DRAFT",
"status": "NEW",
"created": "2014-05-05 07:15:44.639000000",
"updated": "2014-05-05 07:15:44.639000000",
"mergeable": true,
@ -1838,25 +1838,6 @@ message if the set of changes to be submitted with this change
includes changes the caller cannot read.
[[publish-draft-change]]
=== Publish Draft Change
--
'POST /changes/link:#change-id[\{change-id\}]/publish'
--
Publishes a draft change.
.Request
----
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/publish HTTP/1.0
Content-Type: application/json; charset=UTF-8
----
.Response
----
HTTP/1.1 204 No Content
----
[[delete-change]]
=== Delete Change
--
@ -1869,10 +1850,6 @@ New or abandoned changes can be deleted by their owner if the user is granted
the link:access-control.html#category_delete_own_changes[Delete Own Changes] permission,
otherwise only by administrators.
Draft changes can only be deleted by their owner or other users who have the
permissions to view and delete drafts. If the draft workflow is disabled, only
administrators with those permissions may delete draft changes.
.Request
----
DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940 HTTP/1.0
@ -4017,44 +3994,6 @@ message is contained in the response body.
"revision 674ac754f91e64a0efb8087e59a176484bd534d1 is not current revision"
----
[[publish-draft-revision]]
=== Publish Draft Revision
--
'POST /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/publish'
--
Publishes a draft revision.
.Request
----
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/current/publish HTTP/1.0
Content-Type: application/json; charset=UTF-8
----
.Response
----
HTTP/1.1 204 No Content
----
[[delete-draft-revision]]
=== Delete Draft Revision
--
'DELETE /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]'
--
Deletes a draft revision.
.Request
----
DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1 HTTP/1.0
Content-Type: application/json; charset=UTF-8
----
.Response
----
HTTP/1.1 204 No Content
----
[[get-patch]]
=== Get Patch
--
@ -5682,7 +5621,7 @@ is enabled).
|`subject` ||
The subject of the change (header line of the commit message).
|`status` ||
The status of the change (`NEW`, `MERGED`, `ABANDONED`, `DRAFT`).
The status of the change (`NEW`, `MERGED`, `ABANDONED`).
|`created` ||
The link:rest-api.html#timestamp[timestamp] of when the change was
created.
@ -6657,7 +6596,7 @@ link:#commit-info[CommitInfo] entity.
|`_revision_number` |optional|The revision number.
|`_current_revision_number`|optional|The current revision number.
|`status` |optional|The status of the change. The status of
the change is one of (`NEW`, `MERGED`, `ABANDONED`, `DRAFT`).
the change is one of (`NEW`, `MERGED`, `ABANDONED`).
|===========================
[[related-changes-info]]
@ -6881,7 +6820,6 @@ link:#list-changes[Query Changes].
[options="header",cols="1,^1,5"]
|===========================
|Field Name ||Description
|`draft` |not set if `false`|Whether the patch set is a draft.
|`kind` ||The change kind. Valid values are `REWORK`, `TRIVIAL_REBASE`,
`MERGE_FIRST_PARENT_UPDATE`, `NO_CODE_CHANGE`, and `NO_CHANGE`.
|`_number` ||The patch set number.

View File

@ -1021,10 +1021,6 @@ PreferencesInfo] is returned.
"url": "#/dashboard/self",
"name": "Changes"
},
{
"url": "#/q/owner:self+is:draft",
"name": "Drafts"
},
{
"url": "#/q/has:draft",
"name": "Draft Comments"
@ -1104,10 +1100,6 @@ PreferencesInfo] is returned.
"url": "#/dashboard/self",
"name": "Changes"
},
{
"url": "#/q/owner:self+is:draft",
"name": "Drafts"
},
{
"url": "#/q/has:draft",
"name": "Draft Comments"
@ -1396,9 +1388,6 @@ section.
|`allow_blame` |not set if `false`|
link:config-gerrit.html#change.allowBlame[Whether blame on side by side diff is
allowed].
|`allow_drafts` |not set if `false`|
link:config-gerrit.html#change.allowDrafts[Whether draft workflow is
allowed].
|`large_change` ||
link:config-gerrit.html#change.largeChange[Number of changed lines from
which on a change is considered as a large change].

View File

@ -70,10 +70,10 @@ For more details, see link:cmd-hook-commit-msg.html[commit-msg].
Change Upload
--------------
During upload by pushing to `+refs/for/*+`, `+refs/drafts/*+` or
`+refs/heads/*+`, Gerrit will try to find an existing review the
uploaded commit relates to. For an existing review to match, the
following properties have to match:
During upload by pushing to `+refs/for/*+` or `+refs/heads/*+`,
Gerrit will try to find an existing review the uploaded commit
relates to. For an existing review to match, the following properties
have to match:
* Change-Id
* Repository name
@ -104,7 +104,7 @@ message if additional revisions to a change are required.
By default, Gerrit will prevent pushing for review if no Change-Id is provided,
with the following message:
! [remote rejected] HEAD -> refs/publish/master (missing Change-Id in commit
! [remote rejected] HEAD -> refs/for/master (missing Change-Id in commit
message footer)
However, repositories can be configured to allow commits without Change-Ids

View File

@ -61,13 +61,6 @@ The change was successfully merged into the destination branch.
+
The change was abandoned.
- [[draft]]`Draft`:
+
The change is a draft that is only visible to the change owner, the
reviewers that were explicitly added to the change, and users who have
the link:access-control.html#category_view_drafts[View Drafts] global
capability assigned.
[[commit-info]]
=== Commit Info Block
@ -258,28 +251,14 @@ open changes.
Users can only cherry-pick changes to branches for which they are
allowed to upload changes for review.
** [[publish]]`Publish`:
+
Publishes the currently viewed draft patch set. If this is the first
patch set of a change that is published, the change will be published
as well.
+
The `Publish` button is only available if a draft patch set is viewed
and the user is the change owner or has the
link:access-control.html#category_publish_drafts[Publish Drafts] access
right assigned.
** [[delete]]`Delete Change` / `Delete Revision`:
+
Deletes the change / the currently viewed draft patch set.
Deletes the change.
+
For open or abandoned changes, the `Delete Change` button will be available
and if the user is the change owner and is granted the
link:access-control.html#category_delete_own_changes[Delete Own Changes]
permission or if they are an administrator. For draft changes,
the `Delete Change` / `Delete Revision` buttons will be available if the user is
the change owner or has the
link:access-control.html#category_delete_drafts[Delete Drafts] access right assigned.
permission or if they are an administrator.
** [[plugin-actions]]Further actions may be available if plugins are installed.

View File

@ -14,7 +14,6 @@ matches the search, the change will be presented instead of a list.
|All > Open | status:open '(or is:open)'
|All > Merged | status:merged
|All > Abandoned | status:abandoned
|My > Drafts | owner:self is:draft
|My > Watched Changes | status:open is:watched
|My > Starred Changes | is:starred
|My > Draft Comments | has:draft
@ -337,10 +336,6 @@ is:open, is:pending::
+
True if the change is open.
is:draft::
+
True if the change is a draft.
is:closed::
+
True if the change is either merged or abandoned.

View File

@ -18,7 +18,6 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
import static com.google.gerrit.acceptance.GitUtil.initSsh;
import static com.google.gerrit.extensions.api.changes.SubmittedTogetherOption.NON_VISIBLE_CHANGES;
import static com.google.gerrit.extensions.client.ListChangesOption.ALL_REVISIONS;
import static com.google.gerrit.reviewdb.client.Patch.COMMIT_MSG;
import static com.google.gerrit.reviewdb.client.Patch.MERGE_LIST;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
@ -35,7 +34,6 @@ import com.google.common.jimfs.Jimfs;
import com.google.common.primitives.Chars;
import com.google.gerrit.acceptance.AcceptanceTestRequestScope.Context;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.ContributorAgreement;
import com.google.gerrit.common.data.GroupReference;
@ -50,7 +48,6 @@ import com.google.gerrit.extensions.api.groups.GroupInput;
import com.google.gerrit.extensions.api.projects.BranchApi;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.client.ProjectWatchInfo;
@ -60,14 +57,11 @@ import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeType;
import com.google.gerrit.extensions.common.DiffInfo;
import com.google.gerrit.extensions.common.EditInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.IdString;
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;
@ -104,14 +98,11 @@ import com.google.gerrit.server.mail.send.EmailHeader;
import com.google.gerrit.server.notedb.ChangeNoteUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.MutableNotesMigration;
import com.google.gerrit.server.notedb.PatchSetState;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.Util;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
import com.google.gerrit.testutil.ConfigSuite;
import com.google.gerrit.testutil.FakeEmailSender;
import com.google.gerrit.testutil.FakeEmailSender.Message;
@ -307,16 +298,6 @@ public abstract class AbstractDaemonTest {
return cfg;
}
protected static Config allowDraftsDisabledConfig() {
Config cfg = new Config();
cfg.setBoolean("change", null, "allowDrafts", false);
return cfg;
}
protected boolean isAllowDrafts() {
return cfg.getBoolean("change", "allowDrafts", true);
}
protected boolean isSubmitWholeTopicEnabled() {
return cfg.getBoolean("change", null, "submitWholeTopic", false);
}
@ -638,139 +619,6 @@ public abstract class AbstractDaemonTest {
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());
batchUpdate.execute();
}
ChangeStatus changeStatus = gApi.changes().id(id.get()).get().status;
assertThat(changeStatus).isEqualTo(ChangeStatus.DRAFT);
}
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));
batchUpdate.execute();
}
Boolean expectedDraftStatus = draftStatus ? Boolean.TRUE : null;
List<Boolean> patchSetDraftStatuses = getPatchSetDraftStatuses(id);
patchSetDraftStatuses.forEach(status -> assertThat(status).isEqualTo(expectedDraftStatus));
}
private List<Boolean> getPatchSetDraftStatuses(Change.Id id) throws Exception {
Collection<RevisionInfo> revisionInfos =
gApi.changes().id(id.get()).get(ALL_REVISIONS).revisions.values();
return revisionInfos.stream().map(revisionInfo -> revisionInfo.draft).collect(toList());
}
private static class MarkChangeAsDraftUpdateOp implements BatchUpdateOp {
@Override
public boolean updateChange(ChangeContext ctx) throws Exception {
Change change = ctx.getChange();
// Change status in database.
change.setStatus(Change.Status.DRAFT);
// Change status in NoteDb.
PatchSet.Id currentPatchSetId = change.currentPatchSetId();
ctx.getUpdate(currentPatchSetId).setStatus(Change.Status.DRAFT);
return true;
}
}
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;
DraftStatusOfPatchSetsUpdateOp(boolean draftStatus) {
this.draftStatus = draftStatus;
}
@Override
public boolean updateChange(ChangeContext ctx) throws Exception {
Collection<PatchSet> patchSets = psUtil.byChange(db, ctx.getNotes());
// Change status in database.
patchSets.forEach(patchSet -> patchSet.setDraft(draftStatus));
db.patchSets().update(patchSets);
// Change status in NoteDb.
PatchSetState patchSetState = draftStatus ? PatchSetState.DRAFT : PatchSetState.PUBLISHED;
patchSets
.stream()
.map(PatchSet::getId)
.map(ctx::getUpdate)
.forEach(changeUpdate -> changeUpdate.setPatchSetState(patchSetState));
return true;
}
}
protected PushOneCommit.Result createWorkInProgressChange() throws Exception {
return pushTo("refs/for/master%wip");
}
@ -864,23 +712,6 @@ public abstract class AbstractDaemonTest {
revision(r).submit();
}
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 {
return gApi.changes().id(id).info();
}

View File

@ -73,7 +73,7 @@ public class GeneralPreferencesIT extends AbstractDaemonTest {
public void getAndSetPreferences() throws Exception {
GeneralPreferencesInfo o = gApi.accounts().id(user42.id.toString()).getPreferences();
assertPrefs(o, GeneralPreferencesInfo.defaults(), "my", "changeTable");
assertThat(o.my).hasSize(7);
assertThat(o.my).hasSize(6);
assertThat(o.changeTable).isEmpty();
GeneralPreferencesInfo i = GeneralPreferencesInfo.defaults();

View File

@ -98,17 +98,6 @@ public class AbandonIT extends AbstractDaemonTest {
changeAbandoner.batchAbandon(batchUpdateFactory, new Project.NameKey(project1Name), user, list);
}
@Test
public void abandonDraft() throws Exception {
PushOneCommit.Result r = createDraftChange();
String changeId = r.getChangeId();
assertThat(info(changeId).status).isEqualTo(ChangeStatus.DRAFT);
exception.expect(ResourceConflictException.class);
exception.expectMessage("draft changes cannot be abandoned");
gApi.changes().id(changeId).abandon();
}
@Test
@GerritConfig(name = "changeCleanup.abandonAfter", value = "1w")
public void abandonInactiveOpenChanges() throws Exception {

View File

@ -835,23 +835,6 @@ public class ChangeIT extends AbstractDaemonTest {
gApi.changes().id(changeId).rebase();
}
@Test
public void publish() throws Exception {
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);
}
@Test
public void deleteDraftChange() throws Exception {
PushOneCommit.Result r = createDraftChange();
assertThat(query(r.getChangeId())).hasSize(1);
assertThat(info(r.getChangeId()).status).isEqualTo(ChangeStatus.DRAFT);
gApi.changes().id(r.getChangeId()).delete();
assertThat(query(r.getChangeId())).isEmpty();
}
@Test
public void deleteNewChangeAsAdmin() throws Exception {
PushOneCommit.Result changeResult = createChange();
@ -2311,7 +2294,6 @@ public class ChangeIT extends AbstractDaemonTest {
gApi.changes().id(r1.getChangeId()).revision(r1.getCommit().name()).submit();
createChange();
createDraftChange();
setApiUser(user);
AcceptanceTestRequestScope.Context ctx = disableDb();
@ -2462,64 +2444,6 @@ public class ChangeIT extends AbstractDaemonTest {
gApi.changes().create(in).get();
}
@Test
public void createNewPatchSetOnVisibleDraftPatchSet() throws Exception {
// Clone separate repositories of the same project as admin and as user
TestRepository<InMemoryRepository> adminTestRepo = cloneProject(project, admin);
TestRepository<InMemoryRepository> userTestRepo = cloneProject(project, user);
// Create change as admin
PushOneCommit push = pushFactory.create(db, admin.getIdent(), adminTestRepo);
PushOneCommit.Result r1 = push.to("refs/for/master");
r1.assertOkStatus();
// Amend draft as admin
PushOneCommit.Result r2 =
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();
in.reviewer = user.email;
gApi.changes().id(r1.getChangeId()).addReviewer(in);
// Fetch change
GitUtil.fetch(userTestRepo, r2.getPatchSet().getRefName() + ":ps");
userTestRepo.reset("ps");
// Amend change as user
PushOneCommit.Result r3 = amendChange(r2.getChangeId(), "refs/for/master", user, userTestRepo);
r3.assertOkStatus();
setCurrentPatchSetAsDraft(r3.getChange().getId());
}
@Test
public void createNewPatchSetOnInvisibleDraftPatchSet() throws Exception {
// Clone separate repositories of the same project as admin and as user
TestRepository<InMemoryRepository> adminTestRepo = cloneProject(project, admin);
TestRepository<InMemoryRepository> userTestRepo = cloneProject(project, user);
// Create change as admin
PushOneCommit push = pushFactory.create(db, admin.getIdent(), adminTestRepo);
PushOneCommit.Result r1 = push.to("refs/for/master");
r1.assertOkStatus();
// Amend draft as admin
PushOneCommit.Result r2 =
amendChange(r1.getChangeId(), "refs/for/master", admin, adminTestRepo);
r2.assertOkStatus();
setCurrentPatchSetAsDraft(r2.getChange().getId());
// Fetch change
GitUtil.fetch(userTestRepo, r1.getPatchSet().getRefName() + ":ps");
userTestRepo.reset("ps");
// Amend change as user
PushOneCommit.Result r3 = amendChange(r1.getChangeId(), "refs/for/master", user, userTestRepo);
r3.assertErrorStatus("cannot add patch set to " + r3.getChange().change().getChangeId() + ".");
}
@Test
public void createNewPatchSetWithoutPermission() throws Exception {
// Create new project with clean permissions
@ -2591,63 +2515,6 @@ public class ChangeIT extends AbstractDaemonTest {
r2.assertOkStatus();
}
@Test
public void createNewPatchSetAsReviewerOnDraftChange() throws Exception {
// Clone separate repositories of the same project as admin and as user
TestRepository<?> adminTestRepo = cloneProject(project, admin);
TestRepository<?> userTestRepo = cloneProject(project, user);
// Create change as admin
PushOneCommit push = pushFactory.create(db, admin.getIdent(), adminTestRepo);
PushOneCommit.Result r1 = push.to("refs/for/master");
r1.assertOkStatus();
markChangeAsDraft(r1.getChange().getId());
// Add user as reviewer
AddReviewerInput in = new AddReviewerInput();
in.reviewer = user.email;
gApi.changes().id(r1.getChangeId()).addReviewer(in);
// Fetch change
GitUtil.fetch(userTestRepo, r1.getPatchSet().getRefName() + ":ps");
userTestRepo.reset("ps");
// Amend change as user
PushOneCommit.Result r2 = amendChange(r1.getChangeId(), "refs/for/master", user, userTestRepo);
r2.assertOkStatus();
}
@Test
public void createNewDraftPatchSetOnDraftChange() throws Exception {
// Create new project with clean permissions
Project.NameKey p = createProject("addPatchSet4");
// Clone separate repositories of the same project as admin and as user
TestRepository<?> adminTestRepo = cloneProject(p, admin);
TestRepository<?> userTestRepo = cloneProject(p, user);
// Block default permission
block(p, "refs/for/*", Permission.ADD_PATCH_SET, REGISTERED_USERS);
// Create change as admin
PushOneCommit push = pushFactory.create(db, admin.getIdent(), adminTestRepo);
PushOneCommit.Result r1 = push.to("refs/for/master");
r1.assertOkStatus();
markChangeAsDraft(p, r1.getChange().getId());
// Add user as reviewer
AddReviewerInput in = new AddReviewerInput();
in.reviewer = user.email;
gApi.changes().id(r1.getChangeId()).addReviewer(in);
// Fetch change
GitUtil.fetch(userTestRepo, r1.getPatchSet().getRefName() + ":ps");
userTestRepo.reset("ps");
// Amend change as user
PushOneCommit.Result r2 = amendChange(r1.getChangeId(), "refs/for/master", user, userTestRepo);
r2.assertErrorStatus("cannot add patch set to " + r1.getChange().getId().id + ".");
}
@Test
public void createMergePatchSet() throws Exception {
PushOneCommit.Result start = pushTo("refs/heads/master");

View File

@ -301,12 +301,6 @@ public class RevisionIT extends AbstractDaemonTest {
gApi.changes().id(r.getChange().getId().get()).current().review(ReviewInput.approve());
}
@Test
public void deleteDraft() throws Exception {
PushOneCommit.Result r = createDraftChange();
gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).delete();
}
@Test
public void cherryPick() throws Exception {
PushOneCommit.Result r = pushTo("refs/for/master%topic=someTopic");

View File

@ -42,6 +42,7 @@ import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.Sandboxed;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.TestProjectInput;
import com.google.gerrit.common.data.LabelType;
@ -61,6 +62,7 @@ import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
import com.google.gerrit.extensions.common.CommentInfo;
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.common.RevisionInfo;
import com.google.gerrit.reviewdb.client.AccountGroup;
@ -636,34 +638,6 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
r.assertOkStatus();
}
@Test
public void pushForMasterAsDraft() throws Exception {
// 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.NEW, null);
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.NEW, null);
assertThat(gApi.changes().id(r.getChangeId()).get().isPrivate).isTrue();
}
@Test
public void publishDraftChangeByPushingNonDraftPatchSet() throws Exception {
PushOneCommit.Result r = createDraftChange();
r.assertOkStatus();
r.assertChange(Change.Status.DRAFT, null);
// publish draft change by pushing non-draft patch set
r = amendChange(r.getChangeId(), "refs/for/master");
r.assertOkStatus();
r.assertChange(Change.Status.NEW, null);
}
@Test
public void pushForMasterAsEdit() throws Exception {
PushOneCommit.Result r = pushTo("refs/for/master");
@ -1819,6 +1793,42 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
assertThat(getPublishedComments(r.getChangeId())).isEmpty();
}
@Test
public void pushDraftGetsPrivateChange() throws Exception {
String changeId1 = createChange("refs/drafts/master").getChangeId();
String changeId2 = createChange("refs/for/master%draft").getChangeId();
ChangeInfo info1 = gApi.changes().id(changeId1).get();
ChangeInfo info2 = gApi.changes().id(changeId2).get();
assertThat(info1.status).isEqualTo(ChangeStatus.NEW);
assertThat(info2.status).isEqualTo(ChangeStatus.NEW);
assertThat(info1.isPrivate).isEqualTo(true);
assertThat(info2.isPrivate).isEqualTo(true);
assertThat(info1.revisions).hasSize(1);
assertThat(info2.revisions).hasSize(1);
}
@Sandboxed
@Test
public void pushWithDraftOptionToExistingNewChangeGetsChangeEdit() throws Exception {
String changeId = createChange().getChangeId();
EditInfoSubject.assertThat(getEdit(changeId)).isAbsent();
ChangeInfo changeInfo = gApi.changes().id(changeId).get();
ChangeStatus originalChangeStatus = changeInfo.status;
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);
EditInfoSubject.assertThat(getEdit(changeId)).isPresent();
}
@GerritConfig(name = "receive.maxBatchCommits", value = "2")
@Test
public void maxBatchCommits() throws Exception {

View File

@ -1,47 +0,0 @@
// Copyright (C) 2014 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.acceptance.git;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.common.data.Permission;
import org.junit.Before;
import org.junit.Test;
@NoHttpd
public class DraftChangeBlockedIT extends AbstractDaemonTest {
@Before
public void setUp() throws Exception {
block("refs/drafts/*", Permission.PUSH, ANONYMOUS_USERS);
}
@Test
public void pushDraftChange_Blocked() throws Exception {
// create draft by pushing to 'refs/drafts/'
PushOneCommit.Result r = pushTo("refs/drafts/master");
r.assertErrorStatus("cannot upload drafts");
}
@Test
public void pushDraftChangeMagic_Blocked() throws Exception {
// create draft by using 'draft' option
PushOneCommit.Result r = pushTo("refs/for/master%draft");
r.assertErrorStatus("cannot upload drafts");
}
}

View File

@ -319,53 +319,6 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
}
}
@Test
public void uploadPackDraftRefs() throws Exception {
allow("refs/heads/*", Permission.READ, REGISTERED_USERS);
PushOneCommit.Result br = createDraftChange();
br.assertOkStatus();
Change.Id c5 = br.getChange().getId();
String r5 = changeRefPrefix(c5);
// Only admin can see admin's draft change (5).
setApiUser(admin);
assertUploadPackRefs(
"HEAD",
r1 + "1",
r1 + "meta",
r2 + "1",
r2 + "meta",
r3 + "1",
r3 + "meta",
r4 + "1",
r4 + "meta",
r5 + "1",
r5 + "meta",
"refs/heads/branch",
"refs/heads/master",
RefNames.REFS_CONFIG,
"refs/tags/branch-tag",
"refs/tags/master-tag");
// user can't.
setApiUser(user);
assertUploadPackRefs(
"HEAD",
r1 + "1",
r1 + "meta",
r2 + "1",
r2 + "meta",
r3 + "1",
r3 + "meta",
r4 + "1",
r4 + "meta",
"refs/heads/branch",
"refs/heads/master",
"refs/tags/branch-tag",
"refs/tags/master-tag");
}
@Test
public void uploadPackNoSearchingChangeCacheImpl() throws Exception {
allow("refs/heads/*", Permission.READ, REGISTERED_USERS);

View File

@ -169,12 +169,6 @@ public class SubmitOnPushIT extends AbstractDaemonTest {
r.assertErrorStatus("update by submit not permitted");
}
@Test
public void submitOnPushingDraft_Error() throws Exception {
PushOneCommit.Result r = pushTo("refs/for/master%draft,submit");
r.assertErrorStatus();
}
@Test
public void submitOnPushToNonExistingBranch_Error() throws Exception {
String branchName = "non-existing";

View File

@ -520,20 +520,6 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
.containsExactlyElementsIn(expected);
}
@Test
public void submitDraftChange() throws Exception {
PushOneCommit.Result draft = createDraftChange();
Change.Id num = draft.getChange().getId();
submitWithConflict(
draft.getChangeId(),
"Failed to submit 1 change due to the following problems:\n"
+ "Change "
+ num
+ ": Change "
+ num
+ " is draft");
}
@Test
public void submitWorkInProgressChange() throws Exception {
PushOneCommit.Result change = createWorkInProgressChange();
@ -548,21 +534,6 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
+ " is work in progress");
}
@Test
public void submitDraftPatchSet() throws Exception {
PushOneCommit.Result change = createChange();
PushOneCommit.Result draft = amendChangeAndMarkPatchSetAsDraft(change.getChangeId());
Change.Id num = draft.getChange().getId();
submitWithConflict(
draft.getChangeId(),
"Failed to submit 1 change due to the following problems:\n"
+ "Change "
+ num
+ ": submit rule error: "
+ "Cannot submit draft patch sets");
}
@Test
public void submitWithHiddenBranchInSameTopic() throws Exception {
assume().that(isSubmitWholeTopicEnabled()).isTrue();

View File

@ -139,28 +139,6 @@ public class ActionsIT extends AbstractDaemonTest {
}
}
@Test
public void revisionActionsETagWithHiddenDraftInTopic() throws Exception {
String change = createChangeWithTopic().getChangeId();
approve(change);
setApiUser(user);
String etag1 = getETag(change);
setApiUser(admin);
String draft = createDraftChange("topic").getChangeId();
approve(draft);
setApiUser(user);
String etag2 = getETag(change);
if (isSubmitWholeTopicEnabled()) {
assertThat(etag2).isNotEqualTo(etag1);
} else {
assertThat(etag2).isEqualTo(etag1);
}
}
@Test
public void revisionActionsAnonymousETag() throws Exception {
String parent = createChange().getChangeId();

View File

@ -276,19 +276,6 @@ public class ChangeReviewersByEmailIT extends AbstractDaemonTest {
assertThat(result.reviewers).isNull();
}
@Test
public void rejectOnNonPublicChange() throws Exception {
assume().that(notesMigration.readChanges()).isTrue();
PushOneCommit.Result r = createDraftChange();
AddReviewerResult result =
gApi.changes().id(r.getChangeId()).addReviewer("Foo Bar <foo.bar@gerritcodereview.com>");
assertThat(result.error)
.isEqualTo(
"Foo Bar <foo.bar@gerritcodereview.com> does not have permission to see this change");
assertThat(result.reviewers).isNull();
}
@Test
public void rejectWhenFeatureIsDisabled() throws Exception {
assume().that(notesMigration.readChanges()).isTrue();

View File

@ -24,9 +24,7 @@ import static org.eclipse.jgit.lib.Constants.SIGNED_OFF_BY_TAG;
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;
@ -138,13 +136,6 @@ public class CreateChangeIT extends AbstractDaemonTest {
"%sAdministrator <%s>", SIGNED_OFF_BY_TAG, admin.getIdent().getEmailAddress()));
}
@Test
@GerritConfig(name = "change.allowDrafts", value = "true")
public void createNewDraftChangeNotAllowed() throws Exception {
ChangeInput ci = newChangeInput(ChangeStatus.DRAFT);
assertCreateFails(ci, BadRequestException.class, "unsupported change status");
}
@Test
public void createNewPrivateChange() throws Exception {
ChangeInput input = newChangeInput(ChangeStatus.NEW);
@ -388,9 +379,7 @@ public class CreateChangeIT extends AbstractDaemonTest {
assertThat(out.workInProgress).isEqualTo(in.workInProgress);
assertThat(out.revisions).hasSize(1);
assertThat(out.submitted).isNull();
assertThat(out.submitter).isNull();
Boolean draft = Iterables.getOnlyElement(out.revisions.values()).draft;
assertThat(booleanToDraftStatus(draft)).isEqualTo(in.status);
assertThat(in.status).isEqualTo(ChangeStatus.NEW);
return out;
}
@ -402,13 +391,6 @@ public class CreateChangeIT extends AbstractDaemonTest {
gApi.changes().create(in);
}
private ChangeStatus booleanToDraftStatus(Boolean draft) {
if (draft == null) {
return ChangeStatus.NEW;
}
return draft ? ChangeStatus.DRAFT : ChangeStatus.NEW;
}
// TODO(davido): Expose setting of account preferences in the API
private void setSignedOffByFooter() throws Exception {
RestResponse r = adminRestSession.get("/accounts/" + admin.email + "/preferences");

View File

@ -1,261 +0,0 @@
// Copyright (C) 2013 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
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.TestAccount;
import com.google.gerrit.extensions.api.changes.DraftInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.Comment;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.RefNames;
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.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.junit.Test;
@NoHttpd
public class DeleteDraftPatchSetIT extends AbstractDaemonTest {
@Inject private AllUsersName allUsers;
@Test
public void deletePatchSetNotDraft() throws Exception {
String changeId = createChange().getChangeId();
PatchSet ps = getCurrentPatchSet(changeId);
String triplet = project.get() + "~master~" + changeId;
ChangeInfo c = get(triplet);
assertThat(c.id).isEqualTo(triplet);
assertThat(c.status).isEqualTo(ChangeStatus.NEW);
exception.expect(ResourceConflictException.class);
exception.expectMessage("Patch set is not a draft");
setApiUser(admin);
deletePatchSet(changeId, ps);
}
@Test
public void deleteDraftPatchSetNoACL() throws Exception {
String changeId = createDraftChangeWith2PS();
PatchSet ps = getCurrentPatchSet(changeId);
String triplet = project.get() + "~master~" + changeId;
ChangeInfo c = get(triplet);
assertThat(c.id).isEqualTo(triplet);
assertThat(c.status).isEqualTo(ChangeStatus.DRAFT);
exception.expect(ResourceNotFoundException.class);
exception.expectMessage("Not found: " + changeId);
setApiUser(user);
deletePatchSet(changeId, ps);
}
@Test
public void deleteDraftPatchSetAndChange() throws Exception {
String changeId = createDraftChangeWith2PS();
PatchSet ps = getCurrentPatchSet(changeId);
Change.Id id = ps.getId().getParentKey();
DraftInput din = new DraftInput();
din.path = "a.txt";
din.message = "comment on a.txt";
gApi.changes().id(changeId).current().createDraft(din);
if (notesMigration.commitChangeWrites()) {
assertThat(getDraftRef(admin, id)).isNotNull();
}
ChangeData cd = getChange(changeId);
assertThat(cd.patchSets()).hasSize(2);
assertThat(cd.change().currentPatchSetId().get()).isEqualTo(2);
assertThat(cd.change().getStatus()).isEqualTo(Change.Status.DRAFT);
deletePatchSet(changeId, ps);
cd = getChange(changeId);
assertThat(cd.patchSets()).hasSize(1);
assertThat(cd.change().currentPatchSetId().get()).isEqualTo(1);
ps = getCurrentPatchSet(changeId);
deletePatchSet(changeId, ps);
assertThat(queryProvider.get().byKeyPrefix(changeId)).isEmpty();
if (notesMigration.commitChangeWrites()) {
assertThat(getDraftRef(admin, id)).isNull();
assertThat(getMetaRef(id)).isNull();
}
exception.expect(ResourceNotFoundException.class);
gApi.changes().id(id.get());
}
@Test
public void deleteDraftPS1() throws Exception {
String changeId = createDraftChangeWith2PS();
ReviewInput rin = new ReviewInput();
rin.message = "Change message";
CommentInput cin = new CommentInput();
cin.line = 1;
cin.patchSet = 1;
cin.path = PushOneCommit.FILE_NAME;
cin.side = Side.REVISION;
cin.message = "Inline comment";
rin.comments = new HashMap<>();
rin.comments.put(cin.path, ImmutableList.of(cin));
gApi.changes().id(changeId).revision(1).review(rin);
ChangeData cd = getChange(changeId);
PatchSet.Id delPsId = new PatchSet.Id(cd.getId(), 1);
PatchSet ps = cd.patchSet(delPsId);
deletePatchSet(changeId, ps);
cd = getChange(changeId);
assertThat(cd.patchSets()).hasSize(1);
assertThat(Iterables.getOnlyElement(cd.patchSets()).getId().get()).isEqualTo(2);
// Other entities based on deleted patch sets are also deleted.
for (ChangeMessage m : cd.messages()) {
assertThat(m.getPatchSetId()).named(m.toString()).isNotEqualTo(delPsId);
}
for (Comment c : cd.publishedComments()) {
assertThat(c.key.patchSetId).named(c.toString()).isNotEqualTo(delPsId.get());
}
}
@Test
public void deleteDraftPS2() throws Exception {
String changeId = createDraftChangeWith2PS();
ReviewInput rin = new ReviewInput();
rin.message = "Change message";
CommentInput cin = new CommentInput();
cin.line = 1;
cin.patchSet = 1;
cin.path = PushOneCommit.FILE_NAME;
cin.side = Side.REVISION;
cin.message = "Inline comment";
rin.comments = new HashMap<>();
rin.comments.put(cin.path, ImmutableList.of(cin));
gApi.changes().id(changeId).revision(1).review(rin);
ChangeData cd = getChange(changeId);
PatchSet.Id delPsId = new PatchSet.Id(cd.getId(), 2);
PatchSet ps = cd.patchSet(delPsId);
deletePatchSet(changeId, ps);
cd = getChange(changeId);
assertThat(cd.patchSets()).hasSize(1);
assertThat(Iterables.getOnlyElement(cd.patchSets()).getId().get()).isEqualTo(1);
// Other entities based on deleted patch sets are also deleted.
for (ChangeMessage m : cd.messages()) {
assertThat(m.getPatchSetId()).named(m.toString()).isNotEqualTo(delPsId);
}
for (Comment c : cd.publishedComments()) {
assertThat(c.key.patchSetId).named(c.toString()).isNotEqualTo(delPsId.get());
}
}
@Test
public void deleteCurrentDraftPatchSetWhenPreviousPatchSetDoesNotExist() throws Exception {
String changeId = createChange().getChangeId();
amendChangeAndMarkPatchSetAsDraft(changeId);
amendChangeAndMarkPatchSetAsDraft(changeId);
deletePatchSet(changeId, 2);
deletePatchSet(changeId, 3);
ChangeData cd = getChange(changeId);
assertThat(cd.patchSets()).hasSize(1);
assertThat(Iterables.getOnlyElement(cd.patchSets()).getId().get()).isEqualTo(1);
assertThat(cd.currentPatchSet().getId().get()).isEqualTo(1);
}
@Test
public void deleteDraftPatchSetAndPushNewDraftPatchSet() throws Exception {
// Create change
PushOneCommit.Result r1 = createDraftChange();
String changeId = r1.getChangeId();
String revPs1 = r1.getChange().currentPatchSet().getRevision().get();
// Push draft patch set
PushOneCommit.Result r2 = amendChangeAndMarkPatchSetAsDraft(changeId);
String revPs2 = r2.getChange().currentPatchSet().getRevision().get();
assertThat(gApi.changes().id(r1.getChange().getId().get()).get().currentRevision)
.isEqualTo(revPs2);
// Remove draft patch set
gApi.changes().id(r1.getChange().getId().get()).revision(revPs2).delete();
assertThat(gApi.changes().id(r1.getChange().getId().get()).get().currentRevision)
.isEqualTo(revPs1);
// Push new draft patch set
amendChangeAndMarkPatchSetAsDraft(changeId);
String revPs3 = r2.getChange().currentPatchSet().getRevision().get();
assertThat(gApi.changes().id(r1.getChange().getId().get()).get().currentRevision)
.isEqualTo(revPs3);
// Check that all patch sets have different SHA1s
assertThat(revPs1).doesNotMatch(revPs2);
assertThat(revPs2).doesNotMatch(revPs3);
}
private Ref getDraftRef(TestAccount account, Change.Id changeId) throws Exception {
try (Repository repo = repoManager.openRepository(allUsers)) {
return repo.exactRef(RefNames.refsDraftComments(changeId, account.id));
}
}
private Ref getMetaRef(Change.Id changeId) throws Exception {
try (Repository repo = repoManager.openRepository(project)) {
return repo.exactRef(RefNames.changeMetaRef(changeId));
}
}
private PatchSet getCurrentPatchSet(String changeId) throws Exception {
return getChange(changeId).currentPatchSet();
}
private ChangeData getChange(String changeId) throws Exception {
return Iterables.getOnlyElement(queryProvider.get().byKeyPrefix(changeId));
}
private void deletePatchSet(String changeId, PatchSet ps) throws Exception {
deletePatchSet(changeId, ps.getId().get());
}
private void deletePatchSet(String changeId, int ps) throws Exception {
gApi.changes().id(changeId).revision(ps).delete();
}
}

View File

@ -1,295 +0,0 @@
// Copyright (C) 2013 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.RestSession;
import com.google.gerrit.acceptance.TestProjectInput;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.changes.ReviewInput;
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;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
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;
public class DraftChangeIT extends AbstractDaemonTest {
@ConfigSuite.Config
public static Config allowDraftsDisabled() {
return allowDraftsDisabledConfig();
}
@Test
public void forceCreateAndPublishDraftChangeWhenAllowDraftsDisabled() throws Exception {
PushOneCommit.Result result = createDraftChange();
result.assertOkStatus();
String changeId = result.getChangeId();
String triplet = project.get() + "~master~" + changeId;
ChangeInfo c = get(triplet);
assertThat(c.id).isEqualTo(triplet);
assertThat(c.status).isEqualTo(ChangeStatus.DRAFT);
assertThat(c.revisions.get(c.currentRevision).draft).isTrue();
publishPatchSet(changeId).assertNoContent();
assertThat(get(triplet).status).isEqualTo(ChangeStatus.NEW);
}
@Test
public void deleteDraftChange() throws Exception {
assume().that(isAllowDrafts()).isTrue();
PushOneCommit.Result result = createDraftChange();
result.assertOkStatus();
String changeId = result.getChangeId();
String triplet = project.get() + "~master~" + changeId;
ChangeInfo c = get(triplet);
assertThat(c.id).isEqualTo(triplet);
assertThat(c.status).isEqualTo(ChangeStatus.DRAFT);
deleteChange(changeId, adminRestSession).assertNoContent();
exception.expect(ResourceNotFoundException.class);
get(triplet);
}
@Test
public void deleteDraftChangeOfAnotherUser() throws Exception {
assume().that(isAllowDrafts()).isTrue();
PushOneCommit.Result changeResult = createDraftChange();
changeResult.assertOkStatus();
String changeId = changeResult.getChangeId();
// The user needs to be able to see the draft change (which reviewers can).
gApi.changes().id(changeId).addReviewer(user.fullName);
setApiUser(user);
exception.expect(AuthException.class);
exception.expectMessage("delete not permitted");
gApi.changes().id(changeId).delete();
}
@Test
@TestProjectInput(cloneAs = "user")
public void deleteDraftChangeWhenDraftsNotAllowedAsNormalUser() throws Exception {
assume().that(isAllowDrafts()).isFalse();
setApiUser(user);
// We can't create a draft change while the draft workflow is disabled.
// For this reason, we create a normal change and modify the database.
PushOneCommit.Result changeResult =
pushFactory.create(db, user.getIdent(), testRepo).to("refs/for/master");
Change.Id id = changeResult.getChange().getId();
markChangeAsDraft(id);
setDraftStatusOfPatchSets(id, true);
String changeId = changeResult.getChangeId();
exception.expect(MethodNotAllowedException.class);
exception.expectMessage("Draft workflow is disabled");
gApi.changes().id(changeId).delete();
}
@Test
@TestProjectInput(cloneAs = "user")
public void deleteDraftChangeWhenDraftsNotAllowedAsAdmin() throws Exception {
assume().that(isAllowDrafts()).isFalse();
setApiUser(user);
// We can't create a draft change while the draft workflow is disabled.
// For this reason, we create a normal change and modify the database.
PushOneCommit.Result changeResult =
pushFactory.create(db, user.getIdent(), testRepo).to("refs/for/master");
Change.Id id = changeResult.getChange().getId();
markChangeAsDraft(id);
setDraftStatusOfPatchSets(id, true);
String changeId = changeResult.getChangeId();
// Grant those permissions to admins.
grant(project, "refs/*", Permission.VIEW_DRAFTS);
grant(project, "refs/*", Permission.DELETE_DRAFTS);
try {
setApiUser(admin);
gApi.changes().id(changeId).delete();
} finally {
removePermission(project, "refs/*", Permission.DELETE_DRAFTS);
removePermission(project, "refs/*", Permission.VIEW_DRAFTS);
}
setApiUser(user);
assertThat(query(changeId)).isEmpty();
}
@Test
public void deleteDraftChangeWithNonDraftPatchSet() throws Exception {
assume().that(isAllowDrafts()).isTrue();
PushOneCommit.Result changeResult = createDraftChange();
Change.Id id = changeResult.getChange().getId();
setDraftStatusOfPatchSets(id, false);
String changeId = changeResult.getChangeId();
exception.expect(ResourceConflictException.class);
exception.expectMessage(
String.format("Cannot delete draft change %s: patch set 1 is not a draft", id));
gApi.changes().id(changeId).delete();
}
@Test
public void publishDraftChange() throws Exception {
assume().that(isAllowDrafts()).isTrue();
PushOneCommit.Result result = createDraftChange();
result.assertOkStatus();
String changeId = result.getChangeId();
String triplet = project.get() + "~master~" + changeId;
ChangeInfo c = get(triplet);
assertThat(c.id).isEqualTo(triplet);
assertThat(c.status).isEqualTo(ChangeStatus.DRAFT);
assertThat(c.revisions.get(c.currentRevision).draft).isTrue();
publishChange(changeId).assertNoContent();
c = get(triplet);
assertThat(c.status).isEqualTo(ChangeStatus.NEW);
assertThat(c.revisions.get(c.currentRevision).draft).isNull();
}
@Test
public void publishDraftPatchSet() throws Exception {
assume().that(isAllowDrafts()).isTrue();
PushOneCommit.Result result = createDraftChange();
result.assertOkStatus();
String changeId = result.getChangeId();
String triplet = project.get() + "~master~" + changeId;
ChangeInfo c = get(triplet);
assertThat(c.id).isEqualTo(triplet);
assertThat(c.status).isEqualTo(ChangeStatus.DRAFT);
publishPatchSet(changeId).assertNoContent();
assertThat(get(triplet).status).isEqualTo(ChangeStatus.NEW);
}
@Test
public void createDraftChangeWhenDraftsNotAllowed() throws Exception {
assume().that(isAllowDrafts()).isFalse();
PushOneCommit.Result r = pushTo("refs/drafts/master");
r.assertErrorStatus();
}
@Test
public void listApprovalsOnDraftChange() throws Exception {
assume().that(isAllowDrafts()).isTrue();
PushOneCommit.Result result = createDraftChange();
result.assertOkStatus();
String changeId = result.getChangeId();
String triplet = project.get() + "~master~" + changeId;
gApi.changes().id(triplet).addReviewer(user.fullName);
ChangeInfo info = get(triplet);
LabelInfo label = info.labels.get("Code-Review");
assertThat(label.all).hasSize(1);
assertThat(label.all.get(0)._accountId).isEqualTo(user.id.get());
assertThat(label.all.get(0).value).isEqualTo(0);
Collection<AccountInfo> ccs = info.reviewers.get(ReviewerState.REVIEWER);
assertThat(ccs).hasSize(1);
assertThat(ccs.iterator().next()._accountId).isEqualTo(user.id.get());
setApiUser(user);
gApi.changes().id(triplet).current().review(ReviewInput.recommend());
setApiUser(admin);
label = get(triplet).labels.get("Code-Review");
assertThat(label.all).hasSize(1);
assertThat(label.all.get(0)._accountId).isEqualTo(user.id.get());
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);
}
private RestResponse publishChange(String changeId) throws Exception {
return adminRestSession.post("/changes/" + changeId + "/publish");
}
private RestResponse publishPatchSet(String changeId) throws Exception {
PatchSet patchSet =
Iterables.getOnlyElement(queryProvider.get().byKeyPrefix(changeId)).currentPatchSet();
return adminRestSession.post(
"/changes/" + changeId + "/revisions/" + patchSet.getRevision().get() + "/publish");
}
}

View File

@ -19,7 +19,6 @@ import static org.junit.Assert.fail;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestProjectInput;
import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
@ -509,30 +508,6 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
assertChangeMergedEvents();
}
@Test
@TestProjectInput(createEmptyCommit = false)
public void mergeWithMissingChange() throws Exception {
// create a draft change
PushOneCommit.Result draftResult = createDraftChange();
// create a new change based on the draft change
PushOneCommit.Result changeResult = createChange();
// delete the draft change
gApi.changes().id(draftResult.getChangeId()).delete();
// approve and submit the change
submitWithConflict(
changeResult.getChangeId(),
"Failed to submit 1 change due to the following problems:\n"
+ "Change "
+ changeResult.getChange().getId()
+ ": depends on change that was not submitted");
assertRefUpdatedEvents();
assertChangeMergedEvents();
}
@Test
public void testPreviewSubmitTgz() throws Exception {
Project.NameKey p1 = createProject("project-name");

View File

@ -25,15 +25,11 @@ import com.google.gerrit.extensions.api.changes.SubmittedTogetherInfo;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.client.SubmitType;
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;
import java.util.List;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.revwalk.RevCommit;
@ -155,101 +151,6 @@ public class SubmittedTogetherIT extends AbstractDaemonTest {
}
}
@Test
public void hiddenDraftInTopic() throws Exception {
String id1 = createChange("subject", "a", "1", "topic").getChangeId();
createDraftChange("topic");
setApiUser(user);
SubmittedTogetherInfo result =
gApi.changes().id(id1).submittedTogether(EnumSet.of(NON_VISIBLE_CHANGES));
if (isSubmitWholeTopicEnabled()) {
assertThat(result.changes).hasSize(1);
assertThat(result.changes.get(0).changeId).isEqualTo(id1);
assertThat(result.nonVisibleChanges).isEqualTo(1);
} else {
assertThat(result.changes).isEmpty();
assertThat(result.nonVisibleChanges).isEqualTo(0);
}
}
@Test
public void hiddenDraftInTopicOldApi() throws Exception {
String id1 = createChange("subject", "a", "1", "topic").getChangeId();
createDraftChange("topic");
setApiUser(user);
if (isSubmitWholeTopicEnabled()) {
exception.expect(AuthException.class);
exception.expectMessage("change would be submitted with a change that you cannot see");
gApi.changes().id(id1).submittedTogether();
} else {
List<ChangeInfo> result = gApi.changes().id(id1).submittedTogether();
assertThat(result).isEmpty();
}
}
@Test
public void draftPatchSetInTopic() throws Exception {
RevCommit initialHead = getRemoteHead();
RevCommit a1 = commitBuilder().add("a", "1").message("change 1").create();
pushHead(testRepo, "refs/for/master/" + name("topic"), false);
String id1 = getChangeId(a1);
testRepo.reset(initialHead);
String parentId = createChange().getChangeId();
// 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/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();
pushHead(testRepo, "refs/for/master/" + name("topic"), false);
String id2 = getChangeId(b);
if (isSubmitWholeTopicEnabled()) {
setApiUser(user);
assertSubmittedTogether(id2, id2, id1);
setApiUser(admin);
assertSubmittedTogether(id2, id2, id1, parentId);
} else {
setApiUser(user);
assertSubmittedTogether(id2);
setApiUser(admin);
assertSubmittedTogether(id2);
}
}
@Test
public void doNotRevealVisibleAncestorOfHiddenDraft() throws Exception {
RevCommit initialHead = getRemoteHead();
createChange().getChangeId();
createDraftChange("topic");
testRepo.reset(initialHead);
String id = createChange("subject", "b", "1", "topic").getChangeId();
setApiUser(user);
SubmittedTogetherInfo result =
gApi.changes().id(id).submittedTogether(EnumSet.of(NON_VISIBLE_CHANGES));
if (isSubmitWholeTopicEnabled()) {
assertThat(result.changes).hasSize(1);
assertThat(result.changes.get(0).changeId).isEqualTo(id);
assertThat(result.nonVisibleChanges).isEqualTo(2);
} else {
assertThat(result.changes).isEmpty();
assertThat(result.nonVisibleChanges).isEqualTo(0);
}
}
@Test
public void topicChaining() throws Exception {
RevCommit initialHead = getRemoteHead();

View File

@ -42,7 +42,6 @@ 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;
@ -816,20 +815,6 @@ public class ChangeRebuilderIT extends AbstractDaemonTest {
assertThat(nc.getOriginalSubject()).isEqualTo(orig);
}
@Test
public void deleteDraftPS1WithNoOtherEntities() throws Exception {
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()).hasSize(1);
}
@Test
public void ignorePatchLineCommentsOnPatchSet0() throws Exception {
PushOneCommit.Result r = createChange();

View File

@ -16,7 +16,6 @@ package com.google.gerrit.acceptance.ssh;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.acceptance.GitUtil.initSsh;
import com.google.common.collect.Lists;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@ -290,27 +289,6 @@ public class QueryIT extends AbstractDaemonTest {
assertThat(changes.get(0).submitRecords.size()).isEqualTo(1);
}
@Test
public void queryWithNonVisibleCurrentPatchSet() throws Exception {
String changeId = createChange().getChangeId();
amendChangeAndMarkPatchSetAsDraft(changeId);
String query = "--current-patch-set --patch-sets " + changeId;
List<ChangeAttribute> changes = executeSuccessfulQuery(query);
assertThat(changes.size()).isEqualTo(1);
assertThat(changes.get(0).patchSets).isNotNull();
assertThat(changes.get(0).patchSets).hasSize(2);
assertThat(changes.get(0).currentPatchSet).isNotNull();
SshSession userSession = new SshSession(server, user);
initSsh(user);
userSession.open();
changes = executeSuccessfulQuery(query, userSession);
assertThat(changes.size()).isEqualTo(1);
assertThat(changes.get(0).patchSets).hasSize(1);
assertThat(changes.get(0).currentPatchSet).isNull();
userSession.close();
}
@Test
public void allChangeOptionsAreServedWithoutExceptions() throws Exception {
PushOneCommit.Result r = createChange();

View File

@ -142,7 +142,6 @@ public class PageLinks {
switch (status) {
case ABANDONED:
return toChangeQuery(status(status) + " " + op("topic", topic));
case DRAFT:
case MERGED:
case NEW:
return toChangeQuery(
@ -169,7 +168,6 @@ public class PageLinks {
return "status:abandoned";
case MERGED:
return "status:merged";
case DRAFT:
case NEW:
default:
return "status:open";

View File

@ -27,7 +27,6 @@ public class Permission implements Comparable<Permission> {
public static final String DELETE = "delete";
public static final String CREATE_TAG = "createTag";
public static final String CREATE_SIGNED_TAG = "createSignedTag";
public static final String DELETE_DRAFTS = "deleteDrafts";
public static final String DELETE_OWN_CHANGES = "deleteOwnChanges";
public static final String EDIT_HASHTAGS = "editHashtags";
public static final String EDIT_ASSIGNEE = "editAssignee";
@ -38,7 +37,6 @@ public class Permission implements Comparable<Permission> {
public static final String LABEL = "label-";
public static final String LABEL_AS = "labelAs-";
public static final String OWNER = "owner";
public static final String PUBLISH_DRAFTS = "publishDrafts";
public static final String PUSH = "push";
public static final String PUSH_MERGE = "pushMerge";
public static final String READ = "read";
@ -46,7 +44,6 @@ public class Permission implements Comparable<Permission> {
public static final String REMOVE_REVIEWER = "removeReviewer";
public static final String SUBMIT = "submit";
public static final String SUBMIT_AS = "submitAs";
public static final String VIEW_DRAFTS = "viewDrafts";
public static final String VIEW_PRIVATE_CHANGES = "viewPrivateChanges";
private static final List<String> NAMES_LC;
@ -74,14 +71,11 @@ public class Permission implements Comparable<Permission> {
NAMES_LC.add(REMOVE_REVIEWER.toLowerCase());
NAMES_LC.add(SUBMIT.toLowerCase());
NAMES_LC.add(SUBMIT_AS.toLowerCase());
NAMES_LC.add(VIEW_DRAFTS.toLowerCase());
NAMES_LC.add(VIEW_PRIVATE_CHANGES.toLowerCase());
NAMES_LC.add(EDIT_TOPIC_NAME.toLowerCase());
NAMES_LC.add(EDIT_HASHTAGS.toLowerCase());
NAMES_LC.add(EDIT_ASSIGNEE.toLowerCase());
NAMES_LC.add(DELETE_DRAFTS.toLowerCase());
NAMES_LC.add(DELETE_OWN_CHANGES.toLowerCase());
NAMES_LC.add(PUBLISH_DRAFTS.toLowerCase());
LABEL_INDEX = NAMES_LC.indexOf(Permission.LABEL);
LABEL_AS_INDEX = NAMES_LC.indexOf(Permission.LABEL_AS.toLowerCase());

View File

@ -159,6 +159,7 @@ public interface ChangeApi {
throws RestApiException;
/** Publishes a draft change. */
@Deprecated
void publish() throws RestApiException;
/** Rebase the current revision of a change using default options. */
@ -403,6 +404,7 @@ public interface ChangeApi {
throw new NotImplementedException();
}
@Deprecated
@Override
public void rebase() throws RestApiException {
throw new NotImplementedException();

View File

@ -31,6 +31,7 @@ import java.util.Map;
import java.util.Set;
public interface RevisionApi {
@Deprecated
void delete() throws RestApiException;
String description() throws RestApiException;
@ -47,6 +48,7 @@ public interface RevisionApi {
BinaryResult submitPreview(String format) throws RestApiException;
@Deprecated
void publish() throws RestApiException;
ChangeApi cherryPick(CherryPickInput in) throws RestApiException;
@ -155,6 +157,7 @@ public interface RevisionApi {
* interface.
*/
class NotImplemented implements RevisionApi {
@Deprecated
@Override
public void delete() throws RestApiException {
throw new NotImplementedException();
@ -175,6 +178,7 @@ public interface RevisionApi {
throw new NotImplementedException();
}
@Deprecated
@Override
public void publish() throws RestApiException {
throw new NotImplementedException();

View File

@ -33,22 +33,6 @@ public enum ChangeStatus {
*/
NEW,
/**
* Change is a draft change that only consists of draft patchsets.
*
* <p>This is a change that is not meant to be submitted or reviewed yet. If the uploader
* publishes the change, it becomes a NEW change. Publishing is a one-way action, a change cannot
* return to DRAFT status. Draft changes are only visible to the uploader and those explicitly
* added as reviewers. Note that currently draft changes cannot be abandoned.
*
* <p>Changes in the DRAFT state can be moved to:
*
* <ul>
* <li>{@link #NEW} - when the change is published, it becomes a new change.
* </ul>
*/
DRAFT,
/**
* Change is closed, and submitted to its destination branch.
*

View File

@ -20,7 +20,6 @@ import java.util.Map;
public class RevisionInfo {
public transient boolean isCurrent;
public Boolean draft;
public ChangeKind kind;
public int _number;
public Timestamp created;

View File

@ -1,25 +0,0 @@
// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.extensions.events;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
/** Notified whenever a Draft is published. */
@ExtensionPoint
public interface DraftPublishedListener {
interface Event extends RevisionEvent {}
void onDraftPublished(Event event);
}

View File

@ -419,8 +419,6 @@ public class ChangeInfo extends JavaScriptObject {
public final native String name() /*-{ return this.name; }-*/;
public final native boolean draft() /*-{ return this.draft || false; }-*/;
public final native AccountInfo uploader() /*-{ return this.uploader; }-*/;
public final native boolean isEdit() /*-{ return this._number == 0; }-*/;

View File

@ -91,8 +91,6 @@ public interface GerritConstants extends Constants {
String menuMyChanges();
String menuMyDrafts();
String menuMyWatchedChanges();
String menuMyStarredChanges();
@ -175,8 +173,6 @@ public interface GerritConstants extends Constants {
String jumpMine();
String jumpMineDrafts();
String jumpMineWatched();
String jumpMineStarred();

View File

@ -53,7 +53,6 @@ menuAllAbandoned = Abandoned
menuMine = My
menuMyChanges = Changes
menuMyDrafts = Drafts
menuMyStarredChanges = Starred Changes
menuMyWatchedChanges = Watched Changes
menuMyDraftComments = Draft Comments
@ -105,7 +104,6 @@ jumpAllMerged = Go to all merged changes
jumpAllAbandoned = Go to all abandoned changes
jumpMine = Go to my dashboard
jumpMineWatched = Go to watched changes
jumpMineDrafts = Go to drafts
jumpMineStarred = Go to starred changes
jumpMineDraftComments = Go to draft comments

View File

@ -70,13 +70,6 @@ public class JumpKeys {
Gerrit.display(PageLinks.MINE);
}
});
jumps.add(
new KeyCommand(0, 'd', Gerrit.C.jumpMineDrafts()) {
@Override
public void onKeyPress(KeyPressEvent event) {
Gerrit.display(PageLinks.toChangeQuery("owner:self is:draft"));
}
});
jumps.add(
new KeyCommand(0, 'c', Gerrit.C.jumpMineDraftComments()) {
@Override

View File

@ -129,7 +129,6 @@ public class SearchSuggestOracle extends HighlightSuggestOracle {
suggestions.add("is:reviewer");
suggestions.add("is:open");
suggestions.add("is:pending");
suggestions.add("is:draft");
suggestions.add("is:private");
suggestions.add("is:closed");
suggestions.add("is:merged");
@ -145,7 +144,6 @@ public class SearchSuggestOracle extends HighlightSuggestOracle {
suggestions.add("status:closed");
suggestions.add("status:merged");
suggestions.add("status:abandoned");
suggestions.add("status:draft");
suggestions.add("added:");
suggestions.add("deleted:");

View File

@ -137,7 +137,6 @@ permissionNames = \
createTag, \
createSignedTag, \
delete, \
deleteDrafts, \
deleteOwnChanges, \
editAssignee, \
editHashtags, \
@ -146,7 +145,6 @@ permissionNames = \
forgeCommitter, \
forgeServerAsCommitter, \
owner, \
publishDrafts, \
push, \
pushMerge, \
read, \
@ -154,7 +152,6 @@ permissionNames = \
removeReviewer, \
submit, \
submitAs, \
viewDrafts, \
viewPrivateChanges
abandon = Abandon
@ -163,7 +160,6 @@ create = Create Reference
createTag = Create Annotated Tag
createSignedTag = Create Signed Tag
delete = Delete Reference
deleteDrafts = Delete Drafts
deleteOwnChanges = Delete Own Changes
editAssignee = Edit Assignee
editHashtags = Edit Hashtags
@ -172,7 +168,6 @@ forgeAuthor = Forge Author Identity
forgeCommitter = Forge Committer Identity
forgeServerAsCommitter = Forge Server Identity
owner = Owner
publishDrafts = Publish Drafts
push = Push
pushMerge = Push Merge Commit
read = Read
@ -180,7 +175,6 @@ rebase = Rebase
removeReviewer = Remove Reviewer
submit = Submit
submitAs = Submit (On Behalf Of)
viewDrafts = View Drafts
viewPrivateChanges = View Private Changes
refErrorEmpty = Reference must be supplied

View File

@ -26,16 +26,6 @@ import com.google.gwt.user.client.ui.Button;
public class ChangeActions {
static void publish(
Project.NameKey project, Change.Id id, String revision, Button... draftButtons) {
ChangeApi.publish(project.get(), id.get(), revision, cs(project, id, draftButtons));
}
static void delete(
Project.NameKey project, Change.Id id, String revision, Button... draftButtons) {
ChangeApi.deleteRevision(project.get(), id.get(), revision, cs(project, id, draftButtons));
}
static void delete(Project.NameKey project, Change.Id id, Button... draftButtons) {
ChangeApi.deleteChange(project.get(), id.get(), mine(draftButtons));
}

View File

@ -43,8 +43,6 @@ public interface ChangeConstants extends Constants {
String author();
String draft();
String notAvailable();
String relatedChanges();
@ -78,6 +76,4 @@ public interface ChangeConstants extends Constants {
String deleteChangeEdit();
String deleteChange();
String deleteDraftRevision();
}

View File

@ -13,7 +13,6 @@ patchSet = Patch Set
commit = Commit
date = Date
author = Author / Committer
draft = (DRAFT)
notAvailable = N/A
relatedChanges = Related Changes
@ -35,4 +34,3 @@ deleteChangeEdit = Delete Change Edit?\n\
\n\
All changes made in the edit revision will be lost.
deleteChange = Delete Change?
deleteDraftRevision = Delete Draft Revision?

View File

@ -234,8 +234,6 @@ public class ChangeScreen extends Screen {
@UiField Button publishEdit;
@UiField Button rebaseEdit;
@UiField Button deleteEdit;
@UiField Button publish;
@UiField Button deleteRevision;
@UiField Button openAll;
@UiField Button editMode;
@UiField Button reviewMode;
@ -562,8 +560,7 @@ public class ChangeScreen extends Screen {
}
}
private void initRevisionsAction(
ChangeInfo info, String revision, NativeMap<ActionInfo> actions) {
private void initRevisionsAction(ChangeInfo info, String revision) {
int currentPatchSet;
if (info.currentRevision() != null && info.revisions().containsKey(info.currentRevision())) {
currentPatchSet = info.revision(info.currentRevision())._number();
@ -591,18 +588,6 @@ public class ChangeScreen extends Screen {
patchSetsAction =
new PatchSetsAction(
info.projectNameKey(), info.legacyId(), revision, edit, style, headerLine, patchSets);
RevisionInfo revInfo = info.revision(revision);
if (revInfo.draft()) {
if (actions.containsKey("publish")) {
publish.setVisible(true);
publish.setTitle(actions.get("publish").title());
}
if (actions.containsKey("/")) {
deleteRevision.setVisible(true);
deleteRevision.setTitle(actions.get("/").title());
}
}
}
private void initDownloadAction(ChangeInfo info, String revision) {
@ -702,18 +687,6 @@ public class ChangeScreen extends Screen {
}
}
@UiHandler("publish")
void onPublish(@SuppressWarnings("unused") ClickEvent e) {
ChangeActions.publish(getProject(), changeId, revision, publish, deleteRevision);
}
@UiHandler("deleteRevision")
void onDeleteRevision(@SuppressWarnings("unused") ClickEvent e) {
if (Window.confirm(Resources.C.deleteDraftRevision())) {
ChangeActions.delete(getProject(), changeId, revision, publish, deleteRevision);
}
}
@Override
public void registerKeys() {
super.registerKeys();
@ -1323,10 +1296,7 @@ public class ChangeScreen extends Screen {
}
private boolean isSubmittable(ChangeInfo info) {
boolean canSubmit =
info.status().isOpen()
&& revision.equals(info.currentRevision())
&& !info.revision(revision).draft();
boolean canSubmit = info.status().isOpen() && revision.equals(info.currentRevision());
if (canSubmit && info.status() == Change.Status.NEW) {
for (String name : info.labels()) {
LabelInfo label = info.label(name);
@ -1404,7 +1374,7 @@ public class ChangeScreen extends Screen {
// Properly render revision actions initially while waiting for
// the callback to populate them correctly.
NativeMap<ActionInfo> emptyMap = NativeMap.<ActionInfo>create();
initRevisionsAction(info, revision, emptyMap);
initRevisionsAction(info, revision);
quickApprove.setVisible(false);
actions.reloadRevisionActions(emptyMap);
@ -1416,8 +1386,7 @@ public class ChangeScreen extends Screen {
statusText.setInnerText(Util.C.notCurrent());
labels.setVisible(false);
} else {
Status s = info.revision(revision).draft() ? Status.DRAFT : info.status();
statusText.setInnerText(Util.toLongString(s));
statusText.setInnerText(Util.toLongString(info.status()));
}
if (info.isPrivate()) {
@ -1444,7 +1413,7 @@ public class ChangeScreen extends Screen {
}
private void renderRevisionInfo(ChangeInfo info, NativeMap<ActionInfo> actionMap) {
initRevisionsAction(info, revision, actionMap);
initRevisionsAction(info, revision);
commit.setParentNotCurrent(
actionMap.containsKey("rebase") && actionMap.get("rebase").enabled());
actions.reloadRevisionActions(actionMap);

View File

@ -415,13 +415,6 @@ limitations under the License.
<g:Button ui:field='deleteEdit' styleName='' visible='false'>
<div><ui:msg>Delete Edit</ui:msg></div>
</g:Button>
<g:Button ui:field='publish'
styleName='{style.highlight}' visible='false'>
<div><ui:msg>Publish</ui:msg></div>
</g:Button>
<g:Button ui:field='deleteRevision' styleName='' visible='false'>
<div><ui:msg>Delete Revision</ui:msg></div>
</g:Button>
<g:SimplePanel ui:field='headerExtensionMiddle' styleName='{style.headerExtension}'/>
</div>
</div>

View File

@ -192,9 +192,6 @@ class PatchSetsBox extends Composite {
}
sb.openTd().setStyleName(style.legacy_id());
if (r.draft()) {
sb.append(Resources.C.draft()).append(' ');
}
sb.append(r.id());
sb.closeTd();

View File

@ -49,7 +49,7 @@ class QuickApprove extends Button implements ClickHandler {
setVisible(false);
return;
}
if (info.revision(commit).isEdit() || info.revision(commit).draft()) {
if (info.revision(commit).isEdit()) {
setVisible(false);
return;
}

View File

@ -245,25 +245,12 @@ public class ChangeApi {
call(project, id, commit, "submit").post(in, cb);
}
/** Publish a specific revision of a draft change. */
public static void publish(
@Nullable String project, int id, String commit, AsyncCallback<JavaScriptObject> cb) {
JavaScriptObject in = JavaScriptObject.createObject();
call(project, id, commit, "publish").post(in, cb);
}
/** Delete a specific draft change. */
public static void deleteChange(
@Nullable String project, int id, AsyncCallback<JavaScriptObject> cb) {
change(project, id).delete(cb);
}
/** Delete a specific draft patch set. */
public static void deleteRevision(
@Nullable String project, int id, String commit, AsyncCallback<JavaScriptObject> cb) {
revision(project, id, commit).delete(cb);
}
/** Delete change edit. */
public static void deleteEdit(
@Nullable String project, int id, AsyncCallback<JavaScriptObject> cb) {

View File

@ -30,8 +30,6 @@ public class Util {
return "";
}
switch (status) {
case DRAFT:
return C.statusLongDraft();
case NEW:
return C.statusLongNew();
case MERGED:

View File

@ -398,7 +398,6 @@ abstract class DiffScreen extends Screen {
if (Gerrit.isSignedIn()) {
keyMap
.on("G I", () -> Gerrit.display(PageLinks.MINE))
.on("G D", () -> Gerrit.display(PageLinks.toChangeQuery("owner:self is:draft")))
.on("G C", () -> Gerrit.display(PageLinks.toChangeQuery("has:draft")))
.on("G W", () -> Gerrit.display(PageLinks.toChangeQuery("is:watched status:open")))
.on("G S", () -> Gerrit.display(PageLinks.toChangeQuery("is:starred")));

View File

@ -302,8 +302,6 @@ public final class Change {
private static final char MIN_OPEN = 'a';
/** Database constant for {@link Status#NEW}. */
public static final char STATUS_NEW = 'n';
/** Database constant for {@link Status#DRAFT}. */
public static final char STATUS_DRAFT = 'd';
/** Maximum database status constant for an open change. */
private static final char MAX_OPEN = 'z';
@ -340,27 +338,10 @@ public final class Change {
*/
NEW(STATUS_NEW, ChangeStatus.NEW),
/**
* Change is a draft change that only consists of draft patchsets.
*
* <p>This is a change that is not meant to be submitted or reviewed yet. If the uploader
* publishes the change, it becomes a NEW change. Publishing is a one-way action, a change
* cannot return to DRAFT status. Draft changes are only visible to the uploader and those
* explicitly added as reviewers.
*
* <p>Changes in the DRAFT state can be moved to:
*
* <ul>
* <li>{@link #NEW} - when the change is published, it becomes a new change;
* </ul>
*/
DRAFT(STATUS_DRAFT, ChangeStatus.DRAFT),
/**
* Change is closed, and submitted to its destination branch.
*
* <p>Once a change has been merged, it cannot be further modified by adding a replacement patch
* set. Draft comments however may be published, supporting a post-submit review.
*/
MERGED(STATUS_MERGED, ChangeStatus.MERGED),
@ -422,6 +403,14 @@ public final class Change {
return s;
}
}
// TODO(davido): Remove in 3.0, after all sites upgraded to version,
// where DRAFT status was removed. This code path is still needed,
// when changes are deserialized from the secondary index, during
// the online migration to the new schema version wasn't completed.
if (c == 'd') {
return Status.NEW;
}
return null;
}

View File

@ -168,8 +168,7 @@ public final class PatchSet {
@Column(id = 4)
protected Timestamp createdOn;
@Column(id = 5)
protected boolean draft;
// @Column(id = 5)
/**
* Opaque group identifier, usually assigned during creation.
@ -209,7 +208,6 @@ public final class PatchSet {
this.revision = src.revision;
this.uploader = src.uploader;
this.createdOn = src.createdOn;
this.draft = src.draft;
this.groups = src.groups;
this.pushCertificate = src.pushCertificate;
this.description = src.description;
@ -247,14 +245,6 @@ public final class PatchSet {
createdOn = ts;
}
public boolean isDraft() {
return draft;
}
public void setDraft(boolean draftStatus) {
draft = draftStatus;
}
public List<String> getGroups() {
if (groups == null) {
return Collections.emptyList();

View File

@ -18,7 +18,6 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.gerrit.server.ChangeUtil.PS_ID_ORDER;
import static com.google.gerrit.server.notedb.PatchSetState.DRAFT;
import static com.google.gerrit.server.notedb.PatchSetState.PUBLISHED;
import static java.util.function.Function.identity;
@ -34,7 +33,6 @@ import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.notedb.PatchSetState;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@ -104,7 +102,6 @@ public class PatchSetUtil {
ChangeUpdate update,
PatchSet.Id psId,
ObjectId commit,
boolean draft,
List<String> groups,
String pushCertificate,
String description)
@ -116,7 +113,6 @@ public class PatchSetUtil {
ps.setRevision(new RevId(commit.name()));
ps.setUploader(update.getAccountId());
ps.setCreatedOn(new Timestamp(update.getWhen().getTime()));
ps.setDraft(draft);
ps.setGroups(groups);
ps.setPushCertificate(pushCertificate);
ps.setDescription(description);
@ -125,27 +121,16 @@ public class PatchSetUtil {
update.setCommit(rw, commit, pushCertificate);
update.setPsDescription(description);
update.setGroups(groups);
if (draft) {
update.setPatchSetState(DRAFT);
}
return ps;
}
public void publish(ReviewDb db, ChangeUpdate update, PatchSet ps) throws OrmException {
ensurePatchSetMatches(ps.getId(), update);
ps.setDraft(false);
update.setPatchSetState(PUBLISHED);
db.patchSets().update(Collections.singleton(ps));
}
public void delete(ReviewDb db, ChangeUpdate update, PatchSet ps) throws OrmException {
ensurePatchSetMatches(ps.getId(), update);
checkArgument(ps.isDraft(), "cannot delete non-draft patch set %s", ps.getId());
update.setPatchSetState(PatchSetState.DELETED);
db.patchSets().delete(Collections.singleton(ps));
}
private void ensurePatchSetMatches(PatchSet.Id psId, ChangeUpdate update) {
Change.Id changeId = update.getChange().getId();
checkArgument(

View File

@ -141,7 +141,6 @@ public class GeneralPreferencesLoader {
}
if (r.my.isEmpty()) {
r.my.add(new MenuItem("Changes", "#/dashboard/self", null));
r.my.add(new MenuItem("Drafts", "#/q/owner:self+is:draft", null));
r.my.add(new MenuItem("Draft Comments", "#/q/has:draft", null));
r.my.add(new MenuItem("Edits", "#/q/has:edit", null));
r.my.add(new MenuItem("Watched Changes", "#/q/is:watched+is:open", null));

View File

@ -74,7 +74,6 @@ import com.google.gerrit.server.change.Mute;
import com.google.gerrit.server.change.PostHashtags;
import com.google.gerrit.server.change.PostPrivate;
import com.google.gerrit.server.change.PostReviewers;
import com.google.gerrit.server.change.PublishDraftPatchSet;
import com.google.gerrit.server.change.PutAssignee;
import com.google.gerrit.server.change.PutMessage;
import com.google.gerrit.server.change.PutTopic;
@ -117,7 +116,6 @@ class ChangeApiImpl implements ChangeApi {
private final Restore restore;
private final CreateMergePatchSet updateByMerge;
private final Provider<SubmittedTogether> submittedTogether;
private final PublishDraftPatchSet.CurrentRevision publishDraftChange;
private final Rebase.CurrentRevision rebase;
private final DeleteChange deleteChange;
private final GetTopic getTopic;
@ -163,7 +161,6 @@ class ChangeApiImpl implements ChangeApi {
Restore restore,
CreateMergePatchSet updateByMerge,
Provider<SubmittedTogether> submittedTogether,
PublishDraftPatchSet.CurrentRevision publishDraftChange,
Rebase.CurrentRevision rebase,
DeleteChange deleteChange,
GetTopic getTopic,
@ -207,7 +204,6 @@ class ChangeApiImpl implements ChangeApi {
this.restore = restore;
this.updateByMerge = updateByMerge;
this.submittedTogether = submittedTogether;
this.publishDraftChange = publishDraftChange;
this.rebase = rebase;
this.deleteChange = deleteChange;
this.getTopic = getTopic;
@ -403,13 +399,10 @@ class ChangeApiImpl implements ChangeApi {
}
}
@Deprecated
@Override
public void publish() throws RestApiException {
try {
publishDraftChange.apply(change, null);
} catch (Exception e) {
throw asRestApiException("Cannot publish change", e);
}
throw new UnsupportedOperationException("draft workflow is discontinued");
}
@Override

View File

@ -49,7 +49,6 @@ import com.google.gerrit.server.change.ApplyFix;
import com.google.gerrit.server.change.CherryPick;
import com.google.gerrit.server.change.Comments;
import com.google.gerrit.server.change.CreateDraftComment;
import com.google.gerrit.server.change.DeleteDraftPatchSet;
import com.google.gerrit.server.change.DraftComments;
import com.google.gerrit.server.change.FileResource;
import com.google.gerrit.server.change.Files;
@ -65,7 +64,6 @@ import com.google.gerrit.server.change.ListRobotComments;
import com.google.gerrit.server.change.Mergeable;
import com.google.gerrit.server.change.PostReview;
import com.google.gerrit.server.change.PreviewSubmit;
import com.google.gerrit.server.change.PublishDraftPatchSet;
import com.google.gerrit.server.change.PutDescription;
import com.google.gerrit.server.change.Rebase;
import com.google.gerrit.server.change.RebaseUtil;
@ -95,12 +93,10 @@ class RevisionApiImpl implements RevisionApi {
private final RevisionReviewers revisionReviewers;
private final RevisionReviewerApiImpl.Factory revisionReviewerApi;
private final CherryPick cherryPick;
private final DeleteDraftPatchSet deleteDraft;
private final Rebase rebase;
private final RebaseUtil rebaseUtil;
private final Submit submit;
private final PreviewSubmit submitPreview;
private final PublishDraftPatchSet publish;
private final Reviewed.PutReviewed putReviewed;
private final Reviewed.DeleteReviewed deleteReviewed;
private final RevisionResource revision;
@ -137,12 +133,10 @@ class RevisionApiImpl implements RevisionApi {
RevisionReviewers revisionReviewers,
RevisionReviewerApiImpl.Factory revisionReviewerApi,
CherryPick cherryPick,
DeleteDraftPatchSet deleteDraft,
Rebase rebase,
RebaseUtil rebaseUtil,
Submit submit,
PreviewSubmit submitPreview,
PublishDraftPatchSet publish,
Reviewed.PutReviewed putReviewed,
Reviewed.DeleteReviewed deleteReviewed,
Files files,
@ -176,13 +170,11 @@ class RevisionApiImpl implements RevisionApi {
this.revisionReviewers = revisionReviewers;
this.revisionReviewerApi = revisionReviewerApi;
this.cherryPick = cherryPick;
this.deleteDraft = deleteDraft;
this.rebase = rebase;
this.rebaseUtil = rebaseUtil;
this.review = review;
this.submit = submit;
this.submitPreview = submitPreview;
this.publish = publish;
this.files = files;
this.putReviewed = putReviewed;
this.deleteReviewed = deleteReviewed;
@ -253,20 +245,12 @@ class RevisionApiImpl implements RevisionApi {
@Override
public void publish() throws RestApiException {
try {
publish.apply(revision, new PublishDraftPatchSet.Input());
} catch (Exception e) {
throw asRestApiException("Cannot publish draft patch set", e);
}
throw new UnsupportedOperationException("draft workflow is discontinued");
}
@Override
public void delete() throws RestApiException {
try {
deleteDraft.apply(revision, null);
} catch (Exception e) {
throw asRestApiException("Cannot delete draft ps", e);
}
throw new UnsupportedOperationException("draft workflow is discontinued");
}
@Override

View File

@ -204,7 +204,7 @@ public class Abandon extends RetryingRestModifyView<ChangeResource, AbandonInput
.setTitle("Abandon the change")
.setVisible(
and(
change.getStatus().isOpen() && change.getStatus() != Change.Status.DRAFT,
change.getStatus().isOpen(),
rsrc.permissions().database(dbProvider).testCond(ChangePermission.ABANDON)));
}
}

View File

@ -155,7 +155,6 @@ public class ActionJson {
copy.ref = revisionInfo.ref;
copy.created = revisionInfo.created;
copy.uploader = revisionInfo.uploader;
copy.draft = revisionInfo.draft;
copy.fetch = revisionInfo.fetch;
copy.kind = revisionInfo.kind;
copy.description = revisionInfo.description;

View File

@ -405,7 +405,6 @@ public class ChangeInserter implements InsertChangeOp {
update,
psId,
commitId,
false,
newGroups,
pushCert,
patchSetDescription);

View File

@ -610,7 +610,7 @@ public class ChangeJson {
ctl = changeControlFactory.controlFor(db.get(), cd.change(), userProvider.get());
}
if (needMessages) {
out.messages = messages(ctl, cd, src);
out.messages = messages(ctl, cd);
}
finish(out);
@ -1112,8 +1112,8 @@ public class ChangeJson {
return result;
}
private Collection<ChangeMessageInfo> messages(
ChangeControl ctl, ChangeData cd, Map<PatchSet.Id, PatchSet> map) throws OrmException {
private Collection<ChangeMessageInfo> messages(ChangeControl ctl, ChangeData cd)
throws OrmException {
List<ChangeMessage> messages = cmUtil.byChange(db.get(), cd.notes());
if (messages.isEmpty()) {
return Collections.emptyList();
@ -1122,8 +1122,7 @@ public class ChangeJson {
List<ChangeMessageInfo> result = Lists.newArrayListWithCapacity(messages.size());
for (ChangeMessage message : messages) {
PatchSet.Id patchNum = message.getPatchSetId();
PatchSet ps = patchNum != null ? map.get(patchNum) : null;
if (patchNum == null || ctl.isPatchVisible(ps, db.get())) {
if (patchNum == null || ctl.isVisible(db.get())) {
ChangeMessageInfo cmi = new ChangeMessageInfo();
cmi.id = message.getKey().get();
cmi.author = accountLoader.get(message.getAuthor());
@ -1257,7 +1256,7 @@ public class ChangeJson {
} else {
want = id.equals(cd.change().currentPatchSetId());
}
if (want && ctl.isPatchVisible(in, db.get())) {
if (want && ctl.isVisible(db.get())) {
res.put(in.getRevision().get(), toRevisionInfo(cd, in, repo, rw, false, changeInfo));
}
}
@ -1318,7 +1317,6 @@ public class ChangeJson {
out.ref = in.getRefName();
out.created = in.getCreatedOn();
out.uploader = accountLoader.get(in.getUploader());
out.draft = in.isDraft() ? true : null;
out.fetch = makeFetchMap(cd, in);
out.kind = changeKindCache.getChangeKind(rw, repo != null ? repo.getConfig() : null, cd, in);
out.description = in.getDescription();
@ -1356,9 +1354,7 @@ public class ChangeJson {
out.files.remove(Patch.MERGE_LIST);
}
if ((out.isCurrent || (out.draft != null && out.draft))
&& has(CURRENT_ACTIONS)
&& userProvider.get().isIdentifiedUser()) {
if (out.isCurrent && has(CURRENT_ACTIONS) && userProvider.get().isIdentifiedUser()) {
actionJson.addRevisionActions(
changeInfo,
@ -1423,7 +1419,7 @@ public class ChangeJson {
continue;
}
if (!scheme.isAuthSupported() && !ctl.isPatchVisible(in, db.get())) {
if (!scheme.isAuthSupported() && !ctl.isVisible(db.get())) {
continue;
}

View File

@ -37,7 +37,6 @@ import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.git.CodeReviewCommit;
@ -96,7 +95,6 @@ public class CherryPickChange {
private final ProjectControl.GenericFactory projectControlFactory;
private final ApprovalsUtil approvalsUtil;
private final ChangeMessagesUtil changeMessagesUtil;
private final PatchSetUtil psUtil;
private final NotifyUtil notifyUtil;
@Inject
@ -114,7 +112,6 @@ public class CherryPickChange {
ProjectControl.GenericFactory projectControlFactory,
ApprovalsUtil approvalsUtil,
ChangeMessagesUtil changeMessagesUtil,
PatchSetUtil psUtil,
NotifyUtil notifyUtil) {
this.dbProvider = dbProvider;
this.seq = seq;
@ -129,7 +126,6 @@ public class CherryPickChange {
this.projectControlFactory = projectControlFactory;
this.approvalsUtil = approvalsUtil;
this.changeMessagesUtil = changeMessagesUtil;
this.psUtil = psUtil;
this.notifyUtil = notifyUtil;
}
@ -325,12 +321,9 @@ public class CherryPickChange {
throws IOException, OrmException, BadRequestException, ConfigInvalidException {
Change destChange = destNotes.getChange();
PatchSet.Id psId = ChangeUtil.nextPatchSetId(git, destChange.currentPatchSetId());
PatchSet current = psUtil.current(dbProvider.get(), destNotes);
PatchSetInserter inserter = patchSetInserterFactory.create(destNotes, psId, cherryPickCommit);
inserter
.setMessage("Uploaded patch set " + inserter.getPatchSetId().get() + ".")
.setDraft(current.isDraft())
.setNotify(input.notify)
.setAccountsToNotify(notifyUtil.resolveAccounts(input.notifyDetails));
bu.addOp(destChange.getId(), inserter);

View File

@ -161,9 +161,9 @@ public class CreateMergePatchSet
rsrc.getId(),
psInserter
.setMessage("Uploaded patch set " + nextPsId.get() + ".")
.setDraft(ps.isDraft())
.setNotify(NotifyHandling.NONE)
.setCheckAddPatchSetPermission(false));
.setCheckAddPatchSetPermission(false)
.setNotify(NotifyHandling.NONE));
bu.execute();
}

View File

@ -17,18 +17,14 @@ package com.google.gerrit.server.change;
import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.change.DeleteChange.Input;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.update.BatchUpdate;
@ -39,7 +35,6 @@ import com.google.gerrit.server.update.UpdateException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.eclipse.jgit.lib.Config;
@Singleton
public class DeleteChange extends RetryingRestModifyView<ChangeResource, Input, Response<?>>
@ -48,24 +43,13 @@ public class DeleteChange extends RetryingRestModifyView<ChangeResource, Input,
private final Provider<ReviewDb> db;
private final Provider<DeleteChangeOp> opProvider;
private final Provider<CurrentUser> user;
private final PermissionBackend permissionBackend;
private final boolean allowDrafts;
@Inject
public DeleteChange(
Provider<ReviewDb> db,
RetryHelper retryHelper,
Provider<DeleteChangeOp> opProvider,
Provider<CurrentUser> user,
PermissionBackend permissionBackend,
@GerritServerConfig Config cfg) {
Provider<ReviewDb> db, RetryHelper retryHelper, Provider<DeleteChangeOp> opProvider) {
super(retryHelper);
this.db = db;
this.opProvider = opProvider;
this.user = user;
this.permissionBackend = permissionBackend;
this.allowDrafts = DeleteChangeOp.allowDrafts(cfg);
}
@Override
@ -74,16 +58,8 @@ public class DeleteChange extends RetryingRestModifyView<ChangeResource, Input,
throws RestApiException, UpdateException, PermissionBackendException {
if (rsrc.getChange().getStatus() == Change.Status.MERGED) {
throw new MethodNotAllowedException("delete not permitted");
} else if (!allowDrafts && rsrc.getChange().getStatus() == Change.Status.DRAFT) {
// If drafts are disabled, only an administrator can delete a draft.
try {
permissionBackend.user(user).check(GlobalPermission.ADMINISTRATE_SERVER);
} catch (AuthException e) {
throw new MethodNotAllowedException("Draft workflow is disabled");
}
} else {
rsrc.permissions().database(db).check(ChangePermission.DELETE);
}
try (BatchUpdate bu =
updateFactory.create(db.get(), rsrc.getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
@ -115,14 +91,6 @@ public class DeleteChange extends RetryingRestModifyView<ChangeResource, Input,
case MERGED:
// Merged changes should never be deleted.
return false;
case DRAFT:
if (allowDrafts) {
// Drafts can only be deleted if the server has drafts enabled.
return true;
}
// If drafts are disabled, only administrators may delete.
return permissionBackend.user(user).testOrFalse(GlobalPermission.ADMINISTRATE_SERVER);
}
return false;
}

View File

@ -107,19 +107,6 @@ class DeleteChangeOp implements BatchUpdateOp {
id, patchSet.getPatchSetId()));
}
}
if (status == Change.Status.DRAFT) {
for (PatchSet ps : patchSets) {
if (!ps.isDraft()) {
throw new ResourceConflictException(
"Cannot delete draft change "
+ id
+ ": patch set "
+ ps.getPatchSetId()
+ " is not a draft");
}
}
}
}
private boolean isPatchSetMerged(ChangeContext ctx, PatchSet patchSet) throws IOException {

View File

@ -1,236 +0,0 @@
// Copyright (C) 2013 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.change;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.Iterables;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.restapi.AuthException;
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;
import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.change.DeleteDraftPatchSet.Input;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
import com.google.gerrit.server.update.Order;
import com.google.gerrit.server.update.RepoContext;
import com.google.gerrit.server.update.RetryHelper;
import com.google.gerrit.server.update.RetryingRestModifyView;
import com.google.gerrit.server.update.UpdateException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
@Singleton
public class DeleteDraftPatchSet
extends RetryingRestModifyView<RevisionResource, Input, Response<?>>
implements UiAction<RevisionResource> {
public static class Input {}
private final Provider<ReviewDb> db;
private final PatchSetInfoFactory patchSetInfoFactory;
private final PatchSetUtil psUtil;
private final Provider<DeleteChangeOp> deleteChangeOpProvider;
private final DynamicItem<AccountPatchReviewStore> accountPatchReviewStore;
private final boolean allowDrafts;
private final ChangeControl.GenericFactory changeControlFactory;
@Inject
public DeleteDraftPatchSet(
Provider<ReviewDb> db,
RetryHelper retryHelper,
PatchSetInfoFactory patchSetInfoFactory,
PatchSetUtil psUtil,
Provider<DeleteChangeOp> deleteChangeOpProvider,
DynamicItem<AccountPatchReviewStore> accountPatchReviewStore,
@GerritServerConfig Config cfg,
ChangeControl.GenericFactory changeControlFactory) {
super(retryHelper);
this.db = db;
this.patchSetInfoFactory = patchSetInfoFactory;
this.psUtil = psUtil;
this.deleteChangeOpProvider = deleteChangeOpProvider;
this.accountPatchReviewStore = accountPatchReviewStore;
this.allowDrafts = cfg.getBoolean("change", "allowDrafts", true);
this.changeControlFactory = changeControlFactory;
}
@Override
protected Response<?> applyImpl(
BatchUpdate.Factory updateFactory, RevisionResource rsrc, Input input)
throws RestApiException, UpdateException, OrmException, PermissionBackendException {
if (isDeletingOnlyPatchSet(rsrc)) {
// A change cannot have zero patch sets; the change is deleted instead.
rsrc.permissions().database(db).check(ChangePermission.DELETE);
}
try (BatchUpdate bu =
updateFactory.create(db.get(), rsrc.getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
bu.setOrder(Order.DB_BEFORE_REPO);
bu.addOp(rsrc.getChange().getId(), new Op(rsrc.getPatchSet().getId()));
bu.execute();
}
return Response.none();
}
private boolean isDeletingOnlyPatchSet(RevisionResource rsrc) throws OrmException {
Collection<PatchSet> patchSets = psUtil.byChange(db.get(), rsrc.getNotes());
return patchSets.size() == 1
&& Iterables.getOnlyElement(patchSets).getId().equals(rsrc.getPatchSet().getId());
}
private class Op implements BatchUpdateOp {
private final PatchSet.Id psId;
private Collection<PatchSet> patchSetsBeforeDeletion;
private PatchSet patchSet;
private DeleteChangeOp deleteChangeOp;
private Op(PatchSet.Id psId) {
this.psId = psId;
}
@Override
public boolean updateChange(ChangeContext ctx)
throws RestApiException, OrmException, IOException, NoSuchChangeException {
Map<PatchSet.Id, PatchSet> patchSets = psUtil.byChangeAsMap(db.get(), ctx.getNotes());
patchSet = patchSets.get(psId);
if (patchSet == null) {
return false; // Nothing to do.
}
if (!patchSet.isDraft()) {
throw new ResourceConflictException("Patch set is not a draft");
}
if (!allowDrafts) {
throw new MethodNotAllowedException("Draft workflow is disabled");
}
if (!changeControlFactory
.controlFor(ctx.getNotes(), ctx.getUser())
.canDeleteDraft(ctx.getDb())) {
throw new AuthException("Not permitted to delete this draft patch set");
}
patchSetsBeforeDeletion = patchSets.values();
deleteDraftPatchSet(patchSet, ctx);
deleteOrUpdateDraftChange(ctx, patchSets);
return true;
}
@Override
public void updateRepo(RepoContext ctx) throws IOException {
if (deleteChangeOp != null) {
deleteChangeOp.updateRepo(ctx);
return;
}
ctx.addRefUpdate(
ObjectId.fromString(patchSet.getRevision().get()),
ObjectId.zeroId(),
patchSet.getRefName());
}
private void deleteDraftPatchSet(PatchSet patchSet, ChangeContext ctx) throws OrmException {
// For NoteDb itself, no need to delete these entities, as they are
// automatically filtered out when patch sets are deleted.
psUtil.delete(ctx.getDb(), ctx.getUpdate(patchSet.getId()), patchSet);
accountPatchReviewStore.get().clearReviewed(psId);
// Use the unwrap from DeleteChangeOp to handle BatchUpdateReviewDb.
ReviewDb db = DeleteChangeOp.unwrap(ctx.getDb());
db.changeMessages().delete(db.changeMessages().byPatchSet(psId));
db.patchComments().delete(db.patchComments().byPatchSet(psId));
db.patchSetApprovals().delete(db.patchSetApprovals().byPatchSet(psId));
}
private void deleteOrUpdateDraftChange(ChangeContext ctx, Map<PatchSet.Id, PatchSet> patchSets)
throws OrmException, RestApiException, IOException, NoSuchChangeException {
Change c = ctx.getChange();
if (deletedOnlyPatchSet()) {
deleteChangeOp = deleteChangeOpProvider.get();
deleteChangeOp.updateChange(ctx);
return;
}
if (c.currentPatchSetId().equals(psId)) {
c.setCurrentPatchSet(previousPatchSetInfo(ctx, patchSets));
}
}
private boolean deletedOnlyPatchSet() {
return patchSetsBeforeDeletion.size() == 1
&& patchSetsBeforeDeletion.iterator().next().getId().equals(psId);
}
private PatchSetInfo previousPatchSetInfo(
ChangeContext ctx, Map<PatchSet.Id, PatchSet> patchSets) throws OrmException {
PatchSet.Id prevPsId = null;
for (PatchSet.Id id : patchSets.keySet()) {
if (id.get() < psId.get() && (prevPsId == null || id.get() > prevPsId.get())) {
prevPsId = id;
}
}
try {
// TODO(dborowitz): Get this in a way that doesn't involve re-opening
// the repo after the updateRepo phase.
return patchSetInfoFactory.get(
ctx.getDb(),
ctx.getNotes(),
new PatchSet.Id(psId.getParentKey(), checkNotNull(prevPsId).get()));
} catch (PatchSetInfoNotAvailableException e) {
throw new OrmException(e);
}
}
}
@Override
public UiAction.Description getDescription(RevisionResource rsrc) {
try {
return new UiAction.Description()
.setLabel("Delete")
.setTitle(String.format("Delete draft revision %d", rsrc.getPatchSet().getPatchSetId()))
.setVisible(
allowDrafts
&& rsrc.getPatchSet().isDraft()
&& psUtil.byChange(db.get(), rsrc.getNotes()).size() > 1
&& changeControlFactory
.controlFor(rsrc.getNotes(), rsrc.getUser())
.canDeleteDraft(db.get()));
} catch (OrmException e) {
throw new IllegalStateException(e);
}
}
}

View File

@ -90,7 +90,7 @@ public class GetPureRevert implements RestReadView<ChangeResource> {
throw new ResourceConflictException("current revision is missing");
} else if (!changeControlFactory
.controlFor(rsrc.getNotes(), rsrc.getUser())
.isPatchVisible(currentPatchSet, dbProvider.get())) {
.isVisible(dbProvider.get())) {
throw new AuthException("current revision not accessible");
}
return getPureRevert(rsrc.getNotes());

View File

@ -77,7 +77,6 @@ public class Module extends RestApiModule {
delete(CHANGE_KIND).to(DeleteChange.class);
post(CHANGE_KIND, "abandon").to(Abandon.class);
post(CHANGE_KIND, "hashtags").to(PostHashtags.class);
post(CHANGE_KIND, "publish").to(PublishDraftPatchSet.CurrentRevision.class);
post(CHANGE_KIND, "restore").to(Restore.class);
post(CHANGE_KIND, "revert").to(Revert.class);
post(CHANGE_KIND, "submit").to(Submit.CurrentRevision.class);
@ -111,9 +110,7 @@ public class Module extends RestApiModule {
get(REVISION_KIND, "actions").to(GetRevisionActions.class);
post(REVISION_KIND, "cherrypick").to(CherryPick.class);
get(REVISION_KIND, "commit").to(GetCommit.class);
delete(REVISION_KIND).to(DeleteDraftPatchSet.class);
get(REVISION_KIND, "mergeable").to(Mergeable.class);
post(REVISION_KIND, "publish").to(PublishDraftPatchSet.class);
get(REVISION_KIND, "related").to(GetRelated.class);
get(REVISION_KIND, "review").to(GetReview.class);
post(REVISION_KIND, "review").to(PostReview.class);

View File

@ -140,7 +140,7 @@ public class Move extends RetryingRestModifyView<ChangeResource, MoveInput, Chan
public boolean updateChange(ChangeContext ctx)
throws OrmException, ResourceConflictException, RepositoryNotFoundException, IOException {
change = ctx.getChange();
if (change.getStatus() != Status.NEW && change.getStatus() != Status.DRAFT) {
if (change.getStatus() != Status.NEW) {
throw new ResourceConflictException("Change is " + ChangeUtil.status(change));
}

View File

@ -98,7 +98,6 @@ public class PatchSetInserter implements BatchUpdateOp {
private String description;
private boolean validate = true;
private boolean checkAddPatchSetPermission = true;
private boolean draft;
private List<String> groups = Collections.emptyList();
private boolean fireRevisionCreated = true;
private NotifyHandling notify = NotifyHandling.ALL;
@ -168,11 +167,6 @@ public class PatchSetInserter implements BatchUpdateOp {
return this;
}
public PatchSetInserter setDraft(boolean draft) {
this.draft = draft;
return this;
}
public PatchSetInserter setGroups(List<String> groups) {
checkNotNull(groups, "groups may not be null");
this.groups = groups;
@ -253,7 +247,6 @@ public class PatchSetInserter implements BatchUpdateOp {
ctx.getUpdate(psId),
psId,
commitId,
draft,
newGroups,
null,
description);
@ -275,7 +268,7 @@ public class PatchSetInserter implements BatchUpdateOp {
patchSetInfo =
patchSetInfoFactory.get(ctx.getRevWalk(), ctx.getRevWalk().parseCommit(commitId), psId);
if (change.getStatus() != Change.Status.DRAFT && !allowClosed) {
if (!allowClosed) {
change.setStatus(Change.Status.NEW);
}
change.setCurrentPatchSet(patchSetInfo);

View File

@ -1,295 +0,0 @@
// Copyright (C) 2013 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.change;
import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromFooters;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.change.PublishDraftPatchSet.Input;
import com.google.gerrit.server.extensions.events.DraftPublished;
import com.google.gerrit.server.mail.MailUtil.MailRecipients;
import com.google.gerrit.server.mail.send.CreateChangeSender;
import com.google.gerrit.server.mail.send.ReplacePatchSetSender;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
import com.google.gerrit.server.update.Context;
import com.google.gerrit.server.update.RetryHelper;
import com.google.gerrit.server.update.RetryingRestModifyView;
import com.google.gerrit.server.update.UpdateException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.FooterLine;
import org.eclipse.jgit.revwalk.RevCommit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
public class PublishDraftPatchSet
extends RetryingRestModifyView<RevisionResource, Input, Response<?>>
implements UiAction<RevisionResource> {
private static final Logger log = LoggerFactory.getLogger(PublishDraftPatchSet.class);
public static class Input {}
private final AccountResolver accountResolver;
private final ApprovalsUtil approvalsUtil;
private final CreateChangeSender.Factory createChangeSenderFactory;
private final PatchSetInfoFactory patchSetInfoFactory;
private final PatchSetUtil psUtil;
private final Provider<ReviewDb> dbProvider;
private final ReplacePatchSetSender.Factory replacePatchSetFactory;
private final DraftPublished draftPublished;
private final ProjectCache projectCache;
private final ChangeControl.GenericFactory changeControlFactory;
@Inject
public PublishDraftPatchSet(
AccountResolver accountResolver,
ApprovalsUtil approvalsUtil,
RetryHelper retryHelper,
CreateChangeSender.Factory createChangeSenderFactory,
PatchSetInfoFactory patchSetInfoFactory,
PatchSetUtil psUtil,
Provider<ReviewDb> dbProvider,
ReplacePatchSetSender.Factory replacePatchSetFactory,
DraftPublished draftPublished,
ProjectCache projectCache,
ChangeControl.GenericFactory changeControlFactory) {
super(retryHelper);
this.accountResolver = accountResolver;
this.approvalsUtil = approvalsUtil;
this.createChangeSenderFactory = createChangeSenderFactory;
this.patchSetInfoFactory = patchSetInfoFactory;
this.psUtil = psUtil;
this.dbProvider = dbProvider;
this.replacePatchSetFactory = replacePatchSetFactory;
this.draftPublished = draftPublished;
this.projectCache = projectCache;
this.changeControlFactory = changeControlFactory;
}
@Override
protected Response<?> applyImpl(
BatchUpdate.Factory updateFactory, RevisionResource rsrc, Input input)
throws RestApiException, UpdateException, IOException {
return apply(
updateFactory,
rsrc.getUser(),
rsrc.getChange(),
rsrc.getPatchSet().getId(),
rsrc.getPatchSet());
}
private Response<?> apply(
BatchUpdate.Factory updateFactory, CurrentUser u, Change c, PatchSet.Id psId, PatchSet ps)
throws RestApiException, UpdateException, IOException {
try (BatchUpdate bu =
updateFactory.create(dbProvider.get(), c.getProject(), u, TimeUtil.nowTs())) {
bu.addOp(c.getId(), new Op(psId, projectCache.checkedGet(c.getProject()), ps));
bu.execute();
}
return Response.none();
}
@Override
public UiAction.Description getDescription(RevisionResource rsrc) {
try {
return new UiAction.Description()
.setLabel("Publish")
.setTitle(String.format("Publish revision %d", rsrc.getPatchSet().getPatchSetId()))
.setVisible(
rsrc.getPatchSet().isDraft()
&& changeControlFactory
.controlFor(rsrc.getNotes(), rsrc.getUser())
.canPublish(dbProvider.get()));
} catch (OrmException e) {
throw new IllegalStateException(e);
}
}
public static class CurrentRevision
extends RetryingRestModifyView<ChangeResource, Input, Response<?>> {
private final PublishDraftPatchSet publish;
@Inject
CurrentRevision(RetryHelper retryHelper, PublishDraftPatchSet publish) {
super(retryHelper);
this.publish = publish;
}
@Override
protected Response<?> applyImpl(
BatchUpdate.Factory updateFactory, ChangeResource rsrc, Input input)
throws RestApiException, UpdateException, IOException {
return publish.apply(
updateFactory,
rsrc.getUser(),
rsrc.getChange(),
rsrc.getChange().currentPatchSetId(),
null);
}
}
private class Op implements BatchUpdateOp {
private final PatchSet.Id psId;
private final ProjectState projectState;
private PatchSet patchSet;
private Change change;
private boolean wasDraftChange;
private PatchSetInfo patchSetInfo;
private MailRecipients recipients;
private Op(PatchSet.Id psId, ProjectState projectState, @Nullable PatchSet patchSet) {
this.psId = psId;
this.projectState = projectState;
this.patchSet = patchSet;
}
@Override
public boolean updateChange(ChangeContext ctx)
throws RestApiException, OrmException, IOException {
if (!changeControlFactory.controlFor(ctx.getNotes(), ctx.getUser()).canPublish(ctx.getDb())) {
throw new AuthException("Cannot publish this draft patch set");
}
if (patchSet == null) {
patchSet = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
if (patchSet == null) {
throw new ResourceNotFoundException(psId.toString());
}
}
saveChange(ctx);
savePatchSet(ctx);
addReviewers(ctx);
return true;
}
private void saveChange(ChangeContext ctx) {
change = ctx.getChange();
ChangeUpdate update = ctx.getUpdate(psId);
wasDraftChange = change.getStatus() == Change.Status.DRAFT;
if (wasDraftChange) {
change.setStatus(Change.Status.NEW);
update.setStatus(change.getStatus());
}
}
private void savePatchSet(ChangeContext ctx) throws RestApiException, OrmException {
if (!patchSet.isDraft()) {
throw new ResourceConflictException("Patch set is not a draft");
}
psUtil.publish(ctx.getDb(), ctx.getUpdate(psId), patchSet);
}
private void addReviewers(ChangeContext ctx) throws OrmException, IOException {
LabelTypes labelTypes = projectState.getLabelTypes(ctx.getNotes(), ctx.getUser());
Collection<Account.Id> oldReviewers =
approvalsUtil.getReviewers(ctx.getDb(), ctx.getNotes()).all();
RevCommit commit =
ctx.getRevWalk().parseCommit(ObjectId.fromString(patchSet.getRevision().get()));
patchSetInfo = patchSetInfoFactory.get(ctx.getRevWalk(), commit, psId);
List<FooterLine> footerLines = commit.getFooterLines();
recipients =
getRecipientsFromFooters(ctx.getDb(), accountResolver, patchSet.isDraft(), footerLines);
recipients.remove(ctx.getAccountId());
approvalsUtil.addReviewers(
ctx.getDb(),
ctx.getUpdate(psId),
labelTypes,
change,
patchSet,
patchSetInfo,
recipients.getReviewers(),
oldReviewers);
}
@Override
public void postUpdate(Context ctx) throws OrmException {
draftPublished.fire(change, patchSet, ctx.getAccount(), ctx.getWhen());
if (patchSet.isDraft() && change.getStatus() == Change.Status.DRAFT) {
// Skip emails if the patch set is still a draft.
return;
}
try {
if (wasDraftChange) {
sendCreateChange(ctx);
} else {
sendReplacePatchSet(ctx);
}
} catch (EmailException e) {
log.error("Cannot send email for publishing draft " + psId, e);
}
}
private void sendCreateChange(Context ctx) throws EmailException {
CreateChangeSender cm = createChangeSenderFactory.create(ctx.getProject(), change.getId());
cm.setFrom(ctx.getAccountId());
cm.setPatchSet(patchSet, patchSetInfo);
cm.addReviewers(recipients.getReviewers());
cm.addExtraCC(recipients.getCcOnly());
cm.send();
}
private void sendReplacePatchSet(Context ctx) throws EmailException {
ChangeMessage msg =
ChangeMessagesUtil.newMessage(
psId,
ctx.getUser(),
ctx.getWhen(),
"Uploaded patch set " + psId.get() + ".",
ChangeMessagesUtil.TAG_UPLOADED_PATCH_SET);
ReplacePatchSetSender cm = replacePatchSetFactory.create(ctx.getProject(), change.getId());
cm.setFrom(ctx.getAccountId());
cm.setPatchSet(patchSet, patchSetInfo);
cm.setChangeMessage(msg.getMessage(), ctx.getWhen());
cm.addReviewers(recipients.getReviewers());
cm.addExtraCC(recipients.getCcOnly());
cm.send();
}
}
}

View File

@ -111,7 +111,7 @@ public class PutMessage
throw new ResourceConflictException("current revision is missing");
} else if (!changeControlFactory
.controlFor(resource.getNotes(), resource.getUser())
.isPatchVisible(ps, db.get())) {
.isVisible(db.get())) {
throw new AuthException("current revision not accessible");
}

View File

@ -158,7 +158,7 @@ public class Rebase extends RetryingRestModifyView<RevisionResource, RebaseInput
}
PatchSet.Id baseId = base.patchSet().getId();
ChangeControl baseCtl = changeControlFactory.controlFor(base.notes(), userProvider.get());
if (!baseCtl.isPatchVisible(base.patchSet(), db)) {
if (!baseCtl.isVisible(db)) {
throw new AuthException("base revision not accessible: " + str);
} else if (change.getId().equals(baseId.getParentKey())) {
throw new ResourceConflictException("cannot rebase change onto itself");
@ -252,7 +252,7 @@ public class Rebase extends RetryingRestModifyView<RevisionResource, RebaseInput
throw new ResourceConflictException("current revision is missing");
} else if (!changeControlFactory
.controlFor(rsrc.getNotes(), rsrc.getUser())
.isPatchVisible(ps, rebase.dbProvider.get())) {
.isVisible(rebase.dbProvider.get())) {
throw new AuthException("current revision not accessible");
}
return rebase.applyImpl(updateFactory, new RevisionResource(rsrc, ps), input);

View File

@ -186,7 +186,6 @@ public class RebaseChangeOp implements BatchUpdateOp {
patchSetInserterFactory
.create(notes, rebasedPatchSetId, rebasedCommit)
.setDescription("Rebase")
.setDraft(originalPatchSet.isDraft())
.setNotify(NotifyHandling.NONE)
.setFireRevisionCreated(fireRevisionCreated)
.setCopyApprovals(copyApprovals)

View File

@ -129,11 +129,7 @@ public class ReviewerJson {
PatchSet ps = cd.currentPatchSet();
if (ps != null) {
for (SubmitRecord rec :
submitRuleEvaluatorFactory
.create(user, cd)
.setFastEvalLabels(true)
.setAllowDraft(true)
.evaluate()) {
submitRuleEvaluatorFactory.create(user, cd).setFastEvalLabels(true).evaluate()) {
if (rec.labels == null) {
continue;
}

View File

@ -77,7 +77,7 @@ public class Revisions implements ChildCollection<ChangeResource, RevisionResour
throws ResourceNotFoundException, AuthException, OrmException, IOException {
if (id.get().equals("current")) {
PatchSet ps = psUtil.current(dbProvider.get(), change.getNotes());
if (ps != null && visible(change, ps)) {
if (ps != null && visible(change)) {
return new RevisionResource(change, ps).doNotCache();
}
throw new ResourceNotFoundException(id);
@ -85,7 +85,7 @@ public class Revisions implements ChildCollection<ChangeResource, RevisionResour
List<RevisionResource> match = Lists.newArrayListWithExpectedSize(2);
for (RevisionResource rsrc : find(change, id.get())) {
if (visible(change, rsrc.getPatchSet())) {
if (visible(change)) {
match.add(rsrc);
}
}
@ -100,10 +100,10 @@ public class Revisions implements ChildCollection<ChangeResource, RevisionResour
}
}
private boolean visible(ChangeResource change, PatchSet ps) throws OrmException {
private boolean visible(ChangeResource change) throws OrmException {
return changeControlFactory
.controlFor(change.getNotes(), change.getUser())
.isPatchVisible(ps, dbProvider.get());
.isVisible(dbProvider.get());
}
private List<RevisionResource> find(ChangeResource change, String id)

View File

@ -254,7 +254,6 @@ public class Submit
}
// $FALL-THROUGH$
case ABANDONED:
case DRAFT:
default:
throw new ResourceConflictException("change is " + ChangeUtil.status(change));
}
@ -316,7 +315,6 @@ public class Submit
Change change = resource.getChange();
if (!change.getStatus().isOpen()
|| !resource.isCurrent()
|| resource.getPatchSet().isDraft()
|| !resource.permissions().testOrFalse(ChangePermission.SUBMIT)) {
return null; // submit not visible
}
@ -534,7 +532,7 @@ public class Submit
throw new ResourceConflictException("current revision is missing");
} else if (!changeControlFactory
.controlFor(rsrc.getNotes(), rsrc.getUser())
.isPatchVisible(ps, dbProvider.get())) {
.isVisible(dbProvider.get())) {
throw new AuthException("current revision not accessible");
}

View File

@ -41,7 +41,6 @@ import com.google.gerrit.extensions.events.ChangeMergedListener;
import com.google.gerrit.extensions.events.ChangeRestoredListener;
import com.google.gerrit.extensions.events.ChangeRevertedListener;
import com.google.gerrit.extensions.events.CommentAddedListener;
import com.google.gerrit.extensions.events.DraftPublishedListener;
import com.google.gerrit.extensions.events.GarbageCollectorListener;
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
import com.google.gerrit.extensions.events.GroupIndexedListener;
@ -313,7 +312,6 @@ public class GerritGlobalModule extends FactoryModule {
DynamicSet.setOf(binder(), AssigneeChangedListener.class);
DynamicSet.setOf(binder(), ChangeAbandonedListener.class);
DynamicSet.setOf(binder(), CommentAddedListener.class);
DynamicSet.setOf(binder(), DraftPublishedListener.class);
DynamicSet.setOf(binder(), HashtagsEditedListener.class);
DynamicSet.setOf(binder(), ChangeMergedListener.class);
DynamicSet.setOf(binder(), ChangeRestoredListener.class);

View File

@ -25,7 +25,6 @@ public class PatchSetAttribute {
public AccountAttribute uploader;
public Long createdOn;
public AccountAttribute author;
public boolean isDraft;
public ChangeKind kind;
public List<ApprovalAttribute> approvals;

View File

@ -26,7 +26,6 @@ import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.Account;
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.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
@ -205,11 +204,7 @@ public class ChangeEditUtil {
try (BatchUpdate bu =
updateFactory.create(db.get(), change.getProject(), user, TimeUtil.nowTs())) {
bu.setRepository(repo, rw, oi);
bu.addOp(
change.getId(),
inserter
.setDraft(change.getStatus() == Status.DRAFT || basePatchSet.isDraft())
.setMessage(message.toString()));
bu.addOp(change.getId(), inserter.setMessage(message.toString()));
bu.addOp(
change.getId(),
new BatchUpdateOp() {

View File

@ -1,28 +0,0 @@
// Copyright (C) 2012 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.events;
import com.google.common.base.Supplier;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.data.AccountAttribute;
public class DraftPublishedEvent extends PatchSetEvent {
static final String TYPE = "draft-published";
public Supplier<AccountAttribute> uploader;
public DraftPublishedEvent(Change change) {
super(TYPE, change);
}
}

View File

@ -470,7 +470,6 @@ public class EventFactory {
p.ref = patchSet.getRefName();
p.uploader = asAccountAttribute(patchSet.getUploader());
p.createdOn = patchSet.getCreatedOn().getTime() / 1000L;
p.isDraft = patchSet.isDraft();
PatchSet.Id pId = patchSet.getId();
try {
p.parents = new ArrayList<>();

View File

@ -28,7 +28,6 @@ public class EventTypes {
register(ChangeRestoredEvent.TYPE, ChangeRestoredEvent.class);
register(CommentAddedEvent.TYPE, CommentAddedEvent.class);
register(CommitReceivedEvent.TYPE, CommitReceivedEvent.class);
register(DraftPublishedEvent.TYPE, DraftPublishedEvent.class);
register(HashtagsChangedEvent.TYPE, HashtagsChangedEvent.class);
register(RefUpdatedEvent.TYPE, RefUpdatedEvent.class);
register(RefReceivedEvent.TYPE, RefReceivedEvent.class);

View File

@ -29,7 +29,6 @@ import com.google.gerrit.extensions.events.ChangeAbandonedListener;
import com.google.gerrit.extensions.events.ChangeMergedListener;
import com.google.gerrit.extensions.events.ChangeRestoredListener;
import com.google.gerrit.extensions.events.CommentAddedListener;
import com.google.gerrit.extensions.events.DraftPublishedListener;
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
import com.google.gerrit.extensions.events.HashtagsEditedListener;
import com.google.gerrit.extensions.events.NewProjectCreatedListener;
@ -79,7 +78,6 @@ public class StreamEventsApiListener
ChangeMergedListener,
ChangeRestoredListener,
CommentAddedListener,
DraftPublishedListener,
GitReferenceUpdatedListener,
HashtagsEditedListener,
NewProjectCreatedListener,
@ -98,7 +96,6 @@ public class StreamEventsApiListener
DynamicSet.bind(binder(), ChangeMergedListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), ChangeRestoredListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), CommentAddedListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), DraftPublishedListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), GitReferenceUpdatedListener.class)
.to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), HashtagsEditedListener.class).to(StreamEventsApiListener.class);
@ -391,24 +388,6 @@ public class StreamEventsApiListener
}
}
@Override
public void onDraftPublished(DraftPublishedListener.Event ev) {
try {
ChangeNotes notes = getNotes(ev.getChange());
Change change = notes.getChange();
PatchSet ps = getPatchSet(notes, ev.getRevision());
DraftPublishedEvent event = new DraftPublishedEvent(change);
event.change = changeAttributeSupplier(change);
event.patchSet = patchSetAttributeSupplier(change, ps);
event.uploader = accountAttributeSupplier(ev.getWho());
dispatcher.get().postEvent(change, event);
} catch (OrmException | PermissionBackendException e) {
log.error("Failed to dispatch event", e);
}
}
@Override
public void onCommentAdded(CommentAddedListener.Event ev) {
try {

View File

@ -1,73 +0,0 @@
// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.extensions.events;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.events.DraftPublishedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.io.IOException;
import java.sql.Timestamp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DraftPublished {
private static final Logger log = LoggerFactory.getLogger(DraftPublished.class);
private final DynamicSet<DraftPublishedListener> listeners;
private final EventUtil util;
@Inject
public DraftPublished(DynamicSet<DraftPublishedListener> listeners, EventUtil util) {
this.listeners = listeners;
this.util = util;
}
public void fire(Change change, PatchSet patchSet, Account accountId, Timestamp when) {
try {
Event event =
new Event(
util.changeInfo(change),
util.revisionInfo(change.getProject(), patchSet),
util.accountInfo(accountId),
when);
for (DraftPublishedListener l : listeners) {
try {
l.onDraftPublished(event);
} catch (Exception e) {
util.logEventListenerError(this, l, e);
}
}
} catch (PatchListNotAvailableException | GpgException | IOException | OrmException e) {
log.error("Couldn't fire event", e);
}
}
private static class Event extends AbstractRevisionEvent implements DraftPublishedListener.Event {
Event(ChangeInfo change, RevisionInfo revision, AccountInfo publisher, Timestamp when) {
super(change, revision, publisher, when, NotifyHandling.ALL);
}
}
}

View File

@ -98,8 +98,6 @@ public class AbandonOp implements BatchUpdateOp {
ChangeUpdate update = ctx.getUpdate(psId);
if (!change.getStatus().isOpen()) {
throw new ResourceConflictException("change is " + ChangeUtil.status(change));
} else if (change.getStatus() == Change.Status.DRAFT) {
throw new ResourceConflictException("draft changes cannot be abandoned");
}
patchSet = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
change.setStatus(Change.Status.ABANDONED);

View File

@ -26,9 +26,6 @@ public interface ChangeReportFormatter {
@Nullable
public abstract String subject();
@Nullable
public abstract Boolean isDraft();
@Nullable
public abstract Boolean isEdit();
@ -48,8 +45,6 @@ public interface ChangeReportFormatter {
public abstract Builder setSubject(String val);
public abstract Builder setIsDraft(Boolean val);
public abstract Builder setIsEdit(Boolean val);
public abstract Builder setIsPrivate(Boolean val);
@ -60,8 +55,6 @@ public interface ChangeReportFormatter {
abstract String subject();
abstract Boolean isDraft();
abstract Boolean isEdit();
abstract Boolean isPrivate();
@ -73,7 +66,6 @@ public interface ChangeReportFormatter {
public Input build() {
setChange(change());
setSubject(subject() == null ? change().getSubject() : subject());
setIsDraft(isDraft() == null ? Change.Status.DRAFT == change().getStatus() : isDraft());
setIsEdit(isEdit() == null ? false : isEdit());
setIsPrivate(isPrivate() == null ? change().isPrivate() : isPrivate());
setIsWorkInProgress(

View File

@ -49,9 +49,6 @@ public class DefaultChangeReportFormatter implements ChangeReportFormatter {
.append(ChangeUtil.formatChangeUrl(url, input.change()))
.append(" ")
.append(ChangeUtil.cropSubject(input.subject()));
if (input.isDraft()) {
m.append(" [DRAFT]");
}
if (input.isEdit()) {
m.append(" [EDIT]");
}

View File

@ -58,7 +58,6 @@ import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.extensions.api.changes.HashtagsInput;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
@ -662,7 +661,6 @@ class ReceiveCommits {
ChangeReportFormatter.Input.builder()
.setChange(u.notes.getChange())
.setSubject(subject)
.setIsDraft(u.replaceOp != null && u.replaceOp.getPatchSet().isDraft())
.setIsEdit(edit)
.setIsPrivate(isPrivate)
.setIsWorkInProgress(wip)
@ -1239,11 +1237,6 @@ class ReceiveCommits {
cc.add(id);
}
@Option(name = "--publish", usage = "publish new/updated changes")
void publish(boolean publish) {
draft = !publish;
}
@Option(
name = "--label",
aliases = {"-l"},
@ -1473,21 +1466,6 @@ class ReceiveCommits {
return;
}
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");
return;
} else if (projectControl
.controlForRef(MagicBranch.NEW_DRAFT_CHANGE + ref)
.isBlocked(Permission.PUSH)) {
errors.put(ReceiveError.CODE_REVIEW, ref);
reject(cmd, "cannot upload drafts");
return;
}
}
try {
magicBranch.perm.check(RefPermission.CREATE_CHANGE);
} catch (AuthException denied) {
@ -1511,11 +1489,6 @@ class ReceiveCommits {
return;
}
if (magicBranch.draft && magicBranch.submit) {
reject(cmd, "cannot submit draft");
return;
}
if (magicBranch.submit) {
try {
permissions.ref(ref).check(RefPermission.UPDATE_BY_SUBMIT);
@ -1539,10 +1512,6 @@ class ReceiveCommits {
String destBranch = magicBranch.dest.get();
try {
if (magicBranch.merged) {
if (magicBranch.draft) {
reject(cmd, "cannot be draft & merged");
return;
}
if (magicBranch.base != null) {
reject(cmd, "cannot use merged with base");
return;
@ -2157,7 +2126,7 @@ class ReceiveCommits {
checkNotNull(magicBranch);
recipients.add(magicBranch.getMailRecipients());
approvals = magicBranch.labels;
recipients.add(getRecipientsFromFooters(db, accountResolver, false, footerLines));
recipients.add(getRecipientsFromFooters(db, accountResolver, footerLines));
recipients.remove(me);
StringBuilder msg =
new StringBuilder(

View File

@ -27,7 +27,6 @@ import org.eclipse.jgit.lib.Config;
class ReceiveConfig {
final boolean checkMagicRefs;
final boolean checkReferencedObjectsAreReachable;
final boolean allowDrafts;
final int maxBatchCommits;
private final int systemMaxBatchChanges;
private final AccountLimits.Factory limitsFactory;
@ -37,7 +36,6 @@ class ReceiveConfig {
checkMagicRefs = config.getBoolean("receive", null, "checkMagicRefs", true);
checkReferencedObjectsAreReachable =
config.getBoolean("receive", null, "checkReferencedObjectsAreReachable", true);
allowDrafts = config.getBoolean("change", null, "allowDrafts", true);
maxBatchCommits = config.getInt("receive", null, "maxBatchCommits", 10000);
systemMaxBatchChanges = config.getInt("receive", "maxBatchChanges", 0);
this.limitsFactory = limitsFactory;

View File

@ -279,10 +279,6 @@ public class ReplaceOp implements BatchUpdateOp {
}
}
boolean draft = magicBranch != null && magicBranch.draft;
if (change.getStatus() == Change.Status.DRAFT && !draft) {
update.setStatus(Change.Status.NEW);
}
newPatchSet =
psUtil.insert(
ctx.getDb(),
@ -290,14 +286,12 @@ public class ReplaceOp implements BatchUpdateOp {
update,
patchSetId,
commitId,
draft,
groups,
pushCertificate != null ? pushCertificate.toTextWithSignature() : null,
psDescription);
update.setPsDescription(psDescription);
recipients.add(
getRecipientsFromFooters(ctx.getDb(), accountResolver, draft, commit.getFooterLines()));
recipients.add(getRecipientsFromFooters(ctx.getDb(), accountResolver, commit.getFooterLines()));
recipients.remove(ctx.getAccountId());
ChangeData cd = changeDataFactory.create(ctx.getDb(), ctx.getNotes());
MailRecipients oldRecipients = getRecipientsFromReviewers(cd.reviewers());
@ -430,11 +424,7 @@ public class ReplaceOp implements BatchUpdateOp {
if (magicBranch != null && magicBranch.topic != null) {
change.setTopic(magicBranch.topic);
}
if (change.getStatus() == Change.Status.DRAFT && newPatchSet.isDraft()) {
// Leave in draft status.
} else {
change.setStatus(Change.Status.NEW);
}
change.setCurrentPatchSet(info);
List<String> idList = commit.getFooterLines(CHANGE_ID);

View File

@ -157,7 +157,6 @@ public class CherryPick extends SubmitStrategy {
ctx.getUpdate(psId),
psId,
newCommit,
false,
prevPs != null ? prevPs.getGroups() : ImmutableList.<String>of(),
null,
null);

View File

@ -222,7 +222,6 @@ public class RebaseSubmitStrategy extends SubmitStrategy {
ctx.getUpdate(newPatchSetId),
newPatchSetId,
newCommit,
false,
prevPs != null ? prevPs.getGroups() : ImmutableList.<String>of(),
null,
null);

View File

@ -324,7 +324,6 @@ abstract class SubmitStrategyOp implements BatchUpdateOp {
ctx.getUpdate(psId),
psId,
alreadyMergedCommit,
false,
groups,
null,
null);

View File

@ -566,7 +566,7 @@ public class ChangeField {
// Submit rule options in this class should never use fastEvalLabels. This
// slows down indexing slightly but produces correct search results.
public static final SubmitRuleOptions SUBMIT_RULE_OPTIONS_LENIENT =
SubmitRuleOptions.defaults().allowClosed(true).allowDraft(true).build();
SubmitRuleOptions.defaults().allowClosed(true).build();
public static final SubmitRuleOptions SUBMIT_RULE_OPTIONS_STRICT =
SubmitRuleOptions.defaults().build();

View File

@ -90,6 +90,9 @@ public class ChangeSchemaDefinitions extends SchemaDefinitions<ChangeData> {
static final Schema<ChangeData> V46 = schema(V45);
// Removal of draft change workflow requires reindexing
static final Schema<ChangeData> V47 = schema(V46);
public static final String NAME = "changes";
public static final ChangeSchemaDefinitions INSTANCE = new ChangeSchemaDefinitions();

Some files were not shown because too many files have changed in this diff Show More