1
2
3
4
5
6
7
8
9
10 package org.eclipse.jgit.attributes.merge;
11
12 import static org.junit.Assert.assertEquals;
13 import static org.junit.Assert.assertFalse;
14 import static org.junit.Assert.assertNotNull;
15 import static org.junit.Assert.assertNull;
16 import static org.junit.Assert.assertTrue;
17
18 import java.io.BufferedInputStream;
19 import java.io.File;
20 import java.io.FileInputStream;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.nio.file.Files;
24 import java.util.function.Consumer;
25
26 import org.eclipse.jgit.api.Git;
27 import org.eclipse.jgit.api.MergeResult;
28 import org.eclipse.jgit.api.MergeResult.MergeStatus;
29 import org.eclipse.jgit.api.errors.CheckoutConflictException;
30 import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
31 import org.eclipse.jgit.api.errors.GitAPIException;
32 import org.eclipse.jgit.api.errors.InvalidMergeHeadsException;
33 import org.eclipse.jgit.api.errors.NoFilepatternException;
34 import org.eclipse.jgit.api.errors.NoHeadException;
35 import org.eclipse.jgit.api.errors.NoMessageException;
36 import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
37 import org.eclipse.jgit.attributes.Attribute;
38 import org.eclipse.jgit.attributes.Attributes;
39 import org.eclipse.jgit.errors.NoWorkTreeException;
40 import org.eclipse.jgit.junit.RepositoryTestCase;
41 import org.eclipse.jgit.revwalk.RevCommit;
42 import org.eclipse.jgit.treewalk.FileTreeIterator;
43 import org.eclipse.jgit.treewalk.TreeWalk;
44 import org.eclipse.jgit.treewalk.filter.PathFilter;
45 import org.junit.Ignore;
46 import org.junit.Test;
47
48 public class MergeGitAttributeTest extends RepositoryTestCase {
49
50 private static final String REFS_HEADS_RIGHT = "refs/heads/right";
51
52 private static final String REFS_HEADS_MASTER = "refs/heads/master";
53
54 private static final String REFS_HEADS_LEFT = "refs/heads/left";
55
56 private static final String DISABLE_CHECK_BRANCH = "refs/heads/disabled_checked";
57
58 private static final String ENABLE_CHECKED_BRANCH = "refs/heads/enabled_checked";
59
60 private static final String ENABLED_CHECKED_GIF = "enabled_checked.gif";
61
62 public Git createRepositoryBinaryConflict(Consumer<Git> initialCommit,
63 Consumer<Git> leftCommit, Consumer<Git> rightCommit)
64 throws NoFilepatternException, GitAPIException, NoWorkTreeException,
65 IOException {
66
67 Git git = new Git(db);
68
69
70 initialCommit.accept(git);
71 git.add().addFilepattern(".").call();
72 RevCommit firstCommit = git.commit().setAll(true)
73 .setMessage("initial commit adding git attribute file").call();
74
75
76 createBranch(firstCommit, REFS_HEADS_LEFT);
77 checkoutBranch(REFS_HEADS_LEFT);
78 leftCommit.accept(git);
79 git.add().addFilepattern(".").call();
80 git.commit().setMessage("Left").call();
81
82
83 checkoutBranch(REFS_HEADS_MASTER);
84 createBranch(firstCommit, REFS_HEADS_RIGHT);
85 checkoutBranch(REFS_HEADS_RIGHT);
86 rightCommit.accept(git);
87 git.add().addFilepattern(".").call();
88 git.commit().setMessage("Right").call();
89
90 checkoutBranch(REFS_HEADS_LEFT);
91 return git;
92
93 }
94
95 @Test
96 public void mergeTextualFile_NoAttr() throws NoWorkTreeException,
97 NoFilepatternException, GitAPIException, IOException {
98 try (Git git = createRepositoryBinaryConflict(g -> {
99 try {
100 writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n");
101 } catch (IOException e) {
102 e.printStackTrace();
103 }
104 }, g -> {
105 try {
106 writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n");
107 } catch (IOException e) {
108 e.printStackTrace();
109 }
110 }, g -> {
111 try {
112 writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n");
113 } catch (IOException e) {
114 e.printStackTrace();
115 }
116 })) {
117 checkoutBranch(REFS_HEADS_LEFT);
118
119
120 MergeResult mergeResult = git.merge()
121 .include(git.getRepository().resolve(REFS_HEADS_RIGHT))
122 .call();
123 assertEquals(MergeStatus.MERGED, mergeResult.getMergeStatus());
124
125 assertNull(mergeResult.getConflicts());
126
127
128 String result = read(
129 writeTrashFile("res.cat", "A\n" + "E\n" + "C\n" + "F\n"));
130 assertEquals(result, read(git.getRepository().getWorkTree().toPath()
131 .resolve("main.cat").toFile()));
132 }
133 }
134
135 @Test
136 public void mergeTextualFile_UnsetMerge_Conflict()
137 throws NoWorkTreeException, NoFilepatternException, GitAPIException,
138 IOException {
139 try (Git git = createRepositoryBinaryConflict(g -> {
140 try {
141 writeTrashFile(".gitattributes", "*.cat -merge");
142 writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n");
143 } catch (IOException e) {
144 e.printStackTrace();
145 }
146 }, g -> {
147 try {
148 writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n");
149 } catch (IOException e) {
150 e.printStackTrace();
151 }
152 }, g -> {
153 try {
154 writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n");
155 } catch (IOException e) {
156 e.printStackTrace();
157 }
158 })) {
159
160 assertAddMergeAttributeUnset(REFS_HEADS_LEFT, "main.cat");
161 assertAddMergeAttributeUnset(REFS_HEADS_RIGHT, "main.cat");
162
163 checkoutBranch(REFS_HEADS_LEFT);
164
165
166 String catContent = read(git.getRepository().getWorkTree().toPath()
167 .resolve("main.cat").toFile());
168
169 MergeResult mergeResult = git.merge()
170 .include(git.getRepository().resolve(REFS_HEADS_RIGHT))
171 .call();
172 assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
173
174
175 assertEquals(catContent, read(git.getRepository().getWorkTree()
176 .toPath().resolve("main.cat").toFile()));
177 }
178 }
179
180 @Test
181 public void mergeTextualFile_UnsetMerge_NoConflict()
182 throws NoWorkTreeException, NoFilepatternException, GitAPIException,
183 IOException {
184 try (Git git = createRepositoryBinaryConflict(g -> {
185 try {
186 writeTrashFile(".gitattributes", "*.txt -merge");
187 writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n");
188 } catch (IOException e) {
189 e.printStackTrace();
190 }
191 }, g -> {
192 try {
193 writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n");
194 } catch (IOException e) {
195 e.printStackTrace();
196 }
197 }, g -> {
198 try {
199 writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n");
200 } catch (IOException e) {
201 e.printStackTrace();
202 }
203 })) {
204
205 assertAddMergeAttributeUndefined(REFS_HEADS_LEFT, "main.cat");
206 assertAddMergeAttributeUndefined(REFS_HEADS_RIGHT, "main.cat");
207
208 checkoutBranch(REFS_HEADS_LEFT);
209
210
211 MergeResult mergeResult = git.merge()
212 .include(git.getRepository().resolve(REFS_HEADS_RIGHT))
213 .call();
214 assertEquals(MergeStatus.MERGED, mergeResult.getMergeStatus());
215
216
217 String result = read(
218 writeTrashFile("res.cat", "A\n" + "E\n" + "C\n" + "F\n"));
219 assertEquals(result, read(git.getRepository().getWorkTree()
220 .toPath().resolve("main.cat").toFile()));
221 }
222 }
223
224 @Test
225 public void mergeTextualFile_SetBinaryMerge_Conflict()
226 throws NoWorkTreeException, NoFilepatternException, GitAPIException,
227 IOException {
228 try (Git git = createRepositoryBinaryConflict(g -> {
229 try {
230 writeTrashFile(".gitattributes", "*.cat merge=binary");
231 writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n");
232 } catch (IOException e) {
233 e.printStackTrace();
234 }
235 }, g -> {
236 try {
237 writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n");
238 } catch (IOException e) {
239 e.printStackTrace();
240 }
241 }, g -> {
242 try {
243 writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n");
244 } catch (IOException e) {
245 e.printStackTrace();
246 }
247 })) {
248
249 assertAddMergeAttributeCustom(REFS_HEADS_LEFT, "main.cat",
250 "binary");
251 assertAddMergeAttributeCustom(REFS_HEADS_RIGHT, "main.cat",
252 "binary");
253
254 checkoutBranch(REFS_HEADS_LEFT);
255
256
257 String catContent = read(git.getRepository().getWorkTree().toPath()
258 .resolve("main.cat").toFile());
259
260 MergeResult mergeResult = git.merge()
261 .include(git.getRepository().resolve(REFS_HEADS_RIGHT))
262 .call();
263 assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
264
265
266 assertEquals(catContent, read(git.getRepository().getWorkTree()
267 .toPath().resolve("main.cat").toFile()));
268 }
269 }
270
271
272
273
274
275 @Test
276 @Ignore
277 public void mergeBinaryFile_NoAttr_Conflict() throws IllegalStateException,
278 IOException, NoHeadException, ConcurrentRefUpdateException,
279 CheckoutConflictException, InvalidMergeHeadsException,
280 WrongRepositoryStateException, NoMessageException, GitAPIException {
281
282 RevCommit disableCheckedCommit;
283
284 try (Git git = new Git(db)) {
285
286 write(new File(db.getWorkTree(), ".gitattributes"), "");
287 git.add().addFilepattern(".gitattributes").call();
288 RevCommit firstCommit = git.commit()
289 .setMessage("initial commit adding git attribute file")
290 .call();
291
292
293 createBranch(firstCommit, ENABLE_CHECKED_BRANCH);
294 checkoutBranch(ENABLE_CHECKED_BRANCH);
295 copy(ENABLED_CHECKED_GIF, ENABLED_CHECKED_GIF, "");
296 git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
297 git.commit().setMessage("enabled_checked commit").call();
298
299
300 checkoutBranch(REFS_HEADS_MASTER);
301 createBranch(firstCommit, DISABLE_CHECK_BRANCH);
302 checkoutBranch(DISABLE_CHECK_BRANCH);
303 copy("disabled_checked.gif", ENABLED_CHECKED_GIF, "");
304 git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
305 disableCheckedCommit = git.commit()
306 .setMessage("disabled_checked commit").call();
307
308
309 assertAddMergeAttributeUndefined(ENABLE_CHECKED_BRANCH,
310 ENABLED_CHECKED_GIF);
311 assertAddMergeAttributeUndefined(DISABLE_CHECK_BRANCH,
312 ENABLED_CHECKED_GIF);
313
314 checkoutBranch(ENABLE_CHECKED_BRANCH);
315
316 MergeResult mergeResult = git.merge().include(disableCheckedCommit)
317 .call();
318 assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
319
320
321 try (FileInputStream mergeResultFile = new FileInputStream(
322 db.getWorkTree().toPath().resolve(ENABLED_CHECKED_GIF)
323 .toFile())) {
324 assertTrue(contentEquals(
325 getClass().getResourceAsStream(ENABLED_CHECKED_GIF),
326 mergeResultFile));
327 }
328 }
329 }
330
331 @Test
332 public void mergeBinaryFile_UnsetMerge_Conflict()
333 throws IllegalStateException,
334 IOException, NoHeadException, ConcurrentRefUpdateException,
335 CheckoutConflictException, InvalidMergeHeadsException,
336 WrongRepositoryStateException, NoMessageException, GitAPIException {
337
338 RevCommit disableCheckedCommit;
339
340 try (Git git = new Git(db)) {
341
342 write(new File(db.getWorkTree(), ".gitattributes"), "*.gif -merge");
343 git.add().addFilepattern(".gitattributes").call();
344 RevCommit firstCommit = git.commit()
345 .setMessage("initial commit adding git attribute file")
346 .call();
347
348
349 createBranch(firstCommit, ENABLE_CHECKED_BRANCH);
350 checkoutBranch(ENABLE_CHECKED_BRANCH);
351 copy(ENABLED_CHECKED_GIF, ENABLED_CHECKED_GIF, "");
352 git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
353 git.commit().setMessage("enabled_checked commit").call();
354
355
356 checkoutBranch(REFS_HEADS_MASTER);
357 createBranch(firstCommit, DISABLE_CHECK_BRANCH);
358 checkoutBranch(DISABLE_CHECK_BRANCH);
359 copy("disabled_checked.gif", ENABLED_CHECKED_GIF, "");
360 git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
361 disableCheckedCommit = git.commit()
362 .setMessage("disabled_checked commit").call();
363
364
365 assertAddMergeAttributeUnset(ENABLE_CHECKED_BRANCH,
366 ENABLED_CHECKED_GIF);
367 assertAddMergeAttributeUnset(DISABLE_CHECK_BRANCH,
368 ENABLED_CHECKED_GIF);
369
370 checkoutBranch(ENABLE_CHECKED_BRANCH);
371
372 MergeResult mergeResult = git.merge().include(disableCheckedCommit)
373 .call();
374 assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
375
376
377 try (FileInputStream mergeResultFile = new FileInputStream(
378 db.getWorkTree().toPath().resolve(ENABLED_CHECKED_GIF)
379 .toFile())) {
380 assertTrue(contentEquals(
381 getClass().getResourceAsStream(ENABLED_CHECKED_GIF),
382 mergeResultFile));
383 }
384 }
385 }
386
387 @Test
388 public void mergeBinaryFile_SetMerge_Conflict()
389 throws IllegalStateException, IOException, NoHeadException,
390 ConcurrentRefUpdateException, CheckoutConflictException,
391 InvalidMergeHeadsException, WrongRepositoryStateException,
392 NoMessageException, GitAPIException {
393
394 RevCommit disableCheckedCommit;
395
396 try (Git git = new Git(db)) {
397
398 write(new File(db.getWorkTree(), ".gitattributes"), "*.gif merge");
399 git.add().addFilepattern(".gitattributes").call();
400 RevCommit firstCommit = git.commit()
401 .setMessage("initial commit adding git attribute file")
402 .call();
403
404
405 createBranch(firstCommit, ENABLE_CHECKED_BRANCH);
406 checkoutBranch(ENABLE_CHECKED_BRANCH);
407 copy(ENABLED_CHECKED_GIF, ENABLED_CHECKED_GIF, "");
408 git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
409 git.commit().setMessage("enabled_checked commit").call();
410
411
412 checkoutBranch(REFS_HEADS_MASTER);
413 createBranch(firstCommit, DISABLE_CHECK_BRANCH);
414 checkoutBranch(DISABLE_CHECK_BRANCH);
415 copy("disabled_checked.gif", ENABLED_CHECKED_GIF, "");
416 git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
417 disableCheckedCommit = git.commit()
418 .setMessage("disabled_checked commit").call();
419
420
421 assertAddMergeAttributeSet(ENABLE_CHECKED_BRANCH,
422 ENABLED_CHECKED_GIF);
423 assertAddMergeAttributeSet(DISABLE_CHECK_BRANCH,
424 ENABLED_CHECKED_GIF);
425
426 checkoutBranch(ENABLE_CHECKED_BRANCH);
427
428 MergeResult mergeResult = git.merge().include(disableCheckedCommit)
429 .call();
430 assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
431
432
433 try (FileInputStream mergeResultFile = new FileInputStream(
434 db.getWorkTree().toPath().resolve(ENABLED_CHECKED_GIF)
435 .toFile())) {
436 assertFalse(contentEquals(
437 getClass().getResourceAsStream(ENABLED_CHECKED_GIF),
438 mergeResultFile));
439 }
440 }
441 }
442
443
444
445
446 private boolean contentEquals(InputStream input1, InputStream input2)
447 throws IOException {
448 if (input1 == input2) {
449 return true;
450 }
451 if (!(input1 instanceof BufferedInputStream)) {
452 input1 = new BufferedInputStream(input1);
453 }
454 if (!(input2 instanceof BufferedInputStream)) {
455 input2 = new BufferedInputStream(input2);
456 }
457
458 int ch = input1.read();
459 while (-1 != ch) {
460 final int ch2 = input2.read();
461 if (ch != ch2) {
462 return false;
463 }
464 ch = input1.read();
465 }
466
467 final int ch2 = input2.read();
468 return ch2 == -1;
469 }
470
471 private void assertAddMergeAttributeUnset(String branch, String fileName)
472 throws IllegalStateException, IOException {
473 checkoutBranch(branch);
474
475 try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) {
476 treeWaklEnableChecked.addTree(new FileTreeIterator(db));
477 treeWaklEnableChecked.setFilter(PathFilter.create(fileName));
478
479 assertTrue(treeWaklEnableChecked.next());
480 Attributes attributes = treeWaklEnableChecked.getAttributes();
481 Attribute mergeAttribute = attributes.get("merge");
482 assertNotNull(mergeAttribute);
483 assertEquals(Attribute.State.UNSET, mergeAttribute.getState());
484 }
485 }
486
487 private void assertAddMergeAttributeSet(String branch, String fileName)
488 throws IllegalStateException, IOException {
489 checkoutBranch(branch);
490
491 try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) {
492 treeWaklEnableChecked.addTree(new FileTreeIterator(db));
493 treeWaklEnableChecked.setFilter(PathFilter.create(fileName));
494
495 assertTrue(treeWaklEnableChecked.next());
496 Attributes attributes = treeWaklEnableChecked.getAttributes();
497 Attribute mergeAttribute = attributes.get("merge");
498 assertNotNull(mergeAttribute);
499 assertEquals(Attribute.State.SET, mergeAttribute.getState());
500 }
501 }
502
503 private void assertAddMergeAttributeUndefined(String branch,
504 String fileName) throws IllegalStateException, IOException {
505 checkoutBranch(branch);
506
507 try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) {
508 treeWaklEnableChecked.addTree(new FileTreeIterator(db));
509 treeWaklEnableChecked.setFilter(PathFilter.create(fileName));
510
511 assertTrue(treeWaklEnableChecked.next());
512 Attributes attributes = treeWaklEnableChecked.getAttributes();
513 Attribute mergeAttribute = attributes.get("merge");
514 assertNull(mergeAttribute);
515 }
516 }
517
518 private void assertAddMergeAttributeCustom(String branch, String fileName,
519 String value) throws IllegalStateException, IOException {
520 checkoutBranch(branch);
521
522 try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) {
523 treeWaklEnableChecked.addTree(new FileTreeIterator(db));
524 treeWaklEnableChecked.setFilter(PathFilter.create(fileName));
525
526 assertTrue(treeWaklEnableChecked.next());
527 Attributes attributes = treeWaklEnableChecked.getAttributes();
528 Attribute mergeAttribute = attributes.get("merge");
529 assertNotNull(mergeAttribute);
530 assertEquals(Attribute.State.CUSTOM, mergeAttribute.getState());
531 assertEquals(value, mergeAttribute.getValue());
532 }
533 }
534
535 private void copy(String resourcePath, String resourceNewName,
536 String pathInRepo) throws IOException {
537 InputStream input = getClass().getResourceAsStream(resourcePath);
538 Files.copy(input, db.getWorkTree().toPath().resolve(pathInRepo)
539 .resolve(resourceNewName));
540 }
541
542 }