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