/* Copyright (c) 2012-2014 Boundless and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Distribution License v1.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/org/documents/edl-v10.html * * Contributors: * Gabriel Roldan (Boundless) - initial implementation */ package org.locationtech.geogig.api.plumbing; import java.util.Collection; import java.util.Map; import java.util.Set; import org.locationtech.geogig.api.AbstractGeoGigOp; import org.locationtech.geogig.api.ObjectId; import org.locationtech.geogig.api.Ref; import org.locationtech.geogig.api.SymRef; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableSet; /** * Resolve a ref name to the stored {@link Ref reference} object */ public class RefParse extends AbstractGeoGigOp<Optional<Ref>> { private static final Set<String> STANDARD_REFS = ImmutableSet.of(Ref.HEAD, Ref.MASTER, Ref.ORIGIN, Ref.STAGE_HEAD, Ref.WORK_HEAD); private String refSpec; /** * @param name the name of the ref to parse * @return {@code this} */ public RefParse setName(String name) { this.refSpec = name; return this; } /** * Parses a geogig reference string (possibly abbreviated) and return the resolved {@link Ref} * or {@code absent} if the ref spec didn't resolve to any actual reference. * * Combinations of these operators are supported: * <ul> * <li><b>HEAD</b>, <b>MERGE_HEAD</b>, <b>FETCH_HEAD</b>, <b>STAGE_HEAD</b>, <b>WORK_HEAD</b></li> * <li><b>refs/...</b>: a complete reference name</li> * <li><b>short-name</b>: a short reference name under {@code refs/heads}, {@code refs/tags}, or * {@code refs/remotes} namespace, in that order of precedence</li> * </ul> * * @return an {@code Optional} that contains a {@link Ref reference} or * {@link Optional#absent()} if revstr can't be resolved to any {@link ObjectId} * @throws IllegalArgumentException if {@code refSpec} resolves to more than one ref on the same * namespace */ @Override protected Optional<Ref> _call() { Preconditions.checkState(refSpec != null, "name has not been set"); if (STANDARD_REFS.contains(refSpec) || refSpec.startsWith("refs/")) { return getRef(refSpec); } // is it a top level ref? if (!refSpec.startsWith("refs/")) { Optional<Ref> ref = getRef(refSpec); if (ref.isPresent()) { return ref; } } Map<String, String> allRefs = refDatabase().getAll(); class PrePostfixPredicate implements Predicate<String> { private String prefix; private String suffix; public PrePostfixPredicate(String prefix, String suffix) { this.prefix = prefix; this.suffix = suffix; } @Override public boolean apply(String refName) { boolean applies = refName.startsWith(prefix) && refName.endsWith(suffix); return applies; } } Collection<String> heads = Collections2.filter(allRefs.keySet(), new PrePostfixPredicate( "refs/heads", "/" + refSpec)); Collection<String> tags = Collections2.filter(allRefs.keySet(), new PrePostfixPredicate( "refs/tags", "/" + refSpec)); Collection<String> remotes = Collections2.filter(allRefs.keySet(), new PrePostfixPredicate( "refs/remotes", "/" + refSpec)); String refName; refName = resolveSingle(heads); if (refName == null) { refName = resolveSingle(tags); } if (refName == null) { refName = resolveSingle(remotes); } if (refName == null) { return Optional.absent(); } return getRef(refName); } private String resolveSingle(Collection<String> refNames) { if (refNames.isEmpty()) { return null; } if (refNames.size() == 1) { return refNames.iterator().next(); } throw new IllegalArgumentException(refSpec + " resolves to more than one ref: " + refNames); } private Optional<Ref> getRef(final String name) { String storedValue; boolean sym = false; try { storedValue = refDatabase().getRef(name); } catch (IllegalArgumentException notARef) { storedValue = refDatabase().getSymRef(name); if (null == storedValue) { return Optional.absent(); } sym = true; } if (null == storedValue) { storedValue = refDatabase().getSymRef(name); if (null == storedValue) { return Optional.absent(); } sym = true; } if (sym) { Optional<Ref> target = getRef(storedValue); if (!target.isPresent()) { throw new RuntimeException("target '" + storedValue + "' was not present for symref " + name + " -> '" + storedValue + "'"); } Ref resolved = new SymRef(name, target.get()); return Optional.of(resolved); } ObjectId objectId = ObjectId.valueOf(storedValue); return Optional.of(new Ref(name, objectId)); } }