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