2 * Copyright (C) 2007, Charles O'Farrell <charleso@charleso.org>
6 * Redistribution and use in source and binary forms, with or
7 * without modification, are permitted provided that the following
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * - Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials provided
16 * with the distribution.
18 * - Neither the name of the Git Development Community nor the
19 * names of its contributors may be used to endorse or promote
20 * products derived from this software without specific prior
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
24 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
25 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
28 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
30 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
33 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
35 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 package org
.spearce
.jgit
.pgm
;
40 import java
.io
.IOException
;
41 import java
.util
.ArrayList
;
42 import java
.util
.LinkedHashMap
;
43 import java
.util
.List
;
45 import java
.util
.Map
.Entry
;
47 import org
.kohsuke
.args4j
.Argument
;
48 import org
.kohsuke
.args4j
.ExampleMode
;
49 import org
.kohsuke
.args4j
.Option
;
50 import org
.spearce
.jgit
.lib
.Constants
;
51 import org
.spearce
.jgit
.lib
.ObjectId
;
52 import org
.spearce
.jgit
.lib
.Ref
;
53 import org
.spearce
.jgit
.lib
.RefComparator
;
54 import org
.spearce
.jgit
.lib
.RefUpdate
;
55 import org
.spearce
.jgit
.lib
.Repository
;
56 import org
.spearce
.jgit
.lib
.RefUpdate
.Result
;
57 import org
.spearce
.jgit
.pgm
.opt
.CmdLineParser
;
58 import org
.spearce
.jgit
.revwalk
.RevWalk
;
60 @Command(common
= true, usage
= "List, create, or delete branches")
61 class Branch
extends TextBuiltin
{
63 @Option(name
= "--remote", aliases
= { "-r" }, usage
= "act on remote-tracking branches")
64 private boolean remote
= false;
66 @Option(name
= "--all", aliases
= { "-a" }, usage
= "list both remote-tracking and local branches")
67 private boolean all
= false;
69 @Option(name
= "--delete", aliases
= { "-d" }, usage
= "delete fully merged branch")
70 private boolean delete
= false;
72 @Option(name
= "--delete-force", aliases
= { "-D" }, usage
= "delete branch (even if not merged)")
73 private boolean deleteForce
= false;
75 @Option(name
= "--create-force", aliases
= { "-f" }, usage
= "force create branch even exists")
76 private boolean createForce
= false;
78 @Option(name
= "--verbose", aliases
= { "-v" }, usage
= "be verbose")
79 private boolean verbose
= false;
82 private List
<String
> branches
= new ArrayList
<String
>();
84 private final Map
<String
, Ref
> printRefs
= new LinkedHashMap
<String
, Ref
>();
86 /** Only set for verbose branch listing at-the-moment */
89 private int maxNameLength
;
92 protected void run() throws Exception
{
93 if (delete
|| deleteForce
)
96 if (branches
.size() > 2)
97 throw die("Too many refs given\n" + new CmdLineParser(this).printExample(ExampleMode
.ALL
));
99 if (branches
.size() > 0) {
100 String newHead
= branches
.get(0);
102 if (branches
.size() == 2)
103 startAt
= db
.resolve(branches
.get(1) + "^0");
105 startAt
= db
.resolve(Constants
.HEAD
+ "^0");
107 String newRefName
= newHead
;
108 if (!newRefName
.startsWith(Constants
.R_HEADS
))
109 newRefName
= Constants
.R_HEADS
+ newRefName
;
110 if (!Repository
.isValidRefName(newRefName
))
111 throw die(String
.format("%s is not a valid ref name", newRefName
));
112 if (!createForce
&& db
.resolve(newRefName
) != null)
113 throw die(String
.format("branch %s already exists", newHead
));
114 RefUpdate updateRef
= db
.updateRef(newRefName
);
115 updateRef
.setNewObjectId(startAt
);
116 updateRef
.setForceUpdate(createForce
);
117 Result update
= updateRef
.update();
118 if (update
== Result
.REJECTED
)
119 throw die(String
.format("Could not create branch %s: %s", newHead
, update
.toString()));
122 rw
= new RevWalk(db
);
128 private void list() throws Exception
{
129 Map
<String
, Ref
> refs
= db
.getAllRefs();
130 Ref head
= refs
.get(Constants
.HEAD
);
131 // This can happen if HEAD is stillborn
133 String current
= head
.getName();
134 if (current
.equals(Constants
.HEAD
))
135 addRef("(no branch)", head
);
136 addRefs(refs
, Constants
.R_HEADS
, !remote
);
137 addRefs(refs
, Constants
.R_REMOTES
, remote
);
138 for (final Entry
<String
, Ref
> e
: printRefs
.entrySet()) {
139 final Ref ref
= e
.getValue();
140 printHead(e
.getKey(), current
.equals(ref
.getName()), ref
);
145 private void addRefs(final Map
<String
, Ref
> allRefs
, final String prefix
,
148 for (final Ref ref
: RefComparator
.sort(allRefs
.values())) {
149 final String name
= ref
.getName();
150 if (name
.startsWith(prefix
))
151 addRef(name
.substring(name
.indexOf('/', 5) + 1), ref
);
156 private void addRef(final String name
, final Ref ref
) {
157 printRefs
.put(name
, ref
);
158 maxNameLength
= Math
.max(maxNameLength
, name
.length());
161 private void printHead(final String ref
, final boolean isCurrent
,
162 final Ref refObj
) throws Exception
{
163 out
.print(isCurrent ?
'*' : ' ');
167 final int spaces
= maxNameLength
- ref
.length() + 1;
168 out
.print(String
.format("%" + spaces
+ "s", ""));
169 final ObjectId objectId
= refObj
.getObjectId();
170 out
.print(objectId
.abbreviate(db
));
172 out
.print(rw
.parseCommit(objectId
).getShortMessage());
177 private void delete(boolean force
) throws IOException
{
178 String current
= db
.getBranch();
179 ObjectId head
= db
.resolve(Constants
.HEAD
);
180 for (String branch
: branches
) {
181 if (current
.equals(branch
)) {
182 String err
= "Cannot delete the branch '%s' which you are currently on.";
183 throw die(String
.format(err
, branch
));
185 RefUpdate update
= db
.updateRef((remote ? Constants
.R_REMOTES
188 update
.setNewObjectId(head
);
189 update
.setForceUpdate(force
|| remote
);
190 Result result
= update
.delete();
191 if (result
== Result
.REJECTED
) {
192 String err
= "The branch '%s' is not an ancestor of your current HEAD.\n"
193 + "If you are sure you want to delete it, run 'jgit branch -D %1$s'.";
194 throw die(String
.format(err
, branch
));
195 } else if (result
== Result
.NEW
)
196 throw die(String
.format("branch '%s' not found.", branch
));
198 out
.println(String
.format("Deleted remote branch %s", branch
));
200 out
.println(String
.format("Deleted branch %s", branch
));