View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c) 2010-2013, University of Amsterdam,
    7                             VU University
    8    Amsterdam All rights reserved.
    9
   10    Redistribution and use in source and binary forms, with or without
   11    modification, are permitted provided that the following conditions
   12    are met:
   13
   14    1. Redistributions of source code must retain the above copyright
   15       notice, this list of conditions and the following disclaimer.
   16
   17    2. Redistributions in binary form must reproduce the above copyright
   18       notice, this list of conditions and the following disclaimer in
   19       the documentation and/or other materials provided with the
   20       distribution.
   21
   22    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   23    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   24    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   25    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   26    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   27    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   28    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   29    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   30    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   31    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   32    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   33    POSSIBILITY OF SUCH DAMAGE.
   34*/
   35
   36:- module(git,
   37          [ git/2,                      % +Argv, +Options
   38            git_process_output/3,       % +Argv, :OnOutput, +Options
   39            git_open_file/4,            % +Dir, +File, +Branch, -Stream
   40            is_git_directory/1,         % +Dir
   41            git_describe/2,             % -Version, +Options
   42            git_hash/2,                 % -Hash, +Options
   43            git_ls_tree/2,              % -Content, +Options
   44            git_remote_url/3,           % +Remote, -URL, +Options
   45            git_ls_remote/3,            % +GitURL, -Refs, +Options
   46            git_branches/2,             % -Branches, +Options
   47            git_remote_branches/2,      % +GitURL, -Branches
   48            git_default_branch/2,       % -DefaultBranch, +Options
   49            git_tags_on_branch/3,       % +Dir, +Branch, -Tags
   50            git_shortlog/3,             % +Dir, -Shortlog, +Options
   51            git_log_data/3,             % +Field, +Record, -Value
   52            git_show/4,                 % +Dir, +Hash, -Commit, +Options
   53            git_commit_data/3           % +Field, +Record, -Value
   54          ]).   55:- use_module(library(process)).   56:- use_module(library(readutil)).   57:- use_module(library(option)).   58:- use_module(library(dcg/basics)).   59:- use_module(library(record)).   60:- use_module(library(lists)).   61:- use_module(library(error)).   62
   63:- meta_predicate
   64    git_process_output(+, 1, +).

Run GIT commands

This module performs common GIT tasks by calling git as a remote process through process_create/3. It requires that the git executable is in the current PATH.

This module started life in ClioPatria and has been used by the Prolog web-server to provide information on git repositories. It is now moved into the core Prolog library to support the Prolog package manager. */

   77:- predicate_options(git/2, 2,
   78                     [ directory(atom),
   79                       error(-codes),
   80                       output(-codes),
   81                       status(-any),
   82                       askpass(any)
   83                     ]).   84:- predicate_options(git_default_branch/2, 2,
   85                     [ pass_to(git_process_output/3, 3)
   86                     ] ).   87:- predicate_options(git_describe/2, 2,
   88                     [ commit(atom),
   89                       directory(atom),
   90                       match(atom)
   91                     ]).   92:- predicate_options(git_hash/2, 2,
   93                     [ commit(atom),
   94                       directory(atom)
   95                     ]).   96:- predicate_options(git_ls_tree/2, 2,
   97                     [ commit(atom),
   98                       directory(atom)
   99                     ]).  100:- predicate_options(git_process_output/3, 3,
  101                     [ directory(atom),
  102                       askpass(any),
  103                       error(-codes)
  104                     ]).  105:- predicate_options(git_remote_url/3, 3,
  106                     [ pass_to(git_process_output/3, 3)
  107                     ]).  108:- predicate_options(git_shortlog/3, 3,
  109                     [ revisions(atom),
  110                       limit(nonneg),
  111                       path(atom)
  112                     ]).  113:- predicate_options(git_show/4, 4,
  114                     [ diff(oneof([patch,stat]))
  115                     ]).
 git(+Argv, +Options) is det
