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.RefAlreadyExistsException;
55  import org.eclipse.jgit.api.errors.RefNotFoundException;
56  import org.eclipse.jgit.internal.JGitText;
57  import org.eclipse.jgit.lib.ConfigConstants;
58  import org.eclipse.jgit.lib.Constants;
59  import org.eclipse.jgit.lib.ObjectId;
60  import org.eclipse.jgit.lib.Ref;
61  import org.eclipse.jgit.lib.RefRename;
62  import org.eclipse.jgit.lib.RefUpdate.Result;
63  import org.eclipse.jgit.lib.Repository;
64  import org.eclipse.jgit.lib.StoredConfig;
65  
66  /**
67   * Used to rename branches.
68   *
69   * @see <a
70   *      href="http://www.kernel.org/pub/software/scm/git/docs/git-branch.html"
71   *      >Git documentation about Branch</a>
72   */
73  public class RenameBranchCommand extends GitCommand<Ref> {
74  	private String oldName;
75  
76  	private String newName;
77  
78  	/**
79  	 * @param repo
80  	 */
81  	protected RenameBranchCommand(Repository repo) {
82  		super(repo);
83  	}
84  
85  	/**
86  	 * @throws RefNotFoundException
87  	 *             if the old branch can not be found (branch with provided old
88  	 *             name does not exist or old name resolves to a tag)
89  	 * @throws InvalidRefNameException
90  	 *             if the provided new name is <code>null</code> or otherwise
91  	 *             invalid
92  	 * @throws RefAlreadyExistsException
93  	 *             if a branch with the new name already exists
94  	 * @throws DetachedHeadException
95  	 *             if rename is tried without specifying the old name and HEAD
96  	 *             is detached
97  	 */
98  	public Ref call() throws GitAPIException, RefNotFoundException, InvalidRefNameException,
99  			RefAlreadyExistsException, DetachedHeadException {
100 		checkCallable();
101 
102 		if (newName == null)
103 			throw new InvalidRefNameException(MessageFormat.format(JGitText
104 					.get().branchNameInvalid, "<null>")); //$NON-NLS-1$
105 
106 		try {
107 			String fullOldName;
108 			String fullNewName;
109 			if (repo.getRef(newName) != null)
110 				throw new RefAlreadyExistsException(MessageFormat.format(
111 						JGitText.get().refAlreadyExists1, newName));
112 			if (oldName != null) {
113 				Ref ref = repo.getRef(oldName);
114 				if (ref == null)
115 					throw new RefNotFoundException(MessageFormat.format(
116 							JGitText.get().refNotResolved, oldName));
117 				if (ref.getName().startsWith(Constants.R_TAGS))
118 					throw new RefNotFoundException(MessageFormat.format(
119 							JGitText.get().renameBranchFailedBecauseTag,
120 							oldName));
121 				fullOldName = ref.getName();
122 			} else {
123 				fullOldName = repo.getFullBranch();
124 				if (ObjectId.isId(fullOldName))
125 					throw new DetachedHeadException();
126 			}
127 
128 			if (fullOldName.startsWith(Constants.R_REMOTES))
129 				fullNewName = Constants.R_REMOTES + newName;
130 			else {
131 				fullNewName = Constants.R_HEADS + newName;
132 			}
133 
134 			if (!Repository.isValidRefName(fullNewName))
135 				throw new InvalidRefNameException(MessageFormat.format(JGitText
136 						.get().branchNameInvalid, fullNewName));
137 
138 			RefRename rename = repo.renameRef(fullOldName, fullNewName);
139 			Result renameResult = rename.rename();
140 
141 			setCallable(false);
142 
143 			if (Result.RENAMED != renameResult)
144 				throw new JGitInternalException(MessageFormat.format(JGitText
145 						.get().renameBranchUnexpectedResult, renameResult
146 						.name()));
147 
148 			if (fullNewName.startsWith(Constants.R_HEADS)) {
149 				String shortOldName = fullOldName.substring(Constants.R_HEADS
150 						.length());
151 				final StoredConfig repoConfig = repo.getConfig();
152 				// Copy all configuration values over to the new branch
153 				for (String name : repoConfig.getNames(
154 						ConfigConstants.CONFIG_BRANCH_SECTION, shortOldName)) {
155 					String[] values = repoConfig.getStringList(
156 							ConfigConstants.CONFIG_BRANCH_SECTION,
157 							shortOldName, name);
158 					if (values.length == 0)
159 						continue;
160 					// Keep any existing values already configured for the
161 					// new branch name
162 					String[] existing = repoConfig.getStringList(
163 							ConfigConstants.CONFIG_BRANCH_SECTION, newName,
164 							name);
165 					if (existing.length > 0) {
166 						String[] newValues = new String[values.length
167 								+ existing.length];
168 						System.arraycopy(existing, 0, newValues, 0,
169 								existing.length);
170 						System.arraycopy(values, 0, newValues, existing.length,
171 								values.length);
172 						values = newValues;
173 					}
174 
175 					repoConfig.setStringList(
176 							ConfigConstants.CONFIG_BRANCH_SECTION, newName,
177 							name, Arrays.asList(values));
178 				}
179 				repoConfig.unsetSection(ConfigConstants.CONFIG_BRANCH_SECTION,
180 						shortOldName);
181 				repoConfig.save();
182 			}
183 
184 			Ref resultRef = repo.getRef(newName);
185 			if (resultRef == null)
186 				throw new JGitInternalException(
187 						JGitText.get().renameBranchFailedUnknownReason);
188 			return resultRef;
189 		} catch (IOException ioe) {
190 			throw new JGitInternalException(ioe.getMessage(), ioe);
191 		}
192 	}
193 
194 	/**
195 	 * @param newName
196 	 *            the new name
197 	 * @return this instance
198 	 */
199 	public RenameBranchCommand setNewName(String newName) {
200 		checkCallable();
201 		this.newName = newName;
202 		return this;
203 	}
204 
205 	/**
206 	 * @param oldName
207 	 *            the name of the branch to rename; if not set, the currently
208 	 *            checked out branch (if any) will be renamed
209 	 * @return this instance
210 	 */
211 	public RenameBranchCommand setOldName(String oldName) {
212 		checkCallable();
213 		this.oldName = oldName;
214 		return this;
215 	}
216 }