Skip to content

Commit 061d533

Browse files
jujokinigladhorn
authored andcommitted
Add new build ssh command
Fixes: QTBI-1545 Change-Id: I2da62e83eda43fe9127cef238a3c82cf46171202 Reviewed-by: Frederik Gladhorn <[email protected]>
1 parent be31ec0 commit 061d533

File tree

6 files changed

+552
-2
lines changed

6 files changed

+552
-2
lines changed

qt-gerrit-ui-plugin/qt-gerrit-ui-plugin.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,17 @@
8686
link_elem.href = '/q/status:staged';
8787
ul_elem.insertBefore(li_elem, ul_elem.children[2]);
8888

89+
li_elem = htmlToElement(ul_elem.children[1].outerHTML);
90+
link_elem = li_elem.children[0].children[1];
91+
link_elem.text = 'Integrating';
92+
link_elem.href = '/q/status:integrating';
93+
ul_elem.insertBefore(li_elem, ul_elem.children[3]);
8994

9095
li_elem = htmlToElement(ul_elem.children[1].outerHTML);
9196
link_elem = li_elem.children[0].children[1];
9297
link_elem.text = 'Deferred';
9398
link_elem.href = '/q/status:deferred';
94-
ul_elem.insertBefore(li_elem, ul_elem.children[4]);
99+
ul_elem.insertBefore(li_elem, ul_elem.children[5]);
95100
});
96101

