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 }