Просмотр исходного кода

Improve ours/theirs conflict markers for rebase, cherry-pick

On conflicts in rebase or cherry-pick, the conflict markers were like
this:

    <<<<<<< OURS
    a
    =======
    b
    >>>>>>> THEIRS

This is technically correct, but it could be better.

It's especially confusing during a rebase, where the meaning of
OURS/THEIRS is not obvious. The intuition is that "ours" is the commits
that "I" did before the rebase, but it's the other way around because of
the way rebase works. See various bug reports and stackoverflow
discussions.

With this change, in the case of a cherry-pick while on master, the
markers will be like this:

    <<<<<<< master
    a
    =======
    b
    >>>>>>> bad1dea Message of the commit I'm cherry-picking

In the case of a "git rebase master":

    <<<<<<< Upstream, based on master
    a
    =======
    b
    >>>>>>> b161dea Message of a commit I'm rebasing

It's not "master" because that would only be correct for the first
cherry-pick during a rebase, after that, it's master + already
cherry-picked commits.

And in the case of a "git pull --rebase":

    <<<<<<< Upstream, based on branch 'master' of git@example.org:repo
    a
    =======
    b
    >>>>>>> b161dea Message of a commit I'm rebasing

Bug: 336819
Change-Id: I1333a8dd170bb0077f491962013485efb6f2a926
Signed-off-by: Robin Stocker <robin@nibor.org>
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
tags/v2.1.0.201209190230-r
Robin Stocker 11 лет назад
Родитель
Сommit
5854ca091a

+ 26
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java Просмотреть файл

assertEquals(CherryPickStatus.OK, result.getStatus()); assertEquals(CherryPickStatus.OK, result.getStatus());
} }


@Test
public void testCherryPickConflictMarkers() throws Exception {
Git git = new Git(db);
RevCommit sideCommit = prepareCherryPick(git);

CherryPickResult result = git.cherryPick().include(sideCommit.getId())
.call();
assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());

String expected = "<<<<<<< master\na(master)\n=======\na(side)\n>>>>>>> 527460a side\n";
checkFile(new File(db.getWorkTree(), "a"), expected);
}

@Test
public void testCherryPickOurCommitName() throws Exception {
Git git = new Git(db);
RevCommit sideCommit = prepareCherryPick(git);

CherryPickResult result = git.cherryPick().include(sideCommit.getId())
.setOurCommitName("custom name").call();
assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());

String expected = "<<<<<<< custom name\na(master)\n=======\na(side)\n>>>>>>> 527460a side\n";
checkFile(new File(db.getWorkTree(), "a"), expected);
}

