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(String name) {
133 commandName = name;
134 }
135
136 /**
137 * If this command requires a repository.
138 *
139 * @return true if {@link #db}/{@link #getRepository()} is required
140 */
141 protected boolean requiresRepository() {
142 return true;
143 }
144
145 /**
146 * Initializes the command to work with a repository, including setting the
147 * output and error streams.
148 *
149 * @param repository
150 * the opened repository that the command should work on.
151 * @param gitDir
152 * value of the {@code --git-dir} command line option, if
153 * {@code repository} is null.
154 * @param input
155 * input stream from which input will be read
156 * @param output
157 * output stream to which output will be written
158 * @param error
159 * error stream to which errors will be written
160 * @since 4.9
161 */
162 public void initRaw(final Repository repository, final String gitDir,
163 InputStream input, OutputStream output, OutputStream error) {
164 this.ins = input;
165 this.outs = output;
166 this.errs = error;
167 init(repository, gitDir);
168 }
169
170 /**
171 * Initialize the command to work with a repository.
172 *
173 * @param repository
174 * the opened repository that the command should work on.
175 * @param gitDir
176 * value of the {@code --git-dir} command line option, if
177 * {@code repository} is null.
178 */
179 protected void init(Repository repository, String gitDir) {
180 try {
181 final String outputEncoding = repository != null ? repository
182 .getConfig().getString("i18n", null, "logOutputEncoding") : null; //$NON-NLS-1$ //$NON-NLS-2$
183 if (ins == null)
184 ins = new FileInputStream(FileDescriptor.in);
185 if (outs == null)
186 outs = new FileOutputStream(FileDescriptor.out);
187 if (errs == null)
188 errs = new FileOutputStream(FileDescriptor.err);
189 BufferedWriter outbufw;
190 if (outputEncoding != null)
191 outbufw = new BufferedWriter(new OutputStreamWriter(outs,
192 outputEncoding));
193 else
194 outbufw = new BufferedWriter(new OutputStreamWriter(outs));
195 outw = new ThrowingPrintWriter(outbufw);
196 BufferedWriter errbufw;
197 if (outputEncoding != null)
198 errbufw = new BufferedWriter(new OutputStreamWriter(errs,
199 outputEncoding));
200 else
201 errbufw = new BufferedWriter(new OutputStreamWriter(errs));
202 errw = new ThrowingPrintWriter(errbufw);
203 } catch (IOException e) {
204 throw die(CLIText.get().cannotCreateOutputStream);
205 }
206
207 if (repository != null && repository.getDirectory() != null) {
208 db = repository;
209 gitdir = repository.getDirectory().getAbsolutePath();
210 } else {
211 db = repository;
212 gitdir = gitDir;
213 }
214 }
215
216 /**
217 * Parse arguments and run this command.
218 *
219 * @param args
220 * command line arguments passed after the command name.
221 * @throws java.lang.Exception
222 * an error occurred while processing the command. The main
223 * framework will catch the exception and print a message on
224 * standard error.
225 */
226 public final void execute(String[] args) throws Exception {
227 parseArguments(args);
228 run();
229 }
230
231 /**
232 * Parses the command line arguments prior to running.
233 * <p>
234 * This method should only be invoked by {@link #execute(String[])}, prior
235 * to calling {@link #run()}. The default implementation parses all
236 * arguments into this object's instance fields.
237 *
238 * @param args
239 * the arguments supplied on the command line, if any.
240 * @throws java.io.IOException
241 */
242 protected void parseArguments(String[] args) throws IOException {
243 final CmdLineParser clp = new CmdLineParser(this);
244 help = containsHelp(args);
245 try {
246 clp.parseArgument(args);
247 } catch (CmdLineException err) {
248 this.errw.println(CLIText.fatalError(err.getMessage()));
249 if (help) {
250 printUsage("", clp); //$NON-NLS-1$
251 }
252 throw die(true, err);
253 }
254
255 if (help) {
256 printUsage("", clp); //$NON-NLS-1$
257 throw new TerminatedByHelpException();
258 }
259
260 argWalk = clp.getRevWalkGently();
261 }
262
263 /**
264 * Print the usage line
265 *
266 * @param clp
267 * a {@link org.eclipse.jgit.pgm.opt.CmdLineParser} object.
268 * @throws java.io.IOException
269 */
270 public void printUsageAndExit(CmdLineParser clp) throws IOException {
271 printUsageAndExit("", clp); //$NON-NLS-1$
272 }
273
274 /**
275 * Print an error message and the usage line
276 *
277 * @param message
278 * a {@link java.lang.String} object.
279 * @param clp
280 * a {@link org.eclipse.jgit.pgm.opt.CmdLineParser} object.
281 * @throws java.io.IOException
282 */
283 public void printUsageAndExit(String message, CmdLineParser clp) throws IOException {
284 printUsage(message, clp);
285 throw die(true);
286 }
287
288 /**
289 * Print usage help text.
290 *
291 * @param message
292 * non null
293 * @param clp
294 * parser used to print options
295 * @throws java.io.IOException
296 * @since 4.2
297 */
298 protected void printUsage(String message, CmdLineParser clp)
299 throws IOException {
300 errw.println(message);
301 errw.print("jgit "); //$NON-NLS-1$
302 errw.print(commandName);
303 clp.printSingleLineUsage(errw, getResourceBundle());
304 errw.println();
305
306 errw.println();
307 clp.printUsage(errw, getResourceBundle());
308 errw.println();
309
310 errw.flush();
311 }
312
313 /**
314 * Get error writer
315 *
316 * @return error writer, typically this is standard error.
317 * @since 4.2
318 */
319 public ThrowingPrintWriter getErrorWriter() {
320 return errw;
321 }
322
323 /**
324 * Get output writer
325 *
326 * @return output writer, typically this is standard output.
327 * @since 4.9
328 */
329 public ThrowingPrintWriter getOutputWriter() {
330 return outw;
331 }
332
333 /**
334 * Get resource bundle with localized texts
335 *
336 * @return the resource bundle that will be passed to args4j for purpose of
337 * string localization
338 */
339 protected ResourceBundle getResourceBundle() {
340 return CLIText.get().resourceBundle();
341 }
342
343 /**
344 * Perform the actions of this command.
345 * <p>
346 * This method should only be invoked by {@link #execute(String[])}.
347 *
348 * @throws java.lang.Exception
349 * an error occurred while processing the command. The main
350 * framework will catch the exception and print a message on
351 * standard error.
352 */
353 protected abstract void run() throws Exception;
354
355 /**
356 * Get the repository
357 *
358 * @return the repository this command accesses.
359 */
360 public Repository getRepository() {
361 return db;
362 }
363
364 ObjectId resolve(String s) throws IOException {
365 final ObjectId r = db.resolve(s);
366 if (r == null)
367 throw die(MessageFormat.format(CLIText.get().notARevision, s));
368 return r;
369 }
370
371 /**
372 * Exit the command with an error message
373 *
374 * @param why
375 * textual explanation
376 * @return a runtime exception the caller is expected to throw
377 */
378 protected static Die die(String why) {
379 return new Die(why);
380 }
381
382 /**
383 * Exit the command with an error message and an exception
384 *
385 * @param why
386 * textual explanation
387 * @param cause
388 * why the command has failed.
389 * @return a runtime exception the caller is expected to throw
390 */
391 protected static Die die(String why, Throwable cause) {
392 return new Die(why, cause);
393 }
394
395 /**
396 * Exit the command
397 *
398 * @param aborted
399 * boolean indicating that the execution has been aborted before
400 * running
401 * @return a runtime exception the caller is expected to throw
402 * @since 3.4
403 */
404 protected static Die die(boolean aborted) {
405 return new Die(aborted);
406 }
407
408 /**
409 * Exit the command
410 *
411 * @param aborted
412 * boolean indicating that the execution has been aborted before
413 * running
414 * @param cause
415 * why the command has failed.
416 * @return a runtime exception the caller is expected to throw
417 * @since 4.2
418 */
419 protected static Die die(boolean aborted, Throwable cause) {
420 return new Die(aborted, cause);
421 }
422
423 String abbreviateRef(String dst, boolean abbreviateRemote) {
424 if (dst.startsWith(R_HEADS))
425 dst = dst.substring(R_HEADS.length());
426 else if (dst.startsWith(R_TAGS))
427 dst = dst.substring(R_TAGS.length());
428 else if (abbreviateRemote && dst.startsWith(R_REMOTES))
429 dst = dst.substring(R_REMOTES.length());
430 return dst;
431 }
432
433 /**
434 * Check if the arguments contain a help option
435 *
436 * @param args
437 * non null
438 * @return true if the given array contains help option
439 * @since 4.2
440 */
441 public static boolean containsHelp(String[] args) {
442 for (String str : args) {
443 if (str.equals("-h") || str.equals("--help")) { //$NON-NLS-1$ //$NON-NLS-2$
444 return true;
445 }
446 }
447 return false;
448 }
449
450 /**
451 * Exception thrown by {@link TextBuiltin} if it proceeds 'help' option
452 *
453 * @since 4.2
454 */
455 public static class TerminatedByHelpException extends Die {
456 private static final long serialVersionUID = 1L;
457
458 /**
459 * Default constructor
460 */
461 public TerminatedByHelpException() {
462 super(true);
463 }
464
465 }
466 }