RenameBranchCommand.java

  1. /*
  2.  * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
  3.  * Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com> and others
  4.  *
  5.  * This program and the accompanying materials are made available under the
  6.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  7.  * https://www.eclipse.org/org/documents/edl-v10.php.
  8.  *
  9.  * SPDX-License-Identifier: BSD-3-Clause
  10.  */
  11. package org.eclipse.jgit.api;

  12. import java.io.IOException;
  13. import java.text.MessageFormat;
  14. import java.util.Arrays;

  15. import org.eclipse.jgit.api.errors.DetachedHeadException;
  16. import org.eclipse.jgit.api.errors.GitAPIException;
  17. import org.eclipse.jgit.api.errors.InvalidRefNameException;
  18. import org.eclipse.jgit.api.errors.JGitInternalException;
  19. import org.eclipse.jgit.api.errors.NoHeadException;
  20. import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
  21. import org.eclipse.jgit.api.errors.RefNotFoundException;
  22. import org.eclipse.jgit.internal.JGitText;
  23. import org.eclipse.jgit.lib.ConfigConstants;
  24. import org.eclipse.jgit.lib.Constants;
  25. import org.eclipse.jgit.lib.ObjectId;
  26. import org.eclipse.jgit.lib.Ref;
  27. import org.eclipse.jgit.lib.RefRename;
  28. import org.eclipse.jgit.lib.RefUpdate.Result;
  29. import org.eclipse.jgit.lib.Repository;
  30. import org.eclipse.jgit.lib.StoredConfig;

  31. /**
  32.  * Used to rename branches.
  33.  *
  34.  * @see <a
  35.  *      href="http://www.kernel.org/pub/software/scm/git/docs/git-branch.html"
  36.  *      >Git documentation about Branch</a>
  37.  */
  38. public class RenameBranchCommand extends GitCommand<Ref> {
  39.     private String oldName;

  40.     private String newName;

  41.     /**
  42.      * <p>
  43.      * Constructor for RenameBranchCommand.
  44.      * </p>
  45.      *
  46.      * @param repo
  47.      *            the {@link org.eclipse.jgit.lib.Repository}
  48.      */
  49.     protected RenameBranchCommand(Repository repo) {
  50.         super(repo);
  51.     }

  52.     /** {@inheritDoc} */
  53.     @Override
  54.     public Ref call() throws GitAPIException, RefNotFoundException, InvalidRefNameException,
  55.             RefAlreadyExistsException, DetachedHeadException {
  56.         checkCallable();

  57.         if (newName == null) {
  58.             throw new InvalidRefNameException(MessageFormat.format(JGitText
  59.                     .get().branchNameInvalid, "<null>")); //$NON-NLS-1$
  60.         }
  61.         try {
  62.             String fullOldName;
  63.             String fullNewName;
  64.             if (oldName != null) {
  65.                 // Don't just rely on findRef -- if there are local and remote
  66.                 // branches with the same name, and oldName is a short name, it
  67.                 // does not uniquely identify the ref and we might end up
  68.                 // renaming the wrong branch or finding a tag instead even
  69.                 // if a unique branch for the name exists!
  70.                 //
  71.                 // OldName may be a either a short or a full name.
  72.                 Ref ref = repo.exactRef(oldName);
  73.                 if (ref == null) {
  74.                     ref = repo.exactRef(Constants.R_HEADS + oldName);
  75.                     Ref ref2 = repo.exactRef(Constants.R_REMOTES + oldName);
  76.                     if (ref != null && ref2 != null) {
  77.                         throw new RefNotFoundException(MessageFormat.format(
  78.                                 JGitText.get().renameBranchFailedAmbiguous,
  79.                                 oldName, ref.getName(), ref2.getName()));
  80.                     } else if (ref == null) {
  81.                         if (ref2 != null) {
  82.                             ref = ref2;
  83.                         } else {
  84.                             throw new RefNotFoundException(MessageFormat.format(
  85.                                     JGitText.get().refNotResolved, oldName));
  86.                         }
  87.                     }
  88.                 }
  89.                 fullOldName = ref.getName();
  90.             } else {
  91.                 fullOldName = repo.getFullBranch();
  92.                 if (fullOldName == null) {
  93.                     throw new NoHeadException(
  94.                             JGitText.get().invalidRepositoryStateNoHead);
  95.                 }
  96.                 if (ObjectId.isId(fullOldName))
  97.                     throw new DetachedHeadException();
  98.             }

  99.             if (fullOldName.startsWith(Constants.R_REMOTES)) {
  100.                 fullNewName = Constants.R_REMOTES + newName;
  101.             } else if (fullOldName.startsWith(Constants.R_HEADS)) {
  102.                 fullNewName = Constants.R_HEADS + newName;
  103.             } else {
  104.                 throw new RefNotFoundException(MessageFormat.format(
  105.                         JGitText.get().renameBranchFailedNotABranch,
  106.                         fullOldName));
  107.             }

  108.             if (!Repository.isValidRefName(fullNewName)) {
  109.                 throw new InvalidRefNameException(MessageFormat.format(JGitText
  110.                         .get().branchNameInvalid, fullNewName));
  111.             }
  112.             if (repo.exactRef(fullNewName) != null) {
  113.                 throw new RefAlreadyExistsException(MessageFormat
  114.                         .format(JGitText.get().refAlreadyExists1, fullNewName));
  115.             }
  116.             RefRename rename = repo.renameRef(fullOldName, fullNewName);
  117.             Result renameResult = rename.rename();

  118.             setCallable(false);

  119.             if (Result.RENAMED != renameResult) {
  120.                 throw new JGitInternalException(MessageFormat.format(JGitText
  121.                         .get().renameBranchUnexpectedResult, renameResult
  122.                         .name()));
  123.             }
  124.             if (fullNewName.startsWith(Constants.R_HEADS)) {
  125.                 String shortOldName = fullOldName.substring(Constants.R_HEADS
  126.                         .length());
  127.                 final StoredConfig repoConfig = repo.getConfig();
  128.                 // Copy all configuration values over to the new branch
  129.                 for (String name : repoConfig.getNames(
  130.                         ConfigConstants.CONFIG_BRANCH_SECTION, shortOldName)) {
  131.                     String[] values = repoConfig.getStringList(
  132.                             ConfigConstants.CONFIG_BRANCH_SECTION,
  133.                             shortOldName, name);
  134.                     if (values.length == 0) {
  135.                         continue;
  136.                     }
  137.                     // Keep any existing values already configured for the
  138.                     // new branch name
  139.                     String[] existing = repoConfig.getStringList(
  140.                             ConfigConstants.CONFIG_BRANCH_SECTION, newName,
  141.                             name);
  142.                     if (existing.length > 0) {
  143.                         String[] newValues = new String[values.length
  144.                                 + existing.length];
  145.                         System.arraycopy(existing, 0, newValues, 0,
  146.                                 existing.length);
  147.                         System.arraycopy(values, 0, newValues, existing.length,
  148.                                 values.length);
  149.                         values = newValues;
  150.                     }

  151.                     repoConfig.setStringList(
  152.                             ConfigConstants.CONFIG_BRANCH_SECTION, newName,
  153.                             name, Arrays.asList(values));
  154.                 }
  155.                 repoConfig.unsetSection(ConfigConstants.CONFIG_BRANCH_SECTION,
  156.                         shortOldName);
  157.                 repoConfig.save();
  158.             }

  159.             Ref resultRef = repo.exactRef(fullNewName);
  160.             if (resultRef == null) {
  161.                 throw new JGitInternalException(
  162.                         JGitText.get().renameBranchFailedUnknownReason);
  163.             }
  164.             return resultRef;
  165.         } catch (IOException ioe) {
  166.             throw new JGitInternalException(ioe.getMessage(), ioe);
  167.         }
  168.     }

  169.     /**
  170.      * Sets the new short name of the branch.
  171.      * <p>
  172.      * The full name is constructed using the prefix of the branch to be renamed
  173.      * defined by either {@link #setOldName(String)} or HEAD. If that old branch
  174.      * is a local branch, the renamed branch also will be, and if the old branch
  175.      * is a remote branch, so will be the renamed branch.
  176.      * </p>
  177.      *
  178.      * @param newName
  179.      *            the new name
  180.      * @return this instance
  181.      */
  182.     public RenameBranchCommand setNewName(String newName) {
  183.         checkCallable();
  184.         this.newName = newName;
  185.         return this;
  186.     }

  187.     /**
  188.      * Sets the old name of the branch.
  189.      * <p>
  190.      * {@code oldName} may be a short or a full name. Using a full name is
  191.      * recommended to unambiguously identify the branch to be renamed.
  192.      * </p>
  193.      *
  194.      * @param oldName
  195.      *            the name of the branch to rename; if not set, the currently
  196.      *            checked out branch (if any) will be renamed
  197.      * @return this instance
  198.      */
  199.     public RenameBranchCommand setOldName(String oldName) {
  200.         checkCallable();
  201.         this.oldName = oldName;
  202.         return this;
  203.     }
  204. }