1 /*
2 * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4 * and other copyright owners as documented in the project's IP log.
5 *
6 * This program and the accompanying materials are made available
7 * under the terms of the Eclipse Distribution License v1.0 which
8 * accompanies this distribution, is reproduced below, and is
9 * available at http://www.eclipse.org/org/documents/edl-v10.php
10 *
11 * All rights reserved.
12 *
13 * Redistribution and use in source and binary forms, with or
14 * without modification, are permitted provided that the following
15 * conditions are met:
16 *
17 * - Redistributions of source code must retain the above copyright
18 * notice, this list of conditions and the following disclaimer.
19 *
20 * - Redistributions in binary form must reproduce the above
21 * copyright notice, this list of conditions and the following
22 * disclaimer in the documentation and/or other materials provided
23 * with the distribution.
24 *
25 * - Neither the name of the Eclipse Foundation, Inc. nor the
26 * names of its contributors may be used to endorse or promote
27 * products derived from this software without specific prior
28 * written permission.
29 *
30 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
31 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
32 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
33 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
35 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
37 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
38 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
39 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
40 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43 */
44
45 package org.eclipse.jgit.pgm;
46
47 import static org.eclipse.jgit.lib.Constants.R_HEADS;
48 import static org.eclipse.jgit.lib.Constants.R_REMOTES;
49 import static org.eclipse.jgit.lib.Constants.R_TAGS;
50
51 import java.io.BufferedWriter;
52 import java.io.FileDescriptor;
53 import java.io.FileInputStream;
54 import java.io.FileOutputStream;
55 import java.io.IOException;
56 import java.io.InputStream;
57 import java.io.OutputStream;
58 import java.io.OutputStreamWriter;
59 import java.text.MessageFormat;
60 import java.util.ResourceBundle;
61
62 import org.eclipse.jgit.lib.ObjectId;
63 import org.eclipse.jgit.lib.Repository;
64 import org.eclipse.jgit.pgm.internal.CLIText;
65 import org.eclipse.jgit.pgm.opt.CmdLineParser;
66 import org.eclipse.jgit.revwalk.RevWalk;
67 import org.eclipse.jgit.util.io.ThrowingPrintWriter;
68 import org.kohsuke.args4j.CmdLineException;
69 import org.kohsuke.args4j.Option;
70
71 /**
72 * Abstract command which can be invoked from the command line.
73 * <p>
74 * Commands are configured with a single "current" repository and then the
75 * {@link #execute(String[])} method is invoked with the arguments that appear
76 * on the command line after the command name.
77 * <p>
78 * Command constructors should perform as little work as possible as they may be
79 * invoked very early during process loading, and the command may not execute
80 * even though it was constructed.
81 */
82 public abstract class TextBuiltin {
83 private String commandName;
84
85 @Option(name = "--help", usage = "usage_displayThisHelpText", aliases = { "-h" })
86 private boolean help;
87
88 /**
89 * Input stream, typically this is standard input.
90 *
91 * @since 3.4
92 */
93 protected InputStream ins;
94
95 /**
96 * Writer to output to, typically this is standard output.
97 *
98 * @since 2.2
99 */
100 protected ThrowingPrintWriter outw;
101
102 /**
103 * Stream to output to, typically this is standard output.
104 *
105 * @since 2.2
106 */
107 protected OutputStream outs;
108
109 /**
110 * Error writer, typically this is standard error.
111 *
112 * @since 3.4
113 */
114 protected ThrowingPrintWriter errw;
115
116 /**
117 * Error output stream, typically this is standard error.
118 *
119 * @since 3.4
120 */
121 protected OutputStream errs;
122
123 /** Git repository the command was invoked within. */
124 protected Repository db;
125
126 /** Directory supplied via --git-dir command line option. */
127 protected String gitdir;
128
129 /** RevWalk used during command line parsing, if it was required. */
130 protected RevWalk argWalk;
131
132 final void setCommandName(final String name) {
133 commandName = name;
134 }
135
136 /** @return true if {@link #db}/{@link #getRepository()} is required. */
137 protected boolean requiresRepository() {
138 return true;
139 }
140
141 /**
142 * Initialize the command to work with a repository.
143 *
144 * @param repository
145 * the opened repository that the command should work on.
146 * @param gitDir
147 * value of the {@code --git-dir} command line option, if
148 * {@code repository} is null.
149 */
150 protected void init(final Repository repository, final String gitDir) {
151 try {
152 final String outputEncoding = repository != null ? repository
153 .getConfig().getString("i18n", null, "logOutputEncoding") : null; //$NON-NLS-1$ //$NON-NLS-2$
154 if (ins == null)
155 ins = new FileInputStream(FileDescriptor.in);
156 if (outs == null)
157 outs = new FileOutputStream(FileDescriptor.out);
158 if (errs == null)
159 errs = new FileOutputStream(FileDescriptor.err);
160 BufferedWriter outbufw;
161 if (outputEncoding != null)
162 outbufw = new BufferedWriter(new OutputStreamWriter(outs,
163 outputEncoding));
164 else
165 outbufw = new BufferedWriter(new OutputStreamWriter(outs));
166 outw = new ThrowingPrintWriter(outbufw);
167 BufferedWriter errbufw;
168 if (outputEncoding != null)
169 errbufw = new BufferedWriter(new OutputStreamWriter(errs,
170 outputEncoding));
171 else
172 errbufw = new BufferedWriter(new OutputStreamWriter(errs));
173 errw = new ThrowingPrintWriter(errbufw);
174 } catch (IOException e) {
175 throw die(CLIText.get().cannotCreateOutputStream);
176 }
177
178 if (repository != null && repository.getDirectory() != null) {
179 db = repository;
180 gitdir = repository.getDirectory().getAbsolutePath();
181 } else {
182 db = repository;
183 gitdir = gitDir;
184 }
185 }
186
187 /**
188 * Parse arguments and run this command.
189 *
190 * @param args
191 * command line arguments passed after the command name.
192 * @throws Exception
193 * an error occurred while processing the command. The main
194 * framework will catch the exception and print a message on
195 * standard error.
196 */
197 public final void execute(String[] args) throws Exception {
198 parseArguments(args);
199 run();
200 }
201
202 /**
203 * Parses the command line arguments prior to running.
204 * <p>
205 * This method should only be invoked by {@link #execute(String[])}, prior
206 * to calling {@link #run()}. The default implementation parses all
207 * arguments into this object's instance fields.
208 *
209 * @param args
210 * the arguments supplied on the command line, if any.
211 * @throws IOException
212 */
213 protected void parseArguments(final String[] args) throws IOException {
214 final CmdLineParser clp = new CmdLineParser(this);
215 help = containsHelp(args);
216 try {
217 clp.parseArgument(args);
218 } catch (CmdLineException err) {
219 this.errw.println(CLIText.fatalError(err.getMessage()));
220 if (help) {
221 printUsage("", clp); //$NON-NLS-1$
222 }
223 throw die(true, err);
224 }
225
226 if (help) {
227 printUsage("", clp); //$NON-NLS-1$
228 throw new TerminatedByHelpException();
229 }
230
231 argWalk = clp.getRevWalkGently();
232 }
233
234 /**
235 * Print the usage line
236 *
237 * @param clp
238 * @throws IOException
239 */
240 public void printUsageAndExit(final CmdLineParser clp) throws IOException {
241 printUsageAndExit("", clp); //$NON-NLS-1$
242 }
243
244 /**
245 * Print an error message and the usage line
246 *
247 * @param message
248 * @param clp
249 * @throws IOException
250 */
251 public void printUsageAndExit(final String message, final CmdLineParser clp) throws IOException {
252 printUsage(message, clp);
253 throw die(true);
254 }
255
256 /**
257 * @param message
258 * non null
259 * @param clp
260 * parser used to print options
261 * @throws IOException
262 * @since 4.2
263 */
264 protected void printUsage(final String message, final CmdLineParser clp)
265 throws IOException {
266 errw.println(message);
267 errw.print("jgit "); //$NON-NLS-1$
268 errw.print(commandName);
269 clp.printSingleLineUsage(errw, getResourceBundle());
270 errw.println();
271
272 errw.println();
273 clp.printUsage(errw, getResourceBundle());
274 errw.println();
275
276 errw.flush();
277 }
278
279 /**
280 * @return error writer, typically this is standard error.
281 * @since 4.2
282 */
283 public ThrowingPrintWriter getErrorWriter() {
284 return errw;
285 }
286
287 /**
288 * @return the resource bundle that will be passed to args4j for purpose of
289 * string localization
290 */
291 protected ResourceBundle getResourceBundle() {
292 return CLIText.get().resourceBundle();
293 }
294
295 /**
296 * Perform the actions of this command.
297 * <p>
298 * This method should only be invoked by {@link #execute(String[])}.
299 *
300 * @throws Exception
301 * an error occurred while processing the command. The main
302 * framework will catch the exception and print a message on
303 * standard error.
304 */
305 protected abstract void run() throws Exception;
306
307 /**
308 * @return the repository this command accesses.
309 */
310 public Repository getRepository() {
311 return db;
312 }
313
314 ObjectId resolve(final String s) throws IOException {
315 final ObjectId r = db.resolve(s);
316 if (r == null)
317 throw die(MessageFormat.format(CLIText.get().notARevision, s));
318 return r;
319 }
320
321 /**
322 * @param why
323 * textual explanation
324 * @return a runtime exception the caller is expected to throw
325 */
326 protected static Die die(final String why) {
327 return new Die(why);
328 }
329
330 /**
331 * @param why
332 * textual explanation
333 * @param cause
334 * why the command has failed.
335 * @return a runtime exception the caller is expected to throw
336 */
337 protected static Die die(final String why, final Throwable cause) {
338 return new Die(why, cause);
339 }
340
341 /**
342 * @param aborted
343 * boolean indicating that the execution has been aborted before running
344 * @return a runtime exception the caller is expected to throw
345 * @since 3.4
346 */
347 protected static Die die(boolean aborted) {
348 return new Die(aborted);
349 }
350
351 /**
352 * @param aborted
353 * boolean indicating that the execution has been aborted before
354 * running
355 * @param cause
356 * why the command has failed.
357 * @return a runtime exception the caller is expected to throw
358 * @since 4.2
359 */
360 protected static Die die(boolean aborted, final Throwable cause) {
361 return new Die(aborted, cause);
362 }
363
364 String abbreviateRef(String dst, boolean abbreviateRemote) {
365 if (dst.startsWith(R_HEADS))
366 dst = dst.substring(R_HEADS.length());
367 else if (dst.startsWith(R_TAGS))
368 dst = dst.substring(R_TAGS.length());
369 else if (abbreviateRemote && dst.startsWith(R_REMOTES))
370 dst = dst.substring(R_REMOTES.length());
371 return dst;
372 }
373
374 /**
375 * @param args
376 * non null
377 * @return true if the given array contains help option
378 * @since 4.2
379 */
380 public static boolean containsHelp(String[] args) {
381 for (String str : args) {
382 if (str.equals("-h") || str.equals("--help")) { //$NON-NLS-1$ //$NON-NLS-2$
383 return true;
384 }
385 }
386 return false;
387 }
388
389 /**
390 * Exception thrown by {@link TextBuiltin} if it proceeds 'help' option
391 *
392 * @since 4.2
393 */
394 public static class TerminatedByHelpException extends Die {
395 private static final long serialVersionUID = 1L;
396
397 /**
398 * Default constructor
399 */
400 public TerminatedByHelpException() {
401 super(true);
402 }
403
404 }
405 }