- Updated Japanese translation\r
\r
additions: \r
+ - Global and per-repository setting to exclude authors from metrics (issue-251)\r
- Added SalesForce.com user service\r
- Added simple star/unstar function to flag or bookmark interesting repositories\r
- Added Dashboard page which shows a news feed for starred repositories and offers a filterable list of repositories you care about\r
- { name: 'web.activityDurationChoices', defaultValue: '7 14 28 60 90 180' }\r
- { name: 'web.allowAppCloneLinks', defaultValue: true }\r
- { name: 'web.forceDefaultLocale', defaultValue: ' ' }\r
+ - { name: 'web.metricAuthorExclusions', defaultValue: ' ' }\r
- { name: 'web.overviewPushCount', defaultValue: 5 }\r
- { name: 'web.pushesPerPage', defaultValue: 10 }\r
- { name: 'server.nioThreadPoolSize', defaultValue: 50 }\r
# SINCE 1.3.0\r
web.activityDurationChoices = 7 14 28 60 90 180\r
\r
+# Case-insensitive list of authors to exclude from metrics. Useful for\r
+# eliminating bots.\r
+#\r
+# SPACE-DELIMITED\r
+# SINCE 1.3.0\r
+web.metricAuthorExclusions =\r
+\r
# The number of commits to display on the summary page\r
# Value must exceed 0 else default of 20 is used\r
#\r
Constants.CONFIG_GITBLIT, null, "mailingList")));
model.indexedBranches = new ArrayList<String>(Arrays.asList(config.getStringList(
Constants.CONFIG_GITBLIT, null, "indexBranch")));
+ model.metricAuthorExclusions = new ArrayList<String>(Arrays.asList(config.getStringList(
+ Constants.CONFIG_GITBLIT, null, "metricAuthorExclusions")));
// Custom defined properties
model.customFields = new LinkedHashMap<String, String>();
updateList(config, "postReceiveScript", repository.postReceiveScripts);
updateList(config, "mailingList", repository.mailingLists);
updateList(config, "indexBranch", repository.indexedBranches);
+ updateList(config, "metricAuthorExclusions", repository.metricAuthorExclusions);
// User Defined Properties
if (repository.customFields != null) {
\r
import java.io.Serializable;\r
import java.util.ArrayList;\r
+import java.util.Collection;\r
import java.util.Collections;\r
import java.util.Date;\r
import java.util.HashMap;\r
import java.util.List;\r
import java.util.Map;\r
import java.util.Set;\r
+import java.util.TreeSet;\r
\r
import org.eclipse.jgit.revwalk.RevCommit;\r
\r
public final Date startDate;\r
\r
public final Date endDate;\r
-\r
+ \r
private final Set<RepositoryCommit> commits;\r
\r
private final Map<String, Metric> authorMetrics;\r
\r
private final Map<String, Metric> repositoryMetrics;\r
\r
+ private final Set<String> authorExclusions;\r
+\r
/**\r
* Constructor for one day of activity.\r
* \r
commits = new LinkedHashSet<RepositoryCommit>();\r
authorMetrics = new HashMap<String, Metric>();\r
repositoryMetrics = new HashMap<String, Metric>();\r
+ authorExclusions = new TreeSet<String>();\r
+ }\r
+ \r
+ /**\r
+ * Exclude the specified authors from the metrics.\r
+ * \r
+ * @param authors\r
+ */\r
+ public void excludeAuthors(Collection<String> authors) {\r
+ for (String author : authors) {\r
+ authorExclusions.add(author.toLowerCase());\r
+ }\r
}\r
\r
/**\r
public RepositoryCommit addCommit(String repository, String branch, RevCommit commit) {\r
RepositoryCommit commitModel = new RepositoryCommit(repository, branch, commit);\r
if (commits.add(commitModel)) {\r
+ String author = StringUtils.removeNewlines(commit.getAuthorIdent().getName());\r
+ String authorName = author.toLowerCase();\r
+ String authorEmail = StringUtils.removeNewlines(commit.getAuthorIdent().getEmailAddress()).toLowerCase();\r
if (!repositoryMetrics.containsKey(repository)) {\r
repositoryMetrics.put(repository, new Metric(repository));\r
}\r
repositoryMetrics.get(repository).count++;\r
\r
- String author = StringUtils.removeNewlines(commit.getAuthorIdent().getEmailAddress()).toLowerCase(); \r
- if (!authorMetrics.containsKey(author)) {\r
- authorMetrics.put(author, new Metric(author));\r
+ if (!authorExclusions.contains(authorName) && !authorExclusions.contains(authorEmail)) {\r
+ if (!authorMetrics.containsKey(author)) {\r
+ authorMetrics.put(author, new Metric(author));\r
+ }\r
+ authorMetrics.get(author).count++;\r
}\r
- authorMetrics.get(author).count++;\r
return commitModel;\r
}\r
return null;\r
public boolean verifyCommitter;\r
public String gcThreshold;\r
public int gcPeriod;\r
- public int maxActivityCommits;\r
+ public int maxActivityCommits; \r
+ public List<String> metricAuthorExclusions;\r
\r
public transient boolean isCollectingGarbage;\r
public Date lastGC;\r
import java.util.HashMap;\r
import java.util.List;\r
import java.util.Map;\r
+import java.util.Set;\r
import java.util.TimeZone;\r
+import java.util.TreeSet;\r
\r
import org.eclipse.jgit.lib.Constants;\r
import org.eclipse.jgit.lib.ObjectId;\r
import org.eclipse.jgit.revwalk.RevCommit;\r
\r
import com.gitblit.GitBlit;\r
+import com.gitblit.Keys;\r
import com.gitblit.models.Activity;\r
import com.gitblit.models.GravatarProfile;\r
import com.gitblit.models.RefModel;\r
df.setTimeZone(timezone);\r
Calendar cal = Calendar.getInstance();\r
cal.setTimeZone(timezone);\r
+ \r
+ // aggregate author exclusions\r
+ Set<String> authorExclusions = new TreeSet<String>();\r
+ authorExclusions.addAll(GitBlit.getStrings(Keys.web.metricAuthorExclusions));\r
+ for (RepositoryModel model : models) {\r
+ if (!ArrayUtils.isEmpty(model.metricAuthorExclusions)) {\r
+ authorExclusions.addAll(model.metricAuthorExclusions);\r
+ }\r
+ }\r
\r
Map<String, Activity> activity = new HashMap<String, Activity>();\r
for (RepositoryModel model : models) {\r
cal.set(Calendar.MINUTE, 0);\r
cal.set(Calendar.SECOND, 0);\r
cal.set(Calendar.MILLISECOND, 0);\r
- activity.put(dateStr, new Activity(cal.getTime()));\r
+ Activity a = new Activity(cal.getTime());\r
+ a.excludeAuthors(authorExclusions);\r
+ activity.put(dateStr, a);\r
}\r
RepositoryCommit commitModel = activity.get(dateStr)\r
.addCommit(model.name, shortName, commit);\r
package com.gitblit.wicket.charting;\r
\r
import java.text.MessageFormat;\r
+import java.util.ArrayList;\r
import java.util.Collections;\r
+import java.util.List;\r
\r
import com.gitblit.utils.StringUtils;\r
\r
line(sb, MessageFormat.format("{0}.addRows({1,number,0});", dName, values.size()));\r
\r
Collections.sort(values);\r
-\r
- StringBuilder colors = new StringBuilder("colors:[");\r
- for (int i = 0; i < values.size(); i++) {\r
+ List<ChartValue> list = new ArrayList<ChartValue>();\r
+ \r
+ int maxSlices = 10;\r
+ int maxCount = Math.min(maxSlices - 1, values.size());\r
+ \r
+ for (int i = 0; i < maxCount; i++) {\r
ChartValue value = values.get(i);\r
+ list.add(value);\r
+ }\r
+ if (values.size() >= maxSlices) {\r
+ float others = 0;\r
+ for (int i = maxSlices - 1; i < values.size(); i++) {\r
+ others += values.get(i).value; \r
+ }\r
+ ChartValue other = new ChartValue("other", others);\r
+ list.add(other);\r
+ }\r
+ \r
+ StringBuilder colors = new StringBuilder("colors:[");\r
+ for (int i = 0; i < list.size(); i++) {\r
+ ChartValue value = list.get(i);\r
colors.append('\'');\r
colors.append(StringUtils.getColor(value.name));\r
colors.append('\'');\r
import java.util.Map;\r
import java.util.Set;\r
import java.util.TimeZone;\r
+import java.util.TreeSet;\r
\r
import org.apache.wicket.Component;\r
import org.apache.wicket.PageParameters;\r
\r
// add the nifty charts\r
if (!ArrayUtils.isEmpty(pushes)) {\r
- GoogleCharts charts = createCharts(pushes);\r
+ // aggregate author exclusions\r
+ Set<String> authorExclusions = new TreeSet<String>();\r
+ for (String author : GitBlit.getStrings(Keys.web.metricAuthorExclusions)) {\r
+ authorExclusions.add(author.toLowerCase());\r
+ }\r
+ for (RepositoryModel model : feedSources) {\r
+ if (!ArrayUtils.isEmpty(model.metricAuthorExclusions)) {\r
+ for (String author : model.metricAuthorExclusions) {\r
+ authorExclusions.add(author.toLowerCase());\r
+ }\r
+ }\r
+ }\r
+\r
+ GoogleCharts charts = createCharts(pushes, authorExclusions);\r
add(new HeaderContributor(charts));\r
}\r
\r
* @param recentPushes\r
* @return\r
*/\r
- private GoogleCharts createCharts(List<PushLogEntry> recentPushes) {\r
+ private GoogleCharts createCharts(List<PushLogEntry> recentPushes, Set<String> authorExclusions) {\r
// activity metrics\r
Map<String, Metric> repositoryMetrics = new HashMap<String, Metric>();\r
Map<String, Metric> authorMetrics = new HashMap<String, Metric>();\r
repositoryMetrics.get(repository).count += 1;\r
\r
for (RepositoryCommit commit : push.getCommits()) {\r
- String author = commit.getAuthorIdent().getName();\r
- if (!authorMetrics.containsKey(author)) {\r
- authorMetrics.put(author, new Metric(author));\r
+ String author = StringUtils.removeNewlines(commit.getAuthorIdent().getName());\r
+ String authorName = author.toLowerCase();\r
+ String authorEmail = StringUtils.removeNewlines(commit.getAuthorIdent().getEmailAddress()).toLowerCase();\r
+ if (!authorExclusions.contains(authorName) && !authorExclusions.contains(authorEmail)) {\r
+ if (!authorMetrics.containsKey(author)) {\r
+ authorMetrics.put(author, new Metric(author));\r
+ }\r
+ authorMetrics.get(author).count += 1;\r
}\r
- authorMetrics.get(author).count += 1;\r
}\r
}\r
\r
<tr><th><wicket:message key="gb.skipSizeCalculation"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="skipSizeCalculation" tabindex="11" /> <span class="help-inline"><wicket:message key="gb.skipSizeCalculationDescription"></wicket:message></span></label></td></tr>\r
<tr><th><wicket:message key="gb.skipSummaryMetrics"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="skipSummaryMetrics" tabindex="12" /> <span class="help-inline"><wicket:message key="gb.skipSummaryMetricsDescription"></wicket:message></span></label></td></tr>\r
<tr><th><wicket:message key="gb.maxActivityCommits"></wicket:message></th><td class="edit"><select class="span2" wicket:id="maxActivityCommits" tabindex="13" /> <span class="help-inline"><wicket:message key="gb.maxActivityCommitsDescription"></wicket:message></span></td></tr>\r
+ <tr><th><wicket:message key="gb.metricAuthorExclusions"></wicket:message></th><td class="edit"><input class="span8" type="text" wicket:id="metricAuthorExclusions" size="40" tabindex="14" /></td></tr>\r
<tr><th colspan="2"><hr/></th></tr>\r
- <tr><th><wicket:message key="gb.mailingLists"></wicket:message></th><td class="edit"><input class="span8" type="text" wicket:id="mailingLists" size="40" tabindex="14" /></td></tr>\r
+ <tr><th><wicket:message key="gb.mailingLists"></wicket:message></th><td class="edit"><input class="span8" type="text" wicket:id="mailingLists" size="40" tabindex="15" /></td></tr>\r
</tbody>\r
</table>\r
</div>\r
<div class="tab-pane" id="permissions">\r
<table class="plain">\r
<tbody class="settings">\r
- <tr><th><wicket:message key="gb.owners"></wicket:message></th><td class="edit"><span wicket:id="owners" tabindex="15" /> </td></tr>\r
+ <tr><th><wicket:message key="gb.owners"></wicket:message></th><td class="edit"><span wicket:id="owners" tabindex="16" /> </td></tr>\r
<tr><th colspan="2"><hr/></th></tr>\r
- <tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select class="span4" wicket:id="accessRestriction" tabindex="16" /></td></tr>\r
+ <tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select class="span4" wicket:id="accessRestriction" tabindex="17" /></td></tr>\r
<tr><th colspan="2"><hr/></th></tr>\r
<tr><th><wicket:message key="gb.authorizationControl"></wicket:message></th><td style="padding:2px;"><span class="authorizationControl" wicket:id="authorizationControl"></span></td></tr>\r
<tr><th colspan="2"><hr/></th></tr>\r
- <tr><th><wicket:message key="gb.isFrozen"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="isFrozen" tabindex="17" /> <span class="help-inline"><wicket:message key="gb.isFrozenDescription"></wicket:message></span></label></td></tr>\r
- <tr><th><wicket:message key="gb.allowForks"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="allowForks" tabindex="18" /> <span class="help-inline"><wicket:message key="gb.allowForksDescription"></wicket:message></span></label></td></tr>\r
- <tr><th><wicket:message key="gb.verifyCommitter"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="verifyCommitter" tabindex="19" /> <span class="help-inline"><wicket:message key="gb.verifyCommitterDescription"></wicket:message></span><br/><span class="help-inline" style="padding-left:10px;"><wicket:message key="gb.verifyCommitterNote"></wicket:message></span></label></td></tr>\r
+ <tr><th><wicket:message key="gb.isFrozen"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="isFrozen" tabindex="18" /> <span class="help-inline"><wicket:message key="gb.isFrozenDescription"></wicket:message></span></label></td></tr>\r
+ <tr><th><wicket:message key="gb.allowForks"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="allowForks" tabindex="19" /> <span class="help-inline"><wicket:message key="gb.allowForksDescription"></wicket:message></span></label></td></tr>\r
+ <tr><th><wicket:message key="gb.verifyCommitter"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="verifyCommitter" tabindex="20" /> <span class="help-inline"><wicket:message key="gb.verifyCommitterDescription"></wicket:message></span><br/><span class="help-inline" style="padding-left:10px;"><wicket:message key="gb.verifyCommitterNote"></wicket:message></span></label></td></tr>\r
<tr><th colspan="2"><hr/></th></tr>\r
<tr><th><wicket:message key="gb.userPermissions"></wicket:message></th><td style="padding:2px;"><span wicket:id="users"></span></td></tr>\r
<tr><th colspan="2"><hr/></th></tr>\r
<div class="tab-pane" id="federation">\r
<table class="plain">\r
<tbody class="settings">\r
- <tr><th><wicket:message key="gb.federationStrategy"></wicket:message></th><td class="edit"><select class="span4" wicket:id="federationStrategy" tabindex="20" /></td></tr>\r
+ <tr><th><wicket:message key="gb.federationStrategy"></wicket:message></th><td class="edit"><select class="span4" wicket:id="federationStrategy" tabindex="21" /></td></tr>\r
<tr><th><wicket:message key="gb.federationSets"></wicket:message></th><td style="padding:2px;"><span wicket:id="federationSets"></span></td></tr>\r
</tbody>\r
</table>\r
\r
RepositoryModel repositoryModel;\r
\r
+ private IModel<String> metricAuthorExclusions;\r
+ \r
private IModel<String> mailingLists;\r
\r
public EditRepositoryPage() {\r
}\r
}\r
\r
+ // set author metric exclusions\r
+ String ax = metricAuthorExclusions.getObject();\r
+ if (!StringUtils.isEmpty(ax)) {\r
+ Set<String> list = new HashSet<String>();\r
+ for (String exclusion : StringUtils.getStringsFromValue(ax, " ")) {\r
+ if (StringUtils.isEmpty(exclusion)) {\r
+ continue;\r
+ }\r
+ if (exclusion.indexOf(' ') > -1) {\r
+ list.add("\"" + exclusion + "\""); \r
+ } else {\r
+ list.add(exclusion);\r
+ }\r
+ }\r
+ repositoryModel.metricAuthorExclusions = new ArrayList<String>(list);\r
+ }\r
+\r
// set mailing lists\r
String ml = mailingLists.getObject();\r
if (!StringUtils.isEmpty(ml)) {\r
List<Integer> maxActivityCommits = Arrays.asList(-1, 0, 25, 50, 75, 100, 150, 200, 250, 500 );\r
form.add(new DropDownChoice<Integer>("maxActivityCommits", maxActivityCommits, new MaxActivityCommitsRenderer()));\r
\r
+ metricAuthorExclusions = new Model<String>(ArrayUtils.isEmpty(repositoryModel.metricAuthorExclusions) ? ""\r
+ : StringUtils.flattenStrings(repositoryModel.metricAuthorExclusions, " "));\r
+ form.add(new TextField<String>("metricAuthorExclusions", metricAuthorExclusions));\r
+\r
mailingLists = new Model<String>(ArrayUtils.isEmpty(repositoryModel.mailingLists) ? ""\r
: StringUtils.flattenStrings(repositoryModel.mailingLists, " "));\r
form.add(new TextField<String>("mailingLists", mailingLists));\r