19 # identical token maps, e.g. host -> host, will be inserted later
26 password => 'password',
30 # Map each credential protocol token to itself on the netrc side.
31 foreach (values %{$options{tmap}}) {
32 $options{tmap}->{$_} = $_;
35 # Now, $options{tmap} has a mapping from the netrc format to the Git credential
38 # Next, we build the reverse token map.
40 # When $rmap{foo} contains 'bar', that means that what the Git credential helper
41 # protocol calls 'bar' is found as 'foo' in the netrc/authinfo file. Keys in
42 # %rmap are what we expect to read from the netrc/authinfo file.
45 foreach my $k (keys %{$options{tmap}}) {
46 push @{$rmap{$options{tmap}->{$k}}}, $k;
49 Getopt::Long::Configure("bundling");
51 # TODO: maybe allow the token map $options{tmap} to be configurable.
62 my $shortname = basename($0);
63 $shortname =~ s/git-credential-//;
67 $0 [(-f <authfile>)...] [-g <program>] [-d] [-v] [-k] get
69 Version $VERSION by tzz\@lifelogs.com. License: BSD.
73 -f|--file <authfile>: specify netrc-style files. Files with the .gpg
74 extension will be decrypted by GPG before parsing.
75 Multiple -f arguments are OK. They are processed in
76 order, and the first matching entry found is returned
77 via the credential helper protocol (see below).
79 When no -f option is given, .authinfo.gpg, .netrc.gpg,
80 .authinfo, and .netrc files in your home directory are
83 -g|--gpg <program> : specify the program for GPG. By default, this is the
84 value of gpg.program in the git repository or global
87 -k|--insecure : ignore bad file ownership or permissions
89 -d|--debug : turn on debugging (developer info)
91 -v|--verbose : be more verbose (show files and information found)
93 To enable this credential helper:
95 git config credential.helper '$shortname -f AUTHFILE1 -f AUTHFILE2'
97 (Note that Git will prepend "git-credential-" to the helper name and look for it
100 ...and if you want lots of debugging info:
102 git config credential.helper '$shortname -f AUTHFILE -d'
104 ...or to see the files opened and data found:
106 git config credential.helper '$shortname -f AUTHFILE -v'
108 Only "get" mode is supported by this credential helper. It opens every
109 <authfile> and looks for the first entry that matches the requested search
113 The protocol that will be used (e.g., https). (protocol=X)
116 The remote hostname for a network credential. (host=X)
119 The path with which the credential will be used. (path=X)
121 'login|user|username':
122 The credential’s username, if we already have one. (username=X)
124 Thus, when we get this query on STDIN:
130 this credential helper will look for the first entry in every <authfile> that
133 machine github.com port https login tzz
137 machine github.com protocol https login tzz
139 OR... etc. acceptable tokens as listed above. Any unknown tokens are
142 Then, the helper will print out whatever tokens it got from the entry, including
143 "password" tokens, mapping back to Git's helper protocol; e.g. "port" is mapped
144 back to "protocol". Any redundant entry tokens (part of the original query) are
147 Again, note that only the first matching entry from all the <authfile>s,
148 processed in the sequence given on the command line, is used.
150 Netrc/authinfo tokens can be quoted as 'STRING' or "STRING".
152 No caching is performed by this credential helper.
159 my $mode = shift @ARGV;
161 # Credentials must get a parameter, so die if it's missing.
162 die "Syntax: $0 [(-f <authfile>)...] [-d] get" unless defined $mode;
164 # Only support 'get' mode; with any other unsupported ones we just exit.
165 exit 0 unless $mode eq 'get';
167 my $files = $options{file};
169 # if no files were given, use a predefined list.
170 # note that .gpg files come first
171 unless (scalar @$files) {
179 $files = $options{file} = [ map { glob $_ } @candidates ];
182 load_config(\%options);
184 my $query = read_credential_data_from_stdin();
187 foreach my $file (@$files) {
188 my $gpgmode = $file =~ m/\.gpg$/;
190 log_verbose("Unable to read $file; skipping it");
194 # the following check is copied from Net::Netrc, for non-GPG files
195 # OS/2 and Win32 do not handle stat in a way compatible with this check :-(
196 unless ($gpgmode || $options{insecure} ||
200 || $^O =~ /^cygwin/) {
201 my @stat = stat($file);
204 if ($stat[2] & 077) {
205 log_verbose("Insecure $file (mode=%04o); skipping it",
210 if ($stat[4] != $<) {
211 log_verbose("Not owner of $file; skipping it");
217 my @entries = load_netrc($file, $gpgmode);
219 unless (scalar @entries) {
221 log_verbose("Unable to open $file: $!");
223 log_verbose("No netrc entries found in $file");
229 my $entry = find_netrc_entry($query, @entries);
231 print_credential_data($entry, $query);
241 my $gpgmode = shift @_;
245 my @cmd = ($options{'gpg'}, qw(--decrypt), $file);
246 log_verbose("Using GPG to open $file: [@cmd]");
247 open $io, "-|", @cmd;
249 log_verbose("Opening $file...");
250 open $io, '<', $file;
253 # nothing to do if the open failed (we log the error later)
256 # Net::Netrc does this, but the functionality is merged with the file
257 # detection logic, so we have to extract just the part we need
258 my @netrc_entries = net_netrc_loader($io);
260 # these entries will use the credential helper protocol token names
263 foreach my $nentry (@netrc_entries) {
267 if (!defined $nentry->{machine}) {
270 if (defined $nentry->{port} && $nentry->{port} =~ m/^\d+$/) {
271 $num_port = $nentry->{port};
272 delete $nentry->{port};
275 # create the new entry for the credential helper protocol
276 $entry{$options{tmap}->{$_}} = $nentry->{$_} foreach keys %$nentry;
278 # for "host X port Y" where Y is an integer (captured by
279 # $num_port above), set the host to "X:Y"
280 if (defined $entry{host} && defined $num_port) {
281 $entry{host} = join(':', $entry{host}, $num_port);
284 push @entries, \%entry;
290 sub net_netrc_loader {
293 my ($mach, $macdef, $tok, @tok);
297 undef $macdef if /\A\n\Z/;
306 while (length && s/^("((?:[^"]+|\\.)*)"|((?:[^\\\s]+|\\.)*))\s*//) {
307 (my $tok = $+) =~ s/\\(.)/$1/g;
313 if ($tok[0] eq "default") {
315 $mach = { machine => undef };
321 if ($tok eq "machine") {
322 my $host = shift @tok;
323 $mach = { machine => $host };
324 push @entries, $mach;
325 } elsif (exists $options{tmap}->{$tok}) {
327 log_debug("Skipping token $tok because no machine was given");
331 my $value = shift @tok;
332 unless (defined $value) {
333 log_debug("Token $tok had no value, skipping it.");
337 # Following line added by rmerrell to remove '/' escape char in .netrc
338 $value =~ s/\/\\/\\/g;
339 $mach->{$tok} = $value;
340 } elsif ($tok eq "macdef") { # we ignore macros
341 next TOKEN unless $mach;
342 my $value = shift @tok;
351 sub read_credential_data_from_stdin {
352 # the query: start with every token with no value
353 my %q = map { $_ => undef } values(%{$options{tmap}});
356 next unless m/^([^=]+)=(.+)/;
358 my ($token, $value) = ($1, $2);
359 die "Unknown search token $token" unless exists $q{$token};
361 log_debug("We were given search token $token and value $value");
364 foreach (sort keys %q) {
365 log_debug("Searching for %s = %s", $_, $q{$_} || '(any value)');
371 # takes the search tokens and then a list of entries
372 # each entry is a hash reference
373 sub find_netrc_entry {
374 my $query = shift @_;
377 foreach my $entry (@_)
379 my $entry_text = join ', ', map { "$_=$entry->{$_}" } keys %$entry;
380 foreach my $check (sort keys %$query) {
381 if (!defined $entry->{$check}) {
382 log_debug("OK: entry has no $check token, so any value satisfies check $check");
383 } elsif (defined $query->{$check}) {
384 log_debug("compare %s [%s] to [%s] (entry: %s)",
389 unless ($query->{$check} eq $entry->{$check}) {
393 log_debug("OK: any value satisfies check $check");
404 sub print_credential_data {
405 my $entry = shift @_;
406 my $query = shift @_;
408 log_debug("entry has passed all the search checks");
410 foreach my $git_token (sort keys %$entry) {
411 log_debug("looking for useful token $git_token");
412 # don't print unknown (to the credential helper protocol) tokens
413 next TOKEN unless exists $query->{$git_token};
415 # don't print things asked in the query (the entry matches them)
416 next TOKEN if defined $query->{$git_token};
418 log_debug("FOUND: $git_token=$entry->{$git_token}");
419 printf "%s=%s\n", $git_token, $entry->{$git_token};
423 # load settings from git config
425 # set from command argument, gpg.program option, or default to gpg
426 $options->{'gpg'} //= Git->repository()->config('gpg.program')
428 log_verbose("using $options{'gpg'} for GPG operations");
431 return unless $options{verbose};
437 return unless $options{debug};