diff options
author | Thomas Wolf <thomas.wolf@paranor.ch> | 2018-12-06 15:49:25 +0100 |
---|---|---|
committer | Thomas Wolf <thomas.wolf@paranor.ch> | 2019-01-23 19:10:36 +0100 |
commit | 447e1070695a499e10ec7f0d9f147a1694cbdad8 (patch) | |
tree | d5a74681efb514e6919d3d53e9d1ff39d4f6f65d /org.eclipse.jgit | |
parent | 2cb842ef0214a95f77b35b06a3c4992b6c8fbb01 (diff) | |
download | jgit-447e1070695a499e10ec7f0d9f147a1694cbdad8.tar.gz jgit-447e1070695a499e10ec7f0d9f147a1694cbdad8.zip |
RenameBranchCommand: more consistent handling of short ref names
Several problems:
* The command didn't specify whether it expected short or full names.
* For the new name, it expected a short name, but then got confused
if tags or both local and remote branches with the same name existed.
* For the old name, it accepted either a short or a full name, but
again got confused if a short name was given and a tag with the
same name existed.
With such an interface, one cannot use Repository.findRef() to
reliably find the branch to rename. Use exactRef() for the new
name as by the time the Ref is needed its full name is known.
For determining the old Ref from the name, do the resolution
explicitly: first try exactRef (assuming the old name is a full
name); if that doesn't find anything, try "refs/heads/<old>" and
"refs/remotes/<old>" explicitly. Throw an exception if the name
is ambiguous, or if exactRef returned something that is not a
branch (refs/tags/... or also refs/notes/...).
Document in the javadoc what kind of names are valid, and add tests.
A user can still shoot himself in the foot if he chooses exceptionally
stupid branch names. For instance, it is still possible to rename a
branch to "refs/heads/foo" (full name "refs/heads/refs/heads/foo"),
but it cannot be renamed further using the new short name if a branch
with the full name "refs/heads/foo" exists. Similar edge cases exist
for other dumb branch names, like a branch with the short name
"refs/tags/foo". Renaming using the full name is always possible.
Bug: 542446
Change-Id: I34ac91c80c0a00c79a384d16ce1e727c550d54e9
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
Diffstat (limited to 'org.eclipse.jgit')
4 files changed, 64 insertions, 29 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 6ee144ff64..1366031be0 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -590,8 +590,9 @@ remoteConfigHasNoURIAssociated=Remote config "{0}" has no URIs associated remoteDoesNotHaveSpec=Remote does not have {0} available for fetch. remoteDoesNotSupportSmartHTTPPush=remote does not support smart HTTP push remoteHungUpUnexpectedly=remote hung up unexpectedly -remoteNameCantBeNull=Remote name can't be null. -renameBranchFailedBecauseTag=Can not rename as Ref {0} is a tag +remoteNameCannotBeNull=Remote name cannot be null. +renameBranchFailedAmbiguous=Cannot rename branch {0}; name is ambiguous: {1} or {2} +renameBranchFailedNotABranch=Cannot rename {0}: this is not a branch renameBranchFailedUnknownReason=Rename failed with unknown reason renameBranchUnexpectedResult=Unexpected rename result {0} renameCancelled=Rename detection was cancelled diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java index 24d9dd4015..7e8c33c8a1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java @@ -94,25 +94,38 @@ public class RenameBranchCommand extends GitCommand<Ref> { RefAlreadyExistsException, DetachedHeadException { checkCallable(); - if (newName == null) + if (newName == null) { throw new InvalidRefNameException(MessageFormat.format(JGitText .get().branchNameInvalid, "<null>")); //$NON-NLS-1$ - + } try { String fullOldName; String fullNewName; - if (repo.findRef(newName) != null) - throw new RefAlreadyExistsException(MessageFormat.format( - JGitText.get().refAlreadyExists1, newName)); if (oldName != null) { - Ref ref = repo.findRef(oldName); - if (ref == null) - throw new RefNotFoundException(MessageFormat.format( - JGitText.get().refNotResolved, oldName)); - if (ref.getName().startsWith(Constants.R_TAGS)) - throw new RefNotFoundException(MessageFormat.format( - JGitText.get().renameBranchFailedBecauseTag, - oldName)); + // Don't just rely on findRef -- if there are local and remote + // branches with the same name, and oldName is a short name, it + // does not uniquely identify the ref and we might end up + // renaming the wrong branch or finding a tag instead even + // if a unique branch for the name exists! + // + // OldName may be a either a short or a full name. + Ref ref = repo.exactRef(oldName); + if (ref == null) { + ref = repo.exactRef(Constants.R_HEADS + oldName); + Ref ref2 = repo.exactRef(Constants.R_REMOTES + oldName); + if (ref != null && ref2 != null) { + throw new RefNotFoundException(MessageFormat.format( + JGitText.get().renameBranchFailedAmbiguous, + oldName, ref.getName(), ref2.getName())); + } else if (ref == null) { + if (ref2 != null) { + ref = ref2; + } else { + throw new RefNotFoundException(MessageFormat.format( + JGitText.get().refNotResolved, oldName)); + } + } + } fullOldName = ref.getName(); } else { fullOldName = repo.getFullBranch(); @@ -124,26 +137,34 @@ public class RenameBranchCommand extends GitCommand<Ref> { throw new DetachedHeadException(); } - if (fullOldName.startsWith(Constants.R_REMOTES)) + if (fullOldName.startsWith(Constants.R_REMOTES)) { fullNewName = Constants.R_REMOTES + newName; - else { + } else if (fullOldName.startsWith(Constants.R_HEADS)) { fullNewName = Constants.R_HEADS + newName; + } else { + throw new RefNotFoundException(MessageFormat.format( + JGitText.get().renameBranchFailedNotABranch, + fullOldName)); } - if (!Repository.isValidRefName(fullNewName)) + if (!Repository.isValidRefName(fullNewName)) { throw new InvalidRefNameException(MessageFormat.format(JGitText .get().branchNameInvalid, fullNewName)); - + } + if (repo.exactRef(fullNewName) != null) { + throw new RefAlreadyExistsException(MessageFormat + .format(JGitText.get().refAlreadyExists1, fullNewName)); + } RefRename rename = repo.renameRef(fullOldName, fullNewName); Result renameResult = rename.rename(); setCallable(false); - if (Result.RENAMED != renameResult) + if (Result.RENAMED != renameResult) { throw new JGitInternalException(MessageFormat.format(JGitText .get().renameBranchUnexpectedResult, renameResult .name())); - + } if (fullNewName.startsWith(Constants.R_HEADS)) { String shortOldName = fullOldName.substring(Constants.R_HEADS .length()); @@ -154,8 +175,9 @@ public class RenameBranchCommand extends GitCommand<Ref> { String[] values = repoConfig.getStringList( ConfigConstants.CONFIG_BRANCH_SECTION, shortOldName, name); - if (values.length == 0) + if (values.length == 0) { continue; + } // Keep any existing values already configured for the // new branch name String[] existing = repoConfig.getStringList( @@ -180,10 +202,11 @@ public class RenameBranchCommand extends GitCommand<Ref> { repoConfig.save(); } - Ref resultRef = repo.findRef(newName); - if (resultRef == null) + Ref resultRef = repo.exactRef(fullNewName); + if (resultRef == null) { throw new JGitInternalException( JGitText.get().renameBranchFailedUnknownReason); + } return resultRef; } catch (IOException ioe) { throw new JGitInternalException(ioe.getMessage(), ioe); @@ -191,7 +214,13 @@ public class RenameBranchCommand extends GitCommand<Ref> { } /** - * Set the new name of the branch + * Sets the new short name of the branch. + * <p> + * The full name is constructed using the prefix of the branch to be renamed + * defined by either {@link #setOldName(String)} or HEAD. If that old branch + * is a local branch, the renamed branch also will be, and if the old branch + * is a remote branch, so will be the renamed branch. + * </p> * * @param newName * the new name @@ -204,7 +233,11 @@ public class RenameBranchCommand extends GitCommand<Ref> { } /** - * Set the old name of the branch + * Sets the old name of the branch. + * <p> + * {@code oldName} may be a short or a full name. Using a full name is + * recommended to unambiguously identify the branch to be renamed. + * </p> * * @param oldName * the name of the branch to rename; if not set, the currently 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 bd91fe0f82..7dce43a71c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -651,8 +651,9 @@ public class JGitText extends TranslationBundle { /***/ public String remoteDoesNotHaveSpec; /***/ public String remoteDoesNotSupportSmartHTTPPush; /***/ public String remoteHungUpUnexpectedly; - /***/ public String remoteNameCantBeNull; - /***/ public String renameBranchFailedBecauseTag; + /***/ public String remoteNameCannotBeNull; + /***/ public String renameBranchFailedAmbiguous; + /***/ public String renameBranchFailedNotABranch; /***/ public String renameBranchFailedUnknownReason; /***/ public String renameBranchUnexpectedResult; /***/ public String renameCancelled; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java index 9a67f0f8fe..c34e3b8e61 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java @@ -293,7 +293,7 @@ public class RemoteRefUpdate { final boolean forceUpdate, final String localName, final ObjectId expectedOldObjectId) throws IOException { if (remoteName == null) - throw new IllegalArgumentException(JGitText.get().remoteNameCantBeNull); + throw new IllegalArgumentException(JGitText.get().remoteNameCannotBeNull); if (srcId == null && srcRef != null) throw new IOException(MessageFormat.format( JGitText.get().sourceRefDoesntResolveToAnyObject, srcRef)); |