Browse Source

Add Squash/Fixup support for rebase interactive in RebaseCommand

The rebase command now supports squash and fixup. Both actions are not
allowed as the first step of the rebase.

In JGit, before any rebase step is performed, the next commit is
already cherry-picked. This commit keeps that behaviour. In case of
squash or fixup a soft reset to the parent is perfomed afterwards.

CQ: 7684
Bug: 396510
Change-Id: I3c4190940b4d7f19860e223d647fc78705e57203
Signed-off-by: Tobias Pfeifer <to.pfeifer@web.de>
Signed-off-by: Stefan Lay <stefan.lay@sap.com>
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
tags/v3.2.0.201311130903-m3
Tobias Pfeifer 10 years ago
parent
commit
765896febb

+ 338
- 1
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java View File

@@ -42,9 +42,9 @@
*/
package org.eclipse.jgit.api;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -63,6 +63,7 @@ import org.eclipse.jgit.api.MergeResult.MergeStatus;
import org.eclipse.jgit.api.RebaseCommand.InteractiveHandler;
import org.eclipse.jgit.api.RebaseCommand.Operation;
import org.eclipse.jgit.api.RebaseResult.Status;
import org.eclipse.jgit.api.errors.InvalidRebaseStepException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.RefNotFoundException;
import org.eclipse.jgit.api.errors.UnmergedPathsException;
@@ -83,6 +84,8 @@ import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.RawParseUtils;
import org.junit.Before;
import org.junit.Test;

@@ -1957,6 +1960,340 @@ public class RebaseCommandTest extends RepositoryTestCase {
assertEquals("edited commit message", actualCommitMag);
}

@Test
public void testParseSquashFixupSequenceCount() {
int count = RebaseCommand
.parseSquashFixupSequenceCount("# This is a combination of 3 commits.\n# newline");
assertEquals(3, count);
}

@Test
public void testRebaseInteractiveSingleSquashAndModifyMessage() throws Exception {
// create file1 on master
writeTrashFile(FILE1, FILE1);
git.add().addFilepattern(FILE1).call();
git.commit().setMessage("Add file1\nnew line").call();
assertTrue(new File(db.getWorkTree(), FILE1).exists());

// create file2 on master
writeTrashFile("file2", "file2");
git.add().addFilepattern("file2").call();
git.commit().setMessage("Add file2\nnew line").call();
assertTrue(new File(db.getWorkTree(), "file2").exists());

// update FILE1 on master
writeTrashFile(FILE1, "blah");
git.add().addFilepattern(FILE1).call();
git.commit().setMessage("updated file1 on master\nnew line").call();

writeTrashFile("file2", "more change");
git.add().addFilepattern("file2").call();
git.commit().setMessage("update file2 on master\nnew line").call();

git.rebase().setUpstream("HEAD~3")
.runInteractively(new InteractiveHandler() {

public void prepareSteps(List<RebaseTodoLine> steps) {
steps.get(1).setAction(Action.SQUASH);
}

public String modifyCommitMessage(String commit) {
final File messageSquashFile = new File(db
.getDirectory(), "rebase-merge/message-squash");
final File messageFixupFile = new File(db
.getDirectory(), "rebase-merge/message-fixup");

assertFalse(messageFixupFile.exists());
assertTrue(messageSquashFile.exists());
assertEquals(
"# This is a combination of 2 commits.\n# This is the 2nd commit message:\nupdated file1 on master\nnew line\n# The first commit's message is:\nAdd file2\nnew line",
commit);

try {
byte[] messageSquashBytes = IO
.readFully(messageSquashFile);
int end = RawParseUtils.prevLF(messageSquashBytes,
messageSquashBytes.length);
String messageSquashContent = RawParseUtils.decode(
messageSquashBytes, 0, end + 1);
assertEquals(messageSquashContent, commit);
} catch (Throwable t) {
fail(t.getMessage());
}

return "changed";
}
}).call();

RevWalk walk = new RevWalk(db);
ObjectId headId = db.resolve(Constants.HEAD);
RevCommit headCommit = walk.parseCommit(headId);
assertEquals(headCommit.getFullMessage(),
"update file2 on master\nnew line");

ObjectId head2Id = db.resolve(Constants.HEAD + "^1");
RevCommit head1Commit = walk.parseCommit(head2Id);
assertEquals("changed", head1Commit.getFullMessage());
}

