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
45
46
47 package org.eclipse.jgit.internal.storage.file;
48
49 import static java.util.stream.Collectors.toList;
50
51 import java.io.File;
52 import java.io.FileInputStream;
53 import java.io.FileNotFoundException;
54 import java.io.IOException;
55 import java.text.MessageFormat;
56 import java.text.ParseException;
57 import java.util.ArrayList;
58 import java.util.HashSet;
59 import java.util.List;
60 import java.util.Locale;
61 import java.util.Objects;
62 import java.util.Set;
63
64 import org.eclipse.jgit.annotations.Nullable;
65 import org.eclipse.jgit.api.errors.JGitInternalException;
66 import org.eclipse.jgit.attributes.AttributesNode;
67 import org.eclipse.jgit.attributes.AttributesNodeProvider;
68 import org.eclipse.jgit.errors.ConfigInvalidException;
69 import org.eclipse.jgit.events.IndexChangedEvent;
70 import org.eclipse.jgit.internal.JGitText;
71 import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateHandle;
72 import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateRepository;
73 import org.eclipse.jgit.internal.storage.reftree.RefTreeDatabase;
74 import org.eclipse.jgit.lib.BaseRepositoryBuilder;
75 import org.eclipse.jgit.lib.BatchRefUpdate;
76 import org.eclipse.jgit.lib.ConfigConstants;
77 import org.eclipse.jgit.lib.Constants;
78 import org.eclipse.jgit.lib.CoreConfig.HideDotFiles;
79 import org.eclipse.jgit.lib.CoreConfig.SymLinks;
80 import org.eclipse.jgit.lib.NullProgressMonitor;
81 import org.eclipse.jgit.lib.ObjectId;
82 import org.eclipse.jgit.lib.ProgressMonitor;
83 import org.eclipse.jgit.lib.Ref;
84 import org.eclipse.jgit.lib.RefDatabase;
85 import org.eclipse.jgit.lib.RefUpdate;
86 import org.eclipse.jgit.lib.ReflogReader;
87 import org.eclipse.jgit.lib.Repository;
88 import org.eclipse.jgit.lib.StoredConfig;
89 import org.eclipse.jgit.revwalk.RevWalk;
90 import org.eclipse.jgit.storage.file.FileBasedConfig;
91 import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
92 import org.eclipse.jgit.storage.pack.PackConfig;
93 import org.eclipse.jgit.transport.ReceiveCommand;
94 import org.eclipse.jgit.util.FileUtils;
95 import org.eclipse.jgit.util.IO;
96 import org.eclipse.jgit.util.RawParseUtils;
97 import org.eclipse.jgit.util.StringUtils;
98 import org.eclipse.jgit.util.SystemReader;
99 import org.slf4j.Logger;
100 import org.slf4j.LoggerFactory;
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126 public class FileRepository extends Repository {
127 private static final Logger LOG = LoggerFactory
128 .getLogger(FileRepository.class);
129 private static final String UNNAMED = "Unnamed repository; edit this file to name it for gitweb.";
130
131 private final FileBasedConfig repoConfig;
132 private RefDatabase refs;
133 private final ObjectDirectory objectDatabase;
134
135 private final Object snapshotLock = new Object();
136
137
138 private FileSnapshot snapshot;
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160 public FileRepository(File gitDir) throws IOException {
161 this(new FileRepositoryBuilder().setGitDir(gitDir).setup());
162 }
163
164
165
166
167
168
169
170
171
172
173
174 public FileRepository(String gitDir) throws IOException {
175 this(new File(gitDir));
176 }
177
178
179
180
181
182
183
184
185
186
187 public FileRepository(BaseRepositoryBuilder options) throws IOException {
188 super(options);
189 StoredConfig userConfig = null;
190 try {
191 userConfig = SystemReader.getInstance().getUserConfig();
192 } catch (ConfigInvalidException e) {
193 LOG.error(e.getMessage(), e);
194 throw new IOException(e.getMessage(), e);
195 }
196 repoConfig = new FileBasedConfig(userConfig, getFS().resolve(
197 getDirectory(), Constants.CONFIG),
198 getFS());
199 loadRepoConfig();
200
201 repoConfig.addChangeListener(this::fireEvent);
202
203 final long repositoryFormatVersion = getConfig().getLong(
204 ConfigConstants.CONFIG_CORE_SECTION, null,
205 ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);
206
207 String reftype = repoConfig.getString(
208 "extensions", null, "refStorage");
209 if (repositoryFormatVersion >= 1 && reftype != null) {
210 if (StringUtils.equalsIgnoreCase(reftype, "reftable")) {
211 refs = new FileReftableDatabase(this,
212 new File(getDirectory(), "refs"));
213 } else if (StringUtils.equalsIgnoreCase(reftype, "reftree")) {
214 refs = new RefTreeDatabase(this, new RefDirectory(this));
215 } else {
216 throw new IOException(JGitText.get().unknownRepositoryFormat);
217 }
218 } else if (FileReftableDatabase.isReftable(getDirectory())) {
219 refs = new FileReftableDatabase(this, new File(getDirectory(), "refs"));
220 } else {
221 refs = new RefDirectory(this);
222 }
223
224 objectDatabase = new ObjectDirectory(repoConfig,
225 options.getObjectDirectory(),
226 options.getAlternateObjectDirectories(),
227 getFS(),
228 new File(getDirectory(), Constants.SHALLOW));
229
230 if (objectDatabase.exists()) {
231 if (repositoryFormatVersion > 1)
232 throw new IOException(MessageFormat.format(
233 JGitText.get().unknownRepositoryFormat2,
234 Long.valueOf(repositoryFormatVersion)));
235 }
236
237 if (!isBare()) {
238 snapshot = FileSnapshot.save(getIndexFile());
239 }
240 }
241
242 private void loadRepoConfig() throws IOException {
243 try {
244 repoConfig.load();
245 } catch (ConfigInvalidException e) {
246 throw new IOException(JGitText.get().unknownRepositoryFormat, e);
247 }
248 }
249
250
251
252
253
254
255
256 @Override
257 public void create(boolean bare) throws IOException {
258 final FileBasedConfig cfg = getConfig();
259 if (cfg.getFile().exists()) {
260 throw new IllegalStateException(MessageFormat.format(
261 JGitText.get().repositoryAlreadyExists, getDirectory()));
262 }
263 FileUtils.mkdirs(getDirectory(), true);
264 HideDotFiles hideDotFiles = getConfig().getEnum(
265 ConfigConstants.CONFIG_CORE_SECTION, null,
266 ConfigConstants.CONFIG_KEY_HIDEDOTFILES,
267 HideDotFiles.DOTGITONLY);
268 if (hideDotFiles != HideDotFiles.FALSE && !isBare()
269 && getDirectory().getName().startsWith("."))
270 getFS().setHidden(getDirectory(), true);
271 refs.create();
272 objectDatabase.create();
273
274 FileUtils.mkdir(new File(getDirectory(), "branches"));
275 FileUtils.mkdir(new File(getDirectory(), "hooks"));
276
277 RefUpdate head = updateRef(Constants.HEAD);
278 head.disableRefLog();
279 head.link(Constants.R_HEADS + Constants.MASTER);
280
281 final boolean fileMode;
282 if (getFS().supportsExecute()) {
283 File tmp = File.createTempFile("try", "execute", getDirectory());
284
285 getFS().setExecute(tmp, true);
286 final boolean on = getFS().canExecute(tmp);
287
288 getFS().setExecute(tmp, false);
289 final boolean off = getFS().canExecute(tmp);
290 FileUtils.delete(tmp);
291
292 fileMode = on && !off;
293 } else {
294 fileMode = false;
295 }
296
297 SymLinks symLinks = SymLinks.FALSE;
298 if (getFS().supportsSymlinks()) {
299 File tmp = new File(getDirectory(), "tmplink");
300 try {
301 getFS().createSymLink(tmp, "target");
302 symLinks = null;
303 FileUtils.delete(tmp);
304 } catch (IOException e) {
305
306 }
307 }
308 if (symLinks != null)
309 cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
310 ConfigConstants.CONFIG_KEY_SYMLINKS, symLinks.name()
311 .toLowerCase(Locale.ROOT));
312 cfg.setInt(ConfigConstants.CONFIG_CORE_SECTION, null,
313 ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);
314 cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
315 ConfigConstants.CONFIG_KEY_FILEMODE, fileMode);
316 if (bare)
317 cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
318 ConfigConstants.CONFIG_KEY_BARE, true);
319 cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
320 ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, !bare);
321 if (SystemReader.getInstance().isMacOS())
322
323 cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
324 ConfigConstants.CONFIG_KEY_PRECOMPOSEUNICODE, true);
325 if (!bare) {
326 File workTree = getWorkTree();
327 if (!getDirectory().getParentFile().equals(workTree)) {
328 cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
329 ConfigConstants.CONFIG_KEY_WORKTREE, getWorkTree()
330 .getAbsolutePath());
331 LockFile dotGitLockFile = new LockFile(new File(workTree,
332 Constants.DOT_GIT));
333 try {
334 if (dotGitLockFile.lock()) {
335 dotGitLockFile.write(Constants.encode(Constants.GITDIR
336 + getDirectory().getAbsolutePath()));
337 dotGitLockFile.commit();
338 }
339 } finally {
340 dotGitLockFile.unlock();
341 }
342 }
343 }
344 cfg.save();
345 }
346
347
348
349
350
351
352 public File getObjectsDirectory() {
353 return objectDatabase.getDirectory();
354 }
355
356
357 @Override
358 public ObjectDirectory getObjectDatabase() {
359 return objectDatabase;
360 }
361
362
363 @Override
364 public RefDatabase getRefDatabase() {
365 return refs;
366 }
367
368
369 @Override
370 public String getIdentifier() {
371 File directory = getDirectory();
372 if (directory != null) {
373 return directory.getPath();
374 }
375 throw new IllegalStateException();
376 }
377
378
379 @Override
380 public FileBasedConfig getConfig() {
381 try {
382 SystemReader.getInstance().getUserConfig();
383 if (repoConfig.isOutdated()) {
384 loadRepoConfig();
385 }
386 } catch (IOException | ConfigInvalidException e) {
387 throw new RuntimeException(e);
388 }
389 return repoConfig;
390 }
391
392
393 @Override
394 @Nullable
395 public String getGitwebDescription() throws IOException {
396 String d;
397 try {
398 d = RawParseUtils.decode(IO.readFully(descriptionFile()));
399 } catch (FileNotFoundException err) {
400 return null;
401 }
402 if (d != null) {
403 d = d.trim();
404 if (d.isEmpty() || UNNAMED.equals(d)) {
405 return null;
406 }
407 }
408 return d;
409 }
410
411
412 @Override
413 public void setGitwebDescription(@Nullable String description)
414 throws IOException {
415 String old = getGitwebDescription();
416 if (Objects.equals(old, description)) {
417 return;
418 }
419
420 File path = descriptionFile();
421 LockFile lock = new LockFile(path);
422 if (!lock.lock()) {
423 throw new IOException(MessageFormat.format(JGitText.get().lockError,
424 path.getAbsolutePath()));
425 }
426 try {
427 String d = description;
428 if (d != null) {
429 d = d.trim();
430 if (!d.isEmpty()) {
431 d += '\n';
432 }
433 } else {
434 d = "";
435 }
436 lock.write(Constants.encode(d));
437 lock.commit();
438 } finally {
439 lock.unlock();
440 }
441 }
442
443 private File descriptionFile() {
444 return new File(getDirectory(), "description");
445 }
446
447
448
449
450
451
452
453
454
455
456
457 @Override
458 public Set<ObjectId> getAdditionalHaves() {
459 return getAdditionalHaves(null);
460 }
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475 private Set<ObjectId> getAdditionalHaves(Set<AlternateHandle.Id> skips) {
476 HashSet<ObjectId> r = new HashSet<>();
477 skips = objectDatabase.addMe(skips);
478 for (AlternateHandle d : objectDatabase.myAlternates()) {
479 if (d instanceof AlternateRepository && !skips.contains(d.getId())) {
480 FileRepository repo;
481
482 repo = ((AlternateRepository) d).repository;
483 for (Ref ref : repo.getAllRefs().values()) {
484 if (ref.getObjectId() != null)
485 r.add(ref.getObjectId());
486 if (ref.getPeeledObjectId() != null)
487 r.add(ref.getPeeledObjectId());
488 }
489 r.addAll(repo.getAdditionalHaves(skips));
490 }
491 }
492 return r;
493 }
494
495
496
497
498
499
500
501
502
503
504 public void openPack(File pack) throws IOException {
505 objectDatabase.openPack(pack);
506 }
507
508
509 @Override
510 public void scanForRepoChanges() throws IOException {
511 getRefDatabase().getRefs();
512 detectIndexChanges();
513 }
514
515
516 private void detectIndexChanges() {
517 if (isBare()) {
518 return;
519 }
520
521 File indexFile = getIndexFile();
522 synchronized (snapshotLock) {
523 if (snapshot == null) {
524 snapshot = FileSnapshot.save(indexFile);
525 return;
526 }
527 if (!snapshot.isModified(indexFile)) {
528 return;
529 }
530 }
531 notifyIndexChanged(false);
532 }
533
534
535 @Override
536 public void notifyIndexChanged(boolean internal) {
537 synchronized (snapshotLock) {
538 snapshot = FileSnapshot.save(getIndexFile());
539 }
540 fireEvent(new IndexChangedEvent(internal));
541 }
542
543
544 @Override
545 public ReflogReader getReflogReader(String refName) throws IOException {
546 if (refs instanceof FileReftableDatabase) {
547
548
549 return ((FileReftableDatabase)refs).getReflogReader(refName);
550 }
551
552
553
554 Ref ref = findRef(refName);
555 if (ref == null) {
556 return null;
557 }
558 return new ReflogReaderImpl(this, ref.getName());
559 }
560
561
562 @Override
563 public AttributesNodeProvider createAttributesNodeProvider() {
564 return new AttributesNodeProviderImpl(this);
565 }
566
567
568
569
570
571
572
573
574 static class AttributesNodeProviderImpl implements
575 AttributesNodeProvider {
576
577 private AttributesNode infoAttributesNode;
578
579 private AttributesNode globalAttributesNode;
580
581
582
583
584
585
586
587 protected AttributesNodeProviderImpl(Repository repo) {
588 infoAttributesNode = new InfoAttributesNode(repo);
589 globalAttributesNode = new GlobalAttributesNode(repo);
590 }
591
592 @Override
593 public AttributesNode getInfoAttributesNode() throws IOException {
594 if (infoAttributesNode instanceof InfoAttributesNode)
595 infoAttributesNode = ((InfoAttributesNode) infoAttributesNode)
596 .load();
597 return infoAttributesNode;
598 }
599
600 @Override
601 public AttributesNode getGlobalAttributesNode() throws IOException {
602 if (globalAttributesNode instanceof GlobalAttributesNode)
603 globalAttributesNode = ((GlobalAttributesNode) globalAttributesNode)
604 .load();
605 return globalAttributesNode;
606 }
607
608 static void loadRulesFromFile(AttributesNode r, File attrs)
609 throws FileNotFoundException, IOException {
610 if (attrs.exists()) {
611 try (FileInputStream in = new FileInputStream(attrs)) {
612 r.parse(in);
613 }
614 }
615 }
616
617 }
618
619 private boolean shouldAutoDetach() {
620 return getConfig().getBoolean(ConfigConstants.CONFIG_GC_SECTION,
621 ConfigConstants.CONFIG_KEY_AUTODETACH, true);
622 }
623
624
625 @Override
626 public void autoGC(ProgressMonitor monitor) {
627 GC gc = new GC(this);
628 gc.setPackConfig(new PackConfig(this));
629 gc.setProgressMonitor(monitor);
630 gc.setAuto(true);
631 gc.setBackground(shouldAutoDetach());
632 try {
633 gc.gc();
634 } catch (ParseException | IOException e) {
635 throw new JGitInternalException(JGitText.get().gcFailed, e);
636 }
637 }
638
639
640
641
642
643
644
645
646
647
648
649
650
651 void convertToPackedRefs(boolean backup) throws IOException {
652 List<Ref> all = refs.getRefs();
653 File packedRefs = new File(getDirectory(), Constants.PACKED_REFS);
654 if (packedRefs.exists()) {
655 throw new IOException(MessageFormat.format(JGitText.get().fileAlreadyExists,
656 packedRefs.getName()));
657 }
658
659 File refsFile = new File(getDirectory(), "refs");
660
661 refs.close();
662
663 if (backup) {
664 File refsOld = new File(getDirectory(), "refs.old");
665 if (refsOld.exists()) {
666 throw new IOException(MessageFormat.format(
667 JGitText.get().fileAlreadyExists,
668 "refs.old"));
669 }
670 FileUtils.rename(refsFile, refsOld);
671 } else {
672 refsFile.delete();
673 }
674
675
676
677 refs = new RefDirectory(this);
678 refs.create();
679
680 List<Ref> symrefs = new ArrayList<>();
681 BatchRefUpdate bru = refs.newBatchUpdate();
682 for (Ref r : all) {
683 if (r.isSymbolic()) {
684 symrefs.add(r);
685 } else {
686 bru.addCommand(new ReceiveCommand(ObjectId.zeroId(),
687 r.getObjectId(), r.getName()));
688 }
689 }
690
691 try (RevWalkvwalk/RevWalk.html#RevWalk">RevWalk rw = new RevWalk(this)) {
692 bru.execute(rw, NullProgressMonitor.INSTANCE);
693 }
694
695 List<String> failed = new ArrayList<>();
696 for (ReceiveCommand cmd : bru.getCommands()) {
697 if (cmd.getResult() != ReceiveCommand.Result.OK) {
698 failed.add(cmd.getRefName() + ": " + cmd.getResult());
699 }
700 }
701
702 if (!failed.isEmpty()) {
703 throw new IOException(String.format("%s: %s",
704 JGitText.get().failedToConvert,
705 StringUtils.join(failed, ", ")));
706 }
707
708 for (Ref s : symrefs) {
709 RefUpdate up = refs.newUpdate(s.getName(), false);
710 up.setForceUpdate(true);
711 RefUpdate.Result res = up.link(s.getTarget().getName());
712 if (res != RefUpdate.Result.NEW
713 && res != RefUpdate.Result.NO_CHANGE) {
714 throw new IOException(
715 String.format("ref %s: %s", s.getName(), res));
716 }
717 }
718
719 if (!backup) {
720 File reftableDir = new File(getDirectory(), Constants.REFTABLE);
721 FileUtils.delete(reftableDir,
722 FileUtils.RECURSIVE | FileUtils.IGNORE_ERRORS);
723 }
724 }
725
726 @SuppressWarnings("nls")
727 void convertToReftable(boolean writeLogs, boolean backup)
728 throws IOException {
729 File newRefs = new File(getDirectory(), "refs.new");
730 File reftableDir = new File(getDirectory(), Constants.REFTABLE);
731
732 if (reftableDir.exists() && reftableDir.listFiles().length > 0) {
733 throw new IOException(JGitText.get().reftableDirExists);
734 }
735
736
737 FileReftableDatabase.convertFrom(this, newRefs, writeLogs);
738
739 File refsFile = new File(getDirectory(), "refs");
740
741
742 File packedRefs = new File(getDirectory(), Constants.PACKED_REFS);
743 File logsDir = new File(getDirectory(), Constants.LOGS);
744
745
746 List<String> additional = getRefDatabase().getAdditionalRefs().stream()
747 .map(Ref::getName).collect(toList());
748 additional.add(Constants.HEAD);
749 if (backup) {
750 FileUtils.rename(refsFile, new File(getDirectory(), "refs.old"));
751 if (packedRefs.exists()) {
752 FileUtils.rename(packedRefs, new File(getDirectory(),
753 Constants.PACKED_REFS + ".old"));
754 }
755 if (logsDir.exists()) {
756 FileUtils.rename(logsDir,
757 new File(getDirectory(), Constants.LOGS + ".old"));
758 }
759 for (String r : additional) {
760 FileUtils.rename(new File(getDirectory(), r),
761 new File(getDirectory(), r + ".old"));
762 }
763 } else {
764 packedRefs.delete();
765 FileUtils.delete(logsDir, FileUtils.RECURSIVE);
766 FileUtils.delete(refsFile, FileUtils.RECURSIVE);
767 for (String r : additional) {
768 new File(getDirectory(), r).delete();
769 }
770 }
771
772
773 FileUtils.rename(newRefs, refsFile);
774
775 refs.close();
776 refs = new FileReftableDatabase(this, refsFile);
777 }
778
779
780
781
782
783
784
785
786
787
788
789
790
791 @SuppressWarnings("nls")
792 public void convertRefStorage(String format, boolean writeLogs,
793 boolean backup) throws IOException {
794 if (format.equals("reftable")) {
795 if (refs instanceof RefDirectory) {
796 convertToReftable(writeLogs, backup);
797 }
798 } else if (format.equals("refdir")) {
799 if (refs instanceof FileReftableDatabase) {
800 convertToPackedRefs(backup);
801 }
802 } else {
803 throw new IOException(String.format(
804 "unknown supported ref storage format '%s'", format));
805 }
806 }
807 }