[push] support the "matching" RefSpecs ":" and "+:"
The implementation of push.default=matching was not correct. It used the RefSpec "refs/heads/*:refs/heads/*", which would push _all_ local branches. But "matching" must push only those local branches for which a remote branch with the same name already exists at the remote. This RefSpec can be expanded only once the advertisement from the remote has been received. Enhance RefSpec so that ":" and "+:" can be represented. Introduce a special RemoteRefUpdate for such a RefSpec; it must carry through the fetch RefSpecs to be able to fill in the remote tracking updates as needed. Implement the expansion in PushProcess. Bug: 353405 Change-Id: I54a4bfbb0a6a7d77b9128bf4a9c951d6586c3df4 Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
This commit is contained in:
parent
90df7c123e
commit
8a2c769417
|
@ -10,6 +10,7 @@
|
||||||
package org.eclipse.jgit.api;
|
package org.eclipse.jgit.api;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertThrows;
|
import static org.junit.Assert.assertThrows;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
@ -392,28 +393,64 @@ public void testPushDefaultMatching() throws Exception {
|
||||||
git.add().addFilepattern("f").call();
|
git.add().addFilepattern("f").call();
|
||||||
RevCommit commit = git.commit().setMessage("adding f").call();
|
RevCommit commit = git.commit().setMessage("adding f").call();
|
||||||
|
|
||||||
git.checkout().setName("also-pushed").setCreateBranch(true).call();
|
git.checkout().setName("not-pushed").setCreateBranch(true).call();
|
||||||
git.checkout().setName("branchtopush").setCreateBranch(true).call();
|
git.checkout().setName("branchtopush").setCreateBranch(true).call();
|
||||||
|
|
||||||
assertEquals(null,
|
assertEquals(null,
|
||||||
git2.getRepository().resolve("refs/heads/branchtopush"));
|
git2.getRepository().resolve("refs/heads/branchtopush"));
|
||||||
assertEquals(null,
|
assertEquals(null,
|
||||||
git2.getRepository().resolve("refs/heads/also-pushed"));
|
git2.getRepository().resolve("refs/heads/not-pushed"));
|
||||||
assertEquals(null,
|
assertEquals(null,
|
||||||
git2.getRepository().resolve("refs/heads/master"));
|
git2.getRepository().resolve("refs/heads/master"));
|
||||||
git.push().setRemote("test").setPushDefault(PushDefault.MATCHING)
|
// push master and branchtopush
|
||||||
|
git.push().setRemote("test").setRefSpecs(
|
||||||
|
new RefSpec("refs/heads/master:refs/heads/master"),
|
||||||
|
new RefSpec(
|
||||||
|
"refs/heads/branchtopush:refs/heads/branchtopush"))
|
||||||
.call();
|
.call();
|
||||||
assertEquals(commit.getId(),
|
|
||||||
git2.getRepository().resolve("refs/heads/branchtopush"));
|
|
||||||
assertEquals(commit.getId(),
|
|
||||||
git2.getRepository().resolve("refs/heads/also-pushed"));
|
|
||||||
assertEquals(commit.getId(),
|
assertEquals(commit.getId(),
|
||||||
git2.getRepository().resolve("refs/heads/master"));
|
git2.getRepository().resolve("refs/heads/master"));
|
||||||
assertEquals(commit.getId(), git.getRepository()
|
|
||||||
.resolve("refs/remotes/origin/branchtopush"));
|
|
||||||
assertEquals(commit.getId(), git.getRepository()
|
|
||||||
.resolve("refs/remotes/origin/also-pushed"));
|
|
||||||
assertEquals(commit.getId(),
|
assertEquals(commit.getId(),
|
||||||
|
git2.getRepository().resolve("refs/heads/branchtopush"));
|
||||||
|
assertEquals(null,
|
||||||
|
git2.getRepository().resolve("refs/heads/not-pushed"));
|
||||||
|
// Create two different commits on these two branches
|
||||||
|
writeTrashFile("b", "on branchtopush");
|
||||||
|
git.add().addFilepattern("b").call();
|
||||||
|
RevCommit bCommit = git.commit().setMessage("on branchtopush")
|
||||||
|
.call();
|
||||||
|
git.checkout().setName("master").call();
|
||||||
|
writeTrashFile("m", "on master");
|
||||||
|
git.add().addFilepattern("m").call();
|
||||||
|
RevCommit mCommit = git.commit().setMessage("on master").call();
|
||||||
|
// Now push with mode "matching": should push both branches.
|
||||||
|
Iterable<PushResult> result = git.push().setRemote("test")
|
||||||
|
.setPushDefault(PushDefault.MATCHING)
|
||||||
|
.call();
|
||||||
|
int n = 0;
|
||||||
|
for (PushResult r : result) {
|
||||||
|
n++;
|
||||||
|
assertEquals(1, n);
|
||||||
|
assertEquals(2, r.getRemoteUpdates().size());
|
||||||
|
for (RemoteRefUpdate update : r.getRemoteUpdates()) {
|
||||||
|
assertFalse(update.isMatching());
|
||||||
|
assertTrue(update.getSrcRef()
|
||||||
|
.equals("refs/heads/branchtopush")
|
||||||
|
|| update.getSrcRef().equals("refs/heads/master"));
|
||||||
|
assertEquals(RemoteRefUpdate.Status.OK, update.getStatus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(bCommit.getId(),
|
||||||
|
git2.getRepository().resolve("refs/heads/branchtopush"));
|
||||||
|
assertEquals(null,
|
||||||
|
git2.getRepository().resolve("refs/heads/not-pushed"));
|
||||||
|
assertEquals(mCommit.getId(),
|
||||||
|
git2.getRepository().resolve("refs/heads/master"));
|
||||||
|
assertEquals(bCommit.getId(), git.getRepository()
|
||||||
|
.resolve("refs/remotes/origin/branchtopush"));
|
||||||
|
assertEquals(null, git.getRepository()
|
||||||
|
.resolve("refs/remotes/origin/not-pushed"));
|
||||||
|
assertEquals(mCommit.getId(),
|
||||||
git.getRepository().resolve("refs/remotes/origin/master"));
|
git.getRepository().resolve("refs/remotes/origin/master"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -466,4 +466,18 @@ public void onlyWildCard() {
|
||||||
assertTrue(a.matchSource("refs/heads/master"));
|
assertTrue(a.matchSource("refs/heads/master"));
|
||||||
assertNull(a.getDestination());
|
assertNull(a.getDestination());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void matching() {
|
||||||
|
RefSpec a = new RefSpec(":");
|
||||||
|
assertTrue(a.isMatching());
|
||||||
|
assertFalse(a.isForceUpdate());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void matchingForced() {
|
||||||
|
RefSpec a = new RefSpec("+:");
|
||||||
|
assertTrue(a.isMatching());
|
||||||
|
assertTrue(a.isForceUpdate());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -230,7 +230,7 @@ private void determineDefaultRefSpecs(Config config)
|
||||||
refSpecs.add(new RefSpec(getCurrentBranch()));
|
refSpecs.add(new RefSpec(getCurrentBranch()));
|
||||||
break;
|
break;
|
||||||
case MATCHING:
|
case MATCHING:
|
||||||
setPushAll();
|
refSpecs.add(new RefSpec(":")); //$NON-NLS-1$
|
||||||
break;
|
break;
|
||||||
case NOTHING:
|
case NOTHING:
|
||||||
throw new InvalidRefNameException(
|
throw new InvalidRefNameException(
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
import org.eclipse.jgit.errors.TransportException;
|
import org.eclipse.jgit.errors.TransportException;
|
||||||
import org.eclipse.jgit.hooks.PrePushHook;
|
import org.eclipse.jgit.hooks.PrePushHook;
|
||||||
import org.eclipse.jgit.internal.JGitText;
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.ProgressMonitor;
|
import org.eclipse.jgit.lib.ProgressMonitor;
|
||||||
import org.eclipse.jgit.lib.Ref;
|
import org.eclipse.jgit.lib.Ref;
|
||||||
|
@ -141,9 +142,13 @@ PushResult execute(ProgressMonitor monitor)
|
||||||
res.setAdvertisedRefs(transport.getURI(), connection
|
res.setAdvertisedRefs(transport.getURI(), connection
|
||||||
.getRefsMap());
|
.getRefsMap());
|
||||||
res.peerUserAgent = connection.getPeerUserAgent();
|
res.peerUserAgent = connection.getPeerUserAgent();
|
||||||
res.setRemoteUpdates(toPush);
|
|
||||||
monitor.endTask();
|
monitor.endTask();
|
||||||
|
|
||||||
|
Map<String, RemoteRefUpdate> expanded = expandMatching();
|
||||||
|
toPush.clear();
|
||||||
|
toPush.putAll(expanded);
|
||||||
|
|
||||||
|
res.setRemoteUpdates(toPush);
|
||||||
final Map<String, RemoteRefUpdate> preprocessed = prepareRemoteUpdates();
|
final Map<String, RemoteRefUpdate> preprocessed = prepareRemoteUpdates();
|
||||||
List<RemoteRefUpdate> willBeAttempted = preprocessed.values()
|
List<RemoteRefUpdate> willBeAttempted = preprocessed.values()
|
||||||
.stream().filter(u -> {
|
.stream().filter(u -> {
|
||||||
|
@ -237,25 +242,8 @@ private Map<String, RemoteRefUpdate> prepareRemoteUpdates()
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for fast-forward:
|
boolean fastForward = isFastForward(advertisedOld,
|
||||||
// - both old and new ref must point to commits, AND
|
rru.getNewObjectId());
|
||||||
// - both of them must be known for us, exist in repository, AND
|
|
||||||
// - old commit must be ancestor of new commit
|
|
||||||
boolean fastForward = true;
|
|
||||||
try {
|
|
||||||
RevObject oldRev = walker.parseAny(advertisedOld);
|
|
||||||
final RevObject newRev = walker.parseAny(rru.getNewObjectId());
|
|
||||||
if (!(oldRev instanceof RevCommit)
|
|
||||||
|| !(newRev instanceof RevCommit)
|
|
||||||
|| !walker.isMergedInto((RevCommit) oldRev,
|
|
||||||
(RevCommit) newRev))
|
|
||||||
fastForward = false;
|
|
||||||
} catch (MissingObjectException x) {
|
|
||||||
fastForward = false;
|
|
||||||
} catch (Exception x) {
|
|
||||||
throw new TransportException(transport.getURI(), MessageFormat.format(
|
|
||||||
JGitText.get().readingObjectsFromLocalRepositoryFailed, x.getMessage()), x);
|
|
||||||
}
|
|
||||||
rru.setFastForward(fastForward);
|
rru.setFastForward(fastForward);
|
||||||
if (!fastForward && !rru.isForceUpdate()) {
|
if (!fastForward && !rru.isForceUpdate()) {
|
||||||
rru.setStatus(Status.REJECTED_NONFASTFORWARD);
|
rru.setStatus(Status.REJECTED_NONFASTFORWARD);
|
||||||
|
@ -269,6 +257,134 @@ private Map<String, RemoteRefUpdate> prepareRemoteUpdates()
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether an update from {@code oldOid} to {@code newOid} is a
|
||||||
|
* fast-forward update:
|
||||||
|
* <ul>
|
||||||
|
* <li>both old and new must be commits, AND</li>
|
||||||
|
* <li>both of them must be known to us and exist in the repository,
|
||||||
|
* AND</li>
|
||||||
|
* <li>the old commit must be an ancestor of the new commit.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param oldOid
|
||||||
|
* {@link ObjectId} of the old commit
|
||||||
|
* @param newOid
|
||||||
|
* {@link ObjectId} of the new commit
|
||||||
|
* @return {@code true} if the update fast-forwards, {@code false} otherwise
|
||||||
|
* @throws TransportException
|
||||||
|
*/
|
||||||
|
private boolean isFastForward(ObjectId oldOid, ObjectId newOid)
|
||||||
|
throws TransportException {
|
||||||
|
try {
|
||||||
|
RevObject oldRev = walker.parseAny(oldOid);
|
||||||
|
RevObject newRev = walker.parseAny(newOid);
|
||||||
|
if (!(oldRev instanceof RevCommit) || !(newRev instanceof RevCommit)
|
||||||
|
|| !walker.isMergedInto((RevCommit) oldRev,
|
||||||
|
(RevCommit) newRev)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (MissingObjectException x) {
|
||||||
|
return false;
|
||||||
|
} catch (Exception x) {
|
||||||
|
throw new TransportException(transport.getURI(),
|
||||||
|
MessageFormat.format(JGitText
|
||||||
|
.get().readingObjectsFromLocalRepositoryFailed,
|
||||||
|
x.getMessage()),
|
||||||
|
x);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expands all placeholder {@link RemoteRefUpdate}s for "matching"
|
||||||
|
* {@link RefSpec}s ":" in {@link #toPush} and returns the resulting map in
|
||||||
|
* which the placeholders have been replaced by their expansion.
|
||||||
|
*
|
||||||
|
* @return a new map of {@link RemoteRefUpdate}s keyed by remote name
|
||||||
|
* @throws TransportException
|
||||||
|
* if the expansion results in duplicate updates
|
||||||
|
*/
|
||||||
|
private Map<String, RemoteRefUpdate> expandMatching()
|
||||||
|
throws TransportException {
|
||||||
|
Map<String, RemoteRefUpdate> result = new LinkedHashMap<>();
|
||||||
|
boolean hadMatch = false;
|
||||||
|
for (RemoteRefUpdate update : toPush.values()) {
|
||||||
|
if (update.isMatching()) {
|
||||||
|
if (hadMatch) {
|
||||||
|
throw new TransportException(MessageFormat.format(
|
||||||
|
JGitText.get().duplicateRemoteRefUpdateIsIllegal,
|
||||||
|
":")); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
expandMatching(result, update);
|
||||||
|
hadMatch = true;
|
||||||
|
} else if (result.put(update.getRemoteName(), update) != null) {
|
||||||
|
throw new TransportException(MessageFormat.format(
|
||||||
|
JGitText.get().duplicateRemoteRefUpdateIsIllegal,
|
||||||
|
update.getRemoteName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expands the placeholder {@link RemoteRefUpdate} {@code match} for a
|
||||||
|
* "matching" {@link RefSpec} ":" or "+:" and puts the expansion into the
|
||||||
|
* given map {@code updates}.
|
||||||
|
*
|
||||||
|
* @param updates
|
||||||
|
* map to put the expansion in
|
||||||
|
* @param match
|
||||||
|
* the placeholder {@link RemoteRefUpdate} to expand
|
||||||
|
*
|
||||||
|
* @throws TransportException
|
||||||
|
* if the expansion results in duplicate updates, or the local
|
||||||
|
* branches cannot be determined
|
||||||
|
*/
|
||||||
|
private void expandMatching(Map<String, RemoteRefUpdate> updates,
|
||||||
|
RemoteRefUpdate match) throws TransportException {
|
||||||
|
try {
|
||||||
|
Map<String, Ref> advertisement = connection.getRefsMap();
|
||||||
|
Collection<RefSpec> fetchSpecs = match.getFetchSpecs();
|
||||||
|
boolean forceUpdate = match.isForceUpdate();
|
||||||
|
for (Ref local : transport.local.getRefDatabase()
|
||||||
|
.getRefsByPrefix(Constants.R_HEADS)) {
|
||||||
|
if (local.isSymbolic()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String name = local.getName();
|
||||||
|
Ref advertised = advertisement.get(name);
|
||||||
|
if (advertised == null || advertised.isSymbolic()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ObjectId oldOid = advertised.getObjectId();
|
||||||
|
if (oldOid == null || ObjectId.zeroId().equals(oldOid)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ObjectId newOid = local.getObjectId();
|
||||||
|
if (newOid == null || ObjectId.zeroId().equals(newOid)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteRefUpdate rru = new RemoteRefUpdate(transport.local, name,
|
||||||
|
newOid, name, forceUpdate,
|
||||||
|
Transport.findTrackingRefName(name, fetchSpecs),
|
||||||
|
oldOid);
|
||||||
|
if (updates.put(rru.getRemoteName(), rru) != null) {
|
||||||
|
throw new TransportException(MessageFormat.format(
|
||||||
|
JGitText.get().duplicateRemoteRefUpdateIsIllegal,
|
||||||
|
rru.getRemoteName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException x) {
|
||||||
|
throw new TransportException(transport.getURI(),
|
||||||
|
MessageFormat.format(JGitText
|
||||||
|
.get().readingObjectsFromLocalRepositoryFailed,
|
||||||
|
x.getMessage()),
|
||||||
|
x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Map<String, RemoteRefUpdate> rejectAll() {
|
private Map<String, RemoteRefUpdate> rejectAll() {
|
||||||
for (RemoteRefUpdate rru : toPush.values()) {
|
for (RemoteRefUpdate rru : toPush.values()) {
|
||||||
if (rru.getStatus() == Status.NOT_ATTEMPTED) {
|
if (rru.getStatus() == Status.NOT_ATTEMPTED) {
|
||||||
|
|
|
@ -12,11 +12,11 @@
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.eclipse.jgit.internal.JGitText;
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.Ref;
|
import org.eclipse.jgit.lib.Ref;
|
||||||
import org.eclipse.jgit.util.References;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes how refs in one repository copy into another repository.
|
* Describes how refs in one repository copy into another repository.
|
||||||
|
@ -50,6 +50,9 @@ public static boolean isWildcard(String s) {
|
||||||
/** Is this specification actually a wildcard match? */
|
/** Is this specification actually a wildcard match? */
|
||||||
private boolean wildcard;
|
private boolean wildcard;
|
||||||
|
|
||||||
|
/** Is this the special ":" RefSpec? */
|
||||||
|
private boolean matching;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How strict to be about wildcards.
|
* How strict to be about wildcards.
|
||||||
*
|
*
|
||||||
|
@ -71,6 +74,7 @@ public enum WildcardMode {
|
||||||
*/
|
*/
|
||||||
ALLOW_MISMATCH
|
ALLOW_MISMATCH
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Whether a wildcard is allowed on one side but not the other. */
|
/** Whether a wildcard is allowed on one side but not the other. */
|
||||||
private WildcardMode allowMismatchedWildcards;
|
private WildcardMode allowMismatchedWildcards;
|
||||||
|
|
||||||
|
@ -87,6 +91,7 @@ public enum WildcardMode {
|
||||||
* applications, as at least one field must be set to match a source name.
|
* applications, as at least one field must be set to match a source name.
|
||||||
*/
|
*/
|
||||||
public RefSpec() {
|
public RefSpec() {
|
||||||
|
matching = false;
|
||||||
force = false;
|
force = false;
|
||||||
wildcard = false;
|
wildcard = false;
|
||||||
srcName = Constants.HEAD;
|
srcName = Constants.HEAD;
|
||||||
|
@ -133,17 +138,25 @@ public RefSpec(String spec, WildcardMode mode) {
|
||||||
s = s.substring(1);
|
s = s.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean matchPushSpec = false;
|
||||||
final int c = s.lastIndexOf(':');
|
final int c = s.lastIndexOf(':');
|
||||||
if (c == 0) {
|
if (c == 0) {
|
||||||
s = s.substring(1);
|
s = s.substring(1);
|
||||||
if (isWildcard(s)) {
|
if (s.isEmpty()) {
|
||||||
|
matchPushSpec = true;
|
||||||
wildcard = true;
|
wildcard = true;
|
||||||
if (mode == WildcardMode.REQUIRE_MATCH) {
|
srcName = Constants.R_HEADS + '*';
|
||||||
throw new IllegalArgumentException(MessageFormat
|
dstName = srcName;
|
||||||
.format(JGitText.get().invalidWildcards, spec));
|
} else {
|
||||||
|
if (isWildcard(s)) {
|
||||||
|
wildcard = true;
|
||||||
|
if (mode == WildcardMode.REQUIRE_MATCH) {
|
||||||
|
throw new IllegalArgumentException(MessageFormat
|
||||||
|
.format(JGitText.get().invalidWildcards, spec));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
dstName = checkValid(s);
|
||||||
}
|
}
|
||||||
dstName = checkValid(s);
|
|
||||||
} else if (c > 0) {
|
} else if (c > 0) {
|
||||||
String src = s.substring(0, c);
|
String src = s.substring(0, c);
|
||||||
String dst = s.substring(c + 1);
|
String dst = s.substring(c + 1);
|
||||||
|
@ -168,6 +181,7 @@ public RefSpec(String spec, WildcardMode mode) {
|
||||||
}
|
}
|
||||||
srcName = checkValid(s);
|
srcName = checkValid(s);
|
||||||
}
|
}
|
||||||
|
matching = matchPushSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -195,6 +209,7 @@ public RefSpec(String spec) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private RefSpec(RefSpec p) {
|
private RefSpec(RefSpec p) {
|
||||||
|
matching = false;
|
||||||
force = p.isForceUpdate();
|
force = p.isForceUpdate();
|
||||||
wildcard = p.isWildcard();
|
wildcard = p.isWildcard();
|
||||||
srcName = p.getSource();
|
srcName = p.getSource();
|
||||||
|
@ -202,6 +217,17 @@ private RefSpec(RefSpec p) {
|
||||||
allowMismatchedWildcards = p.allowMismatchedWildcards;
|
allowMismatchedWildcards = p.allowMismatchedWildcards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells whether this {@link RefSpec} is the special "matching" RefSpec ":"
|
||||||
|
* for pushing.
|
||||||
|
*
|
||||||
|
* @return whether this is a "matching" RefSpec
|
||||||
|
* @since 6.1
|
||||||
|
*/
|
||||||
|
public boolean isMatching() {
|
||||||
|
return matching;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this specification wants to forcefully update the destination.
|
* Check if this specification wants to forcefully update the destination.
|
||||||
*
|
*
|
||||||
|
@ -220,6 +246,7 @@ public boolean isForceUpdate() {
|
||||||
*/
|
*/
|
||||||
public RefSpec setForceUpdate(boolean forceUpdate) {
|
public RefSpec setForceUpdate(boolean forceUpdate) {
|
||||||
final RefSpec r = new RefSpec(this);
|
final RefSpec r = new RefSpec(this);
|
||||||
|
r.matching = matching;
|
||||||
r.force = forceUpdate;
|
r.force = forceUpdate;
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
@ -541,37 +568,36 @@ public boolean equals(Object obj) {
|
||||||
if (!(obj instanceof RefSpec))
|
if (!(obj instanceof RefSpec))
|
||||||
return false;
|
return false;
|
||||||
final RefSpec b = (RefSpec) obj;
|
final RefSpec b = (RefSpec) obj;
|
||||||
if (isForceUpdate() != b.isForceUpdate())
|
if (isForceUpdate() != b.isForceUpdate()) {
|
||||||
return false;
|
return false;
|
||||||
if (isWildcard() != b.isWildcard())
|
|
||||||
return false;
|
|
||||||
if (!eq(getSource(), b.getSource()))
|
|
||||||
return false;
|
|
||||||
if (!eq(getDestination(), b.getDestination()))
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean eq(String a, String b) {
|
|
||||||
if (References.isSameObject(a, b)) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
if (a == null || b == null)
|
if (isMatching()) {
|
||||||
|
return b.isMatching();
|
||||||
|
} else if (b.isMatching()) {
|
||||||
return false;
|
return false;
|
||||||
return a.equals(b);
|
}
|
||||||
|
return isWildcard() == b.isWildcard()
|
||||||
|
&& Objects.equals(getSource(), b.getSource())
|
||||||
|
&& Objects.equals(getDestination(), b.getDestination());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
final StringBuilder r = new StringBuilder();
|
final StringBuilder r = new StringBuilder();
|
||||||
if (isForceUpdate())
|
if (isForceUpdate()) {
|
||||||
r.append('+');
|
r.append('+');
|
||||||
if (getSource() != null)
|
}
|
||||||
r.append(getSource());
|
if (isMatching()) {
|
||||||
if (getDestination() != null) {
|
|
||||||
r.append(':');
|
r.append(':');
|
||||||
r.append(getDestination());
|
} else {
|
||||||
|
if (getSource() != null) {
|
||||||
|
r.append(getSource());
|
||||||
|
}
|
||||||
|
if (getDestination() != null) {
|
||||||
|
r.append(':');
|
||||||
|
r.append(getDestination());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return r.toString();
|
return r.toString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,9 @@
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.annotations.NonNull;
|
||||||
import org.eclipse.jgit.internal.JGitText;
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.Ref;
|
import org.eclipse.jgit.lib.Ref;
|
||||||
|
@ -115,6 +117,12 @@ public enum Status {
|
||||||
|
|
||||||
private RefUpdate localUpdate;
|
private RefUpdate localUpdate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, the RemoteRefUpdate is a placeholder for the "matching" RefSpec
|
||||||
|
* to be expanded after the advertisements have been received in a push.
|
||||||
|
*/
|
||||||
|
private Collection<RefSpec> fetchSpecs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct remote ref update request by providing an update specification.
|
* Construct remote ref update request by providing an update specification.
|
||||||
* Object is created with default
|
* Object is created with default
|
||||||
|
@ -259,24 +267,38 @@ public RemoteRefUpdate(final Repository localDb, final String srcRef,
|
||||||
final ObjectId srcId, final String remoteName,
|
final ObjectId srcId, final String remoteName,
|
||||||
final boolean forceUpdate, final String localName,
|
final boolean forceUpdate, final String localName,
|
||||||
final ObjectId expectedOldObjectId) throws IOException {
|
final ObjectId expectedOldObjectId) throws IOException {
|
||||||
if (remoteName == null)
|
this(localDb, srcRef, srcId, remoteName, forceUpdate, localName, null,
|
||||||
throw new IllegalArgumentException(JGitText.get().remoteNameCannotBeNull);
|
expectedOldObjectId);
|
||||||
if (srcId == null && srcRef != null)
|
}
|
||||||
throw new IOException(MessageFormat.format(
|
|
||||||
JGitText.get().sourceRefDoesntResolveToAnyObject, srcRef));
|
|
||||||
|
|
||||||
if (srcRef != null)
|
private RemoteRefUpdate(Repository localDb, String srcRef, ObjectId srcId,
|
||||||
|
String remoteName, boolean forceUpdate, String localName,
|
||||||
|
Collection<RefSpec> fetchSpecs, ObjectId expectedOldObjectId)
|
||||||
|
throws IOException {
|
||||||
|
if (fetchSpecs == null) {
|
||||||
|
if (remoteName == null) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
JGitText.get().remoteNameCannotBeNull);
|
||||||
|
}
|
||||||
|
if (srcId == null && srcRef != null) {
|
||||||
|
throw new IOException(MessageFormat.format(
|
||||||
|
JGitText.get().sourceRefDoesntResolveToAnyObject,
|
||||||
|
srcRef));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (srcRef != null) {
|
||||||
this.srcRef = srcRef;
|
this.srcRef = srcRef;
|
||||||
else if (srcId != null && !srcId.equals(ObjectId.zeroId()))
|
} else if (srcId != null && !srcId.equals(ObjectId.zeroId())) {
|
||||||
this.srcRef = srcId.name();
|
this.srcRef = srcId.name();
|
||||||
else
|
} else {
|
||||||
this.srcRef = null;
|
this.srcRef = null;
|
||||||
|
}
|
||||||
if (srcId != null)
|
if (srcId != null) {
|
||||||
this.newObjectId = srcId;
|
this.newObjectId = srcId;
|
||||||
else
|
} else {
|
||||||
this.newObjectId = ObjectId.zeroId();
|
this.newObjectId = ObjectId.zeroId();
|
||||||
|
}
|
||||||
|
this.fetchSpecs = fetchSpecs;
|
||||||
this.remoteName = remoteName;
|
this.remoteName = remoteName;
|
||||||
this.forceUpdate = forceUpdate;
|
this.forceUpdate = forceUpdate;
|
||||||
if (localName != null && localDb != null) {
|
if (localName != null && localDb != null) {
|
||||||
|
@ -292,8 +314,9 @@ else if (srcId != null && !srcId.equals(ObjectId.zeroId()))
|
||||||
? localUpdate.getOldObjectId()
|
? localUpdate.getOldObjectId()
|
||||||
: ObjectId.zeroId(),
|
: ObjectId.zeroId(),
|
||||||
newObjectId);
|
newObjectId);
|
||||||
} else
|
} else {
|
||||||
trackingRefUpdate = null;
|
trackingRefUpdate = null;
|
||||||
|
}
|
||||||
this.localDb = localDb;
|
this.localDb = localDb;
|
||||||
this.expectedOldObjectId = expectedOldObjectId;
|
this.expectedOldObjectId = expectedOldObjectId;
|
||||||
this.status = Status.NOT_ATTEMPTED;
|
this.status = Status.NOT_ATTEMPTED;
|
||||||
|
@ -318,9 +341,55 @@ else if (srcId != null && !srcId.equals(ObjectId.zeroId()))
|
||||||
*/
|
*/
|
||||||
public RemoteRefUpdate(final RemoteRefUpdate base,
|
public RemoteRefUpdate(final RemoteRefUpdate base,
|
||||||
final ObjectId newExpectedOldObjectId) throws IOException {
|
final ObjectId newExpectedOldObjectId) throws IOException {
|
||||||
this(base.localDb, base.srcRef, base.remoteName, base.forceUpdate,
|
this(base.localDb, base.srcRef, base.newObjectId, base.remoteName,
|
||||||
|
base.forceUpdate,
|
||||||
(base.trackingRefUpdate == null ? null : base.trackingRefUpdate
|
(base.trackingRefUpdate == null ? null : base.trackingRefUpdate
|
||||||
.getLocalName()), newExpectedOldObjectId);
|
.getLocalName()),
|
||||||
|
base.fetchSpecs, newExpectedOldObjectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a "placeholder" update for the "matching" RefSpec ":".
|
||||||
|
*
|
||||||
|
* @param localDb
|
||||||
|
* local repository to push from
|
||||||
|
* @param forceUpdate
|
||||||
|
* whether non-fast-forward updates shall be allowed
|
||||||
|
* @param fetchSpecs
|
||||||
|
* The fetch {@link RefSpec}s to use when this placeholder is
|
||||||
|
* expanded to determine remote tracking branch updates
|
||||||
|
*/
|
||||||
|
RemoteRefUpdate(Repository localDb, boolean forceUpdate,
|
||||||
|
@NonNull Collection<RefSpec> fetchSpecs) {
|
||||||
|
this.localDb = localDb;
|
||||||
|
this.forceUpdate = forceUpdate;
|
||||||
|
this.fetchSpecs = fetchSpecs;
|
||||||
|
this.trackingRefUpdate = null;
|
||||||
|
this.srcRef = null;
|
||||||
|
this.remoteName = null;
|
||||||
|
this.newObjectId = null;
|
||||||
|
this.status = Status.NOT_ATTEMPTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells whether this {@link RemoteRefUpdate} is a placeholder for a
|
||||||
|
* "matching" {@link RefSpec}.
|
||||||
|
*
|
||||||
|
* @return {@code true} if this is a placeholder, {@code false} otherwise
|
||||||
|
* @since 6.1
|
||||||
|
*/
|
||||||
|
public boolean isMatching() {
|
||||||
|
return fetchSpecs != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the fetch {@link RefSpec}s of this {@link RemoteRefUpdate}.
|
||||||
|
*
|
||||||
|
* @return the fetch {@link RefSpec}s, or {@code null} if
|
||||||
|
* {@code this.}{@link #isMatching()} {@code == false}
|
||||||
|
*/
|
||||||
|
Collection<RefSpec> getFetchSpecs() {
|
||||||
|
return fetchSpecs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -589,6 +589,11 @@ public static Collection<RemoteRefUpdate> findRemoteRefUpdatesFor(
|
||||||
final Collection<RefSpec> procRefs = expandPushWildcardsFor(db, specs);
|
final Collection<RefSpec> procRefs = expandPushWildcardsFor(db, specs);
|
||||||
|
|
||||||
for (RefSpec spec : procRefs) {
|
for (RefSpec spec : procRefs) {
|
||||||
|
if (spec.isMatching()) {
|
||||||
|
result.add(new RemoteRefUpdate(db, spec.isForceUpdate(),
|
||||||
|
fetchSpecs));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
String srcSpec = spec.getSource();
|
String srcSpec = spec.getSource();
|
||||||
final Ref srcRef = db.findRef(srcSpec);
|
final Ref srcRef = db.findRef(srcSpec);
|
||||||
if (srcRef != null)
|
if (srcRef != null)
|
||||||
|
@ -659,7 +664,7 @@ private static Collection<RefSpec> expandPushWildcardsFor(
|
||||||
|
|
||||||
List<Ref> localRefs = null;
|
List<Ref> localRefs = null;
|
||||||
for (RefSpec spec : specs) {
|
for (RefSpec spec : specs) {
|
||||||
if (spec.isWildcard()) {
|
if (!spec.isMatching() && spec.isWildcard()) {
|
||||||
if (localRefs == null) {
|
if (localRefs == null) {
|
||||||
localRefs = db.getRefDatabase().getRefs();
|
localRefs = db.getRefDatabase().getRefs();
|
||||||
}
|
}
|
||||||
|
@ -675,7 +680,7 @@ private static Collection<RefSpec> expandPushWildcardsFor(
|
||||||
return procRefs;
|
return procRefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String findTrackingRefName(final String remoteName,
|
static String findTrackingRefName(final String remoteName,
|
||||||
final Collection<RefSpec> fetchSpecs) {
|
final Collection<RefSpec> fetchSpecs) {
|
||||||
// try to find matching tracking refs
|
// try to find matching tracking refs
|
||||||
for (RefSpec fetchSpec : fetchSpecs) {
|
for (RefSpec fetchSpec : fetchSpecs) {
|
||||||
|
|
Loading…
Reference in New Issue