1 package org.eclipse.jgit.internal.storage.dfs;
2
3 import java.io.ByteArrayOutputStream;
4 import java.io.FileNotFoundException;
5 import java.io.IOException;
6 import java.nio.ByteBuffer;
7 import java.util.ArrayList;
8 import java.util.Collection;
9 import java.util.HashMap;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.Objects;
13 import java.util.concurrent.ConcurrentHashMap;
14 import java.util.concurrent.ConcurrentMap;
15 import java.util.concurrent.atomic.AtomicInteger;
16 import java.util.concurrent.locks.ReadWriteLock;
17 import java.util.concurrent.locks.ReentrantReadWriteLock;
18
19 import org.eclipse.jgit.annotations.Nullable;
20 import org.eclipse.jgit.internal.storage.pack.PackExt;
21 import org.eclipse.jgit.lib.BatchRefUpdate;
22 import org.eclipse.jgit.lib.ObjectId;
23 import org.eclipse.jgit.lib.ObjectIdRef;
24 import org.eclipse.jgit.lib.ProgressMonitor;
25 import org.eclipse.jgit.lib.Ref;
26 import org.eclipse.jgit.lib.Ref.Storage;
27 import org.eclipse.jgit.lib.RefDatabase;
28 import org.eclipse.jgit.revwalk.RevObject;
29 import org.eclipse.jgit.revwalk.RevTag;
30 import org.eclipse.jgit.revwalk.RevWalk;
31 import org.eclipse.jgit.transport.ReceiveCommand;
32 import org.eclipse.jgit.util.RefList;
33
34
35
36
37
38
39
40
41
42
43
44 public class InMemoryRepository extends DfsRepository {
45
46 public static class Builder
47 extends DfsRepositoryBuilder<Builder, InMemoryRepository> {
48 @Override
49 public InMemoryRepository build() throws IOException {
50 return new InMemoryRepository(this);
51 }
52 }
53
54 static final AtomicInteger packId = new AtomicInteger();
55
56 private final DfsObjDatabase objdb;
57 private final RefDatabase refdb;
58 private String gitwebDescription;
59 private boolean performsAtomicTransactions = true;
60
61
62
63
64
65
66
67 public InMemoryRepository(DfsRepositoryDescription repoDesc) {
68 this(new Builder().setRepositoryDescription(repoDesc));
69 }
70
71 InMemoryRepository(Builder builder) {
72 super(builder);
73 objdb = new MemObjDatabase(this);
74 refdb = new MemRefDatabase();
75 }
76
77 @Override
78 public DfsObjDatabase getObjectDatabase() {
79 return objdb;
80 }
81
82 @Override
83 public RefDatabase getRefDatabase() {
84 return refdb;
85 }
86
87
88
89
90
91
92
93
94 public void setPerformsAtomicTransactions(boolean atomic) {
95 performsAtomicTransactions = atomic;
96 }
97
98 @Override
99 @Nullable
100 public String getGitwebDescription() {
101 return gitwebDescription;
102 }
103
104 @Override
105 public void setGitwebDescription(@Nullable String d) {
106 gitwebDescription = d;
107 }
108
109 private class MemObjDatabase extends DfsObjDatabase {
110 private List<DfsPackDescription> packs = new ArrayList<>();
111
112 MemObjDatabase(DfsRepository repo) {
113 super(repo, new DfsReaderOptions());
114 }
115
116 @Override
117 protected synchronized List<DfsPackDescription> listPacks() {
118 return packs;
119 }
120
121 @Override
122 protected DfsPackDescription newPack(PackSource source) {
123 int id = packId.incrementAndGet();
124 DfsPackDescription desc = new MemPack(
125 "pack-" + id + "-" + source.name(),
126 getRepository().getDescription());
127 return desc.setPackSource(source);
128 }
129
130 @Override
131 protected synchronized void commitPackImpl(
132 Collection<DfsPackDescription> desc,
133 Collection<DfsPackDescription> replace) {
134 List<DfsPackDescription> n;
135 n = new ArrayList<>(desc.size() + packs.size());
136 n.addAll(desc);
137 n.addAll(packs);
138 if (replace != null)
139 n.removeAll(replace);
140 packs = n;
141 }
142
143 @Override
144 protected void rollbackPack(Collection<DfsPackDescription> desc) {
145
146 }
147
148 @Override
149 protected ReadableChannel openFile(DfsPackDescription desc, PackExt ext)
150 throws FileNotFoundException, IOException {
151 MemPack memPack = (MemPack) desc;
152 byte[] file = memPack.fileMap.get(ext);
153 if (file == null)
154 throw new FileNotFoundException(desc.getFileName(ext));
155 return new ByteArrayReadableChannel(file);
156 }
157
158 @Override
159 protected DfsOutputStream writeFile(
160 DfsPackDescription desc, final PackExt ext) throws IOException {
161 final MemPack memPack = (MemPack) desc;
162 return new Out() {
163 @Override
164 public void flush() {
165 memPack.fileMap.put(ext, getData());
166 }
167 };
168 }
169 }
170
171 private static class MemPack extends DfsPackDescription {
172 final Map<PackExt, byte[]>
173 fileMap = new HashMap<>();
174
175 MemPack(String name, DfsRepositoryDescription repoDesc) {
176 super(repoDesc, name);
177 }
178 }
179
180 private abstract static class Out extends DfsOutputStream {
181 private final ByteArrayOutputStream dst = new ByteArrayOutputStream();
182
183 private byte[] data;
184
185 @Override
186 public void write(byte[] buf, int off, int len) {
187 data = null;
188 dst.write(buf, off, len);
189 }
190
191 @Override
192 public int read(long position, ByteBuffer buf) {
193 byte[] d = getData();
194 int n = Math.min(buf.remaining(), d.length - (int) position);
195 if (n == 0)
196 return -1;
197 buf.put(d, (int) position, n);
198 return n;
199 }
200
201 byte[] getData() {
202 if (data == null)
203 data = dst.toByteArray();
204 return data;
205 }
206
207 @Override
208 public abstract void flush();
209
210 @Override
211 public void close() {
212 flush();
213 }
214
215 }
216
217 private static class ByteArrayReadableChannel implements ReadableChannel {
218 private final byte[] data;
219
220 private int position;
221
222 private boolean open = true;
223
224 ByteArrayReadableChannel(byte[] buf) {
225 data = buf;
226 }
227
228 @Override
229 public int read(ByteBuffer dst) {
230 int n = Math.min(dst.remaining(), data.length - position);
231 if (n == 0)
232 return -1;
233 dst.put(data, position, n);
234 position += n;
235 return n;
236 }
237
238 @Override
239 public void close() {
240 open = false;
241 }
242
243 @Override
244 public boolean isOpen() {
245 return open;
246 }
247
248 @Override
249 public long position() {
250 return position;
251 }
252
253 @Override
254 public void position(long newPosition) {
255 position = (int) newPosition;
256 }
257
258 @Override
259 public long size() {
260 return data.length;
261 }
262
263 @Override
264 public int blockSize() {
265 return 0;
266 }
267
268 @Override
269 public void setReadAheadBytes(int b) {
270
271 }
272 }
273
274
275
276
277
278
279
280 protected class MemRefDatabase extends DfsRefDatabase {
281 private final ConcurrentMap<String, Ref> refs = new ConcurrentHashMap<>();
282 private final ReadWriteLock lock = new ReentrantReadWriteLock(true );
283
284
285
286
287 protected MemRefDatabase() {
288 super(InMemoryRepository.this);
289 }
290
291 @Override
292 public boolean performsAtomicTransactions() {
293 return performsAtomicTransactions;
294 }
295
296 @Override
297 public BatchRefUpdate newBatchUpdate() {
298 return new BatchRefUpdate(this) {
299 @Override
300 public void execute(RevWalk walk, ProgressMonitor monitor)
301 throws IOException {
302 if (performsAtomicTransactions() && isAtomic()) {
303 try {
304 lock.writeLock().lock();
305 batch(getCommands());
306 } finally {
307 lock.writeLock().unlock();
308 }
309 } else {
310 super.execute(walk, monitor);
311 }
312 }
313 };
314 }
315
316 @Override
317 protected RefCache scanAllRefs() throws IOException {
318 RefList.Builder<Ref> ids = new RefList.Builder<>();
319 RefList.Builder<Ref> sym = new RefList.Builder<>();
320 try {
321 lock.readLock().lock();
322 for (Ref ref : refs.values()) {
323 if (ref.isSymbolic())
324 sym.add(ref);
325 ids.add(ref);
326 }
327 } finally {
328 lock.readLock().unlock();
329 }
330 ids.sort();
331 sym.sort();
332 objdb.getCurrentPackList().markDirty();
333 return new RefCache(ids.toRefList(), sym.toRefList());
334 }
335
336 private void batch(List<ReceiveCommand> cmds) {
337
338
339 Map<ObjectId, ObjectId> peeled = new HashMap<>();
340 try (RevWalk rw = new RevWalk(getRepository())) {
341 for (ReceiveCommand c : cmds) {
342 if (c.getResult() != ReceiveCommand.Result.NOT_ATTEMPTED) {
343 ReceiveCommand.abort(cmds);
344 return;
345 }
346
347 if (!ObjectId.zeroId().equals(c.getNewId())) {
348 try {
349 RevObject o = rw.parseAny(c.getNewId());
350 if (o instanceof RevTag) {
351 peeled.put(o, rw.peel(o).copy());
352 }
353 } catch (IOException e) {
354 c.setResult(ReceiveCommand.Result.REJECTED_MISSING_OBJECT);
355 ReceiveCommand.abort(cmds);
356 return;
357 }
358 }
359 }
360 }
361
362
363 for (ReceiveCommand c : cmds) {
364 Ref r = refs.get(c.getRefName());
365 if (r == null) {
366 if (c.getType() != ReceiveCommand.Type.CREATE) {
367 c.setResult(ReceiveCommand.Result.LOCK_FAILURE);
368 ReceiveCommand.abort(cmds);
369 return;
370 }
371 } else {
372 ObjectId objectId = r.getObjectId();
373 if (r.isSymbolic() || objectId == null
374 || !objectId.equals(c.getOldId())) {
375 c.setResult(ReceiveCommand.Result.LOCK_FAILURE);
376 ReceiveCommand.abort(cmds);
377 return;
378 }
379 }
380 }
381
382
383 for (ReceiveCommand c : cmds) {
384 if (c.getType() == ReceiveCommand.Type.DELETE) {
385 refs.remove(c.getRefName());
386 c.setResult(ReceiveCommand.Result.OK);
387 continue;
388 }
389
390 ObjectId p = peeled.get(c.getNewId());
391 Ref r;
392 if (p != null) {
393 r = new ObjectIdRef.PeeledTag(Storage.PACKED,
394 c.getRefName(), c.getNewId(), p);
395 } else {
396 r = new ObjectIdRef.PeeledNonTag(Storage.PACKED,
397 c.getRefName(), c.getNewId());
398 }
399 refs.put(r.getName(), r);
400 c.setResult(ReceiveCommand.Result.OK);
401 }
402 clearCache();
403 }
404
405 @Override
406 protected boolean compareAndPut(Ref oldRef, Ref newRef)
407 throws IOException {
408 try {
409 lock.writeLock().lock();
410 ObjectId id = newRef.getObjectId();
411 if (id != null) {
412 try (RevWalk rw = new RevWalk(getRepository())) {
413
414
415 rw.parseAny(id);
416 }
417 }
418 String name = newRef.getName();
419 if (oldRef == null)
420 return refs.putIfAbsent(name, newRef) == null;
421
422 Ref cur = refs.get(name);
423 if (cur != null) {
424 if (eq(cur, oldRef))
425 return refs.replace(name, cur, newRef);
426 }
427
428 if (oldRef.getStorage() == Storage.NEW)
429 return refs.putIfAbsent(name, newRef) == null;
430
431 return false;
432 } finally {
433 lock.writeLock().unlock();
434 }
435 }
436
437 @Override
438 protected boolean compareAndRemove(Ref oldRef) throws IOException {
439 try {
440 lock.writeLock().lock();
441 String name = oldRef.getName();
442 Ref cur = refs.get(name);
443 if (cur != null && eq(cur, oldRef))
444 return refs.remove(name, cur);
445 else
446 return false;
447 } finally {
448 lock.writeLock().unlock();
449 }
450 }
451
452 private boolean eq(Ref a, Ref b) {
453 if (!Objects.equals(a.getName(), b.getName()))
454 return false;
455 if (a.isSymbolic() != b.isSymbolic())
456 return false;
457 if (a.isSymbolic())
458 return Objects.equals(a.getTarget().getName(), b.getTarget().getName());
459 else
460 return Objects.equals(a.getObjectId(), b.getObjectId());
461 }
462 }
463 }