summaryrefslogtreecommitdiff
path: root/perl-lib/PgCommitFest/Patch.pm
blob: dd3ef699fd2d1ea5dfd00360f2d4d63d3814fc1c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
package PgCommitFest::Patch;
use strict;
use warnings;

sub bump {
	my ($r) = @_;
	$r->authenticate('require_login' => 1);
	$r->set_title('Move Patch To New CommitFest');

	# Fetch patch.
	my $d = $r->db->select_one(<<EOM, $r->cgi_required_id);
SELECT id, name, commitfest_id FROM patch_view WHERE id = ?
EOM
	$r->error_exit('Patch not found.') if !defined $d;
	$r->set_title("Move Patch %s To Another CommitFest", $d->{'name'});
	$r->add_link('/action/patch_view?id=' . $d->{'id'},
		'Return to Patch View');

	# Fetch list of commitfests.
	my $list = $r->db->select(<<EOM, $d->{'commitfest_id'});
SELECT id, name, commitfest_status FROM commitfest_view WHERE id != ?
	ORDER BY name DESC
EOM

	# Display template.
	$r->render_template('patch_bump', { 'd' => $d, 'list' => $list });
}

sub delete {
	my ($r) = @_;
	my $aa = $r->authenticate('require_login' => 1);
	$r->set_title('Delete Patch');
	my $d;
	eval {
		# Don't bump last_updated_time, as that would trigger an activity log
		# record.  But do change the last_updater, so that the subsequent
		# delete picks up the correct user id.  This is a pretty ugly kludge,
		# but I don't immediately have a better idea.
		$r->db->update('patch', { 'id' => $r->cgi_required_id },
			{ 'last_updater' => $aa->{'userid'} });
		$d = $r->db->select_one(<<EOM, $r->cgi_required_id);
DELETE FROM patch AS p
	USING commitfest_topic t
WHERE p.commitfest_topic_id = t.id AND p.id = ? RETURNING t.commitfest_id
EOM
	};
	my $err = $@;
	if (! $err) {
		$r->error_exit('Patch not found.') if !defined $d;
		$r->db->commit;
		$r->redirect('/action/commitfest_view?id=' . $d->{'commitfest_id'});
	}
	if ($err =~ /patch_comment_patch_id_fkey/) {
		$r->error(<<EOM);
Because this patch has one or more comments, it may not be deleted.  Unless you
are an administrator, you may only edit or delete your own comments.
EOM
	}
	else {
		$r->error("Internal error: $@");
	}
	$r->render_template('patch_delete', { 'id' => $r->cgi_required_id });
}

