Skip to content
Snippets Groups Projects
get_maintainer.pl 64.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • 
        ($name, $address) = parse_email($email);
    
        if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
    	$name = $deduplicate_name_hash{lc($name)}->[0];
    	$address = $deduplicate_name_hash{lc($name)}->[1];
    	$matched = 1;
        } elsif ($deduplicate_address_hash{lc($address)}) {
    	$name = $deduplicate_address_hash{lc($address)}->[0];
    	$address = $deduplicate_address_hash{lc($address)}->[1];
    	$matched = 1;
        }
        if (!$matched) {
    	$deduplicate_name_hash{lc($name)} = [ $name, $address ];
    	$deduplicate_address_hash{lc($address)} = [ $name, $address ];
        }
        $email = format_email($name, $address, 1);
        $email = mailmap_email($email);
        return $email;
    }
    
    sub save_commits_by_author {
        my (@lines) = @_;
    
        my @authors = ();
        my @commits = ();
        my @subjects = ();
    
        foreach my $line (@lines) {
    	if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
    	    my $author = $1;
    	    $author = deduplicate_email($author);
    	    push(@authors, $author);
    	}
    	push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
    	push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
        }
    
        for (my $i = 0; $i < @authors; $i++) {
    	my $exists = 0;
    	foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
    	    if (@{$ref}[0] eq $commits[$i] &&
    		@{$ref}[1] eq $subjects[$i]) {
    		$exists = 1;
    		last;
    	    }
    	}
    	if (!$exists) {
    	    push(@{$commit_author_hash{$authors[$i]}},
    		 [ ($commits[$i], $subjects[$i]) ]);
    	}
        }
    }
    
    sub save_commits_by_signer {
        my (@lines) = @_;
    
        my $commit = "";
        my $subject = "";
    
        foreach my $line (@lines) {
    	$commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
    	$subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
    	if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
    	    my @signatures = ($line);
    	    my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
    	    my @types = @$types_ref;
    	    my @signers = @$signers_ref;
    
    	    my $type = $types[0];
    	    my $signer = $signers[0];
    
    	    $signer = deduplicate_email($signer);
    
    	    my $exists = 0;
    	    foreach my $ref(@{$commit_signer_hash{$signer}}) {
    		if (@{$ref}[0] eq $commit &&
    		    @{$ref}[1] eq $subject &&
    		    @{$ref}[2] eq $type) {
    		    $exists = 1;
    		    last;
    		}
    	    }
    	    if (!$exists) {
    		push(@{$commit_signer_hash{$signer}},
    		     [ ($commit, $subject, $type) ]);
    	    }
    	}
        }
    }
    
    sub vcs_assign {
        my ($role, $divisor, @lines) = @_;
    
        my %hash;
        my $count = 0;
    
        return if (@lines <= 0);
    
        if ($divisor <= 0) {
    	warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
    	$divisor = 1;
        }
    
        @lines = mailmap(@lines);
    
        return if (@lines <= 0);
    
        @lines = sort(@lines);
    
        # uniq -c
        $hash{$_}++ for @lines;
    
        # sort -rn
        foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
    	my $sign_offs = $hash{$line};
    	my $percent = $sign_offs * 100 / $divisor;
    
    	$percent = 100 if ($percent > 100);
    
    	next if (ignore_email_address($line));
    
    	$count++;
    	last if ($sign_offs < $email_git_min_signatures ||
    		 $count > $email_git_max_maintainers ||
    		 $percent < $email_git_min_percent);
    	push_email_address($line, '');
    	if ($output_rolestats) {
    	    my $fmt_percent = sprintf("%.0f", $percent);
    	    add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
    	} else {
    	    add_role($line, $role);
    	}
        }
    }
    
    sub vcs_file_signoffs {
        my ($file) = @_;
    
        my $authors_ref;
        my $signers_ref;
        my $stats_ref;
        my @authors = ();
        my @signers = ();
        my @stats = ();
        my $commits;
    
        $vcs_used = vcs_exists();
        return if (!$vcs_used);
    
        my $cmd = $VCS_cmds{"find_signers_cmd"};
        $cmd =~ s/(\$\w+)/$1/eeg;		# interpolate $cmd
    
        ($commits, $signers_ref, $authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
    
        @signers = @{$signers_ref} if defined $signers_ref;
        @authors = @{$authors_ref} if defined $authors_ref;
        @stats = @{$stats_ref} if defined $stats_ref;
    
    #    print("commits: <$commits>\nsigners:<@signers>\nauthors: <@authors>\nstats: <@stats>\n");
    
        foreach my $signer (@signers) {
    	$signer = deduplicate_email($signer);
        }
    
        vcs_assign("commit_signer", $commits, @signers);
        vcs_assign("authored", $commits, @authors);
        if ($#authors == $#stats) {
    	my $stat_pattern = $VCS_cmds{"stat_pattern"};
    	$stat_pattern =~ s/(\$\w+)/$1/eeg;	#interpolate $stat_pattern
    
    	my $added = 0;
    	my $deleted = 0;
    	for (my $i = 0; $i <= $#stats; $i++) {
    	    if ($stats[$i] =~ /$stat_pattern/) {
    		$added += $1;
    		$deleted += $2;
    	    }
    	}
    	my @tmp_authors = uniq(@authors);
    	foreach my $author (@tmp_authors) {
    	    $author = deduplicate_email($author);
    	}
    	@tmp_authors = uniq(@tmp_authors);
    	my @list_added = ();
    	my @list_deleted = ();
    	foreach my $author (@tmp_authors) {
    	    my $auth_added = 0;
    	    my $auth_deleted = 0;
    	    for (my $i = 0; $i <= $#stats; $i++) {
    		if ($author eq deduplicate_email($authors[$i]) &&
    		    $stats[$i] =~ /$stat_pattern/) {
    		    $auth_added += $1;
    		    $auth_deleted += $2;
    		}
    	    }
    	    for (my $i = 0; $i < $auth_added; $i++) {
    		push(@list_added, $author);
    	    }
    	    for (my $i = 0; $i < $auth_deleted; $i++) {
    		push(@list_deleted, $author);
    	    }
    	}
    	vcs_assign("added_lines", $added, @list_added);
    	vcs_assign("removed_lines", $deleted, @list_deleted);
        }
    }
    
    sub vcs_file_blame {
        my ($file) = @_;
    
        my @signers = ();
        my @all_commits = ();
        my @commits = ();
        my $total_commits;
        my $total_lines;
    
        $vcs_used = vcs_exists();
        return if (!$vcs_used);
    
        @all_commits = vcs_blame($file);
        @commits = uniq(@all_commits);
        $total_commits = @commits;
        $total_lines = @all_commits;
    
        if ($email_git_blame_signatures) {
    	if (vcs_is_hg()) {
    	    my $commit_count;
    	    my $commit_authors_ref;
    	    my $commit_signers_ref;
    	    my $stats_ref;
    	    my @commit_authors = ();
    	    my @commit_signers = ();
    	    my $commit = join(" -r ", @commits);
    	    my $cmd;
    
    	    $cmd = $VCS_cmds{"find_commit_signers_cmd"};
    	    $cmd =~ s/(\$\w+)/$1/eeg;	#substitute variables in $cmd
    
    	    ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
    	    @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
    	    @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
    
    	    push(@signers, @commit_signers);
    	} else {
    	    foreach my $commit (@commits) {
    		my $commit_count;
    		my $commit_authors_ref;
    		my $commit_signers_ref;
    		my $stats_ref;
    		my @commit_authors = ();
    		my @commit_signers = ();
    		my $cmd;
    
    		$cmd = $VCS_cmds{"find_commit_signers_cmd"};
    		$cmd =~ s/(\$\w+)/$1/eeg;	#substitute variables in $cmd
    
    		($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
    		@commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
    		@commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
    
    		push(@signers, @commit_signers);
    	    }
    	}
        }
    
        if ($from_filename) {
    	if ($output_rolestats) {
    	    my @blame_signers;
    	    if (vcs_is_hg()) {{		# Double brace for last exit
    		my $commit_count;
    		my @commit_signers = ();
    		@commits = uniq(@commits);
    		@commits = sort(@commits);
    		my $commit = join(" -r ", @commits);
    		my $cmd;
    
    		$cmd = $VCS_cmds{"find_commit_author_cmd"};
    		$cmd =~ s/(\$\w+)/$1/eeg;	#substitute variables in $cmd
    
    		my @lines = ();
    
    		@lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
    
    		if (!$email_git_penguin_chiefs) {
    		    @lines = grep(!/${penguin_chiefs}/i, @lines);
    		}
    
    		last if !@lines;
    
    		my @authors = ();
    		foreach my $line (@lines) {
    		    if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
    			my $author = $1;
    			$author = deduplicate_email($author);
    			push(@authors, $author);
    		    }
    		}
    
    		save_commits_by_author(@lines) if ($interactive);
    		save_commits_by_signer(@lines) if ($interactive);
    
    		push(@signers, @authors);
    	    }}
    	    else {
    		foreach my $commit (@commits) {
    		    my $i;
    		    my $cmd = $VCS_cmds{"find_commit_author_cmd"};
    		    $cmd =~ s/(\$\w+)/$1/eeg;	#interpolate $cmd
    		    my @author = vcs_find_author($cmd);
    		    next if !@author;
    
    		    my $formatted_author = deduplicate_email($author[0]);
    
    		    my $count = grep(/$commit/, @all_commits);
    		    for ($i = 0; $i < $count ; $i++) {
    			push(@blame_signers, $formatted_author);
    		    }
    		}
    	    }
    	    if (@blame_signers) {
    		vcs_assign("authored lines", $total_lines, @blame_signers);
    	    }
    	}
    	foreach my $signer (@signers) {
    	    $signer = deduplicate_email($signer);
    	}
    	vcs_assign("commits", $total_commits, @signers);
        } else {
    	foreach my $signer (@signers) {
    	    $signer = deduplicate_email($signer);
    	}
    	vcs_assign("modified commits", $total_commits, @signers);
        }
    }
    
    
    sub vcs_file_exists {
        my ($file) = @_;
    
        my $exists;
    
        my $vcs_used = vcs_exists();
        return 0 if (!$vcs_used);
    
        my $cmd = $VCS_cmds{"file_exists_cmd"};
        $cmd =~ s/(\$\w+)/$1/eeg;		# interpolate $cmd
        $cmd .= " 2>&1";
        $exists = &{$VCS_cmds{"execute_cmd"}}($cmd);
    
        return 0 if ($? != 0);
    
        return $exists;
    }
    
    
    sub vcs_list_files {
        my ($file) = @_;
    
        my @lsfiles = ();
    
        my $vcs_used = vcs_exists();
        return 0 if (!$vcs_used);
    
        my $cmd = $VCS_cmds{"list_files_cmd"};
        $cmd =~ s/(\$\w+)/$1/eeg;   # interpolate $cmd
        @lsfiles = &{$VCS_cmds{"execute_cmd"}}($cmd);
    
        return () if ($? != 0);
    
        return @lsfiles;
    }
    
    
    sub uniq {
        my (@parms) = @_;
    
        my %saw;
        @parms = grep(!$saw{$_}++, @parms);
        return @parms;
    }
    
    sub sort_and_uniq {
        my (@parms) = @_;
    
        my %saw;
        @parms = sort @parms;
        @parms = grep(!$saw{$_}++, @parms);
        return @parms;
    }
    
    sub clean_file_emails {
        my (@file_emails) = @_;
        my @fmt_emails = ();
    
        foreach my $email (@file_emails) {
    	$email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
    	my ($name, $address) = parse_email($email);
    	if ($name eq '"[,\.]"') {
    	    $name = "";
    	}
    
    	my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
    	if (@nw > 2) {
    	    my $first = $nw[@nw - 3];
    	    my $middle = $nw[@nw - 2];
    	    my $last = $nw[@nw - 1];
    
    	    if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
    		 (length($first) == 2 && substr($first, -1) eq ".")) ||
    		(length($middle) == 1 ||
    		 (length($middle) == 2 && substr($middle, -1) eq "."))) {
    		$name = "$first $middle $last";
    	    } else {
    		$name = "$middle $last";
    	    }
    	}
    
    	if (substr($name, -1) =~ /[,\.]/) {
    	    $name = substr($name, 0, length($name) - 1);
    	} elsif (substr($name, -2) =~ /[,\.]"/) {
    	    $name = substr($name, 0, length($name) - 2) . '"';
    	}
    
    	if (substr($name, 0, 1) =~ /[,\.]/) {
    	    $name = substr($name, 1, length($name) - 1);
    	} elsif (substr($name, 0, 2) =~ /"[,\.]/) {
    	    $name = '"' . substr($name, 2, length($name) - 2);
    	}
    
    	my $fmt_email = format_email($name, $address, $email_usename);
    	push(@fmt_emails, $fmt_email);
        }
        return @fmt_emails;
    }
    
    sub merge_email {
        my @lines;
        my %saw;
    
        for (@_) {
    	my ($address, $role) = @$_;
    	if (!$saw{$address}) {
    	    if ($output_roles) {
    		push(@lines, "$address ($role)");
    	    } else {
    		push(@lines, $address);
    	    }
    	    $saw{$address} = 1;
    	}
        }
    
        return @lines;
    }
    
    sub output {
        my (@parms) = @_;
    
        if ($output_multiline) {
    	foreach my $line (@parms) {
    	    print("${line}\n");
    	}
        } else {
    	print(join($output_separator, @parms));
    	print("\n");
        }
    }
    
    my $rfc822re;
    
    sub make_rfc822re {
    #   Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
    #   comment.  We must allow for rfc822_lwsp (or comments) after each of these.
    #   This regexp will only work on addresses which have had comments stripped
    #   and replaced with rfc822_lwsp.
    
        my $specials = '()<>@,;:\\\\".\\[\\]';
        my $controls = '\\000-\\037\\177';
    
        my $dtext = "[^\\[\\]\\r\\\\]";
        my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
    
        my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
    
    #   Use zero-width assertion to spot the limit of an atom.  A simple
    #   $rfc822_lwsp* causes the regexp engine to hang occasionally.
        my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
        my $word = "(?:$atom|$quoted_string)";
        my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
    
        my $sub_domain = "(?:$atom|$domain_literal)";
        my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
    
        my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
    
        my $phrase = "$word*";
        my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
        my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
        my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
    
        my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
        my $address = "(?:$mailbox|$group)";
    
        return "$rfc822_lwsp*$address";
    }
    
    sub rfc822_strip_comments {
        my $s = shift;
    #   Recursively remove comments, and replace with a single space.  The simpler
    #   regexps in the Email Addressing FAQ are imperfect - they will miss escaped
    #   chars in atoms, for example.
    
        while ($s =~ s/^((?:[^"\\]|\\.)*
                        (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
                        \((?:[^()\\]|\\.)*\)/$1 /osx) {}
        return $s;
    }
    
    #   valid: returns true if the parameter is an RFC822 valid address
    #
    sub rfc822_valid {
        my $s = rfc822_strip_comments(shift);
    
        if (!$rfc822re) {
            $rfc822re = make_rfc822re();
        }
    
        return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
    }
    
    #   validlist: In scalar context, returns true if the parameter is an RFC822
    #              valid list of addresses.
    #
    #              In list context, returns an empty list on failure (an invalid
    #              address was found); otherwise a list whose first element is the
    #              number of addresses found and whose remaining elements are the
    #              addresses.  This is needed to disambiguate failure (invalid)
    #              from success with no addresses found, because an empty string is
    #              a valid list.
    
    sub rfc822_validlist {
        my $s = rfc822_strip_comments(shift);
    
        if (!$rfc822re) {
            $rfc822re = make_rfc822re();
        }
        # * null list items are valid according to the RFC
        # * the '1' business is to aid in distinguishing failure from no results
    
        my @r;
        if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
    	$s =~ m/^$rfc822_char*$/) {
            while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
                push(@r, $1);
            }
            return wantarray ? (scalar(@r), @r) : 1;
        }
        return wantarray ? () : 0;
    }