Skip to content

Commit 81011a8

Browse files
committed
Add SSH command to cleanup old ticket branches
1 parent 4219524 commit 81011a8

File tree

5 files changed

+221
-6
lines changed

5 files changed

+221
-6
lines changed

.classpath

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
<classpathentry kind="lib" path="ext/args4j-2.0.26.jar" sourcepath="ext/src/args4j-2.0.26.jar" />
7272
<classpathentry kind="lib" path="ext/jedis-2.3.1.jar" sourcepath="ext/src/jedis-2.3.1.jar" />
7373
<classpathentry kind="lib" path="ext/commons-pool2-2.0.jar" sourcepath="ext/src/commons-pool2-2.0.jar" />
74-
<classpathentry kind="lib" path="ext/pf4j-0.8.0-SNAPSHOT.jar" sourcepath="ext/src/pf4j-0.8.0-SNAPSHOT.jar" />
74+
<classpathentry kind="lib" path="ext/pf4j-0.8.0.jar" sourcepath="ext/src/pf4j-0.8.0.jar" />
7575
<classpathentry kind="lib" path="ext/junit-4.11.jar" sourcepath="ext/src/junit-4.11.jar" />
7676
<classpathentry kind="lib" path="ext/hamcrest-core-1.3.jar" sourcepath="ext/src/hamcrest-core-1.3.jar" />
7777
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" />

.project

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<projectDescription>
33
<name>gitblit-smartticketbranches-plugin</name>
4-
<comment>Remove ticket branches on close and create them on re-open</comment>
4+
<comment>Remove the ticket's branch when it is closed, create the branch when it is re-opened</comment>
55
<projects>
66
</projects>
77
<buildSpec>

README.md

+16-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,22 @@
22

33
*REQUIRES 1.5.0*
44

5-
The Gitblit Smart Ticket Branches plugin deletes the *ticket/N* branches when a ticket is closed and recreates them if a ticket is re-opened.
5+
The Gitblit Smart Ticket Branches plugin provides tools to automatically cleanup closed ticket branches.
6+
7+
1. A ticket hook to delete the *ticket/N* branch on closing a ticket and to re-create it on re-opening a ticket
8+
2. An SSH command to cleanup closed ticket branches
9+
10+
### Usage
11+
12+
#### Ticket Hook
13+
14+
The ticket hook is automatic. Just install the plugin and the hook will manage your ticket branches for you automatically.
15+
16+
#### SSH Command
17+
18+
The SSH command requires administrator permissions.
19+
20+
ssh host stb cleanup ALL|&lt;REPOSITORY&gt; [--dryRun]
621

722
### Building against a Gitblit RELEASE
823

SmartTicketBranches.iml

