James Moger
2016-01-25 252dc07d7f85cc344b5919bb7c6166ef84b2102e
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;
46f33f 160         
PM 161         private final Repository repository;
699e71 162
46f33f 163         public DiffStat(String commitId, Repository repository) {
319342 164             this.commitId = commitId;
46f33f 165             this.repository = repository;
319342 166         }
699e71 167
319342 168         public PathChangeModel addPath(DiffEntry entry) {
46f33f 169             PathChangeModel pcm = PathChangeModel.from(entry, commitId, repository);
319342 170             paths.add(pcm);
JM 171             return pcm;
172         }
173
174         public int getInsertions() {
175             int val = 0;
176             for (PathChangeModel entry : paths) {
177                 val += entry.insertions;
178             }
179             return val;
180         }
181
182         public int getDeletions() {
183             int val = 0;
184             for (PathChangeModel entry : paths) {
185                 val += entry.deletions;
186             }
187             return val;
188         }
699e71 189
319342 190         public PathChangeModel getPath(String path) {
JM 191             PathChangeModel stat = null;
192             for (PathChangeModel p : paths) {
193                 if (p.path.equals(path)) {
194                     stat = p;
195                     break;
196                 }
197             }
198             return stat;
699e71 199         }
319342 200
JM 201         @Override
202         public String toString() {
203             StringBuilder sb = new StringBuilder();
204             for (PathChangeModel entry : paths) {
205                 sb.append(entry.toString()).append('\n');
206             }
207             sb.setLength(sb.length() - 1);
208             return sb.toString();
209         }
210     }
699e71 211
319342 212     public static class NormalizedDiffStat implements Serializable {
699e71 213
319342 214         private static final long serialVersionUID = 1L;
699e71 215
319342 216         public final int insertions;
JM 217         public final int deletions;
218         public final int blanks;
699e71 219
319342 220         NormalizedDiffStat(int insertions, int deletions, int blanks) {
JM 221             this.insertions = insertions;
222             this.deletions = deletions;
223             this.blanks = blanks;
224         }
225     }
a125cf 226
d9f687 227     /**
JM 228      * Returns the complete diff of the specified commit compared to its primary
229      * parent.
319342 230      *
d9f687 231      * @param repository
JM 232      * @param commit
cff352 233      * @param comparator
d9f687 234      * @param outputType
310a80 235      * @param tabLength
319342 236      * @return the diff
d9f687 237      */
319342 238     public static DiffOutput getCommitDiff(Repository repository, RevCommit commit,
310a80 239             DiffComparator comparator, DiffOutputType outputType, int tabLength) {
JM 240         return getDiff(repository, null, commit, null, comparator, outputType, tabLength);
1f9dae 241     }
JM 242
d9f687 243     /**
7dd99f 244      * Returns the complete diff of the specified commit compared to its primary parent.
T 245      *
246      * @param repository
247      * @param commit
cff352 248      * @param comparator
7dd99f 249      * @param outputType
T 250      * @param handler
251      *            to use for rendering binary diffs if {@code outputType} is {@link DiffOutputType#HTML HTML}.
252      *            May be {@code null}, resulting in the default behavior.
310a80 253      * @param tabLength
7dd99f 254      * @return the diff
T 255      */
256     public static DiffOutput getCommitDiff(Repository repository, RevCommit commit,
310a80 257             DiffComparator comparator, DiffOutputType outputType, BinaryDiffHandler handler, int tabLength) {
JM 258         return getDiff(repository, null, commit, null, comparator, outputType, handler, tabLength);
7dd99f 259     }
T 260
261
262     /**
d9f687 263      * Returns the diff for the specified file or folder from the specified
JM 264      * commit compared to its primary parent.
319342 265      *
d9f687 266      * @param repository
JM 267      * @param commit
268      * @param path
cff352 269      * @param comparator
d9f687 270      * @param outputType
310a80 271      * @param tabLength
319342 272      * @return the diff
d9f687 273      */
319342 274     public static DiffOutput getDiff(Repository repository, RevCommit commit, String path,
310a80 275             DiffComparator comparator, DiffOutputType outputType, int tabLength) {
JM 276         return getDiff(repository, null, commit, path, comparator, outputType, tabLength);
1f9dae 277     }
JM 278
d9f687 279     /**
7dd99f 280      * Returns the diff for the specified file or folder from the specified
T 281      * commit compared to its primary parent.
282      *
283      * @param repository
284      * @param commit
285      * @param path
cff352 286      * @param comparator
7dd99f 287      * @param outputType
T 288      * @param handler
289      *            to use for rendering binary diffs if {@code outputType} is {@link DiffOutputType#HTML HTML}.
290      *            May be {@code null}, resulting in the default behavior.
310a80 291      * @param tabLength
7dd99f 292      * @return the diff
T 293      */
294     public static DiffOutput getDiff(Repository repository, RevCommit commit, String path,
310a80 295             DiffComparator comparator, DiffOutputType outputType, BinaryDiffHandler handler, int tabLength) {
JM 296         return getDiff(repository, null, commit, path, comparator, outputType, handler, tabLength);
7dd99f 297     }
T 298
299     /**
d9f687 300      * Returns the complete diff between the two specified commits.
319342 301      *
d9f687 302      * @param repository
JM 303      * @param baseCommit
304      * @param commit
cff352 305      * @param comparator
d9f687 306      * @param outputType
310a80 307      * @param tabLength
JM 308      *
319342 309      * @return the diff
d9f687 310      */
319342 311     public static DiffOutput getDiff(Repository repository, RevCommit baseCommit, RevCommit commit,
310a80 312             DiffComparator comparator, DiffOutputType outputType, int tabLength) {
JM 313         return getDiff(repository, baseCommit, commit, null, comparator, outputType, tabLength);
7dd99f 314     }
T 315
316     /**
317      * Returns the complete diff between the two specified commits.
318      *
319      * @param repository
320      * @param baseCommit
321      * @param commit
cff352 322      * @param comparator
7dd99f 323      * @param outputType
T 324      * @param handler
325      *            to use for rendering binary diffs if {@code outputType} is {@link DiffOutputType#HTML HTML}.
326      *            May be {@code null}, resulting in the default behavior.
310a80 327      * @param tabLength
7dd99f 328      * @return the diff
T 329      */
330     public static DiffOutput getDiff(Repository repository, RevCommit baseCommit, RevCommit commit,
310a80 331             DiffComparator comparator, DiffOutputType outputType, BinaryDiffHandler handler, int tabLength) {
JM 332         return getDiff(repository, baseCommit, commit, null, comparator, outputType, handler, tabLength);
1f9dae 333     }
JM 334
d9f687 335     /**
JM 336      * Returns the diff between two commits for the specified file.
319342 337      *
d9f687 338      * @param repository
JM 339      * @param baseCommit
340      *            if base commit is null the diff is to the primary parent of
341      *            the commit.
342      * @param commit
343      * @param path
344      *            if the path is specified, the diff is restricted to that file
345      *            or folder. if unspecified, the diff is for the entire commit.
346      * @param outputType
cff352 347      * @param diffComparator
310a80 348      * @param tabLength
319342 349      * @return the diff
d9f687 350      */
319342 351     public static DiffOutput getDiff(Repository repository, RevCommit baseCommit, RevCommit commit,
310a80 352             String path, DiffComparator diffComparator, DiffOutputType outputType, int tabLength) {
JM 353         return getDiff(repository, baseCommit, commit, path, diffComparator, outputType, null, tabLength);
7dd99f 354     }
T 355
356     /**
357      * Returns the diff between two commits for the specified file.
358      *
359      * @param repository
360      * @param baseCommit
361      *            if base commit is null the diff is to the primary parent of
362      *            the commit.
363      * @param commit
364      * @param path
365      *            if the path is specified, the diff is restricted to that file
366      *            or folder. if unspecified, the diff is for the entire commit.
cff352 367      * @param comparator
7dd99f 368      * @param outputType
T 369      * @param handler
370      *            to use for rendering binary diffs if {@code outputType} is {@link DiffOutputType#HTML HTML}.
371      *            May be {@code null}, resulting in the default behavior.
310a80 372      * @param tabLength
7dd99f 373      * @return the diff
T 374      */
cff352 375     public static DiffOutput getDiff(Repository repository, RevCommit baseCommit, RevCommit commit, String path,
310a80 376             DiffComparator comparator, DiffOutputType outputType, final BinaryDiffHandler handler, int tabLength) {
319342 377         DiffStat stat = null;
f339f5 378         String diff = null;
1f9dae 379         try {
df0ba7 380             ByteArrayOutputStream os = null;
cff352 381
1f9dae 382             DiffFormatter df;
JM 383             switch (outputType) {
025028 384             case HTML:
46f33f 385                 df = new GitBlitDiffFormatter(commit.getName(), repository, path, handler, tabLength);
1f9dae 386                 break;
JM 387             case PLAIN:
388             default:
df0ba7 389                 os = new ByteArrayOutputStream();
1f9dae 390                 df = new DiffFormatter(os);
JM 391                 break;
392             }
d9f687 393             df.setRepository(repository);
5d59b9 394             df.setDiffComparator((comparator == null ? DiffComparator.SHOW_WHITESPACE : comparator).textComparator);
1f9dae 395             df.setDetectRenames(true);
d9f687 396
JM 397             RevTree commitTree = commit.getTree();
398             RevTree baseTree;
399             if (baseCommit == null) {
a2709d 400                 if (commit.getParentCount() > 0) {
JM 401                     final RevWalk rw = new RevWalk(repository);
402                     RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
403                     rw.dispose();
404                     baseTree = parent.getTree();
405                 } else {
406                     // FIXME initial commit. no parent?!
407                     baseTree = commitTree;
408                 }
d9f687 409             } else {
JM 410                 baseTree = baseCommit.getTree();
411             }
412
f339f5 413             List<DiffEntry> diffEntries = df.scan(baseTree, commitTree);
1f9dae 414             if (path != null && path.length() > 0) {
f339f5 415                 for (DiffEntry diffEntry : diffEntries) {
JM 416                     if (diffEntry.getNewPath().equalsIgnoreCase(path)) {
417                         df.format(diffEntry);
1f9dae 418                         break;
JM 419                     }
420                 }
421             } else {
f339f5 422                 df.format(diffEntries);
1f9dae 423             }
df0ba7 424             df.flush();
025028 425             if (df instanceof GitBlitDiffFormatter) {
1f9dae 426                 // workaround for complex private methods in DiffFormatter
025028 427                 diff = ((GitBlitDiffFormatter) df).getHtml();
319342 428                 stat = ((GitBlitDiffFormatter) df).getDiffStat();
1f9dae 429             } else {
JM 430                 diff = os.toString();
431             }
432         } catch (Throwable t) {
433             LOGGER.error("failed to generate commit diff!", t);
434         }
699e71 435
319342 436         return new DiffOutput(outputType, diff, stat);
1f9dae 437     }
JM 438
d9f687 439     /**
JM 440      * Returns the diff between the two commits for the specified file or folder
441      * formatted as a patch.
319342 442      *
d9f687 443      * @param repository
JM 444      * @param baseCommit
445      *            if base commit is unspecified, the patch is generated against
446      *            the primary parent of the specified commit.
447      * @param commit
448      * @param path
449      *            if path is specified, the patch is generated only for the
450      *            specified file or folder. if unspecified, the patch is
451      *            generated for the entire diff between the two commits.
452      * @return patch as a string
453      */
454     public static String getCommitPatch(Repository repository, RevCommit baseCommit,
455             RevCommit commit, String path) {
f339f5 456         String diff = null;
1f9dae 457         try {
d9f687 458             final ByteArrayOutputStream os = new ByteArrayOutputStream();
JM 459             RawTextComparator cmp = RawTextComparator.DEFAULT;
460             PatchFormatter df = new PatchFormatter(os);
461             df.setRepository(repository);
462             df.setDiffComparator(cmp);
463             df.setDetectRenames(true);
464
465             RevTree commitTree = commit.getTree();
1f9dae 466             RevTree baseTree;
JM 467             if (baseCommit == null) {
a2709d 468                 if (commit.getParentCount() > 0) {
JM 469                     final RevWalk rw = new RevWalk(repository);
470                     RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
471                     baseTree = parent.getTree();
472                 } else {
473                     // FIXME initial commit. no parent?!
474                     baseTree = commitTree;
475                 }
1f9dae 476             } else {
JM 477                 baseTree = baseCommit.getTree();
478             }
479
f339f5 480             List<DiffEntry> diffEntries = df.scan(baseTree, commitTree);
1f9dae 481             if (path != null && path.length() > 0) {
f339f5 482                 for (DiffEntry diffEntry : diffEntries) {
JM 483                     if (diffEntry.getNewPath().equalsIgnoreCase(path)) {
484                         df.format(diffEntry);
1f9dae 485                         break;
JM 486                     }
487                 }
488             } else {
f339f5 489                 df.format(diffEntries);
1f9dae 490             }
f339f5 491             diff = df.getPatch(commit);
1f9dae 492             df.flush();
JM 493         } catch (Throwable t) {
494             LOGGER.error("failed to generate commit diff!", t);
495         }
f339f5 496         return diff;
1f9dae 497     }
008322 498
835529 499     /**
JM 500      * Returns the diffstat between the two commits for the specified file or folder.
501      *
502      * @param repository
503      * @param base
504      *            if base commit is unspecified, the diffstat is generated against
505      *            the primary parent of the specified tip.
506      * @param tip
507      * @param path
508      *            if path is specified, the diffstat is generated only for the
509      *            specified file or folder. if unspecified, the diffstat is
510      *            generated for the entire diff between the two commits.
511      * @return patch as a string
512      */
513     public static DiffStat getDiffStat(Repository repository, String base, String tip) {
514         RevCommit baseCommit = null;
515         RevCommit tipCommit = null;
dfd6f5 516         RevWalk revWalk = new RevWalk(repository);
835529 517         try {
JM 518             tipCommit = revWalk.parseCommit(repository.resolve(tip));
519             if (!StringUtils.isEmpty(base)) {
520                 baseCommit = revWalk.parseCommit(repository.resolve(base));
521             }
dfd6f5 522             return getDiffStat(repository, baseCommit, tipCommit, null);
835529 523         } catch (Exception e) {
JM 524             LOGGER.error("failed to generate diffstat!", e);
525         } finally {
526             revWalk.dispose();
527         }
dfd6f5 528         return null;
835529 529     }
JM 530
319342 531     public static DiffStat getDiffStat(Repository repository, RevCommit commit) {
JM 532         return getDiffStat(repository, null, commit, null);
533     }
534
535     /**
536      * Returns the diffstat between the two commits for the specified file or folder.
537      *
538      * @param repository
539      * @param baseCommit
540      *            if base commit is unspecified, the diffstat is generated against
541      *            the primary parent of the specified commit.
542      * @param commit
543      * @param path
544      *            if path is specified, the diffstat is generated only for the
545      *            specified file or folder. if unspecified, the diffstat is
546      *            generated for the entire diff between the two commits.
547      * @return patch as a string
548      */
549     public static DiffStat getDiffStat(Repository repository, RevCommit baseCommit,
550             RevCommit commit, String path) {
551         DiffStat stat = null;
552         try {
553             RawTextComparator cmp = RawTextComparator.DEFAULT;
46f33f 554             DiffStatFormatter df = new DiffStatFormatter(commit.getName(), repository);
319342 555             df.setRepository(repository);
JM 556             df.setDiffComparator(cmp);
557             df.setDetectRenames(true);
558
559             RevTree commitTree = commit.getTree();
560             RevTree baseTree;
561             if (baseCommit == null) {
562                 if (commit.getParentCount() > 0) {
563                     final RevWalk rw = new RevWalk(repository);
564                     RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
565                     baseTree = parent.getTree();
566                 } else {
567                     // FIXME initial commit. no parent?!
568                     baseTree = commitTree;
569                 }
570             } else {
571                 baseTree = baseCommit.getTree();
572             }
573
574             List<DiffEntry> diffEntries = df.scan(baseTree, commitTree);
575             if (path != null && path.length() > 0) {
576                 for (DiffEntry diffEntry : diffEntries) {
577                     if (diffEntry.getNewPath().equalsIgnoreCase(path)) {
578                         df.format(diffEntry);
579                         break;
580                     }
581                 }
582             } else {
583                 df.format(diffEntries);
584             }
585             stat = df.getDiffStat();
586             df.flush();
587         } catch (Throwable t) {
588             LOGGER.error("failed to generate commit diff!", t);
589         }
590         return stat;
591     }
592
d9f687 593     /**
JM 594      * Returns the list of lines in the specified source file annotated with the
595      * source commit metadata.
319342 596      *
d9f687 597      * @param repository
JM 598      * @param blobPath
599      * @param objectId
600      * @return list of annotated lines
601      */
602     public static List<AnnotatedLine> blame(Repository repository, String blobPath, String objectId) {
008322 603         List<AnnotatedLine> lines = new ArrayList<AnnotatedLine>();
JM 604         try {
a2709d 605             ObjectId object;
f339f5 606             if (StringUtils.isEmpty(objectId)) {
a2709d 607                 object = JGitUtils.getDefaultBranch(repository);
JM 608             } else {
609                 object = repository.resolve(objectId);
f339f5 610             }
d9f687 611             BlameCommand blameCommand = new BlameCommand(repository);
008322 612             blameCommand.setFilePath(blobPath);
a2709d 613             blameCommand.setStartCommit(object);
008322 614             BlameResult blameResult = blameCommand.call();
JM 615             RawText rawText = blameResult.getResultContents();
616             int length = rawText.size();
617             for (int i = 0; i < length; i++) {
618                 RevCommit commit = blameResult.getSourceCommit(i);
619                 AnnotatedLine line = new AnnotatedLine(commit, i + 1, rawText.getString(i));
620                 lines.add(line);
621             }
622         } catch (Throwable t) {
0f47b2 623             LOGGER.error(MessageFormat.format("failed to generate blame for {0} {1}!", blobPath, objectId), t);
008322 624         }
JM 625         return lines;
626     }
699e71 627
319342 628     /**
JM 629      * Normalizes a diffstat to an N-segment display.
699e71 630      *
319342 631      * @params segments
JM 632      * @param insertions
633      * @param deletions
634      * @return a normalized diffstat
635      */
636     public static NormalizedDiffStat normalizeDiffStat(final int segments, final int insertions, final int deletions) {
637         final int total = insertions + deletions;
638         final float fi = ((float) insertions) / total;
639         int si;
640         int sd;
641         int sb;
642         if (deletions == 0) {
643             // only addition
644             si = Math.min(insertions, segments);
645             sd = 0;
646             sb = si < segments ? (segments - si) : 0;
647         } else if (insertions == 0) {
648             // only deletion
649             si = 0;
650             sd = Math.min(deletions, segments);
651             sb = sd < segments ? (segments - sd) : 0;
652         } else if (total <= segments) {
653             // total churn fits in segment display
654             si = insertions;
655             sd = deletions;
656             sb = segments - total;
657         } else if ((segments % 2) > 0 && fi > 0.45f && fi < 0.55f) {
658             // odd segment display, fairly even +/-, use even number of segments
659             si = Math.round(((float) insertions)/total * (segments - 1));
660             sd = segments - 1 - si;
661             sb = 1;
662         } else {
663             si = Math.round(((float) insertions)/total * segments);
664             sd = segments - si;
665             sb = 0;
666         }
699e71 667
319342 668         return new NormalizedDiffStat(si, sd, sb);
JM 669     }
1f9dae 670 }