From 88c1b82e7a27a5e25c9be9cba246f076825da4c6 Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Fri, 7 Nov 2014 18:51:18 -0800 Subject: [PATCH] Implement atomic refs update, if possible by database Inspired by the series[1], this implements the possibility to have atomic ref transactions. If the database supports atomic ref update capabilities, we'll advertise these. If the client wishes to use this feature, either all refs will be updated or none at all. [1] http://thread.gmane.org/gmane.comp.version-control.git/259019/focus=259024 Change-Id: I7b5d19c21f3b5557e41b9bcb5d359a65ff1a493d Signed-off-by: Stefan Beller --- .../eclipse/jgit/internal/JGitText.properties | 1 + .../org/eclipse/jgit/internal/JGitText.java | 1 + .../src/org/eclipse/jgit/lib/RefDatabase.java | 8 ++++++++ .../jgit/transport/BaseReceivePack.java | 20 +++++++++++++++++++ .../jgit/transport/GitProtocolConstants.java | 8 ++++++++ .../eclipse/jgit/transport/ReceivePack.java | 7 +++++++ 6 files changed, 45 insertions(+) diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index 45021e847..524aa3e6a 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -500,6 +500,7 @@ tagOnRepoWithoutHEADCurrentlyNotSupported=Tag on repository without HEAD current theFactoryMustNotBeNull=The factory must not be null timerAlreadyTerminated=Timer already terminated topologicalSortRequired=Topological sort required. +transactionAborted=transaction aborted transportExceptionBadRef=Empty ref: {0}: {1} transportExceptionEmptyRef=Empty ref: {0} transportExceptionInvalid=Invalid {0} {1}:{2} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java index f2a1b948c..dd1be0d5f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -556,6 +556,7 @@ public static JGitText get() { /***/ public String tagAlreadyExists; /***/ public String tagNameInvalid; /***/ public String tagOnRepoWithoutHEADCurrentlyNotSupported; + /***/ public String transactionAborted; /***/ public String theFactoryMustNotBeNull; /***/ public String timerAlreadyTerminated; /***/ public String topologicalSortRequired; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java index 682cac162..0458ac491 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java @@ -196,6 +196,14 @@ public BatchRefUpdate newBatchUpdate() { return new BatchRefUpdate(this); } + /** + * @return if the database performs {@code newBatchUpdate()} as an atomic + * transaction. + */ + public boolean performsAtomicTransactions() { + return false; + } + /** * Read a single reference. *

diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java index 72c169759..475ba3528 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java @@ -43,6 +43,7 @@ package org.eclipse.jgit.transport; +import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_DELETE_REFS; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_OFS_DELTA; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REPORT_STATUS; @@ -908,6 +909,8 @@ public void sendAdvertisedRefs(final RefAdvertiser adv) adv.advertiseCapability(CAPABILITY_SIDE_BAND_64K); adv.advertiseCapability(CAPABILITY_DELETE_REFS); adv.advertiseCapability(CAPABILITY_REPORT_STATUS); + if (db.getRefDatabase().performsAtomicTransactions()) + adv.advertiseCapability(CAPABILITY_ATOMIC); if (allowOfsDelta) adv.advertiseCapability(CAPABILITY_OFS_DELTA); adv.send(getAdvertisedOrDefaultRefs()); @@ -1251,6 +1254,23 @@ protected void validateCommands() { } } + /** @return if any commands have been rejected so far. */ + protected boolean anyRejects() { + for (ReceiveCommand cmd : commands) { + if (cmd.getResult() != Result.NOT_ATTEMPTED && cmd.getResult() != Result.OK) + return true; + } + return false; + } + + /** Set the result to fail for any command that was not processed yet. */ + protected void failPendingCommands() { + for (ReceiveCommand cmd : commands) { + if (cmd.getResult() == Result.NOT_ATTEMPTED) + cmd.setResult(Result.REJECTED_OTHER_REASON, JGitText.get().transactionAborted); + } + } + /** * Filter the list of commands according to result. * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java index c0a70d043..43fd07944 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java @@ -129,6 +129,14 @@ public class GitProtocolConstants { */ public static final String OPTION_ALLOW_TIP_SHA1_IN_WANT = "allow-tip-sha1-in-want"; //$NON-NLS-1$ + /** + * The client supports atomic pushes. If this option is used, the server + * will update all refs within one atomic transaction. + * + * @since 3.6 + */ + public static final String CAPABILITY_ATOMIC = "atomic-push"; //$NON-NLS-1$ + /** * The client expects a status report after the server processes the pack. * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java index 4d931dd5d..e5eb82241 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java @@ -43,6 +43,7 @@ package org.eclipse.jgit.transport; +import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REPORT_STATUS; import java.io.IOException; @@ -199,8 +200,14 @@ private void service() throws IOException { } if (unpackError == null) { + boolean atomic = isCapabilityEnabled(CAPABILITY_ATOMIC); validateCommands(); + if (atomic && anyRejects()) + failPendingCommands(); + preReceive.onPreReceive(this, filterCommands(Result.NOT_ATTEMPTED)); + if (atomic && anyRejects()) + failPendingCommands(); executeCommands(); } unlockPack();