View Javadoc
1   /*
2    * Copyright (C) 2010, 2013 Chris Aniszczyk <caniszczyk@gmail.com> 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 java.io.IOException;
13  import java.text.MessageFormat;
14  
15  import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
16  import org.eclipse.jgit.api.errors.GitAPIException;
17  import org.eclipse.jgit.api.errors.InvalidTagNameException;
18  import org.eclipse.jgit.api.errors.JGitInternalException;
19  import org.eclipse.jgit.api.errors.NoHeadException;
20  import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
21  import org.eclipse.jgit.internal.JGitText;
22  import org.eclipse.jgit.lib.Constants;
23  import org.eclipse.jgit.lib.ObjectId;
24  import org.eclipse.jgit.lib.ObjectInserter;
25  import org.eclipse.jgit.lib.PersonIdent;
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.Repository;
30  import org.eclipse.jgit.lib.RepositoryState;
31  import org.eclipse.jgit.lib.TagBuilder;
32  import org.eclipse.jgit.revwalk.RevObject;
33  import org.eclipse.jgit.revwalk.RevWalk;
34  
35  /**
36   * Create/update an annotated tag object or a simple unannotated tag
37   * <p>
38   * Examples (<code>git</code> is a {@link org.eclipse.jgit.api.Git} instance):
39   * <p>
40   * Create a new tag for the current commit:
41   *
42   * <pre>
43   * git.tag().setName(&quot;v1.0&quot;).setMessage(&quot;First stable release&quot;).call();
44   * </pre>
45   * <p>
46   *
47   * <p>
48   * Create a new unannotated tag for the current commit:
49   *
50   * <pre>
51   * git.tag().setName(&quot;v1.0&quot;).setAnnotated(false).call();
52   * </pre>
53   * <p>
54   *
55   * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-tag.html"
56   *      >Git documentation about Tag</a>
57   */
58  public class TagCommand extends GitCommand<Ref> {
59  	private RevObject id;
60  
61  	private String name;
62  
63  	private String message;
64  
65  	private PersonIdent tagger;
66  
67  	private boolean signed;
68  
69  	private boolean forceUpdate;
70  
71  	private boolean annotated = true;
72  
73  	/**
74  	 * <p>Constructor for TagCommand.</p>
75  	 *
76  	 * @param repo a {@link org.eclipse.jgit.lib.Repository} object.
77  	 */
78  	protected TagCommand(Repository repo) {
79  		super(repo);
80  	}
81  
82  	/**
83  	 * {@inheritDoc}
84  	 * <p>
85  	 * Executes the {@code tag} command with all the options and parameters
86  	 * collected by the setter methods of this class. Each instance of this
87  	 * class should only be used for one invocation of the command (means: one
88  	 * call to {@link #call()})
89  	 *
90  	 * @since 2.0
91  	 */
92  	@Override
93  	public Ref call() throws GitAPIException, ConcurrentRefUpdateException,
94  			InvalidTagNameException, NoHeadException {
95  		checkCallable();
96  
97  		RepositoryState state = repo.getRepositoryState();
98  		processOptions(state);
99  
100 		try (RevWalklk.html#RevWalk">RevWalk revWalk = new RevWalk(repo)) {
101 			// if no id is set, we should attempt to use HEAD
102 			if (id == null) {
103 				ObjectId objectId = repo.resolve(Constants.HEAD + "^{commit}"); //$NON-NLS-1$
104 				if (objectId == null)
105 					throw new NoHeadException(
106 							JGitText.get().tagOnRepoWithoutHEADCurrentlyNotSupported);
107 
108 				id = revWalk.parseCommit(objectId);
109 			}
110 
111 			if (!annotated) {
112 				if (message != null || tagger != null)
113 					throw new JGitInternalException(
114 							JGitText.get().messageAndTaggerNotAllowedInUnannotatedTags);
115 				return updateTagRef(id, revWalk, name,
116 						"SimpleTag[" + name + " : " + id //$NON-NLS-1$ //$NON-NLS-2$
117 								+ "]"); //$NON-NLS-1$
118 			}
119 
120 			// create the tag object
121 			TagBuilder newTag = new TagBuilder();
122 			newTag.setTag(name);
123 			newTag.setMessage(message);
124 			newTag.setTagger(tagger);
125 			newTag.setObjectId(id);
126 
127 			// write the tag object
128 			try (ObjectInserter inserter = repo.newObjectInserter()) {
129 				ObjectId tagId = inserter.insert(newTag);
130 				inserter.flush();
131 
132 				String tag = newTag.getTag();
133 				return updateTagRef(tagId, revWalk, tag, newTag.toString());
134 
135 			}
136 
137 		} catch (IOException e) {
138 			throw new JGitInternalException(
139 					JGitText.get().exceptionCaughtDuringExecutionOfTagCommand,
140 					e);
141 		}
142 	}
143 
144 	private Ref updateTagRef(ObjectId tagId, RevWalk revWalk,
145 			String tagName, String newTagToString) throws IOException,
146 			ConcurrentRefUpdateException, RefAlreadyExistsException {
147 		String refName = Constants.R_TAGS + tagName;
148 		RefUpdate tagRef = repo.updateRef(refName);
149 		tagRef.setNewObjectId(tagId);
150 		tagRef.setForceUpdate(forceUpdate);
151 		tagRef.setRefLogMessage("tagged " + name, false); //$NON-NLS-1$
152 		Result updateResult = tagRef.update(revWalk);
153 		switch (updateResult) {
154 		case NEW:
155 		case FORCED:
156 			return repo.exactRef(refName);
157 		case LOCK_FAILURE:
158 			throw new ConcurrentRefUpdateException(
159 					JGitText.get().couldNotLockHEAD, tagRef.getRef(),
160 					updateResult);
161 		case REJECTED:
162 			throw new RefAlreadyExistsException(MessageFormat.format(
163 					JGitText.get().tagAlreadyExists, newTagToString));
164 		default:
165 			throw new JGitInternalException(MessageFormat.format(
166 					JGitText.get().updatingRefFailed, refName, newTagToString,
167 					updateResult));
168 		}
169 	}
170 
171 	/**
172 	 * Sets default values for not explicitly specified options. Then validates
173 	 * that all required data has been provided.
174 	 *
175 	 * @param state
176 	 *            the state of the repository we are working on
177 	 *
178 	 * @throws InvalidTagNameException
179 	 *             if the tag name is null or invalid
180 	 * @throws UnsupportedOperationException
181 	 *             if the tag is signed (not supported yet)
182 	 */
183 	private void processOptions(RepositoryState state)
184 			throws InvalidTagNameException {
185 		if (tagger == null && annotated)
186 			tagger = new PersonIdent(repo);
187 		if (name == null || !Repository.isValidRefName(Constants.R_TAGS + name))
188 			throw new InvalidTagNameException(
189 					MessageFormat.format(JGitText.get().tagNameInvalid,
190 							name == null ? "<null>" : name)); //$NON-NLS-1$
191 		if (signed)
192 			throw new UnsupportedOperationException(
193 					JGitText.get().signingNotSupportedOnTag);
194 	}
195 
196 	/**
197 	 * Set the tag <code>name</code>.
198 	 *
199 	 * @param name
200 	 *            the tag name used for the {@code tag}
201 	 * @return {@code this}
202 	 */
203 	public TagCommand setName(String name) {
204 		checkCallable();
205 		this.name = name;
206 		return this;
207 	}
208 
209 	/**
210 	 * Get the tag <code>name</code>.
211 	 *
212 	 * @return the tag name used for the <code>tag</code>
213 	 */
214 	public String getName() {
215 		return name;
216 	}
217 
218 	/**
219 	 * Get the tag <code>message</code>.
220 	 *
221 	 * @return the tag message used for the <code>tag</code>
222 	 */
223 	public String getMessage() {
224 		return message;
225 	}
226 
227 	/**
228 	 * Set the tag <code>message</code>.
229 	 *
230 	 * @param message
231 	 *            the tag message used for the {@code tag}
232 	 * @return {@code this}
233 	 */
234 	public TagCommand setMessage(String message) {
235 		checkCallable();
236 		this.message = message;
237 		return this;
238 	}
239 
240 	/**
241 	 * Whether this tag is signed
242 	 *
243 	 * @return whether the tag is signed
244 	 */
245 	public boolean isSigned() {
246 		return signed;
247 	}
248 
249 	/**
250 	 * If set to true the Tag command creates a signed tag object. This
251 	 * corresponds to the parameter -s on the command line.
252 	 *
253 	 * @param signed
254 	 *            a boolean.
255 	 * @return {@code this}
256 	 */
257 	public TagCommand setSigned(boolean signed) {
258 		this.signed = signed;
259 		return this;
260 	}
261 
262 	/**
263 	 * Sets the tagger of the tag. If the tagger is null, a PersonIdent will be
264 	 * created from the info in the repository.
265 	 *
266 	 * @param tagger
267 	 *            a {@link org.eclipse.jgit.lib.PersonIdent} object.
268 	 * @return {@code this}
269 	 */
270 	public TagCommand setTagger(PersonIdent tagger) {
271 		this.tagger = tagger;
272 		return this;
273 	}
274 
275 	/**
276 	 * Get the <code>tagger</code> who created the tag.
277 	 *
278 	 * @return the tagger of the tag
279 	 */
280 	public PersonIdent getTagger() {
281 		return tagger;
282 	}
283 
284 	/**
285 	 * Get the tag's object id
286 	 *
287 	 * @return the object id of the tag
288 	 */
289 	public RevObject getObjectId() {
290 		return id;
291 	}
292 
293 	/**
294 	 * Sets the object id of the tag. If the object id is null, the commit
295 	 * pointed to from HEAD will be used.
296 	 *
297 	 * @param id
298 	 *            a {@link org.eclipse.jgit.revwalk.RevObject} object.
299 	 * @return {@code this}
300 	 */
301 	public TagCommand setObjectId(RevObject id) {
302 		this.id = id;
303 		return this;
304 	}
305 
306 	/**
307 	 * Whether this is a forced update
308 	 *
309 	 * @return is this a force update
310 	 */
311 	public boolean isForceUpdate() {
312 		return forceUpdate;
313 	}
314 
315 	/**
316 	 * If set to true the Tag command may replace an existing tag object. This
317 	 * corresponds to the parameter -f on the command line.
318 	 *
319 	 * @param forceUpdate
320 	 *            whether this is a forced update
321 	 * @return {@code this}
322 	 */
323 	public TagCommand setForceUpdate(boolean forceUpdate) {
324 		this.forceUpdate = forceUpdate;
325 		return this;
326 	}
327 
328 	/**
329 	 * Configure this tag to be created as an annotated tag
330 	 *
331 	 * @param annotated
332 	 *            whether this shall be an annotated tag
333 	 * @return {@code this}
334 	 * @since 3.0
335 	 */
336 	public TagCommand setAnnotated(boolean annotated) {
337 		this.annotated = annotated;
338 		return this;
339 	}
340 
341 	/**
342 	 * Whether this will create an annotated command
343 	 *
344 	 * @return true if this command will create an annotated tag (default is
345 	 *         true)
346 	 * @since 3.0
347 	 */
348 	public boolean isAnnotated() {
349 		return annotated;
350 	}
351 }