Merge changes I11366273,I256e1572

* changes:
  RepoCommand: Offer to set extra files in the destination repository
  RepoCommand: Move bare/regular superproject writing to their own classes
This commit is contained in:
Ivan Frade 2022-02-01 17:41:44 -05:00 committed by Gerrit Code Review @ Eclipse.org
commit 424c861477
4 changed files with 563 additions and 273 deletions

View File

@ -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.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(), List.of());
RevCommit commit = w.write(Arrays.asList(repoProject));
String contents = readContents(bareRepo, commit, ".gitmodules");
List<String> 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")));
}
}
@Test
public void write_setExtraContents() 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(),
List.of(new BareSuperprojectWriter.ExtraContent("x",
"extra-content")));
RevCommit commit = w.write(Arrays.asList(repoProject));
String contents = readContents(bareRepo, commit, "x");
assertThat(contents, is("extra-content"));
}
}
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));
}
}
}

View File

@ -0,0 +1,319 @@
/*
* 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;
private List<ExtraContent> extraContents;
static class BareWriterConfig {
boolean ignoreRemoteFailures = false;
boolean recordRemoteBranch = true;
boolean recordSubmoduleLabels = true;
boolean recordShallowSubmodules = true;
static BareWriterConfig getDefault() {
return new BareWriterConfig();
}
private BareWriterConfig() {
}
}
static class ExtraContent {
final String path;
final String content;
ExtraContent(String path, String content) {
this.path = path;
this.content = content;
}
}
BareSuperprojectWriter(Repository repo, URI targetUri,
String targetBranch,
PersonIdent author, RemoteReader callback,
BareWriterConfig config,
List<ExtraContent> extraContents) {
assert (repo.isBare());
this.repo = repo;
this.targetUri = targetUri;
this.targetBranch = targetBranch;
this.author = author;
this.callback = callback;
this.config = config;
this.extraContents = extraContents;
}
RevCommit write(List<RepoProject> 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<RepoProject> 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);
}
for (ExtraContent ec : extraContents) {
DirCacheEntry extraDcEntry = new DirCacheEntry(ec.path);
ObjectId oid = inserter.insert(Constants.OBJ_BLOB,
ec.content.getBytes(UTF_8));
extraDcEntry.setObjectId(oid);
extraDcEntry.setFileMode(FileMode.REGULAR_FILE);
builder.add(extraDcEntry);
}
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);
}
}

View File

@ -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<RepoProject> 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<CopyFile> copyfiles, List<LinkFile> 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;
}
}

View File

@ -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,21 @@
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.BareSuperprojectWriter.ExtraContent;
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 +62,7 @@
* @since 3.4
*/
public class RepoCommand extends GitCommand<RevCommit> {
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,17 +70,18 @@ public class RepoCommand extends GitCommand<RevCommit> {
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;
private final List<ExtraContent> extraContents = new ArrayList<>();
/**
* A callback to get ref sha1 of a repository from its uri.
*
@ -269,14 +247,14 @@ public RemoteFile readFileWithMode(String uri, String ref, String path)
}
@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 +399,7 @@ public RepoCommand setTargetBranch(String branch) {
* @since 4.2
*/
public RepoCommand setRecordRemoteBranch(boolean enable) {
this.recordRemoteBranch = enable;
this.bareWriterConfig.recordRemoteBranch = enable;
return this;
}
@ -436,7 +414,7 @@ public RepoCommand setRecordRemoteBranch(boolean enable) {
* @since 4.4
*/
public RepoCommand setRecordSubmoduleLabels(boolean enable) {
this.recordSubmoduleLabels = enable;
this.bareWriterConfig.recordSubmoduleLabels = enable;
return this;
}
@ -451,7 +429,7 @@ public RepoCommand setRecordSubmoduleLabels(boolean enable) {
* @since 4.4
*/
public RepoCommand setRecommendShallow(boolean enable) {
this.recordShallowSubmodules = enable;
this.bareWriterConfig.recordShallowSubmodules = enable;
return this;
}
@ -485,7 +463,7 @@ public RepoCommand setProgressMonitor(ProgressMonitor monitor) {
* @since 4.3
*/
public RepoCommand setIgnoreRemoteFailures(boolean ignore) {
this.ignoreRemoteFailures = ignore;
this.bareWriterConfig.ignoreRemoteFailures = ignore;
return this;
}
@ -534,6 +512,22 @@ public RepoCommand setIncludedFileReader(IncludedFileReader reader) {
return this;
}
/**
* Create a file with the given content in the destination repository
*
* @param path
* where to create the file in the destination repository
* @param contents
* content for the create file
* @return this command
*
* @since 6.1
*/
public RepoCommand addToDestination(String path, String contents) {
this.extraContents.add(new ExtraContent(path, contents));
return this;
}
/** {@inheritDoc} */
@Override
public RevCommit call() throws GitAPIException {
@ -570,240 +564,18 @@ public RevCommit call() throws GitAPIException {
}
if (repo.isBare()) {
if (author == null)
author = new PersonIdent(repo);
if (callback == null)
callback = new DefaultRemoteReader();
List<RepoProject> 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);
}
}
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<RepoProject> 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);
BareSuperprojectWriter writer = new BareSuperprojectWriter(repo, targetUri,
targetBranch,
author == null ? new PersonIdent(repo) : author,
callback == null ? new DefaultRemoteReader() : callback,
bareWriterConfig, extraContents);
return writer.write(renamedProjects);
}
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<CopyFile> copyfiles, List<LinkFile> 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 +682,4 @@ static URI relativize(URI current, URI target) {
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;
}
}