You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TextBuiltin.java 13KB

Merge branch 'stable-5.3' into stable-5.4 * stable-5.3: Fix OpenSshConfigTest#config FileSnapshot: fix bug with timestamp thresholding In LockFile#waitForStatChange wait in units of file time resolution Cache FileStoreAttributeCache per directory Fix FileSnapshot#save(long) and FileSnapshot#save(Instant) Persist minimal racy threshold and allow manual configuration Measure minimum racy interval to auto-configure FileSnapshot Reuse FileUtils to recursively delete files created by tests Fix FileAttributeCache.toString() Add test for racy git detection in FileSnapshot Repeat RefDirectoryTest.testGetRef_DiscoversModifiedLoose 100 times Fix org.eclipse.jdt.core.prefs of org.eclipse.jgit.junit Add missing javadoc in org.eclipse.jgit.junit Enhance RepeatRule to report number of failures at the end Fix FileSnapshotTests for filesystem with high timestamp resolution Retry deleting test files in FileBasedConfigTest Measure filesystem timestamp resolution already in test setup Refactor FileSnapshotTest to use NIO APIs Measure stored timestamp resolution instead of time to touch file Handle CancellationException in FileStoreAttributeCache Fix FileSnapshot#saveNoConfig Use Instant for smudge time in DirCache and DirCacheEntry Use Instant instead of milliseconds for filesystem timestamp handling Workaround SecurityException in FS#getFsTimestampResolution Fix NPE in FS$FileStoreAttributeCache.getFsTimestampResolution FS: ignore AccessDeniedException when measuring timestamp resolution Add debug trace for FileSnapshot Use FileChannel.open to touch file and set mtime to now Persist filesystem timestamp resolution and allow manual configuration Increase bazel timeout for long running tests Bazel: Fix lint warning flagged by buildifier Update bazlets to latest version Bazel: Add missing dependencies for ArchiveCommandTest Bazel: Remove FileTreeIteratorWithTimeControl from BUILD file Add support for nanoseconds and microseconds for Config#getTimeUnit Optionally measure filesystem timestamp resolution asynchronously Delete unused FileTreeIteratorWithTimeControl FileSnapshot#equals: consider UNKNOWN_SIZE Timeout measuring file timestamp resolution after 2 seconds Fix RacyGitTests#testRacyGitDetection Change RacyGitTests to create a racy git situation in a stable way Deprecate Constants.CHARACTER_ENCODING in favor of StandardCharsets.UTF_8 Fix non-deterministic hash of archives created by ArchiveCommand Update Maven plugins ecj, plexus, error-prone Update Maven plugins and cleanup Maven warnings Make inner classes static where possible Fix API problem filters Change-Id: Iec3ad6ccc194582cb844310dc172c3103dae4457 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
4 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. /*
  2. * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
  3. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
  4. *
  5. * This program and the accompanying materials are made available under the
  6. * terms of the Eclipse Distribution License v. 1.0 which is available at
  7. * https://www.eclipse.org/org/documents/edl-v10.php.
  8. *
  9. * SPDX-License-Identifier: BSD-3-Clause
  10. */
  11. package org.eclipse.jgit.pgm;
  12. import static java.nio.charset.StandardCharsets.UTF_8;
  13. import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_LOG_OUTPUT_ENCODING;
  14. import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_SECTION_I18N;
  15. import static org.eclipse.jgit.lib.Constants.R_HEADS;
  16. import static org.eclipse.jgit.lib.Constants.R_REMOTES;
  17. import static org.eclipse.jgit.lib.Constants.R_TAGS;
  18. import java.io.BufferedWriter;
  19. import java.io.FileDescriptor;
  20. import java.io.FileInputStream;
  21. import java.io.FileOutputStream;
  22. import java.io.IOException;
  23. import java.io.InputStream;
  24. import java.io.OutputStream;
  25. import java.io.OutputStreamWriter;
  26. import java.nio.charset.Charset;
  27. import java.text.MessageFormat;
  28. import java.util.ResourceBundle;
  29. import org.eclipse.jgit.lib.ObjectId;
  30. import org.eclipse.jgit.lib.Repository;
  31. import org.eclipse.jgit.pgm.internal.CLIText;
  32. import org.eclipse.jgit.pgm.internal.SshDriver;
  33. import org.eclipse.jgit.pgm.opt.CmdLineParser;
  34. import org.eclipse.jgit.revwalk.RevWalk;
  35. import org.eclipse.jgit.transport.JschConfigSessionFactory;
  36. import org.eclipse.jgit.transport.SshSessionFactory;
  37. import org.eclipse.jgit.transport.sshd.DefaultProxyDataFactory;
  38. import org.eclipse.jgit.transport.sshd.JGitKeyCache;
  39. import org.eclipse.jgit.transport.sshd.SshdSessionFactory;
  40. import org.eclipse.jgit.util.io.ThrowingPrintWriter;
  41. import org.kohsuke.args4j.CmdLineException;
  42. import org.kohsuke.args4j.Option;
  43. /**
  44. * Abstract command which can be invoked from the command line.
  45. * <p>
  46. * Commands are configured with a single "current" repository and then the
  47. * {@link #execute(String[])} method is invoked with the arguments that appear
  48. * on the command line after the command name.
  49. * <p>
  50. * Command constructors should perform as little work as possible as they may be
  51. * invoked very early during process loading, and the command may not execute
  52. * even though it was constructed.
  53. */
  54. public abstract class TextBuiltin {
  55. private String commandName;
  56. @Option(name = "--help", usage = "usage_displayThisHelpText", aliases = { "-h" })
  57. private boolean help;
  58. @Option(name = "--ssh", usage = "usage_sshDriver")
  59. private SshDriver sshDriver = SshDriver.APACHE;
  60. /**
  61. * Input stream, typically this is standard input.
  62. *
  63. * @since 3.4
  64. */
  65. protected InputStream ins;
  66. /**
  67. * Writer to output to, typically this is standard output.
  68. *
  69. * @since 2.2
  70. */
  71. protected ThrowingPrintWriter outw;
  72. /**
  73. * Stream to output to, typically this is standard output.
  74. *
  75. * @since 2.2
  76. */
  77. protected OutputStream outs;
  78. /**
  79. * Error writer, typically this is standard error.
  80. *
  81. * @since 3.4
  82. */
  83. protected ThrowingPrintWriter errw;
  84. /**
  85. * Error output stream, typically this is standard error.
  86. *
  87. * @since 3.4
  88. */
  89. protected OutputStream errs;
  90. /** Git repository the command was invoked within. */
  91. protected Repository db;
  92. /** Directory supplied via --git-dir command line option. */
  93. protected String gitdir;
  94. /** RevWalk used during command line parsing, if it was required. */
  95. protected RevWalk argWalk;
  96. final void setCommandName(String name) {
  97. commandName = name;
  98. }
  99. /**
  100. * If this command requires a repository.
  101. *
  102. * @return true if {@link #db}/{@link #getRepository()} is required
  103. */
  104. protected boolean requiresRepository() {
  105. return true;
  106. }
  107. /**
  108. * Initializes the command to work with a repository, including setting the
  109. * output and error streams.
  110. *
  111. * @param repository
  112. * the opened repository that the command should work on.
  113. * @param gitDir
  114. * value of the {@code --git-dir} command line option, if
  115. * {@code repository} is null.
  116. * @param input
  117. * input stream from which input will be read
  118. * @param output
  119. * output stream to which output will be written
  120. * @param error
  121. * error stream to which errors will be written
  122. * @since 4.9
  123. */
  124. public void initRaw(final Repository repository, final String gitDir,
  125. InputStream input, OutputStream output, OutputStream error) {
  126. this.ins = input;
  127. this.outs = output;
  128. this.errs = error;
  129. init(repository, gitDir);
  130. }
  131. /**
  132. * Get the log output encoding specified in the repository's
  133. * {@code i18n.logOutputEncoding} configuration.
  134. *
  135. * @param repository
  136. * the repository.
  137. * @return Charset corresponding to {@code i18n.logOutputEncoding}, or
  138. * {@code UTF_8}.
  139. */
  140. private Charset getLogOutputEncodingCharset(Repository repository) {
  141. if (repository != null) {
  142. String logOutputEncoding = repository.getConfig().getString(
  143. CONFIG_SECTION_I18N, null, CONFIG_KEY_LOG_OUTPUT_ENCODING);
  144. if (logOutputEncoding != null) {
  145. try {
  146. return Charset.forName(logOutputEncoding);
  147. } catch (IllegalArgumentException e) {
  148. throw die(CLIText.get().cannotCreateOutputStream, e);
  149. }
  150. }
  151. }
  152. return UTF_8;
  153. }
  154. /**
  155. * Initialize the command to work with a repository.
  156. *
  157. * @param repository
  158. * the opened repository that the command should work on.
  159. * @param gitDir
  160. * value of the {@code --git-dir} command line option, if
  161. * {@code repository} is null.
  162. */
  163. protected void init(Repository repository, String gitDir) {
  164. Charset charset = getLogOutputEncodingCharset(repository);
  165. if (ins == null)
  166. ins = new FileInputStream(FileDescriptor.in);
  167. if (outs == null)
  168. outs = new FileOutputStream(FileDescriptor.out);
  169. if (errs == null)
  170. errs = new FileOutputStream(FileDescriptor.err);
  171. outw = new ThrowingPrintWriter(new BufferedWriter(
  172. new OutputStreamWriter(outs, charset)));
  173. errw = new ThrowingPrintWriter(new BufferedWriter(
  174. new OutputStreamWriter(errs, charset)));
  175. if (repository != null && repository.getDirectory() != null) {
  176. db = repository;
  177. gitdir = repository.getDirectory().getAbsolutePath();
  178. } else {
  179. db = repository;
  180. gitdir = gitDir;
  181. }
  182. }
  183. /**
  184. * Parse arguments and run this command.
  185. *
  186. * @param args
  187. * command line arguments passed after the command name.
  188. * @throws java.lang.Exception
  189. * an error occurred while processing the command. The main
  190. * framework will catch the exception and print a message on
  191. * standard error.
  192. */
  193. public final void execute(String[] args) throws Exception {
  194. parseArguments(args);
  195. switch (sshDriver) {
  196. case APACHE: {
  197. SshdSessionFactory factory = new SshdSessionFactory(
  198. new JGitKeyCache(), new DefaultProxyDataFactory());
  199. Runtime.getRuntime()
  200. .addShutdownHook(new Thread(factory::close));
  201. SshSessionFactory.setInstance(factory);
  202. break;
  203. }
  204. case JSCH:
  205. JschConfigSessionFactory factory = new JschConfigSessionFactory();
  206. SshSessionFactory.setInstance(factory);
  207. break;
  208. default:
  209. SshSessionFactory.setInstance(null);
  210. break;
  211. }
  212. run();
  213. }
  214. /**
  215. * Parses the command line arguments prior to running.
  216. * <p>
  217. * This method should only be invoked by {@link #execute(String[])}, prior
  218. * to calling {@link #run()}. The default implementation parses all
  219. * arguments into this object's instance fields.
  220. *
  221. * @param args
  222. * the arguments supplied on the command line, if any.
  223. * @throws java.io.IOException
  224. */
  225. protected void parseArguments(String[] args) throws IOException {
  226. final CmdLineParser clp = new CmdLineParser(this);
  227. help = containsHelp(args);
  228. try {
  229. clp.parseArgument(args);
  230. } catch (CmdLineException err) {
  231. this.errw.println(CLIText.fatalError(err.getMessage()));
  232. if (help) {
  233. printUsage("", clp); //$NON-NLS-1$
  234. }
  235. throw die(true, err);
  236. }
  237. if (help) {
  238. printUsage("", clp); //$NON-NLS-1$
  239. throw new TerminatedByHelpException();
  240. }
  241. argWalk = clp.getRevWalkGently();
  242. }
  243. /**
  244. * Print the usage line
  245. *
  246. * @param clp
  247. * a {@link org.eclipse.jgit.pgm.opt.CmdLineParser} object.
  248. * @throws java.io.IOException
  249. */
  250. public void printUsageAndExit(CmdLineParser clp) throws IOException {
  251. printUsageAndExit("", clp); //$NON-NLS-1$
  252. }
  253. /**
  254. * Print an error message and the usage line
  255. *
  256. * @param message
  257. * a {@link java.lang.String} object.
  258. * @param clp
  259. * a {@link org.eclipse.jgit.pgm.opt.CmdLineParser} object.
  260. * @throws java.io.IOException
  261. */
  262. public void printUsageAndExit(String message, CmdLineParser clp) throws IOException {
  263. printUsage(message, clp);
  264. throw die(true);
  265. }
  266. /**
  267. * Print usage help text.
  268. *
  269. * @param message
  270. * non null
  271. * @param clp
  272. * parser used to print options
  273. * @throws java.io.IOException
  274. * @since 4.2
  275. */
  276. protected void printUsage(String message, CmdLineParser clp)
  277. throws IOException {
  278. errw.println(message);
  279. errw.print("jgit "); //$NON-NLS-1$
  280. errw.print(commandName);
  281. clp.printSingleLineUsage(errw, getResourceBundle());
  282. errw.println();
  283. errw.println();
  284. clp.printUsage(errw, getResourceBundle());
  285. errw.println();
  286. errw.flush();
  287. }
  288. /**
  289. * Get error writer
  290. *
  291. * @return error writer, typically this is standard error.
  292. * @since 4.2
  293. */
  294. public ThrowingPrintWriter getErrorWriter() {
  295. return errw;
  296. }
  297. /**
  298. * Get output writer
  299. *
  300. * @return output writer, typically this is standard output.
  301. * @since 4.9
  302. */
  303. public ThrowingPrintWriter getOutputWriter() {
  304. return outw;
  305. }
  306. /**
  307. * Get resource bundle with localized texts
  308. *
  309. * @return the resource bundle that will be passed to args4j for purpose of
  310. * string localization
  311. */
  312. protected ResourceBundle getResourceBundle() {
  313. return CLIText.get().resourceBundle();
  314. }
  315. /**
  316. * Perform the actions of this command.
  317. * <p>
  318. * This method should only be invoked by {@link #execute(String[])}.
  319. *
  320. * @throws java.lang.Exception
  321. * an error occurred while processing the command. The main
  322. * framework will catch the exception and print a message on
  323. * standard error.
  324. */
  325. protected abstract void run() throws Exception;
  326. /**
  327. * Get the repository
  328. *
  329. * @return the repository this command accesses.
  330. */
  331. public Repository getRepository() {
  332. return db;
  333. }
  334. ObjectId resolve(String s) throws IOException {
  335. final ObjectId r = db.resolve(s);
  336. if (r == null)
  337. throw die(MessageFormat.format(CLIText.get().notARevision, s));
  338. return r;
  339. }
  340. /**
  341. * Exit the command with an error message
  342. *
  343. * @param why
  344. * textual explanation
  345. * @return a runtime exception the caller is expected to throw
  346. */
  347. protected static Die die(String why) {
  348. return new Die(why);
  349. }
  350. /**
  351. * Exit the command with an error message and an exception
  352. *
  353. * @param why
  354. * textual explanation
  355. * @param cause
  356. * why the command has failed.
  357. * @return a runtime exception the caller is expected to throw
  358. */
  359. protected static Die die(String why, Throwable cause) {
  360. return new Die(why, cause);
  361. }
  362. /**
  363. * Exit the command
  364. *
  365. * @param aborted
  366. * boolean indicating that the execution has been aborted before
  367. * running
  368. * @return a runtime exception the caller is expected to throw
  369. * @since 3.4
  370. */
  371. protected static Die die(boolean aborted) {
  372. return new Die(aborted);
  373. }
  374. /**
  375. * Exit the command
  376. *
  377. * @param aborted
  378. * boolean indicating that the execution has been aborted before
  379. * running
  380. * @param cause
  381. * why the command has failed.
  382. * @return a runtime exception the caller is expected to throw
  383. * @since 4.2
  384. */
  385. protected static Die die(boolean aborted, Throwable cause) {
  386. return new Die(aborted, cause);
  387. }
  388. String abbreviateRef(String dst, boolean abbreviateRemote) {
  389. if (dst.startsWith(R_HEADS))
  390. dst = dst.substring(R_HEADS.length());
  391. else if (dst.startsWith(R_TAGS))
  392. dst = dst.substring(R_TAGS.length());
  393. else if (abbreviateRemote && dst.startsWith(R_REMOTES))
  394. dst = dst.substring(R_REMOTES.length());
  395. return dst;
  396. }
  397. /**
  398. * Check if the arguments contain a help option
  399. *
  400. * @param args
  401. * non null
  402. * @return true if the given array contains help option
  403. * @since 4.2
  404. */
  405. public static boolean containsHelp(String[] args) {
  406. for (String str : args) {
  407. if (str.equals("-h") || str.equals("--help")) { //$NON-NLS-1$ //$NON-NLS-2$
  408. return true;
  409. }
  410. }
  411. return false;
  412. }
  413. /**
  414. * Exception thrown by {@link TextBuiltin} if it proceeds 'help' option
  415. *
  416. * @since 4.2
  417. */
  418. public static class TerminatedByHelpException extends Die {
  419. private static final long serialVersionUID = 1L;
  420. /**
  421. * Default constructor
  422. */
  423. public TerminatedByHelpException() {
  424. super(true);
  425. }
  426. }
  427. }