123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387 |
- /*
- * Copyright (C) 2016, Google Inc.
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- * names of its contributors may be used to endorse or promote
- * products derived from this software without specific prior
- * written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
- package org.eclipse.jgit.internal.storage.reftree;
-
- import static org.eclipse.jgit.lib.Constants.HEAD;
- import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
- import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
-
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
-
- import org.eclipse.jgit.annotations.Nullable;
- import org.eclipse.jgit.lib.BatchRefUpdate;
- import org.eclipse.jgit.lib.Config;
- import org.eclipse.jgit.lib.ObjectId;
- import org.eclipse.jgit.lib.ObjectIdRef;
- import org.eclipse.jgit.lib.Ref;
- import org.eclipse.jgit.lib.Ref.Storage;
- import org.eclipse.jgit.lib.RefDatabase;
- import org.eclipse.jgit.lib.RefRename;
- import org.eclipse.jgit.lib.RefUpdate;
- import org.eclipse.jgit.lib.Repository;
- import org.eclipse.jgit.lib.SymbolicRef;
- import org.eclipse.jgit.revwalk.RevObject;
- import org.eclipse.jgit.revwalk.RevTag;
- import org.eclipse.jgit.revwalk.RevWalk;
- import org.eclipse.jgit.util.RefList;
- import org.eclipse.jgit.util.RefMap;
-
- /**
- * Reference database backed by a
- * {@link org.eclipse.jgit.internal.storage.reftree.RefTree}.
- * <p>
- * The storage for RefTreeDatabase has two parts. The main part is a native Git
- * tree object stored under the {@code refs/txn} namespace. To avoid cycles,
- * references to {@code refs/txn} are not stored in that tree object, but
- * instead in a "bootstrap" layer, which is a separate
- * {@link org.eclipse.jgit.lib.RefDatabase} such as
- * {@link org.eclipse.jgit.internal.storage.file.RefDirectory} using local
- * reference files inside of {@code $GIT_DIR/refs}.
- */
- public class RefTreeDatabase extends RefDatabase {
- private final Repository repo;
- private final RefDatabase bootstrap;
- private final String txnCommitted;
-
- @Nullable
- private final String txnNamespace;
- private volatile Scanner.Result refs;
-
- /**
- * Create a RefTreeDb for a repository.
- *
- * @param repo
- * the repository using references in this database.
- * @param bootstrap
- * bootstrap reference database storing the references that
- * anchor the
- * {@link org.eclipse.jgit.internal.storage.reftree.RefTree}.
- */
- public RefTreeDatabase(Repository repo, RefDatabase bootstrap) {
- Config cfg = repo.getConfig();
- String committed = cfg.getString("reftree", null, "committedRef"); //$NON-NLS-1$ //$NON-NLS-2$
- if (committed == null || committed.isEmpty()) {
- committed = "refs/txn/committed"; //$NON-NLS-1$
- }
-
- this.repo = repo;
- this.bootstrap = bootstrap;
- this.txnNamespace = initNamespace(committed);
- this.txnCommitted = committed;
- }
-
- /**
- * Create a RefTreeDb for a repository.
- *
- * @param repo
- * the repository using references in this database.
- * @param bootstrap
- * bootstrap reference database storing the references that
- * anchor the
- * {@link org.eclipse.jgit.internal.storage.reftree.RefTree}.
- * @param txnCommitted
- * name of the bootstrap reference holding the committed RefTree.
- */
- public RefTreeDatabase(Repository repo, RefDatabase bootstrap,
- String txnCommitted) {
- this.repo = repo;
- this.bootstrap = bootstrap;
- this.txnNamespace = initNamespace(txnCommitted);
- this.txnCommitted = txnCommitted;
- }
-
- private static String initNamespace(String committed) {
- int s = committed.lastIndexOf('/');
- if (s < 0) {
- return null;
- }
- return committed.substring(0, s + 1); // Keep trailing '/'.
- }
-
- Repository getRepository() {
- return repo;
- }
-
- /**
- * Get the bootstrap reference database
- *
- * @return the bootstrap reference database, which must be used to access
- * {@link #getTxnCommitted()}, {@link #getTxnNamespace()}.
- */
- public RefDatabase getBootstrap() {
- return bootstrap;
- }
-
- /**
- * Get name of bootstrap reference anchoring committed RefTree.
- *
- * @return name of bootstrap reference anchoring committed RefTree.
- */
- public String getTxnCommitted() {
- return txnCommitted;
- }
-
- /**
- * Get namespace used by bootstrap layer.
- *
- * @return namespace used by bootstrap layer, e.g. {@code refs/txn/}. Always
- * ends in {@code '/'}.
- */
- @Nullable
- public String getTxnNamespace() {
- return txnNamespace;
- }
-
- /** {@inheritDoc} */
- @Override
- public void create() throws IOException {
- bootstrap.create();
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean performsAtomicTransactions() {
- return true;
- }
-
- /** {@inheritDoc} */
- @Override
- public void refresh() {
- bootstrap.refresh();
- }
-
- /** {@inheritDoc} */
- @Override
- public void close() {
- refs = null;
- bootstrap.close();
- }
-
- /** {@inheritDoc} */
- @Override
- public Ref exactRef(String name) throws IOException {
- if (!repo.isBare() && name.indexOf('/') < 0 && !HEAD.equals(name)) {
- // Pass through names like MERGE_HEAD, ORIG_HEAD, FETCH_HEAD.
- return bootstrap.exactRef(name);
- } else if (conflictsWithBootstrap(name)) {
- return null;
- }
-
- boolean partial = false;
- Ref src = bootstrap.exactRef(txnCommitted);
- Scanner.Result c = refs;
- if (c == null || !c.refTreeId.equals(idOf(src))) {
- c = Scanner.scanRefTree(repo, src, prefixOf(name), false);
- partial = true;
- }
-
- Ref r = c.all.get(name);
- if (r != null && r.isSymbolic()) {
- r = c.sym.get(name);
- if (partial && r.getObjectId() == null) {
- // Attempting exactRef("HEAD") with partial scan will leave
- // an unresolved symref as its target e.g. refs/heads/master
- // was not read by the partial scan. Scan everything instead.
- return getRefs(ALL).get(name);
- }
- }
- return r;
- }
-
- private static String prefixOf(String name) {
- int s = name.lastIndexOf('/');
- if (s >= 0) {
- return name.substring(0, s);
- }
- return ""; //$NON-NLS-1$
- }
-
- /** {@inheritDoc} */
- @Override
- public Map<String, Ref> getRefs(String prefix) throws IOException {
- if (!prefix.isEmpty() && prefix.charAt(prefix.length() - 1) != '/') {
- return new HashMap<>(0);
- }
-
- Ref src = bootstrap.exactRef(txnCommitted);
- Scanner.Result c = refs;
- if (c == null || !c.refTreeId.equals(idOf(src))) {
- c = Scanner.scanRefTree(repo, src, prefix, true);
- if (prefix.isEmpty()) {
- refs = c;
- }
- }
- return new RefMap(prefix, RefList.<Ref> emptyList(), c.all, c.sym);
- }
-
- private static ObjectId idOf(@Nullable Ref src) {
- return src != null && src.getObjectId() != null
- ? src.getObjectId()
- : ObjectId.zeroId();
- }
-
- /** {@inheritDoc} */
- @Override
- public List<Ref> getAdditionalRefs() throws IOException {
- Collection<Ref> txnRefs;
- if (txnNamespace != null) {
- txnRefs = bootstrap.getRefsByPrefix(txnNamespace);
- } else {
- Ref r = bootstrap.exactRef(txnCommitted);
- if (r != null && r.getObjectId() != null) {
- txnRefs = Collections.singleton(r);
- } else {
- txnRefs = Collections.emptyList();
- }
- }
-
- List<Ref> otherRefs = bootstrap.getAdditionalRefs();
- List<Ref> all = new ArrayList<>(txnRefs.size() + otherRefs.size());
- all.addAll(txnRefs);
- all.addAll(otherRefs);
- return all;
- }
-
- /** {@inheritDoc} */
- @Override
- public Ref peel(Ref ref) throws IOException {
- Ref i = ref.getLeaf();
- ObjectId id = i.getObjectId();
- if (i.isPeeled() || id == null) {
- return ref;
- }
- try (RevWalk rw = new RevWalk(repo)) {
- RevObject obj = rw.parseAny(id);
- if (obj instanceof RevTag) {
- ObjectId p = rw.peel(obj).copy();
- i = new ObjectIdRef.PeeledTag(PACKED, i.getName(), id, p);
- } else {
- i = new ObjectIdRef.PeeledNonTag(PACKED, i.getName(), id);
- }
- }
- return recreate(ref, i);
- }
-
- private static Ref recreate(Ref old, Ref leaf) {
- if (old.isSymbolic()) {
- Ref dst = recreate(old.getTarget(), leaf);
- return new SymbolicRef(old.getName(), dst);
- }
- return leaf;
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean isNameConflicting(String name) throws IOException {
- return conflictsWithBootstrap(name)
- || !getConflictingNames(name).isEmpty();
- }
-
- /** {@inheritDoc} */
- @Override
- public BatchRefUpdate newBatchUpdate() {
- return new RefTreeBatch(this);
- }
-
- /** {@inheritDoc} */
- @Override
- public RefUpdate newUpdate(String name, boolean detach) throws IOException {
- if (!repo.isBare() && name.indexOf('/') < 0 && !HEAD.equals(name)) {
- return bootstrap.newUpdate(name, detach);
- }
- if (conflictsWithBootstrap(name)) {
- return new AlwaysFailUpdate(this, name);
- }
-
- Ref r = exactRef(name);
- if (r == null) {
- r = new ObjectIdRef.Unpeeled(Storage.NEW, name, null);
- }
-
- boolean detaching = detach && r.isSymbolic();
- if (detaching) {
- r = new ObjectIdRef.Unpeeled(LOOSE, name, r.getObjectId());
- }
-
- RefTreeUpdate u = new RefTreeUpdate(this, r);
- if (detaching) {
- u.setDetachingSymbolicRef();
- }
- return u;
- }
-
- /** {@inheritDoc} */
- @Override
- public RefRename newRename(String fromName, String toName)
- throws IOException {
- RefUpdate from = newUpdate(fromName, true);
- RefUpdate to = newUpdate(toName, true);
- return new RefTreeRename(this, from, to);
- }
-
- boolean conflictsWithBootstrap(String name) {
- if (txnNamespace != null && name.startsWith(txnNamespace)) {
- return true;
- } else if (txnCommitted.equals(name)) {
- return true;
- }
-
- if (name.indexOf('/') < 0 && !HEAD.equals(name)) {
- return true;
- }
-
- if (name.length() > txnCommitted.length()
- && name.charAt(txnCommitted.length()) == '/'
- && name.startsWith(txnCommitted)) {
- return true;
- }
- return false;
- }
- }
|