private RevCommit prepareCherryPick(final Git git) throws Exception { private RevCommit prepareCherryPick(final Git git) throws Exception {
// create, add and commit file a // create, add and commit file a
writeTrashFile("a", "a"); writeTrashFile("a", "a");

+ 12
- 2
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java Просмотреть файл

import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode; import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
import org.eclipse.jgit.api.MergeResult.MergeStatus; import org.eclipse.jgit.api.MergeResult.MergeStatus;
import org.eclipse.jgit.api.RebaseResult.Status; import org.eclipse.jgit.api.RebaseResult.Status;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.RepositoryState; import org.eclipse.jgit.lib.RepositoryState;
import org.eclipse.jgit.lib.RepositoryTestCase; import org.eclipse.jgit.lib.RepositoryTestCase;


res = target.pull().call(); res = target.pull().call();


String remoteUri = target
.getRepository()
.getConfig()
.getString(ConfigConstants.CONFIG_REMOTE_SECTION, "origin",
ConfigConstants.CONFIG_KEY_URL);

assertFalse(res.getFetchResult().getTrackingRefUpdates().isEmpty()); assertFalse(res.getFetchResult().getTrackingRefUpdates().isEmpty());
assertTrue(res.getRebaseResult().getStatus().equals(Status.STOPPED)); assertTrue(res.getRebaseResult().getStatus().equals(Status.STOPPED));
String result = "<<<<<<< OURS\nSource change\n=======\nTarget change\n>>>>>>> THEIRS\n";
String result = "<<<<<<< Upstream, based on branch 'master' of "
+ remoteUri
+ "\nSource change\n=======\nTarget change\n>>>>>>> 42453fd Target change in local\n";
assertFileContentsEqual(targetFile, result); assertFileContentsEqual(targetFile, result);
assertEquals(RepositoryState.REBASING_INTERACTIVE, target assertEquals(RepositoryState.REBASING_INTERACTIVE, target
.getRepository().getRepositoryState()); .getRepository().getRepositoryState());


assertNull(res.getFetchResult()); assertNull(res.getFetchResult());
assertEquals(Status.STOPPED, res.getRebaseResult().getStatus()); assertEquals(Status.STOPPED, res.getRebaseResult().getStatus());
String result = "<<<<<<< OURS\nMaster change\n=======\nSlave change\n>>>>>>> THEIRS\n";
String result = "<<<<<<< Upstream, based on branch 'master' of local repository\n"
+ "Master change\n=======\nSlave change\n>>>>>>> 4049c9e Source change in based on master\n";
assertFileContentsEqual(targetFile, result); assertFileContentsEqual(targetFile, result);
assertEquals(RepositoryState.REBASING_INTERACTIVE, target assertEquals(RepositoryState.REBASING_INTERACTIVE, target
.getRepository().getRepositoryState()); .getRepository().getRepositoryState());

+ 6
- 3
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java Просмотреть файл

assertEquals(Status.STOPPED, res.getStatus()); assertEquals(Status.STOPPED, res.getStatus());
assertEquals(conflicting, res.getCurrentCommit()); assertEquals(conflicting, res.getCurrentCommit());
checkFile(FILE1, checkFile(FILE1,
"<<<<<<< OURS\n1master\n=======\n1topic\n>>>>>>> THEIRS\n2\n3\ntopic4");
"<<<<<<< Upstream, based on master\n1master\n=======\n1topic",
">>>>>>> e0d1dea change file1 in topic\n2\n3\ntopic4");


assertEquals(RepositoryState.REBASING_INTERACTIVE, db assertEquals(RepositoryState.REBASING_INTERACTIVE, db
.getRepositoryState()); .getRepositoryState());


res = git.rebase().setOperation(Operation.SKIP).call(); res = git.rebase().setOperation(Operation.SKIP).call();
// TODO is this correct? It is what the command line returns // TODO is this correct? It is what the command line returns
checkFile(FILE1,
"1master\n2\n<<<<<<< OURS\n3master\n=======\n3topic\n>>>>>>> THEIRS\n4\n5topic");
checkFile(
FILE1,
"1master\n2\n<<<<<<< Upstream, based on master\n3master\n=======\n3topic",
">>>>>>> 5afc8df change file1 in topic again\n4\n5topic");
assertEquals(Status.STOPPED, res.getStatus()); assertEquals(Status.STOPPED, res.getStatus());
} }



+ 28
- 0
org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java Просмотреть файл

