Model Merge Tool + classes to do model merges with EGit
[EMFCompare2.git] / plugins / org.eclipse.emf.compare.ide.ui.tests.git / src / org / eclipse / emf / compare / ide / ui / tests / egit / fixture / GitTestRepository.java
bloba15deeb7d912e0f75b4a8d02b391e764b64158cd
1 /*******************************************************************************
2 * Copyright (C) 2013, 2015 Obeo and others
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
8 *
9 * Contributors:
10 * Obeo - initial API and implementation
11 * Florian Zoubek - rebase method added
12 *******************************************************************************/
13 package org.eclipse.emf.compare.ide.ui.tests.egit.fixture;
15 import java.io.File;
16 import java.io.FileOutputStream;
17 import java.io.IOException;
18 import java.io.OutputStreamWriter;
19 import java.io.Writer;
20 import java.util.ArrayList;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.LinkedHashSet;
24 import java.util.List;
25 import java.util.Set;
27 import org.eclipse.core.resources.IFile;
28 import org.eclipse.core.resources.IProject;
29 import org.eclipse.core.resources.IResource;
30 import org.eclipse.core.resources.mapping.IModelProviderDescriptor;
31 import org.eclipse.core.resources.mapping.ModelProvider;
32 import org.eclipse.core.resources.mapping.RemoteResourceMappingContext;
33 import org.eclipse.core.resources.mapping.ResourceMapping;
34 import org.eclipse.core.resources.mapping.ResourceMappingContext;
35 import org.eclipse.core.runtime.CoreException;
36 import org.eclipse.core.runtime.IPath;
37 import org.eclipse.core.runtime.NullProgressMonitor;
38 import org.eclipse.core.runtime.Path;
39 import org.eclipse.egit.core.Activator;
40 import org.eclipse.egit.core.op.BranchOperation;
41 import org.eclipse.egit.core.op.ConnectProviderOperation;
42 import org.eclipse.egit.core.op.DisconnectProviderOperation;
43 import org.eclipse.egit.core.op.IgnoreOperation;
44 import org.eclipse.egit.core.op.MergeOperation;
45 import org.eclipse.egit.core.op.RebaseOperation;
46 import org.eclipse.egit.core.op.ResetOperation;
47 import org.eclipse.egit.core.synchronize.GitResourceVariantTreeSubscriber;
48 import org.eclipse.egit.core.synchronize.GitSubscriberMergeContext;
49 import org.eclipse.egit.core.synchronize.GitSubscriberResourceMappingContext;
50 import org.eclipse.egit.core.synchronize.dto.GitSynchronizeData;
51 import org.eclipse.egit.core.synchronize.dto.GitSynchronizeDataSet;
52 import org.eclipse.emf.compare.ide.ui.tests.workspace.TestProject;
53 import org.eclipse.jgit.api.CommitCommand;
54 import org.eclipse.jgit.api.Git;
55 import org.eclipse.jgit.api.ResetCommand.ResetType;
56 import org.eclipse.jgit.api.Status;
57 import org.eclipse.jgit.api.errors.GitAPIException;
58 import org.eclipse.jgit.api.errors.NoFilepatternException;
59 import org.eclipse.jgit.lib.ObjectId;
60 import org.eclipse.jgit.lib.Ref;
61 import org.eclipse.jgit.lib.RefUpdate;
62 import org.eclipse.jgit.lib.Repository;
63 import org.eclipse.jgit.merge.MergeStrategy;
64 import org.eclipse.jgit.revwalk.RevCommit;
65 import org.eclipse.jgit.revwalk.RevWalk;
66 import org.eclipse.jgit.util.FileUtils;
67 import org.eclipse.team.core.subscribers.Subscriber;
68 import org.eclipse.team.core.subscribers.SubscriberScopeManager;
70 /**
71 * This class is largely inspired from org.eclipse.egit.core.test.TestRepository. It has been copied here in
72 * order to be usable from our build without dependencies towards egit.core.tests.
74 @SuppressWarnings({"nls", "restriction" })
75 public class GitTestRepository {
76 private final List<Runnable> disposers;
78 Repository repository;
80 String workdirPrefix;
82 /**
83 * Creates a new test repository.
85 * @param gitDir
86 * The ".git" file we'll use to create a repository.
87 * @throws IOException
88 * Thrown if we cannot write at the given location.
90 public GitTestRepository(File gitDir) throws IOException {
91 repository = Activator.getDefault().getRepositoryCache().lookupRepository(gitDir);
92 repository.create();
94 try {
95 workdirPrefix = repository.getWorkTree().getCanonicalPath();
96 } catch (IOException err) {
97 workdirPrefix = repository.getWorkTree().getAbsolutePath();
99 workdirPrefix = workdirPrefix.replace('\\', '/');
100 if (!workdirPrefix.endsWith("/")) {
101 workdirPrefix += "/";
104 this.disposers = new ArrayList<Runnable>();
108 * Create a file or get an existing one
110 * @param project
111 * instance of project inside with file will be created
112 * @param name
113 * name of file
114 * @return nearly created file
115 * @throws IOException
117 public File createFile(IProject project, String name) throws IOException {
118 String path = project.getLocation().append(name).toOSString();
119 int lastSeparator = path.lastIndexOf(File.separator);
120 FileUtils.mkdirs(new File(path.substring(0, lastSeparator)), true);
122 File file = new File(path);
123 if (!file.exists()) {
124 FileUtils.createNewFile(file);
127 return file;
130 public IFile getIFile(IProject project, File file) throws CoreException {
131 String relativePath = getRepoRelativePath(file.getAbsolutePath());
133 // In case the project is not at the root of the repository
134 // we need to remove the whole path before the project name.
135 int index = relativePath.indexOf(project.getName());
136 if (index >= 0) {
137 relativePath = relativePath.substring(index + project.getName().length());
139 IFile iFile = project.getFile(relativePath);
140 iFile.refreshLocal(0, null);
142 return iFile;
146 * Appends content to end of given file.
148 * @param file
149 * @param content
150 * @throws IOException
152 public void appendFileContent(File file, byte[] content) throws IOException {
153 appendFileContent(file, new String(content, "UTF-8"), true);
157 * Appends content to end of given file.
159 * @param file
160 * @param content
161 * @throws IOException
163 public void appendFileContent(File file, String content) throws IOException {
164 appendFileContent(file, content, true);
168 * Appends content to given file.
170 * @param file
171 * @param content
172 * @param append
173 * if true, then bytes will be written to the end of the file rather than the beginning
174 * @throws IOException
176 public void appendFileContent(File file, byte[] content, boolean append) throws IOException {
177 appendFileContent(file, new String(content, "UTF-8"), append);
181 * Appends content to given file.
183 * @param file
184 * @param content
185 * @param append
186 * if true, then bytes will be written to the end of the file rather than the beginning
187 * @throws IOException
189 public void appendFileContent(File file, String content, boolean append) throws IOException {
190 Writer fw = null;
191 try {
192 fw = new OutputStreamWriter(new FileOutputStream(file, append), "UTF-8");
193 fw.append(content);
194 } finally {
195 if (fw != null) {
196 fw.close();
201 public RevCommit addAllAndCommit(String commitMessage) throws Exception {
202 Git git = null;
203 try {
204 git = new Git(repository);
205 git.add().addFilepattern(".").call();
206 return commit(commitMessage);
207 } finally {
208 git.close();
213 * Adds all changes and amends the latest commit, also changing its message to the given message.
215 * @param message
216 * the amended commit message, must not be null
217 * @return The RevCommit of the amended commit.
218 * @throws Exception
219 * if anything goes wrong.
221 public RevCommit addAllAndAmend(String message) throws Exception {
222 Git git = null;
223 try {
224 git = new Git(repository);
225 git.add().addFilepattern(".").call();
226 return git.commit().setAmend(true).setMessage(message).call();
227 } finally {
228 git.close();
233 * Track, add to index and finally commit the given files.
235 * @param testProject
236 * The project within which this file is located.
237 * @param commitMessage
238 * Message with which to commit this file.
239 * @param files
240 * The files to add and commit.
241 * @return The RevCommit corresponding to this operation.
243 public RevCommit addAndCommit(TestProject testProject, String commitMessage, File... files)
244 throws Exception {
245 addToIndex(testProject, files);
246 return commit(commitMessage);
249 public void ignore(File... files) throws Exception {
250 final Set<IPath> paths = new LinkedHashSet<IPath>();
251 for (File file : files) {
252 paths.add(new Path(file.getPath()));
254 new IgnoreOperation(paths).execute(new NullProgressMonitor());
258 * Adds the given files to the index.
260 * @param testProject
261 * Project that contains these files.
262 * @param files
263 * Files to add to the index.
265 public void addToIndex(TestProject testProject, File... files) throws Exception {
266 for (File file : files) {
267 addToIndex(testProject.getIFile(testProject.getProject(), file));
272 * Removes the given files from the index.
274 * @param testProject
275 * Project that contains these files.
276 * @param files
277 * Files to remove from the index.
279 public void removeFromIndex(TestProject testProject, File... files) throws Exception {
280 for (File file : files) {
281 removeFromIndex(testProject.getIFile(testProject.getProject(), file));
286 * Adds the given resources to the index
288 * @param resources
289 * Resources to add to the index.
291 public void addToIndex(IResource... resources) throws CoreException, IOException, NoFilepatternException,
292 GitAPIException {
293 Git git = null;
294 try {
295 git = new Git(repository);
296 for (IResource resource : resources) {
297 String repoPath = getRepoRelativePath(resource.getLocation().toString());
298 git.add().addFilepattern(repoPath).call();
300 } finally {
301 git.close();
306 * Adds the given resources to the index
308 * @param resources
309 * Resources to add to the index.
311 public void removeFromIndex(IResource... resources) throws CoreException, IOException,
312 NoFilepatternException, GitAPIException {
313 Git git = null;
314 try {
315 git = new Git(repository);
316 for (IResource resource : resources) {
317 String repoPath = getRepoRelativePath(resource.getLocation().toString());
318 git.rm().addFilepattern(repoPath).call();
320 } finally {
321 git.close();
326 * Commits the current index.
328 * @param message
329 * commit message
330 * @return commit object
332 public RevCommit commit(String message) throws Exception {
333 Git git = null;
334 try {
335 git = new Git(repository);
336 CommitCommand commitCommand = git.commit();
337 commitCommand.setAuthor("J. Git", "j.git@egit.org");
338 commitCommand.setCommitter(commitCommand.getAuthor());
339 commitCommand.setMessage(message);
340 return commitCommand.call();
341 } finally {
342 git.close();
347 * Connect a project to this repository.
349 * @param project
350 * The project to connect
352 public void connect(IProject project) throws CoreException {
353 ConnectProviderOperation op = new ConnectProviderOperation(project, repository.getDirectory());
354 op.execute(null);
358 * Creates a new branch.
360 * @param refName
361 * Starting point for the new branch.
362 * @param newRefName
363 * Name of the new branch.
365 public void createBranch(String refName, String newRefName) throws IOException {
366 RefUpdate updateRef;
367 updateRef = repository.updateRef(newRefName);
368 Ref startRef = repository.getRef(refName);
369 ObjectId startAt = repository.resolve(refName);
370 String startBranch;
371 if (startRef != null) {
372 startBranch = refName;
373 } else {
374 startBranch = startAt.name();
376 startBranch = Repository.shortenRefName(startBranch);
377 updateRef.setNewObjectId(startAt);
378 updateRef.setRefLogMessage("branch: Created from " + startBranch, false);
379 updateRef.update();
383 * Resets branch.
385 * @param refName
386 * Full name of the branch.
387 * @param type
388 * Type of the reset.
390 public void reset(String refName, ResetType type) throws CoreException {
391 new ResetOperation(repository, refName, type).execute(null);
395 * Checkouts branch.
397 * @param refName
398 * Full name of the branch.
400 public void checkoutBranch(String refName) throws CoreException {
401 new BranchOperation(repository, refName).execute(null);
405 * Merge the given ref with the current HEAD, using the default (logical) strategy.
407 * @param refName
408 * Name of a commit to merge with the current HEAD.
410 public void mergeLogical(String refName) throws CoreException {
411 new MergeOperation(repository, refName).execute(null);
415 * Merge the given ref with the current HEAD, using the textual "recursive" strategy.
417 * @param refName
418 * Name of a commit to merge with the current HEAD.
420 public void mergeTextual(String refName) throws CoreException {
421 new MergeOperation(repository, refName, MergeStrategy.RECURSIVE.getName()).execute(null);
425 * Rebase the current HEAD on the given ref, using the default (logical) strategy.
427 * @param refName
428 * Name of a commit to rebase the current HEAD on.
430 public void rebaseLogical(String refName) throws CoreException, IOException {
431 new RebaseOperation(repository, repository.getRef(refName)).execute(null);
435 * Appends file content to given file, then track, add to index and finally commit it.
437 * @param project
438 * @param file
439 * @param content
440 * @param commitMessage
441 * @return commit object
442 * @throws Exception
444 public RevCommit appendContentAndCommit(IProject project, File file, byte[] content, String commitMessage)
445 throws Exception {
446 return appendContentAndCommit(project, file, new String(content, "UTF-8"), commitMessage);
450 * Appends file content to given file, then track, add to index and finally commit it.
452 * @param project
453 * @param file
454 * @param content
455 * @param commitMessage
456 * @return commit object
457 * @throws Exception
459 public RevCommit appendContentAndCommit(IProject project, File file, String content, String commitMessage)
460 throws Exception {
461 appendFileContent(file, content);
462 track(file);
463 addToIndex(project, file);
465 return commit(commitMessage);
469 * Adds the given file to the index
471 * @param project
472 * @param file
473 * @throws Exception
475 public void addToIndex(IProject project, File file) throws Exception {
476 IFile iFile = getIFile(project, file);
477 addToIndex(iFile);
481 * Creates a new branch and immediately checkout it.
483 * @param refName
484 * starting point for the new branch
485 * @param newRefName
486 * @throws Exception
488 public void createAndCheckoutBranch(String refName, String newRefName) throws Exception {
489 createBranch(refName, newRefName);
490 checkoutBranch(newRefName);
494 * Removes file from version control
496 * @param file
497 * @throws IOException
499 public void untrack(File file) throws IOException {
500 String repoPath = getRepoRelativePath(new Path(file.getPath()).toString());
501 try {
502 new Git(repository).rm().addFilepattern(repoPath).call();
503 } catch (GitAPIException e) {
504 throw new IOException(e.getMessage());
509 * Disconnects provider from project
511 * @param project
512 * @throws CoreException
514 public void disconnect(IProject project) throws CoreException {
515 Collection<IProject> projects = Collections.singleton(project.getProject());
516 DisconnectProviderOperation disconnect = new DisconnectProviderOperation(projects);
517 disconnect.execute(null);
521 * Adds the given resource to the index
523 * @param resource
524 * @throws CoreException
525 * @throws IOException
526 * @throws GitAPIException
527 * @throws NoFilepatternException
529 public void addToIndex(IResource resource) throws CoreException, IOException, NoFilepatternException,
530 GitAPIException {
531 String repoPath = getRepoRelativePath(resource.getLocation().toString());
532 Git git = new Git(repository);
533 try {
534 git.add().addFilepattern(repoPath).call();
535 } finally {
536 git.close();
541 * Adds file to version control
543 * @param file
544 * @throws IOException
545 * @throws GitAPIException
546 * @throws NoFilepatternException
548 public void track(File file) throws IOException, NoFilepatternException, GitAPIException {
549 String repoPath = getRepoRelativePath(new Path(file.getPath()).toString());
550 Git git = new Git(repository);
551 try {
552 git.add().addFilepattern(repoPath).call();
553 } finally {
554 git.close();
559 * Returns the status of this repository's files as would "git status".
561 * @return
562 * @throws Exception
564 public Status status() throws Exception {
565 Git git = null;
566 try {
567 git = new Git(repository);
568 return git.status().call();
569 } finally {
570 git.close();
575 * Return the commit with the given name if any.
577 * @param revstr
578 * see {@link Repository#resolve(String)}
579 * @return The commit with the given name if any.
580 * @see {@link Repository#resolve(String)}
582 public RevCommit findCommit(String revstr) throws Exception {
583 RevWalk walk = null;
584 try {
585 walk = new RevWalk(repository);
586 return walk.parseCommit(repository.resolve(revstr));
587 } finally {
588 if (walk != null) {
589 walk.close();
595 * Dispose of this wrapper along with its underlying repository.
597 public void dispose() {
598 if (repository != null) {
599 repository.close();
600 repository = null;
602 for (Runnable disposer : disposers) {
603 disposer.run();
605 disposers.clear();
609 * Creates a subscriber capable of providing synchronization information for the current
610 * {@link #repository}.
612 * @param sourceRef
613 * Source reference (i.e. "left" side of a comparison).
614 * @param targetRef
615 * Target reference (i.e. "right" side of the comparison).
616 * @param includeLocal
617 * Whether to use local data as the "source" side.
618 * @return The created subscriber.
620 public Subscriber createSubscriberForResolution(String sourceRef, String targetRef, boolean includeLocal)
621 throws IOException {
622 final GitSynchronizeData data = new GitSynchronizeData(repository, sourceRef, targetRef, includeLocal);
623 final GitSynchronizeDataSet dataSet = new GitSynchronizeDataSet(data);
624 final GitResourceVariantTreeSubscriber subscriber = new GitResourceVariantTreeSubscriber(dataSet);
625 subscriber.init(new NullProgressMonitor());
626 disposers.add(new Runnable() {
627 public void run() {
628 subscriber.dispose();
632 return subscriber;
636 * Simulate a comparison between the two given references and returns back the subscriber that can provide
637 * all computed synchronization information. It will use the local comparison context for retrieving the
638 * resource mappings.
640 * @param sourceRef
641 * Source reference (i.e. "left" side of the comparison).
642 * @param targetRef
643 * Target reference (i.e. "right" side of the comparison).
644 * @param comparedFile
645 * The file we are comparing (that would be the file right-clicked into the workspace).
646 * @param includeLocal
647 * Whether to use local data as the "source" side.
648 * @return The created subscriber.
650 public Subscriber createSubscriberForComparison(String sourceRef, String targetRef, IFile comparedFile,
651 boolean includeLocal) throws IOException {
652 final GitSynchronizeData data = new GitSynchronizeData(repository, sourceRef, targetRef, includeLocal);
653 final GitSynchronizeDataSet dataSet = new GitSynchronizeDataSet(data);
654 final GitResourceVariantTreeSubscriber subscriber = new GitResourceVariantTreeSubscriber(dataSet);
655 subscriber.init(new NullProgressMonitor());
656 final ResourceMapping[] mappings = getResourceMappings(comparedFile,
657 ResourceMappingContext.LOCAL_CONTEXT);
659 final RemoteResourceMappingContext remoteContext = new GitSubscriberResourceMappingContext(
660 subscriber, dataSet);
661 final SubscriberScopeManager manager = new SubscriberScopeManager(subscriber.getName(), mappings,
662 subscriber, remoteContext, true);
663 final GitSubscriberMergeContext context = new GitSubscriberMergeContext(subscriber, manager, dataSet);
664 disposers.add(new Runnable() {
665 public void run() {
666 manager.dispose();
667 context.dispose();
668 subscriber.dispose();
671 return context.getSubscriber();
675 * Simulate a comparison between the two given references and returns back the subscriber that can provide
676 * all computed synchronization information. It will use a remote comparison context for retrieving the
677 * resource mappings.
679 * @param sourceRef
680 * Source reference (i.e. "left" side of the comparison).
681 * @param targetRef
682 * Target reference (i.e. "right" side of the comparison).
683 * @param comparedFile
684 * The file we are comparing (that would be the file right-clicked into the workspace).
685 * @return The created subscriber.
687 public Subscriber createSubscriberForComparisonWithRemoteMappings(String sourceRef, String targetRef,
688 IFile comparedFile) throws IOException {
689 final GitSynchronizeData data = new GitSynchronizeData(repository, sourceRef, targetRef, false);
690 final GitSynchronizeDataSet dataSet = new GitSynchronizeDataSet(data);
691 final GitResourceVariantTreeSubscriber subscriber = new GitResourceVariantTreeSubscriber(dataSet);
692 subscriber.init(new NullProgressMonitor());
694 final RemoteResourceMappingContext remoteContext = new GitSubscriberResourceMappingContext(
695 subscriber, dataSet);
696 final ResourceMapping[] mappings = getResourceMappings(comparedFile, remoteContext);
697 final SubscriberScopeManager manager = new SubscriberScopeManager(subscriber.getName(), mappings,
698 subscriber, remoteContext, true);
699 final GitSubscriberMergeContext context = new GitSubscriberMergeContext(subscriber, manager, dataSet);
700 disposers.add(new Runnable() {
701 public void run() {
702 manager.dispose();
703 context.dispose();
704 subscriber.dispose();
707 return context.getSubscriber();
710 public String getRepoRelativePath(File file) {
711 return getRepoRelativePath(new Path(file.getPath()).toString());
714 private String getRepoRelativePath(String path) {
715 final int pfxLen = workdirPrefix.length();
716 final int pLen = path.length();
717 if (pLen > pfxLen) {
718 return path.substring(pfxLen);
719 } else if (path.length() == pfxLen - 1) {
720 return "";
722 return null;
726 * This will query all model providers for those that are enabled on the given file and list all mappings
727 * available for that file.
729 * @param file
730 * The file for which we need the associated resource mappings.
731 * @param context
732 * The {@link ResourceMappingContext} that will be used for retrieving the mappings.
733 * @return All mappings available for that file.
735 private static ResourceMapping[] getResourceMappings(IFile file, ResourceMappingContext context) {
736 final IModelProviderDescriptor[] modelDescriptors = ModelProvider.getModelProviderDescriptors();
738 final Set<ResourceMapping> mappings = new LinkedHashSet<ResourceMapping>();
739 for (IModelProviderDescriptor candidate : modelDescriptors) {
740 try {
741 final IResource[] resources = candidate.getMatchingResources(new IResource[] {file, });
742 if (resources.length > 0) {
743 // get mappings from model provider if there are matching
744 // resources
745 final ModelProvider model = candidate.getModelProvider();
746 final ResourceMapping[] modelMappings = model.getMappings(file, context, null);
747 for (ResourceMapping mapping : modelMappings) {
748 mappings.add(mapping);
751 } catch (CoreException e) {
752 Activator.logError(e.getMessage(), e);
755 return mappings.toArray(new ResourceMapping[mappings.size()]);