CreateBranchCommand.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 static org.eclipse.jgit.lib.Constants.HEAD;
  13. import static org.eclipse.jgit.lib.Constants.R_HEADS;

  14. import java.io.IOException;
  15. import java.text.MessageFormat;

  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.RefAlreadyExistsException;
  20. import org.eclipse.jgit.api.errors.RefNotFoundException;
  21. import org.eclipse.jgit.errors.AmbiguousObjectException;
  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.RefUpdate;
  28. import org.eclipse.jgit.lib.RefUpdate.Result;
  29. import org.eclipse.jgit.lib.Repository;
  30. import org.eclipse.jgit.lib.StoredConfig;
  31. import org.eclipse.jgit.revwalk.RevCommit;
  32. import org.eclipse.jgit.revwalk.RevWalk;

  33. /**
  34.  * Used to create a local branch.
  35.  *
  36.  * @see <a
  37.  *      href="http://www.kernel.org/pub/software/scm/git/docs/git-branch.html"
  38.  *      >Git documentation about Branch</a>
  39.  */
  40. public class CreateBranchCommand extends GitCommand<Ref> {
  41.     private String name;

  42.     private boolean force = false;

  43.     private SetupUpstreamMode upstreamMode;

  44.     private String startPoint = HEAD;

  45.     private RevCommit startCommit;

  46.     /**
  47.      * The modes available for setting up the upstream configuration
  48.      * (corresponding to the --set-upstream, --track, --no-track options
  49.      *
  50.      */
  51.     public enum SetupUpstreamMode {
  52.         /**
  53.          * Corresponds to the --track option
  54.          */
  55.         TRACK,
  56.         /**
  57.          * Corresponds to the --no-track option
  58.          */
  59.         NOTRACK,
  60.         /**
  61.          * Corresponds to the --set-upstream option
  62.          */
  63.         SET_UPSTREAM;
  64.     }

  65.     /**
  66.      * Constructor for CreateBranchCommand
  67.      *
  68.      * @param repo
  69.      *            the {@link org.eclipse.jgit.lib.Repository}
  70.      */
  71.     protected CreateBranchCommand(Repository repo) {
  72.         super(repo);
  73.     }

  74.     /** {@inheritDoc} */
  75.     @Override
  76.     public Ref call() throws GitAPIException, RefAlreadyExistsException,
  77.             RefNotFoundException, InvalidRefNameException {
  78.         checkCallable();
  79.         processOptions();
  80.         try (RevWalk revWalk = new RevWalk(repo)) {
  81.             Ref refToCheck = repo.findRef(name);
  82.             boolean exists = refToCheck != null
  83.                     && refToCheck.getName().startsWith(R_HEADS);
  84.             if (!force && exists)
  85.                 throw new RefAlreadyExistsException(MessageFormat.format(
  86.                         JGitText.get().refAlreadyExists1, name));

  87.             ObjectId startAt = getStartPointObjectId();
  88.             String startPointFullName = null;
  89.             if (startPoint != null) {
  90.                 Ref baseRef = repo.findRef(startPoint);
  91.                 if (baseRef != null)
  92.                     startPointFullName = baseRef.getName();
  93.             }

  94.             // determine whether we are based on a commit,
  95.             // a branch, or a tag and compose the reflog message
  96.             String refLogMessage;
  97.             String baseBranch = ""; //$NON-NLS-1$
  98.             if (startPointFullName == null) {
  99.                 String baseCommit;
  100.                 if (startCommit != null)
  101.                     baseCommit = startCommit.getShortMessage();
  102.                 else {
  103.                     RevCommit commit = revWalk.parseCommit(repo
  104.                             .resolve(getStartPointOrHead()));
  105.                     baseCommit = commit.getShortMessage();
  106.                 }
  107.                 if (exists)
  108.                     refLogMessage = "branch: Reset start-point to commit " //$NON-NLS-1$
  109.                             + baseCommit;
  110.                 else
  111.                     refLogMessage = "branch: Created from commit " + baseCommit; //$NON-NLS-1$

  112.             } else if (startPointFullName.startsWith(R_HEADS)
  113.                     || startPointFullName.startsWith(Constants.R_REMOTES)) {
  114.                 baseBranch = startPointFullName;
  115.                 if (exists)
  116.                     refLogMessage = "branch: Reset start-point to branch " //$NON-NLS-1$
  117.                             + startPointFullName; // TODO
  118.                 else
  119.                     refLogMessage = "branch: Created from branch " + baseBranch; //$NON-NLS-1$
  120.             } else {
  121.                 startAt = revWalk.peel(revWalk.parseAny(startAt));
  122.                 if (exists)
  123.                     refLogMessage = "branch: Reset start-point to tag " //$NON-NLS-1$
  124.                             + startPointFullName;
  125.                 else
  126.                     refLogMessage = "branch: Created from tag " //$NON-NLS-1$
  127.                             + startPointFullName;
  128.             }

  129.             RefUpdate updateRef = repo.updateRef(R_HEADS + name);
  130.             updateRef.setNewObjectId(startAt);
  131.             updateRef.setRefLogMessage(refLogMessage, false);
  132.             Result updateResult;
  133.             if (exists && force)
  134.                 updateResult = updateRef.forceUpdate();
  135.             else
  136.                 updateResult = updateRef.update();

  137.             setCallable(false);

  138.             boolean ok = false;
  139.             switch (updateResult) {
  140.             case NEW:
  141.                 ok = !exists;
  142.                 break;
  143.             case NO_CHANGE:
  144.             case FAST_FORWARD:
  145.             case FORCED:
  146.                 ok = exists;
  147.                 break;
  148.             default:
  149.                 break;
  150.             }

  151.             if (!ok)
  152.                 throw new JGitInternalException(MessageFormat.format(JGitText
  153.                         .get().createBranchUnexpectedResult, updateResult
  154.                         .name()));

  155.             Ref result = repo.findRef(name);
  156.             if (result == null)
  157.                 throw new JGitInternalException(
  158.                         JGitText.get().createBranchFailedUnknownReason);

  159.             if (baseBranch.length() == 0) {
  160.                 return result;
  161.             }

  162.             // if we are based on another branch, see
  163.             // if we need to configure upstream configuration: first check
  164.             // whether the setting was done explicitly
  165.             boolean doConfigure;
  166.             if (upstreamMode == SetupUpstreamMode.SET_UPSTREAM
  167.                     || upstreamMode == SetupUpstreamMode.TRACK)
  168.                 // explicitly set to configure
  169.                 doConfigure = true;
  170.             else if (upstreamMode == SetupUpstreamMode.NOTRACK)
  171.                 // explicitly set to not configure
  172.                 doConfigure = false;
  173.             else {
  174.                 // if there was no explicit setting, check the configuration
  175.                 String autosetupflag = repo.getConfig().getString(
  176.                         ConfigConstants.CONFIG_BRANCH_SECTION, null,
  177.                         ConfigConstants.CONFIG_KEY_AUTOSETUPMERGE);
  178.                 if ("false".equals(autosetupflag)) { //$NON-NLS-1$
  179.                     doConfigure = false;
  180.                 } else if ("always".equals(autosetupflag)) { //$NON-NLS-1$
  181.                     doConfigure = true;
  182.                 } else {
  183.                     // in this case, the default is to configure
  184.                     // only in case the base branch was a remote branch
  185.                     doConfigure = baseBranch.startsWith(Constants.R_REMOTES);
  186.                 }
  187.             }

  188.             if (doConfigure) {
  189.                 StoredConfig config = repo.getConfig();

  190.                 String remoteName = repo.getRemoteName(baseBranch);
  191.                 if (remoteName != null) {
  192.                     String branchName = repo
  193.                             .shortenRemoteBranchName(baseBranch);
  194.                     config
  195.                             .setString(ConfigConstants.CONFIG_BRANCH_SECTION,
  196.                                     name, ConfigConstants.CONFIG_KEY_REMOTE,
  197.                                     remoteName);
  198.                     config.setString(ConfigConstants.CONFIG_BRANCH_SECTION,
  199.                             name, ConfigConstants.CONFIG_KEY_MERGE,
  200.                             Constants.R_HEADS + branchName);
  201.                 } else {
  202.                     // set "." as remote
  203.                     config.setString(ConfigConstants.CONFIG_BRANCH_SECTION,
  204.                             name, ConfigConstants.CONFIG_KEY_REMOTE, "."); //$NON-NLS-1$
  205.                     config.setString(ConfigConstants.CONFIG_BRANCH_SECTION,
  206.                             name, ConfigConstants.CONFIG_KEY_MERGE, baseBranch);
  207.                 }
  208.                 config.save();
  209.             }
  210.             return result;
  211.         } catch (IOException ioe) {
  212.             throw new JGitInternalException(ioe.getMessage(), ioe);
  213.         }
  214.     }

  215.     private ObjectId getStartPointObjectId() throws AmbiguousObjectException,
  216.             RefNotFoundException, IOException {
  217.         if (startCommit != null)
  218.             return startCommit.getId();
  219.         String startPointOrHead = getStartPointOrHead();
  220.         ObjectId result = repo.resolve(startPointOrHead);
  221.         if (result == null)
  222.             throw new RefNotFoundException(MessageFormat.format(
  223.                     JGitText.get().refNotResolved, startPointOrHead));
  224.         return result;
  225.     }

  226.     private String getStartPointOrHead() {
  227.         return startPoint != null ? startPoint : HEAD;
  228.     }

  229.     private void processOptions() throws InvalidRefNameException {
  230.         if (name == null
  231.                 || !Repository.isValidRefName(R_HEADS + name)
  232.                 || !isValidBranchName(name))
  233.             throw new InvalidRefNameException(MessageFormat.format(JGitText
  234.                     .get().branchNameInvalid, name == null ? "<null>" : name)); //$NON-NLS-1$
  235.     }

  236.     /**
  237.      * Check if the given branch name is valid
  238.      *
  239.      * @param branchName
  240.      *            branch name to check
  241.      * @return {@code true} if the branch name is valid
  242.      *
  243.      * @since 5.0
  244.      */
  245.     public static boolean isValidBranchName(String branchName) {
  246.         if (HEAD.equals(branchName)) {
  247.             return false;
  248.         }
  249.         return !branchName.startsWith("-"); //$NON-NLS-1$
  250.     }

  251.     /**
  252.      * Set the name of the new branch
  253.      *
  254.      * @param name
  255.      *            the name of the new branch
  256.      * @return this instance
  257.      */
  258.     public CreateBranchCommand setName(String name) {
  259.         checkCallable();
  260.         this.name = name;
  261.         return this;
  262.     }

  263.     /**
  264.      * Set whether to create the branch forcefully
  265.      *
  266.      * @param force
  267.      *            if <code>true</code> and the branch with the given name
  268.      *            already exists, the start-point of an existing branch will be
  269.      *            set to a new start-point; if false, the existing branch will
  270.      *            not be changed
  271.      * @return this instance
  272.      */
  273.     public CreateBranchCommand setForce(boolean force) {
  274.         checkCallable();
  275.         this.force = force;
  276.         return this;
  277.     }

  278.     /**
  279.      * Set the start point
  280.      *
  281.      * @param startPoint
  282.      *            corresponds to the start-point option; if <code>null</code>,
  283.      *            the current HEAD will be used
  284.      * @return this instance
  285.      */
  286.     public CreateBranchCommand setStartPoint(String startPoint) {
  287.         checkCallable();
  288.         this.startPoint = startPoint;
  289.         this.startCommit = null;
  290.         return this;
  291.     }

  292.     /**
  293.      * Set the start point
  294.      *
  295.      * @param startPoint
  296.      *            corresponds to the start-point option; if <code>null</code>,
  297.      *            the current HEAD will be used
  298.      * @return this instance
  299.      */
  300.     public CreateBranchCommand setStartPoint(RevCommit startPoint) {
  301.         checkCallable();
  302.         this.startCommit = startPoint;
  303.         this.startPoint = null;
  304.         return this;
  305.     }

  306.     /**
  307.      * Set the upstream mode
  308.      *
  309.      * @param mode
  310.      *            corresponds to the --track/--no-track/--set-upstream options;
  311.      *            may be <code>null</code>
  312.      * @return this instance
  313.      */
  314.     public CreateBranchCommand setUpstreamMode(SetupUpstreamMode mode) {
  315.         checkCallable();
  316.         this.upstreamMode = mode;
  317.         return this;
  318.     }
  319. }