Ensure ReflogWriter only works with a RefDirectory

The ReflogWriter constructor just took a Repository and called
getDirectory() on it to figure out the reflog dirs, but not all
Repository instances use this storage format for reflogs, so it's
incorrect to attempt to use ReflogWriter when there is not a
RefDirectory directly involved. In practice, ReflogWriter was mostly
only used by the implementation of RefDirectory, so enforcing this is
mostly just shuffling around calls in the same internal package.

The one exception is StashDropCommand, which writes to a reflog lock
file directly. This was a reasonable implementation decision, because
there is no general reflog interface in JGit beyond using
(Batch)RefUpdate to write new entries to the reflog. So to implement
"git stash drop <N>", which removes an arbitrary element from the
reflog, it's fair to fall back to the RefDirectory implementation.
Creating and using a more general interface is well beyond the scope of
this change.

That said, the old behavior of writing out the reflog file even if
that's not the reflog format used by the given Repository is clearly
wrong. Fail fast in this case instead.

Change-Id: I9bd4b047bc3e28a5607fd346ec2400dde9151730
This commit is contained in:
Dave Borowitz 2017-08-01 09:43:22 -04:00 committed by David Pursehouse
parent 4160938c8b
commit b1ae96bf84
8 changed files with 66 additions and 63 deletions

View File