public class CherryPickCommand extends GitCommand<CherryPickResult> { public class CherryPickCommand extends GitCommand<CherryPickResult> {
private List<Ref> commits = new LinkedList<Ref>(); private List<Ref> commits = new LinkedList<Ref>();


private String ourCommitName = null;

/** /**
* @param repo * @param repo
*/ */
RevCommit srcParent = srcCommit.getParent(0); RevCommit srcParent = srcCommit.getParent(0);
revWalk.parseHeaders(srcParent); revWalk.parseHeaders(srcParent);


String ourName = calculateOurName(headRef);
String cherryPickName = srcCommit.getId().abbreviate(7).name()
+ " " + srcCommit.getShortMessage();

ResolveMerger merger = (ResolveMerger) MergeStrategy.RESOLVE ResolveMerger merger = (ResolveMerger) MergeStrategy.RESOLVE
.newMerger(repo); .newMerger(repo);
merger.setWorkingTreeIterator(new FileTreeIterator(repo)); merger.setWorkingTreeIterator(new FileTreeIterator(repo));
merger.setBase(srcParent.getTree()); merger.setBase(srcParent.getTree());
merger.setCommitNames(new String[] { "BASE", ourName,
cherryPickName });
if (merger.merge(headCommit, srcCommit)) { if (merger.merge(headCommit, srcCommit)) {
if (AnyObjectId.equals(headCommit.getTree().getId(), merger if (AnyObjectId.equals(headCommit.getTree().getId(), merger
.getResultTreeId())) .getResultTreeId()))
return include(new ObjectIdRef.Unpeeled(Storage.LOOSE, name, return include(new ObjectIdRef.Unpeeled(Storage.LOOSE, name,
commit.copy())); commit.copy()));
} }

/**
* @param ourCommitName
* the name that should be used in the "OURS" place for conflict
* markers
* @return {@code this}
*/
public CherryPickCommand setOurCommitName(String ourCommitName) {
this.ourCommitName = ourCommitName;
return this;
}

private String calculateOurName(Ref headRef) {
if (ourCommitName != null)
return ourCommitName;

String targetRefName = headRef.getTarget().getName();
String headName = Repository.shortenRefName(targetRefName);
return headName;
}
} }

+ 6
- 4
org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java Просмотреть файл

} }
} }


String upstreamName = "branch \'"
+ Repository.shortenRefName(remoteBranchName) + "\' of "
+ remoteUri;

PullResult result; PullResult result;
if (doRebase) { if (doRebase) {
RebaseCommand rebase = new RebaseCommand(repo); RebaseCommand rebase = new RebaseCommand(repo);
RebaseResult rebaseRes = rebase.setUpstream(commitToMerge) RebaseResult rebaseRes = rebase.setUpstream(commitToMerge)
.setUpstreamName(upstreamName)
.setProgressMonitor(monitor).setOperation(Operation.BEGIN) .setProgressMonitor(monitor).setOperation(Operation.BEGIN)
.call(); .call();
result = new PullResult(fetchRes, remote, rebaseRes); result = new PullResult(fetchRes, remote, rebaseRes);
} else { } else {
MergeCommand merge = new MergeCommand(repo); MergeCommand merge = new MergeCommand(repo);
String name = "branch \'"
+ Repository.shortenRefName(remoteBranchName) + "\' of "
+ remoteUri;
merge.include(name, commitToMerge);
merge.include(upstreamName, commitToMerge);
MergeResult mergeRes = merge.call(); MergeResult mergeRes = merge.call();
monitor.update(1); monitor.update(1);
result = new PullResult(fetchRes, remote, mergeRes); result = new PullResult(fetchRes, remote, mergeRes);

+ 45
- 3
org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java Просмотреть файл



private static final String ONTO = "onto"; private static final String ONTO = "onto";


private static final String ONTO_NAME = "onto-name";

private static final String PATCH = "patch"; private static final String PATCH = "patch";


private static final String REBASE_HEAD = "head"; private static final String REBASE_HEAD = "head";


private RevCommit upstreamCommit; private RevCommit upstreamCommit;


private String upstreamCommitName;

private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;


private final RevWalk walk; private final RevWalk walk;
case SKIP: case SKIP:
// fall through // fall through
case CONTINUE: case CONTINUE:
String upstreamCommitName = readFile(rebaseDir, ONTO);
String upstreamCommitId = readFile(rebaseDir, ONTO);
try {
upstreamCommitName = readFile(rebaseDir, ONTO_NAME);
} catch (FileNotFoundException e) {
// Fall back to commit ID if file doesn't exist (e.g. rebase
// was started by C Git)
upstreamCommitName = upstreamCommitId;
}
this.upstreamCommit = walk.parseCommit(repo this.upstreamCommit = walk.parseCommit(repo
.resolve(upstreamCommitName));
.resolve(upstreamCommitId));
break; break;
case BEGIN: case BEGIN:
RebaseResult res = initFilesAndRewind(); RebaseResult res = initFilesAndRewind();
// TODO if the content of this commit is already merged // TODO if the content of this commit is already merged
// here we should skip this step in order to avoid // here we should skip this step in order to avoid
// confusing pseudo-changed // confusing pseudo-changed
String ourCommitName = getOurCommitName();
CherryPickResult cherryPickResult = new Git(repo) CherryPickResult cherryPickResult = new Git(repo)
.cherryPick().include(commitToPick).call();
.cherryPick().include(commitToPick)
.setOurCommitName(ourCommitName).call();
switch (cherryPickResult.getStatus()) { switch (cherryPickResult.getStatus()) {
case FAILED: case FAILED:
if (operation == Operation.BEGIN) if (operation == Operation.BEGIN)
} }
} }