+3-3
Original file line numberDiff line numberDiff line change
@@ -765,13 +765,13 @@
765765
</library>
766766
</orderEntry>
767767
<orderEntry type="module-library">
768-
<library name="pf4j-0.8.0-SNAPSHOT.jar">
768+
<library name="pf4j-0.8.0.jar">
769769
<CLASSES>
770-
<root url="jar://$MODULE_DIR$/ext/pf4j-0.8.0-SNAPSHOT.jar!/" />
770+
<root url="jar://$MODULE_DIR$/ext/pf4j-0.8.0.jar!/" />
771771
</CLASSES>
772772
<JAVADOC />
773773
<SOURCES>
774-
<root url="jar://$MODULE_DIR$/ext/src/pf4j-0.8.0-SNAPSHOT.jar!/" />
774+
<root url="jar://$MODULE_DIR$/ext/src/pf4j-0.8.0.jar!/" />
775775
</SOURCES>
776776
</library>
777777
</orderEntry>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/*
2+
* Copyright 2014 gitblit.com.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.gitblit.plugin.smartticketbranches;
17+
18+
import java.io.IOException;
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
import java.util.Map;
22+
23+
import org.eclipse.jgit.lib.BatchRefUpdate;
24+
import org.eclipse.jgit.lib.NullProgressMonitor;
25+
import org.eclipse.jgit.lib.ObjectId;
26+
import org.eclipse.jgit.lib.PersonIdent;
27+
import org.eclipse.jgit.lib.Ref;
28+
import org.eclipse.jgit.lib.Repository;
29+
import org.eclipse.jgit.revwalk.RevWalk;
30+
import org.eclipse.jgit.transport.ReceiveCommand;
31+
import org.kohsuke.args4j.Argument;
32+
import org.kohsuke.args4j.Option;
33+
34+
import ro.fortsoft.pf4j.Extension;
35+
36+
import com.gitblit.git.PatchsetCommand;
37+
import com.gitblit.manager.IGitblit;
38+
import com.gitblit.models.RepositoryModel;
39+
import com.gitblit.models.TicketModel.Status;
40+
import com.gitblit.models.UserModel;
41+
import com.gitblit.tickets.ITicketService;
42+
import com.gitblit.tickets.QueryBuilder;
43+
import com.gitblit.tickets.QueryResult;
44+
import com.gitblit.tickets.TicketIndexer.Lucene;
45+
import com.gitblit.transport.ssh.commands.CommandMetaData;
46+
import com.gitblit.transport.ssh.commands.DispatchCommand;
47+
import com.gitblit.transport.ssh.commands.SshCommand;
48+
import com.gitblit.transport.ssh.commands.UsageExample;
49+
import com.gitblit.transport.ssh.commands.UsageExamples;
50+
import com.google.common.collect.Maps;
51+
52+
@Extension
53+
@CommandMetaData(name = "stb", description = "SmartTicketBranches commands", admin = true)
54+
public class SmartTicketBranchesDispatcher extends DispatchCommand {
55+
56+
@Override
57+
protected void setup(UserModel user) {
58+
register(user, CleanupCommand.class);
59+
}
60+
61+
@CommandMetaData(name = "cleanup", description = "Remove ticket branches for closed tickets", admin = true)
62+
@UsageExamples(examples = {
63+
@UsageExample(syntax = "${cmd} gitblit.git --dryRun", description = "Test to see what branches would be removed from gitblit.git"),
64+
@UsageExample(syntax = "${cmd} ALL", description = "Remove all branches for all closed tickets")
65+
})
66+
public static class CleanupCommand extends SshCommand {
67+
68+
@Argument(index = 0, metaVar = "ALL|<REPOSITORY>", required = true, usage = "Repository for cleanup")
69+
String repository;
70+
71+
@Option(name = "--dryRun", usage = "Identify but DO NOT remove ticket branches")
72+
boolean dryRun;
73+
74+
/**
75+
* Remove closed ticket branches.
76+
*/
77+
@Override
78+
public void run() throws Failure {
79+
log.debug("Removing closed ticket branches for {}", repository);
80+
81+
IGitblit gitblit = getContext().getGitblit();
82+
ITicketService tickets = gitblit.getTicketService();
83+
84+
if (tickets == null) {
85+
log.warn("No ticket service is configured!");
86+
return;
87+
}
88+
89+
if (!tickets.isReady()) {
90+
log.warn("Ticket service is not ready!");
91+
return;
92+
}
93+
94+
// query for closed tickets
95+
96+
final QueryBuilder query = new QueryBuilder();
97+
if (!"ALL".equalsIgnoreCase(repository)) {
98+
RepositoryModel r = gitblit.getRepositoryModel(repository);
99+
if (r == null) {
100+
throw new UnloggedFailure(1, String.format("%s is not a repository!", repository));
101+
}
102+
query.and(Lucene.rid.matches(r.getRID()));
103+
} else {
104+
query.and(Lucene.rid.matches("[* TO *]"));
105+
}
106+
query.and(Lucene.status.doesNotMatch(Status.New.toString())).and(Lucene.status.doesNotMatch(Status.Open.toString()));
107+
108+
final String q = query.build();
109+
log.debug(q);
110+
List<QueryResult> closedTickets = tickets.queryFor(q, 0, 0, Lucene.repository.toString(), false);
111+
112+
if (closedTickets.isEmpty()) {
113+
stdout.println(String.format("No closed tickets found for '%s'.", repository));
114+
return;
115+
}
116+
117+
// collate the tickets by repository
118+
Map<String, List<QueryResult>> map = Maps.newTreeMap();
119+
for (QueryResult ticket : closedTickets) {
120+
if (!map.containsKey(ticket.repository)) {
121+
map.put(ticket.repository, new ArrayList<QueryResult>());
122+
}
123+
map.get(ticket.repository).add(ticket);
124+
}
125+
126+
// remove branches for all closed tickets
127+
// group the branch removals for performance
128+
for (Map.Entry<String, List<QueryResult>> entry : map.entrySet()) {
129+
String repo = entry.getKey();
130+
List<QueryResult> list = entry.getValue();
131+
Repository db = gitblit.getRepository(repo);
132+
if (db == null) {
133+
log.warn("failed to find git repository {}", repo);
134+
continue;
135+
}
136+
137+
try {
138+
BatchRefUpdate batch = db.getRefDatabase().newBatchUpdate();
139+
140+
// setup a reflog ident
141+
UserModel user = getContext().getClient().getUser();
142+
PersonIdent ident = new PersonIdent(String.format("%s/%s", user.getDisplayName(), user.username),
143+
user.emailAddress == null ? user.username : user.emailAddress);
144+
batch.setRefLogIdent(ident);
145+
146+
for (QueryResult ticket : list) {
147+
final String branch = PatchsetCommand.getTicketBranch(ticket.number);
148+
try {
149+
Ref ref = db.getRef(branch);
150+
if (ref != null) {
151+
if (dryRun) {
152+
String msg = String.format("would remove %s:%s (%s)",
153+
ticket.repository, branch, ticket.status);
154+
stdout.println(msg);
155+
continue;
156+
}
157+
158+
// queue the branch removal
159+
batch.addCommand(new ReceiveCommand(ref.getObjectId(), ObjectId.zeroId(), branch));
160+
}
161+
} catch (IOException e) {
162+
log.error("SmartTicketBranches plugin", e);
163+
}
164+
}
165+
166+
// apply all queued branch deletions
167+
if (!batch.getCommands().isEmpty()) {
168+
RevWalk rw = new RevWalk(db);
169+
try {
170+
batch.execute(rw, NullProgressMonitor.INSTANCE);
171+
for (ReceiveCommand cmd : batch.getCommands()) {
172+
switch (cmd.getResult()) {
173+
case OK:
174+
String successMsg = String.format("removed %s:%s",
175+
repo, cmd.getRefName());
176+
log.info(successMsg);
177+
stdout.println(successMsg);
178+
break;
179+
default:
180+
String errorMsg = String.format("failed to remove %s:%s (%s)",
181+
repo, cmd.getRefName(), cmd.getResult());
182+
log.error(errorMsg);
183+
stdout.println(errorMsg);
184+
break;
185+
}
186+
}
187+
} finally {
188+
rw.release();
189+
}
190+
}
191+
} catch (IOException e) {
192+
log.error("SmartTicketBranches plugin", e);
193+
} finally {
194+
db.close();
195+
}
196+
}
197+
}
198+
}
199+
}
200+

0 commit comments

Comments
 (0)