James Moger
2015-11-22 ed552ba47c02779c270ffd62841d6d1048dade70
commit | author | age
ec97f7 1 /*
JM 2  * Copyright 2011 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
1f9dae 16 package com.gitblit.utils;
JM 17
18 import java.io.ByteArrayOutputStream;
319342 19 import java.io.Serializable;
0f47b2 20 import java.text.MessageFormat;
008322 21 import java.util.ArrayList;
1f9dae 22 import java.util.List;
JM 23
008322 24 import org.eclipse.jgit.api.BlameCommand;
JM 25 import org.eclipse.jgit.blame.BlameResult;
1f9dae 26 import org.eclipse.jgit.diff.DiffEntry;
JM 27 import org.eclipse.jgit.diff.DiffFormatter;
008322 28 import org.eclipse.jgit.diff.RawText;
1f9dae 29 import org.eclipse.jgit.diff.RawTextComparator;
a2709d 30 import org.eclipse.jgit.lib.ObjectId;
1f9dae 31 import org.eclipse.jgit.lib.Repository;
JM 32 import org.eclipse.jgit.revwalk.RevCommit;
33 import org.eclipse.jgit.revwalk.RevTree;
34 import org.eclipse.jgit.revwalk.RevWalk;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
008322 37
JM 38 import com.gitblit.models.AnnotatedLine;
319342 39 import com.gitblit.models.PathModel.PathChangeModel;
1f9dae 40
d9f687 41 /**
JM 42  * DiffUtils is a class of utility methods related to diff, patch, and blame.
319342 43  *
d9f687 44  * The diff methods support pluggable diff output types like Gitblit, Gitweb,
JM 45  * and Plain.
319342 46  *
d9f687 47  * @author James Moger
319342 48  *
d9f687 49  */
1f9dae 50 public class DiffUtils {
JM 51
52     private static final Logger LOGGER = LoggerFactory.getLogger(DiffUtils.class);
db653a 53
88598b 54     /**
7dd99f 55      * Callback interface for binary diffs. All the getDiff methods here take an optional handler;
T 56      * if given and the {@link DiffOutputType} is {@link DiffOutputType#HTML HTML}, it is responsible
57      * for displaying a binary diff.
58      */
59     public interface BinaryDiffHandler {
60
61         /**
62          * Renders a binary diff. The result must be valid HTML, it will be inserted into an HTML table cell.
63          * May return {@code null} if the default behavior (which is typically just a textual note "Bnary
64          * files differ") is desired.
65          *
66          * @param diffEntry
67          *            current diff entry
68          *
69          * @return the rendered diff as HTML, or {@code null} if the default is desired.
70          */
71         public String renderBinaryDiff(final DiffEntry diffEntry);
72
73     }
74
75     /**
88598b 76      * Enumeration for the diff output types.
JM 77      */
a125cf 78     public static enum DiffOutputType {
025028 79         PLAIN, HTML;
a125cf 80
JM 81         public static DiffOutputType forName(String name) {
82             for (DiffOutputType type : values()) {
83                 if (type.name().equalsIgnoreCase(name)) {
84                     return type;
85                 }
86             }
87             return null;
88         }
89     }
699e71 90
319342 91     /**
cff352 92      * Enumeration for the diff comparator types.
JM 93      */
94     public static enum DiffComparator {
5d59b9 95         SHOW_WHITESPACE(RawTextComparator.DEFAULT),
JM 96         IGNORE_WHITESPACE(RawTextComparator.WS_IGNORE_ALL),
97         IGNORE_LEADING(RawTextComparator.WS_IGNORE_LEADING),
98         IGNORE_TRAILING(RawTextComparator.WS_IGNORE_TRAILING),
99         IGNORE_CHANGES(RawTextComparator.WS_IGNORE_CHANGE);
cff352 100
JM 101         public final RawTextComparator textComparator;
102
103         DiffComparator(RawTextComparator textComparator) {
104             this.textComparator = textComparator;
5d59b9 105         }
JM 106
107         public DiffComparator getOpposite() {
108             return this == SHOW_WHITESPACE ? IGNORE_WHITESPACE : SHOW_WHITESPACE;
109         }
110
111         public String getTranslationKey() {
112             return "gb." + name().toLowerCase();
cff352 113         }
JM 114
115         public static DiffComparator forName(String name) {
116             for (DiffComparator type : values()) {
117                 if (type.name().equalsIgnoreCase(name)) {
118                     return type;
119                 }
120             }
121             return null;
122         }
123     }
124
125     /**
699e71 126      * Encapsulates the output of a diff.
319342 127      */
JM 128     public static class DiffOutput implements Serializable {
129         private static final long serialVersionUID = 1L;
699e71 130
319342 131         public final DiffOutputType type;
JM 132         public final String content;
133         public final DiffStat stat;
699e71 134
319342 135         DiffOutput(DiffOutputType type, String content, DiffStat stat) {
JM 136             this.type = type;
137             this.content = content;
138             this.stat = stat;
139         }
699e71 140
319342 141         public PathChangeModel getPath(String path) {
JM 142             if (stat == null) {
143                 return null;
144             }
145             return stat.getPath(path);
146         }
147     }
148
149     /**
150      * Class that represents the number of insertions and deletions from a
151      * chunk.
152      */
153     public static class DiffStat implements Serializable {
154
155         private static final long serialVersionUID = 1L;
699e71 156
319342 157         public final List<PathChangeModel> paths = new ArrayList<PathChangeModel>();
699e71 158
319342 159         private final String commitId;
699e71 160
319342 161         public DiffStat(String commitId) {
JM 162             this.commitId = commitId;
163         }
699e71 164
319342 165         public PathChangeModel addPath(DiffEntry entry) {
JM 166             PathChangeModel pcm = PathChangeModel.from(entry, commitId);
167             paths.add(pcm);
168             return pcm;
169         }
170
171         public int getInsertions() {
172             int val = 0;
173             for (PathChangeModel entry : paths) {
174                 val += entry.insertions;
175             }
176             return val;
177         }
178
179         public int getDeletions() {
180             int val = 0;
181             for (PathChangeModel entry : paths) {
182                 val += entry.deletions;
183             }
184             return val;
185         }
699e71 186
319342 187         public PathChangeModel getPath(String path) {
JM 188             PathChangeModel stat = null;
189             for (PathChangeModel p : paths) {
190                 if (p.path.equals(path)) {
191                     stat = p;
192                     break;
193                 }
194             }
195             return stat;
699e71 196         }
319342 197
JM 198         @Override
199         public String toString() {
200             StringBuilder sb = new StringBuilder();
201             for (PathChangeModel entry : paths) {
202                 sb.append(entry.toString()).append('\n');
203             }
204             sb.setLength(sb.length() - 1);
205             return sb.toString();
206         }
207     }
699e71 208
319342 209     public static class NormalizedDiffStat implements Serializable {
699e71 210
319342 211         private static final long serialVersionUID = 1L;
699e71 212
319342 213         public final int insertions;
JM 214         public final int deletions;
215         public final int blanks;
699e71 216
319342 217         NormalizedDiffStat(int insertions, int deletions, int blanks) {
JM 218             this.insertions = insertions;
219             this.deletions = deletions;
220             this.blanks = blanks;
221         }
222     }
a125cf 223
d9f687 224     /**
JM 225      * Returns the complete diff of the specified commit compared to its primary
226      * parent.
319342 227      *
d9f687 228      * @param repository
JM 229      * @param commit
cff352 230      * @param comparator
d9f687 231      * @param outputType
310a80 232      * @param tabLength
319342 233      * @return the diff
d9f687 234      */
319342 235     public static DiffOutput getCommitDiff(Repository repository, RevCommit commit,
310a80 236             DiffComparator comparator, DiffOutputType outputType, int tabLength) {
JM 237         return getDiff(repository, null, commit, null, comparator, outputType, tabLength);
1f9dae 238     }
JM 239
d9f687 240     /**
7dd99f 241      * Returns the complete diff of the specified commit compared to its primary parent.
T 242      *
243      * @param repository
244      * @param commit
cff352 245      * @param comparator
7dd99f 246      * @param outputType
T 247      * @param handler
248      *            to use for rendering binary diffs if {@code outputType} is {@link DiffOutputType#HTML HTML}.
249      *            May be {@code null}, resulting in the default behavior.
310a80 250      * @param tabLength
7dd99f 251      * @return the diff
T 252      */
253     public static DiffOutput getCommitDiff(Repository repository, RevCommit commit,
310a80 254             DiffComparator comparator, DiffOutputType outputType, BinaryDiffHandler handler, int tabLength) {
JM 255         return getDiff(repository, null, commit, null, comparator, outputType, handler, tabLength);
7dd99f 256     }
T 257
258
259     /**
d9f687 260      * Returns the diff for the specified file or folder from the specified
JM 261      * commit compared to its primary parent.
319342 262      *
d9f687 263      * @param repository
JM 264      * @param commit
265      * @param path
cff352 266      * @param comparator
d9f687 267      * @param outputType
310a80 268      * @param tabLength
319342 269      * @return the diff
d9f687 270      */
319342 271     public static DiffOutput getDiff(Repository repository, RevCommit commit, String path,
310a80 272             DiffComparator comparator, DiffOutputType outputType, int tabLength) {
JM 273         return getDiff(repository, null, commit, path, comparator, outputType, tabLength);
1f9dae 274     }
JM 275
d9f687 276     /**
7dd99f 277      * Returns the diff for the specified file or folder from the specified
T 278      * commit compared to its primary parent.
279      *
280      * @param repository
281      * @param commit
282      * @param path
cff352 283      * @param comparator
7dd99f 284      * @param outputType
T 285      * @param handler
286      *            to use for rendering binary diffs if {@code outputType} is {@link DiffOutputType#HTML HTML}.
287      *            May be {@code null}, resulting in the default behavior.
310a80 288      * @param tabLength
7dd99f 289      * @return the diff
T 290      */
291     public static DiffOutput getDiff(Repository repository, RevCommit commit, String path,
310a80 292             DiffComparator comparator, DiffOutputType outputType, BinaryDiffHandler handler, int tabLength) {
JM 293         return getDiff(repository, null, commit, path, comparator, outputType, handler, tabLength);
7dd99f 294     }
T 295
296     /**
d9f687 297      * Returns the complete diff between the two specified commits.
319342 298      *
d9f687 299      * @param repository
JM 300      * @param baseCommit
301      * @param commit
cff352 302      * @param comparator
d9f687 303      * @param outputType
310a80 304      * @param tabLength
JM 305      *
319342 306      * @return the diff
d9f687 307      */
319342 308     public static DiffOutput getDiff(Repository repository, RevCommit baseCommit, RevCommit commit,
310a80 309             DiffComparator comparator, DiffOutputType outputType, int tabLength) {
JM 310         return getDiff(repository, baseCommit, commit, null, comparator, outputType, tabLength);
7dd99f 311     }
T 312
313     /**
314      * Returns the complete diff between the two specified commits.
315      *
316      * @param repository
317      * @param baseCommit
318      * @param commit
cff352 319      * @param comparator
7dd99f 320      * @param outputType
T 321      * @param handler
322      *            to use for rendering binary diffs if {@code outputType} is {@link DiffOutputType#HTML HTML}.
323      *            May be {@code null}, resulting in the default behavior.
310a80 324      * @param tabLength
7dd99f 325      * @return the diff
T 326      */
327     public static DiffOutput getDiff(Repository repository, RevCommit baseCommit, RevCommit commit,
310a80 328             DiffComparator comparator, DiffOutputType outputType, BinaryDiffHandler handler, int tabLength) {
JM 329         return getDiff(repository, baseCommit, commit, null, comparator, outputType, handler, tabLength);
1f9dae 330     }
JM 331
d9f687 332     /**
JM 333      * Returns the diff between two commits for the specified file.
319342 334      *
d9f687 335      * @param repository
JM 336      * @param baseCommit
337      *            if base commit is null the diff is to the primary parent of
338      *            the commit.
339      * @param commit
340      * @param path
341      *            if the path is specified, the diff is restricted to that file
342      *            or folder. if unspecified, the diff is for the entire commit.
343      * @param outputType
cff352 344      * @param diffComparator
310a80 345      * @param tabLength
319342 346      * @return the diff
d9f687 347      */
319342 348     public static DiffOutput getDiff(Repository repository, RevCommit baseCommit, RevCommit commit,
310a80 349             String path, DiffComparator diffComparator, DiffOutputType outputType, int tabLength) {
JM 350         return getDiff(repository, baseCommit, commit, path, diffComparator, outputType, null, tabLength);
7dd99f 351     }
T 352
353     /**
354      * Returns the diff between two commits for the specified file.
355      *
356      * @param repository
357      * @param baseCommit
358      *            if base commit is null the diff is to the primary parent of
359      *            the commit.
360      * @param commit
361      * @param path
362      *            if the path is specified, the diff is restricted to that file
363      *            or folder. if unspecified, the diff is for the entire commit.
cff352 364      * @param comparator
7dd99f 365      * @param outputType
T 366      * @param handler
367      *            to use for rendering binary diffs if {@code outputType} is {@link DiffOutputType#HTML HTML}.
368      *            May be {@code null}, resulting in the default behavior.
310a80 369      * @param tabLength
7dd99f 370      * @return the diff
T 371      */
cff352 372     public static DiffOutput getDiff(Repository repository, RevCommit baseCommit, RevCommit commit, String path,
310a80 373             DiffComparator comparator, DiffOutputType outputType, final BinaryDiffHandler handler, int tabLength) {
319342 374         DiffStat stat = null;
f339f5 375         String diff = null;
1f9dae 376         try {
df0ba7 377             ByteArrayOutputStream os = null;
cff352 378
1f9dae 379             DiffFormatter df;
JM 380             switch (outputType) {
025028 381             case HTML:
310a80 382                 df = new GitBlitDiffFormatter(commit.getName(), path, handler, tabLength);
1f9dae 383                 break;
JM 384             case PLAIN:
385             default:
df0ba7 386                 os = new ByteArrayOutputStream();
1f9dae 387                 df = new DiffFormatter(os);
JM 388                 break;
389             }
d9f687 390             df.setRepository(repository);
5d59b9 391             df.setDiffComparator((comparator == null ? DiffComparator.SHOW_WHITESPACE : comparator).textComparator);
1f9dae 392             df.setDetectRenames(true);
d9f687 393
JM 394             RevTree commitTree = commit.getTree();
395             RevTree baseTree;
396             if (baseCommit == null) {
a2709d 397                 if (commit.getParentCount() > 0) {
JM 398                     final RevWalk rw = new RevWalk(repository);
399                     RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
400                     rw.dispose();
401                     baseTree = parent.getTree();
402                 } else {
403                     // FIXME initial commit. no parent?!
404                     baseTree = commitTree;
405                 }
d9f687 406             } else {
JM 407                 baseTree = baseCommit.getTree();
408             }
409
f339f5 410             List<DiffEntry> diffEntries = df.scan(baseTree, commitTree);
1f9dae 411             if (path != null && path.length() > 0) {
f339f5 412                 for (DiffEntry diffEntry : diffEntries) {
JM 413                     if (diffEntry.getNewPath().equalsIgnoreCase(path)) {
414                         df.format(diffEntry);
1f9dae 415                         break;
JM 416                     }
417                 }
418             } else {
f339f5 419                 df.format(diffEntries);
1f9dae 420             }
df0ba7 421             df.flush();
025028 422             if (df instanceof GitBlitDiffFormatter) {
1f9dae 423                 // workaround for complex private methods in DiffFormatter
025028 424                 diff = ((GitBlitDiffFormatter) df).getHtml();
319342 425                 stat = ((GitBlitDiffFormatter) df).getDiffStat();
1f9dae 426             } else {
JM 427                 diff = os.toString();
428             }
429         } catch (Throwable t) {
430             LOGGER.error("failed to generate commit diff!", t);
431         }
699e71 432
319342 433         return new DiffOutput(outputType, diff, stat);
1f9dae 434     }
JM 435
d9f687 436     /**
JM 437      * Returns the diff between the two commits for the specified file or folder
438      * formatted as a patch.
319342 439      *
d9f687 440      * @param repository
JM 441      * @param baseCommit
442      *            if base commit is unspecified, the patch is generated against
443      *            the primary parent of the specified commit.
444      * @param commit
445      * @param path
446      *            if path is specified, the patch is generated only for the
447      *            specified file or folder. if unspecified, the patch is
448      *            generated for the entire diff between the two commits.
449      * @return patch as a string
450      */
451     public static String getCommitPatch(Repository repository, RevCommit baseCommit,
452             RevCommit commit, String path) {
f339f5 453         String diff = null;
1f9dae 454         try {
d9f687 455             final ByteArrayOutputStream os = new ByteArrayOutputStream();
JM 456             RawTextComparator cmp = RawTextComparator.DEFAULT;
457             PatchFormatter df = new PatchFormatter(os);
458             df.setRepository(repository);
459             df.setDiffComparator(cmp);
460             df.setDetectRenames(true);
461
462             RevTree commitTree = commit.getTree();
1f9dae 463             RevTree baseTree;
JM 464             if (baseCommit == null) {
a2709d 465                 if (commit.getParentCount() > 0) {
JM 466                     final RevWalk rw = new RevWalk(repository);
467                     RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
468                     baseTree = parent.getTree();
469                 } else {
470                     // FIXME initial commit. no parent?!
471                     baseTree = commitTree;
472                 }
1f9dae 473             } else {
JM 474                 baseTree = baseCommit.getTree();
475             }
476
f339f5 477             List<DiffEntry> diffEntries = df.scan(baseTree, commitTree);
1f9dae 478             if (path != null && path.length() > 0) {
f339f5 479                 for (DiffEntry diffEntry : diffEntries) {
JM 480                     if (diffEntry.getNewPath().equalsIgnoreCase(path)) {
481                         df.format(diffEntry);
1f9dae 482                         break;
JM 483                     }
484                 }
485             } else {
f339f5 486                 df.format(diffEntries);
1f9dae 487             }
f339f5 488             diff = df.getPatch(commit);
1f9dae 489             df.flush();
JM 490         } catch (Throwable t) {
491             LOGGER.error("failed to generate commit diff!", t);
492         }
f339f5 493         return diff;
1f9dae 494     }
008322 495
835529 496     /**
JM 497      * Returns the diffstat between the two commits for the specified file or folder.
498      *
499      * @param repository
500      * @param base
501      *            if base commit is unspecified, the diffstat is generated against
502      *            the primary parent of the specified tip.
503      * @param tip
504      * @param path
505      *            if path is specified, the diffstat is generated only for the
506      *            specified file or folder. if unspecified, the diffstat is
507      *            generated for the entire diff between the two commits.
508      * @return patch as a string
509      */
510     public static DiffStat getDiffStat(Repository repository, String base, String tip) {
511         RevCommit baseCommit = null;
512         RevCommit tipCommit = null;
dfd6f5 513         RevWalk revWalk = new RevWalk(repository);
835529 514         try {
JM 515             tipCommit = revWalk.parseCommit(repository.resolve(tip));
516             if (!StringUtils.isEmpty(base)) {
517                 baseCommit = revWalk.parseCommit(repository.resolve(base));
518             }
dfd6f5 519             return getDiffStat(repository, baseCommit, tipCommit, null);
835529 520         } catch (Exception e) {
JM 521             LOGGER.error("failed to generate diffstat!", e);
522         } finally {
523             revWalk.dispose();
524         }
dfd6f5 525         return null;
835529 526     }
JM 527
319342 528     public static DiffStat getDiffStat(Repository repository, RevCommit commit) {
JM 529         return getDiffStat(repository, null, commit, null);
530     }
531
532     /**
533      * Returns the diffstat between the two commits for the specified file or folder.
534      *
535      * @param repository
536      * @param baseCommit
537      *            if base commit is unspecified, the diffstat is generated against
538      *            the primary parent of the specified commit.
539      * @param commit
540      * @param path
541      *            if path is specified, the diffstat is generated only for the
542      *            specified file or folder. if unspecified, the diffstat is
543      *            generated for the entire diff between the two commits.
544      * @return patch as a string
545      */
546     public static DiffStat getDiffStat(Repository repository, RevCommit baseCommit,
547             RevCommit commit, String path) {
548         DiffStat stat = null;
549         try {
550             RawTextComparator cmp = RawTextComparator.DEFAULT;
551             DiffStatFormatter df = new DiffStatFormatter(commit.getName());
552             df.setRepository(repository);
553             df.setDiffComparator(cmp);
554             df.setDetectRenames(true);
555
556             RevTree commitTree = commit.getTree();
557             RevTree baseTree;
558             if (baseCommit == null) {
559                 if (commit.getParentCount() > 0) {
560                     final RevWalk rw = new RevWalk(repository);
561                     RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
562                     baseTree = parent.getTree();
563                 } else {
564                     // FIXME initial commit. no parent?!
565                     baseTree = commitTree;
566                 }
567             } else {
568                 baseTree = baseCommit.getTree();
569             }
570
571             List<DiffEntry> diffEntries = df.scan(baseTree, commitTree);
572             if (path != null && path.length() > 0) {
573                 for (DiffEntry diffEntry : diffEntries) {
574                     if (diffEntry.getNewPath().equalsIgnoreCase(path)) {
575                         df.format(diffEntry);
576                         break;
577                     }
578                 }
579             } else {
580                 df.format(diffEntries);
581             }
582             stat = df.getDiffStat();
583             df.flush();
584         } catch (Throwable t) {
585             LOGGER.error("failed to generate commit diff!", t);
586         }
587         return stat;
588     }
589
d9f687 590     /**
JM 591      * Returns the list of lines in the specified source file annotated with the
592      * source commit metadata.
319342 593      *
d9f687 594      * @param repository
JM 595      * @param blobPath
596      * @param objectId
597      * @return list of annotated lines
598      */
599     public static List<AnnotatedLine> blame(Repository repository, String blobPath, String objectId) {
008322 600         List<AnnotatedLine> lines = new ArrayList<AnnotatedLine>();
JM 601         try {
a2709d 602             ObjectId object;
f339f5 603             if (StringUtils.isEmpty(objectId)) {
a2709d 604                 object = JGitUtils.getDefaultBranch(repository);
JM 605             } else {
606                 object = repository.resolve(objectId);
f339f5 607             }
d9f687 608             BlameCommand blameCommand = new BlameCommand(repository);
008322 609             blameCommand.setFilePath(blobPath);
a2709d 610             blameCommand.setStartCommit(object);
008322 611             BlameResult blameResult = blameCommand.call();
JM 612             RawText rawText = blameResult.getResultContents();
613             int length = rawText.size();
614             for (int i = 0; i < length; i++) {
615                 RevCommit commit = blameResult.getSourceCommit(i);
616                 AnnotatedLine line = new AnnotatedLine(commit, i + 1, rawText.getString(i));
617                 lines.add(line);
618             }
619         } catch (Throwable t) {
0f47b2 620             LOGGER.error(MessageFormat.format("failed to generate blame for {0} {1}!", blobPath, objectId), t);
008322 621         }
JM 622         return lines;
623     }
699e71 624
319342 625     /**
JM 626      * Normalizes a diffstat to an N-segment display.
699e71 627      *
319342 628      * @params segments
JM 629      * @param insertions
630      * @param deletions
631      * @return a normalized diffstat
632      */
633     public static NormalizedDiffStat normalizeDiffStat(final int segments, final int insertions, final int deletions) {
634         final int total = insertions + deletions;
635         final float fi = ((float) insertions) / total;
636         int si;
637         int sd;
638         int sb;
639         if (deletions == 0) {
640             // only addition
641             si = Math.min(insertions, segments);
642             sd = 0;
643             sb = si < segments ? (segments - si) : 0;
644         } else if (insertions == 0) {
645             // only deletion
646             si = 0;
647             sd = Math.min(deletions, segments);
648             sb = sd < segments ? (segments - sd) : 0;
649         } else if (total <= segments) {
650             // total churn fits in segment display
651             si = insertions;
652             sd = deletions;
653             sb = segments - total;
654         } else if ((segments % 2) > 0 && fi > 0.45f && fi < 0.55f) {
655             // odd segment display, fairly even +/-, use even number of segments
656             si = Math.round(((float) insertions)/total * (segments - 1));
657             sd = segments - 1 - si;
658             sb = 1;
659         } else {
660             si = Math.round(((float) insertions)/total * segments);
661             sd = segments - si;
662             sb = 0;
663         }
699e71 664
319342 665         return new NormalizedDiffStat(si, sd, sb);
JM 666     }
1f9dae 667 }