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