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

oracle / opengrok / #3728

30 Nov 2023 04:39PM UTC coverage: 74.811% (-1.1%) from 75.91%
#3728

push

vladak
update Tomcat to 10.1.16

fixes #4492

43834 of 58593 relevant lines covered (74.81%)

0.75 hits per line

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

26.76
/opengrok-indexer/src/main/java/org/opengrok/indexer/history/BitKeeperRepository.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) 2017, James Service <jas2701@googlemail.com>
22
 * Portions Copyright (c) 2017, 2021, Oracle and/or its affiliates.
23
 * Portions Copyright (c) 2018, Chris Fraire <cfraire@me.com>.
24
 */
25
package org.opengrok.indexer.history;
26

27
import java.io.File;
28
import java.io.IOException;
29
import java.io.OutputStream;
30
import java.util.ArrayList;
31
import java.util.List;
32
import java.util.logging.Level;
33
import java.util.logging.Logger;
34
import java.util.regex.Matcher;
35
import java.util.regex.Pattern;
36

37
import org.opengrok.indexer.configuration.CommandTimeoutType;
38
import org.suigeneris.jrcs.rcs.InvalidVersionNumberException;
39
import org.suigeneris.jrcs.rcs.Version;
40
import org.opengrok.indexer.configuration.RuntimeEnvironment;
41
import org.opengrok.indexer.logger.LoggerFactory;
42
import org.opengrok.indexer.util.Executor;
43

44
/**
45
 * Access to a BitKeeper repository.
46
 *
47
 * @author James Service  {@literal <jas2701@googlemail.com>}
48
 */
