You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RepositoryTestCase.java 17KB

Persist minimal racy threshold and allow manual configuration To enable persisting the minimal racy threshold per FileStore add a new config option to the user global git configuration: - Config section is "filesystem" - Config subsection is concatenation of - Java vendor (system property "java.vendor") - Java version (system property "java.version") - FileStore's name, on Windows we use the attribute volume:vsn instead since the name is not necessarily unique. - separated by '|' e.g. "AdoptOpenJDK|1.8.0_212-b03|/dev/disk1s1" The same prefix is used as for filesystem timestamp resolution, so both values are stored in the same config section - The config key for minmal racy threshold is "minRacyThreshold" as a time value, supported time units are those supported by DefaultTypedConfigGetter#getTimeUnit - measure for 3 seconds to limit runtime which depends on hardware, OS and Java version being used If the minimal racy threshold is configured for a given FileStore the configured value is used instead of measuring it. When the minimal racy threshold was measured it is persisted in the user global git configuration. Rename FileStoreAttributeCache to FileStoreAttributes since this class is now declared public in order to enable exposing all attributes in one object. Example: [filesystem "AdoptOpenJDK|11.0.3|/dev/disk1s1"] timestampResolution = 7000 nanoseconds minRacyThreshold = 3440 microseconds Change-Id: I22195e488453aae8d011b0a8e3276fe3d99deaea Signed-off-by: Matthias Sohn <matthias.sohn@sap.com> Also-By: Marc Strapetz <marc.strapetz@syntevo.com>
4 年之前
Measure minimum racy interval to auto-configure FileSnapshot By running FileSnapshotTest#detectFileModified we found that the sum of measured filesystem timestamp resolution and measured clock resolution may yield a too small interval after a file has been modified which we need to consider racily clean. In our tests we didn't find this behavior on all systems we tested on, e.g. on MacOS using APFS and Java 8 and 11 this effect was not observed. On Linux (SLES 15, kernel 4.12.14-150.22-default) we collected the following test results using Java 8 and 11: In 23-98% of 10000 test runs (depending on filesystem type and Java version) the test failed, which means the effective interval which needs to be considered racily clean after a file was modified is larger than the measured file timestamp resolution. "delta" is the observed interval after a file has been modified but FileSnapshot did not yet detect the modification: "resolution" is the measured sum of file timestamp resolution and clock resolution seen in Java. Java version filesystem failures resolution min delta max delta 1.8.0_212-b04 btrfs 98.6% 1 ms 3.6 ms 6.6 ms 1.8.0_212-b04 ext4 82.6% 3 ms 1.1 ms 4.1 ms 1.8.0_212-b04 xfs 23.8% 4 ms 3.7 ms 3.9 ms 1.8.0_212-b04 zfs 23.1% 3 ms 4.8 ms 5.0 ms 11.0.3+7 btrfs 98.1% 3 us 0.7 ms 4.7 ms 11.0.3+7 ext4 98.1% 6 us 0.7 ms 4.7 ms 11.0.3+7 xfs 98.5% 7 us 0.1 ms 8.0 ms 11.0.3+7 zfs 98.4% 7 us 0.7 ms 5.2 ms Mac OS 1.8.0_212 APFS 0% 1 s 11.0.3+7 APFS 0% 6 us The observed delta is not distributed according to a normal gaussian distribution but rather random in the observed range between "min delta" and "max delta". Run this test after measuring file timestamp resolution in FS.FileAttributeCache to auto-configure JGit since it's unclear what mechanism is causing this effect. In FileSnapshot#isRacyClean use the maximum of the measured timestamp resolution and the measured "delta" as explained above to decide if a given FileSnapshot is to be considered racily clean. Add a 30% safety margin to ensure we are on the safe side. Change-Id: I1c8bb59f6486f174b7bbdc63072777ddbe06694d Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
4 年之前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. /*
  2. * Copyright (C) 2009, Google Inc.
  3. * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
  4. * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
  5. * Copyright (C) 2009, Yann Simon <yann.simon.fr@gmail.com>
  6. * and other copyright owners as documented in the project's IP log.
  7. *
  8. * This program and the accompanying materials are made available
  9. * under the terms of the Eclipse Distribution License v1.0 which
  10. * accompanies this distribution, is reproduced below, and is
  11. * available at http://www.eclipse.org/org/documents/edl-v10.php
  12. *
  13. * All rights reserved.
  14. *
  15. * Redistribution and use in source and binary forms, with or
  16. * without modification, are permitted provided that the following
  17. * conditions are met:
  18. *
  19. * - Redistributions of source code must retain the above copyright
  20. * notice, this list of conditions and the following disclaimer.
  21. *
  22. * - Redistributions in binary form must reproduce the above
  23. * copyright notice, this list of conditions and the following
  24. * disclaimer in the documentation and/or other materials provided
  25. * with the distribution.
  26. *
  27. * - Neither the name of the Eclipse Foundation, Inc. nor the
  28. * names of its contributors may be used to endorse or promote
  29. * products derived from this software without specific prior
  30. * written permission.
  31. *
  32. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  33. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  34. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  35. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  36. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  37. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  38. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  39. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  40. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  41. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  42. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  43. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  44. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  45. */
  46. package org.eclipse.jgit.junit;
  47. import static java.nio.charset.StandardCharsets.UTF_8;
  48. import static org.junit.Assert.assertEquals;
  49. import java.io.File;
  50. import java.io.FileInputStream;
  51. import java.io.FileNotFoundException;
  52. import java.io.FileOutputStream;
  53. import java.io.IOException;
  54. import java.io.InputStreamReader;
  55. import java.io.Reader;
  56. import java.nio.file.Path;
  57. import java.time.Instant;
  58. import java.util.Map;
  59. import java.util.concurrent.TimeUnit;
  60. import org.eclipse.jgit.api.Git;
  61. import org.eclipse.jgit.api.errors.GitAPIException;
  62. import org.eclipse.jgit.dircache.DirCacheBuilder;
  63. import org.eclipse.jgit.dircache.DirCacheCheckout;
  64. import org.eclipse.jgit.dircache.DirCacheEntry;
  65. import org.eclipse.jgit.internal.storage.file.FileRepository;
  66. import org.eclipse.jgit.lib.Constants;
  67. import org.eclipse.jgit.lib.FileMode;
  68. import org.eclipse.jgit.lib.ObjectId;
  69. import org.eclipse.jgit.lib.ObjectInserter;
  70. import org.eclipse.jgit.lib.RefUpdate;
  71. import org.eclipse.jgit.lib.Repository;
  72. import org.eclipse.jgit.revwalk.RevCommit;
  73. import org.eclipse.jgit.revwalk.RevWalk;
  74. import org.eclipse.jgit.treewalk.FileTreeIterator;
  75. import org.eclipse.jgit.util.FS;
  76. import org.eclipse.jgit.util.FileUtils;
  77. import org.junit.Before;
  78. /**
  79. * Base class for most JGit unit tests.
  80. *
  81. * Sets up a predefined test repository and has support for creating additional
  82. * repositories and destroying them when the tests are finished.
  83. */
  84. public abstract class RepositoryTestCase extends LocalDiskRepositoryTestCase {
  85. /**
  86. * Copy a file
  87. *
  88. * @param src
  89. * @param dst
  90. * @throws IOException
  91. */
  92. protected static void copyFile(File src, File dst)
  93. throws IOException {
  94. try (FileInputStream fis = new FileInputStream(src);
  95. FileOutputStream fos = new FileOutputStream(dst)) {
  96. final byte[] buf = new byte[4096];
  97. int r;
  98. while ((r = fis.read(buf)) > 0) {
  99. fos.write(buf, 0, r);
  100. }
  101. }
  102. }
  103. /**
  104. * Write a trash file
  105. *
  106. * @param name
  107. * @param data
  108. * @return the trash file
  109. * @throws IOException
  110. */
  111. protected File writeTrashFile(String name, String data)
  112. throws IOException {
  113. return JGitTestUtil.writeTrashFile(db, name, data);
  114. }
  115. /**
  116. * Create a symbolic link
  117. *
  118. * @param link
  119. * the path of the symbolic link to create
  120. * @param target
  121. * the target of the symbolic link
  122. * @return the path to the symbolic link
  123. * @throws Exception
  124. * @since 4.2
  125. */
  126. protected Path writeLink(String link, String target)
  127. throws Exception {
  128. return JGitTestUtil.writeLink(db, link, target);
  129. }
  130. /**
  131. * Write a trash file
  132. *
  133. * @param subdir
  134. * @param name
  135. * @param data
  136. * @return the trash file
  137. * @throws IOException
  138. */
  139. protected File writeTrashFile(final String subdir, final String name,
  140. final String data)
  141. throws IOException {
  142. return JGitTestUtil.writeTrashFile(db, subdir, name, data);
  143. }
  144. /**
  145. * Read content of a file
  146. *
  147. * @param name
  148. * @return the file's content
  149. * @throws IOException
  150. */
  151. protected String read(String name) throws IOException {
  152. return JGitTestUtil.read(db, name);
  153. }
  154. /**
  155. * Check if file exists
  156. *
  157. * @param name
  158. * file name
  159. * @return if the file exists
  160. */
  161. protected boolean check(String name) {
  162. return JGitTestUtil.check(db, name);
  163. }
  164. /**
  165. * Delete a trash file
  166. *
  167. * @param name
  168. * file name
  169. * @throws IOException
  170. */
  171. protected void deleteTrashFile(String name) throws IOException {
  172. JGitTestUtil.deleteTrashFile(db, name);
  173. }
  174. /**
  175. * Check content of a file.
  176. *
  177. * @param f
  178. * @param checkData
  179. * expected content
  180. * @throws IOException
  181. */
  182. protected static void checkFile(File f, String checkData)
  183. throws IOException {
  184. try (Reader r = new InputStreamReader(new FileInputStream(f),
  185. UTF_8)) {
  186. if (checkData.length() > 0) {
  187. char[] data = new char[checkData.length()];
  188. assertEquals(data.length, r.read(data));
  189. assertEquals(checkData, new String(data));
  190. }
  191. assertEquals(-1, r.read());
  192. }
  193. }
  194. /** Test repository, initialized for this test case. */
  195. protected FileRepository db;
  196. /** Working directory of {@link #db}. */
  197. protected File trash;
  198. /** {@inheritDoc} */
  199. @Override
  200. @Before
  201. public void setUp() throws Exception {
  202. super.setUp();
  203. db = createWorkRepository();
  204. trash = db.getWorkTree();
  205. }
  206. /**
  207. * Represent the state of the index in one String. This representation is
  208. * useful when writing tests which do assertions on the state of the index.
  209. * By default information about path, mode, stage (if different from 0) is
  210. * included. A bitmask controls which additional info about
  211. * modificationTimes, smudge state and length is included.
  212. * <p>
  213. * The format of the returned string is described with this BNF:
  214. *
  215. * <pre>
  216. * result = ( "[" path mode stage? time? smudge? length? sha1? content? "]" )* .
  217. * mode = ", mode:" number .
  218. * stage = ", stage:" number .
  219. * time = ", time:t" timestamp-index .
  220. * smudge = "" | ", smudged" .
  221. * length = ", length:" number .
  222. * sha1 = ", sha1:" hex-sha1 .
  223. * content = ", content:" blob-data .
  224. * </pre>
  225. *
  226. * 'stage' is only presented when the stage is different from 0. All
  227. * reported time stamps are mapped to strings like "t0", "t1", ... "tn". The
  228. * smallest reported time-stamp will be called "t0". This allows to write
  229. * assertions against the string although the concrete value of the time
  230. * stamps is unknown.
  231. *
  232. * @param includedOptions
  233. * a bitmask constructed out of the constants {@link #MOD_TIME},
  234. * {@link #SMUDGE}, {@link #LENGTH}, {@link #CONTENT_ID} and
  235. * {@link #CONTENT} controlling which info is present in the
  236. * resulting string.
  237. * @return a string encoding the index state
  238. * @throws IllegalStateException
  239. * @throws IOException
  240. */
  241. public String indexState(int includedOptions)
  242. throws IllegalStateException, IOException {
  243. return indexState(db, includedOptions);
  244. }
  245. /**
  246. * Resets the index to represent exactly some filesystem content. E.g. the
  247. * following call will replace the index with the working tree content:
  248. * <p>
  249. * <code>resetIndex(new FileSystemIterator(db))</code>
  250. * <p>
  251. * This method can be used by testcases which first prepare a new commit
  252. * somewhere in the filesystem (e.g. in the working-tree) and then want to
  253. * have an index which matches their prepared content.
  254. *
  255. * @param treeItr
  256. * a {@link org.eclipse.jgit.treewalk.FileTreeIterator} which
  257. * determines which files should go into the new index
  258. * @throws FileNotFoundException
  259. * @throws IOException
  260. */
  261. protected void resetIndex(FileTreeIterator treeItr)
  262. throws FileNotFoundException, IOException {
  263. try (ObjectInserter inserter = db.newObjectInserter()) {
  264. DirCacheBuilder builder = db.lockDirCache().builder();
  265. DirCacheEntry dce;
  266. while (!treeItr.eof()) {
  267. long len = treeItr.getEntryLength();
  268. dce = new DirCacheEntry(treeItr.getEntryPathString());
  269. dce.setFileMode(treeItr.getEntryFileMode());
  270. dce.setLastModified(treeItr.getEntryLastModifiedInstant());
  271. dce.setLength((int) len);
  272. try (FileInputStream in = new FileInputStream(
  273. treeItr.getEntryFile())) {
  274. dce.setObjectId(
  275. inserter.insert(Constants.OBJ_BLOB, len, in));
  276. }
  277. builder.add(dce);
  278. treeItr.next(1);
  279. }
  280. builder.commit();
  281. inserter.flush();
  282. }
  283. }
  284. /**
  285. * Helper method to map arbitrary objects to user-defined names. This can be
  286. * used create short names for objects to produce small and stable debug
  287. * output. It is guaranteed that when you lookup the same object multiple
  288. * times even with different nameTemplates this method will always return
  289. * the same name which was derived from the first nameTemplate.
  290. * nameTemplates can contain "%n" which will be replaced by a running number
  291. * before used as a name.
  292. *
  293. * @param l
  294. * the object to lookup
  295. * @param lookupTable
  296. * a table storing object-name mappings.
  297. * @param nameTemplate
  298. * the name for that object. Can contain "%n" which will be
  299. * replaced by a running number before used as a name. If the
  300. * lookup table already contains the object this parameter will
  301. * be ignored
  302. * @return a name of that object. Is not guaranteed to be unique. Use
  303. * nameTemplates containing "%n" to always have unique names
  304. */
  305. public static String lookup(Object l, String nameTemplate,
  306. Map<Object, String> lookupTable) {
  307. String name = lookupTable.get(l);
  308. if (name == null) {
  309. name = nameTemplate.replaceAll("%n",
  310. Integer.toString(lookupTable.size()));
  311. lookupTable.put(l, name);
  312. }
  313. return name;
  314. }
  315. /**
  316. * Replaces '\' by '/'
  317. *
  318. * @param str
  319. * the string in which backslashes should be replaced
  320. * @return the resulting string with slashes
  321. * @since 4.2
  322. */
  323. public static String slashify(String str) {
  324. str = str.replace('\\', '/');
  325. return str;
  326. }
  327. /**
  328. * Waits until it is guaranteed that a subsequent file modification has a
  329. * younger modification timestamp than the modification timestamp of the
  330. * given file. This is done by touching a temporary file, reading the
  331. * lastmodified attribute and, if needed, sleeping. After sleeping this loop
  332. * starts again until the filesystem timer has advanced enough. The
  333. * temporary file will be created as a sibling of lastFile.
  334. *
  335. * @param lastFile
  336. * the file on which we want to wait until the filesystem timer
  337. * has advanced more than the lastmodification timestamp of this
  338. * file
  339. * @return return the last measured value of the filesystem timer which is
  340. * greater than then the lastmodification time of lastfile.
  341. * @throws InterruptedException
  342. * @throws IOException
  343. */
  344. public static Instant fsTick(File lastFile)
  345. throws InterruptedException,
  346. IOException {
  347. File tmp;
  348. FS fs = FS.DETECTED;
  349. if (lastFile == null) {
  350. lastFile = tmp = File
  351. .createTempFile("fsTickTmpFile", null);
  352. } else {
  353. if (!fs.exists(lastFile)) {
  354. throw new FileNotFoundException(lastFile.getPath());
  355. }
  356. tmp = File.createTempFile("fsTickTmpFile", null,
  357. lastFile.getParentFile());
  358. }
  359. long res = FS.getFileStoreAttributes(tmp.toPath())
  360. .getFsTimestampResolution().toNanos();
  361. long sleepTime = res / 10;
  362. try {
  363. Instant startTime = fs.lastModifiedInstant(lastFile);
  364. Instant actTime = fs.lastModifiedInstant(tmp);
  365. while (actTime.compareTo(startTime) <= 0) {
  366. TimeUnit.NANOSECONDS.sleep(sleepTime);
  367. FileUtils.touch(tmp.toPath());
  368. actTime = fs.lastModifiedInstant(tmp);
  369. }
  370. return actTime;
  371. } finally {
  372. FileUtils.delete(tmp);
  373. }
  374. }
  375. /**
  376. * Create a branch
  377. *
  378. * @param objectId
  379. * @param branchName
  380. * @throws IOException
  381. */
  382. protected void createBranch(ObjectId objectId, String branchName)
  383. throws IOException {
  384. RefUpdate updateRef = db.updateRef(branchName);
  385. updateRef.setNewObjectId(objectId);
  386. updateRef.update();
  387. }
  388. /**
  389. * Checkout a branch
  390. *
  391. * @param branchName
  392. * @throws IllegalStateException
  393. * @throws IOException
  394. */
  395. protected void checkoutBranch(String branchName)
  396. throws IllegalStateException, IOException {
  397. try (RevWalk walk = new RevWalk(db)) {
  398. RevCommit head = walk.parseCommit(db.resolve(Constants.HEAD));
  399. RevCommit branch = walk.parseCommit(db.resolve(branchName));
  400. DirCacheCheckout dco = new DirCacheCheckout(db,
  401. head.getTree().getId(), db.lockDirCache(),
  402. branch.getTree().getId());
  403. dco.setFailOnConflict(true);
  404. dco.checkout();
  405. }
  406. // update the HEAD
  407. RefUpdate refUpdate = db.updateRef(Constants.HEAD);
  408. refUpdate.setRefLogMessage("checkout: moving to " + branchName, false);
  409. refUpdate.link(branchName);
  410. }
  411. /**
  412. * Writes a number of files in the working tree. The first content specified
  413. * will be written into a file named '0', the second into a file named "1"
  414. * and so on. If <code>null</code> is specified as content then this file is
  415. * skipped.
  416. *
  417. * @param ensureDistinctTimestamps
  418. * if set to <code>true</code> then between two write operations
  419. * this method will wait to ensure that the second file will get
  420. * a different lastmodification timestamp than the first file.
  421. * @param contents
  422. * the contents which should be written into the files
  423. * @return the File object associated to the last written file.
  424. * @throws IOException
  425. * @throws InterruptedException
  426. */
  427. protected File writeTrashFiles(boolean ensureDistinctTimestamps,
  428. String... contents)
  429. throws IOException, InterruptedException {
  430. File f = null;
  431. for (int i = 0; i < contents.length; i++)
  432. if (contents[i] != null) {
  433. if (ensureDistinctTimestamps && (f != null))
  434. fsTick(f);
  435. f = writeTrashFile(Integer.toString(i), contents[i]);
  436. }
  437. return f;
  438. }
  439. /**
  440. * Commit a file with the specified contents on the specified branch,
  441. * creating the branch if it didn't exist before.
  442. * <p>
  443. * It switches back to the original branch after the commit if there was
  444. * one.
  445. *
  446. * @param filename
  447. * @param contents
  448. * @param branch
  449. * @return the created commit
  450. */
  451. protected RevCommit commitFile(String filename, String contents, String branch) {
  452. try (Git git = new Git(db)) {
  453. Repository repo = git.getRepository();
  454. String originalBranch = repo.getFullBranch();
  455. boolean empty = repo.resolve(Constants.HEAD) == null;
  456. if (!empty) {
  457. if (repo.findRef(branch) == null)
  458. git.branchCreate().setName(branch).call();
  459. git.checkout().setName(branch).call();
  460. }
  461. writeTrashFile(filename, contents);
  462. git.add().addFilepattern(filename).call();
  463. RevCommit commit = git.commit()
  464. .setMessage(branch + ": " + filename).call();
  465. if (originalBranch != null)
  466. git.checkout().setName(originalBranch).call();
  467. else if (empty)
  468. git.branchCreate().setName(branch).setStartPoint(commit).call();
  469. return commit;
  470. } catch (IOException | GitAPIException e) {
  471. throw new RuntimeException(e);
  472. }
  473. }
  474. /**
  475. * Create <code>DirCacheEntry</code>
  476. *
  477. * @param path
  478. * @param mode
  479. * @return the DirCacheEntry
  480. */
  481. protected DirCacheEntry createEntry(String path, FileMode mode) {
  482. return createEntry(path, mode, DirCacheEntry.STAGE_0, path);
  483. }
  484. /**
  485. * Create <code>DirCacheEntry</code>
  486. *
  487. * @param path
  488. * @param mode
  489. * @param content
  490. * @return the DirCacheEntry
  491. */
  492. protected DirCacheEntry createEntry(final String path, final FileMode mode,
  493. final String content) {
  494. return createEntry(path, mode, DirCacheEntry.STAGE_0, content);
  495. }
  496. /**
  497. * Create <code>DirCacheEntry</code>
  498. *
  499. * @param path
  500. * @param mode
  501. * @param stage
  502. * @param content
  503. * @return the DirCacheEntry
  504. */
  505. protected DirCacheEntry createEntry(final String path, final FileMode mode,
  506. final int stage, final String content) {
  507. final DirCacheEntry entry = new DirCacheEntry(path, stage);
  508. entry.setFileMode(mode);
  509. try (ObjectInserter.Formatter formatter = new ObjectInserter.Formatter()) {
  510. entry.setObjectId(formatter.idFor(
  511. Constants.OBJ_BLOB, Constants.encode(content)));
  512. }
  513. return entry;
  514. }
  515. /**
  516. * Assert files are equal
  517. *
  518. * @param expected
  519. * @param actual
  520. * @throws IOException
  521. */
  522. public static void assertEqualsFile(File expected, File actual)
  523. throws IOException {
  524. assertEquals(expected.getCanonicalFile(), actual.getCanonicalFile());
  525. }
  526. }