blob: 1449a35f2e4efc222d6e89f42b291ed87e789257 [file] [log] [blame]
//
// 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, PrintWriter}
import java.nio.file.Files
import java.text.MessageFormat
import java.util.Date
import com.googlesource.gerrit.plugins.analytics.common.DateConversions.isoStringToLongDate
import com.googlesource.gerrit.plugins.analytics.common.ManagedResource.use
import org.eclipse.jgit.api.MergeCommand.FastForwardMode
import org.eclipse.jgit.api.{Git, MergeResult}
import org.eclipse.jgit.api.errors.GitAPIException
import org.eclipse.jgit.lib.{Constants, PersonIdent, 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}
import com.googlesource.gerrit.plugins.analytics.common.DateConversions._
/**
* 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 dir = Files.createTempDirectory(threadSpecificDirectoryPrefix).toFile
Git.init.setDirectory(dir).setBare(false).call
val repo = new File(dir, Constants.DOT_GIT)
assert(repo.exists)
repo.deleteOnExit()
repo
}
private def threadSpecificDirectoryPrefix =
"git-test-case-" + Thread.currentThread().getName.replaceAll("~[0-9a-zA-Z]", "_") + System.nanoTime
/**
* Create branch with name and checkout
*
* @param name
* @return branch ref
*
*/
protected def branch(name: String): Ref = branch(testRepo, name)
/**
* Delete branch with name
*
* @param name
* @return branch ref
*
*/
protected def deleteBranch(name: String): String = deleteBranch(testRepo, name)
/**
* Create branch with name and checkout
*
* @param repo
* @param name
* @return branch ref
*
*/
protected def branch(repo: File, name: String): Ref = {
use(Git.open(repo)) { git =>
git.branchCreate.setName(name).call
checkout(repo, name)
}
}
/**
* Delete branch with name
*
* @param repo
* @param name
* @return branch ref
*
*/
protected def deleteBranch(repo: File, name: String): String = {
use(Git.open(repo)) { git =>
git.branchDelete().setBranchNames(name).call.get(0)
}
}
/**
* 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 = {
use(Git.open(repo)) { git =>
git.checkout.setName(name).call
}
} ensuring(_ != null, "Unable to checkout result")
/**
* 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 = {
use(Git.open(repo)) { git =>
git.tag.setName(name).setMessage(name).call
git.getRepository.getTags.get(name)
}
} ensuring(_ != null, s"Unable to tag file $name")
/**
* 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()
use(Git.open(repo)) { git =>
git.add.addFilepattern(path).call
git.commit.setOnly(path).setMessage(message).setAuthor(author).setCommitter(committer).call
}
} ensuring (_ != null, s"Unable to commit addition of path $path")
/**
* 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))
use(Git.open(testRepo)) { git =>
git.rm.addFilepattern(from)
git.add.addFilepattern(to).call
git.commit.setAll(true).setMessage(message).setAuthor(author).setCommitter(committer).call
}
} ensuring (_ != null, "Unable to commit MV operation")
/**
* Add files to test repository
*
* @param contents iterable of file names and associated content
* @return commit
*
*/
protected def add(contents: Iterable[(String, String)]): RevCommit = add(testRepo, contents, "Committing multiple files")
/**
* Add files to test repository
*
* @param repo
* @param contents iterable of file names and associated content
* @param message
* @return commit
*
*/
protected def add(repo: File, contents: Iterable[(String, String)], message: String): RevCommit = {
use(Git.open(testRepo)) { git =>
var i = 0
contents.foreach { case (path, content) =>
val file = new File(repo.getParentFile, path)
if (!file.getParentFile.exists) require(file.getParentFile.mkdirs, s"Cannot create parent dir '${file.getParent}'")
if (!file.exists) require(file.createNewFile, s"Cannot create file '$file'")
val writer = new PrintWriter(file)
try
writer.print(content)
finally writer.close()
git.add.addFilepattern(path).call
}
git.commit.setMessage(message).setAuthor(author).setCommitter(committer).call
}
} ensuring (_ != null, "Unable to commit content addition")
/**
* Merge given branch into current branch
*
* @param branch
* @return result
*
*/
protected def mergeBranch(branch: String, withCommit: Boolean): MergeResult = {
use(Git.open(testRepo)) { git =>
git.merge.setStrategy(MergeStrategy.RESOLVE).include(CommitUtils.getRef(git.getRepository, branch)).setCommit(withCommit).setFastForward(FastForwardMode.NO_FF).setMessage(s"merging branch $branch").call
}
}
/**
* Merge ref into current branch
*
* @param ref
* @return result
*
*/
protected def merge(ref: String): MergeResult = {
use(Git.open(testRepo)) { git =>
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 = {
use(Git.open(testRepo)) { git =>
git.notesAdd.setMessage(content).setNotesRef(Constants.R_NOTES + ref).setObjectId(CommitUtils.getHead(git.getRepository)).call
}
} ensuring (_ != null, "Unable to add 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)
use(Git.open(testRepo)) { git =>
git.rm.addFilepattern(path).call
git.commit.setOnly(path).setMessage(message).setAuthor(author).setCommitter(committer).call
}
} ensuring (_ != null, "Unable to commit delete operation")
/**
* commit specified content into a file, as committer
*
* @param committer - the author of this commit
* @param fileName - the name of the file
* @param content - the content of the file
* @param when - the date of the commit
* @return RevCommit
*
*/
protected def commit(committer: String, fileName: String, content: String, when: Date = new Date(), message: Option[String] = None): RevCommit = {
val person = newPersonIdent(committer, committer, when)
add(testRepo, fileName, content, author = person, committer = author, message = message.getOrElse("** no message **"))
}
/**
* commit specified content into a file, as committer and merge into current branch
*
* @param committer - the author of this commit
* @param fileName - the name of the file
* @param content - the content of the file
* @return MergeResult
*
*/
protected def mergeCommit(committer: String, fileName: String, content: String): MergeResult = {
val currentBranch = Git.open(testRepo).getRepository.getBranch
val tmpBranch = branch(testRepo, "tmp")
try {
commit(committer, fileName, content)
checkout(currentBranch)
mergeBranch(tmpBranch.getName, withCommit = true)
} finally {
deleteBranch(testRepo, tmpBranch.getName)
}
}
}