Newer
Older
gitbucket_jkp / src / main / scala / service / WikiService.scala
@takezoe takezoe on 11 Jul 2013 7 KB Improve Git repository creation.
  1. package service
  2.  
  3. import java.io.File
  4. import java.util.Date
  5. import org.eclipse.jgit.api.Git
  6. import org.apache.commons.io.FileUtils
  7. import util.JGitUtil.DiffInfo
  8. import util.{Directory, JGitUtil}
  9. import org.eclipse.jgit.lib.RepositoryBuilder
  10. import org.eclipse.jgit.treewalk.CanonicalTreeParser
  11. import java.util.concurrent.ConcurrentHashMap
  12.  
  13. object WikiService {
  14. /**
  15. * The model for wiki page.
  16. *
  17. * @param name the page name
  18. * @param content the page content
  19. * @param committer the last committer
  20. * @param time the last modified time
  21. */
  22. case class WikiPageInfo(name: String, content: String, committer: String, time: Date)
  23. /**
  24. * The model for wiki page history.
  25. *
  26. * @param name the page name
  27. * @param committer the committer the committer
  28. * @param message the commit message
  29. * @param date the commit date
  30. */
  31. case class WikiPageHistoryInfo(name: String, committer: String, message: String, date: Date)
  32.  
  33. /**
  34. * lock objects
  35. */
  36. private val locks = new ConcurrentHashMap[String, AnyRef]()
  37.  
  38. /**
  39. * Returns the lock object for the specified repository.
  40. */
  41. private def getLockObject(owner: String, repository: String): AnyRef = synchronized {
  42. val key = owner + "/" + repository
  43. if(!locks.containsKey(key)){
  44. locks.put(key, new AnyRef())
  45. }
  46. locks.get(key)
  47. }
  48.  
  49. /**
  50. * Synchronizes a given function which modifies the working copy of the wiki repository.
  51. *
  52. * @param owner the repository owner
  53. * @param repository the repository name
  54. * @param f the function which modifies the working copy of the wiki repository
  55. * @tparam T the return type of the given function
  56. * @return the result of the given function
  57. */
  58. def lock[T](owner: String, repository: String)(f: => T): T = getLockObject(owner, repository).synchronized(f)
  59.  
  60. }
  61.  
  62. trait WikiService {
  63. import WikiService._
  64.  
  65. def createWikiRepository(owner: model.Account, repository: String): Unit = {
  66. lock(owner.userName, repository){
  67. val dir = Directory.getWikiRepositoryDir(owner.userName, repository)
  68. if(!dir.exists){
  69. try {
  70. JGitUtil.initRepository(dir)
  71. saveWikiPage(owner.userName, repository, "Home", "Home", s"Welcome to the ${repository} wiki!!", owner, "Initial Commit")
  72. } finally {
  73. // once delete cloned repository because initial cloned repository does not have 'branch.master.merge'
  74. FileUtils.deleteDirectory(Directory.getWikiWorkDir(owner.userName, repository))
  75. }
  76. }
  77. }
  78. }
  79. /**
  80. * Returns the wiki page.
  81. */
  82. def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = {
  83. JGitUtil.withGit(Directory.getWikiRepositoryDir(owner, repository)){ git =>
  84. try {
  85. JGitUtil.getFileList(git, "master", ".").find(_.name == pageName + ".md").map { file =>
  86. WikiPageInfo(file.name, new String(git.getRepository.open(file.id).getBytes, "UTF-8"), file.committer, file.time)
  87. }
  88. } catch {
  89. // TODO no commit, but it should not judge by exception.
  90. case e: NullPointerException => None
  91. }
  92. }
  93. }
  94.  
  95. /**
  96. * Returns the content of the specified file.
  97. */
  98. def getFileContent(owner: String, repository: String, path: String): Option[Array[Byte]] = {
  99. JGitUtil.withGit(Directory.getWikiRepositoryDir(owner, repository)){ git =>
  100. try {
  101. val index = path.lastIndexOf('/')
  102. val parentPath = if(index < 0) "." else path.substring(0, index)
  103. val fileName = if(index < 0) path else path.substring(index + 1)
  104.  
  105. JGitUtil.getFileList(git, "master", parentPath).find(_.name == fileName).map { file =>
  106. git.getRepository.open(file.id).getBytes
  107. }
  108. } catch {
  109. // TODO no commit, but it should not judge by exception.
  110. case e: NullPointerException => None
  111. }
  112. }
  113. }
  114.  
  115. /**
  116. * Returns the list of wiki page names.
  117. */
  118. def getWikiPageList(owner: String, repository: String): List[String] = {
  119. JGitUtil.getFileList(Git.open(Directory.getWikiRepositoryDir(owner, repository)), "master", ".")
  120. .filter(_.name.endsWith(".md"))
  121. .map(_.name.replaceFirst("\\.md$", ""))
  122. .sortBy(x => x)
  123. }
  124. /**
  125. * Save the wiki page.
  126. */
  127. def saveWikiPage(owner: String, repository: String, currentPageName: String, newPageName: String,
  128. content: String, committer: model.Account, message: String): Unit = {
  129.  
  130. lock(owner, repository){
  131. // clone working copy
  132. val workDir = Directory.getWikiWorkDir(owner, repository)
  133. cloneOrPullWorkingCopy(workDir, owner, repository)
  134.  
  135. // write as file
  136. JGitUtil.withGit(workDir){ git =>
  137. val file = new File(workDir, newPageName + ".md")
  138. val added = if(!file.exists || FileUtils.readFileToString(file, "UTF-8") != content){
  139. FileUtils.writeStringToFile(file, content, "UTF-8")
  140. git.add.addFilepattern(file.getName).call
  141. true
  142. } else {
  143. false
  144. }
  145.  
  146. // delete file
  147. val deleted = if(currentPageName != "" && currentPageName != newPageName){
  148. git.rm.addFilepattern(currentPageName + ".md").call
  149. true
  150. } else {
  151. false
  152. }
  153.  
  154. // commit and push
  155. if(added || deleted){
  156. git.commit.setCommitter(committer.userName, committer.mailAddress).setMessage(message).call
  157. git.push.call
  158. }
  159. }
  160. }
  161. }
  162.  
  163. /**
  164. * Delete the wiki page.
  165. */
  166. def deleteWikiPage(owner: String, repository: String, pageName: String, committer: String, message: String): Unit = {
  167. lock(owner, repository){
  168. // clone working copy
  169. val workDir = Directory.getWikiWorkDir(owner, repository)
  170. cloneOrPullWorkingCopy(workDir, owner, repository)
  171.  
  172. // delete file
  173. new File(workDir, pageName + ".md").delete
  174. JGitUtil.withGit(workDir){ git =>
  175. git.rm.addFilepattern(pageName + ".md").call
  176. // commit and push
  177. // TODO committer's mail address
  178. git.commit.setAuthor(committer, committer + "@devnull").setMessage(message).call
  179. git.push.call
  180. }
  181. }
  182. }
  183.  
  184. /**
  185. * Returns differences between specified commits.
  186. */
  187. def getWikiDiffs(git: Git, commitId1: String, commitId2: String): List[DiffInfo] = {
  188. // get diff between specified commit and its previous commit
  189. val reader = git.getRepository.newObjectReader
  190. val oldTreeIter = new CanonicalTreeParser
  191. oldTreeIter.reset(reader, git.getRepository.resolve(commitId1 + "^{tree}"))
  192. val newTreeIter = new CanonicalTreeParser
  193. newTreeIter.reset(reader, git.getRepository.resolve(commitId2 + "^{tree}"))
  194. import scala.collection.JavaConverters._
  195. git.diff.setNewTree(newTreeIter).setOldTree(oldTreeIter).call.asScala.map { diff =>
  196. DiffInfo(diff.getChangeType, diff.getOldPath, diff.getNewPath,
  197. JGitUtil.getContent(git, diff.getOldId.toObjectId, false).map(new String(_, "UTF-8")),
  198. JGitUtil.getContent(git, diff.getNewId.toObjectId, false).map(new String(_, "UTF-8")))
  199. }.toList
  200. }
  201.  
  202. private def cloneOrPullWorkingCopy(workDir: File, owner: String, repository: String): Unit = {
  203. if(!workDir.exists){
  204. Git.cloneRepository
  205. .setURI(Directory.getWikiRepositoryDir(owner, repository).toURI.toString)
  206. .setDirectory(workDir)
  207. .call
  208. } else {
  209. Git.open(workDir).pull.call
  210. }
  211. }
  212.  
  213. }