From 2642da3be38adb6f313f98cfef45e4c1fd13405d Mon Sep 17 00:00:00 2001 From: takezoe Date: Tue, 29 Oct 2013 09:22:37 +0900 Subject: [PATCH 01/12] (refs #180)Eliminating the working repository cloning in Wiki. --- src/main/scala/service/WikiService.scala | 294 ++++++++++++++--------- 1 file changed, 183 insertions(+), 111 deletions(-) diff --git a/src/main/scala/service/WikiService.scala b/src/main/scala/service/WikiService.scala index bce71e42c..f967519b8 100644 --- a/src/main/scala/service/WikiService.scala +++ b/src/main/scala/service/WikiService.scala @@ -5,10 +5,17 @@ import java.util.Date import org.eclipse.jgit.api.Git import org.apache.commons.io.FileUtils import util.{StringUtil, Directory, JGitUtil, LockUtil} -import util.ControlUtil._ -import org.eclipse.jgit.treewalk.CanonicalTreeParser +import _root_.util.ControlUtil._ +import org.eclipse.jgit.treewalk.{TreeWalk, CanonicalTreeParser} import org.eclipse.jgit.diff.DiffFormatter import org.eclipse.jgit.api.errors.PatchApplyException +import java.util +import org.eclipse.jgit.lib._ +import org.eclipse.jgit.dircache.{DirCache, DirCacheEntry} +import org.eclipse.jgit.merge.{ResolveMerger, MergeStrategy} +import scala.Some +import org.eclipse.jgit.revwalk.RevWalk +import org.eclipse.jgit.api.CheckoutCommand.Stage object WikiService { @@ -99,49 +106,50 @@ trait WikiService { */ def revertWikiPage(owner: String, repository: String, from: String, to: String, committer: model.Account, pageName: Option[String]): Boolean = { - LockUtil.lock(s"${owner}/${repository}/wiki"){ - defining(Directory.getWikiWorkDir(owner, repository)){ workDir => - // clone working copy - cloneOrPullWorkingCopy(workDir, owner, repository) - - using(Git.open(workDir)){ git => - val reader = git.getRepository.newObjectReader - val oldTreeIter = new CanonicalTreeParser - oldTreeIter.reset(reader, git.getRepository.resolve(from + "^{tree}")) - - val newTreeIter = new CanonicalTreeParser - newTreeIter.reset(reader, git.getRepository.resolve(to + "^{tree}")) - - import scala.collection.JavaConverters._ - val diffs = git.diff.setNewTree(oldTreeIter).setOldTree(newTreeIter).call.asScala.filter { diff => - pageName match { - case Some(x) => diff.getNewPath == x + ".md" - case None => true - } - } - - val patch = using(new java.io.ByteArrayOutputStream()){ out => - val formatter = new DiffFormatter(out) - formatter.setRepository(git.getRepository) - formatter.format(diffs.asJava) - new String(out.toByteArray, "UTF-8") - } - - try { - git.apply.setPatch(new java.io.ByteArrayInputStream(patch.getBytes("UTF-8"))).call - git.add.addFilepattern(".").call - git.commit.setCommitter(committer.fullName, committer.mailAddress).setMessage(pageName match { - case Some(x) => s"Revert ${from} ... ${to} on ${x}" - case None => s"Revert ${from} ... ${to}" - }).call - git.push.call - true - } catch { - case ex: PatchApplyException => false - } - } - } - } +// LockUtil.lock(s"${owner}/${repository}/wiki"){ +// defining(Directory.getWikiWorkDir(owner, repository)){ workDir => +// // clone working copy +// cloneOrPullWorkingCopy(workDir, owner, repository) +// +// using(Git.open(workDir)){ git => +// val reader = git.getRepository.newObjectReader +// val oldTreeIter = new CanonicalTreeParser +// oldTreeIter.reset(reader, git.getRepository.resolve(from + "^{tree}")) +// +// val newTreeIter = new CanonicalTreeParser +// newTreeIter.reset(reader, git.getRepository.resolve(to + "^{tree}")) +// +// import scala.collection.JavaConverters._ +// val diffs = git.diff.setNewTree(oldTreeIter).setOldTree(newTreeIter).call.asScala.filter { diff => +// pageName match { +// case Some(x) => diff.getNewPath == x + ".md" +// case None => true +// } +// } +// +// val patch = using(new java.io.ByteArrayOutputStream()){ out => +// val formatter = new DiffFormatter(out) +// formatter.setRepository(git.getRepository) +// formatter.format(diffs.asJava) +// new String(out.toByteArray, "UTF-8") +// } +// +// try { +// git.apply.setPatch(new java.io.ByteArrayInputStream(patch.getBytes("UTF-8"))).call +// git.add.addFilepattern(".").call +// git.commit.setCommitter(committer.fullName, committer.mailAddress).setMessage(pageName match { +// case Some(x) => s"Revert ${from} ... ${to} on ${x}" +// case None => s"Revert ${from} ... ${to}" +// }).call +// git.push.call +// true +// } catch { +// case ex: PatchApplyException => false +// } +// } +// } +// } + true } @@ -151,50 +159,114 @@ trait WikiService { def saveWikiPage(owner: String, repository: String, currentPageName: String, newPageName: String, content: String, committer: model.Account, message: String, currentId: Option[String]): Option[String] = { + import scala.collection.JavaConverters._ + + // TODO conflict detection and rename page LockUtil.lock(s"${owner}/${repository}/wiki"){ - defining(Directory.getWikiWorkDir(owner, repository)){ workDir => - // clone working copy - cloneOrPullWorkingCopy(workDir, owner, repository) + using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => + val repo = git.getRepository() + val dirCache = DirCache.newInCore() + val builder = dirCache.builder() + val inserter = repo.newObjectInserter() + val headId = repo.resolve(Constants.HEAD + "^{commit}") + var created = true - // write as file - using(Git.open(workDir)){ git => - defining(new File(workDir, newPageName + ".md")){ file => - // new page - val created = !file.exists - - // created or updated - val added = executeIf(!file.exists || FileUtils.readFileToString(file, "UTF-8") != content){ - FileUtils.writeStringToFile(file, content, "UTF-8") - git.add.addFilepattern(file.getName).call - } - - // delete file - val deleted = executeIf(currentPageName != "" && currentPageName != newPageName){ - git.rm.addFilepattern(currentPageName + ".md").call - } - - // commit and push - optionIf(added || deleted){ - defining(git.commit.setCommitter(committer.fullName, committer.mailAddress) - .setMessage(if(message.trim.length == 0){ - if(deleted){ - s"Rename ${currentPageName} to ${newPageName}" - } else if(created){ - s"Created ${newPageName}" - } else { - s"Updated ${newPageName}" - } - } else { - message - }).call){ commit => - git.push.call - Some(commit.getName) - } + using(new RevWalk(git.getRepository)){ revWalk => + val treeWalk = new TreeWalk(repo) + val hIdx = treeWalk.addTree(revWalk.parseTree(headId)) + treeWalk.setRecursive(true) + while(treeWalk.next){ + val path = treeWalk.getPathString + if(path != newPageName + ".md"){ + val hTree = treeWalk.getTree(hIdx, classOf[CanonicalTreeParser]) + val entry = new DirCacheEntry(path) + entry.setObjectId(hTree.getEntryObjectId()) + entry.setFileMode(hTree.getEntryFileMode()) + builder.add(entry) + } else { + created = false } } + treeWalk.release() } + + val entry = new DirCacheEntry(newPageName + ".md") + entry.setFileMode(FileMode.REGULAR_FILE) + entry.setObjectId(inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))) + builder.add(entry) + + builder.finish() + val treeId = dirCache.writeTree(inserter) + + val newCommit = new CommitBuilder() + newCommit.setCommitter(new PersonIdent(committer.fullName, committer.mailAddress)) + newCommit.setAuthor(new PersonIdent(committer.fullName, committer.mailAddress)) + newCommit.setMessage(if(message.trim.length == 0) { + if(created){ + s"Created ${newPageName}" + } else { + s"Updated ${newPageName}" + } + } else { + message + }) + newCommit.setParentIds(List(headId).asJava) + newCommit.setTreeId(treeId) + + val newHeadId = inserter.insert(newCommit) + inserter.flush() + + val ru = repo.updateRef(Constants.HEAD) + ru.setNewObjectId(newHeadId) + ru.update() + + Some(newHeadId.getName) } } + +// defining(Directory.getWikiWorkDir(owner, repository)){ workDir => +// // clone working copy +// cloneOrPullWorkingCopy(workDir, owner, repository) +// +// // write as file +// using(Git.open(workDir)){ git => +// defining(new File(workDir, newPageName + ".md")){ file => +// // new page +// val created = !file.exists +// +// // created or updated +// val added = executeIf(!file.exists || FileUtils.readFileToString(file, "UTF-8") != content){ +// FileUtils.writeStringToFile(file, content, "UTF-8") +// git.add.addFilepattern(file.getName).call +// } +// +// // delete file +// val deleted = executeIf(currentPageName != "" && currentPageName != newPageName){ +// git.rm.addFilepattern(currentPageName + ".md").call +// } +// +// // commit and push +// optionIf(added || deleted){ +// defining(git.commit.setCommitter(committer.fullName, committer.mailAddress) +// .setMessage(if(message.trim.length == 0){ +// if(deleted){ +// s"Rename ${currentPageName} to ${newPageName}" +// } else if(created){ +// s"Created ${newPageName}" +// } else { +// s"Updated ${newPageName}" +// } +// } else { +// message +// }).call){ commit => +// git.push.call +// Some(commit.getName) +// } +// } +// } +// } +// } +// } } /** @@ -202,36 +274,36 @@ trait WikiService { */ def deleteWikiPage(owner: String, repository: String, pageName: String, committer: String, mailAddress: String, message: String): Unit = { - LockUtil.lock(s"${owner}/${repository}/wiki"){ - defining(Directory.getWikiWorkDir(owner, repository)){ workDir => - // clone working copy - cloneOrPullWorkingCopy(workDir, owner, repository) - - // delete file - new File(workDir, pageName + ".md").delete - - using(Git.open(workDir)){ git => - git.rm.addFilepattern(pageName + ".md").call - - // commit and push - git.commit.setCommitter(committer, mailAddress).setMessage(message).call - git.push.call - } - } - } +// LockUtil.lock(s"${owner}/${repository}/wiki"){ +// defining(Directory.getWikiWorkDir(owner, repository)){ workDir => +// // clone working copy +// cloneOrPullWorkingCopy(workDir, owner, repository) +// +// // delete file +// new File(workDir, pageName + ".md").delete +// +// using(Git.open(workDir)){ git => +// git.rm.addFilepattern(pageName + ".md").call +// +// // commit and push +// git.commit.setCommitter(committer, mailAddress).setMessage(message).call +// git.push.call +// } +// } +// } } - private def cloneOrPullWorkingCopy(workDir: File, owner: String, repository: String): Unit = { - if(!workDir.exists){ - Git.cloneRepository - .setURI(Directory.getWikiRepositoryDir(owner, repository).toURI.toString) - .setDirectory(workDir) - .call - .getRepository - .close - } else using(Git.open(workDir)){ git => - git.pull.call - } - } +// private def cloneOrPullWorkingCopy(workDir: File, owner: String, repository: String): Unit = { +// if(!workDir.exists){ +// Git.cloneRepository +// .setURI(Directory.getWikiRepositoryDir(owner, repository).toURI.toString) +// .setDirectory(workDir) +// .call +// .getRepository +// .close +// } else using(Git.open(workDir)){ git => +// git.pull.call +// } +// } } From afb23069046abfb4303d010892e480b175f4f762 Mon Sep 17 00:00:00 2001 From: takezoe Date: Tue, 29 Oct 2013 11:39:38 +0900 Subject: [PATCH 02/12] (refs #180)Fix saving and deleting Wiki page. --- src/main/scala/app/WikiController.scala | 2 +- src/main/scala/service/WikiService.scala | 192 +++++++++++------------ 2 files changed, 92 insertions(+), 102 deletions(-) diff --git a/src/main/scala/app/WikiController.scala b/src/main/scala/app/WikiController.scala index 4d141de33..87eefba5f 100644 --- a/src/main/scala/app/WikiController.scala +++ b/src/main/scala/app/WikiController.scala @@ -195,7 +195,7 @@ trait WikiControllerBase extends ControllerBase with FlashMapSupport { private def conflictForEdit: Constraint = new Constraint(){ override def validate(name: String, value: String): Option[String] = { - optionIf(targetWikiPage.map(_.id != params("id")).getOrElse(true)){ + optionIf(targetWikiPage.map(_.id != params("id")).getOrElse(false)){ Some("Someone has edited the wiki since you started. Please reload this page and re-apply your changes.") } } diff --git a/src/main/scala/service/WikiService.scala b/src/main/scala/service/WikiService.scala index f967519b8..998d9a5d5 100644 --- a/src/main/scala/service/WikiService.scala +++ b/src/main/scala/service/WikiService.scala @@ -1,21 +1,17 @@ package service -import java.io.File import java.util.Date import org.eclipse.jgit.api.Git import org.apache.commons.io.FileUtils -import util.{StringUtil, Directory, JGitUtil, LockUtil} +import util.{Directory, JGitUtil, LockUtil} import _root_.util.ControlUtil._ import org.eclipse.jgit.treewalk.{TreeWalk, CanonicalTreeParser} -import org.eclipse.jgit.diff.DiffFormatter -import org.eclipse.jgit.api.errors.PatchApplyException -import java.util import org.eclipse.jgit.lib._ import org.eclipse.jgit.dircache.{DirCache, DirCacheEntry} import org.eclipse.jgit.merge.{ResolveMerger, MergeStrategy} -import scala.Some import org.eclipse.jgit.revwalk.RevWalk -import org.eclipse.jgit.api.CheckoutCommand.Stage +import scala.collection.JavaConverters._ + object WikiService { @@ -159,9 +155,6 @@ trait WikiService { def saveWikiPage(owner: String, repository: String, currentPageName: String, newPageName: String, content: String, committer: model.Account, message: String, currentId: Option[String]): Option[String] = { - import scala.collection.JavaConverters._ - - // TODO conflict detection and rename page LockUtil.lock(s"${owner}/${repository}/wiki"){ using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => val repo = git.getRepository() @@ -170,103 +163,68 @@ trait WikiService { val inserter = repo.newObjectInserter() val headId = repo.resolve(Constants.HEAD + "^{commit}") var created = true + var updated = false + var removed = false using(new RevWalk(git.getRepository)){ revWalk => val treeWalk = new TreeWalk(repo) - val hIdx = treeWalk.addTree(revWalk.parseTree(headId)) + val index = treeWalk.addTree(revWalk.parseTree(headId)) treeWalk.setRecursive(true) while(treeWalk.next){ val path = treeWalk.getPathString - if(path != newPageName + ".md"){ - val hTree = treeWalk.getTree(hIdx, classOf[CanonicalTreeParser]) + val tree = treeWalk.getTree(index, classOf[CanonicalTreeParser]) + if(path == currentPageName + ".md" && currentPageName != newPageName){ + removed = true + } else if(path != newPageName + ".md"){ val entry = new DirCacheEntry(path) - entry.setObjectId(hTree.getEntryObjectId()) - entry.setFileMode(hTree.getEntryFileMode()) + entry.setObjectId(tree.getEntryObjectId()) + entry.setFileMode(tree.getEntryFileMode()) builder.add(entry) } else { created = false + updated = JGitUtil.getContent(git, tree.getEntryObjectId, true).map(new String(_, "UTF-8") != content).getOrElse(false) } } treeWalk.release() } - val entry = new DirCacheEntry(newPageName + ".md") - entry.setFileMode(FileMode.REGULAR_FILE) - entry.setObjectId(inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))) - builder.add(entry) + optionIf(created || updated || removed){ + val entry = new DirCacheEntry(newPageName + ".md") + entry.setFileMode(FileMode.REGULAR_FILE) + entry.setObjectId(inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))) + builder.add(entry) - builder.finish() - val treeId = dirCache.writeTree(inserter) + builder.finish() + val treeId = dirCache.writeTree(inserter) - val newCommit = new CommitBuilder() - newCommit.setCommitter(new PersonIdent(committer.fullName, committer.mailAddress)) - newCommit.setAuthor(new PersonIdent(committer.fullName, committer.mailAddress)) - newCommit.setMessage(if(message.trim.length == 0) { - if(created){ - s"Created ${newPageName}" + val newCommit = new CommitBuilder() + newCommit.setCommitter(new PersonIdent(committer.fullName, committer.mailAddress)) + newCommit.setAuthor(new PersonIdent(committer.fullName, committer.mailAddress)) + newCommit.setMessage(if(message.trim.length == 0) { + if(removed){ + s"Rename ${currentPageName} to ${newPageName}" + } else if(created){ + s"Created ${newPageName}" + } else { + s"Updated ${newPageName}" + } } else { - s"Updated ${newPageName}" - } - } else { - message - }) - newCommit.setParentIds(List(headId).asJava) - newCommit.setTreeId(treeId) + message + }) + newCommit.setParentIds(List(headId).asJava) + newCommit.setTreeId(treeId) - val newHeadId = inserter.insert(newCommit) - inserter.flush() + val newHeadId = inserter.insert(newCommit) + inserter.flush() - val ru = repo.updateRef(Constants.HEAD) - ru.setNewObjectId(newHeadId) - ru.update() + val refUpdate = repo.updateRef(Constants.HEAD) + refUpdate.setNewObjectId(newHeadId) + refUpdate.update() - Some(newHeadId.getName) + Some(newHeadId.getName) + } } } - -// defining(Directory.getWikiWorkDir(owner, repository)){ workDir => -// // clone working copy -// cloneOrPullWorkingCopy(workDir, owner, repository) -// -// // write as file -// using(Git.open(workDir)){ git => -// defining(new File(workDir, newPageName + ".md")){ file => -// // new page -// val created = !file.exists -// -// // created or updated -// val added = executeIf(!file.exists || FileUtils.readFileToString(file, "UTF-8") != content){ -// FileUtils.writeStringToFile(file, content, "UTF-8") -// git.add.addFilepattern(file.getName).call -// } -// -// // delete file -// val deleted = executeIf(currentPageName != "" && currentPageName != newPageName){ -// git.rm.addFilepattern(currentPageName + ".md").call -// } -// -// // commit and push -// optionIf(added || deleted){ -// defining(git.commit.setCommitter(committer.fullName, committer.mailAddress) -// .setMessage(if(message.trim.length == 0){ -// if(deleted){ -// s"Rename ${currentPageName} to ${newPageName}" -// } else if(created){ -// s"Created ${newPageName}" -// } else { -// s"Updated ${newPageName}" -// } -// } else { -// message -// }).call){ commit => -// git.push.call -// Some(commit.getName) -// } -// } -// } -// } -// } -// } } /** @@ -274,23 +232,55 @@ trait WikiService { */ def deleteWikiPage(owner: String, repository: String, pageName: String, committer: String, mailAddress: String, message: String): Unit = { -// LockUtil.lock(s"${owner}/${repository}/wiki"){ -// defining(Directory.getWikiWorkDir(owner, repository)){ workDir => -// // clone working copy -// cloneOrPullWorkingCopy(workDir, owner, repository) -// -// // delete file -// new File(workDir, pageName + ".md").delete -// -// using(Git.open(workDir)){ git => -// git.rm.addFilepattern(pageName + ".md").call -// -// // commit and push -// git.commit.setCommitter(committer, mailAddress).setMessage(message).call -// git.push.call -// } -// } -// } + LockUtil.lock(s"${owner}/${repository}/wiki"){ + using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => + val repo = git.getRepository() + val dirCache = DirCache.newInCore() + val builder = dirCache.builder() + val inserter = repo.newObjectInserter() + val headId = repo.resolve(Constants.HEAD + "^{commit}") + var removed = false + + using(new RevWalk(git.getRepository)){ revWalk => + val treeWalk = new TreeWalk(repo) + val index = treeWalk.addTree(revWalk.parseTree(headId)) + treeWalk.setRecursive(true) + while(treeWalk.next){ + val path = treeWalk.getPathString + val tree = treeWalk.getTree(index, classOf[CanonicalTreeParser]) + if(path != pageName + ".md"){ + val entry = new DirCacheEntry(path) + entry.setObjectId(tree.getEntryObjectId()) + entry.setFileMode(tree.getEntryFileMode()) + builder.add(entry) + } else { + removed = true + } + } + treeWalk.release() + } + + if(removed){ + builder.finish() + val treeId = dirCache.writeTree(inserter) + + val newCommit = new CommitBuilder() + newCommit.setCommitter(new PersonIdent(committer, mailAddress)) + newCommit.setAuthor(new PersonIdent(committer, mailAddress)) + newCommit.setMessage(message) + newCommit.setParentIds(List(headId).asJava) + newCommit.setTreeId(treeId) + + val newHeadId = inserter.insert(newCommit) + inserter.flush() + + val refUpdate = repo.updateRef(Constants.HEAD) + refUpdate.setNewObjectId(newHeadId) + refUpdate.update() + + } + } + } } // private def cloneOrPullWorkingCopy(workDir: File, owner: String, repository: String): Unit = { From 382c5c55ec15ce9424f7d2ad6197b1c9a08b8c0b Mon Sep 17 00:00:00 2001 From: takezoe Date: Tue, 29 Oct 2013 11:52:42 +0900 Subject: [PATCH 03/12] Remove unused import statement. --- src/main/scala/view/helpers.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/scala/view/helpers.scala b/src/main/scala/view/helpers.scala index 9c74fe3c0..a504329fc 100644 --- a/src/main/scala/view/helpers.scala +++ b/src/main/scala/view/helpers.scala @@ -57,7 +57,6 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache value } - import scala.util.matching.Regex import scala.util.matching.Regex._ implicit class RegexReplaceString(s: String) { def replaceAll(pattern: String, replacer: (Match) => String): String = { From 599a8080540b7258936f95776987404dbf34fcb6 Mon Sep 17 00:00:00 2001 From: takezoe Date: Tue, 29 Oct 2013 11:53:05 +0900 Subject: [PATCH 04/12] Fix a link to the committer page. --- src/main/twirl/wiki/history.scala.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/twirl/wiki/history.scala.html b/src/main/twirl/wiki/history.scala.html index 22d0b37e6..ece4df9c8 100644 --- a/src/main/twirl/wiki/history.scala.html +++ b/src/main/twirl/wiki/history.scala.html @@ -35,7 +35,7 @@ @commits.map { commit => - @avatar(commit.committer, 20) @commit.committer + @avatar(commit, 20) @user(commit.committer, commit.mailAddress) @datetime(commit.time): @commit.shortMessage From 68b25ddbb5872ed1ff15bf89e070da60a6364a4b Mon Sep 17 00:00:00 2001 From: takezoe Date: Wed, 30 Oct 2013 08:20:17 +0900 Subject: [PATCH 05/12] (refs #180)Implementing reverting from history without ApplyCommand. --- src/main/java/util/PatchUtil.java | 93 ++++++++++++++++++++++++ src/main/scala/app/WikiController.scala | 2 +- src/main/scala/service/WikiService.scala | 85 +++++++++++++++++++++- 3 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 src/main/java/util/PatchUtil.java diff --git a/src/main/java/util/PatchUtil.java b/src/main/java/util/PatchUtil.java new file mode 100644 index 000000000..2badc3095 --- /dev/null +++ b/src/main/java/util/PatchUtil.java @@ -0,0 +1,93 @@ +package util; + +import org.eclipse.jgit.api.errors.PatchApplyException; +import org.eclipse.jgit.diff.RawText; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.patch.FileHeader; +import org.eclipse.jgit.patch.HunkHeader; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; + +/** + * This class helps to apply patch. Most of these code came from {@link org.eclipse.jgit.api.ApplyCommand}. + */ +public class PatchUtil { + + public static String apply(String source, FileHeader fh) + throws IOException, PatchApplyException { + RawText rt = new RawText(source.getBytes("UTF-8")); + List oldLines = new ArrayList(rt.size()); + for (int i = 0; i < rt.size(); i++) + oldLines.add(rt.getString(i)); + List newLines = new ArrayList(oldLines); + for (HunkHeader hh : fh.getHunks()) { + StringBuilder hunk = new StringBuilder(); + for (int j = hh.getStartOffset(); j < hh.getEndOffset(); j++) + hunk.append((char) hh.getBuffer()[j]); + RawText hrt = new RawText(hunk.toString().getBytes("UTF-8")); + List hunkLines = new ArrayList(hrt.size()); + for (int i = 0; i < hrt.size(); i++) + hunkLines.add(hrt.getString(i)); + int pos = 0; + for (int j = 1; j < hunkLines.size(); j++) { + String hunkLine = hunkLines.get(j); + switch (hunkLine.charAt(0)) { + case ' ': + if (!newLines.get(hh.getNewStartLine() - 1 + pos).equals( + hunkLine.substring(1))) { + throw new PatchApplyException(MessageFormat.format( + JGitText.get().patchApplyException, hh)); + } + pos++; + break; + case '-': + if (!newLines.get(hh.getNewStartLine() - 1 + pos).equals( + hunkLine.substring(1))) { + throw new PatchApplyException(MessageFormat.format( + JGitText.get().patchApplyException, hh)); + } + newLines.remove(hh.getNewStartLine() - 1 + pos); + break; + case '+': + newLines.add(hh.getNewStartLine() - 1 + pos, + hunkLine.substring(1)); + pos++; + break; + } + } + } + if (!isNoNewlineAtEndOfFile(fh)) + newLines.add(""); //$NON-NLS-1$ + if (!rt.isMissingNewlineAtEnd()) + oldLines.add(""); //$NON-NLS-1$ + if (!isChanged(oldLines, newLines)) + return null; // don't touch the file + StringBuilder sb = new StringBuilder(); + for (String l : newLines) { + // don't bother handling line endings - if it was windows, the \r is + // still there! + sb.append(l).append('\n'); + } + sb.deleteCharAt(sb.length() - 1); + return sb.toString(); + } + + private static boolean isChanged(List ol, List nl) { + if (ol.size() != nl.size()) + return true; + for (int i = 0; i < ol.size(); i++) + if (!ol.get(i).equals(nl.get(i))) + return true; + return false; + } + + private static boolean isNoNewlineAtEndOfFile(FileHeader fh) { + HunkHeader lastHunk = fh.getHunks().get(fh.getHunks().size() - 1); + RawText lhrt = new RawText(lastHunk.getBuffer()); + return lhrt.getString(lhrt.size() - 1).equals( + "\\ No newline at end of file"); //$NON-NLS-1$ + } +} diff --git a/src/main/scala/app/WikiController.scala b/src/main/scala/app/WikiController.scala index 87eefba5f..5618a1035 100644 --- a/src/main/scala/app/WikiController.scala +++ b/src/main/scala/app/WikiController.scala @@ -96,7 +96,7 @@ trait WikiControllerBase extends ControllerBase with FlashMapSupport { val Array(from, to) = params("commitId").split("\\.\\.\\.") if(revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, None)){ - redirect(s"/${repository.owner}/${repository.name}/wiki/}") + redirect(s"/${repository.owner}/${repository.name}/wiki/") } else { flash += "info" -> "This patch was not able to be reversed." redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/${from}...${to}") diff --git a/src/main/scala/service/WikiService.scala b/src/main/scala/service/WikiService.scala index 998d9a5d5..8db09fd9c 100644 --- a/src/main/scala/service/WikiService.scala +++ b/src/main/scala/service/WikiService.scala @@ -3,7 +3,7 @@ package service import java.util.Date import org.eclipse.jgit.api.Git import org.apache.commons.io.FileUtils -import util.{Directory, JGitUtil, LockUtil} +import util.{PatchUtil, Directory, JGitUtil, LockUtil} import _root_.util.ControlUtil._ import org.eclipse.jgit.treewalk.{TreeWalk, CanonicalTreeParser} import org.eclipse.jgit.lib._ @@ -11,6 +11,11 @@ import org.eclipse.jgit.dircache.{DirCache, DirCacheEntry} import org.eclipse.jgit.merge.{ResolveMerger, MergeStrategy} import org.eclipse.jgit.revwalk.RevWalk import scala.collection.JavaConverters._ +import org.eclipse.jgit.diff.{DiffEntry, DiffFormatter} +import java.io.ByteArrayInputStream +import org.eclipse.jgit.patch._ +import org.eclipse.jgit.api.errors.PatchFormatException +import scala.collection.JavaConverters._ object WikiService { @@ -102,6 +107,82 @@ trait WikiService { */ def revertWikiPage(owner: String, repository: String, from: String, to: String, committer: model.Account, pageName: Option[String]): Boolean = { + + LockUtil.lock(s"${owner}/${repository}/wiki"){ + using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => + val reader = git.getRepository.newObjectReader + val oldTreeIter = new CanonicalTreeParser + oldTreeIter.reset(reader, git.getRepository.resolve(from + "^{tree}")) + + val newTreeIter = new CanonicalTreeParser + newTreeIter.reset(reader, git.getRepository.resolve(to + "^{tree}")) + + import scala.collection.JavaConverters._ + val diffs = git.diff.setNewTree(oldTreeIter).setOldTree(newTreeIter).call.asScala.filter { diff => + pageName match { + case Some(x) => diff.getNewPath == x + ".md" + case None => true + } + } + + val patch = using(new java.io.ByteArrayOutputStream()){ out => + val formatter = new DiffFormatter(out) + formatter.setRepository(git.getRepository) + formatter.format(diffs.asJava) + new String(out.toByteArray, "UTF-8") + } + + val p = new Patch() + p.parse(new ByteArrayInputStream(patch.getBytes("UTF-8"))) + if(!p.getErrors.isEmpty){ + throw new PatchFormatException(p.getErrors()) + } + val revertInfo = (p.getFiles.asScala.map { fh => + fh.getChangeType match { + case DiffEntry.ChangeType.MODIFY => { + val page = getWikiPage(owner, repository, fh.getNewPath.replaceFirst("\\.md$", "")).get + Seq(RevertInfo("ADD", fh.getNewPath, PatchUtil.apply(page.content, fh))) + } + case DiffEntry.ChangeType.ADD => { + Seq(RevertInfo("ADD", fh.getNewPath, PatchUtil.apply("", fh))) + } + case DiffEntry.ChangeType.DELETE => { + Seq(RevertInfo("DELETE", fh.getNewPath, "")) + } + case DiffEntry.ChangeType.RENAME => { + Seq( + RevertInfo("DELETE", fh.getOldPath, ""), + RevertInfo("ADD", fh.getNewPath, PatchUtil.apply("", fh)) + ) + } + case _ => Nil + } + }).flatten + + revertInfo.foreach { revert => + println(revert) + } + +// val source = getWikiPage(owner, repository, pageName.get) +// PatchUtil.applyToFile(PatchUtil.createPatch(patch), source.get.content, pageName + ".md") + +// try { +// git.apply.setPatch(new java.io.ByteArrayInputStream(patch.getBytes("UTF-8"))).call +// git.add.addFilepattern(".").call +// git.commit.setCommitter(committer.fullName, committer.mailAddress).setMessage(pageName match { +// case Some(x) => s"Revert ${from} ... ${to} on ${x}" +// case None => s"Revert ${from} ... ${to}" +// }).call +// git.push.call +// true +// } catch { +// case ex: PatchApplyException => false +// } + } + } + + + // LockUtil.lock(s"${owner}/${repository}/wiki"){ // defining(Directory.getWikiWorkDir(owner, repository)){ workDir => // // clone working copy @@ -283,6 +364,8 @@ trait WikiService { } } + case class RevertInfo(operation: String, filePath: String, source: String) + // private def cloneOrPullWorkingCopy(workDir: File, owner: String, repository: String): Unit = { // if(!workDir.exists){ // Git.cloneRepository From ef3e7d9286deb0e762acb883093df69c282d4cf4 Mon Sep 17 00:00:00 2001 From: takezoe Date: Wed, 30 Oct 2013 11:13:10 +0900 Subject: [PATCH 06/12] (refs #180)Reverting from history without working repository is completed. --- src/main/scala/service/WikiService.scala | 389 ++++++++++------------- 1 file changed, 169 insertions(+), 220 deletions(-) diff --git a/src/main/scala/service/WikiService.scala b/src/main/scala/service/WikiService.scala index 8db09fd9c..eb77e4bb2 100644 --- a/src/main/scala/service/WikiService.scala +++ b/src/main/scala/service/WikiService.scala @@ -8,9 +8,7 @@ import _root_.util.ControlUtil._ import org.eclipse.jgit.treewalk.{TreeWalk, CanonicalTreeParser} import org.eclipse.jgit.lib._ import org.eclipse.jgit.dircache.{DirCache, DirCacheEntry} -import org.eclipse.jgit.merge.{ResolveMerger, MergeStrategy} import org.eclipse.jgit.revwalk.RevWalk -import scala.collection.JavaConverters._ import org.eclipse.jgit.diff.{DiffEntry, DiffFormatter} import java.io.ByteArrayInputStream import org.eclipse.jgit.patch._ @@ -108,201 +106,159 @@ trait WikiService { def revertWikiPage(owner: String, repository: String, from: String, to: String, committer: model.Account, pageName: Option[String]): Boolean = { - LockUtil.lock(s"${owner}/${repository}/wiki"){ - using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => - val reader = git.getRepository.newObjectReader - val oldTreeIter = new CanonicalTreeParser - oldTreeIter.reset(reader, git.getRepository.resolve(from + "^{tree}")) + case class RevertInfo(operation: String, filePath: String, source: String) - val newTreeIter = new CanonicalTreeParser - newTreeIter.reset(reader, git.getRepository.resolve(to + "^{tree}")) + try { + LockUtil.lock(s"${owner}/${repository}/wiki"){ + using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => - import scala.collection.JavaConverters._ - val diffs = git.diff.setNewTree(oldTreeIter).setOldTree(newTreeIter).call.asScala.filter { diff => - pageName match { - case Some(x) => diff.getNewPath == x + ".md" - case None => true + val reader = git.getRepository.newObjectReader + val oldTreeIter = new CanonicalTreeParser + oldTreeIter.reset(reader, git.getRepository.resolve(from + "^{tree}")) + + val newTreeIter = new CanonicalTreeParser + newTreeIter.reset(reader, git.getRepository.resolve(to + "^{tree}")) + + val diffs = git.diff.setNewTree(oldTreeIter).setOldTree(newTreeIter).call.asScala.filter { diff => + pageName match { + case Some(x) => diff.getNewPath == x + ".md" + case None => true + } + } + + val patch = using(new java.io.ByteArrayOutputStream()){ out => + val formatter = new DiffFormatter(out) + formatter.setRepository(git.getRepository) + formatter.format(diffs.asJava) + new String(out.toByteArray, "UTF-8") + } + + val p = new Patch() + p.parse(new ByteArrayInputStream(patch.getBytes("UTF-8"))) + if(!p.getErrors.isEmpty){ + throw new PatchFormatException(p.getErrors()) + } + val revertInfo = (p.getFiles.asScala.map { fh => + fh.getChangeType match { + case DiffEntry.ChangeType.MODIFY => { + val source = getWikiPage(owner, repository, fh.getNewPath.replaceFirst("\\.md$", "")).map(_.content).getOrElse("") + val applied = PatchUtil.apply(source, fh) + if(applied != null){ + Seq(RevertInfo("ADD", fh.getNewPath, applied)) + } else Nil + } + case DiffEntry.ChangeType.ADD => { + val applied = PatchUtil.apply("", fh) + if(applied != null){ + Seq(RevertInfo("ADD", fh.getNewPath, applied)) + } else Nil + } + case DiffEntry.ChangeType.DELETE => { + Seq(RevertInfo("DELETE", fh.getNewPath, "")) + } + case DiffEntry.ChangeType.RENAME => { + val applied = PatchUtil.apply("", fh) + if(applied != null){ + Seq(RevertInfo("DELETE", fh.getOldPath, ""), RevertInfo("ADD", fh.getNewPath, applied)) + } else { + Seq(RevertInfo("DELETE", fh.getOldPath, "")) + } + } + case _ => Nil + } + }).flatten + + if(revertInfo.nonEmpty){ + val builder = DirCache.newInCore.builder() + val inserter = git.getRepository.newObjectInserter() + val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") + + using(new RevWalk(git.getRepository)){ revWalk => + using(new TreeWalk(git.getRepository)){ treeWalk => + val index = treeWalk.addTree(revWalk.parseTree(headId)) + treeWalk.setRecursive(true) + while(treeWalk.next){ + val path = treeWalk.getPathString + val tree = treeWalk.getTree(index, classOf[CanonicalTreeParser]) + if(revertInfo.find(x => x.filePath == path).isEmpty){ + builder.add(createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) + } + } + } + } + + revertInfo.filter(_.operation == "ADD").foreach { x => + builder.add(createDirCacheEntry(x.filePath, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, x.source.getBytes("UTF-8")))) + } + builder.finish() + + createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer.fullName, committer.mailAddress, + pageName match { + case Some(x) => s"Revert ${from} ... ${to} on ${x}" + case None => s"Revert ${from} ... ${to}" + }) } } - - val patch = using(new java.io.ByteArrayOutputStream()){ out => - val formatter = new DiffFormatter(out) - formatter.setRepository(git.getRepository) - formatter.format(diffs.asJava) - new String(out.toByteArray, "UTF-8") - } - - val p = new Patch() - p.parse(new ByteArrayInputStream(patch.getBytes("UTF-8"))) - if(!p.getErrors.isEmpty){ - throw new PatchFormatException(p.getErrors()) - } - val revertInfo = (p.getFiles.asScala.map { fh => - fh.getChangeType match { - case DiffEntry.ChangeType.MODIFY => { - val page = getWikiPage(owner, repository, fh.getNewPath.replaceFirst("\\.md$", "")).get - Seq(RevertInfo("ADD", fh.getNewPath, PatchUtil.apply(page.content, fh))) - } - case DiffEntry.ChangeType.ADD => { - Seq(RevertInfo("ADD", fh.getNewPath, PatchUtil.apply("", fh))) - } - case DiffEntry.ChangeType.DELETE => { - Seq(RevertInfo("DELETE", fh.getNewPath, "")) - } - case DiffEntry.ChangeType.RENAME => { - Seq( - RevertInfo("DELETE", fh.getOldPath, ""), - RevertInfo("ADD", fh.getNewPath, PatchUtil.apply("", fh)) - ) - } - case _ => Nil - } - }).flatten - - revertInfo.foreach { revert => - println(revert) - } - -// val source = getWikiPage(owner, repository, pageName.get) -// PatchUtil.applyToFile(PatchUtil.createPatch(patch), source.get.content, pageName + ".md") - -// try { -// git.apply.setPatch(new java.io.ByteArrayInputStream(patch.getBytes("UTF-8"))).call -// git.add.addFilepattern(".").call -// git.commit.setCommitter(committer.fullName, committer.mailAddress).setMessage(pageName match { -// case Some(x) => s"Revert ${from} ... ${to} on ${x}" -// case None => s"Revert ${from} ... ${to}" -// }).call -// git.push.call -// true -// } catch { -// case ex: PatchApplyException => false -// } + } + true + } catch { + case e: Exception => { + e.printStackTrace() + false } } - - - -// LockUtil.lock(s"${owner}/${repository}/wiki"){ -// defining(Directory.getWikiWorkDir(owner, repository)){ workDir => -// // clone working copy -// cloneOrPullWorkingCopy(workDir, owner, repository) -// -// using(Git.open(workDir)){ git => -// val reader = git.getRepository.newObjectReader -// val oldTreeIter = new CanonicalTreeParser -// oldTreeIter.reset(reader, git.getRepository.resolve(from + "^{tree}")) -// -// val newTreeIter = new CanonicalTreeParser -// newTreeIter.reset(reader, git.getRepository.resolve(to + "^{tree}")) -// -// import scala.collection.JavaConverters._ -// val diffs = git.diff.setNewTree(oldTreeIter).setOldTree(newTreeIter).call.asScala.filter { diff => -// pageName match { -// case Some(x) => diff.getNewPath == x + ".md" -// case None => true -// } -// } -// -// val patch = using(new java.io.ByteArrayOutputStream()){ out => -// val formatter = new DiffFormatter(out) -// formatter.setRepository(git.getRepository) -// formatter.format(diffs.asJava) -// new String(out.toByteArray, "UTF-8") -// } -// -// try { -// git.apply.setPatch(new java.io.ByteArrayInputStream(patch.getBytes("UTF-8"))).call -// git.add.addFilepattern(".").call -// git.commit.setCommitter(committer.fullName, committer.mailAddress).setMessage(pageName match { -// case Some(x) => s"Revert ${from} ... ${to} on ${x}" -// case None => s"Revert ${from} ... ${to}" -// }).call -// git.push.call -// true -// } catch { -// case ex: PatchApplyException => false -// } -// } -// } -// } - true } - /** * Save the wiki page. */ def saveWikiPage(owner: String, repository: String, currentPageName: String, newPageName: String, content: String, committer: model.Account, message: String, currentId: Option[String]): Option[String] = { - LockUtil.lock(s"${owner}/${repository}/wiki"){ using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => - val repo = git.getRepository() - val dirCache = DirCache.newInCore() - val builder = dirCache.builder() - val inserter = repo.newObjectInserter() - val headId = repo.resolve(Constants.HEAD + "^{commit}") + val builder = DirCache.newInCore.builder() + val inserter = git.getRepository.newObjectInserter() + val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") var created = true var updated = false var removed = false using(new RevWalk(git.getRepository)){ revWalk => - val treeWalk = new TreeWalk(repo) - val index = treeWalk.addTree(revWalk.parseTree(headId)) - treeWalk.setRecursive(true) - while(treeWalk.next){ - val path = treeWalk.getPathString - val tree = treeWalk.getTree(index, classOf[CanonicalTreeParser]) - if(path == currentPageName + ".md" && currentPageName != newPageName){ - removed = true - } else if(path != newPageName + ".md"){ - val entry = new DirCacheEntry(path) - entry.setObjectId(tree.getEntryObjectId()) - entry.setFileMode(tree.getEntryFileMode()) - builder.add(entry) - } else { - created = false - updated = JGitUtil.getContent(git, tree.getEntryObjectId, true).map(new String(_, "UTF-8") != content).getOrElse(false) + using(new TreeWalk(git.getRepository)){ treeWalk => + val index = treeWalk.addTree(revWalk.parseTree(headId)) + treeWalk.setRecursive(true) + while(treeWalk.next){ + val path = treeWalk.getPathString + val tree = treeWalk.getTree(index, classOf[CanonicalTreeParser]) + if(path == currentPageName + ".md" && currentPageName != newPageName){ + removed = true + } else if(path != newPageName + ".md"){ + builder.add(createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) + } else { + created = false + updated = JGitUtil.getContent(git, tree.getEntryObjectId, true).map(new String(_, "UTF-8") != content).getOrElse(false) + } } } - treeWalk.release() } optionIf(created || updated || removed){ - val entry = new DirCacheEntry(newPageName + ".md") - entry.setFileMode(FileMode.REGULAR_FILE) - entry.setObjectId(inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))) - builder.add(entry) - + builder.add(createDirCacheEntry(newPageName + ".md", FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8")))) builder.finish() - val treeId = dirCache.writeTree(inserter) - - val newCommit = new CommitBuilder() - newCommit.setCommitter(new PersonIdent(committer.fullName, committer.mailAddress)) - newCommit.setAuthor(new PersonIdent(committer.fullName, committer.mailAddress)) - newCommit.setMessage(if(message.trim.length == 0) { - if(removed){ - s"Rename ${currentPageName} to ${newPageName}" - } else if(created){ - s"Created ${newPageName}" + val newHeadId = createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer.fullName, committer.mailAddress, + if(message.trim.length == 0) { + if(removed){ + s"Rename ${currentPageName} to ${newPageName}" + } else if(created){ + s"Created ${newPageName}" + } else { + s"Updated ${newPageName}" + } } else { - s"Updated ${newPageName}" - } - } else { - message - }) - newCommit.setParentIds(List(headId).asJava) - newCommit.setTreeId(treeId) + message + }) - val newHeadId = inserter.insert(newCommit) - inserter.flush() - - val refUpdate = repo.updateRef(Constants.HEAD) - refUpdate.setNewObjectId(newHeadId) - refUpdate.update() - - Some(newHeadId.getName) + Some(newHeadId) } } } @@ -315,68 +271,61 @@ trait WikiService { committer: String, mailAddress: String, message: String): Unit = { LockUtil.lock(s"${owner}/${repository}/wiki"){ using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => - val repo = git.getRepository() - val dirCache = DirCache.newInCore() - val builder = dirCache.builder() - val inserter = repo.newObjectInserter() - val headId = repo.resolve(Constants.HEAD + "^{commit}") + val builder = DirCache.newInCore.builder() + val inserter = git.getRepository.newObjectInserter() + val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") var removed = false using(new RevWalk(git.getRepository)){ revWalk => - val treeWalk = new TreeWalk(repo) - val index = treeWalk.addTree(revWalk.parseTree(headId)) - treeWalk.setRecursive(true) - while(treeWalk.next){ - val path = treeWalk.getPathString - val tree = treeWalk.getTree(index, classOf[CanonicalTreeParser]) - if(path != pageName + ".md"){ - val entry = new DirCacheEntry(path) - entry.setObjectId(tree.getEntryObjectId()) - entry.setFileMode(tree.getEntryFileMode()) - builder.add(entry) - } else { - removed = true + using(new TreeWalk(git.getRepository)){ treeWalk => + val index = treeWalk.addTree(revWalk.parseTree(headId)) + treeWalk.setRecursive(true) + while(treeWalk.next){ + val path = treeWalk.getPathString + val tree = treeWalk.getTree(index, classOf[CanonicalTreeParser]) + if(path != pageName + ".md"){ + builder.add(createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) + } else { + removed = true + } } } - treeWalk.release() - } - - if(removed){ - builder.finish() - val treeId = dirCache.writeTree(inserter) - - val newCommit = new CommitBuilder() - newCommit.setCommitter(new PersonIdent(committer, mailAddress)) - newCommit.setAuthor(new PersonIdent(committer, mailAddress)) - newCommit.setMessage(message) - newCommit.setParentIds(List(headId).asJava) - newCommit.setTreeId(treeId) - - val newHeadId = inserter.insert(newCommit) - inserter.flush() - - val refUpdate = repo.updateRef(Constants.HEAD) - refUpdate.setNewObjectId(newHeadId) - refUpdate.update() + if(removed){ + builder.finish() + createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer, mailAddress, message) + } } } } } - case class RevertInfo(operation: String, filePath: String, source: String) + // This method should be moved to JGitUtil? + private def createDirCacheEntry(path: String, mode: FileMode, objectId: ObjectId): DirCacheEntry = { + val entry = new DirCacheEntry(path) + entry.setFileMode(mode) + entry.setObjectId(objectId) + entry + } -// private def cloneOrPullWorkingCopy(workDir: File, owner: String, repository: String): Unit = { -// if(!workDir.exists){ -// Git.cloneRepository -// .setURI(Directory.getWikiRepositoryDir(owner, repository).toURI.toString) -// .setDirectory(workDir) -// .call -// .getRepository -// .close -// } else using(Git.open(workDir)){ git => -// git.pull.call -// } -// } + // This method should be moved to JGitUtil? + private def createNewCommit(git: Git, inserter: ObjectInserter, headId: AnyObjectId, treeId: AnyObjectId, + fullName: String, mailAddress: String, message: String): String = { + val newCommit = new CommitBuilder() + newCommit.setCommitter(new PersonIdent(fullName, mailAddress)) + newCommit.setAuthor(new PersonIdent(fullName, mailAddress)) + newCommit.setMessage(message) + newCommit.setParentIds(List(headId).asJava) + newCommit.setTreeId(treeId) + + val newHeadId = inserter.insert(newCommit) + inserter.flush() + + val refUpdate = git.getRepository.updateRef(Constants.HEAD) + refUpdate.setNewObjectId(newHeadId) + refUpdate.update() + + newHeadId.getName + } } From e79ded934ffa1899ed4c9dd1ac84d23bc1630fc4 Mon Sep 17 00:00:00 2001 From: takezoe Date: Wed, 30 Oct 2013 11:37:53 +0900 Subject: [PATCH 07/12] Display selected page differences only. --- src/main/scala/app/WikiController.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/app/WikiController.scala b/src/main/scala/app/WikiController.scala index 5618a1035..e7eaf23ff 100644 --- a/src/main/scala/app/WikiController.scala +++ b/src/main/scala/app/WikiController.scala @@ -66,7 +66,7 @@ trait WikiControllerBase extends ControllerBase with FlashMapSupport { val Array(from, to) = params("commitId").split("\\.\\.\\.") using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git => - wiki.html.compare(Some(pageName), from, to, JGitUtil.getDiffs(git, from, to, true), repository, + wiki.html.compare(Some(pageName), from, to, JGitUtil.getDiffs(git, from, to, true).filter(_.newPath == pageName + ".md"), repository, hasWritePermission(repository.owner, repository.name, context.loginAccount), flash.get("info")) } }) From a1dc19fa2605e4ce92d6a0f2959065c0ae768d1e Mon Sep 17 00:00:00 2001 From: takezoe Date: Wed, 30 Oct 2013 11:39:55 +0900 Subject: [PATCH 08/12] (refs #180)Remove Directory#getWikiWorkDir() --- src/main/scala/service/WikiService.scala | 9 ++------- src/main/scala/util/Directory.scala | 6 ------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/main/scala/service/WikiService.scala b/src/main/scala/service/WikiService.scala index eb77e4bb2..2a3cd6ec1 100644 --- a/src/main/scala/service/WikiService.scala +++ b/src/main/scala/service/WikiService.scala @@ -48,13 +48,8 @@ trait WikiService { LockUtil.lock(s"${owner}/${repository}/wiki"){ defining(Directory.getWikiRepositoryDir(owner, repository)){ dir => if(!dir.exists){ - try { - JGitUtil.initRepository(dir) - saveWikiPage(owner, repository, "Home", "Home", s"Welcome to the ${repository} wiki!!", loginAccount, "Initial Commit", None) - } finally { - // once delete cloned repository because initial cloned repository does not have 'branch.master.merge' - FileUtils.deleteDirectory(Directory.getWikiWorkDir(owner, repository)) - } + JGitUtil.initRepository(dir) + saveWikiPage(owner, repository, "Home", "Home", s"Welcome to the ${repository} wiki!!", loginAccount, "Initial Commit", None) } } } diff --git a/src/main/scala/util/Directory.scala b/src/main/scala/util/Directory.scala index 540996257..f7f064a58 100644 --- a/src/main/scala/util/Directory.scala +++ b/src/main/scala/util/Directory.scala @@ -70,11 +70,5 @@ object Directory { */ def getWikiRepositoryDir(owner: String, repository: String): File = new File(s"${RepositoryHome}/${owner}/${repository}.wiki.git") - - /** - * Wiki working directory which is cloned from the wiki repository. - */ - def getWikiWorkDir(owner: String, repository: String): File = - new File(getTemporaryDir(owner, repository), "wiki") } \ No newline at end of file From 9e5a302ab1ed5df81faf616b4de4987e92bdd620 Mon Sep 17 00:00:00 2001 From: takezoe Date: Wed, 30 Oct 2013 13:19:25 +0900 Subject: [PATCH 09/12] (refs #180)Fix a problem about multi-byte characters. --- src/main/java/util/PatchUtil.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/util/PatchUtil.java b/src/main/java/util/PatchUtil.java index 2badc3095..afc99b31d 100644 --- a/src/main/java/util/PatchUtil.java +++ b/src/main/java/util/PatchUtil.java @@ -3,9 +3,11 @@ package util; import org.eclipse.jgit.api.errors.PatchApplyException; import org.eclipse.jgit.diff.RawText; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.patch.FileHeader; import org.eclipse.jgit.patch.HunkHeader; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; @@ -16,7 +18,7 @@ import java.util.List; */ public class PatchUtil { - public static String apply(String source, FileHeader fh) + public static String apply(String source, String patch, FileHeader fh) throws IOException, PatchApplyException { RawText rt = new RawText(source.getBytes("UTF-8")); List oldLines = new ArrayList(rt.size()); @@ -24,10 +26,9 @@ public class PatchUtil { oldLines.add(rt.getString(i)); List newLines = new ArrayList(oldLines); for (HunkHeader hh : fh.getHunks()) { - StringBuilder hunk = new StringBuilder(); - for (int j = hh.getStartOffset(); j < hh.getEndOffset(); j++) - hunk.append((char) hh.getBuffer()[j]); - RawText hrt = new RawText(hunk.toString().getBytes("UTF-8")); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + out.write(patch.getBytes("UTF-8"), hh.getStartOffset(), hh.getEndOffset() - hh.getStartOffset()); + RawText hrt = new RawText(out.toByteArray()); List hunkLines = new ArrayList(hrt.size()); for (int i = 0; i < hrt.size(); i++) hunkLines.add(hrt.getString(i)); From f160952817bd506c38a6f91601cf09af80b185fb Mon Sep 17 00:00:00 2001 From: takezoe Date: Wed, 30 Oct 2013 13:20:13 +0900 Subject: [PATCH 10/12] Remove unused import statement. --- src/main/java/util/PatchUtil.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/util/PatchUtil.java b/src/main/java/util/PatchUtil.java index afc99b31d..a316a75a4 100644 --- a/src/main/java/util/PatchUtil.java +++ b/src/main/java/util/PatchUtil.java @@ -3,7 +3,6 @@ package util; import org.eclipse.jgit.api.errors.PatchApplyException; import org.eclipse.jgit.diff.RawText; import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.patch.FileHeader; import org.eclipse.jgit.patch.HunkHeader; From 35c8f02f902e34c0e20fe918f86c47fb25212035 Mon Sep 17 00:00:00 2001 From: takezoe Date: Wed, 30 Oct 2013 13:22:54 +0900 Subject: [PATCH 11/12] (refs #180)Fix compilation error. --- src/main/scala/service/WikiService.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/service/WikiService.scala b/src/main/scala/service/WikiService.scala index 2a3cd6ec1..3a8903142 100644 --- a/src/main/scala/service/WikiService.scala +++ b/src/main/scala/service/WikiService.scala @@ -137,13 +137,13 @@ trait WikiService { fh.getChangeType match { case DiffEntry.ChangeType.MODIFY => { val source = getWikiPage(owner, repository, fh.getNewPath.replaceFirst("\\.md$", "")).map(_.content).getOrElse("") - val applied = PatchUtil.apply(source, fh) + val applied = PatchUtil.apply(source, patch, fh) if(applied != null){ Seq(RevertInfo("ADD", fh.getNewPath, applied)) } else Nil } case DiffEntry.ChangeType.ADD => { - val applied = PatchUtil.apply("", fh) + val applied = PatchUtil.apply("", patch, fh) if(applied != null){ Seq(RevertInfo("ADD", fh.getNewPath, applied)) } else Nil @@ -152,7 +152,7 @@ trait WikiService { Seq(RevertInfo("DELETE", fh.getNewPath, "")) } case DiffEntry.ChangeType.RENAME => { - val applied = PatchUtil.apply("", fh) + val applied = PatchUtil.apply("", patch, fh) if(applied != null){ Seq(RevertInfo("DELETE", fh.getOldPath, ""), RevertInfo("ADD", fh.getNewPath, applied)) } else { From bd0ecd0a9d734fc6e1222f560d8bf5e1e00b4ab4 Mon Sep 17 00:00:00 2001 From: takezoe Date: Wed, 30 Oct 2013 14:52:55 +0900 Subject: [PATCH 12/12] Improve repository creation to not use the working repository. --- .../app/CreateRepositoryController.scala | 41 ++++++----- src/main/scala/service/WikiService.scala | 72 ++++++------------- src/main/scala/util/Directory.scala | 9 --- src/main/scala/util/JGitUtil.scala | 29 ++++++++ 4 files changed, 72 insertions(+), 79 deletions(-) diff --git a/src/main/scala/app/CreateRepositoryController.scala b/src/main/scala/app/CreateRepositoryController.scala index a3c52d005..c596b8c6f 100644 --- a/src/main/scala/app/CreateRepositoryController.scala +++ b/src/main/scala/app/CreateRepositoryController.scala @@ -8,7 +8,8 @@ import java.io.File import org.eclipse.jgit.api.Git import org.apache.commons.io._ import jp.sf.amateras.scalatra.forms._ -import org.eclipse.jgit.lib.PersonIdent +import org.eclipse.jgit.lib.{FileMode, Constants, PersonIdent} +import org.eclipse.jgit.dircache.DirCache class CreateRepositoryController extends CreateRepositoryControllerBase with RepositoryService with AccountService with WikiService with LabelsService with ActivityService @@ -73,28 +74,26 @@ trait CreateRepositoryControllerBase extends ControllerBase { JGitUtil.initRepository(gitdir) if(form.createReadme){ - FileUtil.withTmpDir(getInitRepositoryDir(form.owner, form.name)){ tmpdir => - // Clone the repository - Git.cloneRepository.setURI(gitdir.toURI.toString).setDirectory(tmpdir).call + using(Git.open(gitdir)){ git => + val builder = DirCache.newInCore.builder() + val inserter = git.getRepository.newObjectInserter() + val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") + val content = if(form.description.nonEmpty){ + form.name + "\n" + + "===============\n" + + "\n" + + form.description.get + } else { + form.name + "\n" + + "===============\n" + } - // Create README.md - FileUtils.writeStringToFile(new File(tmpdir, "README.md"), - if(form.description.nonEmpty){ - form.name + "\n" + - "===============\n" + - "\n" + - form.description.get - } else { - form.name + "\n" + - "===============\n" - }, "UTF-8") + builder.add(JGitUtil.createDirCacheEntry("README.md", FileMode.REGULAR_FILE, + inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8")))) + builder.finish() - val git = Git.open(tmpdir) - git.add.addFilepattern("README.md").call - git.commit - .setCommitter(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)) - .setMessage("Initial commit").call - git.push.call + JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), + loginAccount.fullName, loginAccount.mailAddress, "Initial commit") } } diff --git a/src/main/scala/service/WikiService.scala b/src/main/scala/service/WikiService.scala index 3a8903142..4b8a05c76 100644 --- a/src/main/scala/service/WikiService.scala +++ b/src/main/scala/service/WikiService.scala @@ -176,18 +176,18 @@ trait WikiService { val path = treeWalk.getPathString val tree = treeWalk.getTree(index, classOf[CanonicalTreeParser]) if(revertInfo.find(x => x.filePath == path).isEmpty){ - builder.add(createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) + builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) } } } } revertInfo.filter(_.operation == "ADD").foreach { x => - builder.add(createDirCacheEntry(x.filePath, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, x.source.getBytes("UTF-8")))) + builder.add(JGitUtil.createDirCacheEntry(x.filePath, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, x.source.getBytes("UTF-8")))) } builder.finish() - createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer.fullName, committer.mailAddress, + JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer.fullName, committer.mailAddress, pageName match { case Some(x) => s"Revert ${from} ... ${to} on ${x}" case None => s"Revert ${from} ... ${to}" @@ -218,29 +218,31 @@ trait WikiService { var updated = false var removed = false - using(new RevWalk(git.getRepository)){ revWalk => - using(new TreeWalk(git.getRepository)){ treeWalk => - val index = treeWalk.addTree(revWalk.parseTree(headId)) - treeWalk.setRecursive(true) - while(treeWalk.next){ - val path = treeWalk.getPathString - val tree = treeWalk.getTree(index, classOf[CanonicalTreeParser]) - if(path == currentPageName + ".md" && currentPageName != newPageName){ - removed = true - } else if(path != newPageName + ".md"){ - builder.add(createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) - } else { - created = false - updated = JGitUtil.getContent(git, tree.getEntryObjectId, true).map(new String(_, "UTF-8") != content).getOrElse(false) + if(headId != null){ + using(new RevWalk(git.getRepository)){ revWalk => + using(new TreeWalk(git.getRepository)){ treeWalk => + val index = treeWalk.addTree(revWalk.parseTree(headId)) + treeWalk.setRecursive(true) + while(treeWalk.next){ + val path = treeWalk.getPathString + val tree = treeWalk.getTree(index, classOf[CanonicalTreeParser]) + if(path == currentPageName + ".md" && currentPageName != newPageName){ + removed = true + } else if(path != newPageName + ".md"){ + builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) + } else { + created = false + updated = JGitUtil.getContent(git, tree.getEntryObjectId, true).map(new String(_, "UTF-8") != content).getOrElse(false) + } } } } } optionIf(created || updated || removed){ - builder.add(createDirCacheEntry(newPageName + ".md", FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8")))) + builder.add(JGitUtil.createDirCacheEntry(newPageName + ".md", FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8")))) builder.finish() - val newHeadId = createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer.fullName, committer.mailAddress, + val newHeadId = JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer.fullName, committer.mailAddress, if(message.trim.length == 0) { if(removed){ s"Rename ${currentPageName} to ${newPageName}" @@ -279,7 +281,7 @@ trait WikiService { val path = treeWalk.getPathString val tree = treeWalk.getTree(index, classOf[CanonicalTreeParser]) if(path != pageName + ".md"){ - builder.add(createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) + builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) } else { removed = true } @@ -288,39 +290,11 @@ trait WikiService { if(removed){ builder.finish() - createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer, mailAddress, message) + JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer, mailAddress, message) } } } } } - // This method should be moved to JGitUtil? - private def createDirCacheEntry(path: String, mode: FileMode, objectId: ObjectId): DirCacheEntry = { - val entry = new DirCacheEntry(path) - entry.setFileMode(mode) - entry.setObjectId(objectId) - entry - } - - // This method should be moved to JGitUtil? - private def createNewCommit(git: Git, inserter: ObjectInserter, headId: AnyObjectId, treeId: AnyObjectId, - fullName: String, mailAddress: String, message: String): String = { - val newCommit = new CommitBuilder() - newCommit.setCommitter(new PersonIdent(fullName, mailAddress)) - newCommit.setAuthor(new PersonIdent(fullName, mailAddress)) - newCommit.setMessage(message) - newCommit.setParentIds(List(headId).asJava) - newCommit.setTreeId(treeId) - - val newHeadId = inserter.insert(newCommit) - inserter.flush() - - val refUpdate = git.getRepository.updateRef(Constants.HEAD) - refUpdate.setNewObjectId(newHeadId) - refUpdate.update() - - newHeadId.getName - } - } diff --git a/src/main/scala/util/Directory.scala b/src/main/scala/util/Directory.scala index f7f064a58..383a3be2e 100644 --- a/src/main/scala/util/Directory.scala +++ b/src/main/scala/util/Directory.scala @@ -56,15 +56,6 @@ object Directory { def getDownloadWorkDir(owner: String, repository: String, sessionId: String): File = new File(getTemporaryDir(owner, repository), s"download/${sessionId}") - /** - * Temporary directory which is used in the repository creation. - * - * GitBucket generates initial repository contents in this directory and push them. - * This directory is removed after the repository creation. - */ - def getInitRepositoryDir(owner: String, repository: String): File = - new File(getTemporaryDir(owner, repository), "init") - /** * Substance directory of the wiki repository. */ diff --git a/src/main/scala/util/JGitUtil.scala b/src/main/scala/util/JGitUtil.scala index c282e9a57..d54f4fe0d 100644 --- a/src/main/scala/util/JGitUtil.scala +++ b/src/main/scala/util/JGitUtil.scala @@ -15,6 +15,7 @@ import org.eclipse.jgit.errors.MissingObjectException import java.util.Date import org.eclipse.jgit.api.errors.NoHeadException import service.RepositoryService +import org.eclipse.jgit.dircache.DirCacheEntry /** * Provides complex JGit operations. @@ -464,4 +465,32 @@ object JGitUtil { }.find(_._1 != null) } + def createDirCacheEntry(path: String, mode: FileMode, objectId: ObjectId): DirCacheEntry = { + val entry = new DirCacheEntry(path) + entry.setFileMode(mode) + entry.setObjectId(objectId) + entry + } + + def createNewCommit(git: Git, inserter: ObjectInserter, headId: AnyObjectId, treeId: AnyObjectId, + fullName: String, mailAddress: String, message: String): String = { + val newCommit = new CommitBuilder() + newCommit.setCommitter(new PersonIdent(fullName, mailAddress)) + newCommit.setAuthor(new PersonIdent(fullName, mailAddress)) + newCommit.setMessage(message) + if(headId != null){ + newCommit.setParentIds(List(headId).asJava) + } + newCommit.setTreeId(treeId) + + val newHeadId = inserter.insert(newCommit) + inserter.flush() + + val refUpdate = git.getRepository.updateRef(Constants.HEAD) + refUpdate.setNewObjectId(newHeadId) + refUpdate.update() + + newHeadId.getName + } + }