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.

InMemoryRepository.java 12KB

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