]>
Git — Sourcephile - git-remote-gpg.git/blob - git-remote-gpg
2 our $VERSION = '2014.01.28';
4 # This file is a git-remote-helpers(1) to use a gpg(1)
5 # as a cryptographic layer below git(1)'s objects.
6 # Copyright (C) 2014 Julien Moutinho
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published
10 # by the Free Software Foundation, either version 3 of the License,
11 # or any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty
15 # of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 # See the GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 use warnings FATAL
=> qw(all);
28 use File
::Spec
::Functions
qw(:ALL);
32 # NOTE: to debug: IPCRUNDEBUG=basic|data|details|gory
35 use POSIX
qw(WNOHANG);
42 foreach my $msg (@_) {
48 my $call = (caller(1))[3];
52 , "\e[30m\e[1m.", join('.', $call."\e[m")
56 : Data
::Dumper
::Dumper
($_)
63 my $call = (caller(1))[3];
66 , "\e[30m\e[1m.", join('.', $call."\e[m")
67 , " ", (ref $_ eq 'CODE'?(join("\n ", $_->()), "\n"):(@_, "\n"))
71 local $Carp::CarpLevel
= 1;
72 carp
("\e[33mWARNING\e[m ", @_, "\n\t");
75 local $Carp::CarpLevel
= 1;
76 croak
("\e[31mERROR\e[m ", @_, "\n\t");
81 debug
(sub{"file="},$file);
87 debug
(sub{"dir=\"$dir\"\n"});
88 File
::Path
::make_path
($dir, {verbose
=>0, error
=> \
my $error});
90 for my $diag (@$error) {
91 my ($dir, $message) = %$diag;
93 print "general error: $message\n";
96 print "problem mkdir $dir: $message\n";
103 my ($ctx, $size) = @_;
105 IPC
::Run
::run
([@{$ctx->{config
}->{gpg
}}
106 , '--armor', '--gen-rand', '1', $size]
108 or error
("failed to get random bits");
112 sub grg_hash
($$;$) {
113 my ($ctx, $algo, $run) = @_;
114 $run = sub {return @_} unless defined $run;
116 IPC
::Run
::run
($run->([@{$ctx->{config
}->{gpg
}}
117 , '--with-colons', '--print-md', $algo]
119 or error
("failed to hash data");
120 return ((split(':', $hash))[2]);
122 sub gpg_fingerprint
($$$) {
123 my ($ctx, $id, $caps_needed) = @_;
126 if (IPC
::Run
::run
([@{$ctx->{config
}->{gpg
}}
127 , '--fixed-list-mode', '--with-colons', '--with-fingerprint', '--list-keys', $id]
129 my @lines = split(/\n/,$output);
130 while (my $line = shift @lines) {
131 if (my ($longkeyid, $caps) = $line =~ m/^pub:[^:]*:[^:]*:[^:]*:([^:]*):[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:([^:]+):.*$/) {
133 foreach my $cap (@$caps_needed) {
134 if (not ($caps =~ m/$cap/)) {
135 warning
("skipping key 0x$longkeyid which has not usable capability: $cap, but matches: `$id'");
142 while ((not defined $fpr or not defined $uid)
143 and $line = shift @lines) {
144 (not defined $fpr and (($fpr) = $line =~ m/^fpr:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:([0-9A-F]+):.*$/)) or
145 (not defined $uid and (($uid) = $line =~ m/^uid:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:([^:]+):.*$/)) or
148 error
("unable to extract fingerprint and user ID")
156 error
("unable to find any OpenPGP key with usable capability: ".join('', @$caps_needed)." for: `$id'")
157 unless scalar(%h) gt 0;
158 debug
(sub{"$id -> "}, \
%h);
161 sub grg_encrypt_symmetric
($$$;$) {
162 my ($ctx, $clear, $key, $run) = @_;
163 $run = sub {return @_} unless defined $run;
164 IPC
::Run
::run
($run->([@{$ctx->{config
}->{gpg
}}
166 , '--compress-algo', 'none'
168 , '--passphrase-fd', '3'
170 , '--trust-model', 'always'
172 , '<', \
$clear, '3<', \
$key))
173 or error
("failed to encrypt symmetrically data");
175 sub grg_decrypt_symmetric
($$$;$) {
176 my ($ctx, $key, $run) = @_;
177 debug
(sub{'ctx='}, $ctx);
178 debug
(sub{'key='}, $key);
179 $run = sub {return @_} unless defined $run;
180 IPC
::Run
::run
($run->([@{$ctx->{config
}->{gpg
}}
181 , '--batch', '--no-default-keyring', '--keyring', '/dev/null', '--secret-keyring', '/dev/null'
182 , '--passphrase-fd', '3', '--quiet', '--decrypt']
184 or error
("failed to decrypt symmetrically data");
186 sub grg_encrypt_asymmetric
($$;$) {
187 my ($ctx, $clear, $run) = @_;
188 $run = sub {return @_} unless defined $run;
190 ( (map { ('--recipient', '0x'.$_) } (keys %{$ctx->{config
}->{keys}}))
191 , (map { ('--hidden-recipient', '0x'.$_) } (keys %{$ctx->{config
}->{'hidden-keys'}})) );
192 @recipients = ('--default-recipient-self')
194 IPC
::Run
::run
($run->([@{$ctx->{config
}->{gpg
}}
196 , '--compress-algo', 'none'
197 , '--trust-model', 'always'
198 , '--sign', '--encrypt'
199 , ($ctx->{config
}->{signingkey
}->{fpr
} ? ('--local-user', $ctx->{config
}->{signingkey
}->{fpr
}) : ())
202 or error
("failed to encrypt asymmetrically data");
204 sub grg_decrypt_asymmetric
($$;$) {
205 my ($ctx, $run) = @_;
206 my ($clear, $status);
207 $run = sub {return @_} unless defined $run;
208 IPC
::Run
::run
($run->([@{$ctx->{config
}->{gpg
}}
209 , '--batch', '--no-default-keyring',
210 , '--status-fd', '3', '--quiet', '--decrypt']
211 , '>', \
$clear, '3>', \
$status))
212 or error
("failed to decrypt asymmetrically data");
213 debug
(sub{"status=\n$status"});
214 my @lines = split(/\n/,$status);
215 my ($enc_to, $goodsig, $validsig, $validpub, $goodmdc);
216 foreach my $line (@lines) {
217 (not defined $enc_to and (($enc_to) = $line =~ m/^\[GNUPG:\] ENC_TO ([0-9A-F]+).*$/)) or
218 (not defined $goodsig and (($goodsig) = $line =~ m/^\[GNUPG:\] GOODSIG ([0-9A-F]+).*$/)) or
219 (not defined $goodmdc and (($goodmdc) = $line =~ m/^\[GNUPG:\] (GOODMDC)$/)) or
220 (not defined $validsig and not defined $validpub and (($validsig, $validpub)
221 = $line =~ m/^\[GNUPG:\] VALIDSIG ([0-9A-F]+) [^ ]+ [^ ]+ [^ ]+ [^ ]+ [^ ]+ [^ ]+ [^ ]+ [^ ]+ ([0-9A-F]+).*$/)) or
224 error
("data expected to be encrypted")
226 debug
(sub{"enc_to=$enc_to\n"});
227 error
("data expected to be signed")
229 debug
(sub{"goodsig=$goodsig\n"});
230 error
("modification detection code incorrect")
232 debug
(sub{"good_mdc=$goodmdc\n"});
233 error
("data signature invalid")
234 unless $validsig and $validpub;
235 debug
(sub{"validsig=$validsig\n"});
236 debug
(sub{"validpub=$validpub\n"});
237 error
("data signature refused")
238 unless exists $ctx->{config
}->{keys}->{$validpub}
239 or exists $ctx->{config
}->{'hidden-keys'}->{$validpub};
240 debug
(sub{"accepted:$validpub\n"});
244 sub grg_remote_fetch_file
($$$) {
245 my ($ctx, $files, $fetch_files) = @_;
246 # NOTE: avoid File::Copy::copy().
247 while (my ($file, undef) = each %$fetch_files) {
248 my $path = File
::Spec-
>catfile($ctx->{remote
}->{uri
}->file, $file);
249 debug
(sub{'test path='}, $path);
251 my $h = $fetch_files->{$file};
259 sub grg_remote_fetch_rsync
($$$) {
260 my ($ctx, $files, $fetch_files) = @_;
261 my $uri = $ctx->{remote
}->{uri
}->clone;
263 $uri->fragment(undef);
265 $uri = $uri->as_string;
266 IPC
::Run
::run
([@{$ctx->{config
}->{rsync
}}
267 , '--verbose', '--ignore-times', '--inplace', '--progress'
268 , (map { File
::Spec-
>catfile($uri, $_) } @$files)
269 , $ctx->{'dir-cache'}.'/']
272 sub grg_remote_fetch_sftp
($$$) {
273 my ($ctx, $files, $fetch_files) = @_;
274 IPC
::Run
::run
([@{$ctx->{config
}->{curl
}}
276 , '--output', File
::Spec-
>catfile($ctx->{'dir-cache'}, '#1')
277 , File
::Spec-
>catfile($ctx->{remote
}->{uri
}, '{'.join(',',@$files).'}') ])
279 sub grg_remote_fetch
($$) {
280 my ($ctx, $files) = @_;
281 debug
(sub{'files='}, $files);
282 my $scheme = $ctx->{remote
}->{uri
}->scheme;
283 my $fetch_files = {map { $_ => { path
=> File
::Spec-
>catfile($ctx->{'dir-cache'}, $_), preserve
=> 0 } } @$files};
285 { file
=> \
&grg_remote_fetch_file
286 , rsync
=> \
&grg_remote_fetch_rsync
287 , sftp
=> \
&grg_remote_fetch_sftp
289 error
("URL scheme not supported: `$scheme'")
291 debug
(sub{'fetch_files='}, $fetch_files);
292 $fct->($ctx, $files, $fetch_files)
293 or $fetch_files = {};
294 debug
(sub{'fetch_files='}, $fetch_files);
297 sub grg_remote_init_file
($) {
299 my $dst = $ctx->{remote
}->{uri
}->file;
303 sub grg_remote_init_rsync
($) {
305 my $tmp = File
::Temp-
>tempdir(CLEANUP
=> 1);
306 my $path = $ctx->{remote
}->{uri
}->path;
307 my $uri = $ctx->{remote
}->{uri
}->clone;
308 $uri->fragment(undef);
311 &mkdir(File
::Spec-
>catdir($tmp, $path));
312 IPC
::Run
::run
([@{$ctx->{config
}->{rsync
}}
313 , '--verbose', '--recursive', '--relative'
315 , File
::Spec-
>catfile($uri->as_string)]
316 , init
=> sub { chdir $tmp or die $!; })
318 sub grg_remote_init_sftp
($) {
320 my $path = $ctx->{remote
}->{uri
}->path;
321 my $uri = $ctx->{remote
}->{uri
}->clone;
322 $uri->fragment(undef);
325 IPC
::Run
::run
([@{$ctx->{config
}->{curl
}}
326 , '--show-error', '--ftp-create-dirs'
327 , '-Q', "+mkdir ".$path
330 sub grg_remote_init
($) {
332 my $scheme = $ctx->{remote
}->{uri
}->scheme;
334 { file
=> \
&grg_remote_init_file
335 , rsync
=> \
&grg_remote_init_rsync
336 , sftp
=> \
&grg_remote_init_sftp
338 error
("URL scheme not supported: `$scheme'")
341 or error
("remote init failed");
344 sub grg_remote_push_file
($$) {
345 my ($ctx, $files) = @_;
347 foreach my $file (@$files) {
348 my $src = File
::Spec-
>catfile($ctx->{'dir-cache'}, $file);
349 my $dst = File
::Spec-
>catfile($ctx->{remote
}->{uri
}->file, $file);
350 debug
(sub{"File::Copy::move('$src', '$dst')\n"});
351 if (not File
::Copy
::move
($src, $dst)) {
358 sub grg_remote_push_rsync
($$) {
359 my ($ctx, $files) = @_;
360 my $uri = $ctx->{remote
}->{uri
}->clone;
363 IPC
::Run
::run
([@{$ctx->{config
}->{rsync
}}
364 , '--verbose', '--relative'
368 sub grg_remote_push_sftp
($$) {
369 my ($ctx, $files) = @_;
370 my $uri = $ctx->{remote
}->{uri
}->clone;
373 IPC
::Run
::run
([@{$ctx->{config
}->{curl
}}
374 , '--show-error', '--ftp-create-dirs', '--upload-file'
375 , '{'.join(',', @$files).'}'
376 , $uri->as_string.'/'])
378 sub grg_remote_push
($) {
380 my $scheme = $ctx->{remote
}->{uri
}->scheme;
381 grg_remote_init
($ctx)
382 unless $ctx->{remote
}->{checked
};
384 if @{$ctx->{remote
}->{push}} == 0;
386 { file
=> \
&grg_remote_push_file
387 , rsync
=> \
&grg_remote_push_rsync
388 , sftp
=> \
&grg_remote_push_sftp
390 error
("URL scheme not supported: `$scheme'")
392 $fct->($ctx, $ctx->{remote
}->{push})
393 or error
("remote push failed");
396 sub grg_remote_remove
($) {
398 #my $scheme = $ctx->{remote}->{uri}->scheme;
401 # File::Copy::remove_tree
402 # ( map { File::Spec->catfile($ctx->{remote}->{uri}->path, $_) } @$files
406 # IPC::Run::run([@{$ctx->{config}->{rsync}}
407 # , '--verbose', '--ignore-times', '--recursive', '--delete'
409 # , $ctx->{remote}->{uri}])
412 # IPC::Run::run([@{$ctx->{config}->{curl}}
414 # , map { ('-Q', 'rm '.$_) } @$files
415 # , $ctx->{remote}->{uri}])
418 #error("URL scheme not supported: `$scheme'")
420 #$fct->($ctx, $ctx->{remote}->{remove})
421 # or error("remote remove failed");
425 sub grg_pack_fetch
($$) {
426 my ($ctx, $fetch_objects) = @_;
429 my %remote_objects = ();
430 while (my ($pack_id, $pack) = each %{$ctx->{manifest
}->{packs
}}) {
431 foreach my $obj (@{$pack->{objects
}}) {
432 $remote_objects{$obj} = $pack_id;
436 my %packs_to_fetch = ();
437 foreach my $obj (@$fetch_objects) {
438 my @packs = ($remote_objects{$obj});
439 while (my $pack_id = shift @packs) {
440 if (not exists $packs_to_fetch{$pack_id}) {
441 $packs_to_fetch{$pack_id} = 1;
442 my $manifest_pack = $ctx->{manifest
}->{packs
}->{$pack_id};
443 error
("manifest is missing a dependency pack: $pack_id")
444 unless defined $manifest_pack;
445 @packs = (@packs, @{$manifest_pack->{deps
}});
449 my @packs_to_fetch = keys %packs_to_fetch;
450 my $packs_fetched = grg_remote_fetch
($ctx, [@packs_to_fetch]);
451 foreach my $pack_id (@packs_to_fetch) {
453 = exists $packs_fetched->{$pack_id}
454 ? $packs_fetched->{$pack_id}
455 : {path
=> File
::Spec-
>catfile($ctx->{'dir-cache'}, $pack_id), preserve
=> 0};
456 my $manifest_pack = $ctx->{manifest
}->{packs
}->{$pack_id};
457 my $pack_key = $manifest_pack->{key
};
459 grg_decrypt_symmetric
($ctx, $pack_key, sub {
460 push @{$_[0]}, ($pack_fetched->{path
});
461 return (@_, '>', \
$pack_data);
463 my $pack_hash_algo = $manifest_pack->{hash_algo
};
464 my $pack_hash = grg_hash
($ctx
466 , sub { return (@_, '<', \
$pack_data); });
467 error
("pack data hash differs from pack manifest hash")
468 unless $pack_hash eq $manifest_pack->{hash
};
470 unless $pack_fetched->{preserve
};
471 IPC
::Run
::run
(['git', 'index-pack', '-v', '--stdin']
476 sub grg_pack_push
($$) {
477 my ($ctx, $push_objects) = @_;
479 debug
(sub{"push_objects=\n"}, $push_objects);
481 my %remote_objects = ();
482 while (my ($pack_id, $pack) = each %{$ctx->{manifest
}->{packs
}}) {
483 foreach my $obj (@{$pack->{objects
}}) {
484 $remote_objects{$obj} = $pack_id;
488 IPC
::Run
::run
(['git', 'cat-file', '--batch-check']
489 , '<', \
join("\n", keys %remote_objects)
491 or error
("failed to query local git objects");
494 if ($_ =~ m/ missing$/) { () }
497 # @pack_objects, @pack_deps_objects
498 IPC
::Run
::run
(['git', 'rev-list', '--objects-edge', '--stdin', '--']
499 , '<', \
join("\n", ((map {'^'.$_} @common_objects), @$push_objects))
501 or error
("failed to query objects to pack");
502 my @pack_objects_edge = split(/\n/, $_);
503 foreach (@pack_objects_edge) {s/ .*//}
504 my @pack_objects = grep {m/^[^-]/} @pack_objects_edge;
505 my @pack_deps_objects = grep {s/^-//} @pack_objects_edge;
508 foreach my $obj (@pack_deps_objects) {
509 my $pack = $remote_objects{$obj};
510 error
("manifest is missing object dependencies")
511 unless defined $pack;
512 $pack_deps{$pack} = 1;
514 if (@pack_objects > 0) {
518 while (not defined $pack_id
519 or exists $ctx->{manifest
}->{packs
}->{$pack_id}) {
520 $pack_id = grg_rand
($ctx, $ctx->{config
}->{'pack-filename-size'});
521 $pack_id =~ s{/}{-}g;
522 error
("failed to pick an unused random pack filename after 512 tries; retry or increase grg.pack-filename-size")
523 if $pack_id_try++ >= 512;
525 my $pack_key = grg_rand
($ctx, $ctx->{config
}->{'pack-key-size'});
527 IPC
::Run
::run
(['git', 'pack-objects', '--stdout']
528 , '<', \
join("\n", @pack_objects)
530 or error
("failed to pack objects to push");
531 my $pack_hash = grg_hash
($ctx
532 , $ctx->{config
}->{'pack-hash-algo'}
533 , sub { return (@_, '<', \
$pack_data); });
534 grg_encrypt_symmetric
($ctx, $pack_data, $pack_key, sub {
535 push @{$_[0]}, ('--output', File
::Spec-
>catfile($ctx->{'dir-cache'}, $pack_id));
538 push @{$ctx->{remote
}->{push}}, $pack_id;
539 $ctx->{manifest
}->{packs
}->{$pack_id} =
540 { deps
=> [keys %pack_deps]
542 , hash_algo
=> $ctx->{config
}->{'pack-hash-algo'}
544 , objects
=> \
@pack_objects
549 sub grg_manifest_fetch
($) {
552 { 'hidden-keys' => {}
558 my $fetched = grg_remote_fetch
($ctx, [$ctx->{'manifest-file'}]);
559 my $crypt = $fetched->{$ctx->{'manifest-file'}}->{path
};
560 if (defined $crypt) {
561 $ctx->{remote
}->{checked
} = 1;
563 grg_decrypt_asymmetric
($ctx, sub {
564 push @{$_[0]}, $crypt;
565 return (@_, '>', \
$json); });
566 rm
($fetched->{$ctx->{'manifest-file'}}->{path
})
567 unless $fetched->{$ctx->{'manifest-file'}}->{preserve
};
569 ($manifest = JSON
::decode_json
($json) and ref $manifest eq 'HASH')
570 or error
("failed to decode JSON manifest");
571 $ctx->{manifest
} = {%{$ctx->{manifest
}}, %$manifest};
572 foreach my $slot (qw(keys hidden-keys)) {
573 while (my ($fpr, $uid) = each %{$ctx->{manifest
}->{$slot}}) {
574 my %keys = gpg_fingerprint
($ctx, '0x'.$fpr, ['E']);
575 my ($fpr, $uid) = each %keys;
576 $ctx->{config
}->{$slot}->{$fpr} = $uid;
581 debug
(sub{'ctx='}, $ctx);
582 if ($ctx->{command
} eq 'push' or $ctx->{command
} eq 'list for-push') {
583 $ctx->{remote
}->{checked
} = 0;
585 elsif ($ctx->{remote
}->{checking
}) {
589 error
("remote checking failed");
593 sub grg_manifest_push
($) {
595 foreach my $slot (qw(keys hidden-keys)) {
596 $ctx->{manifest
}->{$slot} = {};
597 while (my ($fpr, $uid) = each %{$ctx->{config
}->{$slot}}) {
598 $ctx->{manifest
}->{$slot}->{$fpr} = $uid;
601 my $json = JSON
::encode_json
($ctx->{manifest
})
602 or error
("failed to encode JSON manifest");
603 grg_encrypt_asymmetric
($ctx, $json, sub {
605 , ('--output', File
::Spec-
>catfile($ctx->{'dir-cache'}, $ctx->{'manifest-file'}));
607 push @{$ctx->{remote
}->{push}}, $ctx->{'manifest-file'};
610 sub grg_config_read
($) {
612 my $cfg = $ctx->{config
};
615 foreach my $name (qw(gpg signingkey keys)
616 , grep { !m/^(gpg|signingkey|keys)$/ } (keys %$cfg)) {
618 IPC
::Run
::run
(['git', 'config', '--get', 'remote.'.$ctx->{remote
}->{name
}.'.'.$name, '.+'], '>', \
$value) or
619 IPC
::Run
::run
(['git', 'config', '--get', 'grg.'.$name, '.+'], '>', \
$value) or 1;
620 if ($name eq 'signingkey') {
621 IPC
::Run
::run
(['git', 'config', '--get', 'user.'.$name, '.+'], '>', \
$value)
624 my %keys = gpg_fingerprint
($ctx, $value, ['S']);
625 warning
("signing key ID is not matching a unique key: taking only one")
626 unless scalar(keys %keys) == 1;
627 my ($fpr, $uid) = each %keys;
628 $cfg->{$name} = {fpr
=> $fpr, uid
=> $uid};
630 elsif ($name eq 'keys' or $name eq 'hidden-keys') {
631 IPC
::Run
::run
(['git', 'config', '--get', 'user.'.$name, '.+'], '>', \
$value)
634 my @ids = split(/,/, $value);
636 foreach my $key (@ids) {
637 my %keys = gpg_fingerprint
($ctx, $key, ['E']);
638 while (my ($fpr, $uid) = each %keys) {
639 $cfg->{$name}->{$fpr} = $uid;
644 elsif (grep(/^$name$/, qw(curl gpg rsync))) {
645 IPC
::Run
::run
(['git', 'config', '--get', $name.'.program', '.+'], '>', \
$value)
647 $cfg->{$name} = [split(' ', $value)]
652 $cfg->{$name} = $value
656 error
("no signingkey configured; to do so you may use one of following commands:\n"
657 , "\t\$ git config remote.'$ctx->{remote}->{name}'.signingkey \$your_openpgp_id\n"
658 , "\t\$ git config grg.signingkey \$your_openpgp_id\n"
659 , "\t\$ git config user.signingkey \$your_openpgp_id"
660 ) unless defined $cfg->{signingkey
};
661 if ( (scalar (keys %{$cfg->{keys}}) == 0)
662 and (scalar (keys %{$cfg->{'hidden-keys'}}) == 0) ) {
663 $cfg->{keys} = { $cfg->{signingkey
}->{fpr
} => $cfg->{signingkey
}->{uid
} };
666 debug
(sub{'config='},$cfg);
669 sub grg_connect
($) {
671 grg_config_read
($ctx);
672 grg_manifest_fetch
($ctx);
674 sub grg_disconnect
($) {
676 grg_remote_push
($ctx);
679 sub gpg_command_answer
($) {
681 debug
(sub{join('', @cmd)."\n"});
682 print STDOUT
(@cmd, "\n");
684 sub grg_command_capabilities
($) {
686 $ctx->{command
} = 'capabilities';
687 gpg_command_answer
("fetch");
688 gpg_command_answer
("push");
689 gpg_command_answer
("");
692 sub grg_command_fetch
($$) {
693 my ($ctx, $fetch_refs) = @_;
694 $ctx->{command
} = 'fetch';
695 debug
(sub{"fetch_refs="}, $fetch_refs);
698 my @fetch_objects= ();
699 foreach my $ref (@$fetch_refs) {
700 push @fetch_objects, $ref->{sha1
};
702 grg_pack_fetch
($ctx, \
@fetch_objects);
704 sub grg_command_list
($$) {
705 my ($ctx, $command) = @_;
706 $ctx->{command
} = $command;
708 while (my ($ref, $obj) = each %{$ctx->{manifest
}->{refs
}}) {
709 gpg_command_answer
("$obj $ref");
711 gpg_command_answer
("");
713 sub grg_command_push
($$) {
714 my ($ctx, $push_refs) = @_;
716 $ctx->{command
} = 'push';
717 debug
(sub{"push_refs="}, $push_refs);
720 my @push_objects= ();
721 foreach my $ref (@$push_refs) {
722 IPC
::Run
::run
(['git', 'rev-list', '--ignore-missing', '--max-count=1', $ref->{src
}, '--']
724 or error
("failed to dereference ref to push: ".$ref->{src
});
726 $ref->{src_obj
} = $_;
727 push @push_objects, $_;
729 grg_pack_push
($ctx, \
@push_objects);
730 my $manifest_refs = $ctx->{manifest
}->{refs
};
731 foreach my $ref (@$push_refs) {
732 $manifest_refs->{$ref->{dst
}} = $ref->{src_obj
};
734 $manifest_refs->{HEAD
}
735 = $push_refs->[-1]->{src_obj
}
736 unless exists $manifest_refs->{HEAD
}
738 grg_manifest_push
($ctx);
739 grg_disconnect
($ctx);
741 sub grg_commands
(@) {
745 #STDOUT->autoflush(1);
746 while (defined $line or (not eof(*STDIN
) and
747 (defined($line = readline(*STDIN
)))
749 : error
("readline failed: $!")
751 debug
(sub{"line=\"",$line,"\"\n"});
752 $ctx->{command
} = undef;
753 if ($line eq 'capabilities') {
754 grg_command_capabilities
($ctx);
757 elsif ($line =~ m/^fetch .*$/) {
760 while ((defined $line or (not eof(*STDIN
) and
761 ((defined($line = readline(*STDIN
)))
763 : error
("readline failed: $!")))) and
764 (($sha1, $name) = ($line =~ m/^fetch ([0-9a-f]{40}) (.+)$/))
766 debug
(sub{"fetch line=\"",$line,"\"\n"});
767 push @refs, {sha1
=>$sha1, name
=>$name};
770 error
("failed to parse command: $line")
772 grg_command_fetch
($ctx, \
@refs);
774 elsif ($line eq 'list' or $line eq 'list for-push') {
775 grg_command_list
($ctx, $line);
778 elsif ($line =~ m/^push .*$/) {
780 my ($force, $src, $dst);
781 while ((defined $line or (not eof(*STDIN
) and
782 ((defined($line = readline(*STDIN
)))
784 : error
("readline failed: $!")))) and
785 (($force, $src, $dst) = ($line =~ m/^push (\+)?([^:]+):(.+)$/))
787 debug
(sub{"push line=\"",$line,"\"\n"});
788 push @refs, {force
=>(defined $force), src
=>$src, dst
=>$dst};
791 error
("failed to parse command: $line")
793 grg_command_push
($ctx, \
@refs);
795 elsif ($line =~ m/^$/) {
798 local $SIG{'PIPE'} = 'IGNORE';
799 gpg_command_answer
("");
804 warning
("unsupported command supplied: `$line'");
810 $ENV{GIT_DIR
} = $ENV{GIT_DIR
} || '.git';
811 $ENV{GITCEPTION
} = ($ENV{GITCEPTION
} || '') . '+';
818 , 'hidden-keys' => {}
819 , 'manifest-hash-algo' => 'SHA224' # NOTE: SHA512, SHA384, SHA256, SHA224 supported.
820 , 'pack-filename-size' => 42
821 , 'pack-hash-algo' => 'SHA224' # NOTE: SHA512, SHA384, SHA256, SHA224 supported.
822 , 'pack-key-size' => 64
823 , signingkey
=> undef
826 , 'dir-cache' => undef
828 , 'manifest-file' => undef
837 Getopt
::Long
::Configure
842 Getopt
::Long
::GetOptions
843 ( help
=> sub { Pod
::Usage
::pod2usage
845 , -sections
=> ['SYNOPSIS', 'OPTIONS', 'REMOTES', 'CONFIG']
846 , -verbose
=> 99 ); }
847 , man
=> sub { Pod
::Usage
::pod2usage
(-verbose
=> 2); }
849 $ctx->{remote
}->{checking
} = 1;
852 if (not $ctx->{remote
}->{checking
}) {
853 my $name = shift @ARGV;
854 Pod
::Usage
::pod2usage
(-verbose
=> 1)
855 unless defined $name;
856 ($ctx->{remote
}->{name
}) = ($name =~ m/^((\w|-)+)$/);
857 error
("valid name of remote Git required, got: `$name'")
858 unless $ctx->{remote
}->{name
};
860 my $uri = shift @ARGV;
861 Pod
::Usage
::pod2usage
(-verbose
=> 1)
863 $ctx->{remote
}->{uri
} = URI-
>new($uri);
864 error
("valid URL of remote Git required, got: `$uri'")
865 unless $ctx->{remote
}->{uri
};
866 my $fragment = $ctx->{remote
}->{uri
}->fragment;
868 unless defined $fragment;
869 $ctx->{'manifest-file'} = grg_hash
($ctx
870 , $ctx->{config
}->{'manifest-hash-algo'}
871 , sub { return (@_, '<', \
$fragment); });
872 if (-d
$ENV{GIT_DIR
}) {
873 $ctx->{'dir-cache'} = File
::Spec-
>catdir
874 ( $ENV{GIT_DIR
}, 'cache', 'remotes'
875 , $ctx->{remote
}->{name
}, 'gpg');
876 &mkdir($ctx->{'dir-cache'});
879 $ctx->{'dir-cache'} = File
::Temp-
>tempdir(CLEANUP
=> 1);
881 debug
(sub{"ctx="},$ctx);
892 git-remote-gpg - git-remote-helpers(1) to encrypt remote repository through gpg(1)
896 =item git-remote-gpg $gpg_remote $gpg_url
898 =item git-remote-gpg --check $gpg_url
904 =item B<-h>, B<--help>
914 =item git remote add $remote gpg::rsync://${user:+$user@}$host/$path
918 =item git remote add $remote gpg::sftp://${user:+$user@}$host/$path
920 =head2 Via File::Copy(3pm)
922 =item git remote add $remote gpg::file://$path
936 =item B<grg.hidden-keys>
938 =item B<grg.manifest-hash-algo>
940 =item B<grg.pack-filename-size>
942 =item B<grg.pack-hash-algo>
944 =item B<grg.pack-key-size>
946 =item B<grg.signingkey>