App-karr

 view release on metacpan or  search on metacpan

lib/App/karr/Cmd/Unlock.pm  view on Meta::CPAN

  usage_string => 'USAGE: karr unlock [ID[,ID,...]] [--all] [--json]',
);
use App::karr::Role::BoardAccess;
use App::karr::Role::Output;
use App::karr::Lock;

with 'App::karr::Role::BoardAccess', 'App::karr::Role::Output', 'App::karr::Role::ClaimTimeout';


option all => (
  is  => 'ro',
  doc => 'Break every lock on the board',
);

sub execute {
  my ($self, $args_ref, $chain_ref) = @_;

  $self->check_positional_args($args_ref, 1);

  # Pull first: a lock pushed by a command that died before it could release
  # one is on the remote, and this is the command for exactly that mess. The
  # guard is disarmed on the reporting path below, which writes nothing.
  my $guard = $self->sync_before;

  my $ec = $self->store->effective_config;
  my $lock = App::karr::Lock->new(
    git => $self->git,
    ttl => $self->_parse_timeout($ec->{lock_timeout},
                                 App::karr::Lock->DEFAULT_TTL),
  );

  my @pos = $self->positional_args($args_ref);
  my @held = $lock->locks;

  # No target: report only. Clearing a lock is destructive to whoever holds it,
  # so it takes an explicit id or --all.
  unless ($self->all || defined $pos[0]) {
    $guard->done;
    $self->_report(@held);
    return;
  }

  # break_lock clears both addresses a lock can have -- the current one and the
  # pre-#93 one inside refs/karr/* -- so a task holding one of each appears
  # twice in @held but must only be broken, and reported, once.
  my %seen;
  my @ids = $self->all
    ? grep { !$seen{$_}++ } map { $_->{task_id} } @held
    : $self->parse_ids($pos[0]);

  my @results;
  for my $id (@ids) {
    my ($ok, $owner) = $lock->break_lock($id);
    push @results, {
      id      => 0 + $id,
      broken  => $ok ? \1 : \0,
      ( $ok ? ( owner => $owner ) : () ),
    };
    next if $self->json;
    if ($ok) { printf "Broke lock on task %d (was held by %s)\n", $id, $owner }
    else     { printf "Task %d is not locked\n", $id }
  }

  $self->sync_after;

  $self->print_json_results(@results);
}

sub _report {
  my ($self, @held) = @_;

  if ($self->json) {
    $self->print_json([ map { { %$_,
      expired => $_->{expired} ? \1 : \0,
      legacy  => $_->{legacy}  ? \1 : \0,
    } } @held ]);
    return;
  }

  unless (@held) {
    print "No locks held.\n";
    return;
  }

  for my $l (@held) {
    printf "Task %-4d held by %s%s%s%s\n",
      $l->{task_id},
      $l->{owner},
      ( defined $l->{age} ? sprintf( ' for %s', _duration( $l->{age} ) ) : '' ),
      ( $l->{expired} ? ' [expired]' : '' ),
      # A lock still sitting in the board namespace was written by a karr older
      # than #93, or pulled from a remote that was given one. Nothing takes it
      # into account any more, and breaking it is how it finally goes away.
      ( $l->{legacy} ? ' [stray: pushed by an older karr, safe to break]' : '' );
  }
  print "\nBreak one with 'karr unlock ID', or all of them with 'karr unlock --all'.\n";
}

sub _duration {
  my ($secs) = @_;
  return "${secs}s" if $secs < 60;
  return int( $secs / 60 ) . 'm' if $secs < 3600;
  return int( $secs / 3600 ) . 'h';
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

App::karr::Cmd::Unlock - Show and break task pick locks

=head1 VERSION

version 0.500



( run in 1.595 second using v1.01-cache-2.11-cpan-788537b7465 )