• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

oracle / opengrok / #3650

24 Oct 2023 03:07PM UTC coverage: 66.012% (-8.4%) from 74.444%
#3650

push

vladak
refactory repository history check

9 of 9 new or added lines in 1 file covered. (100.0%)

38668 of 58577 relevant lines covered (66.01%)

0.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.17
/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/Project.java
1
/*
2
 * CDDL HEADER START
3
 *
4
 * The contents of this file are subject to the terms of the
5
 * Common Development and Distribution License (the "License").
6
 * You may not use this file except in compliance with the License.
7
 *
8
 * See LICENSE.txt included in this distribution for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing Covered Code, include this CDDL HEADER in each
12
 * file and include the License file at LICENSE.txt.
13
 * If applicable, add the following below this CDDL HEADER, with the
14
 * fields enclosed by brackets "[]" replaced with your own identifying
15
 * information: Portions Copyright [yyyy] [name of copyright owner]
16
 *
17
 * CDDL HEADER END
18
 */
19

20
/*
21
 * Copyright (c) 2006, 2022, Oracle and/or its affiliates. All rights reserved.
22
 * Portions Copyright (c) 2018, Chris Fraire <cfraire@me.com>.
23
 */
24
package org.opengrok.indexer.configuration;
25

26
import java.io.File;
27
import java.io.FileNotFoundException;
28
import java.io.IOException;
29
import java.io.Serializable;
30
import java.util.Objects;
31
import java.util.Set;
32
import java.util.TreeSet;
33
import java.util.logging.Level;
34
import java.util.logging.Logger;
35
import java.util.regex.PatternSyntaxException;
36

37
import org.jetbrains.annotations.Nullable;
38
import org.jetbrains.annotations.VisibleForTesting;
39
import org.opengrok.indexer.logger.LoggerFactory;
40
import org.opengrok.indexer.util.ClassUtil;
41
import org.opengrok.indexer.util.ForbiddenSymlinkException;
42
import org.opengrok.indexer.web.Util;
43

44
import static org.opengrok.indexer.configuration.PatternUtil.compilePattern;
45

46
/**
47
 * Placeholder for the information that builds up a project.
48
 */
