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.ArrayList;
49  import java.util.HashSet;
50  import java.util.List;
51  import java.util.Set;
52  
53  import org.eclipse.jgit.api.errors.CannotDeleteCurrentBranchException;
54  import org.eclipse.jgit.api.errors.GitAPIException;
55  import org.eclipse.jgit.api.errors.JGitInternalException;
56  import org.eclipse.jgit.api.errors.NotMergedException;
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.Ref;
61  import org.eclipse.jgit.lib.RefUpdate;
62  import org.eclipse.jgit.lib.RefUpdate.Result;
63  import org.eclipse.jgit.lib.Repository;
64  import org.eclipse.jgit.lib.StoredConfig;
65  import org.eclipse.jgit.revwalk.RevCommit;
66  import org.eclipse.jgit.revwalk.RevWalk;
67  
68  /**
69   * Used to delete one or several branches.
70   *
71   * The result of {@link #call()} is a list with the (full) names of the deleted
72   * branches.
73   *
74   * Note that we don't have a setter corresponding to the -r option; remote
75   * tracking branches are simply deleted just like local branches.
76   *
77   * @see <a
78   *      href="http://www.kernel.org/pub/software/scm/git/docs/git-branch.html"
79   *      >Git documentation about Branch</a>
80   */
81  public class DeleteBranchCommand extends GitCommand<List<String>> {
82  	private final Set<String> branchNames = new HashSet<>();
83  
84  	private boolean force;
85  
86  	/**
87  	 * Constructor for DeleteBranchCommand
88  	 *
89  	 * @param repo
90  	 *            the {@link org.eclipse.jgit.lib.Repository}
91  	 */
92  	protected DeleteBranchCommand(Repository repo) {
93  		super(repo);
94  	}
95  
96  	/** {@inheritDoc} */
97  	@Override
98  	public List<String> call() throws GitAPIException,
99  			NotMergedException, CannotDeleteCurrentBranchException {
100 		checkCallable();
101 		List<String> result = new ArrayList<>();
102 		if (branchNames.isEmpty())
103 			return result;
104 		try {
105 			String currentBranch = repo.getFullBranch();
106 			if (!force) {
107 				// check if the branches to be deleted
108 				// are all merged into the current branch
109 				try (RevWalkvWalk.html#RevWalk">RevWalk walk = new RevWalk(repo)) {
110 					RevCommit tip = walk
111 							.parseCommit(repo.resolve(Constants.HEAD));
112 					for (String branchName : branchNames) {
113 						if (branchName == null)
114 							continue;
115 						Ref currentRef = repo.findRef(branchName);
116 						if (currentRef == null)
117 							continue;
118 
119 						RevCommit base = walk
120 								.parseCommit(repo.resolve(branchName));
121 						if (!walk.isMergedInto(base, tip)) {
122 							throw new NotMergedException();
123 						}
124 					}
125 				}
126 			}
127 			setCallable(false);
128 			for (String branchName : branchNames) {
129 				if (branchName == null)
130 					continue;
131 				Ref currentRef = repo.findRef(branchName);
132 				if (currentRef == null)
133 					continue;
134 				String fullName = currentRef.getName();
135 				if (fullName.equals(currentBranch))
136 					throw new CannotDeleteCurrentBranchException(
137 							MessageFormat
138 									.format(
139 											JGitText.get().cannotDeleteCheckedOutBranch,
140 											branchName));
141 				RefUpdate update = repo.updateRef(fullName);
142 				update.setRefLogMessage("branch deleted", false); //$NON-NLS-1$
143 				update.setForceUpdate(true);
144 				Result deleteResult = update.delete();
145 
146 				boolean ok = true;
147 				switch (deleteResult) {
148 				case IO_FAILURE:
149 				case LOCK_FAILURE:
150 				case REJECTED:
151 					ok = false;
152 					break;
153 				default:
154 					break;
155 				}
156 
157 				if (ok) {
158 					result.add(fullName);
159 					if (fullName.startsWith(Constants.R_HEADS)) {
160 						String shortenedName = fullName
161 								.substring(Constants.R_HEADS.length());
162 						// remove upstream configuration if any
163 						final StoredConfig cfg = repo.getConfig();
164 						cfg.unsetSection(
165 								ConfigConstants.CONFIG_BRANCH_SECTION,
166 								shortenedName);
167 						cfg.save();
168 					}
169 				} else
170 					throw new JGitInternalException(MessageFormat.format(
171 							JGitText.get().deleteBranchUnexpectedResult,
172 							deleteResult.name()));
173 			}
174 			return result;
175 		} catch (IOException ioe) {
176 			throw new JGitInternalException(ioe.getMessage(), ioe);
177 		}
178 	}
179 
180 	/**
181 	 * Set the names of the branches to delete
182 	 *
183 	 * @param branchnames
184 	 *            the names of the branches to delete; if not set, this will do
185 	 *            nothing; invalid branch names will simply be ignored
186 	 * @return this instance
187 	 */
188 	public DeleteBranchCommand setBranchNames(String... branchnames) {
189 		checkCallable();
190 		this.branchNames.clear();
191 		for (String branch : branchnames)
192 			this.branchNames.add(branch);
193 		return this;
194 	}
195 
196 	/**
197 	 * Set whether to forcefully delete branches
198 	 *
199 	 * @param force
200 	 *            <code>true</code> corresponds to the -D option,
201 	 *            <code>false</code> to the -d option (default) <br>
202 	 *            if <code>false</code> a check will be performed whether the
203 	 *            branch to be deleted is already merged into the current branch
204 	 *            and deletion will be refused in this case
205 	 * @return this instance
206 	 */
207 	public DeleteBranchCommand setForce(boolean force) {
208 		checkCallable();
209 		this.force = force;
210 		return this;
211 	}
212 }