34
35:- module(gitty,
36 [ gitty_open/2, 37 gitty_close/1, 38 gitty_driver/2, 39
40 gitty_file/3, 41 gitty_file/4, 42 gitty_create/5, 43 gitty_update/5, 44 gitty_commit/3, 45 gitty_data/4, 46 gitty_history/4, 47 gitty_hash/2, 48
49 gitty_fsck/1, 50 gitty_save/4, 51 gitty_load/4, 52
53 gitty_reserved_meta/1, 54 is_gitty_hash/1, 55
56 gitty_diff/4, 57
58 data_diff/3, 59 udiff_string/2 60 ]). 61:- use_module(library(sha)). 62:- use_module(library(lists)). 63:- use_module(library(apply)). 64:- use_module(library(option)). 65:- use_module(library(process)). 66:- use_module(library(debug)). 67:- use_module(library(error)). 68:- use_module(library(filesex)). 69
70:- if(exists_source(library(bdb))). 71:- use_module(gitty_driver_bdb, []). 72:- endif. 73:- use_module(gitty_driver_files, []). 74
75
98
99:- dynamic
100 gitty_store_type/2. 101
112
113gitty_open(Store, Options) :-
114 ( exists_directory(Store)
115 -> true
116 ; existence_error(directory, Store)
117 ),
118 ( option(driver(Driver), Options)
119 -> true
120 ; default_driver(Store, Driver)
121 ),
122 set_driver(Store, Driver).
123
124default_driver(Store, Driver) :-
125 directory_file_path(Store, ref, RefDir),
126 exists_directory(RefDir), !,
127 Driver = files.
128default_driver(Store, Driver) :-
129 directory_file_path(Store, heads, RefDir),
130 exists_file(RefDir), !,
131 Driver = bdb.
132default_driver(_, files).
133
134set_driver(Store, Driver) :-
135 must_be(atom, Store),
136 ( driver_module(Driver, Module)
137 -> retractall(gitty_store_type(Store, _)),
138 asserta(gitty_store_type(Store, Module))
139 ; domain_error(gitty_driver, Driver)
140 ).
141
142driver_module(files, gitty_driver_files).
143driver_module(bdb, gitty_driver_bdb).
144
145store_driver_module(Store, Module) :-
146 atom(Store), !,
147 gitty_store_type(Store, Module).
148
152
153gitty_driver(Store, Driver) :-
154 store_driver_module(Store, Module),
155 driver_module(Driver, Module), !.
156
160
161gitty_close(Store) :-
162 store_driver_module(Store, M),
163 M:gitty_close(Store).
164
170
171gitty_file(Store, Head, Hash) :-
172 gitty_file(Store, Head, _Ext, Hash).
173gitty_file(Store, Head, Ext, Hash) :-
174 store_driver_module(Store, M),
175 M:gitty_file(Store, Head, Ext, Hash).
176
182
183gitty_create(Store, Name, _Data, _Meta, _) :-
184 gitty_file(Store, Name, _Hash), !,
185 throw(error(gitty(file_exists(Name)),_)).
186gitty_create(Store, Name, Data, Meta, CommitRet) :-
187 save_object(Store, Data, blob, Hash),
188 get_time(Now),
189 Commit = gitty{time:Now}.put(Meta)
190 .put(_{ name:Name,
191 data:Hash
192 }),
193 format(string(CommitString), '~q.~n', [Commit]),
194 save_object(Store, CommitString, commit, CommitHash),
195 CommitRet = Commit.put(commit, CommitHash),
196 catch(gitty_update_head(Store, Name, -, CommitHash),
197 E,
198 ( delete_object(Store, CommitHash),
199 throw(E))).
200
204
205gitty_update(Store, Name, Data, Meta, CommitRet) :-
206 gitty_file(Store, Name, OldHead),
207 ( _{previous:OldHead} >:< Meta
208 -> true
209 ; throw(error(gitty(commit_version(Name, OldHead, Meta.previous)), _))
210 ),
211 load_plain_commit(Store, OldHead, OldMeta0),
212 filter_identity(OldMeta0, OldMeta),
213 get_time(Now),
214 save_object(Store, Data, blob, Hash),
215 Commit = gitty{}.put(OldMeta)
216 .put(_{time:Now})
217 .put(Meta)
218 .put(_{ name:Name,
219 data:Hash,
220 previous:OldHead
221 }),
222 format(string(CommitString), '~q.~n', [Commit]),
223 save_object(Store, CommitString, commit, CommitHash),
224 CommitRet = Commit.put(commit, CommitHash),
225 catch(gitty_update_head(Store, Name, OldHead, CommitHash),
226 E,
227 ( delete_object(Store, CommitHash),
228 throw(E))).
229
235
236filter_identity(Meta0, Meta) :-
237 delete_keys([ author,user,avatar,identity,peer,
238 external_identity, identity_provider, profile_id,
239 commit_message
240 ], Meta0, Meta).
241
242delete_keys([], Dict, Dict).
243delete_keys([H|T], Dict0, Dict) :-
244 del_dict(H, Dict0, _, Dict1), !,
245 delete_keys(T, Dict1, Dict).
246delete_keys([_|T], Dict0, Dict) :-
247 delete_keys(T, Dict0, Dict).
248
249
262
263gitty_update_head(Store, Name, OldCommit, NewCommit) :-
264 store_driver_module(Store, Module),
265 Module:gitty_update_head(Store, Name, OldCommit, NewCommit).
266
270
271gitty_data(Store, Name, Data, Meta) :-
272 gitty_commit(Store, Name, Meta),
273 load_object(Store, Meta.data, Data).
274
279
280gitty_commit(Store, Name, Meta) :-
281 must_be(atom, Name),
282 gitty_file(Store, Name, Head), !,
283 load_commit(Store, Head, Meta).
284gitty_commit(Store, Hash, Meta) :-
285 load_commit(Store, Hash, Meta).
286
287load_commit(Store, Hash, Meta) :-
288 load_plain_commit(Store, Hash, Meta0),
289 Meta1 = Meta0.put(commit, Hash),
290 ( gitty_file(Store, Meta0.name, Hash)
291 -> Meta = Meta1.put(symbolic, "HEAD")
292 ; Meta = Meta1
293 ).
294
295load_plain_commit(Store, Hash, Meta) :-
296 store_driver_module(Store, Module),
297 Module:load_plain_commit(Store, Hash, Meta).
298
313
314gitty_history(Store, Name, json{history:History,skipped:Skipped}, Options) :-
315 history_hash_start(Store, Name, Hash0),
316 option(depth(Depth), Options, 5),
317 ( option(includes(Hash), Options)
318 -> read_history_to_hash(Store, Hash0, Hash, History00),
319 length(History00, Before),
320 After is max(Depth-Before, (Depth+1)//2),
321 read_history_depth(Store, Hash, After, History1),
322 length(History1, AfterLen),
323 BeforeLen is Depth - AfterLen,
324 list_prefix(BeforeLen, History00, History0),
325 length(History00, Len00),
326 length(History0, Len0),
327 Skipped is Len00-Len0,
328 append(History0, History1, History)
329 ; read_history_depth(Store, Hash0, Depth, History),
330 Skipped is 0
331 ).
332
333history_hash_start(Store, Name, Hash) :-
334 gitty_file(Store, Name, Head), !,
335 Hash = Head.
336history_hash_start(_, Hash, Hash).
337
338
339read_history_depth(_, _, 0, []) :- !.
340read_history_depth(Store, Hash, Left, [H|T]) :-
341 load_commit(Store, Hash, H), !,
342 Left1 is Left-1,
343 ( read_history_depth(Store, H.get(previous), Left1, T)
344 -> true
345 ; T = []
346 ).
347read_history_depth(_, _, _, []).
348
352
353read_history_to_hash(Store, Hash, Upto, [H|T]) :-
354 Upto \== Hash,
355 load_commit(Store, Hash, H),
356 ( read_history_to_hash(Store, H.get(previous), Upto, T)
357 -> true
358 ; T = []
359 ).
360read_history_to_hash(_, _, _, []).
361
362list_prefix(0, _, []) :- !.
363list_prefix(_, [], []) :- !.
364list_prefix(N, [H|T0], [H|T]) :-
365 N2 is N - 1,
366 list_prefix(N2, T0, T).
367
368
378
379save_object(Store, Data, Type, Hash) :-
380 size_in_bytes(Data, Size),
381 format(string(Hdr), '~w ~d\u0000', [Type, Size]),
382 sha_new_ctx(Ctx0, []),
383 sha_hash_ctx(Ctx0, Hdr, Ctx1, _),
384 sha_hash_ctx(Ctx1, Data, _, HashBin),
385 hash_atom(HashBin, Hash),
386 store_object(Store, Hash, Hdr, Data).
387
388store_object(Store, Hash, Hdr, Data) :-
389 store_driver_module(Store, Module),
390 Module:store_object(Store, Hash, Hdr, Data).
391
392size_in_bytes(Data, Size) :-
393 setup_call_cleanup(
394 open_null_stream(Out),
395 ( format(Out, '~s', [Data]),
396 byte_count(Out, Size)
397 ),
398 close(Out)).
399
400
404
405gitty_fsck(Store) :-
406 forall(gitty_hash(Store, Hash),
407 fsck_object_msg(Store, Hash)),
408 store_driver_module(Store, M),
409 M:gitty_fsck(Store).
410
411fsck_object_msg(Store, Hash) :-
412 fsck_object(Store, Hash), !.
413fsck_object_msg(Store, Hash) :-
414 print_message(error, gitty(Store, fsck(bad_object(Hash)))).
415
419
420:- public
421 fsck_object/2,
422 check_object/4. 423
424fsck_object(Store, Hash) :-
425 load_object(Store, Hash, Data, Type, Size),
426 check_object(Hash, Data, Type, Size).
427
428check_object(Hash, Data, Type, Size) :-
429 format(string(Hdr), '~w ~d\u0000', [Type, Size]),
430 sha_new_ctx(Ctx0, []),
431 sha_hash_ctx(Ctx0, Hdr, Ctx1, _),
432 sha_hash_ctx(Ctx1, Data, _, HashBin),
433 hash_atom(HashBin, Hash).
434
435
436
437
442
443load_object(Store, Hash, Data) :-
444 load_object(Store, Hash, Data, _, _).
445load_object(Store, Hash, Data, Type, Size) :-
446 store_driver_module(Store, Module),
447 Module:load_object(Store, Hash, Data, Type, Size).
448
457
458gitty_save(Store, Data, Type, Hash) :-
459 save_object(Store, Data, Type, Hash).
460gitty_load(Store, Hash, Data, Type) :-
461 load_object(Store, Hash, Data, Type, _Size).
462
466
467gitty_hash(Store, Hash) :-
468 store_driver_module(Store, Module),
469 Module:gitty_hash(Store, Hash).
470
474
475delete_object(Store, Hash) :-
476 store_driver_module(Store, Module),
477 Module:delete_object(Store, Hash).
478
482
483gitty_reserved_meta(name).
484gitty_reserved_meta(time).
485gitty_reserved_meta(data).
486gitty_reserved_meta(previous).
487
488
492
493is_gitty_hash(SHA1) :-
494 atom(SHA1),
495 atom_length(SHA1, 40),
496 atom_codes(SHA1, Codes),
497 maplist(hex_digit, Codes).
498
499hex_digit(C) :- between(0'0, 0'9, C), !.
500hex_digit(C) :- between(0'a, 0'f, C).
501
502
503 506
507:- public
508 delete_object/2,
509 delete_head/2,
510 set_head/3. 511
516
517delete_head(Store, Head) :-
518 store_driver_module(Store, Module),
519 Module:delete_head(Store, Head).
520
525
526set_head(Store, File, Head) :-
527 store_driver_module(Store, Module),
528 Module:set_head(Store, File, Head).
529
530
531 534
554
555gitty_diff(Store, C1, data(Data2), Dict) :- !,
556 must_be(atom, C1),
557 gitty_data(Store, C1, Data1, _Meta1),
558 ( Data1 \== Data2
559 -> udiff_string(Data1, Data2, UDIFF),
560 Dict = json{data:UDIFF}
561 ; Dict = json{}
562 ).
563gitty_diff(Store, C1, C2, Dict) :-
564 gitty_data(Store, C2, Data2, Meta2),
565 ( var(C1)
566 -> C1 = Meta2.get(previous)
567 ; true
568 ), !,
569 gitty_data(Store, C1, Data1, Meta1),
570 Pairs = [ from-Meta1, to-Meta2|_],
571 ( Data1 \== Data2
572 -> udiff_string(Data1, Data2, UDIFF),
573 memberchk(data-UDIFF, Pairs)
574 ; true
575 ),
576 meta_tag_set(Meta1, Tags1),
577 meta_tag_set(Meta2, Tags2),
578 ( Tags1 \== Tags2
579 -> ord_subtract(Tags1, Tags2, Deleted),
580 ord_subtract(Tags2, Tags1, Added),
581 memberchk(tags-_{added:Added, deleted:Deleted}, Pairs)
582 ; true
583 ),
584 once(length(Pairs,_)), 585 dict_pairs(Dict, json, Pairs).
586gitty_diff(_Store, '0000000000000000000000000000000000000000', _C2,
587 json{initial:true}).
588
589
590meta_tag_set(Meta, Tags) :-
591 sort(Meta.get(tags), Tags), !.
592meta_tag_set(_, []).
593
600
601:- if(true). 602
608
609udiff_string(Data1, Data2, UDIFF) :-
610 setup_call_cleanup(
611 tmp_file_stream(utf8, File1, Tmp1),
612 ( save_string(Data1, Tmp1),
613 setup_call_cleanup(
614 tmp_file_stream(utf8, File2, Tmp2),
615 ( save_string(Data2, Tmp2),
616 process_diff(File1, File2, UDIFF)
617 ),
618 reclaim_tmp_file(File2, Tmp2))
619 ),
620 reclaim_tmp_file(File1, Tmp1)).
621
622save_string(String, Stream) :-
623 call_cleanup(
624 format(Stream, '~s', [String]),
625 close(Stream)).
626
627reclaim_tmp_file(File, Stream) :-
628 close(Stream, [force(true)]),
629 delete_file(File).
630
631process_diff(File1, File2, String) :-
632 setup_call_cleanup(
633 process_create(path(diff),
634 ['-u', file(File1), file(File2)],
635 [ stdout(pipe(Out)),
636 process(PID)
637 ]),
638 read_string(Out, _, String),
639 ( close(Out),
640 process_wait(PID, Status)
641 )),
642 assertion(normal_diff_exit(Status)).
643
644normal_diff_exit(exit(0)). 645normal_diff_exit(exit(1)). 646
647:- else. 648
649udiff_string(Data1, Data2, UDIFF) :-
650 data_diff(Data1, Data2, Diffs),
651 maplist(udiff_string, Diffs, Strings),
652 atomics_to_string(Strings, UDIFF).
653
654:- endif. 655
656
657 660
673
674
696
697data_diff(Data, Data, UDiff) :- !,
698 UDiff = [].
699data_diff(Data1, Data2, Diff) :-
700 split_string(Data1, "\n", "", List1),
701 split_string(Data2, "\n", "", List2),
702 list_diff(List1, List2, Diff).
703
704list_diff(List1, List2, UDiff) :-
705 list_lcs(List1, List2, Lcs),
706 make_diff(List1, List2, Lcs, c(), 1, 1, Diff),
707 join_diff(Diff, UDiff).
708
710
711make_diff([], [], [], _, _, _, []) :- !.
712make_diff([H|T1], [H|T2], [H|C], c(_,C0,C1), L1, L2, Diff) :- !,
713 L11 is L1+1,
714 L21 is L2+1,
715 make_diff(T1, T2, C, c(C0,C1,H), L11, L21, Diff).
716make_diff([H|T1], [H|T2], [H|C], C0, L1, L2, Diff) :- !,
717 L11 is L1+1,
718 L21 is L2+1,
719 add_context(C0, H, C1),
720 ( compound_name_arity(C1, _, L1)
721 -> Diff = Diff1
722 ; Diff = [=(H)|Diff1]
723 ),
724 make_diff(T1, T2, C, C1, L11, L21, Diff1).
725make_diff([H|T1], [H2|T2], [H|C], C0, L1, L2, [d(L1,L2,C0,+H2)|Diff]) :- !,
726 L21 is L2+1,
727 make_diff([H|T1], T2, [H|C], c(), L1, L21, Diff).
728make_diff([], [H2|T2], [], C0, L1, L2, [d(L1,L2,C0,+H2)|Diff]) :- !,
729 L21 is L2+1,
730 make_diff([], T2, [], c(), L1, L21, Diff).
731make_diff([H1|T1], [H|T2], [H|C], C0, L1, L2, [d(L1,L2,C0,-H1)|Diff]) :- !,
732 L11 is L1+1,
733 make_diff(T1, [H|T2], [H|C], c(), L11, L2, Diff).
734make_diff([H1|T1], [], [], C0, L1, L2, [d(L1,L2,C0,-H1)|Diff]) :- !,
735 L11 is L1+1,
736 make_diff(T1, [], [], c(), L11, L2, Diff).
737make_diff([H1|T1], [H2|T2], C, C0, L1, L2, [d(L1,L2,C0,H1-H2)|Diff]) :- !,
738 L11 is L1+1,
739 L21 is L2+1,
740 make_diff(T1, T2, C, c(), L11, L21, Diff).
741
742add_context(c(_,B,C),N,c(B,C,N)).
743add_context(c(A,B), N,c(A,B,N)).
744add_context(c(A), N,c(A,N)).
745add_context(c(), N,c(N)).
746
748
749join_diff([], []).
750join_diff([d(L10,L20,C,L)|T0], [udiff(L1,S1,L2,S2,Diff)|T]) :-
751 pre_context(C, S0, Diff, [L|DiffT]),
752 L1 is L10-S0,
753 L2 is L20-S0,
754 diff_affected(L,S10,S20),
755 S11 is S10+S0,
756 S21 is S20+S0,
757 collect_diff(T0,S11,S21,S1,S2,0,DiffT,T1),
758 join_diff(T1, T).
759
760pre_context(c(), 0, L, L).
761pre_context(c(A), 1, [=(A)|L], L).
762pre_context(c(A,B), 2, [=(A),=(B)|L], L).
763pre_context(c(A,B,C), 3, [=(A),=(B),=(C)|L], L).
764
765collect_diff([d(_,_,_,L)|T0], S10,S20,S1,S2,C,[L|Diff],T) :-
766 C < 3, !,
767 diff_affected(L,S1x,S2x),
768 S11 is S10+S1x,
769 S21 is S20+S2x,
770 collect_diff(T0,S11,S21,S1,S2,0,Diff,T).
771collect_diff([=(L)|T0], S10,S20,S1,S2,C0,[=(L)|Diff],T) :- !,
772 S11 is S10+1,
773 S21 is S20+1,
774 C1 is C0+1,
775 collect_diff(T0,S11,S21,S1,S2,C1,Diff,T).
776collect_diff(T,S1,S2,S1,S2,_,[],T).
777
778diff_affected(+(_), 0, 1).
779diff_affected(-(_), 0, 1).
780diff_affected(-(_,_), 1, 1).
781
785
786udiff_string(udiff(L1,S1,L2,S2,Diff), Final) :-
787 format(string(Hdr), '@@ -~d,~d +~d,~d @@', [L1,S1,L2,S2]),
788 udiff_blocks(Diff, Blocks),
789 maplist(block_lines, Blocks, LineSets),
790 append(LineSets, Lines),
791 atomics_to_string([Hdr|Lines], "\n", Final).
792
793block_lines(=(U), Lines) :- maplist(string_concat(' '), U, Lines).
794block_lines(+(U), Lines) :- maplist(string_concat('+'), U, Lines).
795block_lines(-(U), Lines) :- maplist(string_concat('-'), U, Lines).
796
797udiff_blocks([], []) :- !.
798udiff_blocks([=(H)|T0], [=([H|E])|T]) :- !,
799 udiff_cp(T0, E, T1),
800 udiff_blocks(T1, T).
801udiff_blocks(U, List) :-
802 udiff_block(U, D, A, T1),
803 udiff_add(D,A,List,ListT),
804 udiff_blocks(T1, ListT).
805
806udiff_add([],A,[+A|T],T) :- !.
807udiff_add(D,[],[-D|T],T) :- !.
808udiff_add(D,A,[-D,+A|T],T).
809
810udiff_cp([=(H)|T0], [H|E], T) :- !,
811 udiff_cp(T0, E, T).
812udiff_cp(L, [], L).
813
814udiff_block([-L|T], [L|D], A, Rest) :- !,
815 udiff_block(T, D, A, Rest).
816udiff_block([+L|T], D, [L|A], Rest) :- !,
817 udiff_block(T, D, A, Rest).
818udiff_block([L1-L2|T], [L1|D], [L2|A], Rest) :- !,
819 udiff_block(T, D, A, Rest).
820udiff_block(T, [], [], T).
821
825
826:- thread_local lcs_db/2. 827
828list_lcs([], [], []) :- !.
829list_lcs([H|L1], [H|L2], [H|Lcs]) :- !,
830 list_lcs(L1, L2, Lcs).
831list_lcs(List1, List2, Lcs) :-
832 reverse(List1, Rev1),
833 reverse(List2, Rev2),
834 copy_prefix(Rev1, Rev2, RevDiff1, RevDiff2, RevLcs, RevT),
835 list_lcs2(RevDiff1, RevDiff2, RevT),
836 reverse(RevLcs, Lcs).
837
838list_lcs2(List1, List2, Lcs) :-
839 variant_sha1(List1+List2, Hash),
840 call_cleanup(
841 lcs(List1, List2, Hash, Lcs),
842 retractall(lcs_db(_,_))).
843
844copy_prefix([H|T1], [H|T2], L1, L2, [H|L], LT) :- !,
845 copy_prefix(T1, T2, L1, L2, L, LT).
846copy_prefix(R1, R2, R1, R2, L, L).
847
848
849lcs(_,_,Hash,Lcs) :-
850 lcs_db(Hash,Lcs), !.
851lcs([H|L1], [H|L2], _, [H|Lcs]) :- !,
852 variant_sha1(L1+L2,Hash),
853 lcs(L1, L2, Hash, Lcs).
854lcs(List1, List2, Hash, Lcs) :-
855 List1 = [H1|L1],
856 List2 = [H2|L2],
857 variant_sha1(L1+[H2|L2],Hash1),
858 variant_sha1([H1|L1]+L2,Hash2),
859 lcs( L1 , [H2|L2], Hash1, Lcs1),
860 lcs([H1|L1], L2 , Hash2, Lcs2),
861 longest(Lcs1, Lcs2, Lcs),!,
862 asserta(lcs_db(Hash, Lcs)).
863lcs(_,_,_,[]).
864
865longest(L1, L2, Longest) :-
866 length(L1, Length1),
867 length(L2, Length2),
868 ( Length1 > Length2
869 -> Longest = L1
870 ; Longest = L2
871 ).
872
873 876:- multifile
877 prolog:error_message//1. 878
879prolog:error_message(gitty(not_at_head(Name, _OldCommit))) -->
880 [ 'Gitty: cannot update head for "~w" because it was \c
881 updated by someone else'-[Name] ].
882prolog:error_message(gitty(file_exists(Name))) -->
883 [ 'Gitty: File exists: ~p'-[Name] ].
884prolog:error_message(gitty(commit_version(Name, _Head, _Previous))) -->
885 [ 'Gitty: ~p: cannot update (modified by someone else)'-[Name] ]