Coverage Report - com.aragost.javahg.Repository
 
Classes in this File Line Coverage Branch Coverage Complexity
Repository
76%
59/77
57%
11/19
1.656
Repository$1
100%
2/2
N/A
1.656
Repository$2
100%
1/1
N/A
1.656
 
 1  
 /*
 2  
  * #%L
 3  
  * JavaHg
 4  
  * %%
 5  
  * Copyright (C) 2011 aragost Trifork ag
 6  
  * %%
 7  
  * Permission is hereby granted, free of charge, to any person obtaining a copy
 8  
  * of this software and associated documentation files (the "Software"), to deal
 9  
  * in the Software without restriction, including without limitation the rights
 10  
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 11  
  * copies of the Software, and to permit persons to whom the Software is
 12  
  * furnished to do so, subject to the following conditions:
 13  
  * 
 14  
  * The above copyright notice and this permission notice shall be included in
 15  
  * all copies or substantial portions of the Software.
 16  
  * 
 17  
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 18  
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 19  
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 20  
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 21  
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 22  
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 23  
  * THE SOFTWARE.
 24  
  * #L%
 25  
  */
 26  
 package com.aragost.javahg;
 27  
 
 28  
 import java.io.File;
 29  
 import java.io.IOException;
 30  
 import java.nio.charset.CharsetDecoder;
 31  
 import java.util.List;
 32  
 import java.util.Map;
 33  
 import java.util.concurrent.ExecutionException;
 34  
 
 35  
 import com.aragost.javahg.RepositoryConfiguration.CachePolicy;
 36  
 import com.aragost.javahg.commands.HeadsCommand;
 37  
 import com.aragost.javahg.commands.LogCommand;
 38  
 import com.aragost.javahg.internals.GenericCommand;
 39  
 import com.aragost.javahg.internals.GenericLogCommand;
 40  
 import com.aragost.javahg.internals.HgInputStream;
 41  
 import com.aragost.javahg.internals.RuntimeIOException;
 42  
 import com.aragost.javahg.internals.ServerPool;
 43  
 import com.aragost.javahg.internals.Utils;
 44  
 import com.google.common.cache.CacheBuilder;
 45  
 import com.google.common.cache.CacheLoader;
 46  
 import com.google.common.cache.CacheStats;
 47  
 import com.google.common.cache.LoadingCache;
 48  
 import com.google.common.collect.Maps;
 49  
 
 50  
 /**
 51  
  * A Mercurial repository.
 52  
  */
 53  
 public abstract class Repository {
 54  
 
 55  
     private final LoadingCache<String, Changeset> changesetCache;
 56  
 
 57  218
     protected Repository(CachePolicy cachePolicy) {
 58  218
         this.changesetCache = createCache(cachePolicy);
 59  218
     }
 60  
 
 61  
     /**
 62  
      * Open an existing Mercurial repository.
 63  
      * 
 64  
      * @param mercurialRepository
 65  
      *            path to the local repository.
 66  
      * @return the repository.
 67  
      */
 68  
     public static BaseRepository open(RepositoryConfiguration conf, File mercurialRepository) {
 69  1
         return new BaseRepository(conf, mercurialRepository, false, null);
 70  
     }
 71  
 
 72  
     /**
 73  
      * Create a new Mercurial repository and open a javahg Repository
 74  
      * on it.
 75  
      * 
 76  
      * @param directory
 77  
      *            the local destination path of the clone
 78  
      * @return the newly created repository.
 79  
      */
 80  
     public static BaseRepository create(RepositoryConfiguration conf, File directory) {
 81  201
         return new BaseRepository(conf, directory, true, null);
 82  
     }
 83  
 
 84  
     /**
 85  
      * Clone an existing Mercurial repository.
 86  
      * 
 87  
      * @param directory
 88  
      *            the local destination path of the clone
 89  
      * @param otherRepoUrl
 90  
      *            the (possible remote) repository to clone.
 91  
      * @return the newly cloned repository.
 92  
      */
 93  
     public static BaseRepository clone(RepositoryConfiguration conf, File directory, String otherRepoUrl) {
 94  1
         return new BaseRepository(conf, directory, false, otherRepoUrl);
 95  
     }
 96  
 
 97  
     /**
 98  
      * Open an existing Mercurial repository. This uses the default
 99  
      * Mercurial binary from {@link RepositoryConfiguration#DEFAULT}.
 100  
      * 
 101  
      * @param mercurialRepository
 102  
      *            the local path to the repository.
 103  
      * @return the repository.
 104  
      */
 105  
     public static BaseRepository open(File mercurialRepository) {
 106  4
         return new BaseRepository(RepositoryConfiguration.DEFAULT, mercurialRepository, false, null);
 107  
     }
 108  
 
 109  
     /**
 110  
      * Create a new Mercurial repository. This uses the default
 111  
      * Mercurial binary from {@link RepositoryConfiguration#DEFAULT}.
 112  
      * 
 113  
      * @param directory
 114  
      *            the local destination path of the clone
 115  
      * @return the newly created repository.
 116  
      */
 117  
     public static BaseRepository create(File directory) {
 118  2
         return new BaseRepository(RepositoryConfiguration.DEFAULT, directory, true, null);
 119  
     }
 120  
 
 121  
     /**
 122  
      * Clone an existing Mercurial repository. This uses the default
 123  
      * Mercurial binary from {@link RepositoryConfiguration#DEFAULT}.
 124  
      * 
 125  
      * @param directory
 126  
      *            the local destination path of the clone
 127  
      * @param otherRepoUrl
 128  
      *            the (possible remote) repository to clone.
 129  
      * @return the newly cloned repository.
 130  
      */
 131  
     public static BaseRepository clone(File directory, String otherRepoUrl) {
 132  3
         return new BaseRepository(RepositoryConfiguration.DEFAULT, directory, false, otherRepoUrl);
 133  
     }
 134  
 
 135  
     /**
 136  
      * @deprecated Use changeset instead.
 137  
      * 
 138  
      * @param node
 139  
      *            a changeset ID, must be 40 hexadecimal characters.
 140  
      * @return the changeset
 141  
      */
 142  
     @Deprecated
 143  
     public final Changeset changeSet(String node) {
 144  0
         return changeset(node);
 145  
     }
 146  
 
 147  
     /**
 148  
      * Return a {@link Changeset} object for the specified node id.
 149  
      * <p>
 150  
      * The changeset is not actually loaded, but will be loaded on
 151  
      * demand when a getter is called.
 152  
      * <p>
 153  
      * {@code null} is used to represent the null changeset.
 154  
      * 
 155  
      * @param node
 156  
      *            a changeset ID, must be 40 hexadecimal characters.
 157  
      * @return the changeset
 158  
      */
 159  
     public final Changeset changeset(String node) {
 160  1720
         if (node.equals(Changeset.NULL_ID)) {
 161  141
             return null;
 162  
         } else {
 163  
             try {
 164  1579
                 return basicChangeset(node);
 165  0
             } catch (ExecutionException e) {
 166  0
                 throw Utils.asRuntime(e);
 167  
             }
 168  
         }
 169  
     }
 170  
 
 171  
     /**
 172  
      * 
 173  
      * @param node
 174  
      *            a changeset node id, that is assume not to be the
 175  
      *            null id
 176  
      * @return Changeset object with the specified id
 177  
      */
 178  
     protected Changeset basicChangeset(String node) throws ExecutionException {
 179  1579
         return this.changesetCache.get(node);
 180  
     }
 181  
 
 182  
     /**
 183  
      * @return the command server pool associated with this repository.
 184  
      */
 185  
     public abstract ServerPool getServerPool();
 186  
 
 187  
     /**
 188  
      * @return Get a new decoder for data in this repository
 189  
      */
 190  
     public abstract CharsetDecoder newDecoder();
 191  
 
 192  
     /**
 193  
      * Close the repository. This also stops the command server
 194  
      * associated with the repository.
 195  
      */
 196  
     public void close() {
 197  201
         getServerPool().decrementRefCount();
 198  201
     }
 199  
 
 200  
     /**
 201  
      * @return the root directory of this repository.
 202  
      */
 203  
     public abstract File getDirectory();
 204  
 
 205  
     public abstract BaseRepository getBaseRepository();
 206  
 
 207  
     @Override
 208  
     public String toString() {
 209  0
         return "repo@" + getDirectory();
 210  
     }
 211  
 
 212  
     /**
 213  
      * Add repository specific arguments to the hg command line for
 214  
      * command execution
 215  
      * 
 216  
      * @param commandLine
 217  
      */
 218  
     public void addToCommandLine(List<String> commandLine) {
 219  3808
         String sshBin = getBaseRepository().getConfiguration().getSshBin();
 220  3808
         if (sshBin != null) {
 221  0
             commandLine.add("--config");
 222  0
             commandLine.add("ui.ssh=" + sshBin);
 223  
         }
 224  3808
     }
 225  
 
 226  
     /**
 227  
      * @return The version string for the Mercurial server
 228  
      */
 229  
     public HgVersion getHgVersion() {
 230  14
         return getServerPool().getHgVersion(this);
 231  
     }
 232  
 
 233  
     /**
 234  
      * Create a new WorkingCopy object for this repository.
 235  
      * 
 236  
      * @return a working copy object for this repository
 237  
      */
 238  
     public WorkingCopy workingCopy() {
 239  44
         return new WorkingCopy(this);
 240  
     }
 241  
 
 242  
     /**
 243  
      * 
 244  
      * @return heads for the repository
 245  
      */
 246  
     public List<Changeset> heads() {
 247  2
         return HeadsCommand.on(this).execute();
 248  
     }
 249  
 
 250  
     /**
 251  
      * @deprecated use phases instead
 252  
      * 
 253  
      * @param revs
 254  
      * @return mapping from Changeset to Phase
 255  
      */
 256  
     @Deprecated
 257  
     public Map<Changeset, Phase> readPhases(String... revs) {
 258  0
         return phases(revs);
 259  
     }
 260  
 
 261  
     /**
 262  
      * Return the phases of the specified revisions
 263  
      * 
 264  
      * @param revs
 265  
      * @return Map mapping a {@link Changeset} to a {@link Phase}
 266  
      */
 267  
     public Map<Changeset, Phase> phases(String... revs) {
 268  15
         GenericLogCommand cmd = new GenericLogCommand(this).template("{node} {phase}\\0");
 269  15
         cmd.rev(revs);
 270  15
         HgInputStream stream = cmd.stream();
 271  11
         Map<Changeset, Phase> result = Maps.newHashMap();
 272  
         try {
 273  23
             while (!stream.isEof()) {
 274  
                     // $ hg log --debug --template "{node} {phase}" --rev 5b80e11a7c32121b5fd926b06056bb773eff050f
 275  
                     // removing unknown node dd8c766936b9 from 1-phase boundary
 276  
                     // 5b80e11a7c32121b5fd926b06056bb773eff050f draft
 277  
                     // Observed with at least Mercurial Distributed SCM (version 2.3+10-9d9d15928521)
 278  12
                     String node = stream.textUpTo(' ');
 279  
 
 280  12
                     while ("removing".equals(node) && stream.find('\n')) {
 281  0
                             node = stream.textUpTo(' ');
 282  
                     }
 283  
                     
 284  12
                 String phaseName = stream.textUpTo('\0');
 285  12
                 Phase phase = Phase.fromText(phaseName);
 286  12
                 result.put(changeset(node), phase);
 287  12
             }
 288  0
         } catch (IOException e) {
 289  0
             throw new RuntimeIOException(e);
 290  
         } finally {
 291  0
                 try {
 292  11
                                 stream.consumeAll();
 293  0
                         } catch (IOException e) {
 294  0
                                 throw new RuntimeIOException(e);
 295  11
                         }
 296  
         }
 297  
 
 298  11
         return result;
 299  
     }
 300  
 
 301  
     /**
 302  
      * 
 303  
      * @return the tip Changeset for the repository
 304  
      */
 305  
     public Changeset tip() {
 306  10
         List<Changeset> changesets = LogCommand.on(this).rev("tip").execute();
 307  10
         return Utils.single(changesets);
 308  
     }
 309  
 
 310  
     /**
 311  
      * Convert the specified file object to a file object that is
 312  
      * relative to the root of this repository.
 313  
      * <p>
 314  
      * If the file argument is not absolute it is returned as it is.
 315  
      * 
 316  
      * @param file
 317  
      * @return
 318  
      */
 319  
     public File relativeFile(File file) {
 320  44
         if (file.isAbsolute()) {
 321  44
             String filePath = Utils.resolveSymlinks(file).getPath();
 322  44
             String repoPath = getDirectory().getPath() + File.separator;
 323  44
             if (filePath.startsWith(repoPath)) {
 324  44
                 return new File(filePath.substring(repoPath.length()));
 325  
             } else {
 326  0
                 throw new IllegalArgumentException("" + file + " is not under root of repository");
 327  
             }
 328  
         } else {
 329  0
             return file;
 330  
         }
 331  
     }
 332  
 
 333  
     /**
 334  
      * Return a File object for the file name specified in this
 335  
      * repository.
 336  
      * 
 337  
      * @param name
 338  
      * @return the File
 339  
      */
 340  
     public File file(String name) {
 341  14
         return new File(getDirectory(), name);
 342  
     }
 343  
 
 344  
     public void lock() {
 345  5
         GenericCommand lock = new GenericCommand(this, "javahg-lock");
 346  5
         lock.execute();
 347  5
     }
 348  
 
 349  
     public void unlock() {
 350  5
         GenericCommand unlock = new GenericCommand(this, "javahg-unlock");
 351  5
         unlock.execute();
 352  5
     }
 353  
 
 354  
     private LoadingCache<String, Changeset> createCache(CachePolicy cachePolicy) {
 355  1185
         CacheLoader<String, Changeset> cacheLoader = new CacheLoader<String, Changeset>() {
 356  
             @Override
 357  
             public Changeset load(String key) throws Exception {
 358  967
                 return new Changeset(Repository.this, key);
 359  
             }
 360  
         };
 361  218
         CacheBuilder<Object, Object> cacheBuilder = CacheBuilder.newBuilder();
 362  218
         switch (cachePolicy) {
 363  
         case STRONG:
 364  0
             break;
 365  
         case SOFT:
 366  31
             cacheBuilder.softValues();
 367  31
             break;
 368  
         case WEAK:
 369  187
             cacheBuilder.weakValues();
 370  187
             break;
 371  
         case NONE:
 372  
             // Consider not to have any cache at all
 373  0
             cacheBuilder.maximumSize(0);
 374  
             break;
 375  
         }
 376  218
         return cacheBuilder.build(cacheLoader);
 377  
     }
 378  
 
 379  
     protected LoadingCache<String, Changeset> getChangesetCache() {
 380  7
         return changesetCache;
 381  
     }
 382  
 
 383  
     protected Changeset getChangesetIfInCache(String node) {
 384  0
         return this.changesetCache.getIfPresent(node);
 385  
     }
 386  
 
 387  
     public CacheStats getCacheStats() {
 388  2
         return this.changesetCache.stats();
 389  
     }
 390  
 
 391  
 }