diff options
Diffstat (limited to 'org.eclipse.jgit/src/org/eclipse/jgit/api')
9 files changed, 551 insertions, 118 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 7922f9e729..ceba89d166 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java @@ -9,6 +9,8 @@ */ package org.eclipse.jgit.api; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH; + import java.io.IOException; import java.text.MessageFormat; import java.util.LinkedList; @@ -28,6 +30,7 @@ import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.CommitConfig; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; @@ -124,7 +127,7 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> { final RevCommit srcParent = getParentCommit(srcCommit, revWalk); String ourName = calculateOurName(headRef); - String cherryPickName = srcCommit.getId().abbreviate(7).name() + String cherryPickName = srcCommit.getId().abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH).name() + " " + srcCommit.getShortMessage(); //$NON-NLS-1$ Merger merger = strategy.newMerger(repo); @@ -181,9 +184,13 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> { String message; if (unmergedPaths != null) { + CommitConfig cfg = repo.getConfig() + .get(CommitConfig.KEY); + message = srcCommit.getFullMessage(); + char commentChar = cfg.getCommentChar(message); message = new MergeMessageFormatter() - .formatWithConflicts(srcCommit.getFullMessage(), - unmergedPaths); + .formatWithConflicts(message, unmergedPaths, + commentChar); } else { message = srcCommit.getFullMessage(); } 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 37f1d482aa..3b3baf5a12 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java @@ -19,6 +19,7 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.api.errors.AbortedByHookException; import org.eclipse.jgit.api.errors.CanceledException; import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; @@ -46,6 +47,8 @@ import org.eclipse.jgit.hooks.PostCommitHook; import org.eclipse.jgit.hooks.PreCommitHook; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.CommitConfig; +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; @@ -133,6 +136,12 @@ public class CommitCommand extends GitCommand<RevCommit> { private CredentialsProvider credentialsProvider; + private @NonNull CleanupMode cleanupMode = CleanupMode.VERBATIM; + + private boolean cleanDefaultIsStrip = true; + + private Character commentChar; + /** * Constructor for CommitCommand * @@ -200,7 +209,7 @@ public class CommitCommand extends GitCommand<RevCommit> { throw new WrongRepositoryStateException( JGitText.get().commitAmendOnInitialNotPossible); - if (headId != null) + if (headId != null) { if (amend) { RevCommit previousCommit = rw.parseCommit(headId); for (RevCommit p : previousCommit.getParents()) @@ -210,7 +219,7 @@ public class CommitCommand extends GitCommand<RevCommit> { } else { parents.add(0, headId); } - + } if (!noVerify) { message = Hooks .commitMsg(repo, @@ -219,6 +228,33 @@ public class CommitCommand extends GitCommand<RevCommit> { .setCommitMessage(message).call(); } + CommitConfig config = null; + if (CleanupMode.DEFAULT.equals(cleanupMode)) { + config = repo.getConfig().get(CommitConfig.KEY); + cleanupMode = config.resolve(cleanupMode, cleanDefaultIsStrip); + } + char comments = (char) 0; + if (CleanupMode.STRIP.equals(cleanupMode) + || CleanupMode.SCISSORS.equals(cleanupMode)) { + if (commentChar == null) { + if (config == null) { + config = repo.getConfig().get(CommitConfig.KEY); + } + if (config.isAutoCommentChar()) { + // We're supposed to pick a character that isn't used, + // but then cleaning up won't remove any lines. So don't + // bother. + comments = (char) 0; + cleanupMode = CleanupMode.WHITESPACE; + } else { + comments = config.getCommentChar(); + } + } else { + comments = commentChar.charValue(); + } + } + message = CommitConfig.cleanText(message, cleanupMode, comments); + RevCommit revCommit; DirCache index = repo.lockDirCache(); try (ObjectInserter odi = repo.newObjectInserter()) { @@ -287,8 +323,14 @@ public class CommitCommand extends GitCommand<RevCommit> { private void sign(CommitBuilder commit) throws ServiceUnavailableException, CanceledException, UnsupportedSigningFormatException { if (gpgSigner == null) { - throw new ServiceUnavailableException( - JGitText.get().signingServiceUnavailable); + gpgSigner = GpgSigner.getDefault(); + if (gpgSigner == null) { + throw new ServiceUnavailableException( + JGitText.get().signingServiceUnavailable); + } + } + if (signingKey == null) { + signingKey = gpgConfig.getSigningKey(); } if (gpgSigner instanceof GpgObjectSigner) { ((GpgObjectSigner) gpgSigner).signObject(commit, @@ -623,12 +665,6 @@ public class CommitCommand extends GitCommand<RevCommit> { signCommit = gpgConfig.isSignCommits() ? Boolean.TRUE : Boolean.FALSE; } - if (signingKey == null) { - signingKey = gpgConfig.getSigningKey(); - } - if (gpgSigner == null) { - gpgSigner = GpgSigner.getDefault(); - } } private boolean isMergeDuringRebase(RepositoryState state) { @@ -658,6 +694,57 @@ public class CommitCommand extends GitCommand<RevCommit> { } /** + * Sets the {@link CleanupMode} to apply to the commit message. If not + * called, {@link CommitCommand} applies {@link CleanupMode#VERBATIM}. + * + * @param mode + * {@link CleanupMode} to set + * @return {@code this} + * @since 6.1 + */ + public CommitCommand setCleanupMode(@NonNull CleanupMode mode) { + checkCallable(); + this.cleanupMode = mode; + return this; + } + + /** + * Sets the default clean mode if {@link #setCleanupMode(CleanupMode) + * setCleanupMode(CleanupMode.DEFAULT)} is set and git config + * {@code commit.cleanup = default} or is not set. + * + * @param strip + * if {@code true}, default to {@link CleanupMode#STRIP}; + * otherwise default to {@link CleanupMode#WHITESPACE} + * @return {@code this} + * @since 6.1 + */ + public CommitCommand setDefaultClean(boolean strip) { + checkCallable(); + this.cleanDefaultIsStrip = strip; + return this; + } + + /** + * Sets the comment character to apply when cleaning a commit message. If + * {@code null} (the default) and the {@link #setCleanupMode(CleanupMode) + * clean-up mode} is {@link CleanupMode#STRIP} or + * {@link CleanupMode#SCISSORS}, the value of git config + * {@code core.commentChar} will be used. + * + * @param commentChar + * the comment character, or {@code null} to use the value from + * the git config + * @return {@code this} + * @since 6.1 + */ + public CommitCommand setCommentCharacter(Character commentChar) { + checkCallable(); + this.commentChar = commentChar; + return this; + } + + /** * Set whether to allow to create an empty commit * * @param allowEmpty @@ -806,7 +893,7 @@ public class CommitCommand extends GitCommand<RevCommit> { * command line. * * @param amend - * whether to ammend the tip of the current branch + * whether to amend the tip of the current branch * @return {@code this} */ public CommitCommand setAmend(boolean amend) { 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 1e524fadab..805a886392 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java @@ -11,6 +11,7 @@ package org.eclipse.jgit.api; import static org.eclipse.jgit.lib.Constants.R_REFS; import static org.eclipse.jgit.lib.Constants.R_TAGS; +import static org.eclipse.jgit.lib.TypedConfigGetter.UNSET_INT; import java.io.IOException; import java.text.MessageFormat; @@ -33,6 +34,7 @@ import org.eclipse.jgit.errors.InvalidPatternException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.fnmatch.FileNameMatcher; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.AbbrevConfig; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; @@ -89,6 +91,11 @@ public class DescribeCommand extends GitCommand<String> { private boolean always; /** + * The prefix length to use when abbreviating a commit hash. + */ + private int abbrev = UNSET_INT; + + /** * Constructor for DescribeCommand. * * @param repo @@ -205,12 +212,33 @@ public class DescribeCommand extends GitCommand<String> { return this; } + /** + * Sets the prefix length to use when abbreviating an object SHA-1. + * + * @param abbrev + * minimum length of the abbreviated string. Must be in the range + * [{@value AbbrevConfig#MIN_ABBREV}, + * {@value Constants#OBJECT_ID_STRING_LENGTH}]. + * @return {@code this} + * @since 6.1 + */ + public DescribeCommand setAbbrev(int abbrev) { + if (abbrev == 0) { + this.abbrev = 0; + } else { + this.abbrev = AbbrevConfig.capAbbrev(abbrev); + } + return this; + } + private String longDescription(Ref tag, int depth, ObjectId tip) throws IOException { - return String.format( - "%s-%d-g%s", formatRefName(tag.getName()), //$NON-NLS-1$ - Integer.valueOf(depth), w.getObjectReader().abbreviate(tip) - .name()); + if (abbrev == 0) { + return formatRefName(tag.getName()); + } + return String.format("%s-%d-g%s", formatRefName(tag.getName()), //$NON-NLS-1$ + Integer.valueOf(depth), + w.getObjectReader().abbreviate(tip, abbrev).name()); } /** @@ -302,6 +330,9 @@ public class DescribeCommand extends GitCommand<String> { if (target == null) { setTarget(Constants.HEAD); } + if (abbrev == UNSET_INT) { + abbrev = AbbrevConfig.parseFromConfig(repo).get(); + } Collection<Ref> tagList = repo.getRefDatabase() .getRefsByPrefix(useAll ? R_REFS : R_TAGS); @@ -413,7 +444,12 @@ public class DescribeCommand extends GitCommand<String> { // if all the nodes are dominated by all the tags, the walk stops if (candidates.isEmpty()) { - return always ? w.getObjectReader().abbreviate(target).name() : null; + return always + ? w.getObjectReader() + .abbreviate(target, + AbbrevConfig.capAbbrev(abbrev)) + .name() + : null; } Candidate best = Collections.min(candidates, diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java index 0c691062f9..c3415581ef 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011, 2020 Christoph Brill <egore911@egore911.de> and others + * Copyright (C) 2011, 2022 Christoph Brill <egore911@egore911.de> 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 @@ -9,6 +9,7 @@ */ package org.eclipse.jgit.api; +import java.io.IOException; import java.net.URISyntaxException; import java.text.MessageFormat; import java.util.ArrayList; @@ -20,8 +21,8 @@ import java.util.Map; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRemoteException; import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.NotSupportedException; -import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Ref; @@ -30,6 +31,8 @@ import org.eclipse.jgit.transport.FetchConnection; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.Transport; import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.transport.UrlConfig; +import org.eclipse.jgit.util.SystemReader; /** * The ls-remote command @@ -153,7 +156,7 @@ public class LsRemoteCommand extends try (Transport transport = repo != null ? Transport.open(repo, remote) - : Transport.open(new URIish(remote))) { + : Transport.open(new URIish(translate(remote)))) { transport.setOptionUploadPack(uploadPack); configure(transport); Collection<RefSpec> refSpecs = new ArrayList<>(1); @@ -185,11 +188,16 @@ public class LsRemoteCommand extends throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfLsRemoteCommand, e); - } catch (TransportException e) { + } catch (IOException | ConfigInvalidException e) { throw new org.eclipse.jgit.api.errors.TransportException( - e.getMessage(), - e); + e.getMessage(), e); } } + private String translate(String uri) + throws IOException, ConfigInvalidException { + UrlConfig urls = new UrlConfig( + SystemReader.getInstance().getUserConfig()); + return urls.replace(uri); + } } 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 ef56d802c8..ed4a5342b3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java @@ -34,6 +34,7 @@ import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.CommitConfig; import org.eclipse.jgit.lib.Config.ConfigEnum; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; @@ -404,8 +405,11 @@ public class MergeCommand extends GitCommand<MergeResult> { MergeStatus.FAILED, mergeStrategy, lowLevelResults, failingPaths, null); } + CommitConfig cfg = repo.getConfig().get(CommitConfig.KEY); + char commentChar = cfg.getCommentChar(message); String mergeMessageWithConflicts = new MergeMessageFormatter() - .formatWithConflicts(mergeMessage, unmergedPaths); + .formatWithConflicts(mergeMessage, unmergedPaths, + commentChar); repo.writeMergeCommitMsg(mergeMessageWithConflicts); return new MergeResult(null, merger.getBaseCommitId(), new ObjectId[] { headCommit.getId(), 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 aa5a63499c..08353dfdfa 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com> and others + * Copyright (C) 2010, 2022 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 @@ -21,7 +21,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.eclipse.jgit.api.errors.DetachedHeadException; import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.InvalidRemoteException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.NotSupportedException; @@ -29,11 +31,16 @@ import org.eclipse.jgit.errors.TooLargeObjectInPackException; import org.eclipse.jgit.errors.TooLargePackException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.BranchConfig; +import org.eclipse.jgit.lib.Config; +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.Repository; +import org.eclipse.jgit.transport.PushConfig; +import org.eclipse.jgit.transport.PushConfig.PushDefault; import org.eclipse.jgit.transport.PushResult; import org.eclipse.jgit.transport.RefLeaseSpec; import org.eclipse.jgit.transport.RefSpec; @@ -52,7 +59,7 @@ import org.eclipse.jgit.transport.Transport; public class PushCommand extends TransportCommand<PushCommand, Iterable<PushResult>> { - private String remote = Constants.DEFAULT_REMOTE_NAME; + private String remote; private final List<RefSpec> refSpecs; @@ -71,6 +78,10 @@ public class PushCommand extends private List<String> pushOptions; + // Legacy behavior as default. Use setPushDefault(null) to determine the + // value from the git config. + private PushDefault pushDefault = PushDefault.CURRENT; + /** * <p> * Constructor for PushCommand. @@ -98,19 +109,20 @@ public class PushCommand extends InvalidRemoteException, org.eclipse.jgit.api.errors.TransportException { checkCallable(); + setCallable(false); ArrayList<PushResult> pushResults = new ArrayList<>(3); try { + Config config = repo.getConfig(); + remote = determineRemote(config, remote); if (refSpecs.isEmpty()) { - RemoteConfig config = new RemoteConfig(repo.getConfig(), + RemoteConfig rc = new RemoteConfig(config, getRemote()); - refSpecs.addAll(config.getPushRefSpecs()); - } - if (refSpecs.isEmpty()) { - Ref head = repo.exactRef(Constants.HEAD); - if (head != null && head.isSymbolic()) - refSpecs.add(new RefSpec(head.getLeaf().getName())); + refSpecs.addAll(rc.getPushRefSpecs()); + if (refSpecs.isEmpty()) { + determineDefaultRefSpecs(config); + } } if (force) { @@ -118,8 +130,8 @@ public class PushCommand extends refSpecs.set(i, refSpecs.get(i).setForceUpdate(true)); } - final List<Transport> transports; - transports = Transport.openAll(repo, remote, Transport.Operation.PUSH); + List<Transport> transports = Transport.openAll(repo, remote, + Transport.Operation.PUSH); for (@SuppressWarnings("resource") // Explicitly closed in finally final Transport transport : transports) { transport.setPushThin(thin); @@ -171,6 +183,102 @@ public class PushCommand extends return pushResults; } + private String determineRemote(Config config, String remoteName) + throws IOException { + if (remoteName != null) { + return remoteName; + } + Ref head = repo.exactRef(Constants.HEAD); + String effectiveRemote = null; + BranchConfig branchCfg = null; + if (head != null && head.isSymbolic()) { + String currentBranch = head.getLeaf().getName(); + branchCfg = new BranchConfig(config, + Repository.shortenRefName(currentBranch)); + effectiveRemote = branchCfg.getPushRemote(); + } + if (effectiveRemote == null) { + effectiveRemote = config.getString( + ConfigConstants.CONFIG_REMOTE_SECTION, null, + ConfigConstants.CONFIG_KEY_PUSH_DEFAULT); + if (effectiveRemote == null && branchCfg != null) { + effectiveRemote = branchCfg.getRemote(); + } + } + if (effectiveRemote == null) { + effectiveRemote = Constants.DEFAULT_REMOTE_NAME; + } + return effectiveRemote; + } + + private String getCurrentBranch() + throws IOException, DetachedHeadException { + Ref head = repo.exactRef(Constants.HEAD); + if (head != null && head.isSymbolic()) { + return head.getLeaf().getName(); + } + throw new DetachedHeadException(); + } + + private void determineDefaultRefSpecs(Config config) + throws IOException, GitAPIException { + if (pushDefault == null) { + pushDefault = config.get(PushConfig::new).getPushDefault(); + } + switch (pushDefault) { + case CURRENT: + refSpecs.add(new RefSpec(getCurrentBranch())); + break; + case MATCHING: + refSpecs.add(new RefSpec(":")); //$NON-NLS-1$ + break; + case NOTHING: + throw new InvalidRefNameException( + JGitText.get().pushDefaultNothing); + case SIMPLE: + case UPSTREAM: + String currentBranch = getCurrentBranch(); + BranchConfig branchCfg = new BranchConfig(config, + Repository.shortenRefName(currentBranch)); + String fetchRemote = branchCfg.getRemote(); + if (fetchRemote == null) { + fetchRemote = Constants.DEFAULT_REMOTE_NAME; + } + boolean isTriangular = !fetchRemote.equals(remote); + if (isTriangular) { + if (PushDefault.UPSTREAM.equals(pushDefault)) { + throw new InvalidRefNameException(MessageFormat.format( + JGitText.get().pushDefaultTriangularUpstream, + remote, fetchRemote)); + } + // Strange, but consistent with C git: "simple" doesn't even + // check whether there is a configured upstream, and if so, that + // it is equal to the local branch name. It just becomes + // "current". + refSpecs.add(new RefSpec(currentBranch)); + } else { + String trackedBranch = branchCfg.getMerge(); + if (branchCfg.isRemoteLocal() || trackedBranch == null + || !trackedBranch.startsWith(Constants.R_HEADS)) { + throw new InvalidRefNameException(MessageFormat.format( + JGitText.get().pushDefaultNoUpstream, + currentBranch)); + } + if (PushDefault.SIMPLE.equals(pushDefault) + && !trackedBranch.equals(currentBranch)) { + throw new InvalidRefNameException(MessageFormat.format( + JGitText.get().pushDefaultSimple, currentBranch, + trackedBranch)); + } + refSpecs.add(new RefSpec(currentBranch + ':' + trackedBranch)); + } + break; + default: + throw new InvalidRefNameException(MessageFormat + .format(JGitText.get().pushDefaultUnknown, pushDefault)); + } + } + /** * The remote (uri or name) used for the push operation. If no remote is * set, the default value of <code>Constants.DEFAULT_REMOTE_NAME</code> will @@ -336,9 +444,37 @@ public class PushCommand extends } /** + * Retrieves the {@link PushDefault} currently set. + * + * @return the {@link PushDefault}, or {@code null} if not set + * @since 6.1 + */ + public PushDefault getPushDefault() { + return pushDefault; + } + + /** + * Sets an explicit {@link PushDefault}. The default used if this is not + * called is {@link PushDefault#CURRENT} for compatibility reasons with + * earlier JGit versions. + * + * @param pushDefault + * {@link PushDefault} to set; if {@code null} the value defined + * in the git config will be used. + * + * @return {@code this} + * @since 6.1 + */ + public PushCommand setPushDefault(PushDefault pushDefault) { + checkCallable(); + this.pushDefault = pushDefault; + return this; + } + + /** * Push all branches under refs/heads/*. * - * @return {code this} + * @return {@code this} */ public PushCommand setPushAll() { refSpecs.add(Transport.REFSPEC_PUSH_ALL); @@ -348,7 +484,7 @@ public class PushCommand extends /** * Push all tags under refs/tags/*. * - * @return {code this} + * @return {@code this} */ public PushCommand setPushTags() { refSpecs.add(Transport.REFSPEC_TAGS); 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 a26ffc2e66..4e0d9d78c3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java @@ -29,6 +29,7 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.api.RebaseResult.Status; import org.eclipse.jgit.api.ResetCommand.ResetType; import org.eclipse.jgit.api.errors.CheckoutConflictException; @@ -52,6 +53,8 @@ import org.eclipse.jgit.errors.RevisionSyntaxException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.CommitConfig; +import org.eclipse.jgit.lib.CommitConfig.CleanupMode; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; @@ -205,6 +208,8 @@ public class RebaseCommand extends GitCommand<RebaseResult> { private InteractiveHandler interactiveHandler; + private CommitConfig commitConfig; + private boolean stopAfterInitialization = false; private RevCommit newHead; @@ -246,6 +251,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { lastStepWasForward = false; checkCallable(); checkParameters(); + commitConfig = repo.getConfig().get(CommitConfig.KEY); try { switch (operation) { case ABORT: @@ -441,11 +447,17 @@ public class RebaseCommand extends GitCommand<RebaseResult> { return null; // continue rebase process on pick command case REWORD: String oldMessage = commitToPick.getFullMessage(); - String newMessage = interactiveHandler - .modifyCommitMessage(oldMessage); + CleanupMode mode = commitConfig.resolve(CleanupMode.DEFAULT, true); + boolean[] doChangeId = { false }; + String newMessage = editCommitMessage(doChangeId, oldMessage, mode, + commitConfig.getCommentChar(oldMessage)); try (Git git = new Git(repo)) { - newHead = git.commit().setMessage(newMessage).setAmend(true) - .setNoVerify(true).call(); + newHead = git.commit() + .setMessage(newMessage) + .setAmend(true) + .setNoVerify(true) + .setInsertChangeId(doChangeId[0]) + .call(); } return null; case EDIT: @@ -460,17 +472,49 @@ public class RebaseCommand extends GitCommand<RebaseResult> { resetSoftToParent(); List<RebaseTodoLine> steps = repo.readRebaseTodo( rebaseState.getPath(GIT_REBASE_TODO), false); - RebaseTodoLine nextStep = steps.isEmpty() ? null : steps.get(0); + boolean isLast = steps.isEmpty(); + if (!isLast) { + switch (steps.get(0).getAction()) { + case FIXUP: + case SQUASH: + break; + default: + isLast = true; + break; + } + } File messageFixupFile = rebaseState.getFile(MESSAGE_FIXUP); File messageSquashFile = rebaseState.getFile(MESSAGE_SQUASH); - if (isSquash && messageFixupFile.exists()) + if (isSquash && messageFixupFile.exists()) { messageFixupFile.delete(); - newHead = doSquashFixup(isSquash, commitToPick, nextStep, + } + newHead = doSquashFixup(isSquash, commitToPick, isLast, messageFixupFile, messageSquashFile); } return null; } + private String editCommitMessage(boolean[] doChangeId, String message, + @NonNull CleanupMode mode, char commentChar) { + String newMessage; + CommitConfig.CleanupMode cleanup; + if (interactiveHandler instanceof InteractiveHandler2) { + InteractiveHandler2.ModifyResult modification = ((InteractiveHandler2) interactiveHandler) + .editCommitMessage(message, mode, commentChar); + newMessage = modification.getMessage(); + cleanup = modification.getCleanupMode(); + if (CleanupMode.DEFAULT.equals(cleanup)) { + cleanup = mode; + } + doChangeId[0] = modification.shouldAddChangeId(); + } else { + newMessage = interactiveHandler.modifyCommitMessage(message); + cleanup = CommitConfig.CleanupMode.STRIP; + doChangeId[0] = false; + } + return CommitConfig.cleanText(newMessage, cleanup, commentChar); + } + private RebaseResult cherryPickCommit(RevCommit commitToPick) throws IOException, GitAPIException, NoMessageException, UnmergedPathsException, ConcurrentRefUpdateException, @@ -707,7 +751,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { } private RevCommit doSquashFixup(boolean isSquash, RevCommit commitToPick, - RebaseTodoLine nextStep, File messageFixup, File messageSquash) + boolean isLast, File messageFixup, File messageSquash) throws IOException, GitAPIException { if (!messageSquash.exists()) { @@ -717,24 +761,20 @@ public class RebaseCommand extends GitCommand<RebaseResult> { initializeSquashFixupFile(MESSAGE_SQUASH, previousCommit.getFullMessage()); - if (!isSquash) - initializeSquashFixupFile(MESSAGE_FIXUP, - previousCommit.getFullMessage()); + if (!isSquash) { + rebaseState.createFile(MESSAGE_FIXUP, + previousCommit.getFullMessage()); + } } - String currSquashMessage = rebaseState - .readFile(MESSAGE_SQUASH); + String currSquashMessage = rebaseState.readFile(MESSAGE_SQUASH); int count = parseSquashFixupSequenceCount(currSquashMessage) + 1; String content = composeSquashMessage(isSquash, commitToPick, currSquashMessage, count); rebaseState.createFile(MESSAGE_SQUASH, content); - if (messageFixup.exists()) - rebaseState.createFile(MESSAGE_FIXUP, content); - return squashIntoPrevious( - !messageFixup.exists(), - nextStep); + return squashIntoPrevious(!messageFixup.exists(), isLast); } private void resetSoftToParent() throws IOException, @@ -756,26 +796,31 @@ public class RebaseCommand extends GitCommand<RebaseResult> { } private RevCommit squashIntoPrevious(boolean sequenceContainsSquash, - RebaseTodoLine nextStep) + boolean isLast) throws IOException, GitAPIException { RevCommit retNewHead; - String commitMessage = rebaseState - .readFile(MESSAGE_SQUASH); - + String commitMessage; + if (!isLast || sequenceContainsSquash) { + commitMessage = rebaseState.readFile(MESSAGE_SQUASH); + } else { + commitMessage = rebaseState.readFile(MESSAGE_FIXUP); + } try (Git git = new Git(repo)) { - if (nextStep == null || ((nextStep.getAction() != Action.FIXUP) - && (nextStep.getAction() != Action.SQUASH))) { - // this is the last step in this sequence + if (isLast) { + boolean[] doChangeId = { false }; if (sequenceContainsSquash) { - commitMessage = interactiveHandler - .modifyCommitMessage(commitMessage); + char commentChar = commitMessage.charAt(0); + commitMessage = editCommitMessage(doChangeId, commitMessage, + CleanupMode.STRIP, commentChar); } retNewHead = git.commit() - .setMessage(stripCommentLines(commitMessage)) - .setAmend(true).setNoVerify(true).call(); + .setMessage(commitMessage) + .setAmend(true) + .setNoVerify(true) + .setInsertChangeId(doChangeId[0]) + .call(); rebaseState.getFile(MESSAGE_SQUASH).delete(); rebaseState.getFile(MESSAGE_FIXUP).delete(); - } else { // Next step is either Squash or Fixup retNewHead = git.commit().setMessage(commitMessage) @@ -785,46 +830,61 @@ public class RebaseCommand extends GitCommand<RebaseResult> { return retNewHead; } - private static String stripCommentLines(String commitMessage) { - StringBuilder result = new StringBuilder(); - for (String line : commitMessage.split("\n")) { //$NON-NLS-1$ - if (!line.trim().startsWith("#")) //$NON-NLS-1$ - result.append(line).append("\n"); //$NON-NLS-1$ - } - if (!commitMessage.endsWith("\n")) { //$NON-NLS-1$ - int bufferSize = result.length(); - if (bufferSize > 0 && result.charAt(bufferSize - 1) == '\n') { - result.deleteCharAt(bufferSize - 1); - } - } - return result.toString(); - } - @SuppressWarnings("nls") - private static String composeSquashMessage(boolean isSquash, + private String composeSquashMessage(boolean isSquash, RevCommit commitToPick, String currSquashMessage, int count) { StringBuilder sb = new StringBuilder(); String ordinal = getOrdinal(count); - sb.setLength(0); - sb.append("# This is a combination of ").append(count) - .append(" commits.\n"); - // Add the previous message without header (i.e first line) - sb.append(currSquashMessage - .substring(currSquashMessage.indexOf('\n') + 1)); - sb.append("\n"); - if (isSquash) { - sb.append("# This is the ").append(count).append(ordinal) - .append(" commit message:\n"); - sb.append(commitToPick.getFullMessage()); + // currSquashMessage is always non-empty here, and the first character + // is the comment character used so far. + char commentChar = currSquashMessage.charAt(0); + String newMessage = commitToPick.getFullMessage(); + if (!isSquash) { + sb.append(commentChar).append(" This is a combination of ") + .append(count).append(" commits.\n"); + // Add the previous message without header (i.e first line) + sb.append(currSquashMessage + .substring(currSquashMessage.indexOf('\n') + 1)); + sb.append('\n'); + sb.append(commentChar).append(" The ").append(count).append(ordinal) + .append(" commit message will be skipped:\n") + .append(commentChar).append(' '); + sb.append(newMessage.replaceAll("([\n\r])", + "$1" + commentChar + ' ')); } else { - sb.append("# The ").append(count).append(ordinal) - .append(" commit message will be skipped:\n# "); - sb.append(commitToPick.getFullMessage().replaceAll("([\n\r])", - "$1# ")); + String currentMessage = currSquashMessage; + if (commitConfig.isAutoCommentChar()) { + // Figure out a new comment character taking into account the + // new message + String cleaned = CommitConfig.cleanText(currentMessage, + CommitConfig.CleanupMode.STRIP, commentChar) + '\n' + + newMessage; + char newCommentChar = commitConfig.getCommentChar(cleaned); + if (newCommentChar != commentChar) { + currentMessage = replaceCommentChar(currentMessage, + commentChar, newCommentChar); + commentChar = newCommentChar; + } + } + sb.append(commentChar).append(" This is a combination of ") + .append(count).append(" commits.\n"); + // Add the previous message without header (i.e first line) + sb.append( + currentMessage.substring(currentMessage.indexOf('\n') + 1)); + sb.append('\n'); + sb.append(commentChar).append(" This is the ").append(count) + .append(ordinal).append(" commit message:\n"); + sb.append(newMessage); } return sb.toString(); } + private String replaceCommentChar(String message, char oldChar, + char newChar) { + // (?m) - Switch on multi-line matching; \h - horizontal whitespace + return message.replaceAll("(?m)^(\\h*)" + oldChar, "$1" + newChar); //$NON-NLS-1$ //$NON-NLS-2$ + } + private static String getOrdinal(int count) { switch (count % 10) { case 1: @@ -858,10 +918,11 @@ public class RebaseCommand extends GitCommand<RebaseResult> { private void initializeSquashFixupFile(String messageFile, String fullMessage) throws IOException { - rebaseState - .createFile( - messageFile, - "# This is a combination of 1 commits.\n# The first commit's message is:\n" + fullMessage); //$NON-NLS-1$); + char commentChar = commitConfig.getCommentChar(fullMessage); + rebaseState.createFile(messageFile, + commentChar + " This is a combination of 1 commits.\n" //$NON-NLS-1$ + + commentChar + " The first commit's message is:\n" //$NON-NLS-1$ + + fullMessage); } private String getOurCommitName() { @@ -1625,26 +1686,106 @@ public class RebaseCommand extends GitCommand<RebaseResult> { } /** - * Allows configure rebase interactive process and modify commit message + * Allows to configure the interactive rebase process steps and to modify + * commit messages. */ public interface InteractiveHandler { + /** - * Given list of {@code steps} should be modified according to user - * rebase configuration + * Callback API to modify the initial list of interactive rebase steps. + * * @param steps - * initial configuration of rebase interactive + * initial configuration of interactive rebase */ void prepareSteps(List<RebaseTodoLine> steps); /** - * Used for editing commit message on REWORD + * Used for editing commit message on REWORD or SQUASH. * - * @param commit + * @param message + * existing commit message * @return new commit message */ - String modifyCommitMessage(String commit); + String modifyCommitMessage(String message); } + /** + * Extends {@link InteractiveHandler} with an enhanced callback for editing + * commit messages. + * + * @since 6.1 + */ + public interface InteractiveHandler2 extends InteractiveHandler { + + /** + * Callback API for editing a commit message on REWORD or SQUASH. + * <p> + * The callback gets the comment character currently set, and the + * clean-up mode. It can use this information when presenting the + * message to the user, and it also has the possibility to clean the + * message itself (in which case the returned {@link ModifyResult} + * should have {@link CleanupMode#VERBATIM} set lest JGit cleans the + * message again). It can also override the initial clean-up mode by + * returning clean-up mode other than {@link CleanupMode#DEFAULT}. If it + * does return {@code DEFAULT}, the passed-in {@code mode} will be + * applied. + * </p> + * + * @param message + * existing commit message + * @param mode + * {@link CleanupMode} currently set + * @param commentChar + * comment character used + * @return a {@link ModifyResult} + */ + @NonNull + ModifyResult editCommitMessage(@NonNull String message, + @NonNull CleanupMode mode, char commentChar); + + @Override + default String modifyCommitMessage(String message) { + // Should actually not be called; but do something reasonable anyway + ModifyResult result = editCommitMessage( + message == null ? "" : message, CleanupMode.STRIP, //$NON-NLS-1$ + '#'); + return result.getMessage(); + } + + /** + * Describes the result of editing a commit message: the new message, + * and how it should be cleaned. + */ + interface ModifyResult { + + /** + * Retrieves the new commit message. + * + * @return the message + */ + @NonNull + String getMessage(); + + /** + * Tells how the message returned by {@link #getMessage()} should be + * cleaned. + * + * @return the {@link CleanupMode} + */ + @NonNull + CleanupMode getCleanupMode(); + + /** + * Tells whether a Gerrit Change-Id should be computed and added to + * the commit message, as with + * {@link CommitCommand#setInsertChangeId(boolean)}. + * + * @return {@code true} if a Change-Id should be handled, + * {@code false} otherwise + */ + boolean shouldAddChangeId(); + } + } PersonIdent parseAuthor(byte[] raw) { if (raw.length == 0) 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 22ef4d0a32..513f579b67 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java @@ -9,6 +9,8 @@ */ package org.eclipse.jgit.api; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH; + import java.io.IOException; import java.text.MessageFormat; import java.util.LinkedList; @@ -28,6 +30,7 @@ import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.CommitConfig; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; @@ -128,8 +131,9 @@ public class RevertCommand extends GitCommand<RevCommit> { revWalk.parseHeaders(srcParent); String ourName = calculateOurName(headRef); - String revertName = srcCommit.getId().abbreviate(7).name() - + " " + srcCommit.getShortMessage(); //$NON-NLS-1$ + String revertName = srcCommit.getId() + .abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH).name() + " " //$NON-NLS-1$ + + srcCommit.getShortMessage(); ResolveMerger merger = (ResolveMerger) strategy.newMerger(repo); merger.setWorkingTreeIterator(new FileTreeIterator(repo)); @@ -182,9 +186,12 @@ public class RevertCommand extends GitCommand<RevCommit> { MergeStatus.CONFLICTING, strategy, merger.getMergeResults(), failingPaths, null); if (!merger.failed() && !unmergedPaths.isEmpty()) { + CommitConfig config = repo.getConfig() + .get(CommitConfig.KEY); + char commentChar = config.getCommentChar(newMessage); String message = new MergeMessageFormatter() - .formatWithConflicts(newMessage, - merger.getUnmergedPaths()); + .formatWithConflicts(newMessage, + merger.getUnmergedPaths(), commentChar); repo.writeRevertHead(srcCommit.getId()); repo.writeMergeCommitMsg(message); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java index 35fd8992b6..f7a1f4eff8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java @@ -9,6 +9,8 @@ */ package org.eclipse.jgit.api; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -302,7 +304,8 @@ public class StashCreateCommand extends GitCommand<RevCommit> { builder.setParentId(headCommit); builder.setTreeId(cache.writeTree(inserter)); builder.setMessage(MessageFormat.format(indexMessage, branch, - headCommit.abbreviate(7).name(), + headCommit.abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH) + .name(), headCommit.getShortMessage())); ObjectId indexCommit = inserter.insert(builder); @@ -319,7 +322,10 @@ public class StashCreateCommand extends GitCommand<RevCommit> { builder.setParentIds(new ObjectId[0]); builder.setTreeId(untrackedDirCache.writeTree(inserter)); builder.setMessage(MessageFormat.format(MSG_UNTRACKED, - branch, headCommit.abbreviate(7).name(), + branch, + headCommit + .abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH) + .name(), headCommit.getShortMessage())); untrackedCommit = inserter.insert(builder); } @@ -339,7 +345,8 @@ public class StashCreateCommand extends GitCommand<RevCommit> { builder.addParentId(untrackedCommit); builder.setMessage(MessageFormat.format( workingDirectoryMessage, branch, - headCommit.abbreviate(7).name(), + headCommit.abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH) + .name(), headCommit.getShortMessage())); builder.setTreeId(cache.writeTree(inserter)); commitId = inserter.insert(builder); |