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 startBranch
= branches
.get(1);
105 startBranch
= Constants
.HEAD
;
106 Ref startRef
= db
.getRef(startBranch
);
107 ObjectId startAt
= db
.resolve(startBranch
+ "^0");
108 if (startRef
!= null)
109 startBranch
= startRef
.getName();
111 startBranch
= startAt
.name();
112 startBranch
= db
.shortenRefName(startBranch
);
113 String newRefName
= newHead
;
114 if (!newRefName
.startsWith(Constants
.R_HEADS
))
115 newRefName
= Constants
.R_HEADS
+ newRefName
;
116 if (!Repository
.isValidRefName(newRefName
))
117 throw die(String
.format("%s is not a valid ref name", newRefName
));
118 if (!createForce
&& db
.resolve(newRefName
) != null)
119 throw die(String
.format("branch %s already exists", newHead
));
120 RefUpdate updateRef
= db
.updateRef(newRefName
);
121 updateRef
.setNewObjectId(startAt
);
122 updateRef
.setForceUpdate(createForce
);
123 updateRef
.setRefLogMessage("branch: Created from " + startBranch
, false);
124 Result update
= updateRef
.update();
125 if (update
== Result
.REJECTED
)
126 throw die(String
.format("Could not create branch %s: %s", newHead
, update
.toString()));
129 rw
= new RevWalk(db
);
135 private void list() throws Exception
{
136 Map
<String
, Ref
> refs
= db
.getAllRefs();
137 Ref head
= refs
.get(Constants
.HEAD
);
138 // This can happen if HEAD is stillborn
140 String current
= head
.getName();
141 if (current
.equals(Constants
.HEAD
))
142 addRef("(no branch)", head
);
143 addRefs(refs
, Constants
.R_HEADS
, !remote
);
144 addRefs(refs
, Constants
.R_REMOTES
, remote
);
145 for (final Entry
<String
, Ref
> e
: printRefs
.entrySet()) {
146 final Ref ref
= e
.getValue();
147 printHead(e
.getKey(), current
.equals(ref
.getName()), ref
);
152 private void addRefs(final Map
<String
, Ref
> allRefs
, final String prefix
,
155 for (final Ref ref
: RefComparator
.sort(allRefs
.values())) {
156 final String name
= ref
.getName();
157 if (name
.startsWith(prefix
))
158 addRef(name
.substring(name
.indexOf('/', 5) + 1), ref
);
163 private void addRef(final String name
, final Ref ref
) {
164 printRefs
.put(name
, ref
);
165 maxNameLength
= Math
.max(maxNameLength
, name
.length());
168 private void printHead(final String ref
, final boolean isCurrent
,
169 final Ref refObj
) throws Exception
{
170 out
.print(isCurrent ?
'*' : ' ');
174 final int spaces
= maxNameLength
- ref
.length() + 1;
175 out
.print(String
.format("%" + spaces
+ "s", ""));
176 final ObjectId objectId
= refObj
.getObjectId();
177 out
.print(objectId
.abbreviate(db
).name());
179 out
.print(rw
.parseCommit(objectId
).getShortMessage());
184 private void delete(boolean force
) throws IOException
{
185 String current
= db
.getBranch();
186 ObjectId head
= db
.resolve(Constants
.HEAD
);
187 for (String branch
: branches
) {
188 if (current
.equals(branch
)) {
189 String err
= "Cannot delete the branch '%s' which you are currently on.";
190 throw die(String
.format(err
, branch
));
192 RefUpdate update
= db
.updateRef((remote ? Constants
.R_REMOTES
195 update
.setNewObjectId(head
);
196 update
.setForceUpdate(force
|| remote
);
197 Result result
= update
.delete();
198 if (result
== Result
.REJECTED
) {
199 String err
= "The branch '%s' is not an ancestor of your current HEAD.\n"
200 + "If you are sure you want to delete it, run 'jgit branch -D %1$s'.";
201 throw die(String
.format(err
, branch
));
202 } else if (result
== Result
.NEW
)
203 throw die(String
.format("branch '%s' not found.", branch
));
205 out
.println(String
.format("Deleted remote branch %s", branch
));
207 out
.println(String
.format("Deleted branch %s", branch
));