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

oracle / opengrok / #3691

09 Nov 2023 04:15PM UTC coverage: 74.721% (+8.6%) from 66.08%
#3691

push

web-flow
avoid annotations for binary files (#4476)

fixes #4473

6 of 13 new or added lines in 4 files covered. (46.15%)

258 existing lines in 28 files now uncovered.

43797 of 58614 relevant lines covered (74.72%)

0.75 hits per line

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

73.77
/opengrok-indexer/src/main/java/org/opengrok/indexer/history/MercurialRepository.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, 2023, Oracle and/or its affiliates. All rights reserved.
22
 * Portions Copyright (c) 2017, 2019, Chris Fraire <cfraire@me.com>.
23
 */
24
package org.opengrok.indexer.history;
25

26
import java.io.BufferedReader;
27
import java.io.File;
28
import java.io.IOException;
29
import java.io.InputStreamReader;
30
import java.io.OutputStream;
31
import java.util.ArrayList;
32
import java.util.Arrays;
33
import java.util.HashMap;
34
import java.util.List;
35
import java.util.TreeSet;
36
import java.util.function.Consumer;
37
import java.util.function.Supplier;
38
import java.util.logging.Level;
39
import java.util.logging.Logger;
40
import java.util.regex.Matcher;
41
import java.util.regex.Pattern;
42

43
import org.jetbrains.annotations.Nullable;
44
import org.opengrok.indexer.configuration.CommandTimeoutType;
45
import org.opengrok.indexer.configuration.RuntimeEnvironment;
46
import org.opengrok.indexer.logger.LoggerFactory;
47
import org.opengrok.indexer.util.BufferSink;
48
import org.opengrok.indexer.util.Executor;
49
import org.opengrok.indexer.util.LazilyInstantiate;
50
import org.opengrok.indexer.util.Progress;
51

52
/**
53
 * Access to a Mercurial repository.
54
 *
55
 */
56
public class MercurialRepository extends RepositoryWithHistoryTraversal {
57

58
    private static final Logger LOGGER = LoggerFactory.getLogger(MercurialRepository.class);
1✔
59

60
    private static final long serialVersionUID = 1L;
61

62
    public static final int MAX_CHANGESETS = 131072;
63

64
    /**
65
     * The property name used to obtain the client command for this repository.
66
     */
67
    public static final String CMD_PROPERTY_KEY = "org.opengrok.indexer.history.Mercurial";
68
    /**
69
     * The command to use to access the repository if none was given explicitly.
70
     */
71
    public static final String CMD_FALLBACK = "hg";
72

73
    /**
74
     * The boolean property and environment variable name to indicate whether
75
     * forest-extension in Mercurial adds repositories inside the repositories.
76
     */
77
    public static final String NOFOREST_PROPERTY_KEY
78
            = "org.opengrok.indexer.history.mercurial.disableForest";
79

80
    static final String CHANGESET = "changeset: ";
81
    static final String USER = "user: ";
82
    static final String DATE = "date: ";
83
    static final String DESCRIPTION = "description: ";
84
    static final String FILE_COPIES = "file_copies: ";
85
    static final String FILES = "files: ";
86
    static final String END_OF_ENTRY
87
            = "mercurial_history_end_of_entry";
88

89
    private static final String TEMPLATE_REVS = "{rev}:{node|short}\\n";
90
    private static final String TEMPLATE_STUB
91
            = CHANGESET + TEMPLATE_REVS
92
            + USER + "{author}\\n" + DATE + "{date|isodate}\\n"
93
            + DESCRIPTION + "{desc|strip|obfuscate}\\n";
94

95
    private static final String FILE_LIST = FILES + "{files}\\n";
96

97
    /**
98
     * Templates for formatting hg log output for files.
99
     */
100
    private static final String FILE_TEMPLATE = TEMPLATE_STUB
101
            + END_OF_ENTRY + "\\n";
102

103
    /**
104
     * Template for formatting {@code hg log} output for directories.
105
     */
106
    private static final String DIR_TEMPLATE_RENAMED
107
            = TEMPLATE_STUB + FILE_LIST
108
            + FILE_COPIES + "{file_copies}\\n" + END_OF_ENTRY + "\\n";
109
    private static final String DIR_TEMPLATE
110
            = TEMPLATE_STUB + FILE_LIST
111
            + END_OF_ENTRY + "\\n";
112

113
    private static final Pattern LOG_COPIES_PATTERN = Pattern.compile("^(\\d+):(.*)");
1✔
114

115
    /**
116
     * This is a static replacement for 'working' field. Effectively, check if hg is working once in a JVM
117
     * instead of calling it for every MercurialRepository instance.
118
     */
119
    private static final Supplier<Boolean> HG_IS_WORKING = LazilyInstantiate.using(MercurialRepository::isHgWorking);
1✔
120

121
    public MercurialRepository() {
1✔
122
        type = "Mercurial";
1✔
123
        datePatterns = new String[]{
1✔
124
            "yyyy-MM-dd hh:mm ZZZZ"
125
        };
126

127
        ignoredFiles.add(".hgtags");
1✔
128
        ignoredFiles.add(".hgignore");
1✔
129
        ignoredDirs.add(".hg");
1✔
130
    }
1✔
131

132
    @Override
133
    public boolean isMergeCommitsSupported() {
134
        return true;
1✔
135
    }
136

137
    /**
138
     * Return name of the branch or "default".
139
     */
140
    @Override
141
    String determineBranch(CommandTimeoutType cmdType) throws IOException {
142
        List<String> cmd = new ArrayList<>();
1✔
143
        ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1✔
144
        cmd.add(RepoCommand);
1✔
145
        cmd.add("branch");
1✔
146

147
        Executor executor = new Executor(cmd, new File(getDirectoryName()),
1✔
148
                RuntimeEnvironment.getInstance().getCommandTimeout(cmdType));
1✔
149
        if (executor.exec(false) != 0) {
1✔
150
            throw new IOException(executor.getErrorString());
1✔
151
        }
152

153
        return executor.getOutputString().trim();
1✔
154
    }
155

156
    public int getPerPartesCount() {
157
        return MAX_CHANGESETS;
1✔
158
    }
159

160
    private String getRevisionNum(String changeset) throws HistoryException {
161
        String[] parts = changeset.split(":");
1✔
162
        if (parts.length == 2) {
1✔
163
            return parts[0];
1✔
164
        } else {
165
            throw new HistoryException("Don't know how to parse changeset identifier: " + changeset);
×
166
        }
167
    }
168

169
    Executor getHistoryLogExecutor(File file, String sinceRevision, String tillRevision, boolean revisionsOnly)
170
            throws HistoryException, IOException {
171
        return getHistoryLogExecutor(file, sinceRevision, tillRevision, revisionsOnly, null);
1✔
172
    }
173

174
    /**
175
     * Get an executor to be used for retrieving the history log for the named
176
     * file or directory.
177
     *
178
     * @param file The file or directory to retrieve history for
179
     * @param sinceRevision the oldest changeset to return from the executor, or
180
     *                  {@code null} if all changesets should be returned.
181
     *                  For files this does not apply and full history is returned.
182
     * @param tillRevision end revision
183
     * @param revisionsOnly get only revision numbers
184
     * @param numRevisions number of revisions to get
185
     * @return An Executor ready to be started
186
     */
187
    Executor getHistoryLogExecutor(File file, String sinceRevision, String tillRevision, boolean revisionsOnly,
188
                                   Integer numRevisions)
189
            throws HistoryException, IOException {
190

191
        String filename = getRepoRelativePath(file);
1✔
192

193
        List<String> cmd = new ArrayList<>();
1✔
194
        ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1✔
195
        cmd.add(RepoCommand);
1✔
196
        cmd.add("log");
1✔
197

198
        if (file.isDirectory()) {
1✔
199
            // Note: assumes one of them is not null
200
            if ((sinceRevision != null) || (tillRevision != null)) {
1✔
201
                cmd.add("-r");
1✔
202
                StringBuilder stringBuilder = new StringBuilder();
1✔
203
                if (!revisionsOnly) {
1✔
204
                    stringBuilder.append("reverse(");
1✔
205
                }
206
                if (sinceRevision != null) {
1✔
207
                    stringBuilder.append(getRevisionNum(sinceRevision));
1✔
208
                }
209
                stringBuilder.append("::");
1✔
210
                if (tillRevision != null) {
1✔
211
                    stringBuilder.append(getRevisionNum(tillRevision));
1✔
212
                } else {
213
                    // If this is non-default branch we would like to get the changesets
214
                    // on that branch and also follow any changesets from the parent branch.
215
                    stringBuilder.append("'").append(getBranch()).append("'");
1✔
216
                }
217
                if (!revisionsOnly) {
1✔
218
                    stringBuilder.append(")");
1✔
219
                }
220
                cmd.add(stringBuilder.toString());
1✔
221
            } else {
1✔
222
                cmd.add("-r");
1✔
223
                cmd.add("reverse(0::'" + getBranch() + "')");
1✔
224
            }
225
        } else {
226
            // For plain files we would like to follow the complete history
227
            // (this is necessary for getting the original name in given revision
228
            // when handling renamed files)
229
            // It is not needed to filter on a branch as 'hg log' will follow
230
            // the active branch.
231
            // Due to behavior of recent Mercurial versions, it is not possible
232
            // to filter the changesets of a file based on revision.
233
            // For files this does not matter since if getHistory() is called
234
            // for a file, the file has to be renamed so we want its complete history
235
            // if renamed file handling is enabled for this repository.
236
            //
237
            // Getting history for individual files should only be done when generating history for renamed files
238
            // so the fact that filtering on sinceRevision does not work does not matter there as history
239
            // from the initial changeset is needed. The tillRevision filtering works however not
240
            // in combination with --follow so the filtering is done in MercurialHistoryParser.parse().
241
            // Even if the revision filtering worked, this approach would be probably faster and consumed less memory.
242
            if (this.isHandleRenamedFiles()) {
1✔
243
                // When using --follow, the returned revisions are from newest to oldest, hence no reverse() is needed.
244
                cmd.add("--follow");
1✔
245
            }
246
        }
247

248
        // 'hg log' will display merge changesets by default. To honor the 'mergeCommitsEnabled' tunable,
249
        // add the option no to display merges if it is false.
250
        if (!isMergeCommitsEnabled()) {
1✔
251
            cmd.add("--no-merges");
1✔
252
        }
253

254
        cmd.add("--template");
1✔
255
        if (revisionsOnly) {
1✔
256
            cmd.add(TEMPLATE_REVS);
1✔
257
        } else {
258
            if (file.isDirectory()) {
1✔
259
                cmd.add(this.isHandleRenamedFiles() ? DIR_TEMPLATE_RENAMED : DIR_TEMPLATE);
1✔
260
            } else {
261
                cmd.add(FILE_TEMPLATE);
1✔
262
            }
263
        }
264

265
        if (numRevisions != null && numRevisions > 0) {
1✔
266
            cmd.add("-l");
1✔
267
            cmd.add(numRevisions.toString());
1✔
268
        }
269

270
        if (!filename.isEmpty()) {
1✔
271
            cmd.add("--");
1✔
272
            cmd.add(filename);
1✔
273
        }
274

275
        return new Executor(cmd, new File(getDirectoryName()), sinceRevision != null);
1✔
276
    }
277

278
    /**
279
     * Try to get file contents for given revision.
280
     *
281
     * @param sink a required target sink
282
     * @param fullpath full pathname of the file
283
     * @param rev revision
284
     * @return a defined instance with {@code success} == {@code true} if no
285
     * error occurred and with non-zero {@code iterations} if some data was
286
     * transferred
287
     */
288
    private HistoryRevResult getHistoryRev(BufferSink sink, String fullpath, String rev) {
289

290
        HistoryRevResult result = new HistoryRevResult();
1✔
291
        File directory = new File(getDirectoryName());
1✔
292

293
        String revision = rev;
1✔
294
        if (rev.indexOf(':') != -1) {
1✔
295
            revision = rev.substring(0, rev.indexOf(':'));
1✔
296
        }
297

298
        try {
299
            String filename = fullpath.substring(getDirectoryName().length() + 1);
1✔
300
            ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1✔
301
            String[] argv = {RepoCommand, "cat", "-r", revision, filename};
1✔
302
            Executor executor = new Executor(Arrays.asList(argv), directory,
1✔
303
                    RuntimeEnvironment.getInstance().getInteractiveCommandTimeout());
1✔
304
            int status = executor.exec();
1✔
305
            result.iterations = copyBytes(sink, executor.getOutputStream());
1✔
306

307
            /*
308
             * If exit value of the process was not 0 then the file did
309
             * not exist or internal hg error occured.
310
             */
311
            result.success = (status == 0);
1✔
312
        } catch (Exception exp) {
×
313
            LOGGER.log(Level.SEVERE, "Failed to get history", exp);
×
314
        }
1✔
315

316
        return result;
1✔
317
    }
318

319
    /**
320
     * Get the name of file in given revision. This is used to get contents
321
     * of a file in historical revision.
322
     *
323
     * @param fullpath file path
324
     * @param fullRevToFind revision number (in the form of
325
     * {rev}:{node|short})
326
     * @return original filename
327
     */
328
    private String findOriginalName(String fullpath, String fullRevToFind) throws IOException {
329
        Matcher matcher = LOG_COPIES_PATTERN.matcher("");
1✔
330
        String file = fullpath.substring(getDirectoryName().length() + 1);
1✔
331
        ArrayList<String> argv = new ArrayList<>();
1✔
332
        File directory = new File(getDirectoryName());
1✔
333

334
        // Extract {rev} from the full revision specification string.
335
        String[] revArray = fullRevToFind.split(":");
1✔
336
        String revToFind = revArray[0];
1✔
337
        if (revToFind.isEmpty()) {
1✔
338
            LOGGER.log(Level.SEVERE, "Invalid revision string: {0}", fullRevToFind);
×
339
            return null;
×
340
        }
341

342
        /*
343
         * Get the list of file renames for given file to the specified
344
         * revision. We need to get them from the newest to the oldest
345
         * so that we can follow the renames down to the revision we are after.
346
         */
347
        argv.add(ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK));
1✔
348
        argv.add("log");
1✔
349
        argv.add("--follow");
1✔
350
        /*
351
         * hg log --follow -r behavior has changed since Mercurial 3.4
352
         * so filtering the changesets of a file no longer works with --follow.
353
         * This is tracked by https://bz.mercurial-scm.org/show_bug.cgi?id=4959
354
         * Once this is fixed and Mercurial versions with the fix are prevalent,
355
         * we can revert to the old behavior.
356
         */
357
        // argv.add("-r");
358
        // Use reverse() to get the changesets from newest to oldest.
359
        // argv.add("reverse(" + rev_to_find + ":)");
360
        argv.add("--template");
1✔
361
        argv.add("{rev}:{file_copies}\\n");
1✔
362
        argv.add(fullpath);
1✔
363

364
        Executor executor = new Executor(argv, directory,
1✔
365
                RuntimeEnvironment.getInstance().getInteractiveCommandTimeout());
1✔
366
        int status = executor.exec();
1✔
367

368
        try (BufferedReader in = new BufferedReader(
1✔
369
                new InputStreamReader(executor.getOutputStream()))) {
1✔
370
            String line;
371
            while ((line = in.readLine()) != null) {
1✔
372
                matcher.reset(line);
1✔
373
                if (!matcher.find()) {
1✔
374
                    LOGGER.log(Level.SEVERE,
×
375
                            "Failed to match: {0}", line);
376
                    return (null);
×
377
                }
378
                String rev = matcher.group(1);
1✔
379
                String content = matcher.group(2);
1✔
380

381
                if (rev.equals(revToFind)) {
1✔
382
                    break;
1✔
383
                }
384

385
                if (!content.isEmpty()) {
1✔
386
                    /*
387
                     * Split string of 'newfile1 (oldfile1)newfile2 (oldfile2) ...' into pairs of renames.
388
                     */
389
                    String[] splitArray = content.split("\\)");
1✔
390
                    for (String s : splitArray) {
1✔
391
                        /*
392
                         * This will fail for file names containing ' ('.
393
                         */
394
                        String[] move = s.split(" \\(");
1✔
395

396
                        if (file.equals(move[0])) {
1✔
397
                            file = move[1];
1✔
398
                            break;
1✔
399
                        }
400
                    }
401
                }
402
            }
1✔
403
        }
×
404

405
        if (status != 0) {
1✔
406
            LOGGER.log(Level.WARNING,
×
407
                    "Failed to get original name in revision {2} for: \"{0}\" Exit code: {1}",
408
                    new Object[]{fullpath, String.valueOf(status), fullRevToFind});
×
409
            return null;
×
410
        }
411

412
        return (fullpath.substring(0, getDirectoryName().length() + 1) + file);
1✔
413
    }
414

415
    @Override
416
    boolean getHistoryGet(OutputStream out, String parent, String basename, String rev) {
417

418
        String fullpath;
419
        try {
420
            fullpath = new File(parent, basename).getCanonicalPath();
1✔
421
        } catch (IOException exp) {
×
422
            LOGGER.log(Level.SEVERE,
×
423
                    "Failed to get canonical path: {0}", exp.getClass().toString());
×
424
            return false;
×
425
        }
1✔
426

427
        HistoryRevResult result = getHistoryRev(out::write, fullpath, rev);
1✔
428
        if (!result.success && result.iterations < 1) {
1✔
429
            /*
430
             * If we failed to get the contents it might be that the file was
431
             * renamed so we need to find its original name in that revision
432
             * and retry with the original name.
433
             */
434
            String origpath;
435
            try {
436
                origpath = findOriginalName(fullpath, rev);
1✔
437
            } catch (IOException exp) {
×
438
                LOGGER.log(Level.SEVERE,
×
439
                        "Failed to get original revision: {0}",
440
                        exp.getClass().toString());
×
441
                return false;
×
442
            }
1✔
443
            if (origpath != null && !origpath.equals(fullpath)) {
1✔
444
                result = getHistoryRev(out::write, origpath, rev);
1✔
445
            }
446
        }
447

448
        return result.success;
1✔
449
    }
450

451
    /**
452
     * Annotate the specified file/revision.
453
     *
454
     * @param file file to annotate
455
     * @param revision revision to annotate
456
     * @return file annotation
457
     * @throws java.io.IOException if I/O exception occurred
458
     */
459
    @Override
460
    public Annotation annotate(File file, String revision) throws IOException {
UNCOV
461
        ArrayList<String> argv = new ArrayList<>();
×
UNCOV
462
        ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
×
UNCOV
463
        argv.add(RepoCommand);
×
UNCOV
464
        argv.add("annotate");
×
UNCOV
465
        argv.add("-n");
×
UNCOV
466
        if (!this.isHandleRenamedFiles()) {
×
UNCOV
467
            argv.add("--no-follow");
×
468
        }
UNCOV
469
        if (revision != null) {
×
470
            argv.add("-r");
×
471
            if (revision.indexOf(':') == -1) {
×
472
                argv.add(revision);
×
473
            } else {
474
                argv.add(revision.substring(0, revision.indexOf(':')));
×
475
            }
476
        }
UNCOV
477
        argv.add(file.getName());
×
UNCOV
478
        Executor executor = new Executor(argv, file.getParentFile(),
×
UNCOV
479
                RuntimeEnvironment.getInstance().getInteractiveCommandTimeout());
×
UNCOV
480
        HashMap<String, HistoryEntry> revs = new HashMap<>();
×
481

482
        // Construct hash map for history entries from history cache. This is
483
        // needed later to get user string for particular revision.
484
        try {
UNCOV
485
            History hist = HistoryGuru.getInstance().getHistory(file, false);
×
UNCOV
486
            for (HistoryEntry e : hist.getHistoryEntries()) {
×
487
                // Chop out the colon and all hexadecimal what follows.
488
                // This is because the whole changeset identification is
489
                // stored in history index while annotate only needs the
490
                // revision identifier.
UNCOV
491
                revs.put(e.getRevision().replaceFirst(":[a-f0-9]+", ""), e);
×
UNCOV
492
            }
×
493
        } catch (HistoryException he) {
×
494
            LOGGER.log(Level.SEVERE,
×
495
                    "Error: cannot get history for file {0}", file);
496
            return null;
×
UNCOV
497
        }
×
498

UNCOV
499
        MercurialAnnotationParser annotator = new MercurialAnnotationParser(file, revs);
×
UNCOV
500
        executor.exec(true, annotator);
×
501

UNCOV
502
        return annotator.getAnnotation();
×
503
    }
504

505
    @Override
506
    protected String getRevisionForAnnotate(String historyRevision) {
507
        String[] brev = historyRevision.split(":");
×
508

509
        return brev[0];
×
510
    }
511

512
    @Override
513
    public boolean fileHasAnnotation(File file) {
514
        return true;
1✔
515
    }
516

517
    @Override
518
    public boolean fileHasHistory(File file) {
519
        // Todo: is there a cheap test for whether mercurial has history
520
        // available for a file?
521
        // Otherwise, this is harmless, since mercurial's commands will just
522
        // print nothing if there is no history.
523
        return true;
1✔
524
    }
525

526
    @Override
527
    boolean isRepositoryFor(File file, CommandTimeoutType cmdType) {
528
        if (file.isDirectory()) {
1✔
529
            File f = new File(file, ".hg");
1✔
530
            return f.exists() && f.isDirectory();
1✔
531
        }
532
        return false;
×
533
    }
534

535
    @Override
536
    boolean supportsSubRepositories() {
537
        String val = System.getenv(NOFOREST_PROPERTY_KEY);
1✔
538
        return !(val == null
1✔
539
                ? Boolean.getBoolean(NOFOREST_PROPERTY_KEY)
1✔
540
                : Boolean.parseBoolean(val));
1✔
541
    }
542

543
    /**
544
     * Gets a value indicating the instance is nestable.
545
     * @return {@code true}
546
     */
547
    @Override
548
    boolean isNestable() {
549
        return true;
1✔
550
    }
551

552
    private static boolean isHgWorking() {
553
        String repoCommand = getCommand(MercurialRepository.class, CMD_PROPERTY_KEY, CMD_FALLBACK);
1✔
554
        boolean works = checkCmd(repoCommand);
1✔
555
        if (!works) {
1✔
556
            LOGGER.log(Level.WARNING, "Command ''{0}'' does not work. " +
1✔
557
                    "Some operations with Mercurial repositories will fail as a result.", repoCommand);
558
        }
559
        return works;
1✔
560
    }
561

562
    @Override
563
    public boolean isWorking() {
564
        if (working == null) {
1✔
565
            working = HG_IS_WORKING.get();
1✔
566
            ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1✔
567
        }
568
        return working;
1✔
569
    }
570

571
    @Override
572
    boolean hasHistoryForDirectories() {
573
        return true;
1✔
574
    }
575

576
    @Override
577
    History getHistory(File file) throws HistoryException {
578
        return getHistory(file, null);
1✔
579
    }
580

581
    public void accept(String sinceRevision, Consumer<BoundaryChangesets.IdWithProgress> visitor, Progress progress)
582
            throws HistoryException {
583

584
        new MercurialHistoryParserRevisionsOnly(this, visitor, progress).
1✔
585
                parse(new File(getDirectoryName()), sinceRevision);
1✔
586
    }
1✔
587

588
    @Nullable
589
    @Override
590
    public HistoryEntry getLastHistoryEntry(File file, boolean ui) throws HistoryException {
591
        History hist = getHistory(file, null, null, 1);
1✔
592
        return hist.getLastHistoryEntry();
1✔
593
    }
594

595
    @Override
596
    History getHistory(File file, String sinceRevision) throws HistoryException {
597
        return getHistory(file, sinceRevision, null);
1✔
598
    }
599

600
    @Override
601
    History getHistory(File file, String sinceRevision, String tillRevision) throws HistoryException {
602
        return getHistory(file, sinceRevision, tillRevision, null);
1✔
603
    }
604

605
    // TODO: add a test for this
606
    public void traverseHistory(File file, String sinceRevision, String tillRevision,
607
                                Integer numCommits, List<ChangesetVisitor> visitors) throws HistoryException {
608

609
        new MercurialHistoryParser(this, visitors).
1✔
610
                parse(file, sinceRevision, tillRevision, numCommits);
1✔
611
    }
1✔
612

613
    /**
614
     * We need to create list of all tags prior to creation of HistoryEntries
615
     * per file.
616
     *
617
     * @return true.
618
     */
619
    @Override
620
    boolean hasFileBasedTags() {
UNCOV
621
        return true;
×
622
    }
623

624
    @Override
625
    protected void buildTagList(File directory, CommandTimeoutType cmdType) {
UNCOV
626
        this.tagList = new TreeSet<>();
×
UNCOV
627
        ArrayList<String> argv = new ArrayList<>();
×
UNCOV
628
        ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
×
UNCOV
629
        argv.add(RepoCommand);
×
UNCOV
630
        argv.add("tags");
×
631

UNCOV
632
        Executor executor = new Executor(argv, directory,
×
UNCOV
633
                RuntimeEnvironment.getInstance().getCommandTimeout(cmdType));
×
UNCOV
634
        MercurialTagParser parser = new MercurialTagParser();
×
UNCOV
635
        int status = executor.exec(true, parser);
×
UNCOV
636
        if (status != 0) {
×
637
            LOGGER.log(Level.WARNING,
×
638
                    "Failed to get tags for: \"{0}\" Exit code: {1}",
639
                    new Object[]{directory.getAbsolutePath(), String.valueOf(status)});
×
640
        } else {
UNCOV
641
            this.tagList = parser.getEntries();
×
642
        }
UNCOV
643
    }
×
644

645
    @Override
646
    String determineParent(CommandTimeoutType cmdType) throws IOException {
647
        File directory = new File(getDirectoryName());
1✔
648

649
        List<String> cmd = new ArrayList<>();
1✔
650
        ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1✔
651
        cmd.add(RepoCommand);
1✔
652
        cmd.add("paths");
1✔
653
        cmd.add("default");
1✔
654
        Executor executor = new Executor(cmd, directory,
1✔
655
                RuntimeEnvironment.getInstance().getCommandTimeout(cmdType));
1✔
656
        if (executor.exec(false) != 0) {
1✔
657
            throw new IOException(executor.getErrorString());
1✔
658
        }
659

660
        return executor.getOutputString().trim();
1✔
661
    }
662

663
    @Override
664
    public String determineCurrentVersion(CommandTimeoutType cmdType) throws IOException {
665
        File directory = new File(getDirectoryName());
1✔
666

667
        List<String> cmd = new ArrayList<>();
1✔
668
        ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1✔
669
        cmd.add(RepoCommand);
1✔
670
        cmd.add("log");
1✔
671
        cmd.add("-l");
1✔
672
        cmd.add("1");
1✔
673
        cmd.add("--template");
1✔
674
        cmd.add("{date|isodate} {node|short} {author} {desc|strip}");
1✔
675

676
        Executor executor = new Executor(cmd, directory,
1✔
677
                RuntimeEnvironment.getInstance().getCommandTimeout(cmdType));
1✔
678
        if (executor.exec(false) != 0) {
1✔
679
            throw new IOException(executor.getErrorString());
1✔
680
        }
681

682
        return executor.getOutputString().trim();
1✔
683
    }
684
}
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

© 2025 Coveralls, Inc