Merge "Set maxObjectSizeLimit from create-project and set-project SSH commands"
diff --git a/Documentation/cmd-create-project.txt b/Documentation/cmd-create-project.txt
index d0e56fd..666fe91 100644
--- a/Documentation/cmd-create-project.txt
+++ b/Documentation/cmd-create-project.txt
@@ -21,6 +21,7 @@
   [--require-change-id | --id]
   [[--branch <REF> | -b <REF>] ...]
   [--empty-commit]
+  [--max-object-size-limit <N>]
   { <NAME> | --name <NAME> }
 
 DESCRIPTION
@@ -144,6 +145,15 @@
 	Creates an initial empty commit for the Git repository of the
 	project that is newly created.
 
+--max-object-size-limit::
+	Define maximum Git object size for this project. Pushes containing an
+	object larger than this limit will be rejected. This can be used to
+	further limit the global
+  link:config-gerrit.html#receive.maxObjectSizeLimit[receive.maxObjectSizeLimit]
+	and cannot be used to increase that globally set limit.
++
+Common unit suffixes of 'k', 'm', or 'g' are supported.
+
 
 EXAMPLES
 --------
diff --git a/Documentation/cmd-set-project.txt b/Documentation/cmd-set-project.txt
index a0af910..7ff534b 100644
--- a/Documentation/cmd-set-project.txt
+++ b/Documentation/cmd-set-project.txt
@@ -16,6 +16,7 @@
   [--content-merge <true|false|inherit>]
   [--change-id <true|false|inherit>]
   [--project-state <STATE> | --ps <STATE>]
+  [--max-object-size-limit <N>]
   <NAME>
 
 DESCRIPTION
@@ -93,6 +94,15 @@
 is granted, but all modification operations are disabled.
 * HIDDEN: the project is not visible for those who are not owners
 
+--max-object-size-limit::
+	Define maximum Git object size for this project. Pushes containing an
+	object larger than this limit will be rejected. This can be used to
+	further limit the global
+  link:config-gerrit.html#receive.maxObjectSizeLimit[receive.maxObjectSizeLimit]
+	and cannot be used to increase that globally set limit.
++
+Common unit suffixes of 'k', 'm', or 'g' are supported.
+
 EXAMPLES
 --------
 Change project `example` to be hidden, require change id, don't use content merge
@@ -105,4 +115,4 @@
 
 GERRIT
 ------
-Part of link:index.html[Gerrit Code Review]
\ No newline at end of file
+Part of link:index.html[Gerrit Code Review]
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java
index d243496..f3cf471 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java
@@ -107,6 +107,8 @@
 
   protected InheritableBoolean requireChangeID;
 
+  protected String maxObjectSizeLimit;
+
   protected InheritableBoolean useContentMerge;
 
   protected String defaultDashboardId;
@@ -160,6 +162,10 @@
     return requireChangeID;
   }
 
+  public String getMaxObjectSizeLimit() {
+    return maxObjectSizeLimit;
+  }
+
   public void setUseContributorAgreements(final InheritableBoolean u) {
     useContributorAgreements = u;
   }
@@ -176,6 +182,10 @@
     requireChangeID = cid;
   }
 
+  public void setMaxObjectSizeLimit(final String limit) {
+    maxObjectSizeLimit = limit;
+  }
+
   public SubmitType getSubmitType() {
     return submitType;
   }
@@ -224,6 +234,7 @@
     requireChangeID = update.requireChangeID;
     submitType = update.submitType;
     state = update.state;
