Add ::Object::Blob.
[catagits/Gitalist.git] / lib / Gitalist / Git / Object.pm
CommitLineData
a8a8f8f9 1use MooseX::Declare;
84f31a44 2use Moose::Autobox;
a8a8f8f9 3
4class Gitalist::Git::Object {
b36b7e0b 5 use MooseX::Types::Moose qw/Str Int Bool Maybe ArrayRef/;
84f31a44 6 use MooseX::Types::Common::String qw/NonEmptySimpleStr/;
a8a8f8f9 7 use File::Stat::ModeString qw/mode_to_string/;
1501cb4e 8 use List::MoreUtils qw/any zip/;
9
10 our $SHA1RE = qr/[0-9a-fA-F]{40}/;
11
54368e9d 12 # project and sha1 are required initargs
50394a3e 13 has project => ( isa => 'Gitalist::Git::Project',
14 required => 1,
15 is => 'ro',
84f31a44 16 weak_ref => 1,
77edf882 17 handles => {
18 _run_cmd => 'run_cmd',
1501cb4e 19 _run_cmd_list => 'run_cmd_list',
77edf882 20 _get_gpp_object => 'get_gpp_object',
21 },
50394a3e 22 );
84f31a44 23 has sha1 => ( isa => NonEmptySimpleStr,
1501cb4e 24 required => 1,
25 is => 'ro' );
54368e9d 26
84f31a44 27 has $_ => ( isa => NonEmptySimpleStr,
1501cb4e 28 required => 1,
29 is => 'ro',
30 lazy_build => 1 )
483b98b7 31 for qw/type modestr size/;
54368e9d 32
77edf882 33 has _gpp_obj => ( isa => 'Git::PurePerl::Object',
34 required => 1,
35 is => 'ro',
36 lazy_build => 1,
f3083570 37 handles => [ 'content',
38 ],
77edf882 39 );
40
54368e9d 41 # objects can't determine their mode or filename
84f31a44 42 has file => ( isa => NonEmptySimpleStr,
54368e9d 43 required => 0,
44 is => 'ro' );
45 has mode => ( isa => Int,
1501cb4e 46 required => 1,
47 default => 0,
48 is => 'ro' );
54368e9d 49
b36b7e0b 50 has tree => ( isa => 'ArrayRef[Gitalist::Git::Object]',
51 required => 0,
52 is => 'ro',
53 lazy_build => 1 );
54
77edf882 55 method BUILD { $self->$_() for qw/_gpp_obj type size modestr/ }
56
b36b7e0b 57 method _build_tree {
58 confess("Can't list_tree on a blob object.")
59 if $self->type eq 'blob';
60 my $output = $self->_run_cmd(qw/ls-tree -z/, $self->sha1);
61 return unless defined $output;
62
63 my @ret;
64 for my $line (split /\0/, $output) {
65 my ($mode, $type, $object, $file) = split /\s+/, $line, 4;
66 push @ret, Gitalist::Git::Object->new( mode => oct $mode,
67 type => $type,
68 sha1 => $object,
69 file => $file,
70 project => $self->project,
71 );
72 }
73 return \@ret;
74 }
1501cb4e 75
76 method diff ( Maybe[Bool] :$patch?,
77 Maybe[NonEmptySimpleStr] :$parent?,
78 Maybe[NonEmptySimpleStr] :$file?
79 ) {
80 # Use parent if specifed, else take the parent from the commit
81 # if there is only one, otherwise it was a merge commit.
82 $parent = $parent
83 ? $parent
84 : $self->parents <= 1
85 ? $self->parent_sha1
86 : '-c';
87 my @etc = (
88 ( $file ? ('--', $file) : () ),
89 );
90
91 my @out = $self->_raw_diff(
92 ( $patch ? '--patch-with-raw' : () ),
93 ( $parent ? $parent : () ),
94 $self->sha1, @etc,
95 );
96
97 # XXX Yes, there is much wrongness having _parse_diff_tree be destructive.
98 my @difftree = $self->_parse_diff_tree(\@out);
99
100 return \@difftree
101 unless $patch;
102
103 # The blank line between the tree and the patch.
104 shift @out;
105
106 # XXX And no I'm not happy about having diff return tree + patch.
107 return \@difftree, [$self->_parse_diff(@out)];
108 }
109
110## Private methods
111 # gitweb uses the following sort of command for diffing merges:
112 # /home/dbrook/apps/bin/git --git-dir=/home/dbrook/dev/app/.git diff-tree -r -M --no-commit-id --patch-with-raw --full-index --cc 316cf158df3f6207afbae7270bcc5ba0 --
113 # and for regular diffs
114 # /home/dbrook/apps/bin/git --git-dir=/home/dbrook/dev/app/.git diff-tree -r -M --no-commit-id --patch-with-raw --full-index 2e3454ca0749641b42f063730b0090e1 316cf158df3f6207afbae7270bcc5ba0 --
115 method _raw_diff (@args) {
116 return $self->_run_cmd_list(
117 qw(diff-tree -r -M --no-commit-id --full-index),
118 @args
119 );
120 }
121
122 method _parse_diff_tree ($diff) {
123 my @keys = qw(modesrc modedst sha1src sha1dst status src dst);
124 my @ret;
125 while (@$diff and $diff->[0] =~ /^:\d+/) {
126 my $line = shift @$diff;
127 # see. man git-diff-tree for more info
128 # mode src, mode dst, sha1 src, sha1 dst, status, src[, dst]
129 my @vals = $line =~ /^:(\d+) (\d+) ($SHA1RE) ($SHA1RE) ([ACDMRTUX]\d*)\t([^\t]+)(?:\t([^\n]+))?$/;
130 my %line = zip @keys, @vals;
131 # Some convenience keys
132 $line{file} = $line{src};
133 $line{sha1} = $line{sha1dst};
134 $line{is_new} = $line{sha1src} =~ /^0+$/
135 if $line{sha1src};
136 @line{qw/status sim/} = $line{status} =~ /(R)(\d+)/
137 if $line{status} =~ /^R/;
138 push @ret, \%line;
139 }
140
141 return @ret;
142 }
143
144 method _parse_diff (@diff) {
145 my @ret;
146 for (@diff) {
147 # This regex is a little pathological.
148 if (m{^diff --git (a/(.*?)) (b/\2)}) {
149 push @ret, {
150 head => $_,
151 a => $1,
152 b => $3,
153 file => $2,
154 diff => '',
155 };
156 next;
157 }
158
159 if (/^index (\w+)\.\.(\w+) (\d+)$/) {
160 @{$ret[-1]}{qw(index src dst mode)} = ($_, $1, $2, $3);
161 next
162 }
163
164 # XXX Somewhat hacky. Ahem.
165 $ret[@ret ? -1 : 0]{diff} .= "$_\n";
166 }
167
168 return @ret;
169 }
170
171
172## Builders
173method _build__gpp_obj {
77edf882 174 return $self->_get_gpp_object($self->sha1)
175 }
a8a8f8f9 176
84f31a44 177 foreach my $key (qw/ type size /) {
178 method "_build_$key" {
10af354d 179 my $v = $self->_cat_file_with_flag(substr($key, 0, 1));
180 chomp($v);
181 return $v;
84f31a44 182 }
50394a3e 183 }
54368e9d 184
a8a8f8f9 185 method _build_modestr {
8d953eae 186 my $modestr = mode_to_string($self->mode);
a8a8f8f9 187 return $modestr;
188 }
189
84f31a44 190 method _cat_file_with_flag ($flag) {
77edf882 191 $self->_run_cmd('cat-file', '-' . $flag, $self->{sha1})
50394a3e 192 }
193
a8a8f8f9 194} # end class