36
37:- module(git,
38 [ git/2, 39 git_process_output/3, 40 git_open_file/4, 41 is_git_directory/1, 42 git_describe/2, 43 git_hash/2, 44 git_ls_tree/2, 45 git_remote_url/3, 46 git_ls_remote/3, 47 git_branches/2, 48 git_remote_branches/2, 49 git_default_branch/2, 50 git_current_branch/2, 51 git_tags/2, 52 git_tags_on_branch/3, 53 git_shortlog/3, 54 git_log_data/3, 55 git_show/4, 56 git_commit_data/3, 57 is_git_hash/1 58 ]). 59:- use_module(library(record),[(record)/1,current_record/2, op(_,_,record)]). 60
61:- autoload(library(apply),[maplist/3]). 62:- autoload(library(error),[must_be/2,existence_error/2]). 63:- autoload(library(filesex),
64 [directory_file_path/3,relative_file_name/3]). 65:- autoload(library(lists),[append/3,member/2,append/2]). 66:- autoload(library(option),[option/2,option/3,select_option/3]). 67:- autoload(library(process),[process_create/3,process_wait/2]). 68:- autoload(library(readutil),
69 [ read_stream_to_codes/3,
70 read_line_to_codes/2,
71 read_stream_to_codes/2
72 ]). 73:- autoload(library(dcg/basics),
74 [string//1,whites//0,string_without//2,blanks//0]). 75
76
77:- meta_predicate
78 git_process_output(+, 1, +). 79
90
91:- predicate_options(git/2, 2,
92 [ directory(atom),
93 error(-codes),
94 output(-codes),
95 status(-any),
96 askpass(any)
97 ]). 98:- predicate_options(git_default_branch/2, 2,
99 [ pass_to(git_process_output/3, 3)
100 ] ). 101:- predicate_options(git_describe/2, 2,
102 [ commit(atom),
103 directory(atom),
104 match(atom)
105 ]). 106:- predicate_options(git_hash/2, 2,
107 [ commit(atom),
108 directory(atom),
109 pass_to(git_process_output/3, 3)
110 ]). 111:- predicate_options(git_ls_tree/2, 2,
112 [ commit(atom),
113 directory(atom)
114 ]). 115:- predicate_options(git_process_output/3, 3,
116 [ directory(atom),
117 askpass(any),
118 error(-codes),
119 status(-integer)
120 ]). 121:- predicate_options(git_remote_url/3, 3,
122 [ pass_to(git_process_output/3, 3)
123 ]). 124:- predicate_options(git_shortlog/3, 3,
125 [ revisions(atom),
126 limit(nonneg),
127 path(atom)
128 ]). 129:- predicate_options(git_show/4, 4,
130 [ diff(oneof([patch,stat]))
131 ]). 132
133
148
149git(Argv, Options) :-
150 git_cwd_options(Argv, Argv1, Options),
151 env_options(Extra, Options),
152 setup_call_cleanup(
153 process_create(path(git), Argv1,
154 [ stdout(pipe(Out)),
155 stderr(pipe(Error)),
156 process(PID)
157 | Extra
158 ]),
159 call_cleanup(
160 ( read_stream_to_codes(Out, OutCodes, []),
161 read_stream_to_codes(Error, ErrorCodes, [])
162 ),
163 process_wait(PID, Status)),
164 close_streams([Out,Error])),
165 print_error(ErrorCodes, Options),
166 print_output(OutCodes, Options),
167 ( option(status(Status0), Options)
168 -> Status = Status0
169 ; Status == exit(0)
170 -> true
171 ; throw(error(process_error(git(Argv), Status), _))
172 ).
173
174git_cwd_options(Argv0, Argv, Options) :-
175 option(directory(Dir), Options),
176 !,
177 Argv = ['-C', file(Dir) | Argv0 ].
178git_cwd_options(Argv, Argv, _).
179
180env_options([env(['GIT_ASKPASS'=Program])], Options) :-
181 option(askpass(Exe), Options),
182 !,
183 exe_options(ExeOptions),
184 absolute_file_name(Exe, PlProg, ExeOptions),
185 prolog_to_os_filename(PlProg, Program).
186env_options([], _).
187
188exe_options(Options) :-
189 current_prolog_flag(windows, true),
190 !,
191 Options = [ extensions(['',exe,com]), access(read) ].
192exe_options(Options) :-
193 Options = [ access(execute) ].
194
195print_output(OutCodes, Options) :-
196 option(output(Codes), Options),
197 !,
198 Codes = OutCodes.
199print_output([], _) :- !.
200print_output(OutCodes, _) :-
201 print_message(informational, git(output(OutCodes))).
202
203print_error(OutCodes, Options) :-
204 option(error(Codes), Options),
205 !,
206 Codes = OutCodes.
207print_error([], _) :- !.
208print_error(OutCodes, _) :-
209 phrase(classify_message(Level), OutCodes, _),
210 print_message(Level, git(output(OutCodes))).
211
212classify_message(error) -->
213 string(_), "fatal:",
214 !.
215classify_message(error) -->
216 string(_), "error:",
217 !.
218classify_message(warning) -->
219 string(_), "warning:",
220 !.
221classify_message(informational) -->
222 [].
223
228
229close_streams(List) :-
230 phrase(close_streams(List), Errors),
231 ( Errors = [Error|_]
232 -> throw(Error)
233 ; true
234 ).
235
236close_streams([H|T]) -->
237 { catch(close(H), E, true) },
238 ( { var(E) }
239 -> []
240 ; [E]
241 ),
242 close_streams(T).
243
244
249
250git_process_output(Argv, OnOutput, Options) :-
251 git_cwd_options(Argv, Argv1, Options),
252 env_options(Extra, Options),
253 setup_call_cleanup(
254 process_create(path(git), Argv1,
255 [ stdout(pipe(Out)),
256 stderr(pipe(Error)),
257 process(PID)
258 | Extra
259 ]),
260 call_cleanup(
261 ( call(OnOutput, Out),
262 read_stream_to_codes(Error, ErrorCodes, [])
263 ),
264 git_wait(PID, Out, Status)),
265 close_streams([Out,Error])),
266 print_error(ErrorCodes, Options),
267 ( option(status(Status), Options)
268 -> true
269 ; Status = exit(0)
270 -> true
271 ; throw(error(process_error(git, Status)))
272 ).
273
274git_wait(PID, Out, Status) :-
275 at_end_of_stream(Out),
276 !,
277 process_wait(PID, Status).
278git_wait(PID, Out, Status) :-
279 setup_call_cleanup(
280 open_null_stream(Null),
281 copy_stream_data(Out, Null),
282 close(Null)),
283 process_wait(PID, Status).
284
285
292
293git_open_file(Dir, File, Branch, In) :-
294 atomic_list_concat([Branch, :, File], Ref),
295 process_create(path(git),
296 [ '-C', file(Dir), show, Ref ],
297 [ stdout(pipe(In))
298 ]),
299 set_stream(In, file_name(File)).
300
301
306
307is_git_directory(Directory) :-
308 directory_file_path(Directory, '.git', GitDir),
309 exists_directory(GitDir),
310 !.
311is_git_directory(Directory) :-
312 exists_directory(Directory),
313 git(['rev-parse', '--git-dir'],
314 [ output(Codes),
315 error(_),
316 status(Status),
317 directory(Directory)
318 ]),
319 Status == exit(0),
320 string_codes(GitDir0, Codes),
321 split_string(GitDir0, "", " \n", [GitDir]),
322 sub_string(GitDir, B, _, A, "/.git/modules/"),
323 !,
324 sub_string(GitDir, 0, B, _, Main),
325 sub_string(GitDir, _, A, 0, Below),
326 directory_file_path(Main, Below, Dir),
327 same_file(Dir, Directory).
328
344
345git_describe(Version, Options) :-
346 ( option(match(Pattern), Options)
347 -> true
348 ; git_version_pattern(Pattern)
349 ),
350 ( option(commit(Commit), Options)
351 -> Extra = [Commit]
352 ; Extra = []
353 ),
354 option(directory(Dir), Options, .),
355 setup_call_cleanup(
356 process_create(path(git),
357 [ 'describe',
358 '--match', Pattern
359 | Extra
360 ],
361 [ stdout(pipe(Out)),
362 stderr(null),
363 process(PID),
364 cwd(Dir)
365 ]),
366 call_cleanup(
367 read_stream_to_codes(Out, V0, []),
368 git_wait(PID, Out, Status)),
369 close(Out)),
370 Status = exit(0),
371 !,
372 atom_codes(V1, V0),
373 normalize_space(atom(Plain), V1),
374 ( git_is_clean(Dir)
375 -> Version = Plain
376 ; atom_concat(Plain, '-DIRTY', Version)
377 ).
378git_describe(Version, Options) :-
379 option(directory(Dir), Options, .),
380 option(commit(Commit), Options, 'HEAD'),
381 setup_call_cleanup(
382 process_create(path(git),
383 [ 'rev-parse', '--short',
384 Commit
385 ],
386 [ stdout(pipe(Out)),
387 stderr(null),
388 process(PID),
389 cwd(Dir)
390 ]),
391 call_cleanup(
392 read_stream_to_codes(Out, V0, []),
393 git_wait(PID, Out, Status)),
394 close(Out)),
395 Status = exit(0),
396 atom_codes(V1, V0),
397 normalize_space(atom(Plain), V1),
398 ( git_is_clean(Dir)
399 -> Version = Plain
400 ; atom_concat(Plain, '-DIRTY', Version)
401 ).
402
403
404:- multifile
405 git_version_pattern/1. 406
407git_version_pattern('V*').
408git_version_pattern('*').
409
410
416
417git_is_clean(Dir) :-
418 setup_call_cleanup(process_create(path(git), ['diff', '--stat'],
419 [ stdout(pipe(Out)),
420 stderr(null),
421 cwd(Dir)
422 ]),
423 stream_char_count(Out, Count),
424 close(Out)),
425 Count == 0.
426
427stream_char_count(Out, Count) :-
428 setup_call_cleanup(open_null_stream(Null),
429 ( copy_stream_data(Out, Null),
430 character_count(Null, Count)
431 ),
432 close(Null)).
433
434
438
439git_hash(Hash, Options) :-
440 option(commit(Commit), Options, 'HEAD'),
441 git_process_output(['rev-parse', '--verify', Commit],
442 read_hash(Hash),
443 Options).
444
445read_hash(Hash, Stream) :-
446 read_line_to_codes(Stream, Line),
447 atom_codes(Hash, Line).
448
449
454
455is_git_hash(Atom) :-
456 atom_length(Atom, 40),
457 atom_codes(Atom, Codes),
458 maplist(is_hex, Codes),
459 !.
460
461is_hex(Code) :-
462 code_type(Code, xdigit(_)).
463
472
473git_ls_tree(Entries, Options) :-
474 option(commit(Commit), Options, 'HEAD'),
475 git_process_output(['ls-tree', '-z', '-r', '-l', Commit],
476 read_tree(Entries),
477 Options).
478
479read_tree(Entries, Stream) :-
480 read_stream_to_codes(Stream, Codes),
481 phrase(ls_tree(Entries), Codes).
482
483ls_tree([H|T]) -->
484 ls_entry(H),
485 !,
486 ls_tree(T).
487ls_tree([]) --> [].
488
489ls_entry(object(Mode, Type, Hash, Size, Name)) -->
490 string(MS), " ",
491 string(TS), " ",
492 string(HS), " ",
493 string(SS), "\t",
494 string(NS), [0],
495 !,
496 { number_codes(Mode, [0'0,0'o|MS]),
497 atom_codes(Type, TS),
498 atom_codes(Hash, HS),
499 ( Type == blob
500 -> number_codes(Size, SS)
501 ; Size = 0 502 ),
503 atom_codes(Name, NS)
504 }.
505
506
510
511git_remote_url(Remote, URL, Options) :-
512 git_process_output([remote, show, Remote],
513 read_url("Fetch URL:", URL),
514 Options).
515
516read_url(Tag, URL, In) :-
517 repeat,
518 read_line_to_codes(In, Line),
519 ( Line == end_of_file
520 -> !, fail
521 ; phrase(url_codes(Tag, Codes), Line)
522 -> !, atom_codes(URL, Codes)
523 ).
524
525url_codes(Tag, Rest) -->
526 { string_codes(Tag, TagCodes) },
527 whites, string(TagCodes), whites, string(Rest).
528
529
548
549git_ls_remote(GitURL, Refs, Options) :-
550 findall(O, ls_remote_option(Options, O), RemoteOptions),
551 option(refs(LimitRefs), Options, []),
552 must_be(list(atom), LimitRefs),
553 append([ 'ls-remote' | RemoteOptions], [GitURL|LimitRefs], Argv),
554 git_process_output(Argv, remote_refs(Refs), Options).
555
556ls_remote_option(Options, '--heads') :-
557 option(heads(true), Options).
558ls_remote_option(Options, '--tags') :-
559 option(tags(true), Options).
560
561remote_refs(Refs, Out) :-
562 read_line_to_codes(Out, Line0),
563 remote_refs(Line0, Out, Refs).
564
565remote_refs(end_of_file, _, []) :- !.
566remote_refs(Line, Out, [Hash-Ref|Tail]) :-
567 phrase(remote_ref(Hash,Ref), Line),
568 read_line_to_codes(Out, Line1),
569 remote_refs(Line1, Out, Tail).
570
571remote_ref(Hash, Ref) -->
572 string_without("\t ", HashCodes),
573 whites,
574 string_without("\t ", RefCodes),
575 { atom_codes(Hash, HashCodes),
576 atom_codes(Ref, RefCodes)
577 }.
578
579
584
585git_remote_branches(GitURL, Branches) :-
586 git_ls_remote(GitURL, Refs, [heads(true)]),
587 findall(B, (member(_-Head, Refs),
588 atom_concat('refs/heads/', B, Head)),
589 Branches).
590
591
602
603git_default_branch(BranchName, Options) :-
604 option(remote(Remote), Options, origin),
605 atomic_list_concat([Remote, 'HEAD'], '/', HeadRef),
606 git_process_output(['rev-parse', '--abbrev-ref', HeadRef],
607 read_default_branch(BranchName),
608 [ error(_),
609 status(Status)
610 | Options
611 ]),
612 Status == exit(0),
613 !.
614git_default_branch(BranchName, Options) :-
615 option(remote(Remote), Options, origin),
616 git_branches(MyBranches, []),
617 git_branches(RemoteBranches, [remote(true)]),
618 ( preferred_default_branch(BranchName),
619 shared_branch(Remote, MyBranches, RemoteBranches, BranchName)
620 -> true
621 ; shared_branch(Remote, MyBranches, RemoteBranches, BranchName)
622 -> true
623 ).
624
625preferred_default_branch(main).
626preferred_default_branch(master).
627
628shared_branch(Remote, MyBranches, RemoteBranches, BranchName) :-
629 member(BranchName, MyBranches),
630 atomic_list_concat([Remote, BranchName], '/', Orig),
631 memberchk(Orig, RemoteBranches).
632
633read_default_branch(BranchName, In) :-
634 read_line_to_string(In, Result),
635 split_string(Result, "/", "", [_Origin,BranchString]),
636 atom_string(BranchName, BranchString).
637
642
643git_current_branch(BranchName, Options) :-
644 git_process_output([branch],
645 read_current_branch(BranchName),
646 Options).
647
648read_current_branch(BranchName, In) :-
649 repeat,
650 read_line_to_codes(In, Line),
651 ( Line == end_of_file
652 -> !, fail
653 ; phrase(current_branch(Codes), Line)
654 -> !, atom_codes(BranchName, Codes),
655 \+ sub_atom(BranchName, _, _, _, '(HEAD detached')
656 ).
657
658current_branch(Rest) -->
659 "*", whites, string(Rest).
660
670
671git_branches(Branches, Options) :-
672 ( select_option(commit(Commit), Options, GitOptions)
673 -> Extra = ['--contains', Commit]
674 ; select_option(remote(true), Options, GitOptions)
675 -> Extra = ['-r']
676 ; Extra = [],
677 GitOptions = Options
678 ),
679 git_process_output([branch|Extra],
680 read_branches(Branches),
681 GitOptions).
682
683read_branches(Branches, In) :-
684 read_line_to_codes(In, Line),
685 ( Line == end_of_file
686 -> Branches = []
687 ; Line = [_,_|Codes],
688 atom_codes(H, Codes),
689 ( sub_atom(H, _, _, _, '(HEAD detached at')
690 -> Branches = T
691 ; Branches = [H|T]
692 ),
693 read_branches(T, In)
694 ).
695
696
700
701git_tags(Tags, Options) :-
702 git_process_output([tag],
703 read_lines_to_atoms(Tags),
704 Options).
705
706read_lines_to_atoms(Atoms, In) :-
707 read_line_to_string(In, Line),
708 ( Line == end_of_file
709 -> Atoms = []
710 ; atom_string(Atom, Line),
711 Atoms = [Atom|T],
712 read_lines_to_atoms(T, In)
713 ).
714
721
722git_tags_on_branch(Dir, Branch, Tags) :-
723 git_process_output([ log, '--oneline', '--decorate', Branch ],
724 log_to_tags(Tags),
725 [ directory(Dir) ]).
726
727log_to_tags(Tags, Out) :-
728 read_line_to_codes(Out, Line0),
729 log_to_tags(Line0, Out, Tags, []).
730
731log_to_tags(end_of_file, _, Tags, Tags) :- !.
732log_to_tags(Line, Out, Tags, Tail) :-
733 phrase(tags_on_line(Tags, Tail1), Line),
734 read_line_to_codes(Out, Line1),
735 log_to_tags(Line1, Out, Tail1, Tail).
736
737tags_on_line(Tags, Tail) -->
738 string_without(" ", _Hash),
739 tags(Tags, Tail),
740 skip_rest.
741
742tags(Tags, Tail) -->
743 whites,
744 "(",
745 tag_list(Tags, Rest),
746 !,
747 tags(Rest, Tail).
748tags(Tags, Tags) -->
749 skip_rest.
750
751tag_list([H|T], Rest) -->
752 "tag:", !, whites,
753 string(Codes),
754 ( ")"
755 -> { atom_codes(H, Codes),
756 T = Rest
757 }
758 ; ","
759 -> { atom_codes(H, Codes)
760 },
761 whites,
762 tag_list(T, Rest)
763 ).
764tag_list(List, Rest) -->
765 string(_),
766 ( ")"
767 -> { List = Rest }
768 ; ","
769 -> whites,
770 tag_list(List, Rest)
771 ).
772
773skip_rest(_,_).
774
775
776 779
796
797:- record
798 git_log(commit_hash:atom,
799 author_name:atom,
800 author_date_relative:atom,
801 committer_name:atom,
802 committer_date_relative:atom,
803 committer_date_unix:integer,
804 subject:atom,
805 ref_names:list). 806
807git_shortlog(Dir, ShortLog, Options) :-
808 ( option(revisions(Range), Options)
809 -> RangeSpec = [Range]
810 ; option(limit(Limit), Options, 10),
811 RangeSpec = ['-n', Limit]
812 ),
813 ( option(git_path(Path), Options)
814 -> Extra = ['--', Path]
815 ; option(path(Path), Options)
816 -> relative_file_name(Path, Dir, RelPath),
817 Extra = ['--', RelPath]
818 ; Extra = []
819 ),
820 git_format_string(git_log, Fields, Format),
821 append([[log, Format], RangeSpec, Extra], GitArgv),
822 git_process_output(GitArgv,
823 read_git_formatted(git_log, Fields, ShortLog),
824 [directory(Dir)]).
825
826
827read_git_formatted(Record, Fields, ShortLog, In) :-
828 read_line_to_codes(In, Line0),
829 read_git_formatted(Line0, In, Record, Fields, ShortLog).
830
831read_git_formatted(end_of_file, _, _, _, []) :- !.
832read_git_formatted(Line, In, Record, Fields, [H|T]) :-
833 record_from_line(Record, Fields, Line, H),
834 read_line_to_codes(In, Line1),
835 read_git_formatted(Line1, In, Record, Fields, T).
836
837record_from_line(RecordName, Fields, Line, Record) :-
838 phrase(fields_from_line(Fields, Values), Line),
839 Record =.. [RecordName|Values].
840
841fields_from_line([], []) --> [].
842fields_from_line([F|FT], [V|VT]) -->
843 to_nul_s(Codes),
844 { field_to_prolog(F, Codes, V) },
845 fields_from_line(FT, VT).
846
847to_nul_s([]) --> [0], !.
848to_nul_s([H|T]) --> [H], to_nul_s(T).
849
850field_to_prolog(ref_names, Line, List) :-
851 phrase(ref_names(List), Line),
852 !.
853field_to_prolog(committer_date_unix, Line, Stamp) :-
854 !,
855 number_codes(Stamp, Line).
856field_to_prolog(_, Line, Atom) :-
857 atom_codes(Atom, Line).
858
859ref_names([]) --> [].
860ref_names(List) -->
861 blanks, "(", ref_name_list(List), ")".
862
863ref_name_list([H|T]) -->
864 string_without(",)", Codes),
865 { atom_codes(H, Codes) },
866 ( ",", blanks
867 -> ref_name_list(T)
868 ; {T=[]}
869 ).
870
871
884
885:- record
886 git_commit(tree_hash:atom,
887 parent_hashes:list,
888 author_name:atom,
889 author_date:atom,
890 committer_name:atom,
891 committer_date:atom,
892 subject:atom). 893
894git_show(Dir, Hash, Commit, Options) :-
895 git_format_string(git_commit, Fields, Format),
896 option(diff(Diff), Options, patch),
897 diff_arg(Diff, DiffArg),
898 git_process_output([ show, DiffArg, Hash, Format ],
899 read_commit(Fields, Commit, Options),
900 [directory(Dir)]).
901
902diff_arg(patch, '-p').
903diff_arg(stat, '--stat').
904
905read_commit(Fields, Data-Body, Options, In) :-
906 read_line_to_codes(In, Line1),
907 record_from_line(git_commit, Fields, Line1, Data),
908 read_line_to_codes(In, Line2),
909 ( Line2 == []
910 -> option(max_lines(Max), Options, -1),
911 read_n_lines(In, Max, Body)
912 ; Line2 == end_of_file
913 -> Body = []
914 ).
915
916read_n_lines(In, Max, Lines) :-
917 read_line_to_codes(In, Line1),
918 read_n_lines(Line1, Max, In, Lines).
919
920read_n_lines(end_of_file, _, _, []) :- !.
921read_n_lines(_, 0, In, []) :-
922 !,
923 setup_call_cleanup(open_null_stream(Out),
924 copy_stream_data(In, Out),
925 close(Out)).
926read_n_lines(Line, Max0, In, [Line|More]) :-
927 read_line_to_codes(In, Line2),
928 Max is Max0-1,
929 read_n_lines(Line2, Max, In, More).
930
931
938
939:- meta_predicate
940 git_format_string(:, -, -). 941
942git_format_string(M:RecordName, Fields, Format) :-
943 current_record(RecordName, M:Term),
944 findall(F, record_field(Term, F), Fields),
945 maplist(git_field_format, Fields, Formats),
946 atomic_list_concat(['--format='|Formats], Format).
947
948record_field(Term, Name) :-
949 arg(_, Term, Field),
950 field_name(Field, Name).
951
952field_name(Name:_Type=_Default, Name) :- !.
953field_name(Name:_Type, Name) :- !.
954field_name(Name=_Default, Name) :- !.
955field_name(Name, Name).
956
957git_field_format(Field, Fmt) :-
958 ( git_format(NoPercent, Field)
959 -> atomic_list_concat(['%', NoPercent, '%x00'], Fmt)
960 ; existence_error(git_format, Field)
961 ).
962
963git_format('H', commit_hash).
964git_format('h', abbreviated_commit_hash).
965git_format('T', tree_hash).
966git_format('t', abbreviated_tree_hash).
967git_format('P', parent_hashes).
968git_format('p', abbreviated_parent_hashes).
969
970git_format('an', author_name).
971git_format('aN', author_name_mailcap).
972git_format('ae', author_email).
973git_format('aE', author_email_mailcap).
974git_format('ad', author_date).
975git_format('aD', author_date_rfc2822).
976git_format('ar', author_date_relative).
977git_format('at', author_date_unix).
978git_format('ai', author_date_iso8601).
979
980git_format('cn', committer_name).
981git_format('cN', committer_name_mailcap).
982git_format('ce', committer_email).
983git_format('cE', committer_email_mailcap).
984git_format('cd', committer_date).
985git_format('cD', committer_date_rfc2822).
986git_format('cr', committer_date_relative).
987git_format('ct', committer_date_unix).
988git_format('ci', committer_date_iso8601).
989
990git_format('d', ref_names). 991git_format('e', encoding). 992
993git_format('s', subject).
994git_format('f', subject_sanitized).
995git_format('b', body).
996git_format('N', notes).
997
998git_format('gD', reflog_selector).
999git_format('gd', shortened_reflog_selector).
1000git_format('gs', reflog_subject).
1001
1002
1003 1006
1007:- multifile
1008 prolog:message//1. 1009
1010prolog:message(git(output(Codes))) -->
1011 { split_lines(Codes, Lines) },
1012 git_lines(Lines).
1013
1014git_lines([]) --> [].
1015git_lines([H|T]) -->
1016 [ '~s'-[H] ],
1017 ( {T==[]}
1018 -> []
1019 ; [nl], git_lines(T)
1020 ).
1021
1022split_lines([], []) :- !.
1023split_lines(All, [Line1|More]) :-
1024 append(Line1, [0'\n|Rest], All),
1025 !,
1026 split_lines(Rest, More).
1027split_lines(Line, [Line])