From: Michal Nazarewicz Date: Wed, 06 Feb 2013 20:47:05 GMT Subject: [PATCH 3/4] Git.pm: Add interface for git credential command. Message-ID: In-Reply-To: From: Michal Nazarewicz Add a credential() function which is an interface to the git credential command. Signed-off-by: Michal Nazarewicz --- perl/Git.pm | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/perl/Git.pm b/perl/Git.pm index 6a2d52d..5a18921 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -59,7 +59,8 @@ require Exporter; command_bidi_pipe command_close_bidi_pipe version exec_path html_path hash_object git_cmd_try remote_refs prompt - temp_acquire temp_release temp_reset temp_path); + temp_acquire temp_release temp_reset temp_path + credential); =head1 DESCRIPTION @@ -1000,6 +1001,115 @@ sub _close_cat_blob { } +sub _credential_read { + my %credential; + my ($reader, $op) = (@_); + while (<$reader>) { + chomp; + my ($key, $value) = /([^=]*)=(.*)/; + if (not defined $key) { + throw Error::Simple("unable to parse git credential $op response:\n$_\n"); + } + $credential{$key} = $value; + } + return %credential; +} + +sub _credential_write { + my ($credential, $writer) = @_; + + for my $key (sort { + # url overwrites other fields, so it must come first + return -1 if $a eq 'url'; + return 1 if $b eq 'url'; + return $a cmp $b; + } keys %$credential) { + if (defined $credential->{$key} && length $credential->{$key}) { + print $writer $key, '=', $credential->{$key}, "\n"; + } + } + print $writer "\n"; +} + +sub _credential_run { + my ($self, $credential, $op) = _maybe_self(@_); + + my ($pid, $reader, $writer, $ctx) = command_bidi_pipe('credential', $op); + + _credential_write $credential, $writer; + close $writer; + + if ($op eq "fill") { + %$credential = _credential_read $reader, $op; + } elsif (<$reader>) { + throw Error::Simple("unexpected output from git credential $op response:\n$_\n"); + } + + command_close_bidi_pipe($pid, $reader, undef, $ctx); +} + +=item credential( CREDENTIAL_HASH [, OPERATION ] ) + +=item credential( CREDENTIAL_HASH, CODE ) + +Executes C for a given set of credentials and +specified operation. In both form C needs to be +a reference to a hash which stores credentials. Under certain +conditions the hash can change. + +In the first form, C can be C<'fill'> (or omitted), +C<'approve'> or C<'reject'>, and function will execute corresponding +C sub-command. In case of C<'fill'> the values stored +in C will be changed to the ones returned by the +C command. The usual usage would look something like: + + my %cred = ( + 'protocol' => 'https', + 'host' => 'example.com', + 'username' => 'bob' + ); + Git::credential \%cred; + if (try_to_authenticate($cred{'username'}, $cred{'password'})) { + Git::credential \%cred, 'approve'; + ... do more stuff ... + } else { + Git::credential \%cred, 'reject'; + } + +In the second form, C needs to be a reference to a subroutine. +The function will execute C to fill provided +credential hash, than call C with C as the sole +argument, and finally depending on C's return value execute +C (if return value yields true) or C (otherwise). The return value is the same as what +C returned. With this form, the usage might look as follows: + + if (Git::credential { + 'protocol' => 'https', + 'host' => 'example.com', + 'username' => 'bob' + }, sub { + my $cred = shift; + return try_to_authenticate($cred->{'username'}, $cred->{'password'}); + }) { + ... do more stuff ... + } + +=cut + +sub credential { + my ($self, $credential, $op_or_code) = (_maybe_self(@_), 'fill'); + + if ('CODE' eq ref $op_or_code) { + _credential_run $credential, 'fill'; + my $ret = $op_or_code->($credential); + _credential_run $credential, $ret ? 'approve' : 'reject'; + return $ret; + } else { + _credential_run $credential, $op_or_code; + } +} + { # %TEMP_* Lexical Context my (%TEMP_FILEMAP, %TEMP_FILES); -- 1.8.1.2.549.g4fa355e