| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  |  | 
| 2 |  |  |  |  |  |  | use 5.010; | 
| 3 | 50 |  |  | 50 |  | 899 | use utf8; | 
|  | 50 |  |  |  |  | 160 |  | 
| 4 | 50 |  |  | 50 |  | 250 | use strict; | 
|  | 50 |  |  |  |  | 101 |  | 
|  | 50 |  |  |  |  | 280 |  | 
| 5 | 50 |  |  | 50 |  | 1092 | use List::Util; | 
|  | 50 |  |  |  |  | 117 |  | 
|  | 50 |  |  |  |  | 1008 |  | 
| 6 | 50 |  |  | 50 |  | 250 | use Locale::TextDomain qw(App-Sqitch); | 
|  | 50 |  |  |  |  | 2606 |  | 
|  | 50 |  |  |  |  | 2945 |  | 
| 7 | 50 |  |  | 50 |  | 369 | use App::Sqitch::X qw(hurl); | 
|  | 50 |  |  |  |  | 111 |  | 
|  | 50 |  |  |  |  | 340 |  | 
| 8 | 50 |  |  | 50 |  | 8472 |  | 
|  | 50 |  |  |  |  | 1988 |  | 
|  | 50 |  |  |  |  | 474 |  | 
| 9 |  |  |  |  |  |  | our $VERSION = 'v1.3.1'; # VERSION | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | my $class = shift; | 
| 12 |  |  |  |  |  |  | my $self = bless { | 
| 13 | 245 |  |  | 245 | 1 | 14277 | list           => [], | 
| 14 | 245 |  |  |  |  | 1379 | lookup         => {}, | 
| 15 |  |  |  |  |  |  | last_tagged_at => undef, | 
| 16 |  |  |  |  |  |  | } => $class; | 
| 17 |  |  |  |  |  |  | $self->append(@_) if @_; | 
| 18 |  |  |  |  |  |  | return $self; | 
| 19 | 245 | 100 |  |  |  | 1373 | } | 
| 20 | 245 |  |  |  |  | 4339 |  | 
| 21 |  |  |  |  |  |  |  | 
| 22 |  |  |  |  |  |  | # Like [:punct:], but excluding _. Copied from perlrecharclass. | 
| 23 | 125 |  |  | 125 | 1 | 960 | my $punct = q{-!"#$%&'()*+,./:;<=>?@[\\]^`{|}~}; | 
|  | 125 |  |  |  |  | 499 |  | 
| 24 | 41 |  |  | 41 | 1 | 479 |  | 
|  | 41 |  |  |  |  | 265 |  | 
| 25 | 11 |  |  | 11 | 1 | 114 | # Look for symbolic references. | 
|  | 32 |  |  |  |  | 186 |  | 
|  | 11 |  |  |  |  | 34 |  | 
| 26 | 1 |  |  | 1 | 1 | 3 | if ( $_[0] =~ s{(?<![$punct])([~^])(?:(\1)|(\d+))?\z}{} ) { | 
|  | 1 |  |  |  |  | 6 |  | 
| 27 | 599 |  |  | 599 | 1 | 7680 | my $offset = $3 // ($2 ? 2 : 1); | 
| 28 | 21 |  |  | 21 | 1 | 493 | $offset *= -1 if $1 eq '^'; | 
| 29 |  |  |  |  |  |  | return $offset; | 
| 30 |  |  |  |  |  |  | } else { | 
| 31 |  |  |  |  |  |  | return 0; | 
| 32 |  |  |  |  |  |  | } | 
| 33 |  |  |  |  |  |  | } | 
| 34 |  |  |  |  |  |  |  | 
| 35 | 889 | 100 |  | 889 |  | 5738 | my ( $self, $key ) = @_; | 
| 36 | 70 | 100 | 66 |  |  | 415 |  | 
| 37 | 70 | 100 |  |  |  | 213 | # Look for non-deployed symbolic references. | 
| 38 | 70 |  |  |  |  | 224 | if ( my $offset = _offset $key ) { | 
| 39 |  |  |  |  |  |  | my $idx = $self->_index_of( $key ) // return undef; | 
| 40 | 819 |  |  |  |  | 2385 | $idx += $offset; | 
| 41 |  |  |  |  |  |  | return $idx < 0 ? undef : $idx > $#{ $self->{list} } ? undef : $idx; | 
| 42 |  |  |  |  |  |  | } else { | 
| 43 |  |  |  |  |  |  | return $self->_index_of( $key ); | 
| 44 |  |  |  |  |  |  | } | 
| 45 | 577 |  |  | 577 | 1 | 10193 | } | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | my ( $self, $key ) = @_; | 
| 48 | 577 | 100 |  |  |  | 1367 |  | 
| 49 | 43 |  | 50 |  |  | 111 | my ( $change, $tag ) = split /@/ => $key, 2; | 
| 50 | 43 |  |  |  |  | 69 |  | 
| 51 | 43 | 100 |  |  |  | 88 | if ($change eq '') { | 
|  | 40 | 100 |  |  |  | 266 |  | 
| 52 |  |  |  |  |  |  | # Just want the change with the associated tag. | 
| 53 | 534 |  |  |  |  | 1254 | my $idx = $self->{lookup}{'@' . $tag} or return undef; | 
| 54 |  |  |  |  |  |  | return $idx->[0]; | 
| 55 |  |  |  |  |  |  | } | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  | my $idx = $self->{lookup}{$change} or return undef; | 
| 58 | 577 |  |  | 577 |  | 965 | if (defined $tag) { | 
| 59 |  |  |  |  |  |  | # Wanted for a change as of a specific tag. | 
| 60 | 577 |  |  |  |  | 1788 | my $tag_idx = $self->{lookup}{'@' . $tag} or hurl plan => __x( | 
| 61 |  |  |  |  |  |  | 'Unknown tag "{tag}"', | 
| 62 | 577 | 100 |  |  |  | 1375 | tag => '@' . $tag, | 
| 63 |  |  |  |  |  |  | ); | 
| 64 | 158 | 100 |  |  |  | 708 | $tag_idx = $tag_idx->[0]; | 
| 65 | 119 |  |  |  |  | 543 | for my $i (reverse @{ $idx }) { | 
| 66 |  |  |  |  |  |  | return $i if $i <= $tag_idx; | 
| 67 |  |  |  |  |  |  | } | 
| 68 | 419 | 100 |  |  |  | 1585 | return undef; | 
| 69 | 345 | 100 |  |  |  | 724 | }; | 
| 70 |  |  |  |  |  |  |  | 
| 71 | 130 | 100 |  |  |  | 460 | # Just want index for a change name. Return if we have 0 or 1. | 
| 72 |  |  |  |  |  |  | return $idx->[0] if @{ $idx } < 2; | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  |  | 
| 75 | 128 |  |  |  |  | 255 | # Too many changes found. Give the user some options and die. | 
| 76 | 128 |  |  |  |  | 183 | App::Sqitch->vent(__x( | 
|  | 128 |  |  |  |  | 300 |  | 
| 77 | 164 | 100 |  |  |  | 724 | 'Change "{change}" is ambiguous. Please specify a tag-qualified change:', | 
| 78 |  |  |  |  |  |  | change => $key, | 
| 79 | 3 |  |  |  |  | 23 | )); | 
| 80 |  |  |  |  |  |  |  | 
| 81 |  |  |  |  |  |  | my $list = $self->{list}; | 
| 82 |  |  |  |  |  |  | App::Sqitch->vent( '  * ', $list->[$_]->format_tag_qualified_name ) | 
| 83 | 215 | 100 |  |  |  | 325 | for reverse @{ $idx }; | 
|  | 215 |  |  |  |  | 973 |  | 
| 84 |  |  |  |  |  |  |  | 
| 85 |  |  |  |  |  |  | hurl plan => __ 'Change lookup failed'; | 
| 86 |  |  |  |  |  |  | } | 
| 87 | 4 |  |  |  |  | 26 |  | 
| 88 |  |  |  |  |  |  | my ( $self, $key, $since ) = @_; | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  | # Look for non-deployed symbolic references. | 
| 91 |  |  |  |  |  |  | if ( my $offset = _offset $key ) { | 
| 92 | 4 |  |  |  |  | 1093 | my $idx = $self->_first_index_of( $key, $since ) // return undef; | 
| 93 |  |  |  |  |  |  | $idx += $offset; | 
| 94 | 4 |  |  |  |  | 10 | return $idx < 0 ? undef : $idx > $#{ $self->{list} } ? undef : $idx; | 
|  | 4 |  |  |  |  | 35 |  | 
| 95 |  |  |  |  |  |  | } else { | 
| 96 | 4 |  |  |  |  | 38 | return $self->_first_index_of( $key, $since ); | 
| 97 |  |  |  |  |  |  | } | 
| 98 |  |  |  |  |  |  | } | 
| 99 |  |  |  |  |  |  |  | 
| 100 | 286 |  |  | 286 | 1 | 1297 | my ( $self, $change, $since ) = @_; | 
| 101 |  |  |  |  |  |  |  | 
| 102 |  |  |  |  |  |  | # Just return the first index if no tag. | 
| 103 | 286 | 100 |  |  |  | 586 | my $idx = $self->{lookup}{$change} or return undef; | 
| 104 | 24 |  | 50 |  |  | 65 | return $idx->[0] unless defined $since; | 
| 105 | 24 |  |  |  |  | 41 |  | 
| 106 | 24 | 50 |  |  |  | 63 | # Find the tag index. | 
|  | 24 | 50 |  |  |  | 139 |  | 
| 107 |  |  |  |  |  |  | my $since_index = $self->index_of($since) // hurl plan => __x( | 
| 108 | 262 |  |  |  |  | 683 | 'Unknown change: "{change}"', | 
| 109 |  |  |  |  |  |  | change => $since, | 
| 110 |  |  |  |  |  |  | ); | 
| 111 |  |  |  |  |  |  |  | 
| 112 |  |  |  |  |  |  | # Return the first change after the tag. | 
| 113 | 286 |  |  | 286 |  | 548 | return List::Util::first { $_ > $since_index } @{ $idx }; | 
| 114 |  |  |  |  |  |  | } | 
| 115 |  |  |  |  |  |  |  | 
| 116 | 286 | 100 |  |  |  | 1887 | shift->{last_tagged_at}; | 
| 117 | 154 | 100 |  |  |  | 663 | } | 
| 118 |  |  |  |  |  |  |  | 
| 119 |  |  |  |  |  |  | my $self = shift; | 
| 120 | 37 |  | 100 |  |  | 71 | return defined $self->{last_tagged_at} | 
| 121 |  |  |  |  |  |  | ? $self->{list}[ $self->{last_tagged_at} ] | 
| 122 |  |  |  |  |  |  | : undef; | 
| 123 |  |  |  |  |  |  | } | 
| 124 |  |  |  |  |  |  |  | 
| 125 |  |  |  |  |  |  | my $self = shift; | 
| 126 | 34 |  |  | 42 |  | 124 | my $idx = $self->index_of(@_) // return undef; | 
|  | 42 |  |  |  |  | 190 |  | 
|  | 34 |  |  |  |  | 108 |  | 
| 127 |  |  |  |  |  |  | return $self->{list}[ $idx ]; | 
| 128 |  |  |  |  |  |  | } | 
| 129 |  |  |  |  |  |  |  | 
| 130 | 29 |  |  | 29 | 1 | 1017 | my ( $self, $name ) = @_; | 
| 131 |  |  |  |  |  |  | return defined (  $name =~ /@/ | 
| 132 |  |  |  |  |  |  | ? $self->index_of($name) | 
| 133 |  |  |  |  |  |  | : $self->first_index_of($name) | 
| 134 | 22 |  |  | 22 | 1 | 1167 | ); | 
| 135 |  |  |  |  |  |  | } | 
| 136 | 22 | 100 |  |  |  | 137 |  | 
| 137 |  |  |  |  |  |  | my ( $self, $name ) = @_; | 
| 138 |  |  |  |  |  |  | my $idx   = $name =~ /@/ | 
| 139 |  |  |  |  |  |  | ? $self->index_of($name) | 
| 140 |  |  |  |  |  |  | : $self->first_index_of($name); | 
| 141 | 177 |  |  | 177 | 1 | 4109 | return defined $idx ? $self->change_at($idx) : undef; | 
| 142 | 177 |  | 100 |  |  | 478 | } | 
| 143 | 146 |  |  |  |  | 900 |  | 
| 144 |  |  |  |  |  |  | my $self   = shift; | 
| 145 |  |  |  |  |  |  | my $list   = $self->{list}; | 
| 146 |  |  |  |  |  |  | my $lookup = $self->{lookup}; | 
| 147 | 175 |  |  | 175 | 1 | 3342 |  | 
| 148 | 175 | 100 |  |  |  | 851 | for my $change (@_) { | 
| 149 |  |  |  |  |  |  | push @{ $list } => $change; | 
| 150 |  |  |  |  |  |  | push @{ $lookup->{ $change->format_name } } => $#$list; | 
| 151 |  |  |  |  |  |  | $lookup->{ $change->id } = my $pos = [$#$list]; | 
| 152 |  |  |  |  |  |  |  | 
| 153 |  |  |  |  |  |  | # Index on the tags, too. | 
| 154 |  |  |  |  |  |  | for my $tag ($change->tags) { | 
| 155 | 89 |  |  | 89 | 1 | 769 | $lookup->{ $tag->format_name } = $pos; | 
| 156 | 89 | 100 |  |  |  | 378 | $lookup->{ $tag->id }          = $pos; | 
| 157 |  |  |  |  |  |  | $self->{last_tagged_at} = $#$list; | 
| 158 |  |  |  |  |  |  | } | 
| 159 | 89 | 100 |  |  |  | 269 | } | 
| 160 |  |  |  |  |  |  |  | 
| 161 |  |  |  |  |  |  | $lookup->{'HEAD'} = $lookup->{'@HEAD'} = [$#$list]; | 
| 162 |  |  |  |  |  |  | $lookup->{'ROOT'} = $lookup->{'@ROOT'} = [0]; | 
| 163 | 274 |  |  | 274 | 1 | 1360 |  | 
| 164 | 274 |  |  |  |  | 573 | return $self; | 
| 165 | 274 |  |  |  |  | 445 | } | 
| 166 |  |  |  |  |  |  |  | 
| 167 | 274 |  |  |  |  | 637 | my ( $self, $index, $tag ) = @_; | 
| 168 | 777 |  |  |  |  | 6031 | my $list   = $self->{list}; | 
|  | 777 |  |  |  |  | 1429 |  | 
| 169 | 777 |  |  |  |  | 1084 | my $lookup = $self->{lookup}; | 
|  | 777 |  |  |  |  | 2343 |  | 
| 170 | 777 |  |  |  |  | 12624 | my $pos = [$index]; | 
| 171 |  |  |  |  |  |  | $lookup->{ $tag->id } = $pos; | 
| 172 |  |  |  |  |  |  | $lookup->{ $tag->format_name } = $pos; | 
| 173 | 777 |  |  |  |  | 39642 | $self->{last_tagged_at} = $index if $index == $#{ $self->{list} }; | 
| 174 | 393 |  |  |  |  | 3337 | return $self; | 
| 175 | 393 |  |  |  |  | 5760 | } | 
| 176 | 393 |  |  |  |  | 19061 |  | 
| 177 |  |  |  |  |  |  | 1; | 
| 178 |  |  |  |  |  |  |  | 
| 179 |  |  |  |  |  |  |  | 
| 180 | 274 |  |  |  |  | 3308 | =head1 Name | 
| 181 | 274 |  |  |  |  | 845 |  | 
| 182 |  |  |  |  |  |  | App::Sqitch::Plan::ChangeList - Sqitch deployment plan change list | 
| 183 | 274 |  |  |  |  | 520 |  | 
| 184 |  |  |  |  |  |  | =head1 Synopsis | 
| 185 |  |  |  |  |  |  |  | 
| 186 |  |  |  |  |  |  | my $list = App::Sqitch::Plan::ChangeList->new( | 
| 187 | 19 |  |  | 19 | 1 | 59 | $add_roles, | 
| 188 | 19 |  |  |  |  | 39 | $add_users, | 
| 189 | 19 |  |  |  |  | 35 | $insert_user, | 
| 190 | 19 |  |  |  |  | 35 | $insert_user2, | 
| 191 | 19 |  |  |  |  | 311 | ); | 
| 192 | 19 |  |  |  |  | 1131 |  | 
| 193 | 19 | 100 |  |  |  | 36 | my @changes = $list->changes; | 
|  | 19 |  |  |  |  | 80 |  | 
| 194 | 19 |  |  |  |  | 53 | my $add_users = $list->change_at(1); | 
| 195 |  |  |  |  |  |  | my $add_users = $list->get('add_users'); | 
| 196 |  |  |  |  |  |  |  | 
| 197 |  |  |  |  |  |  | my $insert_user1 = $list->get('insert_user@alpha'); | 
| 198 |  |  |  |  |  |  | my $insert_user2 = $list->get('insert_user'); | 
| 199 |  |  |  |  |  |  |  | 
| 200 |  |  |  |  |  |  | =head1 Description | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | This module is used internally by L<App::Sqitch::Plan> to manage plan changes. | 
| 203 |  |  |  |  |  |  | It's modeled on L<Array::AsHash> and L<Hash::MultiValue>, but makes allowances | 
| 204 |  |  |  |  |  |  | for finding changes relative to tags. | 
| 205 |  |  |  |  |  |  |  | 
| 206 |  |  |  |  |  |  | =head1 Interface | 
| 207 |  |  |  |  |  |  |  | 
| 208 |  |  |  |  |  |  | =head2 Constructors | 
| 209 |  |  |  |  |  |  |  | 
| 210 |  |  |  |  |  |  | =head3 C<new> | 
| 211 |  |  |  |  |  |  |  | 
| 212 |  |  |  |  |  |  | my $plan = App::Sqitch::Plan::ChangeList->new( @changes ); | 
| 213 |  |  |  |  |  |  |  | 
| 214 |  |  |  |  |  |  | Instantiates and returns a App::Sqitch::Plan::ChangeList object with the list of | 
| 215 |  |  |  |  |  |  | changes. Each change should be a L<App::Sqitch::Plan::Change> object. Order will be | 
| 216 |  |  |  |  |  |  | preserved but the location of each change will be indexed by its name and ID, as | 
| 217 |  |  |  |  |  |  | well as the names and IDs of any associated tags. | 
| 218 |  |  |  |  |  |  |  | 
| 219 |  |  |  |  |  |  | =head2 Instance Methods | 
| 220 |  |  |  |  |  |  |  | 
| 221 |  |  |  |  |  |  | =head3 C<count> | 
| 222 |  |  |  |  |  |  |  | 
| 223 |  |  |  |  |  |  | my $count = $changelist->count; | 
| 224 |  |  |  |  |  |  |  | 
| 225 |  |  |  |  |  |  | Returns the number of changes in the list. | 
| 226 |  |  |  |  |  |  |  | 
| 227 |  |  |  |  |  |  | =head3 C<changes> | 
| 228 |  |  |  |  |  |  |  | 
| 229 |  |  |  |  |  |  | my @changes = $changelist->changes; | 
| 230 |  |  |  |  |  |  |  | 
| 231 |  |  |  |  |  |  | Returns all of the changes in the list. | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  | =head3 C<tags> | 
| 234 |  |  |  |  |  |  |  | 
| 235 |  |  |  |  |  |  | my @tags = $changelist->tags; | 
| 236 |  |  |  |  |  |  |  | 
| 237 |  |  |  |  |  |  | Returns all of the tags associated with changes in the list. | 
| 238 |  |  |  |  |  |  |  | 
| 239 |  |  |  |  |  |  | =head3 C<items> | 
| 240 |  |  |  |  |  |  |  | 
| 241 |  |  |  |  |  |  | my @changes = $changelist->items; | 
| 242 |  |  |  |  |  |  |  | 
| 243 |  |  |  |  |  |  | An alias for C<changes>. | 
| 244 |  |  |  |  |  |  |  | 
| 245 |  |  |  |  |  |  | =head3 C<change_at> | 
| 246 |  |  |  |  |  |  |  | 
| 247 |  |  |  |  |  |  | my $change = $change_list->change_at(10); | 
| 248 |  |  |  |  |  |  |  | 
| 249 |  |  |  |  |  |  | Returns the change at the specified index. | 
| 250 |  |  |  |  |  |  |  | 
| 251 |  |  |  |  |  |  | =head3 C<index_of> | 
| 252 |  |  |  |  |  |  |  | 
| 253 |  |  |  |  |  |  | my $index = $changelist->index_of($change_id); | 
| 254 |  |  |  |  |  |  | my $index = $changelist->index_of($change_name); | 
| 255 |  |  |  |  |  |  |  | 
| 256 |  |  |  |  |  |  | Returns the index of the change with the specified ID or name. The value passed | 
| 257 |  |  |  |  |  |  | may be one of these forms: | 
| 258 |  |  |  |  |  |  |  | 
| 259 |  |  |  |  |  |  | =over | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | =item * An ID | 
| 262 |  |  |  |  |  |  |  | 
| 263 |  |  |  |  |  |  | my $index = $changelist->index_of('6c2f28d125aff1deea615f8de774599acf39a7a1'); | 
| 264 |  |  |  |  |  |  |  | 
| 265 |  |  |  |  |  |  | This is the SHA1 ID of a change or tag. Currently, the full 40-character hexed | 
| 266 |  |  |  |  |  |  | hash string must be specified. | 
| 267 |  |  |  |  |  |  |  | 
| 268 |  |  |  |  |  |  | =item * A change name | 
| 269 |  |  |  |  |  |  |  | 
| 270 |  |  |  |  |  |  | my $index = $changelist->index_of('users_table'); | 
| 271 |  |  |  |  |  |  |  | 
| 272 |  |  |  |  |  |  | The name of a change. Will throw an exception if the more than one change in the | 
| 273 |  |  |  |  |  |  | list goes by that name. | 
| 274 |  |  |  |  |  |  |  | 
| 275 |  |  |  |  |  |  | =item * A tag name | 
| 276 |  |  |  |  |  |  |  | 
| 277 |  |  |  |  |  |  | my $index = $changelist->index_of('@beta1'); | 
| 278 |  |  |  |  |  |  |  | 
| 279 |  |  |  |  |  |  | The name of a tag, including the leading C<@>. | 
| 280 |  |  |  |  |  |  |  | 
| 281 |  |  |  |  |  |  | =item * A tag-qualified change name | 
| 282 |  |  |  |  |  |  |  | 
| 283 |  |  |  |  |  |  | my $index = $changelist->index_of('users_table@beta1'); | 
| 284 |  |  |  |  |  |  |  | 
| 285 |  |  |  |  |  |  | The named change as it was last seen in the list before the specified tag. | 
| 286 |  |  |  |  |  |  |  | 
| 287 |  |  |  |  |  |  | =back | 
| 288 |  |  |  |  |  |  |  | 
| 289 |  |  |  |  |  |  | =head3 C<first_index_of> | 
| 290 |  |  |  |  |  |  |  | 
| 291 |  |  |  |  |  |  | my $index = $changelist->first_index_of($change_name); | 
| 292 |  |  |  |  |  |  | my $index = $changelist->first_index_of($change_name, $name); | 
| 293 |  |  |  |  |  |  |  | 
| 294 |  |  |  |  |  |  | Returns the index of the first instance of the named change in the list. If a | 
| 295 |  |  |  |  |  |  | second argument is passed, the index of the first instance of the change | 
| 296 |  |  |  |  |  |  | I<after> the index of the second argument will be returned. This is useful | 
| 297 |  |  |  |  |  |  | for getting the index of a change as it was deployed after a particular tag, for | 
| 298 |  |  |  |  |  |  | example: | 
| 299 |  |  |  |  |  |  |  | 
| 300 |  |  |  |  |  |  | my $index = $changelist->first_index_of('foo', '@beta'); | 
| 301 |  |  |  |  |  |  | my $index = $changelist->first_index_of('foo', 'users_table@beta1'); | 
| 302 |  |  |  |  |  |  |  | 
| 303 |  |  |  |  |  |  | The second argument must unambiguously refer to a single change in the list. As | 
| 304 |  |  |  |  |  |  | such, it should usually be a tag name or tag-qualified change name. Returns | 
| 305 |  |  |  |  |  |  | C<undef> if the change does not appear in the list, or if it does not appear | 
| 306 |  |  |  |  |  |  | after the specified second argument change name. | 
| 307 |  |  |  |  |  |  |  | 
| 308 |  |  |  |  |  |  | =head3 C<last_change> | 
| 309 |  |  |  |  |  |  |  | 
| 310 |  |  |  |  |  |  | my $change = $changelist->last_change; | 
| 311 |  |  |  |  |  |  |  | 
| 312 |  |  |  |  |  |  | Returns the last change to be appear in the list. Returns C<undef> if the list | 
| 313 |  |  |  |  |  |  | contains no changes. | 
| 314 |  |  |  |  |  |  |  | 
| 315 |  |  |  |  |  |  | =head3 C<last_tagged_change> | 
| 316 |  |  |  |  |  |  |  | 
| 317 |  |  |  |  |  |  | my $change = $changelist->last_tagged_change; | 
| 318 |  |  |  |  |  |  |  | 
| 319 |  |  |  |  |  |  | Returns the last tagged change in the list. Returns C<undef> if the list | 
| 320 |  |  |  |  |  |  | contains no tagged changes. | 
| 321 |  |  |  |  |  |  |  | 
| 322 |  |  |  |  |  |  | =head3 C<index_of_last_tagged> | 
| 323 |  |  |  |  |  |  |  | 
| 324 |  |  |  |  |  |  | my $index = $changelist->index_of_last_tagged; | 
| 325 |  |  |  |  |  |  |  | 
| 326 |  |  |  |  |  |  | Returns the index of the last tagged change in the list. Returns C<undef> if the | 
| 327 |  |  |  |  |  |  | list contains no tags. | 
| 328 |  |  |  |  |  |  |  | 
| 329 |  |  |  |  |  |  | =head3 C<get> | 
| 330 |  |  |  |  |  |  |  | 
| 331 |  |  |  |  |  |  | my $change = $changelist->get($id); | 
| 332 |  |  |  |  |  |  | my $change = $changelist->get($change_name); | 
| 333 |  |  |  |  |  |  | my $change = $changelist->get($tag_name); | 
| 334 |  |  |  |  |  |  |  | 
| 335 |  |  |  |  |  |  | Returns the change for the specified ID or name. The name may be specified as | 
| 336 |  |  |  |  |  |  | described for C<index_of()>. An exception will be thrown if more than one change | 
| 337 |  |  |  |  |  |  | goes by a specified name. As such, it is best to specify it as unambiguously | 
| 338 |  |  |  |  |  |  | as possible: as a tag name, a tag-qualified change name, or an ID. | 
| 339 |  |  |  |  |  |  |  | 
| 340 |  |  |  |  |  |  | =head3 C<contains> | 
| 341 |  |  |  |  |  |  |  | 
| 342 |  |  |  |  |  |  | say 'Yes!' if $plan->contains('6c2f28d125aff1deea615f8de774599acf39a7a1'); | 
| 343 |  |  |  |  |  |  |  | 
| 344 |  |  |  |  |  |  | Like C<index_of()>, but never throws an exception, and returns true if the | 
| 345 |  |  |  |  |  |  | plan contains the specified change, and false if it does not. | 
| 346 |  |  |  |  |  |  |  | 
| 347 |  |  |  |  |  |  | =head3 C<find> | 
| 348 |  |  |  |  |  |  |  | 
| 349 |  |  |  |  |  |  | my $change = $changelist->find($id); | 
| 350 |  |  |  |  |  |  | my $change = $changelist->find($change_name); | 
| 351 |  |  |  |  |  |  | my $change = $changelist->find($tag_name); | 
| 352 |  |  |  |  |  |  | my $change = $changelist->find("$change_name\@$tag_name"); | 
| 353 |  |  |  |  |  |  |  | 
| 354 |  |  |  |  |  |  | Tries to find and return a change based on the argument. If no tag is specified, | 
| 355 |  |  |  |  |  |  | finds and returns the first instance of the named change. Otherwise, it returns | 
| 356 |  |  |  |  |  |  | the change as of the specified tag. Unlike C<get()>, it will not throw an error | 
| 357 |  |  |  |  |  |  | if more than one change exists with the specified name, but will return the | 
| 358 |  |  |  |  |  |  | first instance. | 
| 359 |  |  |  |  |  |  |  | 
| 360 |  |  |  |  |  |  | =head3 C<append> | 
| 361 |  |  |  |  |  |  |  | 
| 362 |  |  |  |  |  |  | $changelist->append(@changes); | 
| 363 |  |  |  |  |  |  |  | 
| 364 |  |  |  |  |  |  | Append one or more changes to the list. Does not check for duplicates, so | 
| 365 |  |  |  |  |  |  | use with care. | 
| 366 |  |  |  |  |  |  |  | 
| 367 |  |  |  |  |  |  | =head3 C<index_tag> | 
| 368 |  |  |  |  |  |  |  | 
| 369 |  |  |  |  |  |  | $changelist->index_tag($index, $tag); | 
| 370 |  |  |  |  |  |  |  | 
| 371 |  |  |  |  |  |  | Index the tag at the specified index. That is, the tag is assumed to be | 
| 372 |  |  |  |  |  |  | associated with the change at the specified index, and so the internal look up | 
| 373 |  |  |  |  |  |  | table is updated so that the change at that index can be found via the tag's | 
| 374 |  |  |  |  |  |  | name and ID. | 
| 375 |  |  |  |  |  |  |  | 
| 376 |  |  |  |  |  |  | =head1 See Also | 
| 377 |  |  |  |  |  |  |  | 
| 378 |  |  |  |  |  |  | =over | 
| 379 |  |  |  |  |  |  |  | 
| 380 |  |  |  |  |  |  | =item L<App::Sqitch::Plan> | 
| 381 |  |  |  |  |  |  |  | 
| 382 |  |  |  |  |  |  | The Sqitch plan. | 
| 383 |  |  |  |  |  |  |  | 
| 384 |  |  |  |  |  |  | =back | 
| 385 |  |  |  |  |  |  |  | 
| 386 |  |  |  |  |  |  | =head1 Author | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  | David E. Wheeler <david@justatheory.com> | 
| 389 |  |  |  |  |  |  |  | 
| 390 |  |  |  |  |  |  | =head1 License | 
| 391 |  |  |  |  |  |  |  | 
| 392 |  |  |  |  |  |  | Copyright (c) 2012-2022 iovation Inc., David E. Wheeler | 
| 393 |  |  |  |  |  |  |  | 
| 394 |  |  |  |  |  |  | Permission is hereby granted, free of charge, to any person obtaining a copy | 
| 395 |  |  |  |  |  |  | of this software and associated documentation files (the "Software"), to deal | 
| 396 |  |  |  |  |  |  | in the Software without restriction, including without limitation the rights | 
| 397 |  |  |  |  |  |  | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | 
| 398 |  |  |  |  |  |  | copies of the Software, and to permit persons to whom the Software is | 
| 399 |  |  |  |  |  |  | furnished to do so, subject to the following conditions: | 
| 400 |  |  |  |  |  |  |  | 
| 401 |  |  |  |  |  |  | The above copyright notice and this permission notice shall be included in all | 
| 402 |  |  |  |  |  |  | copies or substantial portions of the Software. | 
| 403 |  |  |  |  |  |  |  | 
| 404 |  |  |  |  |  |  | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | 
| 405 |  |  |  |  |  |  | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | 
| 406 |  |  |  |  |  |  | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | 
| 407 |  |  |  |  |  |  | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | 
| 408 |  |  |  |  |  |  | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | 
| 409 |  |  |  |  |  |  | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | 
| 410 |  |  |  |  |  |  | SOFTWARE. | 
| 411 |  |  |  |  |  |  |  | 
| 412 |  |  |  |  |  |  | =cut |