summaryrefslogtreecommitdiff
path: root/lib/App/git_annex_reviewunused.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/App/git_annex_reviewunused.pm')
-rw-r--r--lib/App/git_annex_reviewunused.pm211
1 files changed, 211 insertions, 0 deletions
diff --git a/lib/App/git_annex_reviewunused.pm b/lib/App/git_annex_reviewunused.pm
new file mode 100644
index 0000000..175d7a0
--- /dev/null
+++ b/lib/App/git_annex_reviewunused.pm
@@ -0,0 +1,211 @@
+package App::git_annex_reviewunused;
+# ABSTRACT: interactively process 'git annex unused' output
+#
+# Copyright (C) 2019-2020 Sean Whitton <spwhitton@spwhitton.name>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or (at
+# your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+use 5.028;
+use strict;
+use warnings;
+
+use subs qw(main exit);
+use Getopt::Long;
+use Git::Annex;
+use Try::Tiny;
+use Term::ReadKey;
+use Term::ANSIColor;
+
+my $exit_main = 0;
+
+CORE::exit main unless caller;
+
+=func main
+
+Implementation of git-annex-reviewunused(1). Please see documentation
+for that command.
+
+Normally takes no arguments and responds to C<@ARGV>. If you want to
+override that you can pass an arrayref of arguments, and those will be
+used instead of the contents of C<@ARGV>.
+
+=cut
+
+sub main {
+ shift if $_[0] and ref $_[0] eq ""; # in case main called as a class method
+ local @ARGV = @{ $_[0] } if $_[0] and ref $_[0] ne "";
+
+ my $annex = Git::Annex->new;
+
+ my $just_print = 0;
+ my ($uuid, $from_arg, $used_refspec_arg, %unused_opts, %dropunused_args);
+ GetOptions
+ 'from=s' => \$from_arg,
+ 'used-refspec=s' => \$used_refspec_arg,
+ 'just-print' => \$just_print;
+ if ($from_arg) {
+ $unused_opts{from} = $dropunused_args{from} = $from_arg;
+ #<<<
+ try {
+ ($uuid) = $annex->git->config("remote." . $from_arg . ".annex-uuid");
+ } catch {
+ die "couldn't determine an annex UUID for $from_arg remote";
+ };
+ #>>>
+ }
+ $unused_opts{used_refspec} = $used_refspec_arg if $used_refspec_arg;
+
+ my @to_drop;
+ my @unused_files = grep {
+ # check the unused file still exists i.e. has not been dropped
+ # already (in the case of reviewing unused files at a remote,
+ # just check that it's not been dropped according to the local
+ # git-annex branch by using readpresentkey rather than
+ # checkpresentkey)
+
+ my $ret = $_->{contentlocation}
+ = $annex->abs_contentlocation($_->{key});
+
+ if ($from_arg) {
+ #<<<
+ try {
+ $annex->annex->readpresentkey($_->{key}, $uuid);
+ } catch {
+ $ret = 0;
+ };
+ #>>>
+ }
+
+ $ret;
+ } $annex->unused(%unused_opts, log => 1)->@*;
+ exit unless @unused_files;
+ if ($just_print) {
+ _say_spaced_bullet("There are unused files you can drop with"
+ . " `git annex dropunused':");
+ say " " . $_->{number} . " " . $_->{key} for @unused_files;
+ print "\n";
+ }
+ my $i = 0;
+ UNUSED: while ($i < @unused_files) {
+ my $unused_file = $unused_files[$i];
+ my $contentlocation = $unused_file->{contentlocation};
+
+ system qw(clear -x) unless $just_print;
+ _say_bold("unused file #" . $unused_file->{number} . ":");
+
+ if ($unused_file->{bad} or $unused_file->{tmp}) {
+ say " looks like stale tmp or bad file, with key "
+ . $unused_file->{key};
+ } else {
+ my @log_lines = map { s/^/ /r } @{ $unused_file->{log_lines} };
+ unless ($just_print) {
+ # truncate log output if necessary to ensure user's
+ # terminal does not scroll
+ my (undef, $height) = GetTerminalSize;
+ splice @log_lines, $height - (5 + @log_lines)
+ if 5 + @log_lines > $height;
+ }
+ print "\n";
+ say for @log_lines;
+ unless ($just_print) {
+ my $response;
+ READKEY: while (1) {
+ # before prompting, clear out stdin, to avoid
+ # registered a keypress more than once
+ ReadMode 4;
+ 1 while defined ReadKey(-1);
+
+ my @opts = ('y', 'n');
+ push @opts, 'o' if $contentlocation;
+ push @opts, ('d', 'b') if $i > 0;
+ print "Drop this unused files? ("
+ . join('/', @opts) . ") ";
+
+ # Term::ReadKey docs recommend ReadKey(-1) but that
+ # means we need an infinite loop calling ReadKey(-1)
+ # over and over, which ramps up system load
+ my $response = ReadKey(0);
+ ReadMode 0;
+
+ # respond to C-c
+ exit 0 if ord $response == 3;
+
+ say $response;
+ $response = lc($response);
+ if ($response eq "y") {
+ push @to_drop, $unused_file->{number};
+ last READKEY;
+ } elsif ($response eq "n") {
+ last READKEY;
+ } elsif ($response eq "o" and defined $contentlocation) {
+ system "xdg-open", $contentlocation;
+ } elsif ($response eq "b" and $i > 0) {
+ $i--;
+ pop @to_drop
+ if @to_drop
+ and $to_drop[$#to_drop] eq
+ $unused_files[$i]->{number};
+ next UNUSED;
+ } elsif ($response eq "d" and $i > 0) {
+ # user wants to drop the list we've accumulated up
+ # until now and get out of this script
+ last UNUSED;
+ } else {
+ say "invalid response";
+ }
+ }
+ }
+ }
+ print "\n";
+ $i++;
+ }
+
+ if (@to_drop) {
+ _say_spaced_bullet("Will dropunused with --force:");
+ say "@to_drop\n";
+ $annex->annex->dropunused(\%dropunused_args, "--force", @to_drop)
+ if _prompt_yn("Go ahead with this?");
+ }
+
+ # exit value represents whether or not there are any unused files left
+ # after this run. note that in --just-print mode, @to_drop will be
+ # empty, so we'll always exit non-zero if there are any unused files
+ exit(@to_drop != @unused_files);
+
+ EXIT_MAIN:
+ return $exit_main;
+}
+
+sub _say_bold { print colored(['bold'], @_), "\n" }
+
+sub _say_bullet { _say_bold(" • ", @_) }
+
+sub _say_spaced_bullet { _say_bold("\n", " • ", @_, "\n") }
+
+sub _prompt_yn {
+ my $prompt = shift;
+ local $| = 1;
+ my $response;
+ while (1) {
+ print colored(['bold'], "$prompt ");
+ chomp(my $response = <STDIN>);
+ return 1 if lc($response) eq "y";
+ return 0 if lc($response) eq "n";
+ say "invalid response";
+ }
+}
+
+sub exit { $exit_main = shift // 0; goto EXIT_MAIN }
+
+1;