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