Support pull on detached HEAD

Bug: 485396
Change-Id: I82be09385c9b0bcc0054fea5a9cb9d178a41e278
Signed-off-by: Mickael Istria <mistria@redhat.com>
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java
index b405f6a..6f6b115 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java
@@ -61,6 +61,7 @@
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.RepositoryState;
 import org.eclipse.jgit.lib.StoredConfig;
@@ -148,6 +149,57 @@ public void testPullFastForwardWithBranchInSource() throws Exception {
 	}
 
 	@Test
+	public void testPullFastForwardDetachedHead() throws Exception {
+		Repository repository = source.getRepository();
+		writeToFile(sourceFile, "2nd commit");
+		source.add().addFilepattern("SomeFile.txt").call();
+		source.commit().setMessage("2nd commit").call();
+
+		try (RevWalk revWalk = new RevWalk(repository)) {
+			// git checkout HEAD^
+			String initialBranch = repository.getBranch();
+			Ref initialRef = repository.findRef(Constants.HEAD);
+			RevCommit initialCommit = revWalk
+					.parseCommit(initialRef.getObjectId());
+			assertEquals("this test need linear history", 1,
+					initialCommit.getParentCount());
+			source.checkout().setName(initialCommit.getParent(0).getName())
+					.call();
+			assertFalse("expected detached HEAD",
+					repository.getFullBranch().startsWith(Constants.R_HEADS));
+
+			// change and commit another file
+			File otherFile = new File(sourceFile.getParentFile(),
+					System.currentTimeMillis() + ".tst");
+			writeToFile(otherFile, "other 2nd commit");
+			source.add().addFilepattern(otherFile.getName()).call();
+			RevCommit newCommit = source.commit().setMessage("other 2nd commit")
+					.call();
+
+			// git pull --rebase initialBranch
+			source.pull().setRebase(true).setRemote(".")
+					.setRemoteBranchName(initialBranch)
+					.call();
+
+			assertEquals(RepositoryState.SAFE,
+					source.getRepository().getRepositoryState());
+			Ref head = source.getRepository().findRef(Constants.HEAD);
+			RevCommit headCommit = revWalk.parseCommit(head.getObjectId());
+
+			// HEAD^ == initialCommit, no merge commit
+			assertEquals(1, headCommit.getParentCount());
+			assertEquals(initialCommit, headCommit.getParent(0));
+
+			// both contributions for both commits are available
+			assertFileContentsEqual(sourceFile, "2nd commit");
+			assertFileContentsEqual(otherFile, "other 2nd commit");
+			// HEAD has same message as rebased commit
+			assertEquals(newCommit.getShortMessage(),
+					headCommit.getShortMessage());
+		}
+	}
+
+	@Test
 	public void testPullConflict() throws Exception {
 		PullResult res = target.pull().call();
 		// nothing to update since we don't have different data yet
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
index ae822da..9c5ae43 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
@@ -203,62 +203,63 @@ public PullCommand setRebase(BranchRebaseMode rebaseMode) {
 	@Override
 	public PullResult call() throws GitAPIException,
 			WrongRepositoryStateException, InvalidConfigurationException,
-			DetachedHeadException, InvalidRemoteException, CanceledException,
+			InvalidRemoteException, CanceledException,
 			RefNotFoundException, RefNotAdvertisedException, NoHeadException,
 			org.eclipse.jgit.api.errors.TransportException {
 		checkCallable();
 
 		monitor.beginTask(JGitText.get().pullTaskName, 2);
+		Config repoConfig = repo.getConfig();
 
-		String branchName;
+		String branchName = null;
 		try {
 			String fullBranch = repo.getFullBranch();
-			if (fullBranch == null)
-				throw new NoHeadException(
-						JGitText.get().pullOnRepoWithoutHEADCurrentlyNotSupported);
-			if (!fullBranch.startsWith(Constants.R_HEADS)) {
-				// we can not pull if HEAD is detached and branch is not
-				// specified explicitly
-				throw new DetachedHeadException();
+			if (fullBranch != null
+					&& fullBranch.startsWith(Constants.R_HEADS)) {
+				branchName = fullBranch.substring(Constants.R_HEADS.length());
 			}
-			branchName = fullBranch.substring(Constants.R_HEADS.length());
 		} catch (IOException e) {
 			throw new JGitInternalException(
 					JGitText.get().exceptionCaughtDuringExecutionOfPullCommand,
 					e);
 		}
+		if (remoteBranchName == null && branchName != null) {
+			// get the name of the branch in the remote repository
+			// stored in configuration key branch.<branch name>.merge
+			remoteBranchName = repoConfig.getString(
+					ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
+					ConfigConstants.CONFIG_KEY_MERGE);
+		}
+		if (remoteBranchName == null) {
+			remoteBranchName = branchName;
+		}
+		if (remoteBranchName == null) {
+			throw new NoHeadException(
+					JGitText.get().cannotCheckoutFromUnbornBranch);
+		}
 
 		if (!repo.getRepositoryState().equals(RepositoryState.SAFE))
 			throw new WrongRepositoryStateException(MessageFormat.format(
 					JGitText.get().cannotPullOnARepoWithState, repo
 							.getRepositoryState().name()));
 
-		Config repoConfig = repo.getConfig();
-		if (remote == null) {
+		if (remote == null && branchName != null) {
 			// get the configured remote for the currently checked out branch
 			// stored in configuration key branch.<branch name>.remote
 			remote = repoConfig.getString(
 					ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
 					ConfigConstants.CONFIG_KEY_REMOTE);
 		}
-		if (remote == null)
+		if (remote == null) {
 			// fall back to default remote
 			remote = Constants.DEFAULT_REMOTE_NAME;
-
-		if (remoteBranchName == null)
-			// get the name of the branch in the remote repository
-			// stored in configuration key branch.<branch name>.merge
-			remoteBranchName = repoConfig.getString(
-					ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
-					ConfigConstants.CONFIG_KEY_MERGE);
+		}
 
 		// determines whether rebase should be used after fetching
-		if (pullRebaseMode == null) {
+		if (pullRebaseMode == null && branchName != null) {
 			pullRebaseMode = getRebaseMode(branchName, repoConfig);
 		}
 
-		if (remoteBranchName == null)
-			remoteBranchName = branchName;
 
 		final boolean isRemote = !remote.equals("."); //$NON-NLS-1$
 		String remoteUri;