Add since/until limits to the contributors query
Allow specifying since/until time-stamps for selecting a subset
of commits grouped by contributor.
Parameters are similar to Gerrit's time-stamp based query parameters.
See: https://gerrit-review.googlesource.com/Documentation/user-search.html
Change-Id: I47923130deca53c3a7ff86145597d43b704cbd5e
diff --git a/README.md b/README.md
index 1097fd7..ab9945c 100644
--- a/README.md
+++ b/README.md
@@ -52,11 +52,18 @@
*REST*
-/projects/{project-name}/analytics~contributors
+/projects/{project-name}/analytics~contributors[?since=2006-01-02[15:04:05[.890][-0700]]][&until=2018-01-02[18:01:03[.333][-0700]]]
*SSH*
-analytics contributors {project-name}
+analytics contributors {project-name} [--since 2006-01-02[15:04:05[.890][-0700]]] [--until 2018-01-02[18:01:03[.333][-0700]]]
+
+### Parameters
+
+- --since -b Starting timestamp to consider
+- --until -e Ending timestamp (excluded) to consider
+
+NOTE: Timestamp format is consistent with Gerrit's query syntax, see /Documentation/user-search.html for details.
REST Example:
@@ -70,7 +77,7 @@
SSH Example:
```
- $ ssh -p 29418 admin@gerrit.mycompany.com analytics contributors myproject
+ $ ssh -p 29418 admin@gerrit.mycompany.com analytics contributors myproject --since 2017-08-01 --until 2017-12-31
{"name":"John Doe","email":"john.doe@mycompany.com","num_commits":1,"commits":[{"sha1":"6a1f73738071e299f600017d99f7252d41b96b4b","date":"Apr 28, 2011 5:13:14 AM","merge":false}]}
{"name":"Matt Smith","email":"matt.smith@mycompany.com","num_commits":1,"commits":[{"sha1":"54527e7e3086758a23e3b069f183db6415aca304","date":"Sep 8, 2015 3:11:23 AM","merge":true}]}
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/analytics/Contributors.scala b/src/main/scala/com/googlesource/gerrit/plugins/analytics/Contributors.scala
index df3bbe9..184f550 100644
--- a/src/main/scala/com/googlesource/gerrit/plugins/analytics/Contributors.scala
+++ b/src/main/scala/com/googlesource/gerrit/plugins/analytics/Contributors.scala
@@ -14,14 +14,17 @@
package com.googlesource.gerrit.plugins.analytics
-import com.google.gerrit.extensions.restapi.{Response, RestReadView}
+import com.google.gerrit.extensions.restapi.{BadRequestException, Response, RestReadView}
import com.google.gerrit.server.git.GitRepositoryManager
import com.google.gerrit.server.project.{ProjectResource, ProjectsCollection}
import com.google.gerrit.sshd.{CommandMetaData, SshCommand}
import com.google.inject.Inject
+import com.googlesource.gerrit.plugins.analytics.common.DateConversions._
import com.googlesource.gerrit.plugins.analytics.common._
import org.eclipse.jgit.lib.ObjectId
-import org.gitective.core.stat.{AuthorHistogramFilter, UserCommitActivity}
+import org.gitective.core.stat.UserCommitActivity
+import org.kohsuke.args4j.{Option => ArgOption}
+import org.slf4j.LoggerFactory
@CommandMetaData(name = "contributors", description = "Extracts the list of contributors to a project")
@@ -30,30 +33,78 @@
val gsonFmt: GsonFormatter)
extends SshCommand with ProjectResourceParser {
- override protected def run = gsonFmt.format(executor.get(projectRes), stdout)
+ private var beginDate: Option[Long] = None
+
+ @ArgOption(name = "--since", aliases = Array("--after", "-b"), usage = "(included) begin timestamp. Must be in the format 2006-01-02[ 15:04:05[.890][ -0700]]")
+ def setBeginDate(date: String) {
+ try {
+ beginDate = Some(date)
+ } catch {
+ case e: Exception => throw die(s"Invalid begin date ${e.getMessage}")
+ }
+ }
+
+ private var endDate: Option[Long] = None
+
+ @ArgOption(name = "--until", aliases = Array("--before", "-e"), usage = "(excluded) end timestamp. Must be in the format 2006-01-02[ 15:04:05[.890][ -0700]]")
+ def setEndDate(date: String) {
+ try {
+ endDate = Some(date)
+ } catch {
+ case e: Exception => throw die(s"Invalid end date ${e.getMessage}")
+ }
+ }
+
+ override protected def run =
+ gsonFmt.format(executor.get(projectRes, beginDate, endDate), stdout)
+
}
class ContributorsResource @Inject()(val executor: ContributorsService,
val gson: GsonFormatter)
extends RestReadView[ProjectResource] {
- override def apply(projectRes: ProjectResource) = Response.ok(
- new GsonStreamedResult[UserActivitySummary](gson, executor.get(projectRes)))
+ private var beginDate: Option[Long] = None
+
+ @ArgOption(name = "--since", aliases = Array("--after", "-b"), metaVar = "QUERY", usage = "(included) begin timestamp. Must be in the format 2006-01-02[ 15:04:05[.890][ -0700]]")
+ def setBeginDate(date: String) {
+ try {
+ beginDate = Some(date)
+ } catch {
+ case e: Exception => throw new BadRequestException(s"Invalid begin date ${e.getMessage}")
+ }
+ }
+
+ private var endDate: Option[Long] = None
+
+ @ArgOption(name = "--until", aliases = Array("--before", "-e"), metaVar = "QUERY", usage = "(excluded) end timestamp. Must be in the format 2006-01-02[ 15:04:05[.890][ -0700]]")
+ def setEndDate(date: String) {
+ try {
+ endDate = Some(date)
+ } catch {
+ case e: Exception => throw new BadRequestException(s"Invalid end date ${e.getMessage}")
+ }
+ }
+
+ override def apply(projectRes: ProjectResource) =
+ Response.ok(
+ new GsonStreamedResult[UserActivitySummary](gson, executor.get(projectRes, beginDate, endDate)))
}
class ContributorsService @Inject()(repoManager: GitRepositoryManager,
histogram: UserActivityHistogram,
gsonFmt: GsonFormatter) {
- def get(projectRes: ProjectResource): TraversableOnce[UserActivitySummary] =
+ def get(projectRes: ProjectResource, startDate: Option[Long], stopDate: Option[Long]): TraversableOnce[UserActivitySummary] = {
ManagedResource.use(repoManager.openRepository(projectRes.getNameKey)) {
- histogram.get(_, new AuthorHistogramFilter)
+ histogram.get(_, new AuthorHistogramFilterByDates(startDate, stopDate))
.par
.map(UserActivitySummary.apply).toStream
}
+ }
}
-case class CommitInfo(val sha1: String, val date: Long, val merge: Boolean)
+case class CommitInfo(sha1: String, date: Long, merge: Boolean)
case class UserActivitySummary(name: String, email: String, numCommits: Int,
commits: Array[CommitInfo], lastCommitDate: Long)
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/AuthorHistogramFilterByDates.scala b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/AuthorHistogramFilterByDates.scala
new file mode 100644
index 0000000..b9cf786
--- /dev/null
+++ b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/AuthorHistogramFilterByDates.scala
@@ -0,0 +1,41 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.analytics.common
+
+import java.util.Date
+
+import org.eclipse.jgit.revwalk.{RevCommit, RevWalk}
+import org.gitective.core.stat.CommitHistogramFilter
+
+/**
+ * Commit filter that includes commits only on the specified interval
+ * starting from and to excluded
+ */
+class AuthorHistogramFilterByDates(val from: Option[Long] = None, val to: Option[Long] = None)
+ extends CommitHistogramFilter {
+
+ override def include(walker: RevWalk, commit: RevCommit) = {
+ val commitDate = commit.getCommitterIdent.getWhen.getTime
+ val author = commit.getAuthorIdent
+ if (from.fold(true)(commitDate >=) && to.fold(true)(commitDate <)) {
+ histogram.include(commit, author)
+ true
+ } else {
+ false
+ }
+ }
+
+ override def clone = new AuthorHistogramFilterByDates(from, to)
+}
\ No newline at end of file
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/DateConversions.scala b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/DateConversions.scala
new file mode 100644
index 0000000..4aa31a7
--- /dev/null
+++ b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/DateConversions.scala
@@ -0,0 +1,22 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.analytics.common
+
+import com.google.gwtjsonrpc.common.JavaSqlTimestampHelper
+
+object DateConversions {
+
+ implicit def isoStringToLongDate(s: String): Long = JavaSqlTimestampHelper.parseTimestamp(s).getTime
+}
diff --git a/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/AuthorHistogramFilterByDatesSpec.scala b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/AuthorHistogramFilterByDatesSpec.scala
new file mode 100644
index 0000000..6f4a8bc
--- /dev/null
+++ b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/AuthorHistogramFilterByDatesSpec.scala
@@ -0,0 +1,106 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.analytics.test
+
+import java.util.Date
+
+import com.googlesource.gerrit.plugins.analytics.common.AuthorHistogramFilterByDates
+import org.eclipse.jgit.lib.PersonIdent
+import org.gitective.core.CommitFinder
+import org.scalatest.{BeforeAndAfterEach, FlatSpec, Matchers}
+
+class AuthorHistogramFilterByDatesSpec extends FlatSpec with GitTestCase with BeforeAndAfterEach with Matchers {
+
+ "Author history filter" should
+ "select one commit without intervals restriction" in {
+
+ add("file.txt", "some content")
+ val filter = new AuthorHistogramFilterByDates
+ new CommitFinder(testRepo).setFilter(filter).find
+
+ val userActivity = filter.getHistogram.getUserActivity
+ filter.getHistogram.getUserActivity should have size (1)
+ val activity = userActivity.head
+ activity.getCount should be(1)
+ activity.getName should be(author.getName)
+ activity.getEmail should be(author.getEmailAddress)
+ }
+
+ it should "select only the second of two commits based on the start timestamp" in {
+ val firstCommitTs = add("file.txt", "some content")
+ .getCommitterIdent.getWhen.getTime
+ val person = newPersonIdent("Second person", "second@company.com", new Date(firstCommitTs + 1000L))
+ val secondCommitTs = add("file.txt", "other content", author = person, committer = person)
+ .getCommitterIdent.getWhen.getTime
+
+ secondCommitTs should be > firstCommitTs
+
+ val filter = new AuthorHistogramFilterByDates(from = Some(secondCommitTs))
+ new CommitFinder(testRepo).setFilter(filter).find
+
+ val userActivity = filter.getHistogram.getUserActivity
+ userActivity should have size (1)
+ val activity = userActivity.head
+
+ activity.getTimes should have size (1)
+ activity.getName should be(person.getName)
+ activity.getEmail should be(person.getEmailAddress)
+ }
+
+ it should "select only the first of two commits based on the end timestamp" in {
+ val person = newPersonIdent("First person", "first@company.com")
+ val firstCommitTs = add("file.txt", "some content", author = person, committer = person)
+ .getCommitterIdent.getWhen.getTime
+ val secondCommitTs = add("file.txt", "other content", committer = new PersonIdent(committer, new Date(firstCommitTs + 1000L)))
+ .getCommitterIdent.getWhen.getTime
+
+ secondCommitTs should be > firstCommitTs
+
+ val filter = new AuthorHistogramFilterByDates(to = Some(secondCommitTs))
+ new CommitFinder(testRepo).setFilter(filter).find
+
+ val userActivity = filter.getHistogram.getUserActivity
+ userActivity should have size (1)
+ val activity = userActivity.head
+
+ activity.getTimes should have size (1)
+ activity.getName should be(person.getName)
+ activity.getEmail should be(person.getEmailAddress)
+ }
+
+ it should "select only one middle commit out of three based on interval from/to timestamp" in {
+ val firstCommitTs = add("file.txt", "some content")
+ .getCommitterIdent.getWhen.getTime
+ val person = newPersonIdent("Middle person", "middle@company.com", new Date(firstCommitTs + 1000L))
+ val middleCommitTs = add("file.txt", "other content", author = person, committer = person)
+ .getCommitterIdent.getWhen.getTime
+ val lastCommitTs = add("file.text", "yet other content", committer = new PersonIdent(committer, new Date(middleCommitTs + 1000L)))
+ .getCommitterIdent.getWhen.getTime
+
+ middleCommitTs should be > firstCommitTs
+ lastCommitTs should be > middleCommitTs
+
+ val filter = new AuthorHistogramFilterByDates(from = Some(middleCommitTs), to = Some(lastCommitTs))
+ new CommitFinder(testRepo).setFilter(filter).find
+
+ val userActivity = filter.getHistogram.getUserActivity
+ userActivity should have size (1)
+ val activity = userActivity.head
+
+ activity.getTimes should have size (1)
+ activity.getName should be(person.getName)
+ activity.getEmail should be(person.getEmailAddress)
+ }
+}
diff --git a/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/GitTestCase.scala b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/GitTestCase.scala
new file mode 100644
index 0000000..6375b7f
--- /dev/null
+++ b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/GitTestCase.scala
@@ -0,0 +1,373 @@
+//
+// Copyright (c) 2011 Kevin Sawicki <kevinsawicki@gmail.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.analytics.test
+
+import java.io.File
+import java.io.PrintWriter
+import java.text.MessageFormat
+import java.util.Date
+import java.util
+
+import org.eclipse.jgit.api.Git
+import org.eclipse.jgit.api.MergeResult
+import org.eclipse.jgit.api.errors.GitAPIException
+import org.eclipse.jgit.lib.Constants
+import org.eclipse.jgit.lib.PersonIdent
+import org.eclipse.jgit.lib.Ref
+import org.eclipse.jgit.merge.MergeStrategy
+import org.eclipse.jgit.notes.Note
+import org.eclipse.jgit.revwalk.RevCommit
+import org.gitective.core.CommitUtils
+import org.scalatest.{BeforeAndAfterEach, Suite}
+
+
+/**
+ * Base test case with utilities for common Git operations performed during
+ * testing
+ */
+trait GitTestCase extends BeforeAndAfterEach {
+ self: Suite =>
+ /**
+ * Test repository .git directory
+ */
+ protected var testRepo: File = null
+
+ /**
+ * Author used for commits
+ */
+ protected val author = new PersonIdent("Test Author", "author@test.com")
+
+ def newPersonIdent(name: String = "Test Person", email: String = "person@test.com", ts: Date = new Date()) =
+ new PersonIdent(new PersonIdent(name, email), ts)
+
+ /**
+ * Committer used for commits
+ */
+ protected val committer = new PersonIdent("Test Committer", "committer@test.com")
+
+ /**
+ * Set up method that initializes git repository
+ *
+ *
+ */
+
+ override def beforeEach = {
+ testRepo = initRepo
+ }
+
+ /**
+ * Initialize a new repo in a new directory
+ *
+ * @return created .git folder
+ * @throws GitAPIException
+ */
+ protected def initRepo: File = {
+ val tmpDir = System.getProperty("java.io.tmpdir")
+ assert(tmpDir != null, "java.io.tmpdir was null")
+ val dir = new File(tmpDir, "git-test-case-" + System.nanoTime)
+ assert(dir.mkdir)
+ Git.init.setDirectory(dir).setBare(false).call
+ val repo = new File(dir, Constants.DOT_GIT)
+ assert(repo.exists)
+ repo.deleteOnExit()
+ repo
+ }
+
+ /**
+ * Create branch with name and checkout
+ *
+ * @param name
+ * @return branch ref
+ *
+ */
+ protected def branch(name: String): Ref = branch(testRepo, name)
+
+ /**
+ * Create branch with name and checkout
+ *
+ * @param repo
+ * @param name
+ * @return branch ref
+ *
+ */
+ protected def branch(repo: File, name: String): Ref = {
+ val git = Git.open(repo)
+ git.branchCreate.setName(name).call
+ checkout(repo, name)
+ }
+
+ /**
+ * Checkout branch
+ *
+ * @param name
+ * @return branch ref
+ *
+ */
+ protected def checkout(name: String): Ref = checkout(testRepo, name)
+
+ /**
+ * Checkout branch
+ *
+ * @param repo
+ * @param name
+ * @return branch ref
+ *
+ */
+ @throws[Exception]
+ protected def checkout(repo: File, name: String): Ref = {
+ val git = Git.open(repo)
+ val ref = git.checkout.setName(name).call
+ assert(ref != null)
+ ref
+ }
+
+ /**
+ * Create tag with name
+ *
+ * @param name
+ * @return tag ref
+ *
+ */
+ protected def tag(name: String): Ref = tag(testRepo, name)
+
+ /**
+ * Create tag with name
+ *
+ * @param repo
+ * @param name
+ * @return tag ref
+ *
+ */
+ protected def tag(repo: File, name: String): Ref = {
+ val git = Git.open(repo)
+ git.tag.setName(name).setMessage(name).call
+ val tagRef = git.getRepository.getTags.get(name)
+ assert(tagRef != null)
+ tagRef
+ }
+
+ /**
+ * Add file to test repository
+ *
+ * @param path
+ * @param content
+ * @return commit
+ *
+ */
+ protected def add(path: String, content: String, author: PersonIdent = author, committer: PersonIdent = committer): RevCommit = add(testRepo, path, content, author, committer)
+
+ /**
+ * Add file to test repository
+ *
+ * @param repo
+ * @param path
+ * @param content
+ * @return commit
+ *
+ */
+ protected def add(repo: File, path: String, content: String, author: PersonIdent, committer: PersonIdent): RevCommit = {
+ val message = MessageFormat.format("Committing {0} at {1}", path, new Date)
+ add(repo, path, content, message, author, committer)
+ }
+
+
+ /**
+ * Add file to test repository
+ *
+ * @param repo
+ * @param path
+ * @param content
+ * @param message
+ * @return commit
+ *
+ */
+ protected def add(repo: File, path: String, content: String, message: String, author: PersonIdent, committer: PersonIdent): RevCommit = {
+ val file = new File(repo.getParentFile, path)
+ if (!file.getParentFile.exists) assert(file.getParentFile.mkdirs)
+ if (!file.exists) assert(file.createNewFile)
+ val writer = new PrintWriter(file)
+ try
+ writer.print(Option(content).fold("")(identity))
+ finally writer.close()
+ val git = Git.open(repo)
+ git.add.addFilepattern(path).call
+ val commit = git.commit.setOnly(path).setMessage(message).setAuthor(author).setCommitter(committer).call
+ assert(null != commit)
+ commit
+ }
+
+ /**
+ * Move file in test repository
+ *
+ * @param from
+ * @param to
+ * @return commit
+ *
+ */
+ protected def mv(from: String, to: String): RevCommit = mv(testRepo, from, to, MessageFormat.format("Moving {0} to {1} at {2}", from, to, new Date))
+
+ /**
+ * Move file in test repository
+ *
+ * @param from
+ * @param to
+ * @param message
+ * @return commit
+ *
+ */
+ protected def mv(from: String, to: String, message: String): RevCommit = mv(testRepo, from, to, message)
+
+ /**
+ * Move file in test repository
+ *
+ * @param repo
+ * @param from
+ * @param to
+ * @param message
+ * @return commit
+ *
+ */
+ protected def mv(repo: File, from: String, to: String, message: String): RevCommit = {
+ val file = new File(repo.getParentFile, from)
+ file.renameTo(new File(repo.getParentFile, to))
+ val git = Git.open(repo)
+ git.rm.addFilepattern(from)
+ git.add.addFilepattern(to).call
+ val commit = git.commit.setAll(true).setMessage(message).setAuthor(author).setCommitter(committer).call
+ assert(null != commit)
+ commit
+ }
+
+ /**
+ * Add files to test repository
+ *
+ * @param paths
+ * @param contents
+ * @return commit
+ *
+ */
+ protected def add(paths: util.List[String], contents: util.List[String]): RevCommit = add(testRepo, paths, contents, "Committing multiple files")
+
+ /**
+ * Add files to test repository
+ *
+ * @param repo
+ * @param paths
+ * @param contents
+ * @param message
+ * @return commit
+ *
+ */
+ protected def add(repo: File, paths: util.List[String], contents: util.List[String], message: String): RevCommit = {
+ val git = Git.open(repo)
+ var i = 0
+ while ( {
+ i < paths.size
+ }) {
+ val path = paths.get(i)
+ var content = contents.get(i)
+ val file = new File(repo.getParentFile, path)
+ if (!file.getParentFile.exists) assert(file.getParentFile.mkdirs)
+ if (!file.exists) assert(file.createNewFile)
+ val writer = new PrintWriter(file)
+ if (content == null) content = ""
+ try
+ writer.print(content)
+ finally writer.close()
+ git.add.addFilepattern(path).call
+
+ {
+ i += 1;
+ i - 1
+ }
+ }
+ val commit = git.commit.setMessage(message).setAuthor(author).setCommitter(committer).call
+ assert(null != commit)
+ commit
+ }
+
+ /**
+ * Merge ref into current branch
+ *
+ * @param ref
+ * @return result
+ *
+ */
+ protected def merge(ref: String): MergeResult = {
+ val git = Git.open(testRepo)
+ git.merge.setStrategy(MergeStrategy.RESOLVE).include(CommitUtils.getCommit(git.getRepository, ref)).call
+ }
+
+ /**
+ * Add note to latest commit with given content
+ *
+ * @param content
+ * @return note
+ *
+ */
+ protected def note(content: String): Note = note(content, "commits")
+
+ /**
+ * Add note to latest commit with given content
+ *
+ * @param content
+ * @param ref
+ * @return note
+ *
+ */
+ protected def note(content: String, ref: String): Note = {
+ val git = Git.open(testRepo)
+ val note = git.notesAdd.setMessage(content).setNotesRef(Constants.R_NOTES + ref).setObjectId(CommitUtils.getHead(git.getRepository)).call
+ assert(null != note)
+ note
+ }
+
+ /**
+ * Delete and commit file at path
+ *
+ * @param path
+ * @return commit
+ *
+ */
+ protected def delete(path: String): RevCommit = {
+ val message = MessageFormat.format("Committing {0} at {1}", path, new Date)
+ val git = Git.open(testRepo)
+ git.rm.addFilepattern(path).call
+ val commit = git.commit.setOnly(path).setMessage(message).setAuthor(author).setCommitter(committer).call
+ assert(null != commit)
+ commit
+ }
+}
\ No newline at end of file