@Test
public void testRebaseInteractiveMultipleSquash() throws Exception {
// create file0 on master
writeTrashFile("file0", "file0");
git.add().addFilepattern("file0").call();
git.commit().setMessage("Add file0\nnew line").call();
assertTrue(new File(db.getWorkTree(), "file0").exists());

// create file1 on master
writeTrashFile(FILE1, FILE1);
git.add().addFilepattern(FILE1).call();
git.commit().setMessage("Add file1\nnew line").call();
assertTrue(new File(db.getWorkTree(), FILE1).exists());

// create file2 on master
writeTrashFile("file2", "file2");
git.add().addFilepattern("file2").call();
git.commit().setMessage("Add file2\nnew line").call();
assertTrue(new File(db.getWorkTree(), "file2").exists());

// update FILE1 on master
writeTrashFile(FILE1, "blah");
git.add().addFilepattern(FILE1).call();
git.commit().setMessage("updated file1 on master\nnew line").call();

writeTrashFile("file2", "more change");
git.add().addFilepattern("file2").call();
git.commit().setMessage("update file2 on master\nnew line").call();

git.rebase().setUpstream("HEAD~4")
.runInteractively(new InteractiveHandler() {

public void prepareSteps(List<RebaseTodoLine> steps) {
steps.get(1).setAction(Action.SQUASH);
steps.get(2).setAction(Action.SQUASH);
}

public String modifyCommitMessage(String commit) {
final File messageSquashFile = new File(db.getDirectory(),
"rebase-merge/message-squash");
final File messageFixupFile = new File(db.getDirectory(),
"rebase-merge/message-fixup");
assertFalse(messageFixupFile.exists());
assertTrue(messageSquashFile.exists());
assertEquals(
"# This is a combination of 3 commits.\n# This is the 3rd commit message:\nupdated file1 on master\nnew line\n# This is the 2nd commit message:\nAdd file2\nnew line\n# The first commit's message is:\nAdd file1\nnew line",
commit);

try {
byte[] messageSquashBytes = IO
.readFully(messageSquashFile);
int end = RawParseUtils.prevLF(messageSquashBytes,
messageSquashBytes.length);
String messageSquashContend = RawParseUtils.decode(
messageSquashBytes, 0, end + 1);
assertEquals(messageSquashContend, commit);
} catch (Throwable t) {
fail(t.getMessage());
}

return "# This is a combination of 3 commits.\n# This is the 3rd commit message:\nupdated file1 on master\nnew line\n# This is the 2nd commit message:\nAdd file2\nnew line\n# The first commit's message is:\nAdd file1\nnew line";
}
}).call();

RevWalk walk = new RevWalk(db);
ObjectId headId = db.resolve(Constants.HEAD);
RevCommit headCommit = walk.parseCommit(headId);
assertEquals(headCommit.getFullMessage(),
"update file2 on master\nnew line");

ObjectId head2Id = db.resolve(Constants.HEAD + "^1");
RevCommit head1Commit = walk.parseCommit(head2Id);
assertEquals(
"updated file1 on master\nnew line\nAdd file2\nnew line\nAdd file1\nnew line",
head1Commit.getFullMessage());
}