97102
// Customize change view
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
//
2+
// Copyright (C) 2019 The Qt Company
3+
//
4+
5+
package com.googlesource.gerrit.plugins.qtcodereview;
6+
7+
import com.google.common.flogger.FluentLogger;
8+
import com.google.gerrit.extensions.restapi.AuthException;
9+
import com.google.gerrit.extensions.restapi.RestApiException;
10+
import com.google.gerrit.reviewdb.client.Branch;
11+
import com.google.gerrit.reviewdb.client.Change;
12+
import com.google.gerrit.reviewdb.client.PatchSet;
13+
import com.google.gerrit.reviewdb.client.Project;
14+
import com.google.gerrit.reviewdb.server.ReviewDb;
15+
import com.google.gerrit.server.InternalUser;
16+
import com.google.gerrit.server.git.GitRepositoryManager;
17+
import com.google.gerrit.server.permissions.PermissionBackend;
18+
import com.google.gerrit.server.permissions.PermissionBackendException;
19+
import com.google.gerrit.server.permissions.ProjectPermission;
20+
import com.google.gerrit.server.permissions.RefPermission;
21+
import com.google.gerrit.server.project.NoSuchProjectException;
22+
import com.google.gerrit.server.project.NoSuchRefException;
23+
import com.google.gerrit.server.query.change.ChangeData;
24+
import com.google.gerrit.server.update.BatchUpdate;
25+
import com.google.gerrit.server.update.UpdateException;
26+
import com.google.gerrit.server.util.time.TimeUtil;
27+
import com.google.gerrit.sshd.SshCommand;
28+
import com.google.gerrit.sshd.CommandMetaData;
29+
30+
import com.google.gwtorm.server.OrmException;
31+
32+
import com.google.inject.Inject;
33+
import com.google.inject.Provider;
34+
35+
import org.eclipse.jgit.errors.MissingObjectException;
36+
import org.eclipse.jgit.errors.RepositoryNotFoundException;
37+
import org.eclipse.jgit.lib.RefUpdate.Result;
38+
import org.eclipse.jgit.lib.Repository;
39+
import org.eclipse.jgit.revwalk.RevCommit;
40+
import org.kohsuke.args4j.Option;
41+
42+
import java.io.IOException;
43+
import java.util.List;
44+
import java.util.Map.Entry;
45+
46+
@CommandMetaData(name="staging-new-build", description="Create unique build branch of the current staging branch and change the gerrit status of the changes to INTEGRATING.")
47+
class QtCommandNewBuild extends SshCommand {
48+
49+
@Inject
50+
private PermissionBackend permissionBackend;
51+
52+
@Inject
53+
private GitRepositoryManager gitManager;
54+
55+
@Inject
56+
private Provider<ReviewDb> dbProvider;
57+
58+
@Inject
59+
private BatchUpdate.Factory updateFactory;
60+
61+
@Inject
62+
private QtUtil qtUtil;
63+
64+
@Inject
65+
private QtChangeUpdateOp.Factory qtUpdateFactory;
66+
67+
@Option(name = "--project", aliases = {"-p"},
68+
required = true, usage = "project name")
69+
private String project;
70+
71+
@Option(name = "--staging-branch", aliases = {"-s"},
72+
required = true, usage = "branch name, e.g. refs/staging/master or just master")
73+
private String stagingBranch;
74+
75+
@Option(name = "--build-id", aliases = {"-i"},
76+
required = true, usage = "build id, e.g. refs/builds/my_build or just my_build")
77+
private String build;
78+
79+
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
80+
81+
private Repository git;
82+
83+
84+
@Override
85+
protected void run() throws UnloggedFailure {
86+
87+
logger.atInfo().log("qtcodereview: staging-new-build -p %s -s %s -i %s", project, stagingBranch, build);
88+
89+
try {
90+
Project.NameKey projectKey = new Project.NameKey(project);
91+
git = gitManager.openRepository(projectKey);
92+
93+
Branch.NameKey buildBranchKey = QtUtil.getNameKeyLong(project, QtUtil.R_BUILDS, build);
94+
Branch.NameKey stagingBranchKey = QtUtil.getNameKeyLong(project, QtUtil.R_STAGING, stagingBranch);
95+
Branch.NameKey destBranchShortKey = QtUtil.getNameKeyShort(project, QtUtil.R_STAGING, stagingBranch);
96+
Branch.NameKey destinationKey = QtUtil.getNameKeyLong(project, QtUtil.R_HEADS, stagingBranch);
97+
98+
// Check required permissions
99+
permissionBackend.user(user).project(projectKey).ref(destinationKey.get()).check(RefPermission.UPDATE);
100+
permissionBackend.user(user).project(projectKey).ref(buildBranchKey.get()).check(RefPermission.CREATE);
101+
102+
if (QtUtil.branchExists(git, buildBranchKey) == true) {
103+
logger.atSevere().log("qtcodereview: staging-new-build Target build %s already exists", buildBranchKey);
104+
throw die("Target build already exists!");
105+
}
106+
107+
if (QtUtil.branchExists(git, stagingBranchKey) == false) {
108+
logger.atSevere().log("qtcodereview: staging-new-build staging ref %s not found", stagingBranchKey);
109+
throw die("Staging ref not found!");
110+
}
111+
112+
// Create build reference.
113+
Result result = qtUtil.createBuildRef(git, user.asIdentifiedUser(),
114+
projectKey, stagingBranchKey, buildBranchKey);
115+
String message = String.format("Added to build %s for %s", build, destinationKey);
116+
117+
if (result != Result.NEW && result != Result.FAST_FORWARD) {
118+
logger.atSevere().log("qtcodereview: staging-new-build failed to create new build ref %s result %s",
119+
buildBranchKey, result);
120+
throw new UnloggedFailure(1, "fatal: failed to create new build ref: " + result);
121+
} else {
122+
// list the changes in staging branch but missing from the destination branch
123+
List<Entry<ChangeData, RevCommit>> openChanges = qtUtil.listChangesNotMerged(git, buildBranchKey, destBranchShortKey);
124+
125+
// Make sure that there are changes in the staging branch.
126+
if (openChanges.isEmpty()) {
127+
logger.atSevere().log("qtcodereview: staging-new-build No changes in staging branch %s.", stagingBranchKey);
128+
throw die("No changes in staging branch. Not creating a build reference");
129+
}
130+
131+
QtChangeUpdateOp op = qtUpdateFactory.create(Change.Status.INTEGRATING, message, null, null, null);
132+
try (BatchUpdate u = updateFactory.create(dbProvider.get(), projectKey, user, TimeUtil.nowTs())) {
133+
for (Entry<ChangeData, RevCommit> item: openChanges) {
134+
Change change = item.getKey().change();
135+
logger.atInfo().log("qtcodereview: staging-new-build inserted change %s (%s) into build %s for %s",
136+
change, item.getValue().toString(), build, destinationKey);
137+
u.addOp(change.getId(), op);
138+
}
139+
u.execute();
140+
}
141+
}
142+
143+
logger.atInfo().log("qtcodereview: staging-new-build build %s for %s created", build, destBranchShortKey);
144+
145+
} catch (AuthException e) {
146+
logger.atSevere().log("qtcodereview: staging-new-build Authentication failed to access repository: %s", e);
147+
throw die("Authentication failed to access repository");
148+
} catch (PermissionBackendException e) {
149+
logger.atSevere().log("qtcodereview: staging-new-build Not enough permissions to access repository %s", e);
150+
throw die("Not enough permissions to access repository");
151+
} catch (RepositoryNotFoundException e) {
152+
throw die("project not found");
153+
} catch (IOException e) {
154+
logger.atSevere().log("qtcodereview: staging-new-build Failed to access repository %s", e);
155+
throw die("Failed to access repository");
156+
} catch (OrmException e) {
157+
logger.atSevere().log("qtcodereview: staging-new-build Failed to access database %s", e);
158+
throw die("Failed to access database");
159+
} catch (QtUtil.BranchNotFoundException e) {
160+
logger.atSevere().log("qtcodereview: staging-new-build Failed to access build or staging ref %s", e);
161+
throw die("Failed to access build or staging ref");
162+
} catch (NoSuchRefException e) {
163+
logger.atSevere().log("qtcodereview: staging-new-build Invalid branch name %s", e);
164+
throw die("Invalid branch name");
165+
} catch (UpdateException | RestApiException e) {
166+
logger.atSevere().log("qtcodereview: staging-new-build failed to update change status %s", e);
167+
throw die("Failed to update change status");
168+
} finally {
169+
if (git != null) {
170+
git.close();
171+
}
172+
}
173+
}
174+
175+
}

