Coverage Report - net.sf.statsvn.input.SvnLogfileParser
 
Classes in this File Line Coverage Branch Coverage Complexity
SvnLogfileParser
77%
281/366
64%
167/260
3.364
SvnLogfileParser$DiffTask
0%
0/90
0%
0/12
3.364
SvnLogfileParser$PerRevDiffTask
0%
0/49
0%
0/16
3.364
 
 1  
 /*
 2  
  StatCvs - CVS statistics generation 
 3  
  Copyright (C) 2002  Lukasz Pekacki <lukasz@pekacki.de>
 4  
  http://statcvs.sf.net/
 5  
  
 6  
  This library is free software; you can redistribute it and/or
 7  
  modify it under the terms of the GNU Lesser General Public
 8  
  License as published by the Free Software Foundation; either
 9  
  version 2.1 of the License, or (at your option) any later version.
 10  
 
 11  
  This library is distributed in the hope that it will be useful,
 12  
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 14  
  Lesser General Public License for more details.
 15  
 
 16  
  You should have received a copy of the GNU Lesser General Public
 17  
  License along with this library; if not, write to the Free Software
 18  
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 19  
  
 20  
  $RCSfile: SvnLogfileParser.java,v $ 
 21  
  Created on $Date: 2004/10/10 11:29:07 $ 
 22  
  */
 23  
 
 24  
 package net.sf.statsvn.input;
 25  
 
 26  
 import java.io.FileInputStream;
 27  
 import java.io.FileNotFoundException;
 28  
 import java.io.IOException;
 29  
 import java.io.InputStream;
 30  
 import java.util.ArrayList;
 31  
 import java.util.Collection;
 32  
 import java.util.Collections;
 33  
 import java.util.Date;
 34  
 import java.util.HashMap;
 35  
 import java.util.HashSet;
 36  
 import java.util.Iterator;
 37  
 import java.util.List;
 38  
 import java.util.Map;
 39  
 import java.util.Vector;
 40  
 
 41  
 import javax.xml.parsers.ParserConfigurationException;
 42  
 import javax.xml.parsers.SAXParser;
 43  
 import javax.xml.parsers.SAXParserFactory;
 44  
 
 45  
 import net.sf.statcvs.input.LogSyntaxException;
 46  
 import net.sf.statsvn.output.SvnConfigurationOptions;
 47  
 import net.sf.statsvn.util.BinaryDiffException;
 48  
 import net.sf.statsvn.util.FilenameComparator;
 49  
 import net.sf.statsvn.util.SvnDiffUtils;
 50  
 import net.sf.statsvn.util.XMLUtil;
 51  
 
 52  
 import org.xml.sax.SAXException;
 53  
 
 54  
 import edu.emory.mathcs.backport.java.util.concurrent.ExecutorService;
 55  
 import edu.emory.mathcs.backport.java.util.concurrent.Executors;
 56  
 import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
 57  
 
 58  
 /**
 59  
  * Parses a Subversion logfile and does post-parse processing. A {@link Builder}
 60  
  * must be specified which does the construction work.
 61  
  * 
 62  
  * @author Jason Kealey <jkealey@shade.ca>
 63  
  * @author Gunter Mussbacher <gunterm@site.uottawa.ca>
 64  
  * 
 65  
  * @version $Id: SvnLogfileParser.java 368 2008-06-25 21:23:46Z benoitx $
 66  0
  */
 67  0
 public class SvnLogfileParser {
 68  
     private static final int INTERMEDIARY_SAVE_INTERVAL_MS = 120000;
 69  
 
 70  
     private static final String REPOSITORIES_XML = "repositories.xml";
 71  
 
 72  
     private final SvnLogBuilder builder;
 73  
 
 74  
     private final InputStream logFile;
 75  
 
 76  
     private final RepositoryFileManager repositoryFileManager;
 77  
 
 78  
     private CacheBuilder cacheBuilder;
 79  3
 
 80  1
     private HashSet revsForNewDiff = null;
 81  
 
 82  
     /**
 83  
      * Default Constructor
 84  
      * 
 85  
      * @param repositoryFileManager
 86  
      *            the repository file manager
 87  
      * @param logFile
 88  
      *            a <tt>Reader</tt> containing the SVN logfile
 89  
      * @param builder
 90  
      *            the builder that will process the log information
 91  3
      */
 92  4
     public SvnLogfileParser(final RepositoryFileManager repositoryFileManager, final InputStream logFile, final SvnLogBuilder builder) {
 93  4
         this.logFile = logFile;
 94  4
         this.builder = builder;
 95  4
         this.repositoryFileManager = repositoryFileManager;
 96  1
     }
 97  
 
 98  
     /**
 99  
      * Because the log file does not contain the lines added or removed in a
 100  
      * commit, and because the logfile contains implicit actions (@link
 101  
      * #verifyImplicitActions()), we must query the repository for line
 102  
      * differences. This method uses the (@link LineCountsBuilder) to load the
 103  
      * persisted information and (@link SvnDiffUtils) to find new information.
 104  
      * 
 105  
      * @param factory
 106  
      *            the factory used to create SAX parsers.
 107  
      * @throws IOException
 108  
      */
 109  3
     protected void handleLineCounts(final SAXParserFactory factory) throws IOException {
 110  4
         long startTime = System.currentTimeMillis();
 111  1
         final String xmlFile = SvnConfigurationOptions.getCacheDir() + REPOSITORIES_XML;
 112  3
 
 113  4
         final RepositoriesBuilder repositoriesBuilder = readAndParseXmlFile(factory, xmlFile);
 114  4
         cacheFileName = SvnConfigurationOptions.getCacheDir() + repositoriesBuilder.getFileName(repositoryFileManager.getRepositoryUuid());
 115  4
         XMLUtil.writeXmlFile(repositoriesBuilder.getDocument(), xmlFile);
 116  4
         SvnConfigurationOptions.getTaskLogger().log("parsing repositories finished in " + (System.currentTimeMillis() - startTime) + " ms.");
 117  1
         startTime = System.currentTimeMillis();
 118  3
 
 119  4
         readCache(factory);
 120  4
         SvnConfigurationOptions.getTaskLogger().log("parsing line counts finished in " + (System.currentTimeMillis() - startTime) + " ms.");
 121  1
         startTime = System.currentTimeMillis();
 122  
 
 123  
         // update the cache xml file with the latest binary status information
 124  3
         // from the working copy
 125  1
         cacheBuilder.updateBinaryStatus(builder.getFileBuilders().values(), repositoryFileManager.getRootRevisionNumber());
 126  3
 
 127  1
         final Collection fileBuilders = builder.getFileBuilders().values();
 128  3
 
 129  1
         calculateNumberRequiredCalls(fileBuilders);
 130  
 
 131  3
         // concurrency
 132  4
         ExecutorService poolService = null;
 133  4
         if (SvnConfigurationOptions.getNumberSvnDiffThreads() > 1) {
 134  1
             poolService = Executors.newFixedThreadPool(SvnConfigurationOptions.getNumberSvnDiffThreads());
 135  
         }
 136  3
 
 137  4
         boolean isFirstDiff = true;
 138  4
         calls = 0;
 139  4
         groupStart = System.currentTimeMillis();
 140  1
         boolean poolUseRequired = false;
 141  3
 
 142  1
         if (SvnConfigurationOptions.isLegacyDiff()) {
 143  0
             for (final Iterator iter = fileBuilders.iterator(); iter.hasNext();) {
 144  0
                 final FileBuilder fileBuilder = (FileBuilder) iter.next();
 145  0
                 final String fileName = fileBuilder.getName();
 146  0
                 if (fileBuilder.isBinary() || !builder.matchesPatterns(fileName)) {
 147  0
                     continue;
 148  0
                 }
 149  0
                 final List revisions = fileBuilder.getRevisions();
 150  0
                 for (int i = 0; i < revisions.size(); i++) {
 151  
                     // line diffs are expensive operations. therefore, the
 152  
                     // result is
 153  
                     // stored in the
 154  
                     // cacheBuilder and eventually persisted in the cache xml
 155  
                     // file.
 156  
                     // the next time
 157  
                     // the file is read the line diffs (or 0/0 in case of binary
 158  
                     // files) are intialized
 159  
                     // in the RevisionData. this cause hasNoLines to be false
 160  
                     // which
 161  
                     // in turn causes the
 162  0
                     // if clause below to be skipped.
 163  0
                     if (i + 1 < revisions.size() && ((RevisionData) revisions.get(i)).hasNoLines() && !((RevisionData) revisions.get(i)).isDeletion()) {
 164  0
                         if (((RevisionData) revisions.get(i + 1)).isDeletion()) {
 165  0
                             continue;
 166  0
                         }
 167  0
                         final String revNrNew = ((RevisionData) revisions.get(i)).getRevisionNumber();
 168  0
                         if (cacheBuilder.isBinary(fileName, revNrNew)) {
 169  0
                             continue;
 170  0
                         }
 171  0
                         final String revNrOld = ((RevisionData) revisions.get(i + 1)).getRevisionNumber();
 172  0
 
 173  0
                         if (isFirstDiff) {
 174  0
                             SvnConfigurationOptions.getTaskLogger().info("Contacting server to obtain line count information.");
 175  0
                             SvnConfigurationOptions.getTaskLogger().info(
 176  
                                     "This information will be cached so that the next time you run StatSVN, results will be returned more quickly.");
 177  0
 
 178  0
                             if (SvnConfigurationOptions.isLegacyDiff()) {
 179  0
                                 SvnConfigurationOptions.getTaskLogger().info("Using the legacy Subversion 1.3 diff mechanism: one diff per file per revision.");
 180  0
                             } else {
 181  0
                                 SvnConfigurationOptions.getTaskLogger().info("Using the Subversion 1.4 diff mechanism: one diff per revision.");
 182  
                             }
 183  0
 
 184  0
                             isFirstDiff = false;
 185  
                         }
 186  0
 
 187  0
                         final DiffTask diff = new DiffTask(fileName, revNrNew, revNrOld, fileBuilder);
 188  
 
 189  
                         // SvnConfigurationOptions.getTaskLogger().log(Thread.currentThread().getName()
 190  
                         // + " Schedule task for " + fileName + " rev:" +
 191  
                         // revNrNew);
 192  0
 
 193  0
                         poolUseRequired = executeTask(poolService, poolUseRequired, diff);
 194  
                     }
 195  0
                 }
 196  0
             }
 197  3
         } else {
 198  1
             for (final Iterator iter = revsForNewDiff.iterator(); iter.hasNext();) {
 199  0
                 final String revNrNew = (String) iter.next();
 200  0
                 final PerRevDiffTask diff = new PerRevDiffTask(revNrNew, builder.getFileBuilders());
 201  0
 
 202  0
                 poolUseRequired = executeTask(poolService, poolUseRequired, diff);
 203  0
             }
 204  
 
 205  3
         }
 206  4
         waitForPoolIfRequired(poolService);
 207  4
         SvnConfigurationOptions.getTaskLogger().log("parsing svn diff");
 208  4
         XMLUtil.writeXmlFile(cacheBuilder.getDocument(), cacheFileName);
 209  4
         SvnConfigurationOptions.getTaskLogger().log("parsing svn diff finished in " + (System.currentTimeMillis() - startTime) + " ms.");
 210  1
     }
 211  
 
 212  0
     private boolean executeTask(final ExecutorService poolService, boolean poolUseRequired, final DiffTask diff) {
 213  0
         if (poolUseRequired && SvnConfigurationOptions.getNumberSvnDiffThreads() > 1) {
 214  0
             poolService.execute(diff);
 215  0
         } else {
 216  0
             final long start = System.currentTimeMillis();
 217  0
             diff.run();
 218  0
             final long end = System.currentTimeMillis();
 219  0
             poolUseRequired = (end - start) > SvnConfigurationOptions.getThresholdInMsToUseConcurrency();
 220  0
         }
 221  0
         return poolUseRequired;
 222  
     }
 223  
 
 224  3
     private void waitForPoolIfRequired(final ExecutorService poolService) {
 225  4
         if (SvnConfigurationOptions.getNumberSvnDiffThreads() > 1 && poolService != null) {
 226  1
             SvnConfigurationOptions.getTaskLogger().info(
 227  
                     "Scheduled " + requiredDiffCalls + " svn diff calls on " + Math.min(requiredDiffCalls, SvnConfigurationOptions.getNumberSvnDiffThreads())
 228  3
                             + " threads.");
 229  1
             poolService.shutdown();
 230  3
             try {
 231  4
                 SvnConfigurationOptions.getTaskLogger().log("================ Wait for completion =========================");
 232  1
                 if (!poolService.awaitTermination(2, TimeUnit.DAYS)) {
 233  0
                     SvnConfigurationOptions.getTaskLogger().log("================ TIME OUT!!! =========================");
 234  0
                 }
 235  0
             } catch (final InterruptedException e) {
 236  3
                 SvnConfigurationOptions.getTaskLogger().error(e.toString());
 237  1
             }
 238  3
         }
 239  1
     }
 240  
 
 241  
     private void calculateNumberRequiredCalls(final Collection fileBuilders) {
 242  3
         // Calculate the number of required calls...
 243  1
         requiredDiffCalls = 0;
 244  3
 
 245  4
         if (!SvnConfigurationOptions.isLegacyDiff()) {
 246  1
             revsForNewDiff = new HashSet();
 247  
         }
 248  3
 
 249  3154
         for (final Iterator iter = fileBuilders.iterator(); iter.hasNext();) {
 250  4204
             final FileBuilder fileBuilder = (FileBuilder) iter.next();
 251  4204
             final String fileName = fileBuilder.getName();
 252  3766
             if (!fileBuilder.isBinary() && builder.matchesPatterns(fileName)) {
 253  19442
                 final List revisions = fileBuilder.getRevisions();
 254  22001
                 for (int i = 0; i < revisions.size(); i++) {
 255  5817
                     if (i + 1 < revisions.size() && ((RevisionData) revisions.get(i)).hasNoLines() && !((RevisionData) revisions.get(i)).isDeletion()) {
 256  724
                         if (((RevisionData) revisions.get(i + 1)).isDeletion()) {
 257  181
                             continue;
 258  0
                         }
 259  0
                         final String revNrNew = ((RevisionData) revisions.get(i)).getRevisionNumber();
 260  0
                         if (cacheBuilder.isBinary(fileName, revNrNew)) {
 261  0
                             continue;
 262  
                         }
 263  
                         // count if legacy diff or this rev wasn't already
 264  0
                         // counted.
 265  0
                         if (revsForNewDiff == null || !revsForNewDiff.contains(revNrNew)) {
 266  0
                             requiredDiffCalls++;
 267  0
 
 268  0
                             if (revsForNewDiff != null) {
 269  0
                                 revsForNewDiff.add(revNrNew);
 270  
                             }
 271  
                         }
 272  
                     }
 273  
                 }
 274  3153
             }
 275  1051
         }
 276  3
         // END Calculate the number of required calls...
 277  1
     }
 278  
 
 279  3
     private void readCache(final SAXParserFactory factory) throws IOException {
 280  4
         cacheBuilder = new CacheBuilder(builder, repositoryFileManager);
 281  1
         FileInputStream cacheFile = null;
 282  3
         try {
 283  4
             cacheFile = new FileInputStream(cacheFileName);
 284  4
             final SAXParser parser = factory.newSAXParser();
 285  4
             parser.parse(cacheFile, new SvnXmlCacheFileHandler(cacheBuilder));
 286  1
             cacheFile.close();
 287  0
         } catch (final ParserConfigurationException e) {
 288  0
             SvnConfigurationOptions.getTaskLogger().error("Cache: " + e.toString());
 289  0
         } catch (final SAXException e) {
 290  0
             SvnConfigurationOptions.getTaskLogger().error("Cache: " + e.toString());
 291  0
         } catch (final FileNotFoundException e) {
 292  0
             SvnConfigurationOptions.getTaskLogger().log("Cache: " + e.toString());
 293  0
         } catch (final IOException e) {
 294  0
             SvnConfigurationOptions.getTaskLogger().error("Cache: " + e.toString());
 295  3
         } finally {
 296  4
             if (cacheFile != null) {
 297  1
                 cacheFile.close();
 298  
             }
 299  3
         }
 300  1
     }
 301  
 
 302  3
     private RepositoriesBuilder readAndParseXmlFile(final SAXParserFactory factory, final String xmlFile) throws IOException {
 303  4
         final RepositoriesBuilder repositoriesBuilder = new RepositoriesBuilder();
 304  1
         FileInputStream repositoriesFile = null;
 305  3
         try {
 306  4
             repositoriesFile = new FileInputStream(xmlFile);
 307  4
             final SAXParser parser = factory.newSAXParser();
 308  4
             parser.parse(repositoriesFile, new SvnXmlRepositoriesFileHandler(repositoriesBuilder));
 309  1
             repositoriesFile.close();
 310  0
         } catch (final ParserConfigurationException e) {
 311  0
             SvnConfigurationOptions.getTaskLogger().error("Repositories: " + e.toString());
 312  0
         } catch (final SAXException e) {
 313  0
             SvnConfigurationOptions.getTaskLogger().error("Repositories: " + e.toString());
 314  0
         } catch (final FileNotFoundException e) {
 315  0
             SvnConfigurationOptions.getTaskLogger().log("Repositories: " + e.toString());
 316  0
         } catch (final IOException e) {
 317  0
             SvnConfigurationOptions.getTaskLogger().error("Repositories: " + e.toString());
 318  3
         } finally {
 319  4
             if (repositoriesFile != null) {
 320  1
                 repositoriesFile.close();
 321  
             }
 322  3
         }
 323  1
         return repositoriesBuilder;
 324  
     }
 325  
 
 326  
     /**
 327  
      * Parses the logfile. After <tt>parse()</tt> has finished, the result of
 328  
      * the parsing process can be obtained from the builder.
 329  
      * 
 330  
      * @throws LogSyntaxException
 331  
      *             if syntax errors in log
 332  
      * @throws IOException
 333  
      *             if errors while reading from the log Reader
 334  
      */
 335  
     public void parse() throws LogSyntaxException, IOException {
 336  3
 
 337  1
         final SAXParserFactory factory = parseSvnLog();
 338  3
 
 339  1
         verifyImplicitActions();
 340  
 
 341  3
         // must be after verifyImplicitActions();
 342  1
         removeDirectories();
 343  3
 
 344  1
         handleLineCounts(factory);
 345  3
 
 346  1
     }
 347  
 
 348  
     /**
 349  
      * The svn log can contain deletions of directories which imply that all of
 350  
      * its contents have been deleted.
 351  
      * 
 352  
      * Furthermore, the svn log can contain entries which are copies from other
 353  
      * directories (additions or replacements; I haven't seen modifications with
 354  
      * this property, but am not 100% sure) meaning that all files from the
 355  
      * other directory are copied here. We currently do not go back through
 356  
      * copies, so we must infer what files <i>could</i> have been added during
 357  
      * those copies.
 358  
      * 
 359  
      */
 360  
     protected void verifyImplicitActions() {
 361  
         // this method most certainly has issues with implicit actions on root
 362  
         // folder.
 363  3
 
 364  4
         final long startTime = System.currentTimeMillis();
 365  1
         SvnConfigurationOptions.getTaskLogger().log("verifying implicit actions ...");
 366  3
 
 367  1
         final HashSet implicitActions = new HashSet();
 368  
 
 369  3
         // get all filenames
 370  4
         final ArrayList files = new ArrayList();
 371  1
         final Collection fileBuilders = fetchAllFileNames(files);
 372  
 
 373  
         // sort them so that folders are immediately followed by the folder
 374  
         // entries and then by other files which are prefixed by the folder
 375  3
         // name.
 376  1
         Collections.sort(files, new FilenameComparator());
 377  
 
 378  3444
         // for each file
 379  4589
         for (int i = 0; i < files.size(); i++) {
 380  4588
             final String parent = files.get(i).toString();
 381  1147
             final FileBuilder parentBuilder = (FileBuilder) builder.getFileBuilders().get(parent);
 382  
             // check to see if there are files that indicate that parent is a
 383  17682
             // folder.
 384  5894
             for (int j = i + 1; j < files.size() && files.get(j).toString().indexOf(parent + "/") == 0; j++) {
 385  14241
                 // we might not know that it was a folder.
 386  4747
                 repositoryFileManager.addDirectory(parent);
 387  14241
 
 388  18988
                 final String child = files.get(j).toString();
 389  4747
                 final FileBuilder childBuilder = (FileBuilder) builder.getFileBuilders().get(child);
 390  14241
                 // for all revisions in the the parent folder
 391  20041
                 for (final Iterator iter = parentBuilder.getRevisions().iterator(); iter.hasNext();) {
 392  5098
                     final RevisionData parentData = (RevisionData) iter.next();
 393  
                     int parentRevision;
 394  15294
                     try {
 395  5098
                         parentRevision = Integer.parseInt(parentData.getRevisionNumber());
 396  0
                     } catch (final Exception e) {
 397  15294
                         continue;
 398  5098
                     }
 399  
 
 400  15294
                     // ignore modifications to folders
 401  5098
                     if (parentData.isCreationOrRestore() || parentData.isDeletion()) {
 402  
                         int k;
 403  
 
 404  
                         // check to see if the parent revision is an implicit
 405  15282
                         // action acting on the child.
 406  5094
                         k = detectActionOnChildGivenActionOnParent(childBuilder, parentRevision);
 407  
 
 408  15282
                         // we found something to insert
 409  5811
                         if (k < childBuilder.getRevisions().size()) {
 410  239
                             createImplicitAction(implicitActions, child, childBuilder, parentData, k);
 411  
                         }
 412  15294
                     }
 413  5098
                 }
 414  
             }
 415  
         }
 416  
 
 417  
         // Some implicit revisions may have resulted in double deletion
 418  
         // (e.g. deleting a directory and THEN deleting the parent directory).
 419  3
         // this will get rid of any consecutive deletion.
 420  1
         cleanPotentialDuplicateImplicitActions(fileBuilders);
 421  
 
 422  
         // in the preceeding block, we add implicit additions to too may files.
 423  
         // possibly a folder was deleted and restored later on, without the
 424  
         // specific file being re-added. we get rid of those here. however,
 425  
         // without knowledge of what was copied during the implicit additions /
 426  
         // replacements, we will remove as many implicit actions as possible
 427  
         // 
 428  
         // this solution is imperfect.
 429  
 
 430  
         // Examples:
 431  
         // IA ID IA ID M A -> ID M A
 432  3
         // IA ID A D M A -> ID A D M A
 433  4
         removePotentialInconsistencies(implicitActions, fileBuilders);
 434  4
         SvnConfigurationOptions.getTaskLogger().log("verifying implicit actions finished in " + (System.currentTimeMillis() - startTime) + " ms.");
 435  1
     }
 436  
 
 437  
     private void createImplicitAction(final HashSet implicitActions, final String child, final FileBuilder childBuilder, final RevisionData parentData,
 438  
             final int k) {
 439  717
         // we want to memorize this implicit action.
 440  956
         final RevisionData implicit = parentData.createCopy();
 441  239
         implicitActions.add(implicit);
 442  
 
 443  717
         // avoid concurrent modification errors.
 444  956
         final List toMove = new ArrayList();
 445  3515
         for (final Iterator it = childBuilder.getRevisions().subList(k, childBuilder.getRevisions().size()).iterator(); it.hasNext();) {
 446  1092
             final RevisionData revToMove = (RevisionData) it.next();
 447  
             // if
 448  
             // (!revToMove.getRevisionNumber().equals(implicit.getRevisionNumber()))
 449  3276
             // {
 450  1092
             toMove.add(revToMove);
 451  3276
             // }
 452  1092
         }
 453  
 
 454  717
         // remove the revisions to be moved.
 455  239
         childBuilder.getRevisions().removeAll(toMove);
 456  
 
 457  
         // don't call addRevision directly. buildRevision
 458  717
         // does more.
 459  239
         builder.buildFile(child, false, false, new HashMap(), new HashMap());
 460  
 
 461  
         // only add the implicit if the last one for the
 462  
         // file is NOT a deletion!
 463  
         // if (!toMove.isEmpty() && !((RevisionData)
 464  717
         // toMove.get(0)).isDeletion()) {
 465  239
         builder.buildRevision(implicit);
 466  
         // }
 467  
 
 468  717
         // copy back the revisions we removed.
 469  3515
         for (final Iterator it = toMove.iterator(); it.hasNext();) {
 470  1092
             builder.buildRevision((RevisionData) it.next());
 471  717
         }
 472  239
     }
 473  
 
 474  
     private int detectActionOnChildGivenActionOnParent(final FileBuilder childBuilder, final int parentRevision) {
 475  83664
         int k;
 476  99813
         for (k = 0; k < childBuilder.getRevisions().size(); k++) {
 477  95900
             final RevisionData childData = (RevisionData) childBuilder.getRevisions().get(k);
 478  23975
             final int childRevision = Integer.parseInt(childData.getRevisionNumber());
 479  
 
 480  
             // we don't want to add duplicate entries for the
 481  71925
             // same revision
 482  26801
             if (parentRevision == childRevision) {
 483  3768
                 k = childBuilder.getRevisions().size();
 484  942
                 break;
 485  
             }
 486  69099
 
 487  23750
             if (parentRevision > childRevision) {
 488  239
                 break; // we must insert it here!
 489  
             }
 490  15282
         }
 491  5094
         return k;
 492  
     }
 493  
 
 494  3
     private void removePotentialInconsistencies(final HashSet implicitActions, final Collection fileBuilders) {
 495  3442
         for (final Iterator iter = fileBuilders.iterator(); iter.hasNext();) {
 496  1147
             final FileBuilder filebuilder = (FileBuilder) iter.next();
 497  
 
 498  
             // make sure our attic is well set, with our new deletions that we
 499  3441
             // might have added.
 500  1969
             if (!repositoryFileManager.existsInWorkingCopy(filebuilder.getName())) {
 501  274
                 builder.addToAttic(filebuilder.getName());
 502  
             }
 503  
 
 504  3441
             // do we detect an inconsistency?
 505  1177
             if (!repositoryFileManager.existsInWorkingCopy(filebuilder.getName()) && !filebuilder.finalRevisionIsDead()) {
 506  106
                 int earliestDelete = -1;
 507  128
                 for (int i = 0; i < filebuilder.getRevisions().size(); i++) {
 508  32
                     final RevisionData data = (RevisionData) filebuilder.getRevisions().get(i);
 509  96
 
 510  65
                     if (data.isDeletion()) {
 511  11
                         earliestDelete = i;
 512  
                     }
 513  96
 
 514  41
                     if ((!data.isCreationOrRestore() && data.isChange()) || !implicitActions.contains(data)) {
 515  3
                         break;
 516  
                     }
 517  
                 }
 518  30
 
 519  10
                 if (earliestDelete > 0) {
 520  30
                     // avoid concurrent modification errors.
 521  40
                     final List toRemove = new ArrayList();
 522  46
                     for (final Iterator it = filebuilder.getRevisions().subList(0, earliestDelete).iterator(); it.hasNext();) {
 523  12
                         toRemove.add(it.next());
 524  30
                     }
 525  10
                     filebuilder.getRevisions().removeAll(toRemove);
 526  
                 }
 527  3441
             }
 528  1150
         }
 529  1
     }
 530  
 
 531  3
     private void cleanPotentialDuplicateImplicitActions(final Collection fileBuilders) {
 532  3442
         for (final Iterator iter = fileBuilders.iterator(); iter.hasNext();) {
 533  1147
             final FileBuilder filebuilder = (FileBuilder) iter.next();
 534  3441
 
 535  4588
             boolean previousIsDelete = false;
 536  1147
             final List toRemove = new ArrayList();
 537  
             // for this file, iterate through all revisions and store any
 538  
             // deletion revision that follows
 539  3441
             // a deletion.
 540  18118
             for (final Iterator it = filebuilder.getRevisions().iterator(); it.hasNext();) {
 541  22628
                 final Revisio