| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package CPAN::InGit::MutableTree; |
|
2
|
|
|
|
|
|
|
# ABSTRACT: Utility object that represents a Git Tree and pending changes |
|
3
|
|
|
|
|
|
|
our $VERSION = '0.003'; # VERSION |
|
4
|
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
|
|
6
|
5
|
|
|
5
|
|
3389
|
use Carp; |
|
|
5
|
|
|
|
|
13
|
|
|
|
5
|
|
|
|
|
441
|
|
|
7
|
5
|
|
|
5
|
|
33
|
use Moo; |
|
|
5
|
|
|
|
|
11
|
|
|
|
5
|
|
|
|
|
36
|
|
|
8
|
5
|
|
|
5
|
|
2205
|
use Git::Raw::Index; |
|
|
5
|
|
|
|
|
35
|
|
|
|
5
|
|
|
|
|
170
|
|
|
9
|
5
|
|
|
5
|
|
91
|
use v5.36; |
|
|
5
|
|
|
|
|
21
|
|
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
has parent => ( is => 'ro', required => 1 ); |
|
13
|
|
|
|
|
|
|
has tree => ( is => 'rw' ); |
|
14
|
|
|
|
|
|
|
has branch => ( is => 'rw' ); |
|
15
|
|
|
|
|
|
|
has _changes => ( is => 'rw' ); |
|
16
|
|
|
|
|
|
|
has has_changes => ( is => 'rw' ); |
|
17
|
|
|
|
|
|
|
has use_workdir => ( is => 'rw' ); |
|
18
|
42
|
|
|
42
|
1
|
4009
|
sub git_repo { shift->parent->git_repo } |
|
19
|
|
|
|
|
|
|
|
|
20
|
11
|
|
|
11
|
0
|
18024
|
sub BUILD($self, $args, @) { |
|
|
11
|
|
|
|
|
28
|
|
|
|
11
|
|
|
|
|
26
|
|
|
|
11
|
|
|
|
|
26
|
|
|
21
|
|
|
|
|
|
|
# branch supplied by name? look it up |
|
22
|
11
|
50
|
66
|
|
|
76
|
if (defined $self->{branch} && !ref $self->{branch}) { |
|
23
|
0
|
0
|
|
|
|
0
|
my $b= Git::Raw::Branch->lookup($self->git_repo, $self->{branch}, 1) |
|
24
|
|
|
|
|
|
|
or croak "No local branch named '$self->{branch}'"; |
|
25
|
0
|
|
|
|
|
0
|
$self->{branch}= $b; |
|
26
|
|
|
|
|
|
|
} |
|
27
|
|
|
|
|
|
|
# If branch supplied and tree was not, look up the tree |
|
28
|
11
|
100
|
100
|
|
|
85
|
if ($self->{branch} && !$self->{tree}) { |
|
29
|
1
|
|
|
|
|
55
|
$self->{tree}= $self->{branch}->peel('tree'); |
|
30
|
|
|
|
|
|
|
} |
|
31
|
|
|
|
|
|
|
} |
|
32
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
|
34
|
42
|
|
|
42
|
1
|
3486
|
sub get_path($self, $path) { |
|
|
42
|
|
|
|
|
69
|
|
|
|
42
|
|
|
|
|
77
|
|
|
|
42
|
|
|
|
|
69
|
|
|
35
|
42
|
100
|
|
|
|
173
|
if ($self->has_changes) { |
|
36
|
11
|
100
|
|
|
|
51
|
if (keys $self->_changes->%*) { |
|
37
|
10
|
|
|
|
|
23
|
my $node= $self->_changes; |
|
38
|
10
|
|
|
|
|
40
|
my @path= split '/', $path; |
|
39
|
10
|
|
|
|
|
44
|
my $basename= pop @path; |
|
40
|
10
|
|
|
|
|
29
|
for (@path) { |
|
41
|
33
|
100
|
|
|
|
87
|
$node= $node->{$_} if defined $node; |
|
42
|
|
|
|
|
|
|
} |
|
43
|
10
|
100
|
100
|
|
|
123
|
return $node->{$basename} if ref $node eq 'HASH' && $node->{$basename}; |
|
44
|
|
|
|
|
|
|
} |
|
45
|
5
|
100
|
|
|
|
22
|
if ($self->use_workdir) { |
|
46
|
1
|
|
|
|
|
5
|
my $ent= $self->git_repo->index->find($path); |
|
47
|
1
|
50
|
|
|
|
185
|
return [ $ent->blob, $ent->mode ] |
|
48
|
|
|
|
|
|
|
if $ent; |
|
49
|
|
|
|
|
|
|
} |
|
50
|
|
|
|
|
|
|
} |
|
51
|
35
|
100
|
|
|
|
196
|
if ($self->tree) { |
|
52
|
30
|
100
|
|
|
|
741
|
my $dirent= $self->tree->entry_bypath($path) |
|
53
|
|
|
|
|
|
|
or return undef; |
|
54
|
24
|
|
|
|
|
3020
|
return [ $dirent->object, $dirent->file_mode ]; |
|
55
|
|
|
|
|
|
|
} |
|
56
|
5
|
|
|
|
|
44
|
return undef; |
|
57
|
|
|
|
|
|
|
} |
|
58
|
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
|
|
60
|
22
|
|
|
22
|
1
|
9469
|
sub set_path($self, $path, $data, %opts) { |
|
|
22
|
|
|
|
|
40
|
|
|
|
22
|
|
|
|
|
53
|
|
|
|
22
|
|
|
|
|
36
|
|
|
|
22
|
|
|
|
|
45
|
|
|
|
22
|
|
|
|
|
39
|
|
|
61
|
|
|
|
|
|
|
# Two modes: we can be writing to the working directory and index, or be building a new tree |
|
62
|
|
|
|
|
|
|
# (which may or may not be connected to a branch) |
|
63
|
22
|
|
|
|
|
152
|
my $repo= $self->git_repo; |
|
64
|
22
|
|
100
|
|
|
126
|
my $mode= $opts{mode} // 0100644; |
|
65
|
22
|
|
|
|
|
177
|
my @path= split m{/+}, $path; |
|
66
|
22
|
|
|
|
|
51
|
my $basename= pop @path; |
|
67
|
22
|
100
|
|
|
|
81
|
if ($self->use_workdir) { |
|
68
|
1
|
|
|
|
|
5
|
my $fullpath= $self->git_repo->workdir; |
|
69
|
|
|
|
|
|
|
# create missing directories |
|
70
|
1
|
|
|
|
|
5
|
for (@path) { |
|
71
|
1
|
|
|
|
|
4
|
$fullpath .= '/'.$_; |
|
72
|
1
|
50
|
50
|
|
|
181
|
mkdir $fullpath || die "mkdir($fullpath): $!" |
|
73
|
|
|
|
|
|
|
unless -d $fullpath; |
|
74
|
|
|
|
|
|
|
} |
|
75
|
1
|
|
|
|
|
5
|
$fullpath .= '/'.$basename; |
|
76
|
1
|
50
|
|
|
|
8
|
if (!defined $data) { |
|
77
|
0
|
|
|
|
|
0
|
unlink($fullpath); |
|
78
|
0
|
|
|
|
|
0
|
$self->git_repo->index->remove($path); |
|
79
|
|
|
|
|
|
|
} else { |
|
80
|
|
|
|
|
|
|
# a shame there's no way to add the blob directly... |
|
81
|
1
|
50
|
|
|
|
21
|
$data= \$data->content if ref($data)->isa('Git::Raw::Blob'); |
|
82
|
|
|
|
|
|
|
# Write file |
|
83
|
1
|
|
|
|
|
7
|
_mkfile($fullpath, $data, $mode); |
|
84
|
|
|
|
|
|
|
# Add to the index |
|
85
|
1
|
|
|
|
|
4
|
$self->git_repo->index->add_frombuffer($path, $data, $mode); |
|
86
|
|
|
|
|
|
|
} |
|
87
|
|
|
|
|
|
|
} |
|
88
|
|
|
|
|
|
|
else { |
|
89
|
21
|
|
100
|
|
|
111
|
my $node= ($self->{_changes} //= {}); |
|
90
|
21
|
|
|
|
|
52
|
for (@path) { |
|
91
|
54
|
|
100
|
|
|
196
|
$node= ($node->{$_} //= {}); |
|
92
|
54
|
50
|
|
|
|
226
|
ref $node eq 'HASH' or die "Can't set '$path'; '$_' is not a directory"; |
|
93
|
|
|
|
|
|
|
} |
|
94
|
|
|
|
|
|
|
# Content may either be a Blob object or a scalar-ref of bytes |
|
95
|
21
|
100
|
|
|
|
67
|
if (ref $data eq 'SCALAR') { |
|
96
|
18
|
|
|
|
|
14533
|
$data= Git::Raw::Blob->create($repo, $$data); |
|
97
|
|
|
|
|
|
|
} |
|
98
|
21
|
50
|
|
|
|
191
|
$node->{$basename}= defined $data? [ $data, $mode ] : undef; |
|
99
|
|
|
|
|
|
|
} |
|
100
|
22
|
|
|
|
|
122
|
$self->has_changes(1); |
|
101
|
22
|
|
100
|
|
|
74
|
$self->{_changes} //= {}; |
|
102
|
22
|
|
|
|
|
126
|
$self; |
|
103
|
|
|
|
|
|
|
} |
|
104
|
|
|
|
|
|
|
|
|
105
|
1
|
|
|
1
|
|
2
|
sub _mkfile($path, $scalarref, $mode) { |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
2
|
|
|
106
|
1
|
50
|
|
|
|
238
|
open my $fh, '>', $path or die "open($path): $!"; |
|
107
|
1
|
50
|
|
|
|
44
|
$fh->print($$scalarref) or die "write($path): $!"; |
|
108
|
1
|
50
|
|
|
|
25
|
$fh->close or die "close($path): $!"; |
|
109
|
1
|
50
|
0
|
|
|
81
|
chmod($path, $mode) || die "chmod($path, $mode): $!" |
|
|
|
|
33
|
|
|
|
|
|
110
|
|
|
|
|
|
|
if defined $mode && $mode != 0100644; |
|
111
|
|
|
|
|
|
|
} |
|
112
|
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
|
|
114
|
9
|
|
|
9
|
1
|
4749
|
sub update_tree($self) { |
|
|
9
|
|
|
|
|
19
|
|
|
|
9
|
|
|
|
|
17
|
|
|
115
|
|
|
|
|
|
|
# If using the Index, the index can write the new tree |
|
116
|
9
|
100
|
|
|
|
74
|
if ($self->use_workdir) { |
|
117
|
2
|
|
|
|
|
7
|
$self->tree($self->git_repo->index->write_tree); |
|
118
|
|
|
|
|
|
|
} else { |
|
119
|
7
|
|
|
|
|
24
|
$self->tree(_assemble_tree($self->git_repo, $self->tree, $self->_changes)); |
|
120
|
7
|
|
|
|
|
122
|
$self->_changes({}); # reset the changes hash |
|
121
|
|
|
|
|
|
|
} |
|
122
|
|
|
|
|
|
|
# don't reset has_changes until it has been committed |
|
123
|
|
|
|
|
|
|
} |
|
124
|
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
# merge a hashref of changes into the previous Tree, and return the new Tree |
|
126
|
|
|
|
|
|
|
# Changes look like: |
|
127
|
|
|
|
|
|
|
# { |
|
128
|
|
|
|
|
|
|
# "path1" => { |
|
129
|
|
|
|
|
|
|
# "filename" => [ $blob, $mode ], |
|
130
|
|
|
|
|
|
|
# "fname2" => [ $blob, $mode ], |
|
131
|
|
|
|
|
|
|
# } |
|
132
|
|
|
|
|
|
|
# } |
|
133
|
30
|
|
|
30
|
|
43
|
sub _assemble_tree($repo, $tree, $changes) { |
|
|
30
|
|
|
|
|
40
|
|
|
|
30
|
|
|
|
|
39
|
|
|
|
30
|
|
|
|
|
31
|
|
|
|
30
|
|
|
|
|
38
|
|
|
134
|
30
|
100
|
|
|
|
243
|
my $treebuilder= Git::Raw::Tree::Builder->new($repo, ($tree? ($tree) : ())); |
|
135
|
30
|
|
|
|
|
210
|
for my $name (keys %$changes) { |
|
136
|
39
|
|
|
|
|
67
|
my $ent= $changes->{$name}; |
|
137
|
39
|
50
|
|
|
|
68
|
if (!defined $ent) { |
|
138
|
0
|
|
|
|
|
0
|
$treebuilder->remove($name); |
|
139
|
|
|
|
|
|
|
} |
|
140
|
|
|
|
|
|
|
else { |
|
141
|
39
|
100
|
|
|
|
128
|
if (ref $ent eq 'HASH') { # a subdirectory |
|
142
|
23
|
|
|
|
|
83
|
my $dirent= $treebuilder->get($name); |
|
143
|
23
|
100
|
66
|
|
|
78
|
my $subdir= $dirent && $dirent->type == Git::Raw::Object::TREE() |
|
144
|
|
|
|
|
|
|
? Git::Raw::Tree->lookup($repo, $dirent->id) : undef; |
|
145
|
23
|
|
|
|
|
154
|
$ent= [ _assemble_tree($repo, $subdir, $ent), 0040000 ]; |
|
146
|
|
|
|
|
|
|
} |
|
147
|
39
|
|
|
|
|
2149
|
$treebuilder->insert($name, @$ent); |
|
148
|
|
|
|
|
|
|
} |
|
149
|
|
|
|
|
|
|
} |
|
150
|
30
|
|
|
|
|
20166
|
return $treebuilder->write; # returns Git::Raw::Tree |
|
151
|
|
|
|
|
|
|
} |
|
152
|
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
|
|
154
|
7
|
|
|
7
|
1
|
2268
|
sub commit($self, $message, %opts) { |
|
|
7
|
|
|
|
|
16
|
|
|
|
7
|
|
|
|
|
16
|
|
|
|
7
|
|
|
|
|
31
|
|
|
|
7
|
|
|
|
|
13
|
|
|
155
|
7
|
50
|
|
|
|
36
|
croak "No changes added" unless $self->has_changes; |
|
156
|
7
|
|
|
|
|
40
|
my $repo= $self->git_repo; |
|
157
|
7
|
|
|
|
|
32
|
$self->update_tree; |
|
158
|
7
|
|
|
|
|
46
|
my $branch= $self->branch; |
|
159
|
7
|
|
|
|
|
57
|
my $cur_sig= $self->parent->new_signature; |
|
160
|
7
|
|
33
|
|
|
81
|
my $author= $opts{author} // $cur_sig; |
|
161
|
7
|
|
66
|
|
|
46
|
my $update_head= $self->use_workdir // $opts{update_head}; |
|
162
|
7
|
|
33
|
|
|
48
|
my $committer= $opts{committer} // $cur_sig; |
|
163
|
|
|
|
|
|
|
my $parents= $self->use_workdir? ( |
|
164
|
|
|
|
|
|
|
# dies on new repo if HEAD doesn't exist yet, in which case no parents |
|
165
|
|
|
|
|
|
|
eval { [ $self->git_repo->head->target ] } || [] |
|
166
|
|
|
|
|
|
|
) |
|
167
|
|
|
|
|
|
|
: $branch? [ $self->branch->peel('commit') ] |
|
168
|
7
|
50
|
50
|
|
|
59
|
: length $opts{create_branch}? [] # fresh branch, no parent commit |
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
: croak "Can't commit without a branch or use_workdir or option create_branch"; |
|
170
|
|
|
|
|
|
|
# undef final param means don't update HEAD |
|
171
|
7
|
50
|
|
|
|
5744
|
my $commit= Git::Raw::Commit->create($repo, $message, $author, $committer, $parents, $self->tree, undef) |
|
172
|
|
|
|
|
|
|
or croak "commit failed"; |
|
173
|
7
|
100
|
|
|
|
130
|
if ($opts{create_branch}) { |
|
|
|
50
|
|
|
|
|
|
|
174
|
6
|
|
|
|
|
55
|
$branch= $repo->branch($opts{create_branch}, $commit); |
|
175
|
6
|
|
|
|
|
4577
|
$self->branch($branch); |
|
176
|
|
|
|
|
|
|
} elsif ($branch) { |
|
177
|
|
|
|
|
|
|
# Update the branch |
|
178
|
1
|
|
|
|
|
837
|
$branch->target($commit); |
|
179
|
|
|
|
|
|
|
} |
|
180
|
7
|
100
|
66
|
|
|
1680
|
$repo->head($branch) if $branch && $update_head; |
|
181
|
|
|
|
|
|
|
# persist the index state to disk, which clears the staged changes and brings the index |
|
182
|
|
|
|
|
|
|
# in sync with the commit |
|
183
|
7
|
100
|
|
|
|
487
|
$repo->index->write if $self->use_workdir; |
|
184
|
7
|
|
|
|
|
36
|
$self->has_changes(0); |
|
185
|
7
|
|
|
|
|
117
|
return $commit; |
|
186
|
|
|
|
|
|
|
} |
|
187
|
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
1; |
|
189
|
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
__END__ |