Permit projects to require Signed-off-by lines to create changes
In some projects, for example the Linux kernel and any offshoot
of it, the Signed-off-by line in a change has roughly the same
meaning as signing a site-wide contributor agreement within Gerrit.
For the Linux kernel, this line means that the author agrees to
the "Developer's Certificate of Origin 1.1"[1] and is permitted to
offer the content under the relevant license, thereby granting at
least some redistribution rights.
If use_signed_off_by is true in a project Gerrit requires that the
author, committer, and patch uploader have recorded a Signed-off-by
line within the commit message footer. If any of these are missing
the upload is rejected.
[1] http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=blob;f=Documentation/SubmittingPatches;hb=HEAD
Bug: GERRIT-113
Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/Documentation/index.txt b/Documentation/index.txt
index 924f41d..65cd906 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -6,6 +6,7 @@
* link:cmd-index.html[Command Line Tools]
* link:user-upload.html[Uploading Changes]
+* link:user-signedoffby.html[Signed-off-by Lines]
* link:access-control.html[Access Controls]
Features
diff --git a/Documentation/user-signedoffby.txt b/Documentation/user-signedoffby.txt
new file mode 100644
index 0000000..a535d59
--- /dev/null
+++ b/Documentation/user-signedoffby.txt
@@ -0,0 +1,177 @@
+Gerrit2 - Signed-off-by Lines
+=============================
+
+[NOTE]
+This document was literally taken from link:http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=blob;f=Documentation/SubmittingPatches;hb=4e8a2372f9255a1464ef488ed925455f53fbdaa1[linux-2.6 Documentation/SubmittingPatches]
+and is covered by the GPLv2.
+
+[[Signed-off-by]]
+Signed-off-by:
+--------------
+
+To improve tracking of who did what, especially with patches that can
+percolate to their final resting place in the kernel through several
+layers of maintainers, we've introduced a "sign-off" procedure on
+patches that are being emailed around.
+
+The sign-off is a simple line at the end of the explanation for the
+patch, which certifies that you wrote it or otherwise have the right to
+pass it on as a open-source patch. The rules are pretty simple: if you
+can certify the below:
+
+----
+ Developer's Certificate of Origin 1.1
+
+ By making a contribution to this project, I certify that:
+
+ (a) The contribution was created in whole or in part by me and I
+ have the right to submit it under the open source license
+ indicated in the file; or
+
+ (b) The contribution is based upon previous work that, to the best
+ of my knowledge, is covered under an appropriate open source
+ license and I have the right under that license to submit that
+ work with modifications, whether created in whole or in part
+ by me, under the same open source license (unless I am
+ permitted to submit under a different license), as indicated
+ in the file; or
+
+ (c) The contribution was provided directly to me by some other
+ person who certified (a), (b) or (c) and I have not modified
+ it.
+
+ (d) I understand and agree that this project and the contribution
+ are public and that a record of the contribution (including all
+ personal information I submit with it, including my sign-off) is
+ maintained indefinitely and may be redistributed consistent with
+ this project or the open source license(s) involved.
+----
+
+then you just add a line saying
+
+----
+ Signed-off-by: Random J Developer <random@developer.example.org>
+----
+
+using your real name (sorry, no pseudonyms or anonymous contributions.)
+
+Some people also put extra tags at the end. They'll just be ignored for
+now, but you can do this to mark internal company procedures or just
+point out some special detail about the sign-off.
+
+If you are a subsystem or branch maintainer, sometimes you need to slightly
+modify patches you receive in order to merge them, because the code is not
+exactly the same in your tree and the submitters'. If you stick strictly to
+rule (c), you should ask the submitter to rediff, but this is a totally
+counter-productive waste of time and energy. Rule (b) allows you to adjust
+the code, but then it is very impolite to change one submitter's code and
+make him endorse your bugs. To solve this problem, it is recommended that
+you add a line between the last Signed-off-by header and yours, indicating
+the nature of your changes. While there is nothing mandatory about this, it
+seems like prepending the description with your mail and/or name, all
+enclosed in square brackets, is noticeable enough to make it obvious that
+you are responsible for last-minute changes. Example :
+
+----
+ Signed-off-by: Random J Developer <random@developer.example.org>
+ [lucky@maintainer.example.org: struct foo moved from foo.c to foo.h]
+ Signed-off-by: Lucky K Maintainer <lucky@maintainer.example.org>
+----
+
+This practise is particularly helpful if you maintain a stable branch and
+want at the same time to credit the author, track changes, merge the fix,
+and protect the submitter from complaints. Note that under no circumstances
+can you change the author's identity (the From header), as it is the one
+which appears in the changelog.
+
+[[Acked-by]]
+[[Cc]]
+Acked-by:, Cc:
+--------------
+
+The Signed-off-by: tag indicates that the signer was involved in the
+development of the patch, or that he/she was in the patch's delivery path.
+
+If a person was not directly involved in the preparation or handling of a
+patch but wishes to signify and record their approval of it then they can
+arrange to have an Acked-by: line added to the patch's changelog.
+
+Acked-by: is often used by the maintainer of the affected code when that
+maintainer neither contributed to nor forwarded the patch.
+
+Acked-by: is not as formal as Signed-off-by:. It is a record that the acker
+has at least reviewed the patch and has indicated acceptance. Hence patch
+mergers will sometimes manually convert an acker's "yep, looks good to me"
+into an Acked-by:.
+
+Acked-by: does not necessarily indicate acknowledgement of the entire patch.
+For example, if a patch affects multiple subsystems and has an Acked-by: from
+one subsystem maintainer then this usually indicates acknowledgement of just
+the part which affects that maintainer's code. Judgement should be used here.
+When in doubt people should refer to the original discussion in the mailing
+list archives.
+
+If a person has had the opportunity to comment on a patch, but has not
+provided such comments, you may optionally add a "Cc:" tag to the patch.
+This is the only tag which might be added without an explicit action by the
+person it names. This tag documents that potentially interested parties
+have been included in the discussion
+
+
+[[Reported-by]]
+[[Tested-by]]
+[[Reviewed-by]]
+Reported-by:, Tested-by: and Reviewed-by:
+-----------------------------------------
+
+If this patch fixes a problem reported by somebody else, consider adding a
+Reported-by: tag to credit the reporter for their contribution. Please
+note that this tag should not be added without the reporter's permission,
+especially if the problem was not reported in a public forum. That said,
+if we diligently credit our bug reporters, they will, hopefully, be
+inspired to help us again in the future.
+
+A Tested-by: tag indicates that the patch has been successfully tested (in
+some environment) by the person named. This tag informs maintainers that
+some testing has been performed, provides a means to locate testers for
+future patches, and ensures credit for the testers.
+
+Reviewed-by:, instead, indicates that the patch has been reviewed and found
+acceptable according to the Reviewer's Statement:
+
+----
+ Reviewer's statement of oversight
+
+ By offering my Reviewed-by: tag, I state that:
+
+ (a) I have carried out a technical review of this patch to
+ evaluate its appropriateness and readiness for inclusion into
+ the mainline kernel.
+
+ (b) Any problems, concerns, or questions relating to the patch
+ have been communicated back to the submitter. I am satisfied
+ with the submitter's response to my comments.
+
+ (c) While there may be things that could be improved with this
+ submission, I believe that it is, at this time, (1) a
+ worthwhile modification to the kernel, and (2) free of known
+ issues which would argue against its inclusion.
+
+ (d) While I have reviewed the patch and believe it to be sound, I
+ do not (unless explicitly stated elsewhere) make any
+ warranties or guarantees that it will achieve its stated
+ purpose or function properly in any given situation.
+----
+
+A Reviewed-by tag is a statement of opinion that the patch is an
+appropriate modification of the kernel without any remaining serious
+technical issues. Any interested reviewer (who has done the work) can
+offer a Reviewed-by tag for a patch. This tag serves to give credit to
+reviewers and to inform maintainers of the degree of review which has been
+done on the patch. Reviewed-by: tags, when supplied by reviewers known to
+understand the subject area and to perform thorough reviews, will normally
+increase the likelihood of your patch getting into the kernel.
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
index b220633..d51ff34 100644
--- a/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
+++ b/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
@@ -30,6 +30,8 @@
String buttonChangeGroupOwner();
String buttonAddProjectRight();
String buttonSaveChanges();
+ String useContributorAgreements();
+ String useSignedOffBy();
String headingOwner();
String headingDescription();
@@ -37,6 +39,7 @@
String headingMembers();
String headingCreateGroup();
String headingAccessRights();
+ String headingAgreements();
String projectSubmitType_FAST_FORWARD_ONLY();
String projectSubmitType_MERGE_ALWAYS();
diff --git a/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index f0f8c15..87fea6b 100644
--- a/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -11,6 +11,8 @@
buttonChangeGroupOwner = Change Owner
buttonAddProjectRight = Add Access Right
buttonSaveChanges = Save Changes
+useContributorAgreements = Require a valid contributor agreement to upload
+useSignedOffBy = Require <a href="http://gerrit.googlecode.com/svn/documentation/2.0/user-signedoffby.html#Signed-off-by" target="_blank"><code>Signed-off-by</code></a> in commit message
headingOwner = Owners
headingDescription = Description
@@ -18,6 +20,7 @@
headingMembers = Members
headingCreateGroup = Create New Group
headingAccessRights = Access Rights
+headingAgreements = Contributor Agreements
projectSubmitType_FAST_FORWARD_ONLY = Fast Forward Only
projectSubmitType_MERGE_IF_NECESSARY = Merge If Necessary
diff --git a/src/main/java/com/google/gerrit/client/admin/ProjectInfoPanel.java b/src/main/java/com/google/gerrit/client/admin/ProjectInfoPanel.java
index 0791756..85274d8 100644
--- a/src/main/java/com/google/gerrit/client/admin/ProjectInfoPanel.java
+++ b/src/main/java/com/google/gerrit/client/admin/ProjectInfoPanel.java
@@ -17,6 +17,7 @@
import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.client.reviewdb.Project;
import com.google.gerrit.client.reviewdb.ProjectRight;
+import com.google.gerrit.client.rpc.Common;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.AccountGroupSuggestOracle;
import com.google.gerrit.client.ui.SmallHeading;
@@ -25,7 +26,10 @@
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.ListBox;
@@ -48,6 +52,10 @@
private Panel submitTypePanel;
private ListBox submitType;
+ private Panel agreementsPanel;
+ private CheckBox useContributorAgreements;
+ private CheckBox useSignedOffBy;
+
private NpTextArea descTxt;
private Button saveProject;
@@ -64,6 +72,7 @@
initOwner(body);
initDescription(body);
initSubmitType(body);
+ initAgreements(body);
body.add(saveProject);
initWidget(body);
@@ -95,6 +104,8 @@
submitType.setEnabled(on);
ownerTxtBox.setEnabled(on);
descTxt.setEnabled(on);
+ useContributorAgreements.setEnabled(on);
+ useSignedOffBy.setEnabled(on);
}
private void initOwner(final Panel body) {
@@ -158,6 +169,29 @@
body.add(submitTypePanel);
}
+ private void initAgreements(final Panel body) {
+ final ValueChangeHandler<Boolean> onChangeSave =
+ new ValueChangeHandler<Boolean>() {
+ @Override
+ public void onValueChange(ValueChangeEvent<Boolean> event) {
+ saveProject.setEnabled(true);
+ }
+ };
+
+ agreementsPanel = new VerticalPanel();
+ agreementsPanel.add(new SmallHeading(Util.C.headingAgreements()));
+
+ useContributorAgreements = new CheckBox(Util.C.useContributorAgreements());
+ useContributorAgreements.addValueChangeHandler(onChangeSave);
+ agreementsPanel.add(useContributorAgreements);
+
+ useSignedOffBy = new CheckBox(Util.C.useSignedOffBy(), true);
+ useSignedOffBy.addValueChangeHandler(onChangeSave);
+ agreementsPanel.add(useSignedOffBy);
+
+ body.add(agreementsPanel);
+ }
+
private void setSubmitType(final Project.SubmitType newSubmitType) {
if (submitType != null) {
for (int i = 0; i < submitType.getItemCount(); i++) {
@@ -179,20 +213,23 @@
ownerTxt.setText(Util.M.deletedGroup(project.getOwnerGroupId().get()));
}
- if (ProjectRight.WILD_PROJECT.equals(project.getId())) {
- ownerPanel.setVisible(false);
- submitTypePanel.setVisible(false);
- } else {
- ownerPanel.setVisible(true);
- submitTypePanel.setVisible(true);
- }
+ final boolean isall = ProjectRight.WILD_PROJECT.equals(project.getId());
+ ownerPanel.setVisible(!isall);
+ submitTypePanel.setVisible(!isall);
+ agreementsPanel.setVisible(!isall);
+ useContributorAgreements.setVisible(Common.getGerritConfig()
+ .isUseContributorAgreements());
descTxt.setText(project.getDescription());
+ useContributorAgreements.setValue(project.isUseContributorAgreements());
+ useSignedOffBy.setValue(project.isUseSignedOffBy());
setSubmitType(project.getSubmitType());
}
private void doSave() {
project.setDescription(descTxt.getText().trim());
+ project.setUseContributorAgreements(useContributorAgreements.getValue());
+ project.setUseSignedOffBy(useSignedOffBy.getValue());
if (submitType.getSelectedIndex() >= 0) {
project.setSubmitType(Project.SubmitType.valueOf(submitType
.getValue(submitType.getSelectedIndex())));
diff --git a/src/main/java/com/google/gerrit/client/reviewdb/Project.java b/src/main/java/com/google/gerrit/client/reviewdb/Project.java
index b7d2fa5..2637a46 100644
--- a/src/main/java/com/google/gerrit/client/reviewdb/Project.java
+++ b/src/main/java/com/google/gerrit/client/reviewdb/Project.java
@@ -130,6 +130,9 @@
protected boolean useContributorAgreements;
@Column
+ protected boolean useSignedOffBy;
+
+ @Column
protected char submitType;
protected Project() {
@@ -178,6 +181,14 @@
useContributorAgreements = u;
}
+ public boolean isUseSignedOffBy() {
+ return useSignedOffBy;
+ }
+
+ public void setUseSignedOffBy(final boolean sbo) {
+ useSignedOffBy = sbo;
+ }
+
public SubmitType getSubmitType() {
return SubmitType.forCode(submitType);
}
@@ -189,6 +200,7 @@
public void copySettingsFrom(final Project update) {
description = update.description;
useContributorAgreements = update.useContributorAgreements;
+ useSignedOffBy = update.useSignedOffBy;
submitType = update.submitType;
}
}
diff --git a/src/main/java/com/google/gerrit/client/reviewdb/ReviewDb.java b/src/main/java/com/google/gerrit/client/reviewdb/ReviewDb.java
index 1ef1603..7aef867 100644
--- a/src/main/java/com/google/gerrit/client/reviewdb/ReviewDb.java
+++ b/src/main/java/com/google/gerrit/client/reviewdb/ReviewDb.java
@@ -31,7 +31,7 @@
* </ul>
*/
public interface ReviewDb extends Schema {
- public static final int VERSION = 13;
+ public static final int VERSION = 14;
@Relation
SchemaVersionAccess schemaVersion();
diff --git a/src/main/java/com/google/gerrit/server/ssh/Receive.java b/src/main/java/com/google/gerrit/server/ssh/Receive.java
index bb90ff7..8f868f1 100644
--- a/src/main/java/com/google/gerrit/server/ssh/Receive.java
+++ b/src/main/java/com/google/gerrit/server/ssh/Receive.java
@@ -61,6 +61,8 @@
import org.spearce.jgit.lib.PersonIdent;
import org.spearce.jgit.lib.Ref;
import org.spearce.jgit.lib.RefUpdate;
+import org.spearce.jgit.revwalk.FooterKey;
+import org.spearce.jgit.revwalk.FooterLine;
import org.spearce.jgit.revwalk.RevCommit;
import org.spearce.jgit.revwalk.RevObject;
import org.spearce.jgit.revwalk.RevSort;
@@ -1033,14 +1035,44 @@
private boolean validCommitter(final ReceiveCommand cmd, final RevCommit c) {
// Require that committer matches the uploader.
+ //
final PersonIdent committer = c.getCommitterIdent();
- final String email = committer.getEmailAddress();
- if (myEmails.contains(email)) {
- return true;
- } else {
- reject(cmd, "invalid committer " + email);
+ if (!myEmails.contains(committer.getEmailAddress())) {
+ reject(cmd, "you are not committer " + committer.getEmailAddress());
return false;
}
+
+ if (proj.isUseSignedOffBy()) {
+ // If the project wants Signed-off-by / Acked-by lines, verify we
+ // have them for the blamable parties involved on this change.
+ //
+ final PersonIdent author = c.getAuthorIdent();
+ boolean sboAuthor = false, sboCommitter = false, sboMe = false;
+ for (final FooterLine footer : c.getFooterLines()) {
+ if (footer.matches(FooterKey.SIGNED_OFF_BY)) {
+ final String e = footer.getEmailAddress();
+ if (e != null) {
+ sboAuthor |= author.getEmailAddress().equals(e);
+ sboCommitter |= committer.getEmailAddress().equals(e);
+ sboMe |= myEmails.contains(e);
+ }
+ }
+ }
+ if (!sboMe) {
+ reject(cmd, "not Signed-off-by you");
+ return false;
+ }
+ if (!sboAuthor) {
+ reject(cmd, "not Signed-off-by " + author.getEmailAddress());
+ return false;
+ }
+ if (!sboCommitter) {
+ reject(cmd, "not Signed-off-by " + committer.getEmailAddress());
+ return false;
+ }
+ }
+
+ return true;
}
private void insertBranchEntity(final ReceiveCommand c) {
diff --git a/src/main/webapp/WEB-INF/sql/upgrade013_014_mysql.sql b/src/main/webapp/WEB-INF/sql/upgrade013_014_mysql.sql
new file mode 100644
index 0000000..5e1a56d
--- /dev/null
+++ b/src/main/webapp/WEB-INF/sql/upgrade013_014_mysql.sql
@@ -0,0 +1,8 @@
+-- Upgrade: schema_version 13 to 14 (MySQL)
+--
+
+ALTER TABLE projects ADD use_signed_off_by CHAR(1);
+UPDATE projects SET use_signed_off_by = 'N';
+ALTER TABLE projects MODIFY use_signed_off_by CHAR(1) DEFAULT 'N' NOT NULL;
+
+UPDATE schema_version SET version_nbr = 14;
diff --git a/src/main/webapp/WEB-INF/sql/upgrade013_014_postgres.sql b/src/main/webapp/WEB-INF/sql/upgrade013_014_postgres.sql
new file mode 100644
index 0000000..1e66639
--- /dev/null
+++ b/src/main/webapp/WEB-INF/sql/upgrade013_014_postgres.sql
@@ -0,0 +1,9 @@
+-- Upgrade: schema_version 13 to 14 (PostgreSQL)
+--
+
+ALTER TABLE projects ADD use_signed_off_by CHAR(1);
+UPDATE projects SET use_signed_off_by = 'N';
+ALTER TABLE projects ALTER COLUMN use_signed_off_by SET DEFAULT 'N';
+ALTER TABLE projects ALTER COLUMN use_signed_off_by SET NOT NULL;
+
+UPDATE schema_version SET version_nbr = 14;