GcLog.java

  1. /*
  2.  * Copyright (C) 2017 Two Sigma Open Source and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */

  10. package org.eclipse.jgit.internal.storage.file;

  11. import static java.nio.charset.StandardCharsets.UTF_8;

  12. import java.io.File;
  13. import java.io.IOException;
  14. import java.nio.file.Files;
  15. import java.nio.file.NoSuchFileException;
  16. import java.nio.file.attribute.FileTime;
  17. import java.text.ParseException;
  18. import java.time.Instant;

  19. import org.eclipse.jgit.api.errors.JGitInternalException;
  20. import org.eclipse.jgit.lib.ConfigConstants;
  21. import org.eclipse.jgit.util.FileUtils;
  22. import org.eclipse.jgit.util.GitDateParser;
  23. import org.eclipse.jgit.util.SystemReader;

  24. /**
  25.  * This class manages the gc.log file for a {@link FileRepository}.
  26.  */
  27. class GcLog {
  28.     private final FileRepository repo;

  29.     private final File logFile;

  30.     private final LockFile lock;

  31.     private Instant gcLogExpire;

  32.     private static final String LOG_EXPIRY_DEFAULT = "1.day.ago"; //$NON-NLS-1$

  33.     private boolean nonEmpty = false;

  34.     /**
  35.      * Construct a GcLog object for a {@link FileRepository}
  36.      *
  37.      * @param repo
  38.      *            the repository
  39.      */
  40.     GcLog(FileRepository repo) {
  41.         this.repo = repo;
  42.         logFile = new File(repo.getDirectory(), "gc.log"); //$NON-NLS-1$
  43.         lock = new LockFile(logFile);
  44.     }

  45.     private Instant getLogExpiry() throws ParseException {
  46.         if (gcLogExpire == null) {
  47.             String logExpiryStr = repo.getConfig().getString(
  48.                     ConfigConstants.CONFIG_GC_SECTION, null,
  49.                     ConfigConstants.CONFIG_KEY_LOGEXPIRY);
  50.             if (logExpiryStr == null) {
  51.                 logExpiryStr = LOG_EXPIRY_DEFAULT;
  52.             }
  53.             gcLogExpire = GitDateParser.parse(logExpiryStr, null,
  54.                     SystemReader.getInstance().getLocale()).toInstant();
  55.         }
  56.         return gcLogExpire;
  57.     }

  58.     private boolean autoGcBlockedByOldLockFile() {
  59.         try {
  60.             FileTime lastModified = Files.getLastModifiedTime(FileUtils.toPath(logFile));
  61.             if (lastModified.toInstant().compareTo(getLogExpiry()) > 0) {
  62.                 // There is an existing log file, which is too recent to ignore
  63.                 return true;
  64.             }
  65.         } catch (NoSuchFileException e) {
  66.             // No existing log file, OK.
  67.         } catch (IOException | ParseException e) {
  68.             throw new JGitInternalException(e.getMessage(), e);
  69.         }
  70.         return false;
  71.     }

  72.     /**
  73.      * Lock the GC log file for updates
  74.      *
  75.      * @return {@code true} if we hold the lock
  76.      */
  77.     boolean lock() {
  78.         try {
  79.             if (!lock.lock()) {
  80.                 return false;
  81.             }
  82.         } catch (IOException e) {
  83.             throw new JGitInternalException(e.getMessage(), e);
  84.         }
  85.         if (autoGcBlockedByOldLockFile()) {
  86.             lock.unlock();
  87.             return false;
  88.         }
  89.         return true;
  90.     }

  91.     /**
  92.      * Unlock (roll back) the GC log lock
  93.      */
  94.     void unlock() {
  95.         lock.unlock();
  96.     }

  97.     /**
  98.      * Commit changes to the gc log, if there have been any writes. Otherwise,
  99.      * just unlock and delete the existing file (if any)
  100.      *
  101.      * @return true if committing (or unlocking/deleting) succeeds.
  102.      */
  103.     boolean commit() {
  104.         if (nonEmpty) {
  105.             return lock.commit();
  106.         }
  107.         logFile.delete();
  108.         lock.unlock();
  109.         return true;
  110.     }

  111.     /**
  112.      * Write to the pending gc log. Content will be committed upon a call to
  113.      * commit()
  114.      *
  115.      * @param content
  116.      *            The content to write
  117.      * @throws IOException
  118.      */
  119.     void write(String content) throws IOException {
  120.         if (content.length() > 0) {
  121.             nonEmpty = true;
  122.         }
  123.         lock.write(content.getBytes(UTF_8));
  124.     }
  125. }