Support force writing reflog on a per-update basis

Even if a repository has core.logAllRefUpdates=true, ReflogWriter does
not create reflog files unless the refs are under a hard-coded list of
prefixes, or unless the forceWrite bit is set. Expose the forceWrite bit
on a per-update basis in RefUpdate/BatchRefUpdate/ReceiveCommand,
creating RefLogWriters as necessary.

Change-Id: Ifc851fba00f76bf56d4134f821d0576b37810f80
This commit is contained in:
Dave Borowitz 2017-08-01 08:53:33 -04:00 committed by David Pursehouse
parent b1ae96bf84
commit 77a28e0d58
8 changed files with 196 additions and 20 deletions

View File

@ -142,11 +142,7 @@ public void setUp() throws Exception {
super.setUp();
diskRepo = createBareRepository();
StoredConfig cfg = diskRepo.getConfig();
cfg.load();
cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true);
cfg.save();
setLogAllRefUpdates(true);
refdir = (RefDirectory) diskRepo.getRefDatabase();
refdir.setRetrySleepMs(Arrays.asList(0, 0));
@ -655,6 +651,71 @@ public void overrideDisableRefLog() throws Exception {
getLastReflog("refs/heads/branch"));
}
@Test
public void refLogNotWrittenWithoutConfigOption() throws Exception {
setLogAllRefUpdates(false);
writeRef("refs/heads/master", A);
Map<String, ReflogEntry> oldLogs =
getLastReflogs("refs/heads/master", "refs/heads/branch");
assertTrue(oldLogs.isEmpty());
List<ReceiveCommand> cmds = Arrays.asList(
new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
assertResults(cmds, OK, OK);
assertReflogUnchanged(oldLogs, "refs/heads/master");
assertReflogUnchanged(oldLogs, "refs/heads/branch");
}
@Test
public void forceRefLogInUpdate() throws Exception {
setLogAllRefUpdates(false);
writeRef("refs/heads/master", A);
assertTrue(
getLastReflogs("refs/heads/master", "refs/heads/branch").isEmpty());
List<ReceiveCommand> cmds = Arrays.asList(
new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
execute(
newBatchUpdate(cmds)
.setRefLogMessage("a reflog", false)
.setForceRefLog(true));
assertResults(cmds, OK, OK);
assertReflogEquals(
reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
getLastReflog("refs/heads/master"));
assertReflogEquals(
reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog"),
getLastReflog("refs/heads/branch"));
}
@Test
public void forceRefLogInCommand() throws Exception {
setLogAllRefUpdates(false);
writeRef("refs/heads/master", A);
Map<String, ReflogEntry> oldLogs =
getLastReflogs("refs/heads/master", "refs/heads/branch");
assertTrue(oldLogs.isEmpty());
List<ReceiveCommand> cmds = Arrays.asList(
new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
cmds.get(1).setForceRefLog(true);
execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
assertResults(cmds, OK, OK);
assertReflogUnchanged(oldLogs, "refs/heads/master");
assertReflogEquals(
reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog"),
getLastReflog("refs/heads/branch"));
}
@Test
public void packedRefsLockFailure() throws Exception {
writeLooseRef("refs/heads/master", A);
@ -791,6 +852,14 @@ public void atomicUpdateRespectsInProcessLock() throws Exception {
"refs/heads/branch", B);
}
private void setLogAllRefUpdates(boolean enable) throws Exception {
StoredConfig cfg = diskRepo.getConfig();
cfg.load();
cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, enable);
cfg.save();
}
private void writeLooseRef(String name, AnyObjectId id) throws IOException {
write(new File(diskRepo.getDirectory(), name), id.name() + "\n");
}

View File

@ -1018,7 +1018,7 @@ private static void writeReflog(Repository db, ObjectId newId, String msg,
RefDirectory refs = (RefDirectory) db.getRefDatabase();
RefDirectoryUpdate update = refs.newUpdate(refName, true);
update.setNewObjectId(newId);
refs.log(update, msg, true);
refs.log(false, update, msg, true);
}
private static class SubclassedId extends ObjectId {

View File

@ -406,7 +406,6 @@ private void writeReflog(List<ReceiveCommand> commands) {
if (ident == null) {
ident = new PersonIdent(refdb.getRepository());
}
ReflogWriter w = refdb.getLogWriter();
for (ReceiveCommand cmd : commands) {
// Assume any pending commands have already been executed atomically.
if (cmd.getResult() != ReceiveCommand.Result.OK) {
@ -436,7 +435,8 @@ private void writeReflog(List<ReceiveCommand> commands) {
}
}
try {
w.log(name, cmd.getOldId(), cmd.getNewId(), ident, msg);
new ReflogWriter(refdb, isForceRefLog(cmd))
.log(name, cmd.getOldId(), cmd.getNewId(), ident, msg);
} catch (IOException e) {
// Ignore failures, but continue attempting to write more reflogs.
//

View File

@ -151,8 +151,6 @@ public class RefDirectory extends RefDatabase {
final File refsDir;
private final ReflogWriter logWriter;
final File packedRefsFile;
final File logsDir;
@ -210,7 +208,6 @@ public class RefDirectory extends RefDatabase {
final FS fs = db.getFS();
parent = db;
gitDir = db.getDirectory();
logWriter = new ReflogWriter(this);
refsDir = fs.resolve(gitDir, R_REFS);
logsDir = fs.resolve(gitDir, LOGS);
logsRefsDir = fs.resolve(gitDir, LOGS + '/' + R_REFS);
@ -224,8 +221,8 @@ Repository getRepository() {
return parent;
}
ReflogWriter getLogWriter() {
return logWriter;
ReflogWriter newLogWriter(boolean force) {
return new ReflogWriter(this, force);
}
/**
@ -249,7 +246,7 @@ public void create() throws IOException {
FileUtils.mkdir(refsDir);
FileUtils.mkdir(new File(refsDir, R_HEADS.substring(R_REFS.length())));
FileUtils.mkdir(new File(refsDir, R_TAGS.substring(R_REFS.length())));
logWriter.create();
newLogWriter(false).create();
}
@Override
@ -866,9 +863,9 @@ private Ref peeledPackedRef(Ref f)
}
}
void log(final RefUpdate update, final String msg, final boolean deref)
void log(boolean force, RefUpdate update, String msg, boolean deref)
throws IOException {
logWriter.log(update, msg, deref);
newLogWriter(force).log(update, msg, deref);
}
private Ref resolve(final Ref ref, int depth, String prefix,

View File

@ -120,7 +120,7 @@ protected Result doUpdate(final Result status) throws IOException {
msg = strResult;
}
}
database.log(this, msg, shouldDeref);
database.log(isForceRefLog(), this, msg, shouldDeref);
}
if (!lock.commit())
return Result.LOCK_FAILURE;
@ -159,7 +159,7 @@ protected Result doLink(final String target) throws IOException {
String msg = getRefLogMessage();
if (msg != null)
database.log(this, msg, false);
database.log(isForceRefLog(), this, msg, false);
if (!lock.commit())
return Result.LOCK_FAILURE;
database.storedSymbolicRef(this, lock.getCommitSnapshot(), target);

View File

@ -104,6 +104,12 @@ public class BatchRefUpdate {
/** Should the result value be appended to {@link #refLogMessage}. */
private boolean refLogIncludeResult;
/**
* Should reflogs be written even if the configured default for this ref is
* not to write it.
*/
private boolean forceRefLog;
/** Push certificate associated with this update. */
private PushCertificate pushCert;
@ -198,6 +204,12 @@ public boolean isRefLogIncludingResult() {
/**
* Set the message to include in the reflog.
* <p>
* Repository implementations may limit which reflogs are written by default,
* based on the project configuration. If a repo is not configured to write
* logs for this ref by default, setting the message alone may have no effect.
* To indicate that the repo should write logs for this update in spite of
* configured defaults, use {@link #setForceRefLog(boolean)}.
* <p>
* Describes the default for commands in this batch that do not override it
* with {@link ReceiveCommand#setRefLogMessage(String, boolean)}.
*
@ -235,6 +247,18 @@ public BatchRefUpdate disableRefLog() {
return this;
}
/**
* Force writing a reflog for the updated ref.
*
* @param force whether to force.
* @return {@code this}
* @since 4.9
*/
public BatchRefUpdate setForceRefLog(boolean force) {
forceRefLog = force;
return this;
}
/**
* Check whether log has been disabled by {@link #disableRefLog()}.
*
@ -244,6 +268,16 @@ public boolean isRefLogDisabled() {
return refLogMessage == null;
}
/**
* Check whether the reflog should be written regardless of repo defaults.
*
* @return whether force writing is enabled.
* @since 4.9
*/
protected boolean isForceRefLog() {
return forceRefLog;
}
/**
* Request that all updates in this batch be performed atomically.
* <p>
@ -635,6 +669,7 @@ protected RefUpdate newUpdate(ReceiveCommand cmd) throws IOException {
} else {
ru.setRefLogIdent(refLogIdent);
ru.setRefLogMessage(getRefLogMessage(cmd), isRefLogIncludingResult(cmd));
ru.setForceRefLog(isForceRefLog(cmd));
}
ru.setPushCertificate(pushCert);
switch (cmd.getType()) {
@ -696,6 +731,21 @@ protected boolean isRefLogIncludingResult(ReceiveCommand cmd) {
? cmd.isRefLogIncludingResult() : isRefLogIncludingResult();
}
/**
* Check whether the reflog for a command should be written regardless of repo
* defaults.
*
* @param cmd
* specific command.
* @return whether force writing is enabled.
* @since 4.9
*/
protected boolean isForceRefLog(ReceiveCommand cmd) {
Boolean isForceRefLog = cmd.isForceRefLog();
return isForceRefLog != null ? isForceRefLog.booleanValue()
: isForceRefLog();
}
@Override
public String toString() {
StringBuilder r = new StringBuilder();

View File

@ -185,6 +185,12 @@ public static enum Result {
/** Should the Result value be appended to {@link #refLogMessage}. */
private boolean refLogIncludeResult;
/**
* Should reflogs be written even if the configured default for this ref is
* not to write it.
*/
private boolean forceRefLog;
/** Old value of the ref, obtained after we lock it. */
private ObjectId oldValue;
@ -403,6 +409,12 @@ protected boolean isRefLogIncludingResult() {
/**
* Set the message to include in the reflog.
* <p>
* Repository implementations may limit which reflogs are written by default,
* based on the project configuration. If a repo is not configured to write
* logs for this ref by default, setting the message alone may have no effect.
* To indicate that the repo should write logs for this update in spite of
* configured defaults, use {@link #setForceRefLog(boolean)}.
*
* @param msg
* the message to describe this change. It may be null if
@ -430,6 +442,26 @@ public void disableRefLog() {
refLogIncludeResult = false;
}
/**
* Force writing a reflog for the updated ref.
*
* @param force whether to force.
* @since 4.9
*/
public void setForceRefLog(boolean force) {
forceRefLog = force;
}
/**
* Check whether the reflog should be written regardless of repo defaults.
*
* @return whether force writing is enabled.
* @since 4.9
*/
protected boolean isForceRefLog() {
return forceRefLog;
}
/**
* The old value of the ref, prior to the update being attempted.
* <p>

View File

@ -225,6 +225,8 @@ public static boolean isTransactionAborted(ReceiveCommand cmd) {
private boolean refLogIncludeResult;
private Boolean forceRefLog;
private boolean typeIsCorrect;
/**
@ -390,8 +392,22 @@ public void disableRefLog() {
}
/**
* Check whether this command has a custom reflog setting that should override
* defaults in any containing {@link org.eclipse.jgit.lib.BatchRefUpdate}.
* Force writing a reflog for the updated ref.
*
* @param force whether to force.
* @since 4.9
*/
public void setForceRefLog(boolean force) {
forceRefLog = Boolean.valueOf(force);
}
/**
* Check whether this command has a custom reflog message setting that should
* override defaults in any containing
* {@link org.eclipse.jgit.lib.BatchRefUpdate}.
* <p>
* Does not take into account whether {@code #setForceRefLog(boolean)} has
* been called.
*
* @return whether a custom reflog is set.
* @since 4.9
@ -433,6 +449,18 @@ public boolean isRefLogIncludingResult() {
return refLogIncludeResult;
}
/**
* Check whether the reflog should be written regardless of repo defaults.
*
* @return whether force writing is enabled; null if {@code
* #setForceRefLog(boolean)} was never called.
* @since 4.9
*/
@Nullable
public Boolean isForceRefLog() {
return forceRefLog;
}
/**
* Set the status of this command.
*