diff options
author | Matthias Sohn <matthias.sohn@sap.com> | 2013-12-05 00:14:08 +0100 |
---|---|---|
committer | Matthias Sohn <matthias.sohn@sap.com> | 2013-12-05 00:14:25 +0100 |
commit | 162a5c4c89b289af3755a2f26843cdf908e93c50 (patch) | |
tree | 377792666836715cee1ec2f4d5582641d854dce3 /org.eclipse.jgit | |
parent | fceae20181b2b20632fe44731407bfb940093817 (diff) | |
parent | f86a488e32906593903acb31a93a82bed8d87915 (diff) | |
download | jgit-162a5c4c89b289af3755a2f26843cdf908e93c50.tar.gz jgit-162a5c4c89b289af3755a2f26843cdf908e93c50.zip |
Merge branch 'master' into stable-3.2
* master:
Implement rebase.autostash
CLI status should support --porcelain
More helpful InvalidPathException messages (include reason)
Fix IgnoreRule#isMatch returning wrong result due to missing reset
Fix exception on conflicts with recursive merge
Add pgm test for checkout of existing branch with checkout conflict
Fix broken symbolic links on Cygwin.
Do not allow non-ff-rebase if there are uncommitted changes
Manage CheckoutConflictException in pgm
Fix handling of file/folder conflicts during a checkout
Mention null return in Javadoc of Config#getString
Fix applying stash on other commit
Use static factory methods instead of overloaded constructors
Break up GCTest to run in parallel
Modify T0004_PackReaderTest to use existing pack
Move SampleDataRepositoryTestCase to org.eclipse.jgit.test
Support running from JARs in JGitTestUtil
Cache SimpleDateFormat in GitDateParser per locale
Fix FIXUP error for blank lines in interactive rebase
Fix parsing Rebase todo lines when commit message is missing
Add close() method to API
Update Jetty to 7.6.14.v20131031
Document that path parameters should use '/' as separator
Improve Javadoc for typeHint parameter
Do not update the ref hot bit when checking isIndexLoaded
Don't delete .idx file if .pack file can't be deleted
Change-Id: I02abfc09000d0fe9bdf4331c65bec7046f586179
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
Diffstat (limited to 'org.eclipse.jgit')
42 files changed, 573 insertions, 178 deletions
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index 474b8f349c..bb67c127a7 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -270,6 +270,10 @@ invalidObject=Invalid {0} {1}:{2} invalidOldIdSent=invalid old id sent invalidPacketLineHeader=Invalid packet line header: {0} invalidPath=Invalid path: {0} +invalidPathContainsSeparator=Invalid path (contains separator ''{0}''): {1} +invalidPathPeriodAtEndWindows=Invalid path (period at end is ignored by Windows): {0} +invalidPathSpaceAtEndWindows=Invalid path (space at end is ignored by Windows): {0} +invalidPathReservedOnWindows=Invalid path (''{0}'' is reserved on Windows): {1} invalidReflogRevision=Invalid reflog revision: {0} invalidRefName=Invalid ref name: {0} invalidRemote=Invalid remote: {0} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java index ef2e987b4e..c23256c74b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java @@ -93,11 +93,15 @@ public class AddCommand extends GitCommand<DirCache> { } /** + * Add a path to a file/directory whose content should be added. + * <p> + * A directory name (e.g. <code>dir</code> to add <code>dir/file1</code> and + * <code>dir/file2</code>) can also be given to add all files in the + * directory, recursively. Fileglobs (e.g. *.c) are not yet supported. + * * @param filepattern - * File to add content from. Also a leading directory name (e.g. - * dir to add dir/file1 and dir/file2) can be given to add all - * files in the directory, recursively. Fileglobs (e.g. *.c) are - * not yet supported. + * repository-relative path of file/directory to add (with + * <code>/</code> as separator) * @return {@code this} */ public AddCommand addFilepattern(String filepattern) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java index 12be64bb02..11dfd1c585 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java @@ -86,9 +86,10 @@ public class BlameCommand extends GitCommand<BlameResult> { } /** - * Set file path + * Set file path. * * @param filePath + * file path (with <code>/</code> as separator) * @return this command */ public BlameCommand setFilePath(String filePath) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java index eb447f3142..8dfd211a08 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java @@ -305,15 +305,16 @@ public class CheckoutCommand extends GitCommand<Ref> { } /** - * Add a single path to the list of paths to check out. To check out all - * paths, use {@link #setAllPaths(boolean)}. + * Add a single slash-separated path to the list of paths to check out. To + * check out all paths, use {@link #setAllPaths(boolean)}. * <p> * If this option is set, neither the {@link #setCreateBranch(boolean)} nor * {@link #setName(String)} option is considered. In other words, these * options are exclusive. * * @param path - * path to update in the working tree and index + * path to update in the working tree and index (with + * <code>/</code> as separator) * @return {@code this} */ public CheckoutCommand addPath(String path) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java index b42c80f67b..f273eafe1f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java @@ -174,7 +174,7 @@ public class CleanCommand extends GitCommand<Set<String>> { * If paths are set, only these paths are affected by the cleaning. * * @param paths - * the paths to set + * the paths to set (with <code>/</code> as separator) * @return {@code this} */ public CleanCommand setPaths(Set<String> paths) { 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 399d78e095..21d7138c9f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java @@ -667,14 +667,14 @@ public class CommitCommand extends GitCommand<RevCommit> { } /** - * Commit dedicated path only - * + * Commit dedicated path only. + * <p> * This method can be called several times to add multiple paths. Full file * paths are supported as well as directory paths; in the latter case this - * commits all files/ directories below the specified path. + * commits all files/directories below the specified path. * * @param only - * path to commit + * path to commit (with <code>/</code> as separator) * @return {@code this} */ public CommitCommand setOnly(String only) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java index 08ab88005d..983b6b552e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java @@ -125,6 +125,26 @@ public class Git { } /** + * Frees resources held by the underlying {@link Repository} instance. It is + * recommended to call this method as soon as you don't need a reference to + * this {@link Git} instance and the underlying {@link Repository} instance + * anymore. This method closes the underlying object and ref databases. This + * will free memory and file handles. E.g. on Windows the repository will + * keep file handles on pack files unless you call this method. Such open + * file handles may for example prevent that the repository folder in the + * filesystem can be deleted. + * <p> + * After calling close() you should not use this {@link Git} instance and + * the underlying {@link Repository} instance anymore. + * + * @since 3.2 + */ + public void close() { + if (repo != null) + repo.close(); + } + + /** * Returns a command object to execute a {@code clone} command * * @see <a diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java index 51a233458f..9690f799c1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java @@ -282,11 +282,11 @@ public class LogCommand extends GitCommand<Iterable<RevCommit>> { /** * Show only commits that affect any of the specified paths. The path must - * either name a file or a directory exactly. Note that regex expressions or - * wildcards are not supported. + * either name a file or a directory exactly and use <code>/</code> (slash) + * as separator. Note that regex expressions or wildcards are not supported. * * @param path - * a path is relative to the top level of the repository + * a repository-relative path (with <code>/</code> as separator) * @return {@code this} */ public LogCommand addPath(String path) { 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 509203e528..78b260df71 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java @@ -379,8 +379,7 @@ public class MergeCommand extends GitCommand<MergeResult> { if (failingPaths != null) { repo.writeMergeCommitMsg(null); repo.writeMergeHeads(null); - return new MergeResult(null, - merger.getBaseCommit(0, 1), + return new MergeResult(null, merger.getBaseCommitId(), new ObjectId[] { headCommit.getId(), srcCommit.getId() }, MergeStatus.FAILED, mergeStrategy, @@ -390,8 +389,7 @@ public class MergeCommand extends GitCommand<MergeResult> { .formatWithConflicts(mergeMessage, unmergedPaths); repo.writeMergeCommitMsg(mergeMessageWithConflicts); - return new MergeResult(null, - merger.getBaseCommit(0, 1), + return new MergeResult(null, merger.getBaseCommitId(), new ObjectId[] { headCommit.getId(), srcCommit.getId() }, MergeStatus.CONFLICTING, mergeStrategy, 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 8ae51d74ce..10b273a744 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java @@ -70,6 +70,7 @@ import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.NoMessageException; import org.eclipse.jgit.api.errors.RefAlreadyExistsException; import org.eclipse.jgit.api.errors.RefNotFoundException; +import org.eclipse.jgit.api.errors.StashApplyFailureException; import org.eclipse.jgit.api.errors.UnmergedPathsException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.diff.DiffFormatter; @@ -79,6 +80,7 @@ import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; @@ -158,6 +160,10 @@ public class RebaseCommand extends GitCommand<RebaseResult> { private static final String MESSAGE_SQUASH = "message-squash"; //$NON-NLS-1$ + private static final String AUTOSTASH = "autostash"; //$NON-NLS-1$ + + private static final String AUTOSTASH_MSG = "On {0}: autostash"; + /** * The available operations */ @@ -257,11 +263,26 @@ public class RebaseCommand extends GitCommand<RebaseResult> { .resolve(upstreamCommitId)); break; case BEGIN: + autoStash(); + if (stopAfterInitialization + || !walk.isMergedInto( + walk.parseCommit(repo.resolve(Constants.HEAD)), + upstreamCommit)) { + org.eclipse.jgit.api.Status status = Git.wrap(repo) + .status().call(); + if (status.hasUncommittedChanges()) { + List<String> list = new ArrayList<String>(); + list.addAll(status.getUncommittedChanges()); + return RebaseResult.uncommittedChanges(list); + } + } RebaseResult res = initFilesAndRewind(); if (stopAfterInitialization) return RebaseResult.INTERACTIVE_PREPARED_RESULT; - if (res != null) + if (res != null) { + autoStashApply(); return res; + } } if (monitor.isCancelled()) @@ -321,12 +342,63 @@ public class RebaseCommand extends GitCommand<RebaseResult> { } return finishRebase(newHead, lastStepWasForward); } catch (CheckoutConflictException cce) { - return new RebaseResult(cce.getConflictingPaths()); + return RebaseResult.conflicts(cce.getConflictingPaths()); } catch (IOException ioe) { throw new JGitInternalException(ioe.getMessage(), ioe); } } + private void autoStash() throws GitAPIException, IOException { + if (repo.getConfig().getBoolean(ConfigConstants.CONFIG_REBASE_SECTION, + ConfigConstants.CONFIG_KEY_AUTOSTASH, false)) { + String message = MessageFormat.format( + AUTOSTASH_MSG, + Repository + .shortenRefName(getHeadName(getHead()))); + RevCommit stashCommit = Git.wrap(repo).stashCreate().setRef(null) + .setWorkingDirectoryMessage( + message) + .call(); + if (stashCommit != null) { + FileUtils.mkdir(rebaseState.getDir()); + rebaseState.createFile(AUTOSTASH, stashCommit.getName()); + } + } + } + + private boolean autoStashApply() throws IOException, GitAPIException { + boolean conflicts = false; + if (rebaseState.getFile(AUTOSTASH).exists()) { + String stash = rebaseState.readFile(AUTOSTASH); + try { + Git.wrap(repo).stashApply().setStashRef(stash) + .ignoreRepositoryState(true).call(); + } catch (StashApplyFailureException e) { + conflicts = true; + RevWalk rw = new RevWalk(repo); + ObjectId stashId = repo.resolve(stash); + RevCommit commit = rw.parseCommit(stashId); + updateStashRef(commit, commit.getAuthorIdent(), + commit.getShortMessage()); + } + } + return conflicts; + } + + private void updateStashRef(ObjectId commitId, PersonIdent refLogIdent, + String refLogMessage) throws IOException { + Ref currentRef = repo.getRef(Constants.R_STASH); + RefUpdate refUpdate = repo.updateRef(Constants.R_STASH); + refUpdate.setNewObjectId(commitId); + refUpdate.setRefLogIdent(refLogIdent); + refUpdate.setRefLogMessage(refLogMessage, false); + if (currentRef != null) + refUpdate.setExpectedOldObjectId(currentRef.getObjectId()); + else + refUpdate.setExpectedOldObjectId(ObjectId.zeroId()); + refUpdate.forceUpdate(); + } + private RebaseResult processStep(RebaseTodoLine step, boolean shouldPick) throws IOException, GitAPIException { if (Action.COMMENT.equals(step.getAction())) @@ -340,7 +412,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { RevCommit commitToPick = walk.parseCommit(ids.iterator().next()); if (shouldPick) { if (monitor.isCancelled()) - return new RebaseResult(commitToPick, Status.STOPPED); + return RebaseResult.result(Status.STOPPED, commitToPick); RebaseResult result = cherryPickCommit(commitToPick); if (result != null) return result; @@ -403,8 +475,8 @@ public class RebaseCommand extends GitCommand<RebaseResult> { switch (cherryPickResult.getStatus()) { case FAILED: if (operation == Operation.BEGIN) - return abort(new RebaseResult( - cherryPickResult.getFailingPaths())); + return abort(RebaseResult.failed(cherryPickResult + .getFailingPaths())); else return stop(commitToPick, Status.STOPPED); case CONFLICTING: @@ -420,10 +492,13 @@ public class RebaseCommand extends GitCommand<RebaseResult> { } private RebaseResult finishRebase(RevCommit newHead, - boolean lastStepWasForward) throws IOException { + boolean lastStepWasForward) throws IOException, GitAPIException { String headName = rebaseState.readFile(HEAD_NAME); updateHead(headName, newHead, upstreamCommit); + boolean stashConflicts = autoStashApply(); FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE); + if (stashConflicts) + return RebaseResult.STASH_APPLY_CONFLICTS_RESULT; if (lastStepWasForward || newHead == null) return RebaseResult.FAST_FORWARD_RESULT; return RebaseResult.OK_RESULT; @@ -550,7 +625,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { } else { sb.append("# The ").append(count).append(ordinal) .append(" commit message will be skipped:\n# "); - sb.append(commitToPick.getFullMessage().replaceAll("([\n\r]+)", + sb.append(commitToPick.getFullMessage().replaceAll("([\n\r])", "$1# ")); } // Add the previous message without header (i.e first line) @@ -735,7 +810,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { // Remove cherry pick state file created by CherryPickCommand, it's not // needed for rebase repo.writeCherryPickHead(null); - return new RebaseResult(commitToPick, status); + return RebaseResult.result(status, commitToPick); } String toAuthorScript(PersonIdent author) { @@ -797,16 +872,9 @@ public class RebaseCommand extends GitCommand<RebaseResult> { // we need to store everything into files so that we can implement // --skip, --continue, and --abort - Ref head = repo.getRef(Constants.HEAD); - if (head == null || head.getObjectId() == null) - throw new RefNotFoundException(MessageFormat.format( - JGitText.get().refNotResolved, Constants.HEAD)); + Ref head = getHead(); - String headName; - if (head.isSymbolic()) - headName = head.getTarget().getName(); - else - headName = head.getObjectId().getName(); + String headName = getHeadName(head); ObjectId headId = head.getObjectId(); if (headId == null) throw new RefNotFoundException(MessageFormat.format( @@ -845,7 +913,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { Collections.reverse(cherryPickList); // create the folder for the meta information - FileUtils.mkdir(rebaseState.getDir()); + FileUtils.mkdir(rebaseState.getDir(), true); repo.writeOrigHead(headId); rebaseState.createFile(REBASE_HEAD, headId.name()); @@ -881,6 +949,23 @@ public class RebaseCommand extends GitCommand<RebaseResult> { return null; } + private static String getHeadName(Ref head) { + String headName; + if (head.isSymbolic()) + headName = head.getTarget().getName(); + else + headName = head.getObjectId().getName(); + return headName; + } + + private Ref getHead() throws IOException, RefNotFoundException { + Ref head = repo.getRef(Constants.HEAD); + if (head == null || head.getObjectId() == null) + throw new RefNotFoundException(MessageFormat.format( + JGitText.get().refNotResolved, Constants.HEAD)); + return head; + } + private boolean isInteractive() { return interactiveHandler != null; } @@ -895,10 +980,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { */ public RevCommit tryFastForward(RevCommit newCommit) throws IOException, GitAPIException { - Ref head = repo.getRef(Constants.HEAD); - if (head == null || head.getObjectId() == null) - throw new RefNotFoundException(MessageFormat.format( - JGitText.get().refNotResolved, Constants.HEAD)); + Ref head = getHead(); ObjectId headId = head.getObjectId(); if (headId == null) @@ -908,11 +990,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { if (walk.isMergedInto(newCommit, headCommit)) return newCommit; - String headName; - if (head.isSymbolic()) - headName = head.getTarget().getName(); - else - headName = head.getObjectId().getName(); + String headName = getHeadName(head); return tryFastForward(headName, headCommit, newCommit); } @@ -992,7 +1070,8 @@ public class RebaseCommand extends GitCommand<RebaseResult> { } } - private RebaseResult abort(RebaseResult result) throws IOException { + private RebaseResult abort(RebaseResult result) throws IOException, + GitAPIException { try { ObjectId origHead = repo.readOrigHead(); String commitId = origHead != null ? origHead.name() : null; @@ -1041,9 +1120,12 @@ public class RebaseCommand extends GitCommand<RebaseResult> { JGitText.get().abortingRebaseFailed); } } + boolean stashConflicts = autoStashApply(); // cleanup the files FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE); repo.writeCherryPickHead(null); + if (stashConflicts) + return RebaseResult.STASH_APPLY_CONFLICTS_RESULT; return result; } finally { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java index aaa75d9b88..92c1347ab2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java @@ -105,6 +105,18 @@ public class RebaseResult { } }, /** + * The repository contains uncommitted changes and the rebase is not a + * fast-forward + * + * @since 3.2 + */ + UNCOMMITTED_CHANGES { + @Override + public boolean isSuccessful() { + return false; + } + }, + /** * Conflicts: checkout of target HEAD failed */ CONFLICTS { @@ -153,6 +165,18 @@ public class RebaseResult { public boolean isSuccessful() { return false; } + }, + + /** + * Applying stash resulted in conflicts + * + * @since 3.2 + */ + STASH_APPLY_CONFLICTS { + @Override + public boolean isSuccessful() { + return true; + } }; /** @@ -177,6 +201,9 @@ public class RebaseResult { static final RebaseResult INTERACTIVE_PREPARED_RESULT = new RebaseResult( Status.INTERACTIVE_PREPARED); + static final RebaseResult STASH_APPLY_CONFLICTS_RESULT = new RebaseResult( + Status.STASH_APPLY_CONFLICTS); + private final Status status; private final RevCommit currentCommit; @@ -185,21 +212,29 @@ public class RebaseResult { private List<String> conflicts; + private List<String> uncommittedChanges; + private RebaseResult(Status status) { this.status = status; currentCommit = null; } + private RebaseResult(Status status, RevCommit commit) { + this.status = status; + currentCommit = commit; + } + /** - * Create <code>RebaseResult</code> with status {@link Status#STOPPED} + * Create <code>RebaseResult</code> * + * @param status * @param commit * current commit - * @param status + * @return the RebaseResult */ - RebaseResult(RevCommit commit, RebaseResult.Status status) { - this.status = status; - currentCommit = commit; + static RebaseResult result(RebaseResult.Status status, + RevCommit commit) { + return new RebaseResult(status, commit); } /** @@ -207,11 +242,13 @@ public class RebaseResult { * * @param failingPaths * list of paths causing this rebase to fail + * @return the RebaseResult */ - RebaseResult(Map<String, MergeFailureReason> failingPaths) { - status = Status.FAILED; - currentCommit = null; - this.failingPaths = failingPaths; + static RebaseResult failed( + Map<String, MergeFailureReason> failingPaths) { + RebaseResult result = new RebaseResult(Status.FAILED); + result.failingPaths = failingPaths; + return result; } /** @@ -219,11 +256,26 @@ public class RebaseResult { * * @param conflicts * the list of conflicting paths + * @return the RebaseResult */ - RebaseResult(List<String> conflicts) { - status = Status.CONFLICTS; - currentCommit = null; - this.conflicts = conflicts; + static RebaseResult conflicts(List<String> conflicts) { + RebaseResult result = new RebaseResult(Status.CONFLICTS); + result.conflicts = conflicts; + return result; + } + + /** + * Create <code>RebaseResult</code> with status + * {@link Status#UNCOMMITTED_CHANGES} + * + * @param uncommittedChanges + * the list of paths + * @return the RebaseResult + */ + static RebaseResult uncommittedChanges(List<String> uncommittedChanges) { + RebaseResult result = new RebaseResult(Status.UNCOMMITTED_CHANGES); + result.uncommittedChanges = uncommittedChanges; + return result; } /** @@ -256,4 +308,15 @@ public class RebaseResult { public List<String> getConflicts() { return conflicts; } + + /** + * @return the list of uncommitted changes if status is + * {@link Status#UNCOMMITTED_CHANGES} + * + * @since 3.2 + */ + public List<String> getUncommittedChanges() { + return uncommittedChanges; + } + } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java index 3a1c209a2d..7c2192dd9f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java @@ -279,16 +279,17 @@ public class ResetCommand extends GitCommand<Ref> { } /** - * @param file - * the file to add + * @param path + * repository-relative path of file/directory to reset (with + * <code>/</code> as separator) * @return this instance */ - public ResetCommand addPath(String file) { + public ResetCommand addPath(String path) { if (mode != null) throw new JGitInternalException(MessageFormat.format( JGitText.get().illegalCombinationOfArguments, "<paths>...", "[--mixed | --soft | --hard]")); //$NON-NLS-1$ - filepaths.add(file); + filepaths.add(path); return this; } 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 2bc9d22449..bc1b29c2f6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java @@ -191,14 +191,14 @@ public class RevertCommand extends GitCommand<RevCommit> { .getFailingPaths(); if (failingPaths != null) failingResult = new MergeResult(null, - merger.getBaseCommit(0, 1), + merger.getBaseCommitId(), new ObjectId[] { headCommit.getId(), srcParent.getId() }, MergeStatus.FAILED, MergeStrategy.RECURSIVE, merger.getMergeResults(), failingPaths, null); else failingResult = new MergeResult(null, - merger.getBaseCommit(0, 1), + merger.getBaseCommitId(), new ObjectId[] { headCommit.getId(), srcParent.getId() }, MergeStatus.CONFLICTING, diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java index ac30ffcb7d..c70b4aeaf2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java @@ -104,7 +104,8 @@ public class RmCommand extends GitCommand<DirCache> { /** * @param filepattern - * File to remove. + * repository-relative path of file to remove (with + * <code>/</code> as separator) * @return {@code this} */ public RmCommand addFilepattern(String filepattern) { 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 020a6dc5f2..8440d8b950 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java @@ -90,6 +90,8 @@ public class StashApplyCommand extends GitCommand<ObjectId> { private boolean applyIndex = true; + private boolean ignoreRepositoryState; + /** * Create command to apply the changes of a stashed commit * @@ -113,6 +115,16 @@ public class StashApplyCommand extends GitCommand<ObjectId> { return this; } + /** + * @param ignoreRepositoryState + * @return {@code this} + * @since 3.2 + */ + public StashApplyCommand ignoreRepositoryState(boolean ignoreRepositoryState) { + this.ignoreRepositoryState = ignoreRepositoryState; + return this; + } + private ObjectId getStashId() throws GitAPIException { final String revision = stashRef != null ? stashRef : DEFAULT_REF; final ObjectId stashId; @@ -143,7 +155,8 @@ public class StashApplyCommand extends GitCommand<ObjectId> { StashApplyFailureException { checkCallable(); - if (repo.getRepositoryState() != RepositoryState.SAFE) + if (!ignoreRepositoryState + && repo.getRepositoryState() != RepositoryState.SAFE) throw new WrongRepositoryStateException(MessageFormat.format( JGitText.get().stashApplyOnUnsafeRepository, repo.getRepositoryState())); @@ -185,6 +198,7 @@ public class StashApplyCommand extends GitCommand<ObjectId> { .newMerger(repo, true); ixMerger.setCommitNames(new String[] { "stashed HEAD", "HEAD", "stashed index" }); + ixMerger.setBase(stashHeadCommit); boolean ok = ixMerger.merge(headCommit, stashIndexCommit); if (ok) { resetIndex(revWalk 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 fc21b919b6..cf0b6d1d9c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java @@ -154,6 +154,7 @@ public class StashCreateCommand extends GitCommand<RevCommit> { /** * Set the reference to update with the stashed commit id + * If null, no reference is updated * <p> * This value defaults to {@link Constants#R_STASH} * @@ -185,6 +186,8 @@ public class StashCreateCommand extends GitCommand<RevCommit> { private void updateStashRef(ObjectId commitId, PersonIdent refLogIdent, String refLogMessage) throws IOException { + if (ref == null) + return; Ref currentRef = repo.getRef(ref); RefUpdate refUpdate = repo.updateRef(ref); refUpdate.setNewObjectId(commitId); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/Status.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/Status.java index e840c2f608..c3fcd8bfe8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/Status.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/Status.java @@ -43,6 +43,7 @@ package org.eclipse.jgit.api; import java.util.Collections; +import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -66,19 +67,22 @@ public class Status { private final boolean clean; + private final boolean hasUncommittedChanges;; + /** * @param diff */ public Status(IndexDiff diff) { super(); this.diff = diff; - clean = diff.getAdded().isEmpty() // - && diff.getChanged().isEmpty() // - && diff.getRemoved().isEmpty() // - && diff.getMissing().isEmpty() // - && diff.getModified().isEmpty() // - && diff.getUntracked().isEmpty() // - && diff.getConflicting().isEmpty(); + hasUncommittedChanges = !diff.getAdded().isEmpty() // + || !diff.getChanged().isEmpty() // + || !diff.getRemoved().isEmpty() // + || !diff.getMissing().isEmpty() // + || !diff.getModified().isEmpty() // + || !diff.getConflicting().isEmpty(); + clean = !hasUncommittedChanges // + && diff.getUntracked().isEmpty(); } /** @@ -90,6 +94,15 @@ public class Status { } /** + * @return true if any tracked file is changed + * + * @since 3.2 + */ + public boolean hasUncommittedChanges() { + return hasUncommittedChanges; + } + + /** * @return list of files added to the index, not in HEAD (e.g. what you get * if you call 'git add ...' on a newly created file) */ @@ -168,4 +181,21 @@ public class Status { public Set<String> getIgnoredNotInIndex() { return Collections.unmodifiableSet(diff.getIgnoredNotInIndex()); } + + /** + * @return set of files and folders that are known to the repo and changed + * either in the index or in the working tree. + * + * @since 3.2 + */ + public Set<String> getUncommittedChanges() { + Set<String> uncommittedChanges = new HashSet<String>(); + uncommittedChanges.addAll(diff.getAdded()); + uncommittedChanges.addAll(diff.getChanged()); + uncommittedChanges.addAll(diff.getRemoved()); + uncommittedChanges.addAll(diff.getMissing()); + uncommittedChanges.addAll(diff.getModified()); + uncommittedChanges.addAll(diff.getConflicting()); + return uncommittedChanges; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java index eaf5483671..dee0a31b91 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java @@ -89,7 +89,8 @@ public class StatusCommand extends GitCommand<Status> { * supported. * * @param path - * a path is relative to the top level of the repository + * repository-relative path of file/directory to show status for + * (with <code>/</code> as separator) * @return {@code this} * @since 3.1 */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java index bfef053d85..09e4cf0a1b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java @@ -93,6 +93,7 @@ public class SubmoduleAddCommand extends * Set repository-relative path of submodule * * @param path + * (with <code>/</code> as separator) * @return this command */ public SubmoduleAddCommand setPath(final String path) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java index e799bfb8bd..0ed02acc82 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java @@ -83,6 +83,7 @@ public class SubmoduleInitCommand extends GitCommand<Collection<String>> { * Add repository-relative submodule path to initialize * * @param path + * (with <code>/</code> as separator) * @return this command */ public SubmoduleInitCommand addPath(final String path) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java index bbc01adbae..6e89f9873e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java @@ -83,6 +83,7 @@ public class SubmoduleStatusCommand extends * Add repository-relative submodule path to limit status reporting to * * @param path + * (with <code>/</code> as separator) * @return this command */ public SubmoduleStatusCommand addPath(final String path) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java index 11d3c5acca..e7fe71a890 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java @@ -85,6 +85,7 @@ public class SubmoduleSyncCommand extends GitCommand<Map<String, String>> { * Add repository-relative submodule path to synchronize * * @param path + * (with <code>/</code> as separator) * @return this command */ public SubmoduleSyncCommand addPath(final String path) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java index 40f6a9f9a4..e2f356a08e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java @@ -110,6 +110,7 @@ public class SubmoduleUpdateCommand extends * Add repository-relative submodule path to initialize * * @param path + * (with <code>/</code> as separator) * @return this command */ public SubmoduleUpdateCommand addPath(final String path) { 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 bd896864d7..f8c8442ff8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java @@ -76,6 +76,7 @@ import org.eclipse.jgit.treewalk.WorkingTreeOptions; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; +import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.SystemReader; import org.eclipse.jgit.util.io.AutoCRLFOutputStream; @@ -306,8 +307,7 @@ public class DirCacheCheckout { void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i, WorkingTreeIterator f) throws IOException { if (m != null) { - if (!isValidPath(m)) - throw new InvalidPathException(m.getEntryPathString()); + checkValidPath(m); // There is an entry in the merge commit. Means: we want to update // what's currently in the index and working-tree to that one if (i == null) { @@ -522,8 +522,8 @@ public class DirCacheCheckout { String name = walk.getPathString(); - if (m != null && !isValidPath(m)) - throw new InvalidPathException(m.getEntryPathString()); + if (m != null) + checkValidPath(m); if (i == null && m == null && h == null) { // File/Directory conflict case #20 @@ -555,7 +555,9 @@ public class DirCacheCheckout { * 3 D F D Y N N Keep * 4 D F D N N N Conflict * 5 D F F Y N N Y Keep + * 5b D F F Y N N N Conflict * 6 D F F N N N Y Keep + * 6b D F F N N N N Conflict * 7 F D F Y Y N N Update * 8 F D F N Y N N Conflict * 9 F D F Y N N N Update @@ -620,7 +622,11 @@ public class DirCacheCheckout { case 0xF0D: // 18 remove(name); break; - case 0xDFF: // 5 6 + case 0xDFF: // 5 5b 6 6b + if (equalIdAndMode(iId, iMode, mId, mMode)) + keep(dce); // 5 6 + else + conflict(name, dce, h, m); // 5b 6b case 0xFDD: // 10 11 // TODO: make use of tree extension as soon as available in jgit // we would like to do something like @@ -1151,14 +1157,14 @@ public class DirCacheCheckout { forbidden[i] = Constants.encodeASCII(list[i]); } - private static boolean isValidPath(CanonicalTreeParser t) { + private static void checkValidPath(CanonicalTreeParser t) + throws InvalidPathException { for (CanonicalTreeParser i = t; i != null; i = i.getParent()) - if (!isValidPathSegment(i)) - return false; - return true; + checkValidPathSegment(i); } - private static boolean isValidPathSegment(CanonicalTreeParser t) { + private static void checkValidPathSegment(CanonicalTreeParser t) + throws InvalidPathException { boolean isWindows = SystemReader.getInstance().isWindows(); boolean isOSX = SystemReader.getInstance().isMacOS(); boolean ignCase = isOSX || isWindows; @@ -1171,23 +1177,29 @@ public class DirCacheCheckout { int start = ptr; while (ptr < end) { if (raw[ptr] == '/') - return false; + throw new InvalidPathException( + JGitText.get().invalidPathContainsSeparator, + "/", t.getEntryPathString()); //$NON-NLS-1$ if (isWindows) { if (raw[ptr] == '\\') - return false; + throw new InvalidPathException( + JGitText.get().invalidPathContainsSeparator, + "\\", t.getEntryPathString()); //$NON-NLS-1$ if (raw[ptr] == ':') - return false; + throw new InvalidPathException( + JGitText.get().invalidPathContainsSeparator, + ":", t.getEntryPathString()); //$NON-NLS-1$ } ptr++; } - // '.' and '.'' are invalid here + // '.' and '..' are invalid here if (ptr - start == 1) { if (raw[start] == '.') - return false; + throw new InvalidPathException(t.getEntryPathString()); } else if (ptr - start == 2) { if (raw[start] == '.') if (raw[start + 1] == '.') - return false; + throw new InvalidPathException(t.getEntryPathString()); } else if (ptr - start == 4) { // .git (possibly case insensitive) is disallowed if (raw[start] == '.') @@ -1196,15 +1208,24 @@ public class DirCacheCheckout { || (ignCase && raw[start + 2] == 'I')) if (raw[start + 3] == 't' || (ignCase && raw[start + 3] == 'T')) - return false; + throw new InvalidPathException( + t.getEntryPathString()); } if (isWindows) { // Space or period at end of file name is ignored by Windows. // Treat this as a bad path for now. We may want to handle // this as case insensitivity in the future. - if (ptr > 0) - if (raw[ptr - 1] == '.' || raw[ptr - 1] == ' ') - return false; + if (ptr > 0) { + if (raw[ptr - 1] == '.') + throw new InvalidPathException( + JGitText.get().invalidPathPeriodAtEndWindows, + t.getEntryPathString()); + if (raw[ptr - 1] == ' ') + throw new InvalidPathException( + JGitText.get().invalidPathSpaceAtEndWindows, + t.getEntryPathString()); + } + int i; // Bad names, eliminate suffix first for (i = start; i < ptr; ++i) @@ -1222,13 +1243,14 @@ public class DirCacheCheckout { break; } if (k == len) - return false; + throw new InvalidPathException( + JGitText.get().invalidPathReservedOnWindows, + RawParseUtils.decode(forbidden[j]), t + .getEntryPathString()); } } } } - - return true; } private static byte toUpper(byte b) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/InvalidPathException.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/InvalidPathException.java index 698636c8cf..50d1c4ca38 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/InvalidPathException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/InvalidPathException.java @@ -60,6 +60,10 @@ public class InvalidPathException extends IllegalArgumentException { * @param path */ public InvalidPathException(String path) { - super(MessageFormat.format(JGitText.get().invalidPath, path)); + this(JGitText.get().invalidPath, path); + } + + InvalidPathException(String messagePattern, Object... arguments) { + super(MessageFormat.format(messagePattern, arguments)); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java index e0c780feec..980f2094bd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java @@ -198,6 +198,7 @@ public class IgnoreRule { } } else { + matcher.reset(); matcher.append(target); if (matcher.isMatch()) return true; 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 b2c27a483c..f9700a1ff4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -332,6 +332,10 @@ public class JGitText extends TranslationBundle { /***/ public String invalidOldIdSent; /***/ public String invalidPacketLineHeader; /***/ public String invalidPath; + /***/ public String invalidPathContainsSeparator; + /***/ public String invalidPathPeriodAtEndWindows; + /***/ public String invalidPathSpaceAtEndWindows; + /***/ public String invalidPathReservedOnWindows; /***/ public String invalidReflogRevision; /***/ public String invalidRefName; /***/ public String invalidRemote; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java index a8d797dff2..748a4a38e0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java @@ -556,5 +556,9 @@ public final class DfsBlockCache { hot = true; return v; } + + boolean has() { + return value != null; + } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java index 1c588d2c4e..7c4776ea06 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java @@ -191,7 +191,7 @@ public final class DfsPackFile { */ public boolean isIndexLoaded() { DfsBlockCache.Ref<PackIndex> idxref = index; - return idxref != null && idxref.get() != null; + return idxref != null && idxref.has(); } /** @return bytes cached in memory for this pack, excluding the index. */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java index 3e26bc3e62..e06ff65ee4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java @@ -97,6 +97,7 @@ import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.GitDateParser; +import org.eclipse.jgit.util.SystemReader; /** * A garbage collector for git {@link FileRepository}. Instances of this class @@ -175,21 +176,9 @@ public class GC { * * @param oldPacks * @param newPacks - * @param ignoreErrors - * <code>true</code> if we should ignore the fact that a certain - * pack files or index files couldn't be deleted. - * <code>false</code> if an exception should be thrown in such - * cases - * @throws IOException - * if a pack file couldn't be deleted and - * <code>ignoreErrors</code> is set to <code>false</code> */ private void deleteOldPacks(Collection<PackFile> oldPacks, - Collection<PackFile> newPacks, boolean ignoreErrors) - throws IOException { - int deleteOptions = FileUtils.RETRY | FileUtils.SKIP_MISSING; - if (ignoreErrors) - deleteOptions |= FileUtils.IGNORE_ERRORS; + Collection<PackFile> newPacks) { oldPackLoop: for (PackFile oldPack : oldPacks) { String oldName = oldPack.getPackName(); // check whether an old pack file is also among the list of new @@ -200,10 +189,7 @@ public class GC { if (!oldPack.shouldBeKept()) { oldPack.close(); - for (PackExt ext : PackExt.values()) { - File f = nameFor(oldName, "." + ext.getExtension()); //$NON-NLS-1$ - FileUtils.delete(f, deleteOptions); - } + prunePack(oldName); } } // close the complete object database. Thats my only chance to force @@ -212,6 +198,42 @@ public class GC { } /** + * Delete files associated with a single pack file. First try to delete the + * ".pack" file because on some platforms the ".pack" file may be locked and + * can't be deleted. In such a case it is better to detect this early and + * give up on deleting files for this packfile. Otherwise we may delete the + * ".index" file and when failing to delete the ".pack" file we are left + * with a ".pack" file without a ".index" file. + * + * @param packName + */ + private void prunePack(String packName) { + PackExt[] extensions = PackExt.values(); + try { + // Delete the .pack file first and if this fails give up on deleting + // the other files + int deleteOptions = FileUtils.RETRY | FileUtils.SKIP_MISSING; + for (PackExt ext : extensions) + if (PackExt.PACK.equals(ext)) { + File f = nameFor(packName, "." + ext.getExtension()); //$NON-NLS-1$ + FileUtils.delete(f, deleteOptions); + break; + } + // The .pack file has been deleted. Delete as many as the other + // files as you can. + deleteOptions |= FileUtils.IGNORE_ERRORS; + for (PackExt ext : extensions) { + if (!PackExt.PACK.equals(ext)) { + File f = nameFor(packName, "." + ext.getExtension()); //$NON-NLS-1$ + FileUtils.delete(f, deleteOptions); + } + } + } catch (IOException e) { + // Deletion of the .pack file failed. Silently return. + } + } + + /** * Like "git prune-packed" this method tries to prune all loose objects * which can be found in packs. If certain objects can't be pruned (e.g. * because the filesystem delete operation fails) this is silently ignored. @@ -286,7 +308,8 @@ public class GC { ConfigConstants.CONFIG_KEY_PRUNEEXPIRE); if (pruneExpireStr == null) pruneExpireStr = PRUNE_EXPIRE_DEFAULT; - expire = GitDateParser.parse(pruneExpireStr, null); + expire = GitDateParser.parse(pruneExpireStr, null, SystemReader + .getInstance().getLocale()); expireAgeMillis = -1; } if (expire != null) @@ -533,7 +556,7 @@ public class GC { if (rest != null) ret.add(rest); } - deleteOldPacks(toBeDeleted, ret, true); + deleteOldPacks(toBeDeleted, ret); prunePacked(); lastPackedRefs = refsBefore; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java index 28d90590a3..db622f319d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java @@ -100,7 +100,7 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re && ref[7] == ' '; } - private static File getSymRef(File workTree, File dotGit) + private static File getSymRef(File workTree, File dotGit, FS fs) throws IOException { byte[] content = IO.readFully(dotGit); if (!isSymRef(content)) @@ -116,7 +116,7 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re JGitText.get().invalidGitdirRef, dotGit.getAbsolutePath())); String gitdirPath = RawParseUtils.decode(content, pathStart, lineEnd); - File gitdirFile = new File(gitdirPath); + File gitdirFile = fs.resolve(workTree, gitdirPath); if (gitdirFile.isAbsolute()) return gitdirFile; else @@ -516,7 +516,7 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re break; } else if (dir.isFile()) try { - setGitDir(getSymRef(current, dir)); + setGitDir(getSymRef(current, dir, tryFS)); break; } catch (IOException ignored) { // Continue searching if gitdir ref isn't found @@ -597,7 +597,7 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re if (!dotGit.isFile()) setGitDir(dotGit); else - setGitDir(getSymRef(getWorkTree(), dotGit)); + setGitDir(getSymRef(getWorkTree(), dotGit, safeFS())); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java index 8240ac8f76..81977d74ac 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java @@ -435,7 +435,7 @@ public class Config { } /** - * Get string value + * Get string value or null if not found. * * @param section * the section @@ -443,7 +443,7 @@ public class Config { * the subsection for the value * @param name * the key name - * @return a String value from git config. + * @return a String value from the config, <code>null</code> if not found */ public String getString(final String section, String subsection, final String name) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java index 3ff4eefb1c..fd22764b6a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java @@ -77,6 +77,13 @@ public class ConfigConstants { /** The "submodule" section */ public static final String CONFIG_SUBMODULE_SECTION = "submodule"; + /** + * The "rebase" section + * + * @since 3.2 + */ + public static final String CONFIG_REBASE_SECTION = "rebase"; + /** The "gc" section */ public static final String CONFIG_GC_SECTION = "gc"; @@ -136,6 +143,14 @@ public class ConfigConstants { /** The "autosetuprebase" key */ public static final String CONFIG_KEY_AUTOSETUPREBASE = "autosetuprebase"; + + /** + * The "autostash" key + * + * @since 3.2 + */ + public static final String CONFIG_KEY_AUTOSTASH = "autostash"; + /** The "name" key */ public static final String CONFIG_KEY_NAME = "name"; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java index 15d118c0e9..0cc51d1a52 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java @@ -156,9 +156,10 @@ public abstract class ObjectDatabase { * @param objectId * identity of the object to open. * @param typeHint - * hint about the type of object being requested; - * {@link ObjectReader#OBJ_ANY} if the object type is not known, - * or does not matter to the caller. + * hint about the type of object being requested, e.g. + * {@link Constants#OBJ_BLOB}; {@link ObjectReader#OBJ_ANY} if + * the object type is not known, or does not matter to the + * caller. * @return a {@link ObjectLoader} for accessing the object. * @throws MissingObjectException * the object does not exist. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java index 3bd02500cc..58c141870b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java @@ -194,9 +194,9 @@ public abstract class ObjectReader { * @param objectId * identity of the object to test for existence of. * @param typeHint - * hint about the type of object being requested; - * {@link #OBJ_ANY} if the object type is not known, or does not - * matter to the caller. + * hint about the type of object being requested, e.g. + * {@link Constants#OBJ_BLOB}; {@link #OBJ_ANY} if the object + * type is not known, or does not matter to the caller. * @return true if the specified object is stored in this database. * @throws IncorrectObjectTypeException * typeHint was not OBJ_ANY, and the object's actual type does @@ -235,9 +235,9 @@ public abstract class ObjectReader { * @param objectId * identity of the object to open. * @param typeHint - * hint about the type of object being requested; - * {@link #OBJ_ANY} if the object type is not known, or does not - * matter to the caller. + * hint about the type of object being requested, e.g. + * {@link Constants#OBJ_BLOB}; {@link #OBJ_ANY} if the object + * type is not known, or does not matter to the caller. * @return a {@link ObjectLoader} for accessing the object. * @throws MissingObjectException * the object does not exist. @@ -323,9 +323,9 @@ public abstract class ObjectReader { * @param objectId * identity of the object to open. * @param typeHint - * hint about the type of object being requested; - * {@link #OBJ_ANY} if the object type is not known, or does not - * matter to the caller. + * hint about the type of object being requested, e.g. + * {@link Constants#OBJ_BLOB}; {@link #OBJ_ANY} if the object + * type is not known, or does not matter to the caller. * @return size of object in bytes. * @throws MissingObjectException * the object does not exist. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java index cac67e11e9..ef61e22032 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java @@ -197,6 +197,8 @@ public class RebaseTodoFile { } tokenCount++; } + if (tokenCount == 2) + return new RebaseTodoLine(action, commit, ""); //$NON-NLS-1$ return null; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java index 4a352e8b69..77734bf6b1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -257,9 +257,10 @@ public abstract class Repository { * @param objectId * identity of the object to open. * @param typeHint - * hint about the type of object being requested; - * {@link ObjectReader#OBJ_ANY} if the object type is not known, - * or does not matter to the caller. + * hint about the type of object being requested, e.g. + * {@link Constants#OBJ_BLOB}; {@link ObjectReader#OBJ_ANY} if + * the object type is not known, or does not matter to the + * caller. * @return a {@link ObjectLoader} for accessing the object. * @throws MissingObjectException * the object does not exist. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java index 1346328761..6dba412987 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008-2013, Google Inc. * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -54,8 +54,8 @@ import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; -import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTree; @@ -63,7 +63,6 @@ import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.filter.RevFilter; import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.CanonicalTreeParser; -import org.eclipse.jgit.treewalk.EmptyTreeIterator; /** * Instance of a specific {@link MergeStrategy} for a single {@link Repository}. @@ -186,24 +185,11 @@ public abstract class Merger { } /** - * Create an iterator to walk the merge base of two commits. - * - * @param a - * the first commit in {@link #sourceObjects}. - * @param b - * the second commit in {@link #sourceObjects}. - * @return the new iterator - * @throws IncorrectObjectTypeException - * one of the input objects is not a commit. - * @throws IOException - * objects are missing or multiple merge bases were found. - * @since 3.0 + * @return the ID of the commit that was used as merge base for merging, or + * null if no merge base was used or it was set manually + * @since 3.2 */ - protected AbstractTreeIterator mergeBase(RevCommit a, RevCommit b) - throws IOException { - RevCommit base = getBaseCommit(a, b); - return (base == null) ? new EmptyTreeIterator() : openTree(base.getTree()); - } + public abstract ObjectId getBaseCommitId(); /** * Return the merge base of two commits. @@ -217,7 +203,10 @@ public abstract class Merger { * one of the input objects is not a commit. * @throws IOException * objects are missing or multiple merge bases were found. + * @deprecated use {@link #getBaseCommitId()} instead, as that does not + * require walking the commits again */ + @Deprecated public RevCommit getBaseCommit(final int aIdx, final int bIdx) throws IncorrectObjectTypeException, IOException { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java index 34bc9f5e4d..12d6c6b413 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java @@ -106,5 +106,10 @@ public class StrategyOneSided extends MergeStrategy { public ObjectId getResultTreeId() { return sourceTrees[treeIndex]; } + + @Override + public ObjectId getBaseCommitId() { + return null; + } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java index 1ad791bb79..fbedaef865 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java @@ -49,14 +49,19 @@ import java.io.IOException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.treewalk.AbstractTreeIterator; +import org.eclipse.jgit.treewalk.EmptyTreeIterator; /** A merge of 2 trees, using a common base ancestor tree. */ public abstract class ThreeWayMerger extends Merger { private RevTree baseTree; + private ObjectId baseCommitId; + /** * Create a new merge instance for a repository. * @@ -109,6 +114,11 @@ public abstract class ThreeWayMerger extends Merger { return super.merge(tips); } + @Override + public ObjectId getBaseCommitId() { + return baseCommitId; + } + /** * Create an iterator to walk the merge base. * @@ -119,6 +129,15 @@ public abstract class ThreeWayMerger extends Merger { protected AbstractTreeIterator mergeBase() throws IOException { if (baseTree != null) return openTree(baseTree); - return mergeBase(sourceCommits[0], sourceCommits[1]); + RevCommit baseCommit = (baseCommitId != null) ? walk + .parseCommit(baseCommitId) : getBaseCommit(sourceCommits[0], + sourceCommits[1]); + if (baseCommit == null) { + baseCommitId = null; + return new EmptyTreeIterator(); + } else { + baseCommitId = baseCommit.toObjectId(); + return openTree(baseCommit.getTree()); + } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java index 8f4e491de6..32859c9c58 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java @@ -49,6 +49,7 @@ import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import org.eclipse.jgit.internal.JGitText; @@ -71,23 +72,40 @@ public class GitDateParser { // Since SimpleDateFormat instances are expensive to instantiate they should // be cached. Since they are also not threadsafe they are cached using // ThreadLocal. - private static ThreadLocal<Map<ParseableSimpleDateFormat, SimpleDateFormat>> formatCache = new ThreadLocal<Map<ParseableSimpleDateFormat, SimpleDateFormat>>() { - protected Map<ParseableSimpleDateFormat, SimpleDateFormat> initialValue() { - return new HashMap<ParseableSimpleDateFormat, SimpleDateFormat>(); + private static ThreadLocal<Map<Locale, Map<ParseableSimpleDateFormat, SimpleDateFormat>>> formatCache = + new ThreadLocal<Map<Locale, Map<ParseableSimpleDateFormat, SimpleDateFormat>>>() { + + protected Map<Locale, Map<ParseableSimpleDateFormat, SimpleDateFormat>> initialValue() { + return new HashMap<Locale, Map<ParseableSimpleDateFormat, SimpleDateFormat>>(); } }; - // Gets an instance of a SimpleDateFormat. If there is not already an - // appropriate instance in the (ThreadLocal) cache the create one and put in - // into the cache - private static SimpleDateFormat getDateFormat(ParseableSimpleDateFormat f) { - Map<ParseableSimpleDateFormat, SimpleDateFormat> map = formatCache + // Gets an instance of a SimpleDateFormat for the specified locale. If there + // is not already an appropriate instance in the (ThreadLocal) cache then + // create one and put it into the cache. + private static SimpleDateFormat getDateFormat(ParseableSimpleDateFormat f, + Locale locale) { + Map<Locale, Map<ParseableSimpleDateFormat, SimpleDateFormat>> cache = formatCache .get(); + Map<ParseableSimpleDateFormat, SimpleDateFormat> map = cache + .get(locale); + if (map == null) { + map = new HashMap<ParseableSimpleDateFormat, SimpleDateFormat>(); + cache.put(locale, map); + return getNewSimpleDateFormat(f, locale, map); + } SimpleDateFormat dateFormat = map.get(f); if (dateFormat != null) return dateFormat; + SimpleDateFormat df = getNewSimpleDateFormat(f, locale, map); + return df; + } + + private static SimpleDateFormat getNewSimpleDateFormat( + ParseableSimpleDateFormat f, Locale locale, + Map<ParseableSimpleDateFormat, SimpleDateFormat> map) { SimpleDateFormat df = SystemReader.getInstance().getSimpleDateFormat( - f.formatStr); + f.formatStr, locale); map.put(f, df); return df; } @@ -115,9 +133,9 @@ public class GitDateParser { } /** - * Parses a string into a {@link Date}. Since this parser also supports - * relative formats (e.g. "yesterday") the caller can specify the reference - * date. These types of strings can be parsed: + * Parses a string into a {@link Date} using the default locale. Since this + * parser also supports relative formats (e.g. "yesterday") the caller can + * specify the reference date. These types of strings can be parsed: * <ul> * <li>"never"</li> * <li>"now"</li> @@ -151,6 +169,49 @@ public class GitDateParser { */ public static Date parse(String dateStr, Calendar now) throws ParseException { + return parse(dateStr, now, Locale.getDefault()); + } + + /** + * Parses a string into a {@link Date} using the given locale. Since this + * parser also supports relative formats (e.g. "yesterday") the caller can + * specify the reference date. These types of strings can be parsed: + * <ul> + * <li>"never"</li> + * <li>"now"</li> + * <li>"yesterday"</li> + * <li>"(x) years|months|weeks|days|hours|minutes|seconds ago"<br> + * Multiple specs can be combined like in "2 weeks 3 days ago". Instead of + * ' ' one can use '.' to seperate the words</li> + * <li>"yyyy-MM-dd HH:mm:ss Z" (ISO)</li> + * <li>"EEE, dd MMM yyyy HH:mm:ss Z" (RFC)</li> + * <li>"yyyy-MM-dd"</li> + * <li>"yyyy.MM.dd"</li> + * <li>"MM/dd/yyyy",</li> + * <li>"dd.MM.yyyy"</li> + * <li>"EEE MMM dd HH:mm:ss yyyy Z" (DEFAULT)</li> + * <li>"EEE MMM dd HH:mm:ss yyyy" (LOCAL)</li> + * </ul> + * + * @param dateStr + * the string to be parsed + * @param now + * the base date which is used for the calculation of relative + * formats. E.g. if baseDate is "25.8.2012" then parsing of the + * string "1 week ago" would result in a date corresponding to + * "18.8.2012". This is used when a JGit command calls this + * parser often but wants a consistent starting point for calls.<br> + * If set to <code>null</code> then the current time will be used + * instead. + * @param locale + * locale to be used to parse the date string + * @return the parsed {@link Date} + * @throws ParseException + * if the given dateStr was not recognized + * @since 3.2 + */ + public static Date parse(String dateStr, Calendar now, Locale locale) + throws ParseException { dateStr = dateStr.trim(); Date ret; @@ -161,7 +222,7 @@ public class GitDateParser { return ret; for (ParseableSimpleDateFormat f : ParseableSimpleDateFormat.values()) { try { - return parse_simple(dateStr, f); + return parse_simple(dateStr, f, locale); } catch (ParseException e) { // simply proceed with the next parser } @@ -177,9 +238,10 @@ public class GitDateParser { } // tries to parse a string with the formats supported by SimpleDateFormat - private static Date parse_simple(String dateStr, ParseableSimpleDateFormat f) + private static Date parse_simple(String dateStr, + ParseableSimpleDateFormat f, Locale locale) throws ParseException { - SimpleDateFormat dateFormat = getDateFormat(f); + SimpleDateFormat dateFormat = getDateFormat(f, locale); dateFormat.setLenient(false); return dateFormat.parse(dateStr); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java index b6028610bf..e73f100f96 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java @@ -230,6 +230,21 @@ public abstract class SystemReader { } /** + * Returns a simple date format instance as specified by the given pattern. + * + * @param pattern + * the pattern as defined in + * {@link SimpleDateFormat#SimpleDateFormat(String)} + * @param locale + * locale to be used for the {@code SimpleDateFormat} + * @return the simple date format + * @since 3.2 + */ + public SimpleDateFormat getSimpleDateFormat(String pattern, Locale locale) { + return new SimpleDateFormat(pattern, locale); + } + + /** * Returns a date/time format instance for the given styles. * * @param dateStyle |