Mercurial > jhg
comparison src/org/tmatesoft/hg/repo/HgBranches.java @ 308:3f40262153a4
Recognize closed branches
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
|---|---|
| date | Sat, 24 Sep 2011 07:29:05 +0200 |
| parents | 981f9f50bb6c |
| children | 962f78aac342 |
comparison
equal
deleted
inserted
replaced
| 307:2f2ab5c27f41 | 308:3f40262153a4 |
|---|---|
| 20 import java.io.BufferedWriter; | 20 import java.io.BufferedWriter; |
| 21 import java.io.File; | 21 import java.io.File; |
| 22 import java.io.FileReader; | 22 import java.io.FileReader; |
| 23 import java.io.FileWriter; | 23 import java.io.FileWriter; |
| 24 import java.io.IOException; | 24 import java.io.IOException; |
| 25 import java.util.ArrayList; | |
| 26 import java.util.Arrays; | 25 import java.util.Arrays; |
| 27 import java.util.Collections; | 26 import java.util.Collections; |
| 28 import java.util.HashMap; | 27 import java.util.HashMap; |
| 29 import java.util.HashSet; | 28 import java.util.LinkedHashMap; |
| 30 import java.util.LinkedHashSet; | 29 import java.util.LinkedHashSet; |
| 31 import java.util.LinkedList; | 30 import java.util.LinkedList; |
| 32 import java.util.List; | 31 import java.util.List; |
| 33 import java.util.Map; | 32 import java.util.Map; |
| 34 import java.util.TreeMap; | 33 import java.util.TreeMap; |
| 61 return lastInCache; | 60 return lastInCache; |
| 62 } | 61 } |
| 63 BufferedReader br = null; | 62 BufferedReader br = null; |
| 64 final Pattern spacePattern = Pattern.compile(" "); | 63 final Pattern spacePattern = Pattern.compile(" "); |
| 65 try { | 64 try { |
| 65 final LinkedHashMap<String, List<Nodeid>> branchHeads = new LinkedHashMap<String, List<Nodeid>>(); | |
| 66 br = new BufferedReader(new FileReader(branchheadsCache)); | 66 br = new BufferedReader(new FileReader(branchheadsCache)); |
| 67 String line = br.readLine(); | 67 String line = br.readLine(); |
| 68 if (line == null || line.trim().length() == 0) { | 68 if (line == null || line.trim().length() == 0) { |
| 69 return lastInCache; | 69 return lastInCache; |
| 70 } | 70 } |
| 72 lastInCache = Integer.parseInt(cacheIdentity[1]); | 72 lastInCache = Integer.parseInt(cacheIdentity[1]); |
| 73 // XXX may want to check if nodeid of cset from repo.getChangelog() of lastInCache index match cacheIdentity[0] | 73 // XXX may want to check if nodeid of cset from repo.getChangelog() of lastInCache index match cacheIdentity[0] |
| 74 // | 74 // |
| 75 while ((line = br.readLine()) != null) { | 75 while ((line = br.readLine()) != null) { |
| 76 String[] elements = spacePattern.split(line.trim()); | 76 String[] elements = spacePattern.split(line.trim()); |
| 77 if (elements.length < 2) { | 77 if (elements.length != 2) { |
| 78 // bad entry | 78 // bad entry |
| 79 continue; | 79 continue; |
| 80 } | 80 } |
| 81 Nodeid[] branchHeads = new Nodeid[elements.length - 1]; | |
| 82 for (int i = 0; i < elements.length - 1; i++) { | |
| 83 branchHeads[i] = Nodeid.fromAscii(elements[i]); | |
| 84 } | |
| 85 // I assume split returns substrings of the original string, hence copy of a branch name | 81 // I assume split returns substrings of the original string, hence copy of a branch name |
| 86 String branchName = new String(elements[elements.length-1]); | 82 String branchName = new String(elements[elements.length-1]); |
| 87 BranchInfo bi = new BranchInfo(branchName, branchHeads); | 83 List<Nodeid> heads = branchHeads.get(elements[1]); |
| 88 branches.put(branchName, bi); | 84 if (heads == null) { |
| 85 branchHeads.put(branchName, heads = new LinkedList<Nodeid>()); | |
| 86 } | |
| 87 heads.add(Nodeid.fromAscii(elements[0])); | |
| 88 } | |
| 89 for (Map.Entry<String, List<Nodeid>> e : branchHeads.entrySet()) { | |
| 90 Nodeid[] heads = e.getValue().toArray(new Nodeid[e.getValue().size()]); | |
| 91 BranchInfo bi = new BranchInfo(e.getKey(), heads); | |
| 92 branches.put(e.getKey(), bi); | |
| 89 } | 93 } |
| 90 return lastInCache; | 94 return lastInCache; |
| 91 } catch (IOException ex) { | 95 } catch (IOException ex) { |
| 92 repo.getContext().getLog().warn(getClass(), ex, null); // log error, but otherwise do nothing | 96 repo.getContext().getLog().warn(getClass(), ex, null); // log error, but otherwise do nothing |
| 93 } finally { | 97 } finally { |
| 169 isCacheActual = lastCached == repo.getChangelog().getLastRevision(); | 173 isCacheActual = lastCached == repo.getChangelog().getLastRevision(); |
| 170 if (!isCacheActual) { | 174 if (!isCacheActual) { |
| 171 final HgChangelog.ParentWalker pw = repo.getChangelog().new ParentWalker(); | 175 final HgChangelog.ParentWalker pw = repo.getChangelog().new ParentWalker(); |
| 172 pw.init(); | 176 pw.init(); |
| 173 ps.worked(repo.getChangelog().getRevisionCount()); | 177 ps.worked(repo.getChangelog().getRevisionCount()); |
| 178 // first revision branch found at | |
| 174 final HashMap<String, Nodeid> branchStart = new HashMap<String, Nodeid>(); | 179 final HashMap<String, Nodeid> branchStart = new HashMap<String, Nodeid>(); |
| 180 // last revision seen for the branch | |
| 175 final HashMap<String, Nodeid> branchLastSeen = new HashMap<String, Nodeid>(); | 181 final HashMap<String, Nodeid> branchLastSeen = new HashMap<String, Nodeid>(); |
| 182 // revisions from the branch that have no children at all | |
| 176 final HashMap<String, List<Nodeid>> branchHeads = new HashMap<String, List<Nodeid>>(); | 183 final HashMap<String, List<Nodeid>> branchHeads = new HashMap<String, List<Nodeid>>(); |
| 177 final HashSet<String> closedBranches = new HashSet<String>(); | 184 // revisions that are immediate children of a node from a given branch |
| 185 final HashMap<String, List<Nodeid>> branchHeadCandidates = new HashMap<String, List<Nodeid>>(); | |
| 178 HgChangelog.Inspector insp = new HgChangelog.Inspector() { | 186 HgChangelog.Inspector insp = new HgChangelog.Inspector() { |
| 179 | 187 |
| 180 public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) { | 188 public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) { |
| 181 String branchName = cset.branch(); | 189 String branchName = cset.branch(); |
| 182 if (!branchStart.containsKey(branchName)) { | 190 if (!branchStart.containsKey(branchName)) { |
| 183 branchStart.put(branchName, nodeid); | 191 branchStart.put(branchName, nodeid); |
| 184 branchHeads.put(branchName, new LinkedList<Nodeid>()); | 192 branchHeads.put(branchName, new LinkedList<Nodeid>()); |
| 185 } | 193 branchHeadCandidates.put(branchName, new LinkedList<Nodeid>()); |
| 186 branchLastSeen.remove(branchName); | |
| 187 if ("1".equals(cset.extras().get("close"))) { | |
| 188 branchHeads.get(branchName).add(nodeid); // XXX what if it still has children? | |
| 189 closedBranches.add(branchName); | |
| 190 } else { | 194 } else { |
| 191 if (pw.hasChildren(nodeid)) { | 195 final List<Nodeid> headCandidates = branchHeadCandidates.get(branchName); |
| 192 // children may be in another branch | 196 if (headCandidates.remove(nodeid)) { |
| 193 // and unless we later came across another element from this branch, | 197 // no need to keep parent, as we found at least 1 child thereof to be at the same branch |
| 194 // we need to record all these as valid heads | 198 branchLastSeen.remove(branchName); |
| 195 // XXX what about next case: head1 with children in different branch, and head2 without children | |
| 196 // head1 would get lost | |
| 197 branchLastSeen.put(branchName, nodeid); | |
| 198 } else { | |
| 199 // no more children known for this node, it's (one of the) head of the branch | |
| 200 branchHeads.get(branchName).add(nodeid); | |
| 201 } | 199 } |
| 200 } | |
| 201 List<Nodeid> immediateChildren = pw.directChildren(nodeid); | |
| 202 if (immediateChildren.size() > 0) { | |
| 203 // 1) children may be in another branch | |
| 204 // and unless we later came across another element from this branch, | |
| 205 // we need to record all these as potential heads | |
| 206 // | |
| 207 // 2) head1 with children in different branch, and head2 in this branch without children | |
| 208 branchLastSeen.put(branchName, nodeid); | |
| 209 branchHeadCandidates.get(branchName).addAll(immediateChildren); | |
| 210 } else { | |
| 211 // no more children known for this node, it's (one of the) head of the branch | |
| 212 branchHeads.get(branchName).add(nodeid); | |
| 202 } | 213 } |
| 203 ps.worked(1); | 214 ps.worked(1); |
| 204 } | 215 } |
| 205 }; | 216 }; |
| 206 repo.getChangelog().range(lastCached == -1 ? 0 : lastCached+1, HgRepository.TIP, insp); | 217 repo.getChangelog().range(lastCached == -1 ? 0 : lastCached+1, HgRepository.TIP, insp); |
| 218 // System.out.println("HEAD CANDIDATES>>>"); | |
| 219 // for (String bn : branchHeadCandidates.keySet()) { | |
| 220 // System.out.println(bn + ":" + branchHeadCandidates.get(bn).toString()); | |
| 221 // } | |
| 222 // System.out.println("HEAD CANDIDATES<<<"); | |
| 223 // those last seen revisions from the branch that had no children from the same branch are heads. | |
| 207 for (String bn : branchLastSeen.keySet()) { | 224 for (String bn : branchLastSeen.keySet()) { |
| 225 // these are inactive branches? - there were children, but not from the same branch? | |
| 208 branchHeads.get(bn).add(branchLastSeen.get(bn)); | 226 branchHeads.get(bn).add(branchLastSeen.get(bn)); |
| 209 } | 227 } |
| 210 for (String bn : branchStart.keySet()) { | 228 for (String bn : branchStart.keySet()) { |
| 211 BranchInfo bi = branches.get(bn); | 229 BranchInfo bi = branches.get(bn); |
| 212 if (bi != null) { | 230 if (bi != null) { |
| 227 } | 245 } |
| 228 } | 246 } |
| 229 } // else - oldHead still head for the branch | 247 } // else - oldHead still head for the branch |
| 230 } | 248 } |
| 231 heads.addAll(branchHeads.get(bn)); | 249 heads.addAll(branchHeads.get(bn)); |
| 232 bi = new BranchInfo(bn, bi.getStart(), heads.toArray(new Nodeid[0]), bi.isClosed() && closedBranches.contains(bn)); | 250 bi = new BranchInfo(bn, bi.getStart(), heads.toArray(new Nodeid[0])); |
| 233 } else { | 251 } else { |
| 234 Nodeid[] heads = branchHeads.get(bn).toArray(new Nodeid[0]); | 252 Nodeid[] heads = branchHeads.get(bn).toArray(new Nodeid[0]); |
| 235 bi = new BranchInfo(bn, branchStart.get(bn), heads, closedBranches.contains(bn)); | 253 bi = new BranchInfo(bn, branchStart.get(bn), heads); |
| 236 } | 254 } |
| 237 branches.put(bn, bi); | 255 branches.put(bn, bi); |
| 238 } | 256 } |
| 257 } | |
| 258 final HgChangelog clog = repo.getChangelog(); | |
| 259 final HgChangelog.RevisionMap rmap = clog.new RevisionMap().init(); | |
| 260 for (BranchInfo bi : branches.values()) { | |
| 261 bi.validate(clog, rmap); | |
| 239 } | 262 } |
| 240 ps.done(); | 263 ps.done(); |
| 241 } | 264 } |
| 242 | 265 |
| 243 public List<BranchInfo> getAllBranches() { | 266 public List<BranchInfo> getAllBranches() { |
| 293 return new File(repo.getRepositoryRoot(), "cache/branchheads"); | 316 return new File(repo.getRepositoryRoot(), "cache/branchheads"); |
| 294 } | 317 } |
| 295 | 318 |
| 296 public static class BranchInfo { | 319 public static class BranchInfo { |
| 297 private final String name; | 320 private final String name; |
| 298 private final List<Nodeid> heads; | 321 private List<Nodeid> heads; |
| 299 private final boolean closed; | 322 private boolean closed; |
| 300 private final Nodeid start; | 323 private final Nodeid start; |
| 301 | 324 |
| 302 // XXX in fact, few but not all branchHeads might be closed, and isClosed for whole branch is not | 325 // XXX in fact, few but not all branchHeads might be closed, and isClosed for whole branch is not |
| 303 // possible to determine. | 326 // possible to determine. |
| 304 BranchInfo(String branchName, Nodeid first, Nodeid[] branchHeads, boolean isClosed) { | 327 BranchInfo(String branchName, Nodeid first, Nodeid[] branchHeads) { |
| 305 name = branchName; | 328 name = branchName; |
| 306 start = first; | 329 start = first; |
| 307 heads = Collections.unmodifiableList(new ArrayList<Nodeid>(Arrays.asList(branchHeads))); | 330 heads = Arrays.asList(branchHeads); |
| 308 closed = isClosed; | |
| 309 } | 331 } |
| 310 | 332 |
| 311 // incomplete branch, there's not enough information at the time of creation. shall be replaced with | 333 // incomplete branch, there's not enough information at the time of creation. shall be replaced with |
| 312 // proper BI in #collect() | 334 // proper BI in #collect() |
| 313 BranchInfo(String branchName, Nodeid[] branchHeads) { | 335 BranchInfo(String branchName, Nodeid[] branchHeads) { |
| 314 this(branchName, Nodeid.NULL, branchHeads, false); | 336 this(branchName, Nodeid.NULL, branchHeads); |
| 337 } | |
| 338 | |
| 339 void validate(HgChangelog clog, HgChangelog.RevisionMap rmap) { | |
| 340 int[] localCset = new int[heads.size()]; | |
| 341 int i = 0; | |
| 342 for (Nodeid h : heads) { | |
| 343 localCset[i++] = rmap.localRevision(h); | |
| 344 } | |
| 345 // [0] tipmost, [1] tipmost open | |
| 346 final Nodeid[] tipmost = new Nodeid[] {null, null}; | |
| 347 final boolean[] allClosed = new boolean[] { true }; | |
| 348 clog.range(new HgChangelog.Inspector() { | |
| 349 | |
| 350 public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) { | |
| 351 assert heads.contains(nodeid); | |
| 352 tipmost[0] = nodeid; | |
| 353 if (!"1".equals(cset.extras().get("close"))) { | |
| 354 tipmost[1] = nodeid; | |
| 355 allClosed[0] = false; | |
| 356 } | |
| 357 } | |
| 358 }, localCset); | |
| 359 closed = allClosed[0]; | |
| 360 Nodeid[] outcome = new Nodeid[localCset.length]; | |
| 361 i = 0; | |
| 362 if (!closed && tipmost[1] != null) { | |
| 363 outcome[i++] = tipmost[1]; | |
| 364 if (i < outcome.length && !tipmost[0].equals(tipmost[1])) { | |
| 365 outcome[i++] = tipmost[0]; | |
| 366 } | |
| 367 } else { | |
| 368 outcome[i++] = tipmost[0]; | |
| 369 } | |
| 370 for (Nodeid h : heads) { | |
| 371 if (!h.equals(tipmost[0]) && !h.equals(tipmost[1])) { | |
| 372 outcome[i++] = h; | |
| 373 } | |
| 374 } | |
| 375 heads = Arrays.asList(outcome); | |
| 315 } | 376 } |
| 316 | 377 |
| 317 public String getName() { | 378 public String getName() { |
| 318 return name; | 379 return name; |
| 319 } | 380 } |
| 320 /*public*/ boolean isClosed() { | 381 /** |
| 382 * @return <code>true</code> if all heads of this branch are marked as closed | |
| 383 */ | |
| 384 public boolean isClosed() { | |
| 321 return closed; | 385 return closed; |
| 322 } | 386 } |
| 323 public List<Nodeid> getHeads() { | 387 public List<Nodeid> getHeads() { |
| 324 return heads; | 388 return heads; |
| 325 } | 389 } |
