Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 6afb51f65112bd80f81dcddc6eac28acce99a56d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
/*******************************************************************************
 * Copyright (C) 2010, 2015 Dariusz Luksza <dariusz@luksza.org> and others.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *******************************************************************************/
package org.eclipse.egit.core.synchronize;

import static org.eclipse.jgit.lib.Constants.HEAD;
import static org.eclipse.jgit.lib.Constants.MASTER;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.mapping.ResourceMapping;
import org.eclipse.core.resources.mapping.ResourceMappingContext;
import org.eclipse.core.resources.mapping.ResourceTraversal;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.egit.core.op.ConnectProviderOperation;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.egit.core.synchronize.dto.GitSynchronizeData;
import org.eclipse.egit.core.synchronize.dto.GitSynchronizeDataSet;
import org.eclipse.egit.core.test.GitTestCase;
import org.eclipse.egit.core.test.TestProject;
import org.eclipse.egit.core.test.TestRepository;
import org.eclipse.egit.core.test.models.SampleModelProvider;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.team.core.variants.IResourceVariant;
import org.eclipse.team.core.variants.IResourceVariantTree;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class GitResourceVariantTreeTest extends GitTestCase {

	private Repository repo;

	private IProject iProject;

	private TestRepository testRepo;

	@Before
	public void createGitRepository() throws Exception {
		iProject = project.project;
		testRepo = new TestRepository(gitDir);
		testRepo.connect(iProject);
		repo = RepositoryMapping.getMapping(iProject).getRepository();
	}

	@After
	public void clearGitResources() throws Exception {
		testRepo.disconnect(iProject);
		testRepo.dispose();
		repo = null;
		super.tearDown();
	}

	/**
	 * roots() method should return list of projects that are associated with
	 * given repository. In this case there is only one project associated with
	 * this repository therefore only one root should be returned.
	 *
	 * @throws Exception
	 */
	@Test
	public void shouldReturnOneRoot() throws Exception {
		// when
		try (Git git = new Git(repo)) {
			git.commit().setAuthor("JUnit", "junit@egit.org")
					.setMessage("Initial commit").call();
		}
		GitSynchronizeData data = new GitSynchronizeData(repo, HEAD, HEAD,
				false);
		GitSynchronizeDataSet dataSet = new GitSynchronizeDataSet(data);

		// given
		GitResourceVariantTree grvt = new GitTestResourceVariantTree(dataSet,
				null, null);

		// then
		assertEquals(1, grvt.roots().length);
		IResource actualProject = grvt.roots()[0];
		assertEquals(this.project.getProject(), actualProject);
	}

	/**
	 * When we have two or more project associated with repository, roots()
	 * method should return list of project. In this case we have two project
	 * associated with particular repository, therefore '2' value is expected.
	 *
	 * @throws Exception
	 */
	@Test
	public void shouldReturnTwoRoots() throws Exception {
		// when
		// create second project
		TestProject secondProject = new TestProject(true, "Project-2");
		try {
			IProject secondIProject = secondProject.project;
			// add connect project with repository
			new ConnectProviderOperation(secondIProject, gitDir).execute(null);
			try (Git git = new Git(repo)) {
				git.commit().setAuthor("JUnit", "junit@egit.org")
						.setMessage("Initial commit").call();
			}
			GitSynchronizeData data = new GitSynchronizeData(repo, HEAD, HEAD,
					false);
			GitSynchronizeDataSet dataSet = new GitSynchronizeDataSet(data);

			// given
			GitResourceVariantTree grvt = new GitTestResourceVariantTree(dataSet,
					null, null);

			// then
			IResource[] roots = grvt.roots();
			// sort in order to be able to assert the project instances
			Arrays.sort(roots, new Comparator<IResource>() {
				@Override
				public int compare(IResource r1, IResource r2) {
					String path1 = r1.getFullPath().toString();
					String path2 = r2.getFullPath().toString();
					return path1.compareTo(path2);
				}
			});
			assertEquals(2, roots.length);
			IResource actualProject = roots[0];
			assertEquals(this.project.project, actualProject);
			IResource actualProject1 = roots[1];
			assertEquals(secondIProject, actualProject1);
		} finally {
			secondProject.dispose();
		}
	}

	/**
	 * Checks that getResourceVariant will not throw NPE for null argument. This
	 * method is called with null argument when local or remote resource does
	 * not exist.
	 *
	 * @throws Exception
	 */
	@Test
	public void shouldReturnNullResourceVariant() throws Exception {
		// when
		try (Git git = new Git(repo)) {
			git.commit().setAuthor("JUnit", "junit@egit.org")
					.setMessage("Initial commit").call();
		}
		GitSynchronizeData data = new GitSynchronizeData(repo, HEAD, MASTER,
				false);
		GitSynchronizeDataSet dataSet = new GitSynchronizeDataSet(data);

		// given
		GitResourceVariantTree grvt = new GitRemoteResourceVariantTree(null,
				dataSet);

		// then
		assertNull(grvt.getResourceVariant(null));
	}

	/**
	 * getResourceVariant() should return null when given resource doesn't exist
	 * in repository.
	 *
	 * @throws Exception
	 */
	@Test
	public void shouldReturnNullResourceVariant2() throws Exception {
		// when
		IPackageFragment iPackage = project.createPackage("org.egit.test");
		IType mainJava = project.createType(iPackage, "Main.java",
				"class Main {}");
		try (Git git = new Git(repo)) {
			git.commit().setAuthor("JUnit", "junit@egit.org")
					.setMessage("Initial commit").call();
		}
		GitSynchronizeData data = new GitSynchronizeData(repo, HEAD, MASTER,
				false);
		GitSynchronizeDataSet dataSet = new GitSynchronizeDataSet(data);
		GitSyncCache cache = GitSyncCache.getAllData(dataSet,
				new NullProgressMonitor());

		// given
		GitResourceVariantTree grvt = new GitRemoteResourceVariantTree(cache,
				dataSet);

		// then
		assertNull(grvt.getResourceVariant(mainJava.getResource()));
	}

	/**
	 * Check if getResourceVariant() does return the same resource that was
	 * committed. Passes only when it is run as a single test, not as a part of
	 * largest test suite
	 *
	 * @throws Exception
	 */
	@Test
	public void shoulReturnSameResourceVariant() throws Exception {
		// when
		String fileName = "Main.java";
		File file = testRepo.createFile(iProject, fileName);
		testRepo.appendContentAndCommit(iProject, file, "class Main {}",
				"initial commit");
		IFile mainJava = testRepo.getIFile(iProject, file);
		GitSynchronizeData data = new GitSynchronizeData(repo, HEAD, MASTER,
				false);
		GitSynchronizeDataSet dataSet = new GitSynchronizeDataSet(data);
		GitSyncCache cache = GitSyncCache.getAllData(dataSet,
				new NullProgressMonitor());

		// given
		GitResourceVariantTree grvt = new GitRemoteResourceVariantTree(cache,
				dataSet);

		// then
		// null variant indicates that resource wasn't changed
		assertNull(grvt.getResourceVariant(mainJava));
	}

	@Test
	public void shouldNotReturnNullOnSameResouceVariant() throws Exception {
		String modifiedFileName = "changingFile."
				+ SampleModelProvider.SAMPLE_FILE_EXTENSION;
		String unchangedFileName = "notChangingFile."
				+ SampleModelProvider.SAMPLE_FILE_EXTENSION;
		String removedFileName = "toBeRemovedFile."
				+ SampleModelProvider.SAMPLE_FILE_EXTENSION;

		File modifiedFile = testRepo.createFile(iProject, modifiedFileName);
		File unchangedFile = testRepo.createFile(iProject, unchangedFileName);
		File removedFile = testRepo.createFile(iProject, removedFileName);

		testRepo.appendFileContent(modifiedFile, "My content is changing");
		testRepo.appendFileContent(unchangedFile, "My content is constant");
		testRepo.appendFileContent(removedFile, "I will be removed");

		IFile iModifiedFile = testRepo.getIFile(iProject, modifiedFile);
		IFile iUnchangedFile = testRepo.getIFile(iProject, unchangedFile);
		IFile iRemovedFile = testRepo.getIFile(iProject, removedFile);

		testRepo.trackAllFiles(iProject);

		RevCommit firstCommit = testRepo.commit("C1");

		testRepo.appendFileContent(modifiedFile, " My content has changed");
		testRepo.track(modifiedFile);
		testRepo.removeFromIndex(removedFile);

		RevCommit secondCommit = testRepo.commit("C2");

		//@formatter:off
		// History (X means has changed)
		//------------------------------------------------------------
		// files					   C1 [HEAD]		  	C2
		// changingFile.sample   	|-----X----------|-------X-------|->
		// notChangingFile.sample	|-----X----------|---------------|->
		// toBeRemovedFile.sample	|-----X----------|-------X-------|->
		//-------------------------------------------------------------
		//@formatter:on

		testRepo.checkoutBranch(firstCommit.getName());

		iProject.refreshLocal(IResource.DEPTH_INFINITE,
				new NullProgressMonitor());

		// Now synchronize the two commits using our logical model provider
		SampleModelProvider provider = new SampleModelProvider();
		// Get the affected resources
		ResourceMapping[] mappings = provider
				.getMappings(iModifiedFile,
						ResourceMappingContext.LOCAL_CONTEXT,
						new NullProgressMonitor());

		Set<IResource> includedResource = collectResources(mappings);
		Set<IResource> expectedIncludedResources = new HashSet<IResource>();
		expectedIncludedResources.add(iModifiedFile);
		expectedIncludedResources.add(iUnchangedFile);
		expectedIncludedResources.add(iRemovedFile);

		assertEquals(expectedIncludedResources, includedResource);

		// Synchronize the data
		final GitSynchronizeData data = new GitSynchronizeData(
				testRepo.getRepository(), firstCommit.getName(),
				secondCommit.getName(), true, includedResource);
		GitSynchronizeDataSet gitSynchDataSet = new GitSynchronizeDataSet(data);
		final GitResourceVariantTreeSubscriber subscriber = new GitResourceVariantTreeSubscriber(
				gitSynchDataSet);
		subscriber.init(new NullProgressMonitor());

		IResourceVariantTree sourceVariantTree = subscriber.getSourceTree();
		assertNotNull(sourceVariantTree);

		IResourceVariantTree remoteVariantTree = subscriber.getRemoteTree();
		assertNotNull(remoteVariantTree);

		// In the use case in which the file has been deleted the source variant is
		// not null whereas the remote variant is null.It seems quite logic.
		// However in the second use case we have the same result, the source variant is
		// not null whereas the remote is null. In both cases the null value does
		// not mean the same thing. In the first case, the null value means that
		// the resource is no longer in the repository and in the second the
		// null value means there is no change between the two versions.
		// Using these values I am not able to distinguish both case.
		// It is in contradiction with test #shouldReturnNullResourceVariant2()
		// and test #shoulReturnSameResourceVariant(). However I haven't found
		// another way to handle this case. Maybe something can be
		// done with ThreeWayDiffEntry.scan(tw) to force including in the cache
		// some entry even if they have not changed. For example,
		// ThreeWayDiffEntry.scan(tw,includedSource) or maybe try preventing the variant
		// tree to return null by walking throught the repository and looking for the file...

		IResourceVariant unchangedSourceVariant = sourceVariantTree
				.getResourceVariant(iUnchangedFile);
		IResourceVariant unchangedRemoteVariant = remoteVariantTree
				.getResourceVariant(iUnchangedFile);

		assertNotNull(unchangedSourceVariant);
		assertNotNull(unchangedRemoteVariant);

		IResourceVariant removedSourceVariant = sourceVariantTree
				.getResourceVariant(iRemovedFile);
		IResourceVariant removedRemoteVariant = remoteVariantTree
				.getResourceVariant(iRemovedFile);

		assertNotNull(removedSourceVariant);
		assertNull(removedRemoteVariant);

		GitSubscriberResourceMappingContext context = new GitSubscriberResourceMappingContext(subscriber, gitSynchDataSet);
		assertFalse(context.hasLocalChange(iUnchangedFile,
				new NullProgressMonitor()));
		assertFalse(context.hasRemoteChange(iUnchangedFile,
				new NullProgressMonitor()));

		assertFalse(context.hasLocalChange(iModifiedFile,
				new NullProgressMonitor()));
		assertTrue(context.hasRemoteChange(iModifiedFile,
				new NullProgressMonitor()));

		assertFalse(context.hasLocalChange(iRemovedFile,
				new NullProgressMonitor()));
		assertTrue(context.hasRemoteChange(iRemovedFile,
				new NullProgressMonitor()));
	}

	private static Set<IResource> collectResources(ResourceMapping[] mappings)
			throws CoreException {
		final Set<IResource> resources = new HashSet<IResource>();
		ResourceMappingContext context = ResourceMappingContext.LOCAL_CONTEXT;
		for (ResourceMapping mapping : mappings) {
			ResourceTraversal[] traversals = mapping.getTraversals(context,
					new NullProgressMonitor());
			for (ResourceTraversal traversal : traversals) {
				resources.addAll(Arrays.asList(traversal.getResources()));
			}
		}
		return resources;
	}

	/**
	 * Create and commit Main.java file in master branch, then create branch
	 * "test" checkout nearly created branch and modify Main.java file.
	 * getResourceVariant() should obtain Main.java file content from "master"
	 * branch. Passes only when it is run as a single test, not as a part of
	 * largest test suite
	 *
	 * @throws Exception
	 */
	@Test
	public void shouldReturnDifferentResourceVariant() throws Exception {
		// when
		String fileName = "Main.java";
		File file = testRepo.createFile(iProject, fileName);
		testRepo.appendContentAndCommit(iProject, file, "class Main {}",
				"initial commit");
		IFile mainJava = testRepo.getIFile(iProject, file);

		testRepo.createAndCheckoutBranch(Constants.R_HEADS + Constants.MASTER,
				Constants.R_HEADS + "test");
		testRepo.appendContentAndCommit(iProject, file, "// test",
				"first commit");
		GitSynchronizeData data = new GitSynchronizeData(repo, HEAD, MASTER,
				true);
		GitSynchronizeDataSet dataSet = new GitSynchronizeDataSet(data);
		GitSyncCache cache = GitSyncCache.getAllData(dataSet,
				new NullProgressMonitor());

		// given
		GitResourceVariantTree grvt = new GitBaseResourceVariantTree(cache,
				dataSet);

		// then
		IResourceVariant actual = grvt.getResourceVariant(mainJava);
		assertNotNull(actual);
		assertEquals(fileName, actual.getName());

		InputStream actualIn = actual.getStorage(new NullProgressMonitor())
				.getContents();
		byte[] actualByte = getBytesAndCloseStream(actualIn);
		InputStream expectedIn = mainJava.getContents();
		byte[] expectedByte = getBytesAndCloseStream(expectedIn);

		// assert arrays not equals
		assertFalse(Arrays.equals(expectedByte, actualByte));
	}

	private byte[] getBytesAndCloseStream(InputStream stream) throws Exception {
		try {
			byte[] actualByte = new byte[stream.available()];
			stream.read(actualByte);
			return actualByte;
		} finally {
			stream.close();
		}
	}
}

Back to the top