Browse Source

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
stable-4.9
Dave Borowitz 7 years ago committed by David Pursehouse
parent
commit
b1ae96bf84
  1. 3
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java
  2. 1
      org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
  3. 15
      org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java
  4. 1
      org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
  5. 2
      org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java
  6. 27
      org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
  7. 4
      org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java
  8. 76
      org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java

3
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java

@ -61,7 +61,8 @@ public class ReflogWriterTest extends SampleDataRepositoryTestCase {
@Test @Test
public void shouldFilterLineFeedFromMessage() throws Exception { 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", PersonIdent ident = new PersonIdent("John Doe", "john@doe.com",
1243028200000L, 120); 1243028200000L, 120);
ObjectId oldId = ObjectId ObjectId oldId = ObjectId

1
org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties

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

15
org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java

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

1
org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java

@ -685,6 +685,7 @@ public class JGitText extends TranslationBundle {
/***/ public String stashDropDeleteRefFailed; /***/ public String stashDropDeleteRefFailed;
/***/ public String stashDropFailed; /***/ public String stashDropFailed;
/***/ public String stashDropMissingReflog; /***/ public String stashDropMissingReflog;
/***/ public String stashDropNotSupported;
/***/ public String stashFailed; /***/ public String stashFailed;
/***/ public String stashResolveFailed; /***/ public String stashResolveFailed;
/***/ public String statelessRPCRequiresOptionToBeEnabled; /***/ public String statelessRPCRequiresOptionToBeEnabled;

2
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java

@ -416,7 +416,7 @@ class PackedBatchRefUpdate extends BatchRefUpdate {
if (cmd.getType() == ReceiveCommand.Type.DELETE) { if (cmd.getType() == ReceiveCommand.Type.DELETE) {
try { try {
RefDirectory.delete(w.logFor(name), RefDirectory.levelsIn(name)); RefDirectory.delete(refdb.logFor(name), RefDirectory.levelsIn(name));
} catch (IOException e) { } catch (IOException e) {
// Ignore failures, see below. // Ignore failures, see below.
} }

27
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java

@ -48,6 +48,7 @@ package org.eclipse.jgit.internal.storage.file;
import static org.eclipse.jgit.lib.Constants.CHARSET; import static org.eclipse.jgit.lib.Constants.CHARSET;
import static org.eclipse.jgit.lib.Constants.HEAD; 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.OBJECT_ID_STRING_LENGTH;
import static org.eclipse.jgit.lib.Constants.PACKED_REFS; import static org.eclipse.jgit.lib.Constants.PACKED_REFS;
import static org.eclipse.jgit.lib.Constants.R_HEADS; import static org.eclipse.jgit.lib.Constants.R_HEADS;
@ -154,6 +155,10 @@ public class RefDirectory extends RefDatabase {
final File packedRefsFile; final File packedRefsFile;
final File logsDir;
final File logsRefsDir;
/** /**
* Immutable sorted list of loose references. * Immutable sorted list of loose references.
* <p> * <p>
@ -205,8 +210,10 @@ public class RefDirectory extends RefDatabase {
final FS fs = db.getFS(); final FS fs = db.getFS();
parent = db; parent = db;
gitDir = db.getDirectory(); gitDir = db.getDirectory();
logWriter = new ReflogWriter(db); logWriter = new ReflogWriter(this);
refsDir = fs.resolve(gitDir, R_REFS); refsDir = fs.resolve(gitDir, R_REFS);
logsDir = fs.resolve(gitDir, LOGS);
logsRefsDir = fs.resolve(gitDir, LOGS + '/' + R_REFS);
packedRefsFile = fs.resolve(gitDir, PACKED_REFS); packedRefsFile = fs.resolve(gitDir, PACKED_REFS);
looseRefs.set(RefList.<LooseRef> emptyList()); looseRefs.set(RefList.<LooseRef> emptyList());
@ -221,6 +228,22 @@ public class RefDirectory extends RefDatabase {
return logWriter; 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 @Override
public void create() throws IOException { public void create() throws IOException {
FileUtils.mkdir(refsDir); FileUtils.mkdir(refsDir);
@ -654,7 +677,7 @@ public class RefDirectory extends RefDatabase {
} while (!looseRefs.compareAndSet(curLoose, newLoose)); } while (!looseRefs.compareAndSet(curLoose, newLoose));
int levels = levelsIn(name) - 2; int levels = levelsIn(name) - 2;
delete(logWriter.logFor(name), levels); delete(logFor(name), levels);
if (dst.getStorage().isLoose()) { if (dst.getStorage().isLoose()) {
update.unlock(); update.unlock();
delete(fileFor(name), levels); delete(fileFor(name), levels);

4
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java

@ -188,8 +188,8 @@ class RefDirectoryRename extends RefRename {
} }
private boolean renameLog(RefUpdate src, RefUpdate dst) { private boolean renameLog(RefUpdate src, RefUpdate dst) {
File srcLog = refdb.getLogWriter().logFor(src.getName()); File srcLog = refdb.logFor(src.getName());
File dstLog = refdb.getLogWriter().logFor(dst.getName()); File dstLog = refdb.logFor(dst.getName());
if (!srcLog.exists()) if (!srcLog.exists())
return true; return true;

76
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java

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

Loading…
Cancel
Save