ReflogWriter.java

  1. /*
  2.  * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
  3.  * Copyright (C) 2009-2010, Google Inc.
  4.  * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
  5.  * Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org> and others
  6.  *
  7.  * This program and the accompanying materials are made available under the
  8.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  9.  * https://www.eclipse.org/org/documents/edl-v10.php.
  10.  *
  11.  * SPDX-License-Identifier: BSD-3-Clause
  12.  */
  13. package org.eclipse.jgit.internal.storage.file;

  14. import static org.eclipse.jgit.lib.Constants.HEAD;
  15. import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX;
  16. import static org.eclipse.jgit.lib.Constants.R_HEADS;
  17. import static org.eclipse.jgit.lib.Constants.R_NOTES;
  18. import static org.eclipse.jgit.lib.Constants.R_REFS;
  19. import static org.eclipse.jgit.lib.Constants.R_REMOTES;

  20. import java.io.File;
  21. import java.io.FileNotFoundException;
  22. import java.io.FileOutputStream;
  23. import java.io.IOException;
  24. import java.nio.ByteBuffer;
  25. import java.nio.channels.FileChannel;
  26. import java.text.MessageFormat;

  27. import org.eclipse.jgit.internal.JGitText;
  28. import org.eclipse.jgit.lib.ConfigConstants;
  29. import org.eclipse.jgit.lib.Constants;
  30. import org.eclipse.jgit.lib.CoreConfig;
  31. import org.eclipse.jgit.lib.ObjectId;
  32. import org.eclipse.jgit.lib.PersonIdent;
  33. import org.eclipse.jgit.lib.Ref;
  34. import org.eclipse.jgit.lib.RefUpdate;
  35. import org.eclipse.jgit.lib.ReflogEntry;
  36. import org.eclipse.jgit.lib.Repository;
  37. import org.eclipse.jgit.util.FileUtils;

  38. /**
  39.  * Utility for writing reflog entries using the traditional one-file-per-log
  40.  * format.
  41.  */
  42. public class ReflogWriter {

  43.     /**
  44.      * Get the ref name to be used for when locking a ref's log for rewriting.
  45.      *
  46.      * @param name
  47.      *            name of the ref, relative to the Git repository top level
  48.      *            directory (so typically starts with refs/).
  49.      * @return the name of the ref's lock ref.
  50.      */
  51.     public static String refLockFor(String name) {
  52.         return name + LOCK_SUFFIX;
  53.     }

  54.     private final RefDirectory refdb;

  55.     private final boolean forceWrite;

  56.     /**
  57.      * Create writer for ref directory.
  58.      *
  59.      * @param refdb
  60.      *            a {@link org.eclipse.jgit.internal.storage.file.RefDirectory}
  61.      *            object.
  62.      */
  63.     public ReflogWriter(RefDirectory refdb) {
  64.         this(refdb, false);
  65.     }

  66.     /**
  67.      * Create writer for ref directory.
  68.      *
  69.      * @param refdb
  70.      *            a {@link org.eclipse.jgit.internal.storage.file.RefDirectory}
  71.      *            object.
  72.      * @param forceWrite
  73.      *            true to write to disk all entries logged, false to respect the
  74.      *            repository's config and current log file status.
  75.      */
  76.     public ReflogWriter(RefDirectory refdb, boolean forceWrite) {
  77.         this.refdb = refdb;
  78.         this.forceWrite = forceWrite;
  79.     }

  80.     /**
  81.      * Create the log directories.
  82.      *
  83.      * @throws java.io.IOException
  84.      * @return this writer.
  85.      */
  86.     public ReflogWriter create() throws IOException {
  87.         FileUtils.mkdir(refdb.logsDir);
  88.         FileUtils.mkdir(refdb.logsRefsDir);
  89.         FileUtils.mkdir(
  90.                 new File(refdb.logsRefsDir, R_HEADS.substring(R_REFS.length())));
  91.         return this;
  92.     }

  93.     /**
  94.      * Write the given entry to the ref's log.
  95.      *
  96.      * @param refName
  97.      *            a {@link java.lang.String} object.
  98.      * @param entry
  99.      *            a {@link org.eclipse.jgit.lib.ReflogEntry} object.
  100.      * @return this writer
  101.      * @throws java.io.IOException
  102.      */
  103.     public ReflogWriter log(String refName, ReflogEntry entry)
  104.             throws IOException {
  105.         return log(refName, entry.getOldId(), entry.getNewId(), entry.getWho(),
  106.                 entry.getComment());
  107.     }

  108.     /**
  109.      * Write the given entry information to the ref's log
  110.      *
  111.      * @param refName
  112.      *            ref name
  113.      * @param oldId
  114.      *            old object id
  115.      * @param newId
  116.      *            new object id
  117.      * @param ident
  118.      *            a {@link org.eclipse.jgit.lib.PersonIdent}
  119.      * @param message
  120.      *            reflog message
  121.      * @return this writer
  122.      * @throws java.io.IOException
  123.      */
  124.     public ReflogWriter log(String refName, ObjectId oldId,
  125.             ObjectId newId, PersonIdent ident, String message) throws IOException {
  126.         byte[] encoded = encode(oldId, newId, ident, message);
  127.         return log(refName, encoded);
  128.     }

  129.     /**
  130.      * Write the given ref update to the ref's log.
  131.      *
  132.      * @param update
  133.      *            a {@link org.eclipse.jgit.lib.RefUpdate}
  134.      * @param msg
  135.      *            reflog message
  136.      * @param deref
  137.      *            whether to dereference symbolic refs
  138.      * @return this writer
  139.      * @throws java.io.IOException
  140.      */
  141.     public ReflogWriter log(RefUpdate update, String msg,
  142.             boolean deref) throws IOException {
  143.         ObjectId oldId = update.getOldObjectId();
  144.         ObjectId newId = update.getNewObjectId();
  145.         Ref ref = update.getRef();

  146.         PersonIdent ident = update.getRefLogIdent();
  147.         if (ident == null)
  148.             ident = new PersonIdent(refdb.getRepository());
  149.         else
  150.             ident = new PersonIdent(ident);

  151.         byte[] rec = encode(oldId, newId, ident, msg);
  152.         if (deref && ref.isSymbolic()) {
  153.             log(ref.getName(), rec);
  154.             log(ref.getLeaf().getName(), rec);
  155.         } else
  156.             log(ref.getName(), rec);

  157.         return this;
  158.     }

  159.     private byte[] encode(ObjectId oldId, ObjectId newId, PersonIdent ident,
  160.             String message) {
  161.         StringBuilder r = new StringBuilder();
  162.         r.append(ObjectId.toString(oldId));
  163.         r.append(' ');
  164.         r.append(ObjectId.toString(newId));
  165.         r.append(' ');
  166.         r.append(ident.toExternalString());
  167.         r.append('\t');
  168.         r.append(
  169.                 message.replace("\r\n", " ") //$NON-NLS-1$ //$NON-NLS-2$
  170.                         .replace("\n", " ")); //$NON-NLS-1$ //$NON-NLS-2$
  171.         r.append('\n');
  172.         return Constants.encode(r.toString());
  173.     }

  174.     private FileOutputStream getFileOutputStream(File log) throws IOException {
  175.         try {
  176.             return new FileOutputStream(log, true);
  177.         } catch (FileNotFoundException err) {
  178.             File dir = log.getParentFile();
  179.             if (dir.exists()) {
  180.                 throw err;
  181.             }
  182.             if (!dir.mkdirs() && !dir.isDirectory()) {
  183.                 throw new IOException(MessageFormat
  184.                         .format(JGitText.get().cannotCreateDirectory, dir));
  185.             }
  186.             return new FileOutputStream(log, true);
  187.         }
  188.     }

  189.     private ReflogWriter log(String refName, byte[] rec) throws IOException {
  190.         File log = refdb.logFor(refName);
  191.         boolean write = forceWrite
  192.                 || shouldAutoCreateLog(refName)
  193.                 || log.isFile();
  194.         if (!write)
  195.             return this;

  196.         WriteConfig wc = refdb.getRepository().getConfig().get(WriteConfig.KEY);
  197.         try (FileOutputStream out = getFileOutputStream(log)) {
  198.             if (wc.getFSyncRefFiles()) {
  199.                 FileChannel fc = out.getChannel();
  200.                 ByteBuffer buf = ByteBuffer.wrap(rec);
  201.                 while (0 < buf.remaining()) {
  202.                     fc.write(buf);
  203.                 }
  204.                 fc.force(true);
  205.             } else {
  206.                 out.write(rec);
  207.             }
  208.         }
  209.         return this;
  210.     }

  211.     private boolean shouldAutoCreateLog(String refName) {
  212.         Repository repo = refdb.getRepository();
  213.         CoreConfig.LogRefUpdates value = repo.isBare()
  214.                 ? CoreConfig.LogRefUpdates.FALSE
  215.                 : CoreConfig.LogRefUpdates.TRUE;
  216.         value = repo.getConfig().getEnum(ConfigConstants.CONFIG_CORE_SECTION,
  217.                 null, ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, value);
  218.         if (value != null) {
  219.             switch (value) {
  220.             case FALSE:
  221.                 break;
  222.             case TRUE:
  223.                 return refName.equals(HEAD) || refName.startsWith(R_HEADS)
  224.                         || refName.startsWith(R_REMOTES)
  225.                         || refName.startsWith(R_NOTES);
  226.             case ALWAYS:
  227.                 return refName.equals(HEAD) || refName.startsWith(R_REFS);
  228.             default:
  229.                 break;
  230.             }
  231.         }
  232.         return false;
  233.     }
  234. }