diff --git a/src/main/distrib/data/defaults.properties b/src/main/distrib/data/defaults.properties index 51fa1253a..854af22fd 100644 --- a/src/main/distrib/data/defaults.properties +++ b/src/main/distrib/data/defaults.properties @@ -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 ' +# (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 diff --git a/src/main/java/com/gitblit/client/EditRepositoryDialog.java b/src/main/java/com/gitblit/client/EditRepositoryDialog.java index ef665d10e..107d4ec9c 100644 --- a/src/main/java/com/gitblit/client/EditRepositoryDialog.java +++ b/src/main/java/com/gitblit/client/EditRepositoryDialog.java @@ -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 @@ private void initialize(int protocolVersion, RepositoryModel anRepository) { 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 void itemStateChanged(ItemEvent e) { 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 @@ private boolean validateFields() { 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(); diff --git a/src/main/java/com/gitblit/git/PatchsetReceivePack.java b/src/main/java/com/gitblit/git/PatchsetReceivePack.java index 4a09139aa..59d0651ac 100644 --- a/src/main/java/com/gitblit/git/PatchsetReceivePack.java +++ b/src/main/java/com/gitblit/git/PatchsetReceivePack.java @@ -1268,7 +1268,33 @@ private void updateReflog(RefUpdate ru) { 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); diff --git a/src/main/java/com/gitblit/manager/RepositoryManager.java b/src/main/java/com/gitblit/manager/RepositoryManager.java index 2be658733..f2616cf08 100644 --- a/src/main/java/com/gitblit/manager/RepositoryManager.java +++ b/src/main/java/com/gitblit/manager/RepositoryManager.java @@ -899,6 +899,8 @@ private RepositoryModel loadRepositoryModel(String repositoryName) { 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); @@ -1556,6 +1558,14 @@ public void updateConfiguration(Repository r, RepositoryModel repository) { // 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); } diff --git a/src/main/java/com/gitblit/models/RepositoryModel.java b/src/main/java/com/gitblit/models/RepositoryModel.java index 67ee1c7e0..a202a4313 100644 --- a/src/main/java/com/gitblit/models/RepositoryModel.java +++ b/src/main/java/com/gitblit/models/RepositoryModel.java @@ -88,7 +88,9 @@ public class RepositoryModel implements Serializable, Comparable getReviews(Patchset patchset) { return new ArrayList(reviews.values()); } - public boolean isApproved(Patchset patchset) { if (patchset == null) { return false; @@ -512,6 +511,15 @@ public boolean isApproved(Patchset patchset) { 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; diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties index c46bd63dd..43b29861b 100644 --- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties +++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties @@ -556,6 +556,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 value: reviewer name <reviewer email> added to their merge commit.\nE.g. if set to Signed-off-by: Signed-off-by: reviewer <reviewer@example.com>. 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 diff --git a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html index 2c881efcc..88d5ec563 100644 --- a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html +++ b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html @@ -122,6 +122,15 @@

+
+
+
+ + + + + +
@@ -200,4 +209,4 @@

- \ No newline at end of file + diff --git a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java index bf3eea8ba..e22b8c256 100644 --- a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java +++ b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java @@ -37,6 +37,7 @@ 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 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 @@ protected void onSubmit() { // 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 @@ protected void onSubmit() { getString("gb.requireApproval"), getString("gb.requireApprovalDescription"), new PropertyModel(repositoryModel, "requireApproval"))); - + List scores = Arrays.asList(-1, 0, 2, 4, 5, 6, 8); + final boolean allowRequireScore = repositoryModel.requireApproval; + form.add(new ChoiceOption("requireScore", + getString("gb.requireScore"), + getString("gb.requireScoreDescription"), + new DropDownChoice("choice", + new PropertyModel(repositoryModel, "requireScore"), + scores)).setEnabled(allowRequireScore)); + List signoffCommitMsgs = Arrays.asList( + null, + "Signed-off-by", + "Reviewed-by", + "Acked-by" + ); + form.add(new ChoiceOption("writeSignoffCommit", + getString("gb.writeSignoffCommit"), + new String(), + new PropertyModel(repositoryModel, "writeSignoffCommit"), + signoffCommitMsgs)); form.add(new ChoiceOption("mergeTo", getString("gb.mergeTo"), getString("gb.mergeToDescription"), diff --git a/src/main/java/com/gitblit/wicket/pages/TicketPage.java b/src/main/java/com/gitblit/wicket/pages/TicketPage.java index e2133966a..bec345863 100644 --- a/src/main/java/com/gitblit/wicket/pages/TicketPage.java +++ b/src/main/java/com/gitblit/wicket/pages/TicketPage.java @@ -1407,6 +1407,9 @@ protected Component createMergePanel(UserModel user, RepositoryModel repository) 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);