View Javadoc
1   /*
2    * Copyright (C) 2012, GitHub Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  package org.eclipse.jgit.api;
44  
45  import static org.eclipse.jgit.lib.Constants.R_STASH;
46  
47  import java.io.File;
48  import java.io.IOException;
49  import java.nio.file.StandardCopyOption;
50  import java.text.MessageFormat;
51  import java.util.List;
52  
53  import org.eclipse.jgit.api.errors.GitAPIException;
54  import org.eclipse.jgit.api.errors.InvalidRefNameException;
55  import org.eclipse.jgit.api.errors.JGitInternalException;
56  import org.eclipse.jgit.api.errors.RefNotFoundException;
57  import org.eclipse.jgit.errors.LockFailedException;
58  import org.eclipse.jgit.internal.JGitText;
59  import org.eclipse.jgit.internal.storage.file.RefDirectory;
60  import org.eclipse.jgit.internal.storage.file.ReflogWriter;
61  import org.eclipse.jgit.lib.ObjectId;
62  import org.eclipse.jgit.lib.Ref;
63  import org.eclipse.jgit.lib.RefUpdate;
64  import org.eclipse.jgit.lib.RefUpdate.Result;
65  import org.eclipse.jgit.lib.ReflogEntry;
66  import org.eclipse.jgit.lib.ReflogReader;
67  import org.eclipse.jgit.lib.Repository;
68  import org.eclipse.jgit.util.FileUtils;
69  
70  /**
71   * Command class to delete a stashed commit reference
72   * <p>
73   * Currently only supported on a traditional file repository using
74   * one-file-per-ref reflogs.
75   *
76   * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-stash.html"
77   *      >Git documentation about Stash</a>
78   * @since 2.0
79   */
80  public class StashDropCommand extends GitCommand<ObjectId> {
81  
82  	private int stashRefEntry;
83  
84  	private boolean all;
85  
86  	/**
87  	 * Constructor for StashDropCommand.
88  	 *
89  	 * @param repo
90  	 *            a {@link org.eclipse.jgit.lib.Repository} object.
91  	 */
92  	public StashDropCommand(Repository repo) {
93  		super(repo);
94  		if (!(repo.getRefDatabase() instanceof RefDirectory)) {
95  			throw new UnsupportedOperationException(
96  					JGitText.get().stashDropNotSupported);
97  		}
98  	}
99  
100 	/**
101 	 * Set the stash reference to drop (0-based).
102 	 * <p>
103 	 * This will default to drop the latest stashed commit (stash@{0}) if
104 	 * unspecified
105 	 *
106 	 * @param stashRef
107 	 *            the 0-based index of the stash reference
108 	 * @return {@code this}
109 	 */
110 	public StashDropCommand setStashRef(int stashRef) {
111 		if (stashRef < 0)
112 			throw new IllegalArgumentException();
113 
114 		stashRefEntry = stashRef;
115 		return this;
116 	}
117 
118 	/**
119 	 * Set whether to drop all stashed commits
120 	 *
121 	 * @param all
122 	 *            {@code true} to drop all stashed commits, {@code false} to
123 	 *            drop only the stashed commit set via calling
124 	 *            {@link #setStashRef(int)}
125 	 * @return {@code this}
126 	 */
127 	public StashDropCommand setAll(boolean all) {
128 		this.all = all;
129 		return this;
130 	}
131 
132 	private Ref getRef() throws GitAPIException {
133 		try {
134 			return repo.exactRef(R_STASH);
135 		} catch (IOException e) {
136 			throw new InvalidRefNameException(MessageFormat.format(
137 					JGitText.get().cannotRead, R_STASH), e);
138 		}
139 	}
140 
141 	private RefUpdate createRefUpdate(Ref stashRef) throws IOException {
142 		RefUpdate update = repo.updateRef(R_STASH);
143 		update.disableRefLog();
144 		update.setExpectedOldObjectId(stashRef.getObjectId());
145 		update.setForceUpdate(true);
146 		return update;
147 	}
148 
149 	private void deleteRef(Ref stashRef) {
150 		try {
151 			Result result = createRefUpdate(stashRef).delete();
152 			if (Result.FORCED != result)
153 				throw new JGitInternalException(MessageFormat.format(
154 						JGitText.get().stashDropDeleteRefFailed, result));
155 		} catch (IOException e) {
156 			throw new JGitInternalException(JGitText.get().stashDropFailed, e);
157 		}
158 	}
159 
160 	private void updateRef(Ref stashRef, ObjectId newId) {
161 		try {
162 			RefUpdate update = createRefUpdate(stashRef);
163 			update.setNewObjectId(newId);
164 			Result result = update.update();
165 			switch (result) {
166 			case FORCED:
167 			case NEW:
168 			case NO_CHANGE:
169 				return;
170 			default:
171 				throw new JGitInternalException(MessageFormat.format(
172 						JGitText.get().updatingRefFailed, R_STASH, newId,
173 						result));
174 			}
175 		} catch (IOException e) {
176 			throw new JGitInternalException(JGitText.get().stashDropFailed, e);
177 		}
178 	}
179 
180 	/**
181 	 * {@inheritDoc}
182 	 * <p>
183 	 * Drop the configured entry from the stash reflog and return value of the
184 	 * stash reference after the drop occurs
185 	 */
186 	@Override
187 	public ObjectId call() throws GitAPIException {
188 		checkCallable();
189 
190 		Ref stashRef = getRef();
191 		if (stashRef == null)
192 			return null;
193 
194 		if (all) {
195 			deleteRef(stashRef);
196 			return null;
197 		}
198 
199 		List<ReflogEntry> entries;
200 		try {
201 			ReflogReader reader = repo.getReflogReader(R_STASH);
202 			if (reader == null) {
203 				throw new RefNotFoundException(MessageFormat
204 						.format(JGitText.get().refNotResolved, stashRef));
205 			}
206 			entries = reader.getReverseEntries();
207 		} catch (IOException e) {
208 			throw new JGitInternalException(JGitText.get().stashDropFailed, e);
209 		}
210 
211 		if (stashRefEntry >= entries.size())
212 			throw new JGitInternalException(
213 					JGitText.get().stashDropMissingReflog);
214 
215 		if (entries.size() == 1) {
216 			deleteRef(stashRef);
217 			return null;
218 		}
219 
220 		RefDirectory refdb = (RefDirectory) repo.getRefDatabase();
221 		ReflogWriter writer = new ReflogWriter(refdb, true);
222 		String stashLockRef = ReflogWriter.refLockFor(R_STASH);
223 		File stashLockFile = refdb.logFor(stashLockRef);
224 		File stashFile = refdb.logFor(R_STASH);
225 		if (stashLockFile.exists())
226 			throw new JGitInternalException(JGitText.get().stashDropFailed,
227 					new LockFailedException(stashFile));
228 
229 		entries.remove(stashRefEntry);
230 		ObjectId entryId = ObjectId.zeroId();
231 		try {
232 			for (int i = entries.size() - 1; i >= 0; i--) {
233 				ReflogEntry entry = entries.get(i);
234 				writer.log(stashLockRef, entryId, entry.getNewId(),
235 						entry.getWho(), entry.getComment());
236 				entryId = entry.getNewId();
237 			}
238 			try {
239 				FileUtils.rename(stashLockFile, stashFile,
240 						StandardCopyOption.ATOMIC_MOVE);
241 			} catch (IOException e) {
242 					throw new JGitInternalException(MessageFormat.format(
243 							JGitText.get().renameFileFailed,
244 								stashLockFile.getPath(), stashFile.getPath()),
245 						e);
246 			}
247 		} catch (IOException e) {
248 			throw new JGitInternalException(JGitText.get().stashDropFailed, e);
249 		}
250 		updateRef(stashRef, entryId);
251 
252 		try {
253 			Ref newStashRef = repo.exactRef(R_STASH);
254 			return newStashRef != null ? newStashRef.getObjectId() : null;
255 		} catch (IOException e) {
256 			throw new InvalidRefNameException(MessageFormat.format(
257 					JGitText.get().cannotRead, R_STASH), e);
258 		}
259 	}
260 }