2 * Copyright (C) 2007-2008, Charles O'Farrell <charleso@charleso.org>
3 * and other copyright owners as documented in the project's IP log.
5 * This program and the accompanying materials are made available
6 * under the terms of the Eclipse Distribution License v1.0 which
7 * accompanies this distribution, is reproduced below, and is
8 * available at http://www.eclipse.org/org/documents/edl-v10.php
10 * All rights reserved.
12 * Redistribution and use in source and binary forms, with or
13 * without modification, are permitted provided that the following
16 * - Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
19 * - Redistributions in binary form must reproduce the above
20 * copyright notice, this list of conditions and the following
21 * disclaimer in the documentation and/or other materials provided
22 * with the distribution.
24 * - Neither the name of the Eclipse Foundation, Inc. nor the
25 * names of its contributors may be used to endorse or promote
26 * products derived from this software without specific prior
29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44 package org
.eclipse
.jgit
.pgm
;
46 import java
.io
.IOException
;
47 import java
.util
.ArrayList
;
48 import java
.util
.LinkedHashMap
;
49 import java
.util
.List
;
51 import java
.util
.Map
.Entry
;
53 import org
.kohsuke
.args4j
.Argument
;
54 import org
.kohsuke
.args4j
.ExampleMode
;
55 import org
.kohsuke
.args4j
.Option
;
56 import org
.eclipse
.jgit
.lib
.Constants
;
57 import org
.eclipse
.jgit
.lib
.ObjectId
;
58 import org
.eclipse
.jgit
.lib
.Ref
;
59 import org
.eclipse
.jgit
.lib
.RefComparator
;
60 import org
.eclipse
.jgit
.lib
.RefUpdate
;
61 import org
.eclipse
.jgit
.lib
.Repository
;
62 import org
.eclipse
.jgit
.lib
.RefUpdate
.Result
;
63 import org
.eclipse
.jgit
.pgm
.opt
.CmdLineParser
;
64 import org
.eclipse
.jgit
.revwalk
.RevWalk
;
66 @Command(common
= true, usage
= "List, create, or delete branches")
67 class Branch
extends TextBuiltin
{
69 @Option(name
= "--remote", aliases
= { "-r" }, usage
= "act on remote-tracking branches")
70 private boolean remote
= false;
72 @Option(name
= "--all", aliases
= { "-a" }, usage
= "list both remote-tracking and local branches")
73 private boolean all
= false;
75 @Option(name
= "--delete", aliases
= { "-d" }, usage
= "delete fully merged branch")
76 private boolean delete
= false;
78 @Option(name
= "--delete-force", aliases
= { "-D" }, usage
= "delete branch (even if not merged)")
79 private boolean deleteForce
= false;
81 @Option(name
= "--create-force", aliases
= { "-f" }, usage
= "force create branch even exists")
82 private boolean createForce
= false;
84 @Option(name
= "--verbose", aliases
= { "-v" }, usage
= "be verbose")
85 private boolean verbose
= false;
88 private List
<String
> branches
= new ArrayList
<String
>();
90 private final Map
<String
, Ref
> printRefs
= new LinkedHashMap
<String
, Ref
>();
92 /** Only set for verbose branch listing at-the-moment */
95 private int maxNameLength
;
98 protected void run() throws Exception
{
99 if (delete
|| deleteForce
)
102 if (branches
.size() > 2)
103 throw die("Too many refs given\n" + new CmdLineParser(this).printExample(ExampleMode
.ALL
));
105 if (branches
.size() > 0) {
106 String newHead
= branches
.get(0);
108 if (branches
.size() == 2)
109 startBranch
= branches
.get(1);
111 startBranch
= Constants
.HEAD
;
112 Ref startRef
= db
.getRef(startBranch
);
113 ObjectId startAt
= db
.resolve(startBranch
+ "^0");
114 if (startRef
!= null)
115 startBranch
= startRef
.getName();
117 startBranch
= startAt
.name();
118 startBranch
= db
.shortenRefName(startBranch
);
119 String newRefName
= newHead
;
120 if (!newRefName
.startsWith(Constants
.R_HEADS
))
121 newRefName
= Constants
.R_HEADS
+ newRefName
;
122 if (!Repository
.isValidRefName(newRefName
))
123 throw die(String
.format("%s is not a valid ref name", newRefName
));
124 if (!createForce
&& db
.resolve(newRefName
) != null)
125 throw die(String
.format("branch %s already exists", newHead
));
126 RefUpdate updateRef
= db
.updateRef(newRefName
);
127 updateRef
.setNewObjectId(startAt
);
128 updateRef
.setForceUpdate(createForce
);
129 updateRef
.setRefLogMessage("branch: Created from " + startBranch
, false);
130 Result update
= updateRef
.update();
131 if (update
== Result
.REJECTED
)
132 throw die(String
.format("Could not create branch %s: %s", newHead
, update
.toString()));
135 rw
= new RevWalk(db
);
141 private void list() throws Exception
{
142 Map
<String
, Ref
> refs
= db
.getAllRefs();
143 Ref head
= refs
.get(Constants
.HEAD
);
144 // This can happen if HEAD is stillborn
146 String current
= head
.getName();
147 if (current
.equals(Constants
.HEAD
))
148 addRef("(no branch)", head
);
149 addRefs(refs
, Constants
.R_HEADS
, !remote
);
150 addRefs(refs
, Constants
.R_REMOTES
, remote
);
151 for (final Entry
<String
, Ref
> e
: printRefs
.entrySet()) {
152 final Ref ref
= e
.getValue();
153 printHead(e
.getKey(), current
.equals(ref
.getName()), ref
);
158 private void addRefs(final Map
<String
, Ref
> allRefs
, final String prefix
,
161 for (final Ref ref
: RefComparator
.sort(allRefs
.values())) {
162 final String name
= ref
.getName();
163 if (name
.startsWith(prefix
))
164 addRef(name
.substring(name
.indexOf('/', 5) + 1), ref
);
169 private void addRef(final String name
, final Ref ref
) {
170 printRefs
.put(name
, ref
);
171 maxNameLength
= Math
.max(maxNameLength
, name
.length());
174 private void printHead(final String ref
, final boolean isCurrent
,
175 final Ref refObj
) throws Exception
{
176 out
.print(isCurrent ?
'*' : ' ');
180 final int spaces
= maxNameLength
- ref
.length() + 1;
181 out
.print(String
.format("%" + spaces
+ "s", ""));
182 final ObjectId objectId
= refObj
.getObjectId();
183 out
.print(objectId
.abbreviate(db
).name());
185 out
.print(rw
.parseCommit(objectId
).getShortMessage());
190 private void delete(boolean force
) throws IOException
{
191 String current
= db
.getBranch();
192 ObjectId head
= db
.resolve(Constants
.HEAD
);
193 for (String branch
: branches
) {
194 if (current
.equals(branch
)) {
195 String err
= "Cannot delete the branch '%s' which you are currently on.";
196 throw die(String
.format(err
, branch
));
198 RefUpdate update
= db
.updateRef((remote ? Constants
.R_REMOTES
201 update
.setNewObjectId(head
);
202 update
.setForceUpdate(force
|| remote
);
203 Result result
= update
.delete();
204 if (result
== Result
.REJECTED
) {
205 String err
= "The branch '%s' is not an ancestor of your current HEAD.\n"
206 + "If you are sure you want to delete it, run 'jgit branch -D %1$s'.";
207 throw die(String
.format(err
, branch
));
208 } else if (result
== Result
.NEW
)
209 throw die(String
.format("branch '%s' not found.", branch
));
211 out
.println(String
.format("Deleted remote branch %s", branch
));
213 out
.println(String
.format("Deleted branch %s", branch
));