@@ -582,6 +582,13 @@ tickets.acceptNewPatchsets = true | |||
# SINCE 1.4.0 | |||
tickets.requireApproval = false | |||
# Set to a value >= 0 to require at least that cumulated score on a patchset to merge | |||
# via the WebUI. Requires requireApproval set to true on a repository. | |||
# Set to negative values to disable. | |||
# | |||
# SINCE 1.9.0 | |||
tickets.requireScore = -1 | |||
# Default setting to control how patchsets are merged to the integration branch. | |||
# Valid values: | |||
# MERGE_ALWAYS - Always merge with a merge commit. Every ticket will show up as a branch, | |||
@@ -631,6 +638,23 @@ tickets.redis.url = | |||
# SINCE 1.4.0 | |||
tickets.perPage = 25 | |||
# Appends the reviewers with a positive on a ticket's | |||
# patchset in the form of 'value: author <author@example.com>' | |||
# (where value is this # parameter's value) lines to the | |||
# merge commit message when the WebUI merge button is clicked. | |||
# | |||
# Set to the desired value to enable, e.g.: | |||
# | |||
# tickets.writeSignoffCommit = Signed-off-by | |||
# | |||
# Defaults to being disabled, can be overridden on repositories. | |||
# | |||
# See the conventions on the meaning of possible values for this | |||
# parameter: https://git.wiki.kernel.org/index.php/CommitMessageConventions | |||
# | |||
# SINCE 1.9.0 | |||
tickets.writeSignoffCommit = | |||
# The folder where plugins are loaded from. | |||
# | |||
# SINCE 1.5.0 |
@@ -96,6 +96,10 @@ public class EditRepositoryDialog extends JDialog { | |||
private JCheckBox requireApproval; | |||
private JComboBox requireScore; | |||
private JComboBox writeSignoffCommit; | |||
private JComboBox mergeToField; | |||
private JCheckBox useIncrementalPushTags; | |||
@@ -221,6 +225,19 @@ public class EditRepositoryDialog extends JDialog { | |||
anRepository.acceptNewPatchsets); | |||
requireApproval = new JCheckBox(Translation.get("gb.requireApprovalDescription"), | |||
anRepository.requireApproval); | |||
Integer [] scores = { -1, 0, 2, 4, 5, 6, 8 }; | |||
requireScore = new JComboBox(scores); | |||
requireScore.setSelectedItem(anRepository.requireScore); | |||
requireScore.setEnabled(anRepository.requireApproval); | |||
String [] signoffCommitMsgs = { | |||
null, | |||
"Signed-off-by", | |||
"Reviewed-by", | |||
"Acked-by" | |||
}; | |||
writeSignoffCommit = new JComboBox(signoffCommitMsgs); | |||
writeSignoffCommit.setSelectedItem(anRepository.writeSignoffCommit); | |||
if (ArrayUtils.isEmpty(anRepository.availableRefs)) { | |||
mergeToField = new JComboBox(); | |||
@@ -330,6 +347,10 @@ public class EditRepositoryDialog extends JDialog { | |||
acceptNewPatchsets)); | |||
fieldsPanel.add(newFieldPanel(Translation.get("gb.requireApproval"), | |||
requireApproval)); | |||
fieldsPanel.add(newFieldPanel(Translation.get("gb.requireScore"), | |||
requireScore)); | |||
fieldsPanel.add(newFieldPanel(Translation.get("gb.writeSignoffCommit"), | |||
writeSignoffCommit)); | |||
fieldsPanel.add(newFieldPanel(Translation.get("gb.mergeTo"), mergeToField)); | |||
fieldsPanel | |||
.add(newFieldPanel(Translation.get("gb.enableIncrementalPushTags"), useIncrementalPushTags)); | |||
@@ -588,6 +609,9 @@ public class EditRepositoryDialog extends JDialog { | |||
repository.acceptNewPatchsets = acceptNewPatchsets.isSelected(); | |||
repository.acceptNewTickets = acceptNewTickets.isSelected(); | |||
repository.requireApproval = requireApproval.isSelected(); | |||
repository.requireScore = (Integer) requireScore.getSelectedItem(); | |||
repository.writeSignoffCommit = writeSignoffCommit.getSelectedItem() == null ? null | |||
: writeSignoffCommit.getSelectedItem().toString(); | |||
repository.mergeTo = mergeToField.getSelectedItem() == null ? null | |||
: Repository.shortenRefName(mergeToField.getSelectedItem().toString()); | |||
repository.useIncrementalPushTags = useIncrementalPushTags.isSelected(); |
@@ -1269,7 +1269,33 @@ public class PatchsetReceivePack extends GitblitReceivePack { | |||
public MergeStatus merge(TicketModel ticket) { | |||
PersonIdent committer = new PersonIdent(user.getDisplayName(), StringUtils.isEmpty(user.emailAddress) ? (user.username + "@gitblit") : user.emailAddress); | |||
Patchset patchset = ticket.getCurrentPatchset(); | |||
String message = MessageFormat.format("Merged #{0,number,0} \"{1}\"", ticket.number, ticket.title); | |||
StringBuilder messageBuilder = new StringBuilder(); | |||
messageBuilder.append(MessageFormat.format("Merged #{0,number,0} \"{1}\"", ticket.number, ticket.title)); | |||
// Add sign-off tags to commit message footer if: | |||
// - there are reviewers on this patchset | |||
// - their review is positive | |||
// - the setting is enabled on repo this patchset is for | |||
if (repository.writeSignoffCommit != null && repository.writeSignoffCommit.length() > 0) { | |||
messageBuilder.append("\n\n"); | |||
for (Change change : ticket.getReviews(patchset)) { | |||
// Skip sign-off for negative reviews | |||
if (change.review.score.getValue() < 0) continue; | |||
UserModel ruser = gitblit.getUserModel(change.author); | |||
messageBuilder.append(MessageFormat.format( | |||
"{0}: {1} <{2}>\n", | |||
repository.writeSignoffCommit, | |||
ruser.getDisplayName(), | |||
StringUtils.isEmpty(ruser.emailAddress) ? (ruser.username + "@gitblit") : ruser.emailAddress | |||
) | |||
); | |||
} | |||
// Delete extra the line break at the end of the message | |||
messageBuilder.deleteCharAt(messageBuilder.length()-1); | |||
} | |||
// Convert the constructed message to String and continue | |||
String message = messageBuilder.toString(); | |||
Ref oldRef = null; | |||
try { | |||
oldRef = getRepository().getRef(ticket.mergeTo); |
@@ -897,6 +897,8 @@ public class RepositoryManager implements IRepositoryManager { | |||
model.acceptNewPatchsets = getConfig(config, "acceptNewPatchsets", true); | |||
model.acceptNewTickets = getConfig(config, "acceptNewTickets", true); | |||
model.requireApproval = getConfig(config, "requireApproval", settings.getBoolean(Keys.tickets.requireApproval, false)); | |||
model.requireScore = getConfig(config, "requireScore", settings.getInteger(Keys.tickets.requireScore, -1)); | |||
model.writeSignoffCommit = getConfig(config, "writeSignoffCommit", settings.getString(Keys.tickets.writeSignoffCommit, null)); | |||
model.mergeTo = getConfig(config, "mergeTo", null); | |||
model.mergeType = MergeType.fromName(getConfig(config, "mergeType", settings.getString(Keys.tickets.mergeType, null))); | |||
model.useIncrementalPushTags = getConfig(config, "useIncrementalPushTags", false); | |||
@@ -1554,6 +1556,14 @@ public class RepositoryManager implements IRepositoryManager { | |||
// override default | |||
config.setBoolean(Constants.CONFIG_GITBLIT, null, "requireApproval", repository.requireApproval); | |||
} | |||
config.setInt(Constants.CONFIG_GITBLIT, null, "requireScore", repository.requireScore); | |||
if (settings.getString(Keys.tickets.writeSignoffCommit, null) == repository.writeSignoffCommit) { | |||
// use default | |||
config.unset(Constants.CONFIG_GITBLIT, null, "writeSignoffCommit"); | |||
} else { | |||
// override default | |||
config.setString(Constants.CONFIG_GITBLIT, null, "writeSignoffCommit", repository.writeSignoffCommit); | |||
} | |||
if (!StringUtils.isEmpty(repository.mergeTo)) { | |||
config.setString(Constants.CONFIG_GITBLIT, null, "mergeTo", repository.mergeTo); | |||
} |
@@ -88,7 +88,9 @@ public class RepositoryModel implements Serializable, Comparable<RepositoryModel | |||
public CommitMessageRenderer commitMessageRenderer; | |||
public boolean acceptNewPatchsets; | |||
public boolean acceptNewTickets; | |||
public boolean requireApproval; | |||
public boolean requireApproval; | |||
public int requireScore; | |||
public String writeSignoffCommit; | |||
public String mergeTo; | |||
public MergeType mergeType; | |||
@@ -491,7 +491,6 @@ public class TicketModel implements Serializable, Comparable<TicketModel> { | |||
return new ArrayList<Change>(reviews.values()); | |||
} | |||
public boolean isApproved(Patchset patchset) { | |||
if (patchset == null) { | |||
return false; | |||
@@ -512,6 +511,15 @@ public class TicketModel implements Serializable, Comparable<TicketModel> { | |||
return approved && !vetoed; | |||
} | |||
public int getScore(Patchset patchset) { | |||
int score = 0; | |||
for (Change change : getReviews(patchset)) { | |||
score += change.review.score.getValue(); | |||
} | |||
return score; | |||
} | |||
public boolean isVetoed(Patchset patchset) { | |||
if (patchset == null) { | |||
return false; |
@@ -555,6 +555,11 @@ gb.acceptNewTickets = allow new tickets | |||
gb.acceptNewTicketsDescription = allow creation of bug, enhancement, task ,etc tickets | |||
gb.requireApproval = require approvals | |||
gb.requireApprovalDescription = patchsets must be approved before merge button is enabled | |||
gb.requireScore = require score | |||
gb.requireScoreDescription = patchsets must be rated this high before merge button is enabled | |||
gb.writeSignoffCommit = write signoff commit | |||
gb.writeSignoffCommitDescription = reviewed patchsets will have lines of the form <em>value: reviewer name <reviewer email></em> added to their merge commit.\nE.g. if set to <em>Signed-off-by</em>: <code>Signed-off-by: reviewer <reviewer@example.com></code>. Please see ${commitMessageLink} on the meaning of possible values for this. | |||
gb.writeSignoffCommitLink = Commit Message Conventions in the Linux Kernel | |||
gb.topic = topic | |||
gb.proposalTickets = proposed changes | |||
gb.bugTickets = bugs |
@@ -122,6 +122,15 @@ | |||
<div wicket:id="acceptNewPatchsets"></div> | |||
<div wicket:id="acceptNewTickets"></div> | |||
<div wicket:id="requireApproval"></div> | |||
<div wicket:id="requireScore"></div> | |||
<div wicket:id="writeSignoffCommit"></div> | |||
<div> | |||
<wicket:message key="gb.writeSignoffCommitDescription"> | |||
<a target="_new" wicket:id="commitMessageLink"> | |||
<wicket:message key="gb.writeSignoffCommitLink"></wicket:message> | |||
</a> | |||
</wicket:message> | |||
</div> | |||
<div wicket:id="mergeTo"></div> | |||
<div wicket:id="mergeType"></div> | |||
@@ -200,4 +209,4 @@ | |||
</body> | |||
</wicket:extend> | |||
</html> | |||
</html> |
@@ -37,6 +37,7 @@ import org.apache.wicket.markup.html.form.Button; | |||
import org.apache.wicket.markup.html.form.CheckBox; | |||
import org.apache.wicket.markup.html.form.ChoiceRenderer; | |||
import org.apache.wicket.markup.html.form.DropDownChoice; | |||
import org.apache.wicket.markup.html.link.ExternalLink; | |||
import org.apache.wicket.markup.html.form.Form; | |||
import org.apache.wicket.markup.html.form.IChoiceRenderer; | |||
import org.apache.wicket.markup.html.form.TextField; | |||
@@ -94,6 +95,8 @@ public class EditRepositoryPage extends RootSubPage { | |||
private IModel<String> mailingLists; | |||
private final static String COMMIT_MESSAGE_LINK = "https://git.wiki.kernel.org/index.php/CommitMessageConventions"; | |||
public EditRepositoryPage() { | |||
// create constructor | |||
super(); | |||
@@ -411,6 +414,8 @@ public class EditRepositoryPage extends RootSubPage { | |||
// do not let the browser pre-populate these fields | |||
form.add(new SimpleAttributeModifier("autocomplete", "off")); | |||
// adds documentation link for localization | |||
form.add(new ExternalLink("commitMessageLink", COMMIT_MESSAGE_LINK)); | |||
// | |||
// | |||
@@ -453,7 +458,25 @@ public class EditRepositoryPage extends RootSubPage { | |||
getString("gb.requireApproval"), | |||
getString("gb.requireApprovalDescription"), | |||
new PropertyModel<Boolean>(repositoryModel, "requireApproval"))); | |||
List<Integer> scores = Arrays.asList(-1, 0, 2, 4, 5, 6, 8); | |||
final boolean allowRequireScore = repositoryModel.requireApproval; | |||
form.add(new ChoiceOption<Integer>("requireScore", | |||
getString("gb.requireScore"), | |||
getString("gb.requireScoreDescription"), | |||
new DropDownChoice<Integer>("choice", | |||
new PropertyModel<Integer>(repositoryModel, "requireScore"), | |||
scores)).setEnabled(allowRequireScore)); | |||
List<String> signoffCommitMsgs = Arrays.asList( | |||
null, | |||
"Signed-off-by", | |||
"Reviewed-by", | |||
"Acked-by" | |||
); | |||
form.add(new ChoiceOption<String>("writeSignoffCommit", | |||
getString("gb.writeSignoffCommit"), | |||
new String(), | |||
new PropertyModel<String>(repositoryModel, "writeSignoffCommit"), | |||
signoffCommitMsgs)); | |||
form.add(new ChoiceOption<String>("mergeTo", | |||
getString("gb.mergeTo"), | |||
getString("gb.mergeToDescription"), |
@@ -1411,6 +1411,9 @@ public class TicketPage extends RepositoryPage { | |||
if (repository.requireApproval) { | |||
// repository requires approval | |||
allowMerge = ticket.isOpen() && ticket.isApproved(patchset); | |||
if (repository.requireScore > -1) { | |||
allowMerge &= ticket.getScore(patchset) >= repository.requireScore; | |||
} | |||
} else { | |||
// vetoes are binding | |||
allowMerge = ticket.isOpen() && !ticket.isVetoed(patchset); |