From: Michal Nazarewicz <min...@mina86.com>

Add a credential() function which is an interface to the git
credential command.  The code is heavily based on credential_*
functions in <contrib/mw-to-git/git-remote-mediawiki>.

Signed-off-by: Michal Nazarewicz <min...@mina86.com>
---
 perl/Git.pm | 110 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 109 insertions(+), 1 deletion(-)

 On Thu, Feb 07 2013, Jeff King <p...@peff.net> wrote:
 > On Wed, Feb 06, 2013 at 09:47:05PM +0100, Michal Nazarewicz wrote:
 >
 >> +sub _credential_read {
 >> +   my %credential;
 >> +   my ($reader, $op) = (@_);
 >> +   while (<$reader>) {
 >> +           chomp;
 >> +           my ($key, $value) = /([^=]*)=(.*)/;
 >
 > Empty keys are not valid. Can we make this:
 >
 >   /^([^=]+)=(.*)/
 >
 > to fail the regex? Otherwise, I think this check:
 >
 >> +           if (not defined $key) {
 >> +                   throw Error::Simple("unable to parse git credential $op 
 >> response:\n$_\n");
 >> +           }
 >
 > would not pass because $key would be the empty string.

 Right, fixed.  

 >> +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";
 >> +           }
 >> +   }
 >
 > There are a few disallowed characters, like "\n" in key or value, and
 > "=" in a key. They should never happen unless the caller is buggy, but
 > should we check and catch them here?

 I left it as is for now since it's not entairly clear to me what to
 do in all cases.  In particular:
 
 - when reading, what to do if the line is " foo = bar ",
 - when reading, what to do if the line is "foo=" (ie. empty value),
 - when writing, what to do if value is a single space,
 - when writing, what to do if value ends with a new line,
 - when writing, what to do if value is empty (currently not printed at all),

 On Thu, Feb 07 2013, Matthieu Moy <matthieu....@grenoble-inp.fr> wrote:
 > I think you should credit git-remote-mediawiki for the code in the
 > commit message. Perhaps have a first "copy/paste" commit, and then an
 > "adaptation" commit to add sort, ^ anchor in regexp, doc and your
 > callback mechanism, but I won't insist on that.

 Good point.  Creating additional commit is a bit too much for my
 licking, but added note in commit message.

diff --git a/perl/Git.pm b/perl/Git.pm
index 9dded54..b4adead 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
@@ -1013,6 +1014,113 @@ sub _close_cat_blob {
 }
 
 
+sub _credential_read {
+       my %credential;
+       my ($reader, $op) = (@_);
+       while (<$reader>) {
+               if (!/^([^=\s]+)=(.*?)\s*$/) {
+                       throw Error::Simple("unable to parse git credential $op 
response:\n$_");
+               }
+               $credential{$1} = $2;
+       }
+       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<git credential> for a given set of credentials and
+specified operation.  In both form C<CREDENTIAL_HASH> needs to be
+a reference to a hash which stores credentials.  Under certain
+conditions the hash can change.
+
+In the first form, C<OPERATION> can be C<'fill'> (or omitted),
+C<'approve'> or C<'reject'>, and function will execute corresponding
+C<git credential> sub-command.  In case of C<'fill'> the values stored
+in C<CREDENTIAL_HASH> will be changed to the ones returned by the
+C<git credential> 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<CODE> needs to be a reference to a subroutine.
+The function will execute C<git credential fill> to fill provided
+credential hash, than call C<CODE> with C<CREDENTIAL_HASH> as the sole
+argument, and finally depending on C<CODE>'s return value execute
+C<git credential approve> (if return value yields true) or C<git
+credential reject> (otherwise).  The return value is the same as what
+C<CODE> 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.g1d13f9f

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to