# SINCE 1.4.0 | # SINCE 1.4.0 | ||||
tickets.requireApproval = false | 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. | # Default setting to control how patchsets are merged to the integration branch. | ||||
# Valid values: | # Valid values: | ||||
# MERGE_ALWAYS - Always merge with a merge commit. Every ticket will show up as a branch, | # MERGE_ALWAYS - Always merge with a merge commit. Every ticket will show up as a branch, | ||||
# SINCE 1.4.0 | # SINCE 1.4.0 | ||||
tickets.perPage = 25 | 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. | # The folder where plugins are loaded from. | ||||
# | # | ||||
# SINCE 1.5.0 | # SINCE 1.5.0 |
private JCheckBox requireApproval; | private JCheckBox requireApproval; | ||||
private JComboBox requireScore; | |||||
private JComboBox writeSignoffCommit; | |||||
private JComboBox mergeToField; | private JComboBox mergeToField; | ||||
private JCheckBox useIncrementalPushTags; | private JCheckBox useIncrementalPushTags; | ||||
anRepository.acceptNewPatchsets); | anRepository.acceptNewPatchsets); | ||||
requireApproval = new JCheckBox(Translation.get("gb.requireApprovalDescription"), | requireApproval = new JCheckBox(Translation.get("gb.requireApprovalDescription"), | ||||
anRepository.requireApproval); | 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)) { | if (ArrayUtils.isEmpty(anRepository.availableRefs)) { | ||||
mergeToField = new JComboBox(); | mergeToField = new JComboBox(); | ||||
acceptNewPatchsets)); | acceptNewPatchsets)); | ||||
fieldsPanel.add(newFieldPanel(Translation.get("gb.requireApproval"), | fieldsPanel.add(newFieldPanel(Translation.get("gb.requireApproval"), | ||||
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.mergeTo"), mergeToField)); | ||||
fieldsPanel | fieldsPanel | ||||
.add(newFieldPanel(Translation.get("gb.enableIncrementalPushTags"), useIncrementalPushTags)); | .add(newFieldPanel(Translation.get("gb.enableIncrementalPushTags"), useIncrementalPushTags)); | ||||
repository.acceptNewPatchsets = acceptNewPatchsets.isSelected(); | repository.acceptNewPatchsets = acceptNewPatchsets.isSelected(); | ||||
repository.acceptNewTickets = acceptNewTickets.isSelected(); | repository.acceptNewTickets = acceptNewTickets.isSelected(); | ||||
repository.requireApproval = requireApproval.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.mergeTo = mergeToField.getSelectedItem() == null ? null | ||||
: Repository.shortenRefName(mergeToField.getSelectedItem().toString()); | : Repository.shortenRefName(mergeToField.getSelectedItem().toString()); | ||||
repository.useIncrementalPushTags = useIncrementalPushTags.isSelected(); | repository.useIncrementalPushTags = useIncrementalPushTags.isSelected(); |
public MergeStatus merge(TicketModel ticket) { | public MergeStatus merge(TicketModel ticket) { | ||||
PersonIdent committer = new PersonIdent(user.getDisplayName(), StringUtils.isEmpty(user.emailAddress) ? (user.username + "@gitblit") : user.emailAddress); | PersonIdent committer = new PersonIdent(user.getDisplayName(), StringUtils.isEmpty(user.emailAddress) ? (user.username + "@gitblit") : user.emailAddress); | ||||
Patchset patchset = ticket.getCurrentPatchset(); | 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; | Ref oldRef = null; | ||||
try { | try { | ||||
oldRef = getRepository().getRef(ticket.mergeTo); | oldRef = getRepository().getRef(ticket.mergeTo); |
model.acceptNewPatchsets = getConfig(config, "acceptNewPatchsets", true); | model.acceptNewPatchsets = getConfig(config, "acceptNewPatchsets", true); | ||||
model.acceptNewTickets = getConfig(config, "acceptNewTickets", true); | model.acceptNewTickets = getConfig(config, "acceptNewTickets", true); | ||||
model.requireApproval = getConfig(config, "requireApproval", settings.getBoolean(Keys.tickets.requireApproval, false)); | 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.mergeTo = getConfig(config, "mergeTo", null); | ||||
model.mergeType = MergeType.fromName(getConfig(config, "mergeType", settings.getString(Keys.tickets.mergeType, null))); | model.mergeType = MergeType.fromName(getConfig(config, "mergeType", settings.getString(Keys.tickets.mergeType, null))); | ||||
model.useIncrementalPushTags = getConfig(config, "useIncrementalPushTags", false); | model.useIncrementalPushTags = getConfig(config, "useIncrementalPushTags", false); | ||||
// override default | // override default | ||||
config.setBoolean(Constants.CONFIG_GITBLIT, null, "requireApproval", repository.requireApproval); | 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)) { | if (!StringUtils.isEmpty(repository.mergeTo)) { | ||||
config.setString(Constants.CONFIG_GITBLIT, null, "mergeTo", repository.mergeTo); | config.setString(Constants.CONFIG_GITBLIT, null, "mergeTo", repository.mergeTo); | ||||
} | } |
public CommitMessageRenderer commitMessageRenderer; | public CommitMessageRenderer commitMessageRenderer; | ||||
public boolean acceptNewPatchsets; | public boolean acceptNewPatchsets; | ||||
public boolean acceptNewTickets; | public boolean acceptNewTickets; | ||||
public boolean requireApproval; | |||||
public boolean requireApproval; | |||||
public int requireScore; | |||||
public String writeSignoffCommit; | |||||
public String mergeTo; | public String mergeTo; | ||||
public MergeType mergeType; | public MergeType mergeType; | ||||
return new ArrayList<Change>(reviews.values()); | return new ArrayList<Change>(reviews.values()); | ||||
} | } | ||||
public boolean isApproved(Patchset patchset) { | public boolean isApproved(Patchset patchset) { | ||||
if (patchset == null) { | if (patchset == null) { | ||||
return false; | return false; | ||||
return approved && !vetoed; | 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) { | public boolean isVetoed(Patchset patchset) { | ||||
if (patchset == null) { | if (patchset == null) { | ||||
return false; | return false; |
gb.acceptNewTicketsDescription = allow creation of bug, enhancement, task ,etc tickets | gb.acceptNewTicketsDescription = allow creation of bug, enhancement, task ,etc tickets | ||||
gb.requireApproval = require approvals | gb.requireApproval = require approvals | ||||
gb.requireApprovalDescription = patchsets must be approved before merge button is enabled | 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.topic = topic | ||||
gb.proposalTickets = proposed changes | gb.proposalTickets = proposed changes | ||||
gb.bugTickets = bugs | gb.bugTickets = bugs |
<div wicket:id="acceptNewPatchsets"></div> | <div wicket:id="acceptNewPatchsets"></div> | ||||
<div wicket:id="acceptNewTickets"></div> | <div wicket:id="acceptNewTickets"></div> | ||||
<div wicket:id="requireApproval"></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="mergeTo"></div> | ||||
<div wicket:id="mergeType"></div> | <div wicket:id="mergeType"></div> | ||||
</body> | </body> | ||||
</wicket:extend> | </wicket:extend> | ||||
</html> | |||||
</html> |
import org.apache.wicket.markup.html.form.CheckBox; | import org.apache.wicket.markup.html.form.CheckBox; | ||||
import org.apache.wicket.markup.html.form.ChoiceRenderer; | import org.apache.wicket.markup.html.form.ChoiceRenderer; | ||||
import org.apache.wicket.markup.html.form.DropDownChoice; | 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.Form; | ||||
import org.apache.wicket.markup.html.form.IChoiceRenderer; | import org.apache.wicket.markup.html.form.IChoiceRenderer; | ||||
import org.apache.wicket.markup.html.form.TextField; | import org.apache.wicket.markup.html.form.TextField; | ||||
private IModel<String> mailingLists; | private IModel<String> mailingLists; | ||||
private final static String COMMIT_MESSAGE_LINK = "https://git.wiki.kernel.org/index.php/CommitMessageConventions"; | |||||
public EditRepositoryPage() { | public EditRepositoryPage() { | ||||
// create constructor | // create constructor | ||||
super(); | super(); | ||||
// do not let the browser pre-populate these fields | // do not let the browser pre-populate these fields | ||||
form.add(new SimpleAttributeModifier("autocomplete", "off")); | form.add(new SimpleAttributeModifier("autocomplete", "off")); | ||||
// adds documentation link for localization | |||||
form.add(new ExternalLink("commitMessageLink", COMMIT_MESSAGE_LINK)); | |||||
// | // | ||||
// | // | ||||
getString("gb.requireApproval"), | getString("gb.requireApproval"), | ||||
getString("gb.requireApprovalDescription"), | getString("gb.requireApprovalDescription"), | ||||
new PropertyModel<Boolean>(repositoryModel, "requireApproval"))); | 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", | form.add(new ChoiceOption<String>("mergeTo", | ||||
getString("gb.mergeTo"), | getString("gb.mergeTo"), | ||||
getString("gb.mergeToDescription"), | getString("gb.mergeToDescription"), |
if (repository.requireApproval) { | if (repository.requireApproval) { | ||||
// repository requires approval | // repository requires approval | ||||
allowMerge = ticket.isOpen() && ticket.isApproved(patchset); | allowMerge = ticket.isOpen() && ticket.isApproved(patchset); | ||||
if (repository.requireScore > -1) { | |||||
allowMerge &= ticket.getScore(patchset) >= repository.requireScore; | |||||
} | |||||
} else { | } else { | ||||
// vetoes are binding | // vetoes are binding | ||||
allowMerge = ticket.isOpen() && !ticket.isVetoed(patchset); | allowMerge = ticket.isOpen() && !ticket.isVetoed(patchset); |