@ -61,7 +61,8 @@ public class ReflogWriterTest extends SampleDataRepositoryTestCase {
@Test
public void shouldFilterLineFeedFromMessage() throws Exception {
ReflogWriter writer = new ReflogWriter(db);
ReflogWriter writer =
new ReflogWriter((RefDirectory) db.getRefDatabase());
PersonIdent ident = new PersonIdent("John Doe", "john@doe.com",
1243028200000L, 120);
ObjectId oldId = ObjectId

View File

@ -626,6 +626,7 @@ stashCommitIncorrectNumberOfParents=Stashed commit ''{0}'' does have {1} parent
stashDropDeleteRefFailed=Deleting stash reference failed with result: {0}
stashDropFailed=Dropping stashed commit failed
stashDropMissingReflog=Stash reflog does not contain entry ''{0}''
stashDropNotSupported=Dropping stash not supported on this ref backend
stashFailed=Stashing local changes did not successfully complete
stashResolveFailed=Reference ''{0}'' does not resolve to stashed commit
statelessRPCRequiresOptionToBeEnabled=stateless RPC requires {0} to be enabled

View File

@ -56,6 +56,7 @@
import org.eclipse.jgit.api.errors.RefNotFoundException;
import org.eclipse.jgit.errors.LockFailedException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.file.RefDirectory;
import org.eclipse.jgit.internal.storage.file.ReflogWriter;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
@ -68,6 +69,9 @@
/**
* Command class to delete a stashed commit reference
* <p>
* Currently only supported on a traditional file repository using
* one-file-per-ref reflogs.
*
* @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-stash.html"
* >Git documentation about Stash</a>
@ -84,6 +88,10 @@ public class StashDropCommand extends GitCommand<ObjectId> {
*/
public StashDropCommand(Repository repo) {
super(repo);
if (!(repo.getRefDatabase() instanceof RefDirectory)) {
throw new UnsupportedOperationException(
JGitText.get().stashDropNotSupported);
}
}
/**
@ -205,10 +213,11 @@ public ObjectId call() throws GitAPIException {
return null;
}
ReflogWriter writer = new ReflogWriter(repo, true);
RefDirectory refdb = (RefDirectory) repo.getRefDatabase();
ReflogWriter writer = new ReflogWriter(refdb, true);
String stashLockRef = ReflogWriter.refLockFor(R_STASH);
File stashLockFile = writer.logFor(stashLockRef);
File stashFile = writer.logFor(R_STASH);
File stashLockFile = refdb.logFor(stashLockRef);
File stashFile = refdb.logFor(R_STASH);
if (stashLockFile.exists())
throw new JGitInternalException(JGitText.get().stashDropFailed,
new LockFailedException(stashFile));

View File

@ -685,6 +685,7 @@ public static JGitText get() {
/***/ public String stashDropDeleteRefFailed;
/***/ public String stashDropFailed;
/***/ public String stashDropMissingReflog;
/***/ public String stashDropNotSupported;
/***/ public String stashFailed;
/***/ public String stashResolveFailed;
/***/ public String statelessRPCRequiresOptionToBeEnabled;

View File

@ -416,7 +416,7 @@ private void writeReflog(List<ReceiveCommand> commands) {
if (cmd.getType() == ReceiveCommand.Type.DELETE) {
try {
RefDirectory.delete(w.logFor(name), RefDirectory.levelsIn(name));
RefDirectory.delete(refdb.logFor(name), RefDirectory.levelsIn(name));
} catch (IOException e) {
// Ignore failures, see below.
}

View File

@ -48,6 +48,7 @@
import static org.eclipse.jgit.lib.Constants.CHARSET;
import static org.eclipse.jgit.lib.Constants.HEAD;
import static org.eclipse.jgit.lib.Constants.LOGS;
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
import static org.eclipse.jgit.lib.Constants.PACKED_REFS;
import static org.eclipse.jgit.lib.Constants.R_HEADS;
@ -154,6 +155,10 @@ public class RefDirectory extends RefDatabase {
final File packedRefsFile;
final File logsDir;
final File logsRefsDir;
/**
* Immutable sorted list of loose references.
* <p>
@ -205,8 +210,10 @@ public class RefDirectory extends RefDatabase {
final FS fs = db.getFS();
parent = db;
gitDir = db.getDirectory();
logWriter = new ReflogWriter(db);
logWriter = new ReflogWriter(this);
refsDir = fs.resolve(gitDir, R_REFS);
logsDir = fs.resolve(gitDir, LOGS);
logsRefsDir = fs.resolve(gitDir, LOGS + '/' + R_REFS);
packedRefsFile = fs.resolve(gitDir, PACKED_REFS);
looseRefs.set(RefList.<LooseRef> emptyList());
@ -221,6 +228,22 @@ ReflogWriter getLogWriter() {
return logWriter;
}
/**
* Locate the log file on disk for a single reference name.
*
* @param name
* name of the ref, relative to the Git repository top level
* directory (so typically starts with refs/).
* @return the log file location.
*/
public File logFor(String name) {
if (name.startsWith(R_REFS)) {
name = name.substring(R_REFS.length());
return new File(logsRefsDir, name);
}
return new File(logsDir, name);
}
@Override
public void create() throws IOException {
FileUtils.mkdir(refsDir);
@ -654,7 +677,7 @@ void delete(RefDirectoryUpdate update) throws IOException {
} while (!looseRefs.compareAndSet(curLoose, newLoose));
int levels = levelsIn(name) - 2;
delete(logWriter.logFor(name), levels);
delete(logFor(name), levels);
if (dst.getStorage().isLoose()) {
update.unlock();
delete(fileFor(name), levels);

View File

@ -188,8 +188,8 @@ protected Result doRename() throws IOException {
}
private boolean renameLog(RefUpdate src, RefUpdate dst) {
File srcLog = refdb.getLogWriter().logFor(src.getName());
File dstLog = refdb.getLogWriter().logFor(dst.getName());
File srcLog = refdb.logFor(src.getName());
File dstLog = refdb.logFor(dst.getName());
if (!srcLog.exists())
return true;

View File

@ -46,7 +46,6 @@
package org.eclipse.jgit.internal.storage.file;
import static org.eclipse.jgit.lib.Constants.HEAD;
import static org.eclipse.jgit.lib.Constants.LOGS;
import static org.eclipse.jgit.lib.Constants.R_HEADS;
import static org.eclipse.jgit.lib.Constants.R_REFS;
import static org.eclipse.jgit.lib.Constants.R_REMOTES;
@ -68,11 +67,12 @@
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.ReflogEntry;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
/** Utility for writing reflog entries. */
/**
* Utility for writing reflog entries using the traditional one-file-per-log
* format.
*/
public class ReflogWriter {
/**
@ -87,49 +87,32 @@ public static String refLockFor(String name) {
return name + LockFile.SUFFIX;
}
private final Repository parent;
private final File logsDir;
private final File logsRefsDir;
private final RefDirectory refdb;
private final boolean forceWrite;
/**
* Create writer for repository.
* Create writer for ref directory.
*
* @param repository
* @param refdb
*/
public ReflogWriter(Repository repository) {
this(repository, false);
public ReflogWriter(RefDirectory refdb) {
this(refdb, false);
}
/**
* Create writer for repository.
* Create writer for ref directory.
*
* @param repository
* @param refdb
* @param forceWrite
* true to write to disk all entries logged, false to respect the
* repository's config and current log file status.
*/
public ReflogWriter(Repository repository, boolean forceWrite) {
FS fs = repository.getFS();
parent = repository;
File gitDir = repository.getDirectory();
logsDir = fs.resolve(gitDir, LOGS);
logsRefsDir = fs.resolve(gitDir, LOGS + '/' + R_REFS);
public ReflogWriter(RefDirectory refdb, boolean forceWrite) {
this.refdb = refdb;
this.forceWrite = forceWrite;
}
/**
* Get repository that reflog is being written for.
*
* @return file repository.
*/
public Repository getRepository() {
return parent;
}
/**
* Create the log directories.
*
@ -137,29 +120,13 @@ public Repository getRepository() {
* @return this writer.
*/
public ReflogWriter create() throws IOException {
FileUtils.mkdir(logsDir);
FileUtils.mkdir(logsRefsDir);
FileUtils.mkdir(new File(logsRefsDir,
R_HEADS.substring(R_REFS.length())));
FileUtils.mkdir(refdb.logsDir);
FileUtils.mkdir(refdb.logsRefsDir);
FileUtils.mkdir(
new File(refdb.logsRefsDir, R_HEADS.substring(R_REFS.length())));
return this;
}
/**
* Locate the log file on disk for a single reference name.
*
* @param name
* name of the ref, relative to the Git repository top level
* directory (so typically starts with refs/).
* @return the log file location.
*/
public File logFor(String name) {
if (name.startsWith(R_REFS)) {
name = name.substring(R_REFS.length());
return new File(logsRefsDir, name);
}
return new File(logsDir, name);
}
/**
* Write the given entry to the ref's log.
*
@ -208,7 +175,7 @@ public ReflogWriter log(RefUpdate update, String msg,
PersonIdent ident = update.getRefLogIdent();
if (ident == null)
ident = new PersonIdent(parent);
ident = new PersonIdent(refdb.getRepository());
else
ident = new PersonIdent(ident);
@ -239,14 +206,14 @@ private byte[] encode(ObjectId oldId, ObjectId newId, PersonIdent ident,
}
private ReflogWriter log(String refName, byte[] rec) throws IOException {
File log = logFor(refName);
File log = refdb.logFor(refName);
boolean write = forceWrite
|| (isLogAllRefUpdates() && shouldAutoCreateLog(refName))
|| log.isFile();
if (!write)
return this;
WriteConfig wc = getRepository().getConfig().get(WriteConfig.KEY);
WriteConfig wc = refdb.getRepository().getConfig().get(WriteConfig.KEY);
FileOutputStream out;
try {
out = new FileOutputStream(log, true);
@ -275,7 +242,8 @@ private ReflogWriter log(String refName, byte[] rec) throws IOException {
}
private boolean isLogAllRefUpdates() {
return parent.getConfig().get(CoreConfig.KEY).isLogAllRefUpdates();
return refdb.getRepository().getConfig().get(CoreConfig.KEY)
.isLogAllRefUpdates();
}
private boolean shouldAutoCreateLog(String refName) {