StashDropCommand.java

  1. /*
  2.  * Copyright (C) 2012, GitHub Inc. 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.api;

  11. import static org.eclipse.jgit.lib.Constants.R_STASH;

  12. import java.io.File;
  13. import java.io.IOException;
  14. import java.nio.file.StandardCopyOption;
  15. import java.text.MessageFormat;
  16. import java.util.List;

  17. import org.eclipse.jgit.api.errors.GitAPIException;
  18. import org.eclipse.jgit.api.errors.InvalidRefNameException;
  19. import org.eclipse.jgit.api.errors.JGitInternalException;
  20. import org.eclipse.jgit.api.errors.RefNotFoundException;
  21. import org.eclipse.jgit.errors.LockFailedException;
  22. import org.eclipse.jgit.internal.JGitText;
  23. import org.eclipse.jgit.internal.storage.file.RefDirectory;
  24. import org.eclipse.jgit.internal.storage.file.ReflogWriter;
  25. import org.eclipse.jgit.lib.ObjectId;
  26. import org.eclipse.jgit.lib.Ref;
  27. import org.eclipse.jgit.lib.RefUpdate;
  28. import org.eclipse.jgit.lib.RefUpdate.Result;
  29. import org.eclipse.jgit.lib.ReflogEntry;
  30. import org.eclipse.jgit.lib.ReflogReader;
  31. import org.eclipse.jgit.lib.Repository;
  32. import org.eclipse.jgit.util.FileUtils;

  33. /**
  34.  * Command class to delete a stashed commit reference
  35.  * <p>
  36.  * Currently only supported on a traditional file repository using
  37.  * one-file-per-ref reflogs.
  38.  *
  39.  * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-stash.html"
  40.  *      >Git documentation about Stash</a>
  41.  * @since 2.0
  42.  */
  43. public class StashDropCommand extends GitCommand<ObjectId> {

  44.     private int stashRefEntry;

  45.     private boolean all;

  46.     /**
  47.      * Constructor for StashDropCommand.
  48.      *
  49.      * @param repo
  50.      *            a {@link org.eclipse.jgit.lib.Repository} object.
  51.      */
  52.     public StashDropCommand(Repository repo) {
  53.         super(repo);
  54.         if (!(repo.getRefDatabase() instanceof RefDirectory)) {
  55.             throw new UnsupportedOperationException(
  56.                     JGitText.get().stashDropNotSupported);
  57.         }
  58.     }

  59.     /**
  60.      * Set the stash reference to drop (0-based).
  61.      * <p>
  62.      * This will default to drop the latest stashed commit (stash@{0}) if
  63.      * unspecified
  64.      *
  65.      * @param stashRef
  66.      *            the 0-based index of the stash reference
  67.      * @return {@code this}
  68.      */
  69.     public StashDropCommand setStashRef(int stashRef) {
  70.         if (stashRef < 0)
  71.             throw new IllegalArgumentException();

  72.         stashRefEntry = stashRef;
  73.         return this;
  74.     }

  75.     /**
  76.      * Set whether to drop all stashed commits
  77.      *
  78.      * @param all
  79.      *            {@code true} to drop all stashed commits, {@code false} to
  80.      *            drop only the stashed commit set via calling
  81.      *            {@link #setStashRef(int)}
  82.      * @return {@code this}
  83.      */
  84.     public StashDropCommand setAll(boolean all) {
  85.         this.all = all;
  86.         return this;
  87.     }

  88.     private Ref getRef() throws GitAPIException {
  89.         try {
  90.             return repo.exactRef(R_STASH);
  91.         } catch (IOException e) {
  92.             throw new InvalidRefNameException(MessageFormat.format(
  93.                     JGitText.get().cannotRead, R_STASH), e);
  94.         }
  95.     }

  96.     private RefUpdate createRefUpdate(Ref stashRef) throws IOException {
  97.         RefUpdate update = repo.updateRef(R_STASH);
  98.         update.disableRefLog();
  99.         update.setExpectedOldObjectId(stashRef.getObjectId());
  100.         update.setForceUpdate(true);
  101.         return update;
  102.     }

  103.     private void deleteRef(Ref stashRef) {
  104.         try {
  105.             Result result = createRefUpdate(stashRef).delete();
  106.             if (Result.FORCED != result)
  107.                 throw new JGitInternalException(MessageFormat.format(
  108.                         JGitText.get().stashDropDeleteRefFailed, result));
  109.         } catch (IOException e) {
  110.             throw new JGitInternalException(JGitText.get().stashDropFailed, e);
  111.         }
  112.     }

  113.     private void updateRef(Ref stashRef, ObjectId newId) {
  114.         try {
  115.             RefUpdate update = createRefUpdate(stashRef);
  116.             update.setNewObjectId(newId);
  117.             Result result = update.update();
  118.             switch (result) {
  119.             case FORCED:
  120.             case NEW:
  121.             case NO_CHANGE:
  122.                 return;
  123.             default:
  124.                 throw new JGitInternalException(MessageFormat.format(
  125.                         JGitText.get().updatingRefFailed, R_STASH, newId,
  126.                         result));
  127.             }
  128.         } catch (IOException e) {
  129.             throw new JGitInternalException(JGitText.get().stashDropFailed, e);
  130.         }
  131.     }

  132.     /**
  133.      * {@inheritDoc}
  134.      * <p>
  135.      * Drop the configured entry from the stash reflog and return value of the
  136.      * stash reference after the drop occurs
  137.      */
  138.     @Override
  139.     public ObjectId call() throws GitAPIException {
  140.         checkCallable();

  141.         Ref stashRef = getRef();
  142.         if (stashRef == null)
  143.             return null;

  144.         if (all) {
  145.             deleteRef(stashRef);
  146.             return null;
  147.         }

  148.         List<ReflogEntry> entries;
  149.         try {
  150.             ReflogReader reader = repo.getReflogReader(R_STASH);
  151.             if (reader == null) {
  152.                 throw new RefNotFoundException(MessageFormat
  153.                         .format(JGitText.get().refNotResolved, stashRef));
  154.             }
  155.             entries = reader.getReverseEntries();
  156.         } catch (IOException e) {
  157.             throw new JGitInternalException(JGitText.get().stashDropFailed, e);
  158.         }

  159.         if (stashRefEntry >= entries.size())
  160.             throw new JGitInternalException(
  161.                     JGitText.get().stashDropMissingReflog);

  162.         if (entries.size() == 1) {
  163.             deleteRef(stashRef);
  164.             return null;
  165.         }

  166.         RefDirectory refdb = (RefDirectory) repo.getRefDatabase();
  167.         ReflogWriter writer = new ReflogWriter(refdb, true);
  168.         String stashLockRef = ReflogWriter.refLockFor(R_STASH);
  169.         File stashLockFile = refdb.logFor(stashLockRef);
  170.         File stashFile = refdb.logFor(R_STASH);
  171.         if (stashLockFile.exists())
  172.             throw new JGitInternalException(JGitText.get().stashDropFailed,
  173.                     new LockFailedException(stashFile));

  174.         entries.remove(stashRefEntry);
  175.         ObjectId entryId = ObjectId.zeroId();
  176.         try {
  177.             for (int i = entries.size() - 1; i >= 0; i--) {
  178.                 ReflogEntry entry = entries.get(i);
  179.                 writer.log(stashLockRef, entryId, entry.getNewId(),
  180.                         entry.getWho(), entry.getComment());
  181.                 entryId = entry.getNewId();
  182.             }
  183.             try {
  184.                 FileUtils.rename(stashLockFile, stashFile,
  185.                         StandardCopyOption.ATOMIC_MOVE);
  186.             } catch (IOException e) {
  187.                     throw new JGitInternalException(MessageFormat.format(
  188.                             JGitText.get().renameFileFailed,
  189.                                 stashLockFile.getPath(), stashFile.getPath()),
  190.                         e);
  191.             }
  192.         } catch (IOException e) {
  193.             throw new JGitInternalException(JGitText.get().stashDropFailed, e);
  194.         }
  195.         updateRef(stashRef, entryId);

  196.         try {
  197.             Ref newStashRef = repo.exactRef(R_STASH);
  198.             return newStashRef != null ? newStashRef.getObjectId() : null;
  199.         } catch (IOException e) {
  200.             throw new InvalidRefNameException(MessageFormat.format(
  201.                     JGitText.get().cannotRead, R_STASH), e);
  202.         }
  203.     }
  204. }