@Test
public void testRebaseInteractiveMixedSquashAndFixup() throws Exception {
// create file0 on master
writeTrashFile("file0", "file0");
git.add().addFilepattern("file0").call();
git.commit().setMessage("Add file0\nnew line").call();
assertTrue(new File(db.getWorkTree(), "file0").exists());

// create file1 on master
writeTrashFile(FILE1, FILE1);
git.add().addFilepattern(FILE1).call();
git.commit().setMessage("Add file1\nnew line").call();
assertTrue(new File(db.getWorkTree(), FILE1).exists());

// create file2 on master
writeTrashFile("file2", "file2");
git.add().addFilepattern("file2").call();
git.commit().setMessage("Add file2\nnew line").call();
assertTrue(new File(db.getWorkTree(), "file2").exists());

// update FILE1 on master
writeTrashFile(FILE1, "blah");
git.add().addFilepattern(FILE1).call();
git.commit().setMessage("updated file1 on master\nnew line").call();

writeTrashFile("file2", "more change");
git.add().addFilepattern("file2").call();
git.commit().setMessage("update file2 on master\nnew line").call();

git.rebase().setUpstream("HEAD~4")
.runInteractively(new InteractiveHandler() {

public void prepareSteps(List<RebaseTodoLine> steps) {
steps.get(1).setAction(Action.FIXUP);
steps.get(2).setAction(Action.SQUASH);
}

public String modifyCommitMessage(String commit) {
final File messageSquashFile = new File(db
.getDirectory(), "rebase-merge/message-squash");
final File messageFixupFile = new File(db
.getDirectory(), "rebase-merge/message-fixup");

assertFalse(messageFixupFile.exists());
assertTrue(messageSquashFile.exists());
assertEquals(
"# This is a combination of 3 commits.\n# This is the 3rd commit message:\nupdated file1 on master\nnew line\n# The 2nd commit message will be skipped:\n# Add file2\n# new line\n# The first commit's message is:\nAdd file1\nnew line",
commit);

try {
byte[] messageSquashBytes = IO
.readFully(messageSquashFile);
int end = RawParseUtils.prevLF(messageSquashBytes,
messageSquashBytes.length);
String messageSquashContend = RawParseUtils.decode(
messageSquashBytes, 0, end + 1);
assertEquals(messageSquashContend, commit);
} catch (Throwable t) {
fail(t.getMessage());
}

return "changed";
}
}).call();

RevWalk walk = new RevWalk(db);
ObjectId headId = db.resolve(Constants.HEAD);
RevCommit headCommit = walk.parseCommit(headId);
assertEquals(headCommit.getFullMessage(),
"update file2 on master\nnew line");

ObjectId head2Id = db.resolve(Constants.HEAD + "^1");
RevCommit head1Commit = walk.parseCommit(head2Id);
assertEquals("changed", head1Commit.getFullMessage());
}

@Test
public void testRebaseInteractiveSingleFixup() throws Exception {
// create file1 on master
writeTrashFile(FILE1, FILE1);
git.add().addFilepattern(FILE1).call();
git.commit().setMessage("Add file1\nnew line").call();
assertTrue(new File(db.getWorkTree(), FILE1).exists());

// create file2 on master
writeTrashFile("file2", "file2");
git.add().addFilepattern("file2").call();
git.commit().setMessage("Add file2\nnew line").call();
assertTrue(new File(db.getWorkTree(), "file2").exists());

// update FILE1 on master
writeTrashFile(FILE1, "blah");
git.add().addFilepattern(FILE1).call();
git.commit().setMessage("updated file1 on master\nnew line").call();

writeTrashFile("file2", "more change");
git.add().addFilepattern("file2").call();
git.commit().setMessage("update file2 on master\nnew line").call();

git.rebase().setUpstream("HEAD~3")
.runInteractively(new InteractiveHandler() {

public void prepareSteps(List<RebaseTodoLine> steps) {
steps.get(1).setAction(Action.FIXUP);
}

public String modifyCommitMessage(String commit) {
fail("No callback to modify commit message expected for single fixup");
return commit;
}
}).call();

RevWalk walk = new RevWalk(db);
ObjectId headId = db.resolve(Constants.HEAD);
RevCommit headCommit = walk.parseCommit(headId);
assertEquals("update file2 on master\nnew line",
headCommit.getFullMessage());

ObjectId head1Id = db.resolve(Constants.HEAD + "^1");
RevCommit head1Commit = walk.parseCommit(head1Id);
assertEquals("Add file2\nnew line",
head1Commit.getFullMessage());
}