+    maxObjectSizeLimit = update.maxObjectSizeLimit;
   }
 
   /**
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index a12b948..a2f392d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -382,6 +382,7 @@
     p.setUseContributorAgreements(getEnum(rc, RECEIVE, null, KEY_REQUIRE_CONTRIBUTOR_AGREEMENT, Project.InheritableBoolean.INHERIT));
     p.setUseSignedOffBy(getEnum(rc, RECEIVE, null, KEY_REQUIRE_SIGNED_OFF_BY, Project.InheritableBoolean.INHERIT));
     p.setRequireChangeID(getEnum(rc, RECEIVE, null, KEY_REQUIRE_CHANGE_ID, Project.InheritableBoolean.INHERIT));
+    p.setMaxObjectSizeLimit(rc.getString(RECEIVE, null, KEY_MAX_OBJECT_SIZE_LIMIT));
 
     p.setSubmitType(getEnum(rc, SUBMIT, null, KEY_ACTION, defaultSubmitAction));
     p.setUseContentMerge(getEnum(rc, SUBMIT, null, KEY_MERGE_CONTENT, Project.InheritableBoolean.INHERIT));
@@ -723,6 +724,7 @@
     set(rc, RECEIVE, null, KEY_REQUIRE_CONTRIBUTOR_AGREEMENT, p.getUseContributorAgreements(), Project.InheritableBoolean.INHERIT);
     set(rc, RECEIVE, null, KEY_REQUIRE_SIGNED_OFF_BY, p.getUseSignedOffBy(), Project.InheritableBoolean.INHERIT);
     set(rc, RECEIVE, null, KEY_REQUIRE_CHANGE_ID, p.getRequireChangeID(), Project.InheritableBoolean.INHERIT);
+    set(rc, RECEIVE, null, KEY_MAX_OBJECT_SIZE_LIMIT, validMaxObjectSizeLimit(p.getMaxObjectSizeLimit()));
 
     set(rc, SUBMIT, null, KEY_ACTION, p.getSubmitType(), defaultSubmitAction);
     set(rc, SUBMIT, null, KEY_MERGE_CONTENT, p.getUseContentMerge(), Project.InheritableBoolean.INHERIT);
@@ -744,6 +746,31 @@
     saveGroupList();
   }
 
+  private static final String validMaxObjectSizeLimit(String value)
+      throws ConfigInvalidException {
+    if (value == null) {
+      return null;
+    }
+    Config cfg = new Config();
+    cfg.fromText("[s]\nn=" + value);
+    try {
+      long s = cfg.getLong("s", "n", 0);
+      if (s < 0) {
+        throw new ConfigInvalidException(String.format(
+            "Negative value '%s' not allowed as %s", value,
+            KEY_MAX_OBJECT_SIZE_LIMIT));
+      }
+      if (s == 0) {
+        // return null for the default so that it is not persisted
+        return null;
+      }
+      return value;
+    } catch (IllegalArgumentException e) {
+      throw new ConfigInvalidException(
+          String.format("Value '%s' not parseable as a Long", value), e);
+    }
+  }
+
   private void saveAccountsSection(Config rc, Set<AccountGroup.UUID> keepGroups) {
     if (accountsSection != null) {
       rc.setStringList(ACCOUNTS, null, KEY_SAME_GROUP_VISIBILITY,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java
index 7bbd2e7..ea20cea 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java
@@ -35,6 +35,7 @@
   public InheritableBoolean contentMerge;
   public InheritableBoolean changeIdRequired;
   public boolean createEmptyCommit;
+  public String maxObjectSizeLimit;
 
   public CreateProjectArgs() {
     contributorAgreements = InheritableBoolean.INHERIT;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java
index d68725f..29317440 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java
@@ -181,6 +181,7 @@
       newProject.setUseSignedOffBy(createProjectArgs.signedOffBy);
       newProject.setUseContentMerge(createProjectArgs.contentMerge);
       newProject.setRequireChangeID(createProjectArgs.changeIdRequired);
+      newProject.setMaxObjectSizeLimit(createProjectArgs.maxObjectSizeLimit);
       if (createProjectArgs.newParent != null) {
         newProject.setParentName(createProjectArgs.newParent.getProject()
             .getNameKey());
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
index bd624ae..89bb973 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
@@ -106,6 +106,9 @@
   @Option(name = "--empty-commit", usage = "to create initial empty commit")
   private boolean createEmptyCommit;
 
+  @Option(name = "--max-object-size-limit", usage = "max Git object size for this project")
+  private String maxObjectSizeLimit;
+
   private String projectName;
 
   @Argument(index = 0, metaVar = "NAME", usage = "name of project to be created")
@@ -143,6 +146,7 @@
         args.changeIdRequired = requireChangeID;
         args.branch = branch;
         args.createEmptyCommit = createEmptyCommit;
+        args.maxObjectSizeLimit = maxObjectSizeLimit;
 
         final PerformCreateProject createProject = factory.create(args);
         createProject.createProject();
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
index 8c06c97d..d512fd5 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
@@ -108,6 +108,9 @@
   @Option(name = "--project-state", aliases = {"--ps"}, usage = "project's visibility state")
   private State state;
 
+  @Option(name = "--max-object-size-limit", usage = "max Git object size for this project")
+  private String maxObjectSizeLimit;
+
   @Inject
   private MetaDataUpdate.User metaDataUpdateFactory;
 
@@ -148,6 +151,9 @@
         if (state != null) {
           project.setState(state);
         }
+        if (maxObjectSizeLimit != null) {
+          project.setMaxObjectSizeLimit(maxObjectSizeLimit);
+        }
         md.setMessage("Project settings updated");
         config.commit(md);
       } finally {