From dee4240ce86f21d262ac071ed80838223bf4d516 Mon Sep 17 00:00:00 2001 From: Ivan Frade Date: Thu, 26 Aug 2021 13:19:01 -0700 Subject: [PATCH] RepoCommand: Move bare/regular superproject writing to their own classes RepoCommand parses the manifest to get a list of projects, clears up conflicts and then writes to the superproject. The first steps are common but the writing is completely different for bare or "regular" (with working dir) repository. Split writing to bare and regular repos into its own classes. This simplifies RepoCommand class and makes clearer what happens on each side (e.g. many options apply only to bare repos). Change-Id: I256e15729bd53ee15fc56de88bce86a2edb2417a --- .../gitrepo/BareSuperprojectWriterTest.java | 79 +++++ .../jgit/gitrepo/BareSuperprojectWriter.java | 294 ++++++++++++++++++ .../gitrepo/RegularSuperprojectWriter.java | 104 +++++++ .../org/eclipse/jgit/gitrepo/RepoCommand.java | 291 ++--------------- 4 files changed, 495 insertions(+), 273 deletions(-) create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/BareSuperprojectWriterTest.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/BareSuperprojectWriter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RegularSuperprojectWriter.java diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/BareSuperprojectWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/BareSuperprojectWriterTest.java new file mode 100644 index 0000000000..d52292dc97 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/BareSuperprojectWriterTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2021, Google Inc. and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.gitrepo; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jgit.gitrepo.BareSuperprojectWriter.BareWriterConfig; +import org.eclipse.jgit.gitrepo.RepoCommand.RemoteReader; +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.junit.Test; + +public class BareSuperprojectWriterTest extends RepositoryTestCase { + + private static final String SHA1_A = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + + @Override + public void setUp() throws Exception { + super.setUp(); + } + + @Test + public void write_setGitModulesContents() throws Exception { + try (Repository bareRepo = createBareRepository()) { + RepoProject repoProject = new RepoProject("subprojectX", "path/to", + "refs/heads/branch-x", "remote", ""); + repoProject.setUrl("http://example.com/a"); + + RemoteReader mockRemoteReader = mock(RemoteReader.class); + when(mockRemoteReader.sha1("http://example.com/a", + "refs/heads/branch-x")) + .thenReturn(ObjectId.fromString(SHA1_A)); + + BareSuperprojectWriter w = new BareSuperprojectWriter(bareRepo, + null, "refs/heads/master", author, mockRemoteReader, + BareWriterConfig.getDefault()); + + RevCommit commit = w.write(Arrays.asList(repoProject)); + + String contents = readContents(bareRepo, commit, ".gitmodules"); + List contentLines = Arrays + .asList(contents.split("\n")); + assertThat(contentLines.get(0), + is("[submodule \"subprojectX\"]")); + assertThat(contentLines.subList(1, contentLines.size()), + containsInAnyOrder(is("\tbranch = refs/heads/branch-x"), + is("\tpath = path/to"), + is("\turl = http://example.com/a"))); + } + } + + private String readContents(Repository repo, RevCommit commit, + String path) throws Exception { + String idStr = commit.getId().name() + ":" + path; + ObjectId modId = repo.resolve(idStr); + try (ObjectReader reader = repo.newObjectReader()) { + return new String( + reader.open(modId).getCachedBytes(Integer.MAX_VALUE)); + + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/BareSuperprojectWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/BareSuperprojectWriter.java new file mode 100644 index 0000000000..d6482968fe --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/BareSuperprojectWriter.java @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2021, Google Inc. and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.gitrepo; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.jgit.lib.Constants.R_TAGS; + +import java.io.IOException; +import java.net.URI; +import java.text.MessageFormat; +import java.util.List; + +import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheBuilder; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.gitrepo.RepoCommand.ManifestErrorException; +import org.eclipse.jgit.gitrepo.RepoCommand.RemoteFile; +import org.eclipse.jgit.gitrepo.RepoCommand.RemoteReader; +import org.eclipse.jgit.gitrepo.RepoCommand.RemoteUnavailableException; +import org.eclipse.jgit.gitrepo.RepoProject.CopyFile; +import org.eclipse.jgit.gitrepo.RepoProject.LinkFile; +import org.eclipse.jgit.gitrepo.internal.RepoText; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.RefUpdate.Result; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.util.FileUtils; + +/** + * Writes .gitmodules and gitlinks of parsed manifest projects into a bare + * repository. + * + * To write on a regular repository, see {@link RegularSuperprojectWriter}. + */ +class BareSuperprojectWriter { + private static final int LOCK_FAILURE_MAX_RETRIES = 5; + + // Retry exponentially with delays in this range + private static final int LOCK_FAILURE_MIN_RETRY_DELAY_MILLIS = 50; + + private static final int LOCK_FAILURE_MAX_RETRY_DELAY_MILLIS = 5000; + + private final Repository repo; + + private final URI targetUri; + + private final String targetBranch; + + private final RemoteReader callback; + + private final BareWriterConfig config; + + private final PersonIdent author; + + static class BareWriterConfig { + boolean ignoreRemoteFailures = false; + + boolean recordRemoteBranch = true; + + boolean recordSubmoduleLabels = true; + + boolean recordShallowSubmodules = true; + + static BareWriterConfig getDefault() { + return new BareWriterConfig(); + } + + private BareWriterConfig() { + } + } + + BareSuperprojectWriter(Repository repo, URI targetUri, + String targetBranch, + PersonIdent author, RemoteReader callback, + BareWriterConfig config) { + assert (repo.isBare()); + this.repo = repo; + this.targetUri = targetUri; + this.targetBranch = targetBranch; + this.author = author; + this.callback = callback; + this.config = config; + } + + RevCommit write(List repoProjects) + throws GitAPIException { + DirCache index = DirCache.newInCore(); + ObjectInserter inserter = repo.newObjectInserter(); + + try (RevWalk rw = new RevWalk(repo)) { + prepareIndex(repoProjects, index, inserter); + ObjectId treeId = index.writeTree(inserter); + long prevDelay = 0; + for (int i = 0; i < LOCK_FAILURE_MAX_RETRIES - 1; i++) { + try { + return commitTreeOnCurrentTip(inserter, rw, treeId); + } catch (ConcurrentRefUpdateException e) { + prevDelay = FileUtils.delay(prevDelay, + LOCK_FAILURE_MIN_RETRY_DELAY_MILLIS, + LOCK_FAILURE_MAX_RETRY_DELAY_MILLIS); + Thread.sleep(prevDelay); + repo.getRefDatabase().refresh(); + } + } + // In the last try, just propagate the exceptions + return commitTreeOnCurrentTip(inserter, rw, treeId); + } catch (IOException | InterruptedException e) { + throw new ManifestErrorException(e); + } + } + + private void prepareIndex(List projects, DirCache index, + ObjectInserter inserter) throws IOException, GitAPIException { + Config cfg = new Config(); + StringBuilder attributes = new StringBuilder(); + DirCacheBuilder builder = index.builder(); + for (RepoProject proj : projects) { + String name = proj.getName(); + String path = proj.getPath(); + String url = proj.getUrl(); + ObjectId objectId; + if (ObjectId.isId(proj.getRevision())) { + objectId = ObjectId.fromString(proj.getRevision()); + } else { + objectId = callback.sha1(url, proj.getRevision()); + if (objectId == null && !config.ignoreRemoteFailures) { + throw new RemoteUnavailableException(url); + } + if (config.recordRemoteBranch) { + // "branch" field is only for non-tag references. + // Keep tags in "ref" field as hint for other tools. + String field = proj.getRevision().startsWith(R_TAGS) ? "ref" //$NON-NLS-1$ + : "branch"; //$NON-NLS-1$ + cfg.setString("submodule", name, field, //$NON-NLS-1$ + proj.getRevision()); + } + + if (config.recordShallowSubmodules + && proj.getRecommendShallow() != null) { + // The shallow recommendation is losing information. + // As the repo manifests stores the recommended + // depth in the 'clone-depth' field, while + // git core only uses a binary 'shallow = true/false' + // hint, we'll map any depth to 'shallow = true' + cfg.setBoolean("submodule", name, "shallow", //$NON-NLS-1$ //$NON-NLS-2$ + true); + } + } + if (config.recordSubmoduleLabels) { + StringBuilder rec = new StringBuilder(); + rec.append("/"); //$NON-NLS-1$ + rec.append(path); + for (String group : proj.getGroups()) { + rec.append(" "); //$NON-NLS-1$ + rec.append(group); + } + rec.append("\n"); //$NON-NLS-1$ + attributes.append(rec.toString()); + } + + URI submodUrl = URI.create(url); + if (targetUri != null) { + submodUrl = RepoCommand.relativize(targetUri, submodUrl); + } + cfg.setString("submodule", name, "path", path); //$NON-NLS-1$ //$NON-NLS-2$ + cfg.setString("submodule", name, "url", //$NON-NLS-1$ //$NON-NLS-2$ + submodUrl.toString()); + + // create gitlink + if (objectId != null) { + DirCacheEntry dcEntry = new DirCacheEntry(path); + dcEntry.setObjectId(objectId); + dcEntry.setFileMode(FileMode.GITLINK); + builder.add(dcEntry); + + for (CopyFile copyfile : proj.getCopyFiles()) { + RemoteFile rf = callback.readFileWithMode(url, + proj.getRevision(), copyfile.src); + objectId = inserter.insert(Constants.OBJ_BLOB, + rf.getContents()); + dcEntry = new DirCacheEntry(copyfile.dest); + dcEntry.setObjectId(objectId); + dcEntry.setFileMode(rf.getFileMode()); + builder.add(dcEntry); + } + for (LinkFile linkfile : proj.getLinkFiles()) { + String link; + if (linkfile.dest.contains("/")) { //$NON-NLS-1$ + link = FileUtils.relativizeGitPath( + linkfile.dest.substring(0, + linkfile.dest.lastIndexOf('/')), + proj.getPath() + "/" + linkfile.src); //$NON-NLS-1$ + } else { + link = proj.getPath() + "/" + linkfile.src; //$NON-NLS-1$ + } + + objectId = inserter.insert(Constants.OBJ_BLOB, + link.getBytes(UTF_8)); + dcEntry = new DirCacheEntry(linkfile.dest); + dcEntry.setObjectId(objectId); + dcEntry.setFileMode(FileMode.SYMLINK); + builder.add(dcEntry); + } + } + } + String content = cfg.toText(); + + // create a new DirCacheEntry for .gitmodules file. + DirCacheEntry dcEntry = new DirCacheEntry( + Constants.DOT_GIT_MODULES); + ObjectId objectId = inserter.insert(Constants.OBJ_BLOB, + content.getBytes(UTF_8)); + dcEntry.setObjectId(objectId); + dcEntry.setFileMode(FileMode.REGULAR_FILE); + builder.add(dcEntry); + + if (config.recordSubmoduleLabels) { + // create a new DirCacheEntry for .gitattributes file. + DirCacheEntry dcEntryAttr = new DirCacheEntry( + Constants.DOT_GIT_ATTRIBUTES); + ObjectId attrId = inserter.insert(Constants.OBJ_BLOB, + attributes.toString().getBytes(UTF_8)); + dcEntryAttr.setObjectId(attrId); + dcEntryAttr.setFileMode(FileMode.REGULAR_FILE); + builder.add(dcEntryAttr); + } + + builder.finish(); + } + + private RevCommit commitTreeOnCurrentTip(ObjectInserter inserter, + RevWalk rw, ObjectId treeId) + throws IOException, ConcurrentRefUpdateException { + ObjectId headId = repo.resolve(targetBranch + "^{commit}"); //$NON-NLS-1$ + if (headId != null + && rw.parseCommit(headId).getTree().getId().equals(treeId)) { + // No change. Do nothing. + return rw.parseCommit(headId); + } + + CommitBuilder commit = new CommitBuilder(); + commit.setTreeId(treeId); + if (headId != null) { + commit.setParentIds(headId); + } + commit.setAuthor(author); + commit.setCommitter(author); + commit.setMessage(RepoText.get().repoCommitMessage); + + ObjectId commitId = inserter.insert(commit); + inserter.flush(); + + RefUpdate ru = repo.updateRef(targetBranch); + ru.setNewObjectId(commitId); + ru.setExpectedOldObjectId(headId != null ? headId : ObjectId.zeroId()); + Result rc = ru.update(rw); + switch (rc) { + case NEW: + case FORCED: + case FAST_FORWARD: + // Successful. Do nothing. + break; + case REJECTED: + case LOCK_FAILURE: + throw new ConcurrentRefUpdateException(MessageFormat.format( + JGitText.get().cannotLock, targetBranch), ru.getRef(), rc); + default: + throw new JGitInternalException( + MessageFormat.format(JGitText.get().updatingRefFailed, + targetBranch, commitId.name(), rc)); + } + + return rw.parseCommit(commitId); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RegularSuperprojectWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RegularSuperprojectWriter.java new file mode 100644 index 0000000000..afab9943a7 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RegularSuperprojectWriter.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2021, Google Inc. and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.gitrepo; + +import static org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME; +import static org.eclipse.jgit.lib.Constants.R_REMOTES; + +import java.io.IOException; +import java.util.List; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.SubmoduleAddCommand; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.gitrepo.RepoCommand.ManifestErrorException; +import org.eclipse.jgit.gitrepo.RepoProject.CopyFile; +import org.eclipse.jgit.gitrepo.RepoProject.LinkFile; +import org.eclipse.jgit.gitrepo.internal.RepoText; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; + +/** + * Writes .gitmodules and gitlinks of parsed manifest projects into a regular + * repository (using git submodule commands) + * + * To write on a bare repository, use {@link BareSuperprojectWriter} + */ +class RegularSuperprojectWriter { + + private Repository repo; + + private ProgressMonitor monitor; + + RegularSuperprojectWriter(Repository repo, ProgressMonitor monitor) { + this.repo = repo; + this.monitor = monitor; + } + + RevCommit write(List repoProjects) + throws GitAPIException { + try (Git git = new Git(repo)) { + for (RepoProject proj : repoProjects) { + addSubmodule(proj.getName(), proj.getUrl(), proj.getPath(), + proj.getRevision(), proj.getCopyFiles(), + proj.getLinkFiles(), git); + } + return git.commit().setMessage(RepoText.get().repoCommitMessage) + .call(); + } catch (IOException e) { + throw new ManifestErrorException(e); + } + } + + private void addSubmodule(String name, String url, String path, + String revision, List copyfiles, List linkfiles, + Git git) throws GitAPIException, IOException { + assert (!repo.isBare()); + assert (git != null); + if (!linkfiles.isEmpty()) { + throw new UnsupportedOperationException( + JGitText.get().nonBareLinkFilesNotSupported); + } + + SubmoduleAddCommand add = git.submoduleAdd().setName(name).setPath(path) + .setURI(url); + if (monitor != null) { + add.setProgressMonitor(monitor); + } + + Repository subRepo = add.call(); + if (revision != null) { + try (Git sub = new Git(subRepo)) { + sub.checkout().setName(findRef(revision, subRepo)).call(); + } + subRepo.close(); + git.add().addFilepattern(path).call(); + } + for (CopyFile copyfile : copyfiles) { + copyfile.copy(); + git.add().addFilepattern(copyfile.dest).call(); + } + } + + private static String findRef(String ref, Repository repo) + throws IOException { + if (!ObjectId.isId(ref)) { + Ref r = repo.exactRef(R_REMOTES + DEFAULT_REMOTE_NAME + "/" + ref); //$NON-NLS-1$ + if (r != null) { + return r.getName(); + } + } + return ref; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java index e0a822479f..30b8626730 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java @@ -9,11 +9,6 @@ */ package org.eclipse.jgit.gitrepo; -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME; -import static org.eclipse.jgit.lib.Constants.R_REMOTES; -import static org.eclipse.jgit.lib.Constants.R_TAGS; - import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -31,34 +26,20 @@ import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.GitCommand; -import org.eclipse.jgit.api.SubmoduleAddCommand; -import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRefNameException; -import org.eclipse.jgit.api.errors.JGitInternalException; -import org.eclipse.jgit.dircache.DirCache; -import org.eclipse.jgit.dircache.DirCacheBuilder; -import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.gitrepo.ManifestParser.IncludedFileReader; -import org.eclipse.jgit.gitrepo.RepoProject.CopyFile; -import org.eclipse.jgit.gitrepo.RepoProject.LinkFile; import org.eclipse.jgit.gitrepo.internal.RepoText; import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.lib.CommitBuilder; -import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefDatabase; -import org.eclipse.jgit.lib.RefUpdate; -import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.util.FileUtils; @@ -80,12 +61,7 @@ import org.eclipse.jgit.util.FileUtils; * @since 3.4 */ public class RepoCommand extends GitCommand { - private static final int LOCK_FAILURE_MAX_RETRIES = 5; - - // Retry exponentially with delays in this range - private static final int LOCK_FAILURE_MIN_RETRY_DELAY_MILLIS = 50; - private static final int LOCK_FAILURE_MAX_RETRY_DELAY_MILLIS = 5000; private String manifestPath; private String baseUri; @@ -93,14 +69,13 @@ public class RepoCommand extends GitCommand { private String groupsParam; private String branch; private String targetBranch = Constants.HEAD; - private boolean recordRemoteBranch = true; - private boolean recordSubmoduleLabels = true; - private boolean recordShallowSubmodules = true; private PersonIdent author; private RemoteReader callback; private InputStream inputStream; private IncludedFileReader includedReader; - private boolean ignoreRemoteFailures = false; + + private BareSuperprojectWriter.BareWriterConfig bareWriterConfig = BareSuperprojectWriter.BareWriterConfig + .getDefault(); private ProgressMonitor monitor; @@ -269,14 +244,14 @@ public class RepoCommand extends GitCommand { } @SuppressWarnings("serial") - private static class ManifestErrorException extends GitAPIException { + static class ManifestErrorException extends GitAPIException { ManifestErrorException(Throwable cause) { super(RepoText.get().invalidManifest, cause); } } @SuppressWarnings("serial") - private static class RemoteUnavailableException extends GitAPIException { + static class RemoteUnavailableException extends GitAPIException { RemoteUnavailableException(String uri) { super(MessageFormat.format(RepoText.get().errorRemoteUnavailable, uri)); } @@ -421,7 +396,7 @@ public class RepoCommand extends GitCommand { * @since 4.2 */ public RepoCommand setRecordRemoteBranch(boolean enable) { - this.recordRemoteBranch = enable; + this.bareWriterConfig.recordRemoteBranch = enable; return this; } @@ -436,7 +411,7 @@ public class RepoCommand extends GitCommand { * @since 4.4 */ public RepoCommand setRecordSubmoduleLabels(boolean enable) { - this.recordSubmoduleLabels = enable; + this.bareWriterConfig.recordSubmoduleLabels = enable; return this; } @@ -451,7 +426,7 @@ public class RepoCommand extends GitCommand { * @since 4.4 */ public RepoCommand setRecommendShallow(boolean enable) { - this.recordShallowSubmodules = enable; + this.bareWriterConfig.recordShallowSubmodules = enable; return this; } @@ -485,7 +460,7 @@ public class RepoCommand extends GitCommand { * @since 4.3 */ public RepoCommand setIgnoreRemoteFailures(boolean ignore) { - this.ignoreRemoteFailures = ignore; + this.bareWriterConfig.ignoreRemoteFailures = ignore; return this; } @@ -570,240 +545,18 @@ public class RepoCommand extends GitCommand { } if (repo.isBare()) { - if (author == null) - author = new PersonIdent(repo); - if (callback == null) - callback = new DefaultRemoteReader(); List renamedProjects = renameProjects(filteredProjects); - - DirCache index = DirCache.newInCore(); - ObjectInserter inserter = repo.newObjectInserter(); - - try (RevWalk rw = new RevWalk(repo)) { - prepareIndex(renamedProjects, index, inserter); - ObjectId treeId = index.writeTree(inserter); - long prevDelay = 0; - for (int i = 0; i < LOCK_FAILURE_MAX_RETRIES - 1; i++) { - try { - return commitTreeOnCurrentTip( - inserter, rw, treeId); - } catch (ConcurrentRefUpdateException e) { - prevDelay = FileUtils.delay(prevDelay, - LOCK_FAILURE_MIN_RETRY_DELAY_MILLIS, - LOCK_FAILURE_MAX_RETRY_DELAY_MILLIS); - Thread.sleep(prevDelay); - repo.getRefDatabase().refresh(); - } - } - // In the last try, just propagate the exceptions - return commitTreeOnCurrentTip(inserter, rw, treeId); - } catch (IOException | InterruptedException e) { - throw new ManifestErrorException(e); - } + BareSuperprojectWriter writer = new BareSuperprojectWriter(repo, targetUri, + targetBranch, + author == null ? new PersonIdent(repo) : author, + callback == null ? new DefaultRemoteReader() : callback, + bareWriterConfig); + return writer.write(renamedProjects); } - try (Git git = new Git(repo)) { - for (RepoProject proj : filteredProjects) { - addSubmodule(proj.getName(), proj.getUrl(), proj.getPath(), - proj.getRevision(), proj.getCopyFiles(), - proj.getLinkFiles(), git); - } - return git.commit().setMessage(RepoText.get().repoCommitMessage) - .call(); - } catch (IOException e) { - throw new ManifestErrorException(e); - } - } - private void prepareIndex(List projects, DirCache index, - ObjectInserter inserter) throws IOException, GitAPIException { - Config cfg = new Config(); - StringBuilder attributes = new StringBuilder(); - DirCacheBuilder builder = index.builder(); - for (RepoProject proj : projects) { - String name = proj.getName(); - String path = proj.getPath(); - String url = proj.getUrl(); - ObjectId objectId; - if (ObjectId.isId(proj.getRevision())) { - objectId = ObjectId.fromString(proj.getRevision()); - } else { - objectId = callback.sha1(url, proj.getRevision()); - if (objectId == null && !ignoreRemoteFailures) { - throw new RemoteUnavailableException(url); - } - if (recordRemoteBranch) { - // "branch" field is only for non-tag references. - // Keep tags in "ref" field as hint for other tools. - String field = proj.getRevision().startsWith(R_TAGS) ? "ref" //$NON-NLS-1$ - : "branch"; //$NON-NLS-1$ - cfg.setString("submodule", name, field, //$NON-NLS-1$ - proj.getRevision()); - } - if (recordShallowSubmodules - && proj.getRecommendShallow() != null) { - // The shallow recommendation is losing information. - // As the repo manifests stores the recommended - // depth in the 'clone-depth' field, while - // git core only uses a binary 'shallow = true/false' - // hint, we'll map any depth to 'shallow = true' - cfg.setBoolean("submodule", name, "shallow", //$NON-NLS-1$ //$NON-NLS-2$ - true); - } - } - if (recordSubmoduleLabels) { - StringBuilder rec = new StringBuilder(); - rec.append("/"); //$NON-NLS-1$ - rec.append(path); - for (String group : proj.getGroups()) { - rec.append(" "); //$NON-NLS-1$ - rec.append(group); - } - rec.append("\n"); //$NON-NLS-1$ - attributes.append(rec.toString()); - } - - URI submodUrl = URI.create(url); - if (targetUri != null) { - submodUrl = relativize(targetUri, submodUrl); - } - cfg.setString("submodule", name, "path", path); //$NON-NLS-1$ //$NON-NLS-2$ - cfg.setString("submodule", name, "url", //$NON-NLS-1$ //$NON-NLS-2$ - submodUrl.toString()); - - // create gitlink - if (objectId != null) { - DirCacheEntry dcEntry = new DirCacheEntry(path); - dcEntry.setObjectId(objectId); - dcEntry.setFileMode(FileMode.GITLINK); - builder.add(dcEntry); - - for (CopyFile copyfile : proj.getCopyFiles()) { - RemoteFile rf = callback.readFileWithMode(url, - proj.getRevision(), copyfile.src); - objectId = inserter.insert(Constants.OBJ_BLOB, - rf.getContents()); - dcEntry = new DirCacheEntry(copyfile.dest); - dcEntry.setObjectId(objectId); - dcEntry.setFileMode(rf.getFileMode()); - builder.add(dcEntry); - } - for (LinkFile linkfile : proj.getLinkFiles()) { - String link; - if (linkfile.dest.contains("/")) { //$NON-NLS-1$ - link = FileUtils.relativizeGitPath( - linkfile.dest.substring(0, - linkfile.dest.lastIndexOf('/')), - proj.getPath() + "/" + linkfile.src); //$NON-NLS-1$ - } else { - link = proj.getPath() + "/" + linkfile.src; //$NON-NLS-1$ - } - - objectId = inserter.insert(Constants.OBJ_BLOB, - link.getBytes(UTF_8)); - dcEntry = new DirCacheEntry(linkfile.dest); - dcEntry.setObjectId(objectId); - dcEntry.setFileMode(FileMode.SYMLINK); - builder.add(dcEntry); - } - } - } - String content = cfg.toText(); - - // create a new DirCacheEntry for .gitmodules file. - DirCacheEntry dcEntry = new DirCacheEntry( - Constants.DOT_GIT_MODULES); - ObjectId objectId = inserter.insert(Constants.OBJ_BLOB, - content.getBytes(UTF_8)); - dcEntry.setObjectId(objectId); - dcEntry.setFileMode(FileMode.REGULAR_FILE); - builder.add(dcEntry); - - if (recordSubmoduleLabels) { - // create a new DirCacheEntry for .gitattributes file. - DirCacheEntry dcEntryAttr = new DirCacheEntry( - Constants.DOT_GIT_ATTRIBUTES); - ObjectId attrId = inserter.insert(Constants.OBJ_BLOB, - attributes.toString().getBytes(UTF_8)); - dcEntryAttr.setObjectId(attrId); - dcEntryAttr.setFileMode(FileMode.REGULAR_FILE); - builder.add(dcEntryAttr); - } - - builder.finish(); - } - - private RevCommit commitTreeOnCurrentTip(ObjectInserter inserter, - RevWalk rw, ObjectId treeId) - throws IOException, ConcurrentRefUpdateException { - ObjectId headId = repo.resolve(targetBranch + "^{commit}"); //$NON-NLS-1$ - if (headId != null && rw.parseCommit(headId).getTree().getId().equals(treeId)) { - // No change. Do nothing. - return rw.parseCommit(headId); - } - - CommitBuilder commit = new CommitBuilder(); - commit.setTreeId(treeId); - if (headId != null) - commit.setParentIds(headId); - commit.setAuthor(author); - commit.setCommitter(author); - commit.setMessage(RepoText.get().repoCommitMessage); - - ObjectId commitId = inserter.insert(commit); - inserter.flush(); - - RefUpdate ru = repo.updateRef(targetBranch); - ru.setNewObjectId(commitId); - ru.setExpectedOldObjectId(headId != null ? headId : ObjectId.zeroId()); - Result rc = ru.update(rw); - switch (rc) { - case NEW: - case FORCED: - case FAST_FORWARD: - // Successful. Do nothing. - break; - case REJECTED: - case LOCK_FAILURE: - throw new ConcurrentRefUpdateException(MessageFormat - .format(JGitText.get().cannotLock, targetBranch), - ru.getRef(), rc); - default: - throw new JGitInternalException(MessageFormat.format( - JGitText.get().updatingRefFailed, - targetBranch, commitId.name(), rc)); - } - - return rw.parseCommit(commitId); - } - - private void addSubmodule(String name, String url, String path, - String revision, List copyfiles, List linkfiles, - Git git) throws GitAPIException, IOException { - assert (!repo.isBare()); - assert (git != null); - if (!linkfiles.isEmpty()) { - throw new UnsupportedOperationException( - JGitText.get().nonBareLinkFilesNotSupported); - } - - SubmoduleAddCommand add = git.submoduleAdd().setName(name).setPath(path) - .setURI(url); - if (monitor != null) - add.setProgressMonitor(monitor); - - Repository subRepo = add.call(); - if (revision != null) { - try (Git sub = new Git(subRepo)) { - sub.checkout().setName(findRef(revision, subRepo)).call(); - } - subRepo.close(); - git.add().addFilepattern(path).call(); - } - for (CopyFile copyfile : copyfiles) { - copyfile.copy(); - git.add().addFilepattern(copyfile.dest).call(); - } + RegularSuperprojectWriter writer = new RegularSuperprojectWriter(repo, monitor); + return writer.write(filteredProjects); } /** @@ -910,13 +663,5 @@ public class RepoCommand extends GitCommand { return URI.create(j.toString()); } - private static String findRef(String ref, Repository repo) - throws IOException { - if (!ObjectId.isId(ref)) { - Ref r = repo.exactRef(R_REMOTES + DEFAULT_REMOTE_NAME + "/" + ref); //$NON-NLS-1$ - if (r != null) - return r.getName(); - } - return ref; - } + } -- 2.39.5