sub form {
	my ($r) = @_;
	my $aa = $r->authenticate('require_login' => 1);

	# Decide whether this is a new patch or an edit of an existing
	# patch, and if editing reload data from database.
	my ($d, $cf);
	my $id = $r->cgi_id();
	if (defined $id) {
		$r->set_title('Edit Patch');
		$d = $r->db->select_one(<<EOM, $id);
SELECT id, commitfest_topic_id AS commitfest_topic, commitfest_id, name,
	patch_status_id AS patch_status, author, reviewers, committer, date_closed
FROM patch_view WHERE id = ?
EOM
		$r->error_exit('Patch not found.') if !defined $d;
		$r->redirect('/action/patch_view?id=' . $id) if $r->cgi('cancel');
		my $cfid = $r->cgi_id('commitfest');
		if (defined $cfid && $cfid != $d->{'commitfest_id'}) {
			$cf = $r->db->select_one(<<EOM, $cfid);
SELECT id, name FROM commitfest WHERE id = ?
EOM
			$r->error_exit('New CommitFest not found.') if !defined $cf;
			$d->{'commitfest_id'} = $cf->{'id'};
		}
	}
	else {
		$d = $r->db->select_one(<<EOM, $r->cgi_required_id('commitfest'));
SELECT id AS commitfest_id FROM commitfest WHERE id = ?
EOM
		$r->error_exit('CommitFest topic not found.') if !defined $d;
		$r->set_title('New Patch');
		$r->redirect('/action/commitfest_view?id=' . $d->{'commitfest_id'})
			if $r->cgi('cancel');
	}

	# Add controls.
	$r->add_control('name', 'text', 'Name', 'required' => 1);
	$r->add_control('commitfest_topic', 'select', 'CommitFest Topic',
		'required' => 1);
	my $commitfest_topic = $r->db->select(<<EOM, $d->{'commitfest_id'});
SELECT id, name FROM commitfest_topic WHERE commitfest_id = ? ORDER BY name
EOM
	my $commitfest_topic_warning = ! @$commitfest_topic;
	unshift @$commitfest_topic, { 'id' => '', 'name' => '(None Selected)' };
	$r->control('commitfest_topic')->choice($commitfest_topic);
	$r->add_control('patch_status', 'select', 'Patch Status', 'required' => 1);
	$r->control('patch_status')->choice($r->db->select(<<EOM));
SELECT id, name FROM patch_status ORDER BY id
EOM
	$r->add_control('author', 'text', 'Author', 'required' => 1);
	$r->add_control('reviewers', 'text', 'Reviewers');
	$r->add_control('committer', 'text', 'Committer');
	$r->add_control('date_closed', 'date', 'Date Closed');
	if (!defined $id) {
		$r->add_control('message_id', 'text',
			'Message-ID for Original Patch', 'required' => 1,
			'maxlength' => 255);
	}
	my %value = $r->initialize_controls($d);

	# Cross-field validation.
	if ($r->cgi('go')) {
		if (!defined $value{'date_closed'}
			&& grep { $_ eq $value{'patch_status_id'} } qw(4 5 6)) {
			$value{'date_closed'} = \'now()::date';
		}
		elsif (defined $value{'date_closed'}
			&& !grep { $_ eq $value{'patch_status_id'} } qw(4 5 6)) {
			$r->error(<<EOM);
Date Closed is permitted only for patches which have been Committed, Returned
with Feedback, or Rejected.
EOM
		}
	}

	# Handle commit.
	if ($r->cgi('go') && ! $r->is_error()) {
		$value{'last_updated_time'} = \'now()';
		$value{'last_updater'} = $aa->{'userid'};
		if (defined $id) {
			$r->db->update('patch', { 'id' => $id }, \%value);
		}
		else {
			my $message_id = $value{'message_id'};
			delete $value{'message_id'};
			$id = $r->db->insert_returning_id('patch', \%value);
			$r->db->insert('patch_comment', {
				'patch_id' => $id,
				'patch_comment_type_id' => 2,
				'message_id' => $message_id,
				'content' => 'Initial version.',
				'creator' => $aa->{'userid'},
				'last_updater' => $aa->{'userid'},
			});
		}
		$r->db->commit;
		$r->redirect('/action/patch_view?id=' . $id);
	}

	# Display template.
	$r->render_template('patch_form', { 'id' => $id, 'd' => $d,
		'commitfest_topic_warning' => $commitfest_topic_warning,
		'new_commitfest' => $cf });
}

sub view {
	my ($r) = @_;
	my $aa = $r->authenticate();
	my $id = $r->cgi_id();
	my $d = $r->db->select_one(<<EOM, $id) if defined $id;
SELECT id, name, commitfest_id, commitfest, commitfest_topic_id,
	commitfest_topic, patch_status, author, reviewers, committer, date_closed
FROM patch_view WHERE id = ?
EOM
	$r->error_exit('Patch not found.') if !defined $d;
	$r->set_title('Patch: %s', $d->{'name'});

	my $patch_comment_list = $r->db->select(<<EOM, $d->{'id'});
SELECT v.id, v.patch_comment_type, v.message_id, v.content, v.creator,
to_char(v.creation_time, 'YYYY-MM-DD HH:MI:SS AM') AS creation_time
FROM patch_comment_view v WHERE v.patch_id = ? ORDER BY v.creation_time
EOM

	# Add patch comment controls, so that users can add a patch comment
	# without needing to visit a separate page.
	if (defined $aa) {
		my %value = PgCommitFest::PatchComment::controls($r, {});
		if ($r->cgi('go') && ! $r->is_error()) {
			$value{'patch_id'} = $d->{'id'};
			$value{'creator'} = $aa->{'userid'};
			$value{'last_updater'} = $aa->{'userid'};
			$r->db->insert('patch_comment', \%value);
			$r->db->commit;
			$r->redirect('/action/patch_view?id=' . $d->{'id'});
		}
	}

	$r->add_link('/action/patch_form?id=' . $id, 'Edit Patch');
	$r->add_link('/action/patch_bump?id=' . $id,
		'Move To Another CommitFest');
	$r->add_link('/action/patch_delete?id=' . $id, 'Delete Patch',
		'Are you sure you want to delete this patch?');
	$r->render_template('patch_view', { 'd' => $d, 'patch_comment_list'
		=> $patch_comment_list });
}

1;