View Javadoc
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  
12  import static org.eclipse.jgit.lib.Constants.R_STASH;
13  
14  import java.io.File;
15  import java.io.IOException;
16  import java.nio.file.StandardCopyOption;
17  import java.text.MessageFormat;
18  import java.util.List;
19  
20  import org.eclipse.jgit.api.errors.GitAPIException;
21  import org.eclipse.jgit.api.errors.InvalidRefNameException;
22  import org.eclipse.jgit.api.errors.JGitInternalException;
23  import org.eclipse.jgit.api.errors.RefNotFoundException;
24  import org.eclipse.jgit.errors.LockFailedException;
25  import org.eclipse.jgit.internal.JGitText;
26  import org.eclipse.jgit.internal.storage.file.RefDirectory;
27  import org.eclipse.jgit.internal.storage.file.ReflogWriter;
28  import org.eclipse.jgit.lib.ObjectId;
29  import org.eclipse.jgit.lib.Ref;
30  import org.eclipse.jgit.lib.RefUpdate;
31  import org.eclipse.jgit.lib.RefUpdate.Result;
32  import org.eclipse.jgit.lib.ReflogEntry;
33  import org.eclipse.jgit.lib.ReflogReader;
34  import org.eclipse.jgit.lib.Repository;
35  import org.eclipse.jgit.util.FileUtils;
36  
37  /**
38   * Command class to delete a stashed commit reference
39   * <p>
40   * Currently only supported on a traditional file repository using
41   * one-file-per-ref reflogs.
42   *
43   * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-stash.html"
44   *      >Git documentation about Stash</a>
45   * @since 2.0
46   */
47  public class StashDropCommand extends GitCommand<ObjectId> {
48  
49  	private int stashRefEntry;
50  
51  	private boolean all;
52  
53  	/**
54  	 * Constructor for StashDropCommand.
55  	 *
56  	 * @param repo
57  	 *            a {@link org.eclipse.jgit.lib.Repository} object.
58  	 */
59  	public StashDropCommand(Repository repo) {
60  		super(repo);
61  		if (!(repo.getRefDatabase() instanceof RefDirectory)) {
62  			throw new UnsupportedOperationException(
63  					JGitText.get().stashDropNotSupported);
64  		}
65  	}
66  
67  	/**
68  	 * Set the stash reference to drop (0-based).
69  	 * <p>
70  	 * This will default to drop the latest stashed commit (stash@{0}) if
71  	 * unspecified
72  	 *
73  	 * @param stashRef
74  	 *            the 0-based index of the stash reference
75  	 * @return {@code this}
76  	 */
77  	public StashDropCommand setStashRef(int stashRef) {
78  		if (stashRef < 0)
79  			throw new IllegalArgumentException();
80  
81  		stashRefEntry = stashRef;
82  		return this;
83  	}
84  
85  	/**
86  	 * Set whether to drop all stashed commits
87  	 *
88  	 * @param all
89  	 *            {@code true} to drop all stashed commits, {@code false} to
90  	 *            drop only the stashed commit set via calling
91  	 *            {@link #setStashRef(int)}
92  	 * @return {@code this}
93  	 */
94  	public StashDropCommand setAll(boolean all) {
95  		this.all = all;
96  		return this;
97  	}
98  
99  	private Ref getRef() throws GitAPIException {
100 		try {
101 			return repo.exactRef(R_STASH);
102 		} catch (IOException e) {
103 			throw new InvalidRefNameException(MessageFormat.format(
104 					JGitText.get().cannotRead, R_STASH), e);
105 		}
106 	}
107 
108 	private RefUpdate createRefUpdate(Ref stashRef) throws IOException {
109 		RefUpdate update = repo.updateRef(R_STASH);
110 		update.disableRefLog();
111 		update.setExpectedOldObjectId(stashRef.getObjectId());
112 		update.setForceUpdate(true);
113 		return update;
114 	}
115 
116 	private void deleteRef(Ref stashRef) {
117 		try {
118 			Result result = createRefUpdate(stashRef).delete();
119 			if (Result.FORCED != result)
120 				throw new JGitInternalException(MessageFormat.format(
121 						JGitText.get().stashDropDeleteRefFailed, result));
122 		} catch (IOException e) {
123 			throw new JGitInternalException(JGitText.get().stashDropFailed, e);
124 		}
125 	}
126 
127 	private void updateRef(Ref stashRef, ObjectId newId) {
128 		try {
129 			RefUpdate update = createRefUpdate(stashRef);
130 			update.setNewObjectId(newId);
131 			Result result = update.update();
132 			switch (result) {
133 			case FORCED:
134 			case NEW:
135 			case NO_CHANGE:
136 				return;
137 			default:
138 				throw new JGitInternalException(MessageFormat.format(
139 						JGitText.get().updatingRefFailed, R_STASH, newId,
140 						result));
141 			}
142 		} catch (IOException e) {
143 			throw new JGitInternalException(JGitText.get().stashDropFailed, e);
144 		}
145 	}
146 
147 	/**
148 	 * {@inheritDoc}
149 	 * <p>
150 	 * Drop the configured entry from the stash reflog and return value of the
151 	 * stash reference after the drop occurs
152 	 */
153 	@Override
154 	public ObjectId call() throws GitAPIException {
155 		checkCallable();
156 
157 		Ref stashRef = getRef();
158 		if (stashRef == null)
159 			return null;
160 
161 		if (all) {
162 			deleteRef(stashRef);
163 			return null;
164 		}
165 
166 		List<ReflogEntry> entries;
167 		try {
168 			ReflogReader reader = repo.getReflogReader(R_STASH);
169 			if (reader == null) {
170 				throw new RefNotFoundException(MessageFormat
171 						.format(JGitText.get().refNotResolved, stashRef));
172 			}
173 			entries = reader.getReverseEntries();
174 		} catch (IOException e) {
175 			throw new JGitInternalException(JGitText.get().stashDropFailed, e);
176 		}
177 
178 		if (stashRefEntry >= entries.size())
179 			throw new JGitInternalException(
180 					JGitText.get().stashDropMissingReflog);
181 
182 		if (entries.size() == 1) {
183 			deleteRef(stashRef);
184 			return null;
185 		}
186 
187 		RefDirectory refdb = (RefDirectory) repo.getRefDatabase();
188 		ReflogWriter writer = new ReflogWriter(refdb, true);
189 		String stashLockRef = ReflogWriter.refLockFor(R_STASH);
190 		File stashLockFile = refdb.logFor(stashLockRef);
191 		File stashFile = refdb.logFor(R_STASH);
192 		if (stashLockFile.exists())
193 			throw new JGitInternalException(JGitText.get().stashDropFailed,
194 					new LockFailedException(stashFile));
195 
196 		entries.remove(stashRefEntry);
197 		ObjectId entryId = ObjectId.zeroId();
198 		try {
199 			for (int i = entries.size() - 1; i >= 0; i--) {
200 				ReflogEntry entry = entries.get(i);
201 				writer.log(stashLockRef, entryId, entry.getNewId(),
202 						entry.getWho(), entry.getComment());
203 				entryId = entry.getNewId();
204 			}
205 			try {
206 				FileUtils.rename(stashLockFile, stashFile,
207 						StandardCopyOption.ATOMIC_MOVE);
208 			} catch (IOException e) {
209 					throw new JGitInternalException(MessageFormat.format(
210 							JGitText.get().renameFileFailed,
211 								stashLockFile.getPath(), stashFile.getPath()),
212 						e);
213 			}
214 		} catch (IOException e) {
215 			throw new JGitInternalException(JGitText.get().stashDropFailed, e);
216 		}
217 		updateRef(stashRef, entryId);
218 
219 		try {
220 			Ref newStashRef = repo.exactRef(R_STASH);
221 			return newStashRef != null ? newStashRef.getObjectId() : null;
222 		} catch (IOException e) {
223 			throw new InvalidRefNameException(MessageFormat.format(
224 					JGitText.get().cannotRead, R_STASH), e);
225 		}
226 	}
227 }