View Javadoc
1   /*
2    * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
3    * Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com>
4    * and other copyright owners as documented in the project's IP log.
5    *
6    * This program and the accompanying materials are made available
7    * under the terms of the Eclipse Distribution License v1.0 which
8    * accompanies this distribution, is reproduced below, and is
9    * available at http://www.eclipse.org/org/documents/edl-v10.php
10   *
11   * All rights reserved.
12   *
13   * Redistribution and use in source and binary forms, with or
14   * without modification, are permitted provided that the following
15   * conditions are met:
16   *
17   * - Redistributions of source code must retain the above copyright
18   *   notice, this list of conditions and the following disclaimer.
19   *
20   * - Redistributions in binary form must reproduce the above
21   *   copyright notice, this list of conditions and the following
22   *   disclaimer in the documentation and/or other materials provided
23   *   with the distribution.
24   *
25   * - Neither the name of the Eclipse Foundation, Inc. nor the
26   *   names of its contributors may be used to endorse or promote
27   *   products derived from this software without specific prior
28   *   written permission.
29   *
30   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
31   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
32   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
33   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
35   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
37   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
38   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
39   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
40   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43   */
44  package org.eclipse.jgit.api;
45  
46  import java.io.IOException;
47  import java.text.MessageFormat;
48  import java.util.Arrays;
49  
50  import org.eclipse.jgit.api.errors.DetachedHeadException;
51  import org.eclipse.jgit.api.errors.GitAPIException;
52  import org.eclipse.jgit.api.errors.InvalidRefNameException;
53  import org.eclipse.jgit.api.errors.JGitInternalException;
54  import org.eclipse.jgit.api.errors.NoHeadException;
55  import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
56  import org.eclipse.jgit.api.errors.RefNotFoundException;
57  import org.eclipse.jgit.internal.JGitText;
58  import org.eclipse.jgit.lib.ConfigConstants;
59  import org.eclipse.jgit.lib.Constants;
60  import org.eclipse.jgit.lib.ObjectId;
61  import org.eclipse.jgit.lib.Ref;
62  import org.eclipse.jgit.lib.RefRename;
63  import org.eclipse.jgit.lib.RefUpdate.Result;
64  import org.eclipse.jgit.lib.Repository;
65  import org.eclipse.jgit.lib.StoredConfig;
66  
67  /**
68   * Used to rename branches.
69   *
70   * @see <a
71   *      href="http://www.kernel.org/pub/software/scm/git/docs/git-branch.html"
72   *      >Git documentation about Branch</a>
73   */
74  public class RenameBranchCommand extends GitCommand<Ref> {
75  	private String oldName;
76  
77  	private String newName;
78  
79  	/**
80  	 * <p>
81  	 * Constructor for RenameBranchCommand.
82  	 * </p>
83  	 *
84  	 * @param repo
85  	 *            the {@link org.eclipse.jgit.lib.Repository}
86  	 */
87  	protected RenameBranchCommand(Repository repo) {
88  		super(repo);
89  	}
90  
91  	/** {@inheritDoc} */
92  	@Override
93  	public Ref call() throws GitAPIException, RefNotFoundException, InvalidRefNameException,
94  			RefAlreadyExistsException, DetachedHeadException {
95  		checkCallable();
96  
97  		if (newName == null) {
98  			throw new InvalidRefNameException(MessageFormat.format(JGitText
99  					.get().branchNameInvalid, "<null>")); //$NON-NLS-1$
100 		}
101 		try {
102 			String fullOldName;
103 			String fullNewName;
104 			if (oldName != null) {
105 				// Don't just rely on findRef -- if there are local and remote
106 				// branches with the same name, and oldName is a short name, it
107 				// does not uniquely identify the ref and we might end up
108 				// renaming the wrong branch or finding a tag instead even
109 				// if a unique branch for the name exists!
110 				//
111 				// OldName may be a either a short or a full name.
112 				Ref ref = repo.exactRef(oldName);
113 				if (ref == null) {
114 					ref = repo.exactRef(Constants.R_HEADS + oldName);
115 					Ref ref2 = repo.exactRef(Constants.R_REMOTES + oldName);
116 					if (ref != null && ref2 != null) {
117 						throw new RefNotFoundException(MessageFormat.format(
118 								JGitText.get().renameBranchFailedAmbiguous,
119 								oldName, ref.getName(), ref2.getName()));
120 					} else if (ref == null) {
121 						if (ref2 != null) {
122 							ref = ref2;
123 						} else {
124 							throw new RefNotFoundException(MessageFormat.format(
125 									JGitText.get().refNotResolved, oldName));
126 						}
127 					}
128 				}
129 				fullOldName = ref.getName();
130 			} else {
131 				fullOldName = repo.getFullBranch();
132 				if (fullOldName == null) {
133 					throw new NoHeadException(
134 							JGitText.get().invalidRepositoryStateNoHead);
135 				}
136 				if (ObjectId.isId(fullOldName))
137 					throw new DetachedHeadException();
138 			}
139 
140 			if (fullOldName.startsWith(Constants.R_REMOTES)) {
141 				fullNewName = Constants.R_REMOTES + newName;
142 			} else if (fullOldName.startsWith(Constants.R_HEADS)) {
143 				fullNewName = Constants.R_HEADS + newName;
144 			} else {
145 				throw new RefNotFoundException(MessageFormat.format(
146 						JGitText.get().renameBranchFailedNotABranch,
147 						fullOldName));
148 			}
149 
150 			if (!Repository.isValidRefName(fullNewName)) {
151 				throw new InvalidRefNameException(MessageFormat.format(JGitText
152 						.get().branchNameInvalid, fullNewName));
153 			}
154 			if (repo.exactRef(fullNewName) != null) {
155 				throw new RefAlreadyExistsException(MessageFormat
156 						.format(JGitText.get().refAlreadyExists1, fullNewName));
157 			}
158 			RefRename rename = repo.renameRef(fullOldName, fullNewName);
159 			Result renameResult = rename.rename();
160 
161 			setCallable(false);
162 
163 			if (Result.RENAMED != renameResult) {
164 				throw new JGitInternalException(MessageFormat.format(JGitText
165 						.get().renameBranchUnexpectedResult, renameResult
166 						.name()));
167 			}
168 			if (fullNewName.startsWith(Constants.R_HEADS)) {
169 				String shortOldName = fullOldName.substring(Constants.R_HEADS
170 						.length());
171 				final StoredConfig repoConfig = repo.getConfig();
172 				// Copy all configuration values over to the new branch
173 				for (String name : repoConfig.getNames(
174 						ConfigConstants.CONFIG_BRANCH_SECTION, shortOldName)) {
175 					String[] values = repoConfig.getStringList(
176 							ConfigConstants.CONFIG_BRANCH_SECTION,
177 							shortOldName, name);
178 					if (values.length == 0) {
179 						continue;
180 					}
181 					// Keep any existing values already configured for the
182 					// new branch name
183 					String[] existing = repoConfig.getStringList(
184 							ConfigConstants.CONFIG_BRANCH_SECTION, newName,
185 							name);
186 					if (existing.length > 0) {
187 						String[] newValues = new String[values.length
188 								+ existing.length];
189 						System.arraycopy(existing, 0, newValues, 0,
190 								existing.length);
191 						System.arraycopy(values, 0, newValues, existing.length,
192 								values.length);
193 						values = newValues;
194 					}
195 
196 					repoConfig.setStringList(
197 							ConfigConstants.CONFIG_BRANCH_SECTION, newName,
198 							name, Arrays.asList(values));
199 				}
200 				repoConfig.unsetSection(ConfigConstants.CONFIG_BRANCH_SECTION,
201 						shortOldName);
202 				repoConfig.save();
203 			}
204 
205 			Ref resultRef = repo.exactRef(fullNewName);
206 			if (resultRef == null) {
207 				throw new JGitInternalException(
208 						JGitText.get().renameBranchFailedUnknownReason);
209 			}
210 			return resultRef;
211 		} catch (IOException ioe) {
212 			throw new JGitInternalException(ioe.getMessage(), ioe);
213 		}
214 	}
215 
216 	/**
217 	 * Sets the new short name of the branch.
218 	 * <p>
219 	 * The full name is constructed using the prefix of the branch to be renamed
220 	 * defined by either {@link #setOldName(String)} or HEAD. If that old branch
221 	 * is a local branch, the renamed branch also will be, and if the old branch
222 	 * is a remote branch, so will be the renamed branch.
223 	 * </p>
224 	 *
225 	 * @param newName
226 	 *            the new name
227 	 * @return this instance
228 	 */
229 	public RenameBranchCommand setNewName(String newName) {
230 		checkCallable();
231 		this.newName = newName;
232 		return this;
233 	}
234 
235 	/**
236 	 * Sets the old name of the branch.
237 	 * <p>
238 	 * {@code oldName} may be a short or a full name. Using a full name is
239 	 * recommended to unambiguously identify the branch to be renamed.
240 	 * </p>
241 	 *
242 	 * @param oldName
243 	 *            the name of the branch to rename; if not set, the currently
244 	 *            checked out branch (if any) will be renamed
245 	 * @return this instance
246 	 */
247 	public RenameBranchCommand setOldName(String oldName) {
248 		checkCallable();
249 		this.oldName = oldName;
250 		return this;
251 	}
252 }