1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 package org.eclipse.jgit.pgm;
45
46 import java.io.IOException;
47 import java.text.MessageFormat;
48 import java.util.Collection;
49 import java.util.LinkedHashMap;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.Map.Entry;
53
54 import org.eclipse.jgit.api.Git;
55 import org.eclipse.jgit.api.ListBranchCommand;
56 import org.eclipse.jgit.api.ListBranchCommand.ListMode;
57 import org.eclipse.jgit.api.errors.GitAPIException;
58 import org.eclipse.jgit.lib.Constants;
59 import org.eclipse.jgit.lib.ObjectId;
60 import org.eclipse.jgit.lib.ObjectReader;
61 import org.eclipse.jgit.lib.Ref;
62 import org.eclipse.jgit.lib.RefComparator;
63 import org.eclipse.jgit.lib.RefRename;
64 import org.eclipse.jgit.lib.RefUpdate;
65 import org.eclipse.jgit.lib.RefUpdate.Result;
66 import org.eclipse.jgit.lib.Repository;
67 import org.eclipse.jgit.pgm.internal.CLIText;
68 import org.eclipse.jgit.pgm.opt.OptionWithValuesListHandler;
69 import org.eclipse.jgit.revwalk.RevWalk;
70 import org.kohsuke.args4j.Argument;
71 import org.kohsuke.args4j.Option;
72
73 @Command(common = true, usage = "usage_listCreateOrDeleteBranches")
74 class Branch extends TextBuiltin {
75
76 private String otherBranch;
77 private boolean createForce;
78 private boolean rename;
79
80 @Option(name = "--remote", aliases = { "-r" }, usage = "usage_actOnRemoteTrackingBranches")
81 private boolean remote = false;
82
83 @Option(name = "--all", aliases = { "-a" }, usage = "usage_listBothRemoteTrackingAndLocalBranches")
84 private boolean all = false;
85
86 @Option(name = "--contains", metaVar = "metaVar_commitish", usage = "usage_printOnlyBranchesThatContainTheCommit")
87 private String containsCommitish;
88
89 private List<String> delete;
90
91
92
93
94
95
96
97 @Option(name = "--delete", aliases = {
98 "-d" }, metaVar = "metaVar_branchNames", usage = "usage_deleteFullyMergedBranch", handler = OptionWithValuesListHandler.class)
99 public void delete(List<String> names) {
100 if (names.isEmpty()) {
101 throw die(CLIText.get().branchNameRequired);
102 }
103 delete = names;
104 }
105
106 private List<String> deleteForce;
107
108
109
110
111
112
113
114 @Option(name = "--delete-force", aliases = {
115 "-D" }, metaVar = "metaVar_branchNames", usage = "usage_deleteBranchEvenIfNotMerged", handler = OptionWithValuesListHandler.class)
116 public void deleteForce(List<String> names) {
117 if (names.isEmpty()) {
118 throw die(CLIText.get().branchNameRequired);
119 }
120 deleteForce = names;
121 }
122
123
124
125
126
127
128
129 @Option(name = "--create-force", aliases = {
130 "-f" }, metaVar = "metaVar_branchAndStartPoint", usage = "usage_forceCreateBranchEvenExists", handler = OptionWithValuesListHandler.class)
131 public void createForce(List<String> branchAndStartPoint) {
132 createForce = true;
133 if (branchAndStartPoint.isEmpty()) {
134 throw die(CLIText.get().branchNameRequired);
135 }
136 if (branchAndStartPoint.size() > 2) {
137 throw die(CLIText.get().tooManyRefsGiven);
138 }
139 if (branchAndStartPoint.size() == 1) {
140 branch = branchAndStartPoint.get(0);
141 } else {
142 branch = branchAndStartPoint.get(0);
143 otherBranch = branchAndStartPoint.get(1);
144 }
145 }
146
147
148
149
150
151
152
153 @Option(name = "--move", aliases = {
154 "-m" }, metaVar = "metaVar_oldNewBranchNames", usage = "usage_moveRenameABranch", handler = OptionWithValuesListHandler.class)
155 public void moveRename(List<String> currentAndNew) {
156 rename = true;
157 if (currentAndNew.isEmpty()) {
158 throw die(CLIText.get().branchNameRequired);
159 }
160 if (currentAndNew.size() > 2) {
161 throw die(CLIText.get().tooManyRefsGiven);
162 }
163 if (currentAndNew.size() == 1) {
164 branch = currentAndNew.get(0);
165 } else {
166 branch = currentAndNew.get(0);
167 otherBranch = currentAndNew.get(1);
168 }
169 }
170
171 @Option(name = "--verbose", aliases = { "-v" }, usage = "usage_beVerbose")
172 private boolean verbose = false;
173
174 @Argument(metaVar = "metaVar_name")
175 private String branch;
176
177 private final Map<String, Ref> printRefs = new LinkedHashMap<>();
178
179
180 private RevWalk rw;
181
182 private int maxNameLength;
183
184
185 @Override
186 protected void run() {
187 try {
188 if (delete != null || deleteForce != null) {
189 if (delete != null) {
190 delete(delete, false);
191 }
192 if (deleteForce != null) {
193 delete(deleteForce, true);
194 }
195 return;
196 }
197 if (rename) {
198 String src, dst;
199 if (otherBranch == null) {
200 final Ref head = db.exactRef(Constants.HEAD);
201 if (head != null && head.isSymbolic()) {
202 src = head.getLeaf().getName();
203 } else {
204 throw die(CLIText.get().cannotRenameDetachedHEAD);
205 }
206 dst = branch;
207 } else {
208 src = branch;
209 final Ref old = db.findRef(src);
210 if (old == null) {
211 throw die(MessageFormat.format(CLIText.get().doesNotExist, src));
212 }
213 if (!old.getName().startsWith(Constants.R_HEADS)) {
214 throw die(MessageFormat.format(CLIText.get().notABranch, src));
215 }
216 src = old.getName();
217 dst = otherBranch;
218 }
219
220 if (!dst.startsWith(Constants.R_HEADS)) {
221 dst = Constants.R_HEADS + dst;
222 }
223 if (!Repository.isValidRefName(dst)) {
224 throw die(MessageFormat.format(CLIText.get().notAValidRefName, dst));
225 }
226
227 RefRename r = db.renameRef(src, dst);
228 if (r.rename() != Result.RENAMED) {
229 throw die(MessageFormat.format(CLIText.get().cannotBeRenamed, src));
230 }
231
232 } else if (createForce || branch != null) {
233 String newHead = branch;
234 String startBranch;
235 if (createForce) {
236 startBranch = otherBranch;
237 } else {
238 startBranch = Constants.HEAD;
239 }
240 Ref startRef = db.findRef(startBranch);
241 ObjectId startAt = db.resolve(startBranch + "^0");
242 if (startRef != null) {
243 startBranch = startRef.getName();
244 } else if (startAt != null) {
245 startBranch = startAt.name();
246 } else {
247 throw die(MessageFormat.format(
248 CLIText.get().notAValidCommitName, startBranch));
249 }
250 startBranch = Repository.shortenRefName(startBranch);
251 String newRefName = newHead;
252 if (!newRefName.startsWith(Constants.R_HEADS)) {
253 newRefName = Constants.R_HEADS + newRefName;
254 }
255 if (!Repository.isValidRefName(newRefName)) {
256 throw die(MessageFormat.format(CLIText.get().notAValidRefName, newRefName));
257 }
258 if (!createForce && db.resolve(newRefName) != null) {
259 throw die(MessageFormat.format(CLIText.get().branchAlreadyExists, newHead));
260 }
261 RefUpdate updateRef = db.updateRef(newRefName);
262 updateRef.setNewObjectId(startAt);
263 updateRef.setForceUpdate(createForce);
264 updateRef.setRefLogMessage(MessageFormat.format(CLIText.get().branchCreatedFrom, startBranch), false);
265 Result update = updateRef.update();
266 if (update == Result.REJECTED) {
267 throw die(MessageFormat.format(CLIText.get().couldNotCreateBranch, newHead, update.toString()));
268 }
269 } else {
270 if (verbose) {
271 rw = new RevWalk(db);
272 }
273 list();
274 }
275 } catch (IOException | GitAPIException e) {
276 throw die(e.getMessage(), e);
277 }
278 }
279
280 private void list() throws IOException, GitAPIException {
281 Ref head = db.exactRef(Constants.HEAD);
282
283 if (head != null) {
284 String current = head.getLeaf().getName();
285 try (Gitit.html#Git">Git git = new Git(db)) {
286 ListBranchCommand command = git.branchList();
287 if (all)
288 command.setListMode(ListMode.ALL);
289 else if (remote)
290 command.setListMode(ListMode.REMOTE);
291
292 if (containsCommitish != null)
293 command.setContains(containsCommitish);
294
295 List<Ref> refs = command.call();
296 for (Ref ref : refs) {
297 if (ref.getName().equals(Constants.HEAD))
298 addRef("(no branch)", head);
299 }
300
301 addRefs(refs, Constants.R_HEADS);
302 addRefs(refs, Constants.R_REMOTES);
303
304 try (ObjectReader reader = db.newObjectReader()) {
305 for (Entry<String, Ref> e : printRefs.entrySet()) {
306 final Ref ref = e.getValue();
307 printHead(reader, e.getKey(),
308 current.equals(ref.getName()), ref);
309 }
310 }
311 }
312 }
313 }
314
315 private void addRefs(Collection<Ref> refs, String prefix) {
316 for (Ref ref : RefComparator.sort(refs)) {
317 final String name = ref.getName();
318 if (name.startsWith(prefix))
319 addRef(name.substring(name.indexOf('/', 5) + 1), ref);
320 }
321 }
322
323 private void addRef(String name, Ref ref) {
324 printRefs.put(name, ref);
325 maxNameLength = Math.max(maxNameLength, name.length());
326 }
327
328 private void printHead(final ObjectReader reader, final String ref,
329 final boolean isCurrent, final Ref refObj) throws IOException {
330 outw.print(isCurrent ? '*' : ' ');
331 outw.print(' ');
332 outw.print(ref);
333 if (verbose) {
334 final int spaces = maxNameLength - ref.length() + 1;
335 outw.format("%" + spaces + "s", "");
336 final ObjectId objectId = refObj.getObjectId();
337 outw.print(reader.abbreviate(objectId).name());
338 outw.print(' ');
339 outw.print(rw.parseCommit(objectId).getShortMessage());
340 }
341 outw.println();
342 }
343
344 private void delete(List<String> branches, boolean force)
345 throws IOException {
346 String current = db.getBranch();
347 ObjectId head = db.resolve(Constants.HEAD);
348 for (String b : branches) {
349 if (b.equals(current)) {
350 throw die(MessageFormat.format(CLIText.get().cannotDeleteTheBranchWhichYouAreCurrentlyOn, b));
351 }
352 RefUpdate update = db.updateRef((remote ? Constants.R_REMOTES
353 : Constants.R_HEADS)
354 + b);
355 update.setNewObjectId(head);
356 update.setForceUpdate(force || remote);
357 Result result = update.delete();
358 if (result == Result.REJECTED) {
359 throw die(MessageFormat.format(CLIText.get().branchIsNotAnAncestorOfYourCurrentHEAD, b));
360 } else if (result == Result.NEW)
361 throw die(MessageFormat.format(CLIText.get().branchNotFound, b));
362 if (remote)
363 outw.println(MessageFormat.format(CLIText.get().deletedRemoteBranch, b));
364 else if (verbose)
365 outw.println(MessageFormat.format(CLIText.get().deletedBranch, b));
366 }
367 }
368 }