Run a GIT command. Defined options:
directory(+Dir)
Execute in the given directory
output(-Out)
Unify Out with a list of codes representing stdout of the command. Otherwise the output is handed to print_message/2 with level informational.
error(-Error)
As output(Out), but messages are printed at level error.
askpass(+Program)
Export GIT_ASKPASS=Program
  133git(Argv, Options) :-
  134    git_cwd_options(Argv, Argv1, Options),
  135    env_options(Extra, Options),
  136    setup_call_cleanup(
  137        process_create(path(git), Argv1,
  138                       [ stdout(pipe(Out)),
  139                         stderr(pipe(Error)),
  140                         process(PID)
  141                       | Extra
  142                       ]),
  143        call_cleanup(
  144            ( read_stream_to_codes(Out, OutCodes, []),
  145              read_stream_to_codes(Error, ErrorCodes, [])
  146            ),
  147            process_wait(PID, Status)),
  148        close_streams([Out,Error])),
  149    print_error(ErrorCodes, Options),
  150    print_output(OutCodes, Options),
  151    (   option(status(Status0), Options)
  152    ->  Status = Status0
  153    ;   Status == exit(0)
  154    ->  true
  155    ;   throw(error(process_error(git(Argv), Status), _))
  156    ).
  157
  158git_cwd_options(Argv0, Argv, Options) :-
  159    option(directory(Dir), Options),
  160    !,
  161    Argv = ['-C', file(Dir) | Argv0 ].
  162git_cwd_options(Argv, Argv, _).
  163
  164env_options([env(['GIT_ASKPASS'=Program])], Options) :-
  165    option(askpass(Exe), Options),
  166    !,
  167    exe_options(ExeOptions),
  168    absolute_file_name(Exe, PlProg, ExeOptions),
  169    prolog_to_os_filename(PlProg, Program).
  170env_options([], _).
  171
  172exe_options(Options) :-
  173    current_prolog_flag(windows, true),
  174    !,
  175    Options = [ extensions(['',exe,com]), access(read) ].
  176exe_options(Options) :-
  177    Options = [ access(execute) ].
  178
  179print_output(OutCodes, Options) :-
  180    option(output(Codes), Options),
  181    !,
  182    Codes = OutCodes.
  183print_output([], _) :- !.
  184print_output(OutCodes, _) :-
  185    print_message(informational, git(output(OutCodes))).
  186
  187print_error(OutCodes, Options) :-
  188    option(error(Codes), Options),
  189    !,
  190    Codes = OutCodes.
  191print_error([], _) :- !.
  192print_error(OutCodes, _) :-
  193    phrase(classify_message(Level), OutCodes, _),
  194    print_message(Level, git(output(OutCodes))).
  195
  196classify_message(error) -->
  197    string(_), "fatal:",
  198    !.
  199classify_message(error) -->
  200    string(_), "error:",
  201    !.
  202classify_message(warning) -->
  203    string(_), "warning:",
  204    !.
  205classify_message(informational) -->
  206    [].
 close_streams(+Streams:list) is det
Close a list of streams, throwing the first error if some stream failed to close.
  213close_streams(List) :-
  214    phrase(close_streams(List), Errors),
  215    (   Errors = [Error|_]
  216    ->  throw(Error)
  217    ;   true
  218    ).
  219
  220close_streams([H|T]) -->
  221    { catch(close(H), E, true) },
  222    (   { var(E) }
  223    ->  []
  224    ;   [E]
  225    ),
  226    close_streams(T).
 git_process_output(+Argv, :OnOutput, +Options) is det
Run a git-command and process the output with OnOutput, which is called as call(OnOutput, Stream).
  234git_process_output(Argv, OnOutput, Options) :-
  235    git_cwd_options(Argv, Argv1, Options),
  236    env_options(Extra, Options),
  237    setup_call_cleanup(
  238        process_create(path(git), Argv1,
  239                       [ stdout(pipe(Out)),
  240                         stderr(pipe(Error)),
  241                         process(PID)
  242                       | Extra
  243                       ]),
  244        call_cleanup(
  245            ( call(OnOutput, Out),
  246              read_stream_to_codes(Error, ErrorCodes, [])
  247            ),
  248            git_wait(PID, Out, Status)),
  249        close_streams([Out,Error])),
  250    print_error(ErrorCodes, Options),
  251    (   Status = exit(0)
  252    ->  true
  253    ;   throw(error(process_error(git, Status)))
  254    ).
  255
  256git_wait(PID, Out, Status) :-
  257    at_end_of_stream(Out),
  258    !,
  259    process_wait(PID, Status).
  260git_wait(PID, Out, Status) :-
  261    setup_call_cleanup(
  262        open_null_stream(Null),
  263        copy_stream_data(Out, Null),
  264        close(Null)),
  265    process_wait(PID, Status).
 git_open_file(+GitRepoDir, +File, +Branch, -Stream) is det
