LCOV - code coverage report
Current view: top level - server/restapi/change - GetPatch.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 74 89 83.1 %
Date: 2022-11-19 15:00:39 Functions: 9 10 90.0 %

          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             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750