1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 package org.eclipse.jgit.internal.storage.file;
45
46 import static org.eclipse.jgit.lib.Ref.UNDEFINED_UPDATE_INDEX;
47 import static org.eclipse.jgit.lib.Ref.Storage.NEW;
48 import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
49
50 import java.io.File;
51 import java.io.IOException;
52 import java.util.ArrayList;
53 import java.util.Collections;
54 import java.util.HashSet;
55 import java.util.List;
56 import java.util.Map;
57 import java.util.TreeSet;
58 import java.util.concurrent.locks.ReentrantLock;
59 import java.util.stream.Collectors;
60
61 import org.eclipse.jgit.annotations.NonNull;
62 import org.eclipse.jgit.events.RefsChangedEvent;
63 import org.eclipse.jgit.internal.storage.reftable.MergedReftable;
64 import org.eclipse.jgit.internal.storage.reftable.ReftableBatchRefUpdate;
65 import org.eclipse.jgit.internal.storage.reftable.ReftableDatabase;
66 import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
67 import org.eclipse.jgit.lib.BatchRefUpdate;
68 import org.eclipse.jgit.lib.Constants;
69 import org.eclipse.jgit.lib.ObjectId;
70 import org.eclipse.jgit.lib.ObjectIdRef;
71 import org.eclipse.jgit.lib.PersonIdent;
72 import org.eclipse.jgit.lib.Ref;
73 import org.eclipse.jgit.lib.RefDatabase;
74 import org.eclipse.jgit.lib.RefRename;
75 import org.eclipse.jgit.lib.RefUpdate;
76 import org.eclipse.jgit.lib.ReflogEntry;
77 import org.eclipse.jgit.lib.ReflogReader;
78 import org.eclipse.jgit.lib.Repository;
79 import org.eclipse.jgit.lib.SymbolicRef;
80 import org.eclipse.jgit.revwalk.RevObject;
81 import org.eclipse.jgit.revwalk.RevTag;
82 import org.eclipse.jgit.revwalk.RevWalk;
83 import org.eclipse.jgit.transport.ReceiveCommand;
84 import org.eclipse.jgit.util.FileUtils;
85 import org.eclipse.jgit.util.RefList;
86 import org.eclipse.jgit.util.RefMap;
87
88
89
90
91
92
93 public class FileReftableDatabase extends RefDatabase {
94 private final ReftableDatabase reftableDatabase;
95
96 private final FileRepository fileRepository;
97
98 private final FileReftableStack reftableStack;
99
100 FileReftableDatabase(FileRepository repo, File refstackName) throws IOException {
101 this.fileRepository = repo;
102 this.reftableStack = new FileReftableStack(refstackName,
103 new File(fileRepository.getDirectory(), Constants.REFTABLE),
104 () -> fileRepository.fireEvent(new RefsChangedEvent()),
105 () -> fileRepository.getConfig());
106 this.reftableDatabase = new ReftableDatabase() {
107
108 @Override
109 public MergedReftable openMergedReftable() throws IOException {
110 return reftableStack.getMergedReftable();
111 }
112 };
113 }
114
115 ReflogReader getReflogReader(String refname) throws IOException {
116 return reftableDatabase.getReflogReader(refname);
117 }
118
119
120
121
122
123 public static boolean isReftable(File repoDir) {
124 return new File(repoDir, "refs").isFile()
125 && new File(repoDir, Constants.REFTABLE).isDirectory();
126 }
127
128
129 @Override
130 public boolean hasFastTipsWithSha1() throws IOException {
131 return reftableDatabase.hasFastTipsWithSha1();
132 }
133
134
135
136
137
138 public void compactFully() throws IOException {
139 reftableDatabase.getLock().lock();
140 try {
141 reftableStack.compactFully();
142 } finally {
143 reftableDatabase.getLock().unlock();
144 }
145 }
146
147 private ReentrantLock getLock() {
148 return reftableDatabase.getLock();
149 }
150
151
152 @Override
153 public boolean performsAtomicTransactions() {
154 return true;
155 }
156
157
158 @NonNull
159 @Override
160 public BatchRefUpdate newBatchUpdate() {
161 return new FileReftableBatchRefUpdate(this, fileRepository);
162 }
163
164
165 @Override
166 public RefUpdate newUpdate(String refName, boolean detach)
167 throws IOException {
168 boolean detachingSymbolicRef = false;
169 Ref ref = exactRef(refName);
170
171 if (ref == null) {
172 ref = new ObjectIdRef.Unpeeled(NEW, refName, null);
173 } else {
174 detachingSymbolicRef = detach && ref.isSymbolic();
175 }
176
177 RefUpdate update = new FileReftableRefUpdate(ref);
178 if (detachingSymbolicRef) {
179 update.setDetachingSymbolicRef();
180 }
181 return update;
182 }
183
184
185 @Override
186 public Ref exactRef(String name) throws IOException {
187 return reftableDatabase.exactRef(name);
188 }
189
190
191 @Override
192 public List<Ref> getRefs() throws IOException {
193 return super.getRefs();
194 }
195
196
197 @Override
198 public Map<String, Ref> getRefs(String prefix) throws IOException {
199 List<Ref> refs = reftableDatabase.getRefsByPrefix(prefix);
200 RefList.Builder<Ref> builder = new RefList.Builder<>(refs.size());
201 for (Ref r : refs) {
202 builder.add(r);
203 }
204 return new RefMap(prefix, builder.toRefList(), RefList.emptyList(),
205 RefList.emptyList());
206 }
207
208
209 @Override
210 public List<Ref> getAdditionalRefs() throws IOException {
211 return Collections.emptyList();
212 }
213
214
215 @Override
216 public Ref" href="../../../../../../org/eclipse/jgit/lib/Ref.html#Ref">Ref peel(Ref ref) throws IOException {
217 Ref oldLeaf = ref.getLeaf();
218 if (oldLeaf.isPeeled() || oldLeaf.getObjectId() == null) {
219 return ref;
220 }
221 return recreate(ref, doPeel(oldLeaf), hasVersioning());
222
223 }
224
225 private Refhref="../../../../../../org/eclipse/jgit/lib/Ref.html#Ref">Ref doPeel(Ref leaf) throws IOException {
226 try (RevWalkvwalk/RevWalk.html#RevWalk">RevWalk rw = new RevWalk(fileRepository)) {
227 RevObject obj = rw.parseAny(leaf.getObjectId());
228 if (obj instanceof RevTag) {
229 return new ObjectIdRef.PeeledTag(leaf.getStorage(),
230 leaf.getName(), leaf.getObjectId(), rw.peel(obj).copy(),
231 hasVersioning() ? leaf.getUpdateIndex()
232 : UNDEFINED_UPDATE_INDEX);
233 }
234 return new ObjectIdRef.PeeledNonTag(leaf.getStorage(),
235 leaf.getName(), leaf.getObjectId(),
236 hasVersioning() ? leaf.getUpdateIndex()
237 : UNDEFINED_UPDATE_INDEX);
238
239 }
240 }
241
242 private static Ref" href="../../../../../../org/eclipse/jgit/lib/Ref.html#Ref">Refef="../../../../../../org/eclipse/jgit/lib/Ref.html#Ref">Ref recreate(Ref" href="../../../../../../org/eclipse/jgit/lib/Ref.html#Ref">Ref old, Ref leaf, boolean hasVersioning) {
243 if (old.isSymbolic()) {
244 Ref dst = recreate(old.getTarget(), leaf, hasVersioning);
245 return new SymbolicRef(old.getName(), dst,
246 hasVersioning ? old.getUpdateIndex()
247 : UNDEFINED_UPDATE_INDEX);
248 }
249 return leaf;
250 }
251
252 private class FileRefRename extends RefRename {
253 FileRefRename(RefUpdate="../../../../../../org/eclipse/jgit/lib/RefUpdate.html#RefUpdate">RefUpdate src, RefUpdate dst) {
254 super(src, dst);
255 }
256
257 void writeRename(ReftableWriter w) throws IOException {
258 long idx = reftableDatabase.nextUpdateIndex();
259 w.setMinUpdateIndex(idx).setMaxUpdateIndex(idx).begin();
260 List<Ref> refs = new ArrayList<>(3);
261
262 Ref dest = destination.getRef();
263 Ref head = exactRef(Constants.HEAD);
264 if (head != null && head.isSymbolic()
265 && head.getLeaf().getName().equals(source.getName())) {
266 head = new SymbolicRef(Constants.HEAD, dest, idx);
267 refs.add(head);
268 }
269
270 ObjectId objId = source.getRef().getObjectId();
271
272
273 refs.add(new ObjectIdRef.PeeledNonTag(Ref.Storage.NEW,
274 destination.getName(), objId));
275 refs.add(new ObjectIdRef.Unpeeled(Ref.Storage.NEW, source.getName(),
276 null));
277
278 w.sortAndWriteRefs(refs);
279 PersonIdent who = destination.getRefLogIdent();
280 if (who == null) {
281 who = new PersonIdent(fileRepository);
282 }
283
284 if (!destination.getRefLogMessage().isEmpty()) {
285 List<String> refnames = refs.stream().map(r -> r.getName())
286 .collect(Collectors.toList());
287 Collections.sort(refnames);
288 for (String s : refnames) {
289 ObjectId old = (Constants.HEAD.equals(s)
290 || s.equals(source.getName())) ? objId
291 : ObjectId.zeroId();
292 ObjectId newId = (Constants.HEAD.equals(s)
293 || s.equals(destination.getName())) ? objId
294 : ObjectId.zeroId();
295
296 w.writeLog(s, idx, who, old, newId,
297 destination.getRefLogMessage());
298 }
299 }
300 }
301
302 @Override
303 protected RefUpdate.Result doRename() throws IOException {
304 Ref src = exactRef(source.getName());
305 if (exactRef(destination.getName()) != null || src == null
306 || !source.getOldObjectId().equals(src.getObjectId())) {
307 return RefUpdate.Result.LOCK_FAILURE;
308 }
309
310 if (src.isSymbolic()) {
311
312 return RefUpdate.Result.IO_FAILURE;
313 }
314
315 if (!addReftable(this::writeRename)) {
316 return RefUpdate.Result.LOCK_FAILURE;
317 }
318
319 return RefUpdate.Result.RENAMED;
320 }
321 }
322
323
324 @Override
325 public RefRename newRename(String fromName, String toName)
326 throws IOException {
327 RefUpdate src = newUpdate(fromName, true);
328 RefUpdate dst = newUpdate(toName, true);
329 return new FileRefRename(src, dst);
330 }
331
332
333 @Override
334 public boolean isNameConflicting(String name) throws IOException {
335 return reftableDatabase.isNameConflicting(name, new TreeSet<>(),
336 new HashSet<>());
337 }
338
339
340 @Override
341 public void close() {
342 reftableStack.close();
343 }
344
345
346 @Override
347 public void create() throws IOException {
348 FileUtils.mkdir(
349 new File(fileRepository.getDirectory(), Constants.REFTABLE),
350 true);
351 }
352
353 private boolean addReftable(FileReftableStack.Writer w) throws IOException {
354 if (!reftableStack.addReftable(w)) {
355 reftableStack.reload();
356 reftableDatabase.clearCache();
357 return false;
358 }
359 reftableDatabase.clearCache();
360
361 return true;
362 }
363
364 private class FileReftableBatchRefUpdate extends ReftableBatchRefUpdate {
365 FileReftableBatchRefUpdate(FileReftableDatabase db,
366 Repository repository) {
367 super(db, db.reftableDatabase, db.getLock(), repository);
368 }
369
370 @Override
371 protected void applyUpdates(List<Ref> newRefs,
372 List<ReceiveCommand> pending) throws IOException {
373 if (!addReftable(rw -> write(rw, newRefs, pending))) {
374 for (ReceiveCommand c : pending) {
375 if (c.getResult() == ReceiveCommand.Result.NOT_ATTEMPTED) {
376 c.setResult(RefUpdate.Result.LOCK_FAILURE);
377 }
378 }
379 }
380 }
381 }
382
383 private class FileReftableRefUpdate extends RefUpdate {
384 FileReftableRefUpdate(Ref ref) {
385 super(ref);
386 }
387
388 @Override
389 protected RefDatabase getRefDatabase() {
390 return FileReftableDatabase.this;
391 }
392
393 @Override
394 protected Repository getRepository() {
395 return FileReftableDatabase.this.fileRepository;
396 }
397
398 @Override
399 protected void unlock() {
400
401 }
402
403 private RevWalk rw;
404
405 private Ref dstRef;
406
407 @Override
408 public Result update(RevWalk walk) throws IOException {
409 try {
410 rw = walk;
411 return super.update(walk);
412 } finally {
413 rw = null;
414 }
415 }
416
417 @Override
418 protected boolean tryLock(boolean deref) throws IOException {
419 dstRef = getRef();
420 if (deref) {
421 dstRef = dstRef.getLeaf();
422 }
423
424 Ref derefed = exactRef(dstRef.getName());
425 if (derefed != null) {
426 setOldObjectId(derefed.getObjectId());
427 }
428
429 return true;
430 }
431
432 void writeUpdate(ReftableWriter w) throws IOException {
433 Ref newRef = null;
434 if (rw != null && !ObjectId.zeroId().equals(getNewObjectId())) {
435 RevObject obj = rw.parseAny(getNewObjectId());
436 if (obj instanceof RevTag) {
437 newRef = new ObjectIdRef.PeeledTag(Ref.Storage.PACKED,
438 dstRef.getName(), getNewObjectId(),
439 rw.peel(obj).copy());
440 }
441 }
442 if (newRef == null) {
443 newRef = new ObjectIdRef.PeeledNonTag(Ref.Storage.PACKED,
444 dstRef.getName(), getNewObjectId());
445 }
446
447 long idx = reftableDatabase.nextUpdateIndex();
448 w.setMinUpdateIndex(idx).setMaxUpdateIndex(idx).begin()
449 .writeRef(newRef);
450
451 ObjectId oldId = getOldObjectId();
452 if (oldId == null) {
453 oldId = ObjectId.zeroId();
454 }
455 w.writeLog(dstRef.getName(), idx, getRefLogIdent(), oldId,
456 getNewObjectId(), getRefLogMessage());
457 }
458
459 @Override
460 public PersonIdent getRefLogIdent() {
461 PersonIdent who = super.getRefLogIdent();
462 if (who == null) {
463 who = new PersonIdent(getRepository());
464 }
465 return who;
466 }
467
468 void writeDelete(ReftableWriter w) throws IOException {
469 Ref newRef = new ObjectIdRef.Unpeeled(Ref.Storage.NEW,
470 dstRef.getName(), null);
471 long idx = reftableDatabase.nextUpdateIndex();
472 w.setMinUpdateIndex(idx).setMaxUpdateIndex(idx).begin()
473 .writeRef(newRef);
474
475 ObjectId oldId = ObjectId.zeroId();
476 Ref old = exactRef(dstRef.getName());
477 if (old != null) {
478 old = old.getLeaf();
479 if (old.getObjectId() != null) {
480 oldId = old.getObjectId();
481 }
482 }
483
484 w.writeLog(dstRef.getName(), idx, getRefLogIdent(), oldId,
485 ObjectId.zeroId(), getRefLogMessage());
486 }
487
488 @Override
489 protected Result doUpdate(Result desiredResult) throws IOException {
490 if (isRefLogIncludingResult()) {
491 setRefLogMessage(
492 getRefLogMessage() + ": " + desiredResult.toString(),
493 false);
494 }
495
496 if (!addReftable(this::writeUpdate)) {
497 return Result.LOCK_FAILURE;
498 }
499
500 return desiredResult;
501 }
502
503 @Override
504 protected Result doDelete(Result desiredResult) throws IOException {
505
506 if (isRefLogIncludingResult()) {
507 setRefLogMessage(
508 getRefLogMessage() + ": " + desiredResult.toString(),
509 false);
510 }
511
512 if (!addReftable(this::writeDelete)) {
513 return Result.LOCK_FAILURE;
514 }
515
516 return desiredResult;
517 }
518
519 void writeLink(ReftableWriter w) throws IOException {
520 long idx = reftableDatabase.nextUpdateIndex();
521 w.setMinUpdateIndex(idx).setMaxUpdateIndex(idx).begin()
522 .writeRef(dstRef);
523
524 ObjectId beforeId = ObjectId.zeroId();
525 Ref before = exactRef(dstRef.getName());
526 if (before != null) {
527 before = before.getLeaf();
528 if (before.getObjectId() != null) {
529 beforeId = before.getObjectId();
530 }
531 }
532
533 Ref after = dstRef.getLeaf();
534 ObjectId afterId = ObjectId.zeroId();
535 if (after.getObjectId() != null) {
536 afterId = after.getObjectId();
537 }
538
539 w.writeLog(dstRef.getName(), idx, getRefLogIdent(), beforeId,
540 afterId, getRefLogMessage());
541 }
542
543 @Override
544 protected Result doLink(String target) throws IOException {
545 if (isRefLogIncludingResult()) {
546 setRefLogMessage(
547 getRefLogMessage() + ": " + Result.FORCED.toString(),
548 false);
549 }
550
551 boolean exists = exactRef(getName()) != null;
552 dstRef = new SymbolicRef(getName(),
553 new ObjectIdRef.Unpeeled(Ref.Storage.NEW, target, null),
554 reftableDatabase.nextUpdateIndex());
555
556 if (!addReftable(this::writeLink)) {
557 return Result.LOCK_FAILURE;
558 }
559
560
561 return exists ? Result.FORCED : Result.NEW;
562 }
563 }
564
565 private static void writeConvertTable(Repository repo, ReftableWriter w,
566 boolean writeLogs) throws IOException {
567 int size = 0;
568 List<Ref> refs = repo.getRefDatabase().getRefs();
569 if (writeLogs) {
570 for (Ref r : refs) {
571 ReflogReader rlr = repo.getReflogReader(r.getName());
572 if (rlr != null) {
573 size = Math.max(rlr.getReverseEntries().size(), size);
574 }
575 }
576 }
577
578 w.setMinUpdateIndex(1).setMaxUpdateIndex(size + 1).begin();
579
580
581
582
583 try (RevWalkvwalk/RevWalk.html#RevWalk">RevWalk rw = new RevWalk(repo)) {
584 List<Ref> toWrite = new ArrayList<>(refs.size());
585 for (Ref r : refs) {
586 toWrite.add(refForWrite(rw, r));
587 }
588 w.sortAndWriteRefs(toWrite);
589 }
590
591 if (writeLogs) {
592 for (Ref r : refs) {
593 long idx = size;
594 ReflogReader reader = repo.getReflogReader(r.getName());
595 if (reader == null) {
596 continue;
597 }
598 for (ReflogEntry e : reader.getReverseEntries()) {
599 w.writeLog(r.getName(), idx, e.getWho(), e.getOldId(),
600 e.getNewId(), e.getComment());
601 idx--;
602 }
603 }
604 }
605 }
606
607 private static Ref/../../org/eclipse/jgit/lib/Ref.html#Ref">Ref refForWrite(RevWalk rw, Ref r) throws IOException {
608 if (r.isSymbolic()) {
609 return new SymbolicRef(r.getName(), new ObjectIdRef.Unpeeled(NEW,
610 r.getTarget().getName(), null));
611 }
612 ObjectId newId = r.getObjectId();
613 RevObject obj = rw.parseAny(newId);
614 RevObject peel = null;
615 if (obj instanceof RevTag) {
616 peel = rw.peel(obj);
617 }
618 if (peel != null) {
619 return new ObjectIdRef.PeeledTag(PACKED, r.getName(), newId,
620 peel.copy());
621 }
622 return new ObjectIdRef.PeeledNonTag(PACKED, r.getName(), newId);
623 }
624
625
626
627
628
629
630
631
632
633
634
635
636 public static FileReftableDatabase convertFrom(FileRepository repo,
637 File refstackName, boolean writeLogs) throws IOException {
638 FileReftableDatabase newDb = null;
639 try {
640 File reftableDir = new File(repo.getDirectory(), Constants.REFTABLE);
641 if (!reftableDir.isDirectory()) {
642 reftableDir.mkdir();
643 }
644
645 try (FileReftableStacke/file/FileReftableStack.html#FileReftableStack">FileReftableStack stack = new FileReftableStack(refstackName,
646 reftableDir, null, () -> repo.getConfig())) {
647 stack.addReftable(rw -> writeConvertTable(repo, rw, writeLogs));
648 }
649 refstackName = null;
650 } finally {
651 if (refstackName != null) {
652 refstackName.delete();
653 }
654 }
655 return newDb;
656 }
657 }