49
public class BitKeeperRepository extends Repository {
50

51
    private static final Logger LOGGER = LoggerFactory.getLogger(BitKeeperRepository.class);
1✔
52

53
    private static final long serialVersionUID = 1L;
54
    /**
55
     * The property name used to obtain the client command for this repository.
56
     */
57
    public static final String CMD_PROPERTY_KEY = "org.opengrok.indexer.history.BitKeeper";
58
    /**
59
     * The command to use to access the repository if none was given explicitly.
60
     */
61
    public static final String CMD_FALLBACK = "bk";
62
    /**
63
     * The output format specification for log commands.
64
     */
65
    private static final String LOG_DSPEC =
66
            "D :DPN:\\t:REV:\\t:D_: :T: GMT:TZ:\\t:USER:$if(:RENAME:){\\t:DPN|PARENT:}\\n$each(:C:){C (:C:)\\n}";
67
    /**
68
     * The output format specification for tags commands. Versions 7.3 and greater.
69
     */
70
    private static final String TAG_DSPEC = "D :REV:\\t:D_: :T: GMT:TZ:\\n$each(:TAGS:){T (:TAGS:)\\n}";
71
    /**
72
     * The output format specification for tags commands. Versions 7.2 and less.
73
     */
74
    private static final String TAG_DSPEC_OLD = "D :REV:\\t:D_: :T: GMT:TZ:\\n$each(:TAG:){T (:TAG:)\\n}";
75
    /**
76
     * The output format specification for tags commands. Versions 7.2 and less.
77
     */
78
    private static final Version NEW_DSPEC_VERSION = new Version(7, 3);
1✔
79
    /*
80
     * Using a dspec not only makes it easier to parse, but also means we don't get tripped up by any system-wide
81
     * non-default dspecs on the box we are running on.
82
     */
83
    /**
84
     * Pattern to parse a version number from output of {@code bk --version}.
85
     */
86
    private static final Pattern VERSION_PATTERN = Pattern.compile("BitKeeper version is .*-(\\d(\\.\\d)*)");
1✔
87

88
    /**
89
     * The version of the BitKeeper executable. This affects the correct dspec to use for tags.
90
     */
91
    private Version version = null;
1✔
92

93
    /**
94
     * Constructor to construct the thing to be constructed.
95
     */
96
    public BitKeeperRepository() {
1✔
97
        type = "BitKeeper";
1✔
98
        datePatterns = new String[] {"yyyy-MM-dd HH:mm:ss z"};
1✔
99

100
        ignoredDirs.add(".bk");
1✔
101
    }
1✔
102

103
    /**
104
     * Updates working and version member variables by running {@code bk --version}.
105
     */
106
    private void ensureVersion() {
107
        if (working == null) {
1✔
108
            ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1✔
109
            final Executor exec = new Executor(new String[] {RepoCommand, "--version" });
1✔
110
            if (exec.exec(false) == 0) {
1✔
111
                working = Boolean.TRUE;
×
112
                final Matcher matcher = VERSION_PATTERN.matcher(exec.getOutputString());
×
113
                if (matcher.find()) {
×
114
                    try {
115
                        version = new Version(matcher.group(1));
×
116
                    } catch (final InvalidVersionNumberException e) {
×
117
                        assert false : "Failed to parse a version number.";
×
118
                    }
×
119
                }
120
            } else {
×
121
                working = Boolean.FALSE;
1✔
122
            }
123
            if (version == null) {
1✔
124
                version = new Version(0, 0);
1✔
125
            }
126
        }
127
    }
1✔
128

129
    /**
130
     * Returns whether file represents a BitKeeper repository. A BitKeeper repository has a folder named .bk at its
131
     * source root.
132
     *
133
     * @return ret a boolean denoting whether it is or not
134
     */
135
    @Override
136
    boolean isRepositoryFor(File file, CommandTimeoutType cmdType) {
137
        if (file.isDirectory()) {
1✔
138
            final File f = new File(file, ".bk");
1✔
139
            return f.exists() && f.isDirectory();
1✔
140
        }
141
        return false;
×
142
    }
143

144
    /**
145
     * Returns whether the BitKeeper command is working.
146
     *
147
     * @return working a boolean denoting whether it is or not
148
     */
149
    @Override
150
    public boolean isWorking() {
151
        ensureVersion();
1✔
152
        return working;
1✔
153
    }
154

155
    /**
156
     * Returns the version of the BitKeeper executable.
157
     *
158
     * @return version a Version object
159
     */
160
    public Version getVersion() {
161
        ensureVersion();
×
162
        return version;
×
163
    }
164

165
    /**
166
     * Implementation of abstract method determineBranch. BitKeeper doesn't really have branches as such.
167
     *
168
     * @return null
169
     */
170
    @Override
171
    String determineBranch(CommandTimeoutType cmdType) throws IOException {
172
        return null;
1✔
173
    }
174

175
    /**
176
     * Return the first listed pull parent of this repository BitKeeper can have multiple push parents and pul parents.
177
     *
178
     * @return parent a string denoting the parent, or null.
179
     */
180
    @Override
181
    String determineParent(CommandTimeoutType cmdType) throws IOException {
182
        final File directory = new File(getDirectoryName());
1✔
183

184
        final ArrayList<String> argv = new ArrayList<>();
1✔
185
        ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1✔
186
        argv.add(RepoCommand);
1✔
187
        argv.add("parent");
1✔
188
        argv.add("-1il");
1✔
189

190
        final Executor executor = new Executor(argv, directory,
1✔
191
                RuntimeEnvironment.getInstance().getCommandTimeout(cmdType));
1✔
192
        final int rc = executor.exec(false);
1✔
193
        final String parent = executor.getOutputString().trim();
1✔
194
        if (rc == 0) {
1✔
195
            return parent;
×
196
        } else if (parent.equals("This repository has no pull parent.")) {
1✔
197
            return null;
×
198
        } else {
199
            throw new IOException(executor.getErrorString());
1✔
200
        }
201
    }
202

203
    /* History Stuff */
204
    /*
205
     * BitKeeper has independent revisions for its individual files like CVS, but also provides changesets, which is an
206
     * atomic commit of a group of deltas to files. Changesets have their own revision numbers.
207
     *
208
     * When constructing a history then, we therefore have a choice of whether to go by file revisions, or changeset
209
     * revisions. It seemed like doing it by changeset revisions would be both a) more difficult, and b) not in tune
210
     * with how BitKeeper is actually used (although, in the interest of full disclosure, I have only been using it for
211
     * a month).
212
     */
213

214
    /**
215
     * Returns whether BitKeeper has history for its directories.
216
     *
217
     * @return false
218
     */
219
    @Override
220
    boolean hasHistoryForDirectories() {
221
        return false;
1✔
222
    }
223

224
    /**
225
     * Returns whether BitKeeper has history for a file.
226
     *
227
     * @return ret a boolean denoting whether it does or not
228
     */
229
    @Override
230
    public boolean fileHasHistory(File file) {
231
        final File absolute = file.getAbsoluteFile();
×
232
        final File directory = absolute.getParentFile();
×
233
        final String basename = absolute.getName();
×
234

235
        final ArrayList<String> argv = new ArrayList<>();
×
236
        ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
×
237
        argv.add(RepoCommand);
×
238
        argv.add("files");
×
239
        argv.add(basename);
×
240

241
        final Executor executor = new Executor(argv, directory);
×
242
        if (executor.exec(true) != 0) {
×
243
            LOGGER.log(Level.SEVERE, "Failed to check file: {0}", executor.getErrorString());
×
244
            return false;
×
245
        }
246

247
        return executor.getOutputString().trim().equals(basename);
×
248
    }
249

250
    /**
251
     * Construct a History for a file in this repository.
252
     *
253
     * @param file a file in the repository
254
     * @return history a history object
255
     */
256
    @Override
257
    History getHistory(File file) throws HistoryException {
258
        return getHistory(file, null);
×
259
    }
260

261
    /**
262
     * Construct a History for a file in this repository.
263
     *
264
     * @param file a file in the repository
265
     * @param sinceRevision omit history from before, and including, this revision
266
     * @return history a history object
267
     */
268
    @Override
269
    History getHistory(File file, String sinceRevision) throws HistoryException {
270
        final File absolute = file.getAbsoluteFile();
×
271
        final File directory = absolute.getParentFile();
×
272
        final String basename = absolute.getName();
×
273

274
        final ArrayList<String> argv = new ArrayList<>();
×
275
        ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
×
276
        argv.add(RepoCommand);
×
277
        argv.add("log");
×
278
        if (sinceRevision != null) {
×
279
            argv.add("-r" + sinceRevision + "..");
×
280
        }
281
        argv.add("-d" + LOG_DSPEC);
×
282
        argv.add(basename);
×
283

284
        final Executor executor = new Executor(argv, directory);
×
285
        final BitKeeperHistoryParser parser = new BitKeeperHistoryParser(datePatterns[0]);
×
286
        if (executor.exec(true, parser) != 0) {
×
287
            throw new HistoryException(executor.getErrorString());
×
288
        }
289

290
        final RuntimeEnvironment env = RuntimeEnvironment.getInstance();
×
291
        final History history = parser.getHistory();
×
292

293
        // Assign tags to changesets they represent
294
        // We don't need to check if this repository supports tags,
295
        // because we know it :-)
296
        if (env.isTagsEnabled()) {
×
297
            assignTagsInHistory(history);
×
298
        }
299

300
        return history;
×
301
    }
302

303
    @Override
304
    boolean getHistoryGet(OutputStream out, String parent, String basename, String revision) {
305

306
        final File directory = new File(parent).getAbsoluteFile();
×
307
        final List<String> argv = new ArrayList<>();
×
308
        ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
×
309
        argv.add(RepoCommand);
×
310
        argv.add("get");
×
311
        argv.add("-p");
×
312
        if (revision != null) {
×
313
            argv.add("-r" + revision);
×
314
        }
315
        argv.add(basename);
×
316

317
        final Executor executor = new Executor(argv, directory,
×
318
                RuntimeEnvironment.getInstance().getInteractiveCommandTimeout());
×
319
        if (executor.exec(true) != 0) {
×
320
            LOGGER.log(Level.SEVERE, "Failed to get history: {0}", executor.getErrorString());
×
321
            return false;
×
322
        }
323

324
        try {
325
            copyBytes(out::write, executor.getOutputStream());
×
326
            return true;
×
327
        } catch (IOException e) {
×
328
            LOGGER.log(Level.SEVERE, "Failed to get content for {0}",
×
329
                    basename);
330
        }
331

332
        return false;
×
333
    }
334

335
    /* Annotation Stuff */
336

337
    /**
338
     * Returns whether BitKeeper has annotation for a file. It does if it has history for the file.
339
     *
340
     * @return ret a boolean denoting whether it does or not
341
     */
342
    @Override
343
    public boolean fileHasAnnotation(File file) {
344
        return fileHasHistory(file);
×
345
    }
346

347
    /**
348
     * Annotate the specified file/revision. The options {@code -aur} to @{code bk annotate} specify that Bitkeeper will output the
349
     * last user to edit the line, the last revision the line was edited, and then the line itself, each separated by a
350
     * hard tab.
351
     *
352
     * @param file file to annotate
353
     * @param revision revision to annotate, or null for latest
354
     * @return annotation file annotation
355
     */
356
    @Override
357
    public Annotation annotate(File file, String revision) throws IOException {
358
        final File absolute = file.getCanonicalFile();
×
359
        final File directory = absolute.getParentFile();
×
360
        final String basename = absolute.getName();
×
361

362
        final ArrayList<String> argv = new ArrayList<>();
×
363
        ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
×
364
        argv.add(RepoCommand);
×
365
        argv.add("annotate");
×
366
        argv.add("-aur");
×
367
        if (revision != null) {
×
368
            argv.add("-r" + revision);
×
369
        }
370
        argv.add(basename);
×
371

372
        final Executor executor = new Executor(argv, directory,
×
373
                RuntimeEnvironment.getInstance().getInteractiveCommandTimeout());
×
374
        final BitKeeperAnnotationParser parser = new BitKeeperAnnotationParser(basename);
×
375
        int status = executor.exec(true, parser);
×
376
        if (status != 0) {
×
377
            LOGGER.log(Level.WARNING,
×
378
                    "Failed to get annotations for: \"{0}\" Exit code: {1}",
379
                    new Object[]{file.getAbsolutePath(), String.valueOf(status)});
×
380
            throw new IOException(executor.getErrorString());
×
381
        } else {
382
            return parser.getAnnotation();
×
383
        }
384
    }
385

386
    /* Tag Stuff */
387

388
    /**
389
     * Returns whether a set of tags should be constructed up front. BitKeeper tags changesets, not files, so yes.
390
     *
391
     * @return true
392
     */
393
    @Override
394
    boolean hasFileBasedTags() {
395
        return true;
×
396
    }
397

398
    /**
399
     * Returns the version of the BitKeeper executable.
400
     *
401
     * @return version a Version object
402
     */
403
    private String getTagDspec() {
404
        if (NEW_DSPEC_VERSION.compareVersions(getVersion()) <= 0) {
×
405
            return TAG_DSPEC;
×
406
        } else {
407
            return TAG_DSPEC_OLD;
×
408
        }
409
    }
410

411
    /**
412
     * Constructs a set of tags up front.
413
     *
414
     * @param directory the repository directory
415
     * @param cmdType command timeout type
416
     */
417
    @Override
418
    public void buildTagList(File directory, CommandTimeoutType cmdType) {
419
        final ArrayList<String> argv = new ArrayList<>();
×
420
        argv.add("bk");
×
421
        argv.add("tags");
×
422
        argv.add("-d" + getTagDspec());
×
423

424
        RuntimeEnvironment env = RuntimeEnvironment.getInstance();
×
425
        final Executor executor = new Executor(argv, directory, env.getCommandTimeout(cmdType));
×
426
        final BitKeeperTagParser parser = new BitKeeperTagParser(datePatterns[0]);
×
427
        int status = executor.exec(true, parser);
×
428
        if (status != 0) {
×
429
            LOGGER.log(Level.WARNING,
×
430
                    "Failed to get tags for: \"{0}\" Exit code: {1}",
431
                    new Object[]{directory.getAbsolutePath(), String.valueOf(status)});
×
432
        } else {
433
            tagList = parser.getEntries();
×
434
        }
435
    }
×
436

437
    @Override
438
    String determineCurrentVersion(CommandTimeoutType cmdType) throws IOException {
439
        return null;
1✔
440
    }
441
}
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