Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

RenameDetector.java 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707
  1. /*
  2. * Copyright (C) 2010, Google Inc.
  3. * and other copyright owners as documented in the project's IP log.
  4. *
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Distribution License v1.0 which
  7. * accompanies this distribution, is reproduced below, and is
  8. * available at http://www.eclipse.org/org/documents/edl-v10.php
  9. *
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or
  13. * without modification, are permitted provided that the following
  14. * conditions are met:
  15. *
  16. * - Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. *
  19. * - Redistributions in binary form must reproduce the above
  20. * copyright notice, this list of conditions and the following
  21. * disclaimer in the documentation and/or other materials provided
  22. * with the distribution.
  23. *
  24. * - Neither the name of the Eclipse Foundation, Inc. nor the
  25. * names of its contributors may be used to endorse or promote
  26. * products derived from this software without specific prior
  27. * written permission.
  28. *
  29. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42. */
  43. package org.eclipse.jgit.diff;
  44. import static org.eclipse.jgit.diff.DiffEntry.Side.NEW;
  45. import static org.eclipse.jgit.diff.DiffEntry.Side.OLD;
  46. import java.io.IOException;
  47. import java.util.ArrayList;
  48. import java.util.Arrays;
  49. import java.util.Collection;
  50. import java.util.Collections;
  51. import java.util.Comparator;
  52. import java.util.HashMap;
  53. import java.util.List;
  54. import org.eclipse.jgit.JGitText;
  55. import org.eclipse.jgit.diff.DiffEntry.ChangeType;
  56. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  57. import org.eclipse.jgit.lib.FileMode;
  58. import org.eclipse.jgit.lib.NullProgressMonitor;
  59. import org.eclipse.jgit.lib.ObjectReader;
  60. import org.eclipse.jgit.lib.ProgressMonitor;
  61. import org.eclipse.jgit.lib.Repository;
  62. /** Detect and resolve object renames. */
  63. public class RenameDetector {
  64. private static final int EXACT_RENAME_SCORE = 100;
  65. private static final Comparator<DiffEntry> DIFF_COMPARATOR = new Comparator<DiffEntry>() {
  66. public int compare(DiffEntry a, DiffEntry b) {
  67. int cmp = nameOf(a).compareTo(nameOf(b));
  68. if (cmp == 0)
  69. cmp = sortOf(a.getChangeType()) - sortOf(b.getChangeType());
  70. return cmp;
  71. }
  72. private String nameOf(DiffEntry ent) {
  73. // Sort by the new name, unless the change is a delete. On
  74. // deletes the new name is /dev/null, so we sort instead by
  75. // the old name.
  76. //
  77. if (ent.changeType == ChangeType.DELETE)
  78. return ent.oldPath;
  79. return ent.newPath;
  80. }
  81. private int sortOf(ChangeType changeType) {
  82. // Sort deletes before adds so that a major type change for
  83. // a file path (such as symlink to regular file) will first
  84. // remove the path, then add it back with the new type.
  85. //
  86. switch (changeType) {
  87. case DELETE:
  88. return 1;
  89. case ADD:
  90. return 2;
  91. default:
  92. return 10;
  93. }
  94. }
  95. };
  96. private List<DiffEntry> entries;
  97. private List<DiffEntry> deleted;
  98. private List<DiffEntry> added;
  99. private boolean done;
  100. private final Repository repo;
  101. /** Similarity score required to pair an add/delete as a rename. */
  102. private int renameScore = 60;
  103. /**
  104. * Similarity score required to keep modified file pairs together. Any
  105. * modified file pairs with a similarity score below this will be broken
  106. * apart.
  107. */
  108. private int breakScore = -1;
  109. /** Limit in the number of files to consider for renames. */
  110. private int renameLimit;
  111. /** Set if the number of adds or deletes was over the limit. */
  112. private boolean overRenameLimit;
  113. /**
  114. * Create a new rename detector for the given repository
  115. *
  116. * @param repo
  117. * the repository to use for rename detection
  118. */
  119. public RenameDetector(Repository repo) {
  120. this.repo = repo;
  121. DiffConfig cfg = repo.getConfig().get(DiffConfig.KEY);
  122. renameLimit = cfg.getRenameLimit();
  123. reset();
  124. }
  125. /**
  126. * @return minimum score required to pair an add/delete as a rename. The
  127. * score ranges are within the bounds of (0, 100).
  128. */
  129. public int getRenameScore() {
  130. return renameScore;
  131. }
  132. /**
  133. * Set the minimum score required to pair an add/delete as a rename.
  134. * <p>
  135. * When comparing two files together their score must be greater than or
  136. * equal to the rename score for them to be considered a rename match. The
  137. * score is computed based on content similarity, so a score of 60 implies
  138. * that approximately 60% of the bytes in the files are identical.
  139. *
  140. * @param score
  141. * new rename score, must be within [0, 100].
  142. * @throws IllegalArgumentException
  143. * the score was not within [0, 100].
  144. */
  145. public void setRenameScore(int score) {
  146. if (score < 0 || score > 100)
  147. throw new IllegalArgumentException(
  148. JGitText.get().similarityScoreMustBeWithinBounds);
  149. renameScore = score;
  150. }
  151. /**
  152. * @return the similarity score required to keep modified file pairs
  153. * together. Any modify pairs that score below this will be broken
  154. * apart into separate add/deletes. Values less than or equal to
  155. * zero indicate that no modifies will be broken apart. Values over
  156. * 100 cause all modify pairs to be broken.
  157. */
  158. public int getBreakScore() {
  159. return breakScore;
  160. }
  161. /**
  162. * @param breakScore
  163. * the similarity score required to keep modified file pairs
  164. * together. Any modify pairs that score below this will be
  165. * broken apart into separate add/deletes. Values less than or
  166. * equal to zero indicate that no modifies will be broken apart.
  167. * Values over 100 cause all modify pairs to be broken.
  168. */
  169. public void setBreakScore(int breakScore) {
  170. this.breakScore = breakScore;
  171. }
  172. /** @return limit on number of paths to perform inexact rename detection. */
  173. public int getRenameLimit() {
  174. return renameLimit;
  175. }
  176. /**
  177. * Set the limit on the number of files to perform inexact rename detection.
  178. * <p>
  179. * The rename detector has to build a square matrix of the rename limit on
  180. * each side, then perform that many file compares to determine similarity.
  181. * If 1000 files are added, and 1000 files are deleted, a 1000*1000 matrix
  182. * must be allocated, and 1,000,000 file compares may need to be performed.
  183. *
  184. * @param limit
  185. * new file limit.
  186. */
  187. public void setRenameLimit(int limit) {
  188. renameLimit = limit;
  189. }
  190. /**
  191. * Check if the detector is over the rename limit.
  192. * <p>
  193. * This method can be invoked either before or after {@code getEntries} has
  194. * been used to perform rename detection.
  195. *
  196. * @return true if the detector has more file additions or removals than the
  197. * rename limit is currently set to. In such configurations the
  198. * detector will skip expensive computation.
  199. */
  200. public boolean isOverRenameLimit() {
  201. if (done)
  202. return overRenameLimit;
  203. int cnt = Math.max(added.size(), deleted.size());
  204. return getRenameLimit() != 0 && getRenameLimit() < cnt;
  205. }
  206. /**
  207. * Add entries to be considered for rename detection.
  208. *
  209. * @param entriesToAdd
  210. * one or more entries to add.
  211. * @throws IllegalStateException
  212. * if {@code getEntries} was already invoked.
  213. */
  214. public void addAll(Collection<DiffEntry> entriesToAdd) {
  215. if (done)
  216. throw new IllegalStateException(JGitText.get().renamesAlreadyFound);
  217. for (DiffEntry entry : entriesToAdd) {
  218. switch (entry.getChangeType()) {
  219. case ADD:
  220. added.add(entry);
  221. break;
  222. case DELETE:
  223. deleted.add(entry);
  224. break;
  225. case MODIFY:
  226. if (sameType(entry.getOldMode(), entry.getNewMode())) {
  227. entries.add(entry);
  228. } else {
  229. List<DiffEntry> tmp = DiffEntry.breakModify(entry);
  230. deleted.add(tmp.get(0));
  231. added.add(tmp.get(1));
  232. }
  233. break;
  234. case COPY:
  235. case RENAME:
  236. default:
  237. entriesToAdd.add(entry);
  238. }
  239. }
  240. }
  241. /**
  242. * Add an entry to be considered for rename detection.
  243. *
  244. * @param entry
  245. * to add.
  246. * @throws IllegalStateException
  247. * if {@code getEntries} was already invoked.
  248. */
  249. public void add(DiffEntry entry) {
  250. addAll(Collections.singletonList(entry));
  251. }
  252. /**
  253. * Detect renames in the current file set.
  254. * <p>
  255. * This convenience function runs without a progress monitor.
  256. *
  257. * @return an unmodifiable list of {@link DiffEntry}s representing all files
  258. * that have been changed.
  259. * @throws IOException
  260. * file contents cannot be read from the repository.
  261. */
  262. public List<DiffEntry> compute() throws IOException {
  263. return compute(NullProgressMonitor.INSTANCE);
  264. }
  265. /**
  266. * Detect renames in the current file set.
  267. *
  268. * @param pm
  269. * report progress during the detection phases.
  270. * @return an unmodifiable list of {@link DiffEntry}s representing all files
  271. * that have been changed.
  272. * @throws IOException
  273. * file contents cannot be read from the repository.
  274. */
  275. public List<DiffEntry> compute(ProgressMonitor pm) throws IOException {
  276. if (!done) {
  277. ObjectReader reader = repo.newObjectReader();
  278. try {
  279. return compute(reader, pm);
  280. } finally {
  281. reader.release();
  282. }
  283. }
  284. return Collections.unmodifiableList(entries);
  285. }
  286. /**
  287. * Detect renames in the current file set.
  288. *
  289. * @param reader
  290. * reader to obtain objects from the repository with.
  291. * @param pm
  292. * report progress during the detection phases.
  293. * @return an unmodifiable list of {@link DiffEntry}s representing all files
  294. * that have been changed.
  295. * @throws IOException
  296. * file contents cannot be read from the repository.
  297. */
  298. public List<DiffEntry> compute(ObjectReader reader, ProgressMonitor pm)
  299. throws IOException {
  300. final ContentSource cs = ContentSource.create(reader);
  301. return compute(new ContentSource.Pair(cs, cs), pm);
  302. }
  303. /**
  304. * Detect renames in the current file set.
  305. *
  306. * @param reader
  307. * reader to obtain objects from the repository with.
  308. * @param pm
  309. * report progress during the detection phases.
  310. * @return an unmodifiable list of {@link DiffEntry}s representing all files
  311. * that have been changed.
  312. * @throws IOException
  313. * file contents cannot be read from the repository.
  314. */
  315. public List<DiffEntry> compute(ContentSource.Pair reader, ProgressMonitor pm)
  316. throws IOException {
  317. if (!done) {
  318. done = true;
  319. if (pm == null)
  320. pm = NullProgressMonitor.INSTANCE;
  321. breakModifies(reader, pm);
  322. findExactRenames(pm);
  323. findContentRenames(reader, pm);
  324. rejoinModifies(pm);
  325. entries.addAll(added);
  326. added = null;
  327. entries.addAll(deleted);
  328. deleted = null;
  329. Collections.sort(entries, DIFF_COMPARATOR);
  330. }
  331. return Collections.unmodifiableList(entries);
  332. }
  333. /** Reset this rename detector for another rename detection pass. */
  334. public void reset() {
  335. entries = new ArrayList<DiffEntry>();
  336. deleted = new ArrayList<DiffEntry>();
  337. added = new ArrayList<DiffEntry>();
  338. done = false;
  339. }
  340. private void breakModifies(ContentSource.Pair reader, ProgressMonitor pm)
  341. throws IOException {
  342. if (breakScore <= 0)
  343. return;
  344. ArrayList<DiffEntry> newEntries = new ArrayList<DiffEntry>(entries.size());
  345. pm.beginTask(JGitText.get().renamesBreakingModifies, entries.size());
  346. for (int i = 0; i < entries.size(); i++) {
  347. DiffEntry e = entries.get(i);
  348. if (e.getChangeType() == ChangeType.MODIFY) {
  349. int score = calculateModifyScore(reader, e);
  350. if (score < breakScore) {
  351. List<DiffEntry> tmp = DiffEntry.breakModify(e);
  352. DiffEntry del = tmp.get(0);
  353. del.score = score;
  354. deleted.add(del);
  355. added.add(tmp.get(1));
  356. } else {
  357. newEntries.add(e);
  358. }
  359. } else {
  360. newEntries.add(e);
  361. }
  362. pm.update(1);
  363. }
  364. entries = newEntries;
  365. }
  366. private void rejoinModifies(ProgressMonitor pm) {
  367. HashMap<String, DiffEntry> nameMap = new HashMap<String, DiffEntry>();
  368. ArrayList<DiffEntry> newAdded = new ArrayList<DiffEntry>(added.size());
  369. pm.beginTask(JGitText.get().renamesRejoiningModifies, added.size()
  370. + deleted.size());
  371. for (DiffEntry src : deleted) {
  372. nameMap.put(src.oldPath, src);
  373. pm.update(1);
  374. }
  375. for (DiffEntry dst : added) {
  376. DiffEntry src = nameMap.remove(dst.newPath);
  377. if (src != null) {
  378. if (sameType(src.oldMode, dst.newMode)) {
  379. entries.add(DiffEntry.pair(ChangeType.MODIFY, src, dst,
  380. src.score));
  381. } else {
  382. nameMap.put(src.oldPath, src);
  383. newAdded.add(dst);
  384. }
  385. } else {
  386. newAdded.add(dst);
  387. }
  388. pm.update(1);
  389. }
  390. added = newAdded;
  391. deleted = new ArrayList<DiffEntry>(nameMap.values());
  392. }
  393. private int calculateModifyScore(ContentSource.Pair reader, DiffEntry d)
  394. throws IOException {
  395. SimilarityIndex src = new SimilarityIndex();
  396. src.hash(reader.open(OLD, d));
  397. src.sort();
  398. SimilarityIndex dst = new SimilarityIndex();
  399. dst.hash(reader.open(NEW, d));
  400. dst.sort();
  401. return src.score(dst, 100);
  402. }
  403. private void findContentRenames(ContentSource.Pair reader,
  404. ProgressMonitor pm)
  405. throws IOException {
  406. int cnt = Math.max(added.size(), deleted.size());
  407. if (cnt == 0)
  408. return;
  409. if (getRenameLimit() == 0 || cnt <= getRenameLimit()) {
  410. SimilarityRenameDetector d;
  411. d = new SimilarityRenameDetector(reader, deleted, added);
  412. d.setRenameScore(getRenameScore());
  413. d.compute(pm);
  414. deleted = d.getLeftOverSources();
  415. added = d.getLeftOverDestinations();
  416. entries.addAll(d.getMatches());
  417. } else {
  418. overRenameLimit = true;
  419. }
  420. }
  421. @SuppressWarnings("unchecked")
  422. private void findExactRenames(ProgressMonitor pm) {
  423. if (added.isEmpty() || deleted.isEmpty())
  424. return;
  425. pm.beginTask(JGitText.get().renamesFindingExact, //
  426. added.size() + added.size() + deleted.size()
  427. + added.size() * deleted.size());
  428. HashMap<AbbreviatedObjectId, Object> deletedMap = populateMap(deleted, pm);
  429. HashMap<AbbreviatedObjectId, Object> addedMap = populateMap(added, pm);
  430. ArrayList<DiffEntry> uniqueAdds = new ArrayList<DiffEntry>(added.size());
  431. ArrayList<List<DiffEntry>> nonUniqueAdds = new ArrayList<List<DiffEntry>>();
  432. for (Object o : addedMap.values()) {
  433. if (o instanceof DiffEntry)
  434. uniqueAdds.add((DiffEntry) o);
  435. else
  436. nonUniqueAdds.add((List<DiffEntry>) o);
  437. }
  438. ArrayList<DiffEntry> left = new ArrayList<DiffEntry>(added.size());
  439. for (DiffEntry a : uniqueAdds) {
  440. Object del = deletedMap.get(a.newId);
  441. if (del instanceof DiffEntry) {
  442. // We have one add to one delete: pair them if they are the same
  443. // type
  444. DiffEntry e = (DiffEntry) del;
  445. if (sameType(e.oldMode, a.newMode)) {
  446. e.changeType = ChangeType.RENAME;
  447. entries.add(exactRename(e, a));
  448. } else {
  449. left.add(a);
  450. }
  451. } else if (del != null) {
  452. // We have one add to many deletes: find the delete with the
  453. // same type and closest name to the add, then pair them
  454. List<DiffEntry> list = (List<DiffEntry>) del;
  455. DiffEntry best = bestPathMatch(a, list);
  456. if (best != null) {
  457. best.changeType = ChangeType.RENAME;
  458. entries.add(exactRename(best, a));
  459. } else {
  460. left.add(a);
  461. }
  462. } else {
  463. left.add(a);
  464. }
  465. pm.update(1);
  466. }
  467. for (List<DiffEntry> adds : nonUniqueAdds) {
  468. Object o = deletedMap.get(adds.get(0).newId);
  469. if (o instanceof DiffEntry) {
  470. // We have many adds to one delete: find the add with the same
  471. // type and closest name to the delete, then pair them. Mark the
  472. // rest as copies of the delete.
  473. DiffEntry d = (DiffEntry) o;
  474. DiffEntry best = bestPathMatch(d, adds);
  475. if (best != null) {
  476. d.changeType = ChangeType.RENAME;
  477. entries.add(exactRename(d, best));
  478. for (DiffEntry a : adds) {
  479. if (a != best) {
  480. if (sameType(d.oldMode, a.newMode)) {
  481. entries.add(exactCopy(d, a));
  482. } else {
  483. left.add(a);
  484. }
  485. }
  486. }
  487. } else {
  488. left.addAll(adds);
  489. }
  490. } else if (o != null) {
  491. // We have many adds to many deletes: score all the adds against
  492. // all the deletes by path name, take the best matches, pair
  493. // them as renames, then call the rest copies
  494. List<DiffEntry> dels = (List<DiffEntry>) o;
  495. long[] matrix = new long[dels.size() * adds.size()];
  496. int mNext = 0;
  497. for (int delIdx = 0; delIdx < dels.size(); delIdx++) {
  498. String deletedName = dels.get(delIdx).oldPath;
  499. for (int addIdx = 0; addIdx < adds.size(); addIdx++) {
  500. String addedName = adds.get(addIdx).newPath;
  501. int score = SimilarityRenameDetector.nameScore(addedName, deletedName);
  502. matrix[mNext] = SimilarityRenameDetector.encode(score, delIdx, addIdx);
  503. mNext++;
  504. }
  505. }
  506. Arrays.sort(matrix);
  507. for (--mNext; mNext >= 0; mNext--) {
  508. long ent = matrix[mNext];
  509. int delIdx = SimilarityRenameDetector.srcFile(ent);
  510. int addIdx = SimilarityRenameDetector.dstFile(ent);
  511. DiffEntry d = dels.get(delIdx);
  512. DiffEntry a = adds.get(addIdx);
  513. if (a == null) {
  514. pm.update(1);
  515. continue; // was already matched earlier
  516. }
  517. ChangeType type;
  518. if (d.changeType == ChangeType.DELETE) {
  519. // First use of this source file. Tag it as a rename so we
  520. // later know it is already been used as a rename, other
  521. // matches (if any) will claim themselves as copies instead.
  522. //
  523. d.changeType = ChangeType.RENAME;
  524. type = ChangeType.RENAME;
  525. } else {
  526. type = ChangeType.COPY;
  527. }
  528. entries.add(DiffEntry.pair(type, d, a, 100));
  529. adds.set(addIdx, null); // Claim the destination was matched.
  530. pm.update(1);
  531. }
  532. } else {
  533. left.addAll(adds);
  534. }
  535. }
  536. added = left;
  537. deleted = new ArrayList<DiffEntry>(deletedMap.size());
  538. for (Object o : deletedMap.values()) {
  539. if (o instanceof DiffEntry) {
  540. DiffEntry e = (DiffEntry) o;
  541. if (e.changeType == ChangeType.DELETE)
  542. deleted.add(e);
  543. } else {
  544. List<DiffEntry> list = (List<DiffEntry>) o;
  545. for (DiffEntry e : list) {
  546. if (e.changeType == ChangeType.DELETE)
  547. deleted.add(e);
  548. }
  549. }
  550. }
  551. pm.endTask();
  552. }
  553. /**
  554. * Find the best match by file path for a given DiffEntry from a list of
  555. * DiffEntrys. The returned DiffEntry will be of the same type as <src>. If
  556. * no DiffEntry can be found that has the same type, this method will return
  557. * null.
  558. *
  559. * @param src
  560. * the DiffEntry to try to find a match for
  561. * @param list
  562. * a list of DiffEntrys to search through
  563. * @return the DiffEntry from <list> who's file path best matches <src>
  564. */
  565. private static DiffEntry bestPathMatch(DiffEntry src, List<DiffEntry> list) {
  566. DiffEntry best = null;
  567. int score = -1;
  568. for (DiffEntry d : list) {
  569. if (sameType(mode(d), mode(src))) {
  570. int tmp = SimilarityRenameDetector
  571. .nameScore(path(d), path(src));
  572. if (tmp > score) {
  573. best = d;
  574. score = tmp;
  575. }
  576. }
  577. }
  578. return best;
  579. }
  580. @SuppressWarnings("unchecked")
  581. private HashMap<AbbreviatedObjectId, Object> populateMap(
  582. List<DiffEntry> diffEntries, ProgressMonitor pm) {
  583. HashMap<AbbreviatedObjectId, Object> map = new HashMap<AbbreviatedObjectId, Object>();
  584. for (DiffEntry de : diffEntries) {
  585. Object old = map.put(id(de), de);
  586. if (old instanceof DiffEntry) {
  587. ArrayList<DiffEntry> list = new ArrayList<DiffEntry>(2);
  588. list.add((DiffEntry) old);
  589. list.add(de);
  590. map.put(id(de), list);
  591. } else if (old != null) {
  592. // Must be a list of DiffEntries
  593. ((List<DiffEntry>) old).add(de);
  594. map.put(id(de), old);
  595. }
  596. pm.update(1);
  597. }
  598. return map;
  599. }
  600. private static String path(DiffEntry de) {
  601. return de.changeType == ChangeType.DELETE ? de.oldPath : de.newPath;
  602. }
  603. private static FileMode mode(DiffEntry de) {
  604. return de.changeType == ChangeType.DELETE ? de.oldMode : de.newMode;
  605. }
  606. private static AbbreviatedObjectId id(DiffEntry de) {
  607. return de.changeType == ChangeType.DELETE ? de.oldId : de.newId;
  608. }
  609. static boolean sameType(FileMode a, FileMode b) {
  610. // Files have to be of the same type in order to rename them.
  611. // We would never want to rename a file to a gitlink, or a
  612. // symlink to a file.
  613. //
  614. int aType = a.getBits() & FileMode.TYPE_MASK;
  615. int bType = b.getBits() & FileMode.TYPE_MASK;
  616. return aType == bType;
  617. }
  618. private static DiffEntry exactRename(DiffEntry src, DiffEntry dst) {
  619. return DiffEntry.pair(ChangeType.RENAME, src, dst, EXACT_RENAME_SCORE);
  620. }
  621. private static DiffEntry exactCopy(DiffEntry src, DiffEntry dst) {
  622. return DiffEntry.pair(ChangeType.COPY, src, dst, EXACT_RENAME_SCORE);
  623. }
  624. }