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.

TestRepository.java 36KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396
  1. /*
  2. * Copyright (C) 2009-2010, Google Inc. and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.junit;
  11. import static java.nio.charset.StandardCharsets.UTF_8;
  12. import static org.junit.Assert.assertEquals;
  13. import static org.junit.Assert.fail;
  14. import java.io.BufferedOutputStream;
  15. import java.io.File;
  16. import java.io.FileOutputStream;
  17. import java.io.IOException;
  18. import java.io.OutputStream;
  19. import java.security.MessageDigest;
  20. import java.util.ArrayList;
  21. import java.util.Arrays;
  22. import java.util.Collections;
  23. import java.util.Date;
  24. import java.util.HashSet;
  25. import java.util.List;
  26. import java.util.Set;
  27. import java.util.TimeZone;
  28. import org.eclipse.jgit.api.Git;
  29. import org.eclipse.jgit.dircache.DirCache;
  30. import org.eclipse.jgit.dircache.DirCacheBuilder;
  31. import org.eclipse.jgit.dircache.DirCacheEditor;
  32. import org.eclipse.jgit.dircache.DirCacheEditor.DeletePath;
  33. import org.eclipse.jgit.dircache.DirCacheEditor.DeleteTree;
  34. import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
  35. import org.eclipse.jgit.dircache.DirCacheEntry;
  36. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  37. import org.eclipse.jgit.errors.MissingObjectException;
  38. import org.eclipse.jgit.errors.ObjectWritingException;
  39. import org.eclipse.jgit.internal.storage.file.FileRepository;
  40. import org.eclipse.jgit.internal.storage.file.LockFile;
  41. import org.eclipse.jgit.internal.storage.file.ObjectDirectory;
  42. import org.eclipse.jgit.internal.storage.file.Pack;
  43. import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
  44. import org.eclipse.jgit.internal.storage.pack.PackWriter;
  45. import org.eclipse.jgit.lib.AnyObjectId;
  46. import org.eclipse.jgit.lib.Constants;
  47. import org.eclipse.jgit.lib.FileMode;
  48. import org.eclipse.jgit.lib.NullProgressMonitor;
  49. import org.eclipse.jgit.lib.ObjectChecker;
  50. import org.eclipse.jgit.lib.ObjectId;
  51. import org.eclipse.jgit.lib.ObjectInserter;
  52. import org.eclipse.jgit.lib.PersonIdent;
  53. import org.eclipse.jgit.lib.Ref;
  54. import org.eclipse.jgit.lib.RefUpdate;
  55. import org.eclipse.jgit.lib.RefWriter;
  56. import org.eclipse.jgit.lib.Repository;
  57. import org.eclipse.jgit.lib.TagBuilder;
  58. import org.eclipse.jgit.merge.MergeStrategy;
  59. import org.eclipse.jgit.merge.ThreeWayMerger;
  60. import org.eclipse.jgit.revwalk.ObjectWalk;
  61. import org.eclipse.jgit.revwalk.RevBlob;
  62. import org.eclipse.jgit.revwalk.RevCommit;
  63. import org.eclipse.jgit.revwalk.RevObject;
  64. import org.eclipse.jgit.revwalk.RevTag;
  65. import org.eclipse.jgit.revwalk.RevTree;
  66. import org.eclipse.jgit.revwalk.RevWalk;
  67. import org.eclipse.jgit.treewalk.TreeWalk;
  68. import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
  69. import org.eclipse.jgit.util.ChangeIdUtil;
  70. import org.eclipse.jgit.util.FileUtils;
  71. /**
  72. * Wrapper to make creating test data easier.
  73. *
  74. * @param <R>
  75. * type of Repository the test data is stored on.
  76. */
  77. public class TestRepository<R extends Repository> implements AutoCloseable {
  78. /** Constant <code>AUTHOR="J. Author"</code> */
  79. public static final String AUTHOR = "J. Author";
  80. /** Constant <code>AUTHOR_EMAIL="jauthor@example.com"</code> */
  81. public static final String AUTHOR_EMAIL = "jauthor@example.com";
  82. /** Constant <code>COMMITTER="J. Committer"</code> */
  83. public static final String COMMITTER = "J. Committer";
  84. /** Constant <code>COMMITTER_EMAIL="jcommitter@example.com"</code> */
  85. public static final String COMMITTER_EMAIL = "jcommitter@example.com";
  86. private final PersonIdent defaultAuthor;
  87. private final PersonIdent defaultCommitter;
  88. private final R db;
  89. private final Git git;
  90. private final RevWalk pool;
  91. private final ObjectInserter inserter;
  92. private final MockSystemReader mockSystemReader;
  93. /**
  94. * Wrap a repository with test building tools.
  95. *
  96. * @param db
  97. * the test repository to write into.
  98. * @throws IOException
  99. */
  100. public TestRepository(R db) throws IOException {
  101. this(db, new RevWalk(db), new MockSystemReader());
  102. }
  103. /**
  104. * Wrap a repository with test building tools.
  105. *
  106. * @param db
  107. * the test repository to write into.
  108. * @param rw
  109. * the RevObject pool to use for object lookup.
  110. * @throws IOException
  111. */
  112. public TestRepository(R db, RevWalk rw) throws IOException {
  113. this(db, rw, new MockSystemReader());
  114. }
  115. /**
  116. * Wrap a repository with test building tools.
  117. *
  118. * @param db
  119. * the test repository to write into.
  120. * @param rw
  121. * the RevObject pool to use for object lookup.
  122. * @param reader
  123. * the MockSystemReader to use for clock and other system
  124. * operations.
  125. * @throws IOException
  126. * @since 4.2
  127. */
  128. public TestRepository(R db, RevWalk rw, MockSystemReader reader)
  129. throws IOException {
  130. this.db = db;
  131. this.git = Git.wrap(db);
  132. this.pool = rw;
  133. this.inserter = db.newObjectInserter();
  134. this.mockSystemReader = reader;
  135. long now = mockSystemReader.getCurrentTime();
  136. int tz = mockSystemReader.getTimezone(now);
  137. defaultAuthor = new PersonIdent(AUTHOR, AUTHOR_EMAIL, now, tz);
  138. defaultCommitter = new PersonIdent(COMMITTER, COMMITTER_EMAIL, now, tz);
  139. }
  140. /**
  141. * Get repository
  142. *
  143. * @return the repository this helper class operates against.
  144. */
  145. public R getRepository() {
  146. return db;
  147. }
  148. /**
  149. * Get RevWalk
  150. *
  151. * @return get the RevWalk pool all objects are allocated through.
  152. */
  153. public RevWalk getRevWalk() {
  154. return pool;
  155. }
  156. /**
  157. * Return Git API wrapper
  158. *
  159. * @return an API wrapper for the underlying repository. This wrapper does
  160. * not allocate any new resources and need not be closed (but
  161. * closing it is harmless).
  162. */
  163. public Git git() {
  164. return git;
  165. }
  166. /**
  167. * Get date
  168. *
  169. * @return current date.
  170. * @since 4.2
  171. */
  172. public Date getDate() {
  173. return new Date(mockSystemReader.getCurrentTime());
  174. }
  175. /**
  176. * Get timezone
  177. *
  178. * @return timezone used for default identities.
  179. */
  180. public TimeZone getTimeZone() {
  181. return mockSystemReader.getTimeZone();
  182. }
  183. /**
  184. * Adjust the current time that will used by the next commit.
  185. *
  186. * @param secDelta
  187. * number of seconds to add to the current time.
  188. */
  189. public void tick(int secDelta) {
  190. mockSystemReader.tick(secDelta);
  191. }
  192. /**
  193. * Set the author and committer using {@link #getDate()}.
  194. *
  195. * @param c
  196. * the commit builder to store.
  197. */
  198. public void setAuthorAndCommitter(org.eclipse.jgit.lib.CommitBuilder c) {
  199. c.setAuthor(new PersonIdent(defaultAuthor, getDate()));
  200. c.setCommitter(new PersonIdent(defaultCommitter, getDate()));
  201. }
  202. /**
  203. * Create a new blob object in the repository.
  204. *
  205. * @param content
  206. * file content, will be UTF-8 encoded.
  207. * @return reference to the blob.
  208. * @throws Exception
  209. */
  210. public RevBlob blob(String content) throws Exception {
  211. return blob(content.getBytes(UTF_8));
  212. }
  213. /**
  214. * Create a new blob object in the repository.
  215. *
  216. * @param content
  217. * binary file content.
  218. * @return the new, fully parsed blob.
  219. * @throws Exception
  220. */
  221. public RevBlob blob(byte[] content) throws Exception {
  222. ObjectId id;
  223. try (ObjectInserter ins = inserter) {
  224. id = ins.insert(Constants.OBJ_BLOB, content);
  225. ins.flush();
  226. }
  227. return (RevBlob) pool.parseAny(id);
  228. }
  229. /**
  230. * Construct a regular file mode tree entry.
  231. *
  232. * @param path
  233. * path of the file.
  234. * @param blob
  235. * a blob, previously constructed in the repository.
  236. * @return the entry.
  237. * @throws Exception
  238. */
  239. public DirCacheEntry file(String path, RevBlob blob)
  240. throws Exception {
  241. final DirCacheEntry e = new DirCacheEntry(path);
  242. e.setFileMode(FileMode.REGULAR_FILE);
  243. e.setObjectId(blob);
  244. return e;
  245. }
  246. /**
  247. * Construct a tree from a specific listing of file entries.
  248. *
  249. * @param entries
  250. * the files to include in the tree. The collection does not need
  251. * to be sorted properly and may be empty.
  252. * @return the new, fully parsed tree specified by the entry list.
  253. * @throws Exception
  254. */
  255. public RevTree tree(DirCacheEntry... entries) throws Exception {
  256. final DirCache dc = DirCache.newInCore();
  257. final DirCacheBuilder b = dc.builder();
  258. for (DirCacheEntry e : entries) {
  259. b.add(e);
  260. }
  261. b.finish();
  262. ObjectId root;
  263. try (ObjectInserter ins = inserter) {
  264. root = dc.writeTree(ins);
  265. ins.flush();
  266. }
  267. return pool.parseTree(root);
  268. }
  269. /**
  270. * Lookup an entry stored in a tree, failing if not present.
  271. *
  272. * @param tree
  273. * the tree to search.
  274. * @param path
  275. * the path to find the entry of.
  276. * @return the parsed object entry at this path, never null.
  277. * @throws Exception
  278. */
  279. public RevObject get(RevTree tree, String path)
  280. throws Exception {
  281. try (TreeWalk tw = new TreeWalk(pool.getObjectReader())) {
  282. tw.setFilter(PathFilterGroup.createFromStrings(Collections
  283. .singleton(path)));
  284. tw.reset(tree);
  285. while (tw.next()) {
  286. if (tw.isSubtree() && !path.equals(tw.getPathString())) {
  287. tw.enterSubtree();
  288. continue;
  289. }
  290. final ObjectId entid = tw.getObjectId(0);
  291. final FileMode entmode = tw.getFileMode(0);
  292. return pool.lookupAny(entid, entmode.getObjectType());
  293. }
  294. }
  295. fail("Can't find " + path + " in tree " + tree.name());
  296. return null; // never reached.
  297. }
  298. /**
  299. * Create a new, unparsed commit.
  300. * <p>
  301. * See {@link #unparsedCommit(int, RevTree, ObjectId...)}. The tree is the
  302. * empty tree (no files or subdirectories).
  303. *
  304. * @param parents
  305. * zero or more IDs of the commit's parents.
  306. * @return the ID of the new commit.
  307. * @throws Exception
  308. */
  309. public ObjectId unparsedCommit(ObjectId... parents) throws Exception {
  310. return unparsedCommit(1, tree(), parents);
  311. }
  312. /**
  313. * Create a new commit.
  314. * <p>
  315. * See {@link #commit(int, RevTree, RevCommit...)}. The tree is the empty
  316. * tree (no files or subdirectories).
  317. *
  318. * @param parents
  319. * zero or more parents of the commit.
  320. * @return the new commit.
  321. * @throws Exception
  322. */
  323. public RevCommit commit(RevCommit... parents) throws Exception {
  324. return commit(1, tree(), parents);
  325. }
  326. /**
  327. * Create a new commit.
  328. * <p>
  329. * See {@link #commit(int, RevTree, RevCommit...)}.
  330. *
  331. * @param tree
  332. * the root tree for the commit.
  333. * @param parents
  334. * zero or more parents of the commit.
  335. * @return the new commit.
  336. * @throws Exception
  337. */
  338. public RevCommit commit(RevTree tree, RevCommit... parents)
  339. throws Exception {
  340. return commit(1, tree, parents);
  341. }
  342. /**
  343. * Create a new commit.
  344. * <p>
  345. * See {@link #commit(int, RevTree, RevCommit...)}. The tree is the empty
  346. * tree (no files or subdirectories).
  347. *
  348. * @param secDelta
  349. * number of seconds to advance {@link #tick(int)} by.
  350. * @param parents
  351. * zero or more parents of the commit.
  352. * @return the new commit.
  353. * @throws Exception
  354. */
  355. public RevCommit commit(int secDelta, RevCommit... parents)
  356. throws Exception {
  357. return commit(secDelta, tree(), parents);
  358. }
  359. /**
  360. * Create a new commit.
  361. * <p>
  362. * The author and committer identities are stored using the current
  363. * timestamp, after being incremented by {@code secDelta}. The message body
  364. * is empty.
  365. *
  366. * @param secDelta
  367. * number of seconds to advance {@link #tick(int)} by.
  368. * @param tree
  369. * the root tree for the commit.
  370. * @param parents
  371. * zero or more parents of the commit.
  372. * @return the new, fully parsed commit.
  373. * @throws Exception
  374. */
  375. public RevCommit commit(final int secDelta, final RevTree tree,
  376. final RevCommit... parents) throws Exception {
  377. ObjectId id = unparsedCommit(secDelta, tree, parents);
  378. return pool.parseCommit(id);
  379. }
  380. /**
  381. * Create a new, unparsed commit.
  382. * <p>
  383. * The author and committer identities are stored using the current
  384. * timestamp, after being incremented by {@code secDelta}. The message body
  385. * is empty.
  386. *
  387. * @param secDelta
  388. * number of seconds to advance {@link #tick(int)} by.
  389. * @param tree
  390. * the root tree for the commit.
  391. * @param parents
  392. * zero or more IDs of the commit's parents.
  393. * @return the ID of the new commit.
  394. * @throws Exception
  395. */
  396. public ObjectId unparsedCommit(final int secDelta, final RevTree tree,
  397. final ObjectId... parents) throws Exception {
  398. tick(secDelta);
  399. final org.eclipse.jgit.lib.CommitBuilder c;
  400. c = new org.eclipse.jgit.lib.CommitBuilder();
  401. c.setTreeId(tree);
  402. c.setParentIds(parents);
  403. c.setAuthor(new PersonIdent(defaultAuthor, getDate()));
  404. c.setCommitter(new PersonIdent(defaultCommitter, getDate()));
  405. c.setMessage("");
  406. ObjectId id;
  407. try (ObjectInserter ins = inserter) {
  408. id = ins.insert(c);
  409. ins.flush();
  410. }
  411. return id;
  412. }
  413. /**
  414. * Create commit builder
  415. *
  416. * @return a new commit builder.
  417. */
  418. public CommitBuilder commit() {
  419. return new CommitBuilder();
  420. }
  421. /**
  422. * Construct an annotated tag object pointing at another object.
  423. * <p>
  424. * The tagger is the committer identity, at the current time as specified by
  425. * {@link #tick(int)}. The time is not increased.
  426. * <p>
  427. * The tag message is empty.
  428. *
  429. * @param name
  430. * name of the tag. Traditionally a tag name should not start
  431. * with {@code refs/tags/}.
  432. * @param dst
  433. * object the tag should be pointed at.
  434. * @return the new, fully parsed annotated tag object.
  435. * @throws Exception
  436. */
  437. public RevTag tag(String name, RevObject dst) throws Exception {
  438. final TagBuilder t = new TagBuilder();
  439. t.setObjectId(dst);
  440. t.setTag(name);
  441. t.setTagger(new PersonIdent(defaultCommitter, getDate()));
  442. t.setMessage("");
  443. ObjectId id;
  444. try (ObjectInserter ins = inserter) {
  445. id = ins.insert(t);
  446. ins.flush();
  447. }
  448. return pool.parseTag(id);
  449. }
  450. /**
  451. * Update a reference to point to an object.
  452. *
  453. * @param ref
  454. * the name of the reference to update to. If {@code ref} does
  455. * not start with {@code refs/} and is not the magic names
  456. * {@code HEAD} {@code FETCH_HEAD} or {@code MERGE_HEAD}, then
  457. * {@code refs/heads/} will be prefixed in front of the given
  458. * name, thereby assuming it is a branch.
  459. * @param to
  460. * the target object.
  461. * @return the target object.
  462. * @throws Exception
  463. */
  464. public RevCommit update(String ref, CommitBuilder to) throws Exception {
  465. return update(ref, to.create());
  466. }
  467. /**
  468. * Amend an existing ref.
  469. *
  470. * @param ref
  471. * the name of the reference to amend, which must already exist.
  472. * If {@code ref} does not start with {@code refs/} and is not the
  473. * magic names {@code HEAD} {@code FETCH_HEAD} or {@code
  474. * MERGE_HEAD}, then {@code refs/heads/} will be prefixed in front
  475. * of the given name, thereby assuming it is a branch.
  476. * @return commit builder that amends the branch on commit.
  477. * @throws Exception
  478. */
  479. public CommitBuilder amendRef(String ref) throws Exception {
  480. String name = normalizeRef(ref);
  481. Ref r = db.exactRef(name);
  482. if (r == null)
  483. throw new IOException("Not a ref: " + ref);
  484. return amend(pool.parseCommit(r.getObjectId()), branch(name).commit());
  485. }
  486. /**
  487. * Amend an existing commit.
  488. *
  489. * @param id
  490. * the id of the commit to amend.
  491. * @return commit builder.
  492. * @throws Exception
  493. */
  494. public CommitBuilder amend(AnyObjectId id) throws Exception {
  495. return amend(pool.parseCommit(id), commit());
  496. }
  497. private CommitBuilder amend(RevCommit old, CommitBuilder b) throws Exception {
  498. pool.parseBody(old);
  499. b.author(old.getAuthorIdent());
  500. b.committer(old.getCommitterIdent());
  501. b.message(old.getFullMessage());
  502. // Use the committer name from the old commit, but update it after ticking
  503. // the clock in CommitBuilder#create().
  504. b.updateCommitterTime = true;
  505. // Reset parents to original parents.
  506. b.noParents();
  507. for (int i = 0; i < old.getParentCount(); i++)
  508. b.parent(old.getParent(i));
  509. // Reset tree to original tree; resetting parents reset tree contents to the
  510. // first parent.
  511. b.tree.clear();
  512. try (TreeWalk tw = new TreeWalk(db)) {
  513. tw.reset(old.getTree());
  514. tw.setRecursive(true);
  515. while (tw.next()) {
  516. b.edit(new PathEdit(tw.getPathString()) {
  517. @Override
  518. public void apply(DirCacheEntry ent) {
  519. ent.setFileMode(tw.getFileMode(0));
  520. ent.setObjectId(tw.getObjectId(0));
  521. }
  522. });
  523. }
  524. }
  525. return b;
  526. }
  527. /**
  528. * Update a reference to point to an object.
  529. *
  530. * @param <T>
  531. * type of the target object.
  532. * @param ref
  533. * the name of the reference to update to. If {@code ref} does
  534. * not start with {@code refs/} and is not the magic names
  535. * {@code HEAD} {@code FETCH_HEAD} or {@code MERGE_HEAD}, then
  536. * {@code refs/heads/} will be prefixed in front of the given
  537. * name, thereby assuming it is a branch.
  538. * @param obj
  539. * the target object.
  540. * @return the target object.
  541. * @throws Exception
  542. */
  543. public <T extends AnyObjectId> T update(String ref, T obj) throws Exception {
  544. ref = normalizeRef(ref);
  545. RefUpdate u = db.updateRef(ref);
  546. u.setNewObjectId(obj);
  547. switch (u.forceUpdate()) {
  548. case FAST_FORWARD:
  549. case FORCED:
  550. case NEW:
  551. case NO_CHANGE:
  552. updateServerInfo();
  553. return obj;
  554. default:
  555. throw new IOException("Cannot write " + ref + " " + u.getResult());
  556. }
  557. }
  558. /**
  559. * Delete a reference.
  560. *
  561. * @param ref
  562. * the name of the reference to delete. This is normalized
  563. * in the same way as {@link #update(String, AnyObjectId)}.
  564. * @throws Exception
  565. * @since 4.4
  566. */
  567. public void delete(String ref) throws Exception {
  568. ref = normalizeRef(ref);
  569. RefUpdate u = db.updateRef(ref);
  570. u.setForceUpdate(true);
  571. switch (u.delete()) {
  572. case FAST_FORWARD:
  573. case FORCED:
  574. case NEW:
  575. case NO_CHANGE:
  576. updateServerInfo();
  577. return;
  578. default:
  579. throw new IOException("Cannot delete " + ref + " " + u.getResult());
  580. }
  581. }
  582. private static String normalizeRef(String ref) {
  583. if (Constants.HEAD.equals(ref)) {
  584. // nothing
  585. } else if ("FETCH_HEAD".equals(ref)) {
  586. // nothing
  587. } else if ("MERGE_HEAD".equals(ref)) {
  588. // nothing
  589. } else if (ref.startsWith(Constants.R_REFS)) {
  590. // nothing
  591. } else
  592. ref = Constants.R_HEADS + ref;
  593. return ref;
  594. }
  595. /**
  596. * Soft-reset HEAD to a detached state.
  597. *
  598. * @param id
  599. * ID of detached head.
  600. * @throws Exception
  601. * @see #reset(String)
  602. */
  603. public void reset(AnyObjectId id) throws Exception {
  604. RefUpdate ru = db.updateRef(Constants.HEAD, true);
  605. ru.setNewObjectId(id);
  606. RefUpdate.Result result = ru.forceUpdate();
  607. switch (result) {
  608. case FAST_FORWARD:
  609. case FORCED:
  610. case NEW:
  611. case NO_CHANGE:
  612. break;
  613. default:
  614. throw new IOException(String.format(
  615. "Checkout \"%s\" failed: %s", id.name(), result));
  616. }
  617. }
  618. /**
  619. * Soft-reset HEAD to a different commit.
  620. * <p>
  621. * This is equivalent to {@code git reset --soft} in that it modifies HEAD but
  622. * not the index or the working tree of a non-bare repository.
  623. *
  624. * @param name
  625. * revision string; either an existing ref name, or something that
  626. * can be parsed to an object ID.
  627. * @throws Exception
  628. */
  629. public void reset(String name) throws Exception {
  630. RefUpdate.Result result;
  631. ObjectId id = db.resolve(name);
  632. if (id == null)
  633. throw new IOException("Not a revision: " + name);
  634. RefUpdate ru = db.updateRef(Constants.HEAD, false);
  635. ru.setNewObjectId(id);
  636. result = ru.forceUpdate();
  637. switch (result) {
  638. case FAST_FORWARD:
  639. case FORCED:
  640. case NEW:
  641. case NO_CHANGE:
  642. break;
  643. default:
  644. throw new IOException(String.format(
  645. "Checkout \"%s\" failed: %s", name, result));
  646. }
  647. }
  648. /**
  649. * Cherry-pick a commit onto HEAD.
  650. * <p>
  651. * This differs from {@code git cherry-pick} in that it works in a bare
  652. * repository. As a result, any merge failure results in an exception, as
  653. * there is no way to recover.
  654. *
  655. * @param id
  656. * commit-ish to cherry-pick.
  657. * @return the new, fully parsed commit, or null if no work was done due to
  658. * the resulting tree being identical.
  659. * @throws Exception
  660. */
  661. public RevCommit cherryPick(AnyObjectId id) throws Exception {
  662. RevCommit commit = pool.parseCommit(id);
  663. pool.parseBody(commit);
  664. if (commit.getParentCount() != 1)
  665. throw new IOException(String.format(
  666. "Expected 1 parent for %s, found: %s",
  667. id.name(), Arrays.asList(commit.getParents())));
  668. RevCommit parent = commit.getParent(0);
  669. pool.parseHeaders(parent);
  670. Ref headRef = db.exactRef(Constants.HEAD);
  671. if (headRef == null)
  672. throw new IOException("Missing HEAD");
  673. RevCommit head = pool.parseCommit(headRef.getObjectId());
  674. ThreeWayMerger merger = MergeStrategy.RECURSIVE.newMerger(db, true);
  675. merger.setBase(parent.getTree());
  676. if (merger.merge(head, commit)) {
  677. if (AnyObjectId.isEqual(head.getTree(), merger.getResultTreeId()))
  678. return null;
  679. tick(1);
  680. org.eclipse.jgit.lib.CommitBuilder b =
  681. new org.eclipse.jgit.lib.CommitBuilder();
  682. b.setParentId(head);
  683. b.setTreeId(merger.getResultTreeId());
  684. b.setAuthor(commit.getAuthorIdent());
  685. b.setCommitter(new PersonIdent(defaultCommitter, getDate()));
  686. b.setMessage(commit.getFullMessage());
  687. ObjectId result;
  688. try (ObjectInserter ins = inserter) {
  689. result = ins.insert(b);
  690. ins.flush();
  691. }
  692. update(Constants.HEAD, result);
  693. return pool.parseCommit(result);
  694. }
  695. throw new IOException("Merge conflict");
  696. }
  697. /**
  698. * Update the dumb client server info files.
  699. *
  700. * @throws Exception
  701. */
  702. public void updateServerInfo() throws Exception {
  703. if (db instanceof FileRepository) {
  704. final FileRepository fr = (FileRepository) db;
  705. RefWriter rw = new RefWriter(fr.getRefDatabase().getRefs()) {
  706. @Override
  707. protected void writeFile(String name, byte[] bin)
  708. throws IOException {
  709. File path = new File(fr.getDirectory(), name);
  710. TestRepository.this.writeFile(path, bin);
  711. }
  712. };
  713. rw.writePackedRefs();
  714. rw.writeInfoRefs();
  715. final StringBuilder w = new StringBuilder();
  716. for (Pack p : fr.getObjectDatabase().getPacks()) {
  717. w.append("P ");
  718. w.append(p.getPackFile().getName());
  719. w.append('\n');
  720. }
  721. writeFile(new File(new File(fr.getObjectDatabase().getDirectory(),
  722. "info"), "packs"), Constants.encodeASCII(w.toString()));
  723. }
  724. }
  725. /**
  726. * Ensure the body of the given object has been parsed.
  727. *
  728. * @param <T>
  729. * type of object, e.g. {@link org.eclipse.jgit.revwalk.RevTag}
  730. * or {@link org.eclipse.jgit.revwalk.RevCommit}.
  731. * @param object
  732. * reference to the (possibly unparsed) object to force body
  733. * parsing of.
  734. * @return {@code object}
  735. * @throws Exception
  736. */
  737. public <T extends RevObject> T parseBody(T object) throws Exception {
  738. pool.parseBody(object);
  739. return object;
  740. }
  741. /**
  742. * Create a new branch builder for this repository.
  743. *
  744. * @param ref
  745. * name of the branch to be constructed. If {@code ref} does not
  746. * start with {@code refs/} the prefix {@code refs/heads/} will
  747. * be added.
  748. * @return builder for the named branch.
  749. */
  750. public BranchBuilder branch(String ref) {
  751. if (Constants.HEAD.equals(ref)) {
  752. // nothing
  753. } else if (ref.startsWith(Constants.R_REFS)) {
  754. // nothing
  755. } else
  756. ref = Constants.R_HEADS + ref;
  757. return new BranchBuilder(ref);
  758. }
  759. /**
  760. * Tag an object using a lightweight tag.
  761. *
  762. * @param name
  763. * the tag name. The /refs/tags/ prefix will be added if the name
  764. * doesn't start with it
  765. * @param obj
  766. * the object to tag
  767. * @return the tagged object
  768. * @throws Exception
  769. */
  770. public ObjectId lightweightTag(String name, ObjectId obj) throws Exception {
  771. if (!name.startsWith(Constants.R_TAGS))
  772. name = Constants.R_TAGS + name;
  773. return update(name, obj);
  774. }
  775. /**
  776. * Run consistency checks against the object database.
  777. * <p>
  778. * This method completes silently if the checks pass. A temporary revision
  779. * pool is constructed during the checking.
  780. *
  781. * @param tips
  782. * the tips to start checking from; if not supplied the refs of
  783. * the repository are used instead.
  784. * @throws MissingObjectException
  785. * @throws IncorrectObjectTypeException
  786. * @throws IOException
  787. */
  788. public void fsck(RevObject... tips) throws MissingObjectException,
  789. IncorrectObjectTypeException, IOException {
  790. try (ObjectWalk ow = new ObjectWalk(db)) {
  791. if (tips.length != 0) {
  792. for (RevObject o : tips)
  793. ow.markStart(ow.parseAny(o));
  794. } else {
  795. for (Ref r : db.getRefDatabase().getRefs())
  796. ow.markStart(ow.parseAny(r.getObjectId()));
  797. }
  798. ObjectChecker oc = new ObjectChecker();
  799. for (;;) {
  800. final RevCommit o = ow.next();
  801. if (o == null)
  802. break;
  803. final byte[] bin = db.open(o, o.getType()).getCachedBytes();
  804. oc.checkCommit(o, bin);
  805. assertHash(o, bin);
  806. }
  807. for (;;) {
  808. final RevObject o = ow.nextObject();
  809. if (o == null)
  810. break;
  811. final byte[] bin = db.open(o, o.getType()).getCachedBytes();
  812. oc.check(o, o.getType(), bin);
  813. assertHash(o, bin);
  814. }
  815. }
  816. }
  817. private static void assertHash(RevObject id, byte[] bin) {
  818. MessageDigest md = Constants.newMessageDigest();
  819. md.update(Constants.encodedTypeString(id.getType()));
  820. md.update((byte) ' ');
  821. md.update(Constants.encodeASCII(bin.length));
  822. md.update((byte) 0);
  823. md.update(bin);
  824. assertEquals(id, ObjectId.fromRaw(md.digest()));
  825. }
  826. /**
  827. * Pack all reachable objects in the repository into a single pack file.
  828. * <p>
  829. * All loose objects are automatically pruned. Existing packs however are
  830. * not removed.
  831. *
  832. * @throws Exception
  833. */
  834. public void packAndPrune() throws Exception {
  835. if (db.getObjectDatabase() instanceof ObjectDirectory) {
  836. ObjectDirectory odb = (ObjectDirectory) db.getObjectDatabase();
  837. NullProgressMonitor m = NullProgressMonitor.INSTANCE;
  838. final File pack, idx;
  839. try (PackWriter pw = new PackWriter(db)) {
  840. Set<ObjectId> all = new HashSet<>();
  841. for (Ref r : db.getRefDatabase().getRefs())
  842. all.add(r.getObjectId());
  843. pw.preparePack(m, all, PackWriter.NONE);
  844. final ObjectId name = pw.computeName();
  845. pack = nameFor(odb, name, ".pack");
  846. try (OutputStream out =
  847. new BufferedOutputStream(new FileOutputStream(pack))) {
  848. pw.writePack(m, m, out);
  849. }
  850. pack.setReadOnly();
  851. idx = nameFor(odb, name, ".idx");
  852. try (OutputStream out =
  853. new BufferedOutputStream(new FileOutputStream(idx))) {
  854. pw.writeIndex(out);
  855. }
  856. idx.setReadOnly();
  857. }
  858. odb.openPack(pack);
  859. updateServerInfo();
  860. prunePacked(odb);
  861. }
  862. }
  863. /**
  864. * Closes the underlying {@link Repository} object and any other internal
  865. * resources.
  866. * <p>
  867. * {@link AutoCloseable} resources that may escape this object, such as
  868. * those returned by the {@link #git} and {@link #getRevWalk()} methods are
  869. * not closed.
  870. */
  871. @Override
  872. public void close() {
  873. try {
  874. inserter.close();
  875. } finally {
  876. db.close();
  877. }
  878. }
  879. private static void prunePacked(ObjectDirectory odb) throws IOException {
  880. for (Pack p : odb.getPacks()) {
  881. for (MutableEntry e : p)
  882. FileUtils.delete(odb.fileFor(e.toObjectId()));
  883. }
  884. }
  885. private static File nameFor(ObjectDirectory odb, ObjectId name, String t) {
  886. File packdir = odb.getPackDirectory();
  887. return new File(packdir, "pack-" + name.name() + t);
  888. }
  889. private void writeFile(File p, byte[] bin) throws IOException,
  890. ObjectWritingException {
  891. final LockFile lck = new LockFile(p);
  892. if (!lck.lock())
  893. throw new ObjectWritingException("Can't write " + p);
  894. try {
  895. lck.write(bin);
  896. } catch (IOException ioe) {
  897. throw new ObjectWritingException("Can't write " + p, ioe);
  898. }
  899. if (!lck.commit())
  900. throw new ObjectWritingException("Can't write " + p);
  901. }
  902. /** Helper to build a branch with one or more commits */
  903. public class BranchBuilder {
  904. private final String ref;
  905. BranchBuilder(String ref) {
  906. this.ref = ref;
  907. }
  908. /**
  909. * @return construct a new commit builder that updates this branch. If
  910. * the branch already exists, the commit builder will have its
  911. * first parent as the current commit and its tree will be
  912. * initialized to the current files.
  913. * @throws Exception
  914. * the commit builder can't read the current branch state
  915. */
  916. public CommitBuilder commit() throws Exception {
  917. return new CommitBuilder(this);
  918. }
  919. /**
  920. * Forcefully update this branch to a particular commit.
  921. *
  922. * @param to
  923. * the commit to update to.
  924. * @return {@code to}.
  925. * @throws Exception
  926. */
  927. public RevCommit update(CommitBuilder to) throws Exception {
  928. return update(to.create());
  929. }
  930. /**
  931. * Forcefully update this branch to a particular commit.
  932. *
  933. * @param to
  934. * the commit to update to.
  935. * @return {@code to}.
  936. * @throws Exception
  937. */
  938. public RevCommit update(RevCommit to) throws Exception {
  939. return TestRepository.this.update(ref, to);
  940. }
  941. /**
  942. * Delete this branch.
  943. * @throws Exception
  944. * @since 4.4
  945. */
  946. public void delete() throws Exception {
  947. TestRepository.this.delete(ref);
  948. }
  949. }
  950. /** Helper to generate a commit. */
  951. public class CommitBuilder {
  952. private final BranchBuilder branch;
  953. private final DirCache tree = DirCache.newInCore();
  954. private ObjectId topLevelTree;
  955. private final List<RevCommit> parents = new ArrayList<>(2);
  956. private int tick = 1;
  957. private String message = "";
  958. private RevCommit self;
  959. private PersonIdent author;
  960. private PersonIdent committer;
  961. private String changeId;
  962. private boolean updateCommitterTime;
  963. CommitBuilder() {
  964. branch = null;
  965. }
  966. CommitBuilder(BranchBuilder b) throws Exception {
  967. branch = b;
  968. Ref ref = db.exactRef(branch.ref);
  969. if (ref != null && ref.getObjectId() != null)
  970. parent(pool.parseCommit(ref.getObjectId()));
  971. }
  972. CommitBuilder(CommitBuilder prior) throws Exception {
  973. branch = prior.branch;
  974. DirCacheBuilder b = tree.builder();
  975. for (int i = 0; i < prior.tree.getEntryCount(); i++)
  976. b.add(prior.tree.getEntry(i));
  977. b.finish();
  978. parents.add(prior.create());
  979. }
  980. /**
  981. * set parent commit
  982. *
  983. * @param p
  984. * parent commit
  985. * @return this commit builder
  986. * @throws Exception
  987. */
  988. public CommitBuilder parent(RevCommit p) throws Exception {
  989. if (parents.isEmpty()) {
  990. DirCacheBuilder b = tree.builder();
  991. parseBody(p);
  992. b.addTree(new byte[0], DirCacheEntry.STAGE_0, pool
  993. .getObjectReader(), p.getTree());
  994. b.finish();
  995. }
  996. parents.add(p);
  997. return this;
  998. }
  999. /**
  1000. * Get parent commits
  1001. *
  1002. * @return parent commits
  1003. */
  1004. public List<RevCommit> parents() {
  1005. return Collections.unmodifiableList(parents);
  1006. }
  1007. /**
  1008. * Remove parent commits
  1009. *
  1010. * @return this commit builder
  1011. */
  1012. public CommitBuilder noParents() {
  1013. parents.clear();
  1014. return this;
  1015. }
  1016. /**
  1017. * Remove files
  1018. *
  1019. * @return this commit builder
  1020. */
  1021. public CommitBuilder noFiles() {
  1022. tree.clear();
  1023. return this;
  1024. }
  1025. /**
  1026. * Set top level tree
  1027. *
  1028. * @param treeId
  1029. * the top level tree
  1030. * @return this commit builder
  1031. */
  1032. public CommitBuilder setTopLevelTree(ObjectId treeId) {
  1033. topLevelTree = treeId;
  1034. return this;
  1035. }
  1036. /**
  1037. * Add file with given content
  1038. *
  1039. * @param path
  1040. * path of the file
  1041. * @param content
  1042. * the file content
  1043. * @return this commit builder
  1044. * @throws Exception
  1045. */
  1046. public CommitBuilder add(String path, String content) throws Exception {
  1047. return add(path, blob(content));
  1048. }
  1049. /**
  1050. * Add file with given path and blob
  1051. *
  1052. * @param path
  1053. * path of the file
  1054. * @param id
  1055. * blob for this file
  1056. * @return this commit builder
  1057. * @throws Exception
  1058. */
  1059. public CommitBuilder add(String path, RevBlob id)
  1060. throws Exception {
  1061. return edit(new PathEdit(path) {
  1062. @Override
  1063. public void apply(DirCacheEntry ent) {
  1064. ent.setFileMode(FileMode.REGULAR_FILE);
  1065. ent.setObjectId(id);
  1066. }
  1067. });
  1068. }
  1069. /**
  1070. * Edit the index
  1071. *
  1072. * @param edit
  1073. * the index record update
  1074. * @return this commit builder
  1075. */
  1076. public CommitBuilder edit(PathEdit edit) {
  1077. DirCacheEditor e = tree.editor();
  1078. e.add(edit);
  1079. e.finish();
  1080. return this;
  1081. }
  1082. /**
  1083. * Remove a file
  1084. *
  1085. * @param path
  1086. * path of the file
  1087. * @return this commit builder
  1088. */
  1089. public CommitBuilder rm(String path) {
  1090. DirCacheEditor e = tree.editor();
  1091. e.add(new DeletePath(path));
  1092. e.add(new DeleteTree(path));
  1093. e.finish();
  1094. return this;
  1095. }
  1096. /**
  1097. * Set commit message
  1098. *
  1099. * @param m
  1100. * the message
  1101. * @return this commit builder
  1102. */
  1103. public CommitBuilder message(String m) {
  1104. message = m;
  1105. return this;
  1106. }
  1107. /**
  1108. * Get the commit message
  1109. *
  1110. * @return the commit message
  1111. */
  1112. public String message() {
  1113. return message;
  1114. }
  1115. /**
  1116. * Tick the clock
  1117. *
  1118. * @param secs
  1119. * number of seconds
  1120. * @return this commit builder
  1121. */
  1122. public CommitBuilder tick(int secs) {
  1123. tick = secs;
  1124. return this;
  1125. }
  1126. /**
  1127. * Set author and committer identity
  1128. *
  1129. * @param ident
  1130. * identity to set
  1131. * @return this commit builder
  1132. */
  1133. public CommitBuilder ident(PersonIdent ident) {
  1134. author = ident;
  1135. committer = ident;
  1136. return this;
  1137. }
  1138. /**
  1139. * Set the author identity
  1140. *
  1141. * @param a
  1142. * the author's identity
  1143. * @return this commit builder
  1144. */
  1145. public CommitBuilder author(PersonIdent a) {
  1146. author = a;
  1147. return this;
  1148. }
  1149. /**
  1150. * Get the author identity
  1151. *
  1152. * @return the author identity
  1153. */
  1154. public PersonIdent author() {
  1155. return author;
  1156. }
  1157. /**
  1158. * Set the committer identity
  1159. *
  1160. * @param c
  1161. * the committer identity
  1162. * @return this commit builder
  1163. */
  1164. public CommitBuilder committer(PersonIdent c) {
  1165. committer = c;
  1166. return this;
  1167. }
  1168. /**
  1169. * Get the committer identity
  1170. *
  1171. * @return the committer identity
  1172. */
  1173. public PersonIdent committer() {
  1174. return committer;
  1175. }
  1176. /**
  1177. * Insert changeId
  1178. *
  1179. * @return this commit builder
  1180. */
  1181. public CommitBuilder insertChangeId() {
  1182. changeId = "";
  1183. return this;
  1184. }
  1185. /**
  1186. * Insert given changeId
  1187. *
  1188. * @param c
  1189. * changeId
  1190. * @return this commit builder
  1191. */
  1192. public CommitBuilder insertChangeId(String c) {
  1193. // Validate, but store as a string so we can use "" as a sentinel.
  1194. ObjectId.fromString(c);
  1195. changeId = c;
  1196. return this;
  1197. }
  1198. /**
  1199. * Create the commit
  1200. *
  1201. * @return the new commit
  1202. * @throws Exception
  1203. * if creation failed
  1204. */
  1205. public RevCommit create() throws Exception {
  1206. if (self == null) {
  1207. TestRepository.this.tick(tick);
  1208. final org.eclipse.jgit.lib.CommitBuilder c;
  1209. c = new org.eclipse.jgit.lib.CommitBuilder();
  1210. c.setParentIds(parents);
  1211. setAuthorAndCommitter(c);
  1212. if (author != null)
  1213. c.setAuthor(author);
  1214. if (committer != null) {
  1215. if (updateCommitterTime)
  1216. committer = new PersonIdent(committer, getDate());
  1217. c.setCommitter(committer);
  1218. }
  1219. ObjectId commitId;
  1220. try (ObjectInserter ins = inserter) {
  1221. if (topLevelTree != null)
  1222. c.setTreeId(topLevelTree);
  1223. else
  1224. c.setTreeId(tree.writeTree(ins));
  1225. insertChangeId(c);
  1226. c.setMessage(message);
  1227. commitId = ins.insert(c);
  1228. ins.flush();
  1229. }
  1230. self = pool.parseCommit(commitId);
  1231. if (branch != null)
  1232. branch.update(self);
  1233. }
  1234. return self;
  1235. }
  1236. private void insertChangeId(org.eclipse.jgit.lib.CommitBuilder c) {
  1237. if (changeId == null)
  1238. return;
  1239. int idx = ChangeIdUtil.indexOfChangeId(message, "\n");
  1240. if (idx >= 0)
  1241. return;
  1242. ObjectId firstParentId = null;
  1243. if (!parents.isEmpty())
  1244. firstParentId = parents.get(0);
  1245. ObjectId cid;
  1246. if (changeId.isEmpty())
  1247. cid = ChangeIdUtil.computeChangeId(c.getTreeId(), firstParentId,
  1248. c.getAuthor(), c.getCommitter(), message);
  1249. else
  1250. cid = ObjectId.fromString(changeId);
  1251. message = ChangeIdUtil.insertId(message, cid);
  1252. if (cid != null)
  1253. message = message.replaceAll("\nChange-Id: I" //$NON-NLS-1$
  1254. + ObjectId.zeroId().getName() + "\n", "\nChange-Id: I" //$NON-NLS-1$ //$NON-NLS-2$
  1255. + cid.getName() + "\n"); //$NON-NLS-1$
  1256. }
  1257. /**
  1258. * Create child commit builder
  1259. *
  1260. * @return child commit builder
  1261. * @throws Exception
  1262. */
  1263. public CommitBuilder child() throws Exception {
  1264. return new CommitBuilder(this);
  1265. }
  1266. }
  1267. }