35
36:- module(gitty,
37 [ gitty_open/2, 38 gitty_close/1, 39 gitty_driver/2, 40
41 gitty_file/3, 42 gitty_file/4, 43 gitty_create/5, 44 gitty_update/5, 45 gitty_commit/3, 46 gitty_plain_commit/3, 47 gitty_data/4, 48 gitty_history/4, 49 gitty_hash/2, 50
51 gitty_fsck/1, 52 gitty_save/4, 53 gitty_load/4, 54
55 gitty_reserved_meta/1, 56 is_gitty_hash/1, 57
58 gitty_diff/4, 59
60 data_diff/3, 61 udiff_string/2 62 ]). 63:- use_module(library(sha)). 64:- use_module(library(lists)). 65:- use_module(library(apply)). 66:- use_module(library(option)). 67:- use_module(library(process)). 68:- use_module(library(debug)). 69:- use_module(library(error)). 70:- use_module(library(filesex)). 71
72:- if(exists_source(library(bdb))). 73:- use_module(gitty_driver_bdb, []). 74:- endif. 75:- use_module(gitty_driver_files, []). 76
77
100
101:- dynamic
102 gitty_store_type/2. 103
117
118gitty_open(Store, Options) :-
119 ( exists_directory(Store)
120 -> true
121 ; existence_error(directory, Store)
122 ),
123 ( option(driver(Driver), Options)
124 -> true
125 ; default_driver(Store, Driver)
126 ),
127 set_driver(Store, Driver),
128 gitty_driver_open(Store, Options).
129
130default_driver(Store, Driver) :-
131 directory_file_path(Store, ref, RefDir),
132 exists_directory(RefDir),
133 !,
134 Driver = files.
135default_driver(Store, Driver) :-
136 directory_file_path(Store, heads, RefDir),
137 exists_file(RefDir),
138 !,
139 Driver = bdb.
140default_driver(_, files).
141
142set_driver(Store, Driver) :-
143 must_be(atom, Store),
144 ( driver_module(Driver, Module)
145 -> retractall(gitty_store_type(Store, _)),
146 asserta(gitty_store_type(Store, Module))
147 ; domain_error(gitty_driver, Driver)
148 ).
149
150driver_module(files, gitty_driver_files).
151driver_module(bdb, gitty_driver_bdb).
152
153store_driver_module(Store, Module) :-
154 atom(Store),
155 !,
156 gitty_store_type(Store, Module).
157
161
162gitty_driver(Store, Driver) :-
163 store_driver_module(Store, Module),
164 driver_module(Driver, Module),
165 !.
166
170
171gitty_driver_open(Store, Options) :-
172 store_driver_module(Store, M),
173 M:gitty_open(Store, Options).
174
178
179gitty_close(Store) :-
180 store_driver_module(Store, M),
181 M:gitty_close(Store).
182
188
189gitty_file(Store, Head, Hash) :-
190 gitty_file(Store, Head, _Ext, Hash).
191gitty_file(Store, Head, Ext, Hash) :-
192 store_driver_module(Store, M),
193 M:gitty_file(Store, Head, Ext, Hash).
194
200
201gitty_create(Store, Name, _Data, _Meta, _) :-
202 gitty_file(Store, Name, _Hash),
203 !,
204 throw(error(gitty(file_exists(Name)),_)).
205gitty_create(Store, Name, Data, Meta, CommitRet) :-
206 save_object(Store, Data, blob, Hash),
207 get_time(Now),
208 Commit = gitty{time:Now}.put(Meta)
209 .put(_{ name:Name,
210 data:Hash
211 }),
212 format(string(CommitString), '~q.~n', [Commit]),
213 save_object(Store, CommitString, commit, CommitHash),
214 CommitRet = Commit.put(commit, CommitHash),
215 catch(gitty_update_head(Store, Name, -, CommitHash, Hash),
216 E,
217 ( delete_object(Store, CommitHash),
218 throw(E))).
219
223
224gitty_update(Store, Name, Data, Meta, CommitRet) :-
225 gitty_file(Store, Name, OldHead),
226 ( _{previous:OldHead} >:< Meta
227 -> true
228 ; throw(error(gitty(commit_version(Name, OldHead, Meta.previous)), _))
229 ),
230 gitty_plain_commit(Store, OldHead, OldMeta0),
231 filter_identity(OldMeta0, OldMeta),
232 get_time(Now),
233 save_object(Store, Data, blob, Hash),
234 Commit = gitty{}.put(OldMeta)
235 .put(_{time:Now})
236 .put(Meta)
237 .put(_{ name:Name,
238 data:Hash,
239 previous:OldHead
240 }),
241 format(string(CommitString), '~q.~n', [Commit]),
242 save_object(Store, CommitString, commit, CommitHash),
243 CommitRet = Commit.put(commit, CommitHash),
244 catch(gitty_update_head(Store, Name, OldHead, CommitHash, Hash),
245 E,
246 ( delete_object(Store, CommitHash),
247 throw(E))).
248
249
255
256filter_identity(Meta0, Meta) :-
257 delete_keys([ author,user,avatar,identity,peer,
258 external_identity, identity_provider, profile_id,
259 commit_message
260 ], Meta0, Meta).
261
262delete_keys([], Dict, Dict).
263delete_keys([H|T], Dict0, Dict) :-
264 del_dict(H, Dict0, _, Dict1),
265 !,
266 delete_keys(T, Dict1, Dict).
267delete_keys([_|T], Dict0, Dict) :-
268 delete_keys(T, Dict0, Dict).
269
270
284
285gitty_update_head(Store, Name, OldCommit, NewCommit, DataHash) :-
286 store_driver_module(Store, Module),
287 Module:gitty_update_head(Store, Name, OldCommit, NewCommit, DataHash).
288
292
293gitty_data(Store, Name, Data, Meta) :-
294 gitty_commit(Store, Name, Meta),
295 load_object(Store, Meta.data, Data).
296
301
302gitty_commit(Store, Hash, Meta) :-
303 is_gitty_hash(Hash),
304 !,
305 load_commit(Store, Hash, Meta).
306gitty_commit(Store, Name, Meta) :-
307 must_be(atom, Name),
308 gitty_file(Store, Name, Head),
309 load_commit(Store, Head, Meta).
310
311load_commit(Store, Hash, Meta) :-
312 gitty_plain_commit(Store, Hash, Meta0),
313 ( gitty_file(Store, Meta0.name, Hash)
314 -> Meta = Meta0.put(symbolic, "HEAD")
315 ; Meta = Meta0
316 ).
317
321
322gitty_plain_commit(Store, Hash, Meta) :-
323 store_driver_module(Store, Module),
324 Module:load_plain_commit(Store, Hash, Meta0),
325 Meta = Meta0.put(commit, Hash).
326
341
342gitty_history(Store, Name, json{history:History,skipped:Skipped}, Options) :-
343 history_hash_start(Store, Name, Hash0),
344 option(depth(Depth), Options, 5),
345 ( option(includes(Hash), Options)
346 -> read_history_to_hash(Store, Hash0, Hash, History00),
347 length(History00, Before),
348 After is max(Depth-Before, (Depth+1)//2),
349 read_history_depth(Store, Hash, After, History1),
350 length(History1, AfterLen),
351 BeforeLen is Depth - AfterLen,
352 list_prefix(BeforeLen, History00, History0),
353 length(History00, Len00),
354 length(History0, Len0),
355 Skipped is Len00-Len0,
356 append(History0, History1, History)
357 ; read_history_depth(Store, Hash0, Depth, History),
358 Skipped is 0
359 ).
360
361history_hash_start(Store, Name, Hash) :-
362 gitty_file(Store, Name, Head),
363 !,
364 Hash = Head.
365history_hash_start(_, Hash, Hash).
366
367
368read_history_depth(_, _, 0, []) :- !.
369read_history_depth(Store, Hash, Left, [H|T]) :-
370 load_commit(Store, Hash, H),
371 !,
372 Left1 is Left-1,
373 ( read_history_depth(Store, H.get(previous), Left1, T)
374 -> true
375 ; T = []
376 ).
377read_history_depth(_, _, _, []).
378
382
383read_history_to_hash(Store, Hash, Upto, [H|T]) :-
384 Upto \== Hash,
385 load_commit(Store, Hash, H),
386 ( read_history_to_hash(Store, H.get(previous), Upto, T)
387 -> true
388 ; T = []
389 ).
390read_history_to_hash(_, _, _, []).
391
392list_prefix(0, _, []) :- !.
393list_prefix(_, [], []) :- !.
394list_prefix(N, [H|T0], [H|T]) :-
395 N2 is N - 1,
396 list_prefix(N2, T0, T).
397
398
408
409save_object(Store, Data, Type, Hash) :-
410 size_in_bytes(Data, Size),
411 format(string(Hdr), '~w ~d\u0000', [Type, Size]),
412 sha_new_ctx(Ctx0, []),
413 sha_hash_ctx(Ctx0, Hdr, Ctx1, _),
414 sha_hash_ctx(Ctx1, Data, _, HashBin),
415 hash_atom(HashBin, Hash),
416 store_object(Store, Hash, Hdr, Data).
417
418store_object(Store, Hash, Hdr, Data) :-
419 store_driver_module(Store, Module),
420 Module:store_object(Store, Hash, Hdr, Data).
421
422size_in_bytes(Data, Size) :-
423 setup_call_cleanup(
424 open_null_stream(Out),
425 ( format(Out, '~s', [Data]),
426 byte_count(Out, Size)
427 ),
428 close(Out)).
429
430
434
435gitty_fsck(Store) :-
436 forall(gitty_hash(Store, Hash),
437 fsck_object_msg(Store, Hash)),
438 store_driver_module(Store, M),
439 M:gitty_fsck(Store).
440
441fsck_object_msg(Store, Hash) :-
442 fsck_object(Store, Hash),
443 !.
444fsck_object_msg(Store, Hash) :-
445 print_message(error, gitty(Store, fsck(bad_object(Hash)))).
446
450
451:- public
452 fsck_object/2,
453 check_object/4. 454
455fsck_object(Store, Hash) :-
456 load_object(Store, Hash, Data, Type, Size),
457 check_object(Hash, Data, Type, Size).
458
459check_object(Hash, Data, Type, Size) :-
460 format(string(Hdr), '~w ~d\u0000', [Type, Size]),
461 sha_new_ctx(Ctx0, []),
462 sha_hash_ctx(Ctx0, Hdr, Ctx1, _),
463 sha_hash_ctx(Ctx1, Data, _, HashBin),
464 hash_atom(HashBin, Hash).
465
466
467
468
473
474load_object(Store, Hash, Data) :-
475 load_object(Store, Hash, Data, _, _).
476load_object(Store, Hash, Data, Type, Size) :-
477 store_driver_module(Store, Module),
478 Module:load_object(Store, Hash, Data, Type, Size).
479
488
489gitty_save(Store, Data, Type, Hash) :-
490 save_object(Store, Data, Type, Hash).
491gitty_load(Store, Hash, Data, Type) :-
492 load_object(Store, Hash, Data, Type, _Size).
493
497
498gitty_hash(Store, Hash) :-
499 store_driver_module(Store, Module),
500 Module:gitty_hash(Store, Hash).
501
505
506delete_object(Store, Hash) :-
507 store_driver_module(Store, Module),
508 Module:delete_object(Store, Hash).
509
513
514gitty_reserved_meta(name).
515gitty_reserved_meta(time).
516gitty_reserved_meta(data).
517gitty_reserved_meta(previous).
518
519
523
524is_gitty_hash(SHA1) :-
525 atom(SHA1),
526 atom_length(SHA1, 40),
527 atom_codes(SHA1, Codes),
528 maplist(hex_digit, Codes).
529
530hex_digit(C) :- between(0'0, 0'9, C), !.
531hex_digit(C) :- between(0'a, 0'f, C).
532
533
534 537
538:- public
539 delete_object/2,
540 delete_head/2,
541 set_head/3. 542
547
548delete_head(Store, Head) :-
549 store_driver_module(Store, Module),
550 Module:delete_head(Store, Head).
551
556
557set_head(Store, File, Head) :-
558 store_driver_module(Store, Module),
559 Module:set_head(Store, File, Head).
560
561
562 565
585
586gitty_diff(Store, C1, data(Data2), Dict) :-
587 !,
588 must_be(atom, C1),
589 gitty_data(Store, C1, Data1, _Meta1),
590 ( Data1 \== Data2
591 -> udiff_string(Data1, Data2, UDIFF),
592 Dict = json{data:UDIFF}
593 ; Dict = json{}
594 ).
595gitty_diff(Store, C1, C2, Dict) :-
596 gitty_data(Store, C2, Data2, Meta2),
597 ( var(C1)
598 -> C1 = Meta2.get(previous)
599 ; true
600 ),
601 !,
602 gitty_data(Store, C1, Data1, Meta1),
603 Pairs = [ from-Meta1, to-Meta2|_],
604 ( Data1 \== Data2
605 -> udiff_string(Data1, Data2, UDIFF),
606 memberchk(data-UDIFF, Pairs)
607 ; true
608 ),
609 meta_tag_set(Meta1, Tags1),
610 meta_tag_set(Meta2, Tags2),
611 ( Tags1 \== Tags2
612 -> ord_subtract(Tags1, Tags2, Deleted),
613 ord_subtract(Tags2, Tags1, Added),
614 memberchk(tags-_{added:Added, deleted:Deleted}, Pairs)
615 ; true
616 ),
617 once(length(Pairs,_)), 618 dict_pairs(Dict, json, Pairs).
619gitty_diff(_Store, '0000000000000000000000000000000000000000', _C2,
620 json{initial:true}).
621
622
623meta_tag_set(Meta, Tags) :-
624 sort(Meta.get(tags), Tags),
625 !.
626meta_tag_set(_, []).
627
634
635:- if(true). 636
642
643udiff_string(Data1, Data2, UDIFF) :-
644 setup_call_cleanup(
645 tmp_file_stream(utf8, File1, Tmp1),
646 ( save_string(Data1, Tmp1),
647 setup_call_cleanup(
648 tmp_file_stream(utf8, File2, Tmp2),
649 ( save_string(Data2, Tmp2),
650 process_diff(File1, File2, UDIFF)
651 ),
652 reclaim_tmp_file(File2, Tmp2))
653 ),
654 reclaim_tmp_file(File1, Tmp1)).
655
656save_string(String, Stream) :-
657 call_cleanup(
658 format(Stream, '~s', [String]),
659 close(Stream)).
660
661reclaim_tmp_file(File, Stream) :-
662 close(Stream, [force(true)]),
663 delete_file(File).
664
665process_diff(File1, File2, String) :-
666 setup_call_cleanup(
667 process_create(path(diff),
668 ['-u', file(File1), file(File2)],
669 [ stdout(pipe(Out)),
670 process(PID)
671 ]),
672 read_string(Out, _, String),
673 ( close(Out),
674 process_wait(PID, Status)
675 )),
676 assertion(normal_diff_exit(Status)).
677
678normal_diff_exit(exit(0)). 679normal_diff_exit(exit(1)). 680
681:- else. 682
683udiff_string(Data1, Data2, UDIFF) :-
684 data_diff(Data1, Data2, Diffs),
685 maplist(udiff_string, Diffs, Strings),
686 atomics_to_string(Strings, UDIFF).
687
688:- endif. 689
690
691 694
707
708
730
731data_diff(Data, Data, UDiff) :-
732 !,
733 UDiff = [].
734data_diff(Data1, Data2, Diff) :-
735 split_string(Data1, "\n", "", List1),
736 split_string(Data2, "\n", "", List2),
737 list_diff(List1, List2, Diff).
738
739list_diff(List1, List2, UDiff) :-
740 list_lcs(List1, List2, Lcs),
741 make_diff(List1, List2, Lcs, c(), 1, 1, Diff),
742 join_diff(Diff, UDiff).
743
745
746make_diff([], [], [], _, _, _, []) :- !.
747make_diff([H|T1], [H|T2], [H|C], c(_,C0,C1), L1, L2, Diff) :-
748 !,
749 L11 is L1+1,
750 L21 is L2+1,
751 make_diff(T1, T2, C, c(C0,C1,H), L11, L21, Diff).
752make_diff([H|T1], [H|T2], [H|C], C0, L1, L2, Diff) :-
753 !,
754 L11 is L1+1,
755 L21 is L2+1,
756 add_context(C0, H, C1),
757 ( compound_name_arity(C1, _, L1)
758 -> Diff = Diff1
759 ; Diff = [=(H)|Diff1]
760 ),
761 make_diff(T1, T2, C, C1, L11, L21, Diff1).
762make_diff([H|T1], [H2|T2], [H|C], C0, L1, L2, [d(L1,L2,C0,+H2)|Diff]) :-
763 !,
764 L21 is L2+1,
765 make_diff([H|T1], T2, [H|C], c(), L1, L21, Diff).
766make_diff([], [H2|T2], [], C0, L1, L2, [d(L1,L2,C0,+H2)|Diff]) :-
767 !,
768 L21 is L2+1,
769 make_diff([], T2, [], c(), L1, L21, Diff).
770make_diff([H1|T1], [H|T2], [H|C], C0, L1, L2, [d(L1,L2,C0,-H1)|Diff]) :-
771 !,
772 L11 is L1+1,
773 make_diff(T1, [H|T2], [H|C], c(), L11, L2, Diff).
774make_diff([H1|T1], [], [], C0, L1, L2, [d(L1,L2,C0,-H1)|Diff]) :-
775 !,
776 L11 is L1+1,
777 make_diff(T1, [], [], c(), L11, L2, Diff).
778make_diff([H1|T1], [H2|T2], C, C0, L1, L2, [d(L1,L2,C0,H1-H2)|Diff]) :-
779 !,
780 L11 is L1+1,
781 L21 is L2+1,
782 make_diff(T1, T2, C, c(), L11, L21, Diff).
783
784add_context(c(_,B,C),N,c(B,C,N)).
785add_context(c(A,B), N,c(A,B,N)).
786add_context(c(A), N,c(A,N)).
787add_context(c(), N,c(N)).
788
790
791join_diff([], []).
792join_diff([d(L10,L20,C,L)|T0], [udiff(L1,S1,L2,S2,Diff)|T]) :-
793 pre_context(C, S0, Diff, [L|DiffT]),
794 L1 is L10-S0,
795 L2 is L20-S0,
796 diff_affected(L,S10,S20),
797 S11 is S10+S0,
798 S21 is S20+S0,
799 collect_diff(T0,S11,S21,S1,S2,0,DiffT,T1),
800 join_diff(T1, T).
801
802pre_context(c(), 0, L, L).
803pre_context(c(A), 1, [=(A)|L], L).
804pre_context(c(A,B), 2, [=(A),=(B)|L], L).
805pre_context(c(A,B,C), 3, [=(A),=(B),=(C)|L], L).
806
807collect_diff([d(_,_,_,L)|T0], S10,S20,S1,S2,C,[L|Diff],T) :-
808 C < 3,
809 !,
810 diff_affected(L,S1x,S2x),
811 S11 is S10+S1x,
812 S21 is S20+S2x,
813 collect_diff(T0,S11,S21,S1,S2,0,Diff,T).
814collect_diff([=(L)|T0], S10,S20,S1,S2,C0,[=(L)|Diff],T) :-
815 !,
816 S11 is S10+1,
817 S21 is S20+1,
818 C1 is C0+1,
819 collect_diff(T0,S11,S21,S1,S2,C1,Diff,T).
820collect_diff(T,S1,S2,S1,S2,_,[],T).
821
822diff_affected(+(_), 0, 1).
823diff_affected(-(_), 0, 1).
824diff_affected(-(_,_), 1, 1).
825
829
830udiff_string(udiff(L1,S1,L2,S2,Diff), Final) :-
831 format(string(Hdr), '@@ -~d,~d +~d,~d @@', [L1,S1,L2,S2]),
832 udiff_blocks(Diff, Blocks),
833 maplist(block_lines, Blocks, LineSets),
834 append(LineSets, Lines),
835 atomics_to_string([Hdr|Lines], "\n", Final).
836
837block_lines(=(U), Lines) :- maplist(string_concat(' '), U, Lines).
838block_lines(+(U), Lines) :- maplist(string_concat('+'), U, Lines).
839block_lines(-(U), Lines) :- maplist(string_concat('-'), U, Lines).
840
841udiff_blocks([], []) :- !.
842udiff_blocks([=(H)|T0], [=([H|E])|T]) :-
843 !,
844 udiff_cp(T0, E, T1),
845 udiff_blocks(T1, T).
846udiff_blocks(U, List) :-
847 udiff_block(U, D, A, T1),
848 udiff_add(D,A,List,ListT),
849 udiff_blocks(T1, ListT).
850
851udiff_add([],A,[+A|T],T) :- !.
852udiff_add(D,[],[-D|T],T) :- !.
853udiff_add(D,A,[-D,+A|T],T).
854
855udiff_cp([=(H)|T0], [H|E], T) :-
856 !,
857 udiff_cp(T0, E, T).
858udiff_cp(L, [], L).
859
860udiff_block([-L|T], [L|D], A, Rest) :-
861 !,
862 udiff_block(T, D, A, Rest).
863udiff_block([+L|T], D, [L|A], Rest) :-
864 !,
865 udiff_block(T, D, A, Rest).
866udiff_block([L1-L2|T], [L1|D], [L2|A], Rest) :-
867 !,
868 udiff_block(T, D, A, Rest).
869udiff_block(T, [], [], T).
870
874
875:- thread_local lcs_db/2. 876
877list_lcs([], [], []) :- !.
878list_lcs([H|L1], [H|L2], [H|Lcs]) :-
879 !,
880 list_lcs(L1, L2, Lcs).
881list_lcs(List1, List2, Lcs) :-
882 reverse(List1, Rev1),
883 reverse(List2, Rev2),
884 copy_prefix(Rev1, Rev2, RevDiff1, RevDiff2, RevLcs, RevT),
885 list_lcs2(RevDiff1, RevDiff2, RevT),
886 reverse(RevLcs, Lcs).
887
888list_lcs2(List1, List2, Lcs) :-
889 variant_sha1(List1+List2, Hash),
890 call_cleanup(
891 lcs(List1, List2, Hash, Lcs),
892 retractall(lcs_db(_,_))).
893
894copy_prefix([H|T1], [H|T2], L1, L2, [H|L], LT) :-
895 !,
896 copy_prefix(T1, T2, L1, L2, L, LT).
897copy_prefix(R1, R2, R1, R2, L, L).
898
899
900lcs(_,_,Hash,Lcs) :-
901 lcs_db(Hash,Lcs),
902 !.
903lcs([H|L1], [H|L2], _, [H|Lcs]) :-
904 !,
905 variant_sha1(L1+L2,Hash),
906 lcs(L1, L2, Hash, Lcs).
907lcs(List1, List2, Hash, Lcs) :-
908 List1 = [H1|L1],
909 List2 = [H2|L2],
910 variant_sha1(L1+[H2|L2],Hash1),
911 variant_sha1([H1|L1]+L2,Hash2),
912 lcs( L1 , [H2|L2], Hash1, Lcs1),
913 lcs([H1|L1], L2 , Hash2, Lcs2),
914 longest(Lcs1, Lcs2, Lcs),
915 !,
916 asserta(lcs_db(Hash, Lcs)).
917lcs(_,_,_,[]).
918
919longest(L1, L2, Longest) :-
920 length(L1, Length1),
921 length(L2, Length2),
922 ( Length1 > Length2
923 -> Longest = L1
924 ; Longest = L2
925 ).
926
927 930:- multifile
931 prolog:error_message//1. 932
933prolog:error_message(gitty(not_at_head(Name, _OldCommit))) -->
934 [ 'Gitty: cannot update head for "~w" because it was \c
935 updated by someone else'-[Name] ].
936prolog:error_message(gitty(file_exists(Name))) -->
937 [ 'Gitty: File exists: ~p'-[Name] ].
938prolog:error_message(gitty(commit_version(Name, _Head, _Previous))) -->
939 [ 'Gitty: ~p: cannot update (modified by someone else)'-[Name] ]