View Javadoc
1   /*
2    * Copyright (C) 2019 Google LLC
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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   * Implements RefDatabase using reftable for storage.
90   *
91   * This class is threadsafe.
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 	 * @param repoDir
121 	 * @return whether the given repo uses reftable for refdb storage.
122 	 */
123 	public static boolean isReftable(File repoDir) {
124 		return new File(repoDir, "refs").isFile() //$NON-NLS-1$
125 				&& new File(repoDir, Constants.REFTABLE).isDirectory();
126 	}
127 
128 	/** {@inheritDoc} */
129 	@Override
130 	public boolean hasFastTipsWithSha1() throws IOException {
131 		return reftableDatabase.hasFastTipsWithSha1();
132 	}
133 
134 	/**
135 	 * Runs a full compaction for GC purposes.
136 	 * @throws IOException on I/O errors
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 	/** {@inheritDoc} */
152 	@Override
153 	public boolean performsAtomicTransactions() {
154 		return true;
155 	}
156 
157 	/** {@inheritDoc} */
158 	@NonNull
159 	@Override
160 	public BatchRefUpdate newBatchUpdate() {
161 		return new FileReftableBatchRefUpdate(this, fileRepository);
162 	}
163 
164 	/** {@inheritDoc} */
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 	/** {@inheritDoc} */
185 	@Override
186 	public Ref exactRef(String name) throws IOException {
187 		return reftableDatabase.exactRef(name);
188 	}
189 
190 	/** {@inheritDoc} */
191 	@Override
192 	public List<Ref> getRefs() throws IOException {
193 		return super.getRefs();
194 	}
195 
196 	/** {@inheritDoc} */
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 	/** {@inheritDoc} */
209 	@Override
210 	public List<Ref> getAdditionalRefs() throws IOException {
211 		return Collections.emptyList();
212 	}
213 
214 	/** {@inheritDoc} */
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 			// XXX should we check if the source is a Tag vs. NonTag?
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 				// We could support this, but this is easier and compatible.
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 	/** {@inheritDoc} */
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 	/** {@inheritDoc} */
333 	@Override
334 	public boolean isNameConflicting(String name) throws IOException {
335 		return reftableDatabase.isNameConflicting(name, new TreeSet<>(),
336 				new HashSet<>());
337 	}
338 
339 	/** {@inheritDoc} */
340 	@Override
341 	public void close() {
342 		reftableStack.close();
343 	}
344 
345 	/** {@inheritDoc} */
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 			// nop.
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(), //$NON-NLS-1$
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(), //$NON-NLS-1$
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(), //$NON-NLS-1$
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 			// XXX unclear if we should support FORCED here. Baseclass says
560 			// NEW is OK ?
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 		// We must use 1 here, nextUpdateIndex() on the empty stack is 1.
578 		w.setMinUpdateIndex(1).setMaxUpdateIndex(size + 1).begin();
579 
580 		// The spec says to write the logs in the first table, and put refs in a
581 		// separate table, but this complicates the compaction (when we can we drop
582 		// deletions? Can we compact the .log table and the .ref table together?)
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 	 * @param repo
627 	 *            the repository
628 	 * @param refstackName
629 	 *            the filename for the stack
630 	 * @param writeLogs
631 	 *            whether to write reflogs
632 	 * @return a reftable based RefDB from an existing repository.
633 	 * @throws IOException
634 	 *             on IO error
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 }