[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:
Thomas Wolf 2022-02-20 18:11:49 +01:00 committed by Matthias Sohn
parent 90df7c123e
commit 8a2c769417
7 changed files with 342 additions and 75 deletions

View File

@ -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"));
} }
} }

View File

@ -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());
}
} }

View File

@ -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(

View File

@ -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) {

View File

@ -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();
} }

View File

@ -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;
} }
/** /**

View File

@ -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) {