Merge "For the 'conflicts' operator return only changes that actually conflict"
This commit is contained in:
@@ -77,10 +77,9 @@ Change-Id that was scraped out of the commit message.
|
||||
[[conflicts]]
|
||||
conflicts:'ID'::
|
||||
+
|
||||
Changes that potentially conflict with change 'ID' because they touch
|
||||
at least one file that was also touched by change 'ID'. Change 'ID' can
|
||||
be specified as a legacy numerical 'ID' such as 15183, or a newer style
|
||||
Change-Id that was scraped out of the commit message.
|
||||
Changes that conflict with change 'ID'. Change 'ID' can be specified
|
||||
as a legacy numerical 'ID' such as 15183, or a newer style Change-Id
|
||||
that was scraped out of the commit message.
|
||||
|
||||
[[owner]]
|
||||
owner:'USER'::
|
||||
|
||||
@@ -111,6 +111,7 @@ import com.google.gerrit.server.project.ProjectNode;
|
||||
import com.google.gerrit.server.project.ProjectState;
|
||||
import com.google.gerrit.server.project.SectionSortCache;
|
||||
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
|
||||
import com.google.gerrit.server.query.change.ConflictsCacheImpl;
|
||||
import com.google.gerrit.server.ssh.SshAddressesModule;
|
||||
import com.google.gerrit.server.tools.ToolsCatalog;
|
||||
import com.google.gerrit.server.util.IdGenerator;
|
||||
@@ -143,13 +144,14 @@ public class GerritGlobalModule extends FactoryModule {
|
||||
install(authModule);
|
||||
install(AccountByEmailCacheImpl.module());
|
||||
install(AccountCacheImpl.module());
|
||||
install(ChangeCache.module());
|
||||
install(ConflictsCacheImpl.module());
|
||||
install(GroupCacheImpl.module());
|
||||
install(GroupIncludeCacheImpl.module());
|
||||
install(PatchListCacheImpl.module());
|
||||
install(ProjectCacheImpl.module());
|
||||
install(SectionSortCache.module());
|
||||
install(TagCache.module());
|
||||
install(ChangeCache.module());
|
||||
|
||||
install(new AccessControlModule());
|
||||
install(new CmdLineParserModule());
|
||||
|
||||
@@ -33,8 +33,8 @@ public abstract class BasicChangeRewrites extends QueryRewriter<ChangeData> {
|
||||
new ChangeQueryBuilder.Arguments( //
|
||||
new InvalidProvider<ReviewDb>(), //
|
||||
new InvalidProvider<ChangeQueryRewriter>(), //
|
||||
null, null, null, null, null, null, //
|
||||
null, null, null, null, null, null), null);
|
||||
null, null, null, null, null, null, null, //
|
||||
null, null, null, null, null, null, null), null);
|
||||
|
||||
static Schema<ChangeData> schema(@Nullable IndexCollection indexes) {
|
||||
ChangeIndex index = indexes != null ? indexes.getSearchIndex() : null;
|
||||
|
||||
@@ -32,6 +32,7 @@ import com.google.gerrit.server.account.GroupBackend;
|
||||
import com.google.gerrit.server.account.GroupBackends;
|
||||
import com.google.gerrit.server.config.AllProjectsName;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.git.SubmitStrategyFactory;
|
||||
import com.google.gerrit.server.index.ChangeIndex;
|
||||
import com.google.gerrit.server.index.IndexCollection;
|
||||
import com.google.gerrit.server.index.Schema;
|
||||
@@ -153,6 +154,8 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
final ProjectCache projectCache;
|
||||
final Provider<ListChildProjects> listChildProjects;
|
||||
final IndexCollection indexes;
|
||||
final SubmitStrategyFactory submitStrategyFactory;
|
||||
final ConflictsCache conflictsCache;
|
||||
|
||||
@Inject
|
||||
@VisibleForTesting
|
||||
@@ -169,7 +172,9 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
GitRepositoryManager repoManager,
|
||||
ProjectCache projectCache,
|
||||
Provider<ListChildProjects> listChildProjects,
|
||||
IndexCollection indexes) {
|
||||
IndexCollection indexes,
|
||||
SubmitStrategyFactory submitStrategyFactory,
|
||||
ConflictsCache conflictsCache) {
|
||||
this.dbProvider = dbProvider;
|
||||
this.rewriter = rewriter;
|
||||
this.userFactory = userFactory;
|
||||
@@ -184,6 +189,8 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
this.projectCache = projectCache;
|
||||
this.listChildProjects = listChildProjects;
|
||||
this.indexes = indexes;
|
||||
this.submitStrategyFactory = submitStrategyFactory;
|
||||
this.conflictsCache = conflictsCache;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,8 +324,10 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
public Predicate<ChangeData> conflicts(String value) throws OrmException,
|
||||
QueryParseException {
|
||||
requireIndex(FIELD_CONFLICTS, value);
|
||||
return new ConflictsPredicate(args.dbProvider, args.patchListCache, value,
|
||||
parseChange(value));
|
||||
return new ConflictsPredicate(args.dbProvider, args.patchListCache,
|
||||
args.submitStrategyFactory, args.changeControlGenericFactory,
|
||||
args.userFactory, args.repoManager, args.projectCache,
|
||||
args.conflictsCache, value, parseChange(value));
|
||||
}
|
||||
|
||||
@Operator
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright (C) 2013 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.query.change;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.gerrit.reviewdb.client.Project.SubmitType;
|
||||
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class ConflictKey implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final ObjectId commit;
|
||||
private final ObjectId otherCommit;
|
||||
private final SubmitType submitType;
|
||||
private final boolean contentMerge;
|
||||
|
||||
public ConflictKey(ObjectId commit, ObjectId otherCommit,
|
||||
SubmitType submitType, boolean contentMerge) {
|
||||
if (SubmitType.FAST_FORWARD_ONLY.equals(submitType)
|
||||
|| commit.compareTo(otherCommit) < 0) {
|
||||
this.commit = commit;
|
||||
this.otherCommit = otherCommit;
|
||||
} else {
|
||||
this.commit = otherCommit;
|
||||
this.otherCommit = commit;
|
||||
}
|
||||
this.submitType = submitType;
|
||||
this.contentMerge = contentMerge;
|
||||
}
|
||||
|
||||
public ObjectId getCommit() {
|
||||
return commit;
|
||||
}
|
||||
|
||||
public ObjectId getOtherCommit() {
|
||||
return otherCommit;
|
||||
}
|
||||
|
||||
public SubmitType getSubmitType() {
|
||||
return submitType;
|
||||
}
|
||||
|
||||
public boolean isContentMerge() {
|
||||
return contentMerge;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof ConflictKey)) {
|
||||
return false;
|
||||
}
|
||||
ConflictKey other = (ConflictKey)o;
|
||||
return commit.equals(other.commit)
|
||||
&& otherCommit.equals(other.otherCommit)
|
||||
&& submitType.equals(other.submitType)
|
||||
&& contentMerge == other.contentMerge;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(commit, otherCommit, submitType, contentMerge);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (C) 2013 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.query.change;
|
||||
|
||||
import com.google.gerrit.common.Nullable;
|
||||
|
||||
public interface ConflictsCache {
|
||||
|
||||
public void put(ConflictKey key, Boolean value);
|
||||
|
||||
@Nullable
|
||||
public Boolean getIfPresent(ConflictKey key);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright (C) 2013 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.query.change;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.gerrit.server.cache.CacheModule;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.Singleton;
|
||||
import com.google.inject.name.Named;
|
||||
|
||||
@Singleton
|
||||
public class ConflictsCacheImpl implements ConflictsCache {
|
||||
public final static String NAME = "conflicts";
|
||||
|
||||
public static Module module() {
|
||||
return new CacheModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
persist(NAME, ConflictKey.class, Boolean.class)
|
||||
.maximumWeight(37400);
|
||||
bind(ConflictsCache.class).to(ConflictsCacheImpl.class);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private final Cache<ConflictKey, Boolean> conflictsCache;
|
||||
|
||||
@Inject
|
||||
public ConflictsCacheImpl(
|
||||
@Named(NAME) Cache<ConflictKey, Boolean> conflictsCache) {
|
||||
this.conflictsCache = conflictsCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(ConflictKey key, Boolean value) {
|
||||
conflictsCache.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getIfPresent(ConflictKey key) {
|
||||
return conflictsCache.getIfPresent(key);
|
||||
}
|
||||
}
|
||||
@@ -15,30 +15,71 @@
|
||||
package com.google.gerrit.server.query.change;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gerrit.common.data.SubmitTypeRecord;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.Project.SubmitType;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.git.CodeReviewCommit;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.git.MergeException;
|
||||
import com.google.gerrit.server.git.SubmitStrategy;
|
||||
import com.google.gerrit.server.git.SubmitStrategyFactory;
|
||||
import com.google.gerrit.server.patch.PatchListCache;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
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.query.OperatorPredicate;
|
||||
import com.google.gerrit.server.query.OrPredicate;
|
||||
import com.google.gerrit.server.query.Predicate;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
||||
import org.eclipse.jgit.lib.AnyObjectId;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevFlag;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
class ConflictsPredicate extends OrPredicate<ChangeData> {
|
||||
private final String value;
|
||||
|
||||
ConflictsPredicate(Provider<ReviewDb> db, PatchListCache plc, String value,
|
||||
List<Change> changes) throws OrmException {
|
||||
super(predicates(db, plc, changes));
|
||||
ConflictsPredicate(Provider<ReviewDb> db, PatchListCache plc,
|
||||
SubmitStrategyFactory submitStrategyFactory,
|
||||
ChangeControl.GenericFactory changeControlFactory,
|
||||
IdentifiedUser.GenericFactory identifiedUserFactory,
|
||||
GitRepositoryManager repoManager, ProjectCache projectCache,
|
||||
ConflictsCache conflictsCache, String value, List<Change> changes)
|
||||
throws OrmException {
|
||||
super(predicates(db, plc, submitStrategyFactory, changeControlFactory,
|
||||
identifiedUserFactory, repoManager, projectCache, conflictsCache,
|
||||
value, changes));
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private static List<Predicate<ChangeData>> predicates(Provider<ReviewDb> db,
|
||||
PatchListCache plc, List<Change> changes) throws OrmException {
|
||||
private static List<Predicate<ChangeData>> predicates(
|
||||
final Provider<ReviewDb> db, final PatchListCache plc,
|
||||
final SubmitStrategyFactory submitStrategyFactory,
|
||||
final ChangeControl.GenericFactory changeControlFactory,
|
||||
final IdentifiedUser.GenericFactory identifiedUserFactory,
|
||||
final GitRepositoryManager repoManager, final ProjectCache projectCache,
|
||||
final ConflictsCache conflictsCache, final String value,
|
||||
List<Change> changes) throws OrmException {
|
||||
List<Predicate<ChangeData>> changePredicates =
|
||||
Lists.newArrayListWithCapacity(changes.size());
|
||||
for (Change c : changes) {
|
||||
for (final Change c : changes) {
|
||||
final ChangeDataCache changeDataCache = new ChangeDataCache(c, db, projectCache);
|
||||
List<String> files = new ChangeData(c).currentFilePaths(db, plc);
|
||||
List<Predicate<ChangeData>> filePredicates =
|
||||
Lists.newArrayListWithCapacity(files.size());
|
||||
@@ -47,7 +88,7 @@ class ConflictsPredicate extends OrPredicate<ChangeData> {
|
||||
}
|
||||
|
||||
List<Predicate<ChangeData>> predicatesForOneChange =
|
||||
Lists.newArrayListWithCapacity(4);
|
||||
Lists.newArrayListWithCapacity(5);
|
||||
predicatesForOneChange.add(
|
||||
not(new LegacyChangeIdPredicate(db, c.getId())));
|
||||
predicatesForOneChange.add(
|
||||
@@ -55,7 +96,115 @@ class ConflictsPredicate extends OrPredicate<ChangeData> {
|
||||
predicatesForOneChange.add(
|
||||
new RefPredicate(db, c.getDest().get()));
|
||||
predicatesForOneChange.add(or(filePredicates));
|
||||
predicatesForOneChange.add(new OperatorPredicate<ChangeData>(
|
||||
ChangeQueryBuilder.FIELD_CONFLICTS, value) {
|
||||
|
||||
@Override
|
||||
public boolean match(ChangeData object) throws OrmException {
|
||||
Change otherChange = object.change(db);
|
||||
if (otherChange == null) {
|
||||
return false;
|
||||
}
|
||||
if (!otherChange.getDest().equals(c.getDest())) {
|
||||
return false;
|
||||
}
|
||||
SubmitType submitType = getSubmitType(otherChange, object);
|
||||
if (submitType == null) {
|
||||
return false;
|
||||
}
|
||||
ObjectId other = ObjectId.fromString(
|
||||
object.currentPatchSet(db).getRevision().get());
|
||||
ConflictKey conflictsKey =
|
||||
new ConflictKey(changeDataCache.getTestAgainst(), other, submitType,
|
||||
changeDataCache.getProjectState().isUseContentMerge());
|
||||
Boolean conflicts = conflictsCache.getIfPresent(conflictsKey);
|
||||
if (conflicts != null) {
|
||||
return conflicts;
|
||||
}
|
||||
try {
|
||||
Repository repo =
|
||||
repoManager.openRepository(otherChange.getProject());
|
||||
try {
|
||||
RevWalk rw = new RevWalk(repo) {
|
||||
@Override
|
||||
protected RevCommit createCommit(AnyObjectId id) {
|
||||
return new CodeReviewCommit(id);
|
||||
}
|
||||
};
|
||||
try {
|
||||
RevFlag canMergeFlag = rw.newFlag("CAN_MERGE");
|
||||
CodeReviewCommit commit =
|
||||
(CodeReviewCommit) rw.parseCommit(changeDataCache.getTestAgainst());
|
||||
SubmitStrategy strategy =
|
||||
submitStrategyFactory.create(submitType,
|
||||
db.get(), repo, rw, null, canMergeFlag,
|
||||
getAlreadyAccepted(repo, rw, commit),
|
||||
otherChange.getDest());
|
||||
CodeReviewCommit otherCommit =
|
||||
(CodeReviewCommit) rw.parseCommit(other);
|
||||
otherCommit.add(canMergeFlag);
|
||||
conflicts = !strategy.dryRun(commit, otherCommit);
|
||||
conflictsCache.put(conflictsKey, conflicts);
|
||||
return conflicts;
|
||||
} catch (MergeException e) {
|
||||
throw new IllegalStateException(e);
|
||||
} catch (NoSuchProjectException e) {
|
||||
throw new IllegalStateException(e);
|
||||
} finally {
|
||||
rw.release();
|
||||
}
|
||||
} finally {
|
||||
repo.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCost() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
private SubmitType getSubmitType(Change change, ChangeData cd) throws OrmException {
|
||||
try {
|
||||
final SubmitTypeRecord r =
|
||||
changeControlFactory.controlFor(change,
|
||||
identifiedUserFactory.create(change.getOwner()))
|
||||
.getSubmitTypeRecord(db.get(), cd.currentPatchSet(db), cd);
|
||||
if (r.status != SubmitTypeRecord.Status.OK) {
|
||||
return null;
|
||||
}
|
||||
return r.type;
|
||||
} catch (NoSuchChangeException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Set<RevCommit> getAlreadyAccepted(Repository repo, RevWalk rw,
|
||||
CodeReviewCommit tip) throws MergeException {
|
||||
Set<RevCommit> alreadyAccepted = Sets.newHashSet();
|
||||
|
||||
if (tip != null) {
|
||||
alreadyAccepted.add(tip);
|
||||
}
|
||||
|
||||
try {
|
||||
for (ObjectId id : changeDataCache.getAlreadyAccepted(repo)) {
|
||||
try {
|
||||
alreadyAccepted.add(rw.parseCommit(id));
|
||||
} catch (IncorrectObjectTypeException iote) {
|
||||
// Not a commit? Skip over it.
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new MergeException(
|
||||
"Failed to determine already accepted commits.", e);
|
||||
}
|
||||
|
||||
return alreadyAccepted;
|
||||
}
|
||||
});
|
||||
changePredicates.add(and(predicatesForOneChange));
|
||||
}
|
||||
return changePredicates;
|
||||
@@ -65,4 +214,55 @@ class ConflictsPredicate extends OrPredicate<ChangeData> {
|
||||
public String toString() {
|
||||
return ChangeQueryBuilder.FIELD_CONFLICTS + ":" + value;
|
||||
}
|
||||
|
||||
private static class ChangeDataCache {
|
||||
private final Change change;
|
||||
private final Provider<ReviewDb> db;
|
||||
private final ProjectCache projectCache;
|
||||
|
||||
private ObjectId testAgainst;
|
||||
private ProjectState projectState;
|
||||
private Set<ObjectId> alreadyAccepted;
|
||||
|
||||
ChangeDataCache(Change change, Provider<ReviewDb> db, ProjectCache projectCache) {
|
||||
this.change = change;
|
||||
this.db = db;
|
||||
this.projectCache = projectCache;
|
||||
}
|
||||
|
||||
ObjectId getTestAgainst()
|
||||
throws OrmException {
|
||||
if (testAgainst == null) {
|
||||
testAgainst = ObjectId.fromString(
|
||||
new ChangeData(change).currentPatchSet(db).getRevision().get());
|
||||
}
|
||||
return testAgainst;
|
||||
}
|
||||
|
||||
ProjectState getProjectState() {
|
||||
if (projectState == null) {
|
||||
projectState = projectCache.get(change.getProject());
|
||||
if (projectState == null) {
|
||||
throw new IllegalStateException(
|
||||
new NoSuchProjectException(change.getProject()));
|
||||
}
|
||||
}
|
||||
return projectState;
|
||||
}
|
||||
|
||||
Set<ObjectId> getAlreadyAccepted(Repository repo) {
|
||||
if (alreadyAccepted == null) {
|
||||
alreadyAccepted = Sets.newHashSet();
|
||||
for (Ref r : repo.getAllRefs().values()) {
|
||||
if (r.getName().startsWith(Constants.R_HEADS)
|
||||
|| r.getName().startsWith(Constants.R_TAGS)) {
|
||||
if (r.getObjectId() != null) {
|
||||
alreadyAccepted.add(r.getObjectId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return alreadyAccepted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class FakeQueryBuilder extends ChangeQueryBuilder {
|
||||
new FakeQueryBuilder.Definition<ChangeData, FakeQueryBuilder>(
|
||||
FakeQueryBuilder.class),
|
||||
new ChangeQueryBuilder.Arguments(null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, indexes),
|
||||
null, null, null, null, null, null, null, indexes, null, null),
|
||||
null);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user