AddCommand.java

  1. /*
  2.  * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
  3.  * Copyright (C) 2010, Stefan Lay <stefan.lay@sap.com> 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.api;

  12. import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
  13. import static org.eclipse.jgit.lib.FileMode.GITLINK;
  14. import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK;
  15. import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;

  16. import java.io.IOException;
  17. import java.io.InputStream;
  18. import java.time.Instant;
  19. import java.util.Collection;
  20. import java.util.LinkedList;

  21. import org.eclipse.jgit.api.errors.FilterFailedException;
  22. import org.eclipse.jgit.api.errors.GitAPIException;
  23. import org.eclipse.jgit.api.errors.JGitInternalException;
  24. import org.eclipse.jgit.api.errors.NoFilepatternException;
  25. import org.eclipse.jgit.dircache.DirCache;
  26. import org.eclipse.jgit.dircache.DirCacheBuildIterator;
  27. import org.eclipse.jgit.dircache.DirCacheBuilder;
  28. import org.eclipse.jgit.dircache.DirCacheEntry;
  29. import org.eclipse.jgit.dircache.DirCacheIterator;
  30. import org.eclipse.jgit.internal.JGitText;
  31. import org.eclipse.jgit.lib.FileMode;
  32. import org.eclipse.jgit.lib.ObjectId;
  33. import org.eclipse.jgit.lib.ObjectInserter;
  34. import org.eclipse.jgit.lib.Repository;
  35. import org.eclipse.jgit.treewalk.FileTreeIterator;
  36. import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
  37. import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
  38. import org.eclipse.jgit.treewalk.WorkingTreeIterator;
  39. import org.eclipse.jgit.treewalk.filter.PathFilterGroup;

  40. /**
  41.  * A class used to execute a {@code Add} command. It has setters for all
  42.  * supported options and arguments of this command and a {@link #call()} method
  43.  * to finally execute the command. Each instance of this class should only be
  44.  * used for one invocation of the command (means: one call to {@link #call()})
  45.  *
  46.  * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-add.html"
  47.  *      >Git documentation about Add</a>
  48.  */
  49. public class AddCommand extends GitCommand<DirCache> {

  50.     private Collection<String> filepatterns;

  51.     private WorkingTreeIterator workingTreeIterator;

  52.     private boolean update = false;

  53.     /**
  54.      * Constructor for AddCommand
  55.      *
  56.      * @param repo
  57.      *            the {@link org.eclipse.jgit.lib.Repository}
  58.      */
  59.     public AddCommand(Repository repo) {
  60.         super(repo);
  61.         filepatterns = new LinkedList<>();
  62.     }

  63.     /**
  64.      * Add a path to a file/directory whose content should be added.
  65.      * <p>
  66.      * A directory name (e.g. <code>dir</code> to add <code>dir/file1</code> and
  67.      * <code>dir/file2</code>) can also be given to add all files in the
  68.      * directory, recursively. Fileglobs (e.g. *.c) are not yet supported.
  69.      *
  70.      * @param filepattern
  71.      *            repository-relative path of file/directory to add (with
  72.      *            <code>/</code> as separator)
  73.      * @return {@code this}
  74.      */
  75.     public AddCommand addFilepattern(String filepattern) {
  76.         checkCallable();
  77.         filepatterns.add(filepattern);
  78.         return this;
  79.     }

  80.     /**
  81.      * Allow clients to provide their own implementation of a FileTreeIterator
  82.      *
  83.      * @param f
  84.      *            a {@link org.eclipse.jgit.treewalk.WorkingTreeIterator}
  85.      *            object.
  86.      * @return {@code this}
  87.      */
  88.     public AddCommand setWorkingTreeIterator(WorkingTreeIterator f) {
  89.         workingTreeIterator = f;
  90.         return this;
  91.     }

  92.     /**
  93.      * {@inheritDoc}
  94.      * <p>
  95.      * Executes the {@code Add} command. Each instance of this class should only
  96.      * be used for one invocation of the command. Don't call this method twice
  97.      * on an instance.
  98.      */
  99.     @Override
  100.     public DirCache call() throws GitAPIException, NoFilepatternException {

  101.         if (filepatterns.isEmpty())
  102.             throw new NoFilepatternException(JGitText.get().atLeastOnePatternIsRequired);
  103.         checkCallable();
  104.         DirCache dc = null;
  105.         boolean addAll = filepatterns.contains("."); //$NON-NLS-1$

  106.         try (ObjectInserter inserter = repo.newObjectInserter();
  107.                 NameConflictTreeWalk tw = new NameConflictTreeWalk(repo)) {
  108.             tw.setOperationType(OperationType.CHECKIN_OP);
  109.             dc = repo.lockDirCache();

  110.             DirCacheBuilder builder = dc.builder();
  111.             tw.addTree(new DirCacheBuildIterator(builder));
  112.             if (workingTreeIterator == null)
  113.                 workingTreeIterator = new FileTreeIterator(repo);
  114.             workingTreeIterator.setDirCacheIterator(tw, 0);
  115.             tw.addTree(workingTreeIterator);
  116.             if (!addAll)
  117.                 tw.setFilter(PathFilterGroup.createFromStrings(filepatterns));

  118.             byte[] lastAdded = null;

  119.             while (tw.next()) {
  120.                 DirCacheIterator c = tw.getTree(0, DirCacheIterator.class);
  121.                 WorkingTreeIterator f = tw.getTree(1, WorkingTreeIterator.class);
  122.                 if (c == null && f != null && f.isEntryIgnored()) {
  123.                     // file is not in index but is ignored, do nothing
  124.                     continue;
  125.                 } else if (c == null && update) {
  126.                     // Only update of existing entries was requested.
  127.                     continue;
  128.                 }

  129.                 DirCacheEntry entry = c != null ? c.getDirCacheEntry() : null;
  130.                 if (entry != null && entry.getStage() > 0
  131.                         && lastAdded != null
  132.                         && lastAdded.length == tw.getPathLength()
  133.                         && tw.isPathPrefix(lastAdded, lastAdded.length) == 0) {
  134.                     // In case of an existing merge conflict the
  135.                     // DirCacheBuildIterator iterates over all stages of
  136.                     // this path, we however want to add only one
  137.                     // new DirCacheEntry per path.
  138.                     continue;
  139.                 }

  140.                 if (tw.isSubtree() && !tw.isDirectoryFileConflict()) {
  141.                     tw.enterSubtree();
  142.                     continue;
  143.                 }

  144.                 if (f == null) { // working tree file does not exist
  145.                     if (entry != null
  146.                             && (!update || GITLINK == entry.getFileMode())) {
  147.                         builder.add(entry);
  148.                     }
  149.                     continue;
  150.                 }

  151.                 if (entry != null && entry.isAssumeValid()) {
  152.                     // Index entry is marked assume valid. Even though
  153.                     // the user specified the file to be added JGit does
  154.                     // not consider the file for addition.
  155.                     builder.add(entry);
  156.                     continue;
  157.                 }

  158.                 if ((f.getEntryRawMode() == TYPE_TREE
  159.                         && f.getIndexFileMode(c) != FileMode.GITLINK) ||
  160.                         (f.getEntryRawMode() == TYPE_GITLINK
  161.                                 && f.getIndexFileMode(c) == FileMode.TREE)) {
  162.                     // Index entry exists and is symlink, gitlink or file,
  163.                     // otherwise the tree would have been entered above.
  164.                     // Replace the index entry by diving into tree of files.
  165.                     tw.enterSubtree();
  166.                     continue;
  167.                 }

  168.                 byte[] path = tw.getRawPath();
  169.                 if (entry == null || entry.getStage() > 0) {
  170.                     entry = new DirCacheEntry(path);
  171.                 }
  172.                 FileMode mode = f.getIndexFileMode(c);
  173.                 entry.setFileMode(mode);

  174.                 if (GITLINK != mode) {
  175.                     entry.setLength(f.getEntryLength());
  176.                     entry.setLastModified(f.getEntryLastModifiedInstant());
  177.                     long len = f.getEntryContentLength();
  178.                     // We read and filter the content multiple times.
  179.                     // f.getEntryContentLength() reads and filters the input and
  180.                     // inserter.insert(...) does it again. That's because an
  181.                     // ObjectInserter needs to know the length before it starts
  182.                     // inserting. TODO: Fix this by using Buffers.
  183.                     try (InputStream in = f.openEntryStream()) {
  184.                         ObjectId id = inserter.insert(OBJ_BLOB, len, in);
  185.                         entry.setObjectId(id);
  186.                     }
  187.                 } else {
  188.                     entry.setLength(0);
  189.                     entry.setLastModified(Instant.ofEpochSecond(0));
  190.                     entry.setObjectId(f.getEntryObjectId());
  191.                 }
  192.                 builder.add(entry);
  193.                 lastAdded = path;
  194.             }
  195.             inserter.flush();
  196.             builder.commit();
  197.             setCallable(false);
  198.         } catch (IOException e) {
  199.             Throwable cause = e.getCause();
  200.             if (cause != null && cause instanceof FilterFailedException)
  201.                 throw (FilterFailedException) cause;
  202.             throw new JGitInternalException(
  203.                     JGitText.get().exceptionCaughtDuringExecutionOfAddCommand, e);
  204.         } finally {
  205.             if (dc != null)
  206.                 dc.unlock();
  207.         }

  208.         return dc;
  209.     }

  210.     /**
  211.      * Set whether to only match against already tracked files
  212.      *
  213.      * @param update
  214.      *            If set to true, the command only matches {@code filepattern}
  215.      *            against already tracked files in the index rather than the
  216.      *            working tree. That means that it will never stage new files,
  217.      *            but that it will stage modified new contents of tracked files
  218.      *            and that it will remove files from the index if the
  219.      *            corresponding files in the working tree have been removed. In
  220.      *            contrast to the git command line a {@code filepattern} must
  221.      *            exist also if update is set to true as there is no concept of
  222.      *            a working directory here.
  223.      * @return {@code this}
  224.      */
  225.     public AddCommand setUpdate(boolean update) {
  226.         this.update = update;
  227.         return this;
  228.     }

  229.     /**
  230.      * Whether to only match against already tracked files
  231.      *
  232.      * @return whether to only match against already tracked files
  233.      */
  234.     public boolean isUpdate() {
  235.         return update;
  236.     }
  237. }