LogCommand.java

  1. /*
  2.  * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.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. import java.io.IOException;
  12. import java.text.MessageFormat;
  13. import java.util.ArrayList;
  14. import java.util.List;

  15. import org.eclipse.jgit.api.errors.GitAPIException;
  16. import org.eclipse.jgit.api.errors.JGitInternalException;
  17. import org.eclipse.jgit.api.errors.NoHeadException;
  18. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  19. import org.eclipse.jgit.errors.MissingObjectException;
  20. import org.eclipse.jgit.internal.JGitText;
  21. import org.eclipse.jgit.lib.AnyObjectId;
  22. import org.eclipse.jgit.lib.Constants;
  23. import org.eclipse.jgit.lib.ObjectId;
  24. import org.eclipse.jgit.lib.Ref;
  25. import org.eclipse.jgit.lib.Repository;
  26. import org.eclipse.jgit.revwalk.RevCommit;
  27. import org.eclipse.jgit.revwalk.RevWalk;
  28. import org.eclipse.jgit.revwalk.filter.AndRevFilter;
  29. import org.eclipse.jgit.revwalk.filter.MaxCountRevFilter;
  30. import org.eclipse.jgit.revwalk.filter.RevFilter;
  31. import org.eclipse.jgit.revwalk.filter.SkipRevFilter;
  32. import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
  33. import org.eclipse.jgit.treewalk.filter.PathFilter;
  34. import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
  35. import org.eclipse.jgit.treewalk.filter.TreeFilter;

  36. /**
  37.  * A class used to execute a {@code Log} command. It has setters for all
  38.  * supported options and arguments of this command and a {@link #call()} method
  39.  * to finally execute the command. Each instance of this class should only be
  40.  * used for one invocation of the command (means: one call to {@link #call()})
  41.  * <p>
  42.  * Examples (<code>git</code> is a {@link org.eclipse.jgit.api.Git} instance):
  43.  * <p>
  44.  * Get newest 10 commits, starting from the current branch:
  45.  *
  46.  * <pre>
  47.  * ObjectId head = repository.resolve(Constants.HEAD);
  48.  *
  49.  * Iterable&lt;RevCommit&gt; commits = git.log().add(head).setMaxCount(10).call();
  50.  * </pre>
  51.  * <p>
  52.  *
  53.  * <p>
  54.  * Get commits only for a specific file:
  55.  *
  56.  * <pre>
  57.  * git.log().add(head).addPath(&quot;dir/filename.txt&quot;).call();
  58.  * </pre>
  59.  * <p>
  60.  *
  61.  * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-log.html"
  62.  *      >Git documentation about Log</a>
  63.  */
  64. public class LogCommand extends GitCommand<Iterable<RevCommit>> {
  65.     private RevWalk walk;

  66.     private boolean startSpecified = false;

  67.     private RevFilter revFilter;

  68.     private final List<PathFilter> pathFilters = new ArrayList<>();
  69.     private final List<TreeFilter> excludeTreeFilters = new ArrayList<>();

  70.     private int maxCount = -1;

  71.     private int skip = -1;

  72.     /**
  73.      * Constructor for LogCommand.
  74.      *
  75.      * @param repo
  76.      *            the {@link org.eclipse.jgit.lib.Repository}
  77.      */
  78.     protected LogCommand(Repository repo) {
  79.         super(repo);
  80.         walk = new RevWalk(repo);
  81.     }

  82.     /**
  83.      * {@inheritDoc}
  84.      * <p>
  85.      * Executes the {@code Log} command with all the options and parameters
  86.      * collected by the setter methods (e.g. {@link #add(AnyObjectId)},
  87.      * {@link #not(AnyObjectId)}, ..) of this class. Each instance of this class
  88.      * should only be used for one invocation of the command. Don't call this
  89.      * method twice on an instance.
  90.      */
  91.     @Override
  92.     public Iterable<RevCommit> call() throws GitAPIException, NoHeadException {
  93.         checkCallable();
  94.         List<TreeFilter> filters = new ArrayList<>();
  95.         if (!pathFilters.isEmpty()) {
  96.             filters.add(AndTreeFilter.create(PathFilterGroup.create(pathFilters), TreeFilter.ANY_DIFF));
  97.         }
  98.         if (!excludeTreeFilters.isEmpty()) {
  99.             for (TreeFilter f : excludeTreeFilters) {
  100.                 filters.add(AndTreeFilter.create(f, TreeFilter.ANY_DIFF));
  101.             }
  102.         }
  103.         if (!filters.isEmpty()) {
  104.             if (filters.size() == 1) {
  105.                 filters.add(TreeFilter.ANY_DIFF);
  106.             }
  107.             walk.setTreeFilter(AndTreeFilter.create(filters));

  108.         }
  109.         if (skip > -1 && maxCount > -1)
  110.             walk.setRevFilter(AndRevFilter.create(SkipRevFilter.create(skip),
  111.                     MaxCountRevFilter.create(maxCount)));
  112.         else if (skip > -1)
  113.             walk.setRevFilter(SkipRevFilter.create(skip));
  114.         else if (maxCount > -1)
  115.             walk.setRevFilter(MaxCountRevFilter.create(maxCount));
  116.         if (!startSpecified) {
  117.             try {
  118.                 ObjectId headId = repo.resolve(Constants.HEAD);
  119.                 if (headId == null)
  120.                     throw new NoHeadException(
  121.                             JGitText.get().noHEADExistsAndNoExplicitStartingRevisionWasSpecified);
  122.                 add(headId);
  123.             } catch (IOException e) {
  124.                 // all exceptions thrown by add() shouldn't occur and represent
  125.                 // severe low-level exception which are therefore wrapped
  126.                 throw new JGitInternalException(
  127.                         JGitText.get().anExceptionOccurredWhileTryingToAddTheIdOfHEAD,
  128.                         e);
  129.             }
  130.         }

  131.         if (this.revFilter != null) {
  132.             walk.setRevFilter(this.revFilter);
  133.         }

  134.         setCallable(false);
  135.         return walk;
  136.     }

  137.     /**
  138.      * Mark a commit to start graph traversal from.
  139.      *
  140.      * @see RevWalk#markStart(RevCommit)
  141.      * @param start
  142.      *            the id of the commit to start from
  143.      * @return {@code this}
  144.      * @throws org.eclipse.jgit.errors.MissingObjectException
  145.      *             the commit supplied is not available from the object
  146.      *             database. This usually indicates the supplied commit is
  147.      *             invalid, but the reference was constructed during an earlier
  148.      *             invocation to
  149.      *             {@link org.eclipse.jgit.revwalk.RevWalk#lookupCommit(AnyObjectId)}.
  150.      * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
  151.      *             the object was not parsed yet and it was discovered during
  152.      *             parsing that it is not actually a commit. This usually
  153.      *             indicates the caller supplied a non-commit SHA-1 to
  154.      *             {@link org.eclipse.jgit.revwalk.RevWalk#lookupCommit(AnyObjectId)}.
  155.      * @throws JGitInternalException
  156.      *             a low-level exception of JGit has occurred. The original
  157.      *             exception can be retrieved by calling
  158.      *             {@link java.lang.Exception#getCause()}. Expect only
  159.      *             {@code IOException's} to be wrapped. Subclasses of
  160.      *             {@link java.io.IOException} (e.g.
  161.      *             {@link org.eclipse.jgit.errors.MissingObjectException}) are
  162.      *             typically not wrapped here but thrown as original exception
  163.      */
  164.     public LogCommand add(AnyObjectId start) throws MissingObjectException,
  165.             IncorrectObjectTypeException {
  166.         return add(true, start);
  167.     }

  168.     /**
  169.      * Same as {@code --not start}, or {@code ^start}
  170.      *
  171.      * @param start
  172.      *            a {@link org.eclipse.jgit.lib.AnyObjectId}
  173.      * @return {@code this}
  174.      * @throws org.eclipse.jgit.errors.MissingObjectException
  175.      *             the commit supplied is not available from the object
  176.      *             database. This usually indicates the supplied commit is
  177.      *             invalid, but the reference was constructed during an earlier
  178.      *             invocation to
  179.      *             {@link org.eclipse.jgit.revwalk.RevWalk#lookupCommit(AnyObjectId)}.
  180.      * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
  181.      *             the object was not parsed yet and it was discovered during
  182.      *             parsing that it is not actually a commit. This usually
  183.      *             indicates the caller supplied a non-commit SHA-1 to
  184.      *             {@link org.eclipse.jgit.revwalk.RevWalk#lookupCommit(AnyObjectId)}.
  185.      * @throws JGitInternalException
  186.      *             a low-level exception of JGit has occurred. The original
  187.      *             exception can be retrieved by calling
  188.      *             {@link java.lang.Exception#getCause()}. Expect only
  189.      *             {@code IOException's} to be wrapped. Subclasses of
  190.      *             {@link java.io.IOException} (e.g.
  191.      *             {@link org.eclipse.jgit.errors.MissingObjectException}) are
  192.      *             typically not wrapped here but thrown as original exception
  193.      */
  194.     public LogCommand not(AnyObjectId start) throws MissingObjectException,
  195.             IncorrectObjectTypeException {
  196.         return add(false, start);
  197.     }

  198.     /**
  199.      * Adds the range {@code since..until}
  200.      *
  201.      * @param since
  202.      *            a {@link org.eclipse.jgit.lib.AnyObjectId} object.
  203.      * @param until
  204.      *            a {@link org.eclipse.jgit.lib.AnyObjectId} object.
  205.      * @return {@code this}
  206.      * @throws org.eclipse.jgit.errors.MissingObjectException
  207.      *             the commit supplied is not available from the object
  208.      *             database. This usually indicates the supplied commit is
  209.      *             invalid, but the reference was constructed during an earlier
  210.      *             invocation to
  211.      *             {@link org.eclipse.jgit.revwalk.RevWalk#lookupCommit(AnyObjectId)}.
  212.      * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
  213.      *             the object was not parsed yet and it was discovered during
  214.      *             parsing that it is not actually a commit. This usually
  215.      *             indicates the caller supplied a non-commit SHA-1 to
  216.      *             {@link org.eclipse.jgit.revwalk.RevWalk#lookupCommit(AnyObjectId)}.
  217.      * @throws JGitInternalException
  218.      *             a low-level exception of JGit has occurred. The original
  219.      *             exception can be retrieved by calling
  220.      *             {@link java.lang.Exception#getCause()}. Expect only
  221.      *             {@code IOException's} to be wrapped. Subclasses of
  222.      *             {@link java.io.IOException} (e.g.
  223.      *             {@link org.eclipse.jgit.errors.MissingObjectException}) are
  224.      *             typically not wrapped here but thrown as original exception
  225.      */
  226.     public LogCommand addRange(AnyObjectId since, AnyObjectId until)
  227.             throws MissingObjectException, IncorrectObjectTypeException {
  228.         return not(since).add(until);
  229.     }

  230.     /**
  231.      * Add all refs as commits to start the graph traversal from.
  232.      *
  233.      * @see #add(AnyObjectId)
  234.      * @return {@code this}
  235.      * @throws java.io.IOException
  236.      *             the references could not be accessed
  237.      */
  238.     public LogCommand all() throws IOException {
  239.         for (Ref ref : getRepository().getRefDatabase().getRefs()) {
  240.             if(!ref.isPeeled())
  241.                 ref = getRepository().getRefDatabase().peel(ref);

  242.             ObjectId objectId = ref.getPeeledObjectId();
  243.             if (objectId == null)
  244.                 objectId = ref.getObjectId();
  245.             RevCommit commit = null;
  246.             try {
  247.                 commit = walk.parseCommit(objectId);
  248.             } catch (MissingObjectException | IncorrectObjectTypeException e) {
  249.                 // ignore as traversal starting point:
  250.                 // - the ref points to an object that does not exist
  251.                 // - the ref points to an object that is not a commit (e.g. a
  252.                 // tree or a blob)
  253.             }
  254.             if (commit != null)
  255.                 add(commit);
  256.         }
  257.         return this;
  258.     }

  259.     /**
  260.      * Show only commits that affect any of the specified paths. The path must
  261.      * either name a file or a directory exactly and use <code>/</code> (slash)
  262.      * as separator. Note that regex expressions or wildcards are not supported.
  263.      *
  264.      * @param path
  265.      *            a repository-relative path (with <code>/</code> as separator)
  266.      * @return {@code this}
  267.      */
  268.     public LogCommand addPath(String path) {
  269.         checkCallable();
  270.         pathFilters.add(PathFilter.create(path));
  271.         return this;
  272.     }

  273.     /**
  274.      * Show all commits that are not within any of the specified paths. The path
  275.      * must either name a file or a directory exactly and use <code>/</code>
  276.      * (slash) as separator. Note that regular expressions or wildcards are not
  277.      * yet supported. If a path is both added and excluded from the search, then
  278.      * the exclusion wins.
  279.      *
  280.      * @param path
  281.      *            a repository-relative path (with <code>/</code> as separator)
  282.      * @return {@code this}
  283.      * @since 5.6
  284.      */
  285.     public LogCommand excludePath(String path) {
  286.         checkCallable();
  287.         excludeTreeFilters.add(PathFilter.create(path).negate());
  288.         return this;
  289.     }

  290.     /**
  291.      * Skip the number of commits before starting to show the commit output.
  292.      *
  293.      * @param skip
  294.      *            the number of commits to skip
  295.      * @return {@code this}
  296.      */
  297.     public LogCommand setSkip(int skip) {
  298.         checkCallable();
  299.         this.skip = skip;
  300.         return this;
  301.     }

  302.     /**
  303.      * Limit the number of commits to output.
  304.      *
  305.      * @param maxCount
  306.      *            the limit
  307.      * @return {@code this}
  308.      */
  309.     public LogCommand setMaxCount(int maxCount) {
  310.         checkCallable();
  311.         this.maxCount = maxCount;
  312.         return this;
  313.     }

  314.     private LogCommand add(boolean include, AnyObjectId start)
  315.             throws MissingObjectException, IncorrectObjectTypeException,
  316.             JGitInternalException {
  317.         checkCallable();
  318.         try {
  319.             if (include) {
  320.                 walk.markStart(walk.lookupCommit(start));
  321.                 startSpecified = true;
  322.             } else
  323.                 walk.markUninteresting(walk.lookupCommit(start));
  324.             return this;
  325.         } catch (MissingObjectException | IncorrectObjectTypeException e) {
  326.             throw e;
  327.         } catch (IOException e) {
  328.             throw new JGitInternalException(MessageFormat.format(
  329.                     JGitText.get().exceptionOccurredDuringAddingOfOptionToALogCommand
  330.                     , start), e);
  331.         }
  332.     }


  333.     /**
  334.      * Set a filter for the <code>LogCommand</code>.
  335.      *
  336.      * @param aFilter
  337.      *            the filter that this instance of <code>LogCommand</code>
  338.      *            should use
  339.      * @return {@code this}
  340.      * @since 4.4
  341.      */
  342.     public LogCommand setRevFilter(RevFilter aFilter) {
  343.         checkCallable();
  344.         this.revFilter = aFilter;
  345.         return this;
  346.     }
  347. }