diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt index 7d3dd07779..3be5efb94a 100644 --- a/Documentation/dev-bazel.txt +++ b/Documentation/dev-bazel.txt @@ -9,12 +9,22 @@ To build Gerrit from source, you need: * A JDK for Java 8|9|10|11|... * Python 2 or 3 * Node.js -* link:https://docs.bazel.build/versions/master/install.html[Bazel] directly -or through link:https://github.com/bazelbuild/bazelisk[Bazelisk] +* link:https://docs.bazel.build/versions/master/install.html[Bazel] -launched with +link:https://github.com/bazelbuild/bazelisk[Bazelisk] * Maven * zip, unzip * gcc +[[bazel]] +=== Bazel + +link:https://github.com/bazelbuild/bazelisk[Bazelisk] includes a +link:https://bazel.build/[Bazel] version check and downloads the correct +`bazel` version for the git project/repository. Bazelisk is the recommended +`bazel` launcher for Gerrit. Once Bazelisk is installed locally, a `bazel` +symlink can be created towards it. This is so that every `bazel` command +seamlessly uses Bazelisk, which then runs the proper `bazel` binary version. + [[java]] === Java diff --git a/Documentation/dev-contributing.txt b/Documentation/dev-contributing.txt index 5033d3978b..0930034242 100644 --- a/Documentation/dev-contributing.txt +++ b/Documentation/dev-contributing.txt @@ -171,7 +171,10 @@ To format Java source code, Gerrit uses the link:https://github.com/google/google-java-format[`google-java-format`] tool (version 1.7), and to format Bazel BUILD, WORKSPACE and .bzl files the link:https://github.com/bazelbuild/buildtools/tree/master/buildifier[`buildifier`] -tool (version 0.29.0). +tool (version 0.29.0). Unused dependencies are found and removed using the +link:https://github.com/bazelbuild/buildtools/tree/master/unused_deps[`unused_deps`] +build tool, a sibling of `buildifier`. + These tools automatically apply format according to the style guides; this streamlines code review by reducing the need for time-consuming, tedious, and contentious discussions about trivial issues like whitespace. diff --git a/Documentation/dev-readme.txt b/Documentation/dev-readme.txt index 8bf4814d1c..3833da49e4 100644 --- a/Documentation/dev-readme.txt +++ b/Documentation/dev-readme.txt @@ -1,7 +1,7 @@ = Gerrit Code Review: Developer Setup To build a developer instance, you'll need link:https://bazel.build/[Bazel] to -compile the code. +compile the code, preferably launched with link:https://github.com/bazelbuild/bazelisk[Bazelisk]. == Getting the Source diff --git a/Jenkinsfile b/Jenkinsfile index 7417bb33aa..edd4234033 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -25,15 +25,6 @@ class Globals { static final String gerritRepositoryNameSha1Suffix = "-a6a0e4682515f3521897c5f950d1394f4619d928" } -class Change { - static String sha1 = "" - static String number = "" - static String branch = "" - static String ref = "" - static String patchNum = "" - static String url = "" -} - class Build { String url String result @@ -83,36 +74,17 @@ def postCheck(check) { gerritCheck(checks: [ "${check.uuid}" : "${check.getCheckResultFromBuild()}" ], url: "${check.consoleUrl}") } -def queryChangedFiles(url, changeNum, sha1) { - def queryUrl = "${url}changes/${Change.number}/revisions/${Change.sha1}/files/" +def queryChangedFiles(url) { + def queryUrl = "${url}changes/${env.GERRIT_CHANGE_NUMBER}/revisions/${env.GERRIT_PATCHSET_REVISION}/files/" def response = httpRequest queryUrl def files = response.getContent().substring(5) def filesJson = new JsonSlurper().parseText(files) return filesJson.keySet().findAll { it != "/COMMIT_MSG" } } -def queryChange(){ - def requestedChangeId = env.BRANCH_NAME.split('/')[1] - def queryUrl = "${Globals.gerritUrl}changes/${requestedChangeId}/?pp=0&O=3" - def response = httpRequest queryUrl - def jsonSlurper = new JsonSlurper() - return jsonSlurper.parseText(response.getContent().substring(5)) -} - -def getChangeMetaData(){ - def changeJson = queryChange() - Change.sha1 = changeJson.current_revision - Change.number = changeJson._number - Change.branch = changeJson.branch - def revision = changeJson.revisions.get(Change.sha1) - Change.ref = revision.ref - Change.patchNum = revision._number - Change.url = Globals.gerritUrl + "#/c/" + Change.number + "/" + Change.patchNum -} - def collectBuildModes() { Builds.modes = ["notedb"] - def changedFiles = queryChangedFiles(Globals.gerritUrl, Change.number, Change.sha1) + def changedFiles = queryChangedFiles(Globals.gerritUrl) def polygerritFiles = changedFiles.findAll { it.startsWith("polygerrit-ui") || it.startsWith("lib/js") } @@ -137,11 +109,11 @@ def prepareBuildsForMode(buildName, mode="notedb", retryTimes = 1) { for (int i = 1; i <= retryTimes; i++) { try { slaveBuild = build job: "${buildName}", parameters: [ - string(name: 'REFSPEC', value: Change.ref), - string(name: 'BRANCH', value: Change.sha1), - string(name: 'CHANGE_URL', value: Change.url), + string(name: 'REFSPEC', value: "refs/changes/${env.BRANCH_NAME}"), + string(name: 'BRANCH', value: env.GERRIT_PATCHSET_REVISION), + string(name: 'CHANGE_URL', value: "${Globals.gerritUrl}c/${env.GERRIT_PROJECT}/+/${env.GERRIT_CHANGE_NUMBER}"), string(name: 'MODE', value: mode), - string(name: 'TARGET_BRANCH', value: Change.branch) + string(name: 'TARGET_BRANCH', value: env.GERRIT_BRANCH) ], propagate: false } finally { if (buildName == "Gerrit-codestyle"){ @@ -243,8 +215,6 @@ node ('master') { if (hasChangeNumber()) { stage('Preparing'){ gerritReview labels: ['Verified': 0, 'Code-Style': 0] - - getChangeMetaData() collectBuildModes() } } diff --git a/WORKSPACE b/WORKSPACE index ec7676beda..25a797c804 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -23,13 +23,6 @@ load("@bazel_toolchains//rules:rbe_repo.bzl", "rbe_autoconfig") # otherwise refer to RBE docs. rbe_autoconfig(name = "rbe_default") -http_archive( - name = "bazel_skylib", - sha256 = "2ea8a5ed2b448baf4a6855d3ce049c4c452a6470b1efd1504fdb7c1c134d220a", - strip_prefix = "bazel-skylib-0.8.0", - urls = ["https://github.com/bazelbuild/bazel-skylib/archive/0.8.0.tar.gz"], -) - http_archive( name = "io_bazel_rules_closure", sha256 = "03c3b16f205085817fd89cfdcb2220a0138647ee7992be9cef291b069dd90301", @@ -47,15 +40,6 @@ http_file( urls = ["https://raw.githubusercontent.com/google/closure-compiler/35d2b3340ff23a69441f10fa3bc820691c2942f2/contrib/externs/polymer-1.0.js"], ) -# Check Bazel version when invoked by Bazel directly -load("//tools/bzl:bazelisk_version.bzl", "bazelisk_version") - -bazelisk_version(name = "bazelisk_version") - -load("@bazelisk_version//:check.bzl", "check_bazel_version") - -check_bazel_version() - load("@io_bazel_rules_closure//closure:repositories.bzl", "rules_closure_dependencies", "rules_closure_toolchains") # Prevent redundant loading of dependencies. @@ -64,7 +48,6 @@ load("@io_bazel_rules_closure//closure:repositories.bzl", "rules_closure_depende # https://github.com/google/closure-templates/pull/155 rules_closure_dependencies( omit_aopalliance = True, - omit_bazel_skylib = True, omit_javax_inject = True, omit_rules_cc = True, ) diff --git a/java/com/google/gerrit/pgm/DeleteZombieDrafts.java b/java/com/google/gerrit/pgm/DeleteZombieDrafts.java new file mode 100644 index 0000000000..90a60c101d --- /dev/null +++ b/java/com/google/gerrit/pgm/DeleteZombieDrafts.java @@ -0,0 +1,78 @@ +// Copyright (C) 2020 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.pgm; + +import com.google.gerrit.pgm.init.api.ConsoleUI; +import com.google.gerrit.pgm.util.SiteProgram; +import com.google.gerrit.server.config.GerritServerConfigModule; +import com.google.gerrit.server.config.SitePath; +import com.google.gerrit.server.notedb.DeleteZombieCommentsRefs; +import com.google.gerrit.server.notedb.DeleteZombieCommentsRefs.Factory; +import com.google.gerrit.server.schema.SchemaModule; +import com.google.gerrit.server.securestore.SecureStoreClassName; +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Module; +import com.google.inject.assistedinject.FactoryModuleBuilder; +import com.google.inject.util.Providers; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import org.kohsuke.args4j.Option; + +/** + * A pgm which can be used to clean zombie draft comments refs More context in + * https://gerrit-review.googlesource.com/c/gerrit/+/246233 + * + *