src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtSshModule.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright (C) 2018 The Qt Company
2+
// Copyright (C) 2019 The Qt Company
33
//
44

55
package com.googlesource.gerrit.plugins.qtcodereview;
@@ -11,5 +11,6 @@ class QtSshModule extends PluginCommandModule {
1111
@Override
1212
protected void configureCommands() {
1313
command(QtCommandPing.class);
14+
command(QtCommandNewBuild.class);
1415
}
1516
}

src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtUtil.java

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,14 @@
4848
import org.eclipse.jgit.merge.ThreeWayMerger;
4949
import org.eclipse.jgit.revwalk.RevCommit;
5050
import org.eclipse.jgit.revwalk.RevWalk;
51+
import org.eclipse.jgit.transport.ReceiveCommand;
5152

5253
import java.io.IOException;
54+
import java.util.AbstractMap;
5355
import java.util.ArrayList;
56+
import java.util.Iterator;
5457
import java.util.List;
58+
import java.util.Map;
5559

5660

5761
/**
@@ -64,6 +68,7 @@ public class QtUtil {
6468

6569
public static final String R_HEADS = "refs/heads/";
6670
public static final String R_STAGING = "refs/staging/";
71+
public static final String R_BUILDS = "refs/builds/";
6772

6873
private final Provider<InternalChangeQuery> queryProvider;
6974
private final GitReferenceUpdated referenceUpdated;
@@ -85,6 +90,13 @@ public MergeConflictException(final String message) {
8590
}
8691
}
8792

93+
public static class BranchNotFoundException extends Exception {
94+
private static final long serialVersionUID = 1L;
95+
public BranchNotFoundException(final String message) {
96+
super(message);
97+
}
98+
}
99+
88100
public static Project.NameKey getProjectKey(final String project) {
89101
String projectName = project;
90102
if (project.endsWith(Constants.DOT_GIT_EXT)) {
@@ -93,6 +105,24 @@ public static Project.NameKey getProjectKey(final String project) {
93105
return new Project.NameKey(projectName);
94106
}
95107

108+
/**
109+
* Creates a branch key including ref prefix.
110+
* @param project Project for the branch key.
111+
* @param prefix Expected prefix.
112+
* @param branch Branch name with or without prefix.
113+
* @return Branch name key with prefix.
114+
*/
115+
public static Branch.NameKey getNameKeyLong(final String project,
116+
final String prefix,
117+
final String branch) {
118+
final Project.NameKey projectKey = getProjectKey(project);
119+
if (branch.startsWith(prefix)) {
120+
return new Branch.NameKey(projectKey, branch);
121+
} else {
122+
return new Branch.NameKey(projectKey, prefix + branch);
123+
}
124+
}
125+
96126
/**
97127
* Creates a branch key without any prefix.
98128
* @param project Project for the branch key.
@@ -111,6 +141,11 @@ public static Branch.NameKey getNameKeyShort(final String project,
111141
}
112142
}
113143

144+
public static boolean branchExists(Repository git, final Branch.NameKey branch)
145+
throws IOException {
146+
return git.getRefDatabase().getRef(branch.get()) != null;
147+
}
148+
114149
/**
115150
* Gets a staging branch for a branch.
116151
* @param branch Branch under refs/heads. E.g. refs/heads/master. Can be short
@@ -152,6 +187,51 @@ public static Result createStagingBranch(Repository git,
152187
}
153188
}
154189

190+
/**
191+
* Creates a build ref. Build refs are stored under refs/builds.
192+
*
193+
* @param git Git repository.
194+
* @param stagingBranch Staging branch to create the build ref from. Can be
195+
* short name.
196+
* @param newBranch Build ref name, under refs/builds. Can be short name.
197+
* @return
198+
* @throws IOException
199+
* @throws NoSuchRefException
200+
*/
201+
public Result createBuildRef(Repository git,
202+
IdentifiedUser user,
203+
final Project.NameKey projectKey,
204+
final Branch.NameKey stagingBranch,
205+
final Branch.NameKey newBranch)
206+
throws IOException, NoSuchRefException {
207+
final String stagingBranchName;
208+
if (stagingBranch.get().startsWith(R_STAGING)) {
209+
stagingBranchName = stagingBranch.get();
210+
} else {
211+
stagingBranchName = R_STAGING + stagingBranch.get();
212+
}
213+
214+
final String buildBranchName;
215+
if (newBranch.get().startsWith(R_BUILDS)) {
216+
buildBranchName = newBranch.get();
217+
} else {
218+
buildBranchName = R_BUILDS + newBranch.get();
219+
}
220+
221+
Ref sourceRef = git.getRefDatabase().getRef(stagingBranchName);
222+
if (sourceRef == null) { throw new NoSuchRefException(stagingBranchName); }
223+
224+
RefUpdate refUpdate = git.updateRef(buildBranchName);
225+
refUpdate.setNewObjectId(sourceRef.getObjectId());
226+
refUpdate.setForceUpdate(false);
227+
RefUpdate.Result result = refUpdate.update();
228+
229+
// send ref created event
230+
referenceUpdated.fire(projectKey, refUpdate, ReceiveCommand.Type.CREATE, user.state());
231+
232+
return result;
233+
}
234+
155235
private static Result updateRef(Repository git,
156236
final String ref,
157237
final String newValue,
@@ -305,6 +385,52 @@ public void rebuildStagingBranch(Repository git,
305385
}
306386
}
307387

