-
-
Notifications
You must be signed in to change notification settings - Fork 401
Expand file tree
/
Copy pathGitClientTest.java
More file actions
3437 lines (3065 loc) · 148 KB
/
Copy pathGitClientTest.java
File metadata and controls
3437 lines (3065 loc) · 148 KB
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
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package org.jenkinsci.plugins.gitclient;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.hamcrest.io.FileMatchers.*;
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.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import hudson.EnvVars;
import hudson.FilePath;
import hudson.model.TaskListener;
import hudson.plugins.git.Branch;
import hudson.plugins.git.GitException;
import hudson.plugins.git.GitObject;
import hudson.plugins.git.IGitAPI;
import hudson.plugins.git.IndexEntry;
import hudson.plugins.git.Revision;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import org.apache.commons.io.FileUtils;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.URIish;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.jvnet.hudson.test.Issue;
/**
* Git Client tests, intended as an eventual replacement for CliGitAPIImplTest,
* JGitAPIImplTest, and JGitApacheAPIImplTest.
*
* @author Mark Waite
*/
@RunWith(Parameterized.class)
public class GitClientTest {
/* Git implementation name, either "git", "jgit", or "jgitapache". */
private final String gitImplName;
/* Git client plugin repository directory. */
private static File srcRepoDir = null;
/* GitClient for plugin development repository. */
private GitClient srcGitClient;
/* commit known to exist in upstream. */
private final ObjectId upstreamCommit = ObjectId.fromString("f75720d5de9d79ab4be2633a21de23b3ccbf8ce3");
private final String upstreamCommitAuthor = "Teubel György";
private final String upstreamCommitEmail = "<tgyurci@freemail.hu>";
private final ObjectId upstreamCommitPredecessor = ObjectId.fromString("867e5f148377fd5a6d96e5aafbdaac132a117a5a");
/* URL of upstream (GitHub) repository. */
private final String upstreamRepoURL = "https://github.com/jenkinsci/git-client-plugin";
/* URL of GitHub test repository with large file support. */
private final String lfsTestRepoURL = "https://github.com/MarkEWaite/jenkins-pipeline-utils";
/* Instance of object under test */
private GitClient gitClient = null;
/* Instance of object of another test class*/
private CliGitAPIImplTest cliGitAPIImplTest = new CliGitAPIImplTest();
/* Capabilities of command line git in current environment */
private final boolean CLI_GIT_HAS_GIT_LFS;
private final boolean CLI_GIT_HAS_GIT_LFS_CONFIGURED;
private final boolean LFS_SUPPORTS_SPARSE_CHECKOUT;
@Rule
public TemporaryFolder tempFolder = new TemporaryFolder();
private File repoRoot = null;
public GitClientTest(final String gitImplName) throws Exception {
this.gitImplName = gitImplName;
this.srcGitClient = Git.with(TaskListener.NULL, new EnvVars())
.in(srcRepoDir)
.using(gitImplName)
.getClient();
CliGitAPIImpl cliGitClient;
if (this.srcGitClient instanceof CliGitAPIImpl impl) {
cliGitClient = impl;
} else {
cliGitClient = (CliGitAPIImpl) Git.with(TaskListener.NULL, new EnvVars())
.in(srcRepoDir)
.using("git")
.getClient();
}
boolean gitLFSExists;
boolean gitSparseCheckoutWithLFS;
try {
// If git-lfs is installed then the version string should look like this:
// git-lfs/1.5.6 (GitHub; linux amd64; go 1.7.4)
String lfsVersionOutput =
cliGitClient.launchCommand("lfs", "version").trim();
gitLFSExists = lfsVersionOutput.startsWith("git-lfs");
gitSparseCheckoutWithLFS =
lfsVersionOutput.matches("git-lfs/[3-9][.].*|git-lfs/2[.]1[0-9].*|git-lfs/2[.][89].*");
// Avoid test failures on ci.jenkins.io agents by calling `git lfs install`
// Intentionally ignores the return value, assumes that failure will throw an exception
// and disable the git LFS tests
cliGitClient.launchCommand("lfs", "install");
} catch (GitException exception) {
// This is expected when git-lfs is not installed.
gitLFSExists = false;
gitSparseCheckoutWithLFS = false;
}
CLI_GIT_HAS_GIT_LFS = gitLFSExists;
boolean gitLFSConfigured;
try {
// If git-lfs is configured then the smudge filter will not be empty
gitLFSConfigured =
cliGitClient.launchCommand("config", "filter.lfs.smudge").contains("git-lfs");
} catch (GitException exception) {
// This is expected when git-lfs is not installed.
gitLFSConfigured = false;
}
CLI_GIT_HAS_GIT_LFS_CONFIGURED = gitLFSConfigured;
LFS_SUPPORTS_SPARSE_CHECKOUT =
CLI_GIT_HAS_GIT_LFS_CONFIGURED && gitSparseCheckoutWithLFS && cliGitClient.isAtLeastVersion(2, 0, 0, 0);
}
@Parameterized.Parameters(name = "{0}")
public static Collection gitObjects() {
List<Object[]> arguments = new ArrayList<>();
String[] gitImplNames = {"git", "jgit", "jgitapache"};
for (String gitImplName : gitImplNames) {
Object[] item = {gitImplName};
arguments.add(item);
}
return arguments;
}
/**
* Mirror the git-client-plugin repo so that the tests have a reasonable and
* repeatable set of commits, tags, and branches.
*/
private static File mirrorParent = null;
@BeforeClass
public static void mirrorUpstreamRepositoryLocally() throws Exception {
File currentDir = new File(".");
CliGitAPIImpl currentDirCliGit = (CliGitAPIImpl) Git.with(TaskListener.NULL, new EnvVars())
.in(currentDir)
.using("git")
.getClient();
boolean currentDirIsShallow = currentDirCliGit.isShallowRepository();
mirrorParent = Files.createTempDirectory("mirror").toFile();
/* Clone mirror into mirrorParent/git-client-plugin.git as a bare repo */
CliGitCommand mirrorParentGitCmd = new CliGitCommand(Git.with(TaskListener.NULL, new EnvVars())
.in(mirrorParent)
.using("git")
.getClient());
if (currentDirIsShallow) {
mirrorParentGitCmd.run(
"clone",
// "--reference", currentDir.getAbsolutePath(), // --reference of shallow repo fails
"--mirror",
"https://github.com/jenkinsci/git-client-plugin");
} else {
mirrorParentGitCmd.run(
"clone",
"--reference",
currentDir.getAbsolutePath(),
"--mirror",
"https://github.com/jenkinsci/git-client-plugin");
}
File mirrorDir = new File(mirrorParent, "git-client-plugin.git");
assertTrue("Git client mirror repo not created at " + mirrorDir.getAbsolutePath(), mirrorDir.exists());
GitClient mirrorClient = Git.with(TaskListener.NULL, new EnvVars())
.in(mirrorDir)
.using("git")
.getClient();
assertThat(mirrorClient.getTagNames("git-client-1.6.3"), contains("git-client-1.6.3"));
/* Clone from bare mirrorParent/git-client-plugin.git to working mirrorParent/git-client-plugin */
mirrorParentGitCmd.run("clone", mirrorDir.getAbsolutePath());
srcRepoDir = new File(mirrorParent, "git-client-plugin");
}
/**
* Tests that need the default branch name can use this variable.
*/
private static String defaultBranchName = "mast" + "er"; // Intentionally separated string
/**
* Determine the global default branch name.
* Command line git is moving towards more inclusive naming.
* Git 2.32.0 honors the configuration variable `init.defaultBranch` and uses it for the name of the initial branch.
* This method reads the global configuration and uses it to set the value of `defaultBranchName`.
*/
@BeforeClass
public static void computeDefaultBranchName() throws Exception {
File configDir = Files.createTempDirectory("readGitConfig").toFile();
CliGitCommand getDefaultBranchNameCmd = new CliGitCommand(Git.with(TaskListener.NULL, new EnvVars())
.in(configDir)
.using("git")
.getClient());
String[] output = getDefaultBranchNameCmd.runWithoutAssert("config", "--get", "init.defaultBranch");
for (String s : output) {
String result = s.trim();
if (result != null && !result.isEmpty()) {
defaultBranchName = result;
}
}
assertTrue("Failed to delete temporary readGitConfig directory", configDir.delete());
}
@BeforeClass
public static void addLocalGitConfigChanges() throws Exception {
File currentDir = new File(".");
CliGitAPIImpl currentDirCliGit = (CliGitAPIImpl) Git.with(TaskListener.NULL, new EnvVars())
.in(currentDir)
.using("git")
.getClient();
CliGitCommand gitCmd = new CliGitCommand(currentDirCliGit);
gitCmd.initializeRepository();
}
@AfterClass
public static void removeLocalGitConfigChanges() throws Exception {
File currentDir = new File(".");
CliGitAPIImpl currentDirCliGit = (CliGitAPIImpl) Git.with(TaskListener.NULL, new EnvVars())
.in(currentDir)
.using("git")
.getClient();
CliGitCommand gitCmd = new CliGitCommand(currentDirCliGit);
gitCmd.removeRepositorySettings();
}
@AfterClass
public static void removeMirrorAndSrcRepos() throws Exception {
try {
FileUtils.deleteDirectory(mirrorParent);
} catch (IOException ioe) {
System.out.println("Ignored cleanup failure on " + mirrorParent);
}
}
@Before
public void setGitClient() throws Exception {
repoRoot = tempFolder.newFolder();
gitClient = Git.with(TaskListener.NULL, new EnvVars())
.in(repoRoot)
.using(gitImplName)
.getClient();
File gitDir = gitClient.withRepository((repo, channel) -> repo.getDirectory());
assertFalse("Already found " + gitDir, gitDir.isDirectory());
gitClient.init_().workspace(repoRoot.getAbsolutePath()).execute();
assertTrue("Missing " + gitDir, gitDir.isDirectory());
gitClient.setRemoteUrl("origin", srcRepoDir.getAbsolutePath());
CliGitCommand gitCmd = new CliGitCommand(gitClient);
gitCmd.initializeRepository("Vojtěch GitClientTest Zweibrücken-Šafařík", "email.from.git.client@example.com");
}
/**
* Allow local git clones to use the file:// protocol by setting
* protocol.file.allow=always on the git command line of the
* GitClient argument that is passed.
* <p>
* Command line git 2.38.1 and patches to earlier versions
* disallow local git submodule cloning with the file:// protocol.
* The change resolves a security issue but that security issue is
* not a threat to these tests.
*/
private void allowFileProtocol(GitClient client) throws Exception {
if (client instanceof CliGitAPIImpl cliGit) {
cliGit.allowFileProtocol();
}
}
private static final String COMMITTED_ONE_TEXT_FILE = "Committed one text file";
private ObjectId commitOneFile() throws Exception {
return commitOneFile(COMMITTED_ONE_TEXT_FILE);
}
private ObjectId commitOneFile(final String commitMessage) throws Exception {
final String content = "A random UUID: %s\n".formatted(UUID.randomUUID());
return commitFile("One-File.txt", content, commitMessage);
}
private ObjectId commitFile(final String path, final String content, final String commitMessage) throws Exception {
createFile(path, content);
gitClient.add(path);
gitClient.commit(commitMessage);
List<ObjectId> headList = gitClient.revList(Constants.HEAD);
assertThat(headList.size(), is(greaterThan(0)));
return headList.get(0);
}
private void createFile(String path, String content) throws Exception {
File aFile = new File(repoRoot, path);
File parentDir = aFile.getParentFile();
if (parentDir != null) {
parentDir.mkdirs();
}
try (PrintWriter writer = new PrintWriter(aFile, StandardCharsets.UTF_8)) {
writer.printf(content);
}
}
private final Random random = new Random();
private String randomName() {
final String[] names = {
"Cinda Bückmaster",
"Miloš Obrenović",
"Vojtěch Šafařík",
"Pfalzgraf Wolfgang von Zweibrücken",
"Johann Friedrich Konrad Carl Eduard Horst Arnold Matthias Prinz von Sachsen-Meiningen Herzog zu Sachsen",
"Al-Mu'tamid",
"Øresund Bridge",
"Caterina œil Perrault",
"Kesha Ríckel",
"Shawnda Bœlter",
"Hans Gǿr",
"Thu Null"
};
return names[random.nextInt(names.length)];
}
private String randomEmail(String name) {
return name.replaceAll(" ", ".") + "@middle.earth";
}
/**
* Changelog was formatted on word boundary prior to
* 72 characters with git client plugin 2.0+ when using CLI git.
* Was not truncated by git client plugin using JGit (And Apache version).
* Rely on caller to truncate first line if desired.
* Matching change will be included in git plugin 4.0.0
* to retain existing truncation behavior.
*/
@Test
@Issue("JENKINS-29977")
public void testChangelogVeryLong() throws Exception {
final String gitMessage =
"""
Uno Dos Tres Cuatro Cinco Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut \
posuere tellus eu efficitur tristique. In iaculis neque in dolor vulputate\
sollicitudin eget a quam. Donec finibus sapien quis lectus euismod facilisis. Integer\
massa purus, scelerisque id iaculis ut, blandit vitae velit. Pellentesque lobortis\
aliquet felis, vel laoreet ipsum tincidunt at. Mauris tellus est, cursus vitae ex\
eget, venenatis auctor eros. Sed sagittis porta odio. Donec ut interdum massa. Aliquam\
sagittis, mi sit amet sollicitudin elementum, velit quam eleifend nisl, in rhoncus\
felis nibh eu nibh. Class aptent taciti sociosqu ad litora torquent per conubia \
nostra, per inceptos himenaeos.
seis
asfasfasfasf
""";
final String content = "A random UUID: %s\n".formatted(UUID.randomUUID());
ObjectId message = commitFile("One-File.txt", content, gitMessage);
ChangelogCommand changelog = gitClient.changelog();
StringWriter changelogStringWriter = new StringWriter();
changelog.includes(message).to(changelogStringWriter).execute();
assertThat(changelogStringWriter.toString(), containsString("Ut posuere"));
assertThat(changelogStringWriter.toString(), containsString("conubia nostra"));
}
@Test
@Issue("JENKINS-39832") // Diagnostics of ChangelogCommand were insufficient
public void testChangelogExceptionMessage() throws Exception {
final ObjectId commitA = commitOneFile();
ChangelogCommand changelog = gitClient.changelog();
StringWriter changelogStringWriter = new StringWriter();
changelog.includes(commitA).to(changelogStringWriter).execute();
assertThat(changelogStringWriter.toString(), containsString(COMMITTED_ONE_TEXT_FILE));
final String missingSHA1 = "ca11ab1edeededacecadebadebeaddeadcedeade";
// Confirm includes exception is as expected
changelog = gitClient.changelog();
changelogStringWriter = new StringWriter();
try {
changelog.includes(missingSHA1).to(changelogStringWriter).execute();
fail("Did not throw expected exception");
} catch (GitException ge) {
// Check that directory and SHA1 are included in exception message
assertThat(ge.getMessage(), containsString(missingSHA1));
assertThat(ge.getMessage(), containsString(" in " + repoRoot.getAbsolutePath()));
}
// Confirm excludes exception is as expected
changelog = gitClient.changelog();
changelogStringWriter = new StringWriter();
try {
changelog.excludes(missingSHA1).to(changelogStringWriter).execute();
fail("Did not throw expected exception");
} catch (GitException ge) {
// Check that directory and SHA1 are included in exception message
assertThat(ge.getMessage(), containsString(missingSHA1));
assertThat(ge.getMessage(), containsString(" in " + repoRoot.getAbsolutePath()));
}
final ObjectId missingObject = ObjectId.fromString(missingSHA1);
// Confirm includes exception is as expected
changelog = gitClient.changelog();
changelogStringWriter = new StringWriter();
try {
changelog.includes(missingObject).to(changelogStringWriter).execute();
fail("Did not throw expected exception");
} catch (GitException ge) {
// Check that directory and SHA1 are included in exception message
assertThat(ge.getMessage(), containsString(missingSHA1));
assertThat(ge.getMessage(), containsString(" in " + repoRoot.getAbsolutePath()));
}
// Confirm excludes exception is as expected
changelog = gitClient.changelog();
changelogStringWriter = new StringWriter();
try {
changelog.excludes(missingObject).to(changelogStringWriter).execute();
fail("Did not throw expected exception");
} catch (GitException ge) {
// Check that directory and SHA1 are included in exception message
assertThat(ge.getMessage(), containsString(missingSHA1));
assertThat(ge.getMessage(), containsString(" in " + repoRoot.getAbsolutePath()));
}
}
@Test
public void testNullChangelogDestinationIncludes() throws Exception {
final ObjectId commitA = commitOneFile();
ChangelogCommand changelog = gitClient.changelog();
changelog.includes(commitA);
assertThrows(IllegalStateException.class, changelog::execute);
}
@Test
public void testNullChangelogDestinationExcludes() throws Exception {
final ObjectId commitA = commitOneFile();
ChangelogCommand changelog = gitClient.changelog();
changelog.excludes(commitA);
assertThrows(IllegalStateException.class, changelog::execute);
}
@Test
@Issue("JENKINS-43198")
public void testCleanSubdirGitignore() throws Exception {
final String filename1 = "this_is/not_ok/more/subdirs/file.txt";
final String filename2 = "this_is_also/not_ok_either/more/subdirs/file.txt";
commitFile(".gitignore", "/this_is/not_ok\n/this_is_also/not_ok_either\n", "set up gitignore");
createFile(filename1, "hi there");
createFile(filename2, "hi there");
assertFileInWorkingDir(gitClient, filename1);
assertFileInWorkingDir(gitClient, filename2);
gitClient.clean();
assertDirNotInWorkingDir(gitClient, "this_is");
assertDirNotInWorkingDir(gitClient, "this_is_also");
}
@Test
@Issue("JENKINS-37794")
public void tagWithSlashes() throws Exception {
commitOneFile();
gitClient.tag("has/a/slash", "This tag has a slash ('/')");
assertThat(gitClient.getTagMessage("has/a/slash"), is("This tag has a slash ('/')"));
assertThat(gitClient.getTagNames(null), hasItem("has/a/slash"));
}
private void assertLogContains(ObjectId commitA, ObjectId commitB, String prefix, String expected)
throws GitException, InterruptedException {
boolean found = false;
StringBuilder builder = new StringBuilder();
for (String revisionMessage : gitClient.showRevision(commitA, commitB)) {
builder.append(revisionMessage);
if (revisionMessage.startsWith(prefix)) {
assertThat(revisionMessage, containsString(expected));
found = true;
}
}
assertTrue("no " + prefix + ", expected: '" + expected + "' in " + builder, found);
}
private void assertAuthor(ObjectId commitA, ObjectId commitB, String name, String email)
throws GitException, InterruptedException {
final String prefix = "author ";
final String expected = prefix + name + " <" + email + ">";
assertLogContains(commitA, commitB, prefix, expected);
}
private void assertCommitter(ObjectId commitA, ObjectId commitB, String name, String email)
throws GitException, InterruptedException {
final String prefix = "committer ";
final String expected = prefix + name + " <" + email + ">";
assertLogContains(commitA, commitB, prefix, expected);
}
@Test
public void testSetAuthor_String_String() throws Exception {
final ObjectId commitA = commitOneFile();
final String name = randomName();
final String email = randomEmail(name);
gitClient.setAuthor(name, email);
final ObjectId commitB = commitOneFile();
assertAuthor(commitA, commitB, name, email);
}
@Test(expected = GitException.class)
public void testCommitNotFoundException() throws GitException, InterruptedException {
/* Search wrong repository for a commit */
assertAuthor(upstreamCommitPredecessor, upstreamCommit, upstreamCommitAuthor, upstreamCommitEmail);
}
@Test
public void testSetAuthor_PersonIdent() throws Exception {
final ObjectId commitA = commitOneFile();
final String name = randomName();
final String email = randomEmail(name);
gitClient.setAuthor(new PersonIdent(name, email));
final ObjectId commitB = commitOneFile();
assertAuthor(commitA, commitB, name, email);
}
@Test
public void testGetWorkTree() {
assertThat(gitClient.getWorkTree(), is(new FilePath(repoRoot)));
}
@Test
public void testSetCommitter_String_String() throws Exception {
final ObjectId commitA = commitOneFile();
final String name = randomName();
final String email = randomEmail(name);
gitClient.setCommitter(name, email);
final ObjectId commitB = commitOneFile();
assertCommitter(commitA, commitB, name, email);
}
@Test
public void testSetCommitter_PersonIdent() throws Exception {
final ObjectId commitA = commitOneFile();
final String name = randomName();
final String email = randomEmail(name);
gitClient.setAuthor(new PersonIdent(name, email));
final ObjectId commitB = commitOneFile();
assertAuthor(commitA, commitB, name, email);
}
@Test
public void testGetRepository() throws Exception {
File expectedRepo = new File(repoRoot, ".git");
assertEquals(expectedRepo, gitClient.getRepository().getDirectory());
}
@Test
public void testInit() throws Exception {
File gitDir = gitClient.withRepository((repo, channel) -> repo.getDirectory());
gitClient.init();
assertTrue("init did not create " + gitDir, gitDir.isDirectory());
}
@Test
public void testInit_Bare() throws Exception {
File repoRootTemp = tempFolder.newFolder();
GitClient gitClientTemp = Git.with(TaskListener.NULL, new EnvVars())
.in(repoRootTemp)
.using(gitImplName)
.getClient();
File tempDir = gitClientTemp.withRepository((repo, channel) -> repo.getDirectory());
assertFalse("Missing", tempDir.isDirectory());
gitClientTemp.init_().workspace(repoRootTemp.getPath()).bare(true).execute();
// Bare git_init contains no working tree file
tempDir = gitClientTemp.withRepository((repo, channel) -> repo.getWorkTree());
assertFalse(".refs not found", tempDir.isFile());
}
@Test
public void testInitFailureWindows() throws Exception {
if (!isWindows()) {
return;
}
String badDirName = "CON:";
File badDir = new File(badDirName);
GitClient badGitClient = Git.with(TaskListener.NULL, new EnvVars())
.in(badDir)
.using(gitImplName)
.getClient();
Class expectedExceptionClass = gitImplName.equals("git") ? GitException.class : JGitInternalException.class;
assertThrows(expectedExceptionClass, () -> badGitClient
.init_()
.bare(random.nextBoolean())
.workspace(badDirName)
.execute());
}
@Test
public void testInitFailureNotWindowsNotSuperUser() throws Exception {
if (isWindows() || (new File("/").canWrite())) { // Windows or running as root
return;
}
String badDirName = "/this/directory/is/not/accessible";
File badDir = new File(badDirName);
GitClient badGitClient = Git.with(TaskListener.NULL, new EnvVars())
.in(badDir)
.using(gitImplName)
.getClient();
Class expectedExceptionClass = gitImplName.equals("git") ? GitException.class : JGitInternalException.class;
assertThrows(expectedExceptionClass, () -> badGitClient
.init_()
.bare(random.nextBoolean())
.workspace(badDirName)
.execute());
}
@Test
public void testAdd() throws Exception {
final ObjectId commitA = commitOneFile();
assertNotNull(commitA);
}
@Test
public void testCommit_String() throws Exception {
final ObjectId commitA = commitOneFile();
final String name = randomName();
final String email = randomEmail(name);
gitClient.setAuthor(new PersonIdent(name, email));
final String expectedCommitMessage = "This is commit B's expected message";
final ObjectId commitB = commitOneFile(expectedCommitMessage);
assertLogContains(commitA, commitB, " ", expectedCommitMessage);
}
@Test
public void testCommit_3args() throws Exception {
final ObjectId commitA = commitOneFile();
final String authorName = randomName();
final String authorEmail = randomEmail(authorName);
gitClient.setAuthor(new PersonIdent(authorName, authorEmail));
final String committerName = randomName();
final String committerEmail = randomEmail(committerName);
gitClient.setCommitter(new PersonIdent(committerName, committerEmail));
final String expectedCommitMessage = "This is commit B's expected message";
final ObjectId commitB = commitOneFile(expectedCommitMessage);
assertLogContains(commitA, commitB, " ", expectedCommitMessage);
assertAuthor(commitA, commitB, authorName, authorEmail);
assertCommitter(commitA, commitB, committerName, committerEmail);
}
@Test
public void testHasGitRepo() throws Exception {
assertTrue("Test repo '" + repoRoot.getAbsolutePath() + "' not initialized", gitClient.hasGitRepo());
StringBuilder fileList = new StringBuilder();
for (File file : srcRepoDir.listFiles()) {
fileList.append(file.getAbsolutePath());
fileList.append(" ");
}
assertTrue(
"Source repo '" + srcRepoDir.getAbsolutePath() + "' not initialized, contains " + fileList,
srcGitClient.hasGitRepo());
File emptyDir = tempFolder.newFolder();
assertTrue(emptyDir.exists());
GitClient emptyClient = Git.with(TaskListener.NULL, new EnvVars())
.in(emptyDir)
.using(gitImplName)
.getClient();
assertFalse("Empty repo '" + emptyDir.getAbsolutePath() + "' initialized", emptyClient.hasGitRepo());
}
@Test
public void testHasGitRepoFalse() throws Exception {
/* Use system temp directory so that no parent directory has a git repository */
Path tempDir = Files.createTempDirectory("git-client-hasGitRepo");
GitClient noRepoClient = Git.with(TaskListener.NULL, new EnvVars())
.in(tempDir.toFile())
.using(gitImplName)
.getClient();
assertFalse("New empty temp dir has a git repo(1)", noRepoClient.hasGitRepo());
assertFalse("New empty temp dir has a git repo(2)", noRepoClient.hasGitRepo(false));
assertFalse("New empty temp dir has a git repo(3)", noRepoClient.hasGitRepo(true));
tempDir.toFile().delete(); // Remove the temporary directory
}
@Issue("JENKINS-38699")
@Test
public void testHasGitRepoNestedDir() throws Exception {
File childDir = tempFolder.newFolder("parentDir", "childDir");
File parentDir = childDir.getParentFile();
GitClient parentDirClient = Git.with(TaskListener.NULL, new EnvVars())
.in(parentDir)
.using(gitImplName)
.getClient();
assertFalse("Unexpected has git repo before init(1)", parentDirClient.hasGitRepo());
assertFalse("Unexpected has git repo before init(2)", parentDirClient.hasGitRepo(true));
assertFalse("Unexpected has git repo before init(3)", parentDirClient.hasGitRepo(false));
parentDirClient.init();
assertTrue("Missing git repo after init(1)", parentDirClient.hasGitRepo());
assertTrue("Missing git repo after init(2)", parentDirClient.hasGitRepo(true));
assertTrue("Missing git repo after init(3)", parentDirClient.hasGitRepo(false));
GitClient childDirClient = Git.with(TaskListener.NULL, new EnvVars())
.in(childDir)
.using(gitImplName)
.getClient();
assertFalse("Unexpected has child git repo before child init(1)", childDirClient.hasGitRepo());
assertFalse("Unexpected has child git repo before child init(2)", childDirClient.hasGitRepo(true));
assertFalse("Unexpected has child git repo before child init(3)", childDirClient.hasGitRepo(false));
File childGitDir = new File(childDir, ".git");
boolean dirCreated = childGitDir.mkdir();
assertTrue("Failed to create empty .git dir in childDir", dirCreated);
if (gitImplName.equals("git")) {
// JENKINS-38699 - if an empty .git directory exists, CLI git searches upwards to perform operations
assertTrue("Missing parent git repo before child init(1)", childDirClient.hasGitRepo());
} else {
// JENKINS-38699 - if an empty .git directory exists, JGit does NOT search upwards to perform operations
assertFalse(
"Unexpected parent git repo detected by JGit before child init(1)", childDirClient.hasGitRepo());
}
assertTrue("Missing parent git repo before child init(2)", childDirClient.hasGitRepo(true));
assertFalse("Unexpected has child repo before child init(3)", childDirClient.hasGitRepo(false));
childDirClient.init();
assertTrue("Missing git repo after child init(1)", childDirClient.hasGitRepo());
assertTrue("Missing git repo after child init(2)", childDirClient.hasGitRepo(true));
assertTrue("Missing git repo after child init(3)", childDirClient.hasGitRepo(false));
}
@Test
public void testIsCommitInRepo() throws Exception {
assertTrue(srcGitClient.isCommitInRepo(upstreamCommit));
assertFalse(gitClient.isCommitInRepo(upstreamCommit));
assertFalse(gitClient.isCommitInRepo(null)); // NPE safety check
// this MAY fail if commit has this exact sha1, but please admit this would be unlucky
assertFalse(gitClient.isCommitInRepo(ObjectId.fromString("1111111111111111111111111111111111111111")));
}
private void assertExceptionMessageContains(GitException ge, String expectedSubstring) {
String actual = ge.getMessage().toLowerCase();
assertTrue(
"Expected '" + expectedSubstring + "' exception message, but was: " + actual,
actual.contains(expectedSubstring));
}
private IGitAPI IGitAPIForTrueBareRepositoryTests() throws Exception {
// provides iGitAPI with bare repository initialization
File repoRootTemp = tempFolder.newFolder();
GitClient gitClientTemp = Git.with(TaskListener.NULL, new EnvVars())
.in(repoRootTemp)
.using(gitImplName)
.getClient();
gitClientTemp
.init_()
.workspace(repoRootTemp.getAbsolutePath())
.bare(true)
.execute();
return (IGitAPI) gitClientTemp;
}
@Test
@Deprecated
public void testIsBareRepositoryBareDot() throws Exception {
IGitAPI gitAPI = IGitAPIForTrueBareRepositoryTests();
assertTrue(". is not a bare repository", gitAPI.isBareRepository("."));
}
@Test
@Deprecated
public void testIsBareRepositoryWorkingDotGit() throws Exception {
gitClient.init_().workspace(repoRoot.getAbsolutePath()).bare(true).execute();
IGitAPI gitAPI = (IGitAPI) gitClient;
FilePath gitClientFilePath = gitClient.getWorkTree();
gitClientFilePath.createTextTempFile("aPre", ".txt", "file contents");
gitClient.add(".");
gitClient.commit("Not-a-bare-repository-dot-git");
assertFalse(".git is a bare repository", gitAPI.isBareRepository(".git"));
}
@Test
@Deprecated
public void testIsBareRepositoryBareDotGit() throws Exception {
IGitAPI gitAPI = IGitAPIForTrueBareRepositoryTests();
/* Bare repository does not have a .git directory. This is
* another no-such-location test but is included here for
* consistency.
*/
try {
/* JGit knows that w.igit() has a workspace, and asks the workspace
* if it is bare. That seems more correct than relying on testing
* a specific file that the repository is bare. JGit behaves better
* than CliGit in this case.
*/
assertTrue("non-existent .git is in a bare repository", gitAPI.isBareRepository(".git"));
/* JGit will not throw an exception - it knows the repo is bare */
/* CliGit throws an exception so should not reach the next assertion */
assertFalse("CliGitAPIImpl did not throw expected exception", gitAPI instanceof CliGitAPIImpl);
} catch (GitException ge) {
/* Only enters this path for CliGit */
assertExceptionMessageContains(ge, "not a git repository");
}
}
@Test
@Deprecated
public void testIsBareRepositoryWorkingNoSuchLocation() throws Exception {
gitClient.init_().workspace(repoRoot.getAbsolutePath()).bare(true).execute();
IGitAPI gitAPI = (IGitAPI) gitClient;
FilePath gitClientFilePath = gitClient.getWorkTree();
gitClientFilePath.createTextTempFile("aPre", ".txt", "file contents");
gitClient.add(".");
gitClient.commit("Not-a-bare-repository-working-no-such-location");
try {
assertFalse("non-existent location is in a bare repository", gitAPI.isBareRepository("no-such-location"));
/* JGit will not throw an exception - it knows the repo is not bare */
/* CliGit throws an exception so should not reach the next assertion */
assertFalse("CliGitAPIImpl did not throw expected exception", gitAPI instanceof CliGitAPIImpl);
} catch (GitException ge) {
/* Only enters this path for CliGit */
assertExceptionMessageContains(ge, "not a git repository");
}
}
@Test
@Deprecated
public void testIsBareRepositoryBareNoSuchLocation() throws Exception {
IGitAPI gitAPI = IGitAPIForTrueBareRepositoryTests();
try {
assertTrue("non-existent location is in a bare repository", gitAPI.isBareRepository("no-such-location"));
/* JGit will not throw an exception - it knows the repo is not bare */
/* CliGit throws an exception so should not reach the next assertion */
assertFalse("CliGitAPIImpl did not throw expected exception", gitAPI instanceof CliGitAPIImpl);
} catch (GitException ge) {
/* Only enters this path for CliGit */
assertExceptionMessageContains(ge, "not a git repository");
}
}
@Deprecated
@Test
public void testIsBareRepositoryBareEmptyString() throws Exception {
IGitAPI gitAPI = IGitAPIForTrueBareRepositoryTests();
assertTrue("empty string is not a bare repository", gitAPI.isBareRepository(""));
}
@Deprecated
@Test
public void testIsBareRepositoryWorkingEmptyString() throws Exception {
gitClient.init_().workspace(repoRoot.getAbsolutePath()).bare(true).execute();
IGitAPI gitAPI = (IGitAPI) gitClient;
FilePath gitClientFilePath = gitClient.getWorkTree();
gitClientFilePath.createTextTempFile("aPre", ".txt", "file contents");
gitClient.add(".");
gitClient.commit("Not-a-bare-repository-empty-string");
assertFalse("empty string is a bare repository", gitAPI.isBareRepository(""));
}
@Deprecated
@Test
public void testIsBareRepositoryBareNoArg() throws Exception {
IGitAPI gitAPI = IGitAPIForTrueBareRepositoryTests();
assertTrue("no arg is not a bare repository", gitAPI.isBareRepository());
}
@Deprecated
@Test
public void testIsBareRepositoryWorkingNoArg() throws Exception {
gitClient.init_().workspace(repoRoot.getAbsolutePath()).bare(true).execute();
IGitAPI gitAPI = (IGitAPI) gitClient;
FilePath gitClientFilePath = gitClient.getWorkTree();
gitClientFilePath.createTextTempFile("aPre", ".txt", "file contents");
gitClient.add(".");
gitClient.commit("Not-a-bare-repository-no-arg");
assertFalse("no arg is a bare repository", gitAPI.isBareRepository());
}
@Test
public void testBareRepoInit() throws Exception {
IGitAPI gitAPI = IGitAPIForTrueBareRepositoryTests();
File tempDir = gitAPI.withRepository((repo, channel) -> repo.getWorkTree());
File gitFile = new File(tempDir, ".git");
File gitObjFile = new File(tempDir, ".git/objects");
File objFile = new File(tempDir, "objects");
assertFalse(".git exists unexpectedly", gitFile.exists());
assertFalse(".git/objects exists unexpectedly", gitObjFile.exists());
assertTrue("objects is not a directory", objFile.isDirectory());
}
/* The most critical use cases of isBareRepository respond the
* same for both the JGit implementation and the CliGit
* implementation. Those are asserted first in this section of
* assertions.
*/
@Deprecated
@Test
public void testIsBareRepositoryWorkingRepoPathDotGit() throws Exception {
gitClient.init_().workspace(repoRoot.getAbsolutePath()).bare(true).execute();
IGitAPI gitAPI = (IGitAPI) gitClient;
FilePath gitClientFilePath = gitClient.getWorkTree();
gitClientFilePath.createTextTempFile("aPre", ".txt", "file contents");
gitClient.add(".");
gitClient.commit("Not-a-bare-repository-false-repoPath-dot-git");
assertFalse(
"repoPath/.git is a bare repository",
gitAPI.isBareRepository(repoRoot.getPath() + File.separator + ".git"));
}
@Deprecated
@Test
public void testIsBareRepositoryWorkingNull() throws Exception {
gitClient.init_().workspace(repoRoot.getAbsolutePath()).bare(true).execute();
IGitAPI gitAPI = (IGitAPI) gitClient;
FilePath gitClientFilePath = gitClient.getWorkTree();
gitClientFilePath.createTextTempFile("aPre", ".txt", "file contents");
gitClient.add(".");
gitClient.commit("Not-a-bare-repository-working-null");
try {
assertFalse("null is a bare repository", gitAPI.isBareRepository(null));
fail("Did not throw expected exception");
} catch (GitException ge) {
assertExceptionMessageContains(ge, "not a git repository");
}
}
@Deprecated
@Test
public void testIsBareRepositoryBareNull() throws Exception {
IGitAPI gitAPI = IGitAPIForTrueBareRepositoryTests();
try {
assertTrue("null is not a bare repository", gitAPI.isBareRepository(null));
fail("Did not throw expected exception");
} catch (GitException ge) {
assertExceptionMessageContains(ge, "not a git repository");
}
}
@Deprecated
@Test
public void test_isBareRepository_bare_repoPath() throws Exception {
IGitAPI gitAPI = IGitAPIForTrueBareRepositoryTests();
File tempRepoDir = gitAPI.withRepository((repo, channel) -> repo.getWorkTree());
File dotFile = new File(tempRepoDir, ".");
assertTrue("repoPath is not a bare repository", gitAPI.isBareRepository(tempRepoDir.getPath()));
assertTrue("abs(.) is not a bare repository", gitAPI.isBareRepository(dotFile.getAbsolutePath()));
}
@Test
public void testGetRemoteUrl() throws Exception {
assertEquals(srcRepoDir.getAbsolutePath(), gitClient.getRemoteUrl("origin"));
}
@Test
@Deprecated
public void testGetRemoteUrl_two_args() throws Exception {