The implementation is in {@link DeleteZombieCommentsRefs} + */ +public class DeleteZombieDrafts extends SiteProgram { + @Option( + name = "--cleanup-percentage", + aliases = {"-c"}, + usage = "Clean a % of zombie drafts (default is 100%)") + private Integer cleanupPercentage = 100; + + @Override + public int run() throws IOException { + mustHaveValidSite(); + Injector sysInjector = getSysInjector(); + DeleteZombieCommentsRefs cleanup = + sysInjector.getInstance(Factory.class).create(cleanupPercentage); + cleanup.execute(); + return 0; + } + + private Injector getSysInjector() { + List modules = new ArrayList<>(); + modules.add( + new AbstractModule() { + @Override + protected void configure() { + bind(Path.class).annotatedWith(SitePath.class).toInstance(getSitePath()); + bind(ConsoleUI.class).toInstance(ConsoleUI.getInstance(false)); + bind(String.class) + .annotatedWith(SecureStoreClassName.class) + .toProvider(Providers.of(getConfiguredSecureStoreClass())); + install(new FactoryModuleBuilder().build(DeleteZombieCommentsRefs.Factory.class)); + } + }); + modules.add(new GerritServerConfigModule()); + modules.add(new SchemaModule()); + return Guice.createInjector(modules); + } +} diff --git a/java/com/google/gerrit/server/notedb/DeleteZombieCommentsRefs.java b/java/com/google/gerrit/server/notedb/DeleteZombieCommentsRefs.java new file mode 100644 index 0000000000..2b98554a04 --- /dev/null +++ b/java/com/google/gerrit/server/notedb/DeleteZombieCommentsRefs.java @@ -0,0 +1,130 @@ +// Copyright (C) 2020 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.notedb; + +import static com.google.common.collect.ImmutableList.toImmutableList; + +import com.google.common.collect.Iterables; +import com.google.gerrit.git.RefUpdateUtil; +import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.server.config.AllUsersName; +import com.google.gerrit.server.git.GitRepositoryManager; +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.eclipse.jgit.lib.BatchRefUpdate; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.ReceiveCommand; + +/** + * This class can be used to clean zombie draft comments refs. More context in + * https://gerrit-review.googlesource.com/c/gerrit/+/246233 + * + *

