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(record),[record/1,current_record/2, op(_,_,record)]).
   56
   57:- autoload(library(apply),[maplist/3]).   58:- autoload(library(error),[must_be/2,existence_error/2]).   59:- autoload(library(filesex),
   60	    [directory_file_path/3,relative_file_name/3]).   61:- autoload(library(lists),[append/3,member/2,append/2]).   62:- autoload(library(option),[option/2,option/3,select_option/3]).   63:- autoload(library(process),[process_create/3,process_wait/2]).   64:- autoload(library(readutil),
   65	    [ read_stream_to_codes/3,
   66	      read_line_to_codes/2,
   67	      read_stream_to_codes/2
   68	    ]).   69:- autoload(library(dcg/basics),
   70	    [string//1,whites//0,string_without//2,blanks//0]).   71
   72
   73:- meta_predicate
   74    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. */

   87:- predicate_options(git/2, 2,
   88                     [ directory(atom),
   89                       error(-codes),
   90                       output(-codes),
   91                       status(-any),
   92                       askpass(any)
   93                     ]).   94:- predicate_options(git_default_branch/2, 2,
   95                     [ pass_to(git_process_output/3, 3)
   96                     ] ).   97:- predicate_options(git_describe/2, 2,
   98                     [ commit(atom),
   99                       directory(atom),
  100                       match(atom)
  101                     ]).  102:- predicate_options(git_hash/2, 2,
  103                     [ commit(atom),
  104                       directory(atom)
  105                     ]).  106:- predicate_options(git_ls_tree/2, 2,
  107                     [ commit(atom),
  108                       directory(atom)
  109                     ]).  110:- predicate_options(git_process_output/3, 3,
  111                     [ directory(atom),
  112                       askpass(any),
  113                       error(-codes)
  114                     ]).  115:- predicate_options(git_remote_url/3, 3,
  116                     [ pass_to(git_process_output/3, 3)
  117                     ]).  118:- predicate_options(git_shortlog/3, 3,
  119                     [ revisions(atom),
  120                       limit(nonneg),
  121                       path(atom)
  122                     ]).  123:- predicate_options(git_show/4, 4,
  124                     [ diff(oneof([patch,stat]))
  125                     ]).
 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
  143git(Argv, Options) :-
  144    git_cwd_options(Argv, Argv1, Options),
  145    env_options(Extra, Options),
  146    setup_call_cleanup(
  147        process_create(path(git), Argv1,
  148                       [ stdout(pipe(Out)),
  149                         stderr(pipe(Error)),
  150                         process(PID)
  151                       | Extra
  152                       ]),
  153        call_cleanup(
  154            ( read_stream_to_codes(Out, OutCodes, []),
  155              read_stream_to_codes(Error, ErrorCodes, [])
  156            ),
  157            process_wait(PID, Status)),
  158        close_streams([Out,Error])),
  159    print_error(ErrorCodes, Options),
  160    print_output(OutCodes, Options),
  161    (   option(status(Status0), Options)
  162    ->  Status = Status0
  163    ;   Status == exit(0)
  164    ->  true
  165    ;   throw(error(process_error(git(Argv), Status), _))
  166    ).
  167
  168git_cwd_options(Argv0, Argv, Options) :-
  169    option(directory(Dir), Options),
  170    !,
  171    Argv = ['-C', file(Dir) | Argv0 ].
  172git_cwd_options(Argv, Argv, _).
  173
  174env_options([env(['GIT_ASKPASS'=Program])], Options) :-
  175    option(askpass(Exe), Options),
  176    !,
  177    exe_options(ExeOptions),
  178    absolute_file_name(Exe, PlProg, ExeOptions),
  179    prolog_to_os_filename(PlProg, Program).
  180env_options([], _).
  181
  182exe_options(Options) :-
  183    current_prolog_flag(windows, true),
  184    !,
  185    Options = [ extensions(['',exe,com]), access(read) ].
  186exe_options(Options) :-
  187    Options = [ access(execute) ].
  188
  189print_output(OutCodes, Options) :-
  190    option(output(Codes), Options),
  191    !,
  192    Codes = OutCodes.
  193print_output([], _) :- !.
  194print_output(OutCodes, _) :-
  195    print_message(informational, git(output(OutCodes))).
  196
  197print_error(OutCodes, Options) :-
  198    option(error(Codes), Options),
  199    !,
  200    Codes = OutCodes.
  201print_error([], _) :- !.
  202print_error(OutCodes, _) :-
  203    phrase(classify_message(Level), OutCodes, _),
  204    print_message(Level, git(output(OutCodes))).
  205
  206classify_message(error) -->
  207    string(_), "fatal:",
  208    !.
  209classify_message(error) -->
  210    string(_), "error:",
  211    !.
  212classify_message(warning) -->
  213    string(_), "warning:",
  214    !.
  215classify_message(informational) -->
  216    [].
 close_streams(+Streams:list) is det
Close a list of streams, throwing the first error if some stream failed to close.
  223close_streams(List) :-
  224    phrase(close_streams(List), Errors),
  225    (   Errors = [Error|_]
  226    ->  throw(Error)
  227    ;   true
  228    ).
  229
  230close_streams([H|T]) -->
  231    { catch(close(H), E, true) },
  232    (   { var(E) }
  233    ->  []
  234    ;   [E]
  235    ),
  236    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).
  244git_process_output(Argv, OnOutput, Options) :-
  245    git_cwd_options(Argv, Argv1, Options),
  246    env_options(Extra, Options),
  247    setup_call_cleanup(
  248        process_create(path(git), Argv1,
  249                       [ stdout(pipe(Out)),
  250                         stderr(pipe(Error)),
  251                         process(PID)
  252                       | Extra
  253                       ]),
  254        call_cleanup(
  255            ( call(OnOutput, Out),
  256              read_stream_to_codes(Error, ErrorCodes, [])
  257            ),
  258            git_wait(PID, Out, Status)),
  259        close_streams([Out,Error])),
  260    print_error(ErrorCodes, Options),
  261    (   Status = exit(0)
  262    ->  true
  263    ;   throw(error(process_error(git, Status)))
  264    ).
  265
  266git_wait(PID, Out, Status) :-
  267    at_end_of_stream(Out),
  268    !,
  269    process_wait(PID, Status).
  270git_wait(PID, Out, Status) :-
  271    setup_call_cleanup(
  272        open_null_stream(Null),
  273        copy_stream_data(Out, Null),
  274        close(Null)),
  275    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.
  285git_open_file(Dir, File, Branch, In) :-
  286    atomic_list_concat([Branch, :, File], Ref),
  287    process_create(path(git),
  288                   [ '-C', file(Dir), show, Ref ],
  289                   [ stdout(pipe(In))
  290                   ]),
  291    set_stream(In, file_name(File)).
 is_git_directory(+Directory) is semidet
True if Directory is a git directory (Either checked out or bare).
  299is_git_directory(Directory) :-
  300    directory_file_path(Directory, '.git', GitDir),
  301    exists_directory(GitDir),
  302    !.
  303is_git_directory(Directory) :-
  304    exists_directory(Directory),
  305    git(['rev-parse', '--git-dir'],
  306        [ output(Codes),
  307          error(_),
  308          status(Status),
  309          directory(Directory)
  310        ]),
  311    Status == exit(0),
  312    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
  330git_describe(Version, Options) :-
  331    (   option(match(Pattern), Options)
  332    ->  true
  333    ;   git_version_pattern(Pattern)
  334    ),
  335    (   option(commit(Commit), Options)
  336    ->  Extra = [Commit]
  337    ;   Extra = []
  338    ),
  339    option(directory(Dir), Options, .),
  340    setup_call_cleanup(
  341        process_create(path(git),
  342                       [ 'describe',
  343                         '--match', Pattern
  344                       | Extra
  345                       ],
  346                       [ stdout(pipe(Out)),
  347                         stderr(null),
  348                         process(PID),
  349                         cwd(Dir)
  350                       ]),
  351        call_cleanup(
  352            read_stream_to_codes(Out, V0, []),
  353            git_wait(PID, Out, Status)),
  354        close(Out)),
  355    Status = exit(0),
  356    !,
  357    atom_codes(V1, V0),
  358    normalize_space(atom(Plain), V1),
  359    (   git_is_clean(Dir)
  360    ->  Version = Plain
  361    ;   atom_concat(Plain, '-DIRTY', Version)
  362    ).
  363git_describe(Version, Options) :-
  364    option(directory(Dir), Options, .),
  365    option(commit(Commit), Options, 'HEAD'),
  366    setup_call_cleanup(
  367        process_create(path(git),
  368                       [ 'rev-parse', '--short',
  369                         Commit
  370                       ],
  371                       [ stdout(pipe(Out)),
  372                         stderr(null),
  373                         process(PID),
  374                         cwd(Dir)
  375                       ]),
  376        call_cleanup(
  377            read_stream_to_codes(Out, V0, []),
  378            git_wait(PID, Out, Status)),
  379        close(Out)),
  380    Status = exit(0),
  381    atom_codes(V1, V0),
  382    normalize_space(atom(Plain), V1),
  383    (   git_is_clean(Dir)
  384    ->  Version = Plain
  385    ;   atom_concat(Plain, '-DIRTY', Version)
  386    ).
  387
  388
  389:- multifile
  390    git_version_pattern/1.  391
  392git_version_pattern('V*').
  393git_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.
  402git_is_clean(Dir) :-
  403    setup_call_cleanup(process_create(path(git), ['diff', '--stat'],
  404                                      [ stdout(pipe(Out)),
  405                                        stderr(null),
  406                                        cwd(Dir)
  407                                      ]),
  408                       stream_char_count(Out, Count),
  409                       close(Out)),
  410    Count == 0.
  411
  412stream_char_count(Out, Count) :-
  413    setup_call_cleanup(open_null_stream(Null),
  414                       (   copy_stream_data(Out, Null),
  415                           character_count(Null, Count)
  416                       ),
  417                       close(Null)).
 git_hash(-Hash, +Options) is det
Return the hash of the indicated object.
  424git_hash(Hash, Options) :-
  425    option(commit(Commit), Options, 'HEAD'),
  426    git_process_output(['rev-parse', '--verify', Commit],
  427                       read_hash(Hash),
  428                       Options).
  429
  430read_hash(Hash, Stream) :-
  431    read_line_to_codes(Stream, Line),
  432    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)
  444git_ls_tree(Entries, Options) :-
  445    option(commit(Commit), Options, 'HEAD'),
  446    git_process_output(['ls-tree', '-z', '-r', '-l', Commit],
  447                       read_tree(Entries),
  448                       Options).
  449
  450read_tree(Entries, Stream) :-
  451    read_stream_to_codes(Stream, Codes),
  452    phrase(ls_tree(Entries), Codes).
  453
  454ls_tree([H|T]) -->
  455    ls_entry(H),
  456    !,
  457    ls_tree(T).
  458ls_tree([]) --> [].
  459
  460ls_entry(object(Mode, Type, Hash, Size, Name)) -->
  461    string(MS), " ",
  462    string(TS), " ",
  463    string(HS), " ",
  464    string(SS), "\t",
  465    string(NS), [0],
  466    !,
  467    { number_codes(Mode, [0'0,0'o|MS]),
  468      atom_codes(Type, TS),
  469      atom_codes(Hash, HS),
  470      (   Type == blob
  471      ->  number_codes(Size, SS)
  472      ;   Size = 0          % actually '-', but 0 sums easier
  473      ),
  474      atom_codes(Name, NS)
  475    }.
 git_remote_url(+Remote, -URL, +Options) is det
