1
2
3
4
5
6
7
8
9
10 package org.eclipse.jgit.api;
11
12 import java.io.IOException;
13 import java.io.InputStream;
14 import java.io.PrintStream;
15 import java.text.MessageFormat;
16 import java.util.ArrayList;
17 import java.util.Collections;
18 import java.util.HashMap;
19 import java.util.LinkedList;
20 import java.util.List;
21
22 import org.eclipse.jgit.api.errors.AbortedByHookException;
23 import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
24 import org.eclipse.jgit.api.errors.EmptyCommitException;
25 import org.eclipse.jgit.api.errors.GitAPIException;
26 import org.eclipse.jgit.api.errors.JGitInternalException;
27 import org.eclipse.jgit.api.errors.NoFilepatternException;
28 import org.eclipse.jgit.api.errors.NoHeadException;
29 import org.eclipse.jgit.api.errors.NoMessageException;
30 import org.eclipse.jgit.api.errors.ServiceUnavailableException;
31 import org.eclipse.jgit.api.errors.UnmergedPathsException;
32 import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException;
33 import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
34 import org.eclipse.jgit.dircache.DirCache;
35 import org.eclipse.jgit.dircache.DirCacheBuildIterator;
36 import org.eclipse.jgit.dircache.DirCacheBuilder;
37 import org.eclipse.jgit.dircache.DirCacheEntry;
38 import org.eclipse.jgit.dircache.DirCacheIterator;
39 import org.eclipse.jgit.errors.UnmergedPathException;
40 import org.eclipse.jgit.hooks.CommitMsgHook;
41 import org.eclipse.jgit.hooks.Hooks;
42 import org.eclipse.jgit.hooks.PostCommitHook;
43 import org.eclipse.jgit.hooks.PreCommitHook;
44 import org.eclipse.jgit.internal.JGitText;
45 import org.eclipse.jgit.lib.CommitBuilder;
46 import org.eclipse.jgit.lib.Constants;
47 import org.eclipse.jgit.lib.FileMode;
48 import org.eclipse.jgit.lib.GpgConfig;
49 import org.eclipse.jgit.lib.GpgConfig.GpgFormat;
50 import org.eclipse.jgit.lib.GpgSigner;
51 import org.eclipse.jgit.lib.ObjectId;
52 import org.eclipse.jgit.lib.ObjectInserter;
53 import org.eclipse.jgit.lib.PersonIdent;
54 import org.eclipse.jgit.lib.Ref;
55 import org.eclipse.jgit.lib.RefUpdate;
56 import org.eclipse.jgit.lib.RefUpdate.Result;
57 import org.eclipse.jgit.lib.Repository;
58 import org.eclipse.jgit.lib.RepositoryState;
59 import org.eclipse.jgit.revwalk.RevCommit;
60 import org.eclipse.jgit.revwalk.RevObject;
61 import org.eclipse.jgit.revwalk.RevTag;
62 import org.eclipse.jgit.revwalk.RevWalk;
63 import org.eclipse.jgit.transport.CredentialsProvider;
64 import org.eclipse.jgit.treewalk.CanonicalTreeParser;
65 import org.eclipse.jgit.treewalk.FileTreeIterator;
66 import org.eclipse.jgit.treewalk.TreeWalk;
67 import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
68 import org.eclipse.jgit.util.ChangeIdUtil;
69
70
71
72
73
74
75
76
77
78
79 public class CommitCommand extends GitCommand<RevCommit> {
80 private PersonIdent author;
81
82 private PersonIdent committer;
83
84 private String message;
85
86 private boolean all;
87
88 private List<String> only = new ArrayList<>();
89
90 private boolean[] onlyProcessed;
91
92 private boolean amend;
93
94 private boolean insertChangeId;
95
96
97
98
99
100 private List<ObjectId> parents = new LinkedList<>();
101
102 private String reflogComment;
103
104 private boolean useDefaultReflogMessage = true;
105
106
107
108
109 private boolean noVerify;
110
111 private HashMap<String, PrintStream> hookOutRedirect = new HashMap<>(3);
112
113 private HashMap<String, PrintStream> hookErrRedirect = new HashMap<>(3);
114
115 private Boolean allowEmpty;
116
117 private Boolean signCommit;
118
119 private String signingKey;
120
121 private GpgSigner gpgSigner;
122
123 private CredentialsProvider credentialsProvider;
124
125
126
127
128
129
130
131 protected CommitCommand(Repository repo) {
132 super(repo);
133 this.credentialsProvider = CredentialsProvider.getDefault();
134 }
135
136
137
138
139
140
141
142
143
144
145
146
147
148 @Override
149 public RevCommit call() throws GitAPIException, AbortedByHookException,
150 ConcurrentRefUpdateException, NoHeadException, NoMessageException,
151 ServiceUnavailableException, UnmergedPathsException,
152 WrongRepositoryStateException {
153 checkCallable();
154 Collections.sort(only);
155
156 try (RevWalkRevWalk.html#RevWalk">RevWalk rw = new RevWalk(repo)) {
157 RepositoryState state = repo.getRepositoryState();
158 if (!state.canCommit())
159 throw new WrongRepositoryStateException(MessageFormat.format(
160 JGitText.get().cannotCommitOnARepoWithState,
161 state.name()));
162
163 if (!noVerify) {
164 Hooks.preCommit(repo, hookOutRedirect.get(PreCommitHook.NAME),
165 hookErrRedirect.get(PreCommitHook.NAME))
166 .call();
167 }
168
169 processOptions(state, rw);
170
171 if (all && !repo.isBare()) {
172 try (Gitit.html#Git">Git git = new Git(repo)) {
173 git.add()
174 .addFilepattern(".")
175 .setUpdate(true).call();
176 } catch (NoFilepatternException e) {
177
178 throw new JGitInternalException(e.getMessage(), e);
179 }
180 }
181
182 Ref head = repo.exactRef(Constants.HEAD);
183 if (head == null)
184 throw new NoHeadException(
185 JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported);
186
187
188 ObjectId headId = repo.resolve(Constants.HEAD + "^{commit}");
189 if (headId == null && amend)
190 throw new WrongRepositoryStateException(
191 JGitText.get().commitAmendOnInitialNotPossible);
192
193 if (headId != null)
194 if (amend) {
195 RevCommit previousCommit = rw.parseCommit(headId);
196 for (RevCommit p : previousCommit.getParents())
197 parents.add(p.getId());
198 if (author == null)
199 author = previousCommit.getAuthorIdent();
200 } else {
201 parents.add(0, headId);
202 }
203
204 if (!noVerify) {
205 message = Hooks
206 .commitMsg(repo,
207 hookOutRedirect.get(CommitMsgHook.NAME),
208 hookErrRedirect.get(CommitMsgHook.NAME))
209 .setCommitMessage(message).call();
210 }
211
212
213 DirCache index = repo.lockDirCache();
214 try (ObjectInserter odi = repo.newObjectInserter()) {
215 if (!only.isEmpty())
216 index = createTemporaryIndex(headId, index, rw);
217
218
219
220
221 ObjectId indexTreeId = index.writeTree(odi);
222
223 if (insertChangeId)
224 insertChangeId(indexTreeId);
225
226
227 if (headId != null && !allowEmpty.booleanValue()) {
228 RevCommit headCommit = rw.parseCommit(headId);
229 headCommit.getTree();
230 if (indexTreeId.equals(headCommit.getTree())) {
231 throw new EmptyCommitException(
232 JGitText.get().emptyCommit);
233 }
234 }
235
236
237 CommitBuilder commit = new CommitBuilder();
238 commit.setCommitter(committer);
239 commit.setAuthor(author);
240 commit.setMessage(message);
241
242 commit.setParentIds(parents);
243 commit.setTreeId(indexTreeId);
244
245 if (signCommit.booleanValue()) {
246 if (gpgSigner == null) {
247 throw new ServiceUnavailableException(
248 JGitText.get().signingServiceUnavailable);
249 }
250 gpgSigner.sign(commit, signingKey, committer,
251 credentialsProvider);
252 }
253
254 ObjectId commitId = odi.insert(commit);
255 odi.flush();
256
257 RevCommit revCommit = rw.parseCommit(commitId);
258 RefUpdate ru = repo.updateRef(Constants.HEAD);
259 ru.setNewObjectId(commitId);
260 if (!useDefaultReflogMessage) {
261 ru.setRefLogMessage(reflogComment, false);
262 } else {
263 String prefix = amend ? "commit (amend): "
264 : parents.isEmpty() ? "commit (initial): "
265 : "commit: ";
266 ru.setRefLogMessage(prefix + revCommit.getShortMessage(),
267 false);
268 }
269 if (headId != null)
270 ru.setExpectedOldObjectId(headId);
271 else
272 ru.setExpectedOldObjectId(ObjectId.zeroId());
273 Result rc = ru.forceUpdate();
274 switch (rc) {
275 case NEW:
276 case FORCED:
277 case FAST_FORWARD: {
278 setCallable(false);
279 if (state == RepositoryState.MERGING_RESOLVED
280 || isMergeDuringRebase(state)) {
281
282
283 repo.writeMergeCommitMsg(null);
284 repo.writeMergeHeads(null);
285 } else if (state == RepositoryState.CHERRY_PICKING_RESOLVED) {
286 repo.writeMergeCommitMsg(null);
287 repo.writeCherryPickHead(null);
288 } else if (state == RepositoryState.REVERTING_RESOLVED) {
289 repo.writeMergeCommitMsg(null);
290 repo.writeRevertHead(null);
291 }
292 Hooks.postCommit(repo,
293 hookOutRedirect.get(PostCommitHook.NAME),
294 hookErrRedirect.get(PostCommitHook.NAME)).call();
295
296 return revCommit;
297 }
298 case REJECTED:
299 case LOCK_FAILURE:
300 throw new ConcurrentRefUpdateException(
301 JGitText.get().couldNotLockHEAD, ru.getRef(), rc);
302 default:
303 throw new JGitInternalException(MessageFormat.format(
304 JGitText.get().updatingRefFailed, Constants.HEAD,
305 commitId.toString(), rc));
306 }
307 } finally {
308 index.unlock();
309 }
310 } catch (UnmergedPathException e) {
311 throw new UnmergedPathsException(e);
312 } catch (IOException e) {
313 throw new JGitInternalException(
314 JGitText.get().exceptionCaughtDuringExecutionOfCommitCommand, e);
315 }
316 }
317
318 private void insertChangeId(ObjectId treeId) {
319 ObjectId firstParentId = null;
320 if (!parents.isEmpty())
321 firstParentId = parents.get(0);
322 ObjectId changeId = ChangeIdUtil.computeChangeId(treeId, firstParentId,
323 author, committer, message);
324 message = ChangeIdUtil.insertId(message, changeId);
325 if (changeId != null)
326 message = message.replaceAll("\nChange-Id: I"
327 + ObjectId.zeroId().getName() + "\n", "\nChange-Id: I"
328 + changeId.getName() + "\n");
329 }
330
331 private DirCacheircache/DirCache.html#DirCache">DirCache createTemporaryIndex(ObjectId headId, DirCache index,
332 RevWalk rw)
333 throws IOException {
334 ObjectInserter inserter = null;
335
336
337 DirCacheBuilder existingBuilder = index.builder();
338
339
340
341 DirCache inCoreIndex = DirCache.newInCore();
342 DirCacheBuilder tempBuilder = inCoreIndex.builder();
343
344 onlyProcessed = new boolean[only.size()];
345 boolean emptyCommit = true;
346
347 try (TreeWalklk.html#TreeWalk">TreeWalk treeWalk = new TreeWalk(repo)) {
348 treeWalk.setOperationType(OperationType.CHECKIN_OP);
349 int dcIdx = treeWalk
350 .addTree(new DirCacheBuildIterator(existingBuilder));
351 FileTreeIterator fti = new FileTreeIterator(repo);
352 fti.setDirCacheIterator(treeWalk, 0);
353 int fIdx = treeWalk.addTree(fti);
354 int hIdx = -1;
355 if (headId != null)
356 hIdx = treeWalk.addTree(rw.parseTree(headId));
357 treeWalk.setRecursive(true);
358
359 String lastAddedFile = null;
360 while (treeWalk.next()) {
361 String path = treeWalk.getPathString();
362
363 int pos = lookupOnly(path);
364
365 CanonicalTreeParser hTree = null;
366 if (hIdx != -1)
367 hTree = treeWalk.getTree(hIdx, CanonicalTreeParser.class);
368
369 DirCacheIterator dcTree = treeWalk.getTree(dcIdx,
370 DirCacheIterator.class);
371
372 if (pos >= 0) {
373
374
375 FileTreeIterator fTree = treeWalk.getTree(fIdx,
376 FileTreeIterator.class);
377
378
379 boolean tracked = dcTree != null || hTree != null;
380 if (!tracked)
381 continue;
382
383
384
385 if (path.equals(lastAddedFile))
386 continue;
387
388 lastAddedFile = path;
389
390 if (fTree != null) {
391
392
393 final DirCacheEntrytry.html#DirCacheEntry">DirCacheEntry dcEntry = new DirCacheEntry(path);
394 long entryLength = fTree.getEntryLength();
395 dcEntry.setLength(entryLength);
396 dcEntry.setLastModified(fTree.getEntryLastModifiedInstant());
397 dcEntry.setFileMode(fTree.getIndexFileMode(dcTree));
398
399 boolean objectExists = (dcTree != null
400 && fTree.idEqual(dcTree))
401 || (hTree != null && fTree.idEqual(hTree));
402 if (objectExists) {
403 dcEntry.setObjectId(fTree.getEntryObjectId());
404 } else {
405 if (FileMode.GITLINK.equals(dcEntry.getFileMode()))
406 dcEntry.setObjectId(fTree.getEntryObjectId());
407 else {
408
409 if (inserter == null)
410 inserter = repo.newObjectInserter();
411 long contentLength = fTree
412 .getEntryContentLength();
413 try (InputStream inputStream = fTree
414 .openEntryStream()) {
415 dcEntry.setObjectId(inserter.insert(
416 Constants.OBJ_BLOB, contentLength,
417 inputStream));
418 }
419 }
420 }
421
422
423 existingBuilder.add(dcEntry);
424
425 tempBuilder.add(dcEntry);
426
427 if (emptyCommit
428 && (hTree == null || !hTree.idEqual(fTree)
429 || hTree.getEntryRawMode() != fTree
430 .getEntryRawMode()))
431
432 emptyCommit = false;
433 } else {
434
435
436
437 if (emptyCommit && hTree != null)
438
439 emptyCommit = false;
440 }
441
442
443 onlyProcessed[pos] = true;
444 } else {
445
446 if (hTree != null) {
447
448
449 final DirCacheEntrytry.html#DirCacheEntry">DirCacheEntry dcEntry = new DirCacheEntry(path);
450 dcEntry.setObjectId(hTree.getEntryObjectId());
451 dcEntry.setFileMode(hTree.getEntryFileMode());
452
453
454 tempBuilder.add(dcEntry);
455 }
456
457
458 if (dcTree != null)
459 existingBuilder.add(dcTree.getDirCacheEntry());
460 }
461 }
462 }
463
464
465
466 for (int i = 0; i < onlyProcessed.length; i++)
467 if (!onlyProcessed[i])
468 throw new JGitInternalException(MessageFormat.format(
469 JGitText.get().entryNotFoundByPath, only.get(i)));
470
471
472 if (emptyCommit && !allowEmpty.booleanValue())
473
474
475 throw new JGitInternalException(JGitText.get().emptyCommit);
476
477
478 existingBuilder.commit();
479
480 tempBuilder.finish();
481 return inCoreIndex;
482 }
483
484
485
486
487
488
489
490
491
492
493
494
495
496 private int lookupOnly(String pathString) {
497 String p = pathString;
498 while (true) {
499 int position = Collections.binarySearch(only, p);
500 if (position >= 0)
501 return position;
502 int l = p.lastIndexOf('/');
503 if (l < 1)
504 break;
505 p = p.substring(0, l);
506 }
507 return -1;
508 }
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524 private void processOptions(RepositoryState state, RevWalk rw)
525 throws NoMessageException, UnsupportedSigningFormatException {
526 if (committer == null)
527 committer = new PersonIdent(repo);
528 if (author == null && !amend)
529 author = committer;
530 if (allowEmpty == null)
531
532
533
534
535 allowEmpty = (only.isEmpty()) ? Boolean.TRUE : Boolean.FALSE;
536
537
538 if (state == RepositoryState.MERGING_RESOLVED
539 || isMergeDuringRebase(state)) {
540 try {
541 parents = repo.readMergeHeads();
542 if (parents != null)
543 for (int i = 0; i < parents.size(); i++) {
544 RevObject ro = rw.parseAny(parents.get(i));
545 if (ro instanceof RevTag)
546 parents.set(i, rw.peel(ro));
547 }
548 } catch (IOException e) {
549 throw new JGitInternalException(MessageFormat.format(
550 JGitText.get().exceptionOccurredDuringReadingOfGIT_DIR,
551 Constants.MERGE_HEAD, e), e);
552 }
553 if (message == null) {
554 try {
555 message = repo.readMergeCommitMsg();
556 } catch (IOException e) {
557 throw new JGitInternalException(MessageFormat.format(
558 JGitText.get().exceptionOccurredDuringReadingOfGIT_DIR,
559 Constants.MERGE_MSG, e), e);
560 }
561 }
562 } else if (state == RepositoryState.SAFE && message == null) {
563 try {
564 message = repo.readSquashCommitMsg();
565 if (message != null)
566 repo.writeSquashCommitMsg(null );
567 } catch (IOException e) {
568 throw new JGitInternalException(MessageFormat.format(
569 JGitText.get().exceptionOccurredDuringReadingOfGIT_DIR,
570 Constants.MERGE_MSG, e), e);
571 }
572
573 }
574 if (message == null)
575
576
577 throw new NoMessageException(JGitText.get().commitMessageNotSpecified);
578
579 GpgConfig gpgConfig = new GpgConfig(repo.getConfig());
580 if (signCommit == null) {
581 signCommit = gpgConfig.isSignCommits() ? Boolean.TRUE
582 : Boolean.FALSE;
583 }
584 if (signingKey == null) {
585 signingKey = gpgConfig.getSigningKey();
586 }
587 if (gpgSigner == null) {
588 if (gpgConfig.getKeyFormat() != GpgFormat.OPENPGP) {
589 throw new UnsupportedSigningFormatException(
590 JGitText.get().onlyOpenPgpSupportedForSigning);
591 }
592 gpgSigner = GpgSigner.getDefault();
593 }
594 }
595
596 private boolean isMergeDuringRebase(RepositoryState state) {
597 if (state != RepositoryState.REBASING_INTERACTIVE
598 && state != RepositoryState.REBASING_MERGE)
599 return false;
600 try {
601 return repo.readMergeHeads() != null;
602 } catch (IOException e) {
603 throw new JGitInternalException(MessageFormat.format(
604 JGitText.get().exceptionOccurredDuringReadingOfGIT_DIR,
605 Constants.MERGE_HEAD, e), e);
606 }
607 }
608
609
610
611
612
613
614
615
616 public CommitCommand setMessage(String message) {
617 checkCallable();
618 this.message = message;
619 return this;
620 }
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641 public CommitCommand setAllowEmpty(boolean allowEmpty) {
642 this.allowEmpty = Boolean.valueOf(allowEmpty);
643 return this;
644 }
645
646
647
648
649
650
651 public String getMessage() {
652 return message;
653 }
654
655
656
657
658
659
660
661
662
663
664
665 public CommitCommand setCommitter(PersonIdent committer) {
666 checkCallable();
667 this.committer = committer;
668 return this;
669 }
670
671
672
673
674
675
676
677
678
679
680
681
682 public CommitCommand setCommitter(String name, String email) {
683 checkCallable();
684 return setCommitter(new PersonIdent(name, email));
685 }
686
687
688
689
690
691
692
693
694
695 public PersonIdent getCommitter() {
696 return committer;
697 }
698
699
700
701
702
703
704
705
706
707
708
709 public CommitCommand setAuthor(PersonIdent author) {
710 checkCallable();
711 this.author = author;
712 return this;
713 }
714
715
716
717
718
719
720
721
722
723
724
725
726 public CommitCommand setAuthor(String name, String email) {
727 checkCallable();
728 return setAuthor(new PersonIdent(name, email));
729 }
730
731
732
733
734
735
736
737
738
739 public PersonIdent getAuthor() {
740 return author;
741 }
742
743
744
745
746
747
748
749
750
751
752
753
754
755 public CommitCommand setAll(boolean all) {
756 checkCallable();
757 if (all && !only.isEmpty())
758 throw new JGitInternalException(MessageFormat.format(
759 JGitText.get().illegalCombinationOfArguments, "--all",
760 "--only"));
761 this.all = all;
762 return this;
763 }
764
765
766
767
768
769
770
771
772
773
774 public CommitCommand setAmend(boolean amend) {
775 checkCallable();
776 this.amend = amend;
777 return this;
778 }
779
780
781
782
783
784
785
786
787
788
789
790
791 public CommitCommand setOnly(String only) {
792 checkCallable();
793 if (all)
794 throw new JGitInternalException(MessageFormat.format(
795 JGitText.get().illegalCombinationOfArguments, "--only",
796 "--all"));
797 String o = only.endsWith("/") ? only.substring(0, only.length() - 1)
798 : only;
799
800 if (!this.only.contains(o))
801 this.only.add(o);
802 return this;
803 }
804
805
806
807
808
809
810
811
812
813
814
815 public CommitCommand setInsertChangeId(boolean insertChangeId) {
816 checkCallable();
817 this.insertChangeId = insertChangeId;
818 return this;
819 }
820
821
822
823
824
825
826
827
828
829 public CommitCommand setReflogComment(String reflogComment) {
830 this.reflogComment = reflogComment;
831 useDefaultReflogMessage = false;
832 return this;
833 }
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849 public CommitCommand setNoVerify(boolean noVerify) {
850 this.noVerify = noVerify;
851 return this;
852 }
853
854
855
856
857
858
859
860
861
862
863
864 public CommitCommand setHookOutputStream(PrintStream hookStdOut) {
865 setHookOutputStream(PreCommitHook.NAME, hookStdOut);
866 setHookOutputStream(CommitMsgHook.NAME, hookStdOut);
867 setHookOutputStream(PostCommitHook.NAME, hookStdOut);
868 return this;
869 }
870
871
872
873
874
875
876
877
878
879
880
881 public CommitCommand setHookErrorStream(PrintStream hookStdErr) {
882 setHookErrorStream(PreCommitHook.NAME, hookStdErr);
883 setHookErrorStream(CommitMsgHook.NAME, hookStdErr);
884 setHookErrorStream(PostCommitHook.NAME, hookStdErr);
885 return this;
886 }
887
888
889
890
891
892
893
894
895
896
897
898
899
900 public CommitCommand setHookOutputStream(String hookName,
901 PrintStream hookStdOut) {
902 if (!(PreCommitHook.NAME.equals(hookName)
903 || CommitMsgHook.NAME.equals(hookName)
904 || PostCommitHook.NAME.equals(hookName))) {
905 throw new IllegalArgumentException(
906 MessageFormat.format(JGitText.get().illegalHookName,
907 hookName));
908 }
909 hookOutRedirect.put(hookName, hookStdOut);
910 return this;
911 }
912
913
914
915
916
917
918
919
920
921
922
923
924
925 public CommitCommand setHookErrorStream(String hookName,
926 PrintStream hookStdErr) {
927 if (!(PreCommitHook.NAME.equals(hookName)
928 || CommitMsgHook.NAME.equals(hookName)
929 || PostCommitHook.NAME.equals(hookName))) {
930 throw new IllegalArgumentException(MessageFormat
931 .format(JGitText.get().illegalHookName, hookName));
932 }
933 hookErrRedirect.put(hookName, hookStdErr);
934 return this;
935 }
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953 public CommitCommand setSigningKey(String signingKey) {
954 checkCallable();
955 this.signingKey = signingKey;
956 return this;
957 }
958
959
960
961
962
963
964
965
966
967
968
969 public CommitCommand setSign(Boolean sign) {
970 checkCallable();
971 this.signCommit = sign;
972 return this;
973 }
974
975
976
977
978
979
980
981
982
983 public void setCredentialsProvider(
984 CredentialsProvider credentialsProvider) {
985 this.credentialsProvider = credentialsProvider;
986 }
987 }