From: Michal Nazarewicz <mina86@xxxxxxxxxx> 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 <mina86@xxxxxxxxxx> --- perl/Git.pm | 110 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 1 deletion(-) On Thu, Feb 07 2013, Jeff King <peff@xxxxxxxx> 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.Moy@xxxxxxxxxxxxxxx> 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 majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html