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 java.io.File;
50 import java.io.FileInputStream;
51 import java.io.FileNotFoundException;
52 import java.io.IOException;
53 import java.text.MessageFormat;
54 import java.text.ParseException;
55 import java.util.HashSet;
56 import java.util.Locale;
57 import java.util.Objects;
58 import java.util.Set;
59
60 import org.eclipse.jgit.annotations.Nullable;
61 import org.eclipse.jgit.api.errors.JGitInternalException;
62 import org.eclipse.jgit.attributes.AttributesNode;
63 import org.eclipse.jgit.attributes.AttributesNodeProvider;
64 import org.eclipse.jgit.errors.ConfigInvalidException;
65 import org.eclipse.jgit.events.ConfigChangedEvent;
66 import org.eclipse.jgit.events.ConfigChangedListener;
67 import org.eclipse.jgit.events.IndexChangedEvent;
68 import org.eclipse.jgit.internal.JGitText;
69 import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateHandle;
70 import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateRepository;
71 import org.eclipse.jgit.internal.storage.reftree.RefTreeDatabase;
72 import org.eclipse.jgit.lib.BaseRepositoryBuilder;
73 import org.eclipse.jgit.lib.ConfigConstants;
74 import org.eclipse.jgit.lib.Constants;
75 import org.eclipse.jgit.lib.CoreConfig.HideDotFiles;
76 import org.eclipse.jgit.lib.CoreConfig.SymLinks;
77 import org.eclipse.jgit.lib.ObjectId;
78 import org.eclipse.jgit.lib.ProgressMonitor;
79 import org.eclipse.jgit.lib.Ref;
80 import org.eclipse.jgit.lib.RefDatabase;
81 import org.eclipse.jgit.lib.RefUpdate;
82 import org.eclipse.jgit.lib.ReflogReader;
83 import org.eclipse.jgit.lib.Repository;
84 import org.eclipse.jgit.storage.file.FileBasedConfig;
85 import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
86 import org.eclipse.jgit.storage.pack.PackConfig;
87 import org.eclipse.jgit.util.FS;
88 import org.eclipse.jgit.util.FileUtils;
89 import org.eclipse.jgit.util.IO;
90 import org.eclipse.jgit.util.RawParseUtils;
91 import org.eclipse.jgit.util.StringUtils;
92 import org.eclipse.jgit.util.SystemReader;
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118 public class FileRepository extends Repository {
119 private static final String UNNAMED = "Unnamed repository; edit this file to name it for gitweb.";
120
121 private final FileBasedConfig systemConfig;
122 private final FileBasedConfig userConfig;
123 private final FileBasedConfig repoConfig;
124 private final RefDatabase refs;
125 private final ObjectDirectory objectDatabase;
126
127 private final Object snapshotLock = new Object();
128
129
130 private FileSnapshot snapshot;
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152 public FileRepository(File gitDir) throws IOException {
153 this(new FileRepositoryBuilder().setGitDir(gitDir).setup());
154 }
155
156
157
158
159
160
161
162
163
164
165
166 public FileRepository(String gitDir) throws IOException {
167 this(new File(gitDir));
168 }
169
170
171
172
173
174
175
176
177
178
179 public FileRepository(BaseRepositoryBuilder options) throws IOException {
180 super(options);
181
182 if (StringUtils.isEmptyOrNull(SystemReader.getInstance().getenv(
183 Constants.GIT_CONFIG_NOSYSTEM_KEY)))
184 systemConfig = SystemReader.getInstance().openSystemConfig(null,
185 getFS());
186 else
187 systemConfig = new FileBasedConfig(null, FS.DETECTED) {
188 @Override
189 public void load() {
190
191 }
192
193 @Override
194 public boolean isOutdated() {
195
196 return false;
197 }
198 };
199 userConfig = SystemReader.getInstance().openUserConfig(systemConfig,
200 getFS());
201 repoConfig = new FileBasedConfig(userConfig, getFS().resolve(
202 getDirectory(), Constants.CONFIG),
203 getFS());
204
205 loadSystemConfig();
206 loadUserConfig();
207 loadRepoConfig();
208
209 repoConfig.addChangeListener(new ConfigChangedListener() {
210 @Override
211 public void onConfigChanged(ConfigChangedEvent event) {
212 fireEvent(event);
213 }
214 });
215
216 final long repositoryFormatVersion = getConfig().getLong(
217 ConfigConstants.CONFIG_CORE_SECTION, null,
218 ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);
219
220 String reftype = repoConfig.getString(
221 "extensions", null, "refStorage");
222 if (repositoryFormatVersion >= 1 && reftype != null) {
223 if (StringUtils.equalsIgnoreCase(reftype, "reftree")) {
224 refs = new RefTreeDatabase(this, new RefDirectory(this));
225 } else {
226 throw new IOException(JGitText.get().unknownRepositoryFormat);
227 }
228 } else {
229 refs = new RefDirectory(this);
230 }
231
232 objectDatabase = new ObjectDirectory(repoConfig,
233 options.getObjectDirectory(),
234 options.getAlternateObjectDirectories(),
235 getFS(),
236 new File(getDirectory(), Constants.SHALLOW));
237
238 if (objectDatabase.exists()) {
239 if (repositoryFormatVersion > 1)
240 throw new IOException(MessageFormat.format(
241 JGitText.get().unknownRepositoryFormat2,
242 Long.valueOf(repositoryFormatVersion)));
243 }
244
245 if (!isBare()) {
246 snapshot = FileSnapshot.save(getIndexFile());
247 }
248 }
249
250 private void loadSystemConfig() throws IOException {
251 try {
252 systemConfig.load();
253 } catch (ConfigInvalidException e) {
254 throw new IOException(MessageFormat.format(JGitText
255 .get().systemConfigFileInvalid, systemConfig.getFile()
256 .getAbsolutePath(),
257 e), e);
258 }
259 }
260
261 private void loadUserConfig() throws IOException {
262 try {
263 userConfig.load();
264 } catch (ConfigInvalidException e) {
265 throw new IOException(MessageFormat.format(JGitText
266 .get().userConfigFileInvalid, userConfig.getFile()
267 .getAbsolutePath(),
268 e), e);
269 }
270 }
271
272 private void loadRepoConfig() throws IOException {
273 try {
274 repoConfig.load();
275 } catch (ConfigInvalidException e) {
276 throw new IOException(JGitText.get().unknownRepositoryFormat, e);
277 }
278 }
279
280
281
282
283
284
285
286 @Override
287 public void create(boolean bare) throws IOException {
288 final FileBasedConfig cfg = getConfig();
289 if (cfg.getFile().exists()) {
290 throw new IllegalStateException(MessageFormat.format(
291 JGitText.get().repositoryAlreadyExists, getDirectory()));
292 }
293 FileUtils.mkdirs(getDirectory(), true);
294 HideDotFiles hideDotFiles = getConfig().getEnum(
295 ConfigConstants.CONFIG_CORE_SECTION, null,
296 ConfigConstants.CONFIG_KEY_HIDEDOTFILES,
297 HideDotFiles.DOTGITONLY);
298 if (hideDotFiles != HideDotFiles.FALSE && !isBare()
299 && getDirectory().getName().startsWith("."))
300 getFS().setHidden(getDirectory(), true);
301 refs.create();
302 objectDatabase.create();
303
304 FileUtils.mkdir(new File(getDirectory(), "branches"));
305 FileUtils.mkdir(new File(getDirectory(), "hooks"));
306
307 RefUpdate head = updateRef(Constants.HEAD);
308 head.disableRefLog();
309 head.link(Constants.R_HEADS + Constants.MASTER);
310
311 final boolean fileMode;
312 if (getFS().supportsExecute()) {
313 File tmp = File.createTempFile("try", "execute", getDirectory());
314
315 getFS().setExecute(tmp, true);
316 final boolean on = getFS().canExecute(tmp);
317
318 getFS().setExecute(tmp, false);
319 final boolean off = getFS().canExecute(tmp);
320 FileUtils.delete(tmp);
321
322 fileMode = on && !off;
323 } else {
324 fileMode = false;
325 }
326
327 SymLinks symLinks = SymLinks.FALSE;
328 if (getFS().supportsSymlinks()) {
329 File tmp = new File(getDirectory(), "tmplink");
330 try {
331 getFS().createSymLink(tmp, "target");
332 symLinks = null;
333 FileUtils.delete(tmp);
334 } catch (IOException e) {
335
336 }
337 }
338 if (symLinks != null)
339 cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
340 ConfigConstants.CONFIG_KEY_SYMLINKS, symLinks.name()
341 .toLowerCase(Locale.ROOT));
342 cfg.setInt(ConfigConstants.CONFIG_CORE_SECTION, null,
343 ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);
344 cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
345 ConfigConstants.CONFIG_KEY_FILEMODE, fileMode);
346 if (bare)
347 cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
348 ConfigConstants.CONFIG_KEY_BARE, true);
349 cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
350 ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, !bare);
351 if (SystemReader.getInstance().isMacOS())
352
353 cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
354 ConfigConstants.CONFIG_KEY_PRECOMPOSEUNICODE, true);
355 if (!bare) {
356 File workTree = getWorkTree();
357 if (!getDirectory().getParentFile().equals(workTree)) {
358 cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
359 ConfigConstants.CONFIG_KEY_WORKTREE, getWorkTree()
360 .getAbsolutePath());
361 LockFile dotGitLockFile = new LockFile(new File(workTree,
362 Constants.DOT_GIT));
363 try {
364 if (dotGitLockFile.lock()) {
365 dotGitLockFile.write(Constants.encode(Constants.GITDIR
366 + getDirectory().getAbsolutePath()));
367 dotGitLockFile.commit();
368 }
369 } finally {
370 dotGitLockFile.unlock();
371 }
372 }
373 }
374 cfg.save();
375 }
376
377
378
379
380
381
382 public File getObjectsDirectory() {
383 return objectDatabase.getDirectory();
384 }
385
386
387 @Override
388 public ObjectDirectory getObjectDatabase() {
389 return objectDatabase;
390 }
391
392
393 @Override
394 public RefDatabase getRefDatabase() {
395 return refs;
396 }
397
398
399 @Override
400 public FileBasedConfig getConfig() {
401 if (systemConfig.isOutdated()) {
402 try {
403 loadSystemConfig();
404 } catch (IOException e) {
405 throw new RuntimeException(e);
406 }
407 }
408 if (userConfig.isOutdated()) {
409 try {
410 loadUserConfig();
411 } catch (IOException e) {
412 throw new RuntimeException(e);
413 }
414 }
415 if (repoConfig.isOutdated()) {
416 try {
417 loadRepoConfig();
418 } catch (IOException e) {
419 throw new RuntimeException(e);
420 }
421 }
422 return repoConfig;
423 }
424
425
426 @Override
427 @Nullable
428 public String getGitwebDescription() throws IOException {
429 String d;
430 try {
431 d = RawParseUtils.decode(IO.readFully(descriptionFile()));
432 } catch (FileNotFoundException err) {
433 return null;
434 }
435 if (d != null) {
436 d = d.trim();
437 if (d.isEmpty() || UNNAMED.equals(d)) {
438 return null;
439 }
440 }
441 return d;
442 }
443
444
445 @Override
446 public void setGitwebDescription(@Nullable String description)
447 throws IOException {
448 String old = getGitwebDescription();
449 if (Objects.equals(old, description)) {
450 return;
451 }
452
453 File path = descriptionFile();
454 LockFile lock = new LockFile(path);
455 if (!lock.lock()) {
456 throw new IOException(MessageFormat.format(JGitText.get().lockError,
457 path.getAbsolutePath()));
458 }
459 try {
460 String d = description;
461 if (d != null) {
462 d = d.trim();
463 if (!d.isEmpty()) {
464 d += '\n';
465 }
466 } else {
467 d = "";
468 }
469 lock.write(Constants.encode(d));
470 lock.commit();
471 } finally {
472 lock.unlock();
473 }
474 }
475
476 private File descriptionFile() {
477 return new File(getDirectory(), "description");
478 }
479
480
481
482
483
484
485
486
487
488
489
490 @Override
491 public Set<ObjectId> getAdditionalHaves() {
492 return getAdditionalHaves(null);
493 }
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508 private Set<ObjectId> getAdditionalHaves(Set<AlternateHandle.Id> skips) {
509 HashSet<ObjectId> r = new HashSet<>();
510 skips = objectDatabase.addMe(skips);
511 for (AlternateHandle d : objectDatabase.myAlternates()) {
512 if (d instanceof AlternateRepository && !skips.contains(d.getId())) {
513 FileRepository repo;
514
515 repo = ((AlternateRepository) d).repository;
516 for (Ref ref : repo.getAllRefs().values()) {
517 if (ref.getObjectId() != null)
518 r.add(ref.getObjectId());
519 if (ref.getPeeledObjectId() != null)
520 r.add(ref.getPeeledObjectId());
521 }
522 r.addAll(repo.getAdditionalHaves(skips));
523 }
524 }
525 return r;
526 }
527
528
529
530
531
532
533
534
535
536
537 public void openPack(File pack) throws IOException {
538 objectDatabase.openPack(pack);
539 }
540
541
542 @Override
543 public void scanForRepoChanges() throws IOException {
544 getRefDatabase().getRefs();
545 detectIndexChanges();
546 }
547
548
549 private void detectIndexChanges() {
550 if (isBare()) {
551 return;
552 }
553
554 File indexFile = getIndexFile();
555 synchronized (snapshotLock) {
556 if (snapshot == null) {
557 snapshot = FileSnapshot.save(indexFile);
558 return;
559 }
560 if (!snapshot.isModified(indexFile)) {
561 return;
562 }
563 }
564 notifyIndexChanged(false);
565 }
566
567
568 @Override
569 public void notifyIndexChanged(boolean internal) {
570 synchronized (snapshotLock) {
571 snapshot = FileSnapshot.save(getIndexFile());
572 }
573 fireEvent(new IndexChangedEvent(internal));
574 }
575
576
577 @Override
578 public ReflogReader getReflogReader(String refName) throws IOException {
579 Ref ref = findRef(refName);
580 if (ref != null)
581 return new ReflogReaderImpl(this, ref.getName());
582 return null;
583 }
584
585
586 @Override
587 public AttributesNodeProvider createAttributesNodeProvider() {
588 return new AttributesNodeProviderImpl(this);
589 }
590
591
592
593
594
595
596
597
598 static class AttributesNodeProviderImpl implements
599 AttributesNodeProvider {
600
601 private AttributesNode infoAttributesNode;
602
603 private AttributesNode globalAttributesNode;
604
605
606
607
608
609
610
611 protected AttributesNodeProviderImpl(Repository repo) {
612 infoAttributesNode = new InfoAttributesNode(repo);
613 globalAttributesNode = new GlobalAttributesNode(repo);
614 }
615
616 @Override
617 public AttributesNode getInfoAttributesNode() throws IOException {
618 if (infoAttributesNode instanceof InfoAttributesNode)
619 infoAttributesNode = ((InfoAttributesNode) infoAttributesNode)
620 .load();
621 return infoAttributesNode;
622 }
623
624 @Override
625 public AttributesNode getGlobalAttributesNode() throws IOException {
626 if (globalAttributesNode instanceof GlobalAttributesNode)
627 globalAttributesNode = ((GlobalAttributesNode) globalAttributesNode)
628 .load();
629 return globalAttributesNode;
630 }
631
632 static void loadRulesFromFile(AttributesNode r, File attrs)
633 throws FileNotFoundException, IOException {
634 if (attrs.exists()) {
635 try (FileInputStream in = new FileInputStream(attrs)) {
636 r.parse(in);
637 }
638 }
639 }
640
641 }
642
643 private boolean shouldAutoDetach() {
644 return getConfig().getBoolean(ConfigConstants.CONFIG_GC_SECTION,
645 ConfigConstants.CONFIG_KEY_AUTODETACH, true);
646 }
647
648
649 @Override
650 public void autoGC(ProgressMonitor monitor) {
651 GC gc = new GC(this);
652 gc.setPackConfig(new PackConfig(this));
653 gc.setProgressMonitor(monitor);
654 gc.setAuto(true);
655 gc.setBackground(shouldAutoDetach());
656 try {
657 gc.gc();
658 } catch (ParseException | IOException e) {
659 throw new JGitInternalException(JGitText.get().gcFailed, e);
660 }
661 }
662 }