@Test(expected = InvalidRebaseStepException.class)
public void testRebaseInteractiveFixupFirstCommitShouldFail()
throws Exception {
// create file1 on master
writeTrashFile(FILE1, FILE1);
git.add().addFilepattern(FILE1).call();
git.commit().setMessage("Add file1\nnew line").call();
assertTrue(new File(db.getWorkTree(), FILE1).exists());

// create file2 on master
writeTrashFile("file2", "file2");
git.add().addFilepattern("file2").call();
git.commit().setMessage("Add file2\nnew line").call();
assertTrue(new File(db.getWorkTree(), "file2").exists());

git.rebase().setUpstream("HEAD~1")
.runInteractively(new InteractiveHandler() {

public void prepareSteps(List<RebaseTodoLine> steps) {
steps.get(0).setAction(Action.FIXUP);
}

public String modifyCommitMessage(String commit) {
return commit;
}
}).call();
}

@Test(expected = InvalidRebaseStepException.class)
public void testRebaseInteractiveSquashFirstCommitShouldFail()
throws Exception {
// create file1 on master
writeTrashFile(FILE1, FILE1);
git.add().addFilepattern(FILE1).call();
git.commit().setMessage("Add file1\nnew line").call();
assertTrue(new File(db.getWorkTree(), FILE1).exists());

// create file2 on master
writeTrashFile("file2", "file2");
git.add().addFilepattern("file2").call();
git.commit().setMessage("Add file2\nnew line").call();
assertTrue(new File(db.getWorkTree(), "file2").exists());

git.rebase().setUpstream("HEAD~1")
.runInteractively(new InteractiveHandler() {

public void prepareSteps(List<RebaseTodoLine> steps) {
steps.get(0).setAction(Action.SQUASH);
}

public String modifyCommitMessage(String commit) {
return commit;
}
}).call();
}

private File getTodoFile() {
File todoFile = new File(db.getDirectory(), GIT_REBASE_TODO);
return todoFile;

+ 1
- 0
org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties View File

@@ -78,6 +78,7 @@ cannotReadObject=Cannot read object
cannotReadTree=Cannot read tree {0}
cannotRebaseWithoutCurrentHead=Can not rebase without a current HEAD
cannotResolveLocalTrackingRefForUpdating=Cannot resolve local tracking ref {0} for updating.
cannotSquashFixupWithoutPreviousCommit=Cannot {0} without previous commit.
cannotStoreObjects=cannot store objects
cannotUnloadAModifiedTree=Cannot unload a modified tree.
cannotWorkWithOtherStagesThanZeroRightNow=Cannot work with other stages than zero right now. Won't write corrupt index.

+ 195
- 1
org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java View File

@@ -55,10 +55,14 @@ import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.jgit.api.RebaseResult.Status;
import org.eclipse.jgit.api.ResetCommand.ResetType;
import org.eclipse.jgit.api.errors.CheckoutConflictException;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.InvalidRebaseStepException;
import org.eclipse.jgit.api.errors.InvalidRefNameException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.NoHeadException;
@@ -147,6 +151,10 @@ public class RebaseCommand extends GitCommand<RebaseResult> {

private static final String AMEND = "amend"; //$NON-NLS-1$

private static final String MESSAGE_FIXUP = "message-fixup"; //$NON-NLS-1$

private static final String MESSAGE_SQUASH = "message-squash"; //$NON-NLS-1$

/**
* The available operations
*/
@@ -281,7 +289,9 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
repo.writeRebaseTodoFile(rebaseState.getPath(GIT_REBASE_TODO),
steps, false);
}
for (RebaseTodoLine step : steps) {
checkSteps(steps);
for (int i = 0; i < steps.size(); i++) {
RebaseTodoLine step = steps.get(i);
popSteps(1);
if (Action.COMMENT.equals(step.getAction()))
continue;
@@ -325,6 +335,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
newHead = cherryPickResult.getNewHead();
}
}
boolean isSquash = false;
switch (step.getAction()) {
case PICK:
continue; // continue rebase process on pick command
@@ -340,6 +351,20 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
return stop(commitToPick);
case COMMENT:
break;
case SQUASH:
isSquash = true;
//$FALL-THROUGH$
case FIXUP:
resetSoftToParent();
RebaseTodoLine nextStep = (i >= steps.size() - 1 ? null
: steps.get(i + 1));
File messageFixupFile = rebaseState.getFile(MESSAGE_FIXUP);
File messageSquashFile = rebaseState
.getFile(MESSAGE_SQUASH);
if (isSquash && messageFixupFile.exists())
messageFixupFile.delete();
newHead = doSquashFixup(isSquash, commitToPick,
nextStep, messageFixupFile, messageSquashFile);
}
} finally {
monitor.endTask();
@@ -361,6 +386,175 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
}
}

