diff options
author | Matthias Sohn <matthias.sohn@sap.com> | 2021-05-12 08:59:07 +0200 |
---|---|---|
committer | Matthias Sohn <matthias.sohn@sap.com> | 2021-05-12 08:59:07 +0200 |
commit | 1aa3cf7f416658a4fafd592732ba70f5a1aee19e (patch) | |
tree | 275b1176d04d102832641292a765d1a8f3e1b3a2 /org.eclipse.jgit/src/org/eclipse/jgit | |
parent | b6d4844b5d60f63a46f2b8b42189f70de653b385 (diff) | |
parent | fe3034d5b90a8a1f2592a40a670705fbd7305158 (diff) | |
download | jgit-1aa3cf7f416658a4fafd592732ba70f5a1aee19e.tar.gz jgit-1aa3cf7f416658a4fafd592732ba70f5a1aee19e.zip |
Merge branch 'master' into next
* master: (34 commits)
Remove texts which were added by mistake in 00386272
Fix formatting which was broken in 00386272
LockFile: create OutputStream only when needed
Add a cgit interoperability test for LockFile
Add TemporaryBuffer.toString(int limit)
LockFile: create OutputStream only when needed
Prepare 5.12.0-SNAPSHOT builds
JGit v5.12.0.202105051250-m2
Update jetty to 9.4.40.v20210413
[releng] Update eclipse-jarsigner-plugin to 1.3.1
Implement ours/theirs content conflict resolution
ssh: ensure list is modifiable before using Iterator.remove().
Update orbit to S20210406213021 and add 4.20-staging target
Fix typo in test method name
Allow file mode conflicts in virtual base commit on recursive merge.
sshd: don't lock the known_hosts files on reading
Allow info messages in UsernamePasswordCredentialsProvider
ssh config: do environment variable replacement
sshd: implement server-sig-algs SSH extension (client side)
Upgrade ecj to 3.25.0
...
Change-Id: Ibc39a9c4e431d15b67ab4a307241f47a7f3740a9
Diffstat (limited to 'org.eclipse.jgit/src/org/eclipse/jgit')
18 files changed, 775 insertions, 333 deletions
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 5d0154c6dc..7922f9e729 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com> and others + * Copyright (C) 2010, 2021 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,6 +13,7 @@ import java.io.IOException; import java.text.MessageFormat; import java.util.LinkedList; import java.util.List; +import java.util.Map; import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; import org.eclipse.jgit.api.errors.GitAPIException; @@ -35,9 +36,12 @@ import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.merge.ContentMergeStrategy; import org.eclipse.jgit.merge.MergeMessageFormatter; import org.eclipse.jgit.merge.MergeStrategy; +import org.eclipse.jgit.merge.Merger; import org.eclipse.jgit.merge.ResolveMerger; +import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.FileTreeIterator; @@ -61,6 +65,8 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> { private MergeStrategy strategy = MergeStrategy.RECURSIVE; + private ContentMergeStrategy contentStrategy; + private Integer mainlineParentNumber; private boolean noCommit = false; @@ -121,16 +127,30 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> { String cherryPickName = srcCommit.getId().abbreviate(7).name() + " " + srcCommit.getShortMessage(); //$NON-NLS-1$ - ResolveMerger merger = (ResolveMerger) strategy.newMerger(repo); - merger.setWorkingTreeIterator(new FileTreeIterator(repo)); - merger.setBase(srcParent.getTree()); - merger.setCommitNames(new String[] { "BASE", ourName, //$NON-NLS-1$ - cherryPickName }); - if (merger.merge(newHead, srcCommit)) { - if (!merger.getModifiedFiles().isEmpty()) { + Merger merger = strategy.newMerger(repo); + merger.setProgressMonitor(monitor); + boolean noProblems; + Map<String, MergeFailureReason> failingPaths = null; + List<String> unmergedPaths = null; + if (merger instanceof ResolveMerger) { + ResolveMerger resolveMerger = (ResolveMerger) merger; + resolveMerger.setContentMergeStrategy(contentStrategy); + resolveMerger.setCommitNames( + new String[] { "BASE", ourName, cherryPickName }); //$NON-NLS-1$ + resolveMerger + .setWorkingTreeIterator(new FileTreeIterator(repo)); + resolveMerger.setBase(srcParent.getTree()); + noProblems = merger.merge(newHead, srcCommit); + failingPaths = resolveMerger.getFailingPaths(); + unmergedPaths = resolveMerger.getUnmergedPaths(); + if (!resolveMerger.getModifiedFiles().isEmpty()) { repo.fireEvent(new WorkingTreeModifiedEvent( - merger.getModifiedFiles(), null)); + resolveMerger.getModifiedFiles(), null)); } + } else { + noProblems = merger.merge(newHead, srcCommit); + } + if (noProblems) { if (AnyObjectId.isEqual(newHead.getTree().getId(), merger.getResultTreeId())) { continue; @@ -153,24 +173,26 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> { } cherryPickedRefs.add(src); } else { - if (merger.failed()) { - return new CherryPickResult(merger.getFailingPaths()); + if (failingPaths != null && !failingPaths.isEmpty()) { + return new CherryPickResult(failingPaths); } // there are merge conflicts - String message = new MergeMessageFormatter() + String message; + if (unmergedPaths != null) { + message = new MergeMessageFormatter() .formatWithConflicts(srcCommit.getFullMessage(), - merger.getUnmergedPaths()); + unmergedPaths); + } else { + message = srcCommit.getFullMessage(); + } if (!noCommit) { repo.writeCherryPickHead(srcCommit.getId()); } repo.writeMergeCommitMsg(message); - repo.fireEvent(new WorkingTreeModifiedEvent( - merger.getModifiedFiles(), null)); - return CherryPickResult.CONFLICT; } } @@ -291,6 +313,22 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> { } /** + * Sets the content merge strategy to use if the + * {@link #setStrategy(MergeStrategy) merge strategy} is "resolve" or + * "recursive". + * + * @param strategy + * the {@link ContentMergeStrategy} to be used + * @return {@code this} + * @since 5.12 + */ + public CherryPickCommand setContentMergeStrategy( + ContentMergeStrategy strategy) { + this.contentStrategy = strategy; + return this; + } + + /** * Set the (1-based) parent number to diff against * * @param mainlineParentNumber 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 041276c0f8..37f1d482aa 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java @@ -20,6 +20,7 @@ import java.util.LinkedList; import java.util.List; import org.eclipse.jgit.api.errors.AbortedByHookException; +import org.eclipse.jgit.api.errors.CanceledException; import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; import org.eclipse.jgit.api.errors.EmptyCommitException; import org.eclipse.jgit.api.errors.GitAPIException; @@ -36,6 +37,8 @@ import org.eclipse.jgit.dircache.DirCacheBuildIterator; import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheIterator; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.UnmergedPathException; import org.eclipse.jgit.hooks.CommitMsgHook; import org.eclipse.jgit.hooks.Hooks; @@ -67,6 +70,8 @@ import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.TreeWalk.OperationType; import org.eclipse.jgit.util.ChangeIdUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * A class used to execute a {@code Commit} command. It has setters for all @@ -78,6 +83,9 @@ import org.eclipse.jgit.util.ChangeIdUtil; * >Git documentation about Commit</a> */ public class CommitCommand extends GitCommand<RevCommit> { + private static final Logger log = LoggerFactory + .getLogger(CommitCommand.class); + private PersonIdent author; private PersonIdent committer; @@ -173,8 +181,7 @@ public class CommitCommand extends GitCommand<RevCommit> { if (all && !repo.isBare()) { try (Git git = new Git(repo)) { - git.add() - .addFilepattern(".") //$NON-NLS-1$ + git.add().addFilepattern(".") //$NON-NLS-1$ .setUpdate(true).call(); } catch (NoFilepatternException e) { // should really not happen @@ -212,7 +219,7 @@ public class CommitCommand extends GitCommand<RevCommit> { .setCommitMessage(message).call(); } - // lock the index + RevCommit revCommit; DirCache index = repo.lockDirCache(); try (ObjectInserter odi = repo.newObjectInserter()) { if (!only.isEmpty()) @@ -226,100 +233,37 @@ public class CommitCommand extends GitCommand<RevCommit> { if (insertChangeId) insertChangeId(indexTreeId); - // Check for empty commits - if (headId != null && !allowEmpty.booleanValue()) { - RevCommit headCommit = rw.parseCommit(headId); - headCommit.getTree(); - if (indexTreeId.equals(headCommit.getTree())) { - throw new EmptyCommitException( - JGitText.get().emptyCommit); - } - } + checkIfEmpty(rw, headId, indexTreeId); // Create a Commit object, populate it and write it CommitBuilder commit = new CommitBuilder(); commit.setCommitter(committer); commit.setAuthor(author); commit.setMessage(message); - commit.setParentIds(parents); commit.setTreeId(indexTreeId); if (signCommit.booleanValue()) { - if (gpgSigner == null) { - throw new ServiceUnavailableException( - JGitText.get().signingServiceUnavailable); - } - 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); - } + sign(commit); } ObjectId commitId = odi.insert(commit); odi.flush(); + revCommit = rw.parseCommit(commitId); - RevCommit revCommit = rw.parseCommit(commitId); - RefUpdate ru = repo.updateRef(Constants.HEAD); - ru.setNewObjectId(commitId); - if (!useDefaultReflogMessage) { - ru.setRefLogMessage(reflogComment, false); - } else { - String prefix = amend ? "commit (amend): " //$NON-NLS-1$ - : parents.isEmpty() ? "commit (initial): " //$NON-NLS-1$ - : "commit: "; //$NON-NLS-1$ - ru.setRefLogMessage(prefix + revCommit.getShortMessage(), - false); - } - if (headId != null) - ru.setExpectedOldObjectId(headId); - else - ru.setExpectedOldObjectId(ObjectId.zeroId()); - Result rc = ru.forceUpdate(); - switch (rc) { - case NEW: - case FORCED: - case FAST_FORWARD: { - setCallable(false); - if (state == RepositoryState.MERGING_RESOLVED - || isMergeDuringRebase(state)) { - // Commit was successful. Now delete the files - // used for merge commits - repo.writeMergeCommitMsg(null); - repo.writeMergeHeads(null); - } else if (state == RepositoryState.CHERRY_PICKING_RESOLVED) { - repo.writeMergeCommitMsg(null); - repo.writeCherryPickHead(null); - } else if (state == RepositoryState.REVERTING_RESOLVED) { - repo.writeMergeCommitMsg(null); - repo.writeRevertHead(null); - } - Hooks.postCommit(repo, - hookOutRedirect.get(PostCommitHook.NAME), - hookErrRedirect.get(PostCommitHook.NAME)).call(); - - return revCommit; - } - case REJECTED: - case LOCK_FAILURE: - throw new ConcurrentRefUpdateException( - JGitText.get().couldNotLockHEAD, ru.getRef(), rc); - default: - throw new JGitInternalException(MessageFormat.format( - JGitText.get().updatingRefFailed, Constants.HEAD, - commitId.toString(), rc)); - } + updateRef(state, headId, revCommit, commitId); } finally { index.unlock(); } + try { + Hooks.postCommit(repo, hookOutRedirect.get(PostCommitHook.NAME), + hookErrRedirect.get(PostCommitHook.NAME)).call(); + } catch (Exception e) { + log.error(MessageFormat.format( + JGitText.get().postCommitHookFailed, e.getMessage()), + e); + } + return revCommit; } catch (UnmergedPathException e) { throw new UnmergedPathsException(e); } catch (IOException e) { @@ -328,6 +272,89 @@ public class CommitCommand extends GitCommand<RevCommit> { } } + private void checkIfEmpty(RevWalk rw, ObjectId headId, ObjectId indexTreeId) + throws EmptyCommitException, MissingObjectException, + IncorrectObjectTypeException, IOException { + if (headId != null && !allowEmpty.booleanValue()) { + RevCommit headCommit = rw.parseCommit(headId); + headCommit.getTree(); + if (indexTreeId.equals(headCommit.getTree())) { + throw new EmptyCommitException(JGitText.get().emptyCommit); + } + } + } + + private void sign(CommitBuilder commit) throws ServiceUnavailableException, + CanceledException, UnsupportedSigningFormatException { + if (gpgSigner == null) { + throw new ServiceUnavailableException( + JGitText.get().signingServiceUnavailable); + } + 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); + } + } + + private void updateRef(RepositoryState state, ObjectId headId, + RevCommit revCommit, ObjectId commitId) + throws ConcurrentRefUpdateException, IOException { + RefUpdate ru = repo.updateRef(Constants.HEAD); + ru.setNewObjectId(commitId); + if (!useDefaultReflogMessage) { + ru.setRefLogMessage(reflogComment, false); + } else { + String prefix = amend ? "commit (amend): " //$NON-NLS-1$ + : parents.isEmpty() ? "commit (initial): " //$NON-NLS-1$ + : "commit: "; //$NON-NLS-1$ + ru.setRefLogMessage(prefix + revCommit.getShortMessage(), + false); + } + if (headId != null) { + ru.setExpectedOldObjectId(headId); + } else { + ru.setExpectedOldObjectId(ObjectId.zeroId()); + } + Result rc = ru.forceUpdate(); + switch (rc) { + case NEW: + case FORCED: + case FAST_FORWARD: { + setCallable(false); + if (state == RepositoryState.MERGING_RESOLVED + || isMergeDuringRebase(state)) { + // Commit was successful. Now delete the files + // used for merge commits + repo.writeMergeCommitMsg(null); + repo.writeMergeHeads(null); + } else if (state == RepositoryState.CHERRY_PICKING_RESOLVED) { + repo.writeMergeCommitMsg(null); + repo.writeCherryPickHead(null); + } else if (state == RepositoryState.REVERTING_RESOLVED) { + repo.writeMergeCommitMsg(null); + repo.writeRevertHead(null); + } + break; + } + case REJECTED: + case LOCK_FAILURE: + throw new ConcurrentRefUpdateException( + JGitText.get().couldNotLockHEAD, ru.getRef(), rc); + default: + throw new JGitInternalException(MessageFormat.format( + JGitText.get().updatingRefFailed, Constants.HEAD, + commitId.toString(), rc)); + } + } + private void insertChangeId(ObjectId treeId) { ObjectId firstParentId = null; if (!parents.isEmpty()) 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 d88f4ec561..c611f915ae 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java @@ -1,7 +1,7 @@ /* * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com> - * Copyright (C) 2010-2014, Stefan Lay <stefan.lay@sap.com> - * Copyright (C) 2016, Laurent Delaigue <laurent.delaigue@obeo.fr> and others + * Copyright (C) 2010, 2014, Stefan Lay <stefan.lay@sap.com> + * Copyright (C) 2016, 2021 Laurent Delaigue <laurent.delaigue@obeo.fr> 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 @@ -45,6 +45,7 @@ import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.merge.ContentMergeStrategy; import org.eclipse.jgit.merge.MergeConfig; import org.eclipse.jgit.merge.MergeMessageFormatter; import org.eclipse.jgit.merge.MergeStrategy; @@ -71,6 +72,8 @@ public class MergeCommand extends GitCommand<MergeResult> { private MergeStrategy mergeStrategy = MergeStrategy.RECURSIVE; + private ContentMergeStrategy contentStrategy; + private List<Ref> commits = new LinkedList<>(); private Boolean squash; @@ -320,6 +323,7 @@ public class MergeCommand extends GitCommand<MergeResult> { List<String> unmergedPaths = null; if (merger instanceof ResolveMerger) { ResolveMerger resolveMerger = (ResolveMerger) merger; + resolveMerger.setContentMergeStrategy(contentStrategy); resolveMerger.setCommitNames(new String[] { "BASE", "HEAD", ref.getName() }); //$NON-NLS-1$ //$NON-NLS-2$ resolveMerger.setWorkingTreeIterator(new FileTreeIterator(repo)); @@ -473,6 +477,22 @@ public class MergeCommand extends GitCommand<MergeResult> { } /** + * Sets the content merge strategy to use if the + * {@link #setStrategy(MergeStrategy) merge strategy} is "resolve" or + * "recursive". + * + * @param strategy + * the {@link ContentMergeStrategy} to be used + * @return {@code this} + * @since 5.12 + */ + public MergeCommand setContentMergeStrategy(ContentMergeStrategy strategy) { + checkCallable(); + this.contentStrategy = strategy; + return this; + } + + /** * Reference to a commit to be merged with the current head * * @param aCommit 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 449250890c..281ecfd011 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java @@ -1,7 +1,7 @@ /* * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com> * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com> - * Copyright (C) 2016, Laurent Delaigue <laurent.delaigue@obeo.fr> and others + * Copyright (C) 2016, 2021 Laurent Delaigue <laurent.delaigue@obeo.fr> 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 @@ -43,6 +43,7 @@ import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryState; import org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode; +import org.eclipse.jgit.merge.ContentMergeStrategy; import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; @@ -69,6 +70,8 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> { private MergeStrategy strategy = MergeStrategy.RECURSIVE; + private ContentMergeStrategy contentStrategy; + private TagOpt tagOption; private FastForwardMode fastForwardMode; @@ -275,8 +278,7 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> { JGitText.get().pullTaskName)); // we check the updates to see which of the updated branches - // corresponds - // to the remote branch name + // corresponds to the remote branch name AnyObjectId commitToMerge; if (isRemote) { Ref r = null; @@ -354,8 +356,11 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> { } RebaseCommand rebase = new RebaseCommand(repo); RebaseResult rebaseRes = rebase.setUpstream(commitToMerge) - .setUpstreamName(upstreamName).setProgressMonitor(monitor) - .setOperation(Operation.BEGIN).setStrategy(strategy) + .setProgressMonitor(monitor) + .setUpstreamName(upstreamName) + .setOperation(Operation.BEGIN) + .setStrategy(strategy) + .setContentMergeStrategy(contentStrategy) .setPreserveMerges( pullRebaseMode == BranchRebaseMode.PRESERVE) .call(); @@ -363,7 +368,9 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> { } else { MergeCommand merge = new MergeCommand(repo); MergeResult mergeRes = merge.include(upstreamName, commitToMerge) - .setStrategy(strategy).setProgressMonitor(monitor) + .setProgressMonitor(monitor) + .setStrategy(strategy) + .setContentMergeStrategy(contentStrategy) .setFastForward(getFastForwardMode()).call(); monitor.update(1); result = new PullResult(fetchRes, remote, mergeRes); @@ -442,6 +449,21 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> { } /** + * Sets the content merge strategy to use if the + * {@link #setStrategy(MergeStrategy) merge strategy} is "resolve" or + * "recursive". + * + * @param strategy + * the {@link ContentMergeStrategy} to be used + * @return {@code this} + * @since 5.12 + */ + public PullCommand setContentMergeStrategy(ContentMergeStrategy strategy) { + this.contentStrategy = strategy; + return this; + } + + /** * Set the specification of annotated tag behavior during fetch * * @param tagOpt 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 836175dcea..a26ffc2e66 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2010, 2013 Mathias Kinzler <mathias.kinzler@sap.com> - * Copyright (C) 2016, Laurent Delaigue <laurent.delaigue@obeo.fr> and others + * Copyright (C) 2016, 2021 Laurent Delaigue <laurent.delaigue@obeo.fr> 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 @@ -65,6 +65,7 @@ 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.merge.ContentMergeStrategy; import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevSort; @@ -212,6 +213,8 @@ public class RebaseCommand extends GitCommand<RebaseResult> { private MergeStrategy strategy = MergeStrategy.RECURSIVE; + private ContentMergeStrategy contentStrategy; + private boolean preserveMerges = false; /** @@ -501,8 +504,11 @@ public class RebaseCommand extends GitCommand<RebaseResult> { String ourCommitName = getOurCommitName(); try (Git git = new Git(repo)) { CherryPickResult cherryPickResult = git.cherryPick() - .include(commitToPick).setOurCommitName(ourCommitName) - .setReflogPrefix(REFLOG_PREFIX).setStrategy(strategy) + .include(commitToPick) + .setOurCommitName(ourCommitName) + .setReflogPrefix(REFLOG_PREFIX) + .setStrategy(strategy) + .setContentMergeStrategy(contentStrategy) .call(); switch (cherryPickResult.getStatus()) { case FAILED: @@ -556,7 +562,8 @@ public class RebaseCommand extends GitCommand<RebaseResult> { .include(commitToPick) .setOurCommitName(ourCommitName) .setReflogPrefix(REFLOG_PREFIX) - .setStrategy(strategy); + .setStrategy(strategy) + .setContentMergeStrategy(contentStrategy); if (isMerge) { pickCommand.setMainlineParentNumber(1); // We write a MERGE_HEAD and later commit explicitly @@ -592,6 +599,8 @@ public class RebaseCommand extends GitCommand<RebaseResult> { MergeCommand merge = git.merge() .setFastForward(MergeCommand.FastForwardMode.NO_FF) .setProgressMonitor(monitor) + .setStrategy(strategy) + .setContentMergeStrategy(contentStrategy) .setCommit(false); for (int i = 1; i < commitToPick.getParentCount(); i++) merge.include(newParents.get(i)); @@ -1137,7 +1146,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { } private List<RevCommit> calculatePickList(RevCommit headCommit) - throws GitAPIException, NoHeadException, IOException { + throws IOException { List<RevCommit> cherryPickList = new ArrayList<>(); try (RevWalk r = new RevWalk(repo)) { r.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER, true); @@ -1587,6 +1596,21 @@ public class RebaseCommand extends GitCommand<RebaseResult> { } /** + * Sets the content merge strategy to use if the + * {@link #setStrategy(MergeStrategy) merge strategy} is "resolve" or + * "recursive". + * + * @param strategy + * the {@link ContentMergeStrategy} to be used + * @return {@code this} + * @since 5.12 + */ + public RebaseCommand setContentMergeStrategy(ContentMergeStrategy strategy) { + this.contentStrategy = strategy; + return this; + } + + /** * Whether to preserve merges during rebase * * @param preserve 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 56b3992fcd..1004d3e50f 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, 2017 GitHub Inc. and others + * Copyright (C) 2012, 2021 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 @@ -38,7 +38,9 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryState; +import org.eclipse.jgit.merge.ContentMergeStrategy; import org.eclipse.jgit.merge.MergeStrategy; +import org.eclipse.jgit.merge.Merger; import org.eclipse.jgit.merge.ResolveMerger; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; @@ -71,6 +73,8 @@ public class StashApplyCommand extends GitCommand<ObjectId> { private MergeStrategy strategy = MergeStrategy.RECURSIVE; + private ContentMergeStrategy contentStrategy; + /** * Create command to apply the changes of a stashed commit * @@ -166,16 +170,25 @@ public class StashApplyCommand extends GitCommand<ObjectId> { if (restoreUntracked && stashCommit.getParentCount() == 3) untrackedCommit = revWalk.parseCommit(stashCommit.getParent(2)); - ResolveMerger merger = (ResolveMerger) strategy.newMerger(repo); - merger.setCommitNames(new String[] { "stashed HEAD", "HEAD", //$NON-NLS-1$ //$NON-NLS-2$ - "stash" }); //$NON-NLS-1$ - merger.setBase(stashHeadCommit); - merger.setWorkingTreeIterator(new FileTreeIterator(repo)); - boolean mergeSucceeded = merger.merge(headCommit, stashCommit); - List<String> modifiedByMerge = merger.getModifiedFiles(); - if (!modifiedByMerge.isEmpty()) { - repo.fireEvent( - new WorkingTreeModifiedEvent(modifiedByMerge, null)); + Merger merger = strategy.newMerger(repo); + boolean mergeSucceeded; + if (merger instanceof ResolveMerger) { + ResolveMerger resolveMerger = (ResolveMerger) merger; + resolveMerger + .setCommitNames(new String[] { "stashed HEAD", "HEAD", //$NON-NLS-1$ //$NON-NLS-2$ + "stash" }); //$NON-NLS-1$ + resolveMerger.setBase(stashHeadCommit); + resolveMerger + .setWorkingTreeIterator(new FileTreeIterator(repo)); + resolveMerger.setContentMergeStrategy(contentStrategy); + mergeSucceeded = resolveMerger.merge(headCommit, stashCommit); + List<String> modifiedByMerge = resolveMerger.getModifiedFiles(); + if (!modifiedByMerge.isEmpty()) { + repo.fireEvent(new WorkingTreeModifiedEvent(modifiedByMerge, + null)); + } + } else { + mergeSucceeded = merger.merge(headCommit, stashCommit); } if (mergeSucceeded) { DirCache dc = repo.lockDirCache(); @@ -184,11 +197,14 @@ public class StashApplyCommand extends GitCommand<ObjectId> { dco.setFailOnConflict(true); dco.checkout(); // Ignoring failed deletes.... if (restoreIndex) { - ResolveMerger ixMerger = (ResolveMerger) strategy - .newMerger(repo, true); - ixMerger.setCommitNames(new String[] { "stashed HEAD", //$NON-NLS-1$ - "HEAD", "stashed index" }); //$NON-NLS-1$//$NON-NLS-2$ - ixMerger.setBase(stashHeadCommit); + Merger ixMerger = strategy.newMerger(repo, true); + if (ixMerger instanceof ResolveMerger) { + ResolveMerger resolveMerger = (ResolveMerger) ixMerger; + resolveMerger.setCommitNames(new String[] { "stashed HEAD", //$NON-NLS-1$ + "HEAD", "stashed index" }); //$NON-NLS-1$//$NON-NLS-2$ + resolveMerger.setBase(stashHeadCommit); + resolveMerger.setContentMergeStrategy(contentStrategy); + } boolean ok = ixMerger.merge(headCommit, stashIndexCommit); if (ok) { resetIndex(revWalk @@ -200,16 +216,20 @@ public class StashApplyCommand extends GitCommand<ObjectId> { } if (untrackedCommit != null) { - ResolveMerger untrackedMerger = (ResolveMerger) strategy - .newMerger(repo, true); - untrackedMerger.setCommitNames(new String[] { - "null", "HEAD", "untracked files" }); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ - // There is no common base for HEAD & untracked files - // because the commit for untracked files has no parent. If - // we use stashHeadCommit as common base (as in the other - // merges) we potentially report conflicts for files - // which are not even member of untracked files commit - untrackedMerger.setBase(null); + Merger untrackedMerger = strategy.newMerger(repo, true); + if (untrackedMerger instanceof ResolveMerger) { + ResolveMerger resolveMerger = (ResolveMerger) untrackedMerger; + resolveMerger.setCommitNames(new String[] { "null", "HEAD", //$NON-NLS-1$//$NON-NLS-2$ + "untracked files" }); //$NON-NLS-1$ + // There is no common base for HEAD & untracked files + // because the commit for untracked files has no parent. + // If we use stashHeadCommit as common base (as in the + // other merges) we potentially report conflicts for + // files which are not even member of untracked files + // commit. + resolveMerger.setBase(null); + resolveMerger.setContentMergeStrategy(contentStrategy); + } boolean ok = untrackedMerger.merge(headCommit, untrackedCommit); if (ok) { @@ -279,6 +299,23 @@ public class StashApplyCommand extends GitCommand<ObjectId> { } /** + * Sets the content merge strategy to use if the + * {@link #setStrategy(MergeStrategy) merge strategy} is "resolve" or + * "recursive". + * + * @param strategy + * the {@link ContentMergeStrategy} to be used + * @return {@code this} + * @since 5.12 + */ + public StashApplyCommand setContentMergeStrategy( + ContentMergeStrategy strategy) { + checkCallable(); + this.contentStrategy = strategy; + return this; + } + + /** * Whether the command should restore untracked files * * @param applyUntracked diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java index 671475ed47..c904a782db 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java @@ -1610,11 +1610,9 @@ public class DirCacheCheckout { } if (rc != 0) { throw new IOException(new FilterFailedException(rc, - checkoutMetadata.smudgeFilterCommand, - path, + checkoutMetadata.smudgeFilterCommand, path, result.getStdout().toByteArray(MAX_EXCEPTION_TEXT_SIZE), - RawParseUtils.decode(result.getStderr() - .toByteArray(MAX_EXCEPTION_TEXT_SIZE)))); + result.getStderr().toString(MAX_EXCEPTION_TEXT_SIZE))); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java index 3eef49b1ca..ab9fc5c9bb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -444,10 +444,13 @@ public class JGitText extends TranslationBundle { /***/ public String localRefIsMissingObjects; /***/ public String localRepository; /***/ public String lockCountMustBeGreaterOrEqual1; + /***/ public String lockAlreadyHeld; /***/ public String lockError; /***/ public String lockFailedRetry; /***/ public String lockOnNotClosed; /***/ public String lockOnNotHeld; + /***/ public String lockStreamClosed; + /***/ public String lockStreamMultiple; /***/ public String logInconsistentFiletimeDiff; /***/ public String logLargerFiletimeDiff; /***/ public String logSmallerFiletime; @@ -558,6 +561,7 @@ public class JGitText extends TranslationBundle { /***/ public String peerDidNotSupplyACompleteObjectGraph; /***/ public String personIdentEmailNonNull; /***/ public String personIdentNameNonNull; + /***/ public String postCommitHookFailed; /***/ public String prefixRemote; /***/ public String problemWithResolvingPushRefSpecsLocally; /***/ public String progressMonUploading; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java index b5e3927bcc..f02c8613a0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java @@ -131,33 +131,6 @@ public class FileReftableStack implements AutoCloseable { return stats; } - /** Thrown if the update indices in the stack are not monotonic */ - public static class ReftableNumbersNotIncreasingException - extends RuntimeException { - private static final long serialVersionUID = 1L; - - String name; - - long lastMax; - - long min; - - ReftableNumbersNotIncreasingException(String name, long lastMax, - long min) { - this.name = name; - this.lastMax = lastMax; - this.min = min; - } - - @SuppressWarnings({ "nls", "boxing" }) - @Override - public String toString() { - return String.format( - "ReftableNumbersNotIncreasingException %s: min %d, lastMax %d", - name, min, lastMax); - } - } - /** * Reloads the stack, potentially reusing opened reftableReaders. * @@ -176,7 +149,6 @@ public class FileReftableStack implements AutoCloseable { List<ReftableReader> newTables = new ArrayList<>(); List<StackEntry> newStack = new ArrayList<>(stack.size() + 1); try { - ReftableReader last = null; for (String name : names) { StackEntry entry = new StackEntry(); entry.name = name; @@ -194,15 +166,6 @@ public class FileReftableStack implements AutoCloseable { newTables.add(t); } - if (last != null) { - // TODO: move this to MergedReftable - if (last.maxUpdateIndex() >= t.minUpdateIndex()) { - throw new ReftableNumbersNotIncreasingException(name, - last.maxUpdateIndex(), t.minUpdateIndex()); - } - } - last = t; - entry.reftableReader = t; newStack.add(entry); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java index 2e0a6da3a4..78262e9773 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com> - * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> and others + * Copyright (C) 2006-2021, Shawn O. Pearce <spearce@spearce.org> 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 @@ -96,11 +96,15 @@ public class LockFile { private boolean haveLck; - FileOutputStream os; + private FileOutputStream os; private boolean needSnapshot; - boolean fsync; + private boolean fsync; + + private boolean isAppend; + + private boolean written; private FileSnapshot commitSnapshot; @@ -127,6 +131,10 @@ public class LockFile { * does not hold the lock. */ public boolean lock() throws IOException { + if (haveLck) { + throw new IllegalStateException( + MessageFormat.format(JGitText.get().lockAlreadyHeld, ref)); + } FileUtils.mkdirs(lck.getParentFile(), true); try { token = FS.DETECTED.createNewFileAtomic(lck); @@ -134,18 +142,15 @@ public class LockFile { LOG.error(JGitText.get().failedCreateLockFile, lck, e); throw e; } - if (token.isCreated()) { + boolean obtainedLock = token.isCreated(); + if (obtainedLock) { haveLck = true; - try { - os = new FileOutputStream(lck); - } catch (IOException ioe) { - unlock(); - throw ioe; - } + isAppend = false; + written = false; } else { closeToken(); } - return haveLck; + return obtainedLock; } /** @@ -158,12 +163,24 @@ public class LockFile { * does not hold the lock. */ public boolean lockForAppend() throws IOException { - if (!lock()) + if (!lock()) { return false; + } copyCurrentContent(); + isAppend = true; + written = false; return true; } + // For tests only + boolean isLocked() { + return haveLck; + } + + private FileOutputStream getStream() throws IOException { + return new FileOutputStream(lck, isAppend); + } + /** * Copy the current file content into the temporary file. * <p> @@ -185,32 +202,31 @@ public class LockFile { */ public void copyCurrentContent() throws IOException { requireLock(); - try { + try (FileOutputStream out = getStream()) { try (FileInputStream fis = new FileInputStream(ref)) { if (fsync) { FileChannel in = fis.getChannel(); long pos = 0; long cnt = in.size(); while (0 < cnt) { - long r = os.getChannel().transferFrom(in, pos, cnt); + long r = out.getChannel().transferFrom(in, pos, cnt); pos += r; cnt -= r; } } else { final byte[] buf = new byte[2048]; int r; - while ((r = fis.read(buf)) >= 0) - os.write(buf, 0, r); + while ((r = fis.read(buf)) >= 0) { + out.write(buf, 0, r); + } } + } catch (FileNotFoundException fnfe) { + if (ref.exists()) { + throw fnfe; + } + // Don't worry about a file that doesn't exist yet, it + // conceptually has no current content to copy. } - } catch (FileNotFoundException fnfe) { - if (ref.exists()) { - unlock(); - throw fnfe; - } - // Don't worry about a file that doesn't exist yet, it - // conceptually has no current content to copy. - // } catch (IOException | RuntimeException | Error ioe) { unlock(); throw ioe; @@ -253,18 +269,22 @@ public class LockFile { */ public void write(byte[] content) throws IOException { requireLock(); - try { + try (FileOutputStream out = getStream()) { + if (written) { + throw new IOException(MessageFormat + .format(JGitText.get().lockStreamClosed, ref)); + } if (fsync) { - FileChannel fc = os.getChannel(); + FileChannel fc = out.getChannel(); ByteBuffer buf = ByteBuffer.wrap(content); - while (0 < buf.remaining()) + while (0 < buf.remaining()) { fc.write(buf); + } fc.force(true); } else { - os.write(content); + out.write(content); } - os.close(); - os = null; + written = true; } catch (IOException | RuntimeException | Error ioe) { unlock(); throw ioe; @@ -283,36 +303,67 @@ public class LockFile { public OutputStream getOutputStream() { requireLock(); - final OutputStream out; - if (fsync) - out = Channels.newOutputStream(os.getChannel()); - else - out = os; + if (written || os != null) { + throw new IllegalStateException(MessageFormat + .format(JGitText.get().lockStreamMultiple, ref)); + } return new OutputStream() { + + private OutputStream out; + + private boolean closed; + + private OutputStream get() throws IOException { + if (written) { + throw new IOException(MessageFormat + .format(JGitText.get().lockStreamMultiple, ref)); + } + if (out == null) { + os = getStream(); + if (fsync) { + out = Channels.newOutputStream(os.getChannel()); + } else { + out = os; + } + } + return out; + } + @Override - public void write(byte[] b, int o, int n) - throws IOException { - out.write(b, o, n); + public void write(byte[] b, int o, int n) throws IOException { + get().write(b, o, n); } @Override public void write(byte[] b) throws IOException { - out.write(b); + get().write(b); } @Override public void write(int b) throws IOException { - out.write(b); + get().write(b); } @Override public void close() throws IOException { + if (closed) { + return; + } + closed = true; try { - if (fsync) - os.getChannel().force(true); - out.close(); - os = null; + if (written) { + throw new IOException(MessageFormat + .format(JGitText.get().lockStreamClosed, ref)); + } + if (out != null) { + if (fsync) { + os.getChannel().force(true); + } + out.close(); + os = null; + } + written = true; } catch (IOException | RuntimeException | Error ioe) { unlock(); throw ioe; @@ -322,7 +373,7 @@ public class LockFile { } void requireLock() { - if (os == null) { + if (!haveLck) { unlock(); throw new IllegalStateException(MessageFormat.format(JGitText.get().lockOnNotHeld, ref)); } @@ -411,6 +462,8 @@ public class LockFile { try { FileUtils.rename(lck, ref, StandardCopyOption.ATOMIC_MOVE); haveLck = false; + isAppend = false; + written = false; closeToken(); return true; } catch (IOException e) { @@ -497,6 +550,8 @@ public class LockFile { closeToken(); } } + isAppend = false; + written = false; } /** {@inheritDoc} */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java index 98c63cdcdd..de6a346cb2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java @@ -23,7 +23,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TreeMap; @@ -224,8 +223,17 @@ public class OpenSshConfigFile implements SshConfigStore { entries.put(DEFAULT_NAME, defaults); while ((line = reader.readLine()) != null) { + // OpenSsh ignores trailing comments on a line. Anything after the + // first # on a line is trimmed away (yes, even if the hash is + // inside quotes). + // + // See https://github.com/openssh/openssh-portable/commit/2bcbf679 + int i = line.indexOf('#'); + if (i >= 0) { + line = line.substring(0, i); + } line = line.trim(); - if (line.isEmpty() || line.startsWith("#")) { //$NON-NLS-1$ + if (line.isEmpty()) { continue; } String[] parts = line.split("[ \t]*[= \t]", 2); //$NON-NLS-1$ @@ -484,12 +492,30 @@ public class OpenSshConfigFile implements SshConfigStore { LIST_KEYS.add(SshConstants.USER_KNOWN_HOSTS_FILE); } + /** + * OpenSSH has renamed some config keys. This maps old names to new + * names. + */ + private static final Map<String, String> ALIASES = new TreeMap<>( + String.CASE_INSENSITIVE_ORDER); + + static { + // See https://github.com/openssh/openssh-portable/commit/ee9c0da80 + ALIASES.put("PubkeyAcceptedKeyTypes", //$NON-NLS-1$ + SshConstants.PUBKEY_ACCEPTED_ALGORITHMS); + } + private Map<String, String> options; private Map<String, List<String>> multiOptions; private Map<String, List<String>> listOptions; + private static String toKey(String key) { + String k = ALIASES.get(key); + return k != null ? k : key; + } + /** * Retrieves the value of a single-valued key, or the first if the key * has multiple values. Keys are case-insensitive, so @@ -501,15 +527,15 @@ public class OpenSshConfigFile implements SshConfigStore { */ @Override public String getValue(String key) { - String result = options != null ? options.get(key) : null; + String k = toKey(key); + String result = options != null ? options.get(k) : null; if (result == null) { // Let's be lenient and return at least the first value from // a list-valued or multi-valued key. - List<String> values = listOptions != null ? listOptions.get(key) + List<String> values = listOptions != null ? listOptions.get(k) : null; if (values == null) { - values = multiOptions != null ? multiOptions.get(key) - : null; + values = multiOptions != null ? multiOptions.get(k) : null; } if (values != null && !values.isEmpty()) { result = values.get(0); @@ -529,10 +555,11 @@ public class OpenSshConfigFile implements SshConfigStore { */ @Override public List<String> getValues(String key) { - List<String> values = listOptions != null ? listOptions.get(key) + String k = toKey(key); + List<String> values = listOptions != null ? listOptions.get(k) : null; if (values == null) { - values = multiOptions != null ? multiOptions.get(key) : null; + values = multiOptions != null ? multiOptions.get(k) : null; } if (values == null || values.isEmpty()) { return new ArrayList<>(); @@ -551,34 +578,35 @@ public class OpenSshConfigFile implements SshConfigStore { * to set or add */ public void setValue(String key, String value) { + String k = toKey(key); if (value == null) { if (multiOptions != null) { - multiOptions.remove(key); + multiOptions.remove(k); } if (listOptions != null) { - listOptions.remove(key); + listOptions.remove(k); } if (options != null) { - options.remove(key); + options.remove(k); } return; } - if (MULTI_KEYS.contains(key)) { + if (MULTI_KEYS.contains(k)) { if (multiOptions == null) { multiOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); } - List<String> values = multiOptions.get(key); + List<String> values = multiOptions.get(k); if (values == null) { values = new ArrayList<>(4); - multiOptions.put(key, values); + multiOptions.put(k, values); } values.add(value); } else { if (options == null) { options = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); } - if (!options.containsKey(key)) { - options.put(key, value); + if (!options.containsKey(k)) { + options.put(k, value); } } } @@ -595,20 +623,21 @@ public class OpenSshConfigFile implements SshConfigStore { if (values.isEmpty()) { return; } + String k = toKey(key); // Check multi-valued keys first; because of the replacement // strategy, they must take precedence over list-valued keys // which always follow the "first occurrence wins" strategy. // // Note that SendEnv is a multi-valued list-valued key. (It's // rather immaterial for JGit, though.) - if (MULTI_KEYS.contains(key)) { + if (MULTI_KEYS.contains(k)) { if (multiOptions == null) { multiOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); } - List<String> items = multiOptions.get(key); + List<String> items = multiOptions.get(k); if (items == null) { items = new ArrayList<>(values); - multiOptions.put(key, items); + multiOptions.put(k, items); } else { items.addAll(values); } @@ -616,8 +645,8 @@ public class OpenSshConfigFile implements SshConfigStore { if (listOptions == null) { listOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); } - if (!listOptions.containsKey(key)) { - listOptions.put(key, values); + if (!listOptions.containsKey(k)) { + listOptions.put(k, values); } } } @@ -630,7 +659,7 @@ public class OpenSshConfigFile implements SshConfigStore { * @return {@code true} if the key is a list-valued key. */ public static boolean isListKey(String key) { - return LIST_KEYS.contains(key.toUpperCase(Locale.ROOT)); + return LIST_KEYS.contains(toKey(key)); } void merge(HostEntry entry) { @@ -679,10 +708,10 @@ public class OpenSshConfigFile implements SshConfigStore { } private List<String> substitute(List<String> values, String allowed, - Replacer r) { + Replacer r, boolean withEnv) { List<String> result = new ArrayList<>(values.size()); for (String value : values) { - result.add(r.substitute(value, allowed)); + result.add(r.substitute(value, allowed, withEnv)); } return result; } @@ -714,7 +743,7 @@ public class OpenSshConfigFile implements SshConfigStore { if (hostName == null || hostName.isEmpty()) { options.put(SshConstants.HOST_NAME, originalHostName); } else { - hostName = r.substitute(hostName, "h"); //$NON-NLS-1$ + hostName = r.substitute(hostName, "h", false); //$NON-NLS-1$ options.put(SshConstants.HOST_NAME, hostName); r.update('h', hostName); } @@ -723,13 +752,13 @@ public class OpenSshConfigFile implements SshConfigStore { List<String> values = multiOptions .get(SshConstants.IDENTITY_FILE); if (values != null) { - values = substitute(values, "dhlru", r); //$NON-NLS-1$ + values = substitute(values, "dhlru", r, true); //$NON-NLS-1$ values = replaceTilde(values, home); multiOptions.put(SshConstants.IDENTITY_FILE, values); } values = multiOptions.get(SshConstants.CERTIFICATE_FILE); if (values != null) { - values = substitute(values, "dhlru", r); //$NON-NLS-1$ + values = substitute(values, "dhlru", r, true); //$NON-NLS-1$ values = replaceTilde(values, home); multiOptions.put(SshConstants.CERTIFICATE_FILE, values); } @@ -746,29 +775,29 @@ public class OpenSshConfigFile implements SshConfigStore { // HOSTNAME already done above String value = options.get(SshConstants.IDENTITY_AGENT); if (value != null) { - value = r.substitute(value, "dhlru"); //$NON-NLS-1$ + value = r.substitute(value, "dhlru", true); //$NON-NLS-1$ value = toFile(value, home).getPath(); options.put(SshConstants.IDENTITY_AGENT, value); } value = options.get(SshConstants.CONTROL_PATH); if (value != null) { - value = r.substitute(value, "ChLlnpru"); //$NON-NLS-1$ + value = r.substitute(value, "ChLlnpru", true); //$NON-NLS-1$ value = toFile(value, home).getPath(); options.put(SshConstants.CONTROL_PATH, value); } value = options.get(SshConstants.LOCAL_COMMAND); if (value != null) { - value = r.substitute(value, "CdhlnprTu"); //$NON-NLS-1$ + value = r.substitute(value, "CdhlnprTu", false); //$NON-NLS-1$ options.put(SshConstants.LOCAL_COMMAND, value); } value = options.get(SshConstants.REMOTE_COMMAND); if (value != null) { - value = r.substitute(value, "Cdhlnpru"); //$NON-NLS-1$ + value = r.substitute(value, "Cdhlnpru", false); //$NON-NLS-1$ options.put(SshConstants.REMOTE_COMMAND, value); } value = options.get(SshConstants.PROXY_COMMAND); if (value != null) { - value = r.substitute(value, "hpr"); //$NON-NLS-1$ + value = r.substitute(value, "hpr", false); //$NON-NLS-1$ options.put(SshConstants.PROXY_COMMAND, value); } } @@ -842,7 +871,7 @@ public class OpenSshConfigFile implements SshConfigStore { replacements.put(Character.valueOf('r'), user == null ? "" : user); //$NON-NLS-1$ replacements.put(Character.valueOf('u'), localUserName); replacements.put(Character.valueOf('C'), - substitute("%l%h%p%r", "hlpr")); //$NON-NLS-1$ //$NON-NLS-2$ + substitute("%l%h%p%r", "hlpr", false)); //$NON-NLS-1$ //$NON-NLS-2$ replacements.put(Character.valueOf('T'), "NONE"); //$NON-NLS-1$ } @@ -850,36 +879,63 @@ public class OpenSshConfigFile implements SshConfigStore { replacements.put(Character.valueOf(key), value); if ("lhpr".indexOf(key) >= 0) { //$NON-NLS-1$ replacements.put(Character.valueOf('C'), - substitute("%l%h%p%r", "hlpr")); //$NON-NLS-1$ //$NON-NLS-2$ + substitute("%l%h%p%r", "hlpr", false)); //$NON-NLS-1$ //$NON-NLS-2$ } } - public String substitute(String input, String allowed) { + public String substitute(String input, String allowed, + boolean withEnv) { if (input == null || input.length() <= 1 - || input.indexOf('%') < 0) { + || input.indexOf('%') < 0 + && (!withEnv || input.indexOf("${") < 0)) { //$NON-NLS-1$ return input; } StringBuilder builder = new StringBuilder(); int start = 0; int length = input.length(); while (start < length) { - int percent = input.indexOf('%', start); - if (percent < 0 || percent + 1 >= length) { - builder.append(input.substring(start)); + char ch = input.charAt(start); + switch (ch) { + case '%': + if (start + 1 >= length) { + break; + } + String replacement = null; + ch = input.charAt(start + 1); + if (ch == '%' || allowed.indexOf(ch) >= 0) { + replacement = replacements.get(Character.valueOf(ch)); + } + if (replacement == null) { + builder.append('%').append(ch); + } else { + builder.append(replacement); + } + start += 2; + continue; + case '$': + if (!withEnv || start + 2 >= length) { + break; + } + ch = input.charAt(start + 1); + if (ch == '{') { + int close = input.indexOf('}', start + 2); + if (close > start + 2) { + String variable = SystemReader.getInstance() + .getenv(input.substring(start + 2, close)); + if (!StringUtils.isEmptyOrNull(variable)) { + builder.append(variable); + } + start = close + 1; + continue; + } + } + ch = '$'; + break; + default: break; } - String replacement = null; - char ch = input.charAt(percent + 1); - if (ch == '%' || allowed.indexOf(ch) >= 0) { - replacement = replacements.get(Character.valueOf(ch)); - } - if (replacement == null) { - builder.append(input.substring(start, percent + 2)); - } else { - builder.append(input.substring(start, percent)) - .append(replacement); - } - start = percent + 2; + builder.append(ch); + start++; } return builder.toString(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ContentMergeStrategy.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ContentMergeStrategy.java new file mode 100644 index 0000000000..6d568643d5 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ContentMergeStrategy.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> 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 + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.merge; + +/** + * How to handle content conflicts. + * + * @since 5.12 + */ +public enum ContentMergeStrategy { + + /** Produce a conflict. */ + CONFLICT, + + /** Resolve the conflict hunk using the ours version. */ + OURS, + + /** Resolve the conflict hunk using the theirs version. */ + THEIRS +}
\ No newline at end of file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java index 27141c12c4..80607351ae 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.diff.DiffAlgorithm; import org.eclipse.jgit.diff.Edit; import org.eclipse.jgit.diff.EditList; @@ -28,8 +29,12 @@ import org.eclipse.jgit.merge.MergeChunk.ConflictState; * diff algorithm. */ public final class MergeAlgorithm { + private final DiffAlgorithm diffAlg; + @NonNull + private ContentMergeStrategy strategy = ContentMergeStrategy.CONFLICT; + /** * Creates a new MergeAlgorithm which uses * {@link org.eclipse.jgit.diff.HistogramDiff} as diff algorithm @@ -48,6 +53,30 @@ public final class MergeAlgorithm { this.diffAlg = diff; } + /** + * Retrieves the {@link ContentMergeStrategy}. + * + * @return the {@link ContentMergeStrategy} in effect + * @since 5.12 + */ + @NonNull + public ContentMergeStrategy getContentMergeStrategy() { + return strategy; + } + + /** + * Sets the {@link ContentMergeStrategy}. + * + * @param strategy + * {@link ContentMergeStrategy} to set; if {@code null}, set + * {@link ContentMergeStrategy#CONFLICT} + * @since 5.12 + */ + public void setContentMergeStrategy(ContentMergeStrategy strategy) { + this.strategy = strategy == null ? ContentMergeStrategy.CONFLICT + : strategy; + } + // An special edit which acts as a sentinel value by marking the end the // list of edits private static final Edit END_EDIT = new Edit(Integer.MAX_VALUE, @@ -79,29 +108,54 @@ public final class MergeAlgorithm { if (theirs.size() != 0) { EditList theirsEdits = diffAlg.diff(cmp, base, theirs); if (!theirsEdits.isEmpty()) { - // we deleted, they modified -> Let their complete content - // conflict with empty text - result.add(1, 0, 0, ConflictState.FIRST_CONFLICTING_RANGE); - result.add(2, 0, theirs.size(), - ConflictState.NEXT_CONFLICTING_RANGE); - } else + // we deleted, they modified + switch (strategy) { + case OURS: + result.add(1, 0, 0, ConflictState.NO_CONFLICT); + break; + case THEIRS: + result.add(2, 0, theirs.size(), + ConflictState.NO_CONFLICT); + break; + default: + // Let their complete content conflict with empty text + result.add(1, 0, 0, + ConflictState.FIRST_CONFLICTING_RANGE); + result.add(2, 0, theirs.size(), + ConflictState.NEXT_CONFLICTING_RANGE); + break; + } + } else { // we deleted, they didn't modify -> Let our deletion win result.add(1, 0, 0, ConflictState.NO_CONFLICT); - } else + } + } else { // we and they deleted -> return a single chunk of nothing result.add(1, 0, 0, ConflictState.NO_CONFLICT); + } return result; } else if (theirs.size() == 0) { EditList oursEdits = diffAlg.diff(cmp, base, ours); if (!oursEdits.isEmpty()) { - // we modified, they deleted -> Let our complete content - // conflict with empty text - result.add(1, 0, ours.size(), - ConflictState.FIRST_CONFLICTING_RANGE); - result.add(2, 0, 0, ConflictState.NEXT_CONFLICTING_RANGE); - } else + // we modified, they deleted + switch (strategy) { + case OURS: + result.add(1, 0, ours.size(), ConflictState.NO_CONFLICT); + break; + case THEIRS: + result.add(2, 0, 0, ConflictState.NO_CONFLICT); + break; + default: + // Let our complete content conflict with empty text + result.add(1, 0, ours.size(), + ConflictState.FIRST_CONFLICTING_RANGE); + result.add(2, 0, 0, ConflictState.NEXT_CONFLICTING_RANGE); + break; + } + } else { // they deleted, we didn't modify -> Let their deletion win result.add(2, 0, 0, ConflictState.NO_CONFLICT); + } return result; } @@ -249,12 +303,26 @@ public final class MergeAlgorithm { // Add the conflict (Only if there is a conflict left to report) if (minBSize > 0 || BSizeDelta != 0) { - result.add(1, oursBeginB + commonPrefix, oursEndB - - commonSuffix, - ConflictState.FIRST_CONFLICTING_RANGE); - result.add(2, theirsBeginB + commonPrefix, theirsEndB - - commonSuffix, - ConflictState.NEXT_CONFLICTING_RANGE); + switch (strategy) { + case OURS: + result.add(1, oursBeginB + commonPrefix, + oursEndB - commonSuffix, + ConflictState.NO_CONFLICT); + break; + case THEIRS: + result.add(2, theirsBeginB + commonPrefix, + theirsEndB - commonSuffix, + ConflictState.NO_CONFLICT); + break; + default: + result.add(1, oursBeginB + commonPrefix, + oursEndB - commonSuffix, + ConflictState.FIRST_CONFLICTING_RANGE); + result.add(2, theirsBeginB + commonPrefix, + theirsEndB - commonSuffix, + ConflictState.NEXT_CONFLICTING_RANGE); + break; + } } // Add the common lines at end of conflict diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java index 4bfb38d286..7767662867 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java @@ -37,6 +37,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.attributes.Attributes; import org.eclipse.jgit.diff.DiffAlgorithm; import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm; @@ -268,6 +269,13 @@ public class ResolveMerger extends ThreeWayMerger { private int inCoreLimit; /** + * The {@link ContentMergeStrategy} to use for "resolve" and "recursive" + * merges. + */ + @NonNull + private ContentMergeStrategy contentStrategy = ContentMergeStrategy.CONFLICT; + + /** * Keeps {@link CheckoutMetadata} for {@link #checkout()} and * {@link #cleanUp()}. */ @@ -344,6 +352,29 @@ public class ResolveMerger extends ThreeWayMerger { dircache = DirCache.newInCore(); } + /** + * Retrieves the content merge strategy for content conflicts. + * + * @return the {@link ContentMergeStrategy} in effect + * @since 5.12 + */ + @NonNull + public ContentMergeStrategy getContentMergeStrategy() { + return contentStrategy; + } + + /** + * Sets the content merge strategy for content conflicts. + * + * @param strategy + * {@link ContentMergeStrategy} to use + * @since 5.12 + */ + public void setContentMergeStrategy(ContentMergeStrategy strategy) { + contentStrategy = strategy == null ? ContentMergeStrategy.CONFLICT + : strategy; + } + /** {@inheritDoc} */ @Override protected boolean mergeImpl() throws IOException { @@ -644,15 +675,19 @@ public class ResolveMerger extends ThreeWayMerger { } return true; } - // FileModes are not mergeable. We found a conflict on modes. - // For conflicting entries we don't know lastModified and - // length. - add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0); - add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0); - add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0); - unmergedPaths.add(tw.getPathString()); - mergeResults.put(tw.getPathString(), - new MergeResult<>(Collections.<RawText> emptyList())); + if (!ignoreConflicts) { + // FileModes are not mergeable. We found a conflict on modes. + // For conflicting entries we don't know lastModified and + // length. + // This path can be skipped on ignoreConflicts, so the caller + // could use virtual commit. + add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0); + add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0); + add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0); + unmergedPaths.add(tw.getPathString()); + mergeResults.put(tw.getPathString(), + new MergeResult<>(Collections.emptyList())); + } return true; } @@ -757,6 +792,19 @@ public class ResolveMerger extends ThreeWayMerger { unmergedPaths.add(tw.getPathString()); return true; } else if (!attributes.canBeContentMerged()) { + // File marked as binary + switch (getContentMergeStrategy()) { + case OURS: + keep(ourDce); + return true; + case THEIRS: + DirCacheEntry theirEntry = add(tw.getRawPath(), theirs, + DirCacheEntry.STAGE_0, EPOCH, 0); + addToCheckout(tw.getPathString(), theirEntry, attributes); + return true; + default: + break; + } add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0); add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0); add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0); @@ -771,8 +819,26 @@ public class ResolveMerger extends ThreeWayMerger { return false; } - MergeResult<RawText> result = contentMerge(base, ours, theirs, - attributes); + MergeResult<RawText> result = null; + try { + result = contentMerge(base, ours, theirs, attributes, + getContentMergeStrategy()); + } catch (BinaryBlobException e) { + switch (getContentMergeStrategy()) { + case OURS: + keep(ourDce); + return true; + case THEIRS: + DirCacheEntry theirEntry = add(tw.getRawPath(), theirs, + DirCacheEntry.STAGE_0, EPOCH, 0); + addToCheckout(tw.getPathString(), theirEntry, attributes); + return true; + default: + result = new MergeResult<>(Collections.emptyList()); + result.setContainsConflicts(true); + break; + } + } if (ignoreConflicts) { result.setContainsConflicts(false); } @@ -799,9 +865,16 @@ public class ResolveMerger extends ThreeWayMerger { mergeResults.put(tw.getPathString(), result); unmergedPaths.add(tw.getPathString()); } else { - MergeResult<RawText> result = contentMerge(base, ours, - theirs, attributes); - + // Content merge strategy does not apply to delete-modify + // conflicts! + MergeResult<RawText> result; + try { + result = contentMerge(base, ours, theirs, attributes, + ContentMergeStrategy.CONFLICT); + } catch (BinaryBlobException e) { + result = new MergeResult<>(Collections.emptyList()); + result.setContainsConflicts(true); + } if (ignoreConflicts) { // In case a conflict is detected the working tree file // is again filled with new content (containing conflict @@ -863,32 +936,26 @@ public class ResolveMerger extends ThreeWayMerger { * @param ours * @param theirs * @param attributes + * @param strategy * * @return the result of the content merge + * @throws BinaryBlobException + * if any of the blobs looks like a binary blob * @throws IOException */ private MergeResult<RawText> contentMerge(CanonicalTreeParser base, CanonicalTreeParser ours, CanonicalTreeParser theirs, - Attributes attributes) - throws IOException { - RawText baseText; - RawText ourText; - RawText theirsText; - - try { - baseText = base == null ? RawText.EMPTY_TEXT : getRawText( - base.getEntryObjectId(), attributes); - ourText = ours == null ? RawText.EMPTY_TEXT : getRawText( - ours.getEntryObjectId(), attributes); - theirsText = theirs == null ? RawText.EMPTY_TEXT : getRawText( - theirs.getEntryObjectId(), attributes); - } catch (BinaryBlobException e) { - MergeResult<RawText> r = new MergeResult<>(Collections.<RawText>emptyList()); - r.setContainsConflicts(true); - return r; - } - return (mergeAlgorithm.merge(RawTextComparator.DEFAULT, baseText, - ourText, theirsText)); + Attributes attributes, ContentMergeStrategy strategy) + throws BinaryBlobException, IOException { + RawText baseText = base == null ? RawText.EMPTY_TEXT + : getRawText(base.getEntryObjectId(), attributes); + RawText ourText = ours == null ? RawText.EMPTY_TEXT + : getRawText(ours.getEntryObjectId(), attributes); + RawText theirsText = theirs == null ? RawText.EMPTY_TEXT + : getRawText(theirs.getEntryObjectId(), attributes); + mergeAlgorithm.setContentMergeStrategy(strategy); + return mergeAlgorithm.merge(RawTextComparator.DEFAULT, baseText, + ourText, theirsText); } private boolean isIndexDirty() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java index fff2938e5d..be55cd1b81 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java @@ -114,6 +114,14 @@ public final class SshConstants { /** Key in an ssh config file. */ public static final String PREFERRED_AUTHENTICATIONS = "PreferredAuthentications"; + /** + * Key in an ssh config file; defines signature algorithms for public key + * authentication as a comma-separated list. + * + * @since 5.11 + */ + public static final String PUBKEY_ACCEPTED_ALGORITHMS = "PubkeyAcceptedAlgorithms"; + /** Key in an ssh config file. */ public static final String PROXY_COMMAND = "ProxyCommand"; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UsernamePasswordCredentialsProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UsernamePasswordCredentialsProvider.java index 979961f2ae..c0de42cb57 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UsernamePasswordCredentialsProvider.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UsernamePasswordCredentialsProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010, Google Inc. and others + * Copyright (C) 2010, 2021 Google 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 @@ -58,14 +58,21 @@ public class UsernamePasswordCredentialsProvider extends CredentialsProvider { @Override public boolean supports(CredentialItem... items) { for (CredentialItem i : items) { - if (i instanceof CredentialItem.Username) + if (i instanceof CredentialItem.InformationalMessage) { continue; - - else if (i instanceof CredentialItem.Password) + } + if (i instanceof CredentialItem.Username) { continue; - - else - return false; + } + if (i instanceof CredentialItem.Password) { + continue; + } + if (i instanceof CredentialItem.StringType) { + if (i.getPromptText().equals("Password: ")) { //$NON-NLS-1$ + continue; + } + } + return false; } return true; } @@ -75,6 +82,9 @@ public class UsernamePasswordCredentialsProvider extends CredentialsProvider { public boolean get(URIish uri, CredentialItem... items) throws UnsupportedCredentialItem { for (CredentialItem i : items) { + if (i instanceof CredentialItem.InformationalMessage) { + continue; + } if (i instanceof CredentialItem.Username) { ((CredentialItem.Username) i).setValue(username); continue; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java index 55b7d6279a..0b7c0a9e4b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java @@ -502,8 +502,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { throw new IOException(new FilterFailedException(rc, filterCommand, getEntryPathString(), result.getStdout().toByteArray(MAX_EXCEPTION_TEXT_SIZE), - RawParseUtils.decode(result.getStderr() - .toByteArray(MAX_EXCEPTION_TEXT_SIZE)))); + result.getStderr().toString(MAX_EXCEPTION_TEXT_SIZE))); } return result.getStdout().openInputStreamWithAutoDestroy(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java index 562eb05dd9..fb893a66f0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java @@ -18,6 +18,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.UncheckedIOException; import java.util.ArrayList; import org.eclipse.jgit.internal.JGitText; @@ -213,6 +214,24 @@ public abstract class TemporaryBuffer extends OutputStream { } /** + * Convert first {@code limit} number of bytes of the buffer content to + * String. + * + * @param limit + * the maximum number of bytes to be converted to String + * @return first {@code limit} number of bytes of the buffer content + * converted to String. + * @since 5.12 + */ + public String toString(int limit) { + try { + return RawParseUtils.decode(toByteArray(limit)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** * Convert this buffer's contents into a contiguous byte array. If this size * of the buffer exceeds the limit only return the first {@code limit} bytes * <p> |