You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RefTreeDatabase.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. /*
  2. * Copyright (C) 2016, Google Inc.
  3. * and other copyright owners as documented in the project's IP log.
  4. *
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Distribution License v1.0 which
  7. * accompanies this distribution, is reproduced below, and is
  8. * available at http://www.eclipse.org/org/documents/edl-v10.php
  9. *
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or
  13. * without modification, are permitted provided that the following
  14. * conditions are met:
  15. *
  16. * - Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. *
  19. * - Redistributions in binary form must reproduce the above
  20. * copyright notice, this list of conditions and the following
  21. * disclaimer in the documentation and/or other materials provided
  22. * with the distribution.
  23. *
  24. * - Neither the name of the Eclipse Foundation, Inc. nor the
  25. * names of its contributors may be used to endorse or promote
  26. * products derived from this software without specific prior
  27. * written permission.
  28. *
  29. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42. */
  43. package org.eclipse.jgit.internal.storage.reftree;
  44. import static org.eclipse.jgit.lib.Constants.HEAD;
  45. import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
  46. import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
  47. import java.io.IOException;
  48. import java.util.ArrayList;
  49. import java.util.Collection;
  50. import java.util.Collections;
  51. import java.util.HashMap;
  52. import java.util.List;
  53. import java.util.Map;
  54. import org.eclipse.jgit.annotations.Nullable;
  55. import org.eclipse.jgit.lib.BatchRefUpdate;
  56. import org.eclipse.jgit.lib.Config;
  57. import org.eclipse.jgit.lib.ObjectId;
  58. import org.eclipse.jgit.lib.ObjectIdRef;
  59. import org.eclipse.jgit.lib.Ref;
  60. import org.eclipse.jgit.lib.Ref.Storage;
  61. import org.eclipse.jgit.lib.RefDatabase;
  62. import org.eclipse.jgit.lib.RefRename;
  63. import org.eclipse.jgit.lib.RefUpdate;
  64. import org.eclipse.jgit.lib.Repository;
  65. import org.eclipse.jgit.lib.SymbolicRef;
  66. import org.eclipse.jgit.revwalk.RevObject;
  67. import org.eclipse.jgit.revwalk.RevTag;
  68. import org.eclipse.jgit.revwalk.RevWalk;
  69. import org.eclipse.jgit.util.RefList;
  70. import org.eclipse.jgit.util.RefMap;
  71. /**
  72. * Reference database backed by a
  73. * {@link org.eclipse.jgit.internal.storage.reftree.RefTree}.
  74. * <p>
  75. * The storage for RefTreeDatabase has two parts. The main part is a native Git
  76. * tree object stored under the {@code refs/txn} namespace. To avoid cycles,
  77. * references to {@code refs/txn} are not stored in that tree object, but
  78. * instead in a "bootstrap" layer, which is a separate
  79. * {@link org.eclipse.jgit.lib.RefDatabase} such as
  80. * {@link org.eclipse.jgit.internal.storage.file.RefDirectory} using local
  81. * reference files inside of {@code $GIT_DIR/refs}.
  82. */
  83. public class RefTreeDatabase extends RefDatabase {
  84. private final Repository repo;
  85. private final RefDatabase bootstrap;
  86. private final String txnCommitted;
  87. @Nullable
  88. private final String txnNamespace;
  89. private volatile Scanner.Result refs;
  90. /**
  91. * Create a RefTreeDb for a repository.
  92. *
  93. * @param repo
  94. * the repository using references in this database.
  95. * @param bootstrap
  96. * bootstrap reference database storing the references that
  97. * anchor the
  98. * {@link org.eclipse.jgit.internal.storage.reftree.RefTree}.
  99. */
  100. public RefTreeDatabase(Repository repo, RefDatabase bootstrap) {
  101. Config cfg = repo.getConfig();
  102. String committed = cfg.getString("reftree", null, "committedRef"); //$NON-NLS-1$ //$NON-NLS-2$
  103. if (committed == null || committed.isEmpty()) {
  104. committed = "refs/txn/committed"; //$NON-NLS-1$
  105. }
  106. this.repo = repo;
  107. this.bootstrap = bootstrap;
  108. this.txnNamespace = initNamespace(committed);
  109. this.txnCommitted = committed;
  110. }
  111. /**
  112. * Create a RefTreeDb for a repository.
  113. *
  114. * @param repo
  115. * the repository using references in this database.
  116. * @param bootstrap
  117. * bootstrap reference database storing the references that
  118. * anchor the
  119. * {@link org.eclipse.jgit.internal.storage.reftree.RefTree}.
  120. * @param txnCommitted
  121. * name of the bootstrap reference holding the committed RefTree.
  122. */
  123. public RefTreeDatabase(Repository repo, RefDatabase bootstrap,
  124. String txnCommitted) {
  125. this.repo = repo;
  126. this.bootstrap = bootstrap;
  127. this.txnNamespace = initNamespace(txnCommitted);
  128. this.txnCommitted = txnCommitted;
  129. }
  130. private static String initNamespace(String committed) {
  131. int s = committed.lastIndexOf('/');
  132. if (s < 0) {
  133. return null;
  134. }
  135. return committed.substring(0, s + 1); // Keep trailing '/'.
  136. }
  137. Repository getRepository() {
  138. return repo;
  139. }
  140. /**
  141. * Get the bootstrap reference database
  142. *
  143. * @return the bootstrap reference database, which must be used to access
  144. * {@link #getTxnCommitted()}, {@link #getTxnNamespace()}.
  145. */
  146. public RefDatabase getBootstrap() {
  147. return bootstrap;
  148. }
  149. /**
  150. * Get name of bootstrap reference anchoring committed RefTree.
  151. *
  152. * @return name of bootstrap reference anchoring committed RefTree.
  153. */
  154. public String getTxnCommitted() {
  155. return txnCommitted;
  156. }
  157. /**
  158. * Get namespace used by bootstrap layer.
  159. *
  160. * @return namespace used by bootstrap layer, e.g. {@code refs/txn/}. Always
  161. * ends in {@code '/'}.
  162. */
  163. @Nullable
  164. public String getTxnNamespace() {
  165. return txnNamespace;
  166. }
  167. /** {@inheritDoc} */
  168. @Override
  169. public void create() throws IOException {
  170. bootstrap.create();
  171. }
  172. /** {@inheritDoc} */
  173. @Override
  174. public boolean performsAtomicTransactions() {
  175. return true;
  176. }
  177. /** {@inheritDoc} */
  178. @Override
  179. public void refresh() {
  180. bootstrap.refresh();
  181. }
  182. /** {@inheritDoc} */
  183. @Override
  184. public void close() {
  185. refs = null;
  186. bootstrap.close();
  187. }
  188. /** {@inheritDoc} */
  189. @Override
  190. public Ref exactRef(String name) throws IOException {
  191. if (!repo.isBare() && name.indexOf('/') < 0 && !HEAD.equals(name)) {
  192. // Pass through names like MERGE_HEAD, ORIG_HEAD, FETCH_HEAD.
  193. return bootstrap.exactRef(name);
  194. } else if (conflictsWithBootstrap(name)) {
  195. return null;
  196. }
  197. boolean partial = false;
  198. Ref src = bootstrap.exactRef(txnCommitted);
  199. Scanner.Result c = refs;
  200. if (c == null || !c.refTreeId.equals(idOf(src))) {
  201. c = Scanner.scanRefTree(repo, src, prefixOf(name), false);
  202. partial = true;
  203. }
  204. Ref r = c.all.get(name);
  205. if (r != null && r.isSymbolic()) {
  206. r = c.sym.get(name);
  207. if (partial && r.getObjectId() == null) {
  208. // Attempting exactRef("HEAD") with partial scan will leave
  209. // an unresolved symref as its target e.g. refs/heads/master
  210. // was not read by the partial scan. Scan everything instead.
  211. return getRefs(ALL).get(name);
  212. }
  213. }
  214. return r;
  215. }
  216. private static String prefixOf(String name) {
  217. int s = name.lastIndexOf('/');
  218. if (s >= 0) {
  219. return name.substring(0, s);
  220. }
  221. return ""; //$NON-NLS-1$
  222. }
  223. /** {@inheritDoc} */
  224. @Override
  225. public Map<String, Ref> getRefs(String prefix) throws IOException {
  226. if (!prefix.isEmpty() && prefix.charAt(prefix.length() - 1) != '/') {
  227. return new HashMap<>(0);
  228. }
  229. Ref src = bootstrap.exactRef(txnCommitted);
  230. Scanner.Result c = refs;
  231. if (c == null || !c.refTreeId.equals(idOf(src))) {
  232. c = Scanner.scanRefTree(repo, src, prefix, true);
  233. if (prefix.isEmpty()) {
  234. refs = c;
  235. }
  236. }
  237. return new RefMap(prefix, RefList.<Ref> emptyList(), c.all, c.sym);
  238. }
  239. private static ObjectId idOf(@Nullable Ref src) {
  240. return src != null && src.getObjectId() != null
  241. ? src.getObjectId()
  242. : ObjectId.zeroId();
  243. }
  244. /** {@inheritDoc} */
  245. @Override
  246. public List<Ref> getAdditionalRefs() throws IOException {
  247. Collection<Ref> txnRefs;
  248. if (txnNamespace != null) {
  249. txnRefs = bootstrap.getRefsByPrefix(txnNamespace);
  250. } else {
  251. Ref r = bootstrap.exactRef(txnCommitted);
  252. if (r != null && r.getObjectId() != null) {
  253. txnRefs = Collections.singleton(r);
  254. } else {
  255. txnRefs = Collections.emptyList();
  256. }
  257. }
  258. List<Ref> otherRefs = bootstrap.getAdditionalRefs();
  259. List<Ref> all = new ArrayList<>(txnRefs.size() + otherRefs.size());
  260. all.addAll(txnRefs);
  261. all.addAll(otherRefs);
  262. return all;
  263. }
  264. /** {@inheritDoc} */
  265. @Override
  266. public Ref peel(Ref ref) throws IOException {
  267. Ref i = ref.getLeaf();
  268. ObjectId id = i.getObjectId();
  269. if (i.isPeeled() || id == null) {
  270. return ref;
  271. }
  272. try (RevWalk rw = new RevWalk(repo)) {
  273. RevObject obj = rw.parseAny(id);
  274. if (obj instanceof RevTag) {
  275. ObjectId p = rw.peel(obj).copy();
  276. i = new ObjectIdRef.PeeledTag(PACKED, i.getName(), id, p);
  277. } else {
  278. i = new ObjectIdRef.PeeledNonTag(PACKED, i.getName(), id);
  279. }
  280. }
  281. return recreate(ref, i);
  282. }
  283. private static Ref recreate(Ref old, Ref leaf) {
  284. if (old.isSymbolic()) {
  285. Ref dst = recreate(old.getTarget(), leaf);
  286. return new SymbolicRef(old.getName(), dst);
  287. }
  288. return leaf;
  289. }
  290. /** {@inheritDoc} */
  291. @Override
  292. public boolean isNameConflicting(String name) throws IOException {
  293. return conflictsWithBootstrap(name)
  294. || !getConflictingNames(name).isEmpty();
  295. }
  296. /** {@inheritDoc} */
  297. @Override
  298. public BatchRefUpdate newBatchUpdate() {
  299. return new RefTreeBatch(this);
  300. }
  301. /** {@inheritDoc} */
  302. @Override
  303. public RefUpdate newUpdate(String name, boolean detach) throws IOException {
  304. if (!repo.isBare() && name.indexOf('/') < 0 && !HEAD.equals(name)) {
  305. return bootstrap.newUpdate(name, detach);
  306. }
  307. if (conflictsWithBootstrap(name)) {
  308. return new AlwaysFailUpdate(this, name);
  309. }
  310. Ref r = exactRef(name);
  311. if (r == null) {
  312. r = new ObjectIdRef.Unpeeled(Storage.NEW, name, null);
  313. }
  314. boolean detaching = detach && r.isSymbolic();
  315. if (detaching) {
  316. r = new ObjectIdRef.Unpeeled(LOOSE, name, r.getObjectId());
  317. }
  318. RefTreeUpdate u = new RefTreeUpdate(this, r);
  319. if (detaching) {
  320. u.setDetachingSymbolicRef();
  321. }
  322. return u;
  323. }
  324. /** {@inheritDoc} */
  325. @Override
  326. public RefRename newRename(String fromName, String toName)
  327. throws IOException {
  328. RefUpdate from = newUpdate(fromName, true);
  329. RefUpdate to = newUpdate(toName, true);
  330. return new RefTreeRename(this, from, to);
  331. }
  332. boolean conflictsWithBootstrap(String name) {
  333. if (txnNamespace != null && name.startsWith(txnNamespace)) {
  334. return true;
  335. } else if (txnCommitted.equals(name)) {
  336. return true;
  337. }
  338. if (name.indexOf('/') < 0 && !HEAD.equals(name)) {
  339. return true;
  340. }
  341. if (name.length() > txnCommitted.length()
  342. && name.charAt(txnCommitted.length()) == '/'
  343. && name.startsWith(txnCommitted)) {
  344. return true;
  345. }
  346. return false;
  347. }
  348. }