An earlier bug in the deletion of draft comments {@code + * refs/draft-comments/$change_id_short/$change_id/$user_id} caused some draft refs to remain in Git + * and not get deleted. These refs point to an empty tree. + */ +public class DeleteZombieCommentsRefs { + private final String EMPTY_TREE_ID = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"; + private final String DRAFT_REFS_PREFIX = "refs/draft-comments"; + private final int CHUNK_SIZE = 100; // log progress after deleting every CHUNK_SIZE refs + private final GitRepositoryManager repoManager; + private final AllUsersName allUsers; + private final int cleanupPercentage; + private Repository allUsersRepo; + + public interface Factory { + DeleteZombieCommentsRefs create(int cleanupPercentage); + } + + @Inject + public DeleteZombieCommentsRefs( + AllUsersName allUsers, + GitRepositoryManager repoManager, + @Assisted Integer cleanupPercentage) { + this.allUsers = allUsers; + this.repoManager = repoManager; + this.cleanupPercentage = (cleanupPercentage == null) ? 100 : cleanupPercentage; + } + + public void execute() throws IOException { + allUsersRepo = repoManager.openRepository(allUsers); + + List draftRefs = allUsersRepo.getRefDatabase().getRefsByPrefix(DRAFT_REFS_PREFIX); + List zombieRefs = filterZombieRefs(draftRefs); + + System.out.println( + String.format( + "Found a total of %d zombie draft refs in %s repo.", + zombieRefs.size(), allUsers.get())); + + System.out.println(String.format("Cleanup percentage = %d", cleanupPercentage)); + zombieRefs = + zombieRefs.stream() + .filter(ref -> Change.Id.fromAllUsersRef(ref.getName()).get() % 100 < cleanupPercentage) + .collect(toImmutableList()); + System.out.println( + String.format("Number of zombie refs to be cleaned = %d", zombieRefs.size())); + + long zombieRefsCnt = zombieRefs.size(); + long deletedRefsCnt = 0; + long startTime = System.currentTimeMillis(); + + for (List refsBatch : Iterables.partition(zombieRefs, CHUNK_SIZE)) { + deleteBatchZombieRefs(refsBatch); + long elapsed = (System.currentTimeMillis() - startTime) / 1000; + deletedRefsCnt += refsBatch.size(); + logProgress(deletedRefsCnt, zombieRefsCnt, elapsed); + } + } + + private void deleteBatchZombieRefs(List refsBatch) throws IOException { + List deleteCommands = + refsBatch.stream() + .map( + zombieRef -> + new ReceiveCommand( + zombieRef.getObjectId(), ObjectId.zeroId(), zombieRef.getName())) + .collect(toImmutableList()); + BatchRefUpdate bru = allUsersRepo.getRefDatabase().newBatchUpdate(); + bru.setAtomic(true); + bru.addCommand(deleteCommands); + RefUpdateUtil.executeChecked(bru, allUsersRepo); + } + + private List filterZombieRefs(List allDraftRefs) throws IOException { + List zombieRefs = new ArrayList<>((int) (allDraftRefs.size() * 0.5)); + for (Ref ref : allDraftRefs) { + if (isZombieRef(ref)) { + zombieRefs.add(ref); + } + } + return zombieRefs; + } + + private boolean isZombieRef(Ref ref) throws IOException { + return allUsersRepo.parseCommit(ref.getObjectId()).getTree().getName().equals(EMPTY_TREE_ID); + } + + private void logProgress(long deletedRefsCount, long allRefsCount, long elapsed) { + System.out.format( + "Deleted %d/%d zombie draft refs (%d seconds)\n", deletedRefsCount, allRefsCount, elapsed); + } +} diff --git a/javatests/com/google/gerrit/server/git/DeleteZombieCommentsRefsTest.java b/javatests/com/google/gerrit/server/git/DeleteZombieCommentsRefsTest.java new file mode 100644 index 0000000000..fc41221126 --- /dev/null +++ b/javatests/com/google/gerrit/server/git/DeleteZombieCommentsRefsTest.java @@ -0,0 +1,247 @@ +// Copyright (C) 2020 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 static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.gerrit.git.RefUpdateUtil; +import com.google.gerrit.reviewdb.client.Account; +import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.reviewdb.client.RefNames; +import com.google.gerrit.server.config.AllUsersName; +import com.google.gerrit.server.notedb.DeleteZombieCommentsRefs; +import com.google.gerrit.server.util.time.TimeUtil; +import com.google.gerrit.testing.InMemoryRepositoryManager; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.eclipse.jgit.lib.BatchRefUpdate; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.TreeFormatter; +import org.eclipse.jgit.revwalk.RevBlob; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class DeleteZombieCommentsRefsTest { + private InMemoryRepositoryManager repoManager = new InMemoryRepositoryManager(); + private Project.NameKey allUsersProject = new Project.NameKey("All-Users"); + + @Test + public void cleanZombieDraftsSmall() throws Exception { + try (Repository usersRepo = repoManager.createRepository(allUsersProject)) { + Ref ref1 = createRefWithNonEmptyTreeCommit(usersRepo, 1, 1000001); + Ref ref2 = createRefWithEmptyTreeCommit(usersRepo, 1, 1000002); + + DeleteZombieCommentsRefs clean = + new DeleteZombieCommentsRefs(new AllUsersName("All-Users"), repoManager, null); + clean.execute(); + + /* Check that ref1 still exists, and ref2 is deleted */ + assertThat(usersRepo.exactRef(ref1.getName())).isNotNull(); + assertThat(usersRepo.exactRef(ref2.getName())).isNull(); + } + } + + @Test + public void cleanZombieDraftsWithPercentage() throws Exception { + try (Repository usersRepo = repoManager.createRepository(allUsersProject)) { + Ref ref1 = createRefWithNonEmptyTreeCommit(usersRepo, 1005, 1000001); + Ref ref2 = createRefWithEmptyTreeCommit(usersRepo, 1006, 1000002); + Ref ref3 = createRefWithEmptyTreeCommit(usersRepo, 1060, 1000002); + + assertThat(usersRepo.getRefDatabase().getRefs()).hasSize(3); + + int cleanupPercentage = 50; + DeleteZombieCommentsRefs clean = + new DeleteZombieCommentsRefs( + new AllUsersName("All-Users"), repoManager, cleanupPercentage); + clean.execute(); + + /* ref1 not deleted, ref2 deleted, ref3 not deleted because of the clean percentage */ + assertThat(usersRepo.getRefDatabase().getRefs()).hasSize(2); + assertThat(usersRepo.exactRef(ref1.getName())).isNotNull(); + assertThat(usersRepo.exactRef(ref2.getName())).isNull(); + assertThat(usersRepo.exactRef(ref3.getName())).isNotNull(); + + /* Re-execute the cleanup and make sure nothing's changed */ + clean.execute(); + assertThat(usersRepo.getRefDatabase().getRefs()).hasSize(2); + assertThat(usersRepo.exactRef(ref1.getName())).isNotNull(); + assertThat(usersRepo.exactRef(ref2.getName())).isNull(); + assertThat(usersRepo.exactRef(ref3.getName())).isNotNull(); + + /* Increase the cleanup percentage */ + cleanupPercentage = 70; + clean = + new DeleteZombieCommentsRefs( + new AllUsersName("All-Users"), repoManager, cleanupPercentage); + + clean.execute(); + + /* Now ref3 is deleted */ + assertThat(usersRepo.getRefDatabase().getRefs()).hasSize(1); + assertThat(usersRepo.exactRef(ref1.getName())).isNotNull(); + assertThat(usersRepo.exactRef(ref2.getName())).isNull(); + assertThat(usersRepo.exactRef(ref3.getName())).isNull(); + } + } + + @Test + public void cleanZombieDraftsLarge() throws Exception { + try (Repository usersRepo = repoManager.createRepository(allUsersProject)) { + int goodRefsCnt = 5000; + int zombieRefsCnt = 5000; + int userIdGoodRefs = 1000001; + int userIdBadRefs = 1000002; + + Ref nonEmptyBaseRef = createRefWithNonEmptyTreeCommit(usersRepo, 1, userIdGoodRefs); + Ref emptyBaseRef = createRefWithEmptyTreeCommit(usersRepo, 1, userIdBadRefs); + + List goodRefs = + createNRefsOnCommit( + usersRepo, nonEmptyBaseRef.getObjectId(), goodRefsCnt, userIdGoodRefs); + List badRefs = + createNRefsOnCommit(usersRepo, emptyBaseRef.getObjectId(), zombieRefsCnt, userIdBadRefs); + + goodRefs.add(0, nonEmptyBaseRef.getName()); + badRefs.add(0, emptyBaseRef.getName()); + + assertThat(usersRepo.getRefDatabase().getRefs().size()) + .isEqualTo(goodRefs.size() + badRefs.size()); + + DeleteZombieCommentsRefs clean = + new DeleteZombieCommentsRefs(new AllUsersName("All-Users"), repoManager, null); + clean.execute(); + + assertThat( + usersRepo.getRefDatabase().getRefs().stream() + .map(Ref::getName) + .collect(toImmutableList())) + .containsExactlyElementsIn(goodRefs); + + assertThat( + usersRepo.getRefDatabase().getRefs().stream() + .map(Ref::getName) + .collect(toImmutableList())) + .containsNoneIn(badRefs); + } + } + + private static List createNRefsOnCommit( + Repository usersRepo, ObjectId commitId, int n, int uuid) throws IOException { + List refNames = new ArrayList<>(); + BatchRefUpdate bru = usersRepo.getRefDatabase().newBatchUpdate(); + bru.setAtomic(true); + for (int i = 2; i <= n + 1; i++) { + String refName = getRefName(i, uuid); + bru.addCommand( + new ReceiveCommand(ObjectId.zeroId(), commitId, refName, ReceiveCommand.Type.CREATE)); + refNames.add(refName); + } + RefUpdateUtil.executeChecked(bru, usersRepo); + return refNames; + } + + private static String getRefName(int changeId, int userId) { + Change.Id cId = new Change.Id(changeId); + Account.Id aId = new Account.Id(userId); + return RefNames.refsDraftComments(cId, aId); + } + + private static Ref createRefWithNonEmptyTreeCommit(Repository usersRepo, int changeId, int userId) + throws IOException { + RevWalk rw = new RevWalk(usersRepo); + ObjectId fileObj = createBlob(usersRepo, String.format("file %d content", changeId)); + ObjectId treeObj = + createTree(usersRepo, rw.lookupBlob(fileObj), String.format("file%d.txt", changeId)); + ObjectId commitObj = createCommit(usersRepo, treeObj, null); + Ref refObj = createRef(usersRepo, commitObj, getRefName(changeId, userId)); + return refObj; + } + + private static Ref createRefWithEmptyTreeCommit(Repository usersRepo, int changeId, int userId) + throws IOException { + ObjectId treeEmpty = createTree(usersRepo, null, ""); + ObjectId commitObj = createCommit(usersRepo, treeEmpty, null); + Ref refObj = createRef(usersRepo, commitObj, getRefName(changeId, userId)); + return refObj; + } + + private static Ref createRef(Repository repo, ObjectId commitId, String refName) + throws IOException { + RefUpdate update = repo.updateRef(refName); + update.setNewObjectId(commitId); + update.setForceUpdate(true); + update.update(); + return repo.exactRef(refName); + } + + private static ObjectId createCommit(Repository repo, ObjectId treeId, ObjectId parentCommit) + throws IOException { + try (ObjectInserter oi = repo.newObjectInserter()) { + PersonIdent committer = + new PersonIdent(new PersonIdent("Foo Bar", "foo.bar@baz.com"), TimeUtil.nowTs()); + CommitBuilder cb = new CommitBuilder(); + cb.setTreeId(treeId); + cb.setCommitter(committer); + cb.setAuthor(committer); + cb.setMessage("Test commit"); + if (parentCommit != null) { + cb.setParentIds(parentCommit); + } + ObjectId commitId = oi.insert(cb); + oi.flush(); + oi.close(); + return commitId; + } + } + + private static ObjectId createTree(Repository repo, RevBlob blob, String blobName) + throws IOException { + try (ObjectInserter oi = repo.newObjectInserter()) { + TreeFormatter formatter = new TreeFormatter(); + if (blob != null) { + formatter.append(blobName, blob); + } + ObjectId treeId = oi.insert(formatter); + oi.flush(); + oi.close(); + return treeId; + } + } + + private static ObjectId createBlob(Repository repo, String content) throws IOException { + try (ObjectInserter oi = repo.newObjectInserter()) { + ObjectId blobId = oi.insert(Constants.OBJ_BLOB, content.getBytes(UTF_8)); + oi.flush(); + oi.close(); + return blobId; + } + } +} diff --git a/tools/bzl/bazelisk_version.bzl b/tools/bzl/bazelisk_version.bzl deleted file mode 100644 index d8b3d10982..0000000000 --- a/tools/bzl/bazelisk_version.bzl +++ /dev/null @@ -1,16 +0,0 @@ -_template = """ -load("@bazel_skylib//lib:versions.bzl", "versions") - -def check_bazel_version(): - versions.check(minimum_bazel_version = "{version}") -""".strip() - -def _impl(repository_ctx): - repository_ctx.symlink(Label("@//:.bazelversion"), ".bazelversion") - bazelversion = repository_ctx.read(".bazelversion").strip() - - repository_ctx.file("BUILD", executable = False) - - repository_ctx.file("check.bzl", executable = False, content = _template.format(version = bazelversion)) - -bazelisk_version = repository_rule(implementation = _impl)