private void checkSteps(List<RebaseTodoLine> steps)
throws InvalidRebaseStepException, IOException {
if (steps.isEmpty())
return;
if (RebaseTodoLine.Action.SQUASH.equals(steps.get(0).getAction())
|| RebaseTodoLine.Action.FIXUP.equals(steps.get(0).getAction())) {
if (!rebaseState.getFile(DONE).exists()
|| rebaseState.readFile(DONE).trim().length() == 0) {
throw new InvalidRebaseStepException(MessageFormat.format(
JGitText.get().cannotSquashFixupWithoutPreviousCommit,
steps.get(0).getAction().name()));
}
}

}

private RevCommit doSquashFixup(boolean isSquash, RevCommit commitToPick,
RebaseTodoLine nextStep, File messageFixup, File messageSquash)
throws IOException, GitAPIException {

if (!messageSquash.exists()) {
// init squash/fixup sequence
ObjectId headId = repo.resolve(Constants.HEAD);
RevCommit previousCommit = walk.parseCommit(headId);

initializeSquashFixupFile(MESSAGE_SQUASH,
previousCommit.getFullMessage());
if (!isSquash)
initializeSquashFixupFile(MESSAGE_FIXUP,
previousCommit.getFullMessage());
}
String currSquashMessage = rebaseState
.readFile(MESSAGE_SQUASH);

int count = parseSquashFixupSequenceCount(currSquashMessage) + 1;

String content = composeSquashMessage(isSquash,
commitToPick, currSquashMessage, count);
rebaseState.createFile(MESSAGE_SQUASH, content);
if (messageFixup.exists())
rebaseState.createFile(MESSAGE_FIXUP, content);

return squashIntoPrevious(
!messageFixup.exists(),
nextStep);
}

private void resetSoftToParent() throws IOException,
GitAPIException, CheckoutConflictException {
Ref orig_head = repo.getRef(Constants.ORIG_HEAD);
ObjectId orig_headId = orig_head.getObjectId();
try {
// we have already commited the cherry-picked commit.
// what we need is to have changes introduced by this
// commit to be on the index
// resetting is a workaround
Git.wrap(repo).reset().setMode(ResetType.SOFT)
.setRef("HEAD~1").call(); //$NON-NLS-1$
} finally {
// set ORIG_HEAD back to where we started because soft
// reset moved it
repo.writeOrigHead(orig_headId);
}
}

private RevCommit squashIntoPrevious(boolean sequenceContainsSquash,
RebaseTodoLine nextStep)
throws IOException, GitAPIException {
RevCommit newHead;
String commitMessage = rebaseState
.readFile(MESSAGE_SQUASH);

if (nextStep == null
|| ((nextStep.getAction() != Action.FIXUP) && (nextStep
.getAction() != Action.SQUASH))) {
// this is the last step in this sequence
if (sequenceContainsSquash) {
commitMessage = interactiveHandler
.modifyCommitMessage(commitMessage);
}
newHead = new Git(repo).commit()
.setMessage(stripCommentLines(commitMessage))
.setAmend(true).call();
rebaseState.getFile(MESSAGE_SQUASH).delete();
rebaseState.getFile(MESSAGE_FIXUP).delete();

} else {
// Next step is either Squash or Fixup
newHead = new Git(repo).commit()
.setMessage(commitMessage).setAmend(true)
.call();
}
return newHead;
}

