Skip to content

Commit

Permalink
Add worktrees read support
Browse files Browse the repository at this point in the history
Based on deritative work done in Andre's work in [1].

This change focuses on adding support for reading the repository
state when branches are checked out using git's worktrees.

I've refactored original work by removing all unrelevant
changes which were mostly around refactoring to extract
i.e. constants which mostly created noise for a review.

I've tried to address original review comments:
- Not adding non-behavioral changes
- "HEAD" should get resolved from gitDir
- Reftable recently landed in cgit 2.45,
  see https://github.com/git/git/blob/master/Documentation/RelNotes/2.45.0.txt#L8
  We can add worktree support for reftable in a later change.
- Some new tests to read from a linked worktree which
  is created manually as there's no write support.

[1] https://git.eclipse.org/r/c/jgit/jgit/+/163940/18

Change-Id: Id077d58fb6c09ecb090eb09d5dbc7edc351a581d
  • Loading branch information
jvalkeal authored and msohn committed Jul 14, 2024
1 parent 2a19bfc commit f9beeb3
Show file tree
Hide file tree
Showing 29 changed files with 520 additions and 85 deletions.
2 changes: 1 addition & 1 deletion org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ private void list() throws IOException, ConfigInvalidException {
if (global || isListAll())
list(SystemReader.getInstance().openUserConfig(null, fs));
if (local || isListAll())
list(new FileBasedConfig(fs.resolve(getRepository().getDirectory(),
list(new FileBasedConfig(fs.resolve(getRepository().getCommonDirectory(),
Constants.CONFIG), fs));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ private void setupGitAndDoHardReset(AutoCRLF autoCRLF, EOL eol,

}
if (infoAttributesContent != null) {
File f = new File(db.getDirectory(), Constants.INFO_ATTRIBUTES);
File f = new File(db.getCommonDirectory(), Constants.INFO_ATTRIBUTES);
write(f, infoAttributesContent);
}
config.save();
Expand Down
192 changes: 192 additions & 0 deletions org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LinkedWorktreeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/*
* Copyright (C) 2024, Broadcom 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.api;

import static org.eclipse.jgit.lib.Constants.HEAD;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Iterator;

import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ReflogEntry;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FS.ExecutionResult;
import org.eclipse.jgit.util.RawParseUtils;
import org.eclipse.jgit.util.TemporaryBuffer;
import org.junit.Test;

public class LinkedWorktreeTest extends RepositoryTestCase {

@Override
public void setUp() throws Exception {
super.setUp();

try (Git git = new Git(db)) {
git.commit().setMessage("Initial commit").call();
}
}

@Test
public void testWeCanReadFromLinkedWorktreeFromBare() throws Exception {
FS fs = db.getFS();
File directory = trash.getParentFile();
String dbDirName = db.getWorkTree().getName();
cloneBare(fs, directory, dbDirName, "bare");
File bareDirectory = new File(directory, "bare");
worktreeAddExisting(fs, bareDirectory, "master");

File worktreesDir = new File(bareDirectory, "worktrees");
File masterWorktreesDir = new File(worktreesDir, "master");

FileRepository repository = new FileRepository(masterWorktreesDir);
try (Git git = new Git(repository)) {
ObjectId objectId = repository.resolve(HEAD);
assertNotNull(objectId);

Iterator<RevCommit> log = git.log().all().call().iterator();
assertTrue(log.hasNext());
assertTrue("Initial commit".equals(log.next().getShortMessage()));

// we have reflog entry
// depending on git version we either have one or
// two entries where extra is zeroid entry with
// same message or no message
Collection<ReflogEntry> reflog = git.reflog().call();
assertNotNull(reflog);
assertTrue(reflog.size() > 0);
ReflogEntry[] reflogs = reflog.toArray(new ReflogEntry[0]);
assertEquals(reflogs[reflogs.length - 1].getComment(),
"reset: moving to HEAD");

// index works with file changes
File masterDir = new File(directory, "master");
File testFile = new File(masterDir, "test");

Status status = git.status().call();
assertTrue(status.getUncommittedChanges().size() == 0);
assertTrue(status.getUntracked().size() == 0);

JGitTestUtil.write(testFile, "test");
status = git.status().call();
assertTrue(status.getUncommittedChanges().size() == 0);
assertTrue(status.getUntracked().size() == 1);

git.add().addFilepattern("test").call();
status = git.status().call();
assertTrue(status.getUncommittedChanges().size() == 1);
assertTrue(status.getUntracked().size() == 0);
}
}

@Test
public void testWeCanReadFromLinkedWorktreeFromNonBare() throws Exception {
FS fs = db.getFS();
worktreeAddNew(fs, db.getWorkTree(), "wt");

File worktreesDir = new File(db.getDirectory(), "worktrees");
File masterWorktreesDir = new File(worktreesDir, "wt");

FileRepository repository = new FileRepository(masterWorktreesDir);
try (Git git = new Git(repository)) {
ObjectId objectId = repository.resolve(HEAD);
assertNotNull(objectId);

Iterator<RevCommit> log = git.log().all().call().iterator();
assertTrue(log.hasNext());
assertTrue("Initial commit".equals(log.next().getShortMessage()));

// we have reflog entry
Collection<ReflogEntry> reflog = git.reflog().call();
assertNotNull(reflog);
assertTrue(reflog.size() > 0);
ReflogEntry[] reflogs = reflog.toArray(new ReflogEntry[0]);
assertEquals(reflogs[reflogs.length - 1].getComment(),
"reset: moving to HEAD");

// index works with file changes
File directory = trash.getParentFile();
File wtDir = new File(directory, "wt");
File testFile = new File(wtDir, "test");

Status status = git.status().call();
assertTrue(status.getUncommittedChanges().size() == 0);
assertTrue(status.getUntracked().size() == 0);

JGitTestUtil.write(testFile, "test");
status = git.status().call();
assertTrue(status.getUncommittedChanges().size() == 0);
assertTrue(status.getUntracked().size() == 1);

git.add().addFilepattern("test").call();
status = git.status().call();
assertTrue(status.getUncommittedChanges().size() == 1);
assertTrue(status.getUntracked().size() == 0);
}

}

private static void cloneBare(FS fs, File directory, String from, String to) throws IOException, InterruptedException {
ProcessBuilder builder = fs.runInShell("git",
new String[] { "clone", "--bare", from, to });
builder.directory(directory);
builder.environment().put("HOME", fs.userHome().getAbsolutePath());
StringBuilder input = new StringBuilder();
ExecutionResult result = fs.execute(builder, new ByteArrayInputStream(
input.toString().getBytes(StandardCharsets.UTF_8)));
String stdOut = toString(result.getStdout());
String errorOut = toString(result.getStderr());
assertNotNull(stdOut);
assertNotNull(errorOut);
}

private static void worktreeAddExisting(FS fs, File directory, String name) throws IOException, InterruptedException {
ProcessBuilder builder = fs.runInShell("git",
new String[] { "worktree", "add", "../" + name, name });
builder.directory(directory);
builder.environment().put("HOME", fs.userHome().getAbsolutePath());
StringBuilder input = new StringBuilder();
ExecutionResult result = fs.execute(builder, new ByteArrayInputStream(
input.toString().getBytes(StandardCharsets.UTF_8)));
String stdOut = toString(result.getStdout());
String errorOut = toString(result.getStderr());
assertNotNull(stdOut);
assertNotNull(errorOut);
}

private static void worktreeAddNew(FS fs, File directory, String name) throws IOException, InterruptedException {
ProcessBuilder builder = fs.runInShell("git",
new String[] { "worktree", "add", "-b", name, "../" + name, "master"});
builder.directory(directory);
builder.environment().put("HOME", fs.userHome().getAbsolutePath());
StringBuilder input = new StringBuilder();
ExecutionResult result = fs.execute(builder, new ByteArrayInputStream(
input.toString().getBytes(StandardCharsets.UTF_8)));
String stdOut = toString(result.getStdout());
String errorOut = toString(result.getStderr());
assertNotNull(stdOut);
assertNotNull(errorOut);
}

private static String toString(TemporaryBuffer b) throws IOException {
return RawParseUtils.decode(b.toByteArray());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ private void setupRepo(

}
if (infoAttributesContent != null) {
File f = new File(db.getDirectory(), Constants.INFO_ATTRIBUTES);
File f = new File(db.getCommonDirectory(), Constants.INFO_ATTRIBUTES);
write(f, infoAttributesContent);
}
config.save();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ public void packedRefsFileIsSorted() throws IOException {
assertEquals(c2.getResult(), ReceiveCommand.Result.OK);
}

File packed = new File(diskRepo.getDirectory(), "packed-refs");
File packed = new File(diskRepo.getCommonDirectory(), "packed-refs");
String packedStr = new String(Files.readAllBytes(packed.toPath()),
UTF_8);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public void emptyRefDirectoryDeleted() throws Exception {
String ref = "dir/ref";
tr.branch(ref).commit().create();
String name = repo.findRef(ref).getName();
Path dir = repo.getDirectory().toPath().resolve(name).getParent();
Path dir = repo.getCommonDirectory().toPath().resolve(name).getParent();
assertNotNull(dir);
gc.packRefs();
assertFalse(Files.exists(dir));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public void testPruneNone() throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master");
bb.commit().add("A", "A").add("B", "B").create();
bb.commit().add("A", "A2").add("B", "B2").create();
new File(repo.getDirectory(), Constants.LOGS + "/refs/heads/master")
new File(repo.getCommonDirectory(), Constants.LOGS + "/refs/heads/master")
.delete();
stats = gc.getStatistics();
assertEquals(8, stats.numberOfLooseObjects);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,25 +90,26 @@ public void refDirectorySetup() throws Exception {
@Test
public void testCreate() throws IOException {
// setUp above created the directory. We just have to test it.
File d = diskRepo.getDirectory();
File gitDir = diskRepo.getDirectory();
File commonDir = diskRepo.getCommonDirectory();
assertSame(diskRepo, refdir.getRepository());

assertTrue(new File(d, "refs").isDirectory());
assertTrue(new File(d, "logs").isDirectory());
assertTrue(new File(d, "logs/refs").isDirectory());
assertFalse(new File(d, "packed-refs").exists());
assertTrue(new File(commonDir, "refs").isDirectory());
assertTrue(new File(commonDir, "logs").isDirectory());
assertTrue(new File(commonDir, "logs/refs").isDirectory());
assertFalse(new File(commonDir, "packed-refs").exists());

assertTrue(new File(d, "refs/heads").isDirectory());
assertTrue(new File(d, "refs/tags").isDirectory());
assertEquals(2, new File(d, "refs").list().length);
assertEquals(0, new File(d, "refs/heads").list().length);
assertEquals(0, new File(d, "refs/tags").list().length);
assertTrue(new File(commonDir, "refs/heads").isDirectory());
assertTrue(new File(commonDir, "refs/tags").isDirectory());
assertEquals(2, new File(commonDir, "refs").list().length);
assertEquals(0, new File(commonDir, "refs/heads").list().length);
assertEquals(0, new File(commonDir, "refs/tags").list().length);

assertTrue(new File(d, "logs/refs/heads").isDirectory());
assertFalse(new File(d, "logs/HEAD").exists());
assertEquals(0, new File(d, "logs/refs/heads").list().length);
assertTrue(new File(commonDir, "logs/refs/heads").isDirectory());
assertFalse(new File(gitDir, "logs/HEAD").exists());
assertEquals(0, new File(commonDir, "logs/refs/heads").list().length);

assertEquals("ref: refs/heads/master\n", read(new File(d, HEAD)));
assertEquals("ref: refs/heads/master\n", read(new File(gitDir, HEAD)));
}

@Test(expected = UnsupportedOperationException.class)
Expand Down Expand Up @@ -1382,7 +1383,7 @@ private void writeLooseRef(String name, String content) throws IOException {
}

private void deleteLooseRef(String name) {
File path = new File(diskRepo.getDirectory(), name);
File path = new File(diskRepo.getCommonDirectory(), name);
assertTrue("deleted " + name, path.delete());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ public void testSpecificEntryNumber() throws Exception {

private void setupReflog(String logName, byte[] data)
throws FileNotFoundException, IOException {
File logfile = new File(db.getDirectory(), logName);
File logfile = new File(db.getCommonDirectory(), logName);
if (!logfile.getParentFile().mkdirs()
&& !logfile.getParentFile().isDirectory()) {
throw new IOException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public void shouldFilterLineFeedFromMessage() throws Exception {

private void readReflog(byte[] buffer)
throws FileNotFoundException, IOException {
File logfile = new File(db.getDirectory(), "logs/refs/heads/master");
File logfile = new File(db.getCommonDirectory(), "logs/refs/heads/master");
if (!logfile.getParentFile().mkdirs()
&& !logfile.getParentFile().isDirectory()) {
throw new IOException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ public Repository call() throws GitAPIException {
CloneCommand clone = Git.cloneRepository();
configure(clone);
clone.setDirectory(moduleDirectory);
clone.setGitDir(new File(new File(repo.getDirectory(),
clone.setGitDir(new File(new File(repo.getCommonDirectory(),
Constants.MODULES), path));
clone.setURI(resolvedUri);
if (monitor != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ private Repository getOrCloneSubmodule(SubmoduleWalk generator, String url)
clone.setURI(url);
clone.setDirectory(generator.getDirectory());
clone.setGitDir(
new File(new File(repo.getDirectory(), Constants.MODULES),
new File(new File(repo.getCommonDirectory(), Constants.MODULES),
generator.getPath()));
if (monitor != null) {
clone.setProgressMonitor(monitor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1647,6 +1647,8 @@ private static void runExternalFilterCommand(Repository repo, String path,
filterProcessBuilder.directory(repo.getWorkTree());
filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY,
repo.getDirectory().getAbsolutePath());
filterProcessBuilder.environment().put(Constants.GIT_COMMON_DIR_KEY,
repo.getCommonDirectory().getAbsolutePath());
ExecutionResult result;
int rc;
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ public class FileReftableDatabase extends RefDatabase {
private final FileReftableStack reftableStack;

FileReftableDatabase(FileRepository repo) throws IOException {
this(repo, new File(new File(repo.getDirectory(), Constants.REFTABLE),
this(repo, new File(new File(repo.getCommonDirectory(), Constants.REFTABLE),
Constants.TABLES_LIST));
}

FileReftableDatabase(FileRepository repo, File refstackName) throws IOException {
this.fileRepository = repo;
this.reftableStack = new FileReftableStack(refstackName,
new File(fileRepository.getDirectory(), Constants.REFTABLE),
new File(fileRepository.getCommonDirectory(), Constants.REFTABLE),
() -> fileRepository.fireEvent(new RefsChangedEvent()),
() -> fileRepository.getConfig());
this.reftableDatabase = new ReftableDatabase() {
Expand Down Expand Up @@ -318,7 +318,7 @@ public void close() {
@Override
public void create() throws IOException {
FileUtils.mkdir(
new File(fileRepository.getDirectory(), Constants.REFTABLE),
new File(fileRepository.getCommonDirectory(), Constants.REFTABLE),
true);
}

Expand Down Expand Up @@ -615,7 +615,7 @@ public static FileReftableDatabase convertFrom(FileRepository repo,
FileReftableDatabase newDb = null;
File reftableList = null;
try {
File reftableDir = new File(repo.getDirectory(),
File reftableDir = new File(repo.getCommonDirectory(),
Constants.REFTABLE);
reftableList = new File(reftableDir, Constants.TABLES_LIST);
if (!reftableDir.isDirectory()) {
Expand Down
Loading

0 comments on commit f9beeb3

Please sign in to comment.