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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  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.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 DfsRefDatabase 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 DfsRefDatabase 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(walk, 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(RevWalk walk, 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 (!ObjectId.zeroId().equals(c.getNewId())) {
  268. try {
  269. RevObject o = rw.parseAny(c.getNewId());
  270. if (o instanceof RevTag) {
  271. peeled.put(o, rw.peel(o).copy());
  272. }
  273. } catch (IOException e) {
  274. c.setResult(ReceiveCommand.Result.REJECTED_MISSING_OBJECT);
  275. reject(cmds);
  276. return;
  277. }
  278. }
  279. }
  280. }
  281. // Check all references conform to expected old value.
  282. for (ReceiveCommand c : cmds) {
  283. Ref r = refs.get(c.getRefName());
  284. if (r == null) {
  285. if (c.getType() != ReceiveCommand.Type.CREATE) {
  286. c.setResult(ReceiveCommand.Result.LOCK_FAILURE);
  287. reject(cmds);
  288. return;
  289. }
  290. } else if (r.isSymbolic() || r.getObjectId() == null
  291. || !r.getObjectId().equals(c.getOldId())) {
  292. c.setResult(ReceiveCommand.Result.LOCK_FAILURE);
  293. reject(cmds);
  294. return;
  295. }
  296. }
  297. // Write references.
  298. for (ReceiveCommand c : cmds) {
  299. if (c.getType() == ReceiveCommand.Type.DELETE) {
  300. refs.remove(c.getRefName());
  301. c.setResult(ReceiveCommand.Result.OK);
  302. continue;
  303. }
  304. ObjectId p = peeled.get(c.getNewId());
  305. Ref r;
  306. if (p != null) {
  307. r = new ObjectIdRef.PeeledTag(Storage.PACKED,
  308. c.getRefName(), c.getNewId(), p);
  309. } else {
  310. r = new ObjectIdRef.PeeledNonTag(Storage.PACKED,
  311. c.getRefName(), c.getNewId());
  312. }
  313. refs.put(r.getName(), r);
  314. c.setResult(ReceiveCommand.Result.OK);
  315. }
  316. clearCache();
  317. }
  318. private void reject(List<ReceiveCommand> cmds) {
  319. for (ReceiveCommand c : cmds) {
  320. if (c.getResult() == ReceiveCommand.Result.NOT_ATTEMPTED) {
  321. c.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON,
  322. JGitText.get().transactionAborted);
  323. }
  324. }
  325. }
  326. @Override
  327. protected boolean compareAndPut(Ref oldRef, Ref newRef)
  328. throws IOException {
  329. try {
  330. lock.writeLock().lock();
  331. ObjectId id = newRef.getObjectId();
  332. if (id != null) {
  333. try (RevWalk rw = new RevWalk(getRepository())) {
  334. // Validate that the target exists in a new RevWalk, as the RevWalk
  335. // from the RefUpdate might be reading back unflushed objects.
  336. rw.parseAny(id);
  337. }
  338. }
  339. String name = newRef.getName();
  340. if (oldRef == null)
  341. return refs.putIfAbsent(name, newRef) == null;
  342. Ref cur = refs.get(name);
  343. Ref toCompare = cur;
  344. if (toCompare != null) {
  345. if (toCompare.isSymbolic()) {
  346. // Arm's-length dereference symrefs before the compare, since
  347. // DfsRefUpdate#doLink(String) stores them undereferenced.
  348. Ref leaf = toCompare.getLeaf();
  349. if (leaf.getObjectId() == null) {
  350. leaf = refs.get(leaf.getName());
  351. if (leaf.isSymbolic())
  352. // Not supported at the moment.
  353. throw new IllegalArgumentException();
  354. toCompare = new SymbolicRef(
  355. name,
  356. new ObjectIdRef.Unpeeled(
  357. Storage.NEW,
  358. leaf.getName(),
  359. leaf.getObjectId()));
  360. } else
  361. toCompare = toCompare.getLeaf();
  362. }
  363. if (eq(toCompare, oldRef))
  364. return refs.replace(name, cur, newRef);
  365. }
  366. if (oldRef.getStorage() == Storage.NEW)
  367. return refs.putIfAbsent(name, newRef) == null;
  368. return false;
  369. } finally {
  370. lock.writeLock().unlock();
  371. }
  372. }
  373. @Override
  374. protected boolean compareAndRemove(Ref oldRef) throws IOException {
  375. try {
  376. lock.writeLock().lock();
  377. String name = oldRef.getName();
  378. Ref cur = refs.get(name);
  379. if (cur != null && eq(cur, oldRef))
  380. return refs.remove(name, cur);
  381. else
  382. return false;
  383. } finally {
  384. lock.writeLock().unlock();
  385. }
  386. }
  387. private boolean eq(Ref a, Ref b) {
  388. if (!Objects.equals(a.getName(), b.getName()))
  389. return false;
  390. // Compare leaf object IDs, since the oldRef passed into compareAndPut
  391. // when detaching a symref is an ObjectIdRef.
  392. return Objects.equals(a.getLeaf().getObjectId(),
  393. b.getLeaf().getObjectId());
  394. }
  395. }
  396. }