
* stable-2.15: LogFileCompressor: Do not compress live logfile error_log.json Specify new tip of the branch when updating submodules Change-Id: I4dedeefda7b448fc1f0b2f9f78186a5540fb2e20
683 lines
24 KiB
Java
683 lines
24 KiB
Java
// Copyright (C) 2011 The Android Open Source Project
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package com.google.gerrit.server.git;
|
|
|
|
import com.google.common.collect.ImmutableSet;
|
|
import com.google.common.collect.MultimapBuilder;
|
|
import com.google.common.collect.SetMultimap;
|
|
import com.google.gerrit.common.data.SubscribeSection;
|
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
|
import com.google.gerrit.reviewdb.client.Branch;
|
|
import com.google.gerrit.reviewdb.client.Project;
|
|
import com.google.gerrit.reviewdb.client.RefNames;
|
|
import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
|
|
import com.google.gerrit.server.GerritPersonIdent;
|
|
import com.google.gerrit.server.config.GerritServerConfig;
|
|
import com.google.gerrit.server.config.VerboseSuperprojectUpdate;
|
|
import com.google.gerrit.server.git.MergeOpRepoManager.OpenRepo;
|
|
import com.google.gerrit.server.project.NoSuchProjectException;
|
|
import com.google.gerrit.server.project.ProjectCache;
|
|
import com.google.gerrit.server.project.ProjectState;
|
|
import com.google.gerrit.server.update.BatchUpdate;
|
|
import com.google.gerrit.server.update.BatchUpdateListener;
|
|
import com.google.gerrit.server.update.RepoContext;
|
|
import com.google.gerrit.server.update.RepoOnlyOp;
|
|
import com.google.gerrit.server.update.UpdateException;
|
|
import com.google.inject.Inject;
|
|
import com.google.inject.Singleton;
|
|
import java.io.IOException;
|
|
import java.util.ArrayDeque;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.Deque;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.LinkedHashSet;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
import org.apache.commons.lang.StringUtils;
|
|
import org.eclipse.jgit.dircache.DirCache;
|
|
import org.eclipse.jgit.dircache.DirCacheBuilder;
|
|
import org.eclipse.jgit.dircache.DirCacheEditor;
|
|
import org.eclipse.jgit.dircache.DirCacheEditor.DeletePath;
|
|
import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
|
|
import org.eclipse.jgit.dircache.DirCacheEntry;
|
|
import org.eclipse.jgit.lib.CommitBuilder;
|
|
import org.eclipse.jgit.lib.Config;
|
|
import org.eclipse.jgit.lib.FileMode;
|
|
import org.eclipse.jgit.lib.ObjectId;
|
|
import org.eclipse.jgit.lib.PersonIdent;
|
|
import org.eclipse.jgit.lib.Ref;
|
|
import org.eclipse.jgit.revwalk.RevCommit;
|
|
import org.eclipse.jgit.revwalk.RevWalk;
|
|
import org.eclipse.jgit.transport.RefSpec;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
public class SubmoduleOp {
|
|
|
|
/** Only used for branches without code review changes */
|
|
public class GitlinkOp implements RepoOnlyOp {
|
|
private final Branch.NameKey branch;
|
|
|
|
GitlinkOp(Branch.NameKey branch) {
|
|
this.branch = branch;
|
|
}
|
|
|
|
@Override
|
|
public void updateRepo(RepoContext ctx) throws Exception {
|
|
CodeReviewCommit c = composeGitlinksCommit(branch);
|
|
if (c != null) {
|
|
ctx.addRefUpdate(c.getParent(0), c, branch.get());
|
|
addBranchTip(branch, c);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Singleton
|
|
public static class Factory {
|
|
private final GitModules.Factory gitmodulesFactory;
|
|
private final PersonIdent myIdent;
|
|
private final Config cfg;
|
|
private final ProjectCache projectCache;
|
|
private final ProjectState.Factory projectStateFactory;
|
|
private final BatchUpdate.Factory batchUpdateFactory;
|
|
|
|
@Inject
|
|
Factory(
|
|
GitModules.Factory gitmodulesFactory,
|
|
@GerritPersonIdent PersonIdent myIdent,
|
|
@GerritServerConfig Config cfg,
|
|
ProjectCache projectCache,
|
|
ProjectState.Factory projectStateFactory,
|
|
BatchUpdate.Factory batchUpdateFactory) {
|
|
this.gitmodulesFactory = gitmodulesFactory;
|
|
this.myIdent = myIdent;
|
|
this.cfg = cfg;
|
|
this.projectCache = projectCache;
|
|
this.projectStateFactory = projectStateFactory;
|
|
this.batchUpdateFactory = batchUpdateFactory;
|
|
}
|
|
|
|
public SubmoduleOp create(Set<Branch.NameKey> updatedBranches, MergeOpRepoManager orm)
|
|
throws SubmoduleException {
|
|
return new SubmoduleOp(
|
|
gitmodulesFactory,
|
|
myIdent,
|
|
cfg,
|
|
projectCache,
|
|
projectStateFactory,
|
|
batchUpdateFactory,
|
|
updatedBranches,
|
|
orm);
|
|
}
|
|
}
|
|
|
|
private static final Logger log = LoggerFactory.getLogger(SubmoduleOp.class);
|
|
|
|
private final GitModules.Factory gitmodulesFactory;
|
|
private final PersonIdent myIdent;
|
|
private final ProjectCache projectCache;
|
|
private final ProjectState.Factory projectStateFactory;
|
|
private final BatchUpdate.Factory batchUpdateFactory;
|
|
private final VerboseSuperprojectUpdate verboseSuperProject;
|
|
private final boolean enableSuperProjectSubscriptions;
|
|
private final long maxCombinedCommitMessageSize;
|
|
private final long maxCommitMessages;
|
|
private final MergeOpRepoManager orm;
|
|
private final Map<Branch.NameKey, GitModules> branchGitModules;
|
|
|
|
// always update-to-current branch tips during submit process
|
|
private final Map<Branch.NameKey, CodeReviewCommit> branchTips;
|
|
// branches for all the submitting changes
|
|
private final Set<Branch.NameKey> updatedBranches;
|
|
// branches which in either a submodule or a superproject
|
|
private final Set<Branch.NameKey> affectedBranches;
|
|
// sorted version of affectedBranches
|
|
private final ImmutableSet<Branch.NameKey> sortedBranches;
|
|
// map of superproject branch and its submodule subscriptions
|
|
private final SetMultimap<Branch.NameKey, SubmoduleSubscription> targets;
|
|
// map of superproject and its branches which has submodule subscriptions
|
|
private final SetMultimap<Project.NameKey, Branch.NameKey> branchesByProject;
|
|
|
|
private SubmoduleOp(
|
|
GitModules.Factory gitmodulesFactory,
|
|
PersonIdent myIdent,
|
|
Config cfg,
|
|
ProjectCache projectCache,
|
|
ProjectState.Factory projectStateFactory,
|
|
BatchUpdate.Factory batchUpdateFactory,
|
|
Set<Branch.NameKey> updatedBranches,
|
|
MergeOpRepoManager orm)
|
|
throws SubmoduleException {
|
|
this.gitmodulesFactory = gitmodulesFactory;
|
|
this.myIdent = myIdent;
|
|
this.projectCache = projectCache;
|
|
this.projectStateFactory = projectStateFactory;
|
|
this.batchUpdateFactory = batchUpdateFactory;
|
|
this.verboseSuperProject =
|
|
cfg.getEnum("submodule", null, "verboseSuperprojectUpdate", VerboseSuperprojectUpdate.TRUE);
|
|
this.enableSuperProjectSubscriptions =
|
|
cfg.getBoolean("submodule", "enableSuperProjectSubscriptions", true);
|
|
this.maxCombinedCommitMessageSize =
|
|
cfg.getLong("submodule", "maxCombinedCommitMessageSize", 256 << 10);
|
|
this.maxCommitMessages = cfg.getLong("submodule", "maxCommitMessages", 1000);
|
|
this.orm = orm;
|
|
this.updatedBranches = updatedBranches;
|
|
this.targets = MultimapBuilder.hashKeys().hashSetValues().build();
|
|
this.affectedBranches = new HashSet<>();
|
|
this.branchTips = new HashMap<>();
|
|
this.branchGitModules = new HashMap<>();
|
|
this.branchesByProject = MultimapBuilder.hashKeys().hashSetValues().build();
|
|
this.sortedBranches = calculateSubscriptionMap();
|
|
}
|
|
|
|
private ImmutableSet<Branch.NameKey> calculateSubscriptionMap() throws SubmoduleException {
|
|
if (!enableSuperProjectSubscriptions) {
|
|
logDebug("Updating superprojects disabled");
|
|
return null;
|
|
}
|
|
|
|
logDebug("Calculating superprojects - submodules map");
|
|
LinkedHashSet<Branch.NameKey> allVisited = new LinkedHashSet<>();
|
|
for (Branch.NameKey updatedBranch : updatedBranches) {
|
|
if (allVisited.contains(updatedBranch)) {
|
|
continue;
|
|
}
|
|
|
|
searchForSuperprojects(updatedBranch, new LinkedHashSet<Branch.NameKey>(), allVisited);
|
|
}
|
|
|
|
// Since the searchForSuperprojects will add all branches (related or
|
|
// unrelated) and ensure the superproject's branches get added first before
|
|
// a submodule branch. Need remove all unrelated branches and reverse
|
|
// the order.
|
|
allVisited.retainAll(affectedBranches);
|
|
reverse(allVisited);
|
|
return ImmutableSet.copyOf(allVisited);
|
|
}
|
|
|
|
private void searchForSuperprojects(
|
|
Branch.NameKey current,
|
|
LinkedHashSet<Branch.NameKey> currentVisited,
|
|
LinkedHashSet<Branch.NameKey> allVisited)
|
|
throws SubmoduleException {
|
|
logDebug("Now processing " + current);
|
|
|
|
if (currentVisited.contains(current)) {
|
|
throw new SubmoduleException(
|
|
"Branch level circular subscriptions detected: "
|
|
+ printCircularPath(currentVisited, current));
|
|
}
|
|
|
|
if (allVisited.contains(current)) {
|
|
return;
|
|
}
|
|
|
|
currentVisited.add(current);
|
|
try {
|
|
Collection<SubmoduleSubscription> subscriptions =
|
|
superProjectSubscriptionsForSubmoduleBranch(current);
|
|
for (SubmoduleSubscription sub : subscriptions) {
|
|
Branch.NameKey superBranch = sub.getSuperProject();
|
|
searchForSuperprojects(superBranch, currentVisited, allVisited);
|
|
targets.put(superBranch, sub);
|
|
branchesByProject.put(superBranch.getParentKey(), superBranch);
|
|
affectedBranches.add(superBranch);
|
|
affectedBranches.add(sub.getSubmodule());
|
|
}
|
|
} catch (IOException e) {
|
|
throw new SubmoduleException("Cannot find superprojects for " + current, e);
|
|
}
|
|
currentVisited.remove(current);
|
|
allVisited.add(current);
|
|
}
|
|
|
|
private static <T> void reverse(LinkedHashSet<T> set) {
|
|
if (set == null) {
|
|
return;
|
|
}
|
|
|
|
Deque<T> q = new ArrayDeque<>(set);
|
|
set.clear();
|
|
|
|
while (!q.isEmpty()) {
|
|
set.add(q.removeLast());
|
|
}
|
|
}
|
|
|
|
private <T> String printCircularPath(LinkedHashSet<T> p, T target) {
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.append(target);
|
|
ArrayList<T> reverseP = new ArrayList<>(p);
|
|
Collections.reverse(reverseP);
|
|
for (T t : reverseP) {
|
|
sb.append("->");
|
|
sb.append(t);
|
|
if (t.equals(target)) {
|
|
break;
|
|
}
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
private Collection<Branch.NameKey> getDestinationBranches(Branch.NameKey src, SubscribeSection s)
|
|
throws IOException {
|
|
Collection<Branch.NameKey> ret = new HashSet<>();
|
|
logDebug("Inspecting SubscribeSection " + s);
|
|
for (RefSpec r : s.getMatchingRefSpecs()) {
|
|
logDebug("Inspecting [matching] ref " + r);
|
|
if (!r.matchSource(src.get())) {
|
|
continue;
|
|
}
|
|
if (r.isWildcard()) {
|
|
// refs/heads/*[:refs/somewhere/*]
|
|
ret.add(new Branch.NameKey(s.getProject(), r.expandFromSource(src.get()).getDestination()));
|
|
} else {
|
|
// e.g. refs/heads/master[:refs/heads/stable]
|
|
String dest = r.getDestination();
|
|
if (dest == null) {
|
|
dest = r.getSource();
|
|
}
|
|
ret.add(new Branch.NameKey(s.getProject(), dest));
|
|
}
|
|
}
|
|
|
|
for (RefSpec r : s.getMultiMatchRefSpecs()) {
|
|
logDebug("Inspecting [all] ref " + r);
|
|
if (!r.matchSource(src.get())) {
|
|
continue;
|
|
}
|
|
OpenRepo or;
|
|
try {
|
|
or = orm.getRepo(s.getProject());
|
|
} catch (NoSuchProjectException e) {
|
|
// A project listed a non existent project to be allowed
|
|
// to subscribe to it. Allow this for now, i.e. no exception is
|
|
// thrown.
|
|
continue;
|
|
}
|
|
|
|
for (Ref ref : or.repo.getRefDatabase().getRefs(RefNames.REFS_HEADS).values()) {
|
|
if (r.getDestination() != null && !r.matchDestination(ref.getName())) {
|
|
continue;
|
|
}
|
|
Branch.NameKey b = new Branch.NameKey(s.getProject(), ref.getName());
|
|
if (!ret.contains(b)) {
|
|
ret.add(b);
|
|
}
|
|
}
|
|
}
|
|
logDebug("Returning possible branches: " + ret + "for project " + s.getProject());
|
|
return ret;
|
|
}
|
|
|
|
public Collection<SubmoduleSubscription> superProjectSubscriptionsForSubmoduleBranch(
|
|
Branch.NameKey srcBranch) throws IOException {
|
|
logDebug("Calculating possible superprojects for " + srcBranch);
|
|
Collection<SubmoduleSubscription> ret = new ArrayList<>();
|
|
Project.NameKey srcProject = srcBranch.getParentKey();
|
|
ProjectConfig cfg = projectCache.get(srcProject).getConfig();
|
|
for (SubscribeSection s : projectStateFactory.create(cfg).getSubscribeSections(srcBranch)) {
|
|
logDebug("Checking subscribe section " + s);
|
|
Collection<Branch.NameKey> branches = getDestinationBranches(srcBranch, s);
|
|
for (Branch.NameKey targetBranch : branches) {
|
|
Project.NameKey targetProject = targetBranch.getParentKey();
|
|
try {
|
|
OpenRepo or = orm.getRepo(targetProject);
|
|
ObjectId id = or.repo.resolve(targetBranch.get());
|
|
if (id == null) {
|
|
logDebug("The branch " + targetBranch + " doesn't exist.");
|
|
continue;
|
|
}
|
|
} catch (NoSuchProjectException e) {
|
|
logDebug("The project " + targetProject + " doesn't exist");
|
|
continue;
|
|
}
|
|
|
|
GitModules m = branchGitModules.get(targetBranch);
|
|
if (m == null) {
|
|
m = gitmodulesFactory.create(targetBranch, orm);
|
|
branchGitModules.put(targetBranch, m);
|
|
}
|
|
ret.addAll(m.subscribedTo(srcBranch));
|
|
}
|
|
}
|
|
logDebug("Calculated superprojects for " + srcBranch + " are " + ret);
|
|
return ret;
|
|
}
|
|
|
|
public void updateSuperProjects() throws SubmoduleException {
|
|
ImmutableSet<Project.NameKey> projects = getProjectsInOrder();
|
|
if (projects == null) {
|
|
return;
|
|
}
|
|
|
|
LinkedHashSet<Project.NameKey> superProjects = new LinkedHashSet<>();
|
|
try {
|
|
for (Project.NameKey project : projects) {
|
|
// only need superprojects
|
|
if (branchesByProject.containsKey(project)) {
|
|
superProjects.add(project);
|
|
// get a new BatchUpdate for the super project
|
|
OpenRepo or = orm.getRepo(project);
|
|
for (Branch.NameKey branch : branchesByProject.get(project)) {
|
|
addOp(or.getUpdate(), branch);
|
|
}
|
|
}
|
|
}
|
|
batchUpdateFactory.execute(
|
|
orm.batchUpdates(superProjects), BatchUpdateListener.NONE, orm.getSubmissionId(), false);
|
|
} catch (RestApiException | UpdateException | IOException | NoSuchProjectException e) {
|
|
throw new SubmoduleException("Cannot update gitlinks", e);
|
|
}
|
|
}
|
|
|
|
/** Create a separate gitlink commit */
|
|
public CodeReviewCommit composeGitlinksCommit(Branch.NameKey subscriber)
|
|
throws IOException, SubmoduleException {
|
|
OpenRepo or;
|
|
try {
|
|
or = orm.getRepo(subscriber.getParentKey());
|
|
} catch (NoSuchProjectException | IOException e) {
|
|
throw new SubmoduleException("Cannot access superproject", e);
|
|
}
|
|
|
|
CodeReviewCommit currentCommit;
|
|
if (branchTips.containsKey(subscriber)) {
|
|
currentCommit = branchTips.get(subscriber);
|
|
} else {
|
|
Ref r = or.repo.exactRef(subscriber.get());
|
|
if (r == null) {
|
|
throw new SubmoduleException(
|
|
"The branch was probably deleted from the subscriber repository");
|
|
}
|
|
currentCommit = or.rw.parseCommit(r.getObjectId());
|
|
addBranchTip(subscriber, currentCommit);
|
|
}
|
|
|
|
StringBuilder msgbuf = new StringBuilder("");
|
|
PersonIdent author = null;
|
|
DirCache dc = readTree(or.rw, currentCommit);
|
|
DirCacheEditor ed = dc.editor();
|
|
for (SubmoduleSubscription s : targets.get(subscriber)) {
|
|
RevCommit newCommit = updateSubmodule(dc, ed, msgbuf, s);
|
|
if (newCommit != null) {
|
|
if (author == null) {
|
|
author = newCommit.getAuthorIdent();
|
|
} else if (!author.equals(newCommit.getAuthorIdent())) {
|
|
author = myIdent;
|
|
}
|
|
}
|
|
}
|
|
ed.finish();
|
|
ObjectId newTreeId = dc.writeTree(or.ins);
|
|
|
|
// Gitlinks are already in the branch, return null
|
|
if (newTreeId.equals(currentCommit.getTree())) {
|
|
return null;
|
|
}
|
|
CommitBuilder commit = new CommitBuilder();
|
|
commit.setTreeId(newTreeId);
|
|
commit.setParentId(currentCommit);
|
|
StringBuilder commitMsg = new StringBuilder("Update git submodules\n\n");
|
|
if (verboseSuperProject != VerboseSuperprojectUpdate.FALSE) {
|
|
commitMsg.append(msgbuf);
|
|
}
|
|
commit.setMessage(commitMsg.toString());
|
|
commit.setAuthor(author);
|
|
commit.setCommitter(myIdent);
|
|
ObjectId id = or.ins.insert(commit);
|
|
return or.rw.parseCommit(id);
|
|
}
|
|
|
|
/** Amend an existing commit with gitlink updates */
|
|
public CodeReviewCommit composeGitlinksCommit(
|
|
Branch.NameKey subscriber, CodeReviewCommit currentCommit)
|
|
throws IOException, SubmoduleException {
|
|
OpenRepo or;
|
|
try {
|
|
or = orm.getRepo(subscriber.getParentKey());
|
|
} catch (NoSuchProjectException | IOException e) {
|
|
throw new SubmoduleException("Cannot access superproject", e);
|
|
}
|
|
|
|
StringBuilder msgbuf = new StringBuilder("");
|
|
DirCache dc = readTree(or.rw, currentCommit);
|
|
DirCacheEditor ed = dc.editor();
|
|
for (SubmoduleSubscription s : targets.get(subscriber)) {
|
|
updateSubmodule(dc, ed, msgbuf, s);
|
|
}
|
|
ed.finish();
|
|
ObjectId newTreeId = dc.writeTree(or.ins);
|
|
|
|
// Gitlinks are already updated, just return the commit
|
|
if (newTreeId.equals(currentCommit.getTree())) {
|
|
return currentCommit;
|
|
}
|
|
or.rw.parseBody(currentCommit);
|
|
CommitBuilder commit = new CommitBuilder();
|
|
commit.setTreeId(newTreeId);
|
|
commit.setParentIds(currentCommit.getParents());
|
|
if (verboseSuperProject != VerboseSuperprojectUpdate.FALSE) {
|
|
// TODO(czhen): handle cherrypick footer
|
|
commit.setMessage(currentCommit.getFullMessage() + "\n\n* submodules:\n" + msgbuf.toString());
|
|
} else {
|
|
commit.setMessage(currentCommit.getFullMessage());
|
|
}
|
|
commit.setAuthor(currentCommit.getAuthorIdent());
|
|
commit.setCommitter(myIdent);
|
|
ObjectId id = or.ins.insert(commit);
|
|
CodeReviewCommit newCommit = or.rw.parseCommit(id);
|
|
newCommit.copyFrom(currentCommit);
|
|
return newCommit;
|
|
}
|
|
|
|
private RevCommit updateSubmodule(
|
|
DirCache dc, DirCacheEditor ed, StringBuilder msgbuf, SubmoduleSubscription s)
|
|
throws SubmoduleException, IOException {
|
|
OpenRepo subOr;
|
|
try {
|
|
subOr = orm.getRepo(s.getSubmodule().getParentKey());
|
|
} catch (NoSuchProjectException | IOException e) {
|
|
throw new SubmoduleException("Cannot access submodule", e);
|
|
}
|
|
|
|
DirCacheEntry dce = dc.getEntry(s.getPath());
|
|
RevCommit oldCommit = null;
|
|
if (dce != null) {
|
|
if (!dce.getFileMode().equals(FileMode.GITLINK)) {
|
|
String errMsg =
|
|
"Requested to update gitlink "
|
|
+ s.getPath()
|
|
+ " in "
|
|
+ s.getSubmodule().getParentKey().get()
|
|
+ " but entry "
|
|
+ "doesn't have gitlink file mode.";
|
|
throw new SubmoduleException(errMsg);
|
|
}
|
|
oldCommit = subOr.rw.parseCommit(dce.getObjectId());
|
|
}
|
|
|
|
final CodeReviewCommit newCommit;
|
|
if (branchTips.containsKey(s.getSubmodule())) {
|
|
newCommit = branchTips.get(s.getSubmodule());
|
|
} else {
|
|
Ref ref = subOr.repo.getRefDatabase().exactRef(s.getSubmodule().get());
|
|
if (ref == null) {
|
|
ed.add(new DeletePath(s.getPath()));
|
|
return null;
|
|
}
|
|
newCommit = subOr.rw.parseCommit(ref.getObjectId());
|
|
addBranchTip(s.getSubmodule(), newCommit);
|
|
}
|
|
|
|
if (Objects.equals(newCommit, oldCommit)) {
|
|
// gitlink have already been updated for this submodule
|
|
return null;
|
|
}
|
|
ed.add(
|
|
new PathEdit(s.getPath()) {
|
|
@Override
|
|
public void apply(DirCacheEntry ent) {
|
|
ent.setFileMode(FileMode.GITLINK);
|
|
ent.setObjectId(newCommit.getId());
|
|
}
|
|
});
|
|
|
|
if (verboseSuperProject != VerboseSuperprojectUpdate.FALSE) {
|
|
createSubmoduleCommitMsg(msgbuf, s, subOr, newCommit, oldCommit);
|
|
}
|
|
subOr.rw.parseBody(newCommit);
|
|
return newCommit;
|
|
}
|
|
|
|
private void createSubmoduleCommitMsg(
|
|
StringBuilder msgbuf,
|
|
SubmoduleSubscription s,
|
|
OpenRepo subOr,
|
|
RevCommit newCommit,
|
|
RevCommit oldCommit)
|
|
throws SubmoduleException {
|
|
msgbuf.append("* Update ");
|
|
msgbuf.append(s.getPath());
|
|
msgbuf.append(" from branch '");
|
|
msgbuf.append(s.getSubmodule().getShortName());
|
|
msgbuf.append("'");
|
|
msgbuf.append("\n to " + newCommit.getName());
|
|
|
|
// newly created submodule gitlink, do not append whole history
|
|
if (oldCommit == null) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
subOr.rw.resetRetain(subOr.canMergeFlag);
|
|
subOr.rw.markStart(newCommit);
|
|
subOr.rw.markUninteresting(oldCommit);
|
|
int numMessages = 0;
|
|
for (Iterator<RevCommit> iter = subOr.rw.iterator(); iter.hasNext(); ) {
|
|
RevCommit c = iter.next();
|
|
subOr.rw.parseBody(c);
|
|
|
|
String message =
|
|
verboseSuperProject == VerboseSuperprojectUpdate.SUBJECT_ONLY
|
|
? c.getShortMessage()
|
|
: StringUtils.replace(c.getFullMessage(), "\n", "\n ");
|
|
|
|
String bullet = "\n - ";
|
|
String ellipsis = "\n\n[...]";
|
|
int newSize = msgbuf.length() + bullet.length() + message.length();
|
|
if (++numMessages > maxCommitMessages
|
|
|| newSize > maxCombinedCommitMessageSize
|
|
|| iter.hasNext() && (newSize + ellipsis.length()) > maxCombinedCommitMessageSize) {
|
|
msgbuf.append(ellipsis);
|
|
break;
|
|
}
|
|
msgbuf.append(bullet);
|
|
msgbuf.append(message);
|
|
}
|
|
} catch (IOException e) {
|
|
throw new SubmoduleException(
|
|
"Could not perform a revwalk to create superproject commit message", e);
|
|
}
|
|
}
|
|
|
|
private static DirCache readTree(RevWalk rw, ObjectId base) throws IOException {
|
|
final DirCache dc = DirCache.newInCore();
|
|
final DirCacheBuilder b = dc.builder();
|
|
b.addTree(
|
|
new byte[0], // no prefix path
|
|
DirCacheEntry.STAGE_0, // standard stage
|
|
rw.getObjectReader(),
|
|
rw.parseTree(base));
|
|
b.finish();
|
|
return dc;
|
|
}
|
|
|
|
public ImmutableSet<Project.NameKey> getProjectsInOrder() throws SubmoduleException {
|
|
LinkedHashSet<Project.NameKey> projects = new LinkedHashSet<>();
|
|
for (Project.NameKey project : branchesByProject.keySet()) {
|
|
addAllSubmoduleProjects(project, new LinkedHashSet<>(), projects);
|
|
}
|
|
|
|
for (Branch.NameKey branch : updatedBranches) {
|
|
projects.add(branch.getParentKey());
|
|
}
|
|
return ImmutableSet.copyOf(projects);
|
|
}
|
|
|
|
private void addAllSubmoduleProjects(
|
|
Project.NameKey project,
|
|
LinkedHashSet<Project.NameKey> current,
|
|
LinkedHashSet<Project.NameKey> projects)
|
|
throws SubmoduleException {
|
|
if (current.contains(project)) {
|
|
throw new SubmoduleException(
|
|
"Project level circular subscriptions detected: " + printCircularPath(current, project));
|
|
}
|
|
|
|
if (projects.contains(project)) {
|
|
return;
|
|
}
|
|
|
|
current.add(project);
|
|
Set<Project.NameKey> subprojects = new HashSet<>();
|
|
for (Branch.NameKey branch : branchesByProject.get(project)) {
|
|
Collection<SubmoduleSubscription> subscriptions = targets.get(branch);
|
|
for (SubmoduleSubscription s : subscriptions) {
|
|
subprojects.add(s.getSubmodule().getParentKey());
|
|
}
|
|
}
|
|
|
|
for (Project.NameKey p : subprojects) {
|
|
addAllSubmoduleProjects(p, current, projects);
|
|
}
|
|
|
|
current.remove(project);
|
|
projects.add(project);
|
|
}
|
|
|
|
public ImmutableSet<Branch.NameKey> getBranchesInOrder() {
|
|
LinkedHashSet<Branch.NameKey> branches = new LinkedHashSet<>();
|
|
if (sortedBranches != null) {
|
|
branches.addAll(sortedBranches);
|
|
}
|
|
branches.addAll(updatedBranches);
|
|
return ImmutableSet.copyOf(branches);
|
|
}
|
|
|
|
public boolean hasSubscription(Branch.NameKey branch) {
|
|
return targets.containsKey(branch);
|
|
}
|
|
|
|
public void addBranchTip(Branch.NameKey branch, CodeReviewCommit tip) {
|
|
branchTips.put(branch, tip);
|
|
}
|
|
|
|
public void addOp(BatchUpdate bu, Branch.NameKey branch) {
|
|
bu.addRepoOnlyOp(new GitlinkOp(branch));
|
|
}
|
|
|
|
private void logDebug(String msg, Object... args) {
|
|
if (log.isDebugEnabled()) {
|
|
log.debug(orm.getSubmissionId() + msg, args);
|
|
}
|
|
}
|
|
}
|