If the source branch for a local tracking branch has been removed from the remote repository, users may also want (or need) to delete it from the local repository. A good example is when a branch named "refs/heads/bar" switches from a file to a directory, and the local path name in "refs/remotes/origin/bar" must also change. Local deletes are done before updates, to handle this special case of file to directory (or directory to file) conversions with as few errors as possible. Signed-off-by: Shawn O. Pearce <spearce@xxxxxxxxxxx> --- .../org/spearce/jgit/pgm/AbstractFetchCommand.java | 8 ++- .../src/org/spearce/jgit/pgm/Fetch.java | 9 ++++ .../org/spearce/jgit/transport/FetchProcess.java | 48 ++++++++++++++++++++ .../src/org/spearce/jgit/transport/Transport.java | 27 +++++++++++ 4 files changed, 89 insertions(+), 3 deletions(-) diff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/AbstractFetchCommand.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/AbstractFetchCommand.java index ea6f277..f5a9d65 100644 --- a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/AbstractFetchCommand.java +++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/AbstractFetchCommand.java @@ -39,6 +39,7 @@ import org.kohsuke.args4j.Option; import org.spearce.jgit.lib.Constants; +import org.spearce.jgit.lib.ObjectId; import org.spearce.jgit.lib.RefUpdate; import org.spearce.jgit.transport.FetchResult; import org.spearce.jgit.transport.TrackingRefUpdate; @@ -75,9 +76,12 @@ private String longTypeOf(final TrackingRefUpdate u) { final RefUpdate.Result r = u.getResult(); if (r == RefUpdate.Result.LOCK_FAILURE) return "[lock fail]"; - if (r == RefUpdate.Result.IO_FAILURE) return "[i/o error]"; + if (r == RefUpdate.Result.REJECTED) + return "[rejected]"; + if (ObjectId.zeroId().equals(u.getNewObjectId())) + return "[deleted]"; if (r == RefUpdate.Result.NEW) { if (u.getRemoteName().startsWith(Constants.R_HEADS)) @@ -99,8 +103,6 @@ else if (u.getLocalName().startsWith(Constants.R_TAGS)) return aOld + ".." + aNew; } - if (r == RefUpdate.Result.REJECTED) - return "[rejected]"; if (r == RefUpdate.Result.NO_CHANGE) return "[up to date]"; return "[" + r.name() + "]"; diff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Fetch.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Fetch.java index 8f3f7d5..81d6893 100644 --- a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Fetch.java +++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Fetch.java @@ -56,6 +56,12 @@ void nofsck(final boolean ignored) { fsck = Boolean.FALSE; } + @Option(name = "--prune", usage = "prune stale tracking refs") + private Boolean prune; + + @Option(name = "--dry-run") + private boolean dryRun; + @Option(name = "--thin", usage = "fetch thin pack") private Boolean thin; @@ -75,6 +81,9 @@ protected void run() throws Exception { final Transport tn = Transport.open(db, remote); if (fsck != null) tn.setCheckFetchedObjects(fsck.booleanValue()); + if (prune != null) + tn.setRemoveDeletedRefs(prune.booleanValue()); + tn.setDryRun(dryRun); if (thin != null) tn.setFetchThin(thin.booleanValue()); final FetchResult r; diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/FetchProcess.java b/org.spearce.jgit/src/org/spearce/jgit/transport/FetchProcess.java index 2ca8aeb..df64817 100644 --- a/org.spearce.jgit/src/org/spearce/jgit/transport/FetchProcess.java +++ b/org.spearce.jgit/src/org/spearce/jgit/transport/FetchProcess.java @@ -59,6 +59,7 @@ import org.spearce.jgit.lib.ObjectId; import org.spearce.jgit.lib.ProgressMonitor; import org.spearce.jgit.lib.Ref; +import org.spearce.jgit.lib.Repository; import org.spearce.jgit.revwalk.ObjectWalk; import org.spearce.jgit.revwalk.RevWalk; @@ -152,6 +153,8 @@ else if (tagopt == TagOpt.FETCH_TAGS) } final RevWalk walk = new RevWalk(transport.local); + if (transport.isRemoveDeletedRefs()) + deleteStaleTrackingRefs(result, walk); for (TrackingRefUpdate u : localUpdates) { try { u.update(walk); @@ -366,6 +369,51 @@ private TrackingRefUpdate createUpdate(final RefSpec spec, return new TrackingRefUpdate(transport.local, spec, newId, "fetch"); } + private void deleteStaleTrackingRefs(final FetchResult result, + final RevWalk walk) throws TransportException { + final Repository db = transport.local; + for (final Ref ref : db.getAllRefs().values()) { + final String refname = ref.getName(); + for (final RefSpec spec : toFetch) { + if (spec.matchDestination(refname)) { + final RefSpec s = spec.expandFromDestination(refname); + if (result.getAdvertisedRef(s.getSource()) == null) { + deleteTrackingRef(result, db, walk, s, ref); + } + } + } + } + } + + private void deleteTrackingRef(final FetchResult result, + final Repository db, final RevWalk walk, final RefSpec spec, + final Ref localRef) throws TransportException { + final String name = localRef.getName(); + try { + final TrackingRefUpdate u = new TrackingRefUpdate(db, name, spec + .getSource(), true, ObjectId.zeroId(), "deleted"); + result.add(u); + if (transport.isDryRun()){ + return; + } + u.delete(walk); + switch (u.getResult()) { + case NEW: + case NO_CHANGE: + case FAST_FORWARD: + case FORCED: + break; + default: + throw new TransportException(transport.getURI(), + "Cannot delete stale tracking ref " + name + ": " + + u.getResult().name()); + } + } catch (IOException e) { + throw new TransportException(transport.getURI(), + "Cannot delete stale tracking ref " + name, e); + } + } + private static boolean isTag(final Ref r) { return isTag(r.getName()); } diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java b/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java index e58b72a..3aec5ca 100644 --- a/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java +++ b/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java @@ -355,6 +355,9 @@ private static String findTrackingRefName(final String remoteName, /** Should an incoming (fetch) transfer validate objects? */ private boolean checkFetchedObjects; + /** Should refs no longer on the source be pruned from the destination? */ + private boolean removeDeletedRefs; + /** * Create a new transport instance. * @@ -516,6 +519,30 @@ public void setPushThin(final boolean pushThin) { } /** + * @return true if destination refs should be removed if they no longer + * exist at the source repository. + */ + public boolean isRemoveDeletedRefs() { + return removeDeletedRefs; + } + + /** + * Set whether or not to remove refs which no longer exist in the source. + * <p> + * If true, refs at the destination repository (local for fetch, remote for + * push) are deleted if they no longer exist on the source side (remote for + * fetch, local for push). + * <p> + * False by default, as this may cause data to become unreachable, and + * eventually be deleted on the next GC. + * + * @param remove true to remove refs that no longer exist. + */ + public void setRemoveDeletedRefs(final boolean remove) { + removeDeletedRefs = remove; + } + + /** * Apply provided remote configuration on this transport. * * @param cfg -- 1.6.2.rc0.226.gf08f -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html