]>
Git — Sourcephile - git-remote-gpg.git/blob - git-remote-gpg
2 package git
::remote
::gpg
;
4 our $VERSION = '2020.03.20';
6 # This file is a git-remote-helpers(1) to use a gpg(1)
7 # as a cryptographic layer below git(1)'s objects.
8 # Copyright (C) 2014 Julien Moutinho
10 # This program is free software: you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published
12 # by the Free Software Foundation, either version 3 of the License,
13 # or any later version.
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty
17 # of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 # See the GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 use warnings FATAL
=> qw(all);
30 use File
::Spec
::Functions
qw(:ALL);
31 use File
::Temp
qw(tempdir);
34 # NOTE: to debug: IPCRUNDEBUG=basic|data|details|gory
37 use POSIX
qw(WNOHANG);
44 foreach my $msg (@_) {
50 my $call = (caller(1))[3];
54 , "\e[30m\e[1m.", join('.', $call."\e[m")
58 : Data
::Dumper
::Dumper
($_)
65 my $call = (caller(1))[3];
68 , "\e[30m\e[1m.", join('.', $call."\e[m")
69 , " ", (ref $_ eq 'CODE'?(join("\n ", $_->()), "\n"):(@_, "\n"))
73 local $Carp::CarpLevel
= 1;
74 carp
("\e[33mWARNING\e[m ", @_, "\n\t");
77 local $Carp::CarpLevel
= 1;
78 croak
("\e[31mERROR\e[m ", @_, "\n\t");
82 foreach my $file (@_) {
83 debug
(sub{"file=$file\n"});
91 foreach my $dir (@_) {
92 debug
(sub{"dir=$dir\n"});
93 File
::Path
::make_path
($dir, {verbose
=>0, error
=> \
my $error});
95 for my $diag (@$error) {
96 my ($dir, $message) = %$diag;
97 error
("dir=$dir: $message");
104 my ($ctx, $size) = @_;
106 IPC
::Run
::run
([@{$ctx->{config
}->{gpg
}}
107 , '--armor', '--gen-rand', '1', $size]
109 or error
("failed to get random bits");
113 sub grg_hash
($$;$) {
114 my ($ctx, $algo, $run) = @_;
115 $run = sub {return @_} unless defined $run;
117 IPC
::Run
::run
($run->([@{$ctx->{config
}->{gpg
}}
118 , '--with-colons', '--print-md', $algo]
120 or error
("failed to hash data");
121 return ((split(':', $hash))[2]);
123 sub gpg_fingerprint
($$$) {
124 my ($ctx, $id, $caps_needed) = @_;
127 if (IPC
::Run
::run
([@{$ctx->{config
}->{gpg
}}
128 , '--fixed-list-mode', '--with-colons', '--with-fingerprint', '--list-keys', $id]
130 my @lines = split(/\n/,$output);
131 while (my $line = shift @lines) {
132 if (my ($longkeyid, $caps) = $line =~ m/^pub:[^:]*:[^:]*:[^:]*:([^:]*):[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:([^:]+):.*$/) {
134 foreach my $cap (@$caps_needed) {
135 if (not ($caps =~ m/$cap/)) {
136 warning
("skipping key 0x$longkeyid which has not usable capability: $cap, but matches: `$id'");
143 while ((not defined $fpr or not defined $uid)
144 and $line = shift @lines) {
145 (not defined $fpr and (($fpr) = $line =~ m/^fpr:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:([0-9A-F]+):.*$/)) or
146 (not defined $uid and (($uid) = $line =~ m/^uid:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:([^:]+):.*$/)) or
149 error
("unable to extract fingerprint and user ID")
157 error
("unable to find any OpenPGP key with usable capability: ".join('', @$caps_needed)." for: `$id'")
158 unless scalar(%h) gt 0;
159 debug
(sub{"$id -> "}, \
%h);
162 sub grg_encrypt_symmetric
($$$;$) {
163 my ($ctx, $clear, $key, $run) = @_;
164 $run = sub {return @_} unless defined $run;
165 IPC
::Run
::run
($run->([@{$ctx->{config
}->{gpg
}}
167 , '--compress-algo', 'none'
169 , '--passphrase-fd', '3'
171 , '--trust-model', 'always'
173 , '<', \
$clear, '3<', \
$key))
174 or error
("failed to encrypt symmetrically data");
176 sub grg_decrypt_symmetric
($$$;$) {
177 my ($ctx, $key, $run) = @_;
178 $run = sub {return @_} unless defined $run;
179 IPC
::Run
::run
($run->([@{$ctx->{config
}->{gpg
}}
180 , '--batch', '--no-default-keyring', '--keyring', '/dev/null', '--secret-keyring', '/dev/null'
181 , '--passphrase-fd', '3', '--quiet', '--decrypt']
183 or error
("failed to decrypt symmetrically data");
185 sub grg_encrypt_asymmetric
($$;$) {
186 my ($ctx, $clear, $run) = @_;
187 $run = sub {return @_} unless defined $run;
189 ( (map { ('--recipient', '0x'.$_) } (keys %{$ctx->{config
}->{keys}}))
190 , (map { ('--hidden-recipient', '0x'.$_) } (keys %{$ctx->{config
}->{'hidden-keys'}})) );
191 @recipients = ('--default-recipient-self')
193 IPC
::Run
::run
($run->([@{$ctx->{config
}->{gpg
}}
195 , '--compress-algo', 'none'
196 , '--trust-model', 'always'
197 , '--sign', '--encrypt'
198 , ($ctx->{config
}->{signingkey
}->{fpr
} ? ('--local-user', $ctx->{config
}->{signingkey
}->{fpr
}) : ())
201 or error
("failed to encrypt asymmetrically data");
203 sub grg_decrypt_asymmetric
($$;$) {
204 my ($ctx, $run) = @_;
205 my ($clear, $status);
206 $run = sub {return @_} unless defined $run;
207 IPC
::Run
::run
($run->([@{$ctx->{config
}->{gpg
}}
208 , '--batch', '--no-default-keyring',
209 , '--status-fd', '3', '--quiet', '--decrypt']
210 , '>', \
$clear, '3>', \
$status))
211 or error
("failed to decrypt asymmetrically data");
212 debug
(sub{"status=\n$status"});
213 my @lines = split(/\n/,$status);
214 my ($enc_to, $goodsig, $validsig, $validpub, $goodmdc);
215 foreach my $line (@lines) {
216 (not defined $enc_to and (($enc_to) = $line =~ m/^\[GNUPG:\] ENC_TO ([0-9A-F]+).*$/)) or
217 (not defined $goodsig and (($goodsig) = $line =~ m/^\[GNUPG:\] GOODSIG ([0-9A-F]+).*$/)) or
218 (not defined $goodmdc and (($goodmdc) = $line =~ m/^\[GNUPG:\] (GOODMDC)$/)) or
219 (not defined $validsig and not defined $validpub and (($validsig, $validpub)
220 = $line =~ m/^\[GNUPG:\] VALIDSIG ([0-9A-F]+) [^ ]+ [^ ]+ [^ ]+ [^ ]+ [^ ]+ [^ ]+ [^ ]+ [^ ]+ ([0-9A-F]+).*$/)) or
223 error
("data expected to be encrypted")
225 debug
(sub{"enc_to=$enc_to\n"});
226 error
("data expected to be signed")
228 debug
(sub{"goodsig=$goodsig\n"});
229 error
("modification detection code incorrect")
231 debug
(sub{"good_mdc=$goodmdc\n"});
232 error
("data signature invalid")
233 unless $validsig and $validpub;
234 debug
(sub{"validsig=$validsig\n"});
235 debug
(sub{"validpub=$validpub\n"});
236 error
("data signature refused")
237 unless exists $ctx->{config
}->{keys}->{$validpub}
238 or exists $ctx->{config
}->{'hidden-keys'}->{$validpub};
239 debug
(sub{"accepted:$validpub\n"});
243 # XXX: there is no locking mechanism
244 sub grg_remote_fetch_file
($) {
246 # NOTE: avoid File::Copy::copy().
247 while (my ($file, undef) = each %{$ctx->{remote
}->{fetch
}}) {
248 my $path = File
::Spec-
>catfile($ctx->{remote
}->{uri
}->file, $file);
250 my $h = $ctx->{remote
}->{fetch
}->{$file};
258 sub grg_remote_fetch_rsync
($) {
260 my $uri = $ctx->{remote
}->{uri
}->clone;
262 if ($uri->opaque =~ m{^//}) {
263 $uri->fragment(undef);
265 @src = map { $uri->path($_); $uri->as_string; }
266 (keys %{$ctx->{remote
}->{fetch
}});
269 my ($authority, $path, $fragment)
270 = $uri->as_string =~ m
|^rsync
:(?:([^/#:]+):)?([^?#]*)(?:#(.*))?$|;
271 @src = map { "$authority:$path/$_" }
272 (keys %{$ctx->{remote
}->{fetch
}});
274 IPC
::Run
::run
([@{$ctx->{config
}->{rsync
}}
275 , '-i', '--ignore-times', '--inplace', '--progress'
277 , $ctx->{'dir-cache'}.'/']
280 sub grg_remote_fetch_sftp
($) {
282 IPC
::Run
::run
([@{$ctx->{config
}->{curl
}}
284 , '--output', File
::Spec-
>catfile($ctx->{'dir-cache'}, '#1')
285 , $ctx->{remote
}->{uri
}->as_string.'/'.'{'.join(',', (keys %{$ctx->{remote
}->{fetch
}})).'}' ]
288 sub grg_remote_fetch
($$) {
289 my ($ctx, $files) = @_;
290 debug
(sub{'files='}, $files);
291 my $scheme = $ctx->{remote
}->{uri
}->scheme;
292 $ctx->{remote
}->{fetch
}
294 { path
=> File
::Spec-
>catfile($ctx->{'dir-cache'}, $_)
298 { file
=> \
&grg_remote_fetch_file
299 , rsync
=> \
&grg_remote_fetch_rsync
300 , sftp
=> \
&grg_remote_fetch_sftp
302 error
("URL scheme not supported: `$scheme'")
305 or $ctx->{remote
}->{fetch
} = {};
306 return $ctx->{remote
}->{fetch
};
308 sub grg_remote_init_file
($) {
310 my $dst = $ctx->{remote
}->{uri
}->file;
314 sub grg_remote_init_rsync
($) {
316 my $tmp = tempdir
('grg_rsync_XXXXXXXX', CLEANUP
=> 1);
317 my $uri = $ctx->{remote
}->{uri
}->clone;
319 if ($uri->opaque =~ m{^//}) {
320 $uri->fragment(undef);
323 $dst = $uri->as_string;
326 my ($authority, $fragment);
327 ($authority, $path, $fragment)
328 = $uri->as_string =~ m
|^rsync
:(?:([^/#:]+):)?([^?#]*)(?:#(.*))?$|;
329 $dst = "$authority:";
331 &mkdir(File
::Spec-
>catdir($tmp, $path));
332 IPC
::Run
::run
([@{$ctx->{config
}->{rsync
}}
333 , '-i', '--recursive', '--relative'
337 , init
=> sub { chdir $tmp or die $!; })
339 sub grg_remote_init_sftp
($) {
341 my $uri = $ctx->{remote
}->{uri
}->clone;
342 my ($path) = $uri->path =~ m
|^/?(.*)$|;
343 $uri->fragment(undef);
346 IPC
::Run
::run
([@{$ctx->{config
}->{curl
}}
347 , '--show-error', '--ftp-create-dirs'
348 , '-Q', "+mkdir ".$path
352 sub grg_remote_init
($) {
354 my $scheme = $ctx->{remote
}->{uri
}->scheme;
356 { file
=> \
&grg_remote_init_file
357 , rsync
=> \
&grg_remote_init_rsync
358 , sftp
=> \
&grg_remote_init_sftp
360 error
("URL scheme not supported: `$scheme'")
363 or error
("remote init failed");
366 sub grg_remote_push_file
($) {
369 foreach my $file (@{$ctx->{remote
}->{push}}) {
370 my $src = File
::Spec-
>catfile($ctx->{'dir-cache'}, $file);
371 my $dst = File
::Spec-
>catfile($ctx->{remote
}->{uri
}->file, $file);
372 debug
(sub{"File::Copy::move('$src', '$dst')\n"});
373 if (not File
::Copy
::move
($src, $dst)) {
380 sub grg_remote_push_rsync
($) {
382 my $uri = $ctx->{remote
}->{uri
}->clone;
383 $uri->fragment(undef);
386 if ($uri->opaque =~ m{^//}) {
387 $uri->fragment(undef);
389 $dst = $uri->as_string;
392 my ($authority, $path, $fragment)
393 = $uri->as_string =~ m
|^rsync
:(?:([^/#:]+):)?([^?#]*)(?:#(.*))?$|;
394 $dst = "$authority:$path/";
396 IPC
::Run
::run
([@{$ctx->{config
}->{rsync
}}
398 , (@{$ctx->{remote
}->{push}})
401 , init
=> sub { chdir $ctx->{'dir-cache'} or die $!; });
403 sub grg_remote_push_sftp
($) {
405 my $uri = $ctx->{remote
}->{uri
}->clone;
406 $uri->fragment(undef);
408 IPC
::Run
::run
([@{$ctx->{config
}->{curl
}}
409 , '--show-error', '--ftp-create-dirs', '--upload-file'
410 , File
::Spec-
>catfile($ctx->{'dir-cache'},'{'.join(',', @{$ctx->{remote
}->{push}}).'}')
411 , $uri->as_string.'/']
414 sub grg_remote_push
($) {
416 my $scheme = $ctx->{remote
}->{uri
}->scheme;
417 grg_remote_init
($ctx)
418 unless $ctx->{remote
}->{checked
};
420 if @{$ctx->{remote
}->{push}} == 0;
422 { file
=> \
&grg_remote_push_file
423 , rsync
=> \
&grg_remote_push_rsync
424 , sftp
=> \
&grg_remote_push_sftp
426 error
("URL scheme not supported: `$scheme'")
429 or error
("remote push failed");
430 rm
(map {File
::Spec-
>catfile($ctx->{'dir-cache'}, $_)} @{$ctx->{remote
}->{push}});
433 sub grg_remote_remove
($) {
435 #my $scheme = $ctx->{remote}->{uri}->scheme;
438 # File::Copy::remove_tree
439 # ( map { File::Spec->catfile($ctx->{remote}->{uri}->path, $_) } @$files
443 # IPC::Run::run([@{$ctx->{config}->{rsync}}
444 # , '--verbose', '--ignore-times', '--recursive', '--delete'
446 # , $ctx->{remote}->{uri}])
449 # IPC::Run::run([@{$ctx->{config}->{curl}}
451 # , map { ('-Q', 'rm '.$_) } @$files
452 # , $ctx->{remote}->{uri}])
455 #error("URL scheme not supported: `$scheme'")
457 #$fct->($ctx, $ctx->{remote}->{remove})
458 # or error("remote remove failed");
462 sub grg_pack_fetch
($$) {
463 my ($ctx, $fetch_objects) = @_;
466 my %remote_objects = ();
467 while (my ($pack_id, $pack) = each %{$ctx->{manifest
}->{packs
}}) {
468 foreach my $obj (@{$pack->{objects
}}) {
469 $remote_objects{$obj} = $pack_id;
473 my %packs_to_fetch = ();
474 foreach my $obj (@$fetch_objects) {
475 my @packs = ($remote_objects{$obj});
476 while (my $pack_id = shift @packs) {
477 if (not exists $packs_to_fetch{$pack_id}) {
478 $packs_to_fetch{$pack_id} = 1;
479 my $manifest_pack = $ctx->{manifest
}->{packs
}->{$pack_id};
480 error
("manifest is missing a dependency pack: $pack_id")
481 unless defined $manifest_pack;
482 @packs = (@packs, @{$manifest_pack->{deps
}});
486 my @packs_to_fetch = keys %packs_to_fetch;
487 my $packs_fetched = grg_remote_fetch
($ctx, [@packs_to_fetch]);
488 foreach my $pack_id (@packs_to_fetch) {
490 = exists $packs_fetched->{$pack_id}
491 ? $packs_fetched->{$pack_id}
492 : {path
=> File
::Spec-
>catfile($ctx->{'dir-cache'}, $pack_id), preserve
=> 0};
493 my $manifest_pack = $ctx->{manifest
}->{packs
}->{$pack_id};
494 my $pack_key = $manifest_pack->{key
};
496 grg_decrypt_symmetric
($ctx, $pack_key, sub {
497 push @{$_[0]}, ($pack_fetched->{path
});
498 return (@_, '>', \
$pack_data);
500 my $pack_hash_algo = $manifest_pack->{hash_algo
};
501 my $pack_hash = grg_hash
($ctx
503 , sub { return (@_, '<', \
$pack_data); });
504 error
("pack data hash differs from pack manifest hash")
505 unless $pack_hash eq $manifest_pack->{hash
};
506 rm
($pack_fetched->{path
})
507 unless $pack_fetched->{preserve
};
508 IPC
::Run
::run
(['git', 'index-pack', '-v', '--stdin']
513 sub grg_pack_push
($$) {
514 my ($ctx, $push_objects) = @_;
516 debug
(sub{"push_objects=\n"}, $push_objects);
518 my %remote_objects = ();
519 while (my ($pack_id, $pack) = each %{$ctx->{manifest
}->{packs
}}) {
520 foreach my $obj (@{$pack->{objects
}}) {
521 $remote_objects{$obj} = $pack_id;
525 IPC
::Run
::run
(['git', 'cat-file', '--batch-check']
526 , '<', \
join("\n", keys %remote_objects)
528 or error
("failed to query local git objects");
531 if ($_ =~ m/ missing$/) { () }
534 # @pack_objects, @pack_deps_objects
535 IPC
::Run
::run
(['git', 'rev-list', '--objects-edge', '--stdin', '--']
536 , '<', \
join("\n", ((map {'^'.$_} @common_objects), @$push_objects))
538 or error
("failed to query objects to pack");
539 my @pack_objects_edge = split(/\n/, $_);
540 foreach (@pack_objects_edge) {s/ .*//}
541 my @pack_objects = grep {m/^[^-]/} @pack_objects_edge;
542 my @pack_deps_objects = grep {s/^-//} @pack_objects_edge;
545 foreach my $obj (@pack_deps_objects) {
546 my $pack = $remote_objects{$obj};
547 error
("manifest is missing object dependencies")
548 unless defined $pack;
549 $pack_deps{$pack} = 1;
551 if (@pack_objects > 0) {
555 while (not defined $pack_id
556 or exists $ctx->{manifest
}->{packs
}->{$pack_id}) {
557 $pack_id = grg_rand
($ctx, $ctx->{config
}->{'pack-filename-size'});
558 $pack_id =~ s{/}{-}g;
559 error
("failed to pick an unused random pack filename after 512 tries; retry or increase grg.pack-filename-size")
560 if $pack_id_try++ >= 512;
562 my $pack_key = grg_rand
($ctx, $ctx->{config
}->{'pack-key-size'});
564 IPC
::Run
::run
(['git', 'pack-objects', '--stdout']
565 , '<', \
join("\n", @pack_objects)
567 or error
("failed to pack objects to push");
568 my $pack_hash = grg_hash
($ctx
569 , $ctx->{config
}->{'pack-hash-algo'}
570 , sub { return (@_, '<', \
$pack_data); });
571 grg_encrypt_symmetric
($ctx, $pack_data, $pack_key, sub {
572 push @{$_[0]}, ('--output', File
::Spec-
>catfile($ctx->{'dir-cache'}, $pack_id));
575 push @{$ctx->{remote
}->{push}}, $pack_id;
576 $ctx->{manifest
}->{packs
}->{$pack_id} =
577 { deps
=> [keys %pack_deps]
579 , hash_algo
=> $ctx->{config
}->{'pack-hash-algo'}
581 , objects
=> \
@pack_objects
586 sub grg_manifest_fetch
($) {
588 debug
(sub{'remote->checked='},$ctx->{remote
}->{checked
});
590 if defined $ctx->{remote
}->{checked
};
592 { 'hidden-keys' => {}
596 , version
=> $VERSION
598 my $fetched = grg_remote_fetch
($ctx, [$ctx->{'manifest-file'}]);
599 my $crypt = $fetched->{$ctx->{'manifest-file'}}->{path
};
600 if (defined $crypt) {
602 grg_decrypt_asymmetric
($ctx, sub {
603 push @{$_[0]}, $crypt;
604 return (@_, '>', \
$json); });
606 ($manifest = JSON
::decode_json
($json) and ref $manifest eq 'HASH')
607 or error
("failed to decode JSON manifest");
608 $ctx->{remote
}->{checked
} = 1;
609 rm
($fetched->{$ctx->{'manifest-file'}}->{path
})
610 unless $fetched->{$ctx->{'manifest-file'}}->{preserve
};
611 $ctx->{manifest
} = {%{$ctx->{manifest
}}, %$manifest};
612 foreach my $slot (qw(keys hidden-keys)) {
613 while (my ($fpr, $uid) = each %{$ctx->{manifest
}->{$slot}}) {
614 my %keys = gpg_fingerprint
($ctx, '0x'.$fpr, ['E']);
615 my ($fpr, $uid) = each %keys;
616 $ctx->{config
}->{$slot}->{$fpr} = $uid;
621 if ($ctx->{command
} eq 'push' or $ctx->{command
} eq 'list for-push') {
622 $ctx->{remote
}->{checked
} = 0;
624 elsif ($ctx->{remote
}->{checking
}) {
628 error
("remote checking failed");
632 sub grg_manifest_push
($) {
634 foreach my $slot (qw(keys hidden-keys)) {
635 $ctx->{manifest
}->{$slot} = {};
636 while (my ($fpr, $uid) = each %{$ctx->{config
}->{$slot}}) {
637 $ctx->{manifest
}->{$slot}->{$fpr} = $uid;
640 my $json = JSON
::encode_json
($ctx->{manifest
})
641 or error
("failed to encode JSON manifest");
642 grg_encrypt_asymmetric
($ctx, $json, sub {
644 , ('--output', File
::Spec-
>catfile($ctx->{'dir-cache'}, $ctx->{'manifest-file'}));
646 push @{$ctx->{remote
}->{push}}, $ctx->{'manifest-file'};
649 sub grg_config_read
($) {
651 my $cfg = $ctx->{config
};
654 foreach my $name (qw(gpg signingkey keys)
655 , grep { !m/^(gpg|signingkey|keys)$/ } (keys %$cfg)) {
657 IPC
::Run
::run
(['git', 'config', '--get', 'remote.'.$ctx->{remote
}->{name
}.'.'.$name, '.+'], '>', \
$value) or
658 IPC
::Run
::run
(['git', 'config', '--get', 'grg.'.$name, '.+'], '>', \
$value) or 1;
659 if ($name eq 'signingkey') {
660 IPC
::Run
::run
(['git', 'config', '--get', 'user.'.$name, '.+'], '>', \
$value)
663 my %keys = gpg_fingerprint
($ctx, $value, ['S']);
664 warning
("signing key ID is not matching a unique key: taking only one")
665 unless scalar(keys %keys) == 1;
666 my ($fpr, $uid) = each %keys;
667 $cfg->{$name} = {fpr
=> $fpr, uid
=> $uid};
669 elsif ($name eq 'keys' or $name eq 'hidden-keys') {
670 IPC
::Run
::run
(['git', 'config', '--get', 'user.'.$name, '.+'], '>', \
$value)
673 my @ids = split(/,/, $value);
675 foreach my $key (@ids) {
676 my %keys = gpg_fingerprint
($ctx, $key, ['E']);
677 while (my ($fpr, $uid) = each %keys) {
678 $cfg->{$name}->{$fpr} = $uid;
683 elsif (grep(/^$name$/, qw(curl gpg rsync))) {
684 IPC
::Run
::run
(['git', 'config', '--get', $name.'.program', '.+'], '>', \
$value)
686 $cfg->{$name} = [split(' ', $value)]
691 $cfg->{$name} = $value
695 error
("no signingkey configured; to do so you may use one of following commands:\n"
696 , "\t\$ git config remote.'$ctx->{remote}->{name}'.signingkey \$your_openpgp_id\n"
697 , "\t\$ git config grg.signingkey \$your_openpgp_id\n"
698 , "\t\$ git config user.signingkey \$your_openpgp_id"
699 ) unless defined $cfg->{signingkey
};
700 if ( (scalar (keys %{$cfg->{keys}}) == 0)
701 and (scalar (keys %{$cfg->{'hidden-keys'}}) == 0) ) {
702 $cfg->{keys} = { $cfg->{signingkey
}->{fpr
} => $cfg->{signingkey
}->{uid
} };
705 debug
(sub{'config='},$cfg);
708 sub grg_connect
($) {
710 grg_config_read
($ctx);
711 grg_manifest_fetch
($ctx);
713 sub grg_disconnect
($) {
715 grg_remote_push
($ctx);
718 sub gpg_command_answer
($) {
720 debug
(sub{join('', @cmd)."\n"});
721 print STDOUT
(@cmd, "\n");
723 if (@cmd == 1 and $cmd[0] eq "");
725 sub grg_command_capabilities
($) {
727 $ctx->{command
} = 'capabilities';
728 gpg_command_answer
("fetch");
729 gpg_command_answer
("push");
730 gpg_command_answer
("");
732 sub grg_command_fetch
($$) {
733 my ($ctx, $fetch_refs) = @_;
734 $ctx->{command
} = 'fetch';
735 debug
(sub{"fetch_refs="}, $fetch_refs);
738 my @fetch_objects= ();
739 foreach my $ref (@$fetch_refs) {
740 push @fetch_objects, $ref->{sha1
};
742 grg_pack_fetch
($ctx, \
@fetch_objects);
744 sub grg_command_list
($$) {
745 my ($ctx, $command) = @_;
746 $ctx->{command
} = $command;
748 my $manifest_refs = $ctx->{manifest
}->{refs
};
749 while (my ($ref, $obj) = each %$manifest_refs) {
750 if ($obj =~ m
|^ref: *(.*) *$|) {
751 $obj = $manifest_refs->{$1};
753 gpg_command_answer
("$obj $ref")
756 gpg_command_answer
("");
758 sub grg_command_push
($$) {
759 my ($ctx, $push_refs) = @_;
761 $ctx->{command
} = 'push';
762 debug
(sub{"push_refs="}, $push_refs);
765 my @push_objects= ();
766 foreach my $ref (@$push_refs) {
767 IPC
::Run
::run
(['git', 'rev-list', '--ignore-missing', '--max-count=1', $ref->{src
}, '--']
769 or error
("failed to dereference ref to push: ".$ref->{src
});
771 $ref->{src_obj
} = $_;
772 push @push_objects, $_;
774 grg_pack_push
($ctx, \
@push_objects);
775 my $manifest_refs = $ctx->{manifest
}->{refs
};
776 foreach my $ref (@$push_refs) {
777 $manifest_refs->{$ref->{dst
}} = $ref->{src_obj
};
779 $manifest_refs->{HEAD
} = 'ref: refs/heads/master'
780 unless defined $manifest_refs->{HEAD
};
781 grg_manifest_push
($ctx);
782 grg_disconnect
($ctx);
784 sub grg_commands
(@) {
788 #STDOUT->autoflush(1);
789 while (defined $line or (not eof(*STDIN
) and
790 (defined($line = readline(*STDIN
)))
792 : error
("readline failed: $!")
794 debug
(sub{"line=\"",$line,"\"\n"});
795 $ctx->{command
} = undef;
796 if ($line eq 'capabilities') {
797 grg_command_capabilities
($ctx);
800 elsif ($line =~ m/^fetch .*$/) {
803 while ((defined $line or (not eof(*STDIN
) and
804 ((defined($line = readline(*STDIN
)))
806 : error
("readline failed: $!")))) and
807 (($sha1, $name) = ($line =~ m/^fetch ([0-9a-f]{40}) (.+)$/))
809 debug
(sub{"fetch line=\"",$line,"\"\n"});
810 push @refs, {sha1
=>$sha1, name
=>$name};
813 error
("failed to parse command: $line")
815 grg_command_fetch
($ctx, \
@refs);
817 elsif ($line eq 'list' or $line eq 'list for-push') {
818 grg_command_list
($ctx, $line);
821 elsif ($line =~ m/^push .*$/) {
823 my ($force, $src, $dst);
824 while ((defined $line or (not eof(*STDIN
) and
825 ((defined($line = readline(*STDIN
)))
827 : error
("readline failed: $!")))) and
828 (($force, $src, $dst) = ($line =~ m/^push (\+)?([^:]+):(.+)$/))
830 debug
(sub{"push line=\"",$line,"\"\n"});
831 push @refs, {force
=>(defined $force), src
=>$src, dst
=>$dst};
834 error
("failed to parse command: $line")
836 grg_command_push
($ctx, \
@refs);
838 elsif ($line =~ m/^$/) {
841 local $SIG{'PIPE'} = 'IGNORE';
842 gpg_command_answer
("");
847 warning
("unsupported command supplied: `$line'");
853 $ENV{GIT_DIR
} = $ENV{GIT_DIR
} || '.git';
854 $ENV{GITCEPTION
} = ($ENV{GITCEPTION
} || '') . '+';
861 , 'hidden-keys' => {}
862 , 'manifest-hash-algo' => 'SHA224' # NOTE: SHA512, SHA384, SHA256, SHA224 supported.
863 , 'pack-filename-size' => 42
864 , 'pack-hash-algo' => 'SHA224' # NOTE: SHA512, SHA384, SHA256, SHA224 supported.
865 , 'pack-key-size' => 64
866 , signingkey
=> undef
869 , 'dir-cache' => undef
871 , 'manifest-file' => undef
880 Getopt
::Long
::Configure
885 Getopt
::Long
::GetOptions
886 ( help
=> sub { Pod
::Usage
::pod2usage
888 , -sections
=> ['SYNOPSIS', 'OPTIONS', 'REMOTES', 'CONFIG']
889 , -verbose
=> 99 ); }
890 , man
=> sub { Pod
::Usage
::pod2usage
(-verbose
=> 2); }
892 $ctx->{remote
}->{checking
} = 1;
895 if (not $ctx->{remote
}->{checking
}) {
896 my $name = shift @ARGV;
897 Pod
::Usage
::pod2usage
(-verbose
=> 1)
898 unless defined $name;
899 ($ctx->{remote
}->{name
}) = ($name =~ m/^((\w|-)+)$/);
900 error
("valid name of remote Git required, got: `$name'")
901 unless $ctx->{remote
}->{name
};
903 my $uri = shift @ARGV;
904 Pod
::Usage
::pod2usage
(-verbose
=> 1)
906 $ctx->{remote
}->{uri
} = URI-
>new($uri);
907 error
("valid URL of remote Git required, got: `$uri'")
908 unless $ctx->{remote
}->{uri
};
909 my $fragment = $ctx->{remote
}->{uri
}->fragment;
911 unless defined $fragment;
912 $ctx->{'manifest-file'} = grg_hash
($ctx
913 , $ctx->{config
}->{'manifest-hash-algo'}
914 , sub { return (@_, '<', \
$fragment); });
915 if (-d
$ENV{GIT_DIR
}) {
916 $ctx->{'dir-cache'} = File
::Spec-
>catdir
917 ( $ENV{GIT_DIR
}, 'cache', 'remotes'
918 , $ctx->{remote
}->{name
}, 'gpg');
919 &mkdir($ctx->{'dir-cache'});
922 $ctx->{'dir-cache'} = tempdir
('grg_cache_XXXXXXXX', CLEANUP
=> 1);
924 debug
(sub{"ctx="},$ctx);
935 git-remote-gpg - git-remote-helpers(1) to encrypt remote repository through gpg(1)
939 =item git-remote-gpg $gpg_remote $gpg_url
941 =item git-remote-gpg --check $gpg_url
947 =item B<-h>, B<--help>
957 =item git remote add $remote gpg::rsync:${user:+$user@}$host:$path
959 =item git remote add $remote gpg::rsync://${user:+$user@}$host${port:+:$port}/$path
963 =item git remote add $remote gpg::sftp://${user:+$user@}$host${port:+:$port}/$path
965 =head2 Via File::Copy(3pm)
967 =item git remote add $remote gpg::file://$path
975 =item B<grg.curl>, B<remote.$remote.curl>
977 =item B<grg.gpg>, B<remote.$remote.gpg>
979 =item B<grg.keys>, B<remote.$remote.keys>
981 =item B<grg.hidden-keys>, B<remote.$remote.hidden-keys>
983 =item B<grg.manifest-hash-algo>, B<remote.$remote.manifest-hash-algo>
985 =item B<grg.pack-filename-size>, B<remote.$remote.pack-filename-size>
987 =item B<grg.pack-hash-algo>, B<remote.$remote.pack-hash-algo>
989 =item B<grg.pack-key-size>, B<remote.$remote.pack-key-size>
991 =item B<grg.signingkey>, B<remote.$remote.signingkey>
993 =item B<grg.rsync>, B<remote.$remote.rsync>