diff --git a/src/main/java/util/PatchUtil.java b/src/main/java/util/PatchUtil.java new file mode 100644 index 0000000..2badc30 --- /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 87eefba..5618a10 100644 --- a/src/main/scala/app/WikiController.scala +++ b/src/main/scala/app/WikiController.scala @@ -96,7 +96,7 @@ 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 998d9a5..8db09fd 100644 --- a/src/main/scala/service/WikiService.scala +++ b/src/main/scala/service/WikiService.scala @@ -3,7 +3,7 @@ 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.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 @@ */ 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 @@ } } + case class RevertInfo(operation: String, filePath: String, source: String) + // private def cloneOrPullWorkingCopy(workDir: File, owner: String, repository: String): Unit = { // if(!workDir.exists){ // Git.cloneRepository