diff options
Diffstat (limited to 'org.eclipse.jgit/src/org/eclipse/jgit/api')
57 files changed, 956 insertions, 478 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java index cb32324043..b4d1cab513 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com> - * Copyright (C) 2010, Stefan Lay <stefan.lay@sap.com> and others + * Copyright (C) 2010, 2025 Stefan Lay <stefan.lay@sap.com> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -17,9 +17,10 @@ import static org.eclipse.jgit.lib.FileMode.TYPE_TREE; import java.io.IOException; import java.io.InputStream; +import java.text.MessageFormat; import java.time.Instant; -import java.util.Collection; -import java.util.LinkedList; +import java.util.ArrayList; +import java.util.List; import org.eclipse.jgit.api.errors.FilterFailedException; import org.eclipse.jgit.api.errors.GitAPIException; @@ -55,12 +56,19 @@ import org.eclipse.jgit.treewalk.filter.TreeFilter; */ public class AddCommand extends GitCommand<DirCache> { - private Collection<String> filepatterns; + private List<String> filepatterns; private WorkingTreeIterator workingTreeIterator; + // Update only known index entries, don't add new ones. If there's no file + // for an index entry, remove it: stage deletions. private boolean update = false; + // If TRUE, also stage deletions, otherwise only update and add index + // entries. + // If not set explicitly + private Boolean all; + // This defaults to true because it's what JGit has been doing // traditionally. The C git default would be false. private boolean renormalize = true; @@ -73,7 +81,7 @@ public class AddCommand extends GitCommand<DirCache> { */ public AddCommand(Repository repo) { super(repo); - filepatterns = new LinkedList<>(); + filepatterns = new ArrayList<>(); } /** @@ -82,6 +90,17 @@ public class AddCommand extends GitCommand<DirCache> { * A directory name (e.g. <code>dir</code> to add <code>dir/file1</code> and * <code>dir/file2</code>) can also be given to add all files in the * directory, recursively. Fileglobs (e.g. *.c) are not yet supported. + * </p> + * <p> + * If a pattern {@code "."} is added, all changes in the git repository's + * working tree will be added. + * </p> + * <p> + * File patterns are required unless {@code isUpdate() == true} or + * {@link #setAll(boolean)} is called. If so and no file patterns are given, + * all changes will be added (i.e., a file pattern of {@code "."} is + * implied). + * </p> * * @param filepattern * repository-relative path of file/directory to add (with @@ -113,15 +132,41 @@ public class AddCommand extends GitCommand<DirCache> { * Executes the {@code Add} command. Each instance of this class should only * be used for one invocation of the command. Don't call this method twice * on an instance. + * </p> + * + * @throws JGitInternalException + * on errors, but also if {@code isUpdate() == true} _and_ + * {@link #setAll(boolean)} had been called + * @throws NoFilepatternException + * if no file patterns are given if {@code isUpdate() == false} + * and {@link #setAll(boolean)} was not called */ @Override public DirCache call() throws GitAPIException, NoFilepatternException { - - if (filepatterns.isEmpty()) - throw new NoFilepatternException(JGitText.get().atLeastOnePatternIsRequired); checkCallable(); + + if (update && all != null) { + throw new JGitInternalException(MessageFormat.format( + JGitText.get().illegalCombinationOfArguments, + "--update", "--all/--no-all")); //$NON-NLS-1$ //$NON-NLS-2$ + } + boolean addAll; + if (filepatterns.isEmpty()) { + if (update || all != null) { + addAll = true; + } else { + throw new NoFilepatternException( + JGitText.get().atLeastOnePatternIsRequired); + } + } else { + addAll = filepatterns.contains("."); //$NON-NLS-1$ + if (all == null && !update) { + all = Boolean.TRUE; + } + } + boolean stageDeletions = update || (all != null && all.booleanValue()); + DirCache dc = null; - boolean addAll = filepatterns.contains("."); //$NON-NLS-1$ try (ObjectInserter inserter = repo.newObjectInserter(); NameConflictTreeWalk tw = new NameConflictTreeWalk(repo)) { @@ -181,7 +226,8 @@ public class AddCommand extends GitCommand<DirCache> { if (f == null) { // working tree file does not exist if (entry != null - && (!update || GITLINK == entry.getFileMode())) { + && (!stageDeletions + || GITLINK == entry.getFileMode())) { builder.add(entry); } continue; @@ -252,7 +298,8 @@ public class AddCommand extends GitCommand<DirCache> { } /** - * Set whether to only match against already tracked files + * Set whether to only match against already tracked files. If + * {@code update == true}, re-sets a previous {@link #setAll(boolean)}. * * @param update * If set to true, the command only matches {@code filepattern} @@ -314,4 +361,32 @@ public class AddCommand extends GitCommand<DirCache> { public boolean isRenormalize() { return renormalize; } + + /** + * Defines whether the command will use '--all' mode: update existing index + * entries, add new entries, and remove index entries for which there is no + * file. (In other words: also stage deletions.) + * <p> + * The setting is independent of {@link #setUpdate(boolean)}. + * </p> + * + * @param all + * whether to enable '--all' mode + * @return {@code this} + * @since 7.2 + */ + public AddCommand setAll(boolean all) { + this.all = Boolean.valueOf(all); + return this; + } + + /** + * Tells whether '--all' has been set for this command. + * + * @return {@code true} if it was set; {@code false} otherwise + * @since 7.2 + */ + public boolean isAll() { + return all != null && all.booleanValue(); + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java index ceb17fbe25..8805ea2353 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java @@ -51,7 +51,6 @@ public class AddNoteCommand extends GitCommand<Note> { super(repo); } - /** {@inheritDoc} */ @Override public Note call() throws GitAPIException { checkCallable(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java index e612924771..df0f616256 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java @@ -37,11 +37,12 @@ public class ApplyCommand extends GitCommand<ApplyResult> { /** * Constructs the command. * - * @param local + * @param repo + * the repository this command will be used on */ - ApplyCommand(Repository local) { - super(local); - if (local == null) { + ApplyCommand(Repository repo) { + super(repo); + if (repo == null) { throw new NullPointerException(JGitText.get().repositoryIsRequired); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java index fdf8b80cd4..81b3611774 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java @@ -177,6 +177,8 @@ public class ArchiveCommand extends GitCommand<OutputStream> { } /** + * Get the problematic format name + * * @return the problematic format name */ public String getFormat() { @@ -389,7 +391,6 @@ public class ArchiveCommand extends GitCommand<OutputStream> { } } - /** {@inheritDoc} */ @Override public OutputStream call() throws GitAPIException { checkCallable(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java index 7319ff4b2f..32c242f271 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com> - * Copyright (C) 2011, 2020 Matthias Sohn <matthias.sohn@sap.com> and others + * Copyright (C) 2011, 2023 Matthias Sohn <matthias.sohn@sap.com> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -17,7 +17,6 @@ import java.text.MessageFormat; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -28,6 +27,7 @@ import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.RefAlreadyExistsException; import org.eclipse.jgit.api.errors.RefNotFoundException; +import org.eclipse.jgit.dircache.Checkout; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata; @@ -55,7 +55,6 @@ import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.TreeWalk; -import org.eclipse.jgit.treewalk.WorkingTreeOptions; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; /** @@ -164,10 +163,9 @@ public class CheckoutCommand extends GitCommand<Ref> { */ protected CheckoutCommand(Repository repo) { super(repo); - this.paths = new LinkedList<>(); + this.paths = new ArrayList<>(); } - /** {@inheritDoc} */ @Override public Ref call() throws GitAPIException, RefAlreadyExistsException, RefNotFoundException, InvalidRefNameException, @@ -326,6 +324,8 @@ public class CheckoutCommand extends GitCommand<Ref> { } /** + * Set progress monitor + * * @param monitor * a progress monitor * @return this instance @@ -407,13 +407,14 @@ public class CheckoutCommand extends GitCommand<Ref> { * * @return this instance * @throws java.io.IOException + * if an IO error occurred * @throws org.eclipse.jgit.api.errors.RefNotFoundException + * if {@code Ref} couldn't be resolved */ protected CheckoutCommand checkoutPaths() throws IOException, RefNotFoundException { actuallyModifiedPaths = new HashSet<>(); - WorkingTreeOptions options = repo.getConfig() - .get(WorkingTreeOptions.KEY); + Checkout checkout = new Checkout(repo).setRecursiveDeletion(true); DirCache dc = repo.lockDirCache(); try (RevWalk revWalk = new RevWalk(repo); TreeWalk treeWalk = new TreeWalk(repo, @@ -422,10 +423,10 @@ public class CheckoutCommand extends GitCommand<Ref> { if (!checkoutAllPaths) treeWalk.setFilter(PathFilterGroup.createFromStrings(paths)); if (isCheckoutIndex()) - checkoutPathsFromIndex(treeWalk, dc, options); + checkoutPathsFromIndex(treeWalk, dc, checkout); else { RevCommit commit = revWalk.parseCommit(getStartPointObjectId()); - checkoutPathsFromCommit(treeWalk, dc, commit, options); + checkoutPathsFromCommit(treeWalk, dc, commit, checkout); } } finally { try { @@ -443,7 +444,7 @@ public class CheckoutCommand extends GitCommand<Ref> { } private void checkoutPathsFromIndex(TreeWalk treeWalk, DirCache dc, - WorkingTreeOptions options) + Checkout checkout) throws IOException { DirCacheIterator dci = new DirCacheIterator(dc); treeWalk.addTree(dci); @@ -469,7 +470,7 @@ public class CheckoutCommand extends GitCommand<Ref> { if (stage > DirCacheEntry.STAGE_0) { if (checkoutStage != null) { if (stage == checkoutStage.number) { - checkoutPath(ent, r, options, + checkoutPath(ent, r, checkout, path, new CheckoutMetadata(eolStreamType, filterCommand)); actuallyModifiedPaths.add(path); @@ -480,7 +481,7 @@ public class CheckoutCommand extends GitCommand<Ref> { throw new JGitInternalException(e.getMessage(), e); } } else { - checkoutPath(ent, r, options, + checkoutPath(ent, r, checkout, path, new CheckoutMetadata(eolStreamType, filterCommand)); actuallyModifiedPaths.add(path); @@ -494,7 +495,7 @@ public class CheckoutCommand extends GitCommand<Ref> { } private void checkoutPathsFromCommit(TreeWalk treeWalk, DirCache dc, - RevCommit commit, WorkingTreeOptions options) throws IOException { + RevCommit commit, Checkout checkout) throws IOException { treeWalk.addTree(commit.getTree()); final ObjectReader r = treeWalk.getObjectReader(); DirCacheEditor editor = dc.editor(); @@ -516,7 +517,7 @@ public class CheckoutCommand extends GitCommand<Ref> { } ent.setObjectId(blobId); ent.setFileMode(mode); - checkoutPath(ent, r, options, + checkoutPath(ent, r, checkout, path, new CheckoutMetadata(eolStreamType, filterCommand)); actuallyModifiedPaths.add(path); } @@ -526,10 +527,9 @@ public class CheckoutCommand extends GitCommand<Ref> { } private void checkoutPath(DirCacheEntry entry, ObjectReader reader, - WorkingTreeOptions options, CheckoutMetadata checkoutMetadata) { + Checkout checkout, String path, CheckoutMetadata checkoutMetadata) { try { - DirCacheCheckout.checkoutEntry(repo, entry, reader, true, - checkoutMetadata, options); + checkout.checkout(entry, checkoutMetadata, reader, path); } catch (IOException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().checkoutConflictWithFile, @@ -644,24 +644,6 @@ public class CheckoutCommand extends GitCommand<Ref> { /** * Specify to force the ref update in case of a branch switch. * - * @param force - * if <code>true</code> and the branch with the given name - * already exists, the start-point of an existing branch will be - * set to a new start-point; if false, the existing branch will - * not be changed - * @return this instance - * @deprecated this method was badly named comparing its semantics to native - * git's checkout --force option, use - * {@link #setForceRefUpdate(boolean)} instead - */ - @Deprecated - public CheckoutCommand setForce(boolean force) { - return setForceRefUpdate(force); - } - - /** - * Specify to force the ref update in case of a branch switch. - * * In releases prior to 5.2 this method was called setForce() but this name * was misunderstood to implement native git's --force option, which is not * true. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java index 5f8c2b728a..a353d1a135 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java @@ -9,11 +9,12 @@ */ package org.eclipse.jgit.api; +import static org.eclipse.jgit.api.CherryPickCommitMessageProvider.ORIGINAL; import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH; import java.io.IOException; import java.text.MessageFormat; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -62,10 +63,12 @@ import org.eclipse.jgit.treewalk.FileTreeIterator; public class CherryPickCommand extends GitCommand<CherryPickResult> { private String reflogPrefix = "cherry-pick:"; //$NON-NLS-1$ - private List<Ref> commits = new LinkedList<>(); + private List<Ref> commits = new ArrayList<>(); private String ourCommitName = null; + private CherryPickCommitMessageProvider messageProvider = ORIGINAL; + private MergeStrategy strategy = MergeStrategy.RECURSIVE; private ContentMergeStrategy contentStrategy; @@ -99,7 +102,7 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> { UnmergedPathsException, ConcurrentRefUpdateException, WrongRepositoryStateException, NoHeadException { RevCommit newHead = null; - List<Ref> cherryPickedRefs = new LinkedList<>(); + List<Ref> cherryPickedRefs = new ArrayList<>(); checkCallable(); try (RevWalk revWalk = new RevWalk(repo)) { @@ -168,8 +171,10 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> { dco.checkout(); if (!noCommit) { try (Git git = new Git(getRepository())) { + String commitMessage = messageProvider + .getCherryPickedCommitMessage(srcCommit); newHead = git.commit() - .setMessage(srcCommit.getFullMessage()) + .setMessage(commitMessage) .setReflogComment(reflogPrefix + " " //$NON-NLS-1$ + srcCommit.getShortMessage()) .setAuthor(srcCommit.getAuthorIdent()) @@ -297,6 +302,22 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> { } /** + * Set a message provider for a target cherry-picked commit<br> + * By default original commit message is used (see + * {@link CherryPickCommitMessageProvider#ORIGINAL}) + * + * @param messageProvider + * the commit message provider + * @return {@code this} + * @since 6.9 + */ + public CherryPickCommand setCherryPickCommitMessageProvider( + CherryPickCommitMessageProvider messageProvider) { + this.messageProvider = messageProvider; + return this; + } + + /** * Set the prefix to use in the reflog. * <p> * This is primarily needed for implementing rebase in terms of @@ -399,7 +420,6 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> { return headName; } - /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommitMessageProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommitMessageProvider.java new file mode 100644 index 0000000000..50d65b6fa8 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommitMessageProvider.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2023 Dmitrii Naumenko <dmitrii.naumenko@jetbrains.com> + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is aailable at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.api; + +import java.util.List; + +import org.eclipse.jgit.revwalk.FooterLine; +import org.eclipse.jgit.revwalk.RevCommit; + +/** + * The interface is used to construct a cherry-picked commit message based on + * the original commit + * + * @see #ORIGINAL + * @see #ORIGINAL_WITH_REFERENCE + * @since 6.9 + */ +public interface CherryPickCommitMessageProvider { + + /** + * This provider returns the original commit message + */ + static final CherryPickCommitMessageProvider ORIGINAL = RevCommit::getFullMessage; + + /** + * This provider returns the original commit message with original commit + * hash in SHA-1 form.<br> + * Example: + * + * <pre> + * <code>my original commit message + * + * (cherry picked from commit 75355897dc28e9975afed028c1a6d8c6b97b2a3c)</code> + * </pre> + * + * This is similar to <code>-x</code> flag in git-scm (see <a href= + * "https://git-scm.com/docs/git-cherry-pick#_options">https://git-scm.com/docs/git-cherry-pick#_options</a>) + */ + static final CherryPickCommitMessageProvider ORIGINAL_WITH_REFERENCE = srcCommit -> { + String fullMessage = srcCommit.getFullMessage(); + + // Don't add extra new line after footer (aka trailer) + // https://stackoverflow.com/questions/70007405/git-log-exclude-cherry-pick-messages-for-trailers + // https://lore.kernel.org/git/7vmx136cdc.fsf@alter.siamese.dyndns.org + String separator = messageEndsWithFooter(srcCommit) ? "\n" : "\n\n"; //$NON-NLS-1$//$NON-NLS-2$ + String revisionString = srcCommit.getName(); + return String.format("%s%s(cherry picked from commit %s)", //$NON-NLS-1$ + fullMessage, separator, revisionString); + }; + + /** + * @param srcCommit + * original cherry-picked commit + * @return target cherry-picked commit message + */ + String getCherryPickedCommitMessage(RevCommit srcCommit); + + private static boolean messageEndsWithFooter(RevCommit srcCommit) { + byte[] rawBuffer = srcCommit.getRawBuffer(); + List<FooterLine> footers = srcCommit.getFooterLines(); + int maxFooterEnd = footers.stream().mapToInt(FooterLine::getEndOffset) + .max().orElse(-1); + return rawBuffer.length == maxFooterEnd; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java index 55d86b7402..995f890389 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java @@ -25,21 +25,21 @@ public class CherryPickResult { * The cherry-pick status */ public enum CherryPickStatus { - /** */ + /** Cherry-pick succeeded */ OK { @Override public String toString() { return "Ok"; //$NON-NLS-1$ } }, - /** */ + /** Cherry-pick failed */ FAILED { @Override public String toString() { return "Failed"; //$NON-NLS-1$ } }, - /** */ + /** Cherry-pick found conflicts to be resolved */ CONFLICTING { @Override public String toString() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java index 36ca97d694..a4a0c49f45 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java @@ -113,24 +113,25 @@ public class CleanCommand extends GitCommand<Set<String>> { } /** - * When dryRun is false, deletes the specified path from disk. If dryRun - * is true, no paths are actually deleted. In both cases, the paths that - * would have been deleted are added to inFiles and returned. + * When dryRun is false, deletes the specified path from disk. If dryRun is + * true, no paths are actually deleted. In both cases, the paths that would + * have been deleted are added to inFiles and returned. * * Paths that are directories are recursively deleted when - * {@link #directories} is true. - * Paths that are git repositories are recursively deleted when - * {@link #directories} and {@link #force} are both true. + * {@link #directories} is true. Paths that are git repositories are + * recursively deleted when {@link #directories} and {@link #force} are both + * true. * * @param path - * The path to be cleaned + * The path to be cleaned * @param inFiles - * A set of strings representing the files that have been cleaned - * already, the path to be cleaned will be added to this set - * before being returned. + * A set of strings representing the files that have been cleaned + * already, the path to be cleaned will be added to this set + * before being returned. * * @return a set of strings with the cleaned path added to it * @throws IOException + * if an IO error occurred */ private Set<String> cleanPath(String path, Set<String> inFiles) throws IOException { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java index 107b00e274..4a536b9534 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java @@ -29,6 +29,7 @@ import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.util.ShutdownHook; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode; import org.eclipse.jgit.lib.ConfigConstants; @@ -66,6 +67,8 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { private boolean bare; + private boolean relativePaths; + private FS fs; private String remote = Constants.DEFAULT_REMOTE_NAME; @@ -100,6 +103,8 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { private List<String> shallowExcludes = new ArrayList<>(); + private ShutdownHook.Listener shutdownListener = this::cleanup; + private enum FETCH_TYPE { MULTIPLE_BRANCHES, ALL_BRANCHES, MIRROR } @@ -181,12 +186,7 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { @SuppressWarnings("resource") // Closed by caller Repository repository = init(); FetchResult fetchResult = null; - Thread cleanupHook = new Thread(() -> cleanup()); - try { - Runtime.getRuntime().addShutdownHook(cleanupHook); - } catch (IllegalStateException e) { - // ignore - the VM is already shutting down - } + ShutdownHook.INSTANCE.register(shutdownListener); try { fetchResult = fetch(repository, u); } catch (IOException ioe) { @@ -210,11 +210,7 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { cleanup(); throw e; } finally { - try { - Runtime.getRuntime().removeShutdownHook(cleanupHook); - } catch (IllegalStateException e) { - // ignore - the VM is already shutting down - } + ShutdownHook.INSTANCE.unregister(shutdownListener); } try { checkout(repository, fetchResult); @@ -270,6 +266,7 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { private Repository init() throws GitAPIException { InitCommand command = Git.init(); command.setBare(bare); + command.setRelativeDirs(relativePaths); if (fs != null) { command.setFs(fs); } @@ -561,6 +558,20 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { } /** + * Set whether the cloned repository shall use relative paths for GIT_DIR + * and GIT_WORK_TREE + * + * @param relativePaths + * if true, use relative paths for GIT_DIR and GIT_WORK_TREE + * @return this instance + * @since 7.2 + */ + public CloneCommand setRelativePaths(boolean relativePaths) { + this.relativePaths = relativePaths; + return this; + } + + /** * Set the file system abstraction to be used for repositories created by * this command. * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java index 3b3baf5a12..a7d409c3f5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java @@ -16,7 +16,6 @@ import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.LinkedList; import java.util.List; import org.eclipse.jgit.annotations.NonNull; @@ -52,9 +51,6 @@ import org.eclipse.jgit.lib.CommitConfig.CleanupMode; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.GpgConfig; -import org.eclipse.jgit.lib.GpgConfig.GpgFormat; -import org.eclipse.jgit.lib.GpgObjectSigner; -import org.eclipse.jgit.lib.GpgSigner; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PersonIdent; @@ -63,6 +59,8 @@ import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryState; +import org.eclipse.jgit.lib.Signer; +import org.eclipse.jgit.lib.Signers; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; @@ -109,7 +107,7 @@ public class CommitCommand extends GitCommand<RevCommit> { * parents this commit should have. The current HEAD will be in this list * and also all commits mentioned in .git/MERGE_HEAD */ - private List<ObjectId> parents = new LinkedList<>(); + private List<ObjectId> parents = new ArrayList<>(); private String reflogComment; @@ -130,7 +128,7 @@ public class CommitCommand extends GitCommand<RevCommit> { private String signingKey; - private GpgSigner gpgSigner; + private Signer signer; private GpgConfig gpgConfig; @@ -320,30 +318,22 @@ public class CommitCommand extends GitCommand<RevCommit> { } } - private void sign(CommitBuilder commit) throws ServiceUnavailableException, - CanceledException, UnsupportedSigningFormatException { - if (gpgSigner == null) { - gpgSigner = GpgSigner.getDefault(); - if (gpgSigner == null) { - throw new ServiceUnavailableException( - JGitText.get().signingServiceUnavailable); + private void sign(CommitBuilder commit) + throws CanceledException, IOException, + UnsupportedSigningFormatException { + if (signer == null) { + signer = Signers.get(gpgConfig.getKeyFormat()); + if (signer == null) { + throw new UnsupportedSigningFormatException(MessageFormat + .format(JGitText.get().signatureTypeUnknown, + gpgConfig.getKeyFormat().toConfigValue())); } } if (signingKey == null) { signingKey = gpgConfig.getSigningKey(); } - if (gpgSigner instanceof GpgObjectSigner) { - ((GpgObjectSigner) gpgSigner).signObject(commit, - signingKey, committer, credentialsProvider, - gpgConfig); - } else { - if (gpgConfig.getKeyFormat() != GpgFormat.OPENPGP) { - throw new UnsupportedSigningFormatException(JGitText - .get().onlyOpenPgpSupportedForSigning); - } - gpgSigner.sign(commit, signingKey, committer, - credentialsProvider); - } + signer.signObject(repo, gpgConfig, commit, committer, signingKey, + credentialsProvider); } private void updateRef(RepositoryState state, ObjectId headId, @@ -614,7 +604,7 @@ public class CommitCommand extends GitCommand<RevCommit> { // specified the commit should not be empty. This behaviour differs // from native git but can only be adapted in the next release. // TODO(ch) align the defaults with native git - allowEmpty = (only.isEmpty()) ? Boolean.TRUE : Boolean.FALSE; + allowEmpty = only.isEmpty() ? Boolean.TRUE : Boolean.FALSE; // when doing a merge commit parse MERGE_HEAD and MERGE_MSG files if (state == RepositoryState.MERGING_RESOLVED @@ -1098,22 +1088,22 @@ public class CommitCommand extends GitCommand<RevCommit> { } /** - * Sets the {@link GpgSigner} to use if the commit is to be signed. + * Sets the {@link Signer} to use if the commit is to be signed. * * @param signer * to use; if {@code null}, the default signer will be used * @return {@code this} - * @since 5.11 + * @since 7.0 */ - public CommitCommand setGpgSigner(GpgSigner signer) { + public CommitCommand setSigner(Signer signer) { checkCallable(); - this.gpgSigner = signer; + this.signer = signer; return this; } /** * Sets an external {@link GpgConfig} to use. Whether it will be used is at - * the discretion of the {@link #setGpgSigner(GpgSigner)}. + * the discretion of the {@link #setSigner(Signer)}. * * @param config * to set; if {@code null}, the config will be loaded from the diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CreateBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CreateBranchCommand.java index 9e77dd7348..013e0ff131 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CreateBranchCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CreateBranchCommand.java @@ -82,16 +82,13 @@ public class CreateBranchCommand extends GitCommand<Ref> { super(repo); } - /** {@inheritDoc} */ @Override public Ref call() throws GitAPIException, RefAlreadyExistsException, RefNotFoundException, InvalidRefNameException { checkCallable(); processOptions(); try (RevWalk revWalk = new RevWalk(repo)) { - Ref refToCheck = repo.findRef(name); - boolean exists = refToCheck != null - && refToCheck.getName().startsWith(R_HEADS); + boolean exists = repo.findRef(R_HEADS + name) != null; if (!force && exists) throw new RefAlreadyExistsException(MessageFormat.format( JGitText.get().refAlreadyExists1, name)); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java index 3d3ee63568..a5c535f772 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com> - * Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com> and others + * Copyright (C) 2010, 2023 Chris Aniszczyk <caniszczyk@gmail.com> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -14,6 +14,7 @@ import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -25,6 +26,8 @@ import org.eclipse.jgit.api.errors.NotMergedException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; @@ -47,8 +50,11 @@ import org.eclipse.jgit.revwalk.RevWalk; * >Git documentation about Branch</a> */ public class DeleteBranchCommand extends GitCommand<List<String>> { + private final Set<String> branchNames = new HashSet<>(); + private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; + private boolean force; /** @@ -61,14 +67,34 @@ public class DeleteBranchCommand extends GitCommand<List<String>> { super(repo); } - /** {@inheritDoc} */ @Override public List<String> call() throws GitAPIException, NotMergedException, CannotDeleteCurrentBranchException { checkCallable(); List<String> result = new ArrayList<>(); - if (branchNames.isEmpty()) + Set<String> shortNames = new HashSet<>(); + if (branchNames.isEmpty()) { return result; + } + Exception error = null; + try { + deleteBranches(result, shortNames); + } catch (Exception e) { + error = e; + } + monitor.beginTask(JGitText.get().updatingConfig, 1); + try { + updateConfig(shortNames, error); + } finally { + monitor.update(1); + monitor.endTask(); + } + return result; + } + + private void deleteBranches(List<String> result, + Set<String> shortNames) throws GitAPIException, NotMergedException, + CannotDeleteCurrentBranchException { try { String currentBranch = repo.getFullBranch(); if (!force) { @@ -78,12 +104,13 @@ public class DeleteBranchCommand extends GitCommand<List<String>> { RevCommit tip = walk .parseCommit(repo.resolve(Constants.HEAD)); for (String branchName : branchNames) { - if (branchName == null) + if (branchName == null) { continue; + } Ref currentRef = repo.findRef(branchName); - if (currentRef == null) + if (currentRef == null) { continue; - + } RevCommit base = walk .parseCommit(repo.resolve(branchName)); if (!walk.isMergedInto(base, tip)) { @@ -93,58 +120,105 @@ public class DeleteBranchCommand extends GitCommand<List<String>> { } } setCallable(false); - for (String branchName : branchNames) { - if (branchName == null) - continue; - Ref currentRef = repo.findRef(branchName); - if (currentRef == null) - continue; - String fullName = currentRef.getName(); - if (fullName.equals(currentBranch)) - throw new CannotDeleteCurrentBranchException( - MessageFormat - .format( - JGitText.get().cannotDeleteCheckedOutBranch, - branchName)); - RefUpdate update = repo.updateRef(fullName); - update.setRefLogMessage("branch deleted", false); //$NON-NLS-1$ - update.setForceUpdate(true); - Result deleteResult = update.delete(); - - boolean ok = true; - switch (deleteResult) { - case IO_FAILURE: - case LOCK_FAILURE: - case REJECTED: - ok = false; - break; - default: - break; - } + monitor.start(2); + monitor.beginTask(JGitText.get().deletingBranches, + branchNames.size()); + try { + for (String branchName : branchNames) { + if (branchName == null) { + monitor.update(1); + continue; + } + Ref currentRef = repo.findRef(branchName); + if (currentRef == null) { + monitor.update(1); + continue; + } + String fullName = currentRef.getName(); + if (fullName.equals(currentBranch)) { + throw new CannotDeleteCurrentBranchException( + MessageFormat.format(JGitText + .get().cannotDeleteCheckedOutBranch, + branchName)); + } + RefUpdate update = repo.updateRef(fullName); + update.setRefLogMessage("branch deleted", false); //$NON-NLS-1$ + update.setForceUpdate(true); + Result deleteResult = update.delete(); - if (ok) { - result.add(fullName); - if (fullName.startsWith(Constants.R_HEADS)) { - String shortenedName = fullName - .substring(Constants.R_HEADS.length()); - // remove upstream configuration if any - final StoredConfig cfg = repo.getConfig(); - cfg.unsetSection( - ConfigConstants.CONFIG_BRANCH_SECTION, - shortenedName); - cfg.save(); + switch (deleteResult) { + case IO_FAILURE: + case LOCK_FAILURE: + case REJECTED: + throw new JGitInternalException(MessageFormat.format( + JGitText.get().deleteBranchUnexpectedResult, + deleteResult.name())); + default: + result.add(fullName); + if (fullName.startsWith(Constants.R_HEADS)) { + shortNames.add(fullName + .substring(Constants.R_HEADS.length())); + } + break; + } + monitor.update(1); + if (monitor.isCancelled()) { + break; } - } else - throw new JGitInternalException(MessageFormat.format( - JGitText.get().deleteBranchUnexpectedResult, - deleteResult.name())); + } + } finally { + monitor.endTask(); } - return result; } catch (IOException ioe) { throw new JGitInternalException(ioe.getMessage(), ioe); } } + private void updateConfig(Set<String> shortNames, Exception error) + throws GitAPIException { + IOException configError = null; + if (!shortNames.isEmpty()) { + try { + // Remove upstream configurations if any + StoredConfig cfg = repo.getConfig(); + boolean changed = false; + for (String branchName : shortNames) { + changed |= cfg.removeSection( + ConfigConstants.CONFIG_BRANCH_SECTION, + branchName); + } + if (changed) { + cfg.save(); + } + } catch (IOException e) { + configError = e; + } + } + if (error == null) { + if (configError != null) { + throw new JGitInternalException(configError.getMessage(), + configError); + } + } else if (error instanceof GitAPIException) { + if (configError != null) { + error.addSuppressed(configError); + } + throw (GitAPIException) error; + } else if (error instanceof RuntimeException) { + if (configError != null) { + error.addSuppressed(configError); + } + throw (RuntimeException) error; + } else { + JGitInternalException internal = new JGitInternalException( + error.getMessage(), error); + if (configError != null) { + internal.addSuppressed(configError); + } + throw internal; + } + } + /** * Set the names of the branches to delete * @@ -161,6 +235,22 @@ public class DeleteBranchCommand extends GitCommand<List<String>> { } /** + * Sets the names of the branches to delete + * + * @param branchNames + * the names of the branches to delete; if not set, this will do + * nothing; invalid branch names will simply be ignored + * @return {@code this} + * @since 6.8 + */ + public DeleteBranchCommand setBranchNames(Collection<String> branchNames) { + checkCallable(); + this.branchNames.clear(); + this.branchNames.addAll(branchNames); + return this; + } + + /** * Set whether to forcefully delete branches * * @param force @@ -176,4 +266,34 @@ public class DeleteBranchCommand extends GitCommand<List<String>> { this.force = force; return this; } + + /** + * Retrieves the progress monitor. + * + * @return the {@link ProgressMonitor} for the delete operation + * @since 6.8 + */ + public ProgressMonitor getProgressMonitor() { + return monitor; + } + + /** + * Sets the progress monitor associated with the delete operation. By + * default, this is set to <code>NullProgressMonitor</code> + * + * @see NullProgressMonitor + * @param monitor + * a {@link ProgressMonitor} + * @return {@code this} + * @since 6.8 + */ + public DeleteBranchCommand setProgressMonitor(ProgressMonitor monitor) { + checkCallable(); + if (monitor == null) { + monitor = NullProgressMonitor.INSTANCE; + } + this.monitor = monitor; + return this; + } + } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteTagCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteTagCommand.java index 64d0d94171..92d88aad7d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteTagCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteTagCommand.java @@ -48,7 +48,6 @@ public class DeleteTagCommand extends GitCommand<List<String>> { super(repo); } - /** {@inheritDoc} */ @Override public List<String> call() throws GitAPIException { checkCallable(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java index 805a886392..d2526287f9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java @@ -15,11 +15,11 @@ import static org.eclipse.jgit.lib.TypedConfigGetter.UNSET_INT; import java.io.IOException; import java.text.MessageFormat; +import java.time.Instant; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; -import java.util.Date; import java.util.List; import java.util.Map; import java.util.Optional; @@ -76,6 +76,11 @@ public class DescribeCommand extends GitCommand<String> { private List<FileNameMatcher> matchers = new ArrayList<>(); /** + * Pattern matchers to be applied to tags for exclusion. + */ + private List<FileNameMatcher> excludeMatchers = new ArrayList<>(); + + /** * Whether to use all refs in the refs/ namespace */ private boolean useAll; @@ -263,6 +268,27 @@ public class DescribeCommand extends GitCommand<String> { return this; } + /** + * Sets one or more {@code glob(7)} patterns that tags must not match to be + * considered. If multiple patterns are provided, they will all be applied. + * + * @param patterns + * the {@code glob(7)} pattern or patterns + * @return {@code this} + * @throws org.eclipse.jgit.errors.InvalidPatternException + * if the pattern passed in was invalid. + * @see <a href= + * "https://www.kernel.org/pub/software/scm/git/docs/git-describe.html" + * >Git documentation about describe</a> + * @since 7.2 + */ + public DescribeCommand setExclude(String... patterns) throws InvalidPatternException { + for (String p : patterns) { + excludeMatchers.add(new FileNameMatcher(p, null)); + } + return this; + } + private final Comparator<Ref> TAG_TIE_BREAKER = new Comparator<>() { @Override @@ -274,25 +300,28 @@ public class DescribeCommand extends GitCommand<String> { } } - private Date tagDate(Ref tag) throws IOException { + private Instant tagDate(Ref tag) throws IOException { RevTag t = w.parseTag(tag.getObjectId()); w.parseBody(t); - return t.getTaggerIdent().getWhen(); + return t.getTaggerIdent().getWhenAsInstant(); } }; private Optional<Ref> getBestMatch(List<Ref> tags) { if (tags == null || tags.isEmpty()) { return Optional.empty(); - } else if (matchers.isEmpty()) { + } else if (matchers.isEmpty() && excludeMatchers.isEmpty()) { Collections.sort(tags, TAG_TIE_BREAKER); return Optional.of(tags.get(0)); - } else { + } + + Stream<Ref> matchingTags; + if (!matchers.isEmpty()) { // Find the first tag that matches in the stream of all tags // filtered by matchers ordered by tie break order - Stream<Ref> matchingTags = Stream.empty(); + matchingTags = Stream.empty(); for (FileNameMatcher matcher : matchers) { - Stream<Ref> m = tags.stream().filter( + Stream<Ref> m = tags.stream().filter( // tag -> { matcher.append(formatRefName(tag.getName())); boolean result = matcher.isMatch(); @@ -301,8 +330,22 @@ public class DescribeCommand extends GitCommand<String> { }); matchingTags = Stream.of(matchingTags, m).flatMap(i -> i); } - return matchingTags.sorted(TAG_TIE_BREAKER).findFirst(); + } else { + // If there are no matchers, there are only excluders + // Assume all tags match for now before applying excluders + matchingTags = tags.stream(); + } + + for (FileNameMatcher matcher : excludeMatchers) { + matchingTags = matchingTags.filter( // + tag -> { + matcher.append(formatRefName(tag.getName())); + boolean result = matcher.isMatch(); + matcher.reset(); + return !result; + }); } + return matchingTags.sorted(TAG_TIE_BREAKER).findFirst(); } private ObjectId getObjectIdFromRef(Ref r) throws JGitInternalException { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java index 3c772c2765..f24127bd51 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java @@ -124,7 +124,7 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> { FetchRecurseSubmodulesMode mode = repo.getConfig().getEnum( FetchRecurseSubmodulesMode.values(), ConfigConstants.CONFIG_SUBMODULE_SECTION, path, - ConfigConstants.CONFIG_KEY_FETCH_RECURSE_SUBMODULES, null); + ConfigConstants.CONFIG_KEY_FETCH_RECURSE_SUBMODULES); if (mode != null) { return mode; } @@ -132,7 +132,7 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> { // Fall back to fetch.recurseSubmodules, if set mode = repo.getConfig().getEnum(FetchRecurseSubmodulesMode.values(), ConfigConstants.CONFIG_FETCH_SECTION, null, - ConfigConstants.CONFIG_KEY_RECURSE_SUBMODULES, null); + ConfigConstants.CONFIG_KEY_RECURSE_SUBMODULES); if (mode != null) { return mode; } @@ -491,13 +491,13 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> { * * Default setting is Transport.DEFAULT_FETCH_THIN * - * @param thin + * @param thinPack * the thin-pack preference * @return {@code this} */ - public FetchCommand setThin(boolean thin) { + public FetchCommand setThin(boolean thinPack) { checkCallable(); - this.thin = thin; + this.thin = thinPack; return this; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java index 584d2bc394..f6935e1c67 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java @@ -12,6 +12,7 @@ package org.eclipse.jgit.api; import java.io.IOException; import java.text.MessageFormat; import java.text.ParseException; +import java.time.Instant; import java.util.Date; import java.util.Properties; import java.util.concurrent.ExecutionException; @@ -59,10 +60,12 @@ public class GarbageCollectCommand extends GitCommand<Properties> { private ProgressMonitor monitor; - private Date expire; + private Instant expire; private PackConfig pconfig; + private Boolean packKeptObjects; + /** * Constructor for GarbageCollectCommand. * @@ -96,8 +99,29 @@ public class GarbageCollectCommand extends GitCommand<Properties> { * @param expire * minimal age of objects to be pruned. * @return this instance + * @deprecated use {@link #setExpire(Instant)} instead */ + @Deprecated(since = "7.2") public GarbageCollectCommand setExpire(Date expire) { + if (expire != null) { + this.expire = expire.toInstant(); + } + return this; + } + + /** + * During gc() or prune() each unreferenced, loose object which has been + * created or modified after <code>expire</code> will not be pruned. Only + * older objects may be pruned. If set to null then every object is a + * candidate for pruning. Use {@link org.eclipse.jgit.util.GitTimeParser} to + * parse time formats used by git gc. + * + * @param expire + * minimal age of objects to be pruned. + * @return this instance + * @since 7.2 + */ + public GarbageCollectCommand setExpire(Instant expire) { this.expire = expire; return this; } @@ -106,8 +130,8 @@ public class GarbageCollectCommand extends GitCommand<Properties> { * Whether to use aggressive mode or not. If set to true JGit behaves more * similar to native git's "git gc --aggressive". If set to * <code>true</code> compressed objects found in old packs are not reused - * but every object is compressed again. Configuration variables - * pack.window and pack.depth are set to 250 for this GC. + * but every object is compressed again. Configuration variables pack.window + * and pack.depth are set to 250 for this GC. * * @since 3.6 * @param aggressive @@ -132,6 +156,19 @@ public class GarbageCollectCommand extends GitCommand<Properties> { } /** + * Whether to include objects in `.keep` packs when repacking. + * + * @param packKeptObjects + * whether to include objects in `.keep` files when repacking. + * @return this instance + * @since 5.13.3 + */ + public GarbageCollectCommand setPackKeptObjects(boolean packKeptObjects) { + this.packKeptObjects = Boolean.valueOf(packKeptObjects); + return this; + } + + /** * Whether to preserve old pack files instead of deleting them. * * @since 4.7 @@ -163,7 +200,6 @@ public class GarbageCollectCommand extends GitCommand<Properties> { return this; } - /** {@inheritDoc} */ @Override public Properties call() throws GitAPIException { checkCallable(); @@ -175,7 +211,9 @@ public class GarbageCollectCommand extends GitCommand<Properties> { gc.setProgressMonitor(monitor); if (this.expire != null) gc.setExpire(expire); - + if (this.packKeptObjects != null) { + gc.setPackKeptObjects(packKeptObjects.booleanValue()); + } try { gc.gc().get(); return toProperties(gc.getStatistics()); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java index 3b3e10e7b2..a8b866d25b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java @@ -68,6 +68,7 @@ public class Git implements AutoCloseable { * @return a {@link org.eclipse.jgit.api.Git} object for the existing git * repository * @throws java.io.IOException + * if an IO error occurred */ public static Git open(File dir) throws IOException { return open(dir, FS.DETECTED); @@ -84,6 +85,7 @@ public class Git implements AutoCloseable { * @return a {@link org.eclipse.jgit.api.Git} object for the existing git * repository. Closing this instance will close the repo. * @throws java.io.IOException + * if an IO error occurred */ public static Git open(File dir, FS fs) throws IOException { RepositoryCache.FileKey key; @@ -199,7 +201,24 @@ public class Git implements AutoCloseable { this(repo, false); } - Git(Repository repo, boolean closeRepo) { + /** + * Construct a new {@link org.eclipse.jgit.api.Git} object which can + * interact with the specified git repository. + * <p> + * All command classes returned by methods of this class will always + * interact with this git repository. + * <p> + * If {@code closeRepo = false} the caller is responsible for closing the + * repository. + * + * @param repo + * the git repository this class is interacting with; + * {@code null} is not allowed. + * @param closeRepo + * whether to close the repository when this instance is closed + * @since 7.4 + */ + public Git(Repository repo, boolean closeRepo) { this.repo = requireNonNull(repo); this.closeRepo = closeRepo; } @@ -712,6 +731,16 @@ public class Git implements AutoCloseable { } /** + * Return a command object to execute a {@code PackRefs} command + * + * @return a {@link org.eclipse.jgit.api.PackRefsCommand} + * @since 7.1 + */ + public PackRefsCommand packRefs() { + return new PackRefsCommand(repo); + } + + /** * Return a command object to find human-readable names of revisions. * * @return a {@link org.eclipse.jgit.api.NameRevCommand}. @@ -792,7 +821,6 @@ public class Git implements AutoCloseable { return repo; } - /** {@inheritDoc} */ @Override public String toString() { return "Git[" + repo + "]"; //$NON-NLS-1$//$NON-NLS-2$ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java index 240290f4f9..1da71aa6eb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java @@ -19,6 +19,7 @@ import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.storage.file.FileRepository; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; @@ -44,6 +45,8 @@ public class InitCommand implements Callable<Git> { private String initialBranch; + private boolean relativePaths; + /** * {@inheritDoc} * <p> @@ -100,7 +103,11 @@ public class InitCommand implements Callable<Git> { : initialBranch); Repository repository = builder.build(); if (!repository.getObjectDatabase().exists()) - repository.create(bare); + if (repository instanceof FileRepository) { + ((FileRepository) repository).create(bare, relativePaths); + } else { + repository.create(bare); + } return new Git(repository, true); } catch (IOException | ConfigInvalidException e) { throw new JGitInternalException(e.getMessage(), e); @@ -214,4 +221,18 @@ public class InitCommand implements Callable<Git> { this.initialBranch = branch; return this; } + + /** + * * Set whether the repository shall use relative paths for GIT_DIR and + * GIT_WORK_TREE + * + * @param relativePaths + * if true, use relative paths for GIT_DIR and GIT_WORK_TREE + * @return {@code this} + * @since 7.2 + */ + public InitCommand setRelativeDirs(boolean relativePaths) { + this.relativePaths = relativePaths; + return this; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java index 4b7445fc95..e3c3c89bcb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java @@ -73,7 +73,6 @@ public class ListBranchCommand extends GitCommand<List<Ref>> { super(repo); } - /** {@inheritDoc} */ @Override public List<Ref> call() throws GitAPIException { checkCallable(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListNotesCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListNotesCommand.java index 34955e86f9..9eb52866dc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListNotesCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListNotesCommand.java @@ -44,7 +44,6 @@ public class ListNotesCommand extends GitCommand<List<Note>> { super(repo); } - /** {@inheritDoc} */ @Override public List<Note> call() throws GitAPIException { checkCallable(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListTagCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListTagCommand.java index 27a5288429..9a4a822b59 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListTagCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListTagCommand.java @@ -54,8 +54,11 @@ public class ListTagCommand extends GitCommand<List<Ref>> { * the specified commit * @return this command * @throws IOException + * if an IO error occurred * @throws IncorrectObjectTypeException + * if commit has an incorrect object type * @throws MissingObjectException + * if the commit is missing * * @since 6.6 */ @@ -67,7 +70,6 @@ public class ListTagCommand extends GitCommand<List<Ref>> { return this; } - /** {@inheritDoc} */ @Override public List<Ref> call() throws GitAPIException { checkCallable(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java index fa40d93e52..2a8d34ed68 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java @@ -53,13 +53,11 @@ import org.eclipse.jgit.treewalk.filter.TreeFilter; * </pre> * <p> * - * <p> * Get commits only for a specific file: * * <pre> * git.log().add(head).addPath("dir/filename.txt").call(); * </pre> - * <p> * * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-log.html" * >Git documentation about Log</a> @@ -112,10 +110,10 @@ public class LogCommand extends GitCommand<Iterable<RevCommit>> { } if (!filters.isEmpty()) { if (filters.size() == 1) { - filters.add(TreeFilter.ANY_DIFF); + walk.setTreeFilter(filters.get(0)); + } else { + walk.setTreeFilter(AndTreeFilter.create(filters)); } - walk.setTreeFilter(AndTreeFilter.create(filters)); - } if (skip > -1 && maxCount > -1) walk.setRevFilter(AndRevFilter.create(SkipRevFilter.create(skip), diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java index ed4a5342b3..7064f5a57a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java @@ -13,9 +13,9 @@ package org.eclipse.jgit.api; import java.io.IOException; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; @@ -75,7 +75,7 @@ public class MergeCommand extends GitCommand<MergeResult> { private ContentMergeStrategy contentStrategy; - private List<Ref> commits = new LinkedList<>(); + private List<Ref> commits = new ArrayList<>(); private Boolean squash; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java index 7347f63889..0fa11f2cbb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java @@ -31,7 +31,9 @@ public class MergeResult { * The status the merge resulted in. */ public enum MergeStatus { - /** */ + /** + * Merge is a fast-forward + */ FAST_FORWARD { @Override public String toString() { @@ -44,6 +46,8 @@ public class MergeResult { } }, /** + * Merge is a fast-forward, squashed + * * @since 2.0 */ FAST_FORWARD_SQUASHED { @@ -57,7 +61,9 @@ public class MergeResult { return true; } }, - /** */ + /** + * Already up to date, merge was a no-op + */ ALREADY_UP_TO_DATE { @Override public String toString() { @@ -69,7 +75,9 @@ public class MergeResult { return true; } }, - /** */ + /** + * Merge failed + */ FAILED { @Override public String toString() { @@ -81,7 +89,9 @@ public class MergeResult { return false; } }, - /** */ + /** + * Merged + */ MERGED { @Override public String toString() { @@ -94,6 +104,8 @@ public class MergeResult { } }, /** + * Merged, squashed, not updating HEAD + * * @since 2.0 */ MERGED_SQUASHED { @@ -108,6 +120,8 @@ public class MergeResult { } }, /** + * Merged, squashed, not committed + * * @since 3.0 */ MERGED_SQUASHED_NOT_COMMITTED { @@ -121,7 +135,9 @@ public class MergeResult { return true; } }, - /** */ + /** + * Merge raised conflicts to be resolved + */ CONFLICTING { @Override public String toString() { @@ -134,6 +150,8 @@ public class MergeResult { } }, /** + * Merge was aborted + * * @since 2.2 */ ABORTED { @@ -148,6 +166,8 @@ public class MergeResult { } }, /** + * Merged, not committed + * * @since 3.0 **/ MERGED_NOT_COMMITTED { @@ -161,7 +181,7 @@ public class MergeResult { return true; } }, - /** */ + /** Not yet supported */ NOT_SUPPORTED { @Override public String toString() { @@ -191,6 +211,8 @@ public class MergeResult { }; /** + * Whether the merge was successful + * * @return whether the status indicates a successful result */ public abstract boolean isSuccessful(); @@ -364,7 +386,6 @@ public class MergeResult { return base; } - /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java index e6ab1e6588..1ff6e98b12 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java @@ -102,7 +102,6 @@ public class NameRevCommand extends GitCommand<Map<ObjectId, String>> { }; } - /** {@inheritDoc} */ @Override public Map<ObjectId, String> call() throws GitAPIException { try { @@ -349,7 +348,7 @@ public class NameRevCommand extends GitCommand<Map<ObjectId, String>> { } // Don't tiebreak if prefixes are the same, in order to prefer first-parent // paths. - return li - ri; + return (long) li - ri; } private static String simplify(String refName) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PackRefsCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PackRefsCommand.java new file mode 100644 index 0000000000..29a69c5ac4 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PackRefsCommand.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.eclipse.jgit.api; + +import java.io.IOException; + +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.lib.Repository; + +/** + * Optimize storage of references. + * + * @since 7.1 + */ +public class PackRefsCommand extends GitCommand<String> { + private ProgressMonitor monitor; + + private boolean all; + + /** + * Creates a new {@link PackRefsCommand} instance with default values. + * + * @param repo + * the repository this command will be used on + */ + public PackRefsCommand(Repository repo) { + super(repo); + this.monitor = NullProgressMonitor.INSTANCE; + } + + /** + * Set progress monitor + * + * @param monitor + * a progress monitor + * @return this instance + */ + public PackRefsCommand setProgressMonitor(ProgressMonitor monitor) { + this.monitor = monitor; + return this; + } + + /** + * Specify whether to pack all the references. + * + * @param all + * if <code>true</code> all the loose refs will be packed + * @return this instance + */ + public PackRefsCommand setAll(boolean all) { + this.all = all; + return this; + } + + /** + * Whether to pack all the references + * + * @return whether to pack all the references + */ + public boolean isAll() { + return all; + } + + @Override + public String call() throws GitAPIException { + checkCallable(); + try { + repo.getRefDatabase().packRefs(monitor, this); + return JGitText.get().packRefsSuccessful; + } catch (IOException e) { + throw new JGitInternalException(JGitText.get().packRefsFailed, e); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java index 83ae0fc9d4..4b2cee45c2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java @@ -533,9 +533,9 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> { Config config) { BranchRebaseMode mode = config.getEnum(BranchRebaseMode.values(), ConfigConstants.CONFIG_BRANCH_SECTION, - branchName, ConfigConstants.CONFIG_KEY_REBASE, null); + branchName, ConfigConstants.CONFIG_KEY_REBASE); if (mode == null) { - mode = config.getEnum(BranchRebaseMode.values(), + mode = config.getEnum( ConfigConstants.CONFIG_PULL_SECTION, null, ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.NONE); } @@ -549,7 +549,7 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> { Config config = repo.getConfig(); Merge ffMode = config.getEnum(Merge.values(), ConfigConstants.CONFIG_PULL_SECTION, null, - ConfigConstants.CONFIG_KEY_FF, null); + ConfigConstants.CONFIG_KEY_FF); return ffMode != null ? FastForwardMode.valueOf(ffMode) : null; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullResult.java index cbb9cc2f78..fdc7f3f333 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullResult.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullResult.java @@ -89,7 +89,6 @@ public class PullResult { return true; } - /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java index 2ed1c52fd7..e9d1a3213b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java @@ -614,13 +614,13 @@ public class PushCommand extends * * Default setting is Transport.DEFAULT_PUSH_THIN * - * @param thin + * @param thinPack * the thin-pack preference value * @return {@code this} */ - public PushCommand setThin(boolean thin) { + public PushCommand setThin(boolean thinPack) { checkCallable(); - this.thin = thin; + this.thin = thinPack; return this; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java index 1e5523f275..3ae7a6c81e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java @@ -18,12 +18,13 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.text.MessageFormat; +import java.time.Instant; +import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.regex.Matcher; @@ -86,7 +87,6 @@ import org.eclipse.jgit.util.RawParseUtils; * supported options and arguments of this command and a {@link #call()} method * to finally execute the command. Each instance of this class should only be * used for one invocation of the command (means: one call to {@link #call()}) - * <p> * * @see <a * href="http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html" @@ -290,13 +290,17 @@ public class RebaseCommand extends GitCommand<RebaseResult> { } } RebaseResult res = initFilesAndRewind(); - if (stopAfterInitialization) + if (stopAfterInitialization) { return RebaseResult.INTERACTIVE_PREPARED_RESULT; + } if (res != null) { - autoStashApply(); - if (rebaseState.getDir().exists()) + if (!autoStashApply()) { + res = RebaseResult.STASH_APPLY_CONFLICTS_RESULT; + } + if (rebaseState.getDir().exists()) { FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE); + } return res; } } @@ -382,7 +386,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { } private boolean autoStashApply() throws IOException, GitAPIException { - boolean conflicts = false; + boolean success = true; if (rebaseState.getFile(AUTOSTASH).exists()) { String stash = rebaseState.readFile(AUTOSTASH); try (Git git = Git.wrap(repo)) { @@ -390,7 +394,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { .ignoreRepositoryState(true).setStrategy(strategy) .call(); } catch (StashApplyFailureException e) { - conflicts = true; + success = false; try (RevWalk rw = new RevWalk(repo)) { ObjectId stashId = repo.resolve(stash); RevCommit commit = rw.parseCommit(stashId); @@ -399,7 +403,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { } } } - return conflicts; + return success; } private void updateStashRef(ObjectId commitId, PersonIdent refLogIdent, @@ -724,13 +728,15 @@ public class RebaseCommand extends GitCommand<RebaseResult> { boolean lastStepIsForward) throws IOException, GitAPIException { String headName = rebaseState.readFile(HEAD_NAME); updateHead(headName, finalHead, upstreamCommit); - boolean stashConflicts = autoStashApply(); + boolean unstashSuccessful = autoStashApply(); getRepository().autoGC(monitor); FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE); - if (stashConflicts) + if (!unstashSuccessful) { return RebaseResult.STASH_APPLY_CONFLICTS_RESULT; - if (lastStepIsForward || finalHead == null) + } + if (lastStepIsForward || finalHead == null) { return RebaseResult.FAST_FORWARD_RESULT; + } return RebaseResult.OK_RESULT; } @@ -1000,7 +1006,9 @@ public class RebaseCommand extends GitCommand<RebaseResult> { /** * @return the commit if we had to do a commit, otherwise null * @throws GitAPIException + * if JGit API failed * @throws IOException + * if an IO error occurred */ private RevCommit continueRebase() throws GitAPIException, IOException { // if there are still conflicts, we throw a specific Exception @@ -1057,12 +1065,16 @@ public class RebaseCommand extends GitCommand<RebaseResult> { String authorScript = toAuthorScript(author); rebaseState.createFile(AUTHOR_SCRIPT, authorScript); rebaseState.createFile(MESSAGE, commitToPick.getFullMessage()); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - try (DiffFormatter df = new DiffFormatter(bos)) { - df.setRepository(repo); - df.format(commitToPick.getParent(0), commitToPick); + if (commitToPick.getParentCount() > 0) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try (DiffFormatter df = new DiffFormatter(bos)) { + df.setRepository(repo); + df.format(commitToPick.getParent(0), commitToPick); + } + rebaseState.createFile(PATCH, new String(bos.toByteArray(), UTF_8)); + } else { + rebaseState.createFile(PATCH, ""); //$NON-NLS-1$ } - rebaseState.createFile(PATCH, new String(bos.toByteArray(), UTF_8)); rebaseState.createFile(STOPPED_SHA, repo.newObjectReader() .abbreviate( @@ -1102,13 +1114,15 @@ public class RebaseCommand extends GitCommand<RebaseResult> { * that can not be parsed as steps * * @param numSteps + * number of steps to remove * @throws IOException + * if an IO error occurred */ private void popSteps(int numSteps) throws IOException { if (numSteps == 0) return; - List<RebaseTodoLine> todoLines = new LinkedList<>(); - List<RebaseTodoLine> poppedLines = new LinkedList<>(); + List<RebaseTodoLine> todoLines = new ArrayList<>(); + List<RebaseTodoLine> poppedLines = new ArrayList<>(); for (RebaseTodoLine line : repo.readRebaseTodo( rebaseState.getPath(GIT_REBASE_TODO), true)) { @@ -1146,7 +1160,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { if (!isInteractive() && walk.isMergedInto(upstream, headCommit)) return RebaseResult.UP_TO_DATE_RESULT; else if (!isInteractive() && walk.isMergedInto(headCommit, upstream)) { - // head is already merged into upstream, fast-foward + // head is already merged into upstream, fast-forward monitor.beginTask(MessageFormat.format( JGitText.get().resettingHead, upstreamCommit.getShortMessage()), ProgressMonitor.UNKNOWN); @@ -1217,7 +1231,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { Iterator<RevCommit> commitsToUse = r.iterator(); while (commitsToUse.hasNext()) { RevCommit commit = commitsToUse.next(); - if (preserveMerges || commit.getParentCount() == 1) { + if (preserveMerges || commit.getParentCount() <= 1) { cherryPickList.add(commit); } } @@ -1234,23 +1248,31 @@ public class RebaseCommand extends GitCommand<RebaseResult> { walk.markStart(upstreamCommit); walk.markStart(headCommit); RevCommit base; - while ((base = walk.next()) != null) + while ((base = walk.next()) != null) { RebaseState.createFile(rewrittenDir, base.getName(), upstreamCommit.getName()); - + } Iterator<RevCommit> iterator = cherryPickList.iterator(); pickLoop: while(iterator.hasNext()){ RevCommit commit = iterator.next(); - for (int i = 0; i < commit.getParentCount(); i++) { - boolean parentRewritten = new File(rewrittenDir, commit - .getParent(i).getName()).exists(); - if (parentRewritten) { - new File(rewrittenDir, commit.getName()).createNewFile(); - continue pickLoop; + int nOfParents = commit.getParentCount(); + if (nOfParents == 0) { + // Must be the very first commit in the cherryPickList. We + // have independent branches. + new File(rewrittenDir, commit.getName()).createNewFile(); + } else { + for (int i = 0; i < nOfParents; i++) { + boolean parentRewritten = new File(rewrittenDir, + commit.getParent(i).getName()).exists(); + if (parentRewritten) { + new File(rewrittenDir, commit.getName()) + .createNewFile(); + continue pickLoop; + } } + // commit is only merged in, needs not be rewritten + iterator.remove(); } - // commit is only merged in, needs not be rewritten - iterator.remove(); } } return cherryPickList; @@ -1289,7 +1311,9 @@ public class RebaseCommand extends GitCommand<RebaseResult> { * if we can fast-forward to. * @return the new head, or null * @throws java.io.IOException + * if an IO error occurred * @throws org.eclipse.jgit.api.errors.GitAPIException + * if a JGit API exception occurred */ public RevCommit tryFastForward(RevCommit newCommit) throws IOException, GitAPIException { @@ -1434,13 +1458,14 @@ public class RebaseCommand extends GitCommand<RebaseResult> { throw new JGitInternalException( JGitText.get().abortingRebaseFailed); } - boolean stashConflicts = autoStashApply(); + boolean unstashSuccessful = autoStashApply(); // cleanup the files FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE); repo.writeCherryPickHead(null); repo.writeMergeHeads(null); - if (stashConflicts) + if (!unstashSuccessful) { return RebaseResult.STASH_APPLY_CONFLICTS_RESULT; + } return result; } finally { @@ -1540,6 +1565,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { * the name of the upstream branch * @return {@code this} * @throws org.eclipse.jgit.api.errors.RefNotFoundException + * if {@code upstream} Ref couldn't be resolved */ public RebaseCommand setUpstream(String upstream) throws RefNotFoundException { @@ -1811,23 +1837,26 @@ public class RebaseCommand extends GitCommand<RebaseResult> { // the time is saved as <seconds since 1970> <timezone offset> int timeStart = 0; - if (time.startsWith("@")) //$NON-NLS-1$ + if (time.startsWith("@")) { //$NON-NLS-1$ timeStart = 1; - else + } else { timeStart = 0; - long when = Long - .parseLong(time.substring(timeStart, time.indexOf(' '))) * 1000; + } + Instant when = Instant.ofEpochSecond( + Long.parseLong(time.substring(timeStart, time.indexOf(' ')))); String tzOffsetString = time.substring(time.indexOf(' ') + 1); int multiplier = -1; - if (tzOffsetString.charAt(0) == '+') + if (tzOffsetString.charAt(0) == '+') { multiplier = 1; + } int hours = Integer.parseInt(tzOffsetString.substring(1, 3)); int minutes = Integer.parseInt(tzOffsetString.substring(3, 5)); // this is in format (+/-)HHMM (hours and minutes) - // we need to convert into minutes - int tz = (hours * 60 + minutes) * multiplier; - if (name != null && email != null) + ZoneOffset tz = ZoneOffset.ofHoursMinutes(hours * multiplier, + minutes * multiplier); + if (name != null && email != null) { return new PersonIdent(name, email, when, tz); + } return null; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java index 826ea518e4..2aa64df46f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java @@ -146,6 +146,8 @@ public class RebaseResult { }; /** + * Whether the rebase was successful + * * @return whether the status indicates a successful result */ public abstract boolean isSuccessful(); @@ -194,6 +196,7 @@ public class RebaseResult { * Create <code>RebaseResult</code> * * @param status + * the overall rebase status * @param commit * current commit * @return the RebaseResult diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java index dead2749b7..a149649004 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java @@ -68,7 +68,7 @@ public class ReflogCommand extends GitCommand<Collection<ReflogEntry>> { checkCallable(); try { - ReflogReader reader = repo.getReflogReader(ref); + ReflogReader reader = repo.getRefDatabase().getReflogReader(ref); if (reader == null) throw new RefNotFoundException(MessageFormat.format( JGitText.get().refNotResolved, ref)); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java index 553fc2e7a0..ad553f07a9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java @@ -49,18 +49,6 @@ public class RemoteRemoveCommand extends GitCommand<RemoteConfig> { /** * The name of the remote to remove. * - * @param name - * a remote name - * @deprecated use {@link #setRemoteName} instead - */ - @Deprecated - public void setName(String name) { - this.remoteName = name; - } - - /** - * The name of the remote to remove. - * * @param remoteName * a remote name * @return {@code this} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java index e3d01861fc..68ddce361f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java @@ -71,18 +71,6 @@ public class RemoteSetUrlCommand extends GitCommand<RemoteConfig> { /** * The name of the remote to change the URL for. * - * @param name - * a remote name - * @deprecated use {@link #setRemoteName} instead - */ - @Deprecated - public void setName(String name) { - this.remoteName = name; - } - - /** - * The name of the remote to change the URL for. - * * @param remoteName * a remote remoteName * @return {@code this} @@ -96,18 +84,6 @@ public class RemoteSetUrlCommand extends GitCommand<RemoteConfig> { /** * The new URL for the remote. * - * @param uri - * an URL for the remote - * @deprecated use {@link #setRemoteUri} instead - */ - @Deprecated - public void setUri(URIish uri) { - this.remoteUri = uri; - } - - /** - * The new URL for the remote. - * * @param remoteUri * an URL for the remote * @return {@code this} @@ -121,23 +97,6 @@ public class RemoteSetUrlCommand extends GitCommand<RemoteConfig> { /** * Whether to change the push URL of the remote instead of the fetch URL. * - * @param push - * <code>true</code> to set the push url, <code>false</code> to - * set the fetch url - * @deprecated use {@link #setUriType} instead - */ - @Deprecated - public void setPush(boolean push) { - if (push) { - setUriType(UriType.PUSH); - } else { - setUriType(UriType.FETCH); - } - } - - /** - * Whether to change the push URL of the remote instead of the fetch URL. - * * @param type * the <code>UriType</code> value to set * @return {@code this} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoveNoteCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoveNoteCommand.java index f4b60adedb..c65cbf160f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoveNoteCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoveNoteCommand.java @@ -47,7 +47,6 @@ public class RemoveNoteCommand extends GitCommand<Note> { super(repo); } - /** {@inheritDoc} */ @Override public Note call() throws GitAPIException { checkCallable(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java index 922cb531de..029f9d02d6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java @@ -55,7 +55,6 @@ public class RenameBranchCommand extends GitCommand<Ref> { super(repo); } - /** {@inheritDoc} */ @Override public Ref call() throws GitAPIException, RefNotFoundException, InvalidRefNameException, RefAlreadyExistsException, DetachedHeadException { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java index 87b4acb14c..47145a0563 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java @@ -11,8 +11,8 @@ package org.eclipse.jgit.api; import java.io.IOException; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.Collection; -import java.util.LinkedList; import org.eclipse.jgit.api.errors.CheckoutConflictException; import org.eclipse.jgit.api.errors.GitAPIException; @@ -90,7 +90,7 @@ public class ResetCommand extends GitCommand<Ref> { private ResetType mode; - private Collection<String> filepaths = new LinkedList<>(); + private Collection<String> filepaths = new ArrayList<>(); private boolean isReflogDisabled; @@ -436,7 +436,6 @@ public class ResetCommand extends GitCommand<Ref> { repo.writeMergeCommitMsg(null); } - /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java index 513f579b67..6643c83662 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com> and others + * Copyright (C) 2010, 2024 Christian Halstrick <christian.halstrick@sap.com> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -13,7 +13,7 @@ import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH; import java.io.IOException; import java.text.MessageFormat; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -58,11 +58,13 @@ import org.eclipse.jgit.treewalk.FileTreeIterator; * >Git documentation about revert</a> */ public class RevertCommand extends GitCommand<RevCommit> { - private List<Ref> commits = new LinkedList<>(); + private List<Ref> commits = new ArrayList<>(); private String ourCommitName = null; - private List<Ref> revertedRefs = new LinkedList<>(); + private boolean insertChangeId; + + private List<Ref> revertedRefs = new ArrayList<>(); private MergeResult failingResult; @@ -132,7 +134,7 @@ public class RevertCommand extends GitCommand<RevCommit> { String ourName = calculateOurName(headRef); String revertName = srcCommit.getId() - .abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH).name() + " " //$NON-NLS-1$ + .abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH).name() + ' ' + srcCommit.getShortMessage(); ResolveMerger merger = (ResolveMerger) strategy.newMerger(repo); @@ -141,8 +143,8 @@ public class RevertCommand extends GitCommand<RevCommit> { merger.setCommitNames(new String[] { "BASE", ourName, revertName }); //$NON-NLS-1$ - String shortMessage = "Revert \"" + srcCommit.getShortMessage() //$NON-NLS-1$ - + "\""; //$NON-NLS-1$ + String shortMessage = "Revert \"" //$NON-NLS-1$ + + srcCommit.getFirstMessageLine() + '"'; String newMessage = shortMessage + "\n\n" //$NON-NLS-1$ + "This reverts commit " + srcCommit.getId().getName() //$NON-NLS-1$ + ".\n"; //$NON-NLS-1$ @@ -162,6 +164,7 @@ public class RevertCommand extends GitCommand<RevCommit> { dco.checkout(); try (Git git = new Git(getRepository())) { newHead = git.commit().setMessage(newMessage) + .setInsertChangeId(insertChangeId) .setReflogComment("revert: " + shortMessage) //$NON-NLS-1$ .call(); } @@ -327,4 +330,18 @@ public class RevertCommand extends GitCommand<RevCommit> { this.monitor = monitor; return this; } + + /** + * Defines whether to add a Gerrit change ID to each revert commit message. + * + * @param insertChangeId + * whether to insert a change ID + * @return {@code this} + * @since 6.8 + */ + public RevertCommand setInsertChangeId(boolean insertChangeId) { + this.insertChangeId = insertChangeId; + return this; + } + } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java index 656f36a81a..7459e7298f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java @@ -13,7 +13,6 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; -import java.util.LinkedList; import java.util.List; import org.eclipse.jgit.api.errors.GitAPIException; @@ -71,7 +70,7 @@ public class RmCommand extends GitCommand<DirCache> { */ public RmCommand(Repository repo) { super(repo); - filepatterns = new LinkedList<>(); + filepatterns = new ArrayList<>(); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ShowNoteCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ShowNoteCommand.java index f64cb6b831..7bb9de0ef4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ShowNoteCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ShowNoteCommand.java @@ -44,7 +44,6 @@ public class ShowNoteCommand extends GitCommand<Note> { super(repo); } - /** {@inheritDoc} */ @Override public Note call() throws GitAPIException { checkCallable(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java index 17036a9cd3..b0b715e06b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012, 2021 GitHub Inc. and others + * Copyright (C) 2012, 2023 GitHub Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -23,6 +23,7 @@ import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.StashApplyFailureException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; +import org.eclipse.jgit.dircache.Checkout; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheCheckout; @@ -48,7 +49,6 @@ import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; -import org.eclipse.jgit.treewalk.WorkingTreeOptions; /** * Command class to apply a stashed commit. @@ -263,18 +263,6 @@ public class StashApplyCommand extends GitCommand<ObjectId> { /** * Whether to restore the index state * - * @param applyIndex - * true (default) if the command should restore the index state - * @deprecated use {@link #setRestoreIndex} instead - */ - @Deprecated - public void setApplyIndex(boolean applyIndex) { - this.restoreIndex = applyIndex; - } - - /** - * Whether to restore the index state - * * @param restoreIndex * true (default) if the command should restore the index state * @return {@code this} @@ -319,19 +307,6 @@ public class StashApplyCommand extends GitCommand<ObjectId> { /** * Whether the command should restore untracked files * - * @param applyUntracked - * true (default) if the command should restore untracked files - * @since 3.4 - * @deprecated use {@link #setRestoreUntracked} instead - */ - @Deprecated - public void setApplyUntracked(boolean applyUntracked) { - this.restoreUntracked = applyUntracked; - } - - /** - * Whether the command should restore untracked files - * * @param restoreUntracked * true (default) if the command should restore untracked files * @return {@code this} @@ -383,8 +358,7 @@ public class StashApplyCommand extends GitCommand<ObjectId> { private void resetUntracked(RevTree tree) throws CheckoutConflictException, IOException { Set<String> actuallyModifiedPaths = new HashSet<>(); - WorkingTreeOptions options = repo.getConfig() - .get(WorkingTreeOptions.KEY); + Checkout checkout = new Checkout(repo).setRecursiveDeletion(true); // TODO maybe NameConflictTreeWalk ? try (TreeWalk walk = new TreeWalk(repo)) { walk.addTree(tree); @@ -408,17 +382,17 @@ public class StashApplyCommand extends GitCommand<ObjectId> { FileTreeIterator fIter = walk .getTree(1, FileTreeIterator.class); + String gitPath = entry.getPathString(); if (fIter != null) { if (fIter.isModified(entry, true, reader)) { // file exists and is dirty - throw new CheckoutConflictException( - entry.getPathString()); + throw new CheckoutConflictException(gitPath); } } - checkoutPath(entry, reader, options, + checkoutPath(entry, gitPath, reader, checkout, new CheckoutMetadata(eolStreamType, null)); - actuallyModifiedPaths.add(entry.getPathString()); + actuallyModifiedPaths.add(gitPath); } } finally { if (!actuallyModifiedPaths.isEmpty()) { @@ -428,11 +402,11 @@ public class StashApplyCommand extends GitCommand<ObjectId> { } } - private void checkoutPath(DirCacheEntry entry, ObjectReader reader, - WorkingTreeOptions options, CheckoutMetadata checkoutMetadata) { + private void checkoutPath(DirCacheEntry entry, String gitPath, + ObjectReader reader, + Checkout checkout, CheckoutMetadata checkoutMetadata) { try { - DirCacheCheckout.checkoutEntry(repo, entry, reader, true, - checkoutMetadata, options); + checkout.checkout(entry, checkoutMetadata, reader, gitPath); } catch (IOException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().checkoutConflictWithFile, diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java index 23fbe0197f..2dba0ef0f2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java @@ -165,7 +165,8 @@ public class StashDropCommand extends GitCommand<ObjectId> { List<ReflogEntry> entries; try { - ReflogReader reader = repo.getReflogReader(R_STASH); + ReflogReader reader = repo.getRefDatabase() + .getReflogReader(R_STASH); if (reader == null) { throw new RefNotFoundException(MessageFormat .format(JGitText.get().refNotResolved, stashRef)); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashListCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashListCommand.java index 8171c4c3ae..828a3020f8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashListCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashListCommand.java @@ -43,7 +43,6 @@ public class StashListCommand extends GitCommand<Collection<RevCommit>> { super(repo); } - /** {@inheritDoc} */ @Override public Collection<RevCommit> call() throws GitAPIException, InvalidRefNameException { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java index eab389460a..cdd078ea25 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java @@ -10,7 +10,7 @@ package org.eclipse.jgit.api; import java.io.IOException; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; import org.eclipse.jgit.api.errors.GitAPIException; @@ -83,7 +83,7 @@ public class StatusCommand extends GitCommand<Status> { */ public StatusCommand addPath(String path) { if (paths == null) - paths = new LinkedList<>(); + paths = new ArrayList<>(); paths.add(path); return this; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java index 606d567393..5105dfc2e5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java @@ -66,6 +66,7 @@ public class SubmoduleAddCommand extends * Set the submodule name * * @param name + * name of the submodule * @return this command * @since 5.1 */ @@ -117,6 +118,7 @@ public class SubmoduleAddCommand extends * * @return true if submodule exists in index, false otherwise * @throws java.io.IOException + * if an IO error occurred */ protected boolean submoduleExists() throws IOException { TreeFilter filter = PathFilter.create(path); @@ -174,8 +176,9 @@ public class SubmoduleAddCommand extends CloneCommand clone = Git.cloneRepository(); configure(clone); clone.setDirectory(moduleDirectory); - clone.setGitDir(new File(new File(repo.getDirectory(), - Constants.MODULES), path)); + clone.setGitDir(new File( + new File(repo.getCommonDirectory(), Constants.MODULES), path)); + clone.setRelativePaths(true); clone.setURI(resolvedUri); if (monitor != null) clone.setProgressMonitor(monitor); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java index f4b8ac2e07..0aa1515334 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java @@ -61,6 +61,7 @@ public class SubmoduleDeinitCommand * Constructor of SubmoduleDeinitCommand * * @param repo + * repository this command works on */ public SubmoduleDeinitCommand(Repository repo) { super(repo); @@ -69,7 +70,6 @@ public class SubmoduleDeinitCommand /** * {@inheritDoc} - * <p> * * @return the set of repositories successfully deinitialized. * @throws NoSuchSubmoduleException @@ -135,6 +135,7 @@ public class SubmoduleDeinitCommand * @param path * the path to clean * @throws IOException + * if an IO error occurred */ private void deinit(String path) throws IOException { File dir = new File(repo.getWorkTree(), path); @@ -157,10 +158,14 @@ public class SubmoduleDeinitCommand * the parent repo's index or HEAD. * * @param revWalk + * used to walk commit graph * @param path + * path of the submodule * @return status of the command * @throws GitAPIException + * if JGit API failed * @throws IOException + * if an IO error occurred */ private SubmoduleDeinitStatus checkDirty(RevWalk revWalk, String path) throws GitAPIException, IOException { @@ -216,6 +221,7 @@ public class SubmoduleDeinitCommand * @return {@code true} if path exists and is a submodule in index, * {@code false} otherwise * @throws IOException + * if an IO error occurred */ private boolean submoduleExists(String path) throws IOException { TreeFilter filter = PathFilter.create(path); @@ -241,6 +247,7 @@ public class SubmoduleDeinitCommand * else it will refuse to do so. * * @param force + * execute the command forcefully if there are local modifications * @return {@code this} */ public SubmoduleDeinitCommand setForce(boolean force) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitResult.java index 8129be4a71..b7d7b66333 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitResult.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitResult.java @@ -25,6 +25,7 @@ public class SubmoduleDeinitResult { * @param path * path of the submodule * @param status + * effect of a SubmoduleDeinitCommand's execution */ public SubmoduleDeinitResult(String path, SubmoduleDeinitCommand.SubmoduleDeinitStatus status) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java index bdceabad37..03b4a000ad 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java @@ -61,7 +61,6 @@ public class SubmoduleInitCommand extends GitCommand<Collection<String>> { return this; } - /** {@inheritDoc} */ @Override public Collection<String> call() throws GitAPIException { checkCallable(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java index 196ef7b2a9..d5bc0dda98 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java @@ -61,7 +61,6 @@ public class SubmoduleStatusCommand extends return this; } - /** {@inheritDoc} */ @Override public Map<String, SubmoduleStatus> call() throws GitAPIException { checkCallable(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java index b319a1b478..4f3e8512c3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java @@ -70,6 +70,7 @@ public class SubmoduleSyncCommand extends GitCommand<Map<String, String>> { * a {@link org.eclipse.jgit.lib.Repository} object. * @return shortened branch name, null on failures * @throws java.io.IOException + * if an IO error occurred */ protected String getHeadBranch(Repository subRepo) throws IOException { Ref head = subRepo.exactRef(Constants.HEAD); @@ -79,7 +80,6 @@ public class SubmoduleSyncCommand extends GitCommand<Map<String, String>> { return null; } - /** {@inheritDoc} */ @Override public Map<String, String> call() throws GitAPIException { checkCallable(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java index df73164161..5e4b2ee0b7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java @@ -28,6 +28,7 @@ import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.internal.storage.file.LockFile; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; @@ -39,6 +40,7 @@ import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.submodule.SubmoduleWalk; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; +import org.eclipse.jgit.util.FileUtils; /** * A class used to execute a submodule update command. @@ -62,6 +64,8 @@ public class SubmoduleUpdateCommand extends private boolean fetch = false; + private boolean clonedRestored; + /** * <p> * Constructor for SubmoduleUpdateCommand. @@ -116,25 +120,77 @@ public class SubmoduleUpdateCommand extends return this; } + private static boolean submoduleExists(File gitDir) { + if (gitDir != null && gitDir.isDirectory()) { + File[] files = gitDir.listFiles(); + return files != null && files.length != 0; + } + return false; + } + + private static void restoreSubmodule(File gitDir, File workingTree) + throws IOException { + LockFile dotGitLock = new LockFile( + new File(workingTree, Constants.DOT_GIT)); + if (dotGitLock.lock()) { + String content = Constants.GITDIR + + getRelativePath(gitDir, workingTree); + dotGitLock.write(Constants.encode(content)); + dotGitLock.commit(); + } + } + + private static String getRelativePath(File gitDir, File workingTree) { + File relPath; + try { + relPath = workingTree.toPath().relativize(gitDir.toPath()) + .toFile(); + } catch (IllegalArgumentException e) { + relPath = gitDir; + } + return FileUtils.pathToString(relPath); + } + + private String determineUpdateMode(String mode) { + if (clonedRestored) { + return ConfigConstants.CONFIG_KEY_CHECKOUT; + } + return mode; + } + private Repository getOrCloneSubmodule(SubmoduleWalk generator, String url) throws IOException, GitAPIException { Repository repository = generator.getRepository(); + boolean restored = false; + boolean cloned = false; if (repository == null) { - if (callback != null) { - callback.cloningSubmodule(generator.getPath()); - } - CloneCommand clone = Git.cloneRepository(); - configure(clone); - clone.setURI(url); - clone.setDirectory(generator.getDirectory()); - clone.setGitDir( - new File(new File(repo.getDirectory(), Constants.MODULES), - generator.getPath())); - if (monitor != null) { - clone.setProgressMonitor(monitor); + File gitDir = new File( + new File(repo.getCommonDirectory(), Constants.MODULES), + generator.getPath()); + if (submoduleExists(gitDir)) { + restoreSubmodule(gitDir, generator.getDirectory()); + restored = true; + clonedRestored = true; + repository = generator.getRepository(); + } else { + if (callback != null) { + callback.cloningSubmodule(generator.getPath()); + } + CloneCommand clone = Git.cloneRepository(); + configure(clone); + clone.setURI(url); + clone.setDirectory(generator.getDirectory()); + clone.setGitDir(gitDir); + clone.setRelativePaths(true); + if (monitor != null) { + clone.setProgressMonitor(monitor); + } + repository = clone.call().getRepository(); + cloned = true; + clonedRestored = true; } - repository = clone.call().getRepository(); - } else if (this.fetch) { + } + if ((this.fetch || restored) && !cloned) { if (fetchCallback != null) { fetchCallback.fetchingSubmodule(generator.getPath()); } @@ -171,15 +227,17 @@ public class SubmoduleUpdateCommand extends continue; // Skip submodules not registered in parent repository's config String url = generator.getConfigUrl(); - if (url == null) + if (url == null) { continue; - + } + clonedRestored = false; try (Repository submoduleRepo = getOrCloneSubmodule(generator, url); RevWalk walk = new RevWalk(submoduleRepo)) { RevCommit commit = walk .parseCommit(generator.getObjectId()); - String update = generator.getConfigUpdate(); + String update = determineUpdateMode( + generator.getConfigUpdate()); if (ConfigConstants.CONFIG_KEY_MERGE.equals(update)) { MergeCommand merge = new MergeCommand(submoduleRepo); merge.include(commit); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java index 58c18b38d1..cc8589fa1c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java @@ -18,14 +18,11 @@ import org.eclipse.jgit.api.errors.InvalidTagNameException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.RefAlreadyExistsException; -import org.eclipse.jgit.api.errors.ServiceUnavailableException; import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.GpgConfig; import org.eclipse.jgit.lib.GpgConfig.GpgFormat; -import org.eclipse.jgit.lib.GpgObjectSigner; -import org.eclipse.jgit.lib.GpgSigner; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PersonIdent; @@ -33,7 +30,8 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.RepositoryState; +import org.eclipse.jgit.lib.Signer; +import org.eclipse.jgit.lib.Signers; import org.eclipse.jgit.lib.TagBuilder; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; @@ -51,13 +49,11 @@ import org.eclipse.jgit.transport.CredentialsProvider; * </pre> * <p> * - * <p> * Create a new unannotated tag for the current commit: * * <pre> * git.tag().setName("v1.0").setAnnotated(false).call(); * </pre> - * <p> * * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-tag.html" * >Git documentation about Tag</a> @@ -82,7 +78,7 @@ public class TagCommand extends GitCommand<Ref> { private GpgConfig gpgConfig; - private GpgObjectSigner gpgSigner; + private Signer signer; private CredentialsProvider credentialsProvider; @@ -110,9 +106,7 @@ public class TagCommand extends GitCommand<Ref> { public Ref call() throws GitAPIException, ConcurrentRefUpdateException, InvalidTagNameException, NoHeadException { checkCallable(); - - RepositoryState state = repo.getRepositoryState(); - processOptions(state); + processOptions(); try (RevWalk revWalk = new RevWalk(repo)) { // if no id is set, we should attempt to use HEAD @@ -138,9 +132,9 @@ public class TagCommand extends GitCommand<Ref> { newTag.setTagger(tagger); newTag.setObjectId(id); - if (gpgSigner != null) { - gpgSigner.signObject(newTag, signingKey, tagger, - credentialsProvider, gpgConfig); + if (signer != null) { + signer.signObject(repo, gpgConfig, newTag, tagger, signingKey, + credentialsProvider); } // write the tag object @@ -199,20 +193,14 @@ public class TagCommand extends GitCommand<Ref> { * Sets default values for not explicitly specified options. Then validates * that all required data has been provided. * - * @param state - * the state of the repository we are working on - * * @throws InvalidTagNameException * if the tag name is null or invalid - * @throws ServiceUnavailableException - * if the tag should be signed but no signer can be found * @throws UnsupportedSigningFormatException * if the tag should be signed but {@code gpg.format} is not * {@link GpgFormat#OPENPGP} */ - private void processOptions(RepositoryState state) - throws InvalidTagNameException, ServiceUnavailableException, - UnsupportedSigningFormatException { + private void processOptions() + throws InvalidTagNameException, UnsupportedSigningFormatException { if (name == null || !Repository.isValidRefName(Constants.R_TAGS + name)) { throw new InvalidTagNameException( @@ -238,16 +226,15 @@ public class TagCommand extends GitCommand<Ref> { doSign = gpgConfig.isSignAnnotated(); } if (doSign) { - if (signingKey == null) { - signingKey = gpgConfig.getSigningKey(); - } - if (gpgSigner == null) { - GpgSigner signer = GpgSigner.getDefault(); - if (!(signer instanceof GpgObjectSigner)) { - throw new ServiceUnavailableException( - JGitText.get().signingServiceUnavailable); + if (signer == null) { + signer = Signers.get(gpgConfig.getKeyFormat()); + if (signer == null) { + throw new UnsupportedSigningFormatException( + MessageFormat.format( + JGitText.get().signatureTypeUnknown, + gpgConfig.getKeyFormat() + .toConfigValue())); } - gpgSigner = (GpgObjectSigner) signer; } // The message of a signed tag must end in a newline because // the signature will be appended. @@ -334,22 +321,22 @@ public class TagCommand extends GitCommand<Ref> { } /** - * Sets the {@link GpgSigner} to use if the commit is to be signed. + * Sets the {@link Signer} to use if the commit is to be signed. * * @param signer * to use; if {@code null}, the default signer will be used * @return {@code this} - * @since 5.11 + * @since 7.0 */ - public TagCommand setGpgSigner(GpgObjectSigner signer) { + public TagCommand setSigner(Signer signer) { checkCallable(); - this.gpgSigner = signer; + this.signer = signer; return this; } /** * Sets an external {@link GpgConfig} to use. Whether it will be used is at - * the discretion of the {@link #setGpgSigner(GpgObjectSigner)}. + * the discretion of the {@link #setSigner(Signer)}. * * @param config * to set; if {@code null}, the config will be loaded from the diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java index 1af880d792..30f1bc9cc6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java @@ -22,7 +22,9 @@ import org.eclipse.jgit.transport.Transport; * {@link org.eclipse.jgit.api.TransportConfigCallback}. * * @param <C> + * concrete type of this {@code GitCommand} * @param <T> + * the return type of the {@code GitCommand}'s {@code call()} method */ public abstract class TransportCommand<C extends GitCommand, T> extends GitCommand<T> { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/VerificationResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/VerificationResult.java index 21cddf75b7..f5f4b06e45 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/VerificationResult.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/VerificationResult.java @@ -9,7 +9,7 @@ */ package org.eclipse.jgit.api; -import org.eclipse.jgit.lib.GpgSignatureVerifier; +import org.eclipse.jgit.lib.SignatureVerifier; import org.eclipse.jgit.revwalk.RevObject; /** @@ -34,8 +34,9 @@ public interface VerificationResult { * Retrieves the signature verification result. * * @return the result, or {@code null} if none was computed + * @since 7.0 */ - GpgSignatureVerifier.SignatureVerification getVerification(); + SignatureVerifier.SignatureVerification getVerification(); /** * Retrieves the git object of which the signature was verified. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/VerifySignatureCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/VerifySignatureCommand.java index 6a2a44ea2d..487ff04323 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/VerifySignatureCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/VerifySignatureCommand.java @@ -25,11 +25,10 @@ import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.GpgConfig; -import org.eclipse.jgit.lib.GpgSignatureVerifier; -import org.eclipse.jgit.lib.GpgSignatureVerifier.SignatureVerification; -import org.eclipse.jgit.lib.GpgSignatureVerifierFactory; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.SignatureVerifier.SignatureVerification; +import org.eclipse.jgit.lib.SignatureVerifiers; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; @@ -65,12 +64,8 @@ public class VerifySignatureCommand extends GitCommand<Map<String, VerificationR private VerifyMode mode = VerifyMode.ANY; - private GpgSignatureVerifier verifier; - private GpgConfig config; - private boolean ownVerifier; - /** * Creates a new {@link VerifySignatureCommand} for the given {@link Repository}. * @@ -140,22 +135,7 @@ public class VerifySignatureCommand extends GitCommand<Map<String, VerificationR } /** - * Sets the {@link GpgSignatureVerifier} to use. - * - * @param verifier - * the {@link GpgSignatureVerifier} to use, or {@code null} to - * use the default verifier - * @return {@code this} - */ - public VerifySignatureCommand setVerifier(GpgSignatureVerifier verifier) { - checkCallable(); - this.verifier = verifier; - return this; - } - - /** - * Sets an external {@link GpgConfig} to use. Whether it will be used it at - * the discretion of the {@link #setVerifier(GpgSignatureVerifier)}. + * Sets an external {@link GpgConfig} to use. * * @param config * to set; if {@code null}, the config will be loaded from the @@ -170,16 +150,6 @@ public class VerifySignatureCommand extends GitCommand<Map<String, VerificationR } /** - * Retrieves the currently set {@link GpgSignatureVerifier}. Can be used - * after a successful {@link #call()} to get the verifier that was used. - * - * @return the {@link GpgSignatureVerifier} - */ - public GpgSignatureVerifier getVerifier() { - return verifier; - } - - /** * {@link Repository#resolve(String) Resolves} all names added to the * command to git objects and verifies their signature. Non-existing objects * are ignored. @@ -193,9 +163,6 @@ public class VerifySignatureCommand extends GitCommand<Map<String, VerificationR * * @return a map of the given names to the corresponding * {@link VerificationResult}, excluding ignored or skipped objects. - * @throws ServiceUnavailableException - * if no {@link GpgSignatureVerifier} was set and no - * {@link GpgSignatureVerifierFactory} is available * @throws WrongObjectTypeException * if a name resolves to an object of a type not allowed by the * {@link #setMode(VerifyMode)} mode @@ -207,16 +174,6 @@ public class VerifySignatureCommand extends GitCommand<Map<String, VerificationR checkCallable(); setCallable(false); Map<String, VerificationResult> result = new HashMap<>(); - if (verifier == null) { - GpgSignatureVerifierFactory factory = GpgSignatureVerifierFactory - .getDefault(); - if (factory == null) { - throw new ServiceUnavailableException( - JGitText.get().signatureVerificationUnavailable); - } - verifier = factory.getVerifier(); - ownVerifier = true; - } if (config == null) { config = new GpgConfig(repo.getConfig()); } @@ -239,10 +196,6 @@ public class VerifySignatureCommand extends GitCommand<Map<String, VerificationR } catch (IOException e) { throw new JGitInternalException( JGitText.get().signatureVerificationError, e); - } finally { - if (ownVerifier) { - verifier.clear(); - } } return result; } @@ -258,8 +211,8 @@ public class VerifySignatureCommand extends GitCommand<Map<String, VerificationR } if (type == Constants.OBJ_COMMIT || type == Constants.OBJ_TAG) { try { - GpgSignatureVerifier.SignatureVerification verification = verifier - .verifySignature(object, config); + SignatureVerification verification = SignatureVerifiers + .verify(repo, config, object); if (verification == null) { // Not signed return null; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CheckoutConflictException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CheckoutConflictException.java index 3b71373b6e..5538711192 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CheckoutConflictException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CheckoutConflictException.java @@ -37,7 +37,7 @@ */ package org.eclipse.jgit.api.errors; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; /** @@ -94,11 +94,12 @@ public class CheckoutConflictException extends GitAPIException { * Adds a new conflicting path * * @param conflictingPath + * the new conflicting path * @return {@code this} */ CheckoutConflictException addConflictingPath(String conflictingPath) { if (conflictingPaths == null) - conflictingPaths = new LinkedList<>(); + conflictingPaths = new ArrayList<>(); conflictingPaths.add(conflictingPath); return this; } |