Open the file File in the given bare GIT repository on the given branch (treeisch).
bug
- We cannot tell whether opening failed for some reason.
  275git_open_file(Dir, File, Branch, In) :-
  276    atomic_list_concat([Branch, :, File], Ref),
  277    process_create(path(git),
  278                   [ '-C', file(Dir), show, Ref ],
  279                   [ stdout(pipe(In))
  280                   ]),
  281    set_stream(In, file_name(File)).
 is_git_directory(+Directory) is semidet
True if Directory is a git directory (Either checked out or bare).
  289is_git_directory(Directory) :-
  290    directory_file_path(Directory, '.git', GitDir),
  291    exists_directory(GitDir),
  292    !.
  293is_git_directory(Directory) :-
  294    exists_directory(Directory),
  295    git(['rev-parse', '--git-dir'],
  296        [ output(Codes),
  297          error(_),
  298          status(Status),
  299          directory(Directory)
  300        ]),
  301    Status == exit(0),
  302    string_codes(".\n", Codes).
 git_describe(-Version, +Options) is semidet
Describe the running version based on GIT tags and hashes. Options:
match(+Pattern)
Only use tags that match Pattern (a Unix glob-pattern; e.g. V*)
directory(Dir)
Provide the version-info for a directory that is part of a GIT-repository.
commit(+Commit)
Describe Commit rather than HEAD
See also
- git describe
  320git_describe(Version, Options) :-
  321    (   option(match(Pattern), Options)
  322    ->  true
  323    ;   git_version_pattern(Pattern)
  324    ),
  325    (   option(commit(Commit), Options)
  326    ->  Extra = [Commit]
  327    ;   Extra = []
  328    ),
  329    option(directory(Dir), Options, .),
  330    setup_call_cleanup(
  331        process_create(path(git),
  332                       [ 'describe',
  333                         '--match', Pattern
  334                       | Extra
  335                       ],
  336                       [ stdout(pipe(Out)),
  337                         stderr(null),
  338                         process(PID),
  339                         cwd(Dir)
  340                       ]),
  341        call_cleanup(
  342            read_stream_to_codes(Out, V0, []),
  343            git_wait(PID, Out, Status)),
  344        close(Out)),
  345    Status = exit(0),
  346    !,
  347    atom_codes(V1, V0),
  348    normalize_space(atom(Plain), V1),
  349    (   git_is_clean(Dir)
  350    ->  Version = Plain
  351    ;   atom_concat(Plain, '-DIRTY', Version)
  352    ).
  353git_describe(Version, Options) :-
  354    option(directory(Dir), Options, .),
  355    option(commit(Commit), Options, 'HEAD'),
  356    setup_call_cleanup(
  357        process_create(path(git),
  358                       [ 'rev-parse', '--short',
  359                         Commit
  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    atom_codes(V1, V0),
  372    normalize_space(atom(Plain), V1),
  373    (   git_is_clean(Dir)
  374    ->  Version = Plain
  375    ;   atom_concat(Plain, '-DIRTY', Version)
  376    ).
  377
  378
  379:- multifile
  380    git_version_pattern/1.  381
  382git_version_pattern('V*').
  383git_version_pattern('*').
 git_is_clean(+Dir) is semidet
True if the given directory is in a git module and this module is clean. To us, clean only implies that git diff produces no output.
  392git_is_clean(Dir) :-
  393    setup_call_cleanup(process_create(path(git), ['diff', '--stat'],
  394                                      [ stdout(pipe(Out)),
  395                                        stderr(null),
  396                                        cwd(Dir)
  397                                      ]),
  398                       stream_char_count(Out, Count),
  399                       close(Out)),
  400    Count == 0.
  401
  402stream_char_count(Out, Count) :-
  403    setup_call_cleanup(open_null_stream(Null),
  404                       (   copy_stream_data(Out, Null),
  405                           character_count(Null, Count)
  406                       ),
  407                       close(Null)).
 git_hash(-Hash, +Options) is det
Return the hash of the indicated object.
  414git_hash(Hash, Options) :-
  415    option(commit(Commit), Options, 'HEAD'),
  416    git_process_output(['rev-parse', '--verify', Commit],
  417                       read_hash(Hash),
  418                       Options).
  419
  420read_hash(Hash, Stream) :-
  421    read_line_to_codes(Stream, Line),
  422    atom_codes(Hash, Line).
 git_ls_tree(-Entries, +Options) is det
True when Entries is a list of entries in the the GIT repository, Each entry is a term:
object(Mode, Type, Hash, Size, Name)
  434git_ls_tree(Entries, Options) :-
  435    option(commit(Commit), Options, 'HEAD'),
  436    git_process_output(['ls-tree', '-z', '-r', '-l', Commit],
  437                       read_tree(Entries),
  438                       Options).
  439
  440read_tree(Entries, Stream) :-
  441    read_stream_to_codes(Stream, Codes),
  442    phrase(ls_tree(Entries), Codes).
  443
  444ls_tree([H|T]) -->
  445    ls_entry(H),
  446    !,
  447    ls_tree(T).
  448ls_tree([]) --> [].
  449
  450ls_entry(object(Mode, Type, Hash, Size, Name)) -->
  451    string(MS), " ",
  452    string(TS), " ",
  453    string(HS), " ",
  454    string(SS), "\t",
  455    string(NS), [0],
  456    !,
  457    { number_codes(Mode, [0'0,0'o|MS]),
  458      atom_codes(Type, TS),
  459      atom_codes(Hash, HS),
  460      (   Type == blob
  461      ->  number_codes(Size, SS)
  462      ;   Size = 0          % actually '-', but 0 sums easier
  463      ),
  464      atom_codes(Name, NS)
  465    }.
 git_remote_url(+Remote, -URL, +Options) is det
URL is the remote (fetch) URL for the given Remote.
  472git_remote_url(Remote, URL, Options) :-
  473    git_process_output([remote, show, Remote],
  474                       read_url("Fetch URL:", URL),
  475                       Options).
  476
  477read_url(Tag, URL, In) :-
  478    repeat,
  479        read_line_to_codes(In, Line),
  480        (   Line == end_of_file
  481        ->  !, fail
  482        ;   phrase(url_codes(Tag, Codes), Line)
  483        ->  !, atom_codes(URL, Codes)
  484        ).
  485
  486url_codes(Tag, Rest) -->
  487    { string_codes(Tag, TagCodes) },
  488    whites, string(TagCodes), whites, string(Rest).
 git_ls_remote(+GitURL, -Refs, +Options) is det
Execute git ls-remote against the remote repository to fetch references from the remote. Options processed:

For example, to find the hash of the remote HEAD, one can use

?- git_ls_remote('git://www.swi-prolog.org/home/pl/git/pl-devel.git',
                 Refs, [refs(['HEAD'])]).
Refs = ['5d596c52aa969d88e7959f86327f5c7ff23695f3'-'HEAD'].
Arguments:
Refs- is a list of pairs hash-name.
  510git_ls_remote(GitURL, Refs, Options) :-
  511    findall(O, ls_remote_option(Options, O), RemoteOptions),
  512    option(refs(LimitRefs), Options, []),
  513    must_be(list(atom), LimitRefs),
  514    append([ 'ls-remote' | RemoteOptions], [GitURL|LimitRefs], Argv),
  515    git_process_output(Argv, remote_refs(Refs), []).
  516
  517ls_remote_option(Options, '--heads') :-
  518    option(heads(true), Options).
  519ls_remote_option(Options, '--tags') :-
  520    option(tags(true), Options).
  521
  522remote_refs(Refs, Out) :-
  523    read_line_to_codes(Out, Line0),
  524    remote_refs(Line0, Out, Refs).
  525
  526remote_refs(end_of_file, _, []) :- !.
  527remote_refs(Line, Out, [Hash-Ref|Tail]) :-
  528    phrase(remote_ref(Hash,Ref), Line),
  529    read_line_to_codes(Out, Line1),
  530    remote_refs(Line1, Out, Tail).
  531
  532remote_ref(Hash, Ref) -->
  533    string_without("\t ", HashCodes),
  534    whites,
  535    string_without("\t ", RefCodes),
  536    { atom_codes(Hash, HashCodes),
  537      atom_codes(Ref, RefCodes)
  538    }.
 git_remote_branches(+GitURL, -Branches) is det
Exploit git_ls_remote/3 to fetch the branches from a remote repository without downloading it.
  546git_remote_branches(GitURL, Branches) :-
  547    git_ls_remote(GitURL, Refs, [heads(true)]),
  548    findall(B, (member(_-Head, Refs),
  549                atom_concat('refs/heads/', B, Head)),
  550            Branches).
 git_default_branch(-BranchName, +Options) is det
True when BranchName is the default branch of a repository.
  557git_default_branch(BranchName, Options) :-
  558    git_process_output([branch],
  559                       read_default_branch(BranchName),
  560                       Options).
  561
  562read_default_branch(BranchName, In) :-
  563    repeat,
  564        read_line_to_codes(In, Line),
  565        (   Line == end_of_file
  566        ->  !, fail
  567        ;   phrase(default_branch(Codes), Line)
  568        ->  !, atom_codes(BranchName, Codes)
  569        ).
  570
  571default_branch(Rest) -->
  572    "*", whites, string(Rest).
 git_branches(-Branches, +Options) is det
True when Branches is the list of branches in the repository. In addition to the usual options, this processes:
contains(Commit)
Return only branches that contain Commit.
  582git_branches(Branches, Options) :-
  583    (   select_option(commit(Commit), Options, GitOptions)
  584    ->  Extra = ['--contains', Commit]
  585    ;   Extra = [],
  586        GitOptions = Options
  587    ),
  588    git_process_output([branch|Extra],
  589                       read_branches(Branches),
  590                       GitOptions).
  591
  592read_branches(Branches, In) :-
  593    read_line_to_codes(In, Line),
  594    (   Line == end_of_file
  595    ->  Branches = []
  596    ;   Line = [_,_|Codes],
  597        atom_codes(H, Codes),
  598        Branches = [H|T],
  599        read_branches(T, In)
  600    ).
 git_tags_on_branch(+Dir, +Branch, -Tags) is det
Tags is a list of tags in Branch on the GIT repository Dir, most recent tag first.
See also
- Git tricks at http://mislav.uniqpath.com/2010/07/git-tips/
  610git_tags_on_branch(Dir, Branch, Tags) :-
  611    git_process_output([ log, '--oneline', '--decorate', Branch ],
  612                       log_to_tags(Tags),
  613                       [ directory(Dir) ]).
  614
  615log_to_tags(Tags, Out) :-
  616    read_line_to_codes(Out, Line0),
  617    log_to_tags(Line0, Out, Tags, []).
  618
  619log_to_tags(end_of_file, _, Tags, Tags) :- !.
  620log_to_tags(Line, Out, Tags, Tail) :-
  621    phrase(tags_on_line(Tags, Tail1), Line),
  622    read_line_to_codes(Out, Line1),
  623    log_to_tags(Line1, Out, Tail1, Tail).
  624
  625tags_on_line(Tags, Tail) -->
  626    string_without(" ", _Hash),
  627    tags(Tags, Tail),
  628    skip_rest.
  629
  630tags(Tags, Tail) -->
  631    whites,
  632    "(",
  633    tag_list(Tags, Rest),
  634    !,
  635    tags(Rest, Tail).
  636tags(Tags, Tags) -->
  637    skip_rest.
  638
  639tag_list([H|T], Rest) -->
  640    "tag:", !, whites,
  641    string(Codes),
  642    (   ")"
  643    ->  { atom_codes(H, Codes),
  644          T = Rest
  645        }
  646    ;   ","
  647    ->  { atom_codes(H, Codes)
  648        },
  649        whites,
  650        tag_list(T, Rest)
  651    ).
  652tag_list(List, Rest) -->
  653    string(_),
  654    (   ")"
  655    ->  { List = Rest }
  656    ;   ","
  657    ->  whites,
  658        tag_list(List, Rest)
  659    ).
  660
  661skip_rest(_,_).
  662
  663
  664                 /*******************************
  665                 *        READ GIT HISTORY      *
  666                 *******************************/
 git_shortlog(+Dir, -ShortLog, +Options) is det
Fetch information like the GitWeb change overview. Processed options:
limit(+Count)
Maximum number of commits to show (default is 10)
revisions(+Revisions)
Git revision specification
path(+Path)
Only show commits that affect Path. Path is the path of a checked out file.
git_path(+Path)
Similar to path, but Path is relative to the repository.
Arguments:
ShortLog- is a list of git_log records.
  685:- record
  686    git_log(commit_hash:atom,
  687            author_name:atom,
  688            author_date_relative:atom,
  689            committer_name:atom,
  690            committer_date_relative:atom,
  691            committer_date_unix:integer,
  692            subject:atom,
  693            ref_names:list).  694
  695git_shortlog(Dir, ShortLog, Options) :-
  696    (   option(revisions(Range), Options)
  697    ->  RangeSpec = [Range]
  698    ;   option(limit(Limit), Options, 10),
  699        RangeSpec = ['-n', Limit]
  700    ),
  701    (   option(git_path(Path), Options)
  702    ->  Extra = ['--', Path]
  703    ;   option(path(Path), Options)
  704    ->  relative_file_name(Path, Dir, RelPath),
  705        Extra = ['--', RelPath]
  706    ;   Extra = []
  707    ),
  708    git_format_string(git_log, Fields, Format),
  709    append([[log, Format], RangeSpec, Extra], GitArgv),
  710    git_process_output(GitArgv,
  711                       read_git_formatted(git_log, Fields, ShortLog),
  712                       [directory(Dir)]).
  713
  714
  715read_git_formatted(Record, Fields, ShortLog, In) :-
  716    read_line_to_codes(In, Line0),
  717    read_git_formatted(Line0, In, Record, Fields, ShortLog).
  718
  719read_git_formatted(end_of_file, _, _, _, []) :- !.
  720read_git_formatted(Line, In, Record, Fields, [H|T]) :-
  721    record_from_line(Record, Fields, Line, H),
  722    read_line_to_codes(In, Line1),
  723    read_git_formatted(Line1, In, Record, Fields, T).
  724
  725record_from_line(RecordName, Fields, Line, Record) :-
  726    phrase(fields_from_line(Fields, Values), Line),
  727    Record =.. [RecordName|Values].
  728
  729fields_from_line([], []) --> [].
  730fields_from_line([F|FT], [V|VT]) -->
  731    to_nul_s(Codes),
  732    { field_to_prolog(F, Codes, V) },
  733    fields_from_line(FT, VT).
  734
  735to_nul_s([]) --> [0], !.
  736to_nul_s([H|T]) --> [H], to_nul_s(T).
  737
  738field_to_prolog(ref_names, Line, List) :-
  739    phrase(ref_names(List), Line),
  740    !.
  741field_to_prolog(committer_date_unix, Line, Stamp) :-
  742    !,
  743    number_codes(Stamp, Line).
  744field_to_prolog(_, Line, Atom) :-
  745    atom_codes(Atom, Line).
  746
  747ref_names([]) --> [].
  748ref_names(List) -->
  749    blanks, "(", ref_name_list(List), ")".
  750
  751ref_name_list([H|T]) -->
  752    string_without(",)", Codes),
  753    { atom_codes(H, Codes) },
  754    (   ",", blanks
  755    ->  ref_name_list(T)
  756    ;   {T=[]}
  757    ).
 git_show(+Dir, +Hash, -Commit, +Options) is det
Fetch info from a GIT commit. Options processed:
diff(Diff)
GIT option on how to format diffs. E.g. stat
max_lines(Count)
Truncate the body at Count lines.
Arguments:
Commit- is a term git_commit(...)-Body. Body is currently a list of lines, each line represented as a list of codes.
  773:- record
  774    git_commit(tree_hash:atom,
  775               parent_hashes:list,
  776               author_name:atom,
  777               author_date:atom,
  778               committer_name:atom,
  779               committer_date:atom,
  780               subject:atom).  781
  782git_show(Dir, Hash, Commit, Options) :-
  783    git_format_string(git_commit, Fields, Format),
  784    option(diff(Diff), Options, patch),
  785    diff_arg(Diff, DiffArg),
  786    git_process_output([ show, DiffArg, Hash, Format ],
  787                       read_commit(Fields, Commit, Options),
  788                       [directory(Dir)]).
  789
  790diff_arg(patch, '-p').
  791diff_arg(stat, '--stat').
  792
  793read_commit(Fields, Data-Body, Options, In) :-
  794    read_line_to_codes(In, Line1),
  795    record_from_line(git_commit, Fields, Line1, Data),
  796    read_line_to_codes(In, Line2),
  797    (   Line2 == []
  798    ->  option(max_lines(Max), Options, -1),
  799        read_n_lines(In, Max, Body)
  800    ;   Line2 == end_of_file
  801    ->  Body = []
  802    ).
  803
  804read_n_lines(In, Max, Lines) :-
  805    read_line_to_codes(In, Line1),
  806    read_n_lines(Line1, Max, In, Lines).
  807
  808read_n_lines(end_of_file, _, _, []) :- !.
  809read_n_lines(_, 0, In, []) :-
  810    !,
  811    setup_call_cleanup(open_null_stream(Out),
  812                       copy_stream_data(In, Out),
  813                       close(Out)).
  814read_n_lines(Line, Max0, In, [Line|More]) :-
  815    read_line_to_codes(In, Line2),
  816    Max is Max0-1,
  817    read_n_lines(Line2, Max, In, More).
 git_format_string(:Record, -FieldNames, -Format)
If Record is a record with fields whose names match the GIT format field-names, Format is a git --format= argument with the appropriate format-specifiers, terminated by %x00, which causes the actual field to be 0-terminated.
  827:- meta_predicate
  828    git_format_string(:, -, -).  829
  830git_format_string(M:RecordName, Fields, Format) :-
  831    current_record(RecordName, M:Term),
  832    findall(F, record_field(Term, F), Fields),
  833    maplist(git_field_format, Fields, Formats),
  834    atomic_list_concat(['--format='|Formats], Format).
  835
  836record_field(Term, Name) :-
  837    arg(_, Term, Field),
  838    field_name(Field, Name).
  839
  840field_name(Name:_Type=_Default, Name) :- !.
  841field_name(Name:_Type, Name) :- !.
  842field_name(Name=_Default, Name) :- !.
  843field_name(Name, Name).
  844
  845git_field_format(Field, Fmt) :-
  846    (   git_format(NoPercent, Field)
  847    ->  atomic_list_concat(['%', NoPercent, '%x00'], Fmt)
  848    ;   existence_error(git_format, Field)
  849    ).
  850
  851git_format('H', commit_hash).
  852git_format('h', abbreviated_commit_hash).
  853git_format('T', tree_hash).
  854git_format('t', abbreviated_tree_hash).
  855git_format('P', parent_hashes).
  856git_format('p', abbreviated_parent_hashes).
  857
  858git_format('an', author_name).
  859git_format('aN', author_name_mailcap).
  860git_format('ae', author_email).
  861git_format('aE', author_email_mailcap).
  862git_format('ad', author_date).
  863git_format('aD', author_date_rfc2822).
  864git_format('ar', author_date_relative).
  865git_format('at', author_date_unix).
  866git_format('ai', author_date_iso8601).
  867
  868git_format('cn', committer_name).
  869git_format('cN', committer_name_mailcap).
  870git_format('ce', committer_email).
  871git_format('cE', committer_email_mailcap).
  872git_format('cd', committer_date).
  873git_format('cD', committer_date_rfc2822).
  874git_format('cr', committer_date_relative).
  875git_format('ct', committer_date_unix).
  876git_format('ci', committer_date_iso8601).
  877
  878git_format('d', ref_names).             % git log?
  879git_format('e', encoding).              % git log?
  880
  881git_format('s', subject).
  882git_format('f', subject_sanitized).
  883git_format('b', body).
  884git_format('N', notes).
  885
  886git_format('gD', reflog_selector).
  887git_format('gd', shortened_reflog_selector).
  888git_format('gs', reflog_subject).
  889
  890
  891                 /*******************************
  892                 *            MESSAGES          *
  893                 *******************************/
  894
  895:- multifile
  896    prolog:message//1.  897
  898prolog:message(git(output(Codes))) -->
  899    { split_lines(Codes, Lines) },
  900    git_lines(Lines).
  901
  902git_lines([]) --> [].
  903git_lines([H|T]) -->
  904    [ '~s'-[H] ],
  905    (   {T==[]}
  906    ->  []
  907    ;   [nl], git_lines(T)
  908    ).
  909
  910split_lines([], []) :- !.
  911split_lines(All, [Line1|More]) :-
  912    append(Line1, [0'\n|Rest], All),
  913    !,
  914    split_lines(Rest, More).
  915split_lines(Line, [Line])