View Javadoc
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  
13  import java.io.IOException;
14  import java.text.MessageFormat;
15  import java.util.Arrays;
16  
17  import org.eclipse.jgit.api.errors.DetachedHeadException;
18  import org.eclipse.jgit.api.errors.GitAPIException;
19  import org.eclipse.jgit.api.errors.InvalidRefNameException;
20  import org.eclipse.jgit.api.errors.JGitInternalException;
21  import org.eclipse.jgit.api.errors.NoHeadException;
22  import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
23  import org.eclipse.jgit.api.errors.RefNotFoundException;
24  import org.eclipse.jgit.internal.JGitText;
25  import org.eclipse.jgit.lib.ConfigConstants;
26  import org.eclipse.jgit.lib.Constants;
27  import org.eclipse.jgit.lib.ObjectId;
28  import org.eclipse.jgit.lib.Ref;
29  import org.eclipse.jgit.lib.RefRename;
30  import org.eclipse.jgit.lib.RefUpdate.Result;
31  import org.eclipse.jgit.lib.Repository;
32  import org.eclipse.jgit.lib.StoredConfig;
33  
34  /**
35   * Used to rename branches.
36   *
37   * @see <a
38   *      href="http://www.kernel.org/pub/software/scm/git/docs/git-branch.html"
39   *      >Git documentation about Branch</a>
40   */
41  public class RenameBranchCommand extends GitCommand<Ref> {
42  	private String oldName;
43  
44  	private String newName;
45  
46  	/**
47  	 * <p>
48  	 * Constructor for RenameBranchCommand.
49  	 * </p>
50  	 *
51  	 * @param repo
52  	 *            the {@link org.eclipse.jgit.lib.Repository}
53  	 */
54  	protected RenameBranchCommand(Repository repo) {
55  		super(repo);
56  	}
57  
58  	/** {@inheritDoc} */
59  	@Override
60  	public Ref call() throws GitAPIException, RefNotFoundException, InvalidRefNameException,
61  			RefAlreadyExistsException, DetachedHeadException {
62  		checkCallable();
63  
64  		if (newName == null) {
65  			throw new InvalidRefNameException(MessageFormat.format(JGitText
66  					.get().branchNameInvalid, "<null>")); //$NON-NLS-1$
67  		}
68  		try {
69  			String fullOldName;
70  			String fullNewName;
71  			if (oldName != null) {
72  				// Don't just rely on findRef -- if there are local and remote
73  				// branches with the same name, and oldName is a short name, it
74  				// does not uniquely identify the ref and we might end up
75  				// renaming the wrong branch or finding a tag instead even
76  				// if a unique branch for the name exists!
77  				//
78  				// OldName may be a either a short or a full name.
79  				Ref ref = repo.exactRef(oldName);
80  				if (ref == null) {
81  					ref = repo.exactRef(Constants.R_HEADS + oldName);
82  					Ref ref2 = repo.exactRef(Constants.R_REMOTES + oldName);
83  					if (ref != null && ref2 != null) {
84  						throw new RefNotFoundException(MessageFormat.format(
85  								JGitText.get().renameBranchFailedAmbiguous,
86  								oldName, ref.getName(), ref2.getName()));
87  					} else if (ref == null) {
88  						if (ref2 != null) {
89  							ref = ref2;
90  						} else {
91  							throw new RefNotFoundException(MessageFormat.format(
92  									JGitText.get().refNotResolved, oldName));
93  						}
94  					}
95  				}
96  				fullOldName = ref.getName();
97  			} else {
98  				fullOldName = repo.getFullBranch();
99  				if (fullOldName == null) {
100 					throw new NoHeadException(
101 							JGitText.get().invalidRepositoryStateNoHead);
102 				}
103 				if (ObjectId.isId(fullOldName))
104 					throw new DetachedHeadException();
105 			}
106 
107 			if (fullOldName.startsWith(Constants.R_REMOTES)) {
108 				fullNewName = Constants.R_REMOTES + newName;
109 			} else if (fullOldName.startsWith(Constants.R_HEADS)) {
110 				fullNewName = Constants.R_HEADS + newName;
111 			} else {
112 				throw new RefNotFoundException(MessageFormat.format(
113 						JGitText.get().renameBranchFailedNotABranch,
114 						fullOldName));
115 			}
116 
117 			if (!Repository.isValidRefName(fullNewName)) {
118 				throw new InvalidRefNameException(MessageFormat.format(JGitText
119 						.get().branchNameInvalid, fullNewName));
120 			}
121 			if (repo.exactRef(fullNewName) != null) {
122 				throw new RefAlreadyExistsException(MessageFormat
123 						.format(JGitText.get().refAlreadyExists1, fullNewName));
124 			}
125 			RefRename rename = repo.renameRef(fullOldName, fullNewName);
126 			Result renameResult = rename.rename();
127 
128 			setCallable(false);
129 
130 			if (Result.RENAMED != renameResult) {
131 				throw new JGitInternalException(MessageFormat.format(JGitText
132 						.get().renameBranchUnexpectedResult, renameResult
133 						.name()));
134 			}
135 			if (fullNewName.startsWith(Constants.R_HEADS)) {
136 				String shortOldName = fullOldName.substring(Constants.R_HEADS
137 						.length());
138 				final StoredConfig repoConfig = repo.getConfig();
139 				// Copy all configuration values over to the new branch
140 				for (String name : repoConfig.getNames(
141 						ConfigConstants.CONFIG_BRANCH_SECTION, shortOldName)) {
142 					String[] values = repoConfig.getStringList(
143 							ConfigConstants.CONFIG_BRANCH_SECTION,
144 							shortOldName, name);
145 					if (values.length == 0) {
146 						continue;
147 					}
148 					// Keep any existing values already configured for the
149 					// new branch name
150 					String[] existing = repoConfig.getStringList(
151 							ConfigConstants.CONFIG_BRANCH_SECTION, newName,
152 							name);
153 					if (existing.length > 0) {
154 						String[] newValues = new String[values.length
155 								+ existing.length];
156 						System.arraycopy(existing, 0, newValues, 0,
157 								existing.length);
158 						System.arraycopy(values, 0, newValues, existing.length,
159 								values.length);
160 						values = newValues;
161 					}
162 
163 					repoConfig.setStringList(
164 							ConfigConstants.CONFIG_BRANCH_SECTION, newName,
165 							name, Arrays.asList(values));
166 				}
167 				repoConfig.unsetSection(ConfigConstants.CONFIG_BRANCH_SECTION,
168 						shortOldName);
169 				repoConfig.save();
170 			}
171 
172 			Ref resultRef = repo.exactRef(fullNewName);
173 			if (resultRef == null) {
174 				throw new JGitInternalException(
175 						JGitText.get().renameBranchFailedUnknownReason);
176 			}
177 			return resultRef;
178 		} catch (IOException ioe) {
179 			throw new JGitInternalException(ioe.getMessage(), ioe);
180 		}
181 	}
182 
183 	/**
184 	 * Sets the new short name of the branch.
185 	 * <p>
186 	 * The full name is constructed using the prefix of the branch to be renamed
187 	 * defined by either {@link #setOldName(String)} or HEAD. If that old branch
188 	 * is a local branch, the renamed branch also will be, and if the old branch
189 	 * is a remote branch, so will be the renamed branch.
190 	 * </p>
191 	 *
192 	 * @param newName
193 	 *            the new name
194 	 * @return this instance
195 	 */
196 	public RenameBranchCommand setNewName(String newName) {
197 		checkCallable();
198 		this.newName = newName;
199 		return this;
200 	}
201 
202 	/**
203 	 * Sets the old name of the branch.
204 	 * <p>
205 	 * {@code oldName} may be a short or a full name. Using a full name is
206 	 * recommended to unambiguously identify the branch to be renamed.
207 	 * </p>
208 	 *
209 	 * @param oldName
210 	 *            the name of the branch to rename; if not set, the currently
211 	 *            checked out branch (if any) will be renamed
212 	 * @return this instance
213 	 */
214 	public RenameBranchCommand setOldName(String oldName) {
215 		checkCallable();
216 		this.oldName = oldName;
217 		return this;
218 	}
219 }