URL is the remote (fetch) URL for the given Remote.
  482git_remote_url(Remote, URL, Options) :-
  483    git_process_output([remote, show, Remote],
  484                       read_url("Fetch URL:", URL),
  485                       Options).
  486
  487read_url(Tag, URL, In) :-
  488    repeat,
  489        read_line_to_codes(In, Line),
  490        (   Line == end_of_file
  491        ->  !, fail
  492        ;   phrase(url_codes(Tag, Codes), Line)
  493        ->  !, atom_codes(URL, Codes)
  494        ).
  495
  496url_codes(Tag, Rest) -->
  497    { string_codes(Tag, TagCodes) },
  498    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.
  520git_ls_remote(GitURL, Refs, Options) :-
  521    findall(O, ls_remote_option(Options, O), RemoteOptions),
  522    option(refs(LimitRefs), Options, []),
  523    must_be(list(atom), LimitRefs),
  524    append([ 'ls-remote' | RemoteOptions], [GitURL|LimitRefs], Argv),
  525    git_process_output(Argv, remote_refs(Refs), []).
  526
  527ls_remote_option(Options, '--heads') :-
  528    option(heads(true), Options).
  529ls_remote_option(Options, '--tags') :-
  530    option(tags(true), Options).
  531
  532remote_refs(Refs, Out) :-
  533    read_line_to_codes(Out, Line0),
  534    remote_refs(Line0, Out, Refs).
  535
  536remote_refs(end_of_file, _, []) :- !.
  537remote_refs(Line, Out, [Hash-Ref|Tail]) :-
  538    phrase(remote_ref(Hash,Ref), Line),
  539    read_line_to_codes(Out, Line1),
  540    remote_refs(Line1, Out, Tail).
  541
  542remote_ref(Hash, Ref) -->
  543    string_without("\t ", HashCodes),
  544    whites,
  545    string_without("\t ", RefCodes),
  546    { atom_codes(Hash, HashCodes),
  547      atom_codes(Ref, RefCodes)
  548    }.
 git_remote_branches(+GitURL, -Branches) is det