49
public class Project implements Comparable<Project>, Nameable, Serializable {
50

51
    private static final long serialVersionUID = 1L;
52

53
    private static final Logger LOGGER = LoggerFactory.getLogger(Project.class);
1✔
54

55
    static {
56
        ClassUtil.remarkTransientFields(Project.class);
1✔
57
    }
1✔
58

59
    /**
60
     * Path relative to source root. Uses the '/' separator on all platforms.
61
     */
62
    private String path;
63

64
    /**
65
     * This variable is very important, since it's used as the project
66
     * identifier all over xrefs and webapp.
67
     */
68
    private String name;
69

70
    /**
71
     * Size of tabs in this project. Used for displaying the xrefs correctly in
72
     * projects with non-standard tab size.
73
     */
74
    private int tabSize;
75

76
    /**
77
     * If navigate window should be opened by default when browsing the source code of this project.
78
     */
79
    private Boolean navigateWindowEnabled = null;
1✔
80

81
    /**
82
     * This flag sets per-project handling of renamed files.
83
     */
84
    private Boolean handleRenamedFiles = null;
1✔
85

86
    /**
87
     * This flag enables/disables per-project history cache.
88
     */
89
    private Boolean historyEnabled = null;
1✔
90

91
    /**
92
     * This flag enables/disables per-project annotation cache.
93
     */
94
    private Boolean annotationCacheEnabled = null;
1✔
95

96
    /**
97
     * This flag enables/disables per project merge commits.
98
     */
99
    private Boolean mergeCommitsEnabled = null;
1✔
100

101
    /**
102
     * Username to used for repository authentication. This is propagated to all repositories of this project.
103
     */
104
    private String username = null;
1✔
105
    /**
106
     * Password to used for repository authentication. This is propagated to all repositories of this project.
107
     */
108
    private String password = null;
1✔
109

110
    /**
111
     * This marks the project as (not)ready before initial index is done. this
112
     * is to avoid all/multi-project searches referencing this project from
113
     * failing.
114
     */
115
    private boolean indexed = false;
1✔
116

117
    /**
118
     * This flag sets per-project reindex based on traversing SCM history.
119
     */
120
    private Boolean historyBasedReindex = null;
1✔
121

122
    /**
123
     * Set of groups which match this project.
124
     */
125
    private transient Set<Group> groups = new TreeSet<>();
1✔
126

127
    /**
128
     * These properties override global settings, if set.
129
     */
130
    private String bugPage;
131
    private String bugPattern;
132
    private String reviewPage;
133
    private String reviewPattern;
134

135
    // This empty constructor is needed for serialization.
136
    public Project() {
1✔
137
    }
1✔
138

139
    /**
140
     * Create a project with given name.
141
     *
142
     * @param name the name of the project
143
     */
144
    public Project(String name) {
1✔
145
        this.name = name;
1✔
146
    }
1✔
147

148
    /**
149
     * Create a project with given name and path and default configuration values.
150
     *
151
     * @param name the name of the project
152
     * @param path the path of the project relative to the source root
153
     */
154
    public Project(String name, String path) {
1✔
155
        this.name = name;
1✔
156
        this.path = Util.fixPathIfWindows(path);
1✔
157
        completeWithDefaults();
1✔
158
    }
1✔
159

160
    /**
161
     * Get a textual name of this project.
162
     *
163
     * @return a textual name of the project
164
     */
165
    @Override
166
    public String getName() {
167
        return name;
1✔
168
    }
169

170
    /**
171
     * Get the path (relative from source root) where this project is located.
172
     *
173
     * @return the relative path
174
     */
175
    public String getPath() {
176
        return path;
1✔
177
    }
178

179
    public boolean isIndexed() {
180
        return indexed;
1✔
181
    }
182

183
    /**
184
     * Get the project id.
185
     *
186
     * @return the id of the project
187
     */
188
    public String getId() {
189
        return path;
1✔
190
    }
191

192
    /**
193
     * Get the tab size for this project, if tab size has been set.
194
     *
195
     * @return tab size if set, 0 otherwise
196
     * @see #hasTabSizeSetting()
197
     */
198
    public int getTabSize() {
199
        return tabSize;
1✔
200
    }
201

202
    /**
203
     * Set a textual name of this project, preferably don't use " , " in the
204
     * name, since it's used as delimiter for more projects
205
     *
206
     * XXX we should not allow setting project name after it has been
207
     * constructed because it is probably part of HashMap.
208
     *
209
     * @param name a textual name of the project
210
     */
211
    @Override
212
    public void setName(String name) {
213
        this.name = name;
1✔
214
    }
1✔
215

216
    /**
217
     * Set the path (relative from source root) this project is located.
218
     *
219
     * @param path the relative path from source root where this project is
220
     * located, starting with path separator.
221
     */
222
    public void setPath(String path) {
223
        this.path = Util.fixPathIfWindows(path);
1✔
224
    }
1✔
225

226
    public void setIndexed(boolean flag) {
227
        this.indexed = flag;
1✔
228
    }
1✔
229

230
    /**
231
     * Set tab size for this project. Used for expanding tabs to spaces in
232
     * xrefs.
233
     *
234
     * @param tabSize the size of tabs in this project
235
     */
236
    public void setTabSize(int tabSize) {
237
        this.tabSize = tabSize;
1✔
238
    }
1✔
239

240
    /**
241
     * Has this project an explicit tab size setting?
242
     *
243
     * @return {@code true} if the tab size has been set for this project, or
244
     * {@code false} if it hasn't and the default should be used
245
     */
246
    public boolean hasTabSizeSetting() {
247
        return tabSize > 0;
1✔
248
    }
249

250
    /**
251
     * Indicate whether the navigate window should be opened by default when
252
     * browsing a source code from this project.
253
     *
254
     * @return true if yes; false otherwise
255
     */
256
    public boolean isNavigateWindowEnabled() {
257
        return navigateWindowEnabled != null && navigateWindowEnabled;
1✔
258
    }
259

260
    /**
261
     * Set the value of navigateWindowEnabled.
262
     *
263
     * @param navigateWindowEnabled new value of navigateWindowEnabled
264
     */
265
    public void setNavigateWindowEnabled(boolean navigateWindowEnabled) {
266
        this.navigateWindowEnabled = navigateWindowEnabled;
1✔
267
    }
1✔
268

269
    /**
270
     * @return true if this project handles renamed files.
271
     */
272
    public boolean isHandleRenamedFiles() {
273
        return handleRenamedFiles != null && handleRenamedFiles;
1✔
274
    }
275

276
    /**
277
     * @return true if merge commits are enabled.
278
     */
279
    public boolean isMergeCommitsEnabled() {
280
        return mergeCommitsEnabled != null && mergeCommitsEnabled;
1✔
281
    }
282

283
    /**
284
     * @param flag true if project should handle renamed files, false otherwise.
285
     */
286
    public void setHandleRenamedFiles(boolean flag) {
287
        this.handleRenamedFiles = flag;
1✔
288
    }
1✔
289

290
    /**
291
     * @return true if this project should have history cache.
292
     */
293
    public boolean isHistoryEnabled() {
294
        return historyEnabled != null && historyEnabled;
1✔
295
    }
296

297
    /**
298
     * @param flag true if project should have history cache, false otherwise.
299
     */
300
    public void setHistoryEnabled(boolean flag) {
301
        this.historyEnabled = flag;
1✔
302
    }
1✔
303

304
    /**
305
     * @return true if this project should have annotation cache.
306
     */
307
    public boolean isAnnotationCacheEnabled() {
308
        return annotationCacheEnabled != null && annotationCacheEnabled;
1✔
309
    }
310

311
    /**
312
     * @param flag true if project should have annotation cache, false otherwise.
313
     */
314
    public void setAnnotationCacheEnabled(boolean flag) {
315
        this.annotationCacheEnabled = flag;
1✔
316
    }
1✔
317

318
    /**
319
     * @param flag true if project's repositories should deal with merge commits.
320
     */
321
    public void setMergeCommitsEnabled(boolean flag) {
322
        this.mergeCommitsEnabled = flag;
1✔
323
    }
1✔
324

325
    /**
326
     * @return true if this project handles renamed files.
327
     */
328
    public boolean isHistoryBasedReindex() {
329
        return historyBasedReindex != null && historyBasedReindex;
1✔
330
    }
331

332
    /**
333
     * @param flag true if project should handle renamed files, false otherwise.
334
     */
335
    public void setHistoryBasedReindex(boolean flag) {
336
        this.historyBasedReindex = flag;
1✔
337
    }
1✔
338

339
    /**
340
     * Set username to be used for repository authentication.
341
     * @param username username
342
     */
343
    public void setUsername(String username) {
344
        this.username = username;
1✔
345
    }
1✔
346

347
    /**
348
     * @return username used for repository authentication
349
     */
350
    public String getUsername() {
351
        return username;
1✔
352
    }
353

354
    /**
355
     * Set password to be used for repository authentication.
356
     * @param password password
357
     */
358
    public void setPassword(String password) {
359
        this.password = password;
1✔
360
    }
1✔
361

362
    /**
363
     * @return password used for repository authentication
364
     */
365
    public String getPassword() {
366
        return password;
1✔
367
    }
368

369
    @VisibleForTesting
370
    public void clearProperties() {
371
        historyBasedReindex = null;
1✔
372
        mergeCommitsEnabled = null;
1✔
373
        historyEnabled = null;
1✔
374
        annotationCacheEnabled = null;
1✔
375
        handleRenamedFiles = null;
1✔
376
    }
1✔
377

378
    /**
379
     * Return groups where this project belongs.
380
     *
381
     * @return set of groups|empty if none
382
     */
383
    public Set<Group> getGroups() {
384
        return groups;
1✔
385
    }
386

387
    public void setGroups(Set<Group> groups) {
388
        this.groups = groups;
×
389
    }
×
390

391
    /**
392
     * Adds a group where this project belongs.
393
     *
394
     * @param group group to add
395
     */
396
    public void addGroup(Group group) {
397
        while (group != null) {
1✔
398
            this.groups.add(group);
1✔
399
            group = group.getParent();
1✔
400
        }
401
    }
1✔
402

403
    public void setBugPage(String bugPage) {
404
        this.bugPage = bugPage;
1✔
405
    }
1✔
406

407
    public String getBugPage() {
408
        if (bugPage != null) {
1✔
409
            return bugPage;
1✔
410
        } else {
411
            return RuntimeEnvironment.getInstance().getBugPage();
1✔
412
        }
413
    }
414

415
    /**
416
     * Set the bug pattern to a new value.
417
     *
418
     * @param bugPattern the new pattern
419
     * @throws PatternSyntaxException when the pattern is not a valid regexp or
420
     * does not contain at least one capture group and the group does not
421
     * contain a single character
422
     */
423
    public void setBugPattern(String bugPattern) throws PatternSyntaxException {
424
        this.bugPattern = compilePattern(bugPattern);
1✔
425
    }
1✔
426

427
    public String getBugPattern() {
428
        if (bugPattern != null) {
1✔
429
            return bugPattern;
1✔
430
        } else {
431
            return RuntimeEnvironment.getInstance().getBugPattern();
1✔
432
        }
433
    }
434

435
    public String getReviewPage() {
436
        if (reviewPage != null) {
1✔
437
            return reviewPage;
1✔
438
        } else {
439
            return RuntimeEnvironment.getInstance().getReviewPage();
1✔
440
        }
441
    }
442

443
    public void setReviewPage(String reviewPage) {
444
        this.reviewPage = reviewPage;
1✔
445
    }
1✔
446

447
    public String getReviewPattern() {
448
        if (reviewPattern != null) {
1✔
449
            return reviewPattern;
1✔
450
        } else {
451
            return RuntimeEnvironment.getInstance().getReviewPattern();
1✔
452
        }
453
    }
454

455
    /**
456
     * Set the review pattern to a new value.
457
     *
458
     * @param reviewPattern the new pattern
459
     * @throws PatternSyntaxException when the pattern is not a valid regexp or
460
     * does not contain at least one capture group and the group does not
461
     * contain a single character
462
     */
463
    public void setReviewPattern(String reviewPattern) throws PatternSyntaxException {
464
        this.reviewPattern = compilePattern(reviewPattern);
1✔
465
    }
1✔
466

467
    /**
468
     * Fill the project with the current configuration where the applicable
469
     * project property has a default value.
470
     */
471
    public final void completeWithDefaults() {
472
        Configuration defaultCfg = new Configuration();
1✔
473
        final RuntimeEnvironment env = RuntimeEnvironment.getInstance();
1✔
474

475
        /*
476
         * Choosing strategy for properties (tabSize used as example here):
477
         * <pre>
478
         * this       cfg        defaultCfg   chosen value
479
         * ===============================================
480
         *  |5|        4             0             5
481
         *   0        |4|            0             4
482
         * </pre>
483
         *
484
         * The strategy is:
485
         * 1) if the project has some non-default value; use that
486
         * 2) if the project has a default value; use the provided configuration
487
         */
488
        if (getTabSize() == defaultCfg.getTabSize()) {
1✔
489
            setTabSize(env.getTabSize());
1✔
490
        }
491

492
        // Allow project to override global setting of renamed file handling.
493
        if (handleRenamedFiles == null) {
1✔
494
            setHandleRenamedFiles(env.isHandleHistoryOfRenamedFiles());
1✔
495
        }
496

497
        // Allow project to override global setting of history cache generation.
498
        if (historyEnabled == null) {
1✔
499
            setHistoryEnabled(env.isHistoryEnabled());
1✔
500
        }
501

502
        // Allow project to override global setting of annotation cache generation.
503
        if (annotationCacheEnabled == null) {
1✔
504
            setAnnotationCacheEnabled(env.isAnnotationCacheEnabled());
1✔
505
        }
506

507
        // Allow project to override global setting of navigate window.
508
        if (navigateWindowEnabled == null) {
1✔
509
            setNavigateWindowEnabled(env.isNavigateWindowEnabled());
1✔
510
        }
511

512
        // Allow project to override global setting of merge commits.
513
        if (mergeCommitsEnabled == null) {
1✔
514
            setMergeCommitsEnabled(env.isMergeCommitsEnabled());
1✔
515
        }
516

517
        if (bugPage == null) {
1✔
518
            setBugPage(env.getBugPage());
1✔
519
        }
520
        if (bugPattern == null) {
1✔
521
            setBugPattern(env.getBugPattern());
1✔
522
        }
523

524
        if (reviewPage == null) {
1✔
525
            setReviewPage(env.getReviewPage());
1✔
526
        }
527
        if (reviewPattern == null) {
1✔
528
            setReviewPattern(env.getReviewPattern());
1✔
529
        }
530

531
        if (historyBasedReindex == null) {
1✔
532
            setHistoryBasedReindex(env.isHistoryBasedReindex());
1✔
533
        }
534
    }
1✔
535

536
    /**
537
     * Get the project for a specific file.
538
     *
539
     * @param path the file to lookup (relative to source root)
540
     * @return the project that this file belongs to (or null if the file
541
     * doesn't belong to a project)
542
     */
543
    public static Project getProject(String path) {
544
        // Try to match each project path as prefix of the given path.
545
        final RuntimeEnvironment env = RuntimeEnvironment.getInstance();
1✔
546
        if (env.hasProjects()) {
1✔
547
            final String lpath = Util.fixPathIfWindows(path);
1✔
548
            for (Project p : env.getProjectList()) {
1✔
549
                String projectPath = p.getPath();
1✔
550
                if (projectPath == null) {
1✔
551
                    LOGGER.log(Level.WARNING, "Path of project {0} is not set", p.getName());
×
552
                    return null;
×
553
                }
554

555
                // Check if the project's path is a prefix of the given
556
                // path. It has to be an exact match, or the project's path
557
                // must be immediately followed by a separator. "/foo" is
558
                // a prefix for "/foo" and "/foo/bar", but not for "/foof".
559
                if (lpath.startsWith(projectPath)
1✔
560
                        && (projectPath.length() == lpath.length()
1✔
561
                        || lpath.charAt(projectPath.length()) == '/')) {
1✔
562
                    return p;
1✔
563
                }
564
            }
1✔
565
        }
566

567
        return null;
1✔
568
    }
569

570
    /**
571
     * Get the project for a specific file.
572
     *
573
     * @param file file under source root
574
     * @return the project that this file belongs to (or {@code null} if the file doesn't belong to a project,
575
     * or it is a symbolic link that is not allowed)
576
     */
577
    @Nullable
578
    public static Project getProject(File file) {
579
        Project ret = null;
1✔
580
        try {
581
            ret = getProject(RuntimeEnvironment.getInstance().getPathRelativeToSourceRoot(file));
1✔
582
        } catch (FileNotFoundException e) { // NOPMD
×
583
            // ignore if not under source root
584
        } catch (ForbiddenSymlinkException e) {
×
585
            LOGGER.log(Level.FINER, e.getMessage());
×
586
            // ignore
587
        } catch (IOException e) { // NOPMD
×
588
            // problem has already been logged, just return null
589
        }
1✔
590
        return ret;
1✔
591
    }
592

593
    /**
594
     * Returns project object by its name, used in webapp to figure out which
595
     * project is to be searched.
596
     *
597
     * @param name name of the project
598
     * @return project that fits the name
599
     */
600
    public static Project getByName(String name) {
601
        RuntimeEnvironment env = RuntimeEnvironment.getInstance();
1✔
602
        if (env.hasProjects()) {
1✔
603
            Project proj;
604
            if ((proj = env.getProjects().get(name)) != null) {
1✔
605
                return (proj);
1✔
606
            }
607
        }
608
        return null;
1✔
609
    }
610

611
    @Override
612
    public int compareTo(Project p2) {
613
        return getName().compareTo(p2.getName());
1✔
614
    }
615

616
    @Override
617
    public int hashCode() {
618
        return Objects.hashCode(name);
1✔
619
    }
620

621
    @Override
622
    public boolean equals(Object obj) {
623
        if (this == obj) {
1✔
624
            return true;
1✔
625
        }
626
        if (obj == null) {
1✔
627
            return false;
×
628
        }
629
        if (getClass() != obj.getClass()) {
1✔
630
            return false;
×
631
        }
632
        final Project other = (Project) obj;
1✔
633

634
        int numNull = (name == null ? 1 : 0) + (other.name == null ? 1 : 0);
1✔
635
        switch (numNull) {
1✔
636
            case 0:
637
                return name.equals(other.name);
1✔
638
            case 1:
639
                return false;
×
640
            default:
641
                return true;
1✔
642
        }
643
    }
644

645
    @Override
646
    public String toString() {
647
        return getName() + ":indexed=" + isIndexed() + ",history=" + isHistoryEnabled();
1✔
648
    }
649
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc