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.

RebaseCommandTest.java 32KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928
  1. /*
  2. * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
  3. * and other copyright owners as documented in the project's IP log.
  4. *
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Distribution License v1.0 which
  7. * accompanies this distribution, is reproduced below, and is
  8. * available at http://www.eclipse.org/org/documents/edl-v10.php
  9. *
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or
  13. * without modification, are permitted provided that the following
  14. * conditions are met:
  15. *
  16. * - Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. *
  19. * - Redistributions in binary form must reproduce the above
  20. * copyright notice, this list of conditions and the following
  21. * disclaimer in the documentation and/or other materials provided
  22. * with the distribution.
  23. *
  24. * - Neither the name of the Eclipse Foundation, Inc. nor the
  25. * names of its contributors may be used to endorse or promote
  26. * products derived from this software without specific prior
  27. * written permission.
  28. *
  29. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42. */
  43. package org.eclipse.jgit.api;
  44. import static org.junit.Assert.assertEquals;
  45. import static org.junit.Assert.assertFalse;
  46. import static org.junit.Assert.assertNotNull;
  47. import static org.junit.Assert.assertTrue;
  48. import static org.junit.Assert.fail;
  49. import java.io.BufferedReader;
  50. import java.io.File;
  51. import java.io.FileInputStream;
  52. import java.io.IOException;
  53. import java.io.InputStreamReader;
  54. import org.eclipse.jgit.api.RebaseCommand.Action;
  55. import org.eclipse.jgit.api.RebaseCommand.Operation;
  56. import org.eclipse.jgit.api.RebaseResult.Status;
  57. import org.eclipse.jgit.api.errors.RefNotFoundException;
  58. import org.eclipse.jgit.api.errors.UnmergedPathsException;
  59. import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
  60. import org.eclipse.jgit.dircache.DirCacheCheckout;
  61. import org.eclipse.jgit.lib.Constants;
  62. import org.eclipse.jgit.lib.ObjectId;
  63. import org.eclipse.jgit.lib.PersonIdent;
  64. import org.eclipse.jgit.lib.RefUpdate;
  65. import org.eclipse.jgit.lib.RepositoryState;
  66. import org.eclipse.jgit.lib.RepositoryTestCase;
  67. import org.eclipse.jgit.revwalk.RevCommit;
  68. import org.eclipse.jgit.revwalk.RevWalk;
  69. import org.junit.Before;
  70. import org.junit.Test;
  71. public class RebaseCommandTest extends RepositoryTestCase {
  72. private static final String FILE1 = "file1";
  73. protected Git git;
  74. @Override
  75. @Before
  76. public void setUp() throws Exception {
  77. super.setUp();
  78. this.git = new Git(db);
  79. }
  80. private void createBranch(ObjectId objectId, String branchName)
  81. throws IOException {
  82. RefUpdate updateRef = db.updateRef(branchName);
  83. updateRef.setNewObjectId(objectId);
  84. updateRef.update();
  85. }
  86. private void checkoutBranch(String branchName)
  87. throws IllegalStateException, IOException {
  88. RevWalk walk = new RevWalk(db);
  89. RevCommit head = walk.parseCommit(db.resolve(Constants.HEAD));
  90. RevCommit branch = walk.parseCommit(db.resolve(branchName));
  91. DirCacheCheckout dco = new DirCacheCheckout(db, head.getTree().getId(),
  92. db.lockDirCache(), branch.getTree().getId());
  93. dco.setFailOnConflict(true);
  94. dco.checkout();
  95. walk.release();
  96. // update the HEAD
  97. RefUpdate refUpdate = db.updateRef(Constants.HEAD);
  98. refUpdate.link(branchName);
  99. }
  100. private void checkoutCommit(RevCommit commit) throws IllegalStateException,
  101. IOException {
  102. RevWalk walk = new RevWalk(db);
  103. RevCommit head = walk.parseCommit(db.resolve(Constants.HEAD));
  104. DirCacheCheckout dco = new DirCacheCheckout(db, head.getTree(), db
  105. .lockDirCache(), commit.getTree());
  106. dco.setFailOnConflict(true);
  107. dco.checkout();
  108. walk.release();
  109. // update the HEAD
  110. RefUpdate refUpdate = db.updateRef(Constants.HEAD, true);
  111. refUpdate.setNewObjectId(commit);
  112. refUpdate.forceUpdate();
  113. }
  114. @Test
  115. public void testFastForwardWithNewFile() throws Exception {
  116. // create file1 on master
  117. writeTrashFile(FILE1, FILE1);
  118. git.add().addFilepattern(FILE1).call();
  119. RevCommit first = git.commit().setMessage("Add file1").call();
  120. assertTrue(new File(db.getWorkTree(), FILE1).exists());
  121. // create a topic branch
  122. createBranch(first, "refs/heads/topic");
  123. // create file2 on master
  124. File file2 = writeTrashFile("file2", "file2");
  125. git.add().addFilepattern("file2").call();
  126. git.commit().setMessage("Add file2").call();
  127. assertTrue(new File(db.getWorkTree(), "file2").exists());
  128. checkoutBranch("refs/heads/topic");
  129. assertFalse(new File(db.getWorkTree(), "file2").exists());
  130. RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
  131. assertTrue(new File(db.getWorkTree(), "file2").exists());
  132. checkFile(file2, "file2");
  133. assertEquals(Status.FAST_FORWARD, res.getStatus());
  134. }
  135. @Test
  136. public void testFastForwardWithMultipleCommits() throws Exception {
  137. // create file1 on master
  138. writeTrashFile(FILE1, FILE1);
  139. git.add().addFilepattern(FILE1).call();
  140. RevCommit first = git.commit().setMessage("Add file1").call();
  141. assertTrue(new File(db.getWorkTree(), FILE1).exists());
  142. // create a topic branch
  143. createBranch(first, "refs/heads/topic");
  144. // create file2 on master
  145. File file2 = writeTrashFile("file2", "file2");
  146. git.add().addFilepattern("file2").call();
  147. git.commit().setMessage("Add file2").call();
  148. assertTrue(new File(db.getWorkTree(), "file2").exists());
  149. // write a second commit
  150. writeTrashFile("file2", "file2 new content");
  151. git.add().addFilepattern("file2").call();
  152. git.commit().setMessage("Change content of file2").call();
  153. checkoutBranch("refs/heads/topic");
  154. assertFalse(new File(db.getWorkTree(), "file2").exists());
  155. RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
  156. assertTrue(new File(db.getWorkTree(), "file2").exists());
  157. checkFile(file2, "file2 new content");
  158. assertEquals(Status.FAST_FORWARD, res.getStatus());
  159. }
  160. @Test
  161. public void testUpToDate() throws Exception {
  162. // create file1 on master
  163. writeTrashFile(FILE1, FILE1);
  164. git.add().addFilepattern(FILE1).call();
  165. RevCommit first = git.commit().setMessage("Add file1").call();
  166. assertTrue(new File(db.getWorkTree(), FILE1).exists());
  167. RebaseResult res = git.rebase().setUpstream(first).call();
  168. assertEquals(Status.UP_TO_DATE, res.getStatus());
  169. }
  170. @Test
  171. public void testUnknownUpstream() throws Exception {
  172. // create file1 on master
  173. writeTrashFile(FILE1, FILE1);
  174. git.add().addFilepattern(FILE1).call();
  175. git.commit().setMessage("Add file1").call();
  176. assertTrue(new File(db.getWorkTree(), FILE1).exists());
  177. try {
  178. git.rebase().setUpstream("refs/heads/xyz").call();
  179. fail("expected exception was not thrown");
  180. } catch (RefNotFoundException e) {
  181. // expected exception
  182. }
  183. }
  184. @Test
  185. public void testConflictFreeWithSingleFile() throws Exception {
  186. // create file1 on master
  187. File theFile = writeTrashFile(FILE1, "1\n2\n3\n");
  188. git.add().addFilepattern(FILE1).call();
  189. RevCommit second = git.commit().setMessage("Add file1").call();
  190. assertTrue(new File(db.getWorkTree(), FILE1).exists());
  191. // change first line in master and commit
  192. writeTrashFile(FILE1, "1master\n2\n3\n");
  193. checkFile(theFile, "1master\n2\n3\n");
  194. git.add().addFilepattern(FILE1).call();
  195. RevCommit lastMasterChange = git.commit().setMessage(
  196. "change file1 in master").call();
  197. // create a topic branch based on second commit
  198. createBranch(second, "refs/heads/topic");
  199. checkoutBranch("refs/heads/topic");
  200. // we have the old content again
  201. checkFile(theFile, "1\n2\n3\n");
  202. assertTrue(new File(db.getWorkTree(), FILE1).exists());
  203. // change third line in topic branch
  204. writeTrashFile(FILE1, "1\n2\n3\ntopic\n");
  205. git.add().addFilepattern(FILE1).call();
  206. git.commit().setMessage("change file1 in topic").call();
  207. RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
  208. assertEquals(Status.OK, res.getStatus());
  209. checkFile(theFile, "1master\n2\n3\ntopic\n");
  210. // our old branch should be checked out again
  211. assertEquals("refs/heads/topic", db.getFullBranch());
  212. assertEquals(lastMasterChange, new RevWalk(db).parseCommit(
  213. db.resolve(Constants.HEAD)).getParent(0));
  214. }
  215. @Test
  216. public void testDetachedHead() throws Exception {
  217. // create file1 on master
  218. File theFile = writeTrashFile(FILE1, "1\n2\n3\n");
  219. git.add().addFilepattern(FILE1).call();
  220. RevCommit second = git.commit().setMessage("Add file1").call();
  221. assertTrue(new File(db.getWorkTree(), FILE1).exists());
  222. // change first line in master and commit
  223. writeTrashFile(FILE1, "1master\n2\n3\n");
  224. checkFile(theFile, "1master\n2\n3\n");
  225. git.add().addFilepattern(FILE1).call();
  226. RevCommit lastMasterChange = git.commit().setMessage(
  227. "change file1 in master").call();
  228. // create a topic branch based on second commit
  229. createBranch(second, "refs/heads/topic");
  230. checkoutBranch("refs/heads/topic");
  231. // we have the old content again
  232. checkFile(theFile, "1\n2\n3\n");
  233. assertTrue(new File(db.getWorkTree(), FILE1).exists());
  234. // change third line in topic branch
  235. writeTrashFile(FILE1, "1\n2\n3\ntopic\n");
  236. git.add().addFilepattern(FILE1).call();
  237. RevCommit topicCommit = git.commit()
  238. .setMessage("change file1 in topic").call();
  239. checkoutBranch("refs/heads/master");
  240. checkoutCommit(topicCommit);
  241. assertEquals(topicCommit.getId().getName(), db.getFullBranch());
  242. RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
  243. assertEquals(Status.OK, res.getStatus());
  244. checkFile(theFile, "1master\n2\n3\ntopic\n");
  245. assertEquals(lastMasterChange, new RevWalk(db).parseCommit(
  246. db.resolve(Constants.HEAD)).getParent(0));
  247. }
  248. @Test
  249. public void testFilesAddedFromTwoBranches() throws Exception {
  250. // create file1 on master
  251. writeTrashFile(FILE1, FILE1);
  252. git.add().addFilepattern(FILE1).call();
  253. RevCommit masterCommit = git.commit().setMessage("Add file1 to master")
  254. .call();
  255. // create a branch named file2 and add file2
  256. createBranch(masterCommit, "refs/heads/file2");
  257. checkoutBranch("refs/heads/file2");
  258. writeTrashFile("file2", "file2");
  259. git.add().addFilepattern("file2").call();
  260. RevCommit addFile2 = git.commit().setMessage(
  261. "Add file2 to branch file2").call();
  262. // create a branch named file3 and add file3
  263. createBranch(masterCommit, "refs/heads/file3");
  264. checkoutBranch("refs/heads/file3");
  265. writeTrashFile("file3", "file3");
  266. git.add().addFilepattern("file3").call();
  267. git.commit().setMessage("Add file3 to branch file3").call();
  268. assertTrue(new File(db.getWorkTree(), FILE1).exists());
  269. assertFalse(new File(db.getWorkTree(), "file2").exists());
  270. assertTrue(new File(db.getWorkTree(), "file3").exists());
  271. RebaseResult res = git.rebase().setUpstream("refs/heads/file2").call();
  272. assertEquals(Status.OK, res.getStatus());
  273. assertTrue(new File(db.getWorkTree(), FILE1).exists());
  274. assertTrue(new File(db.getWorkTree(), "file2").exists());
  275. assertTrue(new File(db.getWorkTree(), "file3").exists());
  276. // our old branch should be checked out again
  277. assertEquals("refs/heads/file3", db.getFullBranch());
  278. assertEquals(addFile2, new RevWalk(db).parseCommit(
  279. db.resolve(Constants.HEAD)).getParent(0));
  280. checkoutBranch("refs/heads/file2");
  281. assertTrue(new File(db.getWorkTree(), FILE1).exists());
  282. assertTrue(new File(db.getWorkTree(), "file2").exists());
  283. assertFalse(new File(db.getWorkTree(), "file3").exists());
  284. }
  285. @Test
  286. public void testStopOnConflict() throws Exception {
  287. // create file1 on master
  288. RevCommit firstInMaster = writeFileAndCommit(FILE1, "Add file1", "1",
  289. "2", "3");
  290. // change first line in master
  291. writeFileAndCommit(FILE1, "change file1 in master", "1master", "2", "3");
  292. checkFile(FILE1, "1master", "2", "3");
  293. // create a topic branch based on second commit
  294. createBranch(firstInMaster, "refs/heads/topic");
  295. checkoutBranch("refs/heads/topic");
  296. // we have the old content again
  297. checkFile(FILE1, "1", "2", "3");
  298. // add a line (non-conflicting)
  299. writeFileAndCommit(FILE1, "add a line to file1 in topic", "1", "2",
  300. "3", "topic4");
  301. // change first line (conflicting)
  302. RevCommit conflicting = writeFileAndCommit(FILE1,
  303. "change file1 in topic", "1topic", "2", "3", "topic4");
  304. RevCommit lastTopicCommit = writeFileAndCommit(FILE1,
  305. "change file1 in topic again", "1topic", "2", "3", "topic4");
  306. RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
  307. assertEquals(Status.STOPPED, res.getStatus());
  308. assertEquals(conflicting, res.getCurrentCommit());
  309. checkFile(FILE1,
  310. "<<<<<<< OURS\n1master\n=======\n1topic\n>>>>>>> THEIRS\n2\n3\ntopic4");
  311. assertEquals(RepositoryState.REBASING_INTERACTIVE, db
  312. .getRepositoryState());
  313. assertTrue(new File(db.getDirectory(), "rebase-merge").exists());
  314. // the first one should be included, so we should have left two picks in
  315. // the file
  316. assertEquals(1, countPicks());
  317. // rebase should not succeed in this state
  318. try {
  319. git.rebase().setUpstream("refs/heads/master").call();
  320. fail("Expected exception was not thrown");
  321. } catch (WrongRepositoryStateException e) {
  322. // expected
  323. }
  324. // abort should reset to topic branch
  325. res = git.rebase().setOperation(Operation.ABORT).call();
  326. assertEquals(res.getStatus(), Status.ABORTED);
  327. assertEquals("refs/heads/topic", db.getFullBranch());
  328. checkFile(FILE1, "1topic", "2", "3", "topic4");
  329. RevWalk rw = new RevWalk(db);
  330. assertEquals(lastTopicCommit, rw
  331. .parseCommit(db.resolve(Constants.HEAD)));
  332. assertEquals(RepositoryState.SAFE, db.getRepositoryState());
  333. // rebase- dir in .git must be deleted
  334. assertFalse(new File(db.getDirectory(), "rebase-merge").exists());
  335. }
  336. @Test
  337. public void testStopOnConflictAndContinue() throws Exception {
  338. // create file1 on master
  339. RevCommit firstInMaster = writeFileAndCommit(FILE1, "Add file1", "1",
  340. "2", "3");
  341. // change in master
  342. writeFileAndCommit(FILE1, "change file1 in master", "1master", "2", "3");
  343. checkFile(FILE1, "1master", "2", "3");
  344. // create a topic branch based on the first commit
  345. createBranch(firstInMaster, "refs/heads/topic");
  346. checkoutBranch("refs/heads/topic");
  347. // we have the old content again
  348. checkFile(FILE1, "1", "2", "3");
  349. // add a line (non-conflicting)
  350. writeFileAndCommit(FILE1, "add a line to file1 in topic", "1", "2",
  351. "3", "4topic");
  352. // change first line (conflicting)
  353. writeFileAndCommit(FILE1,
  354. "change file1 in topic\n\nThis is conflicting", "1topic", "2",
  355. "3", "4topic");
  356. // change second line (not conflicting)
  357. writeFileAndCommit(FILE1, "change file1 in topic again", "1topic",
  358. "2topic", "3", "4topic");
  359. RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
  360. assertEquals(Status.STOPPED, res.getStatus());
  361. // continue should throw a meaningful exception
  362. try {
  363. res = git.rebase().setOperation(Operation.CONTINUE).call();
  364. fail("Expected Exception not thrown");
  365. } catch (UnmergedPathsException e) {
  366. // expected
  367. }
  368. // merge the file; the second topic commit should go through
  369. writeFileAndAdd(FILE1, "1topic", "2", "3", "4topic");
  370. res = git.rebase().setOperation(Operation.CONTINUE).call();
  371. assertNotNull(res);
  372. assertEquals(Status.OK, res.getStatus());
  373. assertEquals(RepositoryState.SAFE, db.getRepositoryState());
  374. ObjectId headId = db.resolve(Constants.HEAD);
  375. RevWalk rw = new RevWalk(db);
  376. RevCommit rc = rw.parseCommit(headId);
  377. RevCommit parent = rw.parseCommit(rc.getParent(0));
  378. assertEquals("change file1 in topic\n\nThis is conflicting", parent
  379. .getFullMessage());
  380. }
  381. @Test
  382. public void testStopOnConflictAndFailContinueIfFileIsDirty()
  383. throws Exception {
  384. // create file1 on master
  385. RevCommit firstInMaster = writeFileAndCommit(FILE1, "Add file1", "1",
  386. "2", "3");
  387. // change in master
  388. writeFileAndCommit(FILE1, "change file1 in master", "1master", "2", "3");
  389. checkFile(FILE1, "1master", "2", "3");
  390. // create a topic branch based on the first commit
  391. createBranch(firstInMaster, "refs/heads/topic");
  392. checkoutBranch("refs/heads/topic");
  393. // we have the old content again
  394. checkFile(FILE1, "1", "2", "3");
  395. // add a line (non-conflicting)
  396. writeFileAndCommit(FILE1, "add a line to file1 in topic", "1", "2",
  397. "3", "4topic");
  398. // change first line (conflicting)
  399. writeFileAndCommit(FILE1,
  400. "change file1 in topic\n\nThis is conflicting", "1topic", "2",
  401. "3", "4topic");
  402. // change second line (not conflicting)
  403. writeFileAndCommit(FILE1, "change file1 in topic again", "1topic",
  404. "2topic", "3", "4topic");
  405. RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
  406. assertEquals(Status.STOPPED, res.getStatus());
  407. git.add().addFilepattern(FILE1).call();
  408. File trashFile = writeTrashFile(FILE1, "Some local change");
  409. res = git.rebase().setOperation(Operation.CONTINUE).call();
  410. assertNotNull(res);
  411. assertEquals(Status.STOPPED, res.getStatus());
  412. checkFile(trashFile, "Some local change");
  413. }
  414. @Test
  415. public void testStopOnLastConflictAndContinue() throws Exception {
  416. // create file1 on master
  417. RevCommit firstInMaster = writeFileAndCommit(FILE1, "Add file1", "1",
  418. "2", "3");
  419. // change in master
  420. writeFileAndCommit(FILE1, "change file1 in master", "1master", "2", "3");
  421. checkFile(FILE1, "1master", "2", "3");
  422. // create a topic branch based on the first commit
  423. createBranch(firstInMaster, "refs/heads/topic");
  424. checkoutBranch("refs/heads/topic");
  425. // we have the old content again
  426. checkFile(FILE1, "1", "2", "3");
  427. // add a line (non-conflicting)
  428. writeFileAndCommit(FILE1, "add a line to file1 in topic", "1", "2",
  429. "3", "4topic");
  430. // change first line (conflicting)
  431. writeFileAndCommit(FILE1,
  432. "change file1 in topic\n\nThis is conflicting", "1topic", "2",
  433. "3", "4topic");
  434. RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
  435. assertEquals(Status.STOPPED, res.getStatus());
  436. // merge the file; the second topic commit should go through
  437. writeFileAndAdd(FILE1, "1topic", "2", "3", "4topic");
  438. res = git.rebase().setOperation(Operation.CONTINUE).call();
  439. assertNotNull(res);
  440. assertEquals(Status.OK, res.getStatus());
  441. assertEquals(RepositoryState.SAFE, db.getRepositoryState());
  442. }
  443. @Test
  444. public void testStopOnLastConflictAndSkip() throws Exception {
  445. // create file1 on master
  446. RevCommit firstInMaster = writeFileAndCommit(FILE1, "Add file1", "1",
  447. "2", "3");
  448. // change in master
  449. writeFileAndCommit(FILE1, "change file1 in master", "1master", "2", "3");
  450. checkFile(FILE1, "1master", "2", "3");
  451. // create a topic branch based on the first commit
  452. createBranch(firstInMaster, "refs/heads/topic");
  453. checkoutBranch("refs/heads/topic");
  454. // we have the old content again
  455. checkFile(FILE1, "1", "2", "3");
  456. // add a line (non-conflicting)
  457. writeFileAndCommit(FILE1, "add a line to file1 in topic", "1", "2",
  458. "3", "4topic");
  459. // change first line (conflicting)
  460. writeFileAndCommit(FILE1,
  461. "change file1 in topic\n\nThis is conflicting", "1topic", "2",
  462. "3", "4topic");
  463. RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
  464. assertEquals(Status.STOPPED, res.getStatus());
  465. // merge the file; the second topic commit should go through
  466. writeFileAndAdd(FILE1, "1topic", "2", "3", "4topic");
  467. res = git.rebase().setOperation(Operation.SKIP).call();
  468. assertNotNull(res);
  469. assertEquals(Status.OK, res.getStatus());
  470. assertEquals(RepositoryState.SAFE, db.getRepositoryState());
  471. }
  472. @Test
  473. public void testMergeFirstStopOnLastConflictAndSkip() throws Exception {
  474. // create file1 on master
  475. RevCommit firstInMaster = writeFileAndCommit(FILE1, "Add file1", "1",
  476. "2", "3");
  477. // change in master
  478. writeFileAndCommit(FILE1, "change file1 in master", "1master", "2", "3");
  479. checkFile(FILE1, "1master", "2", "3");
  480. // create a topic branch based on the first commit
  481. createBranch(firstInMaster, "refs/heads/topic");
  482. checkoutBranch("refs/heads/topic");
  483. // we have the old content again
  484. checkFile(FILE1, "1", "2", "3");
  485. // add a line (conflicting)
  486. writeFileAndCommit(FILE1, "add a line to file1 in topic", "1topic",
  487. "2", "3", "4topic");
  488. // change first line (conflicting again)
  489. writeFileAndCommit(FILE1,
  490. "change file1 in topic\n\nThis is conflicting", "1topicagain",
  491. "2", "3", "4topic");
  492. RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
  493. assertEquals(Status.STOPPED, res.getStatus());
  494. writeFileAndAdd(FILE1, "merged");
  495. res = git.rebase().setOperation(Operation.CONTINUE).call();
  496. assertEquals(Status.STOPPED, res.getStatus());
  497. res = git.rebase().setOperation(Operation.SKIP).call();
  498. assertNotNull(res);
  499. assertEquals(Status.OK, res.getStatus());
  500. assertEquals(RepositoryState.SAFE, db.getRepositoryState());
  501. checkFile(FILE1, "merged");
  502. }
  503. @Test
  504. public void testStopOnConflictAndSkipNoConflict() throws Exception {
  505. // create file1 on master
  506. RevCommit firstInMaster = writeFileAndCommit(FILE1, "Add file1", "1",
  507. "2", "3");
  508. // change in master
  509. writeFileAndCommit(FILE1, "change file1 in master", "1master", "2", "3");
  510. checkFile(FILE1, "1master", "2", "3");
  511. // create a topic branch based on the first commit
  512. createBranch(firstInMaster, "refs/heads/topic");
  513. checkoutBranch("refs/heads/topic");
  514. // we have the old content again
  515. checkFile(FILE1, "1", "2", "3");
  516. // add a line (non-conflicting)
  517. writeFileAndCommit(FILE1, "add a line to file1 in topic", "1", "2",
  518. "3", "4topic");
  519. // change first line (conflicting)
  520. writeFileAndCommit(FILE1,
  521. "change file1 in topic\n\nThis is conflicting", "1topic", "2",
  522. "3", "4topic");
  523. // change third line (not conflicting)
  524. writeFileAndCommit(FILE1, "change file1 in topic again", "1topic", "2",
  525. "3topic", "4topic");
  526. RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
  527. assertEquals(Status.STOPPED, res.getStatus());
  528. res = git.rebase().setOperation(Operation.SKIP).call();
  529. checkFile(FILE1, "1master", "2", "3topic", "4topic");
  530. assertEquals(Status.OK, res.getStatus());
  531. }
  532. @Test
  533. public void testStopOnConflictAndSkipWithConflict() throws Exception {
  534. // create file1 on master
  535. RevCommit firstInMaster = writeFileAndCommit(FILE1, "Add file1", "1",
  536. "2", "3", "4");
  537. // change in master
  538. writeFileAndCommit(FILE1, "change file1 in master", "1master", "2",
  539. "3master", "4");
  540. checkFile(FILE1, "1master", "2", "3master", "4");
  541. // create a topic branch based on the first commit
  542. createBranch(firstInMaster, "refs/heads/topic");
  543. checkoutBranch("refs/heads/topic");
  544. // we have the old content again
  545. checkFile(FILE1, "1", "2", "3", "4");
  546. // add a line (non-conflicting)
  547. writeFileAndCommit(FILE1, "add a line to file1 in topic", "1", "2",
  548. "3", "4", "5topic");
  549. // change first line (conflicting)
  550. writeFileAndCommit(FILE1,
  551. "change file1 in topic\n\nThis is conflicting", "1topic", "2",
  552. "3", "4", "5topic");
  553. // change third line (conflicting)
  554. writeFileAndCommit(FILE1, "change file1 in topic again", "1topic", "2",
  555. "3topic", "4", "5topic");
  556. RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
  557. assertEquals(Status.STOPPED, res.getStatus());
  558. res = git.rebase().setOperation(Operation.SKIP).call();
  559. // TODO is this correct? It is what the command line returns
  560. checkFile(FILE1,
  561. "1master\n2\n<<<<<<< OURS\n3master\n=======\n3topic\n>>>>>>> THEIRS\n4\n5topic");
  562. assertEquals(Status.STOPPED, res.getStatus());
  563. }
  564. @Test
  565. public void testStopOnConflictCommitAndContinue() throws Exception {
  566. // create file1 on master
  567. RevCommit firstInMaster = writeFileAndCommit(FILE1, "Add file1", "1",
  568. "2", "3");
  569. // change in master
  570. writeFileAndCommit(FILE1, "change file1 in master", "1master", "2", "3");
  571. checkFile(FILE1, "1master", "2", "3");
  572. // create a topic branch based on the first commit
  573. createBranch(firstInMaster, "refs/heads/topic");
  574. checkoutBranch("refs/heads/topic");
  575. // we have the old content again
  576. checkFile(FILE1, "1", "2", "3");
  577. // add a line (non-conflicting)
  578. writeFileAndCommit(FILE1, "add a line to file1 in topic", "1", "2",
  579. "3", "4topic");
  580. // change first line (conflicting)
  581. writeFileAndCommit(FILE1,
  582. "change file1 in topic\n\nThis is conflicting", "1topic", "2",
  583. "3", "4topic");
  584. // change second line (not conflicting)
  585. writeFileAndCommit(FILE1, "change file1 in topic again", "1topic", "2",
  586. "3topic", "4topic");
  587. RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
  588. assertEquals(Status.STOPPED, res.getStatus());
  589. // continue should throw a meaningful exception
  590. try {
  591. res = git.rebase().setOperation(Operation.CONTINUE).call();
  592. fail("Expected Exception not thrown");
  593. } catch (UnmergedPathsException e) {
  594. // expected
  595. }
  596. // merge the file; the second topic commit should go through
  597. writeFileAndCommit(FILE1, "A different commit message", "1topic", "2",
  598. "3", "4topic");
  599. res = git.rebase().setOperation(Operation.CONTINUE).call();
  600. assertNotNull(res);
  601. assertEquals(Status.OK, res.getStatus());
  602. assertEquals(RepositoryState.SAFE, db.getRepositoryState());
  603. ObjectId headId = db.resolve(Constants.HEAD);
  604. RevWalk rw = new RevWalk(db);
  605. RevCommit rc = rw.parseCommit(headId);
  606. RevCommit parent = rw.parseCommit(rc.getParent(0));
  607. assertEquals("A different commit message", parent.getFullMessage());
  608. }
  609. private RevCommit writeFileAndCommit(String fileName, String commitMessage,
  610. String... lines) throws Exception {
  611. StringBuilder sb = new StringBuilder();
  612. for (String line : lines) {
  613. sb.append(line);
  614. sb.append('\n');
  615. }
  616. writeTrashFile(fileName, sb.toString());
  617. git.add().addFilepattern(fileName).call();
  618. return git.commit().setMessage(commitMessage).call();
  619. }
  620. private void writeFileAndAdd(String fileName, String... lines)
  621. throws Exception {
  622. StringBuilder sb = new StringBuilder();
  623. for (String line : lines) {
  624. sb.append(line);
  625. sb.append('\n');
  626. }
  627. writeTrashFile(fileName, sb.toString());
  628. git.add().addFilepattern(fileName).call();
  629. }
  630. private void checkFile(String fileName, String... lines) throws Exception {
  631. File file = new File(db.getWorkTree(), fileName);
  632. StringBuilder sb = new StringBuilder();
  633. for (String line : lines) {
  634. sb.append(line);
  635. sb.append('\n');
  636. }
  637. checkFile(file, sb.toString());
  638. }
  639. @Test
  640. public void testStopOnConflictFileCreationAndDeletion() throws Exception {
  641. // create file1 on master
  642. writeTrashFile(FILE1, "Hello World");
  643. git.add().addFilepattern(FILE1).call();
  644. // create file2 on master
  645. File file2 = writeTrashFile("file2", "Hello World 2");
  646. git.add().addFilepattern("file2").call();
  647. // create file3 on master
  648. File file3 = writeTrashFile("file3", "Hello World 3");
  649. git.add().addFilepattern("file3").call();
  650. RevCommit firstInMaster = git.commit()
  651. .setMessage("Add file 1, 2 and 3").call();
  652. // create file4 on master
  653. File file4 = writeTrashFile("file4", "Hello World 4");
  654. git.add().addFilepattern("file4").call();
  655. deleteTrashFile("file2");
  656. git.add().setUpdate(true).addFilepattern("file2").call();
  657. // create folder folder6 on topic (conflicts with file folder6 on topic
  658. // later on)
  659. writeTrashFile("folder6/file1", "Hello World folder6");
  660. git.add().addFilepattern("folder6/file1").call();
  661. git.commit().setMessage(
  662. "Add file 4 and folder folder6, delete file2 on master").call();
  663. // create a topic branch based on second commit
  664. createBranch(firstInMaster, "refs/heads/topic");
  665. checkoutBranch("refs/heads/topic");
  666. deleteTrashFile("file3");
  667. git.add().setUpdate(true).addFilepattern("file3").call();
  668. // create file5 on topic
  669. File file5 = writeTrashFile("file5", "Hello World 5");
  670. git.add().addFilepattern("file5").call();
  671. git.commit().setMessage("Delete file3 and add file5 in topic").call();
  672. // create file folder6 on topic (conflicts with folder6 on master)
  673. writeTrashFile("folder6", "Hello World 6");
  674. git.add().addFilepattern("folder6").call();
  675. // create file7 on topic
  676. File file7 = writeTrashFile("file7", "Hello World 7");
  677. git.add().addFilepattern("file7").call();
  678. deleteTrashFile("file5");
  679. git.add().setUpdate(true).addFilepattern("file5").call();
  680. RevCommit conflicting = git.commit().setMessage(
  681. "Delete file5, add file folder6 and file7 in topic").call();
  682. RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
  683. assertEquals(Status.STOPPED, res.getStatus());
  684. assertEquals(conflicting, res.getCurrentCommit());
  685. assertEquals(RepositoryState.REBASING_INTERACTIVE, db
  686. .getRepositoryState());
  687. assertTrue(new File(db.getDirectory(), "rebase-merge").exists());
  688. // the first one should be included, so we should have left two picks in
  689. // the file
  690. assertEquals(0, countPicks());
  691. assertFalse(file2.exists());
  692. assertFalse(file3.exists());
  693. assertTrue(file4.exists());
  694. assertFalse(file5.exists());
  695. assertTrue(file7.exists());
  696. // abort should reset to topic branch
  697. res = git.rebase().setOperation(Operation.ABORT).call();
  698. assertEquals(res.getStatus(), Status.ABORTED);
  699. assertEquals("refs/heads/topic", db.getFullBranch());
  700. RevWalk rw = new RevWalk(db);
  701. assertEquals(conflicting, rw.parseCommit(db.resolve(Constants.HEAD)));
  702. assertEquals(RepositoryState.SAFE, db.getRepositoryState());
  703. // rebase- dir in .git must be deleted
  704. assertFalse(new File(db.getDirectory(), "rebase-merge").exists());
  705. assertTrue(file2.exists());
  706. assertFalse(file3.exists());
  707. assertFalse(file4.exists());
  708. assertFalse(file5.exists());
  709. assertTrue(file7.exists());
  710. }
  711. @Test
  712. public void testAuthorScriptConverter() throws Exception {
  713. // -1 h timezone offset
  714. PersonIdent ident = new PersonIdent("Author name", "a.mail@some.com",
  715. 123456789123L, -60);
  716. String convertedAuthor = git.rebase().toAuthorScript(ident);
  717. String[] lines = convertedAuthor.split("\n");
  718. assertEquals("GIT_AUTHOR_NAME='Author name'", lines[0]);
  719. assertEquals("GIT_AUTHOR_EMAIL='a.mail@some.com'", lines[1]);
  720. assertEquals("GIT_AUTHOR_DATE='123456789 -0100'", lines[2]);
  721. PersonIdent parsedIdent = git.rebase().parseAuthor(
  722. convertedAuthor.getBytes("UTF-8"));
  723. assertEquals(ident.getName(), parsedIdent.getName());
  724. assertEquals(ident.getEmailAddress(), parsedIdent.getEmailAddress());
  725. // this is rounded to the last second
  726. assertEquals(123456789000L, parsedIdent.getWhen().getTime());
  727. assertEquals(ident.getTimeZoneOffset(), parsedIdent.getTimeZoneOffset());
  728. // + 9.5h timezone offset
  729. ident = new PersonIdent("Author name", "a.mail@some.com",
  730. 123456789123L, +570);
  731. convertedAuthor = git.rebase().toAuthorScript(ident);
  732. lines = convertedAuthor.split("\n");
  733. assertEquals("GIT_AUTHOR_NAME='Author name'", lines[0]);
  734. assertEquals("GIT_AUTHOR_EMAIL='a.mail@some.com'", lines[1]);
  735. assertEquals("GIT_AUTHOR_DATE='123456789 +0930'", lines[2]);
  736. parsedIdent = git.rebase().parseAuthor(
  737. convertedAuthor.getBytes("UTF-8"));
  738. assertEquals(ident.getName(), parsedIdent.getName());
  739. assertEquals(ident.getEmailAddress(), parsedIdent.getEmailAddress());
  740. assertEquals(123456789000L, parsedIdent.getWhen().getTime());
  741. assertEquals(ident.getTimeZoneOffset(), parsedIdent.getTimeZoneOffset());
  742. }
  743. @Test
  744. public void testRepositoryStateChecks() throws Exception {
  745. try {
  746. git.rebase().setOperation(Operation.ABORT).call();
  747. fail("Expected Exception not thrown");
  748. } catch (WrongRepositoryStateException e) {
  749. // expected
  750. }
  751. try {
  752. git.rebase().setOperation(Operation.SKIP).call();
  753. fail("Expected Exception not thrown");
  754. } catch (WrongRepositoryStateException e) {
  755. // expected
  756. }
  757. try {
  758. git.rebase().setOperation(Operation.CONTINUE).call();
  759. fail("Expected Exception not thrown");
  760. } catch (WrongRepositoryStateException e) {
  761. // expected
  762. }
  763. }
  764. private int countPicks() throws IOException {
  765. int count = 0;
  766. File todoFile = new File(db.getDirectory(),
  767. "rebase-merge/git-rebase-todo");
  768. BufferedReader br = new BufferedReader(new InputStreamReader(
  769. new FileInputStream(todoFile), "UTF-8"));
  770. try {
  771. String line = br.readLine();
  772. while (line != null) {
  773. String actionToken = line.substring(0, line.indexOf(' '));
  774. Action action = null;
  775. try {
  776. action = Action.parse(actionToken);
  777. } catch (Exception e) {
  778. // ignore
  779. }
  780. if (action != null)
  781. count++;
  782. line = br.readLine();
  783. }
  784. return count;
  785. } finally {
  786. br.close();
  787. }
  788. }
  789. }