Exploit git_ls_remote/3 to fetch the branches from a remote repository without downloading it.
  556git_remote_branches(GitURL, Branches) :-
  557    git_ls_remote(GitURL, Refs, [heads(true)]),
  558    findall(B, (member(_-Head, Refs),
  559                atom_concat('refs/heads/', B, Head)),
  560            Branches).
 git_default_branch(-BranchName, +Options) is det
True when BranchName is the default branch of a repository.
  567git_default_branch(BranchName, Options) :-
  568    git_process_output([branch],
  569                       read_default_branch(BranchName),
  570                       Options).
  571
  572read_default_branch(BranchName, In) :-
  573    repeat,
  574        read_line_to_codes(In, Line),
  575        (   Line == end_of_file
  576        ->  !, fail
  577        ;   phrase(default_branch(Codes), Line)
  578        ->  !, atom_codes(BranchName, Codes)
  579        ).
  580
  581default_branch(Rest) -->
  582    "*", 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.
  592git_branches(Branches, Options) :-
  593    (   select_option(commit(Commit), Options, GitOptions)
  594    ->  Extra = ['--contains', Commit]
  595    ;   Extra = [],
  596        GitOptions = Options
  597    ),
  598    git_process_output([branch|Extra],
  599                       read_branches(Branches),
  600                       GitOptions).
  601
  602read_branches(Branches, In) :-
  603    read_line_to_codes(In, Line),
  604    (   Line == end_of_file
  605    ->  Branches = []
  606    ;   Line = [_,_|Codes],
  607        atom_codes(H, Codes),
  608        Branches = [H|T],
  609        read_branches(T, In)
  610    ).
 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/
  620git_tags_on_branch(Dir, Branch, Tags) :-
  621    git_process_output([ log, '--oneline', '--decorate', Branch ],
  622                       log_to_tags(Tags),
  623                       [ directory(Dir) ]).
  624
  625log_to_tags(Tags, Out) :-
  626    read_line_to_codes(Out, Line0),
  627    log_to_tags(Line0, Out, Tags, []).
  628
  629log_to_tags(end_of_file, _, Tags, Tags) :- !.
  630log_to_tags(Line, Out, Tags, Tail) :-
  631    phrase(tags_on_line(Tags, Tail1), Line),
  632    read_line_to_codes(Out, Line1),
  633    log_to_tags(Line1, Out, Tail1, Tail).
  634
  635tags_on_line(Tags, Tail) -->
  636    string_without(" ", _Hash),
  637    tags(Tags, Tail),
  638    skip_rest.
  639
  640tags(Tags, Tail) -->
  641    whites,
  642    "(",
  643    tag_list(Tags, Rest),
  644    !,
  645    tags(Rest, Tail).
  646tags(Tags, Tags) -->
  647    skip_rest.
  648
  649tag_list([H|T], Rest) -->
  650    "tag:", !, whites,
  651    string(Codes),
  652    (   ")"
  653    ->  { atom_codes(H, Codes),
  654          T = Rest
  655        }
  656    ;   ","
  657    ->  { atom_codes(H, Codes)
  658        },
  659        whites,
  660        tag_list(T, Rest)
  661    ).
  662tag_list(List, Rest) -->
  663    string(_),
  664    (   ")"
  665    ->  { List = Rest }
  666    ;   ","
  667    ->  whites,
  668        tag_list(List, Rest)
  669    ).
  670
  671skip_rest(_,_).
  672
  673
  674                 /*******************************
  675                 *        READ GIT HISTORY      *
  676                 *******************************/
 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.
  695:- record
  696    git_log(commit_hash:atom,
  697            author_name:atom,
  698            author_date_relative:atom,
  699            committer_name:atom,
  700            committer_date_relative:atom,
  701            committer_date_unix:integer,
  702            subject:atom,
  703            ref_names:list).  704
  705git_shortlog(Dir, ShortLog, Options) :-
  706    (   option(revisions(Range), Options)
  707    ->  RangeSpec = [Range]
  708    ;   option(limit(Limit), Options, 10),
  709        RangeSpec = ['-n', Limit]
  710    ),
  711    (   option(git_path(Path), Options)
  712    ->  Extra = ['--', Path]
  713    ;   option(path(Path), Options)
  714    ->  relative_file_name(Path, Dir, RelPath),
  715        Extra = ['--', RelPath]
  716    ;   Extra = []
  717    ),
  718    git_format_string(git_log, Fields, Format),
  719    append([[log, Format], RangeSpec, Extra], GitArgv),
  720    git_process_output(GitArgv,
  721                       read_git_formatted(git_log, Fields, ShortLog),
  722                       [directory(Dir)]).
  723
  724
  725read_git_formatted(Record, Fields, ShortLog, In) :-
  726    read_line_to_codes(In, Line0),
  727    read_git_formatted(Line0, In, Record, Fields, ShortLog).
  728
  729read_git_formatted(end_of_file, _, _, _, []) :- !.
  730read_git_formatted(Line, In, Record, Fields, [H|T]) :-
  731    record_from_line(Record, Fields, Line, H),
  732    read_line_to_codes(In, Line1),
  733    read_git_formatted(Line1, In, Record, Fields, T).
  734
  735record_from_line(RecordName, Fields, Line, Record) :-
  736    phrase(fields_from_line(Fields, Values), Line),
  737    Record =.. [RecordName|Values].
  738
  739fields_from_line([], []) --> [].
  740fields_from_line([F|FT], [V|VT]) -->
  741    to_nul_s(Codes),
  742    { field_to_prolog(F, Codes, V) },
  743    fields_from_line(FT, VT).
  744
  745to_nul_s([]) --> [0], !.
  746to_nul_s([H|T]) --> [H], to_nul_s(T).
  747
  748field_to_prolog(ref_names, Line, List) :-
  749    phrase(ref_names(List), Line),
  750    !.
  751field_to_prolog(committer_date_unix, Line, Stamp) :-
  752    !,
  753    number_codes(Stamp, Line).
  754field_to_prolog(_, Line, Atom) :-
  755    atom_codes(Atom, Line).
  756
  757ref_names([]) --> [].
  758ref_names(List) -->
  759    blanks, "(", ref_name_list(List), ")".
  760
  761ref_name_list([H|T]) -->
  762    string_without(",)", Codes),
  763    { atom_codes(H, Codes) },
  764    (   ",", blanks
  765    ->  ref_name_list(T)
  766    ;   {T=[]}
  767    ).
 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.
  783:- record
  784    git_commit(tree_hash:atom,
  785               parent_hashes:list,
  786               author_name:atom,
  787               author_date:atom,
  788               committer_name:atom,
  789               committer_date:atom,
  790               subject:atom).  791
  792git_show(Dir, Hash, Commit, Options) :-
  793    git_format_string(git_commit, Fields, Format),
  794    option(diff(Diff), Options, patch),
  795    diff_arg(Diff, DiffArg),
  796    git_process_output([ show, DiffArg, Hash, Format ],
  797                       read_commit(Fields, Commit, Options),
  798                       [directory(Dir)]).
  799
  800diff_arg(patch, '-p').
  801diff_arg(stat, '--stat').
  802
  803read_commit(Fields, Data-Body, Options, In) :-
  804    read_line_to_codes(In, Line1),
  805    record_from_line(git_commit, Fields, Line1, Data),
  806    read_line_to_codes(In, Line2),
  807    (   Line2 == []
  808    ->  option(max_lines(Max), Options, -1),
  809        read_n_lines(In, Max, Body)
  810    ;   Line2 == end_of_file
  811    ->  Body = []
  812    ).
  813
  814read_n_lines(In, Max, Lines) :-
  815    read_line_to_codes(In, Line1),
  816    read_n_lines(Line1, Max, In, Lines).
  817
  818read_n_lines(end_of_file, _, _, []) :- !.
  819read_n_lines(_, 0, In, []) :-
  820    !,
  821    setup_call_cleanup(open_null_stream(Out),
  822                       copy_stream_data(In, Out),
  823                       close(Out)).
  824read_n_lines(Line, Max0, In, [Line|More]) :-
  825    read_line_to_codes(In, Line2),
  826    Max is Max0-1,
  827    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.
  837:- meta_predicate
  838    git_format_string(:, -, -).  839
  840git_format_string(M:RecordName, Fields, Format) :-
  841    current_record(RecordName, M:Term),
  842    findall(F, record_field(Term, F), Fields),
  843    maplist(git_field_format, Fields, Formats),
  844    atomic_list_concat(['--format='|Formats], Format).
  845
  846record_field(Term, Name) :-
  847    arg(_, Term, Field),
  848    field_name(Field, Name).
  849
  850field_name(Name:_Type=_Default, Name) :- !.
  851field_name(Name:_Type, Name) :- !.
  852field_name(Name=_Default, Name) :- !.
  853field_name(Name, Name).
  854
  855git_field_format(Field, Fmt) :-
  856    (   git_format(NoPercent, Field)
  857    ->  atomic_list_concat(['%', NoPercent, '%x00'], Fmt)
  858    ;   existence_error(git_format, Field)
  859    ).
  860
  861git_format('H', commit_hash).
  862git_format('h', abbreviated_commit_hash).
  863git_format('T', tree_hash).
  864git_format('t', abbreviated_tree_hash).
  865git_format('P', parent_hashes).
  866git_format('p', abbreviated_parent_hashes).
  867
  868git_format('an', author_name).
  869git_format('aN', author_name_mailcap).
  870git_format('ae', author_email).
  871git_format('aE', author_email_mailcap).
  872git_format('ad', author_date).
  873git_format('aD', author_date_rfc2822).
  874git_format('ar', author_date_relative).
  875git_format('at', author_date_unix).
  876git_format('ai', author_date_iso8601).
  877
  878git_format('cn', committer_name).
  879git_format('cN', committer_name_mailcap).
  880git_format('ce', committer_email).
  881git_format('cE', committer_email_mailcap).
  882git_format('cd', committer_date).
  883git_format('cD', committer_date_rfc2822).
  884git_format('cr', committer_date_relative).
  885git_format('ct', committer_date_unix).
  886git_format('ci', committer_date_iso8601).
  887
  888git_format('d', ref_names).             % git log?
  889git_format('e', encoding).              % git log?
  890
  891git_format('s', subject).
  892git_format('f', subject_sanitized).
  893git_format('b', body).
  894git_format('N', notes).
  895
  896git_format('gD', reflog_selector).
  897git_format('gd', shortened_reflog_selector).
  898git_format('gs', reflog_subject).
  899
  900
  901                 /*******************************
  902                 *            MESSAGES          *
  903                 *******************************/
  904
  905:- multifile
  906    prolog:message//1.  907
  908prolog:message(git(output(Codes))) -->
  909    { split_lines(Codes, Lines) },
  910    git_lines(Lines).
  911
  912git_lines([]) --> [].
  913git_lines([H|T]) -->
  914    [ '~s'-[H] ],
  915    (   {T==[]}
  916    ->  []
  917    ;   [nl], git_lines(T)
  918    ).
  919
  920split_lines([], []) :- !.
  921split_lines(All, [Line1|More]) :-
  922    append(Line1, [0'\n|Rest], All),
  923    !,
  924    split_lines(Rest, More).
  925split_lines(Line, [Line])