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

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