Line data Source code
1 : // Copyright (C) 2013 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.restapi.change; 16 : 17 : import static com.google.gerrit.git.ObjectIds.abbreviateName; 18 : import static java.nio.charset.StandardCharsets.UTF_8; 19 : 20 : import com.google.gerrit.extensions.restapi.BinaryResult; 21 : import com.google.gerrit.extensions.restapi.ResourceConflictException; 22 : import com.google.gerrit.extensions.restapi.ResourceNotFoundException; 23 : import com.google.gerrit.extensions.restapi.Response; 24 : import com.google.gerrit.extensions.restapi.RestReadView; 25 : import com.google.gerrit.server.change.RevisionResource; 26 : import com.google.gerrit.server.git.GitRepositoryManager; 27 : import com.google.inject.Inject; 28 : import java.io.IOException; 29 : import java.io.OutputStream; 30 : import java.text.SimpleDateFormat; 31 : import java.util.Calendar; 32 : import java.util.Locale; 33 : import java.util.zip.ZipEntry; 34 : import java.util.zip.ZipOutputStream; 35 : import org.eclipse.jgit.diff.DiffFormatter; 36 : import org.eclipse.jgit.lib.PersonIdent; 37 : import org.eclipse.jgit.lib.Repository; 38 : import org.eclipse.jgit.revwalk.RevCommit; 39 : import org.eclipse.jgit.revwalk.RevWalk; 40 : import org.eclipse.jgit.treewalk.filter.PathFilter; 41 : import org.kohsuke.args4j.Option; 42 : 43 : public class GetPatch implements RestReadView<RevisionResource> { 44 : private final GitRepositoryManager repoManager; 45 : 46 : @Option(name = "--zip") 47 : private boolean zip; 48 : 49 : @Option(name = "--download") 50 : private boolean download; 51 : 52 : @Option(name = "--path") 53 : private String path; 54 : 55 : @Inject 56 86 : GetPatch(GitRepositoryManager repoManager) { 57 86 : this.repoManager = repoManager; 58 86 : } 59 : 60 : @Override 61 : public Response<BinaryResult> apply(RevisionResource rsrc) 62 : throws ResourceConflictException, IOException, ResourceNotFoundException { 63 4 : final Repository repo = repoManager.openRepository(rsrc.getProject()); 64 4 : boolean close = true; 65 : try { 66 4 : final RevWalk rw = new RevWalk(repo); 67 4 : BinaryResult bin = null; 68 : try { 69 4 : final RevCommit commit = rw.parseCommit(rsrc.getPatchSet().commitId()); 70 4 : RevCommit[] parents = commit.getParents(); 71 4 : if (parents.length > 1) { 72 0 : throw new ResourceConflictException("Revision has more than 1 parent."); 73 4 : } else if (parents.length == 0) { 74 0 : throw new ResourceConflictException("Revision has no parent."); 75 : } 76 4 : final RevCommit base = parents[0]; 77 4 : rw.parseBody(base); 78 : 79 4 : bin = 80 4 : new BinaryResult() { 81 : @Override 82 : public void writeTo(OutputStream out) throws IOException { 83 4 : if (zip) { 84 0 : ZipOutputStream zos = new ZipOutputStream(out); 85 0 : ZipEntry e = new ZipEntry(fileName(rw, commit)); 86 0 : e.setTime(commit.getCommitTime() * 1000L); 87 0 : zos.putNextEntry(e); 88 0 : format(zos); 89 0 : zos.closeEntry(); 90 0 : zos.finish(); 91 0 : } else { 92 4 : format(out); 93 : } 94 4 : } 95 : 96 : private void format(OutputStream out) throws IOException { 97 : // Only add header if no path is specified 98 4 : if (path == null) { 99 4 : out.write(formatEmailHeader(commit).getBytes(UTF_8)); 100 : } 101 4 : try (DiffFormatter fmt = new DiffFormatter(out)) { 102 4 : fmt.setRepository(repo); 103 4 : if (path != null) { 104 1 : fmt.setPathFilter(PathFilter.create(path)); 105 : } 106 4 : fmt.format(base.getTree(), commit.getTree()); 107 4 : fmt.flush(); 108 : } 109 4 : } 110 : 111 : @Override 112 : public void close() throws IOException { 113 4 : rw.close(); 114 4 : repo.close(); 115 4 : } 116 : }; 117 : 118 4 : if (path != null && bin.asString().isEmpty()) { 119 1 : throw new ResourceNotFoundException(String.format("File not found: %s.", path)); 120 : } 121 : 122 4 : if (zip) { 123 0 : bin.disableGzip() 124 0 : .setContentType("application/zip") 125 0 : .setAttachmentName(fileName(rw, commit) + ".zip"); 126 : } else { 127 4 : bin.base64() 128 4 : .setContentType("application/mbox") 129 4 : .setAttachmentName(download ? fileName(rw, commit) + ".base64" : null); 130 : } 131 : 132 4 : close = false; 133 4 : return Response.ok(bin); 134 : } finally { 135 4 : if (close) { 136 1 : rw.close(); 137 1 : if (bin != null) { 138 1 : bin.close(); 139 : } 140 : } 141 : } 142 : } finally { 143 4 : if (close) { 144 1 : repo.close(); 145 : } 146 : } 147 : } 148 : 149 : public GetPatch setPath(String path) { 150 1 : this.path = path; 151 1 : return this; 152 : } 153 : 154 : private static String formatEmailHeader(RevCommit commit) { 155 4 : StringBuilder b = new StringBuilder(); 156 4 : PersonIdent author = commit.getAuthorIdent(); 157 4 : String subject = commit.getShortMessage(); 158 4 : String msg = commit.getFullMessage().substring(subject.length()); 159 4 : if (msg.startsWith("\n\n")) { 160 4 : msg = msg.substring(2); 161 : } 162 4 : b.append("From ") 163 4 : .append(commit.getName()) 164 4 : .append(' ') 165 4 : .append( 166 : "Mon Sep 17 00:00:00 2001\n") // Fixed timestamp to match output of C Git's format-patch 167 4 : .append("From: ") 168 4 : .append(author.getName()) 169 4 : .append(" <") 170 4 : .append(author.getEmailAddress()) 171 4 : .append(">\n") 172 4 : .append("Date: ") 173 4 : .append(formatDate(author)) 174 4 : .append('\n') 175 4 : .append("Subject: [PATCH] ") 176 4 : .append(subject) 177 4 : .append('\n') 178 4 : .append('\n') 179 4 : .append(msg); 180 4 : if (!msg.endsWith("\n")) { 181 0 : b.append('\n'); 182 : } 183 4 : return b.append("---\n\n").toString(); 184 : } 185 : 186 : private static String formatDate(PersonIdent author) { 187 4 : SimpleDateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US); 188 4 : df.setCalendar(Calendar.getInstance(author.getTimeZone(), Locale.US)); 189 4 : return df.format(author.getWhen()); 190 : } 191 : 192 : private static String fileName(RevWalk rw, RevCommit commit) throws IOException { 193 0 : return abbreviateName(commit, rw.getObjectReader()) + ".diff"; 194 : } 195 : }