Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

InMemoryRepository.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. package org.eclipse.jgit.internal.storage.dfs;
  2. import java.io.ByteArrayOutputStream;
  3. import java.io.FileNotFoundException;
  4. import java.io.IOException;
  5. import java.nio.ByteBuffer;
  6. import java.util.ArrayList;
  7. import java.util.Collection;
  8. import java.util.HashMap;
  9. import java.util.List;
  10. import java.util.Map;
  11. import java.util.Objects;
  12. import java.util.concurrent.ConcurrentHashMap;
  13. import java.util.concurrent.ConcurrentMap;
  14. import java.util.concurrent.atomic.AtomicInteger;
  15. import java.util.concurrent.locks.ReadWriteLock;
  16. import java.util.concurrent.locks.ReentrantReadWriteLock;
  17. import org.eclipse.jgit.internal.JGitText;
  18. import org.eclipse.jgit.internal.storage.pack.PackExt;
  19. import org.eclipse.jgit.lib.BatchRefUpdate;
  20. import org.eclipse.jgit.lib.ObjectId;
  21. import org.eclipse.jgit.lib.ObjectIdRef;
  22. import org.eclipse.jgit.lib.ProgressMonitor;
  23. import org.eclipse.jgit.lib.Ref;
  24. import org.eclipse.jgit.lib.Ref.Storage;
  25. import org.eclipse.jgit.lib.RefDatabase;
  26. import org.eclipse.jgit.lib.SymbolicRef;
  27. import org.eclipse.jgit.revwalk.RevObject;
  28. import org.eclipse.jgit.revwalk.RevTag;
  29. import org.eclipse.jgit.revwalk.RevWalk;
  30. import org.eclipse.jgit.transport.ReceiveCommand;
  31. import org.eclipse.jgit.util.RefList;
  32. /**
  33. * Git repository stored entirely in the local process memory.
  34. * <p>
  35. * This implementation builds on the DFS repository by storing all reference and
  36. * object data in the local process. It is not very efficient and exists only
  37. * for unit testing and small experiments.
  38. * <p>
  39. * The repository is thread-safe. Memory used is released only when this object
  40. * is garbage collected. Closing the repository has no impact on its memory.
  41. */
  42. public class InMemoryRepository extends DfsRepository {
  43. /** Builder for in-memory repositories. */
  44. public static class Builder
  45. extends DfsRepositoryBuilder<Builder, InMemoryRepository> {
  46. @Override
  47. public InMemoryRepository build() throws IOException {
  48. return new InMemoryRepository(this);
  49. }
  50. }
  51. static final AtomicInteger packId = new AtomicInteger();
  52. private final DfsObjDatabase objdb;
  53. private final RefDatabase refdb;
  54. private boolean performsAtomicTransactions = true;
  55. /**
  56. * Initialize a new in-memory repository.
  57. *
  58. * @param repoDesc
  59. * description of the repository.
  60. * @since 2.0
  61. */
  62. public InMemoryRepository(DfsRepositoryDescription repoDesc) {
  63. this(new Builder().setRepositoryDescription(repoDesc));
  64. }
  65. InMemoryRepository(Builder builder) {
  66. super(builder);
  67. objdb = new MemObjDatabase(this);
  68. refdb = new MemRefDatabase();
  69. }
  70. @Override
  71. public DfsObjDatabase getObjectDatabase() {
  72. return objdb;
  73. }
  74. @Override
  75. public RefDatabase getRefDatabase() {
  76. return refdb;
  77. }
  78. /**
  79. * Enable (or disable) the atomic reference transaction support.
  80. * <p>
  81. * Useful for testing atomic support enabled or disabled.
  82. *
  83. * @param atomic
  84. */
  85. public void setPerformsAtomicTransactions(boolean atomic) {
  86. performsAtomicTransactions = atomic;
  87. }
  88. private class MemObjDatabase extends DfsObjDatabase {
  89. private List<DfsPackDescription> packs = new ArrayList<DfsPackDescription>();
  90. MemObjDatabase(DfsRepository repo) {
  91. super(repo, new DfsReaderOptions());
  92. }
  93. @Override
  94. protected synchronized List<DfsPackDescription> listPacks() {
  95. return packs;
  96. }
  97. @Override
  98. protected DfsPackDescription newPack(PackSource source) {
  99. int id = packId.incrementAndGet();
  100. DfsPackDescription desc = new MemPack(
  101. "pack-" + id + "-" + source.name(), //$NON-NLS-1$ //$NON-NLS-2$
  102. getRepository().getDescription());
  103. return desc.setPackSource(source);
  104. }
  105. @Override
  106. protected synchronized void commitPackImpl(
  107. Collection<DfsPackDescription> desc,
  108. Collection<DfsPackDescription> replace) {
  109. List<DfsPackDescription> n;
  110. n = new ArrayList<DfsPackDescription>(desc.size() + packs.size());
  111. n.addAll(desc);
  112. n.addAll(packs);
  113. if (replace != null)
  114. n.removeAll(replace);
  115. packs = n;
  116. }
  117. @Override
  118. protected void rollbackPack(Collection<DfsPackDescription> desc) {
  119. // Do nothing. Pack is not recorded until commitPack.
  120. }
  121. @Override
  122. protected ReadableChannel openFile(DfsPackDescription desc, PackExt ext)
  123. throws FileNotFoundException, IOException {
  124. MemPack memPack = (MemPack) desc;
  125. byte[] file = memPack.fileMap.get(ext);
  126. if (file == null)
  127. throw new FileNotFoundException(desc.getFileName(ext));
  128. return new ByteArrayReadableChannel(file);
  129. }
  130. @Override
  131. protected DfsOutputStream writeFile(
  132. DfsPackDescription desc, final PackExt ext) throws IOException {
  133. final MemPack memPack = (MemPack) desc;
  134. return new Out() {
  135. @Override
  136. public void flush() {
  137. memPack.fileMap.put(ext, getData());
  138. }
  139. };
  140. }
  141. }
  142. private static class MemPack extends DfsPackDescription {
  143. final Map<PackExt, byte[]>
  144. fileMap = new HashMap<PackExt, byte[]>();
  145. MemPack(String name, DfsRepositoryDescription repoDesc) {
  146. super(repoDesc, name);
  147. }
  148. }
  149. private abstract static class Out extends DfsOutputStream {
  150. private final ByteArrayOutputStream dst = new ByteArrayOutputStream();
  151. private byte[] data;
  152. @Override
  153. public void write(byte[] buf, int off, int len) {
  154. data = null;
  155. dst.write(buf, off, len);
  156. }
  157. @Override
  158. public int read(long position, ByteBuffer buf) {
  159. byte[] d = getData();
  160. int n = Math.min(buf.remaining(), d.length - (int) position);
  161. if (n == 0)
  162. return -1;
  163. buf.put(d, (int) position, n);
  164. return n;
  165. }
  166. byte[] getData() {
  167. if (data == null)
  168. data = dst.toByteArray();
  169. return data;
  170. }
  171. @Override
  172. public abstract void flush();
  173. @Override
  174. public void close() {
  175. flush();
  176. }
  177. }
  178. private static class ByteArrayReadableChannel implements ReadableChannel {
  179. private final byte[] data;
  180. private int position;
  181. private boolean open = true;
  182. ByteArrayReadableChannel(byte[] buf) {
  183. data = buf;
  184. }
  185. public int read(ByteBuffer dst) {
  186. int n = Math.min(dst.remaining(), data.length - position);
  187. if (n == 0)
  188. return -1;
  189. dst.put(data, position, n);
  190. position += n;
  191. return n;
  192. }
  193. public void close() {
  194. open = false;
  195. }
  196. public boolean isOpen() {
  197. return open;
  198. }
  199. public long position() {
  200. return position;
  201. }
  202. public void position(long newPosition) {
  203. position = (int) newPosition;
  204. }
  205. public long size() {
  206. return data.length;
  207. }
  208. public int blockSize() {
  209. return 0;
  210. }
  211. public void setReadAheadBytes(int b) {
  212. // Unnecessary on a byte array.
  213. }
  214. }
  215. private class MemRefDatabase extends DfsRefDatabase {
  216. private final ConcurrentMap<String, Ref> refs = new ConcurrentHashMap<String, Ref>();
  217. private final ReadWriteLock lock = new ReentrantReadWriteLock(true /* fair */);
  218. MemRefDatabase() {
  219. super(InMemoryRepository.this);
  220. }
  221. @Override
  222. public boolean performsAtomicTransactions() {
  223. return performsAtomicTransactions;
  224. }
  225. @Override
  226. public BatchRefUpdate newBatchUpdate() {
  227. return new BatchRefUpdate(this) {
  228. @Override
  229. public void execute(RevWalk walk, ProgressMonitor monitor)
  230. throws IOException {
  231. if (performsAtomicTransactions()) {
  232. try {
  233. lock.writeLock().lock();
  234. batch(walk, getCommands());
  235. } finally {
  236. lock.writeLock().unlock();
  237. }
  238. } else {
  239. super.execute(walk, monitor);
  240. }
  241. }
  242. };
  243. }
  244. @Override
  245. protected RefCache scanAllRefs() throws IOException {
  246. RefList.Builder<Ref> ids = new RefList.Builder<Ref>();
  247. RefList.Builder<Ref> sym = new RefList.Builder<Ref>();
  248. try {
  249. lock.readLock().lock();
  250. for (Ref ref : refs.values()) {
  251. if (ref.isSymbolic())
  252. sym.add(ref);
  253. ids.add(ref);
  254. }
  255. } finally {
  256. lock.readLock().unlock();
  257. }
  258. ids.sort();
  259. sym.sort();
  260. return new RefCache(ids.toRefList(), sym.toRefList());
  261. }
  262. private void batch(RevWalk walk, List<ReceiveCommand> cmds) {
  263. // Validate that the target exists in a new RevWalk, as the RevWalk
  264. // from the RefUpdate might be reading back unflushed objects.
  265. Map<ObjectId, ObjectId> peeled = new HashMap<>();
  266. try (RevWalk rw = new RevWalk(getRepository())) {
  267. for (ReceiveCommand c : cmds) {
  268. if (c.getResult() != ReceiveCommand.Result.NOT_ATTEMPTED) {
  269. reject(cmds);
  270. return;
  271. }
  272. if (!ObjectId.zeroId().equals(c.getNewId())) {
  273. try {
  274. RevObject o = rw.parseAny(c.getNewId());
  275. if (o instanceof RevTag) {
  276. peeled.put(o, rw.peel(o).copy());
  277. }
  278. } catch (IOException e) {
  279. c.setResult(ReceiveCommand.Result.REJECTED_MISSING_OBJECT);
  280. reject(cmds);
  281. return;
  282. }
  283. }
  284. }
  285. }
  286. // Check all references conform to expected old value.
  287. for (ReceiveCommand c : cmds) {
  288. Ref r = refs.get(c.getRefName());
  289. if (r == null) {
  290. if (c.getType() != ReceiveCommand.Type.CREATE) {
  291. c.setResult(ReceiveCommand.Result.LOCK_FAILURE);
  292. reject(cmds);
  293. return;
  294. }
  295. } else {
  296. ObjectId objectId = r.getObjectId();
  297. if (r.isSymbolic() || objectId == null
  298. || !objectId.equals(c.getOldId())) {
  299. c.setResult(ReceiveCommand.Result.LOCK_FAILURE);
  300. reject(cmds);
  301. return;
  302. }
  303. }
  304. }
  305. // Write references.
  306. for (ReceiveCommand c : cmds) {
  307. if (c.getType() == ReceiveCommand.Type.DELETE) {
  308. refs.remove(c.getRefName());
  309. c.setResult(ReceiveCommand.Result.OK);
  310. continue;
  311. }
  312. ObjectId p = peeled.get(c.getNewId());
  313. Ref r;
  314. if (p != null) {
  315. r = new ObjectIdRef.PeeledTag(Storage.PACKED,
  316. c.getRefName(), c.getNewId(), p);
  317. } else {
  318. r = new ObjectIdRef.PeeledNonTag(Storage.PACKED,
  319. c.getRefName(), c.getNewId());
  320. }
  321. refs.put(r.getName(), r);
  322. c.setResult(ReceiveCommand.Result.OK);
  323. }
  324. clearCache();
  325. }
  326. private void reject(List<ReceiveCommand> cmds) {
  327. for (ReceiveCommand c : cmds) {
  328. if (c.getResult() == ReceiveCommand.Result.NOT_ATTEMPTED) {
  329. c.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON,
  330. JGitText.get().transactionAborted);
  331. }
  332. }
  333. }
  334. @Override
  335. protected boolean compareAndPut(Ref oldRef, Ref newRef)
  336. throws IOException {
  337. try {
  338. lock.writeLock().lock();
  339. ObjectId id = newRef.getObjectId();
  340. if (id != null) {
  341. try (RevWalk rw = new RevWalk(getRepository())) {
  342. // Validate that the target exists in a new RevWalk, as the RevWalk
  343. // from the RefUpdate might be reading back unflushed objects.
  344. rw.parseAny(id);
  345. }
  346. }
  347. String name = newRef.getName();
  348. if (oldRef == null)
  349. return refs.putIfAbsent(name, newRef) == null;
  350. Ref cur = refs.get(name);
  351. Ref toCompare = cur;
  352. if (toCompare != null) {
  353. if (toCompare.isSymbolic()) {
  354. // Arm's-length dereference symrefs before the compare, since
  355. // DfsRefUpdate#doLink(String) stores them undereferenced.
  356. Ref leaf = toCompare.getLeaf();
  357. if (leaf.getObjectId() == null) {
  358. leaf = refs.get(leaf.getName());
  359. if (leaf.isSymbolic())
  360. // Not supported at the moment.
  361. throw new IllegalArgumentException();
  362. toCompare = new SymbolicRef(
  363. name,
  364. new ObjectIdRef.Unpeeled(
  365. Storage.NEW,
  366. leaf.getName(),
  367. leaf.getObjectId()));
  368. } else
  369. toCompare = toCompare.getLeaf();
  370. }
  371. if (eq(toCompare, oldRef))
  372. return refs.replace(name, cur, newRef);
  373. }
  374. if (oldRef.getStorage() == Storage.NEW)
  375. return refs.putIfAbsent(name, newRef) == null;
  376. return false;
  377. } finally {
  378. lock.writeLock().unlock();
  379. }
  380. }
  381. @Override
  382. protected boolean compareAndRemove(Ref oldRef) throws IOException {
  383. try {
  384. lock.writeLock().lock();
  385. String name = oldRef.getName();
  386. Ref cur = refs.get(name);
  387. if (cur != null && eq(cur, oldRef))
  388. return refs.remove(name, cur);
  389. else
  390. return false;
  391. } finally {
  392. lock.writeLock().unlock();
  393. }
  394. }
  395. private boolean eq(Ref a, Ref b) {
  396. if (!Objects.equals(a.getName(), b.getName()))
  397. return false;
  398. // Compare leaf object IDs, since the oldRef passed into compareAndPut
  399. // when detaching a symref is an ObjectIdRef.
  400. return Objects.equals(a.getLeaf().getObjectId(),
  401. b.getLeaf().getObjectId());
  402. }
  403. }
  404. }