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
10 * Obeo - initial API and implementation
11 * Florian Zoubek - rebase method added
12 *******************************************************************************/
13 package org
.eclipse
.emf
.compare
.ide
.ui
.tests
.egit
.fixture
;
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
;
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
;
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
;
83 * Creates a new test repository.
86 * The ".git" file we'll use to create a repository.
88 * Thrown if we cannot write at the given location.
90 public GitTestRepository(File gitDir
) throws IOException
{
91 repository
= Activator
.getDefault().getRepositoryCache().lookupRepository(gitDir
);
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
111 * instance of project inside with file will be created
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
);
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());
137 relativePath
= relativePath
.substring(index
+ project
.getName().length());
139 IFile iFile
= project
.getFile(relativePath
);
140 iFile
.refreshLocal(0, null);
146 * Appends content to end of given file.
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.
161 * @throws IOException
163 public void appendFileContent(File file
, String content
) throws IOException
{
164 appendFileContent(file
, content
, true);
168 * Appends content to given file.
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.
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
{
192 fw
= new OutputStreamWriter(new FileOutputStream(file
, append
), "UTF-8");
201 public RevCommit
addAllAndCommit(String commitMessage
) throws Exception
{
204 git
= new Git(repository
);
205 git
.add().addFilepattern(".").call();
206 return commit(commitMessage
);
213 * Adds all changes and amends the latest commit, also changing its message to the given message.
216 * the amended commit message, must not be null
217 * @return The RevCommit of the amended commit.
219 * if anything goes wrong.
221 public RevCommit
addAllAndAmend(String message
) throws Exception
{
224 git
= new Git(repository
);
225 git
.add().addFilepattern(".").call();
226 return git
.commit().setAmend(true).setMessage(message
).call();
233 * Track, add to index and finally commit the given files.
236 * The project within which this file is located.
237 * @param commitMessage
238 * Message with which to commit this file.
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
)
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.
261 * Project that contains these 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.
275 * Project that contains these 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
289 * Resources to add to the index.
291 public void addToIndex(IResource
... resources
) throws CoreException
, IOException
, NoFilepatternException
,
295 git
= new Git(repository
);
296 for (IResource resource
: resources
) {
297 String repoPath
= getRepoRelativePath(resource
.getLocation().toString());
298 git
.add().addFilepattern(repoPath
).call();
306 * Adds the given resources to the index
309 * Resources to add to the index.
311 public void removeFromIndex(IResource
... resources
) throws CoreException
, IOException
,
312 NoFilepatternException
, GitAPIException
{
315 git
= new Git(repository
);
316 for (IResource resource
: resources
) {
317 String repoPath
= getRepoRelativePath(resource
.getLocation().toString());
318 git
.rm().addFilepattern(repoPath
).call();
326 * Commits the current index.
330 * @return commit object
332 public RevCommit
commit(String message
) throws Exception
{
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();
347 * Connect a project to this repository.
350 * The project to connect
352 public void connect(IProject project
) throws CoreException
{
353 ConnectProviderOperation op
= new ConnectProviderOperation(project
, repository
.getDirectory());
358 * Creates a new branch.
361 * Starting point for the new branch.
363 * Name of the new branch.
365 public void createBranch(String refName
, String newRefName
) throws IOException
{
367 updateRef
= repository
.updateRef(newRefName
);
368 Ref startRef
= repository
.getRef(refName
);
369 ObjectId startAt
= repository
.resolve(refName
);
371 if (startRef
!= null) {
372 startBranch
= refName
;
374 startBranch
= startAt
.name();
376 startBranch
= Repository
.shortenRefName(startBranch
);
377 updateRef
.setNewObjectId(startAt
);
378 updateRef
.setRefLogMessage("branch: Created from " + startBranch
, false);
386 * Full name of the branch.
390 public void reset(String refName
, ResetType type
) throws CoreException
{
391 new ResetOperation(repository
, refName
, type
).execute(null);
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.
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.
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.
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.
440 * @param commitMessage
441 * @return commit object
444 public RevCommit
appendContentAndCommit(IProject project
, File file
, byte[] content
, String commitMessage
)
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.
455 * @param commitMessage
456 * @return commit object
459 public RevCommit
appendContentAndCommit(IProject project
, File file
, String content
, String commitMessage
)
461 appendFileContent(file
, content
);
463 addToIndex(project
, file
);
465 return commit(commitMessage
);
469 * Adds the given file to the index
475 public void addToIndex(IProject project
, File file
) throws Exception
{
476 IFile iFile
= getIFile(project
, file
);
481 * Creates a new branch and immediately checkout it.
484 * starting point for the new branch
488 public void createAndCheckoutBranch(String refName
, String newRefName
) throws Exception
{
489 createBranch(refName
, newRefName
);
490 checkoutBranch(newRefName
);
494 * Removes file from version control
497 * @throws IOException
499 public void untrack(File file
) throws IOException
{
500 String repoPath
= getRepoRelativePath(new Path(file
.getPath()).toString());
502 new Git(repository
).rm().addFilepattern(repoPath
).call();
503 } catch (GitAPIException e
) {
504 throw new IOException(e
.getMessage());
509 * Disconnects provider from 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
524 * @throws CoreException
525 * @throws IOException
526 * @throws GitAPIException
527 * @throws NoFilepatternException
529 public void addToIndex(IResource resource
) throws CoreException
, IOException
, NoFilepatternException
,
531 String repoPath
= getRepoRelativePath(resource
.getLocation().toString());
532 Git git
= new Git(repository
);
534 git
.add().addFilepattern(repoPath
).call();
541 * Adds file to version control
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
);
552 git
.add().addFilepattern(repoPath
).call();
559 * Returns the status of this repository's files as would "git status".
564 public Status
status() throws Exception
{
567 git
= new Git(repository
);
568 return git
.status().call();
575 * Return the commit with the given name if any.
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
{
585 walk
= new RevWalk(repository
);
586 return walk
.parseCommit(repository
.resolve(revstr
));
595 * Dispose of this wrapper along with its underlying repository.
597 public void dispose() {
598 if (repository
!= null) {
602 for (Runnable disposer
: disposers
) {
609 * Creates a subscriber capable of providing synchronization information for the current
610 * {@link #repository}.
613 * Source reference (i.e. "left" side of a comparison).
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
)
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() {
628 subscriber
.dispose();
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
641 * Source reference (i.e. "left" side of the comparison).
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() {
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
680 * Source reference (i.e. "left" side of the comparison).
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() {
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();
718 return path
.substring(pfxLen
);
719 } else if (path
.length() == pfxLen
- 1) {
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.
730 * The file for which we need the associated resource mappings.
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
) {
741 final IResource
[] resources
= candidate
.getMatchingResources(new IResource
[] {file
, });
742 if (resources
.length
> 0) {
743 // get mappings from model provider if there are matching
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()]);