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