Git core learned about attributes in pathspecs: pathspec: allow querying for attributes The pathspec mechanism is extended via the new ":(attr:eol=input)pattern/to/match" syntax to filter paths so that it requires paths to not just match the given pattern but also have the specified attrs attached for them to be chosen. (177161a5f7, 2016-05-20) We intend to use these pathspec attribute patterns for submodule grouping, similar to the grouping in repo. So the RepoCommand which translates repo manifest files into submodules should propagate this information along. This requires writing information to the .gitattributes file instead of the .gitmodules file. For now we just overwrite any existing .gitattributes file and do not care about prior attributes set. If this becomes an issue we need to figure out how to correctly amend the grouping information to an existing .gitattributes file. Change-Id: I0f55b45786b6b8fc3d5be62d7f6aab9ac00ed60e Signed-off-by: Stefan Beller <sbeller@google.com>tags/v4.4.0.201606011500-rc2
import java.io.BufferedReader; | import java.io.BufferedReader; | ||||
import java.io.File; | import java.io.File; | ||||
import java.io.FileReader; | import java.io.FileReader; | ||||
import java.util.Arrays; | |||||
import java.util.HashSet; | |||||
import java.util.Set; | |||||
import org.eclipse.jgit.api.Git; | import org.eclipse.jgit.api.Git; | ||||
import org.eclipse.jgit.junit.JGitTestUtil; | import org.eclipse.jgit.junit.JGitTestUtil; | ||||
} | } | ||||
} | } | ||||
@Test | |||||
public void testRecordSubmoduleLabels() throws Exception { | |||||
try ( | |||||
Repository remoteDb = createBareRepository(); | |||||
Repository tempDb = createWorkRepository()) { | |||||
StringBuilder xmlContent = new StringBuilder(); | |||||
xmlContent | |||||
.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n") | |||||
.append("<manifest>") | |||||
.append("<remote name=\"remote1\" fetch=\".\" />") | |||||
.append("<default revision=\"master\" remote=\"remote1\" />") | |||||
.append("<project path=\"test\" ") | |||||
.append("revision=\"master\" ") | |||||
.append("name=\"").append(notDefaultUri).append("\" ") | |||||
.append("groups=\"a1,a2\" />") | |||||
.append("</manifest>"); | |||||
JGitTestUtil.writeTrashFile(tempDb, "manifest.xml", | |||||
xmlContent.toString()); | |||||
RepoCommand command = new RepoCommand(remoteDb); | |||||
command.setPath(tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml") | |||||
.setURI(rootUri) | |||||
.setRecordSubmoduleLabels(true) | |||||
.call(); | |||||
// Clone it | |||||
File directory = createTempDirectory("testBareRepo"); | |||||
try (Repository localDb = Git.cloneRepository() | |||||
.setDirectory(directory) | |||||
.setURI(remoteDb.getDirectory().toURI().toString()).call() | |||||
.getRepository();) { | |||||
// The .gitattributes file should exist | |||||
File gitattributes = new File(localDb.getWorkTree(), | |||||
".gitattributes"); | |||||
assertTrue("The .gitattributes file should exist", | |||||
gitattributes.exists()); | |||||
try (BufferedReader reader = new BufferedReader( | |||||
new FileReader(gitattributes));) { | |||||
String content = reader.readLine(); | |||||
assertEquals(".gitattributes content should be as expected", | |||||
"/test a1 a2", content); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
@Test | @Test | ||||
public void testRemoteRevision() throws Exception { | public void testRemoteRevision() throws Exception { | ||||
StringBuilder xmlContent = new StringBuilder(); | StringBuilder xmlContent = new StringBuilder(); |
import java.io.InputStream; | import java.io.InputStream; | ||||
import java.text.MessageFormat; | import java.text.MessageFormat; | ||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Arrays; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.Set; | |||||
import org.eclipse.jgit.annotations.Nullable; | import org.eclipse.jgit.annotations.Nullable; | ||||
import org.eclipse.jgit.api.Git; | import org.eclipse.jgit.api.Git; | ||||
private String branch; | private String branch; | ||||
private String targetBranch = Constants.HEAD; | private String targetBranch = Constants.HEAD; | ||||
private boolean recordRemoteBranch = false; | private boolean recordRemoteBranch = false; | ||||
private boolean recordSubmoduleLabels = false; | |||||
private PersonIdent author; | private PersonIdent author; | ||||
private RemoteReader callback; | private RemoteReader callback; | ||||
private InputStream inputStream; | private InputStream inputStream; | ||||
return this; | return this; | ||||
} | } | ||||
/** | |||||
* Set whether the labels field should be recorded as a label in | |||||
* .gitattributes. | |||||
* <p> | |||||
* Not implemented for non-bare repositories. | |||||
* | |||||
* @param enable Whether to record the labels in the .gitattributes | |||||
* @return this command | |||||
* @since 4.4 | |||||
*/ | |||||
public RepoCommand setRecordSubmoduleLabels(boolean enable) { | |||||
this.recordSubmoduleLabels = enable; | |||||
return this; | |||||
} | |||||
/** | /** | ||||
* The progress monitor associated with the clone operation. By default, | * The progress monitor associated with the clone operation. By default, | ||||
* this is set to <code>NullProgressMonitor</code> | * this is set to <code>NullProgressMonitor</code> | ||||
addSubmodule(proj.getUrl(), | addSubmodule(proj.getUrl(), | ||||
proj.getPath(), | proj.getPath(), | ||||
proj.getRevision(), | proj.getRevision(), | ||||
proj.getCopyFiles()); | |||||
proj.getCopyFiles(), | |||||
proj.getGroups()); | |||||
} | } | ||||
} catch (GitAPIException | IOException e) { | } catch (GitAPIException | IOException e) { | ||||
throw new ManifestErrorException(e); | throw new ManifestErrorException(e); | ||||
ObjectInserter inserter = repo.newObjectInserter(); | ObjectInserter inserter = repo.newObjectInserter(); | ||||
try (RevWalk rw = new RevWalk(repo)) { | try (RevWalk rw = new RevWalk(repo)) { | ||||
Config cfg = new Config(); | Config cfg = new Config(); | ||||
StringBuilder attributes = new StringBuilder(); | |||||
for (RepoProject proj : bareProjects) { | for (RepoProject proj : bareProjects) { | ||||
String name = proj.getPath(); | String name = proj.getPath(); | ||||
String nameUri = proj.getName(); | String nameUri = proj.getName(); | ||||
proj.getRevision()); | proj.getRevision()); | ||||
} | } | ||||
} | } | ||||
if (recordSubmoduleLabels) { | |||||
StringBuilder rec = new StringBuilder(); | |||||
rec.append("/"); //$NON-NLS-1$ | |||||
rec.append(name); | |||||
for (String group : proj.getGroups()) { | |||||
rec.append(" "); //$NON-NLS-1$ | |||||
rec.append(group); | |||||
} | |||||
rec.append("\n"); //$NON-NLS-1$ | |||||
attributes.append(rec.toString()); | |||||
} | |||||
cfg.setString("submodule", name, "path", name); //$NON-NLS-1$ //$NON-NLS-2$ | cfg.setString("submodule", name, "path", name); //$NON-NLS-1$ //$NON-NLS-2$ | ||||
cfg.setString("submodule", name, "url", nameUri); //$NON-NLS-1$ //$NON-NLS-2$ | cfg.setString("submodule", name, "url", nameUri); //$NON-NLS-1$ //$NON-NLS-2$ | ||||
dcEntry.setFileMode(FileMode.REGULAR_FILE); | dcEntry.setFileMode(FileMode.REGULAR_FILE); | ||||
builder.add(dcEntry); | builder.add(dcEntry); | ||||
if (recordSubmoduleLabels) { | |||||
// create a new DirCacheEntry for .gitattributes file. | |||||
final DirCacheEntry dcEntryAttr = new DirCacheEntry(Constants.DOT_GIT_ATTRIBUTES); | |||||
ObjectId attrId = inserter.insert(Constants.OBJ_BLOB, | |||||
attributes.toString().getBytes(Constants.CHARACTER_ENCODING)); | |||||
dcEntryAttr.setObjectId(attrId); | |||||
dcEntryAttr.setFileMode(FileMode.REGULAR_FILE); | |||||
builder.add(dcEntryAttr); | |||||
} | |||||
builder.finish(); | builder.finish(); | ||||
ObjectId treeId = index.writeTree(inserter); | ObjectId treeId = index.writeTree(inserter); | ||||
} | } | ||||
private void addSubmodule(String url, String name, String revision, | private void addSubmodule(String url, String name, String revision, | ||||
List<CopyFile> copyfiles) throws GitAPIException, IOException { | |||||
List<CopyFile> copyfiles, Set<String> groups) | |||||
throws GitAPIException, IOException { | |||||
if (repo.isBare()) { | if (repo.isBare()) { | ||||
RepoProject proj = new RepoProject(url, name, revision, null, null); | |||||
RepoProject proj = new RepoProject(url, name, revision, null, groups); | |||||
proj.addCopyFiles(copyfiles); | proj.addCopyFiles(copyfiles); | ||||
bareProjects.add(proj); | bareProjects.add(proj); | ||||
} else { | } else { |
* @param remote | * @param remote | ||||
* name of the remote definition | * name of the remote definition | ||||
* @param groups | * @param groups | ||||
* comma separated group list | |||||
* set of groups | |||||
* @since 4.4 | |||||
*/ | */ | ||||
public RepoProject(String name, String path, String revision, | public RepoProject(String name, String path, String revision, | ||||
String remote, String groups) { | |||||
String remote, Set<String> groups) { | |||||
if (name == null) { | if (name == null) { | ||||
throw new NullPointerException(); | throw new NullPointerException(); | ||||
} | } | ||||
this.path = name; | this.path = name; | ||||
this.revision = revision; | this.revision = revision; | ||||
this.remote = remote; | this.remote = remote; | ||||
this.groups = new HashSet<String>(); | |||||
if (groups != null && groups.length() > 0) | |||||
this.groups.addAll(Arrays.asList(groups.split(","))); //$NON-NLS-1$ | |||||
this.groups = groups; | |||||
copyfiles = new ArrayList<CopyFile>(); | copyfiles = new ArrayList<CopyFile>(); | ||||
} | } | ||||
/** | |||||
* @param name | |||||
* the relative path to the {@code remote} | |||||
* @param path | |||||
* the relative path to the super project | |||||
* @param revision | |||||
* a SHA-1 or branch name or tag name | |||||
* @param remote | |||||
* name of the remote definition | |||||
* @param groups | |||||
* comma separated group list | |||||
*/ | |||||
public RepoProject(String name, String path, String revision, | |||||
String remote, String groups) { | |||||
this(name, path, revision, remote, new HashSet<String>()); | |||||
if (groups != null && groups.length() > 0) | |||||
this.setGroups(groups); | |||||
} | |||||
/** | /** | ||||
* Set the url of the sub repo. | * Set the url of the sub repo. | ||||
* | * | ||||
return this; | return this; | ||||
} | } | ||||
/** | |||||
* Set the url of the sub repo. | |||||
* | |||||
* @param url | |||||
* @return this for chaining. | |||||
* @since 4.4 | |||||
*/ | |||||
public RepoProject setGroups(String groups) { | |||||
this.groups.clear(); | |||||
this.groups.addAll(Arrays.asList(groups.split(","))); //$NON-NLS-1$ | |||||
return this; | |||||
} | |||||
/** | /** | ||||
* Set the default revision for the sub repo. | * Set the default revision for the sub repo. | ||||
* | * | ||||
return groups.contains(group); | return groups.contains(group); | ||||
} | } | ||||
/** | |||||
* Return the set of groups. | |||||
* | |||||
* @return a Set of groups. | |||||
* @since 4.4 | |||||
*/ | |||||
public Set<String> getGroups() { | |||||
return groups; | |||||
} | |||||
/** | /** | ||||
* Add a copy file configuration. | * Add a copy file configuration. | ||||
* | * |