private static String stripCommentLines(String commitMessage) {
StringBuilder result = new StringBuilder();
for (String line : commitMessage.split("\n")) { //$NON-NLS-1$
if (!line.trim().startsWith("#")) //$NON-NLS-1$
result.append(line).append("\n"); //$NON-NLS-1$
}
if (!commitMessage.endsWith("\n")) //$NON-NLS-1$
result.deleteCharAt(result.length() - 1);
return result.toString();
}

@SuppressWarnings("nls")
private static String composeSquashMessage(boolean isSquash,
RevCommit commitToPick, String currSquashMessage, int count) {
StringBuilder sb = new StringBuilder();
String ordinal = getOrdinal(count);
sb.setLength(0);
sb.append("# This is a combination of ").append(count)
.append(" commits.\n");
if (isSquash) {
sb.append("# This is the ").append(count).append(ordinal)
.append(" commit message:\n");
sb.append(commitToPick.getFullMessage());
} else {
sb.append("# The ").append(count).append(ordinal)
.append(" commit message will be skipped:\n# ");
sb.append(commitToPick.getFullMessage().replaceAll("([\n\r]+)",
"$1# "));
}
// Add the previous message without header (i.e first line)
sb.append("\n");
sb.append(currSquashMessage.substring(currSquashMessage.indexOf("\n") + 1));
return sb.toString();
}

private static String getOrdinal(int count) {
switch (count % 10) {
case 1:
return "st"; //$NON-NLS-1$
case 2:
return "nd"; //$NON-NLS-1$
case 3:
return "rd"; //$NON-NLS-1$
default:
return "th"; //$NON-NLS-1$
}
}

/**
* Parse the count from squashed commit messages
*
* @param currSquashMessage
* the squashed commit message to be parsed
* @return the count of squashed messages in the given string
*/
static int parseSquashFixupSequenceCount(String currSquashMessage) {
String regex = "This is a combination of (.*) commits"; //$NON-NLS-1$
String firstLine = currSquashMessage.substring(0,
currSquashMessage.indexOf("\n")); //$NON-NLS-1$
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(firstLine);
if (!matcher.find())
throw new IllegalArgumentException();
return Integer.parseInt(matcher.group(1));
}

private void initializeSquashFixupFile(String messageFile,
String fullMessage) throws IOException {
rebaseState
.createFile(
messageFile,
"# This is a combination of 1 commits.\n# The first commit's message is:\n" + fullMessage); //$NON-NLS-1$);
}

private String getOurCommitName() {
// If onto is different from upstream, this should say "onto", but
// RebaseCommand doesn't support a different "onto" at the moment.

+ 60
- 0
org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidRebaseStepException.java View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2013, Stefan Lay <stefan.lay@sap.com> and
* other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v1.0 which accompanies this
* distribution, is reproduced below, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.api.errors;

/**
* Exception thrown if a rebase step is invalid. E.g., a rebase must not start
* with squash or fixup.
*/
public class InvalidRebaseStepException extends GitAPIException {
private static final long serialVersionUID = 1L;
/**
* @param msg
*/
public InvalidRebaseStepException(String msg) {
super(msg);
}

/**
* @param msg
* @param cause
*/
public InvalidRebaseStepException(String msg, Throwable cause) {
super(msg, cause);
}
}

+ 1
- 0
org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java View File

@@ -140,6 +140,7 @@ public class JGitText extends TranslationBundle {
/***/ public String cannotReadTree;
/***/ public String cannotRebaseWithoutCurrentHead;
/***/ public String cannotResolveLocalTrackingRefForUpdating;
/***/ public String cannotSquashFixupWithoutPreviousCommit;
/***/ public String cannotStoreObjects;
/***/ public String cannotUnloadAModifiedTree;
/***/ public String cannotWorkWithOtherStagesThanZeroRightNow;

+ 5
- 1
org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoLine.java View File

@@ -66,7 +66,11 @@ public class RebaseTodoLine {
/** Use commit, but stop for amending */
EDIT("edit", "e"),

// TODO: add SQUASH, FIXUP, etc.
/** Use commit, but meld into previous commit */
SQUASH("squash", "s"),

/** like "squash", but discard this commit's log message */
FIXUP("fixup", "f"),

/**
* A comment in the file. Also blank lines (or lines containing only

Loading…
Cancel
Save