private String getOurCommitName() {
// If onto is different from upstream, this should say "onto", but
// RebaseCommand doesn't support a different "onto" at the moment.
String ourCommitName = "Upstream, based on "
+ Repository.shortenRefName(upstreamCommitName);
return ourCommitName;
}

private void updateHead(String headName, RevCommit newHead) private void updateHead(String headName, RevCommit newHead)
throws IOException { throws IOException {
// point the previous head (if any) to the new commit // point the previous head (if any) to the new commit
createFile(rebaseDir, REBASE_HEAD, headId.name()); createFile(rebaseDir, REBASE_HEAD, headId.name());
createFile(rebaseDir, HEAD_NAME, headName); createFile(rebaseDir, HEAD_NAME, headName);
createFile(rebaseDir, ONTO, upstreamCommit.name()); createFile(rebaseDir, ONTO, upstreamCommit.name());
createFile(rebaseDir, ONTO_NAME, upstreamCommitName);
createFile(rebaseDir, INTERACTIVE, ""); createFile(rebaseDir, INTERACTIVE, "");
BufferedWriter fw = new BufferedWriter(new OutputStreamWriter( BufferedWriter fw = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(new File(rebaseDir, GIT_REBASE_TODO)), new FileOutputStream(new File(rebaseDir, GIT_REBASE_TODO)),
*/ */
public RebaseCommand setUpstream(RevCommit upstream) { public RebaseCommand setUpstream(RevCommit upstream) {
this.upstreamCommit = upstream; this.upstreamCommit = upstream;
this.upstreamCommitName = upstream.name();
return this; return this;
} }


public RebaseCommand setUpstream(AnyObjectId upstream) { public RebaseCommand setUpstream(AnyObjectId upstream) {
try { try {
this.upstreamCommit = walk.parseCommit(upstream); this.upstreamCommit = walk.parseCommit(upstream);
this.upstreamCommitName = upstream.name();
} catch (IOException e) { } catch (IOException e) {
throw new JGitInternalException(MessageFormat.format( throw new JGitInternalException(MessageFormat.format(
JGitText.get().couldNotReadObjectWhileParsingCommit, JGitText.get().couldNotReadObjectWhileParsingCommit,
throw new RefNotFoundException(MessageFormat.format(JGitText throw new RefNotFoundException(MessageFormat.format(JGitText
.get().refNotResolved, upstream)); .get().refNotResolved, upstream));
upstreamCommit = walk.parseCommit(repo.resolve(upstream)); upstreamCommit = walk.parseCommit(repo.resolve(upstream));
upstreamCommitName = upstream;
return this; return this;
} catch (IOException ioe) { } catch (IOException ioe) {
throw new JGitInternalException(ioe.getMessage(), ioe); throw new JGitInternalException(ioe.getMessage(), ioe);
} }
} }


/**
* Optionally override the name of the upstream. If this is used, it has to
* come after any {@link #setUpstream} call.
*
* @param upstreamName
* the name which will be used to refer to upstream in conflicts
* @return {@code this}
*/
public RebaseCommand setUpstreamName(String upstreamName) {
if (upstreamCommit == null) {
throw new IllegalStateException(
"setUpstreamName must be called after setUpstream.");
}
this.upstreamCommitName = upstreamName;
return this;
}

/** /**
* @param operation * @param operation
* the operation to perform * the operation to perform

Загрузка…
Отмена
Сохранить