Mercurial > jhg
comparison src/com/tmate/hgkit/ll/LocalHgRepo.java @ 8:a78c980749e3
Filename mangling according to requires options of the store (fncache incomplete for long names)
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
|---|---|
| date | Thu, 23 Dec 2010 01:31:40 +0100 |
| parents | 24bb4f365164 |
| children | d6d2a630f4a6 |
comparison
equal
deleted
inserted
replaced
| 7:286d221f6c28 | 8:a78c980749e3 |
|---|---|
| 1 /** | 1 /** |
| 2 * Copyright (c) 2010 Artem Tikhomirov | 2 * Copyright (c) 2010 Artem Tikhomirov |
| 3 */ | 3 */ |
| 4 package com.tmate.hgkit.ll; | 4 package com.tmate.hgkit.ll; |
| 5 | 5 |
| 6 import java.io.BufferedReader; | |
| 6 import java.io.File; | 7 import java.io.File; |
| 8 import java.io.FileInputStream; | |
| 7 import java.io.IOException; | 9 import java.io.IOException; |
| 10 import java.io.InputStreamReader; | |
| 8 import java.lang.ref.SoftReference; | 11 import java.lang.ref.SoftReference; |
| 12 import java.util.Arrays; | |
| 9 import java.util.HashMap; | 13 import java.util.HashMap; |
| 14 import java.util.TreeSet; | |
| 10 | 15 |
| 11 /** | 16 /** |
| 12 * @author artem | 17 * @author artem |
| 13 */ | 18 */ |
| 14 public class LocalHgRepo extends HgRepository { | 19 public class LocalHgRepo extends HgRepository { |
| 15 | 20 |
| 16 private File repoDir; | 21 private File repoDir; // .hg folder |
| 17 private final String repoLocation; | 22 private final String repoLocation; |
| 18 | 23 |
| 19 public LocalHgRepo(String repositoryPath) { | 24 public LocalHgRepo(String repositoryPath) { |
| 20 setInvalid(true); | 25 setInvalid(true); |
| 21 repoLocation = repositoryPath; | 26 repoLocation = repositoryPath; |
| 24 public LocalHgRepo(File repositoryRoot) throws IOException { | 29 public LocalHgRepo(File repositoryRoot) throws IOException { |
| 25 assert ".hg".equals(repositoryRoot.getName()) && repositoryRoot.isDirectory(); | 30 assert ".hg".equals(repositoryRoot.getName()) && repositoryRoot.isDirectory(); |
| 26 setInvalid(false); | 31 setInvalid(false); |
| 27 repoDir = repositoryRoot; | 32 repoDir = repositoryRoot; |
| 28 repoLocation = repositoryRoot.getParentFile().getCanonicalPath(); | 33 repoLocation = repositoryRoot.getParentFile().getCanonicalPath(); |
| 34 parseRequires(); | |
| 29 } | 35 } |
| 30 | 36 |
| 31 @Override | 37 @Override |
| 32 public String getLocation() { | 38 public String getLocation() { |
| 33 return repoLocation; | 39 return repoLocation; |
| 55 } | 61 } |
| 56 | 62 |
| 57 @Override | 63 @Override |
| 58 public HgDataFile getFileNode(String path) { | 64 public HgDataFile getFileNode(String path) { |
| 59 String nPath = normalize(path); | 65 String nPath = normalize(path); |
| 60 String storagePath = toStoragePath(nPath); | 66 String storagePath = toStoragePath(nPath, true); |
| 61 RevlogStream content = resolve(storagePath); | 67 RevlogStream content = resolve(storagePath); |
| 62 // XXX no content when no file? or HgDataFile.exists() to detect that? How about files that were removed in previous releases? | 68 // XXX no content when no file? or HgDataFile.exists() to detect that? How about files that were removed in previous releases? |
| 63 return new HgDataFile(this, nPath, content); | 69 return new HgDataFile(this, nPath, content); |
| 64 } | 70 } |
| 71 | |
| 72 private boolean revlogv1; | |
| 73 private boolean store; | |
| 74 private boolean fncache; | |
| 75 private boolean dotencode; | |
| 65 | 76 |
| 77 | |
| 78 private void parseRequires() { | |
| 79 File requiresFile = new File(repoDir, "requires"); | |
| 80 if (!requiresFile.exists()) { | |
| 81 return; | |
| 82 } | |
| 83 try { | |
| 84 BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(requiresFile))); | |
| 85 String line; | |
| 86 while ((line = br.readLine()) != null) { | |
| 87 revlogv1 |= "revlogv1".equals(line); | |
| 88 store |= "store".equals(line); | |
| 89 fncache |= "fncache".equals(line); | |
| 90 dotencode |= "dotencode".equals(line); | |
| 91 } | |
| 92 } catch (IOException ex) { | |
| 93 ex.printStackTrace(); // FIXME log | |
| 94 } | |
| 95 } | |
| 96 | |
| 66 // FIXME much more to be done, see store.py:_hybridencode | 97 // FIXME much more to be done, see store.py:_hybridencode |
| 67 private static String toStoragePath(String path) { | 98 // @see http://mercurial.selenic.com/wiki/CaseFoldingPlan |
| 68 // XXX works for lowercase names only | 99 protected String toStoragePath(String path, boolean data) { |
| 69 return "store/data/" + path.replace('\\', '/') + ".i"; | 100 path = normalize(path); |
| 101 final String STR_STORE = "store/"; | |
| 102 final String STR_DATA = "data/"; | |
| 103 final String STR_DH = "dh/"; | |
| 104 if (!data) { | |
| 105 return this.store ? STR_STORE + path : path; | |
| 106 } | |
| 107 path = path.replace(".hg/", ".hg.hg/").replace(".i/", ".i.hg/").replace(".d/", ".d.hg/"); | |
| 108 StringBuilder sb = new StringBuilder(path.length() << 1); | |
| 109 if (store || fncache) { | |
| 110 // encodefilename | |
| 111 final String reservedChars = "\\:*?\"<>|"; | |
| 112 // in fact, \\ is unlikely to match, ever - we've replaced all of them already, above. Just regards to store.py | |
| 113 int x; | |
| 114 char[] hexByte = new char[2]; | |
| 115 for (int i = 0; i < path.length(); i++) { | |
| 116 final char ch = path.charAt(i); | |
| 117 if (ch >= 'a' && ch <= 'z') { | |
| 118 sb.append(ch); // POIRAE | |
| 119 } else if (ch >= 'A' && ch <= 'Z') { | |
| 120 sb.append('_'); | |
| 121 sb.append(Character.toLowerCase(ch)); // Perhaps, (char) (((int) ch) + 32)? Even better, |= 0x20? | |
| 122 } else if ( (x = reservedChars.indexOf(ch)) != -1) { | |
| 123 sb.append('~'); | |
| 124 sb.append(toHexByte(reservedChars.charAt(x), hexByte)); | |
| 125 } else if ((ch >= '~' /*126*/ && ch <= 255) || ch < ' ' /*32*/) { | |
| 126 sb.append('~'); | |
| 127 sb.append(toHexByte(ch, hexByte)); | |
| 128 } else if (ch == '_') { | |
| 129 // note, encoding from store.py:_buildencodefun and :_build_lower_encodefun | |
| 130 // differ in the way they process '_' (latter doesn't escape it) | |
| 131 sb.append('_'); | |
| 132 sb.append('_'); | |
| 133 } else { | |
| 134 sb.append(ch); | |
| 135 } | |
| 136 } | |
| 137 // auxencode | |
| 138 if (fncache) { | |
| 139 x = 0; // last segment start | |
| 140 final TreeSet<String> windowsReservedFilenames = new TreeSet<String>(); | |
| 141 windowsReservedFilenames.addAll(Arrays.asList("con prn aux nul com1 com2 com3 com4 com5 com6 com7 com8 com9 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9".split(" "))); | |
| 142 do { | |
| 143 int i = sb.indexOf("/", x); | |
| 144 if (i == -1) { | |
| 145 i = sb.length(); | |
| 146 } | |
| 147 // windows reserved filenames are at least of length 3 | |
| 148 if (i - x >= 3) { | |
| 149 boolean found = false; | |
| 150 if (i-x == 3) { | |
| 151 found = windowsReservedFilenames.contains(sb.subSequence(x, i)); | |
| 152 } else if (sb.charAt(x+3) == '.') { // implicit i-x > 3 | |
| 153 found = windowsReservedFilenames.contains(sb.subSequence(x, x+3)); | |
| 154 } else if (i-x > 4 && sb.charAt(x+4) == '.') { | |
| 155 found = windowsReservedFilenames.contains(sb.subSequence(x, x+4)); | |
| 156 } | |
| 157 if (found) { | |
| 158 sb.setCharAt(x, '~'); | |
| 159 sb.insert(x+1, toHexByte(sb.charAt(x+2), hexByte)); | |
| 160 i += 2; | |
| 161 } | |
| 162 } | |
| 163 if (dotencode && (sb.charAt(x) == '.' || sb.charAt(x) == ' ')) { | |
| 164 sb.insert(x+1, toHexByte(sb.charAt(x), hexByte)); | |
| 165 sb.setCharAt(x, '~'); // setChar *after* charAt/insert to get ~2e, not ~7e for '.' | |
| 166 i += 2; | |
| 167 } | |
| 168 x = i+1; | |
| 169 } while (x < sb.length()); | |
| 170 } | |
| 171 } | |
| 172 final int MAX_PATH_LEN_IN_HGSTORE = 120; | |
| 173 if (fncache && (sb.length() + STR_DATA.length() > MAX_PATH_LEN_IN_HGSTORE)) { | |
| 174 throw HgRepository.notImplemented(); // FIXME digest and fncache use | |
| 175 } | |
| 176 if (this.store) { | |
| 177 sb.insert(0, STR_STORE + STR_DATA); | |
| 178 } | |
| 179 sb.append(".i"); | |
| 180 return sb.toString(); | |
| 181 } | |
| 182 | |
| 183 private static char[] toHexByte(int ch, char[] buf) { | |
| 184 assert buf.length > 1; | |
| 185 final String hexDigits = "0123456789abcdef"; | |
| 186 buf[0] = hexDigits.charAt((ch & 0x00F0) >> 4); | |
| 187 buf[1] = hexDigits.charAt(ch & 0x0F); | |
| 188 return buf; | |
| 70 } | 189 } |
| 71 | 190 |
| 72 private static String normalize(String path) { | 191 private static String normalize(String path) { |
| 73 return path.replace('\\', '/'); | 192 path = path.replace('\\', '/').replace("//", "/"); |
| 193 if (path.startsWith("/")) { | |
| 194 path = path.substring(1); | |
| 195 } | |
| 196 return path; | |
| 74 } | 197 } |
| 75 } | 198 } |