388+
/**
389+
* Lists not merged changes between branches.
390+
* @param git jGit Repository. Must be open.
391+
* @param db ReviewDb of a Gerrit site.
392+
* @param branch Branch to search for the changes.
393+
* @param destination Destination branch for changes.
394+
* @return List of not merged changes.
395+
* @throws IOException Thrown by Repository or RevWalk if repository is not
396+
* accessible.
397+
* @throws OrmException Thrown if ReviewDb is not accessible.
398+
*/
399+
public List<Map.Entry<ChangeData,RevCommit>> listChangesNotMerged(Repository git,
400+
final Branch.NameKey branch,
401+
final Branch.NameKey destination)
402+
throws IOException, OrmException,
403+
BranchNotFoundException {
404+
405+
List<Map.Entry<ChangeData, RevCommit>> result = new ArrayList<Map.Entry<ChangeData, RevCommit>>();
406+
RevWalk revWalk = new RevWalk(git);
407+
408+
try {
409+
Ref ref = git.getRefDatabase().getRef(branch.get());
410+
if (ref == null) throw new BranchNotFoundException("No such branch: " + branch);
411+
Ref refDest = git.getRefDatabase().getRef(destination.get());
412+
if (refDest == null) throw new BranchNotFoundException("No such branch: " + destination);
413+
RevCommit firstCommit = revWalk.parseCommit(ref.getObjectId());
414+
revWalk.markStart(firstCommit);
415+
// Destination is the walker end point
416+
revWalk.markUninteresting(revWalk.parseCommit(refDest.getObjectId()));
417+
418+
Iterator<RevCommit> i = revWalk.iterator();
419+
while (i.hasNext()) {
420+
RevCommit commit = i.next();
421+
List<ChangeData> changes = queryProvider.get().byBranchCommit(destination, commit.name());
422+
if (changes != null && !changes.isEmpty()) {
423+
if (changes.size() > 1) logger.atWarning().log("qtcodereview: commit belongs to multiple changes: %s", commit.name());
424+
ChangeData cd = changes.get(0);
425+
result.add(new AbstractMap.SimpleEntry<ChangeData,RevCommit>(cd, commit));
426+
}
427+
}
428+
} finally {
429+
revWalk.dispose();
430+
}
431+
return result;
432+
}
433+
308434
public static RevCommit merge(PersonIdent committerIdent,
309435
Repository git,
310436
ObjectInserter objInserter,

0 commit comments

Comments
 (0)