merge(third_party/git): Merge squashed git subtree at v2.23.0
Merge commit '1b593e1ea4' as 'third_party/git'
This commit is contained in:
commit
7ef0d62730
3629 changed files with 1139935 additions and 0 deletions
43
third_party/git/contrib/README
vendored
Normal file
43
third_party/git/contrib/README
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
Contributed Software
|
||||
|
||||
Although these pieces are available as part of the official git
|
||||
source tree, they are in somewhat different status. The
|
||||
intention is to keep interesting tools around git here, maybe
|
||||
even experimental ones, to give users an easier access to them,
|
||||
and to give tools wider exposure, so that they can be improved
|
||||
faster.
|
||||
|
||||
I am not expecting to touch these myself that much. As far as
|
||||
my day-to-day operation is concerned, these subdirectories are
|
||||
owned by their respective primary authors. I am willing to help
|
||||
if users of these components and the contrib/ subtree "owners"
|
||||
have technical/design issues to resolve, but the initiative to
|
||||
fix and/or enhance things _must_ be on the side of the subtree
|
||||
owners. IOW, I won't be actively looking for bugs and rooms for
|
||||
enhancements in them as the git maintainer -- I may only do so
|
||||
just as one of the users when I want to scratch my own itch. If
|
||||
you have patches to things in contrib/ area, the patch should be
|
||||
first sent to the primary author, and then the primary author
|
||||
should ack and forward it to me (git pull request is nicer).
|
||||
This is the same way as how I have been treating gitk, and to a
|
||||
lesser degree various foreign SCM interfaces, so you know the
|
||||
drill.
|
||||
|
||||
I expect that things that start their life in the contrib/ area
|
||||
to graduate out of contrib/ once they mature, either by becoming
|
||||
projects on their own, or moving to the toplevel directory. On
|
||||
the other hand, I expect I'll be proposing removal of disused
|
||||
and inactive ones from time to time.
|
||||
|
||||
If you have new things to add to this area, please first propose
|
||||
it on the git mailing list, and after a list discussion proves
|
||||
there are some general interests (it does not have to be a
|
||||
list-wide consensus for a tool targeted to a relatively narrow
|
||||
audience -- for example I do not work with projects whose
|
||||
upstream is svn, so I have no use for git-svn myself, but it is
|
||||
of general interest for people who need to interoperate with SVN
|
||||
repositories in a way git-svn works better than git-svnimport),
|
||||
submit a patch to create a subdirectory of contrib/ and put your
|
||||
stuff there.
|
||||
|
||||
-jc
|
||||
42
third_party/git/contrib/buildsystems/Generators.pm
vendored
Normal file
42
third_party/git/contrib/buildsystems/Generators.pm
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package Generators;
|
||||
require Exporter;
|
||||
|
||||
use strict;
|
||||
use File::Basename;
|
||||
no strict 'refs';
|
||||
use vars qw($VERSION @AVAILABLE);
|
||||
|
||||
our $VERSION = '1.00';
|
||||
our(@ISA, @EXPORT, @EXPORT_OK, @AVAILABLE);
|
||||
@ISA = qw(Exporter);
|
||||
|
||||
BEGIN {
|
||||
local(*D);
|
||||
my $me = $INC{"Generators.pm"};
|
||||
die "Couldn't find myself in \@INC, which is required to load the generators!" if ("$me" eq "");
|
||||
$me = dirname($me);
|
||||
if (opendir(D,"$me/Generators")) {
|
||||
foreach my $gen (readdir(D)) {
|
||||
next unless ($gen =~ /\.pm$/);
|
||||
require "${me}/Generators/$gen";
|
||||
$gen =~ s,\.pm,,;
|
||||
push(@AVAILABLE, $gen);
|
||||
}
|
||||
closedir(D);
|
||||
my $gens = join(', ', @AVAILABLE);
|
||||
}
|
||||
|
||||
push @EXPORT_OK, qw(available);
|
||||
}
|
||||
|
||||
sub available {
|
||||
return @AVAILABLE;
|
||||
}
|
||||
|
||||
sub generate {
|
||||
my ($gen, $git_dir, $out_dir, $rel_dir, %build_structure) = @_;
|
||||
return eval("Generators::${gen}::generate(\$git_dir, \$out_dir, \$rel_dir, \%build_structure)") if grep(/^$gen$/, @AVAILABLE);
|
||||
die "Generator \"${gen}\" is not available!\nAvailable generators are: @AVAILABLE\n";
|
||||
}
|
||||
|
||||
1;
|
||||
189
third_party/git/contrib/buildsystems/Generators/QMake.pm
vendored
Normal file
189
third_party/git/contrib/buildsystems/Generators/QMake.pm
vendored
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
package Generators::QMake;
|
||||
require Exporter;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
|
||||
our $VERSION = '1.00';
|
||||
our(@ISA, @EXPORT, @EXPORT_OK, @AVAILABLE);
|
||||
@ISA = qw(Exporter);
|
||||
|
||||
BEGIN {
|
||||
push @EXPORT_OK, qw(generate);
|
||||
}
|
||||
|
||||
sub generate {
|
||||
my ($git_dir, $out_dir, $rel_dir, %build_structure) = @_;
|
||||
|
||||
my @libs = @{$build_structure{"LIBS"}};
|
||||
foreach (@libs) {
|
||||
createLibProject($_, $git_dir, $out_dir, $rel_dir, %build_structure);
|
||||
}
|
||||
|
||||
my @apps = @{$build_structure{"APPS"}};
|
||||
foreach (@apps) {
|
||||
createAppProject($_, $git_dir, $out_dir, $rel_dir, %build_structure);
|
||||
}
|
||||
|
||||
createGlueProject($git_dir, $out_dir, $rel_dir, %build_structure);
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub createLibProject {
|
||||
my ($libname, $git_dir, $out_dir, $rel_dir, %build_structure) = @_;
|
||||
print "Generate $libname lib project\n";
|
||||
$rel_dir = "../$rel_dir";
|
||||
|
||||
my $sources = join(" \\\n\t", sort(map("$rel_dir/$_", @{$build_structure{"LIBS_${libname}_SOURCES"}})));
|
||||
my $defines = join(" \\\n\t", sort(@{$build_structure{"LIBS_${libname}_DEFINES"}}));
|
||||
my $includes= join(" \\\n\t", sort(map("$rel_dir/$_", @{$build_structure{"LIBS_${libname}_INCLUDES"}})));
|
||||
my $cflags = join(" ", sort(@{$build_structure{"LIBS_${libname}_CFLAGS"}}));
|
||||
|
||||
my $cflags_debug = $cflags;
|
||||
$cflags_debug =~ s/-MT/-MTd/;
|
||||
$cflags_debug =~ s/-O.//;
|
||||
|
||||
my $cflags_release = $cflags;
|
||||
$cflags_release =~ s/-MTd/-MT/;
|
||||
|
||||
my @tmp = @{$build_structure{"LIBS_${libname}_LFLAGS"}};
|
||||
my @tmp2 = ();
|
||||
foreach (@tmp) {
|
||||
if (/^-LTCG/) {
|
||||
} elsif (/^-L/) {
|
||||
$_ =~ s/^-L/-LIBPATH:$rel_dir\//;
|
||||
}
|
||||
push(@tmp2, $_);
|
||||
}
|
||||
my $lflags = join(" ", sort(@tmp));
|
||||
|
||||
my $target = $libname;
|
||||
$target =~ s/\//_/g;
|
||||
$defines =~ s/-D//g;
|
||||
$defines =~ s/"/\\\\"/g;
|
||||
$includes =~ s/-I//g;
|
||||
mkdir "$target" || die "Could not create the directory $target for lib project!\n";
|
||||
open F, ">$target/$target.pro" || die "Could not open $target/$target.pro for writing!\n";
|
||||
print F << "EOM";
|
||||
TEMPLATE = lib
|
||||
TARGET = $target
|
||||
DESTDIR = $rel_dir
|
||||
|
||||
CONFIG -= qt
|
||||
CONFIG += static
|
||||
|
||||
QMAKE_CFLAGS =
|
||||
QMAKE_CFLAGS_RELEASE = $cflags_release
|
||||
QMAKE_CFLAGS_DEBUG = $cflags_debug
|
||||
QMAKE_LIBFLAGS = $lflags
|
||||
|
||||
DEFINES += \\
|
||||
$defines
|
||||
|
||||
INCLUDEPATH += \\
|
||||
$includes
|
||||
|
||||
SOURCES += \\
|
||||
$sources
|
||||
EOM
|
||||
close F;
|
||||
}
|
||||
|
||||
sub createAppProject {
|
||||
my ($appname, $git_dir, $out_dir, $rel_dir, %build_structure) = @_;
|
||||
print "Generate $appname app project\n";
|
||||
$rel_dir = "../$rel_dir";
|
||||
|
||||
my $sources = join(" \\\n\t", sort(map("$rel_dir/$_", @{$build_structure{"APPS_${appname}_SOURCES"}})));
|
||||
my $defines = join(" \\\n\t", sort(@{$build_structure{"APPS_${appname}_DEFINES"}}));
|
||||
my $includes= join(" \\\n\t", sort(map("$rel_dir/$_", @{$build_structure{"APPS_${appname}_INCLUDES"}})));
|
||||
my $cflags = join(" ", sort(@{$build_structure{"APPS_${appname}_CFLAGS"}}));
|
||||
|
||||
my $cflags_debug = $cflags;
|
||||
$cflags_debug =~ s/-MT/-MTd/;
|
||||
$cflags_debug =~ s/-O.//;
|
||||
|
||||
my $cflags_release = $cflags;
|
||||
$cflags_release =~ s/-MTd/-MT/;
|
||||
|
||||
my $libs;
|
||||
foreach (sort(@{$build_structure{"APPS_${appname}_LIBS"}})) {
|
||||
$_ =~ s/\//_/g;
|
||||
$libs .= " $_";
|
||||
}
|
||||
my @tmp = @{$build_structure{"APPS_${appname}_LFLAGS"}};
|
||||
my @tmp2 = ();
|
||||
foreach (@tmp) {
|
||||
# next if ($_ eq "-NODEFAULTLIB:MSVCRT.lib");
|
||||
if (/^-LTCG/) {
|
||||
} elsif (/^-L/) {
|
||||
$_ =~ s/^-L/-LIBPATH:$rel_dir\//;
|
||||
}
|
||||
push(@tmp2, $_);
|
||||
}
|
||||
my $lflags = join(" ", sort(@tmp));
|
||||
|
||||
my $target = $appname;
|
||||
$target =~ s/\.exe//;
|
||||
$target =~ s/\//_/g;
|
||||
$defines =~ s/-D//g;
|
||||
$defines =~ s/"/\\\\"/g;
|
||||
$includes =~ s/-I//g;
|
||||
mkdir "$target" || die "Could not create the directory $target for app project!\n";
|
||||
open F, ">$target/$target.pro" || die "Could not open $target/$target.pro for writing!\n";
|
||||
print F << "EOM";
|
||||
TEMPLATE = app
|
||||
TARGET = $target
|
||||
DESTDIR = $rel_dir
|
||||
|
||||
CONFIG -= qt embed_manifest_exe
|
||||
CONFIG += console
|
||||
|
||||
QMAKE_CFLAGS =
|
||||
QMAKE_CFLAGS_RELEASE = $cflags_release
|
||||
QMAKE_CFLAGS_DEBUG = $cflags_debug
|
||||
QMAKE_LFLAGS = $lflags
|
||||
LIBS = $libs
|
||||
|
||||
DEFINES += \\
|
||||
$defines
|
||||
|
||||
INCLUDEPATH += \\
|
||||
$includes
|
||||
|
||||
win32:QMAKE_LFLAGS += -LIBPATH:$rel_dir
|
||||
else: QMAKE_LFLAGS += -L$rel_dir
|
||||
|
||||
SOURCES += \\
|
||||
$sources
|
||||
EOM
|
||||
close F;
|
||||
}
|
||||
|
||||
sub createGlueProject {
|
||||
my ($git_dir, $out_dir, $rel_dir, %build_structure) = @_;
|
||||
my $libs = join(" \\ \n", map("\t$_|$_.pro", @{$build_structure{"LIBS"}}));
|
||||
my $apps = join(" \\ \n", map("\t$_|$_.pro", @{$build_structure{"APPS"}}));
|
||||
$libs =~ s/\.a//g;
|
||||
$libs =~ s/\//_/g;
|
||||
$libs =~ s/\|/\//g;
|
||||
$apps =~ s/\.exe//g;
|
||||
$apps =~ s/\//_/g;
|
||||
$apps =~ s/\|/\//g;
|
||||
|
||||
my $filename = $out_dir;
|
||||
$filename =~ s/.*\/([^\/]+)$/$1/;
|
||||
$filename =~ s/\/$//;
|
||||
print "Generate glue project $filename.pro\n";
|
||||
open F, ">$filename.pro" || die "Could not open $filename.pro for writing!\n";
|
||||
print F << "EOM";
|
||||
TEMPLATE = subdirs
|
||||
CONFIG += ordered
|
||||
SUBDIRS += \\
|
||||
$libs \\
|
||||
$apps
|
||||
EOM
|
||||
close F;
|
||||
}
|
||||
|
||||
1;
|
||||
579
third_party/git/contrib/buildsystems/Generators/Vcproj.pm
vendored
Normal file
579
third_party/git/contrib/buildsystems/Generators/Vcproj.pm
vendored
Normal file
|
|
@ -0,0 +1,579 @@
|
|||
package Generators::Vcproj;
|
||||
require Exporter;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Digest::SHA qw(sha256_hex);
|
||||
|
||||
our $VERSION = '1.00';
|
||||
our(@ISA, @EXPORT, @EXPORT_OK, @AVAILABLE);
|
||||
@ISA = qw(Exporter);
|
||||
|
||||
BEGIN {
|
||||
push @EXPORT_OK, qw(generate);
|
||||
}
|
||||
|
||||
sub generate_guid ($) {
|
||||
my $hex = sha256_hex($_[0]);
|
||||
$hex =~ s/^(.{8})(.{4})(.{4})(.{4})(.{12}).*/{$1-$2-$3-$4-$5}/;
|
||||
$hex =~ tr/a-z/A-Z/;
|
||||
return $hex;
|
||||
}
|
||||
|
||||
sub generate {
|
||||
my ($git_dir, $out_dir, $rel_dir, %build_structure) = @_;
|
||||
my @libs = @{$build_structure{"LIBS"}};
|
||||
foreach (@libs) {
|
||||
createLibProject($_, $git_dir, $out_dir, $rel_dir, \%build_structure);
|
||||
}
|
||||
|
||||
my @apps = @{$build_structure{"APPS"}};
|
||||
foreach (@apps) {
|
||||
createAppProject($_, $git_dir, $out_dir, $rel_dir, \%build_structure);
|
||||
}
|
||||
|
||||
createGlueProject($git_dir, $out_dir, $rel_dir, %build_structure);
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub createLibProject {
|
||||
my ($libname, $git_dir, $out_dir, $rel_dir, $build_structure) = @_;
|
||||
print "Generate $libname vcproj lib project\n";
|
||||
$rel_dir = "..\\$rel_dir";
|
||||
$rel_dir =~ s/\//\\/g;
|
||||
|
||||
my $target = $libname;
|
||||
$target =~ s/\//_/g;
|
||||
$target =~ s/\.a//;
|
||||
|
||||
my $uuid = generate_guid($libname);
|
||||
$$build_structure{"LIBS_${target}_GUID"} = $uuid;
|
||||
|
||||
my @srcs = sort(map("$rel_dir\\$_", @{$$build_structure{"LIBS_${libname}_SOURCES"}}));
|
||||
my @sources;
|
||||
foreach (@srcs) {
|
||||
$_ =~ s/\//\\/g;
|
||||
push(@sources, $_);
|
||||
}
|
||||
my $defines = join(",", sort(@{$$build_structure{"LIBS_${libname}_DEFINES"}}));
|
||||
my $includes= join(";", sort(map(""$rel_dir\\$_"", @{$$build_structure{"LIBS_${libname}_INCLUDES"}})));
|
||||
my $cflags = join(" ", sort(@{$$build_structure{"LIBS_${libname}_CFLAGS"}}));
|
||||
$cflags =~ s/\"/"/g;
|
||||
$cflags =~ s/</</g;
|
||||
$cflags =~ s/>/>/g;
|
||||
|
||||
my $cflags_debug = $cflags;
|
||||
$cflags_debug =~ s/-MT/-MTd/;
|
||||
$cflags_debug =~ s/-O.//;
|
||||
|
||||
my $cflags_release = $cflags;
|
||||
$cflags_release =~ s/-MTd/-MT/;
|
||||
|
||||
my @tmp = @{$$build_structure{"LIBS_${libname}_LFLAGS"}};
|
||||
my @tmp2 = ();
|
||||
foreach (@tmp) {
|
||||
if (/^-LTCG/) {
|
||||
} elsif (/^-L/) {
|
||||
$_ =~ s/^-L/-LIBPATH:$rel_dir\//;
|
||||
}
|
||||
push(@tmp2, $_);
|
||||
}
|
||||
my $lflags = join(" ", sort(@tmp));
|
||||
|
||||
$defines =~ s/-D//g;
|
||||
$defines =~ s/\"/\\"/g;
|
||||
$defines =~ s/</</g;
|
||||
$defines =~ s/>/>/g;
|
||||
$defines =~ s/\'//g;
|
||||
$includes =~ s/-I//g;
|
||||
mkdir "$target" || die "Could not create the directory $target for lib project!\n";
|
||||
open F, ">$target/$target.vcproj" || die "Could not open $target/$target.pro for writing!\n";
|
||||
binmode F, ":crlf";
|
||||
print F << "EOM";
|
||||
<?xml version="1.0" encoding = "Windows-1252"?>
|
||||
<VisualStudioProject
|
||||
ProjectType="Visual C++"
|
||||
Version="9,00"
|
||||
Name="$target"
|
||||
ProjectGUID="$uuid">
|
||||
<Platforms>
|
||||
<Platform
|
||||
Name="Win32"/>
|
||||
</Platforms>
|
||||
<ToolFiles>
|
||||
</ToolFiles>
|
||||
<Configurations>
|
||||
<Configuration
|
||||
Name="Debug|Win32"
|
||||
OutputDirectory="$rel_dir"
|
||||
ConfigurationType="4"
|
||||
CharacterSet="0"
|
||||
IntermediateDirectory="\$(ProjectDir)\$(ConfigurationName)"
|
||||
>
|
||||
<Tool
|
||||
Name="VCPreBuildEventTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCustomBuildTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXMLDataGeneratorTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCMIDLTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
AdditionalOptions="$cflags_debug"
|
||||
Optimization="0"
|
||||
InlineFunctionExpansion="1"
|
||||
AdditionalIncludeDirectories="$includes"
|
||||
PreprocessorDefinitions="WIN32,_DEBUG,$defines"
|
||||
MinimalRebuild="true"
|
||||
RuntimeLibrary="1"
|
||||
UsePrecompiledHeader="0"
|
||||
ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb"
|
||||
WarningLevel="3"
|
||||
DebugInformationFormat="3"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCManagedResourceCompilerTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCResourceCompilerTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPreLinkEventTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCLibrarianTool"
|
||||
SuppressStartupBanner="true"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCALinkTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXDCMakeTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCBscMakeTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCFxCopTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPostBuildEventTool"
|
||||
/>
|
||||
</Configuration>
|
||||
<Configuration
|
||||
Name="Release|Win32"
|
||||
OutputDirectory="$rel_dir"
|
||||
ConfigurationType="4"
|
||||
CharacterSet="0"
|
||||
WholeProgramOptimization="1"
|
||||
IntermediateDirectory="\$(ProjectDir)\$(ConfigurationName)"
|
||||
>
|
||||
<Tool
|
||||
Name="VCPreBuildEventTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCustomBuildTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXMLDataGeneratorTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCMIDLTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
AdditionalOptions="$cflags_release"
|
||||
Optimization="2"
|
||||
InlineFunctionExpansion="1"
|
||||
EnableIntrinsicFunctions="true"
|
||||
AdditionalIncludeDirectories="$includes"
|
||||
PreprocessorDefinitions="WIN32,NDEBUG,$defines"
|
||||
RuntimeLibrary="0"
|
||||
EnableFunctionLevelLinking="true"
|
||||
UsePrecompiledHeader="0"
|
||||
ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb"
|
||||
WarningLevel="3"
|
||||
DebugInformationFormat="3"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCManagedResourceCompilerTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCResourceCompilerTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPreLinkEventTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCLibrarianTool"
|
||||
SuppressStartupBanner="true"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCALinkTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXDCMakeTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCBscMakeTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCFxCopTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPostBuildEventTool"
|
||||
/>
|
||||
</Configuration>
|
||||
</Configurations>
|
||||
<Files>
|
||||
<Filter
|
||||
Name="Source Files"
|
||||
Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx"
|
||||
UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}">
|
||||
EOM
|
||||
foreach(@sources) {
|
||||
print F << "EOM";
|
||||
<File
|
||||
RelativePath="$_"/>
|
||||
EOM
|
||||
}
|
||||
print F << "EOM";
|
||||
</Filter>
|
||||
</Files>
|
||||
<Globals>
|
||||
</Globals>
|
||||
</VisualStudioProject>
|
||||
EOM
|
||||
close F;
|
||||
}
|
||||
|
||||
sub createAppProject {
|
||||
my ($appname, $git_dir, $out_dir, $rel_dir, $build_structure) = @_;
|
||||
print "Generate $appname vcproj app project\n";
|
||||
$rel_dir = "..\\$rel_dir";
|
||||
$rel_dir =~ s/\//\\/g;
|
||||
|
||||
my $target = $appname;
|
||||
$target =~ s/\//_/g;
|
||||
$target =~ s/\.exe//;
|
||||
|
||||
my $uuid = generate_guid($appname);
|
||||
$$build_structure{"APPS_${target}_GUID"} = $uuid;
|
||||
|
||||
my @srcs = sort(map("$rel_dir\\$_", @{$$build_structure{"APPS_${appname}_SOURCES"}}));
|
||||
my @sources;
|
||||
foreach (@srcs) {
|
||||
$_ =~ s/\//\\/g;
|
||||
push(@sources, $_);
|
||||
}
|
||||
my $defines = join(",", sort(@{$$build_structure{"APPS_${appname}_DEFINES"}}));
|
||||
my $includes= join(";", sort(map(""$rel_dir\\$_"", @{$$build_structure{"APPS_${appname}_INCLUDES"}})));
|
||||
my $cflags = join(" ", sort(@{$$build_structure{"APPS_${appname}_CFLAGS"}}));
|
||||
$cflags =~ s/\"/"/g;
|
||||
$cflags =~ s/</</g;
|
||||
$cflags =~ s/>/>/g;
|
||||
|
||||
my $cflags_debug = $cflags;
|
||||
$cflags_debug =~ s/-MT/-MTd/;
|
||||
$cflags_debug =~ s/-O.//;
|
||||
|
||||
my $cflags_release = $cflags;
|
||||
$cflags_release =~ s/-MTd/-MT/;
|
||||
|
||||
my $libs;
|
||||
foreach (sort(@{$$build_structure{"APPS_${appname}_LIBS"}})) {
|
||||
$_ =~ s/\//_/g;
|
||||
$libs .= " $_";
|
||||
}
|
||||
my @tmp = @{$$build_structure{"APPS_${appname}_LFLAGS"}};
|
||||
my @tmp2 = ();
|
||||
foreach (@tmp) {
|
||||
if (/^-LTCG/) {
|
||||
} elsif (/^-L/) {
|
||||
$_ =~ s/^-L/-LIBPATH:$rel_dir\//;
|
||||
}
|
||||
push(@tmp2, $_);
|
||||
}
|
||||
my $lflags = join(" ", sort(@tmp)) . " -LIBPATH:$rel_dir";
|
||||
|
||||
$defines =~ s/-D//g;
|
||||
$defines =~ s/\"/\\"/g;
|
||||
$defines =~ s/</</g;
|
||||
$defines =~ s/>/>/g;
|
||||
$defines =~ s/\'//g;
|
||||
$defines =~ s/\\\\/\\/g;
|
||||
$includes =~ s/-I//g;
|
||||
mkdir "$target" || die "Could not create the directory $target for lib project!\n";
|
||||
open F, ">$target/$target.vcproj" || die "Could not open $target/$target.pro for writing!\n";
|
||||
binmode F, ":crlf";
|
||||
print F << "EOM";
|
||||
<?xml version="1.0" encoding = "Windows-1252"?>
|
||||
<VisualStudioProject
|
||||
ProjectType="Visual C++"
|
||||
Version="9,00"
|
||||
Name="$target"
|
||||
ProjectGUID="$uuid">
|
||||
<Platforms>
|
||||
<Platform
|
||||
Name="Win32"/>
|
||||
</Platforms>
|
||||
<ToolFiles>
|
||||
</ToolFiles>
|
||||
<Configurations>
|
||||
<Configuration
|
||||
Name="Debug|Win32"
|
||||
OutputDirectory="$rel_dir"
|
||||
ConfigurationType="1"
|
||||
CharacterSet="0"
|
||||
IntermediateDirectory="\$(ProjectDir)\$(ConfigurationName)"
|
||||
>
|
||||
<Tool
|
||||
Name="VCPreBuildEventTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCustomBuildTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXMLDataGeneratorTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCMIDLTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
AdditionalOptions="$cflags_debug"
|
||||
Optimization="0"
|
||||
InlineFunctionExpansion="1"
|
||||
AdditionalIncludeDirectories="$includes"
|
||||
PreprocessorDefinitions="WIN32,_DEBUG,$defines"
|
||||
MinimalRebuild="true"
|
||||
RuntimeLibrary="1"
|
||||
UsePrecompiledHeader="0"
|
||||
ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb"
|
||||
WarningLevel="3"
|
||||
DebugInformationFormat="3"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCManagedResourceCompilerTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCResourceCompilerTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPreLinkEventTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCLinkerTool"
|
||||
AdditionalDependencies="$libs"
|
||||
AdditionalOptions="$lflags"
|
||||
LinkIncremental="2"
|
||||
GenerateDebugInformation="true"
|
||||
SubSystem="1"
|
||||
TargetMachine="1"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCALinkTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXDCMakeTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCBscMakeTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCFxCopTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPostBuildEventTool"
|
||||
/>
|
||||
</Configuration>
|
||||
<Configuration
|
||||
Name="Release|Win32"
|
||||
OutputDirectory="$rel_dir"
|
||||
ConfigurationType="1"
|
||||
CharacterSet="0"
|
||||
WholeProgramOptimization="1"
|
||||
IntermediateDirectory="\$(ProjectDir)\$(ConfigurationName)"
|
||||
>
|
||||
<Tool
|
||||
Name="VCPreBuildEventTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCustomBuildTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXMLDataGeneratorTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCMIDLTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
AdditionalOptions="$cflags_release"
|
||||
Optimization="2"
|
||||
InlineFunctionExpansion="1"
|
||||
EnableIntrinsicFunctions="true"
|
||||
AdditionalIncludeDirectories="$includes"
|
||||
PreprocessorDefinitions="WIN32,NDEBUG,$defines"
|
||||
RuntimeLibrary="0"
|
||||
EnableFunctionLevelLinking="true"
|
||||
UsePrecompiledHeader="0"
|
||||
ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb"
|
||||
WarningLevel="3"
|
||||
DebugInformationFormat="3"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCManagedResourceCompilerTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCResourceCompilerTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPreLinkEventTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCLinkerTool"
|
||||
AdditionalDependencies="$libs"
|
||||
AdditionalOptions="$lflags"
|
||||
LinkIncremental="1"
|
||||
GenerateDebugInformation="true"
|
||||
SubSystem="1"
|
||||
TargetMachine="1"
|
||||
OptimizeReferences="2"
|
||||
EnableCOMDATFolding="2"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCALinkTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXDCMakeTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCBscMakeTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCFxCopTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPostBuildEventTool"
|
||||
/>
|
||||
</Configuration>
|
||||
</Configurations>
|
||||
<Files>
|
||||
<Filter
|
||||
Name="Source Files"
|
||||
Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx"
|
||||
UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}">
|
||||
EOM
|
||||
foreach(@sources) {
|
||||
print F << "EOM";
|
||||
<File
|
||||
RelativePath="$_"/>
|
||||
EOM
|
||||
}
|
||||
print F << "EOM";
|
||||
</Filter>
|
||||
</Files>
|
||||
<Globals>
|
||||
</Globals>
|
||||
</VisualStudioProject>
|
||||
EOM
|
||||
close F;
|
||||
}
|
||||
|
||||
sub createGlueProject {
|
||||
my ($git_dir, $out_dir, $rel_dir, %build_structure) = @_;
|
||||
print "Generate solutions file\n";
|
||||
$rel_dir = "..\\$rel_dir";
|
||||
$rel_dir =~ s/\//\\/g;
|
||||
my $SLN_HEAD = "Microsoft Visual Studio Solution File, Format Version 10.00\n# Visual Studio 2008\n";
|
||||
my $SLN_PRE = "Project(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = ";
|
||||
my $SLN_POST = "\nEndProject\n";
|
||||
|
||||
my @libs = @{$build_structure{"LIBS"}};
|
||||
my @tmp;
|
||||
foreach (@libs) {
|
||||
$_ =~ s/\//_/g;
|
||||
$_ =~ s/\.a//;
|
||||
push(@tmp, $_);
|
||||
}
|
||||
@libs = @tmp;
|
||||
|
||||
my @apps = @{$build_structure{"APPS"}};
|
||||
@tmp = ();
|
||||
foreach (@apps) {
|
||||
$_ =~ s/\//_/g;
|
||||
$_ =~ s/\.exe//;
|
||||
if ($_ eq "git" ) {
|
||||
unshift(@tmp, $_);
|
||||
} else {
|
||||
push(@tmp, $_);
|
||||
}
|
||||
}
|
||||
@apps = @tmp;
|
||||
|
||||
open F, ">git.sln" || die "Could not open git.sln for writing!\n";
|
||||
binmode F, ":crlf";
|
||||
print F "$SLN_HEAD";
|
||||
|
||||
my $uuid_libgit = $build_structure{"LIBS_libgit_GUID"};
|
||||
my $uuid_xdiff_lib = $build_structure{"LIBS_xdiff_lib_GUID"};
|
||||
foreach (@apps) {
|
||||
my $appname = $_;
|
||||
my $uuid = $build_structure{"APPS_${appname}_GUID"};
|
||||
print F "$SLN_PRE";
|
||||
print F "\"${appname}\", \"${appname}\\${appname}.vcproj\", \"${uuid}\"\n";
|
||||
print F " ProjectSection(ProjectDependencies) = postProject\n";
|
||||
print F " ${uuid_libgit} = ${uuid_libgit}\n";
|
||||
print F " ${uuid_xdiff_lib} = ${uuid_xdiff_lib}\n";
|
||||
print F " EndProjectSection";
|
||||
print F "$SLN_POST";
|
||||
}
|
||||
foreach (@libs) {
|
||||
my $libname = $_;
|
||||
my $uuid = $build_structure{"LIBS_${libname}_GUID"};
|
||||
print F "$SLN_PRE";
|
||||
print F "\"${libname}\", \"${libname}\\${libname}.vcproj\", \"${uuid}\"";
|
||||
print F "$SLN_POST";
|
||||
}
|
||||
|
||||
print F << "EOM";
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Win32 = Debug|Win32
|
||||
Release|Win32 = Release|Win32
|
||||
EndGlobalSection
|
||||
EOM
|
||||
print F << "EOM";
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
EOM
|
||||
foreach (@apps) {
|
||||
my $appname = $_;
|
||||
my $uuid = $build_structure{"APPS_${appname}_GUID"};
|
||||
print F "\t\t${uuid}.Debug|Win32.ActiveCfg = Debug|Win32\n";
|
||||
print F "\t\t${uuid}.Debug|Win32.Build.0 = Debug|Win32\n";
|
||||
print F "\t\t${uuid}.Release|Win32.ActiveCfg = Release|Win32\n";
|
||||
print F "\t\t${uuid}.Release|Win32.Build.0 = Release|Win32\n";
|
||||
}
|
||||
foreach (@libs) {
|
||||
my $libname = $_;
|
||||
my $uuid = $build_structure{"LIBS_${libname}_GUID"};
|
||||
print F "\t\t${uuid}.Debug|Win32.ActiveCfg = Debug|Win32\n";
|
||||
print F "\t\t${uuid}.Debug|Win32.Build.0 = Debug|Win32\n";
|
||||
print F "\t\t${uuid}.Release|Win32.ActiveCfg = Release|Win32\n";
|
||||
print F "\t\t${uuid}.Release|Win32.Build.0 = Release|Win32\n";
|
||||
}
|
||||
|
||||
print F << "EOM";
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
EOM
|
||||
close F;
|
||||
}
|
||||
|
||||
1;
|
||||
388
third_party/git/contrib/buildsystems/Generators/Vcxproj.pm
vendored
Normal file
388
third_party/git/contrib/buildsystems/Generators/Vcxproj.pm
vendored
Normal file
|
|
@ -0,0 +1,388 @@
|
|||
package Generators::Vcxproj;
|
||||
require Exporter;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Digest::SHA qw(sha256_hex);
|
||||
|
||||
our $VERSION = '1.00';
|
||||
our(@ISA, @EXPORT, @EXPORT_OK, @AVAILABLE);
|
||||
@ISA = qw(Exporter);
|
||||
|
||||
BEGIN {
|
||||
push @EXPORT_OK, qw(generate);
|
||||
}
|
||||
|
||||
sub generate_guid ($) {
|
||||
my $hex = sha256_hex($_[0]);
|
||||
$hex =~ s/^(.{8})(.{4})(.{4})(.{4})(.{12}).*/{$1-$2-$3-$4-$5}/;
|
||||
$hex =~ tr/a-z/A-Z/;
|
||||
return $hex;
|
||||
}
|
||||
|
||||
sub generate {
|
||||
my ($git_dir, $out_dir, $rel_dir, %build_structure) = @_;
|
||||
my @libs = @{$build_structure{"LIBS"}};
|
||||
foreach (@libs) {
|
||||
createProject($_, $git_dir, $out_dir, $rel_dir, \%build_structure, 1);
|
||||
}
|
||||
|
||||
my @apps = @{$build_structure{"APPS"}};
|
||||
foreach (@apps) {
|
||||
createProject($_, $git_dir, $out_dir, $rel_dir, \%build_structure, 0);
|
||||
}
|
||||
|
||||
createGlueProject($git_dir, $out_dir, $rel_dir, %build_structure);
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub createProject {
|
||||
my ($name, $git_dir, $out_dir, $rel_dir, $build_structure, $static_library) = @_;
|
||||
my $label = $static_library ? "lib" : "app";
|
||||
my $prefix = $static_library ? "LIBS_" : "APPS_";
|
||||
my $config_type = $static_library ? "StaticLibrary" : "Application";
|
||||
print "Generate $name vcxproj $label project\n";
|
||||
my $cdup = $name;
|
||||
$cdup =~ s/[^\/]+/../g;
|
||||
$cdup =~ s/\//\\/g;
|
||||
$rel_dir = $rel_dir eq "." ? $cdup : "$cdup\\$rel_dir";
|
||||
$rel_dir =~ s/\//\\/g;
|
||||
|
||||
my $target = $name;
|
||||
if ($static_library) {
|
||||
$target =~ s/\.a//;
|
||||
} else {
|
||||
$target =~ s/\.exe//;
|
||||
}
|
||||
|
||||
my $uuid = generate_guid($name);
|
||||
$$build_structure{"$prefix${target}_GUID"} = $uuid;
|
||||
my $vcxproj = $target;
|
||||
$vcxproj =~ s/(.*\/)?(.*)/$&\/$2.vcxproj/;
|
||||
$vcxproj =~ s/([^\/]*)(\/lib)\/(lib.vcxproj)/$1$2\/$1_$3/;
|
||||
$$build_structure{"$prefix${target}_VCXPROJ"} = $vcxproj;
|
||||
|
||||
my @srcs = sort(map("$rel_dir\\$_", @{$$build_structure{"$prefix${name}_SOURCES"}}));
|
||||
my @sources;
|
||||
foreach (@srcs) {
|
||||
$_ =~ s/\//\\/g;
|
||||
push(@sources, $_);
|
||||
}
|
||||
my $defines = join(";", sort(@{$$build_structure{"$prefix${name}_DEFINES"}}));
|
||||
my $includes= join(";", sort(map { s/^-I//; s/\//\\/g; File::Spec->file_name_is_absolute($_) ? $_ : "$rel_dir\\$_" } @{$$build_structure{"$prefix${name}_INCLUDES"}}));
|
||||
my $cflags = join(" ", sort(map { s/^-[GLMOWZ].*//; s/.* .*/"$&"/; $_; } @{$$build_structure{"$prefix${name}_CFLAGS"}}));
|
||||
$cflags =~ s/</</g;
|
||||
$cflags =~ s/>/>/g;
|
||||
|
||||
my $libs_release = "\n ";
|
||||
my $libs_debug = "\n ";
|
||||
if (!$static_library) {
|
||||
$libs_release = join(";", sort(grep /^(?!libgit\.lib|xdiff\/lib\.lib|vcs-svn\/lib\.lib)/, @{$$build_structure{"$prefix${name}_LIBS"}}));
|
||||
$libs_debug = $libs_release;
|
||||
$libs_debug =~ s/zlib\.lib/zlibd\.lib/;
|
||||
}
|
||||
|
||||
$defines =~ s/-D//g;
|
||||
$defines =~ s/</</g;
|
||||
$defines =~ s/>/>/g;
|
||||
$defines =~ s/\'//g;
|
||||
|
||||
die "Could not create the directory $target for $label project!\n" unless (-d "$target" || mkdir "$target");
|
||||
|
||||
open F, ">$vcxproj" or die "Could not open $vcxproj for writing!\n";
|
||||
binmode F, ":crlf :utf8";
|
||||
print F chr(0xFEFF);
|
||||
print F << "EOM";
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>$uuid</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<VCPKGArch Condition="'\$(Platform)'=='Win32'">x86-windows</VCPKGArch>
|
||||
<VCPKGArch Condition="'\$(Platform)'!='Win32'">x64-windows</VCPKGArch>
|
||||
<VCPKGArchDirectory>$cdup\\compat\\vcbuild\\vcpkg\\installed\\\$(VCPKGArch)</VCPKGArchDirectory>
|
||||
<VCPKGBinDirectory Condition="'\(Configuration)'=='Debug'">\$(VCPKGArchDirectory)\\debug\\bin</VCPKGBinDirectory>
|
||||
<VCPKGLibDirectory Condition="'\(Configuration)'=='Debug'">\$(VCPKGArchDirectory)\\debug\\lib</VCPKGLibDirectory>
|
||||
<VCPKGBinDirectory Condition="'\(Configuration)'!='Debug'">\$(VCPKGArchDirectory)\\bin</VCPKGBinDirectory>
|
||||
<VCPKGLibDirectory Condition="'\(Configuration)'!='Debug'">\$(VCPKGArchDirectory)\\lib</VCPKGLibDirectory>
|
||||
<VCPKGIncludeDirectory>\$(VCPKGArchDirectory)\\include</VCPKGIncludeDirectory>
|
||||
<VCPKGLibs Condition="'\(Configuration)'=='Debug'">$libs_debug</VCPKGLibs>
|
||||
<VCPKGLibs Condition="'\(Configuration)'!='Debug'">$libs_release</VCPKGLibs>
|
||||
</PropertyGroup>
|
||||
<Import Project="\$(VCTargetsPath)\\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'\$(Configuration)'=='Debug'" Label="Configuration">
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'\$(Configuration)'=='Release'" Label="Configuration">
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ConfigurationType>$config_type</ConfigurationType>
|
||||
<PlatformToolset>v140</PlatformToolset>
|
||||
<!-- <CharacterSet>UTF-8</CharacterSet> -->
|
||||
<OutDir>..\\</OutDir>
|
||||
<!-- <IntDir>\$(ProjectDir)\$(Configuration)\\</IntDir> -->
|
||||
</PropertyGroup>
|
||||
<Import Project="\$(VCTargetsPath)\\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="\$(UserRootDir)\\Microsoft.Cpp.\$(Platform).user.props" Condition="exists('\$(UserRootDir)\\Microsoft.Cpp.\$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<GenerateManifest>false</GenerateManifest>
|
||||
<EnableManagedIncrementalBuild>true</EnableManagedIncrementalBuild>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalOptions>$cflags %(AdditionalOptions)</AdditionalOptions>
|
||||
<AdditionalIncludeDirectories>$cdup;$cdup\\compat;$cdup\\compat\\regex;$cdup\\compat\\win32;$cdup\\compat\\poll;$cdup\\compat\\vcbuild\\include;\$(VCPKGIncludeDirectory);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<EnableParallelCodeGeneration />
|
||||
<InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
|
||||
<PrecompiledHeader />
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
</ClCompile>
|
||||
<Lib>
|
||||
<SuppressStartupBanner>true</SuppressStartupBanner>
|
||||
</Lib>
|
||||
<Link>
|
||||
<AdditionalLibraryDirectories>\$(VCPKGLibDirectory);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>\$(VCPKGLibs);\$(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalOptions>invalidcontinue.obj %(AdditionalOptions)</AdditionalOptions>
|
||||
<EntryPointSymbol>wmainCRTStartup</EntryPointSymbol>
|
||||
<ManifestFile>$cdup\\compat\\win32\\git.manifest</ManifestFile>
|
||||
<SubSystem>Console</SubSystem>
|
||||
</Link>
|
||||
EOM
|
||||
if ($target eq 'libgit') {
|
||||
print F << "EOM";
|
||||
<PreBuildEvent Condition="!Exists('$cdup\\compat\\vcbuild\\vcpkg\\installed\\\$(VCPKGArch)\\include\\openssl\\ssl.h')">
|
||||
<Message>Initialize VCPKG</Message>
|
||||
<Command>del "$cdup\\compat\\vcbuild\\vcpkg"</Command>
|
||||
<Command>call "$cdup\\compat\\vcbuild\\vcpkg_install.bat"</Command>
|
||||
</PreBuildEvent>
|
||||
EOM
|
||||
}
|
||||
print F << "EOM";
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'\$(Platform)'=='Win32'">
|
||||
<Link>
|
||||
<TargetMachine>MachineX86</TargetMachine>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'\$(Configuration)'=='Debug'">
|
||||
<ClCompile>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;$defines;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'\$(Configuration)'=='Release'">
|
||||
<ClCompile>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;$defines;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
EOM
|
||||
foreach(@sources) {
|
||||
print F << "EOM";
|
||||
<ClCompile Include="$_" />
|
||||
EOM
|
||||
}
|
||||
print F << "EOM";
|
||||
</ItemGroup>
|
||||
EOM
|
||||
if (!$static_library || $target =~ 'vcs-svn' || $target =~ 'xdiff') {
|
||||
my $uuid_libgit = $$build_structure{"LIBS_libgit_GUID"};
|
||||
my $uuid_xdiff_lib = $$build_structure{"LIBS_xdiff/lib_GUID"};
|
||||
|
||||
print F << "EOM";
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$cdup\\libgit\\libgit.vcxproj">
|
||||
<Project>$uuid_libgit</Project>
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
EOM
|
||||
if (!($name =~ 'xdiff')) {
|
||||
print F << "EOM";
|
||||
<ProjectReference Include="$cdup\\xdiff\\lib\\xdiff_lib.vcxproj">
|
||||
<Project>$uuid_xdiff_lib</Project>
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
EOM
|
||||
}
|
||||
if ($name =~ /(test-(line-buffer|svn-fe)|^git-remote-testsvn)\.exe$/) {
|
||||
my $uuid_vcs_svn_lib = $$build_structure{"LIBS_vcs-svn/lib_GUID"};
|
||||
print F << "EOM";
|
||||
<ProjectReference Include="$cdup\\vcs-svn\\lib\\vcs-svn_lib.vcxproj">
|
||||
<Project>$uuid_vcs_svn_lib</Project>
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
EOM
|
||||
}
|
||||
print F << "EOM";
|
||||
</ItemGroup>
|
||||
EOM
|
||||
}
|
||||
print F << "EOM";
|
||||
<Import Project="\$(VCTargetsPath)\\Microsoft.Cpp.targets" />
|
||||
EOM
|
||||
if (!$static_library) {
|
||||
print F << "EOM";
|
||||
<Target Name="${target}_AfterBuild" AfterTargets="AfterBuild">
|
||||
<ItemGroup>
|
||||
<DLLsAndPDBs Include="\$(VCPKGBinDirectory)\\*.dll;\$(VCPKGBinDirectory)\\*.pdb" />
|
||||
</ItemGroup>
|
||||
<Copy SourceFiles="@(DLLsAndPDBs)" DestinationFolder="\$(OutDir)" SkipUnchangedFiles="true" UseHardlinksIfPossible="true" />
|
||||
<MakeDir Directories="..\\templates\\blt\\branches" />
|
||||
</Target>
|
||||
EOM
|
||||
}
|
||||
if ($target eq 'git') {
|
||||
print F " <Import Project=\"LinkOrCopyBuiltins.targets\" />\n";
|
||||
}
|
||||
print F << "EOM";
|
||||
</Project>
|
||||
EOM
|
||||
close F;
|
||||
}
|
||||
|
||||
sub createGlueProject {
|
||||
my ($git_dir, $out_dir, $rel_dir, %build_structure) = @_;
|
||||
print "Generate solutions file\n";
|
||||
$rel_dir = "..\\$rel_dir";
|
||||
$rel_dir =~ s/\//\\/g;
|
||||
my $SLN_HEAD = "Microsoft Visual Studio Solution File, Format Version 11.00\n# Visual Studio 2010\n";
|
||||
my $SLN_PRE = "Project(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = ";
|
||||
my $SLN_POST = "\nEndProject\n";
|
||||
|
||||
my @libs = @{$build_structure{"LIBS"}};
|
||||
my @tmp;
|
||||
foreach (@libs) {
|
||||
$_ =~ s/\.a//;
|
||||
push(@tmp, $_);
|
||||
}
|
||||
@libs = @tmp;
|
||||
|
||||
my @apps = @{$build_structure{"APPS"}};
|
||||
@tmp = ();
|
||||
foreach (@apps) {
|
||||
$_ =~ s/\.exe//;
|
||||
if ($_ eq "git" ) {
|
||||
unshift(@tmp, $_);
|
||||
} else {
|
||||
push(@tmp, $_);
|
||||
}
|
||||
}
|
||||
@apps = @tmp;
|
||||
|
||||
open F, ">git.sln" || die "Could not open git.sln for writing!\n";
|
||||
binmode F, ":crlf :utf8";
|
||||
print F chr(0xFEFF);
|
||||
print F "$SLN_HEAD";
|
||||
|
||||
foreach (@apps) {
|
||||
my $appname = $_;
|
||||
my $uuid = $build_structure{"APPS_${appname}_GUID"};
|
||||
print F "$SLN_PRE";
|
||||
my $vcxproj = $build_structure{"APPS_${appname}_VCXPROJ"};
|
||||
$vcxproj =~ s/\//\\/g;
|
||||
$appname =~ s/.*\///;
|
||||
print F "\"${appname}\", \"${vcxproj}\", \"${uuid}\"";
|
||||
print F "$SLN_POST";
|
||||
}
|
||||
foreach (@libs) {
|
||||
my $libname = $_;
|
||||
my $uuid = $build_structure{"LIBS_${libname}_GUID"};
|
||||
print F "$SLN_PRE";
|
||||
my $vcxproj = $build_structure{"LIBS_${libname}_VCXPROJ"};
|
||||
$vcxproj =~ s/\//\\/g;
|
||||
$libname =~ s/\//_/g;
|
||||
print F "\"${libname}\", \"${vcxproj}\", \"${uuid}\"";
|
||||
print F "$SLN_POST";
|
||||
}
|
||||
|
||||
print F << "EOM";
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
EOM
|
||||
print F << "EOM";
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
EOM
|
||||
foreach (@apps) {
|
||||
my $appname = $_;
|
||||
my $uuid = $build_structure{"APPS_${appname}_GUID"};
|
||||
print F "\t\t${uuid}.Debug|x64.ActiveCfg = Debug|x64\n";
|
||||
print F "\t\t${uuid}.Debug|x64.Build.0 = Debug|x64\n";
|
||||
print F "\t\t${uuid}.Debug|x86.ActiveCfg = Debug|Win32\n";
|
||||
print F "\t\t${uuid}.Debug|x86.Build.0 = Debug|Win32\n";
|
||||
print F "\t\t${uuid}.Release|x64.ActiveCfg = Release|x64\n";
|
||||
print F "\t\t${uuid}.Release|x64.Build.0 = Release|x64\n";
|
||||
print F "\t\t${uuid}.Release|x86.ActiveCfg = Release|Win32\n";
|
||||
print F "\t\t${uuid}.Release|x86.Build.0 = Release|Win32\n";
|
||||
}
|
||||
foreach (@libs) {
|
||||
my $libname = $_;
|
||||
my $uuid = $build_structure{"LIBS_${libname}_GUID"};
|
||||
print F "\t\t${uuid}.Debug|x64.ActiveCfg = Debug|x64\n";
|
||||
print F "\t\t${uuid}.Debug|x64.Build.0 = Debug|x64\n";
|
||||
print F "\t\t${uuid}.Debug|x86.ActiveCfg = Debug|Win32\n";
|
||||
print F "\t\t${uuid}.Debug|x86.Build.0 = Debug|Win32\n";
|
||||
print F "\t\t${uuid}.Release|x64.ActiveCfg = Release|x64\n";
|
||||
print F "\t\t${uuid}.Release|x64.Build.0 = Release|x64\n";
|
||||
print F "\t\t${uuid}.Release|x86.ActiveCfg = Release|Win32\n";
|
||||
print F "\t\t${uuid}.Release|x86.Build.0 = Release|Win32\n";
|
||||
}
|
||||
|
||||
print F << "EOM";
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
EOM
|
||||
close F;
|
||||
}
|
||||
|
||||
1;
|
||||
394
third_party/git/contrib/buildsystems/engine.pl
vendored
Executable file
394
third_party/git/contrib/buildsystems/engine.pl
vendored
Executable file
|
|
@ -0,0 +1,394 @@
|
|||
#!/usr/bin/perl -w
|
||||
######################################################################
|
||||
# Do not call this script directly!
|
||||
#
|
||||
# The generate script ensures that @INC is correct before the engine
|
||||
# is executed.
|
||||
#
|
||||
# Copyright (C) 2009 Marius Storm-Olsen <mstormo@gmail.com>
|
||||
######################################################################
|
||||
use strict;
|
||||
use File::Basename;
|
||||
use File::Spec;
|
||||
use Cwd;
|
||||
use Generators;
|
||||
use Text::ParseWords;
|
||||
|
||||
my (%build_structure, %compile_options, @makedry);
|
||||
my $out_dir = getcwd();
|
||||
my $git_dir = $out_dir;
|
||||
$git_dir =~ s=\\=/=g;
|
||||
$git_dir = dirname($git_dir) while (!-e "$git_dir/git.c" && "$git_dir" ne "");
|
||||
die "Couldn't find Git repo" if ("$git_dir" eq "");
|
||||
|
||||
my @gens = Generators::available();
|
||||
my $gen = "Vcproj";
|
||||
|
||||
sub showUsage
|
||||
{
|
||||
my $genlist = join(', ', @gens);
|
||||
print << "EOM";
|
||||
generate usage:
|
||||
-g <GENERATOR> --gen <GENERATOR> Specify the buildsystem generator (default: $gen)
|
||||
Available: $genlist
|
||||
-o <PATH> --out <PATH> Specify output directory generation (default: .)
|
||||
--make-out <PATH> Write the output of GNU Make into a file
|
||||
-i <FILE> --in <FILE> Specify input file, instead of running GNU Make
|
||||
-h,-? --help This help
|
||||
EOM
|
||||
exit 0;
|
||||
}
|
||||
|
||||
# Parse command-line options
|
||||
my $make_out;
|
||||
while (@ARGV) {
|
||||
my $arg = shift @ARGV;
|
||||
if ("$arg" eq "-h" || "$arg" eq "--help" || "$arg" eq "-?") {
|
||||
showUsage();
|
||||
exit(0);
|
||||
} elsif("$arg" eq "--out" || "$arg" eq "-o") {
|
||||
$out_dir = shift @ARGV;
|
||||
} elsif("$arg" eq "--make-out") {
|
||||
$make_out = shift @ARGV;
|
||||
} elsif("$arg" eq "--gen" || "$arg" eq "-g") {
|
||||
$gen = shift @ARGV;
|
||||
} elsif("$arg" eq "--in" || "$arg" eq "-i") {
|
||||
my $infile = shift @ARGV;
|
||||
open(F, "<$infile") || die "Couldn't open file $infile";
|
||||
@makedry = <F>;
|
||||
close(F);
|
||||
} else {
|
||||
die "Unknown option: " . $arg;
|
||||
}
|
||||
}
|
||||
|
||||
# NOT using File::Spec->rel2abs($path, $base) here, as
|
||||
# it fails badly for me in the msysgit environment
|
||||
$git_dir = File::Spec->rel2abs($git_dir);
|
||||
$out_dir = File::Spec->rel2abs($out_dir);
|
||||
my $rel_dir = makeOutRel2Git($git_dir, $out_dir);
|
||||
|
||||
# Print some information so the user feels informed
|
||||
print << "EOM";
|
||||
-----
|
||||
Generator: $gen
|
||||
Git dir: $git_dir
|
||||
Out dir: $out_dir
|
||||
-----
|
||||
Running GNU Make to figure out build structure...
|
||||
EOM
|
||||
|
||||
# Pipe a make --dry-run into a variable, if not already loaded from file
|
||||
# Capture the make dry stderr to file for review (will be empty for a release build).
|
||||
|
||||
my $ErrsFile = "msvc-build-makedryerrors.txt";
|
||||
@makedry = `make -C $git_dir -n MSVC=1 SKIP_VCPKG=1 V=1 2>$ErrsFile`
|
||||
if !@makedry;
|
||||
# test for an empty Errors file and remove it
|
||||
unlink $ErrsFile if -f -z $ErrsFile;
|
||||
|
||||
if (defined $make_out) {
|
||||
open OUT, ">" . $make_out;
|
||||
print OUT @makedry;
|
||||
close OUT;
|
||||
}
|
||||
|
||||
# Parse the make output into usable info
|
||||
parseMakeOutput();
|
||||
|
||||
# Finally, ask the generator to start generating..
|
||||
Generators::generate($gen, $git_dir, $out_dir, $rel_dir, %build_structure);
|
||||
|
||||
# main flow ends here
|
||||
# -------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# 1) path: /foo/bar/baz 2) path: /foo/bar/baz 3) path: /foo/bar/baz
|
||||
# base: /foo/bar/baz/temp base: /foo/bar base: /tmp
|
||||
# rel: .. rel: baz rel: ../foo/bar/baz
|
||||
sub makeOutRel2Git
|
||||
{
|
||||
my ($path, $base) = @_;
|
||||
my $rel;
|
||||
if ("$path" eq "$base") {
|
||||
return ".";
|
||||
} elsif ($base =~ /^$path/) {
|
||||
# case 1
|
||||
my $tmp = $base;
|
||||
$tmp =~ s/^$path//;
|
||||
foreach (split('/', $tmp)) {
|
||||
$rel .= "../" if ("$_" ne "");
|
||||
}
|
||||
} elsif ($path =~ /^$base/) {
|
||||
# case 2
|
||||
$rel = $path;
|
||||
$rel =~ s/^$base//;
|
||||
$rel = "./$rel";
|
||||
} else {
|
||||
my $tmp = $base;
|
||||
foreach (split('/', $tmp)) {
|
||||
$rel .= "../" if ("$_" ne "");
|
||||
}
|
||||
$rel .= $path;
|
||||
}
|
||||
$rel =~ s/\/\//\//g; # simplify
|
||||
$rel =~ s/\/$//; # don't end with /
|
||||
return $rel;
|
||||
}
|
||||
|
||||
sub parseMakeOutput
|
||||
{
|
||||
print "Parsing GNU Make output to figure out build structure...\n";
|
||||
my $line = 0;
|
||||
while (my $text = shift @makedry) {
|
||||
my $ate_next;
|
||||
do {
|
||||
$ate_next = 0;
|
||||
$line++;
|
||||
chomp $text;
|
||||
chop $text if ($text =~ /\r$/);
|
||||
if ($text =~ /\\$/) {
|
||||
$text =~ s/\\$//;
|
||||
$text .= shift @makedry;
|
||||
$ate_next = 1;
|
||||
}
|
||||
} while($ate_next);
|
||||
|
||||
if ($text =~ /^test /) {
|
||||
# options to test (eg -o) may be mistaken for linker options
|
||||
next;
|
||||
}
|
||||
|
||||
if ($text =~ /^(mkdir|msgfmt) /) {
|
||||
# options to the Portable Object translations
|
||||
# the line "mkdir ... && msgfmt ..." contains no linker options
|
||||
next;
|
||||
}
|
||||
|
||||
if($text =~ / -c /) {
|
||||
# compilation
|
||||
handleCompileLine($text, $line);
|
||||
|
||||
} elsif ($text =~ / -o /) {
|
||||
# linking executable
|
||||
handleLinkLine($text, $line);
|
||||
|
||||
} elsif ($text =~ /\.o / && $text =~ /\.a /) {
|
||||
# libifying
|
||||
handleLibLine($text, $line);
|
||||
#
|
||||
# } elsif ($text =~ /^cp /) {
|
||||
# # copy file around
|
||||
#
|
||||
# } elsif ($text =~ /^rm -f /) {
|
||||
# # shell command
|
||||
#
|
||||
# } elsif ($text =~ /^make[ \[]/) {
|
||||
# # make output
|
||||
#
|
||||
# } elsif ($text =~ /^echo /) {
|
||||
# # echo to file
|
||||
#
|
||||
# } elsif ($text =~ /^if /) {
|
||||
# # shell conditional
|
||||
#
|
||||
# } elsif ($text =~ /^tclsh /) {
|
||||
# # translation stuff
|
||||
#
|
||||
# } elsif ($text =~ /^umask /) {
|
||||
# # handling boilerplates
|
||||
#
|
||||
# } elsif ($text =~ /\$\(\:\)/) {
|
||||
# # ignore
|
||||
#
|
||||
# } elsif ($text =~ /^FLAGS=/) {
|
||||
# # flags check for dependencies
|
||||
#
|
||||
# } elsif ($text =~ /^'\/usr\/bin\/perl' -MError -e/) {
|
||||
# # perl commands for copying files
|
||||
#
|
||||
# } elsif ($text =~ /generate-cmdlist\.sh/) {
|
||||
# # command for generating list of commands
|
||||
#
|
||||
# } elsif ($text =~ /new locations or Tcl/) {
|
||||
# # command for detecting Tcl/Tk changes
|
||||
#
|
||||
# } elsif ($text =~ /mkdir -p/) {
|
||||
# # command creating path
|
||||
#
|
||||
# } elsif ($text =~ /: no custom templates yet/) {
|
||||
# # whatever
|
||||
#
|
||||
# } else {
|
||||
# print "Unhandled (line: $line): $text\n";
|
||||
}
|
||||
}
|
||||
|
||||
# use Data::Dumper;
|
||||
# print "Parsed build structure:\n";
|
||||
# print Dumper(%build_structure);
|
||||
}
|
||||
|
||||
# variables for the compilation part of each step
|
||||
my (@defines, @incpaths, @cflags, @sources);
|
||||
|
||||
sub clearCompileStep
|
||||
{
|
||||
@defines = ();
|
||||
@incpaths = ();
|
||||
@cflags = ();
|
||||
@sources = ();
|
||||
}
|
||||
|
||||
sub removeDuplicates
|
||||
{
|
||||
my (%dupHash, $entry);
|
||||
%dupHash = map { $_, 1 } @defines;
|
||||
@defines = keys %dupHash;
|
||||
|
||||
%dupHash = map { $_, 1 } @incpaths;
|
||||
@incpaths = keys %dupHash;
|
||||
|
||||
%dupHash = map { $_, 1 } @cflags;
|
||||
@cflags = keys %dupHash;
|
||||
}
|
||||
|
||||
sub handleCompileLine
|
||||
{
|
||||
my ($line, $lineno) = @_;
|
||||
my @parts = shellwords($line);
|
||||
my $sourcefile;
|
||||
shift(@parts); # ignore cmd
|
||||
while (my $part = shift @parts) {
|
||||
if ("$part" eq "-o") {
|
||||
# ignore object file
|
||||
shift @parts;
|
||||
} elsif ("$part" eq "-c") {
|
||||
# ignore compile flag
|
||||
} elsif ("$part" eq "-c") {
|
||||
} elsif ($part =~ /^.?-I/) {
|
||||
push(@incpaths, $part);
|
||||
} elsif ($part =~ /^.?-D/) {
|
||||
push(@defines, $part);
|
||||
} elsif ($part =~ /^-/) {
|
||||
push(@cflags, $part);
|
||||
} elsif ($part =~ /\.(c|cc|cpp)$/) {
|
||||
$sourcefile = $part;
|
||||
} else {
|
||||
die "Unhandled compiler option @ line $lineno: $part";
|
||||
}
|
||||
}
|
||||
@{$compile_options{"${sourcefile}_CFLAGS"}} = @cflags;
|
||||
@{$compile_options{"${sourcefile}_DEFINES"}} = @defines;
|
||||
@{$compile_options{"${sourcefile}_INCPATHS"}} = @incpaths;
|
||||
clearCompileStep();
|
||||
}
|
||||
|
||||
sub handleLibLine
|
||||
{
|
||||
my ($line, $lineno) = @_;
|
||||
my (@objfiles, @lflags, $libout, $part);
|
||||
# kill cmd and rm 'prefix'
|
||||
$line =~ s/^rm -f .* && .* rcs //;
|
||||
my @parts = shellwords($line);
|
||||
while ($part = shift @parts) {
|
||||
if ($part =~ /^-/) {
|
||||
push(@lflags, $part);
|
||||
} elsif ($part =~ /\.(o|obj)$/) {
|
||||
push(@objfiles, $part);
|
||||
} elsif ($part =~ /\.(a|lib)$/) {
|
||||
$libout = $part;
|
||||
$libout =~ s/\.a$//;
|
||||
} else {
|
||||
die "Unhandled lib option @ line $lineno: $part";
|
||||
}
|
||||
}
|
||||
# print "LibOut: '$libout'\nLFlags: @lflags\nOfiles: @objfiles\n";
|
||||
# exit(1);
|
||||
foreach (@objfiles) {
|
||||
my $sourcefile = $_;
|
||||
$sourcefile =~ s/\.o$/.c/;
|
||||
push(@sources, $sourcefile);
|
||||
push(@cflags, @{$compile_options{"${sourcefile}_CFLAGS"}});
|
||||
push(@defines, @{$compile_options{"${sourcefile}_DEFINES"}});
|
||||
push(@incpaths, @{$compile_options{"${sourcefile}_INCPATHS"}});
|
||||
}
|
||||
removeDuplicates();
|
||||
|
||||
push(@{$build_structure{"LIBS"}}, $libout);
|
||||
@{$build_structure{"LIBS_${libout}"}} = ("_DEFINES", "_INCLUDES", "_CFLAGS", "_SOURCES",
|
||||
"_OBJECTS");
|
||||
@{$build_structure{"LIBS_${libout}_DEFINES"}} = @defines;
|
||||
@{$build_structure{"LIBS_${libout}_INCLUDES"}} = @incpaths;
|
||||
@{$build_structure{"LIBS_${libout}_CFLAGS"}} = @cflags;
|
||||
@{$build_structure{"LIBS_${libout}_LFLAGS"}} = @lflags;
|
||||
@{$build_structure{"LIBS_${libout}_SOURCES"}} = @sources;
|
||||
@{$build_structure{"LIBS_${libout}_OBJECTS"}} = @objfiles;
|
||||
clearCompileStep();
|
||||
}
|
||||
|
||||
sub handleLinkLine
|
||||
{
|
||||
my ($line, $lineno) = @_;
|
||||
my (@objfiles, @lflags, @libs, $appout, $part);
|
||||
my @parts = shellwords($line);
|
||||
shift(@parts); # ignore cmd
|
||||
while ($part = shift @parts) {
|
||||
if ($part =~ /^-IGNORE/) {
|
||||
push(@lflags, $part);
|
||||
} elsif ($part =~ /^-[GRIMDO]/) {
|
||||
# eat compiler flags
|
||||
} elsif ("$part" eq "-o") {
|
||||
$appout = shift @parts;
|
||||
} elsif ("$part" eq "-lz") {
|
||||
push(@libs, "zlib.lib");
|
||||
} elsif ("$part" eq "-lcrypto") {
|
||||
push(@libs, "libeay32.lib");
|
||||
} elsif ("$part" eq "-lssl") {
|
||||
push(@libs, "ssleay32.lib");
|
||||
} elsif ("$part" eq "-lcurl") {
|
||||
push(@libs, "libcurl.lib");
|
||||
} elsif ("$part" eq "-lexpat") {
|
||||
push(@libs, "expat.lib");
|
||||
} elsif ("$part" eq "-liconv") {
|
||||
push(@libs, "libiconv.lib");
|
||||
} elsif ($part =~ /^[-\/]/) {
|
||||
push(@lflags, $part);
|
||||
} elsif ($part =~ /\.(a|lib)$/) {
|
||||
$part =~ s/\.a$/.lib/;
|
||||
push(@libs, $part);
|
||||
} elsif ($part eq 'invalidcontinue.obj') {
|
||||
# ignore - known to MSVC
|
||||
} elsif ($part =~ /\.o$/) {
|
||||
push(@objfiles, $part);
|
||||
} elsif ($part =~ /\.obj$/) {
|
||||
# do nothing, 'make' should not be producing .obj, only .o files
|
||||
} else {
|
||||
die "Unhandled link option @ line $lineno: $part";
|
||||
}
|
||||
}
|
||||
# print "AppOut: '$appout'\nLFlags: @lflags\nLibs : @libs\nOfiles: @objfiles\n";
|
||||
# exit(1);
|
||||
foreach (@objfiles) {
|
||||
my $sourcefile = $_;
|
||||
$sourcefile =~ s/\.o$/.c/;
|
||||
push(@sources, $sourcefile);
|
||||
push(@cflags, @{$compile_options{"${sourcefile}_CFLAGS"}});
|
||||
push(@defines, @{$compile_options{"${sourcefile}_DEFINES"}});
|
||||
push(@incpaths, @{$compile_options{"${sourcefile}_INCPATHS"}});
|
||||
}
|
||||
removeDuplicates();
|
||||
|
||||
removeDuplicates();
|
||||
push(@{$build_structure{"APPS"}}, $appout);
|
||||
@{$build_structure{"APPS_${appout}"}} = ("_DEFINES", "_INCLUDES", "_CFLAGS", "_LFLAGS",
|
||||
"_SOURCES", "_OBJECTS", "_LIBS");
|
||||
@{$build_structure{"APPS_${appout}_DEFINES"}} = @defines;
|
||||
@{$build_structure{"APPS_${appout}_INCLUDES"}} = @incpaths;
|
||||
@{$build_structure{"APPS_${appout}_CFLAGS"}} = @cflags;
|
||||
@{$build_structure{"APPS_${appout}_LFLAGS"}} = @lflags;
|
||||
@{$build_structure{"APPS_${appout}_SOURCES"}} = @sources;
|
||||
@{$build_structure{"APPS_${appout}_OBJECTS"}} = @objfiles;
|
||||
@{$build_structure{"APPS_${appout}_LIBS"}} = @libs;
|
||||
clearCompileStep();
|
||||
}
|
||||
29
third_party/git/contrib/buildsystems/generate
vendored
Executable file
29
third_party/git/contrib/buildsystems/generate
vendored
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
#!/usr/bin/perl -w
|
||||
######################################################################
|
||||
# Generate buildsystem files
|
||||
#
|
||||
# This script generate buildsystem files based on the output of a
|
||||
# GNU Make --dry-run, enabling Windows users to develop Git with their
|
||||
# trusted IDE with native projects.
|
||||
#
|
||||
# Note:
|
||||
# It is not meant as *the* way of building Git with MSVC, but merely a
|
||||
# convenience. The correct way of building Git with MSVC is to use the
|
||||
# GNU Make tool to build with the maintained Makefile in the root of
|
||||
# the project. If you have the msysgit environment installed and
|
||||
# available in your current console, together with the Visual Studio
|
||||
# environment you wish to build for, all you have to do is run the
|
||||
# command:
|
||||
# make MSVC=1
|
||||
#
|
||||
# Copyright (C) 2009 Marius Storm-Olsen <mstormo@gmail.com>
|
||||
######################################################################
|
||||
use strict;
|
||||
use File::Basename;
|
||||
use Cwd;
|
||||
|
||||
my $git_dir = getcwd();
|
||||
$git_dir =~ s=\\=/=g;
|
||||
$git_dir = dirname($git_dir) while (!-e "$git_dir/git.c" && "$git_dir" ne "");
|
||||
die "Couldn't find Git repo" if ("$git_dir" eq "");
|
||||
exec join(" ", ("PERL5LIB=${git_dir}/contrib/buildsystems ${git_dir}/contrib/buildsystems/engine.pl", @ARGV));
|
||||
228
third_party/git/contrib/buildsystems/parse.pl
vendored
Executable file
228
third_party/git/contrib/buildsystems/parse.pl
vendored
Executable file
|
|
@ -0,0 +1,228 @@
|
|||
#!/usr/bin/perl -w
|
||||
######################################################################
|
||||
# Do not call this script directly!
|
||||
#
|
||||
# The generate script ensures that @INC is correct before the engine
|
||||
# is executed.
|
||||
#
|
||||
# Copyright (C) 2009 Marius Storm-Olsen <mstormo@gmail.com>
|
||||
######################################################################
|
||||
use strict;
|
||||
use File::Basename;
|
||||
use Cwd;
|
||||
|
||||
my $file = $ARGV[0];
|
||||
die "No file provided!" if !defined $file;
|
||||
|
||||
my ($cflags, $target, $type, $line);
|
||||
|
||||
open(F, "<$file") || die "Couldn't open file $file";
|
||||
my @data = <F>;
|
||||
close(F);
|
||||
|
||||
while (my $text = shift @data) {
|
||||
my $ate_next;
|
||||
do {
|
||||
$ate_next = 0;
|
||||
$line++;
|
||||
chomp $text;
|
||||
chop $text if ($text =~ /\r$/);
|
||||
if ($text =~ /\\$/) {
|
||||
$text =~ s/\\$//;
|
||||
$text .= shift @data;
|
||||
$ate_next = 1;
|
||||
}
|
||||
} while($ate_next);
|
||||
|
||||
if($text =~ / -c /) {
|
||||
# compilation
|
||||
handleCompileLine($text, $line);
|
||||
|
||||
} elsif ($text =~ / -o /) {
|
||||
# linking executable
|
||||
handleLinkLine($text, $line);
|
||||
|
||||
} elsif ($text =~ /\.o / && $text =~ /\.a /) {
|
||||
# libifying
|
||||
handleLibLine($text, $line);
|
||||
|
||||
# } elsif ($text =~ /^cp /) {
|
||||
# # copy file around
|
||||
#
|
||||
# } elsif ($text =~ /^rm -f /) {
|
||||
# # shell command
|
||||
#
|
||||
# } elsif ($text =~ /^make[ \[]/) {
|
||||
# # make output
|
||||
#
|
||||
# } elsif ($text =~ /^echo /) {
|
||||
# # echo to file
|
||||
#
|
||||
# } elsif ($text =~ /^if /) {
|
||||
# # shell conditional
|
||||
#
|
||||
# } elsif ($text =~ /^tclsh /) {
|
||||
# # translation stuff
|
||||
#
|
||||
# } elsif ($text =~ /^umask /) {
|
||||
# # handling boilerplates
|
||||
#
|
||||
# } elsif ($text =~ /\$\(\:\)/) {
|
||||
# # ignore
|
||||
#
|
||||
# } elsif ($text =~ /^FLAGS=/) {
|
||||
# # flags check for dependencies
|
||||
#
|
||||
# } elsif ($text =~ /^'\/usr\/bin\/perl' -MError -e/) {
|
||||
# # perl commands for copying files
|
||||
#
|
||||
# } elsif ($text =~ /generate-cmdlist\.sh/) {
|
||||
# # command for generating list of commands
|
||||
#
|
||||
# } elsif ($text =~ /^test / && $text =~ /|| rm -f /) {
|
||||
# # commands removing executables, if they exist
|
||||
#
|
||||
# } elsif ($text =~ /new locations or Tcl/) {
|
||||
# # command for detecting Tcl/Tk changes
|
||||
#
|
||||
# } elsif ($text =~ /mkdir -p/) {
|
||||
# # command creating path
|
||||
#
|
||||
# } elsif ($text =~ /: no custom templates yet/) {
|
||||
# # whatever
|
||||
|
||||
} else {
|
||||
# print "Unhandled (line: $line): $text\n";
|
||||
}
|
||||
}
|
||||
close(F);
|
||||
|
||||
# use Data::Dumper;
|
||||
# print "Parsed build structure:\n";
|
||||
# print Dumper(%build_structure);
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Functions under here
|
||||
# -------------------------------------------------------------------
|
||||
my (%build_structure, @defines, @incpaths, @cflags, @sources);
|
||||
|
||||
sub clearCompileStep
|
||||
{
|
||||
@defines = ();
|
||||
@incpaths = ();
|
||||
@cflags = ();
|
||||
@sources = ();
|
||||
}
|
||||
|
||||
sub removeDuplicates
|
||||
{
|
||||
my (%dupHash, $entry);
|
||||
%dupHash = map { $_, 1 } @defines;
|
||||
@defines = keys %dupHash;
|
||||
|
||||
%dupHash = map { $_, 1 } @incpaths;
|
||||
@incpaths = keys %dupHash;
|
||||
|
||||
%dupHash = map { $_, 1 } @cflags;
|
||||
@cflags = keys %dupHash;
|
||||
|
||||
%dupHash = map { $_, 1 } @sources;
|
||||
@sources = keys %dupHash;
|
||||
}
|
||||
|
||||
sub handleCompileLine
|
||||
{
|
||||
my ($line, $lineno) = @_;
|
||||
my @parts = split(' ', $line);
|
||||
shift(@parts); # ignore cmd
|
||||
while (my $part = shift @parts) {
|
||||
if ("$part" eq "-o") {
|
||||
# ignore object file
|
||||
shift @parts;
|
||||
} elsif ("$part" eq "-c") {
|
||||
# ignore compile flag
|
||||
} elsif ("$part" eq "-c") {
|
||||
} elsif ($part =~ /^.?-I/) {
|
||||
push(@incpaths, $part);
|
||||
} elsif ($part =~ /^.?-D/) {
|
||||
push(@defines, $part);
|
||||
} elsif ($part =~ /^-/) {
|
||||
push(@cflags, $part);
|
||||
} elsif ($part =~ /\.(c|cc|cpp)$/) {
|
||||
push(@sources, $part);
|
||||
} else {
|
||||
die "Unhandled compiler option @ line $lineno: $part";
|
||||
}
|
||||
}
|
||||
#print "Sources: @sources\nCFlags: @cflags\nDefine: @defines\nIncpat: @incpaths\n";
|
||||
#exit(1);
|
||||
}
|
||||
|
||||
sub handleLibLine
|
||||
{
|
||||
my ($line, $lineno) = @_;
|
||||
my (@objfiles, @lflags, $libout, $part);
|
||||
# kill cmd and rm 'prefix'
|
||||
$line =~ s/^rm -f .* && .* rcs //;
|
||||
my @parts = split(' ', $line);
|
||||
while ($part = shift @parts) {
|
||||
if ($part =~ /^-/) {
|
||||
push(@lflags, $part);
|
||||
} elsif ($part =~ /\.(o|obj)$/) {
|
||||
push(@objfiles, $part);
|
||||
} elsif ($part =~ /\.(a|lib)$/) {
|
||||
$libout = $part;
|
||||
} else {
|
||||
die "Unhandled lib option @ line $lineno: $part";
|
||||
}
|
||||
}
|
||||
#print "LibOut: '$libout'\nLFlags: @lflags\nOfiles: @objfiles\n";
|
||||
#exit(1);
|
||||
removeDuplicates();
|
||||
push(@{$build_structure{"LIBS"}}, $libout);
|
||||
@{$build_structure{"LIBS_${libout}"}} = ("_DEFINES", "_INCLUDES", "_CFLAGS", "_SOURCES",
|
||||
"_OBJECTS");
|
||||
@{$build_structure{"LIBS_${libout}_DEFINES"}} = @defines;
|
||||
@{$build_structure{"LIBS_${libout}_INCLUDES"}} = @incpaths;
|
||||
@{$build_structure{"LIBS_${libout}_CFLAGS"}} = @cflags;
|
||||
@{$build_structure{"LIBS_${libout}_SOURCES"}} = @sources;
|
||||
@{$build_structure{"LIBS_${libout}_OBJECTS"}} = @objfiles;
|
||||
clearCompileStep();
|
||||
}
|
||||
|
||||
sub handleLinkLine
|
||||
{
|
||||
my ($line, $lineno) = @_;
|
||||
my (@objfiles, @lflags, @libs, $appout, $part);
|
||||
my @parts = split(' ', $line);
|
||||
shift(@parts); # ignore cmd
|
||||
while ($part = shift @parts) {
|
||||
if ($part =~ /^-[GRIDO]/) {
|
||||
# eat compiler flags
|
||||
} elsif ("$part" eq "-o") {
|
||||
$appout = shift @parts;
|
||||
} elsif ($part =~ /^-/) {
|
||||
push(@lflags, $part);
|
||||
} elsif ($part =~ /\.(a|lib)$/) {
|
||||
push(@libs, $part);
|
||||
} elsif ($part =~ /\.(o|obj)$/) {
|
||||
push(@objfiles, $part);
|
||||
} else {
|
||||
die "Unhandled lib option @ line $lineno: $part";
|
||||
}
|
||||
}
|
||||
#print "AppOut: '$appout'\nLFlags: @lflags\nLibs : @libs\nOfiles: @objfiles\n";
|
||||
#exit(1);
|
||||
removeDuplicates();
|
||||
push(@{$build_structure{"APPS"}}, $appout);
|
||||
@{$build_structure{"APPS_${appout}"}} = ("_DEFINES", "_INCLUDES", "_CFLAGS", "_LFLAGS",
|
||||
"_SOURCES", "_OBJECTS", "_LIBS");
|
||||
@{$build_structure{"APPS_${appout}_DEFINES"}} = @defines;
|
||||
@{$build_structure{"APPS_${appout}_INCLUDES"}} = @incpaths;
|
||||
@{$build_structure{"APPS_${appout}_CFLAGS"}} = @cflags;
|
||||
@{$build_structure{"APPS_${appout}_LFLAGS"}} = @lflags;
|
||||
@{$build_structure{"APPS_${appout}_SOURCES"}} = @sources;
|
||||
@{$build_structure{"APPS_${appout}_OBJECTS"}} = @objfiles;
|
||||
@{$build_structure{"APPS_${appout}_LIBS"}} = @libs;
|
||||
clearCompileStep();
|
||||
}
|
||||
1
third_party/git/contrib/coccinelle/.gitignore
vendored
Normal file
1
third_party/git/contrib/coccinelle/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
*.patch*
|
||||
43
third_party/git/contrib/coccinelle/README
vendored
Normal file
43
third_party/git/contrib/coccinelle/README
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
This directory provides examples of Coccinelle (http://coccinelle.lip6.fr/)
|
||||
semantic patches that might be useful to developers.
|
||||
|
||||
There are two types of semantic patches:
|
||||
|
||||
* Using the semantic transformation to check for bad patterns in the code;
|
||||
The target 'make coccicheck' is designed to check for these patterns and
|
||||
it is expected that any resulting patch indicates a regression.
|
||||
The patches resulting from 'make coccicheck' are small and infrequent,
|
||||
so once they are found, they can be sent to the mailing list as per usual.
|
||||
|
||||
Example for introducing new patterns:
|
||||
67947c34ae (convert "hashcmp() != 0" to "!hasheq()", 2018-08-28)
|
||||
b84c783882 (fsck: s/++i > 1/i++/, 2018-10-24)
|
||||
|
||||
Example of fixes using this approach:
|
||||
248f66ed8e (run-command: use strbuf_addstr() for adding a string to
|
||||
a strbuf, 2018-03-25)
|
||||
f919ffebed (Use MOVE_ARRAY, 2018-01-22)
|
||||
|
||||
These types of semantic patches are usually part of testing, c.f.
|
||||
0860a7641b (travis-ci: fail if Coccinelle static analysis found something
|
||||
to transform, 2018-07-23)
|
||||
|
||||
* Using semantic transformations in large scale refactorings throughout
|
||||
the code base.
|
||||
|
||||
When applying the semantic patch into a real patch, sending it to the
|
||||
mailing list in the usual way, such a patch would be expected to have a
|
||||
lot of textual and semantic conflicts as such large scale refactorings
|
||||
change function signatures that are used widely in the code base.
|
||||
A textual conflict would arise if surrounding code near any call of such
|
||||
function changes. A semantic conflict arises when other patch series in
|
||||
flight introduce calls to such functions.
|
||||
|
||||
So to aid these large scale refactorings, semantic patches can be used.
|
||||
However we do not want to store them in the same place as the checks for
|
||||
bad patterns, as then automated builds would fail.
|
||||
That is why semantic patches 'contrib/coccinelle/*.pending.cocci'
|
||||
are ignored for checks, and can be applied using 'make coccicheck-pending'.
|
||||
|
||||
This allows to expose plans of pending large scale refactorings without
|
||||
impacting the bad pattern checks.
|
||||
90
third_party/git/contrib/coccinelle/array.cocci
vendored
Normal file
90
third_party/git/contrib/coccinelle/array.cocci
vendored
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
@@
|
||||
expression dst, src, n, E;
|
||||
@@
|
||||
memcpy(dst, src, n * sizeof(
|
||||
- E[...]
|
||||
+ *(E)
|
||||
))
|
||||
|
||||
@@
|
||||
type T;
|
||||
T *ptr;
|
||||
T[] arr;
|
||||
expression E, n;
|
||||
@@
|
||||
(
|
||||
memcpy(ptr, E,
|
||||
- n * sizeof(*(ptr))
|
||||
+ n * sizeof(T)
|
||||
)
|
||||
|
|
||||
memcpy(arr, E,
|
||||
- n * sizeof(*(arr))
|
||||
+ n * sizeof(T)
|
||||
)
|
||||
|
|
||||
memcpy(E, ptr,
|
||||
- n * sizeof(*(ptr))
|
||||
+ n * sizeof(T)
|
||||
)
|
||||
|
|
||||
memcpy(E, arr,
|
||||
- n * sizeof(*(arr))
|
||||
+ n * sizeof(T)
|
||||
)
|
||||
)
|
||||
|
||||
@@
|
||||
type T;
|
||||
T *dst_ptr;
|
||||
T *src_ptr;
|
||||
T[] dst_arr;
|
||||
T[] src_arr;
|
||||
expression n;
|
||||
@@
|
||||
(
|
||||
- memcpy(dst_ptr, src_ptr, (n) * sizeof(T))
|
||||
+ COPY_ARRAY(dst_ptr, src_ptr, n)
|
||||
|
|
||||
- memcpy(dst_ptr, src_arr, (n) * sizeof(T))
|
||||
+ COPY_ARRAY(dst_ptr, src_arr, n)
|
||||
|
|
||||
- memcpy(dst_arr, src_ptr, (n) * sizeof(T))
|
||||
+ COPY_ARRAY(dst_arr, src_ptr, n)
|
||||
|
|
||||
- memcpy(dst_arr, src_arr, (n) * sizeof(T))
|
||||
+ COPY_ARRAY(dst_arr, src_arr, n)
|
||||
)
|
||||
|
||||
@@
|
||||
type T;
|
||||
T *dst;
|
||||
T *src;
|
||||
expression n;
|
||||
@@
|
||||
(
|
||||
- memmove(dst, src, (n) * sizeof(*dst));
|
||||
+ MOVE_ARRAY(dst, src, n);
|
||||
|
|
||||
- memmove(dst, src, (n) * sizeof(*src));
|
||||
+ MOVE_ARRAY(dst, src, n);
|
||||
|
|
||||
- memmove(dst, src, (n) * sizeof(T));
|
||||
+ MOVE_ARRAY(dst, src, n);
|
||||
)
|
||||
|
||||
@@
|
||||
type T;
|
||||
T *ptr;
|
||||
expression n;
|
||||
@@
|
||||
- ptr = xmalloc((n) * sizeof(*ptr));
|
||||
+ ALLOC_ARRAY(ptr, n);
|
||||
|
||||
@@
|
||||
type T;
|
||||
T *ptr;
|
||||
expression n;
|
||||
@@
|
||||
- ptr = xmalloc((n) * sizeof(T));
|
||||
+ ALLOC_ARRAY(ptr, n);
|
||||
34
third_party/git/contrib/coccinelle/commit.cocci
vendored
Normal file
34
third_party/git/contrib/coccinelle/commit.cocci
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
@@
|
||||
expression c;
|
||||
@@
|
||||
- &c->maybe_tree->object.oid
|
||||
+ get_commit_tree_oid(c)
|
||||
|
||||
@@
|
||||
expression c;
|
||||
@@
|
||||
- c->maybe_tree->object.oid.hash
|
||||
+ get_commit_tree_oid(c)->hash
|
||||
|
||||
@@
|
||||
identifier f !~ "^set_commit_tree$";
|
||||
expression c;
|
||||
expression s;
|
||||
@@
|
||||
f(...) {<...
|
||||
- c->maybe_tree = s
|
||||
+ set_commit_tree(c, s)
|
||||
...>}
|
||||
|
||||
// These excluded functions must access c->maybe_tree direcly.
|
||||
// Note that if c->maybe_tree is written somewhere outside of these
|
||||
// functions, then the recommended transformation will be bogus with
|
||||
// repo_get_commit_tree() on the LHS.
|
||||
@@
|
||||
identifier f !~ "^(repo_get_commit_tree|get_commit_tree_in_graph_one|load_tree_for_commit|set_commit_tree)$";
|
||||
expression c;
|
||||
@@
|
||||
f(...) {<...
|
||||
- c->maybe_tree
|
||||
+ repo_get_commit_tree(specify_the_right_repo_here, c)
|
||||
...>}
|
||||
13
third_party/git/contrib/coccinelle/flex_alloc.cocci
vendored
Normal file
13
third_party/git/contrib/coccinelle/flex_alloc.cocci
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
@@
|
||||
expression str;
|
||||
identifier x, flexname;
|
||||
@@
|
||||
- FLEX_ALLOC_MEM(x, flexname, str, strlen(str));
|
||||
+ FLEX_ALLOC_STR(x, flexname, str);
|
||||
|
||||
@@
|
||||
expression str;
|
||||
identifier x, ptrname;
|
||||
@@
|
||||
- FLEXPTR_ALLOC_MEM(x, ptrname, str, strlen(str));
|
||||
+ FLEXPTR_ALLOC_STR(x, ptrname, str);
|
||||
18
third_party/git/contrib/coccinelle/free.cocci
vendored
Normal file
18
third_party/git/contrib/coccinelle/free.cocci
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
@@
|
||||
expression E;
|
||||
@@
|
||||
- if (E)
|
||||
free(E);
|
||||
|
||||
@@
|
||||
expression E;
|
||||
@@
|
||||
- if (!E)
|
||||
free(E);
|
||||
|
||||
@@
|
||||
expression E;
|
||||
@@
|
||||
- free(E);
|
||||
+ FREE_AND_NULL(E);
|
||||
- E = NULL;
|
||||
119
third_party/git/contrib/coccinelle/object_id.cocci
vendored
Normal file
119
third_party/git/contrib/coccinelle/object_id.cocci
vendored
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
@@
|
||||
struct object_id OID;
|
||||
@@
|
||||
- is_null_sha1(OID.hash)
|
||||
+ is_null_oid(&OID)
|
||||
|
||||
@@
|
||||
struct object_id *OIDPTR;
|
||||
@@
|
||||
- is_null_sha1(OIDPTR->hash)
|
||||
+ is_null_oid(OIDPTR)
|
||||
|
||||
@@
|
||||
struct object_id OID;
|
||||
@@
|
||||
- sha1_to_hex(OID.hash)
|
||||
+ oid_to_hex(&OID)
|
||||
|
||||
@@
|
||||
identifier f != oid_to_hex;
|
||||
struct object_id *OIDPTR;
|
||||
@@
|
||||
f(...) {<...
|
||||
- sha1_to_hex(OIDPTR->hash)
|
||||
+ oid_to_hex(OIDPTR)
|
||||
...>}
|
||||
|
||||
@@
|
||||
expression E;
|
||||
struct object_id OID;
|
||||
@@
|
||||
- sha1_to_hex_r(E, OID.hash)
|
||||
+ oid_to_hex_r(E, &OID)
|
||||
|
||||
@@
|
||||
identifier f != oid_to_hex_r;
|
||||
expression E;
|
||||
struct object_id *OIDPTR;
|
||||
@@
|
||||
f(...) {<...
|
||||
- sha1_to_hex_r(E, OIDPTR->hash)
|
||||
+ oid_to_hex_r(E, OIDPTR)
|
||||
...>}
|
||||
|
||||
@@
|
||||
struct object_id OID;
|
||||
@@
|
||||
- hashclr(OID.hash)
|
||||
+ oidclr(&OID)
|
||||
|
||||
@@
|
||||
identifier f != oidclr;
|
||||
struct object_id *OIDPTR;
|
||||
@@
|
||||
f(...) {<...
|
||||
- hashclr(OIDPTR->hash)
|
||||
+ oidclr(OIDPTR)
|
||||
...>}
|
||||
|
||||
@@
|
||||
struct object_id OID1, OID2;
|
||||
@@
|
||||
- hashcmp(OID1.hash, OID2.hash)
|
||||
+ oidcmp(&OID1, &OID2)
|
||||
|
||||
@@
|
||||
identifier f != oidcmp;
|
||||
struct object_id *OIDPTR1, OIDPTR2;
|
||||
@@
|
||||
f(...) {<...
|
||||
- hashcmp(OIDPTR1->hash, OIDPTR2->hash)
|
||||
+ oidcmp(OIDPTR1, OIDPTR2)
|
||||
...>}
|
||||
|
||||
@@
|
||||
struct object_id *OIDPTR;
|
||||
struct object_id OID;
|
||||
@@
|
||||
- hashcmp(OIDPTR->hash, OID.hash)
|
||||
+ oidcmp(OIDPTR, &OID)
|
||||
|
||||
@@
|
||||
struct object_id *OIDPTR;
|
||||
struct object_id OID;
|
||||
@@
|
||||
- hashcmp(OID.hash, OIDPTR->hash)
|
||||
+ oidcmp(&OID, OIDPTR)
|
||||
|
||||
@@
|
||||
struct object_id *OIDPTR1;
|
||||
struct object_id *OIDPTR2;
|
||||
@@
|
||||
- oidcmp(OIDPTR1, OIDPTR2) == 0
|
||||
+ oideq(OIDPTR1, OIDPTR2)
|
||||
|
||||
@@
|
||||
identifier f != hasheq;
|
||||
expression E1, E2;
|
||||
@@
|
||||
f(...) {<...
|
||||
- hashcmp(E1, E2) == 0
|
||||
+ hasheq(E1, E2)
|
||||
...>}
|
||||
|
||||
@@
|
||||
struct object_id *OIDPTR1;
|
||||
struct object_id *OIDPTR2;
|
||||
@@
|
||||
- oidcmp(OIDPTR1, OIDPTR2) != 0
|
||||
+ !oideq(OIDPTR1, OIDPTR2)
|
||||
|
||||
@@
|
||||
identifier f != hasheq;
|
||||
expression E1, E2;
|
||||
@@
|
||||
f(...) {<...
|
||||
- hashcmp(E1, E2) != 0
|
||||
+ !hasheq(E1, E2)
|
||||
...>}
|
||||
5
third_party/git/contrib/coccinelle/preincr.cocci
vendored
Normal file
5
third_party/git/contrib/coccinelle/preincr.cocci
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
@ preincrement @
|
||||
identifier i;
|
||||
@@
|
||||
- ++i > 1
|
||||
+ i++
|
||||
37
third_party/git/contrib/coccinelle/qsort.cocci
vendored
Normal file
37
third_party/git/contrib/coccinelle/qsort.cocci
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
@@
|
||||
expression base, nmemb, compar;
|
||||
@@
|
||||
- qsort(base, nmemb, sizeof(*base), compar);
|
||||
+ QSORT(base, nmemb, compar);
|
||||
|
||||
@@
|
||||
expression base, nmemb, compar;
|
||||
@@
|
||||
- qsort(base, nmemb, sizeof(base[0]), compar);
|
||||
+ QSORT(base, nmemb, compar);
|
||||
|
||||
@@
|
||||
type T;
|
||||
T *base;
|
||||
expression nmemb, compar;
|
||||
@@
|
||||
- qsort(base, nmemb, sizeof(T), compar);
|
||||
+ QSORT(base, nmemb, compar);
|
||||
|
||||
@@
|
||||
expression base, nmemb, compar;
|
||||
@@
|
||||
- if (nmemb)
|
||||
QSORT(base, nmemb, compar);
|
||||
|
||||
@@
|
||||
expression base, nmemb, compar;
|
||||
@@
|
||||
- if (nmemb > 0)
|
||||
QSORT(base, nmemb, compar);
|
||||
|
||||
@@
|
||||
expression base, nmemb, compar;
|
||||
@@
|
||||
- if (nmemb > 1)
|
||||
QSORT(base, nmemb, compar);
|
||||
62
third_party/git/contrib/coccinelle/strbuf.cocci
vendored
Normal file
62
third_party/git/contrib/coccinelle/strbuf.cocci
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
@ strbuf_addf_with_format_only @
|
||||
expression E;
|
||||
constant fmt !~ "%";
|
||||
@@
|
||||
- strbuf_addf
|
||||
+ strbuf_addstr
|
||||
(E,
|
||||
(
|
||||
fmt
|
||||
|
|
||||
_(fmt)
|
||||
)
|
||||
);
|
||||
|
||||
@@
|
||||
expression E;
|
||||
struct strbuf SB;
|
||||
format F =~ "s";
|
||||
@@
|
||||
- strbuf_addf(E, "%@F@", SB.buf);
|
||||
+ strbuf_addbuf(E, &SB);
|
||||
|
||||
@@
|
||||
expression E;
|
||||
struct strbuf *SBP;
|
||||
format F =~ "s";
|
||||
@@
|
||||
- strbuf_addf(E, "%@F@", SBP->buf);
|
||||
+ strbuf_addbuf(E, SBP);
|
||||
|
||||
@@
|
||||
expression E;
|
||||
struct strbuf SB;
|
||||
@@
|
||||
- strbuf_addstr(E, SB.buf);
|
||||
+ strbuf_addbuf(E, &SB);
|
||||
|
||||
@@
|
||||
expression E;
|
||||
struct strbuf *SBP;
|
||||
@@
|
||||
- strbuf_addstr(E, SBP->buf);
|
||||
+ strbuf_addbuf(E, SBP);
|
||||
|
||||
@@
|
||||
expression E1, E2;
|
||||
format F =~ "s";
|
||||
@@
|
||||
- strbuf_addf(E1, "%@F@", E2);
|
||||
+ strbuf_addstr(E1, E2);
|
||||
|
||||
@@
|
||||
expression E1, E2, E3;
|
||||
@@
|
||||
- strbuf_addstr(E1, find_unique_abbrev(E2, E3));
|
||||
+ strbuf_add_unique_abbrev(E1, E2, E3);
|
||||
|
||||
@@
|
||||
expression E1, E2;
|
||||
@@
|
||||
- strbuf_addstr(E1, real_path(E2));
|
||||
+ strbuf_add_real_path(E1, E2);
|
||||
28
third_party/git/contrib/coccinelle/swap.cocci
vendored
Normal file
28
third_party/git/contrib/coccinelle/swap.cocci
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
@ swap_with_declaration @
|
||||
type T;
|
||||
identifier tmp;
|
||||
T a, b;
|
||||
@@
|
||||
- T tmp = a;
|
||||
+ T tmp;
|
||||
+ tmp = a;
|
||||
a = b;
|
||||
b = tmp;
|
||||
|
||||
@ swap @
|
||||
type T;
|
||||
T tmp, a, b;
|
||||
@@
|
||||
- tmp = a;
|
||||
- a = b;
|
||||
- b = tmp;
|
||||
+ SWAP(a, b);
|
||||
|
||||
@ extends swap @
|
||||
identifier unused;
|
||||
@@
|
||||
{
|
||||
...
|
||||
- T unused;
|
||||
... when != unused
|
||||
}
|
||||
144
third_party/git/contrib/coccinelle/the_repository.pending.cocci
vendored
Normal file
144
third_party/git/contrib/coccinelle/the_repository.pending.cocci
vendored
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
// This file is used for the ongoing refactoring of
|
||||
// bringing the index or repository struct in all of
|
||||
// our code base.
|
||||
|
||||
@@
|
||||
expression E;
|
||||
expression F;
|
||||
expression G;
|
||||
@@
|
||||
- read_object_file(
|
||||
+ repo_read_object_file(the_repository,
|
||||
E, F, G)
|
||||
|
||||
@@
|
||||
expression E;
|
||||
@@
|
||||
- has_sha1_file(
|
||||
+ repo_has_sha1_file(the_repository,
|
||||
E)
|
||||
|
||||
@@
|
||||
expression E;
|
||||
expression F;
|
||||
@@
|
||||
- has_sha1_file_with_flags(
|
||||
+ repo_has_sha1_file_with_flags(the_repository,
|
||||
E)
|
||||
|
||||
@@
|
||||
expression E;
|
||||
@@
|
||||
- has_object_file(
|
||||
+ repo_has_object_file(the_repository,
|
||||
E)
|
||||
|
||||
@@
|
||||
expression E;
|
||||
expression F;
|
||||
@@
|
||||
- has_object_file_with_flags(
|
||||
+ repo_has_object_file_with_flags(the_repository,
|
||||
E)
|
||||
|
||||
@@
|
||||
expression E;
|
||||
expression F;
|
||||
expression G;
|
||||
@@
|
||||
- parse_commit_internal(
|
||||
+ repo_parse_commit_internal(the_repository,
|
||||
E, F, G)
|
||||
|
||||
@@
|
||||
expression E;
|
||||
expression F;
|
||||
@@
|
||||
- parse_commit_gently(
|
||||
+ repo_parse_commit_gently(the_repository,
|
||||
E, F)
|
||||
|
||||
@@
|
||||
expression E;
|
||||
@@
|
||||
- parse_commit(
|
||||
+ repo_parse_commit(the_repository,
|
||||
E)
|
||||
|
||||
@@
|
||||
expression E;
|
||||
expression F;
|
||||
@@
|
||||
- get_merge_bases(
|
||||
+ repo_get_merge_bases(the_repository,
|
||||
E, F);
|
||||
|
||||
@@
|
||||
expression E;
|
||||
expression F;
|
||||
expression G;
|
||||
@@
|
||||
- get_merge_bases_many(
|
||||
+ repo_get_merge_bases_many(the_repository,
|
||||
E, F, G);
|
||||
|
||||
@@
|
||||
expression E;
|
||||
expression F;
|
||||
expression G;
|
||||
@@
|
||||
- get_merge_bases_many_dirty(
|
||||
+ repo_get_merge_bases_many_dirty(the_repository,
|
||||
E, F, G);
|
||||
|
||||
@@
|
||||
expression E;
|
||||
expression F;
|
||||
@@
|
||||
- in_merge_bases(
|
||||
+ repo_in_merge_bases(the_repository,
|
||||
E, F);
|
||||
|
||||
@@
|
||||
expression E;
|
||||
expression F;
|
||||
expression G;
|
||||
@@
|
||||
- in_merge_bases_many(
|
||||
+ repo_in_merge_bases_many(the_repository,
|
||||
E, F, G);
|
||||
|
||||
@@
|
||||
expression E;
|
||||
expression F;
|
||||
@@
|
||||
- get_commit_buffer(
|
||||
+ repo_get_commit_buffer(the_repository,
|
||||
E, F);
|
||||
|
||||
@@
|
||||
expression E;
|
||||
expression F;
|
||||
@@
|
||||
- unuse_commit_buffer(
|
||||
+ repo_unuse_commit_buffer(the_repository,
|
||||
E, F);
|
||||
|
||||
@@
|
||||
expression E;
|
||||
expression F;
|
||||
expression G;
|
||||
@@
|
||||
- logmsg_reencode(
|
||||
+ repo_logmsg_reencode(the_repository,
|
||||
E, F, G);
|
||||
|
||||
@@
|
||||
expression E;
|
||||
expression F;
|
||||
expression G;
|
||||
expression H;
|
||||
@@
|
||||
- format_commit_message(
|
||||
+ repo_format_commit_message(the_repository,
|
||||
E, F, G, H);
|
||||
13
third_party/git/contrib/coccinelle/xstrdup_or_null.cocci
vendored
Normal file
13
third_party/git/contrib/coccinelle/xstrdup_or_null.cocci
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
@@
|
||||
expression E;
|
||||
expression V;
|
||||
@@
|
||||
- if (E)
|
||||
- V = xstrdup(E);
|
||||
+ V = xstrdup_or_null(E);
|
||||
|
||||
@@
|
||||
expression E;
|
||||
@@
|
||||
- xstrdup(absolute_path(E))
|
||||
+ absolute_pathdup(E)
|
||||
1
third_party/git/contrib/completion/.gitattributes
vendored
Normal file
1
third_party/git/contrib/completion/.gitattributes
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
*.bash eol=lf
|
||||
3151
third_party/git/contrib/completion/git-completion.bash
vendored
Normal file
3151
third_party/git/contrib/completion/git-completion.bash
vendored
Normal file
File diff suppressed because it is too large
Load diff
126
third_party/git/contrib/completion/git-completion.tcsh
vendored
Normal file
126
third_party/git/contrib/completion/git-completion.tcsh
vendored
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
# tcsh completion support for core Git.
|
||||
#
|
||||
# Copyright (C) 2012 Marc Khouzam <marc.khouzam@gmail.com>
|
||||
# Distributed under the GNU General Public License, version 2.0.
|
||||
#
|
||||
# When sourced, this script will generate a new script that uses
|
||||
# the git-completion.bash script provided by core Git. This new
|
||||
# script can be used by tcsh to perform git completion.
|
||||
# The current script also issues the necessary tcsh 'complete'
|
||||
# commands.
|
||||
#
|
||||
# To use this completion script:
|
||||
#
|
||||
# 0) You need tcsh 6.16.00 or newer.
|
||||
# 1) Copy both this file and the bash completion script to ${HOME}.
|
||||
# You _must_ use the name ${HOME}/.git-completion.bash for the
|
||||
# bash script.
|
||||
# (e.g. ~/.git-completion.tcsh and ~/.git-completion.bash).
|
||||
# 2) Add the following line to your .tcshrc/.cshrc:
|
||||
# source ~/.git-completion.tcsh
|
||||
# 3) For completion similar to bash, it is recommended to also
|
||||
# add the following line to your .tcshrc/.cshrc:
|
||||
# set autolist=ambiguous
|
||||
# It will tell tcsh to list the possible completion choices.
|
||||
|
||||
set __git_tcsh_completion_version = `\echo ${tcsh} | \sed 's/\./ /g'`
|
||||
if ( ${__git_tcsh_completion_version[1]} < 6 || \
|
||||
( ${__git_tcsh_completion_version[1]} == 6 && \
|
||||
${__git_tcsh_completion_version[2]} < 16 ) ) then
|
||||
echo "git-completion.tcsh: Your version of tcsh is too old, you need version 6.16.00 or newer. Git completion will not work."
|
||||
exit
|
||||
endif
|
||||
unset __git_tcsh_completion_version
|
||||
|
||||
set __git_tcsh_completion_original_script = ${HOME}/.git-completion.bash
|
||||
set __git_tcsh_completion_script = ${HOME}/.git-completion.tcsh.bash
|
||||
|
||||
# Check that the user put the script in the right place
|
||||
if ( ! -e ${__git_tcsh_completion_original_script} ) then
|
||||
echo "git-completion.tcsh: Cannot find: ${__git_tcsh_completion_original_script}. Git completion will not work."
|
||||
exit
|
||||
endif
|
||||
|
||||
cat << EOF >! ${__git_tcsh_completion_script}
|
||||
#!bash
|
||||
#
|
||||
# This script is GENERATED and will be overwritten automatically.
|
||||
# Do not modify it directly. Instead, modify git-completion.tcsh
|
||||
# and source it again.
|
||||
|
||||
source ${__git_tcsh_completion_original_script}
|
||||
|
||||
# Remove the colon as a completion separator because tcsh cannot handle it
|
||||
COMP_WORDBREAKS=\${COMP_WORDBREAKS//:}
|
||||
|
||||
# For file completion, tcsh needs the '/' to be appended to directories.
|
||||
# By default, the bash script does not do that.
|
||||
# We can achieve this by using the below compatibility
|
||||
# method of the git-completion.bash script.
|
||||
__git_index_file_list_filter ()
|
||||
{
|
||||
__git_index_file_list_filter_compat
|
||||
}
|
||||
|
||||
# Set COMP_WORDS in a way that can be handled by the bash script.
|
||||
COMP_WORDS=(\$2)
|
||||
|
||||
# The cursor is at the end of parameter #1.
|
||||
# We must check for a space as the last character which will
|
||||
# tell us that the previous word is complete and the cursor
|
||||
# is on the next word.
|
||||
if [ "\${2: -1}" == " " ]; then
|
||||
# The last character is a space, so our location is at the end
|
||||
# of the command-line array
|
||||
COMP_CWORD=\${#COMP_WORDS[@]}
|
||||
else
|
||||
# The last character is not a space, so our location is on the
|
||||
# last word of the command-line array, so we must decrement the
|
||||
# count by 1
|
||||
COMP_CWORD=\$((\${#COMP_WORDS[@]}-1))
|
||||
fi
|
||||
|
||||
# Call _git() or _gitk() of the bash script, based on the first argument
|
||||
_\${1}
|
||||
|
||||
IFS=\$'\n'
|
||||
if [ \${#COMPREPLY[*]} -eq 0 ]; then
|
||||
# No completions suggested. In this case, we want tcsh to perform
|
||||
# standard file completion. However, there does not seem to be way
|
||||
# to tell tcsh to do that. To help the user, we try to simulate
|
||||
# file completion directly in this script.
|
||||
#
|
||||
# Known issues:
|
||||
# - Possible completions are shown with their directory prefix.
|
||||
# - Completions containing shell variables are not handled.
|
||||
# - Completions with ~ as the first character are not handled.
|
||||
|
||||
# No file completion should be done unless we are completing beyond
|
||||
# the git sub-command. An improvement on the bash completion :)
|
||||
if [ \${COMP_CWORD} -gt 1 ]; then
|
||||
TO_COMPLETE="\${COMP_WORDS[\${COMP_CWORD}]}"
|
||||
|
||||
# We don't support ~ expansion: too tricky.
|
||||
if [ "\${TO_COMPLETE:0:1}" != "~" ]; then
|
||||
# Use ls so as to add the '/' at the end of directories.
|
||||
COMPREPLY=(\`ls -dp \${TO_COMPLETE}* 2> /dev/null\`)
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# tcsh does not automatically remove duplicates, so we do it ourselves
|
||||
echo "\${COMPREPLY[*]}" | sort | uniq
|
||||
|
||||
# If there is a single completion and it is a directory, we output it
|
||||
# a second time to trick tcsh into not adding a space after it.
|
||||
if [ \${#COMPREPLY[*]} -eq 1 ] && [ "\${COMPREPLY[0]: -1}" == "/" ]; then
|
||||
echo "\${COMPREPLY[*]}"
|
||||
fi
|
||||
|
||||
EOF
|
||||
|
||||
# Don't need this variable anymore, so don't pollute the users environment
|
||||
unset __git_tcsh_completion_original_script
|
||||
|
||||
complete git 'p,*,`bash ${__git_tcsh_completion_script} git "${COMMAND_LINE}"`,'
|
||||
complete gitk 'p,*,`bash ${__git_tcsh_completion_script} gitk "${COMMAND_LINE}"`,'
|
||||
243
third_party/git/contrib/completion/git-completion.zsh
vendored
Normal file
243
third_party/git/contrib/completion/git-completion.zsh
vendored
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
#compdef git gitk
|
||||
|
||||
# zsh completion wrapper for git
|
||||
#
|
||||
# Copyright (c) 2012-2013 Felipe Contreras <felipe.contreras@gmail.com>
|
||||
#
|
||||
# You need git's bash completion script installed somewhere, by default it
|
||||
# would be the location bash-completion uses.
|
||||
#
|
||||
# If your script is somewhere else, you can configure it on your ~/.zshrc:
|
||||
#
|
||||
# zstyle ':completion:*:*:git:*' script ~/.git-completion.zsh
|
||||
#
|
||||
# The recommended way to install this script is to copy to '~/.zsh/_git', and
|
||||
# then add the following to your ~/.zshrc file:
|
||||
#
|
||||
# fpath=(~/.zsh $fpath)
|
||||
|
||||
complete ()
|
||||
{
|
||||
# do nothing
|
||||
return 0
|
||||
}
|
||||
|
||||
zstyle -T ':completion:*:*:git:*' tag-order && \
|
||||
zstyle ':completion:*:*:git:*' tag-order 'common-commands'
|
||||
|
||||
zstyle -s ":completion:*:*:git:*" script script
|
||||
if [ -z "$script" ]; then
|
||||
local -a locations
|
||||
local e
|
||||
locations=(
|
||||
$(dirname ${funcsourcetrace[1]%:*})/git-completion.bash
|
||||
'/etc/bash_completion.d/git' # fedora, old debian
|
||||
'/usr/share/bash-completion/completions/git' # arch, ubuntu, new debian
|
||||
'/usr/share/bash-completion/git' # gentoo
|
||||
)
|
||||
for e in $locations; do
|
||||
test -f $e && script="$e" && break
|
||||
done
|
||||
fi
|
||||
GIT_SOURCING_ZSH_COMPLETION=y . "$script"
|
||||
|
||||
__gitcomp ()
|
||||
{
|
||||
emulate -L zsh
|
||||
|
||||
local cur_="${3-$cur}"
|
||||
|
||||
case "$cur_" in
|
||||
--*=)
|
||||
;;
|
||||
*)
|
||||
local c IFS=$' \t\n'
|
||||
local -a array
|
||||
for c in ${=1}; do
|
||||
c="$c${4-}"
|
||||
case $c in
|
||||
--*=*|*.) ;;
|
||||
*) c="$c " ;;
|
||||
esac
|
||||
array+=("$c")
|
||||
done
|
||||
compset -P '*[=:]'
|
||||
compadd -Q -S '' -p "${2-}" -a -- array && _ret=0
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
__gitcomp_direct ()
|
||||
{
|
||||
emulate -L zsh
|
||||
|
||||
local IFS=$'\n'
|
||||
compset -P '*[=:]'
|
||||
compadd -Q -- ${=1} && _ret=0
|
||||
}
|
||||
|
||||
__gitcomp_nl ()
|
||||
{
|
||||
emulate -L zsh
|
||||
|
||||
local IFS=$'\n'
|
||||
compset -P '*[=:]'
|
||||
compadd -Q -S "${4- }" -p "${2-}" -- ${=1} && _ret=0
|
||||
}
|
||||
|
||||
__gitcomp_nl_append ()
|
||||
{
|
||||
emulate -L zsh
|
||||
|
||||
local IFS=$'\n'
|
||||
compadd -Q -S "${4- }" -p "${2-}" -- ${=1} && _ret=0
|
||||
}
|
||||
|
||||
__gitcomp_file_direct ()
|
||||
{
|
||||
emulate -L zsh
|
||||
|
||||
local IFS=$'\n'
|
||||
compset -P '*[=:]'
|
||||
compadd -f -- ${=1} && _ret=0
|
||||
}
|
||||
|
||||
__gitcomp_file ()
|
||||
{
|
||||
emulate -L zsh
|
||||
|
||||
local IFS=$'\n'
|
||||
compset -P '*[=:]'
|
||||
compadd -p "${2-}" -f -- ${=1} && _ret=0
|
||||
}
|
||||
|
||||
__git_zsh_bash_func ()
|
||||
{
|
||||
emulate -L ksh
|
||||
|
||||
local command=$1
|
||||
|
||||
local completion_func="_git_${command//-/_}"
|
||||
declare -f $completion_func >/dev/null && $completion_func && return
|
||||
|
||||
local expansion=$(__git_aliased_command "$command")
|
||||
if [ -n "$expansion" ]; then
|
||||
words[1]=$expansion
|
||||
completion_func="_git_${expansion//-/_}"
|
||||
declare -f $completion_func >/dev/null && $completion_func
|
||||
fi
|
||||
}
|
||||
|
||||
__git_zsh_cmd_common ()
|
||||
{
|
||||
local -a list
|
||||
list=(
|
||||
add:'add file contents to the index'
|
||||
bisect:'find by binary search the change that introduced a bug'
|
||||
branch:'list, create, or delete branches'
|
||||
checkout:'checkout a branch or paths to the working tree'
|
||||
clone:'clone a repository into a new directory'
|
||||
commit:'record changes to the repository'
|
||||
diff:'show changes between commits, commit and working tree, etc'
|
||||
fetch:'download objects and refs from another repository'
|
||||
grep:'print lines matching a pattern'
|
||||
init:'create an empty Git repository or reinitialize an existing one'
|
||||
log:'show commit logs'
|
||||
merge:'join two or more development histories together'
|
||||
mv:'move or rename a file, a directory, or a symlink'
|
||||
pull:'fetch from and merge with another repository or a local branch'
|
||||
push:'update remote refs along with associated objects'
|
||||
rebase:'forward-port local commits to the updated upstream head'
|
||||
reset:'reset current HEAD to the specified state'
|
||||
rm:'remove files from the working tree and from the index'
|
||||
show:'show various types of objects'
|
||||
status:'show the working tree status'
|
||||
tag:'create, list, delete or verify a tag object signed with GPG')
|
||||
_describe -t common-commands 'common commands' list && _ret=0
|
||||
}
|
||||
|
||||
__git_zsh_cmd_alias ()
|
||||
{
|
||||
local -a list
|
||||
list=(${${${(0)"$(git config -z --get-regexp '^alias\.')"}#alias.}%$'\n'*})
|
||||
_describe -t alias-commands 'aliases' list $* && _ret=0
|
||||
}
|
||||
|
||||
__git_zsh_cmd_all ()
|
||||
{
|
||||
local -a list
|
||||
emulate ksh -c __git_compute_all_commands
|
||||
list=( ${=__git_all_commands} )
|
||||
_describe -t all-commands 'all commands' list && _ret=0
|
||||
}
|
||||
|
||||
__git_zsh_main ()
|
||||
{
|
||||
local curcontext="$curcontext" state state_descr line
|
||||
typeset -A opt_args
|
||||
local -a orig_words
|
||||
|
||||
orig_words=( ${words[@]} )
|
||||
|
||||
_arguments -C \
|
||||
'(-p --paginate --no-pager)'{-p,--paginate}'[pipe all output into ''less'']' \
|
||||
'(-p --paginate)--no-pager[do not pipe git output into a pager]' \
|
||||
'--git-dir=-[set the path to the repository]: :_directories' \
|
||||
'--bare[treat the repository as a bare repository]' \
|
||||
'(- :)--version[prints the git suite version]' \
|
||||
'--exec-path=-[path to where your core git programs are installed]:: :_directories' \
|
||||
'--html-path[print the path where git''s HTML documentation is installed]' \
|
||||
'--info-path[print the path where the Info files are installed]' \
|
||||
'--man-path[print the manpath (see `man(1)`) for the man pages]' \
|
||||
'--work-tree=-[set the path to the working tree]: :_directories' \
|
||||
'--namespace=-[set the git namespace]' \
|
||||
'--no-replace-objects[do not use replacement refs to replace git objects]' \
|
||||
'(- :)--help[prints the synopsis and a list of the most commonly used commands]: :->arg' \
|
||||
'(-): :->command' \
|
||||
'(-)*:: :->arg' && return
|
||||
|
||||
case $state in
|
||||
(command)
|
||||
_alternative \
|
||||
'alias-commands:alias:__git_zsh_cmd_alias' \
|
||||
'common-commands:common:__git_zsh_cmd_common' \
|
||||
'all-commands:all:__git_zsh_cmd_all' && _ret=0
|
||||
;;
|
||||
(arg)
|
||||
local command="${words[1]}" __git_dir
|
||||
|
||||
if (( $+opt_args[--bare] )); then
|
||||
__git_dir='.'
|
||||
else
|
||||
__git_dir=${opt_args[--git-dir]}
|
||||
fi
|
||||
|
||||
(( $+opt_args[--help] )) && command='help'
|
||||
|
||||
words=( ${orig_words[@]} )
|
||||
|
||||
__git_zsh_bash_func $command
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_git ()
|
||||
{
|
||||
local _ret=1
|
||||
local cur cword prev
|
||||
|
||||
cur=${words[CURRENT]}
|
||||
prev=${words[CURRENT-1]}
|
||||
let cword=CURRENT-1
|
||||
|
||||
if (( $+functions[__${service}_zsh_main] )); then
|
||||
__${service}_zsh_main
|
||||
else
|
||||
emulate ksh -c __${service}_main
|
||||
fi
|
||||
|
||||
let _ret && _default && _ret=0
|
||||
return _ret
|
||||
}
|
||||
|
||||
_git
|
||||
564
third_party/git/contrib/completion/git-prompt.sh
vendored
Normal file
564
third_party/git/contrib/completion/git-prompt.sh
vendored
Normal file
|
|
@ -0,0 +1,564 @@
|
|||
# bash/zsh git prompt support
|
||||
#
|
||||
# Copyright (C) 2006,2007 Shawn O. Pearce <spearce@spearce.org>
|
||||
# Distributed under the GNU General Public License, version 2.0.
|
||||
#
|
||||
# This script allows you to see repository status in your prompt.
|
||||
#
|
||||
# To enable:
|
||||
#
|
||||
# 1) Copy this file to somewhere (e.g. ~/.git-prompt.sh).
|
||||
# 2) Add the following line to your .bashrc/.zshrc:
|
||||
# source ~/.git-prompt.sh
|
||||
# 3a) Change your PS1 to call __git_ps1 as
|
||||
# command-substitution:
|
||||
# Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
|
||||
# ZSH: setopt PROMPT_SUBST ; PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ '
|
||||
# the optional argument will be used as format string.
|
||||
# 3b) Alternatively, for a slightly faster prompt, __git_ps1 can
|
||||
# be used for PROMPT_COMMAND in Bash or for precmd() in Zsh
|
||||
# with two parameters, <pre> and <post>, which are strings
|
||||
# you would put in $PS1 before and after the status string
|
||||
# generated by the git-prompt machinery. e.g.
|
||||
# Bash: PROMPT_COMMAND='__git_ps1 "\u@\h:\w" "\\\$ "'
|
||||
# will show username, at-sign, host, colon, cwd, then
|
||||
# various status string, followed by dollar and SP, as
|
||||
# your prompt.
|
||||
# ZSH: precmd () { __git_ps1 "%n" ":%~$ " "|%s" }
|
||||
# will show username, pipe, then various status string,
|
||||
# followed by colon, cwd, dollar and SP, as your prompt.
|
||||
# Optionally, you can supply a third argument with a printf
|
||||
# format string to finetune the output of the branch status
|
||||
#
|
||||
# The repository status will be displayed only if you are currently in a
|
||||
# git repository. The %s token is the placeholder for the shown status.
|
||||
#
|
||||
# The prompt status always includes the current branch name.
|
||||
#
|
||||
# In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty value,
|
||||
# unstaged (*) and staged (+) changes will be shown next to the branch
|
||||
# name. You can configure this per-repository with the
|
||||
# bash.showDirtyState variable, which defaults to true once
|
||||
# GIT_PS1_SHOWDIRTYSTATE is enabled.
|
||||
#
|
||||
# You can also see if currently something is stashed, by setting
|
||||
# GIT_PS1_SHOWSTASHSTATE to a nonempty value. If something is stashed,
|
||||
# then a '$' will be shown next to the branch name.
|
||||
#
|
||||
# If you would like to see if there're untracked files, then you can set
|
||||
# GIT_PS1_SHOWUNTRACKEDFILES to a nonempty value. If there're untracked
|
||||
# files, then a '%' will be shown next to the branch name. You can
|
||||
# configure this per-repository with the bash.showUntrackedFiles
|
||||
# variable, which defaults to true once GIT_PS1_SHOWUNTRACKEDFILES is
|
||||
# enabled.
|
||||
#
|
||||
# If you would like to see the difference between HEAD and its upstream,
|
||||
# set GIT_PS1_SHOWUPSTREAM="auto". A "<" indicates you are behind, ">"
|
||||
# indicates you are ahead, "<>" indicates you have diverged and "="
|
||||
# indicates that there is no difference. You can further control
|
||||
# behaviour by setting GIT_PS1_SHOWUPSTREAM to a space-separated list
|
||||
# of values:
|
||||
#
|
||||
# verbose show number of commits ahead/behind (+/-) upstream
|
||||
# name if verbose, then also show the upstream abbrev name
|
||||
# legacy don't use the '--count' option available in recent
|
||||
# versions of git-rev-list
|
||||
# git always compare HEAD to @{upstream}
|
||||
# svn always compare HEAD to your SVN upstream
|
||||
#
|
||||
# You can change the separator between the branch name and the above
|
||||
# state symbols by setting GIT_PS1_STATESEPARATOR. The default separator
|
||||
# is SP.
|
||||
#
|
||||
# By default, __git_ps1 will compare HEAD to your SVN upstream if it can
|
||||
# find one, or @{upstream} otherwise. Once you have set
|
||||
# GIT_PS1_SHOWUPSTREAM, you can override it on a per-repository basis by
|
||||
# setting the bash.showUpstream config variable.
|
||||
#
|
||||
# If you would like to see more information about the identity of
|
||||
# commits checked out as a detached HEAD, set GIT_PS1_DESCRIBE_STYLE
|
||||
# to one of these values:
|
||||
#
|
||||
# contains relative to newer annotated tag (v1.6.3.2~35)
|
||||
# branch relative to newer tag or branch (master~4)
|
||||
# describe relative to older annotated tag (v1.6.3.1-13-gdd42c2f)
|
||||
# tag relative to any older tag (v1.6.3.1-13-gdd42c2f)
|
||||
# default exactly matching tag
|
||||
#
|
||||
# If you would like a colored hint about the current dirty state, set
|
||||
# GIT_PS1_SHOWCOLORHINTS to a nonempty value. The colors are based on
|
||||
# the colored output of "git status -sb" and are available only when
|
||||
# using __git_ps1 for PROMPT_COMMAND or precmd.
|
||||
#
|
||||
# If you would like __git_ps1 to do nothing in the case when the current
|
||||
# directory is set up to be ignored by git, then set
|
||||
# GIT_PS1_HIDE_IF_PWD_IGNORED to a nonempty value. Override this on the
|
||||
# repository level by setting bash.hideIfPwdIgnored to "false".
|
||||
|
||||
# check whether printf supports -v
|
||||
__git_printf_supports_v=
|
||||
printf -v __git_printf_supports_v -- '%s' yes >/dev/null 2>&1
|
||||
|
||||
# stores the divergence from upstream in $p
|
||||
# used by GIT_PS1_SHOWUPSTREAM
|
||||
__git_ps1_show_upstream ()
|
||||
{
|
||||
local key value
|
||||
local svn_remote svn_url_pattern count n
|
||||
local upstream=git legacy="" verbose="" name=""
|
||||
|
||||
svn_remote=()
|
||||
# get some config options from git-config
|
||||
local output="$(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')"
|
||||
while read -r key value; do
|
||||
case "$key" in
|
||||
bash.showupstream)
|
||||
GIT_PS1_SHOWUPSTREAM="$value"
|
||||
if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then
|
||||
p=""
|
||||
return
|
||||
fi
|
||||
;;
|
||||
svn-remote.*.url)
|
||||
svn_remote[$((${#svn_remote[@]} + 1))]="$value"
|
||||
svn_url_pattern="$svn_url_pattern\\|$value"
|
||||
upstream=svn+git # default upstream is SVN if available, else git
|
||||
;;
|
||||
esac
|
||||
done <<< "$output"
|
||||
|
||||
# parse configuration values
|
||||
for option in ${GIT_PS1_SHOWUPSTREAM}; do
|
||||
case "$option" in
|
||||
git|svn) upstream="$option" ;;
|
||||
verbose) verbose=1 ;;
|
||||
legacy) legacy=1 ;;
|
||||
name) name=1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Find our upstream
|
||||
case "$upstream" in
|
||||
git) upstream="@{upstream}" ;;
|
||||
svn*)
|
||||
# get the upstream from the "git-svn-id: ..." in a commit message
|
||||
# (git-svn uses essentially the same procedure internally)
|
||||
local -a svn_upstream
|
||||
svn_upstream=($(git log --first-parent -1 \
|
||||
--grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null))
|
||||
if [[ 0 -ne ${#svn_upstream[@]} ]]; then
|
||||
svn_upstream=${svn_upstream[${#svn_upstream[@]} - 2]}
|
||||
svn_upstream=${svn_upstream%@*}
|
||||
local n_stop="${#svn_remote[@]}"
|
||||
for ((n=1; n <= n_stop; n++)); do
|
||||
svn_upstream=${svn_upstream#${svn_remote[$n]}}
|
||||
done
|
||||
|
||||
if [[ -z "$svn_upstream" ]]; then
|
||||
# default branch name for checkouts with no layout:
|
||||
upstream=${GIT_SVN_ID:-git-svn}
|
||||
else
|
||||
upstream=${svn_upstream#/}
|
||||
fi
|
||||
elif [[ "svn+git" = "$upstream" ]]; then
|
||||
upstream="@{upstream}"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# Find how many commits we are ahead/behind our upstream
|
||||
if [[ -z "$legacy" ]]; then
|
||||
count="$(git rev-list --count --left-right \
|
||||
"$upstream"...HEAD 2>/dev/null)"
|
||||
else
|
||||
# produce equivalent output to --count for older versions of git
|
||||
local commits
|
||||
if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)"
|
||||
then
|
||||
local commit behind=0 ahead=0
|
||||
for commit in $commits
|
||||
do
|
||||
case "$commit" in
|
||||
"<"*) ((behind++)) ;;
|
||||
*) ((ahead++)) ;;
|
||||
esac
|
||||
done
|
||||
count="$behind $ahead"
|
||||
else
|
||||
count=""
|
||||
fi
|
||||
fi
|
||||
|
||||
# calculate the result
|
||||
if [[ -z "$verbose" ]]; then
|
||||
case "$count" in
|
||||
"") # no upstream
|
||||
p="" ;;
|
||||
"0 0") # equal to upstream
|
||||
p="=" ;;
|
||||
"0 "*) # ahead of upstream
|
||||
p=">" ;;
|
||||
*" 0") # behind upstream
|
||||
p="<" ;;
|
||||
*) # diverged from upstream
|
||||
p="<>" ;;
|
||||
esac
|
||||
else
|
||||
case "$count" in
|
||||
"") # no upstream
|
||||
p="" ;;
|
||||
"0 0") # equal to upstream
|
||||
p=" u=" ;;
|
||||
"0 "*) # ahead of upstream
|
||||
p=" u+${count#0 }" ;;
|
||||
*" 0") # behind upstream
|
||||
p=" u-${count% 0}" ;;
|
||||
*) # diverged from upstream
|
||||
p=" u+${count#* }-${count% *}" ;;
|
||||
esac
|
||||
if [[ -n "$count" && -n "$name" ]]; then
|
||||
__git_ps1_upstream_name=$(git rev-parse \
|
||||
--abbrev-ref "$upstream" 2>/dev/null)
|
||||
if [ $pcmode = yes ] && [ $ps1_expanded = yes ]; then
|
||||
p="$p \${__git_ps1_upstream_name}"
|
||||
else
|
||||
p="$p ${__git_ps1_upstream_name}"
|
||||
# not needed anymore; keep user's
|
||||
# environment clean
|
||||
unset __git_ps1_upstream_name
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
# Helper function that is meant to be called from __git_ps1. It
|
||||
# injects color codes into the appropriate gitstring variables used
|
||||
# to build a gitstring.
|
||||
__git_ps1_colorize_gitstring ()
|
||||
{
|
||||
if [[ -n ${ZSH_VERSION-} ]]; then
|
||||
local c_red='%F{red}'
|
||||
local c_green='%F{green}'
|
||||
local c_lblue='%F{blue}'
|
||||
local c_clear='%f'
|
||||
else
|
||||
# Using \[ and \] around colors is necessary to prevent
|
||||
# issues with command line editing/browsing/completion!
|
||||
local c_red='\[\e[31m\]'
|
||||
local c_green='\[\e[32m\]'
|
||||
local c_lblue='\[\e[1;34m\]'
|
||||
local c_clear='\[\e[0m\]'
|
||||
fi
|
||||
local bad_color=$c_red
|
||||
local ok_color=$c_green
|
||||
local flags_color="$c_lblue"
|
||||
|
||||
local branch_color=""
|
||||
if [ $detached = no ]; then
|
||||
branch_color="$ok_color"
|
||||
else
|
||||
branch_color="$bad_color"
|
||||
fi
|
||||
c="$branch_color$c"
|
||||
|
||||
z="$c_clear$z"
|
||||
if [ "$w" = "*" ]; then
|
||||
w="$bad_color$w"
|
||||
fi
|
||||
if [ -n "$i" ]; then
|
||||
i="$ok_color$i"
|
||||
fi
|
||||
if [ -n "$s" ]; then
|
||||
s="$flags_color$s"
|
||||
fi
|
||||
if [ -n "$u" ]; then
|
||||
u="$bad_color$u"
|
||||
fi
|
||||
r="$c_clear$r"
|
||||
}
|
||||
|
||||
# Helper function to read the first line of a file into a variable.
|
||||
# __git_eread requires 2 arguments, the file path and the name of the
|
||||
# variable, in that order.
|
||||
__git_eread ()
|
||||
{
|
||||
test -r "$1" && IFS=$'\r\n' read "$2" <"$1"
|
||||
}
|
||||
|
||||
# see if a cherry-pick or revert is in progress, if the user has committed a
|
||||
# conflict resolution with 'git commit' in the middle of a sequence of picks or
|
||||
# reverts then CHERRY_PICK_HEAD/REVERT_HEAD will not exist so we have to read
|
||||
# the todo file.
|
||||
__git_sequencer_status ()
|
||||
{
|
||||
local todo
|
||||
if test -f "$g/CHERRY_PICK_HEAD"
|
||||
then
|
||||
r="|CHERRY-PICKING"
|
||||
return 0;
|
||||
elif test -f "$g/REVERT_HEAD"
|
||||
then
|
||||
r="|REVERTING"
|
||||
return 0;
|
||||
elif __git_eread "$g/sequencer/todo" todo
|
||||
then
|
||||
case "$todo" in
|
||||
p[\ \ ]|pick[\ \ ]*)
|
||||
r="|CHERRY-PICKING"
|
||||
return 0
|
||||
;;
|
||||
revert[\ \ ]*)
|
||||
r="|REVERTING"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# __git_ps1 accepts 0 or 1 arguments (i.e., format string)
|
||||
# when called from PS1 using command substitution
|
||||
# in this mode it prints text to add to bash PS1 prompt (includes branch name)
|
||||
#
|
||||
# __git_ps1 requires 2 or 3 arguments when called from PROMPT_COMMAND (pc)
|
||||
# in that case it _sets_ PS1. The arguments are parts of a PS1 string.
|
||||
# when two arguments are given, the first is prepended and the second appended
|
||||
# to the state string when assigned to PS1.
|
||||
# The optional third parameter will be used as printf format string to further
|
||||
# customize the output of the git-status string.
|
||||
# In this mode you can request colored hints using GIT_PS1_SHOWCOLORHINTS=true
|
||||
__git_ps1 ()
|
||||
{
|
||||
# preserve exit status
|
||||
local exit=$?
|
||||
local pcmode=no
|
||||
local detached=no
|
||||
local ps1pc_start='\u@\h:\w '
|
||||
local ps1pc_end='\$ '
|
||||
local printf_format=' (%s)'
|
||||
|
||||
case "$#" in
|
||||
2|3) pcmode=yes
|
||||
ps1pc_start="$1"
|
||||
ps1pc_end="$2"
|
||||
printf_format="${3:-$printf_format}"
|
||||
# set PS1 to a plain prompt so that we can
|
||||
# simply return early if the prompt should not
|
||||
# be decorated
|
||||
PS1="$ps1pc_start$ps1pc_end"
|
||||
;;
|
||||
0|1) printf_format="${1:-$printf_format}"
|
||||
;;
|
||||
*) return $exit
|
||||
;;
|
||||
esac
|
||||
|
||||
# ps1_expanded: This variable is set to 'yes' if the shell
|
||||
# subjects the value of PS1 to parameter expansion:
|
||||
#
|
||||
# * bash does unless the promptvars option is disabled
|
||||
# * zsh does not unless the PROMPT_SUBST option is set
|
||||
# * POSIX shells always do
|
||||
#
|
||||
# If the shell would expand the contents of PS1 when drawing
|
||||
# the prompt, a raw ref name must not be included in PS1.
|
||||
# This protects the user from arbitrary code execution via
|
||||
# specially crafted ref names. For example, a ref named
|
||||
# 'refs/heads/$(IFS=_;cmd=sudo_rm_-rf_/;$cmd)' might cause the
|
||||
# shell to execute 'sudo rm -rf /' when the prompt is drawn.
|
||||
#
|
||||
# Instead, the ref name should be placed in a separate global
|
||||
# variable (in the __git_ps1_* namespace to avoid colliding
|
||||
# with the user's environment) and that variable should be
|
||||
# referenced from PS1. For example:
|
||||
#
|
||||
# __git_ps1_foo=$(do_something_to_get_ref_name)
|
||||
# PS1="...stuff...\${__git_ps1_foo}...stuff..."
|
||||
#
|
||||
# If the shell does not expand the contents of PS1, the raw
|
||||
# ref name must be included in PS1.
|
||||
#
|
||||
# The value of this variable is only relevant when in pcmode.
|
||||
#
|
||||
# Assume that the shell follows the POSIX specification and
|
||||
# expands PS1 unless determined otherwise. (This is more
|
||||
# likely to be correct if the user has a non-bash, non-zsh
|
||||
# shell and safer than the alternative if the assumption is
|
||||
# incorrect.)
|
||||
#
|
||||
local ps1_expanded=yes
|
||||
[ -z "${ZSH_VERSION-}" ] || [[ -o PROMPT_SUBST ]] || ps1_expanded=no
|
||||
[ -z "${BASH_VERSION-}" ] || shopt -q promptvars || ps1_expanded=no
|
||||
|
||||
local repo_info rev_parse_exit_code
|
||||
repo_info="$(git rev-parse --git-dir --is-inside-git-dir \
|
||||
--is-bare-repository --is-inside-work-tree \
|
||||
--short HEAD 2>/dev/null)"
|
||||
rev_parse_exit_code="$?"
|
||||
|
||||
if [ -z "$repo_info" ]; then
|
||||
return $exit
|
||||
fi
|
||||
|
||||
local short_sha=""
|
||||
if [ "$rev_parse_exit_code" = "0" ]; then
|
||||
short_sha="${repo_info##*$'\n'}"
|
||||
repo_info="${repo_info%$'\n'*}"
|
||||
fi
|
||||
local inside_worktree="${repo_info##*$'\n'}"
|
||||
repo_info="${repo_info%$'\n'*}"
|
||||
local bare_repo="${repo_info##*$'\n'}"
|
||||
repo_info="${repo_info%$'\n'*}"
|
||||
local inside_gitdir="${repo_info##*$'\n'}"
|
||||
local g="${repo_info%$'\n'*}"
|
||||
|
||||
if [ "true" = "$inside_worktree" ] &&
|
||||
[ -n "${GIT_PS1_HIDE_IF_PWD_IGNORED-}" ] &&
|
||||
[ "$(git config --bool bash.hideIfPwdIgnored)" != "false" ] &&
|
||||
git check-ignore -q .
|
||||
then
|
||||
return $exit
|
||||
fi
|
||||
|
||||
local r=""
|
||||
local b=""
|
||||
local step=""
|
||||
local total=""
|
||||
if [ -d "$g/rebase-merge" ]; then
|
||||
__git_eread "$g/rebase-merge/head-name" b
|
||||
__git_eread "$g/rebase-merge/msgnum" step
|
||||
__git_eread "$g/rebase-merge/end" total
|
||||
if [ -f "$g/rebase-merge/interactive" ]; then
|
||||
r="|REBASE-i"
|
||||
else
|
||||
r="|REBASE-m"
|
||||
fi
|
||||
else
|
||||
if [ -d "$g/rebase-apply" ]; then
|
||||
__git_eread "$g/rebase-apply/next" step
|
||||
__git_eread "$g/rebase-apply/last" total
|
||||
if [ -f "$g/rebase-apply/rebasing" ]; then
|
||||
__git_eread "$g/rebase-apply/head-name" b
|
||||
r="|REBASE"
|
||||
elif [ -f "$g/rebase-apply/applying" ]; then
|
||||
r="|AM"
|
||||
else
|
||||
r="|AM/REBASE"
|
||||
fi
|
||||
elif [ -f "$g/MERGE_HEAD" ]; then
|
||||
r="|MERGING"
|
||||
elif __git_sequencer_status; then
|
||||
:
|
||||
elif [ -f "$g/BISECT_LOG" ]; then
|
||||
r="|BISECTING"
|
||||
fi
|
||||
|
||||
if [ -n "$b" ]; then
|
||||
:
|
||||
elif [ -h "$g/HEAD" ]; then
|
||||
# symlink symbolic ref
|
||||
b="$(git symbolic-ref HEAD 2>/dev/null)"
|
||||
else
|
||||
local head=""
|
||||
if ! __git_eread "$g/HEAD" head; then
|
||||
return $exit
|
||||
fi
|
||||
# is it a symbolic ref?
|
||||
b="${head#ref: }"
|
||||
if [ "$head" = "$b" ]; then
|
||||
detached=yes
|
||||
b="$(
|
||||
case "${GIT_PS1_DESCRIBE_STYLE-}" in
|
||||
(contains)
|
||||
git describe --contains HEAD ;;
|
||||
(branch)
|
||||
git describe --contains --all HEAD ;;
|
||||
(tag)
|
||||
git describe --tags HEAD ;;
|
||||
(describe)
|
||||
git describe HEAD ;;
|
||||
(* | default)
|
||||
git describe --tags --exact-match HEAD ;;
|
||||
esac 2>/dev/null)" ||
|
||||
|
||||
b="$short_sha..."
|
||||
b="($b)"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$step" ] && [ -n "$total" ]; then
|
||||
r="$r $step/$total"
|
||||
fi
|
||||
|
||||
local w=""
|
||||
local i=""
|
||||
local s=""
|
||||
local u=""
|
||||
local c=""
|
||||
local p=""
|
||||
|
||||
if [ "true" = "$inside_gitdir" ]; then
|
||||
if [ "true" = "$bare_repo" ]; then
|
||||
c="BARE:"
|
||||
else
|
||||
b="GIT_DIR!"
|
||||
fi
|
||||
elif [ "true" = "$inside_worktree" ]; then
|
||||
if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ] &&
|
||||
[ "$(git config --bool bash.showDirtyState)" != "false" ]
|
||||
then
|
||||
git diff --no-ext-diff --quiet || w="*"
|
||||
git diff --no-ext-diff --cached --quiet || i="+"
|
||||
if [ -z "$short_sha" ] && [ -z "$i" ]; then
|
||||
i="#"
|
||||
fi
|
||||
fi
|
||||
if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ] &&
|
||||
git rev-parse --verify --quiet refs/stash >/dev/null
|
||||
then
|
||||
s="$"
|
||||
fi
|
||||
|
||||
if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ] &&
|
||||
[ "$(git config --bool bash.showUntrackedFiles)" != "false" ] &&
|
||||
git ls-files --others --exclude-standard --directory --no-empty-directory --error-unmatch -- ':/*' >/dev/null 2>/dev/null
|
||||
then
|
||||
u="%${ZSH_VERSION+%}"
|
||||
fi
|
||||
|
||||
if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
|
||||
__git_ps1_show_upstream
|
||||
fi
|
||||
fi
|
||||
|
||||
local z="${GIT_PS1_STATESEPARATOR-" "}"
|
||||
|
||||
# NO color option unless in PROMPT_COMMAND mode
|
||||
if [ $pcmode = yes ] && [ -n "${GIT_PS1_SHOWCOLORHINTS-}" ]; then
|
||||
__git_ps1_colorize_gitstring
|
||||
fi
|
||||
|
||||
b=${b##refs/heads/}
|
||||
if [ $pcmode = yes ] && [ $ps1_expanded = yes ]; then
|
||||
__git_ps1_branch_name=$b
|
||||
b="\${__git_ps1_branch_name}"
|
||||
fi
|
||||
|
||||
local f="$w$i$s$u"
|
||||
local gitstring="$c$b${f:+$z$f}$r$p"
|
||||
|
||||
if [ $pcmode = yes ]; then
|
||||
if [ "${__git_printf_supports_v-}" != yes ]; then
|
||||
gitstring=$(printf -- "$printf_format" "$gitstring")
|
||||
else
|
||||
printf -v gitstring -- "$printf_format" "$gitstring"
|
||||
fi
|
||||
PS1="$ps1pc_start$gitstring$ps1pc_end"
|
||||
else
|
||||
printf -- "$printf_format" "$gitstring"
|
||||
fi
|
||||
|
||||
return $exit
|
||||
}
|
||||
3
third_party/git/contrib/contacts/.gitignore
vendored
Normal file
3
third_party/git/contrib/contacts/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
git-contacts.1
|
||||
git-contacts.html
|
||||
git-contacts.xml
|
||||
71
third_party/git/contrib/contacts/Makefile
vendored
Normal file
71
third_party/git/contrib/contacts/Makefile
vendored
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# The default target of this Makefile is...
|
||||
all::
|
||||
|
||||
-include ../../config.mak.autogen
|
||||
-include ../../config.mak
|
||||
|
||||
prefix ?= /usr/local
|
||||
gitexecdir ?= $(prefix)/libexec/git-core
|
||||
mandir ?= $(prefix)/share/man
|
||||
man1dir ?= $(mandir)/man1
|
||||
htmldir ?= $(prefix)/share/doc/git-doc
|
||||
|
||||
../../GIT-VERSION-FILE: FORCE
|
||||
$(MAKE) -C ../../ GIT-VERSION-FILE
|
||||
|
||||
-include ../../GIT-VERSION-FILE
|
||||
|
||||
# this should be set to a 'standard' bsd-type install program
|
||||
INSTALL ?= install
|
||||
RM ?= rm -f
|
||||
|
||||
ASCIIDOC = asciidoc
|
||||
XMLTO = xmlto
|
||||
|
||||
ifndef SHELL_PATH
|
||||
SHELL_PATH = /bin/sh
|
||||
endif
|
||||
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
|
||||
|
||||
ASCIIDOC_CONF = ../../Documentation/asciidoc.conf
|
||||
MANPAGE_XSL = ../../Documentation/manpage-normal.xsl
|
||||
|
||||
GIT_CONTACTS := git-contacts
|
||||
|
||||
GIT_CONTACTS_DOC := git-contacts.1
|
||||
GIT_CONTACTS_XML := git-contacts.xml
|
||||
GIT_CONTACTS_TXT := git-contacts.txt
|
||||
GIT_CONTACTS_HTML := git-contacts.html
|
||||
|
||||
doc: $(GIT_CONTACTS_DOC) $(GIT_CONTACTS_HTML)
|
||||
|
||||
install: $(GIT_CONTACTS)
|
||||
$(INSTALL) -d -m 755 $(DESTDIR)$(gitexecdir)
|
||||
$(INSTALL) -m 755 $(GIT_CONTACTS) $(DESTDIR)$(gitexecdir)
|
||||
|
||||
install-doc: install-man install-html
|
||||
|
||||
install-man: $(GIT_CONTACTS_DOC)
|
||||
$(INSTALL) -d -m 755 $(DESTDIR)$(man1dir)
|
||||
$(INSTALL) -m 644 $^ $(DESTDIR)$(man1dir)
|
||||
|
||||
install-html: $(GIT_CONTACTS_HTML)
|
||||
$(INSTALL) -d -m 755 $(DESTDIR)$(htmldir)
|
||||
$(INSTALL) -m 644 $^ $(DESTDIR)$(htmldir)
|
||||
|
||||
$(GIT_CONTACTS_DOC): $(GIT_CONTACTS_XML)
|
||||
$(XMLTO) -m $(MANPAGE_XSL) man $^
|
||||
|
||||
$(GIT_CONTACTS_XML): $(GIT_CONTACTS_TXT)
|
||||
$(ASCIIDOC) -b docbook -d manpage -f $(ASCIIDOC_CONF) \
|
||||
-agit_version=$(GIT_VERSION) $^
|
||||
|
||||
$(GIT_CONTACTS_HTML): $(GIT_CONTACTS_TXT)
|
||||
$(ASCIIDOC) -b xhtml11 -d manpage -f $(ASCIIDOC_CONF) \
|
||||
-agit_version=$(GIT_VERSION) $^
|
||||
|
||||
clean:
|
||||
$(RM) $(GIT_CONTACTS)
|
||||
$(RM) *.xml *.html *.1
|
||||
|
||||
.PHONY: FORCE
|
||||
203
third_party/git/contrib/contacts/git-contacts
vendored
Executable file
203
third_party/git/contrib/contacts/git-contacts
vendored
Executable file
|
|
@ -0,0 +1,203 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
# List people who might be interested in a patch. Useful as the argument to
|
||||
# git-send-email --cc-cmd option, and in other situations.
|
||||
#
|
||||
# Usage: git contacts <file | rev-list option> ...
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use IPC::Open2;
|
||||
|
||||
my $since = '5-years-ago';
|
||||
my $min_percent = 10;
|
||||
my $labels_rx = qr/Signed-off-by|Reviewed-by|Acked-by|Cc|Reported-by/i;
|
||||
my %seen;
|
||||
|
||||
sub format_contact {
|
||||
my ($name, $email) = @_;
|
||||
return "$name <$email>";
|
||||
}
|
||||
|
||||
sub parse_commit {
|
||||
my ($commit, $data) = @_;
|
||||
my $contacts = $commit->{contacts};
|
||||
my $inbody = 0;
|
||||
for (split(/^/m, $data)) {
|
||||
if (not $inbody) {
|
||||
if (/^author ([^<>]+) <(\S+)> .+$/) {
|
||||
$contacts->{format_contact($1, $2)} = 1;
|
||||
} elsif (/^$/) {
|
||||
$inbody = 1;
|
||||
}
|
||||
} elsif (/^$labels_rx:\s+([^<>]+)\s+<(\S+?)>$/o) {
|
||||
$contacts->{format_contact($1, $2)} = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub import_commits {
|
||||
my ($commits) = @_;
|
||||
return unless %$commits;
|
||||
my $pid = open2 my $reader, my $writer, qw(git cat-file --batch);
|
||||
for my $id (keys(%$commits)) {
|
||||
print $writer "$id\n";
|
||||
my $line = <$reader>;
|
||||
if ($line =~ /^([0-9a-f]{40}) commit (\d+)/) {
|
||||
my ($cid, $len) = ($1, $2);
|
||||
die "expected $id but got $cid\n" unless $id eq $cid;
|
||||
my $data;
|
||||
# cat-file emits newline after data, so read len+1
|
||||
read $reader, $data, $len + 1;
|
||||
parse_commit($commits->{$id}, $data);
|
||||
}
|
||||
}
|
||||
close $reader;
|
||||
close $writer;
|
||||
waitpid($pid, 0);
|
||||
die "git-cat-file error: $?\n" if $?;
|
||||
}
|
||||
|
||||
sub get_blame {
|
||||
my ($commits, $source, $from, $ranges) = @_;
|
||||
return unless @$ranges;
|
||||
open my $f, '-|',
|
||||
qw(git blame --porcelain -C),
|
||||
map({"-L$_->[0],+$_->[1]"} @$ranges),
|
||||
'--since', $since, "$from^", '--', $source or die;
|
||||
while (<$f>) {
|
||||
if (/^([0-9a-f]{40}) \d+ \d+ \d+$/) {
|
||||
my $id = $1;
|
||||
$commits->{$id} = { id => $id, contacts => {} }
|
||||
unless $seen{$id};
|
||||
$seen{$id} = 1;
|
||||
}
|
||||
}
|
||||
close $f;
|
||||
}
|
||||
|
||||
sub blame_sources {
|
||||
my ($sources, $commits) = @_;
|
||||
for my $s (keys %$sources) {
|
||||
for my $id (keys %{$sources->{$s}}) {
|
||||
get_blame($commits, $s, $id, $sources->{$s}{$id});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub scan_patches {
|
||||
my ($sources, $id, $f) = @_;
|
||||
my $source;
|
||||
while (<$f>) {
|
||||
if (/^From ([0-9a-f]{40}) Mon Sep 17 00:00:00 2001$/) {
|
||||
$id = $1;
|
||||
$seen{$id} = 1;
|
||||
}
|
||||
next unless $id;
|
||||
if (m{^--- (?:a/(.+)|/dev/null)$}) {
|
||||
$source = $1;
|
||||
} elsif (/^@@ -(\d+)(?:,(\d+))?/ && $source) {
|
||||
my $len = defined($2) ? $2 : 1;
|
||||
push @{$sources->{$source}{$id}}, [$1, $len] if $len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub scan_patch_file {
|
||||
my ($commits, $file) = @_;
|
||||
open my $f, '<', $file or die "read failure: $file: $!\n";
|
||||
scan_patches($commits, undef, $f);
|
||||
close $f;
|
||||
}
|
||||
|
||||
sub parse_rev_args {
|
||||
my @args = @_;
|
||||
open my $f, '-|',
|
||||
qw(git rev-parse --revs-only --default HEAD --symbolic), @args
|
||||
or die;
|
||||
my @revs;
|
||||
while (<$f>) {
|
||||
chomp;
|
||||
push @revs, $_;
|
||||
}
|
||||
close $f;
|
||||
return @revs if scalar(@revs) != 1;
|
||||
return "^$revs[0]", 'HEAD' unless $revs[0] =~ /^-/;
|
||||
return $revs[0], 'HEAD';
|
||||
}
|
||||
|
||||
sub scan_rev_args {
|
||||
my ($commits, $args) = @_;
|
||||
my @revs = parse_rev_args(@$args);
|
||||
open my $f, '-|', qw(git rev-list --reverse), @revs or die;
|
||||
while (<$f>) {
|
||||
chomp;
|
||||
my $id = $_;
|
||||
$seen{$id} = 1;
|
||||
open my $g, '-|', qw(git show -C --oneline), $id or die;
|
||||
scan_patches($commits, $id, $g);
|
||||
close $g;
|
||||
}
|
||||
close $f;
|
||||
}
|
||||
|
||||
sub mailmap_contacts {
|
||||
my ($contacts) = @_;
|
||||
my %mapped;
|
||||
my $pid = open2 my $reader, my $writer, qw(git check-mailmap --stdin);
|
||||
for my $contact (keys(%$contacts)) {
|
||||
print $writer "$contact\n";
|
||||
my $canonical = <$reader>;
|
||||
chomp $canonical;
|
||||
$mapped{$canonical} += $contacts->{$contact};
|
||||
}
|
||||
close $reader;
|
||||
close $writer;
|
||||
waitpid($pid, 0);
|
||||
die "git-check-mailmap error: $?\n" if $?;
|
||||
return \%mapped;
|
||||
}
|
||||
|
||||
if (!@ARGV) {
|
||||
die "No input revisions or patch files\n";
|
||||
}
|
||||
|
||||
my (@files, @rev_args);
|
||||
for (@ARGV) {
|
||||
if (-e) {
|
||||
push @files, $_;
|
||||
} else {
|
||||
push @rev_args, $_;
|
||||
}
|
||||
}
|
||||
|
||||
my %sources;
|
||||
for (@files) {
|
||||
scan_patch_file(\%sources, $_);
|
||||
}
|
||||
if (@rev_args) {
|
||||
scan_rev_args(\%sources, \@rev_args)
|
||||
}
|
||||
|
||||
my $toplevel = `git rev-parse --show-toplevel`;
|
||||
chomp $toplevel;
|
||||
chdir($toplevel) or die "chdir failure: $toplevel: $!\n";
|
||||
|
||||
my %commits;
|
||||
blame_sources(\%sources, \%commits);
|
||||
import_commits(\%commits);
|
||||
|
||||
my $contacts = {};
|
||||
for my $commit (values %commits) {
|
||||
for my $contact (keys %{$commit->{contacts}}) {
|
||||
$contacts->{$contact}++;
|
||||
}
|
||||
}
|
||||
$contacts = mailmap_contacts($contacts);
|
||||
|
||||
my $ncommits = scalar(keys %commits);
|
||||
for my $contact (keys %$contacts) {
|
||||
my $percent = $contacts->{$contact} * 100 / $ncommits;
|
||||
next if $percent < $min_percent;
|
||||
print "$contact\n";
|
||||
}
|
||||
94
third_party/git/contrib/contacts/git-contacts.txt
vendored
Normal file
94
third_party/git/contrib/contacts/git-contacts.txt
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
git-contacts(1)
|
||||
===============
|
||||
|
||||
NAME
|
||||
----
|
||||
git-contacts - List people who might be interested in a set of changes
|
||||
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'git contacts' (<patch>|<range>|<rev>)...
|
||||
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
|
||||
Given a set of changes, specified as patch files or revisions, determine people
|
||||
who might be interested in those changes. This is done by consulting the
|
||||
history of each patch or revision hunk to find people mentioned by commits
|
||||
which touched the lines of files under consideration.
|
||||
|
||||
Input consists of one or more patch files or revision arguments. A revision
|
||||
argument can be a range or a single `<rev>` which is interpreted as
|
||||
`<rev>..HEAD`, thus the same revision arguments are accepted as for
|
||||
linkgit:git-format-patch[1]. Patch files and revision arguments can be combined
|
||||
in the same invocation.
|
||||
|
||||
This command can be useful for determining the list of people with whom to
|
||||
discuss proposed changes, or for finding the list of recipients to Cc: when
|
||||
submitting a patch series via `git send-email`. For the latter case, `git
|
||||
contacts` can be used as the argument to `git send-email`'s `--cc-cmd` option.
|
||||
|
||||
|
||||
DISCUSSION
|
||||
----------
|
||||
|
||||
`git blame` is invoked for each hunk in a patch file or revision. For each
|
||||
commit mentioned by `git blame`, the commit message is consulted for people who
|
||||
authored, reviewed, signed, acknowledged, or were Cc:'d. Once the list of
|
||||
participants is known, each person's relevance is computed by considering how
|
||||
many commits mentioned that person compared with the total number of commits
|
||||
under consideration. The final output consists only of participants who exceed
|
||||
a minimum threshold of participation.
|
||||
|
||||
|
||||
OUTPUT
|
||||
------
|
||||
|
||||
For each person of interest, a single line is output, terminated by a newline.
|
||||
If the person's name is known, ``Name $$<user@host>$$'' is printed; otherwise
|
||||
only ``$$<user@host>$$'' is printed.
|
||||
|
||||
|
||||
EXAMPLES
|
||||
--------
|
||||
|
||||
* Consult patch files:
|
||||
+
|
||||
------------
|
||||
$ git contacts feature/*.patch
|
||||
------------
|
||||
|
||||
* Revision range:
|
||||
+
|
||||
------------
|
||||
$ git contacts R1..R2
|
||||
------------
|
||||
|
||||
* From a single revision to `HEAD`:
|
||||
+
|
||||
------------
|
||||
$ git contacts origin
|
||||
------------
|
||||
|
||||
* Helper for `git send-email`:
|
||||
+
|
||||
------------
|
||||
$ git send-email --cc-cmd='git contacts' feature/*.patch
|
||||
------------
|
||||
|
||||
|
||||
LIMITATIONS
|
||||
-----------
|
||||
|
||||
Several conditions controlling a person's significance are currently
|
||||
hard-coded, such as minimum participation level (10%), blame date-limiting (5
|
||||
years), and `-C` level for detecting moved and copied lines (a single `-C`). In
|
||||
the future, these conditions may become configurable.
|
||||
|
||||
|
||||
GIT
|
||||
---
|
||||
Part of the linkgit:git[1] suite
|
||||
108
third_party/git/contrib/coverage-diff.sh
vendored
Executable file
108
third_party/git/contrib/coverage-diff.sh
vendored
Executable file
|
|
@ -0,0 +1,108 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Usage: Run 'contrib/coverage-diff.sh <version1> <version2>' from source-root
|
||||
# after running
|
||||
#
|
||||
# make coverage-test
|
||||
# make coverage-report
|
||||
#
|
||||
# while checked out at <version2>. This script combines the *.gcov files
|
||||
# generated by the 'make' commands above with 'git diff <version1> <version2>'
|
||||
# to report new lines that are not covered by the test suite.
|
||||
|
||||
V1=$1
|
||||
V2=$2
|
||||
|
||||
diff_lines () {
|
||||
perl -e '
|
||||
my $line_num;
|
||||
while (<>) {
|
||||
# Hunk header? Grab the beginning in postimage.
|
||||
if (/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/) {
|
||||
$line_num = $1;
|
||||
next;
|
||||
}
|
||||
|
||||
# Have we seen a hunk? Ignore "diff --git" etc.
|
||||
next unless defined $line_num;
|
||||
|
||||
# Deleted line? Ignore.
|
||||
if (/^-/) {
|
||||
next;
|
||||
}
|
||||
|
||||
# Show only the line number of added lines.
|
||||
if (/^\+/) {
|
||||
print "$line_num\n";
|
||||
}
|
||||
# Either common context or added line appear in
|
||||
# the postimage. Count it.
|
||||
$line_num++;
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
files=$(git diff --name-only "$V1" "$V2" -- \*.c)
|
||||
|
||||
# create empty file
|
||||
>coverage-data.txt
|
||||
|
||||
for file in $files
|
||||
do
|
||||
git diff "$V1" "$V2" -- "$file" |
|
||||
diff_lines |
|
||||
sort >new_lines.txt
|
||||
|
||||
if ! test -s new_lines.txt
|
||||
then
|
||||
continue
|
||||
fi
|
||||
|
||||
hash_file=$(echo $file | sed "s/\//\#/")
|
||||
|
||||
if ! test -s "$hash_file.gcov"
|
||||
then
|
||||
continue
|
||||
fi
|
||||
|
||||
sed -ne '/#####:/{
|
||||
s/ #####://
|
||||
s/:.*//
|
||||
s/ //g
|
||||
p
|
||||
}' "$hash_file.gcov" |
|
||||
sort >uncovered_lines.txt
|
||||
|
||||
comm -12 uncovered_lines.txt new_lines.txt |
|
||||
sed -e 's/$/\)/' |
|
||||
sed -e 's/^/ /' >uncovered_new_lines.txt
|
||||
|
||||
grep -q '[^[:space:]]' <uncovered_new_lines.txt &&
|
||||
echo $file >>coverage-data.txt &&
|
||||
git blame -s "$V2" -- "$file" |
|
||||
sed 's/\t//g' |
|
||||
grep -f uncovered_new_lines.txt >>coverage-data.txt &&
|
||||
echo >>coverage-data.txt
|
||||
|
||||
rm -f new_lines.txt uncovered_lines.txt uncovered_new_lines.txt
|
||||
done
|
||||
|
||||
cat coverage-data.txt
|
||||
|
||||
echo "Commits introducing uncovered code:"
|
||||
|
||||
commit_list=$(cat coverage-data.txt |
|
||||
grep -E '^[0-9a-f]{7,} ' |
|
||||
awk '{print $1;}' |
|
||||
sort |
|
||||
uniq)
|
||||
|
||||
(
|
||||
for commit in $commit_list
|
||||
do
|
||||
git log --no-decorate --pretty=format:'%an %h: %s' -1 $commit
|
||||
echo
|
||||
done
|
||||
) | sort
|
||||
|
||||
rm coverage-data.txt
|
||||
1
third_party/git/contrib/credential/gnome-keyring/.gitignore
vendored
Normal file
1
third_party/git/contrib/credential/gnome-keyring/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
git-credential-gnome-keyring
|
||||
25
third_party/git/contrib/credential/gnome-keyring/Makefile
vendored
Normal file
25
third_party/git/contrib/credential/gnome-keyring/Makefile
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
MAIN:=git-credential-gnome-keyring
|
||||
all:: $(MAIN)
|
||||
|
||||
CC = gcc
|
||||
RM = rm -f
|
||||
CFLAGS = -g -O2 -Wall
|
||||
PKG_CONFIG = pkg-config
|
||||
|
||||
-include ../../../config.mak.autogen
|
||||
-include ../../../config.mak
|
||||
|
||||
INCS:=$(shell $(PKG_CONFIG) --cflags gnome-keyring-1 glib-2.0)
|
||||
LIBS:=$(shell $(PKG_CONFIG) --libs gnome-keyring-1 glib-2.0)
|
||||
|
||||
SRCS:=$(MAIN).c
|
||||
OBJS:=$(SRCS:.c=.o)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) $(INCS) -o $@ -c $<
|
||||
|
||||
$(MAIN): $(OBJS)
|
||||
$(CC) -o $@ $(LDFLAGS) $^ $(LIBS)
|
||||
|
||||
clean:
|
||||
@$(RM) $(MAIN) $(OBJS)
|
||||
470
third_party/git/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c
vendored
Normal file
470
third_party/git/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c
vendored
Normal file
|
|
@ -0,0 +1,470 @@
|
|||
/*
|
||||
* Copyright (C) 2011 John Szakmeister <john@szakmeister.net>
|
||||
* 2012 Philipp A. Hartmann <pah@qo.cx>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Credits:
|
||||
* - GNOME Keyring API handling originally written by John Szakmeister
|
||||
* - ported to credential helper API by Philipp A. Hartmann
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <glib.h>
|
||||
#include <gnome-keyring.h>
|
||||
|
||||
#ifdef GNOME_KEYRING_DEFAULT
|
||||
|
||||
/* Modern gnome-keyring */
|
||||
|
||||
#include <gnome-keyring-memory.h>
|
||||
|
||||
#else
|
||||
|
||||
/*
|
||||
* Support ancient gnome-keyring, circ. RHEL 5.X.
|
||||
* GNOME_KEYRING_DEFAULT seems to have been introduced with Gnome 2.22,
|
||||
* and the other features roughly around Gnome 2.20, 6 months before.
|
||||
* Ubuntu 8.04 used Gnome 2.22 (I think). Not sure any distro used 2.20.
|
||||
* So the existence/non-existence of GNOME_KEYRING_DEFAULT seems like
|
||||
* a decent thing to use as an indicator.
|
||||
*/
|
||||
|
||||
#define GNOME_KEYRING_DEFAULT NULL
|
||||
|
||||
/*
|
||||
* ancient gnome-keyring returns DENIED when an entry is not found.
|
||||
* Setting NO_MATCH to DENIED will prevent us from reporting DENIED
|
||||
* errors during get and erase operations, but we will still report
|
||||
* DENIED errors during a store.
|
||||
*/
|
||||
#define GNOME_KEYRING_RESULT_NO_MATCH GNOME_KEYRING_RESULT_DENIED
|
||||
|
||||
#define gnome_keyring_memory_alloc g_malloc
|
||||
#define gnome_keyring_memory_free gnome_keyring_free_password
|
||||
#define gnome_keyring_memory_strdup g_strdup
|
||||
|
||||
static const char *gnome_keyring_result_to_message(GnomeKeyringResult result)
|
||||
{
|
||||
switch (result) {
|
||||
case GNOME_KEYRING_RESULT_OK:
|
||||
return "OK";
|
||||
case GNOME_KEYRING_RESULT_DENIED:
|
||||
return "Denied";
|
||||
case GNOME_KEYRING_RESULT_NO_KEYRING_DAEMON:
|
||||
return "No Keyring Daemon";
|
||||
case GNOME_KEYRING_RESULT_ALREADY_UNLOCKED:
|
||||
return "Already UnLocked";
|
||||
case GNOME_KEYRING_RESULT_NO_SUCH_KEYRING:
|
||||
return "No Such Keyring";
|
||||
case GNOME_KEYRING_RESULT_BAD_ARGUMENTS:
|
||||
return "Bad Arguments";
|
||||
case GNOME_KEYRING_RESULT_IO_ERROR:
|
||||
return "IO Error";
|
||||
case GNOME_KEYRING_RESULT_CANCELLED:
|
||||
return "Cancelled";
|
||||
case GNOME_KEYRING_RESULT_ALREADY_EXISTS:
|
||||
return "Already Exists";
|
||||
default:
|
||||
return "Unknown Error";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Support really ancient gnome-keyring, circ. RHEL 4.X.
|
||||
* Just a guess for the Glib version. Glib 2.8 was roughly Gnome 2.12 ?
|
||||
* Which was released with gnome-keyring 0.4.3 ??
|
||||
*/
|
||||
#if GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION < 8
|
||||
|
||||
static void gnome_keyring_done_cb(GnomeKeyringResult result, gpointer user_data)
|
||||
{
|
||||
gpointer *data = (gpointer *)user_data;
|
||||
int *done = (int *)data[0];
|
||||
GnomeKeyringResult *r = (GnomeKeyringResult *)data[1];
|
||||
|
||||
*r = result;
|
||||
*done = 1;
|
||||
}
|
||||
|
||||
static void wait_for_request_completion(int *done)
|
||||
{
|
||||
GMainContext *mc = g_main_context_default();
|
||||
while (!*done)
|
||||
g_main_context_iteration(mc, TRUE);
|
||||
}
|
||||
|
||||
static GnomeKeyringResult gnome_keyring_item_delete_sync(const char *keyring, guint32 id)
|
||||
{
|
||||
int done = 0;
|
||||
GnomeKeyringResult result;
|
||||
gpointer data[] = { &done, &result };
|
||||
|
||||
gnome_keyring_item_delete(keyring, id, gnome_keyring_done_cb, data,
|
||||
NULL);
|
||||
|
||||
wait_for_request_completion(&done);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This credential struct and API is simplified from git's credential.{h,c}
|
||||
*/
|
||||
struct credential {
|
||||
char *protocol;
|
||||
char *host;
|
||||
unsigned short port;
|
||||
char *path;
|
||||
char *username;
|
||||
char *password;
|
||||
};
|
||||
|
||||
#define CREDENTIAL_INIT { NULL, NULL, 0, NULL, NULL, NULL }
|
||||
|
||||
typedef int (*credential_op_cb)(struct credential *);
|
||||
|
||||
struct credential_operation {
|
||||
char *name;
|
||||
credential_op_cb op;
|
||||
};
|
||||
|
||||
#define CREDENTIAL_OP_END { NULL, NULL }
|
||||
|
||||
/* ----------------- GNOME Keyring functions ----------------- */
|
||||
|
||||
/* create a special keyring option string, if path is given */
|
||||
static char *keyring_object(struct credential *c)
|
||||
{
|
||||
if (!c->path)
|
||||
return NULL;
|
||||
|
||||
if (c->port)
|
||||
return g_strdup_printf("%s:%hd/%s", c->host, c->port, c->path);
|
||||
|
||||
return g_strdup_printf("%s/%s", c->host, c->path);
|
||||
}
|
||||
|
||||
static int keyring_get(struct credential *c)
|
||||
{
|
||||
char *object = NULL;
|
||||
GList *entries;
|
||||
GnomeKeyringNetworkPasswordData *password_data;
|
||||
GnomeKeyringResult result;
|
||||
|
||||
if (!c->protocol || !(c->host || c->path))
|
||||
return EXIT_FAILURE;
|
||||
|
||||
object = keyring_object(c);
|
||||
|
||||
result = gnome_keyring_find_network_password_sync(
|
||||
c->username,
|
||||
NULL /* domain */,
|
||||
c->host,
|
||||
object,
|
||||
c->protocol,
|
||||
NULL /* authtype */,
|
||||
c->port,
|
||||
&entries);
|
||||
|
||||
g_free(object);
|
||||
|
||||
if (result == GNOME_KEYRING_RESULT_NO_MATCH)
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
if (result == GNOME_KEYRING_RESULT_CANCELLED)
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
if (result != GNOME_KEYRING_RESULT_OK) {
|
||||
g_critical("%s", gnome_keyring_result_to_message(result));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* pick the first one from the list */
|
||||
password_data = (GnomeKeyringNetworkPasswordData *)entries->data;
|
||||
|
||||
gnome_keyring_memory_free(c->password);
|
||||
c->password = gnome_keyring_memory_strdup(password_data->password);
|
||||
|
||||
if (!c->username)
|
||||
c->username = g_strdup(password_data->user);
|
||||
|
||||
gnome_keyring_network_password_list_free(entries);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static int keyring_store(struct credential *c)
|
||||
{
|
||||
guint32 item_id;
|
||||
char *object = NULL;
|
||||
GnomeKeyringResult result;
|
||||
|
||||
/*
|
||||
* Sanity check that what we are storing is actually sensible.
|
||||
* In particular, we can't make a URL without a protocol field.
|
||||
* Without either a host or pathname (depending on the scheme),
|
||||
* we have no primary key. And without a username and password,
|
||||
* we are not actually storing a credential.
|
||||
*/
|
||||
if (!c->protocol || !(c->host || c->path) ||
|
||||
!c->username || !c->password)
|
||||
return EXIT_FAILURE;
|
||||
|
||||
object = keyring_object(c);
|
||||
|
||||
result = gnome_keyring_set_network_password_sync(
|
||||
GNOME_KEYRING_DEFAULT,
|
||||
c->username,
|
||||
NULL /* domain */,
|
||||
c->host,
|
||||
object,
|
||||
c->protocol,
|
||||
NULL /* authtype */,
|
||||
c->port,
|
||||
c->password,
|
||||
&item_id);
|
||||
|
||||
g_free(object);
|
||||
|
||||
if (result != GNOME_KEYRING_RESULT_OK &&
|
||||
result != GNOME_KEYRING_RESULT_CANCELLED) {
|
||||
g_critical("%s", gnome_keyring_result_to_message(result));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
static int keyring_erase(struct credential *c)
|
||||
{
|
||||
char *object = NULL;
|
||||
GList *entries;
|
||||
GnomeKeyringNetworkPasswordData *password_data;
|
||||
GnomeKeyringResult result;
|
||||
|
||||
/*
|
||||
* Sanity check that we actually have something to match
|
||||
* against. The input we get is a restrictive pattern,
|
||||
* so technically a blank credential means "erase everything".
|
||||
* But it is too easy to accidentally send this, since it is equivalent
|
||||
* to empty input. So explicitly disallow it, and require that the
|
||||
* pattern have some actual content to match.
|
||||
*/
|
||||
if (!c->protocol && !c->host && !c->path && !c->username)
|
||||
return EXIT_FAILURE;
|
||||
|
||||
object = keyring_object(c);
|
||||
|
||||
result = gnome_keyring_find_network_password_sync(
|
||||
c->username,
|
||||
NULL /* domain */,
|
||||
c->host,
|
||||
object,
|
||||
c->protocol,
|
||||
NULL /* authtype */,
|
||||
c->port,
|
||||
&entries);
|
||||
|
||||
g_free(object);
|
||||
|
||||
if (result == GNOME_KEYRING_RESULT_NO_MATCH)
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
if (result == GNOME_KEYRING_RESULT_CANCELLED)
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
if (result != GNOME_KEYRING_RESULT_OK) {
|
||||
g_critical("%s", gnome_keyring_result_to_message(result));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* pick the first one from the list (delete all matches?) */
|
||||
password_data = (GnomeKeyringNetworkPasswordData *)entries->data;
|
||||
|
||||
result = gnome_keyring_item_delete_sync(
|
||||
password_data->keyring, password_data->item_id);
|
||||
|
||||
gnome_keyring_network_password_list_free(entries);
|
||||
|
||||
if (result != GNOME_KEYRING_RESULT_OK) {
|
||||
g_critical("%s", gnome_keyring_result_to_message(result));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Table with helper operation callbacks, used by generic
|
||||
* credential helper main function.
|
||||
*/
|
||||
static struct credential_operation const credential_helper_ops[] = {
|
||||
{ "get", keyring_get },
|
||||
{ "store", keyring_store },
|
||||
{ "erase", keyring_erase },
|
||||
CREDENTIAL_OP_END
|
||||
};
|
||||
|
||||
/* ------------------ credential functions ------------------ */
|
||||
|
||||
static void credential_init(struct credential *c)
|
||||
{
|
||||
memset(c, 0, sizeof(*c));
|
||||
}
|
||||
|
||||
static void credential_clear(struct credential *c)
|
||||
{
|
||||
g_free(c->protocol);
|
||||
g_free(c->host);
|
||||
g_free(c->path);
|
||||
g_free(c->username);
|
||||
gnome_keyring_memory_free(c->password);
|
||||
|
||||
credential_init(c);
|
||||
}
|
||||
|
||||
static int credential_read(struct credential *c)
|
||||
{
|
||||
char *buf;
|
||||
size_t line_len;
|
||||
char *key;
|
||||
char *value;
|
||||
|
||||
key = buf = gnome_keyring_memory_alloc(1024);
|
||||
|
||||
while (fgets(buf, 1024, stdin)) {
|
||||
line_len = strlen(buf);
|
||||
|
||||
if (line_len && buf[line_len-1] == '\n')
|
||||
buf[--line_len] = '\0';
|
||||
|
||||
if (!line_len)
|
||||
break;
|
||||
|
||||
value = strchr(buf, '=');
|
||||
if (!value) {
|
||||
g_warning("invalid credential line: %s", key);
|
||||
gnome_keyring_memory_free(buf);
|
||||
return -1;
|
||||
}
|
||||
*value++ = '\0';
|
||||
|
||||
if (!strcmp(key, "protocol")) {
|
||||
g_free(c->protocol);
|
||||
c->protocol = g_strdup(value);
|
||||
} else if (!strcmp(key, "host")) {
|
||||
g_free(c->host);
|
||||
c->host = g_strdup(value);
|
||||
value = strrchr(c->host, ':');
|
||||
if (value) {
|
||||
*value++ = '\0';
|
||||
c->port = atoi(value);
|
||||
}
|
||||
} else if (!strcmp(key, "path")) {
|
||||
g_free(c->path);
|
||||
c->path = g_strdup(value);
|
||||
} else if (!strcmp(key, "username")) {
|
||||
g_free(c->username);
|
||||
c->username = g_strdup(value);
|
||||
} else if (!strcmp(key, "password")) {
|
||||
gnome_keyring_memory_free(c->password);
|
||||
c->password = gnome_keyring_memory_strdup(value);
|
||||
while (*value)
|
||||
*value++ = '\0';
|
||||
}
|
||||
/*
|
||||
* Ignore other lines; we don't know what they mean, but
|
||||
* this future-proofs us when later versions of git do
|
||||
* learn new lines, and the helpers are updated to match.
|
||||
*/
|
||||
}
|
||||
|
||||
gnome_keyring_memory_free(buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void credential_write_item(FILE *fp, const char *key, const char *value)
|
||||
{
|
||||
if (!value)
|
||||
return;
|
||||
fprintf(fp, "%s=%s\n", key, value);
|
||||
}
|
||||
|
||||
static void credential_write(const struct credential *c)
|
||||
{
|
||||
/* only write username/password, if set */
|
||||
credential_write_item(stdout, "username", c->username);
|
||||
credential_write_item(stdout, "password", c->password);
|
||||
}
|
||||
|
||||
static void usage(const char *name)
|
||||
{
|
||||
struct credential_operation const *try_op = credential_helper_ops;
|
||||
const char *basename = strrchr(name, '/');
|
||||
|
||||
basename = (basename) ? basename + 1 : name;
|
||||
fprintf(stderr, "usage: %s <", basename);
|
||||
while (try_op->name) {
|
||||
fprintf(stderr, "%s", (try_op++)->name);
|
||||
if (try_op->name)
|
||||
fprintf(stderr, "%s", "|");
|
||||
}
|
||||
fprintf(stderr, "%s", ">\n");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int ret = EXIT_SUCCESS;
|
||||
|
||||
struct credential_operation const *try_op = credential_helper_ops;
|
||||
struct credential cred = CREDENTIAL_INIT;
|
||||
|
||||
if (!argv[1]) {
|
||||
usage(argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
g_set_application_name("Git Credential Helper");
|
||||
|
||||
/* lookup operation callback */
|
||||
while (try_op->name && strcmp(argv[1], try_op->name))
|
||||
try_op++;
|
||||
|
||||
/* unsupported operation given -- ignore silently */
|
||||
if (!try_op->name || !try_op->op)
|
||||
goto out;
|
||||
|
||||
ret = credential_read(&cred);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/* perform credential operation */
|
||||
ret = (*try_op->op)(&cred);
|
||||
|
||||
credential_write(&cred);
|
||||
|
||||
out:
|
||||
credential_clear(&cred);
|
||||
return ret;
|
||||
}
|
||||
25
third_party/git/contrib/credential/libsecret/Makefile
vendored
Normal file
25
third_party/git/contrib/credential/libsecret/Makefile
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
MAIN:=git-credential-libsecret
|
||||
all:: $(MAIN)
|
||||
|
||||
CC = gcc
|
||||
RM = rm -f
|
||||
CFLAGS = -g -O2 -Wall
|
||||
PKG_CONFIG = pkg-config
|
||||
|
||||
-include ../../../config.mak.autogen
|
||||
-include ../../../config.mak
|
||||
|
||||
INCS:=$(shell $(PKG_CONFIG) --cflags libsecret-1 glib-2.0)
|
||||
LIBS:=$(shell $(PKG_CONFIG) --libs libsecret-1 glib-2.0)
|
||||
|
||||
SRCS:=$(MAIN).c
|
||||
OBJS:=$(SRCS:.c=.o)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) $(INCS) -o $@ -c $<
|
||||
|
||||
$(MAIN): $(OBJS)
|
||||
$(CC) -o $@ $(LDFLAGS) $^ $(LIBS)
|
||||
|
||||
clean:
|
||||
@$(RM) $(MAIN) $(OBJS)
|
||||
369
third_party/git/contrib/credential/libsecret/git-credential-libsecret.c
vendored
Normal file
369
third_party/git/contrib/credential/libsecret/git-credential-libsecret.c
vendored
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
/*
|
||||
* Copyright (C) 2011 John Szakmeister <john@szakmeister.net>
|
||||
* 2012 Philipp A. Hartmann <pah@qo.cx>
|
||||
* 2016 Mantas Mikulėnas <grawity@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Credits:
|
||||
* - GNOME Keyring API handling originally written by John Szakmeister
|
||||
* - ported to credential helper API by Philipp A. Hartmann
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <glib.h>
|
||||
#include <libsecret/secret.h>
|
||||
|
||||
/*
|
||||
* This credential struct and API is simplified from git's credential.{h,c}
|
||||
*/
|
||||
struct credential {
|
||||
char *protocol;
|
||||
char *host;
|
||||
unsigned short port;
|
||||
char *path;
|
||||
char *username;
|
||||
char *password;
|
||||
};
|
||||
|
||||
#define CREDENTIAL_INIT { NULL, NULL, 0, NULL, NULL, NULL }
|
||||
|
||||
typedef int (*credential_op_cb)(struct credential *);
|
||||
|
||||
struct credential_operation {
|
||||
char *name;
|
||||
credential_op_cb op;
|
||||
};
|
||||
|
||||
#define CREDENTIAL_OP_END { NULL, NULL }
|
||||
|
||||
/* ----------------- Secret Service functions ----------------- */
|
||||
|
||||
static char *make_label(struct credential *c)
|
||||
{
|
||||
if (c->port)
|
||||
return g_strdup_printf("Git: %s://%s:%hu/%s",
|
||||
c->protocol, c->host, c->port, c->path ? c->path : "");
|
||||
else
|
||||
return g_strdup_printf("Git: %s://%s/%s",
|
||||
c->protocol, c->host, c->path ? c->path : "");
|
||||
}
|
||||
|
||||
static GHashTable *make_attr_list(struct credential *c)
|
||||
{
|
||||
GHashTable *al = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
|
||||
|
||||
if (c->username)
|
||||
g_hash_table_insert(al, "user", g_strdup(c->username));
|
||||
if (c->protocol)
|
||||
g_hash_table_insert(al, "protocol", g_strdup(c->protocol));
|
||||
if (c->host)
|
||||
g_hash_table_insert(al, "server", g_strdup(c->host));
|
||||
if (c->port)
|
||||
g_hash_table_insert(al, "port", g_strdup_printf("%hu", c->port));
|
||||
if (c->path)
|
||||
g_hash_table_insert(al, "object", g_strdup(c->path));
|
||||
|
||||
return al;
|
||||
}
|
||||
|
||||
static int keyring_get(struct credential *c)
|
||||
{
|
||||
SecretService *service = NULL;
|
||||
GHashTable *attributes = NULL;
|
||||
GError *error = NULL;
|
||||
GList *items = NULL;
|
||||
|
||||
if (!c->protocol || !(c->host || c->path))
|
||||
return EXIT_FAILURE;
|
||||
|
||||
service = secret_service_get_sync(0, NULL, &error);
|
||||
if (error != NULL) {
|
||||
g_critical("could not connect to Secret Service: %s", error->message);
|
||||
g_error_free(error);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
attributes = make_attr_list(c);
|
||||
items = secret_service_search_sync(service,
|
||||
SECRET_SCHEMA_COMPAT_NETWORK,
|
||||
attributes,
|
||||
SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_UNLOCK,
|
||||
NULL,
|
||||
&error);
|
||||
g_hash_table_unref(attributes);
|
||||
if (error != NULL) {
|
||||
g_critical("lookup failed: %s", error->message);
|
||||
g_error_free(error);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (items != NULL) {
|
||||
SecretItem *item;
|
||||
SecretValue *secret;
|
||||
const char *s;
|
||||
|
||||
item = items->data;
|
||||
secret = secret_item_get_secret(item);
|
||||
attributes = secret_item_get_attributes(item);
|
||||
|
||||
s = g_hash_table_lookup(attributes, "user");
|
||||
if (s) {
|
||||
g_free(c->username);
|
||||
c->username = g_strdup(s);
|
||||
}
|
||||
|
||||
s = secret_value_get_text(secret);
|
||||
if (s) {
|
||||
g_free(c->password);
|
||||
c->password = g_strdup(s);
|
||||
}
|
||||
|
||||
g_hash_table_unref(attributes);
|
||||
secret_value_unref(secret);
|
||||
g_list_free_full(items, g_object_unref);
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static int keyring_store(struct credential *c)
|
||||
{
|
||||
char *label = NULL;
|
||||
GHashTable *attributes = NULL;
|
||||
GError *error = NULL;
|
||||
|
||||
/*
|
||||
* Sanity check that what we are storing is actually sensible.
|
||||
* In particular, we can't make a URL without a protocol field.
|
||||
* Without either a host or pathname (depending on the scheme),
|
||||
* we have no primary key. And without a username and password,
|
||||
* we are not actually storing a credential.
|
||||
*/
|
||||
if (!c->protocol || !(c->host || c->path) ||
|
||||
!c->username || !c->password)
|
||||
return EXIT_FAILURE;
|
||||
|
||||
label = make_label(c);
|
||||
attributes = make_attr_list(c);
|
||||
secret_password_storev_sync(SECRET_SCHEMA_COMPAT_NETWORK,
|
||||
attributes,
|
||||
NULL,
|
||||
label,
|
||||
c->password,
|
||||
NULL,
|
||||
&error);
|
||||
g_free(label);
|
||||
g_hash_table_unref(attributes);
|
||||
|
||||
if (error != NULL) {
|
||||
g_critical("store failed: %s", error->message);
|
||||
g_error_free(error);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
static int keyring_erase(struct credential *c)
|
||||
{
|
||||
GHashTable *attributes = NULL;
|
||||
GError *error = NULL;
|
||||
|
||||
/*
|
||||
* Sanity check that we actually have something to match
|
||||
* against. The input we get is a restrictive pattern,
|
||||
* so technically a blank credential means "erase everything".
|
||||
* But it is too easy to accidentally send this, since it is equivalent
|
||||
* to empty input. So explicitly disallow it, and require that the
|
||||
* pattern have some actual content to match.
|
||||
*/
|
||||
if (!c->protocol && !c->host && !c->path && !c->username)
|
||||
return EXIT_FAILURE;
|
||||
|
||||
attributes = make_attr_list(c);
|
||||
secret_password_clearv_sync(SECRET_SCHEMA_COMPAT_NETWORK,
|
||||
attributes,
|
||||
NULL,
|
||||
&error);
|
||||
g_hash_table_unref(attributes);
|
||||
|
||||
if (error != NULL) {
|
||||
g_critical("erase failed: %s", error->message);
|
||||
g_error_free(error);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Table with helper operation callbacks, used by generic
|
||||
* credential helper main function.
|
||||
*/
|
||||
static struct credential_operation const credential_helper_ops[] = {
|
||||
{ "get", keyring_get },
|
||||
{ "store", keyring_store },
|
||||
{ "erase", keyring_erase },
|
||||
CREDENTIAL_OP_END
|
||||
};
|
||||
|
||||
/* ------------------ credential functions ------------------ */
|
||||
|
||||
static void credential_init(struct credential *c)
|
||||
{
|
||||
memset(c, 0, sizeof(*c));
|
||||
}
|
||||
|
||||
static void credential_clear(struct credential *c)
|
||||
{
|
||||
g_free(c->protocol);
|
||||
g_free(c->host);
|
||||
g_free(c->path);
|
||||
g_free(c->username);
|
||||
g_free(c->password);
|
||||
|
||||
credential_init(c);
|
||||
}
|
||||
|
||||
static int credential_read(struct credential *c)
|
||||
{
|
||||
char *buf;
|
||||
size_t line_len;
|
||||
char *key;
|
||||
char *value;
|
||||
|
||||
key = buf = g_malloc(1024);
|
||||
|
||||
while (fgets(buf, 1024, stdin)) {
|
||||
line_len = strlen(buf);
|
||||
|
||||
if (line_len && buf[line_len-1] == '\n')
|
||||
buf[--line_len] = '\0';
|
||||
|
||||
if (!line_len)
|
||||
break;
|
||||
|
||||
value = strchr(buf, '=');
|
||||
if (!value) {
|
||||
g_warning("invalid credential line: %s", key);
|
||||
g_free(buf);
|
||||
return -1;
|
||||
}
|
||||
*value++ = '\0';
|
||||
|
||||
if (!strcmp(key, "protocol")) {
|
||||
g_free(c->protocol);
|
||||
c->protocol = g_strdup(value);
|
||||
} else if (!strcmp(key, "host")) {
|
||||
g_free(c->host);
|
||||
c->host = g_strdup(value);
|
||||
value = strrchr(c->host, ':');
|
||||
if (value) {
|
||||
*value++ = '\0';
|
||||
c->port = atoi(value);
|
||||
}
|
||||
} else if (!strcmp(key, "path")) {
|
||||
g_free(c->path);
|
||||
c->path = g_strdup(value);
|
||||
} else if (!strcmp(key, "username")) {
|
||||
g_free(c->username);
|
||||
c->username = g_strdup(value);
|
||||
} else if (!strcmp(key, "password")) {
|
||||
g_free(c->password);
|
||||
c->password = g_strdup(value);
|
||||
while (*value)
|
||||
*value++ = '\0';
|
||||
}
|
||||
/*
|
||||
* Ignore other lines; we don't know what they mean, but
|
||||
* this future-proofs us when later versions of git do
|
||||
* learn new lines, and the helpers are updated to match.
|
||||
*/
|
||||
}
|
||||
|
||||
g_free(buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void credential_write_item(FILE *fp, const char *key, const char *value)
|
||||
{
|
||||
if (!value)
|
||||
return;
|
||||
fprintf(fp, "%s=%s\n", key, value);
|
||||
}
|
||||
|
||||
static void credential_write(const struct credential *c)
|
||||
{
|
||||
/* only write username/password, if set */
|
||||
credential_write_item(stdout, "username", c->username);
|
||||
credential_write_item(stdout, "password", c->password);
|
||||
}
|
||||
|
||||
static void usage(const char *name)
|
||||
{
|
||||
struct credential_operation const *try_op = credential_helper_ops;
|
||||
const char *basename = strrchr(name, '/');
|
||||
|
||||
basename = (basename) ? basename + 1 : name;
|
||||
fprintf(stderr, "usage: %s <", basename);
|
||||
while (try_op->name) {
|
||||
fprintf(stderr, "%s", (try_op++)->name);
|
||||
if (try_op->name)
|
||||
fprintf(stderr, "%s", "|");
|
||||
}
|
||||
fprintf(stderr, "%s", ">\n");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int ret = EXIT_SUCCESS;
|
||||
|
||||
struct credential_operation const *try_op = credential_helper_ops;
|
||||
struct credential cred = CREDENTIAL_INIT;
|
||||
|
||||
if (!argv[1]) {
|
||||
usage(argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
g_set_application_name("Git Credential Helper");
|
||||
|
||||
/* lookup operation callback */
|
||||
while (try_op->name && strcmp(argv[1], try_op->name))
|
||||
try_op++;
|
||||
|
||||
/* unsupported operation given -- ignore silently */
|
||||
if (!try_op->name || !try_op->op)
|
||||
goto out;
|
||||
|
||||
ret = credential_read(&cred);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/* perform credential operation */
|
||||
ret = (*try_op->op)(&cred);
|
||||
|
||||
credential_write(&cred);
|
||||
|
||||
out:
|
||||
credential_clear(&cred);
|
||||
return ret;
|
||||
}
|
||||
8
third_party/git/contrib/credential/netrc/Makefile
vendored
Normal file
8
third_party/git/contrib/credential/netrc/Makefile
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# The default target of this Makefile is...
|
||||
all::
|
||||
|
||||
test:
|
||||
./t-git-credential-netrc.sh
|
||||
|
||||
testverbose:
|
||||
./t-git-credential-netrc.sh -d -v
|
||||
440
third_party/git/contrib/credential/netrc/git-credential-netrc
vendored
Executable file
440
third_party/git/contrib/credential/netrc/git-credential-netrc
vendored
Executable file
|
|
@ -0,0 +1,440 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Getopt::Long;
|
||||
use File::Basename;
|
||||
use Git;
|
||||
|
||||
my $VERSION = "0.2";
|
||||
|
||||
my %options = (
|
||||
help => 0,
|
||||
debug => 0,
|
||||
verbose => 0,
|
||||
insecure => 0,
|
||||
file => [],
|
||||
|
||||
# identical token maps, e.g. host -> host, will be inserted later
|
||||
tmap => {
|
||||
port => 'protocol',
|
||||
machine => 'host',
|
||||
path => 'path',
|
||||
login => 'username',
|
||||
user => 'username',
|
||||
password => 'password',
|
||||
}
|
||||
);
|
||||
|
||||
# Map each credential protocol token to itself on the netrc side.
|
||||
foreach (values %{$options{tmap}}) {
|
||||
$options{tmap}->{$_} = $_;
|
||||
}
|
||||
|
||||
# Now, $options{tmap} has a mapping from the netrc format to the Git credential
|
||||
# helper protocol.
|
||||
|
||||
# Next, we build the reverse token map.
|
||||
|
||||
# When $rmap{foo} contains 'bar', that means that what the Git credential helper
|
||||
# protocol calls 'bar' is found as 'foo' in the netrc/authinfo file. Keys in
|
||||
# %rmap are what we expect to read from the netrc/authinfo file.
|
||||
|
||||
my %rmap;
|
||||
foreach my $k (keys %{$options{tmap}}) {
|
||||
push @{$rmap{$options{tmap}->{$k}}}, $k;
|
||||
}
|
||||
|
||||
Getopt::Long::Configure("bundling");
|
||||
|
||||
# TODO: maybe allow the token map $options{tmap} to be configurable.
|
||||
GetOptions(\%options,
|
||||
"help|h",
|
||||
"debug|d",
|
||||
"insecure|k",
|
||||
"verbose|v",
|
||||
"file|f=s@",
|
||||
'gpg|g:s',
|
||||
);
|
||||
|
||||
if ($options{help}) {
|
||||
my $shortname = basename($0);
|
||||
$shortname =~ s/git-credential-//;
|
||||
|
||||
print <<EOHIPPUS;
|
||||
|
||||
$0 [(-f <authfile>)...] [-g <program>] [-d] [-v] [-k] get
|
||||
|
||||
Version $VERSION by tzz\@lifelogs.com. License: BSD.
|
||||
|
||||
Options:
|
||||
|
||||
-f|--file <authfile>: specify netrc-style files. Files with the .gpg
|
||||
extension will be decrypted by GPG before parsing.
|
||||
Multiple -f arguments are OK. They are processed in
|
||||
order, and the first matching entry found is returned
|
||||
via the credential helper protocol (see below).
|
||||
|
||||
When no -f option is given, .authinfo.gpg, .netrc.gpg,
|
||||
.authinfo, and .netrc files in your home directory are
|
||||
used in this order.
|
||||
|
||||
-g|--gpg <program> : specify the program for GPG. By default, this is the
|
||||
value of gpg.program in the git repository or global
|
||||
option or gpg.
|
||||
|
||||
-k|--insecure : ignore bad file ownership or permissions
|
||||
|
||||
-d|--debug : turn on debugging (developer info)
|
||||
|
||||
-v|--verbose : be more verbose (show files and information found)
|
||||
|
||||
To enable this credential helper:
|
||||
|
||||
git config credential.helper '$shortname -f AUTHFILE1 -f AUTHFILE2'
|
||||
|
||||
(Note that Git will prepend "git-credential-" to the helper name and look for it
|
||||
in the path.)
|
||||
|
||||
...and if you want lots of debugging info:
|
||||
|
||||
git config credential.helper '$shortname -f AUTHFILE -d'
|
||||
|
||||
...or to see the files opened and data found:
|
||||
|
||||
git config credential.helper '$shortname -f AUTHFILE -v'
|
||||
|
||||
Only "get" mode is supported by this credential helper. It opens every
|
||||
<authfile> and looks for the first entry that matches the requested search
|
||||
criteria:
|
||||
|
||||
'port|protocol':
|
||||
The protocol that will be used (e.g., https). (protocol=X)
|
||||
|
||||
'machine|host':
|
||||
The remote hostname for a network credential. (host=X)
|
||||
|
||||
'path':
|
||||
The path with which the credential will be used. (path=X)
|
||||
|
||||
'login|user|username':
|
||||
The credential’s username, if we already have one. (username=X)
|
||||
|
||||
Thus, when we get this query on STDIN:
|
||||
|
||||
host=github.com
|
||||
protocol=https
|
||||
username=tzz
|
||||
|
||||
this credential helper will look for the first entry in every <authfile> that
|
||||
matches
|
||||
|
||||
machine github.com port https login tzz
|
||||
|
||||
OR
|
||||
|
||||
machine github.com protocol https login tzz
|
||||
|
||||
OR... etc. acceptable tokens as listed above. Any unknown tokens are
|
||||
simply ignored.
|
||||
|
||||
Then, the helper will print out whatever tokens it got from the entry, including
|
||||
"password" tokens, mapping back to Git's helper protocol; e.g. "port" is mapped
|
||||
back to "protocol". Any redundant entry tokens (part of the original query) are
|
||||
skipped.
|
||||
|
||||
Again, note that only the first matching entry from all the <authfile>s,
|
||||
processed in the sequence given on the command line, is used.
|
||||
|
||||
Netrc/authinfo tokens can be quoted as 'STRING' or "STRING".
|
||||
|
||||
No caching is performed by this credential helper.
|
||||
|
||||
EOHIPPUS
|
||||
|
||||
exit 0;
|
||||
}
|
||||
|
||||
my $mode = shift @ARGV;
|
||||
|
||||
# Credentials must get a parameter, so die if it's missing.
|
||||
die "Syntax: $0 [(-f <authfile>)...] [-d] get" unless defined $mode;
|
||||
|
||||
# Only support 'get' mode; with any other unsupported ones we just exit.
|
||||
exit 0 unless $mode eq 'get';
|
||||
|
||||
my $files = $options{file};
|
||||
|
||||
# if no files were given, use a predefined list.
|
||||
# note that .gpg files come first
|
||||
unless (scalar @$files) {
|
||||
my @candidates = qw[
|
||||
~/.authinfo.gpg
|
||||
~/.netrc.gpg
|
||||
~/.authinfo
|
||||
~/.netrc
|
||||
];
|
||||
|
||||
$files = $options{file} = [ map { glob $_ } @candidates ];
|
||||
}
|
||||
|
||||
load_config(\%options);
|
||||
|
||||
my $query = read_credential_data_from_stdin();
|
||||
|
||||
FILE:
|
||||
foreach my $file (@$files) {
|
||||
my $gpgmode = $file =~ m/\.gpg$/;
|
||||
unless (-r $file) {
|
||||
log_verbose("Unable to read $file; skipping it");
|
||||
next FILE;
|
||||
}
|
||||
|
||||
# the following check is copied from Net::Netrc, for non-GPG files
|
||||
# OS/2 and Win32 do not handle stat in a way compatible with this check :-(
|
||||
unless ($gpgmode || $options{insecure} ||
|
||||
$^O eq 'os2'
|
||||
|| $^O eq 'MSWin32'
|
||||
|| $^O eq 'MacOS'
|
||||
|| $^O =~ /^cygwin/) {
|
||||
my @stat = stat($file);
|
||||
|
||||
if (@stat) {
|
||||
if ($stat[2] & 077) {
|
||||
log_verbose("Insecure $file (mode=%04o); skipping it",
|
||||
$stat[2] & 07777);
|
||||
next FILE;
|
||||
}
|
||||
|
||||
if ($stat[4] != $<) {
|
||||
log_verbose("Not owner of $file; skipping it");
|
||||
next FILE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my @entries = load_netrc($file, $gpgmode);
|
||||
|
||||
unless (scalar @entries) {
|
||||
if ($!) {
|
||||
log_verbose("Unable to open $file: $!");
|
||||
} else {
|
||||
log_verbose("No netrc entries found in $file");
|
||||
}
|
||||
|
||||
next FILE;
|
||||
}
|
||||
|
||||
my $entry = find_netrc_entry($query, @entries);
|
||||
if ($entry) {
|
||||
print_credential_data($entry, $query);
|
||||
# we're done!
|
||||
last FILE;
|
||||
}
|
||||
}
|
||||
|
||||
exit 0;
|
||||
|
||||
sub load_netrc {
|
||||
my $file = shift @_;
|
||||
my $gpgmode = shift @_;
|
||||
|
||||
my $io;
|
||||
if ($gpgmode) {
|
||||
my @cmd = ($options{'gpg'}, qw(--decrypt), $file);
|
||||
log_verbose("Using GPG to open $file: [@cmd]");
|
||||
open $io, "-|", @cmd;
|
||||
} else {
|
||||
log_verbose("Opening $file...");
|
||||
open $io, '<', $file;
|
||||
}
|
||||
|
||||
# nothing to do if the open failed (we log the error later)
|
||||
return unless $io;
|
||||
|
||||
# Net::Netrc does this, but the functionality is merged with the file
|
||||
# detection logic, so we have to extract just the part we need
|
||||
my @netrc_entries = net_netrc_loader($io);
|
||||
|
||||
# these entries will use the credential helper protocol token names
|
||||
my @entries;
|
||||
|
||||
foreach my $nentry (@netrc_entries) {
|
||||
my %entry;
|
||||
my $num_port;
|
||||
|
||||
if (!defined $nentry->{machine}) {
|
||||
next;
|
||||
}
|
||||
if (defined $nentry->{port} && $nentry->{port} =~ m/^\d+$/) {
|
||||
$num_port = $nentry->{port};
|
||||
delete $nentry->{port};
|
||||
}
|
||||
|
||||
# create the new entry for the credential helper protocol
|
||||
$entry{$options{tmap}->{$_}} = $nentry->{$_} foreach keys %$nentry;
|
||||
|
||||
# for "host X port Y" where Y is an integer (captured by
|
||||
# $num_port above), set the host to "X:Y"
|
||||
if (defined $entry{host} && defined $num_port) {
|
||||
$entry{host} = join(':', $entry{host}, $num_port);
|
||||
}
|
||||
|
||||
push @entries, \%entry;
|
||||
}
|
||||
|
||||
return @entries;
|
||||
}
|
||||
|
||||
sub net_netrc_loader {
|
||||
my $fh = shift @_;
|
||||
my @entries;
|
||||
my ($mach, $macdef, $tok, @tok);
|
||||
|
||||
LINE:
|
||||
while (<$fh>) {
|
||||
undef $macdef if /\A\n\Z/;
|
||||
|
||||
if ($macdef) {
|
||||
next LINE;
|
||||
}
|
||||
|
||||
s/^\s*//;
|
||||
chomp;
|
||||
|
||||
while (length && s/^("((?:[^"]+|\\.)*)"|((?:[^\\\s]+|\\.)*))\s*//) {
|
||||
(my $tok = $+) =~ s/\\(.)/$1/g;
|
||||
push(@tok, $tok);
|
||||
}
|
||||
|
||||
TOKEN:
|
||||
while (@tok) {
|
||||
if ($tok[0] eq "default") {
|
||||
shift(@tok);
|
||||
$mach = { machine => undef };
|
||||
next TOKEN;
|
||||
}
|
||||
|
||||
$tok = shift(@tok);
|
||||
|
||||
if ($tok eq "machine") {
|
||||
my $host = shift @tok;
|
||||
$mach = { machine => $host };
|
||||
push @entries, $mach;
|
||||
} elsif (exists $options{tmap}->{$tok}) {
|
||||
unless ($mach) {
|
||||
log_debug("Skipping token $tok because no machine was given");
|
||||
next TOKEN;
|
||||
}
|
||||
|
||||
my $value = shift @tok;
|
||||
unless (defined $value) {
|
||||
log_debug("Token $tok had no value, skipping it.");
|
||||
next TOKEN;
|
||||
}
|
||||
|
||||
# Following line added by rmerrell to remove '/' escape char in .netrc
|
||||
$value =~ s/\/\\/\\/g;
|
||||
$mach->{$tok} = $value;
|
||||
} elsif ($tok eq "macdef") { # we ignore macros
|
||||
next TOKEN unless $mach;
|
||||
my $value = shift @tok;
|
||||
$macdef = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return @entries;
|
||||
}
|
||||
|
||||
sub read_credential_data_from_stdin {
|
||||
# the query: start with every token with no value
|
||||
my %q = map { $_ => undef } values(%{$options{tmap}});
|
||||
|
||||
while (<STDIN>) {
|
||||
next unless m/^([^=]+)=(.+)/;
|
||||
|
||||
my ($token, $value) = ($1, $2);
|
||||
die "Unknown search token $token" unless exists $q{$token};
|
||||
$q{$token} = $value;
|
||||
log_debug("We were given search token $token and value $value");
|
||||
}
|
||||
|
||||
foreach (sort keys %q) {
|
||||
log_debug("Searching for %s = %s", $_, $q{$_} || '(any value)');
|
||||
}
|
||||
|
||||
return \%q;
|
||||
}
|
||||
|
||||
# takes the search tokens and then a list of entries
|
||||
# each entry is a hash reference
|
||||
sub find_netrc_entry {
|
||||
my $query = shift @_;
|
||||
|
||||
ENTRY:
|
||||
foreach my $entry (@_)
|
||||
{
|
||||
my $entry_text = join ', ', map { "$_=$entry->{$_}" } keys %$entry;
|
||||
foreach my $check (sort keys %$query) {
|
||||
if (!defined $entry->{$check}) {
|
||||
log_debug("OK: entry has no $check token, so any value satisfies check $check");
|
||||
} elsif (defined $query->{$check}) {
|
||||
log_debug("compare %s [%s] to [%s] (entry: %s)",
|
||||
$check,
|
||||
$entry->{$check},
|
||||
$query->{$check},
|
||||
$entry_text);
|
||||
unless ($query->{$check} eq $entry->{$check}) {
|
||||
next ENTRY;
|
||||
}
|
||||
} else {
|
||||
log_debug("OK: any value satisfies check $check");
|
||||
}
|
||||
}
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
# nothing was found
|
||||
return;
|
||||
}
|
||||
|
||||
sub print_credential_data {
|
||||
my $entry = shift @_;
|
||||
my $query = shift @_;
|
||||
|
||||
log_debug("entry has passed all the search checks");
|
||||
TOKEN:
|
||||
foreach my $git_token (sort keys %$entry) {
|
||||
log_debug("looking for useful token $git_token");
|
||||
# don't print unknown (to the credential helper protocol) tokens
|
||||
next TOKEN unless exists $query->{$git_token};
|
||||
|
||||
# don't print things asked in the query (the entry matches them)
|
||||
next TOKEN if defined $query->{$git_token};
|
||||
|
||||
log_debug("FOUND: $git_token=$entry->{$git_token}");
|
||||
printf "%s=%s\n", $git_token, $entry->{$git_token};
|
||||
}
|
||||
}
|
||||
sub load_config {
|
||||
# load settings from git config
|
||||
my $options = shift;
|
||||
# set from command argument, gpg.program option, or default to gpg
|
||||
$options->{'gpg'} //= Git->repository()->config('gpg.program')
|
||||
// 'gpg';
|
||||
log_verbose("using $options{'gpg'} for GPG operations");
|
||||
}
|
||||
sub log_verbose {
|
||||
return unless $options{verbose};
|
||||
printf STDERR @_;
|
||||
printf STDERR "\n";
|
||||
}
|
||||
|
||||
sub log_debug {
|
||||
return unless $options{debug};
|
||||
printf STDERR @_;
|
||||
printf STDERR "\n";
|
||||
}
|
||||
32
third_party/git/contrib/credential/netrc/t-git-credential-netrc.sh
vendored
Executable file
32
third_party/git/contrib/credential/netrc/t-git-credential-netrc.sh
vendored
Executable file
|
|
@ -0,0 +1,32 @@
|
|||
#!/bin/sh
|
||||
(
|
||||
cd ../../../t
|
||||
test_description='git-credential-netrc'
|
||||
. ./test-lib.sh
|
||||
|
||||
if ! test_have_prereq PERL; then
|
||||
skip_all='skipping perl interface tests, perl not available'
|
||||
test_done
|
||||
fi
|
||||
|
||||
perl -MTest::More -e 0 2>/dev/null || {
|
||||
skip_all="Perl Test::More unavailable, skipping test"
|
||||
test_done
|
||||
}
|
||||
|
||||
# set up test repository
|
||||
|
||||
test_expect_success \
|
||||
'set up test repository' \
|
||||
'git config --add gpg.program test.git-config-gpg'
|
||||
|
||||
# The external test will outputs its own plan
|
||||
test_external_has_tap=1
|
||||
|
||||
export PERL5LIB="$GITPERLLIB"
|
||||
test_external \
|
||||
'git-credential-netrc' \
|
||||
perl "$GIT_BUILD_DIR"/contrib/credential/netrc/test.pl
|
||||
|
||||
test_done
|
||||
)
|
||||
2
third_party/git/contrib/credential/netrc/test.command-option-gpg
vendored
Executable file
2
third_party/git/contrib/credential/netrc/test.command-option-gpg
vendored
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
echo machine command-option-gpg login username password password
|
||||
2
third_party/git/contrib/credential/netrc/test.git-config-gpg
vendored
Executable file
2
third_party/git/contrib/credential/netrc/test.git-config-gpg
vendored
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
echo machine git-config-gpg login username password password
|
||||
13
third_party/git/contrib/credential/netrc/test.netrc
vendored
Normal file
13
third_party/git/contrib/credential/netrc/test.netrc
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
machine imap login tzz@lifelogs.com port imaps password letmeknow
|
||||
machine imap login bob port imaps password bobwillknow
|
||||
|
||||
# comment test
|
||||
|
||||
machine imap2 login tzz port 1099 password tzzknow
|
||||
machine imap2 login bob password bobwillknow
|
||||
|
||||
# another command
|
||||
|
||||
machine github.com
|
||||
multilinetoken anothervalue
|
||||
login carol password carolknows
|
||||
0
third_party/git/contrib/credential/netrc/test.netrc.gpg
vendored
Normal file
0
third_party/git/contrib/credential/netrc/test.netrc.gpg
vendored
Normal file
139
third_party/git/contrib/credential/netrc/test.pl
vendored
Executable file
139
third_party/git/contrib/credential/netrc/test.pl
vendored
Executable file
|
|
@ -0,0 +1,139 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use warnings;
|
||||
use strict;
|
||||
use Test::More qw(no_plan);
|
||||
use File::Basename;
|
||||
use File::Spec::Functions qw(:DEFAULT rel2abs);
|
||||
use IPC::Open2;
|
||||
|
||||
BEGIN {
|
||||
# t-git-credential-netrc.sh kicks off our testing, so we have to go
|
||||
# from there.
|
||||
Test::More->builder->current_test(1);
|
||||
}
|
||||
|
||||
my @global_credential_args = @ARGV;
|
||||
my $scriptDir = dirname rel2abs $0;
|
||||
my ($netrc, $netrcGpg, $gcNetrc) = map { catfile $scriptDir, $_; }
|
||||
qw(test.netrc
|
||||
test.netrc.gpg
|
||||
git-credential-netrc);
|
||||
local $ENV{PATH} = join ':'
|
||||
, $scriptDir
|
||||
, $ENV{PATH}
|
||||
? $ENV{PATH}
|
||||
: ();
|
||||
|
||||
diag "Testing insecure file, nothing should be found\n";
|
||||
chmod 0644, $netrc;
|
||||
my $cred = run_credential(['-f', $netrc, 'get'],
|
||||
{ host => 'github.com' });
|
||||
|
||||
ok(scalar keys %$cred == 0, "Got 0 keys from insecure file");
|
||||
|
||||
diag "Testing missing file, nothing should be found\n";
|
||||
chmod 0644, $netrc;
|
||||
$cred = run_credential(['-f', '///nosuchfile///', 'get'],
|
||||
{ host => 'github.com' });
|
||||
|
||||
ok(scalar keys %$cred == 0, "Got 0 keys from missing file");
|
||||
|
||||
chmod 0600, $netrc;
|
||||
|
||||
diag "Testing with invalid data\n";
|
||||
$cred = run_credential(['-f', $netrc, 'get'],
|
||||
"bad data");
|
||||
ok(scalar keys %$cred == 4, "Got first found keys with bad data");
|
||||
|
||||
diag "Testing netrc file for a missing corovamilkbar entry\n";
|
||||
$cred = run_credential(['-f', $netrc, 'get'],
|
||||
{ host => 'corovamilkbar' });
|
||||
|
||||
ok(scalar keys %$cred == 0, "Got no corovamilkbar keys");
|
||||
|
||||
diag "Testing netrc file for a github.com entry\n";
|
||||
$cred = run_credential(['-f', $netrc, 'get'],
|
||||
{ host => 'github.com' });
|
||||
|
||||
ok(scalar keys %$cred == 2, "Got 2 Github keys");
|
||||
|
||||
is($cred->{password}, 'carolknows', "Got correct Github password");
|
||||
is($cred->{username}, 'carol', "Got correct Github username");
|
||||
|
||||
diag "Testing netrc file for a username-specific entry\n";
|
||||
$cred = run_credential(['-f', $netrc, 'get'],
|
||||
{ host => 'imap', username => 'bob' });
|
||||
|
||||
ok(scalar keys %$cred == 2, "Got 2 username-specific keys");
|
||||
|
||||
is($cred->{password}, 'bobwillknow', "Got correct user-specific password");
|
||||
is($cred->{protocol}, 'imaps', "Got correct user-specific protocol");
|
||||
|
||||
diag "Testing netrc file for a host:port-specific entry\n";
|
||||
$cred = run_credential(['-f', $netrc, 'get'],
|
||||
{ host => 'imap2:1099' });
|
||||
|
||||
ok(scalar keys %$cred == 2, "Got 2 host:port-specific keys");
|
||||
|
||||
is($cred->{password}, 'tzzknow', "Got correct host:port-specific password");
|
||||
is($cred->{username}, 'tzz', "Got correct host:port-specific username");
|
||||
|
||||
diag "Testing netrc file that 'host:port kills host' entry\n";
|
||||
$cred = run_credential(['-f', $netrc, 'get'],
|
||||
{ host => 'imap2' });
|
||||
|
||||
ok(scalar keys %$cred == 2, "Got 2 'host:port kills host' keys");
|
||||
|
||||
is($cred->{password}, 'bobwillknow', "Got correct 'host:port kills host' password");
|
||||
is($cred->{username}, 'bob', "Got correct 'host:port kills host' username");
|
||||
|
||||
diag 'Testing netrc file decryption by git config gpg.program setting\n';
|
||||
$cred = run_credential( ['-f', $netrcGpg, 'get']
|
||||
, { host => 'git-config-gpg' }
|
||||
);
|
||||
|
||||
ok(scalar keys %$cred == 2, 'Got keys decrypted by git config option');
|
||||
|
||||
diag 'Testing netrc file decryption by gpg option\n';
|
||||
$cred = run_credential( ['-f', $netrcGpg, '-g', 'test.command-option-gpg', 'get']
|
||||
, { host => 'command-option-gpg' }
|
||||
);
|
||||
|
||||
ok(scalar keys %$cred == 2, 'Got keys decrypted by command option');
|
||||
|
||||
my $is_passing = eval { Test::More->is_passing };
|
||||
exit($is_passing ? 0 : 1) unless $@ =~ /Can't locate object method/;
|
||||
|
||||
sub run_credential
|
||||
{
|
||||
my $args = shift @_;
|
||||
my $data = shift @_;
|
||||
my $pid = open2(my $chld_out, my $chld_in,
|
||||
$gcNetrc, @global_credential_args,
|
||||
@$args);
|
||||
|
||||
die "Couldn't open pipe to netrc credential helper: $!" unless $pid;
|
||||
|
||||
if (ref $data eq 'HASH')
|
||||
{
|
||||
print $chld_in "$_=$data->{$_}\n" foreach sort keys %$data;
|
||||
}
|
||||
else
|
||||
{
|
||||
print $chld_in "$data\n";
|
||||
}
|
||||
|
||||
close $chld_in;
|
||||
my %ret;
|
||||
|
||||
while (<$chld_out>)
|
||||
{
|
||||
chomp;
|
||||
next unless m/^([^=]+)=(.+)/;
|
||||
|
||||
$ret{$1} = $2;
|
||||
}
|
||||
|
||||
return \%ret;
|
||||
}
|
||||
1
third_party/git/contrib/credential/osxkeychain/.gitignore
vendored
Normal file
1
third_party/git/contrib/credential/osxkeychain/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
git-credential-osxkeychain
|
||||
17
third_party/git/contrib/credential/osxkeychain/Makefile
vendored
Normal file
17
third_party/git/contrib/credential/osxkeychain/Makefile
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
all:: git-credential-osxkeychain
|
||||
|
||||
CC = gcc
|
||||
RM = rm -f
|
||||
CFLAGS = -g -O2 -Wall
|
||||
|
||||
-include ../../../config.mak.autogen
|
||||
-include ../../../config.mak
|
||||
|
||||
git-credential-osxkeychain: git-credential-osxkeychain.o
|
||||
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) -Wl,-framework -Wl,Security
|
||||
|
||||
git-credential-osxkeychain.o: git-credential-osxkeychain.c
|
||||
$(CC) -c $(CFLAGS) $<
|
||||
|
||||
clean:
|
||||
$(RM) git-credential-osxkeychain git-credential-osxkeychain.o
|
||||
183
third_party/git/contrib/credential/osxkeychain/git-credential-osxkeychain.c
vendored
Normal file
183
third_party/git/contrib/credential/osxkeychain/git-credential-osxkeychain.c
vendored
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <Security/Security.h>
|
||||
|
||||
static SecProtocolType protocol;
|
||||
static char *host;
|
||||
static char *path;
|
||||
static char *username;
|
||||
static char *password;
|
||||
static UInt16 port;
|
||||
|
||||
static void die(const char *err, ...)
|
||||
{
|
||||
char msg[4096];
|
||||
va_list params;
|
||||
va_start(params, err);
|
||||
vsnprintf(msg, sizeof(msg), err, params);
|
||||
fprintf(stderr, "%s\n", msg);
|
||||
va_end(params);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void *xstrdup(const char *s1)
|
||||
{
|
||||
void *ret = strdup(s1);
|
||||
if (!ret)
|
||||
die("Out of memory");
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define KEYCHAIN_ITEM(x) (x ? strlen(x) : 0), x
|
||||
#define KEYCHAIN_ARGS \
|
||||
NULL, /* default keychain */ \
|
||||
KEYCHAIN_ITEM(host), \
|
||||
0, NULL, /* account domain */ \
|
||||
KEYCHAIN_ITEM(username), \
|
||||
KEYCHAIN_ITEM(path), \
|
||||
port, \
|
||||
protocol, \
|
||||
kSecAuthenticationTypeDefault
|
||||
|
||||
static void write_item(const char *what, const char *buf, int len)
|
||||
{
|
||||
printf("%s=", what);
|
||||
fwrite(buf, 1, len, stdout);
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
static void find_username_in_item(SecKeychainItemRef item)
|
||||
{
|
||||
SecKeychainAttributeList list;
|
||||
SecKeychainAttribute attr;
|
||||
|
||||
list.count = 1;
|
||||
list.attr = &attr;
|
||||
attr.tag = kSecAccountItemAttr;
|
||||
|
||||
if (SecKeychainItemCopyContent(item, NULL, &list, NULL, NULL))
|
||||
return;
|
||||
|
||||
write_item("username", attr.data, attr.length);
|
||||
SecKeychainItemFreeContent(&list, NULL);
|
||||
}
|
||||
|
||||
static void find_internet_password(void)
|
||||
{
|
||||
void *buf;
|
||||
UInt32 len;
|
||||
SecKeychainItemRef item;
|
||||
|
||||
if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, &len, &buf, &item))
|
||||
return;
|
||||
|
||||
write_item("password", buf, len);
|
||||
if (!username)
|
||||
find_username_in_item(item);
|
||||
|
||||
SecKeychainItemFreeContent(NULL, buf);
|
||||
}
|
||||
|
||||
static void delete_internet_password(void)
|
||||
{
|
||||
SecKeychainItemRef item;
|
||||
|
||||
/*
|
||||
* Require at least a protocol and host for removal, which is what git
|
||||
* will give us; if you want to do something more fancy, use the
|
||||
* Keychain manager.
|
||||
*/
|
||||
if (!protocol || !host)
|
||||
return;
|
||||
|
||||
if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, 0, NULL, &item))
|
||||
return;
|
||||
|
||||
SecKeychainItemDelete(item);
|
||||
}
|
||||
|
||||
static void add_internet_password(void)
|
||||
{
|
||||
/* Only store complete credentials */
|
||||
if (!protocol || !host || !username || !password)
|
||||
return;
|
||||
|
||||
if (SecKeychainAddInternetPassword(
|
||||
KEYCHAIN_ARGS,
|
||||
KEYCHAIN_ITEM(password),
|
||||
NULL))
|
||||
return;
|
||||
}
|
||||
|
||||
static void read_credential(void)
|
||||
{
|
||||
char buf[1024];
|
||||
|
||||
while (fgets(buf, sizeof(buf), stdin)) {
|
||||
char *v;
|
||||
|
||||
if (!strcmp(buf, "\n"))
|
||||
break;
|
||||
buf[strlen(buf)-1] = '\0';
|
||||
|
||||
v = strchr(buf, '=');
|
||||
if (!v)
|
||||
die("bad input: %s", buf);
|
||||
*v++ = '\0';
|
||||
|
||||
if (!strcmp(buf, "protocol")) {
|
||||
if (!strcmp(v, "imap"))
|
||||
protocol = kSecProtocolTypeIMAP;
|
||||
else if (!strcmp(v, "imaps"))
|
||||
protocol = kSecProtocolTypeIMAPS;
|
||||
else if (!strcmp(v, "ftp"))
|
||||
protocol = kSecProtocolTypeFTP;
|
||||
else if (!strcmp(v, "ftps"))
|
||||
protocol = kSecProtocolTypeFTPS;
|
||||
else if (!strcmp(v, "https"))
|
||||
protocol = kSecProtocolTypeHTTPS;
|
||||
else if (!strcmp(v, "http"))
|
||||
protocol = kSecProtocolTypeHTTP;
|
||||
else if (!strcmp(v, "smtp"))
|
||||
protocol = kSecProtocolTypeSMTP;
|
||||
else /* we don't yet handle other protocols */
|
||||
exit(0);
|
||||
}
|
||||
else if (!strcmp(buf, "host")) {
|
||||
char *colon = strchr(v, ':');
|
||||
if (colon) {
|
||||
*colon++ = '\0';
|
||||
port = atoi(colon);
|
||||
}
|
||||
host = xstrdup(v);
|
||||
}
|
||||
else if (!strcmp(buf, "path"))
|
||||
path = xstrdup(v);
|
||||
else if (!strcmp(buf, "username"))
|
||||
username = xstrdup(v);
|
||||
else if (!strcmp(buf, "password"))
|
||||
password = xstrdup(v);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
const char *usage =
|
||||
"usage: git credential-osxkeychain <get|store|erase>";
|
||||
|
||||
if (!argv[1])
|
||||
die(usage);
|
||||
|
||||
read_credential();
|
||||
|
||||
if (!strcmp(argv[1], "get"))
|
||||
find_internet_password();
|
||||
else if (!strcmp(argv[1], "store"))
|
||||
add_internet_password();
|
||||
else if (!strcmp(argv[1], "erase"))
|
||||
delete_internet_password();
|
||||
/* otherwise, ignore unknown action */
|
||||
|
||||
return 0;
|
||||
}
|
||||
22
third_party/git/contrib/credential/wincred/Makefile
vendored
Normal file
22
third_party/git/contrib/credential/wincred/Makefile
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
all: git-credential-wincred.exe
|
||||
|
||||
-include ../../../config.mak.autogen
|
||||
-include ../../../config.mak
|
||||
|
||||
CC ?= gcc
|
||||
RM ?= rm -f
|
||||
CFLAGS ?= -O2 -Wall
|
||||
|
||||
prefix ?= /usr/local
|
||||
libexecdir ?= $(prefix)/libexec/git-core
|
||||
|
||||
INSTALL ?= install
|
||||
|
||||
git-credential-wincred.exe : git-credential-wincred.c
|
||||
$(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@
|
||||
|
||||
install: git-credential-wincred.exe
|
||||
$(INSTALL) -m 755 $^ $(libexecdir)
|
||||
|
||||
clean:
|
||||
$(RM) git-credential-wincred.exe
|
||||
327
third_party/git/contrib/credential/wincred/git-credential-wincred.c
vendored
Normal file
327
third_party/git/contrib/credential/wincred/git-credential-wincred.c
vendored
Normal file
|
|
@ -0,0 +1,327 @@
|
|||
/*
|
||||
* A git credential helper that interface with Windows' Credential Manager
|
||||
*
|
||||
*/
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
/* common helpers */
|
||||
|
||||
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
|
||||
|
||||
static void die(const char *err, ...)
|
||||
{
|
||||
char msg[4096];
|
||||
va_list params;
|
||||
va_start(params, err);
|
||||
vsnprintf(msg, sizeof(msg), err, params);
|
||||
fprintf(stderr, "%s\n", msg);
|
||||
va_end(params);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void *xmalloc(size_t size)
|
||||
{
|
||||
void *ret = malloc(size);
|
||||
if (!ret && !size)
|
||||
ret = malloc(1);
|
||||
if (!ret)
|
||||
die("Out of memory");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* MinGW doesn't have wincred.h, so we need to define stuff */
|
||||
|
||||
typedef struct _CREDENTIAL_ATTRIBUTEW {
|
||||
LPWSTR Keyword;
|
||||
DWORD Flags;
|
||||
DWORD ValueSize;
|
||||
LPBYTE Value;
|
||||
} CREDENTIAL_ATTRIBUTEW, *PCREDENTIAL_ATTRIBUTEW;
|
||||
|
||||
typedef struct _CREDENTIALW {
|
||||
DWORD Flags;
|
||||
DWORD Type;
|
||||
LPWSTR TargetName;
|
||||
LPWSTR Comment;
|
||||
FILETIME LastWritten;
|
||||
DWORD CredentialBlobSize;
|
||||
LPBYTE CredentialBlob;
|
||||
DWORD Persist;
|
||||
DWORD AttributeCount;
|
||||
PCREDENTIAL_ATTRIBUTEW Attributes;
|
||||
LPWSTR TargetAlias;
|
||||
LPWSTR UserName;
|
||||
} CREDENTIALW, *PCREDENTIALW;
|
||||
|
||||
#define CRED_TYPE_GENERIC 1
|
||||
#define CRED_PERSIST_LOCAL_MACHINE 2
|
||||
#define CRED_MAX_ATTRIBUTES 64
|
||||
|
||||
typedef BOOL (WINAPI *CredWriteWT)(PCREDENTIALW, DWORD);
|
||||
typedef BOOL (WINAPI *CredEnumerateWT)(LPCWSTR, DWORD, DWORD *,
|
||||
PCREDENTIALW **);
|
||||
typedef VOID (WINAPI *CredFreeT)(PVOID);
|
||||
typedef BOOL (WINAPI *CredDeleteWT)(LPCWSTR, DWORD, DWORD);
|
||||
|
||||
static HMODULE advapi;
|
||||
static CredWriteWT CredWriteW;
|
||||
static CredEnumerateWT CredEnumerateW;
|
||||
static CredFreeT CredFree;
|
||||
static CredDeleteWT CredDeleteW;
|
||||
|
||||
static void load_cred_funcs(void)
|
||||
{
|
||||
/* load DLLs */
|
||||
advapi = LoadLibraryExA("advapi32.dll", NULL,
|
||||
LOAD_LIBRARY_SEARCH_SYSTEM32);
|
||||
if (!advapi)
|
||||
die("failed to load advapi32.dll");
|
||||
|
||||
/* get function pointers */
|
||||
CredWriteW = (CredWriteWT)GetProcAddress(advapi, "CredWriteW");
|
||||
CredEnumerateW = (CredEnumerateWT)GetProcAddress(advapi,
|
||||
"CredEnumerateW");
|
||||
CredFree = (CredFreeT)GetProcAddress(advapi, "CredFree");
|
||||
CredDeleteW = (CredDeleteWT)GetProcAddress(advapi, "CredDeleteW");
|
||||
if (!CredWriteW || !CredEnumerateW || !CredFree || !CredDeleteW)
|
||||
die("failed to load functions");
|
||||
}
|
||||
|
||||
static WCHAR *wusername, *password, *protocol, *host, *path, target[1024];
|
||||
|
||||
static void write_item(const char *what, LPCWSTR wbuf, int wlen)
|
||||
{
|
||||
char *buf;
|
||||
|
||||
if (!wbuf || !wlen) {
|
||||
printf("%s=\n", what);
|
||||
return;
|
||||
}
|
||||
|
||||
int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, wlen, NULL, 0, NULL,
|
||||
FALSE);
|
||||
buf = xmalloc(len);
|
||||
|
||||
if (!WideCharToMultiByte(CP_UTF8, 0, wbuf, wlen, buf, len, NULL, FALSE))
|
||||
die("WideCharToMultiByte failed!");
|
||||
|
||||
printf("%s=", what);
|
||||
fwrite(buf, 1, len, stdout);
|
||||
putchar('\n');
|
||||
free(buf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Match an (optional) expected string and a delimiter in the target string,
|
||||
* consuming the matched text by updating the target pointer.
|
||||
*/
|
||||
|
||||
static LPCWSTR wcsstr_last(LPCWSTR str, LPCWSTR find)
|
||||
{
|
||||
LPCWSTR res = NULL, pos;
|
||||
for (pos = wcsstr(str, find); pos; pos = wcsstr(pos + 1, find))
|
||||
res = pos;
|
||||
return res;
|
||||
}
|
||||
|
||||
static int match_part_with_last(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim, int last)
|
||||
{
|
||||
LPCWSTR delim_pos, start = *ptarget;
|
||||
int len;
|
||||
|
||||
/* find start of delimiter (or end-of-string if delim is empty) */
|
||||
if (*delim)
|
||||
delim_pos = last ? wcsstr_last(start, delim) : wcsstr(start, delim);
|
||||
else
|
||||
delim_pos = start + wcslen(start);
|
||||
|
||||
/*
|
||||
* match text up to delimiter, or end of string (e.g. the '/' after
|
||||
* host is optional if not followed by a path)
|
||||
*/
|
||||
if (delim_pos)
|
||||
len = delim_pos - start;
|
||||
else
|
||||
len = wcslen(start);
|
||||
|
||||
/* update ptarget if we either found a delimiter or need a match */
|
||||
if (delim_pos || want)
|
||||
*ptarget = delim_pos ? delim_pos + wcslen(delim) : start + len;
|
||||
|
||||
return !want || (!wcsncmp(want, start, len) && !want[len]);
|
||||
}
|
||||
|
||||
static int match_part(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim)
|
||||
{
|
||||
return match_part_with_last(ptarget, want, delim, 0);
|
||||
}
|
||||
|
||||
static int match_part_last(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim)
|
||||
{
|
||||
return match_part_with_last(ptarget, want, delim, 1);
|
||||
}
|
||||
|
||||
static int match_cred(const CREDENTIALW *cred)
|
||||
{
|
||||
LPCWSTR target = cred->TargetName;
|
||||
if (wusername && wcscmp(wusername, cred->UserName ? cred->UserName : L""))
|
||||
return 0;
|
||||
|
||||
return match_part(&target, L"git", L":") &&
|
||||
match_part(&target, protocol, L"://") &&
|
||||
match_part_last(&target, wusername, L"@") &&
|
||||
match_part(&target, host, L"/") &&
|
||||
match_part(&target, path, L"");
|
||||
}
|
||||
|
||||
static void get_credential(void)
|
||||
{
|
||||
CREDENTIALW **creds;
|
||||
DWORD num_creds;
|
||||
int i;
|
||||
|
||||
if (!CredEnumerateW(L"git:*", 0, &num_creds, &creds))
|
||||
return;
|
||||
|
||||
/* search for the first credential that matches username */
|
||||
for (i = 0; i < num_creds; ++i)
|
||||
if (match_cred(creds[i])) {
|
||||
write_item("username", creds[i]->UserName,
|
||||
creds[i]->UserName ? wcslen(creds[i]->UserName) : 0);
|
||||
write_item("password",
|
||||
(LPCWSTR)creds[i]->CredentialBlob,
|
||||
creds[i]->CredentialBlobSize / sizeof(WCHAR));
|
||||
break;
|
||||
}
|
||||
|
||||
CredFree(creds);
|
||||
}
|
||||
|
||||
static void store_credential(void)
|
||||
{
|
||||
CREDENTIALW cred;
|
||||
|
||||
if (!wusername || !password)
|
||||
return;
|
||||
|
||||
cred.Flags = 0;
|
||||
cred.Type = CRED_TYPE_GENERIC;
|
||||
cred.TargetName = target;
|
||||
cred.Comment = L"saved by git-credential-wincred";
|
||||
cred.CredentialBlobSize = (wcslen(password)) * sizeof(WCHAR);
|
||||
cred.CredentialBlob = (LPVOID)password;
|
||||
cred.Persist = CRED_PERSIST_LOCAL_MACHINE;
|
||||
cred.AttributeCount = 0;
|
||||
cred.Attributes = NULL;
|
||||
cred.TargetAlias = NULL;
|
||||
cred.UserName = wusername;
|
||||
|
||||
if (!CredWriteW(&cred, 0))
|
||||
die("CredWrite failed");
|
||||
}
|
||||
|
||||
static void erase_credential(void)
|
||||
{
|
||||
CREDENTIALW **creds;
|
||||
DWORD num_creds;
|
||||
int i;
|
||||
|
||||
if (!CredEnumerateW(L"git:*", 0, &num_creds, &creds))
|
||||
return;
|
||||
|
||||
for (i = 0; i < num_creds; ++i) {
|
||||
if (match_cred(creds[i]))
|
||||
CredDeleteW(creds[i]->TargetName, creds[i]->Type, 0);
|
||||
}
|
||||
|
||||
CredFree(creds);
|
||||
}
|
||||
|
||||
static WCHAR *utf8_to_utf16_dup(const char *str)
|
||||
{
|
||||
int wlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
|
||||
WCHAR *wstr = xmalloc(sizeof(WCHAR) * wlen);
|
||||
MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, wlen);
|
||||
return wstr;
|
||||
}
|
||||
|
||||
static void read_credential(void)
|
||||
{
|
||||
char buf[1024];
|
||||
|
||||
while (fgets(buf, sizeof(buf), stdin)) {
|
||||
char *v;
|
||||
int len = strlen(buf);
|
||||
/* strip trailing CR / LF */
|
||||
while (len && strchr("\r\n", buf[len - 1]))
|
||||
buf[--len] = 0;
|
||||
|
||||
if (!*buf)
|
||||
break;
|
||||
|
||||
v = strchr(buf, '=');
|
||||
if (!v)
|
||||
die("bad input: %s", buf);
|
||||
*v++ = '\0';
|
||||
|
||||
if (!strcmp(buf, "protocol"))
|
||||
protocol = utf8_to_utf16_dup(v);
|
||||
else if (!strcmp(buf, "host"))
|
||||
host = utf8_to_utf16_dup(v);
|
||||
else if (!strcmp(buf, "path"))
|
||||
path = utf8_to_utf16_dup(v);
|
||||
else if (!strcmp(buf, "username")) {
|
||||
wusername = utf8_to_utf16_dup(v);
|
||||
} else if (!strcmp(buf, "password"))
|
||||
password = utf8_to_utf16_dup(v);
|
||||
else
|
||||
die("unrecognized input");
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
const char *usage =
|
||||
"usage: git credential-wincred <get|store|erase>\n";
|
||||
|
||||
if (!argv[1])
|
||||
die(usage);
|
||||
|
||||
/* git use binary pipes to avoid CRLF-issues */
|
||||
_setmode(_fileno(stdin), _O_BINARY);
|
||||
_setmode(_fileno(stdout), _O_BINARY);
|
||||
|
||||
read_credential();
|
||||
|
||||
load_cred_funcs();
|
||||
|
||||
if (!protocol || !(host || path))
|
||||
return 0;
|
||||
|
||||
/* prepare 'target', the unique key for the credential */
|
||||
wcscpy(target, L"git:");
|
||||
wcsncat(target, protocol, ARRAY_SIZE(target));
|
||||
wcsncat(target, L"://", ARRAY_SIZE(target));
|
||||
if (wusername) {
|
||||
wcsncat(target, wusername, ARRAY_SIZE(target));
|
||||
wcsncat(target, L"@", ARRAY_SIZE(target));
|
||||
}
|
||||
if (host)
|
||||
wcsncat(target, host, ARRAY_SIZE(target));
|
||||
if (path) {
|
||||
wcsncat(target, L"/", ARRAY_SIZE(target));
|
||||
wcsncat(target, path, ARRAY_SIZE(target));
|
||||
}
|
||||
|
||||
if (!strcmp(argv[1], "get"))
|
||||
get_credential();
|
||||
else if (!strcmp(argv[1], "store"))
|
||||
store_credential();
|
||||
else if (!strcmp(argv[1], "erase"))
|
||||
erase_credential();
|
||||
/* otherwise, ignore unknown action */
|
||||
return 0;
|
||||
}
|
||||
2
third_party/git/contrib/diff-highlight/.gitignore
vendored
Normal file
2
third_party/git/contrib/diff-highlight/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
shebang.perl
|
||||
diff-highlight
|
||||
285
third_party/git/contrib/diff-highlight/DiffHighlight.pm
vendored
Normal file
285
third_party/git/contrib/diff-highlight/DiffHighlight.pm
vendored
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
package DiffHighlight;
|
||||
|
||||
use 5.008;
|
||||
use warnings FATAL => 'all';
|
||||
use strict;
|
||||
|
||||
# Use the correct value for both UNIX and Windows (/dev/null vs nul)
|
||||
use File::Spec;
|
||||
|
||||
my $NULL = File::Spec->devnull();
|
||||
|
||||
# Highlight by reversing foreground and background. You could do
|
||||
# other things like bold or underline if you prefer.
|
||||
my @OLD_HIGHLIGHT = (
|
||||
color_config('color.diff-highlight.oldnormal'),
|
||||
color_config('color.diff-highlight.oldhighlight', "\x1b[7m"),
|
||||
color_config('color.diff-highlight.oldreset', "\x1b[27m")
|
||||
);
|
||||
my @NEW_HIGHLIGHT = (
|
||||
color_config('color.diff-highlight.newnormal', $OLD_HIGHLIGHT[0]),
|
||||
color_config('color.diff-highlight.newhighlight', $OLD_HIGHLIGHT[1]),
|
||||
color_config('color.diff-highlight.newreset', $OLD_HIGHLIGHT[2])
|
||||
);
|
||||
|
||||
my $RESET = "\x1b[m";
|
||||
my $COLOR = qr/\x1b\[[0-9;]*m/;
|
||||
my $BORING = qr/$COLOR|\s/;
|
||||
|
||||
my @removed;
|
||||
my @added;
|
||||
my $in_hunk;
|
||||
my $graph_indent = 0;
|
||||
|
||||
our $line_cb = sub { print @_ };
|
||||
our $flush_cb = sub { local $| = 1 };
|
||||
|
||||
# Count the visible width of a string, excluding any terminal color sequences.
|
||||
sub visible_width {
|
||||
local $_ = shift;
|
||||
my $ret = 0;
|
||||
while (length) {
|
||||
if (s/^$COLOR//) {
|
||||
# skip colors
|
||||
} elsif (s/^.//) {
|
||||
$ret++;
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
# Return a substring of $str, omitting $len visible characters from the
|
||||
# beginning, where terminal color sequences do not count as visible.
|
||||
sub visible_substr {
|
||||
my ($str, $len) = @_;
|
||||
while ($len > 0) {
|
||||
if ($str =~ s/^$COLOR//) {
|
||||
next
|
||||
}
|
||||
$str =~ s/^.//;
|
||||
$len--;
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
sub handle_line {
|
||||
my $orig = shift;
|
||||
local $_ = $orig;
|
||||
|
||||
# match a graph line that begins a commit
|
||||
if (/^(?:$COLOR?\|$COLOR?[ ])* # zero or more leading "|" with space
|
||||
$COLOR?\*$COLOR?[ ] # a "*" with its trailing space
|
||||
(?:$COLOR?\|$COLOR?[ ])* # zero or more trailing "|"
|
||||
[ ]* # trailing whitespace for merges
|
||||
/x) {
|
||||
my $graph_prefix = $&;
|
||||
|
||||
# We must flush before setting graph indent, since the
|
||||
# new commit may be indented differently from what we
|
||||
# queued.
|
||||
flush();
|
||||
$graph_indent = visible_width($graph_prefix);
|
||||
|
||||
} elsif ($graph_indent) {
|
||||
if (length($_) < $graph_indent) {
|
||||
$graph_indent = 0;
|
||||
} else {
|
||||
$_ = visible_substr($_, $graph_indent);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$in_hunk) {
|
||||
$line_cb->($orig);
|
||||
$in_hunk = /^$COLOR*\@\@ /;
|
||||
}
|
||||
elsif (/^$COLOR*-/) {
|
||||
push @removed, $orig;
|
||||
}
|
||||
elsif (/^$COLOR*\+/) {
|
||||
push @added, $orig;
|
||||
}
|
||||
else {
|
||||
flush();
|
||||
$line_cb->($orig);
|
||||
$in_hunk = /^$COLOR*[\@ ]/;
|
||||
}
|
||||
|
||||
# Most of the time there is enough output to keep things streaming,
|
||||
# but for something like "git log -Sfoo", you can get one early
|
||||
# commit and then many seconds of nothing. We want to show
|
||||
# that one commit as soon as possible.
|
||||
#
|
||||
# Since we can receive arbitrary input, there's no optimal
|
||||
# place to flush. Flushing on a blank line is a heuristic that
|
||||
# happens to match git-log output.
|
||||
if (!length) {
|
||||
$flush_cb->();
|
||||
}
|
||||
}
|
||||
|
||||
sub flush {
|
||||
# Flush any queued hunk (this can happen when there is no trailing
|
||||
# context in the final diff of the input).
|
||||
show_hunk(\@removed, \@added);
|
||||
@removed = ();
|
||||
@added = ();
|
||||
}
|
||||
|
||||
sub highlight_stdin {
|
||||
while (<STDIN>) {
|
||||
handle_line($_);
|
||||
}
|
||||
flush();
|
||||
}
|
||||
|
||||
# Ideally we would feed the default as a human-readable color to
|
||||
# git-config as the fallback value. But diff-highlight does
|
||||
# not otherwise depend on git at all, and there are reports
|
||||
# of it being used in other settings. Let's handle our own
|
||||
# fallback, which means we will work even if git can't be run.
|
||||
sub color_config {
|
||||
my ($key, $default) = @_;
|
||||
my $s = `git config --get-color $key 2>$NULL`;
|
||||
return length($s) ? $s : $default;
|
||||
}
|
||||
|
||||
sub show_hunk {
|
||||
my ($a, $b) = @_;
|
||||
|
||||
# If one side is empty, then there is nothing to compare or highlight.
|
||||
if (!@$a || !@$b) {
|
||||
$line_cb->(@$a, @$b);
|
||||
return;
|
||||
}
|
||||
|
||||
# If we have mismatched numbers of lines on each side, we could try to
|
||||
# be clever and match up similar lines. But for now we are simple and
|
||||
# stupid, and only handle multi-line hunks that remove and add the same
|
||||
# number of lines.
|
||||
if (@$a != @$b) {
|
||||
$line_cb->(@$a, @$b);
|
||||
return;
|
||||
}
|
||||
|
||||
my @queue;
|
||||
for (my $i = 0; $i < @$a; $i++) {
|
||||
my ($rm, $add) = highlight_pair($a->[$i], $b->[$i]);
|
||||
$line_cb->($rm);
|
||||
push @queue, $add;
|
||||
}
|
||||
$line_cb->(@queue);
|
||||
}
|
||||
|
||||
sub highlight_pair {
|
||||
my @a = split_line(shift);
|
||||
my @b = split_line(shift);
|
||||
|
||||
# Find common prefix, taking care to skip any ansi
|
||||
# color codes.
|
||||
my $seen_plusminus;
|
||||
my ($pa, $pb) = (0, 0);
|
||||
while ($pa < @a && $pb < @b) {
|
||||
if ($a[$pa] =~ /$COLOR/) {
|
||||
$pa++;
|
||||
}
|
||||
elsif ($b[$pb] =~ /$COLOR/) {
|
||||
$pb++;
|
||||
}
|
||||
elsif ($a[$pa] eq $b[$pb]) {
|
||||
$pa++;
|
||||
$pb++;
|
||||
}
|
||||
elsif (!$seen_plusminus && $a[$pa] eq '-' && $b[$pb] eq '+') {
|
||||
$seen_plusminus = 1;
|
||||
$pa++;
|
||||
$pb++;
|
||||
}
|
||||
else {
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
# Find common suffix, ignoring colors.
|
||||
my ($sa, $sb) = ($#a, $#b);
|
||||
while ($sa >= $pa && $sb >= $pb) {
|
||||
if ($a[$sa] =~ /$COLOR/) {
|
||||
$sa--;
|
||||
}
|
||||
elsif ($b[$sb] =~ /$COLOR/) {
|
||||
$sb--;
|
||||
}
|
||||
elsif ($a[$sa] eq $b[$sb]) {
|
||||
$sa--;
|
||||
$sb--;
|
||||
}
|
||||
else {
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_pair_interesting(\@a, $pa, $sa, \@b, $pb, $sb)) {
|
||||
return highlight_line(\@a, $pa, $sa, \@OLD_HIGHLIGHT),
|
||||
highlight_line(\@b, $pb, $sb, \@NEW_HIGHLIGHT);
|
||||
}
|
||||
else {
|
||||
return join('', @a),
|
||||
join('', @b);
|
||||
}
|
||||
}
|
||||
|
||||
# we split either by $COLOR or by character. This has the side effect of
|
||||
# leaving in graph cruft. It works because the graph cruft does not contain "-"
|
||||
# or "+"
|
||||
sub split_line {
|
||||
local $_ = shift;
|
||||
return utf8::decode($_) ?
|
||||
map { utf8::encode($_); $_ }
|
||||
map { /$COLOR/ ? $_ : (split //) }
|
||||
split /($COLOR+)/ :
|
||||
map { /$COLOR/ ? $_ : (split //) }
|
||||
split /($COLOR+)/;
|
||||
}
|
||||
|
||||
sub highlight_line {
|
||||
my ($line, $prefix, $suffix, $theme) = @_;
|
||||
|
||||
my $start = join('', @{$line}[0..($prefix-1)]);
|
||||
my $mid = join('', @{$line}[$prefix..$suffix]);
|
||||
my $end = join('', @{$line}[($suffix+1)..$#$line]);
|
||||
|
||||
# If we have a "normal" color specified, then take over the whole line.
|
||||
# Otherwise, we try to just manipulate the highlighted bits.
|
||||
if (defined $theme->[0]) {
|
||||
s/$COLOR//g for ($start, $mid, $end);
|
||||
chomp $end;
|
||||
return join('',
|
||||
$theme->[0], $start, $RESET,
|
||||
$theme->[1], $mid, $RESET,
|
||||
$theme->[0], $end, $RESET,
|
||||
"\n"
|
||||
);
|
||||
} else {
|
||||
return join('',
|
||||
$start,
|
||||
$theme->[1], $mid, $theme->[2],
|
||||
$end
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
# Pairs are interesting to highlight only if we are going to end up
|
||||
# highlighting a subset (i.e., not the whole line). Otherwise, the highlighting
|
||||
# is just useless noise. We can detect this by finding either a matching prefix
|
||||
# or suffix (disregarding boring bits like whitespace and colorization).
|
||||
sub is_pair_interesting {
|
||||
my ($a, $pa, $sa, $b, $pb, $sb) = @_;
|
||||
my $prefix_a = join('', @$a[0..($pa-1)]);
|
||||
my $prefix_b = join('', @$b[0..($pb-1)]);
|
||||
my $suffix_a = join('', @$a[($sa+1)..$#$a]);
|
||||
my $suffix_b = join('', @$b[($sb+1)..$#$b]);
|
||||
|
||||
return visible_substr($prefix_a, $graph_indent) !~ /^$COLOR*-$BORING*$/ ||
|
||||
visible_substr($prefix_b, $graph_indent) !~ /^$COLOR*\+$BORING*$/ ||
|
||||
$suffix_a !~ /^$BORING*$/ ||
|
||||
$suffix_b !~ /^$BORING*$/;
|
||||
}
|
||||
23
third_party/git/contrib/diff-highlight/Makefile
vendored
Normal file
23
third_party/git/contrib/diff-highlight/Makefile
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
all: diff-highlight
|
||||
|
||||
PERL_PATH = /usr/bin/perl
|
||||
-include ../../config.mak
|
||||
|
||||
PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
|
||||
|
||||
diff-highlight: shebang.perl DiffHighlight.pm diff-highlight.perl
|
||||
cat $^ >$@+
|
||||
chmod +x $@+
|
||||
mv $@+ $@
|
||||
|
||||
shebang.perl: FORCE
|
||||
@echo '#!$(PERL_PATH_SQ)' >$@+
|
||||
@cmp $@+ $@ >/dev/null 2>/dev/null || mv $@+ $@
|
||||
|
||||
test: all
|
||||
$(MAKE) -C t
|
||||
|
||||
clean:
|
||||
$(RM) diff-highlight
|
||||
|
||||
.PHONY: FORCE
|
||||
223
third_party/git/contrib/diff-highlight/README
vendored
Normal file
223
third_party/git/contrib/diff-highlight/README
vendored
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
diff-highlight
|
||||
==============
|
||||
|
||||
Line oriented diffs are great for reviewing code, because for most
|
||||
hunks, you want to see the old and the new segments of code next to each
|
||||
other. Sometimes, though, when an old line and a new line are very
|
||||
similar, it's hard to immediately see the difference.
|
||||
|
||||
You can use "--color-words" to highlight only the changed portions of
|
||||
lines. However, this can often be hard to read for code, as it loses
|
||||
the line structure, and you end up with oddly formatted bits.
|
||||
|
||||
Instead, this script post-processes the line-oriented diff, finds pairs
|
||||
of lines, and highlights the differing segments. It's currently very
|
||||
simple and stupid about doing these tasks. In particular:
|
||||
|
||||
1. It will only highlight hunks in which the number of removed and
|
||||
added lines is the same, and it will pair lines within the hunk by
|
||||
position (so the first removed line is compared to the first added
|
||||
line, and so forth). This is simple and tends to work well in
|
||||
practice. More complex changes don't highlight well, so we tend to
|
||||
exclude them due to the "same number of removed and added lines"
|
||||
restriction. Or even if we do try to highlight them, they end up
|
||||
not highlighting because of our "don't highlight if the whole line
|
||||
would be highlighted" rule.
|
||||
|
||||
2. It will find the common prefix and suffix of two lines, and
|
||||
consider everything in the middle to be "different". It could
|
||||
instead do a real diff of the characters between the two lines and
|
||||
find common subsequences. However, the point of the highlight is to
|
||||
call attention to a certain area. Even if some small subset of the
|
||||
highlighted area actually didn't change, that's OK. In practice it
|
||||
ends up being more readable to just have a single blob on the line
|
||||
showing the interesting bit.
|
||||
|
||||
The goal of the script is therefore not to be exact about highlighting
|
||||
changes, but to call attention to areas of interest without being
|
||||
visually distracting. Non-diff lines and existing diff coloration is
|
||||
preserved; the intent is that the output should look exactly the same as
|
||||
the input, except for the occasional highlight.
|
||||
|
||||
Use
|
||||
---
|
||||
|
||||
You can try out the diff-highlight program with:
|
||||
|
||||
---------------------------------------------
|
||||
git log -p --color | /path/to/diff-highlight
|
||||
---------------------------------------------
|
||||
|
||||
If you want to use it all the time, drop it in your $PATH and put the
|
||||
following in your git configuration:
|
||||
|
||||
---------------------------------------------
|
||||
[pager]
|
||||
log = diff-highlight | less
|
||||
show = diff-highlight | less
|
||||
diff = diff-highlight | less
|
||||
---------------------------------------------
|
||||
|
||||
|
||||
Color Config
|
||||
------------
|
||||
|
||||
You can configure the highlight colors and attributes using git's
|
||||
config. The colors for "old" and "new" lines can be specified
|
||||
independently. There are two "modes" of configuration:
|
||||
|
||||
1. You can specify a "highlight" color and a matching "reset" color.
|
||||
This will retain any existing colors in the diff, and apply the
|
||||
"highlight" and "reset" colors before and after the highlighted
|
||||
portion.
|
||||
|
||||
2. You can specify a "normal" color and a "highlight" color. In this
|
||||
case, existing colors are dropped from that line. The non-highlighted
|
||||
bits of the line get the "normal" color, and the highlights get the
|
||||
"highlight" color.
|
||||
|
||||
If no "new" colors are specified, they default to the "old" colors. If
|
||||
no "old" colors are specified, the default is to reverse the foreground
|
||||
and background for highlighted portions.
|
||||
|
||||
Examples:
|
||||
|
||||
---------------------------------------------
|
||||
# Underline highlighted portions
|
||||
[color "diff-highlight"]
|
||||
oldHighlight = ul
|
||||
oldReset = noul
|
||||
---------------------------------------------
|
||||
|
||||
---------------------------------------------
|
||||
# Varying background intensities
|
||||
[color "diff-highlight"]
|
||||
oldNormal = "black #f8cbcb"
|
||||
oldHighlight = "black #ffaaaa"
|
||||
newNormal = "black #cbeecb"
|
||||
newHighlight = "black #aaffaa"
|
||||
---------------------------------------------
|
||||
|
||||
|
||||
Using diff-highlight as a module
|
||||
--------------------------------
|
||||
|
||||
If you want to pre- or post- process the highlighted lines as part of
|
||||
another perl script, you can use the DiffHighlight module. You can
|
||||
either "require" it or just cat the module together with your script (to
|
||||
avoid run-time dependencies).
|
||||
|
||||
Your script may set up one or more of the following variables:
|
||||
|
||||
- $DiffHighlight::line_cb - this should point to a function which is
|
||||
called whenever DiffHighlight has lines (which may contain
|
||||
highlights) to output. The default function prints each line to
|
||||
stdout. Note that the function may be called with multiple lines.
|
||||
|
||||
- $DiffHighlight::flush_cb - this should point to a function which
|
||||
flushes the output (because DiffHighlight believes it has completed
|
||||
processing a logical chunk of input). The default function flushes
|
||||
stdout.
|
||||
|
||||
The script may then feed lines, one at a time, to DiffHighlight::handle_line().
|
||||
When lines are done processing, they will be fed to $line_cb. Note that
|
||||
DiffHighlight may queue up many input lines (to analyze a whole hunk)
|
||||
before calling $line_cb. After providing all lines, call
|
||||
DiffHighlight::flush() to flush any unprocessed lines.
|
||||
|
||||
If you just want to process stdin, DiffHighlight::highlight_stdin()
|
||||
is a convenience helper which will loop and flush for you.
|
||||
|
||||
|
||||
Bugs
|
||||
----
|
||||
|
||||
Because diff-highlight relies on heuristics to guess which parts of
|
||||
changes are important, there are some cases where the highlighting is
|
||||
more distracting than useful. Fortunately, these cases are rare in
|
||||
practice, and when they do occur, the worst case is simply a little
|
||||
extra highlighting. This section documents some cases known to be
|
||||
sub-optimal, in case somebody feels like working on improving the
|
||||
heuristics.
|
||||
|
||||
1. Two changes on the same line get highlighted in a blob. For example,
|
||||
highlighting:
|
||||
|
||||
----------------------------------------------
|
||||
-foo(buf, size);
|
||||
+foo(obj->buf, obj->size);
|
||||
----------------------------------------------
|
||||
|
||||
yields (where the inside of "+{}" would be highlighted):
|
||||
|
||||
----------------------------------------------
|
||||
-foo(buf, size);
|
||||
+foo(+{obj->buf, obj->}size);
|
||||
----------------------------------------------
|
||||
|
||||
whereas a more semantically meaningful output would be:
|
||||
|
||||
----------------------------------------------
|
||||
-foo(buf, size);
|
||||
+foo(+{obj->}buf, +{obj->}size);
|
||||
----------------------------------------------
|
||||
|
||||
Note that doing this right would probably involve a set of
|
||||
content-specific boundary patterns, similar to word-diff. Otherwise
|
||||
you get junk like:
|
||||
|
||||
-----------------------------------------------------
|
||||
-this line has some -{i}nt-{ere}sti-{ng} text on it
|
||||
+this line has some +{fa}nt+{a}sti+{c} text on it
|
||||
-----------------------------------------------------
|
||||
|
||||
which is less readable than the current output.
|
||||
|
||||
2. The multi-line matching assumes that lines in the pre- and post-image
|
||||
match by position. This is often the case, but can be fooled when a
|
||||
line is removed from the top and a new one added at the bottom (or
|
||||
vice versa). Unless the lines in the middle are also changed, diffs
|
||||
will show this as two hunks, and it will not get highlighted at all
|
||||
(which is good). But if the lines in the middle are changed, the
|
||||
highlighting can be misleading. Here's a pathological case:
|
||||
|
||||
-----------------------------------------------------
|
||||
-one
|
||||
-two
|
||||
-three
|
||||
-four
|
||||
+two 2
|
||||
+three 3
|
||||
+four 4
|
||||
+five 5
|
||||
-----------------------------------------------------
|
||||
|
||||
which gets highlighted as:
|
||||
|
||||
-----------------------------------------------------
|
||||
-one
|
||||
-t-{wo}
|
||||
-three
|
||||
-f-{our}
|
||||
+two 2
|
||||
+t+{hree 3}
|
||||
+four 4
|
||||
+f+{ive 5}
|
||||
-----------------------------------------------------
|
||||
|
||||
because it matches "two" to "three 3", and so forth. It would be
|
||||
nicer as:
|
||||
|
||||
-----------------------------------------------------
|
||||
-one
|
||||
-two
|
||||
-three
|
||||
-four
|
||||
+two +{2}
|
||||
+three +{3}
|
||||
+four +{4}
|
||||
+five 5
|
||||
-----------------------------------------------------
|
||||
|
||||
which would probably involve pre-matching the lines into pairs
|
||||
according to some heuristic.
|
||||
8
third_party/git/contrib/diff-highlight/diff-highlight.perl
vendored
Normal file
8
third_party/git/contrib/diff-highlight/diff-highlight.perl
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package main;
|
||||
|
||||
# Some scripts may not realize that SIGPIPE is being ignored when launching the
|
||||
# pager--for instance scripts written in Python.
|
||||
$SIG{PIPE} = 'DEFAULT';
|
||||
|
||||
DiffHighlight::highlight_stdin();
|
||||
exit 0;
|
||||
2
third_party/git/contrib/diff-highlight/t/.gitignore
vendored
Normal file
2
third_party/git/contrib/diff-highlight/t/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/trash directory*
|
||||
/test-results
|
||||
22
third_party/git/contrib/diff-highlight/t/Makefile
vendored
Normal file
22
third_party/git/contrib/diff-highlight/t/Makefile
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
-include ../../../config.mak.autogen
|
||||
-include ../../../config.mak
|
||||
|
||||
# copied from ../../t/Makefile
|
||||
SHELL_PATH ?= $(SHELL)
|
||||
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
|
||||
T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
|
||||
|
||||
all: test
|
||||
test: $(T)
|
||||
|
||||
.PHONY: help clean all test $(T)
|
||||
|
||||
help:
|
||||
@echo 'Run "$(MAKE) test" to launch test scripts'
|
||||
@echo 'Run "$(MAKE) clean" to remove trash folders'
|
||||
|
||||
$(T):
|
||||
@echo "*** $@ ***"; '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
|
||||
|
||||
clean:
|
||||
$(RM) -r 'trash directory'.*
|
||||
341
third_party/git/contrib/diff-highlight/t/t9400-diff-highlight.sh
vendored
Executable file
341
third_party/git/contrib/diff-highlight/t/t9400-diff-highlight.sh
vendored
Executable file
|
|
@ -0,0 +1,341 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='Test diff-highlight'
|
||||
|
||||
CURR_DIR=$(pwd)
|
||||
TEST_OUTPUT_DIRECTORY=$(pwd)
|
||||
TEST_DIRECTORY="$CURR_DIR"/../../../t
|
||||
DIFF_HIGHLIGHT="$CURR_DIR"/../diff-highlight
|
||||
|
||||
CW="$(printf "\033[7m")" # white
|
||||
CR="$(printf "\033[27m")" # reset
|
||||
|
||||
. "$TEST_DIRECTORY"/test-lib.sh
|
||||
|
||||
if ! test_have_prereq PERL
|
||||
then
|
||||
skip_all='skipping diff-highlight tests; perl not available'
|
||||
test_done
|
||||
fi
|
||||
|
||||
# dh_test is a test helper function which takes 3 file names as parameters. The
|
||||
# first 2 files are used to generate diff and commit output, which is then
|
||||
# piped through diff-highlight. The 3rd file should contain the expected output
|
||||
# of diff-highlight (minus the diff/commit header, ie. everything after and
|
||||
# including the first @@ line).
|
||||
dh_test () {
|
||||
a="$1" b="$2" &&
|
||||
|
||||
cat >patch.exp &&
|
||||
|
||||
{
|
||||
cat "$a" >file &&
|
||||
git add file &&
|
||||
git commit -m "Add a file" &&
|
||||
|
||||
cat "$b" >file &&
|
||||
git diff file >diff.raw &&
|
||||
git commit -a -m "Update a file" &&
|
||||
git show >commit.raw
|
||||
} >/dev/null &&
|
||||
|
||||
"$DIFF_HIGHLIGHT" <diff.raw | test_strip_patch_header >diff.act &&
|
||||
"$DIFF_HIGHLIGHT" <commit.raw | test_strip_patch_header >commit.act &&
|
||||
test_cmp patch.exp diff.act &&
|
||||
test_cmp patch.exp commit.act
|
||||
}
|
||||
|
||||
test_strip_patch_header () {
|
||||
sed -n '/^@@/,$p' $*
|
||||
}
|
||||
|
||||
# dh_test_setup_history generates a contrived graph such that we have at least
|
||||
# 1 nesting (E) and 2 nestings (F).
|
||||
#
|
||||
# A---B master
|
||||
# /
|
||||
# D---E---F branch
|
||||
#
|
||||
# git log --all --graph
|
||||
# * commit
|
||||
# | B
|
||||
# | * commit
|
||||
# | | F
|
||||
# * | commit
|
||||
# | | A
|
||||
# | * commit
|
||||
# |/
|
||||
# | E
|
||||
# * commit
|
||||
# D
|
||||
#
|
||||
dh_test_setup_history () {
|
||||
echo file1 >file &&
|
||||
git add file &&
|
||||
test_tick &&
|
||||
git commit -m "D" &&
|
||||
|
||||
git checkout -b branch &&
|
||||
echo file2 >file &&
|
||||
test_tick &&
|
||||
git commit -a -m "E" &&
|
||||
|
||||
git checkout master &&
|
||||
echo file2 >file &&
|
||||
test_tick &&
|
||||
git commit -a -m "A" &&
|
||||
|
||||
git checkout branch &&
|
||||
echo file3 >file &&
|
||||
test_tick &&
|
||||
git commit -a -m "F" &&
|
||||
|
||||
git checkout master &&
|
||||
echo file3 >file &&
|
||||
test_tick &&
|
||||
git commit -a -m "B"
|
||||
}
|
||||
|
||||
left_trim () {
|
||||
"$PERL_PATH" -pe 's/^\s+//'
|
||||
}
|
||||
|
||||
trim_graph () {
|
||||
# graphs start with * or |
|
||||
# followed by a space or / or \
|
||||
"$PERL_PATH" -pe 's@^((\*|\|)( |/|\\))+@@'
|
||||
}
|
||||
|
||||
test_expect_success 'diff-highlight highlights the beginning of a line' '
|
||||
cat >a <<-\EOF &&
|
||||
aaa
|
||||
bbb
|
||||
ccc
|
||||
EOF
|
||||
|
||||
cat >b <<-\EOF &&
|
||||
aaa
|
||||
0bb
|
||||
ccc
|
||||
EOF
|
||||
|
||||
dh_test a b <<-EOF
|
||||
@@ -1,3 +1,3 @@
|
||||
aaa
|
||||
-${CW}b${CR}bb
|
||||
+${CW}0${CR}bb
|
||||
ccc
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'diff-highlight highlights the end of a line' '
|
||||
cat >a <<-\EOF &&
|
||||
aaa
|
||||
bbb
|
||||
ccc
|
||||
EOF
|
||||
|
||||
cat >b <<-\EOF &&
|
||||
aaa
|
||||
bb0
|
||||
ccc
|
||||
EOF
|
||||
|
||||
dh_test a b <<-EOF
|
||||
@@ -1,3 +1,3 @@
|
||||
aaa
|
||||
-bb${CW}b${CR}
|
||||
+bb${CW}0${CR}
|
||||
ccc
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'diff-highlight highlights the middle of a line' '
|
||||
cat >a <<-\EOF &&
|
||||
aaa
|
||||
bbb
|
||||
ccc
|
||||
EOF
|
||||
|
||||
cat >b <<-\EOF &&
|
||||
aaa
|
||||
b0b
|
||||
ccc
|
||||
EOF
|
||||
|
||||
dh_test a b <<-EOF
|
||||
@@ -1,3 +1,3 @@
|
||||
aaa
|
||||
-b${CW}b${CR}b
|
||||
+b${CW}0${CR}b
|
||||
ccc
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'diff-highlight does not highlight whole line' '
|
||||
cat >a <<-\EOF &&
|
||||
aaa
|
||||
bbb
|
||||
ccc
|
||||
EOF
|
||||
|
||||
cat >b <<-\EOF &&
|
||||
aaa
|
||||
000
|
||||
ccc
|
||||
EOF
|
||||
|
||||
dh_test a b <<-EOF
|
||||
@@ -1,3 +1,3 @@
|
||||
aaa
|
||||
-bbb
|
||||
+000
|
||||
ccc
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_failure 'diff-highlight highlights mismatched hunk size' '
|
||||
cat >a <<-\EOF &&
|
||||
aaa
|
||||
bbb
|
||||
EOF
|
||||
|
||||
cat >b <<-\EOF &&
|
||||
aaa
|
||||
b0b
|
||||
ccc
|
||||
EOF
|
||||
|
||||
dh_test a b <<-EOF
|
||||
@@ -1,3 +1,3 @@
|
||||
aaa
|
||||
-b${CW}b${CR}b
|
||||
+b${CW}0${CR}b
|
||||
+ccc
|
||||
EOF
|
||||
'
|
||||
|
||||
# These two code points share the same leading byte in UTF-8 representation;
|
||||
# a naive byte-wise diff would highlight only the second byte.
|
||||
#
|
||||
# - U+00f3 ("o" with acute)
|
||||
o_accent=$(printf '\303\263')
|
||||
# - U+00f8 ("o" with stroke)
|
||||
o_stroke=$(printf '\303\270')
|
||||
|
||||
test_expect_success 'diff-highlight treats multibyte utf-8 as a unit' '
|
||||
echo "unic${o_accent}de" >a &&
|
||||
echo "unic${o_stroke}de" >b &&
|
||||
dh_test a b <<-EOF
|
||||
@@ -1 +1 @@
|
||||
-unic${CW}${o_accent}${CR}de
|
||||
+unic${CW}${o_stroke}${CR}de
|
||||
EOF
|
||||
'
|
||||
|
||||
# Unlike the UTF-8 above, these are combining code points which are meant
|
||||
# to modify the character preceding them:
|
||||
#
|
||||
# - U+0301 (combining acute accent)
|
||||
combine_accent=$(printf '\314\201')
|
||||
# - U+0302 (combining circumflex)
|
||||
combine_circum=$(printf '\314\202')
|
||||
|
||||
test_expect_failure 'diff-highlight treats combining code points as a unit' '
|
||||
echo "unico${combine_accent}de" >a &&
|
||||
echo "unico${combine_circum}de" >b &&
|
||||
dh_test a b <<-EOF
|
||||
@@ -1 +1 @@
|
||||
-unic${CW}o${combine_accent}${CR}de
|
||||
+unic${CW}o${combine_circum}${CR}de
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'diff-highlight works with the --graph option' '
|
||||
dh_test_setup_history &&
|
||||
|
||||
# date-order so that the commits are interleaved for both
|
||||
# trim graph elements so we can do a diff
|
||||
# trim leading space because our trim_graph is not perfect
|
||||
git log --branches -p --date-order |
|
||||
"$DIFF_HIGHLIGHT" | left_trim >graph.exp &&
|
||||
git log --branches -p --date-order --graph |
|
||||
"$DIFF_HIGHLIGHT" | trim_graph | left_trim >graph.act &&
|
||||
test_cmp graph.exp graph.act
|
||||
'
|
||||
|
||||
# Just reuse the previous graph test, but with --color. Our trimming
|
||||
# doesn't know about color, so just sanity check that something got
|
||||
# highlighted.
|
||||
test_expect_success 'diff-highlight works with color graph' '
|
||||
git log --branches -p --date-order --graph --color |
|
||||
"$DIFF_HIGHLIGHT" | trim_graph | left_trim >graph &&
|
||||
grep "\[7m" graph
|
||||
'
|
||||
|
||||
# Most combined diffs won't meet diff-highlight's line-number filter. So we
|
||||
# create one here where one side drops a line and the other modifies it. That
|
||||
# should result in a diff like:
|
||||
#
|
||||
# - modified content
|
||||
# ++resolved content
|
||||
#
|
||||
# which naively looks like one side added "+resolved".
|
||||
test_expect_success 'diff-highlight ignores combined diffs' '
|
||||
echo "content" >file &&
|
||||
git add file &&
|
||||
git commit -m base &&
|
||||
|
||||
>file &&
|
||||
git commit -am master &&
|
||||
|
||||
git checkout -b other HEAD^ &&
|
||||
echo "modified content" >file &&
|
||||
git commit -am other &&
|
||||
|
||||
test_must_fail git merge master &&
|
||||
echo "resolved content" >file &&
|
||||
git commit -am resolved &&
|
||||
|
||||
cat >expect <<-\EOF &&
|
||||
--- a/file
|
||||
+++ b/file
|
||||
@@@ -1,1 -1,0 +1,1 @@@
|
||||
- modified content
|
||||
++resolved content
|
||||
EOF
|
||||
|
||||
git show -c | "$DIFF_HIGHLIGHT" >actual.raw &&
|
||||
sed -n "/^---/,\$p" <actual.raw >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'diff-highlight handles --graph with leading dash' '
|
||||
cat >file <<-\EOF &&
|
||||
before
|
||||
the old line
|
||||
-leading dash
|
||||
EOF
|
||||
git add file &&
|
||||
git commit -m before &&
|
||||
|
||||
sed s/old/new/ <file >file.tmp &&
|
||||
mv file.tmp file &&
|
||||
git add file &&
|
||||
git commit -m after &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
--- a/file
|
||||
+++ b/file
|
||||
@@ -1,3 +1,3 @@
|
||||
before
|
||||
-the ${CW}old${CR} line
|
||||
+the ${CW}new${CR} line
|
||||
-leading dash
|
||||
EOF
|
||||
git log --graph -p -1 | "$DIFF_HIGHLIGHT" >actual.raw &&
|
||||
trim_graph <actual.raw | sed -n "/^---/,\$p" >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_done
|
||||
33
third_party/git/contrib/emacs/README
vendored
Normal file
33
third_party/git/contrib/emacs/README
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
This directory used to contain various modules for Emacs support.
|
||||
|
||||
These were added shortly after Git was first released. Since then
|
||||
Emacs's own support for Git got better than what was offered by these
|
||||
modes. There are also popular 3rd-party Git modes such as Magit which
|
||||
offer replacements for these.
|
||||
|
||||
The following modules were available, and can be dug up from the Git
|
||||
history:
|
||||
|
||||
* git.el:
|
||||
|
||||
Wrapper for "git status" that provided access to other git commands.
|
||||
|
||||
Modern alternatives to this include Magit, and VC mode that ships
|
||||
with Emacs.
|
||||
|
||||
* git-blame.el:
|
||||
|
||||
A wrapper for "git blame" written before Emacs's own vc-annotate
|
||||
mode learned to invoke git-blame, which can be done via C-x v g.
|
||||
|
||||
* vc-git.el:
|
||||
|
||||
This file used to contain the VC-mode backend for git, but it is no
|
||||
longer distributed with git. It is now maintained as part of Emacs
|
||||
and included in standard Emacs distributions starting from version
|
||||
22.2.
|
||||
|
||||
If you have an earlier Emacs version, upgrading to Emacs 22 is
|
||||
recommended, since the VC mode in older Emacs is not generic enough
|
||||
to be able to support git in a reasonable manner, and no attempt has
|
||||
been made to backport vc-git.el.
|
||||
6
third_party/git/contrib/emacs/git-blame.el
vendored
Normal file
6
third_party/git/contrib/emacs/git-blame.el
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
(error "git-blame.el no longer ships with git. It's recommended
|
||||
to replace its use with Emacs's own vc-annotate. See
|
||||
contrib/emacs/README in git's
|
||||
sources (https://github.com/git/git/blob/master/contrib/emacs/README)
|
||||
for more info on suggested alternatives and for why this
|
||||
happened.")
|
||||
6
third_party/git/contrib/emacs/git.el
vendored
Normal file
6
third_party/git/contrib/emacs/git.el
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
(error "git.el no longer ships with git. It's recommended to
|
||||
replace its use with Magit, or simply delete references to git.el
|
||||
in your initialization file(s). See contrib/emacs/README in git's
|
||||
sources (https://github.com/git/git/blob/master/contrib/emacs/README)
|
||||
for suggested alternatives and for why this happened. Emacs's own
|
||||
VC mode and Magit are viable alternatives.")
|
||||
20
third_party/git/contrib/examples/README
vendored
Normal file
20
third_party/git/contrib/examples/README
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
This directory used to contain scripted implementations of builtins
|
||||
that have since been rewritten in C.
|
||||
|
||||
They have now been removed, but can be retrieved from an older commit
|
||||
that removed them from this directory.
|
||||
|
||||
They're interesting for their reference value to any aspiring plumbing
|
||||
users who want to learn how pieces can be fit together, but in many
|
||||
cases have drifted enough from the actual implementations Git uses to
|
||||
be instructive.
|
||||
|
||||
Other things that can be useful:
|
||||
|
||||
* Some commands such as git-gc wrap other commands, and what they're
|
||||
doing behind the scenes can be seen by running them under
|
||||
GIT_TRACE=1
|
||||
|
||||
* Doing `git log` on paths matching '*--helper.c' will show
|
||||
incremental effort in the direction of moving existing shell
|
||||
scripts to C.
|
||||
64
third_party/git/contrib/fast-import/git-import.perl
vendored
Executable file
64
third_party/git/contrib/fast-import/git-import.perl
vendored
Executable file
|
|
@ -0,0 +1,64 @@
|
|||
#!/usr/bin/perl
|
||||
#
|
||||
# Performs an initial import of a directory. This is the equivalent
|
||||
# of doing 'git init; git add .; git commit'. It's a little slower,
|
||||
# but is meant to be a simple fast-import example.
|
||||
|
||||
use strict;
|
||||
use File::Find;
|
||||
|
||||
my $USAGE = 'usage: git-import branch import-message';
|
||||
my $branch = shift or die "$USAGE\n";
|
||||
my $message = shift or die "$USAGE\n";
|
||||
|
||||
chomp(my $username = `git config user.name`);
|
||||
chomp(my $email = `git config user.email`);
|
||||
die 'You need to set user name and email'
|
||||
unless $username && $email;
|
||||
|
||||
system('git init');
|
||||
open(my $fi, '|-', qw(git fast-import --date-format=now))
|
||||
or die "unable to spawn fast-import: $!";
|
||||
|
||||
print $fi <<EOF;
|
||||
commit refs/heads/$branch
|
||||
committer $username <$email> now
|
||||
data <<MSGEOF
|
||||
$message
|
||||
MSGEOF
|
||||
|
||||
EOF
|
||||
|
||||
find(
|
||||
sub {
|
||||
if($File::Find::name eq './.git') {
|
||||
$File::Find::prune = 1;
|
||||
return;
|
||||
}
|
||||
return unless -f $_;
|
||||
|
||||
my $fn = $File::Find::name;
|
||||
$fn =~ s#^.\/##;
|
||||
|
||||
open(my $in, '<', $_)
|
||||
or die "unable to open $fn: $!";
|
||||
my @st = stat($in)
|
||||
or die "unable to stat $fn: $!";
|
||||
my $len = $st[7];
|
||||
|
||||
print $fi "M 644 inline $fn\n";
|
||||
print $fi "data $len\n";
|
||||
while($len > 0) {
|
||||
my $r = read($in, my $buf, $len < 4096 ? $len : 4096);
|
||||
defined($r) or die "read error from $fn: $!";
|
||||
$r > 0 or die "premature EOF from $fn: $!";
|
||||
print $fi $buf;
|
||||
$len -= $r;
|
||||
}
|
||||
print $fi "\n";
|
||||
|
||||
}, '.'
|
||||
);
|
||||
|
||||
close($fi);
|
||||
exit $?;
|
||||
38
third_party/git/contrib/fast-import/git-import.sh
vendored
Executable file
38
third_party/git/contrib/fast-import/git-import.sh
vendored
Executable file
|
|
@ -0,0 +1,38 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Performs an initial import of a directory. This is the equivalent
|
||||
# of doing 'git init; git add .; git commit'. It's a lot slower,
|
||||
# but is meant to be a simple fast-import example.
|
||||
|
||||
if [ -z "$1" -o -z "$2" ]; then
|
||||
echo "usage: git-import branch import-message"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
USERNAME="$(git config user.name)"
|
||||
EMAIL="$(git config user.email)"
|
||||
|
||||
if [ -z "$USERNAME" -o -z "$EMAIL" ]; then
|
||||
echo "You need to set user name and email"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git init
|
||||
|
||||
(
|
||||
cat <<EOF
|
||||
commit refs/heads/$1
|
||||
committer $USERNAME <$EMAIL> now
|
||||
data <<MSGEOF
|
||||
$2
|
||||
MSGEOF
|
||||
|
||||
EOF
|
||||
find * -type f|while read i;do
|
||||
echo "M 100644 inline $i"
|
||||
echo data $(stat -c '%s' "$i")
|
||||
cat "$i"
|
||||
echo
|
||||
done
|
||||
echo
|
||||
) | git fast-import --date-format=now
|
||||
12
third_party/git/contrib/fast-import/git-p4.README
vendored
Normal file
12
third_party/git/contrib/fast-import/git-p4.README
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
The git-p4 script moved to the top-level of the git source directory.
|
||||
|
||||
Invoke it as any other git command, like "git p4 clone", for instance.
|
||||
|
||||
Note that the top-level git-p4.py script is now the source. It is
|
||||
built using make to git-p4, which will be installed.
|
||||
|
||||
Windows users can copy the git-p4.py source script directly, possibly
|
||||
invoking it through a batch file called "git-p4.bat" in the same folder.
|
||||
It should contain just one line:
|
||||
|
||||
@python "%~d0%~p0git-p4.py" %*
|
||||
416
third_party/git/contrib/fast-import/import-directories.perl
vendored
Executable file
416
third_party/git/contrib/fast-import/import-directories.perl
vendored
Executable file
|
|
@ -0,0 +1,416 @@
|
|||
#!/usr/bin/perl
|
||||
#
|
||||
# Copyright 2008-2009 Peter Krefting <peter@softwolves.pp.se>
|
||||
#
|
||||
# ------------------------------------------------------------------------
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
=pod
|
||||
|
||||
=head1 NAME
|
||||
|
||||
import-directories - Import bits and pieces to Git.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
B<import-directories.perl> F<configfile> F<outputfile>
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Script to import arbitrary projects version controlled by the "copy the
|
||||
source directory to a new location and edit it there"-version controlled
|
||||
projects into version control. Handles projects with arbitrary branching
|
||||
and version trees, taking a file describing the inputs and generating a
|
||||
file compatible with the L<git-fast-import(1)> format.
|
||||
|
||||
=head1 CONFIGURATION FILE
|
||||
|
||||
=head2 Format
|
||||
|
||||
The configuration file is based on the standard I<.ini> format.
|
||||
|
||||
; Comments start with semi-colons
|
||||
[section]
|
||||
key=value
|
||||
|
||||
Please see below for information on how to escape special characters.
|
||||
|
||||
=head2 Global configuration
|
||||
|
||||
Global configuration is done in the B<[config]> section, which should be
|
||||
the first section in the file. Configuration can be changed by
|
||||
repeating configuration sections later on.
|
||||
|
||||
[config]
|
||||
; configure conversion of CRLFs. "convert" means that all CRLFs
|
||||
; should be converted into LFs (suitable for the core.autocrlf
|
||||
; setting set to true in Git). "none" means that all data is
|
||||
; treated as binary.
|
||||
crlf=convert
|
||||
|
||||
=head2 Revision configuration
|
||||
|
||||
Each revision that is to be imported is described in three
|
||||
sections. Revisions should be defined in topological order, so
|
||||
that a revision's parent has always been defined when a new revision
|
||||
is introduced. All the sections for one revision must be defined
|
||||
before defining the next revision.
|
||||
|
||||
Each revision is assigned a unique numerical identifier. The
|
||||
numbers do not need to be consecutive, nor monotonically
|
||||
increasing.
|
||||
|
||||
For instance, if your configuration file contains only the two
|
||||
revisions 4711 and 42, where 4711 is the initial commit, the
|
||||
only requirement is that 4711 is completely defined before 42.
|
||||
|
||||
=pod
|
||||
|
||||
=head3 Revision description section
|
||||
|
||||
A section whose section name is just an integer gives meta-data
|
||||
about the revision.
|
||||
|
||||
[3]
|
||||
; author sets the author of the revisions
|
||||
author=Peter Krefting <peter@softwolves.pp.se>
|
||||
; branch sets the branch that the revision should be committed to
|
||||
branch=master
|
||||
; parent describes the revision that is the parent of this commit
|
||||
; (optional)
|
||||
parent=1
|
||||
; merges describes a revision that is merged into this commit
|
||||
; (optional; can be repeated)
|
||||
merges=2
|
||||
; selects one file to take the timestamp from
|
||||
; (optional; if unspecified, the most recent file from the .files
|
||||
; section is used)
|
||||
timestamp=3/source.c
|
||||
|
||||
=head3 Revision contents section
|
||||
|
||||
A section whose section name is an integer followed by B<.files>
|
||||
describe all the files included in this revision. If a file that
|
||||
was available previously is not included in this revision, it will
|
||||
be removed.
|
||||
|
||||
If an on-disk revision is incomplete, you can point to files from
|
||||
a previous revision. There are no restrictions on where the source
|
||||
files are located, nor on their names.
|
||||
|
||||
[3.files]
|
||||
; the key is the path inside the repository, the value is the path
|
||||
; as seen from the importer script.
|
||||
source.c=ver-3.00/source.c
|
||||
source.h=ver-2.99/source.h
|
||||
readme.txt=ver-3.00/introduction to the project.txt
|
||||
|
||||
File names are treated as byte strings (but please see below on
|
||||
quoting rules), and should be stored in the configuration file in
|
||||
the encoding that should be used in the generated repository.
|
||||
|
||||
=head3 Revision commit message section
|
||||
|
||||
A section whose section name is an integer followed by B<.message>
|
||||
gives the commit message. This section is read verbatim, up until
|
||||
the beginning of the next section. As such, a commit message may not
|
||||
contain a line that begins with an opening square bracket ("[") and
|
||||
ends with a closing square bracket ("]"), unless they are surrounded
|
||||
by whitespace or other characters.
|
||||
|
||||
[3.message]
|
||||
Implement foobar.
|
||||
; trailing blank lines are ignored.
|
||||
|
||||
=cut
|
||||
|
||||
# Globals
|
||||
use strict;
|
||||
use warnings;
|
||||
use integer;
|
||||
my $crlfmode = 0;
|
||||
my @revs;
|
||||
my (%revmap, %message, %files, %author, %branch, %parent, %merges, %time, %timesource);
|
||||
my $sectiontype = 0;
|
||||
my $rev = 0;
|
||||
my $mark = 1;
|
||||
|
||||
# Check command line
|
||||
if ($#ARGV < 1 || $ARGV[0] =~ /^--?h/)
|
||||
{
|
||||
exec('perldoc', $0);
|
||||
exit 1;
|
||||
}
|
||||
|
||||
# Open configuration
|
||||
my $config = $ARGV[0];
|
||||
open CFG, '<', $config or die "Cannot open configuration file \"$config\": ";
|
||||
|
||||
# Open output
|
||||
my $output = $ARGV[1];
|
||||
open OUT, '>', $output or die "Cannot create output file \"$output\": ";
|
||||
binmode OUT;
|
||||
|
||||
LINE: while (my $line = <CFG>)
|
||||
{
|
||||
$line =~ s/\r?\n$//;
|
||||
next LINE if $sectiontype != 4 && $line eq '';
|
||||
next LINE if $line =~ /^;/;
|
||||
my $oldsectiontype = $sectiontype;
|
||||
my $oldrev = $rev;
|
||||
|
||||
# Sections
|
||||
if ($line =~ m"^\[(config|(\d+)(|\.files|\.message))\]$")
|
||||
{
|
||||
if ($1 eq 'config')
|
||||
{
|
||||
$sectiontype = 1;
|
||||
}
|
||||
elsif ($3 eq '')
|
||||
{
|
||||
$sectiontype = 2;
|
||||
$rev = $2;
|
||||
# Create a new revision
|
||||
die "Duplicate rev: $line\n " if defined $revmap{$rev};
|
||||
print "Reading revision $rev\n";
|
||||
push @revs, $rev;
|
||||
$revmap{$rev} = $mark ++;
|
||||
$time{$revmap{$rev}} = 0;
|
||||
}
|
||||
elsif ($3 eq '.files')
|
||||
{
|
||||
$sectiontype = 3;
|
||||
$rev = $2;
|
||||
die "Revision mismatch: $line\n " unless $rev == $oldrev;
|
||||
}
|
||||
elsif ($3 eq '.message')
|
||||
{
|
||||
$sectiontype = 4;
|
||||
$rev = $2;
|
||||
die "Revision mismatch: $line\n " unless $rev == $oldrev;
|
||||
}
|
||||
else
|
||||
{
|
||||
die "Internal parse error: $line\n ";
|
||||
}
|
||||
next LINE;
|
||||
}
|
||||
|
||||
# Parse data
|
||||
if ($sectiontype != 4)
|
||||
{
|
||||
# Key and value
|
||||
if ($line =~ m"^\s*([^\s].*=.*[^\s])\s*$")
|
||||
{
|
||||
my ($key, $value) = &parsekeyvaluepair($1);
|
||||
# Global configuration
|
||||
if (1 == $sectiontype)
|
||||
{
|
||||
if ($key eq 'crlf')
|
||||
{
|
||||
$crlfmode = 1, next LINE if $value eq 'convert';
|
||||
$crlfmode = 0, next LINE if $value eq 'none';
|
||||
}
|
||||
die "Unknown configuration option: $line\n ";
|
||||
}
|
||||
# Revision specification
|
||||
if (2 == $sectiontype)
|
||||
{
|
||||
my $current = $revmap{$rev};
|
||||
$author{$current} = $value, next LINE if $key eq 'author';
|
||||
$branch{$current} = $value, next LINE if $key eq 'branch';
|
||||
$parent{$current} = $value, next LINE if $key eq 'parent';
|
||||
$timesource{$current} = $value, next LINE if $key eq 'timestamp';
|
||||
push(@{$merges{$current}}, $value), next LINE if $key eq 'merges';
|
||||
die "Unknown revision option: $line\n ";
|
||||
}
|
||||
# Filespecs
|
||||
if (3 == $sectiontype)
|
||||
{
|
||||
# Add the file and create a marker
|
||||
die "File not found: $line\n " unless -f $value;
|
||||
my $current = $revmap{$rev};
|
||||
${$files{$current}}{$key} = $mark;
|
||||
my $time = &fileblob($value, $crlfmode, $mark ++);
|
||||
|
||||
# Update revision timestamp if more recent than other
|
||||
# files seen, or if this is the file we have selected
|
||||
# to take the time stamp from using the "timestamp"
|
||||
# directive.
|
||||
if ((defined $timesource{$current} && $timesource{$current} eq $value)
|
||||
|| $time > $time{$current})
|
||||
{
|
||||
$time{$current} = $time;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
die "Parse error: $line\n ";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
# Commit message
|
||||
my $current = $revmap{$rev};
|
||||
if (defined $message{$current})
|
||||
{
|
||||
$message{$current} .= "\n";
|
||||
}
|
||||
$message{$current} .= $line;
|
||||
}
|
||||
}
|
||||
close CFG;
|
||||
|
||||
# Start spewing out data for git-fast-import
|
||||
foreach my $commit (@revs)
|
||||
{
|
||||
# Progress
|
||||
print OUT "progress Creating revision $commit\n";
|
||||
|
||||
# Create commit header
|
||||
my $mark = $revmap{$commit};
|
||||
|
||||
# Branch and commit id
|
||||
print OUT "commit refs/heads/", $branch{$mark}, "\nmark :", $mark, "\n";
|
||||
|
||||
# Author and timestamp
|
||||
die "No timestamp defined for $commit (no files?)\n" unless defined $time{$mark};
|
||||
print OUT "committer ", $author{$mark}, " ", $time{$mark}, " +0100\n";
|
||||
|
||||
# Commit message
|
||||
die "No message defined for $commit\n" unless defined $message{$mark};
|
||||
my $message = $message{$mark};
|
||||
$message =~ s/\n$//; # Kill trailing empty line
|
||||
print OUT "data ", length($message), "\n", $message, "\n";
|
||||
|
||||
# Parent and any merges
|
||||
print OUT "from :", $revmap{$parent{$mark}}, "\n" if defined $parent{$mark};
|
||||
if (defined $merges{$mark})
|
||||
{
|
||||
foreach my $merge (@{$merges{$mark}})
|
||||
{
|
||||
print OUT "merge :", $revmap{$merge}, "\n";
|
||||
}
|
||||
}
|
||||
|
||||
# Output file marks
|
||||
print OUT "deleteall\n"; # start from scratch
|
||||
foreach my $file (sort keys %{$files{$mark}})
|
||||
{
|
||||
print OUT "M 644 :", ${$files{$mark}}{$file}, " $file\n";
|
||||
}
|
||||
print OUT "\n";
|
||||
}
|
||||
|
||||
# Create one file blob
|
||||
sub fileblob
|
||||
{
|
||||
my ($filename, $crlfmode, $mark) = @_;
|
||||
|
||||
# Import the file
|
||||
print OUT "progress Importing $filename\nblob\nmark :$mark\n";
|
||||
open FILE, '<', $filename or die "Cannot read $filename\n ";
|
||||
binmode FILE;
|
||||
my ($size, $mtime) = (stat(FILE))[7,9];
|
||||
my $file;
|
||||
read FILE, $file, $size;
|
||||
close FILE;
|
||||
$file =~ s/\r\n/\n/g if $crlfmode;
|
||||
print OUT "data ", length($file), "\n", $file, "\n";
|
||||
|
||||
return $mtime;
|
||||
}
|
||||
|
||||
# Parse a key=value pair
|
||||
sub parsekeyvaluepair
|
||||
{
|
||||
=pod
|
||||
|
||||
=head2 Escaping special characters
|
||||
|
||||
Key and value strings may be enclosed in quotes, in which case
|
||||
whitespace inside the quotes is preserved. Additionally, an equal
|
||||
sign may be included in the key by preceding it with a backslash.
|
||||
For example:
|
||||
|
||||
"key1 "=value1
|
||||
key2=" value2"
|
||||
key\=3=value3
|
||||
key4=value=4
|
||||
"key5""=value5
|
||||
|
||||
Here the first key is "key1 " (note the trailing white-space) and the
|
||||
second value is " value2" (note the leading white-space). The third
|
||||
key contains an equal sign "key=3" and so does the fourth value, which
|
||||
does not need to be escaped. The fifth key contains a trailing quote,
|
||||
which does not need to be escaped since it is inside a surrounding
|
||||
quote.
|
||||
|
||||
=cut
|
||||
my $pair = shift;
|
||||
|
||||
# Separate key and value by the first non-quoted equal sign
|
||||
my ($key, $value);
|
||||
if ($pair =~ /^(.*[^\\])=(.*)$/)
|
||||
{
|
||||
($key, $value) = ($1, $2)
|
||||
}
|
||||
else
|
||||
{
|
||||
die "Parse error: $pair\n ";
|
||||
}
|
||||
|
||||
# Unquote and unescape the key and value separately
|
||||
return (&unescape($key), &unescape($value));
|
||||
}
|
||||
|
||||
# Unquote and unescape
|
||||
sub unescape
|
||||
{
|
||||
my $string = shift;
|
||||
|
||||
# First remove enclosing quotes. Backslash before the trailing
|
||||
# quote leaves both.
|
||||
if ($string =~ /^"(.*[^\\])"$/)
|
||||
{
|
||||
$string = $1;
|
||||
}
|
||||
|
||||
# Second remove any backslashes inside the unquoted string.
|
||||
# For later: Handle special sequences like \t ?
|
||||
$string =~ s/\\(.)/$1/g;
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
__END__
|
||||
|
||||
=pod
|
||||
|
||||
=head1 EXAMPLES
|
||||
|
||||
B<import-directories.perl> F<project.import>
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2008-2009 Peter Krefting E<lt>peter@softwolves.pp.se>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation.
|
||||
|
||||
=cut
|
||||
225
third_party/git/contrib/fast-import/import-tars.perl
vendored
Executable file
225
third_party/git/contrib/fast-import/import-tars.perl
vendored
Executable file
|
|
@ -0,0 +1,225 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
## tar archive frontend for git-fast-import
|
||||
##
|
||||
## For example:
|
||||
##
|
||||
## mkdir project; cd project; git init
|
||||
## perl import-tars.perl *.tar.bz2
|
||||
## git whatchanged import-tars
|
||||
##
|
||||
## Use --metainfo to specify the extension for a meta data file, where
|
||||
## import-tars can read the commit message and optionally author and
|
||||
## committer information.
|
||||
##
|
||||
## echo 'This is the commit message' > myfile.tar.bz2.msg
|
||||
## perl import-tars.perl --metainfo=msg myfile.tar.bz2
|
||||
|
||||
use strict;
|
||||
use Getopt::Long;
|
||||
|
||||
my $metaext = '';
|
||||
|
||||
die "usage: import-tars [--metainfo=extension] *.tar.{gz,bz2,lzma,xz,Z}\n"
|
||||
unless GetOptions('metainfo=s' => \$metaext) && @ARGV;
|
||||
|
||||
my $branch_name = 'import-tars';
|
||||
my $branch_ref = "refs/heads/$branch_name";
|
||||
my $author_name = $ENV{'GIT_AUTHOR_NAME'} || 'T Ar Creator';
|
||||
my $author_email = $ENV{'GIT_AUTHOR_EMAIL'} || 'tar@example.com';
|
||||
my $committer_name = $ENV{'GIT_COMMITTER_NAME'} || `git config --get user.name`;
|
||||
my $committer_email = $ENV{'GIT_COMMITTER_EMAIL'} || `git config --get user.email`;
|
||||
|
||||
chomp($committer_name, $committer_email);
|
||||
|
||||
open(FI, '|-', 'git', 'fast-import', '--quiet')
|
||||
or die "Unable to start git fast-import: $!\n";
|
||||
foreach my $tar_file (@ARGV)
|
||||
{
|
||||
my $commit_time = time;
|
||||
$tar_file =~ m,([^/]+)$,;
|
||||
my $tar_name = $1;
|
||||
|
||||
if ($tar_name =~ s/\.(tar\.gz|tgz)$//) {
|
||||
open(I, '-|', 'gunzip', '-c', $tar_file)
|
||||
or die "Unable to gunzip -c $tar_file: $!\n";
|
||||
} elsif ($tar_name =~ s/\.(tar\.bz2|tbz2)$//) {
|
||||
open(I, '-|', 'bunzip2', '-c', $tar_file)
|
||||
or die "Unable to bunzip2 -c $tar_file: $!\n";
|
||||
} elsif ($tar_name =~ s/\.tar\.Z$//) {
|
||||
open(I, '-|', 'uncompress', '-c', $tar_file)
|
||||
or die "Unable to uncompress -c $tar_file: $!\n";
|
||||
} elsif ($tar_name =~ s/\.(tar\.(lzma|xz)|(tlz|txz))$//) {
|
||||
open(I, '-|', 'xz', '-dc', $tar_file)
|
||||
or die "Unable to xz -dc $tar_file: $!\n";
|
||||
} elsif ($tar_name =~ s/\.tar$//) {
|
||||
open(I, $tar_file) or die "Unable to open $tar_file: $!\n";
|
||||
} else {
|
||||
die "Unrecognized compression format: $tar_file\n";
|
||||
}
|
||||
|
||||
my $author_time = 0;
|
||||
my $next_mark = 1;
|
||||
my $have_top_dir = 1;
|
||||
my ($top_dir, %files);
|
||||
|
||||
my $next_path = '';
|
||||
|
||||
while (read(I, $_, 512) == 512) {
|
||||
my ($name, $mode, $uid, $gid, $size, $mtime,
|
||||
$chksum, $typeflag, $linkname, $magic,
|
||||
$version, $uname, $gname, $devmajor, $devminor,
|
||||
$prefix) = unpack 'Z100 Z8 Z8 Z8 Z12 Z12
|
||||
Z8 Z1 Z100 Z6
|
||||
Z2 Z32 Z32 Z8 Z8 Z*', $_;
|
||||
|
||||
unless ($next_path eq '') {
|
||||
# Recover name from previous extended header
|
||||
$name = $next_path;
|
||||
$next_path = '';
|
||||
}
|
||||
|
||||
last unless length($name);
|
||||
if ($name eq '././@LongLink') {
|
||||
# GNU tar extension
|
||||
if (read(I, $_, 512) != 512) {
|
||||
die ('Short archive');
|
||||
}
|
||||
$name = unpack 'Z257', $_;
|
||||
next unless $name;
|
||||
|
||||
my $dummy;
|
||||
if (read(I, $_, 512) != 512) {
|
||||
die ('Short archive');
|
||||
}
|
||||
($dummy, $mode, $uid, $gid, $size, $mtime,
|
||||
$chksum, $typeflag, $linkname, $magic,
|
||||
$version, $uname, $gname, $devmajor, $devminor,
|
||||
$prefix) = unpack 'Z100 Z8 Z8 Z8 Z12 Z12
|
||||
Z8 Z1 Z100 Z6
|
||||
Z2 Z32 Z32 Z8 Z8 Z*', $_;
|
||||
}
|
||||
$mode = oct $mode;
|
||||
$size = oct $size;
|
||||
$mtime = oct $mtime;
|
||||
next if $typeflag == 5; # directory
|
||||
|
||||
if ($typeflag eq 'x') { # extended header
|
||||
# If extended header, check for path
|
||||
my $pax_header = '';
|
||||
while ($size > 0 && read(I, $_, 512) == 512) {
|
||||
$pax_header = $pax_header . substr($_, 0, $size);
|
||||
$size -= 512;
|
||||
}
|
||||
|
||||
my @lines = split /\n/, $pax_header;
|
||||
foreach my $line (@lines) {
|
||||
my ($len, $entry) = split / /, $line;
|
||||
my ($key, $value) = split /=/, $entry;
|
||||
if ($key eq 'path') {
|
||||
$next_path = $value;
|
||||
}
|
||||
}
|
||||
next;
|
||||
} elsif ($name =~ m{/\z}) { # directory
|
||||
next;
|
||||
} elsif ($typeflag != 1) { # handle hard links later
|
||||
print FI "blob\n", "mark :$next_mark\n";
|
||||
if ($typeflag == 2) { # symbolic link
|
||||
print FI "data ", length($linkname), "\n",
|
||||
$linkname;
|
||||
$mode = 0120000;
|
||||
} else {
|
||||
print FI "data $size\n";
|
||||
while ($size > 0 && read(I, $_, 512) == 512) {
|
||||
print FI substr($_, 0, $size);
|
||||
$size -= 512;
|
||||
}
|
||||
}
|
||||
print FI "\n";
|
||||
}
|
||||
|
||||
my $path;
|
||||
if ($prefix) {
|
||||
$path = "$prefix/$name";
|
||||
} else {
|
||||
$path = "$name";
|
||||
}
|
||||
|
||||
if ($typeflag == 1) { # hard link
|
||||
$linkname = "$prefix/$linkname" if $prefix;
|
||||
$files{$path} = [ $files{$linkname}->[0], $mode ];
|
||||
} else {
|
||||
$files{$path} = [$next_mark++, $mode];
|
||||
}
|
||||
|
||||
$author_time = $mtime if $mtime > $author_time;
|
||||
$path =~ m,^([^/]+)/,;
|
||||
$top_dir = $1 unless $top_dir;
|
||||
$have_top_dir = 0 if $top_dir ne $1;
|
||||
}
|
||||
|
||||
my $commit_msg = "Imported from $tar_file.";
|
||||
my $this_committer_name = $committer_name;
|
||||
my $this_committer_email = $committer_email;
|
||||
my $this_author_name = $author_name;
|
||||
my $this_author_email = $author_email;
|
||||
if ($metaext ne '') {
|
||||
# Optionally read a commit message from <filename.tar>.msg
|
||||
# Add a line on the form "Committer: name <e-mail>" to override
|
||||
# the committer and "Author: name <e-mail>" to override the
|
||||
# author for this tar ball.
|
||||
if (open MSG, '<', "${tar_file}.${metaext}") {
|
||||
my $header_done = 0;
|
||||
$commit_msg = '';
|
||||
while (<MSG>) {
|
||||
if (!$header_done && /^Committer:\s+([^<>]*)\s+<(.*)>\s*$/i) {
|
||||
$this_committer_name = $1;
|
||||
$this_committer_email = $2;
|
||||
} elsif (!$header_done && /^Author:\s+([^<>]*)\s+<(.*)>\s*$/i) {
|
||||
$this_author_name = $1;
|
||||
$this_author_email = $2;
|
||||
} elsif (!$header_done && /^$/) { # empty line ends header.
|
||||
$header_done = 1;
|
||||
} else {
|
||||
$commit_msg .= $_;
|
||||
$header_done = 1;
|
||||
}
|
||||
}
|
||||
close MSG;
|
||||
}
|
||||
}
|
||||
|
||||
print FI <<EOF;
|
||||
commit $branch_ref
|
||||
author $this_author_name <$this_author_email> $author_time +0000
|
||||
committer $this_committer_name <$this_committer_email> $commit_time +0000
|
||||
data <<END_OF_COMMIT_MESSAGE
|
||||
$commit_msg
|
||||
END_OF_COMMIT_MESSAGE
|
||||
|
||||
deleteall
|
||||
EOF
|
||||
|
||||
foreach my $path (keys %files)
|
||||
{
|
||||
my ($mark, $mode) = @{$files{$path}};
|
||||
$path =~ s,^([^/]+)/,, if $have_top_dir;
|
||||
$mode = $mode & 0111 ? 0755 : 0644 unless $mode == 0120000;
|
||||
printf FI "M %o :%i %s\n", $mode, $mark, $path;
|
||||
}
|
||||
print FI "\n";
|
||||
|
||||
print FI <<EOF;
|
||||
tag $tar_name
|
||||
from $branch_ref
|
||||
tagger $author_name <$author_email> $author_time +0000
|
||||
data <<END_OF_TAG_MESSAGE
|
||||
Package $tar_name
|
||||
END_OF_TAG_MESSAGE
|
||||
|
||||
EOF
|
||||
|
||||
close I;
|
||||
}
|
||||
close FI;
|
||||
78
third_party/git/contrib/fast-import/import-zips.py
vendored
Executable file
78
third_party/git/contrib/fast-import/import-zips.py
vendored
Executable file
|
|
@ -0,0 +1,78 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
## zip archive frontend for git-fast-import
|
||||
##
|
||||
## For example:
|
||||
##
|
||||
## mkdir project; cd project; git init
|
||||
## python import-zips.py *.zip
|
||||
## git log --stat import-zips
|
||||
|
||||
from os import popen, path
|
||||
from sys import argv, exit, hexversion, stderr
|
||||
from time import mktime
|
||||
from zipfile import ZipFile
|
||||
|
||||
if hexversion < 0x01060000:
|
||||
# The limiter is the zipfile module
|
||||
stderr.write("import-zips.py: requires Python 1.6.0 or later.\n")
|
||||
exit(1)
|
||||
|
||||
if len(argv) < 2:
|
||||
print 'usage:', argv[0], '<zipfile>...'
|
||||
exit(1)
|
||||
|
||||
branch_ref = 'refs/heads/import-zips'
|
||||
committer_name = 'Z Ip Creator'
|
||||
committer_email = 'zip@example.com'
|
||||
|
||||
fast_import = popen('git fast-import --quiet', 'w')
|
||||
def printlines(list):
|
||||
for str in list:
|
||||
fast_import.write(str + "\n")
|
||||
|
||||
for zipfile in argv[1:]:
|
||||
commit_time = 0
|
||||
next_mark = 1
|
||||
common_prefix = None
|
||||
mark = dict()
|
||||
|
||||
zip = ZipFile(zipfile, 'r')
|
||||
for name in zip.namelist():
|
||||
if name.endswith('/'):
|
||||
continue
|
||||
info = zip.getinfo(name)
|
||||
|
||||
if commit_time < info.date_time:
|
||||
commit_time = info.date_time
|
||||
if common_prefix == None:
|
||||
common_prefix = name[:name.rfind('/') + 1]
|
||||
else:
|
||||
while not name.startswith(common_prefix):
|
||||
last_slash = common_prefix[:-1].rfind('/') + 1
|
||||
common_prefix = common_prefix[:last_slash]
|
||||
|
||||
mark[name] = ':' + str(next_mark)
|
||||
next_mark += 1
|
||||
|
||||
printlines(('blob', 'mark ' + mark[name], \
|
||||
'data ' + str(info.file_size)))
|
||||
fast_import.write(zip.read(name) + "\n")
|
||||
|
||||
committer = committer_name + ' <' + committer_email + '> %d +0000' % \
|
||||
mktime(commit_time + (0, 0, 0))
|
||||
|
||||
printlines(('commit ' + branch_ref, 'committer ' + committer, \
|
||||
'data <<EOM', 'Imported from ' + zipfile + '.', 'EOM', \
|
||||
'', 'deleteall'))
|
||||
|
||||
for name in mark.keys():
|
||||
fast_import.write('M 100644 ' + mark[name] + ' ' +
|
||||
name[len(common_prefix):] + "\n")
|
||||
|
||||
printlines(('', 'tag ' + path.basename(zipfile), \
|
||||
'from ' + branch_ref, 'tagger ' + committer, \
|
||||
'data <<EOM', 'Package ' + zipfile, 'EOM', ''))
|
||||
|
||||
if fast_import.close():
|
||||
exit(1)
|
||||
112
third_party/git/contrib/git-jump/README
vendored
Normal file
112
third_party/git/contrib/git-jump/README
vendored
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
git-jump
|
||||
========
|
||||
|
||||
Git-jump is a script for helping you jump to "interesting" parts of your
|
||||
project in your editor. It works by outputting a set of interesting
|
||||
spots in the "quickfix" format, which editors like vim can use as a
|
||||
queue of places to visit (this feature is usually used to jump to errors
|
||||
produced by a compiler). For example, given a diff like this:
|
||||
|
||||
------------------------------------
|
||||
diff --git a/foo.c b/foo.c
|
||||
index a655540..5a59044 100644
|
||||
--- a/foo.c
|
||||
+++ b/foo.c
|
||||
@@ -1,3 +1,3 @@
|
||||
int main(void) {
|
||||
- printf("hello word!\n");
|
||||
+ printf("hello world!\n");
|
||||
}
|
||||
-----------------------------------
|
||||
|
||||
git-jump will feed this to the editor:
|
||||
|
||||
-----------------------------------
|
||||
foo.c:2: printf("hello word!\n");
|
||||
-----------------------------------
|
||||
|
||||
Or, when running 'git jump grep', column numbers will also be emitted,
|
||||
e.g. `git jump grep "hello"` would return:
|
||||
|
||||
-----------------------------------
|
||||
foo.c:2:9: printf("hello word!\n");
|
||||
-----------------------------------
|
||||
|
||||
Obviously this trivial case isn't that interesting; you could just open
|
||||
`foo.c` yourself. But when you have many changes scattered across a
|
||||
project, you can use the editor's support to "jump" from point to point.
|
||||
|
||||
Git-jump can generate four types of interesting lists:
|
||||
|
||||
1. The beginning of any diff hunks.
|
||||
|
||||
2. The beginning of any merge conflict markers.
|
||||
|
||||
3. Any grep matches, including the column of the first match on a
|
||||
line.
|
||||
|
||||
4. Any whitespace errors detected by `git diff --check`.
|
||||
|
||||
|
||||
Using git-jump
|
||||
--------------
|
||||
|
||||
To use it, just drop git-jump in your PATH, and then invoke it like
|
||||
this:
|
||||
|
||||
--------------------------------------------------
|
||||
# jump to changes not yet staged for commit
|
||||
git jump diff
|
||||
|
||||
# jump to changes that are staged for commit; you can give
|
||||
# arbitrary diff options
|
||||
git jump diff --cached
|
||||
|
||||
# jump to merge conflicts
|
||||
git jump merge
|
||||
|
||||
# jump to all instances of foo_bar
|
||||
git jump grep foo_bar
|
||||
|
||||
# same as above, but case-insensitive; you can give
|
||||
# arbitrary grep options
|
||||
git jump grep -i foo_bar
|
||||
|
||||
# use the silver searcher for git jump grep
|
||||
git config jump.grepCmd "ag --column"
|
||||
--------------------------------------------------
|
||||
|
||||
|
||||
Related Programs
|
||||
----------------
|
||||
|
||||
You can accomplish some of the same things with individual tools. For
|
||||
example, you can use `git mergetool` to start vimdiff on each unmerged
|
||||
file. `git jump merge` is for the vim-wielding luddite who just wants to
|
||||
jump straight to the conflict text with no fanfare.
|
||||
|
||||
As of git v1.7.2, `git grep` knows the `--open-files-in-pager` option,
|
||||
which does something similar to `git jump grep`. However, it is limited
|
||||
to positioning the cursor to the correct line in only the first file,
|
||||
leaving you to locate subsequent hits in that file or other files using
|
||||
the editor or pager. By contrast, git-jump provides the editor with a
|
||||
complete list of files, lines, and a column number for each match.
|
||||
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
This script was written and tested with vim. Given that the quickfix
|
||||
format is the same as what gcc produces, I expect emacs users have a
|
||||
similar feature for iterating through the list, but I know nothing about
|
||||
how to activate it.
|
||||
|
||||
The shell snippets to generate the quickfix lines will almost certainly
|
||||
choke on filenames with exotic characters (like newlines).
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
Bug fixes, bug reports, and feature requests should be discussed on the
|
||||
Git mailing list <git@vger.kernel.org>, and cc'd to the git-jump
|
||||
maintainer, Jeff King <peff@peff.net>.
|
||||
78
third_party/git/contrib/git-jump/git-jump
vendored
Executable file
78
third_party/git/contrib/git-jump/git-jump
vendored
Executable file
|
|
@ -0,0 +1,78 @@
|
|||
#!/bin/sh
|
||||
|
||||
usage() {
|
||||
cat <<\EOF
|
||||
usage: git jump <mode> [<args>]
|
||||
|
||||
Jump to interesting elements in an editor.
|
||||
The <mode> parameter is one of:
|
||||
|
||||
diff: elements are diff hunks. Arguments are given to diff.
|
||||
|
||||
merge: elements are merge conflicts. Arguments are ignored.
|
||||
|
||||
grep: elements are grep hits. Arguments are given to git grep or, if
|
||||
configured, to the command in `jump.grepCmd`.
|
||||
|
||||
ws: elements are whitespace errors. Arguments are given to diff --check.
|
||||
EOF
|
||||
}
|
||||
|
||||
open_editor() {
|
||||
editor=`git var GIT_EDITOR`
|
||||
eval "$editor -q \$1"
|
||||
}
|
||||
|
||||
mode_diff() {
|
||||
git diff --no-prefix --relative "$@" |
|
||||
perl -ne '
|
||||
if (m{^\+\+\+ (.*)}) { $file = $1; next }
|
||||
defined($file) or next;
|
||||
if (m/^@@ .*?\+(\d+)/) { $line = $1; next }
|
||||
defined($line) or next;
|
||||
if (/^ /) { $line++; next }
|
||||
if (/^[-+]\s*(.*)/) {
|
||||
print "$file:$line: $1\n";
|
||||
$line = undef;
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
mode_merge() {
|
||||
git ls-files -u |
|
||||
perl -pe 's/^.*?\t//' |
|
||||
sort -u |
|
||||
while IFS= read fn; do
|
||||
grep -Hn '^<<<<<<<' "$fn"
|
||||
done
|
||||
}
|
||||
|
||||
# Grep -n generates nice quickfix-looking lines by itself,
|
||||
# but let's clean up extra whitespace, so they look better if the
|
||||
# editor shows them to us in the status bar.
|
||||
mode_grep() {
|
||||
cmd=$(git config jump.grepCmd)
|
||||
test -n "$cmd" || cmd="git grep -n --column"
|
||||
$cmd "$@" |
|
||||
perl -pe '
|
||||
s/[ \t]+/ /g;
|
||||
s/^ *//;
|
||||
'
|
||||
}
|
||||
|
||||
mode_ws() {
|
||||
git diff --check "$@"
|
||||
}
|
||||
|
||||
if test $# -lt 1; then
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
mode=$1; shift
|
||||
|
||||
trap 'rm -f "$tmp"' 0 1 2 3 15
|
||||
tmp=`mktemp -t git-jump.XXXXXX` || exit 1
|
||||
type "mode_$mode" >/dev/null 2>&1 || { usage >&2; exit 1; }
|
||||
"mode_$mode" "$@" >"$tmp"
|
||||
test -s "$tmp" || exit 0
|
||||
open_editor "$tmp"
|
||||
182
third_party/git/contrib/git-resurrect.sh
vendored
Executable file
182
third_party/git/contrib/git-resurrect.sh
vendored
Executable file
|
|
@ -0,0 +1,182 @@
|
|||
#!/bin/sh
|
||||
|
||||
USAGE="[-a] [-r] [-m] [-t] [-n] [-b <newname>] <name>"
|
||||
LONG_USAGE="git-resurrect attempts to find traces of a branch tip
|
||||
called <name>, and tries to resurrect it. Currently, the reflog is
|
||||
searched for checkout messages, and with -r also merge messages. With
|
||||
-m and -t, the history of all refs is scanned for Merge <name> into
|
||||
other/Merge <other> into <name> (respectively) commit subjects, which
|
||||
is rather slow but allows you to resurrect other people's topic
|
||||
branches."
|
||||
|
||||
OPTIONS_KEEPDASHDASH=
|
||||
OPTIONS_STUCKLONG=
|
||||
OPTIONS_SPEC="\
|
||||
git resurrect $USAGE
|
||||
--
|
||||
b,branch= save branch as <newname> instead of <name>
|
||||
a,all same as -l -r -m -t
|
||||
k,keep-going full rev-list scan (instead of first match)
|
||||
l,reflog scan reflog for checkouts (enabled by default)
|
||||
r,reflog-merges scan for merges recorded in reflog
|
||||
m,merges scan for merges into other branches (slow)
|
||||
t,merge-targets scan for merges of other branches into <name>
|
||||
n,dry-run don't recreate the branch"
|
||||
|
||||
. git-sh-setup
|
||||
|
||||
search_reflog () {
|
||||
sed -ne 's~^\([^ ]*\) .* checkout: moving from '"$1"' .*~\1~p' \
|
||||
< "$GIT_DIR"/logs/HEAD
|
||||
}
|
||||
|
||||
search_reflog_merges () {
|
||||
git rev-parse $(
|
||||
sed -ne 's~^[^ ]* \([^ ]*\) .* merge '"$1"':.*~\1^2~p' \
|
||||
< "$GIT_DIR"/logs/HEAD
|
||||
)
|
||||
}
|
||||
|
||||
_x40="[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]"
|
||||
_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
|
||||
|
||||
search_merges () {
|
||||
git rev-list --all --grep="Merge branch '$1'" \
|
||||
--pretty=tformat:"%P %s" |
|
||||
sed -ne "/^$_x40 \($_x40\) Merge .*/ {s//\1/p;$early_exit}"
|
||||
}
|
||||
|
||||
search_merge_targets () {
|
||||
git rev-list --all --grep="Merge branch '[^']*' into $branch\$" \
|
||||
--pretty=tformat:"%H %s" --all |
|
||||
sed -ne "/^\($_x40\) Merge .*/ {s//\1/p;$early_exit} "
|
||||
}
|
||||
|
||||
dry_run=
|
||||
early_exit=q
|
||||
scan_reflog=t
|
||||
scan_reflog_merges=
|
||||
scan_merges=
|
||||
scan_merge_targets=
|
||||
new_name=
|
||||
|
||||
while test "$#" != 0; do
|
||||
case "$1" in
|
||||
-b|--branch)
|
||||
shift
|
||||
new_name="$1"
|
||||
;;
|
||||
-n|--dry-run)
|
||||
dry_run=t
|
||||
;;
|
||||
--no-dry-run)
|
||||
dry_run=
|
||||
;;
|
||||
-k|--keep-going)
|
||||
early_exit=
|
||||
;;
|
||||
--no-keep-going)
|
||||
early_exit=q
|
||||
;;
|
||||
-m|--merges)
|
||||
scan_merges=t
|
||||
;;
|
||||
--no-merges)
|
||||
scan_merges=
|
||||
;;
|
||||
-l|--reflog)
|
||||
scan_reflog=t
|
||||
;;
|
||||
--no-reflog)
|
||||
scan_reflog=
|
||||
;;
|
||||
-r|--reflog_merges)
|
||||
scan_reflog_merges=t
|
||||
;;
|
||||
--no-reflog_merges)
|
||||
scan_reflog_merges=
|
||||
;;
|
||||
-t|--merge-targets)
|
||||
scan_merge_targets=t
|
||||
;;
|
||||
--no-merge-targets)
|
||||
scan_merge_targets=
|
||||
;;
|
||||
-a|--all)
|
||||
scan_reflog=t
|
||||
scan_reflog_merges=t
|
||||
scan_merges=t
|
||||
scan_merge_targets=t
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
break
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
test "$#" = 1 || usage
|
||||
|
||||
all_strategies="$scan_reflog$scan_reflog_merges$scan_merges$scan_merge_targets"
|
||||
if test -z "$all_strategies"; then
|
||||
die "must enable at least one of -lrmt"
|
||||
fi
|
||||
|
||||
branch="$1"
|
||||
test -z "$new_name" && new_name="$branch"
|
||||
|
||||
if test ! -z "$scan_reflog"; then
|
||||
if test -r "$GIT_DIR"/logs/HEAD; then
|
||||
candidates="$(search_reflog $branch)"
|
||||
else
|
||||
die 'reflog scanning requested, but' \
|
||||
'$GIT_DIR/logs/HEAD not readable'
|
||||
fi
|
||||
fi
|
||||
if test ! -z "$scan_reflog_merges"; then
|
||||
if test -r "$GIT_DIR"/logs/HEAD; then
|
||||
candidates="$candidates $(search_reflog_merges $branch)"
|
||||
else
|
||||
die 'reflog scanning requested, but' \
|
||||
'$GIT_DIR/logs/HEAD not readable'
|
||||
fi
|
||||
fi
|
||||
if test ! -z "$scan_merges"; then
|
||||
candidates="$candidates $(search_merges $branch)"
|
||||
fi
|
||||
if test ! -z "$scan_merge_targets"; then
|
||||
candidates="$candidates $(search_merge_targets $branch)"
|
||||
fi
|
||||
|
||||
candidates="$(git rev-parse $candidates | sort -u)"
|
||||
|
||||
if test -z "$candidates"; then
|
||||
hint=
|
||||
test "z$all_strategies" != "ztttt" \
|
||||
&& hint=" (maybe try again with -a)"
|
||||
die "no candidates for $branch found$hint"
|
||||
fi
|
||||
|
||||
echo "** Candidates for $branch **"
|
||||
for cmt in $candidates; do
|
||||
git --no-pager log --pretty=tformat:"%ct:%h [%cr] %s" --abbrev-commit -1 $cmt
|
||||
done \
|
||||
| sort -n | cut -d: -f2-
|
||||
|
||||
newest="$(git rev-list -1 $candidates)"
|
||||
if test ! -z "$dry_run"; then
|
||||
printf "** Most recent: "
|
||||
git --no-pager log -1 --pretty=tformat:"%h %s" $newest
|
||||
elif ! git rev-parse --verify --quiet $new_name >/dev/null; then
|
||||
printf "** Restoring $new_name to "
|
||||
git --no-pager log -1 --pretty=tformat:"%h %s" $newest
|
||||
git branch $new_name $newest
|
||||
else
|
||||
printf "Most recent: "
|
||||
git --no-pager log -1 --pretty=tformat:"%h %s" $newest
|
||||
echo "** $new_name already exists, doing nothing"
|
||||
fi
|
||||
18
third_party/git/contrib/git-shell-commands/README
vendored
Normal file
18
third_party/git/contrib/git-shell-commands/README
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
Sample programs callable through git-shell. Place a directory named
|
||||
'git-shell-commands' in the home directory of a user whose shell is
|
||||
git-shell. Then anyone logging in as that user will be able to run
|
||||
executables in the 'git-shell-commands' directory.
|
||||
|
||||
Provided commands:
|
||||
|
||||
help: Prints out the names of available commands. When run
|
||||
interactively, git-shell will automatically run 'help' on startup,
|
||||
provided it exists.
|
||||
|
||||
list: Displays any bare repository whose name ends with ".git" under
|
||||
user's home directory. No other git repositories are visible,
|
||||
although they might be clonable through git-shell. 'list' is designed
|
||||
to minimize the number of calls to git that must be made in finding
|
||||
available repositories; if your setup has additional repositories that
|
||||
should be user-discoverable, you may wish to modify 'list'
|
||||
accordingly.
|
||||
18
third_party/git/contrib/git-shell-commands/help
vendored
Executable file
18
third_party/git/contrib/git-shell-commands/help
vendored
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
#!/bin/sh
|
||||
|
||||
if tty -s
|
||||
then
|
||||
echo "Run 'help' for help, or 'exit' to leave. Available commands:"
|
||||
else
|
||||
echo "Run 'help' for help. Available commands:"
|
||||
fi
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
for cmd in *
|
||||
do
|
||||
case "$cmd" in
|
||||
help) ;;
|
||||
*) [ -f "$cmd" ] && [ -x "$cmd" ] && echo "$cmd" ;;
|
||||
esac
|
||||
done
|
||||
10
third_party/git/contrib/git-shell-commands/list
vendored
Executable file
10
third_party/git/contrib/git-shell-commands/list
vendored
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
#!/bin/sh
|
||||
|
||||
print_if_bare_repo='
|
||||
if "$(git --git-dir="$1" rev-parse --is-bare-repository)" = true
|
||||
then
|
||||
printf "%s\n" "${1#./}"
|
||||
fi
|
||||
'
|
||||
|
||||
find -type d -name "*.git" -exec sh -c "$print_if_bare_repo" -- \{} \; -prune 2>/dev/null
|
||||
254
third_party/git/contrib/hg-to-git/hg-to-git.py
vendored
Executable file
254
third_party/git/contrib/hg-to-git/hg-to-git.py
vendored
Executable file
|
|
@ -0,0 +1,254 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
""" hg-to-git.py - A Mercurial to GIT converter
|
||||
|
||||
Copyright (C)2007 Stelian Pop <stelian@popies.net>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import os, os.path, sys
|
||||
import tempfile, pickle, getopt
|
||||
import re
|
||||
|
||||
if sys.hexversion < 0x02030000:
|
||||
# The behavior of the pickle module changed significantly in 2.3
|
||||
sys.stderr.write("hg-to-git.py: requires Python 2.3 or later.\n")
|
||||
sys.exit(1)
|
||||
|
||||
# Maps hg version -> git version
|
||||
hgvers = {}
|
||||
# List of children for each hg revision
|
||||
hgchildren = {}
|
||||
# List of parents for each hg revision
|
||||
hgparents = {}
|
||||
# Current branch for each hg revision
|
||||
hgbranch = {}
|
||||
# Number of new changesets converted from hg
|
||||
hgnewcsets = 0
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
def usage():
|
||||
|
||||
print """\
|
||||
%s: [OPTIONS] <hgprj>
|
||||
|
||||
options:
|
||||
-s, --gitstate=FILE: name of the state to be saved/read
|
||||
for incrementals
|
||||
-n, --nrepack=INT: number of changesets that will trigger
|
||||
a repack (default=0, -1 to deactivate)
|
||||
-v, --verbose: be verbose
|
||||
|
||||
required:
|
||||
hgprj: name of the HG project to import (directory)
|
||||
""" % sys.argv[0]
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
def getgitenv(user, date):
|
||||
env = ''
|
||||
elems = re.compile('(.*?)\s+<(.*)>').match(user)
|
||||
if elems:
|
||||
env += 'export GIT_AUTHOR_NAME="%s" ;' % elems.group(1)
|
||||
env += 'export GIT_COMMITTER_NAME="%s" ;' % elems.group(1)
|
||||
env += 'export GIT_AUTHOR_EMAIL="%s" ;' % elems.group(2)
|
||||
env += 'export GIT_COMMITTER_EMAIL="%s" ;' % elems.group(2)
|
||||
else:
|
||||
env += 'export GIT_AUTHOR_NAME="%s" ;' % user
|
||||
env += 'export GIT_COMMITTER_NAME="%s" ;' % user
|
||||
env += 'export GIT_AUTHOR_EMAIL= ;'
|
||||
env += 'export GIT_COMMITTER_EMAIL= ;'
|
||||
|
||||
env += 'export GIT_AUTHOR_DATE="%s" ;' % date
|
||||
env += 'export GIT_COMMITTER_DATE="%s" ;' % date
|
||||
return env
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
state = ''
|
||||
opt_nrepack = 0
|
||||
verbose = False
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], 's:t:n:v', ['gitstate=', 'tempdir=', 'nrepack=', 'verbose'])
|
||||
for o, a in opts:
|
||||
if o in ('-s', '--gitstate'):
|
||||
state = a
|
||||
state = os.path.abspath(state)
|
||||
if o in ('-n', '--nrepack'):
|
||||
opt_nrepack = int(a)
|
||||
if o in ('-v', '--verbose'):
|
||||
verbose = True
|
||||
if len(args) != 1:
|
||||
raise Exception('params')
|
||||
except:
|
||||
usage()
|
||||
sys.exit(1)
|
||||
|
||||
hgprj = args[0]
|
||||
os.chdir(hgprj)
|
||||
|
||||
if state:
|
||||
if os.path.exists(state):
|
||||
if verbose:
|
||||
print 'State does exist, reading'
|
||||
f = open(state, 'r')
|
||||
hgvers = pickle.load(f)
|
||||
else:
|
||||
print 'State does not exist, first run'
|
||||
|
||||
sock = os.popen('hg tip --template "{rev}"')
|
||||
tip = sock.read()
|
||||
if sock.close():
|
||||
sys.exit(1)
|
||||
if verbose:
|
||||
print 'tip is', tip
|
||||
|
||||
# Calculate the branches
|
||||
if verbose:
|
||||
print 'analysing the branches...'
|
||||
hgchildren["0"] = ()
|
||||
hgparents["0"] = (None, None)
|
||||
hgbranch["0"] = "master"
|
||||
for cset in range(1, int(tip) + 1):
|
||||
hgchildren[str(cset)] = ()
|
||||
prnts = os.popen('hg log -r %d --template "{parents}"' % cset).read().strip().split(' ')
|
||||
prnts = map(lambda x: x[:x.find(':')], prnts)
|
||||
if prnts[0] != '':
|
||||
parent = prnts[0].strip()
|
||||
else:
|
||||
parent = str(cset - 1)
|
||||
hgchildren[parent] += ( str(cset), )
|
||||
if len(prnts) > 1:
|
||||
mparent = prnts[1].strip()
|
||||
hgchildren[mparent] += ( str(cset), )
|
||||
else:
|
||||
mparent = None
|
||||
|
||||
hgparents[str(cset)] = (parent, mparent)
|
||||
|
||||
if mparent:
|
||||
# For merge changesets, take either one, preferably the 'master' branch
|
||||
if hgbranch[mparent] == 'master':
|
||||
hgbranch[str(cset)] = 'master'
|
||||
else:
|
||||
hgbranch[str(cset)] = hgbranch[parent]
|
||||
else:
|
||||
# Normal changesets
|
||||
# For first children, take the parent branch, for the others create a new branch
|
||||
if hgchildren[parent][0] == str(cset):
|
||||
hgbranch[str(cset)] = hgbranch[parent]
|
||||
else:
|
||||
hgbranch[str(cset)] = "branch-" + str(cset)
|
||||
|
||||
if not hgvers.has_key("0"):
|
||||
print 'creating repository'
|
||||
os.system('git init')
|
||||
|
||||
# loop through every hg changeset
|
||||
for cset in range(int(tip) + 1):
|
||||
|
||||
# incremental, already seen
|
||||
if hgvers.has_key(str(cset)):
|
||||
continue
|
||||
hgnewcsets += 1
|
||||
|
||||
# get info
|
||||
log_data = os.popen('hg log -r %d --template "{tags}\n{date|date}\n{author}\n"' % cset).readlines()
|
||||
tag = log_data[0].strip()
|
||||
date = log_data[1].strip()
|
||||
user = log_data[2].strip()
|
||||
parent = hgparents[str(cset)][0]
|
||||
mparent = hgparents[str(cset)][1]
|
||||
|
||||
#get comment
|
||||
(fdcomment, filecomment) = tempfile.mkstemp()
|
||||
csetcomment = os.popen('hg log -r %d --template "{desc}"' % cset).read().strip()
|
||||
os.write(fdcomment, csetcomment)
|
||||
os.close(fdcomment)
|
||||
|
||||
print '-----------------------------------------'
|
||||
print 'cset:', cset
|
||||
print 'branch:', hgbranch[str(cset)]
|
||||
print 'user:', user
|
||||
print 'date:', date
|
||||
print 'comment:', csetcomment
|
||||
if parent:
|
||||
print 'parent:', parent
|
||||
if mparent:
|
||||
print 'mparent:', mparent
|
||||
if tag:
|
||||
print 'tag:', tag
|
||||
print '-----------------------------------------'
|
||||
|
||||
# checkout the parent if necessary
|
||||
if cset != 0:
|
||||
if hgbranch[str(cset)] == "branch-" + str(cset):
|
||||
print 'creating new branch', hgbranch[str(cset)]
|
||||
os.system('git checkout -b %s %s' % (hgbranch[str(cset)], hgvers[parent]))
|
||||
else:
|
||||
print 'checking out branch', hgbranch[str(cset)]
|
||||
os.system('git checkout %s' % hgbranch[str(cset)])
|
||||
|
||||
# merge
|
||||
if mparent:
|
||||
if hgbranch[parent] == hgbranch[str(cset)]:
|
||||
otherbranch = hgbranch[mparent]
|
||||
else:
|
||||
otherbranch = hgbranch[parent]
|
||||
print 'merging', otherbranch, 'into', hgbranch[str(cset)]
|
||||
os.system(getgitenv(user, date) + 'git merge --no-commit -s ours "" %s %s' % (hgbranch[str(cset)], otherbranch))
|
||||
|
||||
# remove everything except .git and .hg directories
|
||||
os.system('find . \( -path "./.hg" -o -path "./.git" \) -prune -o ! -name "." -print | xargs rm -rf')
|
||||
|
||||
# repopulate with checkouted files
|
||||
os.system('hg update -C %d' % cset)
|
||||
|
||||
# add new files
|
||||
os.system('git ls-files -x .hg --others | git update-index --add --stdin')
|
||||
# delete removed files
|
||||
os.system('git ls-files -x .hg --deleted | git update-index --remove --stdin')
|
||||
|
||||
# commit
|
||||
os.system(getgitenv(user, date) + 'git commit --allow-empty --allow-empty-message -a -F %s' % filecomment)
|
||||
os.unlink(filecomment)
|
||||
|
||||
# tag
|
||||
if tag and tag != 'tip':
|
||||
os.system(getgitenv(user, date) + 'git tag %s' % tag)
|
||||
|
||||
# delete branch if not used anymore...
|
||||
if mparent and len(hgchildren[str(cset)]):
|
||||
print "Deleting unused branch:", otherbranch
|
||||
os.system('git branch -d %s' % otherbranch)
|
||||
|
||||
# retrieve and record the version
|
||||
vvv = os.popen('git show --quiet --pretty=format:%H').read()
|
||||
print 'record', cset, '->', vvv
|
||||
hgvers[str(cset)] = vvv
|
||||
|
||||
if hgnewcsets >= opt_nrepack and opt_nrepack != -1:
|
||||
os.system('git repack -a -d')
|
||||
|
||||
# write the state for incrementals
|
||||
if state:
|
||||
if verbose:
|
||||
print 'Writing state'
|
||||
f = open(state, 'w')
|
||||
pickle.dump(hgvers, f)
|
||||
|
||||
# vim: et ts=8 sw=4 sts=4
|
||||
21
third_party/git/contrib/hg-to-git/hg-to-git.txt
vendored
Normal file
21
third_party/git/contrib/hg-to-git/hg-to-git.txt
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
hg-to-git.py is able to convert a Mercurial repository into a git one,
|
||||
and preserves the branches in the process (unlike tailor)
|
||||
|
||||
hg-to-git.py can probably be greatly improved (it's a rather crude
|
||||
combination of shell and python) but it does already work quite well for
|
||||
me. Features:
|
||||
- supports incremental conversion
|
||||
(for keeping a git repo in sync with a hg one)
|
||||
- supports hg branches
|
||||
- converts hg tags
|
||||
|
||||
Note that the git repository will be created 'in place' (at the same
|
||||
location as the source hg repo). You will have to manually remove the
|
||||
'.hg' directory after the conversion.
|
||||
|
||||
Also note that the incremental conversion uses 'simple' hg changesets
|
||||
identifiers (ordinals, as opposed to SHA-1 ids), and since these ids
|
||||
are not stable across different repositories the hg-to-git.py state file
|
||||
is forever tied to one hg repository.
|
||||
|
||||
Stelian Pop <stelian@popies.net>
|
||||
285
third_party/git/contrib/hooks/multimail/CHANGES
vendored
Normal file
285
third_party/git/contrib/hooks/multimail/CHANGES
vendored
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
Release 1.5.0
|
||||
=============
|
||||
|
||||
Backward-incompatible change
|
||||
----------------------------
|
||||
|
||||
The name of classes for environment was misnamed as `*Environement`.
|
||||
It is now `*Environment`.
|
||||
|
||||
New features
|
||||
------------
|
||||
|
||||
* A Thread-Index header is now added to each email sent (except for
|
||||
combined emails where it would not make sense), so that MS Outlook
|
||||
properly groups messages by threads even though they have a
|
||||
different subject line. Unfortunately, even adding this header the
|
||||
threading still seems to be unreliable, but it is unclear whether
|
||||
this is an issue on our side or on MS Outlook's side (see discussion
|
||||
here: https://github.com/git-multimail/git-multimail/pull/194).
|
||||
|
||||
* A new variable multimailhook.ExcludeMergeRevisions was added to send
|
||||
notification emails only for non-merge commits.
|
||||
|
||||
* For gitolite environment, it is now possible to specify the mail map
|
||||
in a separate file in addition to gitolite.conf, using the variable
|
||||
multimailhook.MailaddressMap.
|
||||
|
||||
Internal changes
|
||||
----------------
|
||||
|
||||
* The testsuite now uses GIT_PRINT_SHA1_ELLIPSIS where needed for
|
||||
compatibility with recent Git versions. Only tests are affected.
|
||||
|
||||
* We don't try to install pyflakes in the continuous integration job
|
||||
for old Python versions where it's no longer available.
|
||||
|
||||
* Stop using the deprecated cgi.escape in Python 3.
|
||||
|
||||
* New flake8 warnings have been fixed.
|
||||
|
||||
* Python 3.6 is now tested against on Travis-CI.
|
||||
|
||||
* A bunch of lgtm.com warnings have been fixed.
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
* SMTPMailer logs in only once now. It used to re-login for each email
|
||||
sent which triggered errors for some SMTP servers.
|
||||
|
||||
* migrate-mailhook-config was broken by internal refactoring, it
|
||||
should now work again.
|
||||
|
||||
This version was tested with Python 2.6 to 3.7. It was tested with Git
|
||||
1.7.10.406.gdc801, 2.15.1 and 2.20.1.98.gecbdaf0.
|
||||
|
||||
Release 1.4.0
|
||||
=============
|
||||
|
||||
New features to troubleshoot a git-multimail installation
|
||||
---------------------------------------------------------
|
||||
|
||||
* One can now perform a basic check of git-multimail's setup by
|
||||
running the hook with the environment variable
|
||||
GIT_MULTIMAIL_CHECK_SETUP set to a non-empty string. See
|
||||
doc/troubleshooting.rst for details.
|
||||
|
||||
* A new log files system was added. See the multimailhook.logFile,
|
||||
multimailhook.errorLogFile and multimailhook.debugLogFile variables.
|
||||
|
||||
* git_multimail.py can now be made more verbose using
|
||||
multimailhook.verbose.
|
||||
|
||||
* A new option --check-ref-filter is now available to help debugging
|
||||
the refFilter* options.
|
||||
|
||||
Formatting emails
|
||||
-----------------
|
||||
|
||||
* Formatting of emails was made slightly more compact, to reduce the
|
||||
odds of having long subject lines truncated or wrapped in short list
|
||||
of commits.
|
||||
|
||||
* multimailhook.emailPrefix may now use the '%(repo_shortname)s'
|
||||
placeholder for the repository's short name.
|
||||
|
||||
* A new option multimailhook.subjectMaxLength is available to truncate
|
||||
overly long subject lines.
|
||||
|
||||
Bug fixes and minor changes
|
||||
---------------------------
|
||||
|
||||
* Options refFilterDoSendRegex and refFilterDontSendRegex were
|
||||
essentially broken. They should work now.
|
||||
|
||||
* The behavior when both refFilter{Do,Dont}SendRegex and
|
||||
refFilter{Exclusion,Inclusion}Regex are set have been slightly
|
||||
changed. Exclusion/Inclusion is now strictly stronger than
|
||||
DoSend/DontSend.
|
||||
|
||||
* The management of precedence when a setting can be computed in
|
||||
multiple ways has been considerably refactored and modified.
|
||||
multimailhook.from and multimailhook.reponame now have precedence
|
||||
over the environment-specific settings ($GL_REPO/$GL_USER for
|
||||
gitolite, --stash-user/repo for Stash, --submitter/--project for
|
||||
Gerrit).
|
||||
|
||||
* The coverage of the testsuite has been considerably improved. All
|
||||
configuration variables now appear at least once in the testsuite.
|
||||
|
||||
This version was tested with Python 2.6 to 3.5. It also mostly works
|
||||
with Python 2.4, but there is one known breakage in the testsuite
|
||||
related to non-ascii characters. It was tested with Git
|
||||
1.7.10.406.gdc801, 1.8.5.6, 2.1.4, and 2.10.0.rc0.1.g07c9292.
|
||||
|
||||
Release 1.3.1 (bugfix-only release)
|
||||
===================================
|
||||
|
||||
* Generate links to commits in combined emails (it was done only for
|
||||
commit emails in 1.3.0).
|
||||
|
||||
* Fix broken links on PyPi.
|
||||
|
||||
Release 1.3.0
|
||||
=============
|
||||
|
||||
* New options multimailhook.htmlInIntro and multimailhook.htmlInFooter
|
||||
now allow using HTML in the introduction and footer of emails (e.g.
|
||||
for a more pleasant formatting or to insert a link to the commit on
|
||||
a web interface).
|
||||
|
||||
* A new option multimailhook.commitBrowseURL gives a simpler (and less
|
||||
flexible) way to add a link to a web interface for commit emails
|
||||
than multimailhook.htmlInIntro and multimailhook.htmlInFooter.
|
||||
|
||||
* A new public function config.add_config_parameters was added to
|
||||
allow custom hooks to set specific Git configuration variables
|
||||
without modifying the configuration files. See an example in
|
||||
post-receive.example.
|
||||
|
||||
* Error handling for SMTP has been improved (we used to print Python
|
||||
backtraces for legitimate errors).
|
||||
|
||||
* The SMTP mailer can now check TLS certificates when the newly added
|
||||
configuration variable multimailhook.smtpCACerts.
|
||||
|
||||
* Python 3 portability has been improved.
|
||||
|
||||
* The documentation's formatting has been improved.
|
||||
|
||||
* The testsuite has been improved (we now use pyflakes to check for
|
||||
errors in the code).
|
||||
|
||||
This version has been tested with Python 2.4 and 2.6 to 3.5, and Git
|
||||
v1.7.10-406-gdc801e7, 2.1.4 and 2.8.1.339.g3ad15fd.
|
||||
|
||||
No change since 1.3 RC1.
|
||||
|
||||
Release 1.2.0
|
||||
=============
|
||||
|
||||
* It is now possible to exclude some refs (e.g. exclude some branches
|
||||
or tags). See refFilterDoSendRegex, refFilterDontSendRegex,
|
||||
refFilterInclusionRegex and refFilterExclusionRegex.
|
||||
|
||||
* New commitEmailFormat option which can be set to "html" to generate
|
||||
simple colorized diffs using HTML for the commit emails.
|
||||
|
||||
* git-multimail can now be ran as a Gerrit ref-updated hook, or from
|
||||
Atlassian BitBucket Server (formerly known as Atlassian Stash).
|
||||
|
||||
* The From: field is now more customizeable. It can be set
|
||||
independently for refchange emails and commit emails (see
|
||||
fromCommit, fromRefChange). The special values pusher and author can
|
||||
be used in these configuration variable.
|
||||
|
||||
* A new command-line option, --version, was added. The version is also
|
||||
available in the X-Git-Multimail-Version header of sent emails.
|
||||
|
||||
* Set X-Git-NotificationType header to differentiate the various types
|
||||
of notifications. Current values are: diff, ref_changed_plus_diff,
|
||||
ref_changed.
|
||||
|
||||
* Preliminary support for Python 3. The testsuite passes with Python 3,
|
||||
but it has not received as much testing as the Python 2 version yet.
|
||||
|
||||
* Several encoding-related fixes. UTF-8 characters work in more
|
||||
situations (but non-ascii characters in email address are still not
|
||||
supported).
|
||||
|
||||
* The testsuite and its documentation has been greatly improved.
|
||||
|
||||
Plus all the bugfixes from version 1.1.1.
|
||||
|
||||
This version has been tested with Python 2.4 and 2.6 to 3.5, and Git
|
||||
v1.7.10-406-gdc801e7, git-1.8.2.3 and 2.6.0. Git versions prior to
|
||||
v1.7.10-406-gdc801e7 probably work, but cannot run the testsuite
|
||||
properly.
|
||||
|
||||
Release 1.1.1 (bugfix-only release)
|
||||
===================================
|
||||
|
||||
* The SMTP mailer was not working with Python 2.4.
|
||||
|
||||
Release 1.1.0
|
||||
=============
|
||||
|
||||
* When a single commit is pushed, omit the reference changed email.
|
||||
Set multimailhook.combineWhenSingleCommit to false to disable this
|
||||
new feature.
|
||||
|
||||
* In gitolite environments, the pusher's email address can be used as
|
||||
the From address by creating a specially formatted comment block in
|
||||
gitolite.conf (see multimailhook.from in README).
|
||||
|
||||
* Support for SMTP authentication and SSL/TLS encryption was added,
|
||||
see smtpUser, smtpPass, smtpEncryption in README.
|
||||
|
||||
* A new option scanCommitForCc was added to allow git-multimail to
|
||||
search the commit message for 'Cc: ...' lines, and add the
|
||||
corresponding emails in Cc.
|
||||
|
||||
* If $USER is not set, use the variable $USERNAME. This is needed on
|
||||
Windows platform to recognize the pusher.
|
||||
|
||||
* The emailPrefix variable can now be set to an empty string to remove
|
||||
the prefix.
|
||||
|
||||
* A short tutorial was added in doc/gitolite.rst to set up
|
||||
git-multimail with gitolite.
|
||||
|
||||
* The post-receive file was renamed to post-receive.example. It has
|
||||
always been an example (the standard way to call git-multimail is to
|
||||
call git_multimail.py), but it was unclear to many users.
|
||||
|
||||
* A new refchangeShowGraph option was added to make it possible to
|
||||
include both a graph and a log in the summary emails. The options
|
||||
to control the graph formatting can be set via the new graphOpts
|
||||
option.
|
||||
|
||||
* New option --force-send was added to disable new commit detection
|
||||
for update hook. One use-case is to run git_multimail.py after
|
||||
running "git fetch" to send emails about commits that have just been
|
||||
fetched (the detection of new commits was unreliable in this mode).
|
||||
|
||||
* The testing infrastructure was considerably improved (continuous
|
||||
integration with travis-ci, automatic check of PEP8 and RST syntax,
|
||||
many improvements to the test scripts).
|
||||
|
||||
This version has been tested with Python 2.4 to 2.7, and Git 1.7.1 to
|
||||
2.4.
|
||||
|
||||
Release 1.0.0
|
||||
=============
|
||||
|
||||
* Fix encoding of non-ASCII email addresses in email headers.
|
||||
|
||||
* Fix backwards-compatibility bugs for older Python 2.x versions.
|
||||
|
||||
* Fix a backwards-compatibility bug for Git 1.7.1.
|
||||
|
||||
* Add an option commitDiffOpts to customize logs for revisions.
|
||||
|
||||
* Pass "-oi" to sendmail by default to prevent premature termination
|
||||
on a line containing only ".".
|
||||
|
||||
* Stagger email "Date:" values in an attempt to help mail clients
|
||||
thread the emails in the right order.
|
||||
|
||||
* If a mailing list setting is missing, just skip sending the
|
||||
corresponding email (with a warning) instead of failing.
|
||||
|
||||
* Add a X-Git-Host header that can be used for email filtering.
|
||||
|
||||
* Allow the sender's fully-qualified domain name to be configured.
|
||||
|
||||
* Minor documentation improvements.
|
||||
|
||||
* Add this CHANGES file.
|
||||
|
||||
|
||||
Release 0.9.0
|
||||
=============
|
||||
|
||||
* Initial release.
|
||||
60
third_party/git/contrib/hooks/multimail/CONTRIBUTING.rst
vendored
Normal file
60
third_party/git/contrib/hooks/multimail/CONTRIBUTING.rst
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
Contributing
|
||||
============
|
||||
|
||||
git-multimail is an open-source project, built by volunteers. We would
|
||||
welcome your help!
|
||||
|
||||
The current maintainers are `Matthieu Moy <http://matthieu-moy.fr>`__ and
|
||||
`Michael Haggerty <https://github.com/mhagger>`__.
|
||||
|
||||
Please note that although a copy of git-multimail is distributed in
|
||||
the "contrib" section of the main Git project, development takes place
|
||||
in a separate `git-multimail repository on GitHub`_.
|
||||
|
||||
Whenever enough changes to git-multimail have accumulated, a new
|
||||
code-drop of git-multimail will be submitted for inclusion in the Git
|
||||
project.
|
||||
|
||||
We use the GitHub issue tracker to keep track of bugs and feature
|
||||
requests, and we use GitHub pull requests to exchange patches (though,
|
||||
if you prefer, you can send patches via the Git mailing list with CC
|
||||
to the maintainers). Please sign off your patches as per the `Git
|
||||
project practice
|
||||
<https://github.com/git/git/blob/master/Documentation/SubmittingPatches#L234>`__.
|
||||
|
||||
Please vote for issues you would like to be addressed in priority
|
||||
(click "add your reaction" and then the "+1" thumbs-up button on the
|
||||
GitHub issue).
|
||||
|
||||
General discussion of git-multimail can take place on the main `Git
|
||||
mailing list`_.
|
||||
|
||||
Please CC emails regarding git-multimail to the maintainers so that we
|
||||
don't overlook them.
|
||||
|
||||
Help needed: testers/maintainer for specific environments/OS
|
||||
------------------------------------------------------------
|
||||
|
||||
The current maintainer uses and tests git-multimail on Linux with the
|
||||
Generic environment. More testers, or better contributors are needed
|
||||
to test git-multimail on other real-life setups:
|
||||
|
||||
* Mac OS X, Windows: git-multimail is currently not supported on these
|
||||
platforms. But since we have no external dependencies and try to
|
||||
write code as portable as possible, it is possible that
|
||||
git-multimail already runs there and if not, it is likely that it
|
||||
could be ported easily.
|
||||
|
||||
Patches to improve support for Windows and OS X are welcome.
|
||||
Ideally, there would be a sub-maintainer for each OS who would test
|
||||
at least once before each release (around twice a year).
|
||||
|
||||
* Gerrit, Stash, Gitolite environments: although the testsuite
|
||||
contains tests for these environments, a tester/maintainer for each
|
||||
environment would be welcome to test and report failure (or success)
|
||||
on real-life environments periodically (here also, feedback before
|
||||
each release would be highly appreciated).
|
||||
|
||||
|
||||
.. _`git-multimail repository on GitHub`: https://github.com/git-multimail/git-multimail
|
||||
.. _`Git mailing list`: git@vger.kernel.org
|
||||
15
third_party/git/contrib/hooks/multimail/README.Git
vendored
Normal file
15
third_party/git/contrib/hooks/multimail/README.Git
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
This copy of git-multimail is distributed as part of the "contrib"
|
||||
section of the Git project as a convenience to Git users.
|
||||
git-multimail is developed as an independent project at the following
|
||||
website:
|
||||
|
||||
https://github.com/git-multimail/git-multimail
|
||||
|
||||
The version in this directory was obtained from the upstream project
|
||||
on January 07 2019 and consists of the "git-multimail" subdirectory from
|
||||
revision
|
||||
|
||||
04e80e6c40be465cc62b6c246f0fcb8fd2cfd454 refs/tags/1.5.0
|
||||
|
||||
Please see the README file in this directory for information about how
|
||||
to report bugs or contribute to git-multimail.
|
||||
145
third_party/git/contrib/hooks/multimail/README.migrate-from-post-receive-email
vendored
Normal file
145
third_party/git/contrib/hooks/multimail/README.migrate-from-post-receive-email
vendored
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
git-multimail is close to, but not exactly, a plug-in replacement for
|
||||
the old Git project script contrib/hooks/post-receive-email. This
|
||||
document describes the differences and explains how to configure
|
||||
git-multimail to get behavior closest to that of post-receive-email.
|
||||
|
||||
If you are in a hurry
|
||||
=====================
|
||||
|
||||
A script called migrate-mailhook-config is included with
|
||||
git-multimail. If you run this script within a Git repository that is
|
||||
configured to use post-receive-email, it will convert the
|
||||
configuration settings into the approximate equivalent settings for
|
||||
git-multimail. For more information, run
|
||||
|
||||
migrate-mailhook-config --help
|
||||
|
||||
|
||||
Configuration differences
|
||||
=========================
|
||||
|
||||
* The names of the config options for git-multimail are in namespace
|
||||
"multimailhook.*" instead of "hooks.*". (Editorial comment:
|
||||
post-receive-email should never have used such a generic top-level
|
||||
namespace.)
|
||||
|
||||
* In emails about new annotated tags, post-receive-email includes a
|
||||
shortlog of all changes since the previous annotated tag. To get
|
||||
this behavior with git-multimail, you need to set
|
||||
multimailhook.announceshortlog to true:
|
||||
|
||||
git config multimailhook.announceshortlog true
|
||||
|
||||
* multimailhook.commitlist -- This is a new configuration variable.
|
||||
Recipients listed here will receive a separate email for each new
|
||||
commit. However, if this variable is *not* set, it defaults to the
|
||||
value of multimailhook.mailinglist. Therefore, if you *don't* want
|
||||
the members of multimailhook.mailinglist to receive one email per
|
||||
commit, then set this value to the empty string:
|
||||
|
||||
git config multimailhook.commitlist ''
|
||||
|
||||
* multimailhook.emailprefix -- If this value is not set, then the
|
||||
subjects of generated emails are prefixed with the short name of the
|
||||
repository enclosed in square brackets; e.g., "[myrepo]".
|
||||
post-receive-email defaults to prefix "[SCM]" if this option is not
|
||||
set. So if you were using the old default and want to retain it
|
||||
(for example, to avoid having to change your email filters), set
|
||||
this variable explicitly to the old value:
|
||||
|
||||
git config multimailhook.emailprefix "[SCM]"
|
||||
|
||||
* The "multimailhook.showrev" configuration option is not supported.
|
||||
Its main use is obsoleted by the one-email-per-commit feature of
|
||||
git-multimail.
|
||||
|
||||
|
||||
Other differences
|
||||
=================
|
||||
|
||||
This section describes other differences in the behavior of
|
||||
git-multimail vs. post-receive-email. For full details, please refer
|
||||
to the main README file:
|
||||
|
||||
* One email per commit. For each reference change, the script first
|
||||
outputs one email summarizing the reference change (including
|
||||
one-line summaries of the new commits), then it outputs a separate
|
||||
email for each new commit that was introduced, including patches.
|
||||
These one-email-per-commit emails go to the addresses listed in
|
||||
multimailhook.commitlist. post-receive-email sends only one email
|
||||
for each *reference* that is changed, no matter how many commits
|
||||
were added to the reference.
|
||||
|
||||
* Better algorithm for detecting new commits. post-receive-email
|
||||
processes one reference change at a time, which causes it to fail to
|
||||
describe new commits that were included in multiple branches. For
|
||||
example, if a single push adds the "*" commits in the diagram below,
|
||||
then post-receive-email would never include the details of the two
|
||||
commits that are common to "master" and "branch" in its
|
||||
notifications.
|
||||
|
||||
o---o---o---*---*---* <-- master
|
||||
\
|
||||
*---* <-- branch
|
||||
|
||||
git-multimail analyzes all reference modifications to determine
|
||||
which commits were not present before the change, therefore avoiding
|
||||
that error.
|
||||
|
||||
* In reference change emails, git-multimail tells which commits have
|
||||
been added to the reference vs. are entirely new to the repository,
|
||||
and which commits that have been omitted from the reference
|
||||
vs. entirely discarded from the repository.
|
||||
|
||||
* The environment in which Git is running can be configured via an
|
||||
"Environment" abstraction.
|
||||
|
||||
* Built-in support for Gitolite-managed repositories.
|
||||
|
||||
* Instead of using full SHA1 object names in emails, git-multimail
|
||||
mostly uses abbreviated SHA1s, plus one-line log message summaries
|
||||
where appropriate.
|
||||
|
||||
* In the schematic diagrams that explain non-fast-forward commits,
|
||||
git-multimail shows the names of the branches involved.
|
||||
|
||||
* The emails generated by git-multimail include the name of the Git
|
||||
repository that was modified; this is convenient for recipients who
|
||||
are monitoring multiple repositories.
|
||||
|
||||
* git-multimail allows the email "From" addresses to be configured.
|
||||
|
||||
* The recipients lists (multimailhook.mailinglist,
|
||||
multimailhook.refchangelist, multimailhook.announcelist, and
|
||||
multimailhook.commitlist) can be comma-separated values and/or
|
||||
multivalued settings in the config file; e.g.,
|
||||
|
||||
[multimailhook]
|
||||
mailinglist = mr.brown@example.com, mr.black@example.com
|
||||
announcelist = Him <him@example.com>
|
||||
announcelist = Jim <jim@example.com>
|
||||
announcelist = pop@example.com
|
||||
|
||||
This might make it easier to maintain short recipients lists without
|
||||
requiring full-fledged mailing list software.
|
||||
|
||||
* By default, git-multimail sets email "Reply-To" headers to reply to
|
||||
the pusher (for reference updates) and to the author (for commit
|
||||
notifications). By default, the pusher's email address is
|
||||
constructed by appending "multimailhook.emaildomain" to the pusher's
|
||||
username.
|
||||
|
||||
* The generated emails contain a configurable footer. By default, it
|
||||
lists the name of the administrator who should be contacted to
|
||||
unsubscribe from notification emails.
|
||||
|
||||
* New option multimailhook.emailmaxlinelength to limit the length of
|
||||
lines in the main part of the email body. The default limit is 500
|
||||
characters.
|
||||
|
||||
* New option multimailhook.emailstrictutf8 to ensure that the main
|
||||
part of the email body is valid UTF-8. Invalid characters are
|
||||
turned into the Unicode replacement character, U+FFFD. By default
|
||||
this option is turned on.
|
||||
|
||||
* Written in Python. Easier to add new features.
|
||||
774
third_party/git/contrib/hooks/multimail/README.rst
vendored
Normal file
774
third_party/git/contrib/hooks/multimail/README.rst
vendored
Normal file
|
|
@ -0,0 +1,774 @@
|
|||
git-multimail version 1.5.0
|
||||
===========================
|
||||
|
||||
.. image:: https://travis-ci.org/git-multimail/git-multimail.svg?branch=master
|
||||
:target: https://travis-ci.org/git-multimail/git-multimail
|
||||
|
||||
git-multimail is a tool for sending notification emails on pushes to a
|
||||
Git repository. It includes a Python module called ``git_multimail.py``,
|
||||
which can either be used as a hook script directly or can be imported
|
||||
as a Python module into another script.
|
||||
|
||||
git-multimail is derived from the Git project's old
|
||||
contrib/hooks/post-receive-email, and is mostly compatible with that
|
||||
script. See README.migrate-from-post-receive-email for details about
|
||||
the differences and for how to migrate from post-receive-email to
|
||||
git-multimail.
|
||||
|
||||
git-multimail, like the rest of the Git project, is licensed under
|
||||
GPLv2 (see the COPYING file for details).
|
||||
|
||||
Please note: although, as a convenience, git-multimail may be
|
||||
distributed along with the main Git project, development of
|
||||
git-multimail takes place in its own, separate project. Please, read
|
||||
`<CONTRIBUTING.rst>`__ for more information.
|
||||
|
||||
|
||||
By default, for each push received by the repository, git-multimail:
|
||||
|
||||
1. Outputs one email summarizing each reference that was changed.
|
||||
These "reference change" (called "refchange" below) emails describe
|
||||
the nature of the change (e.g., was the reference created, deleted,
|
||||
fast-forwarded, etc.) and include a one-line summary of each commit
|
||||
that was added to the reference.
|
||||
|
||||
2. Outputs one email for each new commit that was introduced by the
|
||||
reference change. These "commit" emails include a list of the
|
||||
files changed by the commit, followed by the diffs of files
|
||||
modified by the commit. The commit emails are threaded to the
|
||||
corresponding reference change email via "In-Reply-To". This style
|
||||
(similar to the "git format-patch" style used on the Git mailing
|
||||
list) makes it easy to scan through the emails, jump to patches
|
||||
that need further attention, and write comments about specific
|
||||
commits. Commits are handled in reverse topological order (i.e.,
|
||||
parents shown before children). For example::
|
||||
|
||||
[git] branch master updated
|
||||
+ [git] 01/08: doc: fix xref link from api docs to manual pages
|
||||
+ [git] 02/08: api-credentials.txt: show the big picture first
|
||||
+ [git] 03/08: api-credentials.txt: mention credential.helper explicitly
|
||||
+ [git] 04/08: api-credentials.txt: add "see also" section
|
||||
+ [git] 05/08: t3510 (cherry-pick-sequence): add missing '&&'
|
||||
+ [git] 06/08: Merge branch 'rr/maint-t3510-cascade-fix'
|
||||
+ [git] 07/08: Merge branch 'mm/api-credentials-doc'
|
||||
+ [git] 08/08: Git 1.7.11-rc2
|
||||
|
||||
By default, each commit appears in exactly one commit email, the
|
||||
first time that it is pushed to the repository. If a commit is later
|
||||
merged into another branch, then a one-line summary of the commit
|
||||
is included in the reference change email (as usual), but no
|
||||
additional commit email is generated. See
|
||||
`multimailhook.refFilter(Inclusion|Exclusion|DoSend|DontSend)Regex`
|
||||
below to configure which branches and tags are watched by the hook.
|
||||
|
||||
By default, reference change emails have their "Reply-To" field set
|
||||
to the person who pushed the change, and commit emails have their
|
||||
"Reply-To" field set to the author of the commit.
|
||||
|
||||
3. Output one "announce" mail for each new annotated tag, including
|
||||
information about the tag and optionally a shortlog describing the
|
||||
changes since the previous tag. Such emails might be useful if you
|
||||
use annotated tags to mark releases of your project.
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
* Python 2.x, version 2.4 or later. No non-standard Python modules
|
||||
are required. git-multimail has preliminary support for Python 3
|
||||
(but it has been better tested with Python 2).
|
||||
|
||||
* The ``git`` command must be in your PATH. git-multimail is known to
|
||||
work with Git versions back to 1.7.1. (Earlier versions have not
|
||||
been tested; if you do so, please report your results.)
|
||||
|
||||
* To send emails using the default configuration, a standard sendmail
|
||||
program must be located at '/usr/sbin/sendmail' or
|
||||
'/usr/lib/sendmail' and must be configured correctly to send emails.
|
||||
If this is not the case, set multimailhook.sendmailCommand, or see
|
||||
the multimailhook.mailer configuration variable below for how to
|
||||
configure git-multimail to send emails via an SMTP server.
|
||||
|
||||
* git-multimail is currently tested only on Linux. It may or may not
|
||||
work on other platforms such as Windows and Mac OS. See
|
||||
`<CONTRIBUTING.rst>`__ to improve the situation.
|
||||
|
||||
|
||||
Invocation
|
||||
----------
|
||||
|
||||
``git_multimail.py`` is designed to be used as a ``post-receive`` hook in a
|
||||
Git repository (see githooks(5)). Link or copy it to
|
||||
$GIT_DIR/hooks/post-receive within the repository for which email
|
||||
notifications are desired. Usually it should be installed on the
|
||||
central repository for a project, to which all commits are eventually
|
||||
pushed.
|
||||
|
||||
For use on pre-v1.5.1 Git servers, ``git_multimail.py`` can also work as
|
||||
an ``update`` hook, taking its arguments on the command line. To use
|
||||
this script in this manner, link or copy it to $GIT_DIR/hooks/update.
|
||||
Please note that the script is not completely reliable in this mode
|
||||
[1]_.
|
||||
|
||||
Alternatively, ``git_multimail.py`` can be imported as a Python module
|
||||
into your own Python post-receive script. This method is a bit more
|
||||
work, but allows the behavior of the hook to be customized using
|
||||
arbitrary Python code. For example, you can use a custom environment
|
||||
(perhaps inheriting from GenericEnvironment or GitoliteEnvironment) to
|
||||
|
||||
* change how the user who did the push is determined
|
||||
|
||||
* read users' email addresses from an LDAP server or from a database
|
||||
|
||||
* decide which users should be notified about which commits based on
|
||||
the contents of the commits (e.g., for users who want to be notified
|
||||
only about changes affecting particular files or subdirectories)
|
||||
|
||||
Or you can change how emails are sent by writing your own Mailer
|
||||
class. The ``post-receive`` script in this directory demonstrates how
|
||||
to use ``git_multimail.py`` as a Python module. (If you make interesting
|
||||
changes of this type, please consider sharing them with the
|
||||
community.)
|
||||
|
||||
|
||||
Troubleshooting/FAQ
|
||||
-------------------
|
||||
|
||||
Please read `<doc/troubleshooting.rst>`__ for frequently asked
|
||||
questions and common issues with git-multimail.
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
By default, git-multimail mostly takes its configuration from the
|
||||
following ``git config`` settings:
|
||||
|
||||
multimailhook.environment
|
||||
This describes the general environment of the repository. In most
|
||||
cases, you do not need to specify a value for this variable:
|
||||
`git-multimail` will autodetect which environment to use.
|
||||
Currently supported values:
|
||||
|
||||
generic
|
||||
the username of the pusher is read from $USER or $USERNAME and
|
||||
the repository name is derived from the repository's path.
|
||||
|
||||
gitolite
|
||||
Environment to use when ``git-multimail`` is ran as a gitolite_
|
||||
hook.
|
||||
|
||||
The username of the pusher is read from $GL_USER, the repository
|
||||
name is read from $GL_REPO, and the From: header value is
|
||||
optionally read from gitolite.conf (see multimailhook.from).
|
||||
|
||||
For more information about gitolite and git-multimail, read
|
||||
`<doc/gitolite.rst>`__
|
||||
|
||||
stash
|
||||
Environment to use when ``git-multimail`` is ran as an Atlassian
|
||||
BitBucket Server (formerly known as Atlassian Stash) hook.
|
||||
|
||||
**Warning:** this mode was provided by a third-party contributor
|
||||
and never tested by the git-multimail maintainers. It is
|
||||
provided as-is and may or may not work for you.
|
||||
|
||||
This value is automatically assumed when the stash-specific
|
||||
flags (``--stash-user`` and ``--stash-repo``) are specified on
|
||||
the command line. When this environment is active, the username
|
||||
and repo come from these two command line flags, which must be
|
||||
specified.
|
||||
|
||||
gerrit
|
||||
Environment to use when ``git-multimail`` is ran as a
|
||||
``ref-updated`` Gerrit hook.
|
||||
|
||||
This value is used when the gerrit-specific command line flags
|
||||
(``--oldrev``, ``--newrev``, ``--refname``, ``--project``) for
|
||||
gerrit's ref-updated hook are present. When this environment is
|
||||
active, the username of the pusher is taken from the
|
||||
``--submitter`` argument if that command line option is passed,
|
||||
otherwise 'Gerrit' is used. The repository name is taken from
|
||||
the ``--project`` option on the command line, which must be passed.
|
||||
|
||||
For more information about gerrit and git-multimail, read
|
||||
`<doc/gerrit.rst>`__
|
||||
|
||||
If none of these environments is suitable for your setup, then you
|
||||
can implement a Python class that inherits from Environment and
|
||||
instantiate it via a script that looks like the example
|
||||
post-receive script.
|
||||
|
||||
The environment value can be specified on the command line using
|
||||
the ``--environment`` option. If it is not specified on the
|
||||
command line or by ``multimailhook.environment``, the value is
|
||||
guessed as follows:
|
||||
|
||||
* If stash-specific (respectively gerrit-specific) command flags
|
||||
are present on the command-line, then ``stash`` (respectively
|
||||
``gerrit``) is used.
|
||||
|
||||
* If the environment variables $GL_USER and $GL_REPO are set, then
|
||||
``gitolite`` is used.
|
||||
|
||||
* If none of the above apply, then ``generic`` is used.
|
||||
|
||||
multimailhook.repoName
|
||||
A short name of this Git repository, to be used in various places
|
||||
in the notification email text. The default is to use $GL_REPO
|
||||
for gitolite repositories, or otherwise to derive this value from
|
||||
the repository path name.
|
||||
|
||||
multimailhook.mailingList
|
||||
The list of email addresses to which notification emails should be
|
||||
sent, as RFC 2822 email addresses separated by commas. This
|
||||
configuration option can be multivalued. Leave it unset or set it
|
||||
to the empty string to not send emails by default. The next few
|
||||
settings can be used to configure specific address lists for
|
||||
specific types of notification email.
|
||||
|
||||
multimailhook.refchangeList
|
||||
The list of email addresses to which summary emails about
|
||||
reference changes should be sent, as RFC 2822 email addresses
|
||||
separated by commas. This configuration option can be
|
||||
multivalued. The default is the value in
|
||||
multimailhook.mailingList. Set this value to "none" (or the empty
|
||||
string) to prevent reference change emails from being sent even if
|
||||
multimailhook.mailingList is set.
|
||||
|
||||
multimailhook.announceList
|
||||
The list of email addresses to which emails about new annotated
|
||||
tags should be sent, as RFC 2822 email addresses separated by
|
||||
commas. This configuration option can be multivalued. The
|
||||
default is the value in multimailhook.refchangeList or
|
||||
multimailhook.mailingList. Set this value to "none" (or the empty
|
||||
string) to prevent annotated tag announcement emails from being sent
|
||||
even if one of the other values is set.
|
||||
|
||||
multimailhook.commitList
|
||||
The list of email addresses to which emails about individual new
|
||||
commits should be sent, as RFC 2822 email addresses separated by
|
||||
commas. This configuration option can be multivalued. The
|
||||
default is the value in multimailhook.mailingList. Set this value
|
||||
to "none" (or the empty string) to prevent notification emails about
|
||||
individual commits from being sent even if
|
||||
multimailhook.mailingList is set.
|
||||
|
||||
multimailhook.announceShortlog
|
||||
If this option is set to true, then emails about changes to
|
||||
annotated tags include a shortlog of changes since the previous
|
||||
tag. This can be useful if the annotated tags represent releases;
|
||||
then the shortlog will be a kind of rough summary of what has
|
||||
happened since the last release. But if your tagging policy is
|
||||
not so straightforward, then the shortlog might be confusing
|
||||
rather than useful. Default is false.
|
||||
|
||||
multimailhook.commitEmailFormat
|
||||
The format of email messages for the individual commits, can be "text" or
|
||||
"html". In the latter case, the emails will include diffs using colorized
|
||||
HTML instead of plain text used by default. Note that this currently the
|
||||
ref change emails are always sent in plain text.
|
||||
|
||||
Note that when using "html", the formatting is done by parsing the
|
||||
output of ``git log`` with ``-p``. When using
|
||||
``multimailhook.commitLogOpts`` to specify a ``--format`` for
|
||||
``git log``, one may get false positive (e.g. lines in the body of
|
||||
the message starting with ``+++`` or ``---`` colored in red or
|
||||
green).
|
||||
|
||||
By default, all the message is HTML-escaped. See
|
||||
``multimailhook.htmlInIntro`` to change this behavior.
|
||||
|
||||
multimailhook.commitBrowseURL
|
||||
Used to generate a link to an online repository browser in commit
|
||||
emails. This variable must be a string. Format directives like
|
||||
``%(<variable>)s`` will be expanded the same way as template
|
||||
strings. In particular, ``%(id)s`` will be replaced by the full
|
||||
Git commit identifier (40-chars hexadecimal).
|
||||
|
||||
If the string does not contain any format directive, then
|
||||
``%(id)s`` will be automatically added to the string. If you don't
|
||||
want ``%(id)s`` to be automatically added, use the empty format
|
||||
directive ``%()s`` anywhere in the string.
|
||||
|
||||
For example, a suitable value for the git-multimail project itself
|
||||
would be
|
||||
``https://github.com/git-multimail/git-multimail/commit/%(id)s``.
|
||||
|
||||
multimailhook.htmlInIntro, multimailhook.htmlInFooter
|
||||
When generating an HTML message, git-multimail escapes any HTML
|
||||
sequence by default. This means that if a template contains HTML
|
||||
like ``<a href="foo">link</a>``, the reader will see the HTML
|
||||
source code and not a proper link.
|
||||
|
||||
Set ``multimailhook.htmlInIntro`` to true to allow writing HTML
|
||||
formatting in introduction templates. Similarly, set
|
||||
``multimailhook.htmlInFooter`` for HTML in the footer.
|
||||
|
||||
Variables expanded in the template are still escaped. For example,
|
||||
if a repository's path contains a ``<``, it will be rendered as
|
||||
such in the message.
|
||||
|
||||
Read `<doc/customizing-emails.rst>`__ for more details and
|
||||
examples.
|
||||
|
||||
multimailhook.refchangeShowGraph
|
||||
If this option is set to true, then summary emails about reference
|
||||
changes will additionally include:
|
||||
|
||||
* a graph of the added commits (if any)
|
||||
|
||||
* a graph of the discarded commits (if any)
|
||||
|
||||
The log is generated by running ``git log --graph`` with the options
|
||||
specified in graphOpts. The default is false.
|
||||
|
||||
multimailhook.refchangeShowLog
|
||||
If this option is set to true, then summary emails about reference
|
||||
changes will include a detailed log of the added commits in
|
||||
addition to the one line summary. The log is generated by running
|
||||
``git log`` with the options specified in multimailhook.logOpts.
|
||||
Default is false.
|
||||
|
||||
multimailhook.mailer
|
||||
This option changes the way emails are sent. Accepted values are:
|
||||
|
||||
* **sendmail (the default)**: use the command ``/usr/sbin/sendmail`` or
|
||||
``/usr/lib/sendmail`` (or sendmailCommand, if configured). This
|
||||
mode can be further customized via the following options:
|
||||
|
||||
multimailhook.sendmailCommand
|
||||
The command used by mailer ``sendmail`` to send emails. Shell
|
||||
quoting is allowed in the value of this setting, but remember that
|
||||
Git requires double-quotes to be escaped; e.g.::
|
||||
|
||||
git config multimailhook.sendmailcommand '/usr/sbin/sendmail -oi -t -F \"Git Repo\"'
|
||||
|
||||
Default is '/usr/sbin/sendmail -oi -t' or
|
||||
'/usr/lib/sendmail -oi -t' (depending on which file is
|
||||
present and executable).
|
||||
|
||||
multimailhook.envelopeSender
|
||||
If set then pass this value to sendmail via the -f option to set
|
||||
the envelope sender address.
|
||||
|
||||
* **smtp**: use Python's smtplib. This is useful when the sendmail
|
||||
command is not available on the system. This mode can be
|
||||
further customized via the following options:
|
||||
|
||||
multimailhook.smtpServer
|
||||
The name of the SMTP server to connect to. The value can
|
||||
also include a colon and a port number; e.g.,
|
||||
``mail.example.com:25``. Default is 'localhost' using port 25.
|
||||
|
||||
multimailhook.smtpUser, multimailhook.smtpPass
|
||||
Server username and password. Required if smtpEncryption is 'ssl'.
|
||||
Note that the username and password currently need to be
|
||||
set cleartext in the configuration file, which is not
|
||||
recommended. If you need to use this option, be sure your
|
||||
configuration file is read-only.
|
||||
|
||||
multimailhook.envelopeSender
|
||||
The sender address to be passed to the SMTP server. If
|
||||
unset, then the value of multimailhook.from is used.
|
||||
|
||||
multimailhook.smtpServerTimeout
|
||||
Timeout in seconds. Default is 10.
|
||||
|
||||
multimailhook.smtpEncryption
|
||||
Set the security type. Allowed values: ``none``, ``ssl``, ``tls`` (starttls).
|
||||
Default is ``none``.
|
||||
|
||||
multimailhook.smtpCACerts
|
||||
Set the path to a list of trusted CA certificate to verify the
|
||||
server certificate, only supported when ``smtpEncryption`` is
|
||||
``tls``. If unset or empty, the server certificate is not
|
||||
verified. If it targets a file containing a list of trusted CA
|
||||
certificates (PEM format) these CAs will be used to verify the
|
||||
server certificate. For debian, you can set
|
||||
``/etc/ssl/certs/ca-certificates.crt`` for using the system
|
||||
trusted CAs. For self-signed server, you can add your server
|
||||
certificate to the system store::
|
||||
|
||||
cd /usr/local/share/ca-certificates/
|
||||
openssl s_client -starttls smtp \
|
||||
-connect mail.example.net:587 -showcerts \
|
||||
</dev/null 2>/dev/null \
|
||||
| openssl x509 -outform PEM >mail.example.net.crt
|
||||
update-ca-certificates
|
||||
|
||||
and used the updated ``/etc/ssl/certs/ca-certificates.crt``. Or
|
||||
directly use your ``/path/to/mail.example.net.crt``. Default is
|
||||
unset.
|
||||
|
||||
multimailhook.smtpServerDebugLevel
|
||||
Integer number. Set to greater than 0 to activate debugging.
|
||||
|
||||
multimailhook.from, multimailhook.fromCommit, multimailhook.fromRefchange
|
||||
If set, use this value in the From: field of generated emails.
|
||||
``fromCommit`` is used for commit emails, ``fromRefchange`` is
|
||||
used for refchange emails, and ``from`` is used as fall-back in
|
||||
all cases.
|
||||
|
||||
The value for these variables can be either:
|
||||
|
||||
- An email address, which will be used directly.
|
||||
|
||||
- The value ``pusher``, in which case the pusher's address (if
|
||||
available) will be used.
|
||||
|
||||
- The value ``author`` (meaningful only for ``fromCommit``), in which
|
||||
case the commit author's address will be used.
|
||||
|
||||
If config values are unset, the value of the From: header is
|
||||
determined as follows:
|
||||
|
||||
1. (gitolite environment only)
|
||||
1.a) If ``multimailhook.MailaddressMap`` is set, and is a path
|
||||
to an existing file (if relative, it is considered relative to
|
||||
the place where ``gitolite.conf`` is located), then this file
|
||||
should contain lines like::
|
||||
|
||||
username Firstname Lastname <email@example.com>
|
||||
|
||||
git-multimail will then look for a line where ``$GL_USER``
|
||||
matches the ``username`` part, and use the rest of the line for
|
||||
the ``From:`` header.
|
||||
|
||||
1.b) Parse gitolite.conf, looking for a block of comments that
|
||||
looks like this::
|
||||
|
||||
# BEGIN USER EMAILS
|
||||
# username Firstname Lastname <email@example.com>
|
||||
# END USER EMAILS
|
||||
|
||||
If that block exists, and there is a line between the BEGIN
|
||||
USER EMAILS and END USER EMAILS lines where the first field
|
||||
matches the gitolite username ($GL_USER), use the rest of the
|
||||
line for the From: header.
|
||||
|
||||
2. If the user.email configuration setting is set, use its value
|
||||
(and the value of user.name, if set).
|
||||
|
||||
3. Use the value of multimailhook.envelopeSender.
|
||||
|
||||
multimailhook.MailaddressMap
|
||||
(gitolite environment only)
|
||||
File to look for a ``From:`` address based on the user doing the
|
||||
push. Defaults to unset. See ``multimailhook.from`` for details.
|
||||
|
||||
multimailhook.administrator
|
||||
The name and/or email address of the administrator of the Git
|
||||
repository; used in FOOTER_TEMPLATE. Default is
|
||||
multimailhook.envelopesender if it is set; otherwise a generic
|
||||
string is used.
|
||||
|
||||
multimailhook.emailPrefix
|
||||
All emails have this string prepended to their subjects, to aid
|
||||
email filtering (though filtering based on the X-Git-* email
|
||||
headers is probably more robust). Default is the short name of
|
||||
the repository in square brackets; e.g., ``[myrepo]``. Set this
|
||||
value to the empty string to suppress the email prefix. You may
|
||||
use the placeholder ``%(repo_shortname)s`` for the short name of
|
||||
the repository.
|
||||
|
||||
multimailhook.emailMaxLines
|
||||
The maximum number of lines that should be included in the body of
|
||||
a generated email. If not specified, there is no limit. Lines
|
||||
beyond the limit are suppressed and counted, and a final line is
|
||||
added indicating the number of suppressed lines.
|
||||
|
||||
multimailhook.emailMaxLineLength
|
||||
The maximum length of a line in the email body. Lines longer than
|
||||
this limit are truncated to this length with a trailing ``[...]``
|
||||
added to indicate the missing text. The default is 500, because
|
||||
(a) diffs with longer lines are probably from binary files, for
|
||||
which a diff is useless, and (b) even if a text file has such long
|
||||
lines, the diffs are probably unreadable anyway. To disable line
|
||||
truncation, set this option to 0.
|
||||
|
||||
multimailhook.subjectMaxLength
|
||||
The maximum length of the subject line (i.e. the ``oneline`` field
|
||||
in templates, not including the prefix). Lines longer than this
|
||||
limit are truncated to this length with a trailing ``[...]`` added
|
||||
to indicate the missing text. This option The default is to use
|
||||
``multimailhook.emailMaxLineLength``. This option avoids sending
|
||||
emails with overly long subject lines, but should not be needed if
|
||||
the commit messages follow the Git convention (one short subject
|
||||
line, then a blank line, then the message body). To disable line
|
||||
truncation, set this option to 0.
|
||||
|
||||
multimailhook.maxCommitEmails
|
||||
The maximum number of commit emails to send for a given change.
|
||||
When the number of patches is larger that this value, only the
|
||||
summary refchange email is sent. This can avoid accidental
|
||||
mailbombing, for example on an initial push. To disable commit
|
||||
emails limit, set this option to 0. The default is 500.
|
||||
|
||||
multimailhook.excludeMergeRevisions
|
||||
When sending out revision emails, do not consider merge commits (the
|
||||
functional equivalent of `rev-list --no-merges`).
|
||||
The default is `false` (send merge commit emails).
|
||||
|
||||
multimailhook.emailStrictUTF8
|
||||
If this boolean option is set to `true`, then the main part of the
|
||||
email body is forced to be valid UTF-8. Any characters that are
|
||||
not valid UTF-8 are converted to the Unicode replacement
|
||||
character, U+FFFD. The default is `true`.
|
||||
|
||||
This option is ineffective with Python 3, where non-UTF-8
|
||||
characters are unconditionally replaced.
|
||||
|
||||
multimailhook.diffOpts
|
||||
Options passed to ``git diff-tree`` when generating the summary
|
||||
information for ReferenceChange emails. Default is ``--stat
|
||||
--summary --find-copies-harder``. Add -p to those options to
|
||||
include a unified diff of changes in addition to the usual summary
|
||||
output. Shell quoting is allowed; see ``multimailhook.logOpts`` for
|
||||
details.
|
||||
|
||||
multimailhook.graphOpts
|
||||
Options passed to ``git log --graph`` when generating graphs for the
|
||||
reference change summary emails (used only if refchangeShowGraph
|
||||
is true). The default is '--oneline --decorate'.
|
||||
|
||||
Shell quoting is allowed; see logOpts for details.
|
||||
|
||||
multimailhook.logOpts
|
||||
Options passed to ``git log`` to generate additional info for
|
||||
reference change emails (used only if refchangeShowLog is set).
|
||||
For example, adding -p will show each commit's complete diff. The
|
||||
default is empty.
|
||||
|
||||
Shell quoting is allowed; for example, a log format that contains
|
||||
spaces can be specified using something like::
|
||||
|
||||
git config multimailhook.logopts '--pretty=format:"%h %aN <%aE>%n%s%n%n%b%n"'
|
||||
|
||||
If you want to set this by editing your configuration file
|
||||
directly, remember that Git requires double-quotes to be escaped
|
||||
(see git-config(1) for more information)::
|
||||
|
||||
[multimailhook]
|
||||
logopts = --pretty=format:\"%h %aN <%aE>%n%s%n%n%b%n\"
|
||||
|
||||
multimailhook.commitLogOpts
|
||||
Options passed to ``git log`` to generate additional info for
|
||||
revision change emails. For example, adding --ignore-all-spaces
|
||||
will suppress whitespace changes. The default options are ``-C
|
||||
--stat -p --cc``. Shell quoting is allowed; see
|
||||
multimailhook.logOpts for details.
|
||||
|
||||
multimailhook.dateSubstitute
|
||||
String to use as a substitute for ``Date:`` in the output of ``git
|
||||
log`` while formatting commit messages. This is useful to avoid
|
||||
emitting a line that can be interpreted by mailers as the start of
|
||||
a cited message (Zimbra webmail in particular). Defaults to
|
||||
``CommitDate:``. Set to an empty string or ``none`` to deactivate
|
||||
the behavior.
|
||||
|
||||
multimailhook.emailDomain
|
||||
Domain name appended to the username of the person doing the push
|
||||
to convert it into an email address
|
||||
(via ``"%s@%s" % (username, emaildomain)``). More complicated
|
||||
schemes can be implemented by overriding Environment and
|
||||
overriding its get_pusher_email() method.
|
||||
|
||||
multimailhook.replyTo, multimailhook.replyToCommit, multimailhook.replyToRefchange
|
||||
Addresses to use in the Reply-To: field for commit emails
|
||||
(replyToCommit) and refchange emails (replyToRefchange).
|
||||
multimailhook.replyTo is used as default when replyToCommit or
|
||||
replyToRefchange is not set. The shortcuts ``pusher`` and
|
||||
``author`` are allowed with the same semantics as for
|
||||
``multimailhook.from``. In addition, the value ``none`` can be
|
||||
used to omit the ``Reply-To:`` field.
|
||||
|
||||
The default is ``pusher`` for refchange emails, and ``author`` for
|
||||
commit emails.
|
||||
|
||||
multimailhook.quiet
|
||||
Do not output the list of email recipients from the hook
|
||||
|
||||
multimailhook.stdout
|
||||
For debugging, send emails to stdout rather than to the
|
||||
mailer. Equivalent to the --stdout command line option
|
||||
|
||||
multimailhook.scanCommitForCc
|
||||
If this option is set to true, than recipients from lines in commit body
|
||||
that starts with ``CC:`` will be added to CC list.
|
||||
Default: false
|
||||
|
||||
multimailhook.combineWhenSingleCommit
|
||||
If this option is set to true and a single new commit is pushed to
|
||||
a branch, combine the summary and commit email messages into a
|
||||
single email.
|
||||
Default: true
|
||||
|
||||
multimailhook.refFilterInclusionRegex, multimailhook.refFilterExclusionRegex, multimailhook.refFilterDoSendRegex, multimailhook.refFilterDontSendRegex
|
||||
**Warning:** these options are experimental. They should work, but
|
||||
the user-interface is not stable yet (in particular, the option
|
||||
names may change). If you want to participate in stabilizing the
|
||||
feature, please contact the maintainers and/or send pull-requests.
|
||||
If you are happy with the current shape of the feature, please
|
||||
report it too.
|
||||
|
||||
Regular expressions that can be used to limit refs for which email
|
||||
updates will be sent. It is an error to specify both an inclusion
|
||||
and an exclusion regex. If a ``refFilterInclusionRegex`` is
|
||||
specified, emails will only be sent for refs which match this
|
||||
regex. If a ``refFilterExclusionRegex`` regex is specified,
|
||||
emails will be sent for all refs except those that match this
|
||||
regex (or that match a predefined regex specific to the
|
||||
environment, such as "^refs/notes" for most environments and
|
||||
"^refs/notes|^refs/changes" for the gerrit environment).
|
||||
|
||||
The expressions are matched against the complete refname, and is
|
||||
considered to match if any substring matches. For example, to
|
||||
filter-out all tags, set ``refFilterExclusionRegex`` to
|
||||
``^refs/tags/`` (note the leading ``^`` but no trailing ``$``). If
|
||||
you set ``refFilterExclusionRegex`` to ``master``, then any ref
|
||||
containing ``master`` will be excluded (the ``master`` branch, but
|
||||
also ``refs/tags/master`` or ``refs/heads/foo-master-bar``).
|
||||
|
||||
``refFilterDoSendRegex`` and ``refFilterDontSendRegex`` are
|
||||
analogous to ``refFilterInclusionRegex`` and
|
||||
``refFilterExclusionRegex`` with one difference: with
|
||||
``refFilterDoSendRegex`` and ``refFilterDontSendRegex``, commits
|
||||
introduced by one excluded ref will not be considered as new when
|
||||
they reach an included ref. Typically, if you add a branch ``foo``
|
||||
to ``refFilterDontSendRegex``, push commits to this branch, and
|
||||
later merge branch ``foo`` into ``master``, then the notification
|
||||
email for ``master`` will contain a commit email only for the
|
||||
merge commit. If you include ``foo`` in
|
||||
``refFilterExclusionRegex``, then at the time of merge, you will
|
||||
receive one commit email per commit in the branch.
|
||||
|
||||
These variables can be multi-valued, like::
|
||||
|
||||
[multimailhook]
|
||||
refFilterExclusionRegex = ^refs/tags/
|
||||
refFilterExclusionRegex = ^refs/heads/master$
|
||||
|
||||
You can also provide a whitespace-separated list like::
|
||||
|
||||
[multimailhook]
|
||||
refFilterExclusionRegex = ^refs/tags/ ^refs/heads/master$
|
||||
|
||||
Both examples exclude tags and the master branch, and are
|
||||
equivalent to::
|
||||
|
||||
[multimailhook]
|
||||
refFilterExclusionRegex = ^refs/tags/|^refs/heads/master$
|
||||
|
||||
``refFilterInclusionRegex`` and ``refFilterExclusionRegex`` are
|
||||
strictly stronger than ``refFilterDoSendRegex`` and
|
||||
``refFilterDontSendRegex``. In other words, adding a ref to a
|
||||
DoSend/DontSend regex has no effect if it is already excluded by a
|
||||
Exclusion/Inclusion regex.
|
||||
|
||||
multimailhook.logFile, multimailhook.errorLogFile, multimailhook.debugLogFile
|
||||
|
||||
When set, these variable designate path to files where
|
||||
git-multimail will log some messages. Normal messages and error
|
||||
messages are sent to ``logFile``, and error messages are also sent
|
||||
to ``errorLogFile``. Debug messages and all other messages are
|
||||
sent to ``debugLogFile``. The recommended way is to set only one
|
||||
of these variables, but it is also possible to set several of them
|
||||
(part of the information is then duplicated in several log files,
|
||||
for example errors are duplicated to all log files).
|
||||
|
||||
Relative path are relative to the Git repository where the push is
|
||||
done.
|
||||
|
||||
multimailhook.verbose
|
||||
|
||||
Verbosity level of git-multimail on its standard output. By
|
||||
default, show only error and info messages. If set to true, show
|
||||
also debug messages.
|
||||
|
||||
Email filtering aids
|
||||
--------------------
|
||||
|
||||
All emails include extra headers to enable fine tuned filtering and
|
||||
give information for debugging. All emails include the headers
|
||||
``X-Git-Host``, ``X-Git-Repo``, ``X-Git-Refname``, and ``X-Git-Reftype``.
|
||||
ReferenceChange emails also include headers ``X-Git-Oldrev`` and ``X-Git-Newrev``;
|
||||
Revision emails also include header ``X-Git-Rev``.
|
||||
|
||||
|
||||
Customizing email contents
|
||||
--------------------------
|
||||
|
||||
git-multimail mostly generates emails by expanding templates. The
|
||||
templates can be customized. To avoid the need to edit
|
||||
``git_multimail.py`` directly, the preferred way to change the templates
|
||||
is to write a separate Python script that imports ``git_multimail.py`` as
|
||||
a module, then replaces the templates in place. See the provided
|
||||
post-receive script for an example of how this is done.
|
||||
|
||||
|
||||
Customizing git-multimail for your environment
|
||||
----------------------------------------------
|
||||
|
||||
git-multimail is mostly customized via an "environment" that describes
|
||||
the local environment in which Git is running. Two types of
|
||||
environment are built in:
|
||||
|
||||
GenericEnvironment
|
||||
a stand-alone Git repository.
|
||||
|
||||
GitoliteEnvironment
|
||||
a Git repository that is managed by gitolite_. For such
|
||||
repositories, the identity of the pusher is read from
|
||||
environment variable $GL_USER, the name of the repository is read
|
||||
from $GL_REPO (if it is not overridden by multimailhook.reponame),
|
||||
and the From: header value is optionally read from gitolite.conf
|
||||
(see multimailhook.from).
|
||||
|
||||
By default, git-multimail assumes GitoliteEnvironment if $GL_USER and
|
||||
$GL_REPO are set, and otherwise assumes GenericEnvironment.
|
||||
Alternatively, you can choose one of these two environments explicitly
|
||||
by setting a ``multimailhook.environment`` config setting (which can
|
||||
have the value `generic` or `gitolite`) or by passing an --environment
|
||||
option to the script.
|
||||
|
||||
If you need to customize the script in ways that are not supported by
|
||||
the existing environments, you can define your own environment class
|
||||
class using arbitrary Python code. To do so, you need to import
|
||||
``git_multimail.py`` as a Python module, as demonstrated by the example
|
||||
post-receive script. Then implement your environment class; it should
|
||||
usually inherit from one of the existing Environment classes and
|
||||
possibly one or more of the EnvironmentMixin classes. Then set the
|
||||
``environment`` variable to an instance of your own environment class
|
||||
and pass it to ``run_as_post_receive_hook()``.
|
||||
|
||||
The standard environment classes, GenericEnvironment and
|
||||
GitoliteEnvironment, are in fact themselves put together out of a
|
||||
number of mixin classes, each of which handles one aspect of the
|
||||
customization. For the finest control over your configuration, you
|
||||
can specify exactly which mixin classes your own environment class
|
||||
should inherit from, and override individual methods (or even add your
|
||||
own mixin classes) to implement entirely new behaviors. If you
|
||||
implement any mixins that might be useful to other people, please
|
||||
consider sharing them with the community!
|
||||
|
||||
|
||||
Getting involved
|
||||
----------------
|
||||
|
||||
Please, read `<CONTRIBUTING.rst>`__ for instructions on how to
|
||||
contribute to git-multimail.
|
||||
|
||||
|
||||
Footnotes
|
||||
---------
|
||||
|
||||
.. [1] Because of the way information is passed to update hooks, the
|
||||
script's method of determining whether a commit has already
|
||||
been seen does not work when it is used as an ``update`` script.
|
||||
In particular, no notification email will be generated for a
|
||||
new commit that is added to multiple references in the same
|
||||
push. A workaround is to use --force-send to force sending the
|
||||
emails.
|
||||
|
||||
.. _gitolite: https://github.com/sitaramc/gitolite
|
||||
56
third_party/git/contrib/hooks/multimail/doc/customizing-emails.rst
vendored
Normal file
56
third_party/git/contrib/hooks/multimail/doc/customizing-emails.rst
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
Customizing the content and formatting of emails
|
||||
================================================
|
||||
|
||||
Overloading template strings
|
||||
----------------------------
|
||||
|
||||
The content of emails is generated based on template strings defined
|
||||
in ``git_multimail.py``. You can customize these template strings
|
||||
without changing the script itself, by defining a Python wrapper
|
||||
around it. The python wrapper should ``import git_multimail`` and then
|
||||
override the ``git_multimail.*`` strings like this::
|
||||
|
||||
import sys # needed for sys.argv
|
||||
|
||||
# Import and customize git_multimail:
|
||||
import git_multimail
|
||||
git_multimail.REVISION_INTRO_TEMPLATE = """..."""
|
||||
git_multimail.COMBINED_INTRO_TEMPLATE = git_multimail.REVISION_INTRO_TEMPLATE
|
||||
|
||||
# start git_multimail itself:
|
||||
git_multimail.main(sys.argv[1:])
|
||||
|
||||
The template strings can use any value already used in the existing
|
||||
templates (read the source code).
|
||||
|
||||
Using HTML in template strings
|
||||
------------------------------
|
||||
|
||||
If ``multimailhook.commitEmailFormat`` is set to HTML, then
|
||||
git-multimail will generate HTML emails for commit notifications. The
|
||||
log and diff will be formatted automatically by git-multimail. By
|
||||
default, any HTML special character in the templates will be escaped.
|
||||
|
||||
To use HTML formatting in the introduction of the email, set
|
||||
``multimailhook.htmlInIntro`` to ``true``. Then, the template can
|
||||
contain any HTML tags, that will be sent as-is in the email. For
|
||||
example, to add some formatting and a link to the online commit, use
|
||||
a format like::
|
||||
|
||||
git_multimail.REVISION_INTRO_TEMPLATE = """\
|
||||
<span style="color:#808080">This is an automated email from the git hooks/post-receive script.</span><br /><br />
|
||||
|
||||
<strong>%(pusher)s</strong> pushed a commit to %(refname_type)s %(short_refname)s
|
||||
in repository %(repo_shortname)s.<br />
|
||||
|
||||
<a href="https://github.com/git-multimail/git-multimail/commit/%(newrev)s">View on GitHub</a>.
|
||||
"""
|
||||
|
||||
Note that the values expanded from ``%(variable)s`` in the format
|
||||
strings will still be escaped.
|
||||
|
||||
For a less flexible but easier to set up way to add a link to commit
|
||||
emails, see ``multimailhook.commitBrowseURL``.
|
||||
|
||||
Similarly, one can set ``multimailhook.htmlInFooter`` and override any
|
||||
of the ``*_FOOTER*`` template strings.
|
||||
56
third_party/git/contrib/hooks/multimail/doc/gerrit.rst
vendored
Normal file
56
third_party/git/contrib/hooks/multimail/doc/gerrit.rst
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
Setting up git-multimail on Gerrit
|
||||
==================================
|
||||
|
||||
Gerrit has its own email-sending system, but you may prefer using
|
||||
``git-multimail`` instead. It supports Gerrit natively as a Gerrit
|
||||
``ref-updated`` hook (Warning: `Gerrit hooks
|
||||
<https://gerrit-review.googlesource.com/Documentation/config-hooks.html>`__
|
||||
are distinct from Git hooks). Setting up ``git-multimail`` on a Gerrit
|
||||
installation can be done following the instructions below.
|
||||
|
||||
The explanations show an easy way to set up ``git-multimail``,
|
||||
but leave ``git-multimail`` installed and unconfigured for a while. If
|
||||
you run Gerrit on a production server, it is advised that you
|
||||
execute the step "Set up the hook" last to avoid confusing your users
|
||||
in the meantime.
|
||||
|
||||
Set up the hook
|
||||
---------------
|
||||
|
||||
Create a directory ``$site_path/hooks/`` if it does not exist (if you
|
||||
don't know what ``$site_path`` is, run ``gerrit.sh status`` and look
|
||||
for a ``GERRIT_SITE`` line). Either copy ``git_multimail.py`` to
|
||||
``$site_path/hooks/ref-updated`` or create a wrapper script like
|
||||
this::
|
||||
|
||||
#! /bin/sh
|
||||
exec /path/to/git_multimail.py "$@"
|
||||
|
||||
In both cases, make sure the file is named exactly
|
||||
``$site_path/hooks/ref-updated`` and is executable.
|
||||
|
||||
(Alternatively, you may configure the ``[hooks]`` section of
|
||||
gerrit.config)
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
Log on the gerrit server and edit ``$site_path/git/$project/config``
|
||||
to configure ``git-multimail``.
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
|
||||
Warning: this will disable ``git-multimail`` during the debug, and
|
||||
could confuse your users. Don't run on a production server.
|
||||
|
||||
To debug configuration issues with ``git-multimail``, you can add the
|
||||
``--stdout`` option when calling ``git_multimail.py`` like this::
|
||||
|
||||
#!/bin/sh
|
||||
exec /path/to/git-multimail/git-multimail/git_multimail.py \
|
||||
--stdout "$@" >> /tmp/log.txt
|
||||
|
||||
and try pushing from a test repository. You should see the source of
|
||||
the email that would have been sent in the output of ``git push`` in
|
||||
the file ``/tmp/log.txt``.
|
||||
118
third_party/git/contrib/hooks/multimail/doc/gitolite.rst
vendored
Normal file
118
third_party/git/contrib/hooks/multimail/doc/gitolite.rst
vendored
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
Setting up git-multimail on gitolite
|
||||
====================================
|
||||
|
||||
``git-multimail`` supports gitolite 3 natively.
|
||||
The explanations below show an easy way to set up ``git-multimail``,
|
||||
but leave ``git-multimail`` installed and unconfigured for a while. If
|
||||
you run gitolite on a production server, it is advised that you
|
||||
execute the step "Set up the hook" last to avoid confusing your users
|
||||
in the meantime.
|
||||
|
||||
Set up the hook
|
||||
---------------
|
||||
|
||||
Log in as your gitolite user.
|
||||
|
||||
Create a file ``.gitolite/hooks/common/post-receive`` on your gitolite
|
||||
account containing (adapt the path, obviously)::
|
||||
|
||||
#!/bin/sh
|
||||
exec /path/to/git-multimail/git-multimail/git_multimail.py "$@"
|
||||
|
||||
Make sure it's executable (``chmod +x``). Record the hook in
|
||||
gitolite::
|
||||
|
||||
gitolite setup
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
First, you have to allow the admin to set Git configuration variables.
|
||||
|
||||
As gitolite user, edit the line containing ``GIT_CONFIG_KEYS`` in file
|
||||
``.gitolite.rc``, to make it look like::
|
||||
|
||||
GIT_CONFIG_KEYS => 'multimailhook\..*',
|
||||
|
||||
You can now log out and return to your normal user.
|
||||
|
||||
In the ``gitolite-admin`` clone, edit the file ``conf/gitolite.conf``
|
||||
and add::
|
||||
|
||||
repo @all
|
||||
# Not strictly needed as git_multimail.py will chose gitolite if
|
||||
# $GL_USER is set.
|
||||
config multimailhook.environment = gitolite
|
||||
config multimailhook.mailingList = # Where emails should be sent
|
||||
config multimailhook.from = # From address to use
|
||||
|
||||
Note that by default, gitolite forbids ``<`` and ``>`` in variable
|
||||
values (for security/paranoia reasons, see
|
||||
`compensating for UNSAFE_PATT
|
||||
<http://gitolite.com/gitolite/git-config/index.html#compensating-for-unsafe95patt>`__
|
||||
in gitolite's documentation for explanations and a way to disable
|
||||
this). As a consequence, you will not be able to use ``First Last
|
||||
<First.Last@example.com>`` as recipient email, but specifying
|
||||
``First.Last@example.com`` alone works.
|
||||
|
||||
Obviously, you can customize all parameters on a per-repository basis by
|
||||
adding these ``config multimailhook.*`` lines in the section
|
||||
corresponding to a repository or set of repositories.
|
||||
|
||||
To activate ``git-multimail`` on a per-repository basis, do not set
|
||||
``multimailhook.mailingList`` in the ``@all`` section and set it only
|
||||
for repositories for which you want ``git-multimail``.
|
||||
|
||||
Alternatively, you can set up the ``From:`` field on a per-user basis
|
||||
by adding a ``BEGIN USER EMAILS``/``END USER EMAILS`` section (see
|
||||
``../README``).
|
||||
|
||||
Specificities of Gitolite for Configuration
|
||||
-------------------------------------------
|
||||
|
||||
Empty configuration variables
|
||||
.............................
|
||||
|
||||
With gitolite, the syntax ``config multimailhook.commitList = ""``
|
||||
unsets the variable instead of setting it to an empty string (see
|
||||
`here
|
||||
<http://gitolite.com/gitolite/git-config.html#an-important-warning-about-deleting-a-config-line>`__).
|
||||
As a result, there is no way to set a variable to the empty string.
|
||||
In all most places where an empty value is required, git-multimail
|
||||
now allows to specify special ``"none"`` value (case-sensitive) to
|
||||
mean the same.
|
||||
|
||||
Alternatively, one can use ``" "`` (a single space) instead of ``""``.
|
||||
In most cases (in particular ``multimailhook.*List`` variables), this
|
||||
will be equivalent to an empty string.
|
||||
|
||||
If you have a use-case where ``"none"`` is not an acceptable value and
|
||||
you need ``" "`` or ``""`` instead, please report it as a bug to
|
||||
git-multimail.
|
||||
|
||||
Allowing Regular Expressions in Configuration
|
||||
.............................................
|
||||
|
||||
gitolite has a mechanism to prevent unsafe configuration variable
|
||||
values, which prevent characters like ``|`` commonly used in regular
|
||||
expressions. If you do not need the safety feature of gitolite and
|
||||
need to use regular expressions in your configuration (e.g. for
|
||||
``multimailhook.refFilter*`` variables), set
|
||||
`UNSAFE_PATT
|
||||
<http://gitolite.com/gitolite/git-config.html#unsafe-patt>`__ to a
|
||||
less restrictive value.
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
|
||||
Warning: this will disable ``git-multimail`` during the debug, and
|
||||
could confuse your users. Don't run on a production server.
|
||||
|
||||
To debug configuration issues with ``git-multimail``, you can add the
|
||||
``--stdout`` option when calling ``git_multimail.py`` like this::
|
||||
|
||||
#!/bin/sh
|
||||
exec /path/to/git-multimail/git-multimail/git_multimail.py --stdout "$@"
|
||||
|
||||
and try pushing from a test repository. You should see the source of
|
||||
the email that would have been sent in the output of ``git push``.
|
||||
78
third_party/git/contrib/hooks/multimail/doc/troubleshooting.rst
vendored
Normal file
78
third_party/git/contrib/hooks/multimail/doc/troubleshooting.rst
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
Troubleshooting issues with git-multimail: a FAQ
|
||||
================================================
|
||||
|
||||
How to check that git-multimail is properly set up?
|
||||
---------------------------------------------------
|
||||
|
||||
Since version 1.4.0, git-multimail allows a simple self-checking of
|
||||
its configuration: run it with the environment variable
|
||||
``GIT_MULTIMAIL_CHECK_SETUP`` set to a non-empty string. You should
|
||||
get something like this::
|
||||
|
||||
$ GIT_MULTIMAIL_CHECK_SETUP=true /home/moy/dev/git-multimail/git-multimail/git_multimail.py
|
||||
Environment values:
|
||||
administrator : 'the administrator of this repository'
|
||||
charset : 'utf-8'
|
||||
emailprefix : '[git-multimail] '
|
||||
fqdn : 'anie'
|
||||
projectdesc : 'UNNAMED PROJECT'
|
||||
pusher : 'moy'
|
||||
repo_path : '/home/moy/dev/git-multimail'
|
||||
repo_shortname : 'git-multimail'
|
||||
|
||||
Now, checking that git-multimail's standard input is properly set ...
|
||||
Please type some text and then press Return
|
||||
foo
|
||||
You have just entered:
|
||||
foo
|
||||
git-multimail seems properly set up.
|
||||
|
||||
If you forgot to set an important variable, you may get instead::
|
||||
|
||||
$ GIT_MULTIMAIL_CHECK_SETUP=true /home/moy/dev/git-multimail/git-multimail/git_multimail.py
|
||||
No email recipients configured!
|
||||
|
||||
Do not set ``$GIT_MULTIMAIL_CHECK_SETUP`` other than for testing your
|
||||
configuration: it would disable the hook completely.
|
||||
|
||||
Git is not using the right address in the From/To/Reply-To field
|
||||
----------------------------------------------------------------
|
||||
|
||||
First, make sure that git-multimail actually uses what you think it is
|
||||
using. A lot happens to your email (especially when posting to a
|
||||
mailing-list) between the time `git_multimail.py` sends it and the
|
||||
time it reaches your inbox.
|
||||
|
||||
A simple test (to do on a test repository, do not use in production as
|
||||
it would disable email sending): change your post-receive hook to call
|
||||
`git_multimail.py` with the `--stdout` option, and try to push to the
|
||||
repository. You should see something like::
|
||||
|
||||
Counting objects: 3, done.
|
||||
Writing objects: 100% (3/3), 263 bytes | 0 bytes/s, done.
|
||||
Total 3 (delta 0), reused 0 (delta 0)
|
||||
remote: Sending notification emails to: foo.bar@example.com
|
||||
remote: ===========================================================================
|
||||
remote: Date: Mon, 25 Apr 2016 18:39:59 +0200
|
||||
remote: To: foo.bar@example.com
|
||||
remote: Subject: [git] branch master updated: foo
|
||||
remote: MIME-Version: 1.0
|
||||
remote: Content-Type: text/plain; charset=utf-8
|
||||
remote: Content-Transfer-Encoding: 8bit
|
||||
remote: Message-ID: <20160425163959.2311.20498@anie>
|
||||
remote: From: Auth Or <Foo.Bar@example.com>
|
||||
remote: Reply-To: Auth Or <Foo.Bar@example.com>
|
||||
remote: X-Git-Host: example
|
||||
...
|
||||
remote: --
|
||||
remote: To stop receiving notification emails like this one, please contact
|
||||
remote: the administrator of this repository.
|
||||
remote: ===========================================================================
|
||||
To /path/to/repo
|
||||
6278f04..e173f20 master -> master
|
||||
|
||||
Note: this does not include the sender (Return-Path: header), as it is
|
||||
not part of the message content but passed to the mailer. Some mailer
|
||||
show the ``Sender:`` field instead of the ``From:`` field (for
|
||||
example, Zimbra Webmail shows ``From: <sender-field> on behalf of
|
||||
<from-field>``).
|
||||
4346
third_party/git/contrib/hooks/multimail/git_multimail.py
vendored
Executable file
4346
third_party/git/contrib/hooks/multimail/git_multimail.py
vendored
Executable file
File diff suppressed because it is too large
Load diff
274
third_party/git/contrib/hooks/multimail/migrate-mailhook-config
vendored
Executable file
274
third_party/git/contrib/hooks/multimail/migrate-mailhook-config
vendored
Executable file
|
|
@ -0,0 +1,274 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
"""Migrate a post-receive-email configuration to be usable with git_multimail.py.
|
||||
|
||||
See README.migrate-from-post-receive-email for more information.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import optparse
|
||||
|
||||
from git_multimail import CommandError
|
||||
from git_multimail import Config
|
||||
from git_multimail import read_output
|
||||
|
||||
|
||||
OLD_NAMES = [
|
||||
'mailinglist',
|
||||
'announcelist',
|
||||
'envelopesender',
|
||||
'emailprefix',
|
||||
'showrev',
|
||||
'emailmaxlines',
|
||||
'diffopts',
|
||||
'scancommitforcc',
|
||||
]
|
||||
|
||||
NEW_NAMES = [
|
||||
'environment',
|
||||
'reponame',
|
||||
'mailinglist',
|
||||
'refchangelist',
|
||||
'commitlist',
|
||||
'announcelist',
|
||||
'announceshortlog',
|
||||
'envelopesender',
|
||||
'administrator',
|
||||
'emailprefix',
|
||||
'emailmaxlines',
|
||||
'diffopts',
|
||||
'emaildomain',
|
||||
'scancommitforcc',
|
||||
]
|
||||
|
||||
|
||||
INFO = """\
|
||||
|
||||
SUCCESS!
|
||||
|
||||
Your post-receive-email configuration has been converted to
|
||||
git-multimail format. Please see README and
|
||||
README.migrate-from-post-receive-email to learn about other
|
||||
git-multimail configuration possibilities.
|
||||
|
||||
For example, git-multimail has the following new options with no
|
||||
equivalent in post-receive-email. You might want to read about them
|
||||
to see if they would be useful in your situation:
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def _check_old_config_exists(old):
|
||||
"""Check that at least one old configuration value is set."""
|
||||
|
||||
for name in OLD_NAMES:
|
||||
if name in old:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _check_new_config_clear(new):
|
||||
"""Check that none of the new configuration names are set."""
|
||||
|
||||
retval = True
|
||||
for name in NEW_NAMES:
|
||||
if name in new:
|
||||
if retval:
|
||||
sys.stderr.write('INFO: The following configuration values already exist:\n\n')
|
||||
sys.stderr.write(' "%s.%s"\n' % (new.section, name))
|
||||
retval = False
|
||||
|
||||
return retval
|
||||
|
||||
|
||||
def erase_values(config, names):
|
||||
for name in names:
|
||||
if name in config:
|
||||
try:
|
||||
sys.stderr.write('...unsetting "%s.%s"\n' % (config.section, name))
|
||||
config.unset_all(name)
|
||||
except CommandError:
|
||||
sys.stderr.write(
|
||||
'\nWARNING: could not unset "%s.%s". '
|
||||
'Perhaps it is not set at the --local level?\n\n'
|
||||
% (config.section, name)
|
||||
)
|
||||
|
||||
|
||||
def is_section_empty(section, local):
|
||||
"""Return True iff the specified configuration section is empty.
|
||||
|
||||
Iff local is True, use the --local option when invoking 'git
|
||||
config'."""
|
||||
|
||||
if local:
|
||||
local_option = ['--local']
|
||||
else:
|
||||
local_option = []
|
||||
|
||||
try:
|
||||
read_output(
|
||||
['git', 'config'] +
|
||||
local_option +
|
||||
['--get-regexp', '^%s\.' % (section,)]
|
||||
)
|
||||
except CommandError:
|
||||
t, e, traceback = sys.exc_info()
|
||||
if e.retcode == 1:
|
||||
# This means that no settings were found.
|
||||
return True
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def remove_section_if_empty(section):
|
||||
"""If the specified configuration section is empty, delete it."""
|
||||
|
||||
try:
|
||||
empty = is_section_empty(section, local=True)
|
||||
except CommandError:
|
||||
# Older versions of git do not support the --local option, so
|
||||
# if the first attempt fails, try without --local.
|
||||
try:
|
||||
empty = is_section_empty(section, local=False)
|
||||
except CommandError:
|
||||
sys.stderr.write(
|
||||
'\nINFO: If configuration section "%s.*" is empty, you might want '
|
||||
'to delete it.\n\n'
|
||||
% (section,)
|
||||
)
|
||||
return
|
||||
|
||||
if empty:
|
||||
sys.stderr.write('...removing section "%s.*"\n' % (section,))
|
||||
read_output(['git', 'config', '--remove-section', section])
|
||||
else:
|
||||
sys.stderr.write(
|
||||
'\nINFO: Configuration section "%s.*" still has contents. '
|
||||
'It will not be deleted.\n\n'
|
||||
% (section,)
|
||||
)
|
||||
|
||||
|
||||
def migrate_config(strict=False, retain=False, overwrite=False):
|
||||
old = Config('hooks')
|
||||
new = Config('multimailhook')
|
||||
if not _check_old_config_exists(old):
|
||||
sys.exit(
|
||||
'Your repository has no post-receive-email configuration. '
|
||||
'Nothing to do.'
|
||||
)
|
||||
if not _check_new_config_clear(new):
|
||||
if overwrite:
|
||||
sys.stderr.write('\nWARNING: Erasing the above values...\n\n')
|
||||
erase_values(new, NEW_NAMES)
|
||||
else:
|
||||
sys.exit(
|
||||
'\nERROR: Refusing to overwrite existing values. Use the --overwrite\n'
|
||||
'option to continue anyway.'
|
||||
)
|
||||
|
||||
name = 'showrev'
|
||||
if name in old:
|
||||
msg = 'git-multimail does not support "%s.%s"' % (old.section, name,)
|
||||
if strict:
|
||||
sys.exit(
|
||||
'ERROR: %s.\n'
|
||||
'Please unset that value then try again, or run without --strict.'
|
||||
% (msg,)
|
||||
)
|
||||
else:
|
||||
sys.stderr.write('\nWARNING: %s (ignoring).\n\n' % (msg,))
|
||||
|
||||
for name in ['mailinglist', 'announcelist']:
|
||||
if name in old:
|
||||
sys.stderr.write(
|
||||
'...copying "%s.%s" to "%s.%s"\n' % (old.section, name, new.section, name)
|
||||
)
|
||||
old_recipients = old.get_all(name, default=None)
|
||||
old_recipients = ', '.join(o.strip() for o in old_recipients)
|
||||
new.set_recipients(name, old_recipients)
|
||||
|
||||
if strict:
|
||||
sys.stderr.write(
|
||||
'...setting "%s.commitlist" to the empty string\n' % (new.section,)
|
||||
)
|
||||
new.set_recipients('commitlist', '')
|
||||
sys.stderr.write(
|
||||
'...setting "%s.announceshortlog" to "true"\n' % (new.section,)
|
||||
)
|
||||
new.set('announceshortlog', 'true')
|
||||
|
||||
for name in ['envelopesender', 'emailmaxlines', 'diffopts', 'scancommitforcc']:
|
||||
if name in old:
|
||||
sys.stderr.write(
|
||||
'...copying "%s.%s" to "%s.%s"\n' % (old.section, name, new.section, name)
|
||||
)
|
||||
new.set(name, old.get(name))
|
||||
|
||||
name = 'emailprefix'
|
||||
if name in old:
|
||||
sys.stderr.write(
|
||||
'...copying "%s.%s" to "%s.%s"\n' % (old.section, name, new.section, name)
|
||||
)
|
||||
new.set(name, old.get(name))
|
||||
elif strict:
|
||||
sys.stderr.write(
|
||||
'...setting "%s.%s" to "[SCM]" to preserve old subject lines\n'
|
||||
% (new.section, name)
|
||||
)
|
||||
new.set(name, '[SCM]')
|
||||
|
||||
if not retain:
|
||||
erase_values(old, OLD_NAMES)
|
||||
remove_section_if_empty(old.section)
|
||||
|
||||
sys.stderr.write(INFO)
|
||||
for name in NEW_NAMES:
|
||||
if name not in OLD_NAMES:
|
||||
sys.stderr.write(' "%s.%s"\n' % (new.section, name,))
|
||||
sys.stderr.write('\n')
|
||||
|
||||
|
||||
def main(args):
|
||||
parser = optparse.OptionParser(
|
||||
description=__doc__,
|
||||
usage='%prog [OPTIONS]',
|
||||
)
|
||||
|
||||
parser.add_option(
|
||||
'--strict', action='store_true', default=False,
|
||||
help=(
|
||||
'Slavishly configure git-multimail as closely as possible to '
|
||||
'the post-receive-email configuration. Default is to turn '
|
||||
'on some new features that have no equivalent in post-receive-email.'
|
||||
),
|
||||
)
|
||||
parser.add_option(
|
||||
'--retain', action='store_true', default=False,
|
||||
help=(
|
||||
'Retain the post-receive-email configuration values. '
|
||||
'Default is to delete them after the new values are set.'
|
||||
),
|
||||
)
|
||||
parser.add_option(
|
||||
'--overwrite', action='store_true', default=False,
|
||||
help=(
|
||||
'Overwrite any existing git-multimail configuration settings. '
|
||||
'Default is to abort if such settings already exist.'
|
||||
),
|
||||
)
|
||||
|
||||
(options, args) = parser.parse_args(args)
|
||||
|
||||
if args:
|
||||
parser.error('Unexpected arguments: %s' % (' '.join(args),))
|
||||
|
||||
migrate_config(strict=options.strict, retain=options.retain, overwrite=options.overwrite)
|
||||
|
||||
|
||||
main(sys.argv[1:])
|
||||
101
third_party/git/contrib/hooks/multimail/post-receive.example
vendored
Executable file
101
third_party/git/contrib/hooks/multimail/post-receive.example
vendored
Executable file
|
|
@ -0,0 +1,101 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
"""Example post-receive hook based on git-multimail.
|
||||
|
||||
The simplest way to use git-multimail is to use the script
|
||||
git_multimail.py directly as a post-receive hook, and to configure it
|
||||
using Git's configuration files and command-line parameters. You can
|
||||
also write your own Python wrapper for more advanced configurability,
|
||||
using git_multimail.py as a Python module.
|
||||
|
||||
This script is a simple example of such a post-receive hook. It is
|
||||
intended to be customized before use; see the comments in the script
|
||||
to help you get started.
|
||||
|
||||
Using git-multimail as a Python module as done here provides more
|
||||
flexibility. It has the following advantages:
|
||||
|
||||
* The tool's behavior can be customized using arbitrary Python code,
|
||||
without having to edit git_multimail.py.
|
||||
|
||||
* Configuration settings can be read from other sources; for example,
|
||||
user names and email addresses could be read from LDAP or from a
|
||||
database. Or the settings can even be hardcoded in the importing
|
||||
Python script, if this is preferred.
|
||||
|
||||
This script is a very basic example of how to use git_multimail.py as
|
||||
a module. The comments below explain some of the points at which the
|
||||
script's behavior could be changed or customized.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
# If necessary, add the path to the directory containing
|
||||
# git_multimail.py to the Python path as follows. (This is not
|
||||
# necessary if git_multimail.py is in the same directory as this
|
||||
# script):
|
||||
|
||||
#LIBDIR = 'path/to/directory/containing/module'
|
||||
#sys.path.insert(0, LIBDIR)
|
||||
|
||||
import git_multimail
|
||||
|
||||
# It is possible to modify the output templates here; e.g.:
|
||||
|
||||
#git_multimail.FOOTER_TEMPLATE = """\
|
||||
#
|
||||
#-- \n\
|
||||
#This email was generated by the wonderful git-multimail tool.
|
||||
#"""
|
||||
|
||||
|
||||
# Specify which "git config" section contains the configuration for
|
||||
# git-multimail:
|
||||
config = git_multimail.Config('multimailhook')
|
||||
|
||||
# Set some Git configuration variables. Equivalent to passing var=val
|
||||
# to "git -c var=val" each time git is called, or to adding the
|
||||
# configuration in .git/config (must come before instanciating the
|
||||
# environment) :
|
||||
#git_multimail.Config.add_config_parameters('multimailhook.commitEmailFormat=html')
|
||||
#git_multimail.Config.add_config_parameters(('user.name=foo', 'user.email=foo@example.com'))
|
||||
|
||||
# Select the type of environment:
|
||||
try:
|
||||
environment = git_multimail.GenericEnvironment(config=config)
|
||||
#environment = git_multimail.GitoliteEnvironment(config=config)
|
||||
except git_multimail.ConfigurationException:
|
||||
sys.stderr.write('*** %s\n' % sys.exc_info()[1])
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# Choose the method of sending emails based on the git config:
|
||||
mailer = git_multimail.choose_mailer(config, environment)
|
||||
|
||||
# Alternatively, you may hardcode the mailer using code like one of
|
||||
# the following:
|
||||
|
||||
# Use "/usr/sbin/sendmail -oi -t" to send emails. The envelopesender
|
||||
# argument is optional:
|
||||
#mailer = git_multimail.SendMailer(
|
||||
# command=['/usr/sbin/sendmail', '-oi', '-t'],
|
||||
# envelopesender='git-repo@example.com',
|
||||
# )
|
||||
|
||||
# Use Python's smtplib to send emails. Both arguments are required.
|
||||
#mailer = git_multimail.SMTPMailer(
|
||||
# environment=environment,
|
||||
# envelopesender='git-repo@example.com',
|
||||
# # The smtpserver argument can also include a port number; e.g.,
|
||||
# # smtpserver='mail.example.com:25'
|
||||
# smtpserver='mail.example.com',
|
||||
# )
|
||||
|
||||
# OutputMailer is intended only for testing; it writes the emails to
|
||||
# the specified file stream.
|
||||
#mailer = git_multimail.OutputMailer(sys.stdout)
|
||||
|
||||
|
||||
# Read changes from stdin and send notification emails:
|
||||
git_multimail.run_as_post_receive_hook(environment, mailer)
|
||||
759
third_party/git/contrib/hooks/post-receive-email
vendored
Executable file
759
third_party/git/contrib/hooks/post-receive-email
vendored
Executable file
|
|
@ -0,0 +1,759 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2007 Andy Parkins
|
||||
#
|
||||
# An example hook script to mail out commit update information.
|
||||
#
|
||||
# NOTE: This script is no longer under active development. There
|
||||
# is another script, git-multimail, which is more capable and
|
||||
# configurable and is largely backwards-compatible with this script;
|
||||
# please see "contrib/hooks/multimail/". For instructions on how to
|
||||
# migrate from post-receive-email to git-multimail, please see
|
||||
# "README.migrate-from-post-receive-email" in that directory.
|
||||
#
|
||||
# This hook sends emails listing new revisions to the repository
|
||||
# introduced by the change being reported. The rule is that (for
|
||||
# branch updates) each commit will appear on one email and one email
|
||||
# only.
|
||||
#
|
||||
# This hook is stored in the contrib/hooks directory. Your distribution
|
||||
# will have put this somewhere standard. You should make this script
|
||||
# executable then link to it in the repository you would like to use it in.
|
||||
# For example, on debian the hook is stored in
|
||||
# /usr/share/git-core/contrib/hooks/post-receive-email:
|
||||
#
|
||||
# cd /path/to/your/repository.git
|
||||
# ln -sf /usr/share/git-core/contrib/hooks/post-receive-email hooks/post-receive
|
||||
#
|
||||
# This hook script assumes it is enabled on the central repository of a
|
||||
# project, with all users pushing only to it and not between each other. It
|
||||
# will still work if you don't operate in that style, but it would become
|
||||
# possible for the email to be from someone other than the person doing the
|
||||
# push.
|
||||
#
|
||||
# To help with debugging and use on pre-v1.5.1 git servers, this script will
|
||||
# also obey the interface of hooks/update, taking its arguments on the
|
||||
# command line. Unfortunately, hooks/update is called once for each ref.
|
||||
# To avoid firing one email per ref, this script just prints its output to
|
||||
# the screen when used in this mode. The output can then be redirected if
|
||||
# wanted.
|
||||
#
|
||||
# Config
|
||||
# ------
|
||||
# hooks.mailinglist
|
||||
# This is the list that all pushes will go to; leave it blank to not send
|
||||
# emails for every ref update.
|
||||
# hooks.announcelist
|
||||
# This is the list that all pushes of annotated tags will go to. Leave it
|
||||
# blank to default to the mailinglist field. The announce emails lists
|
||||
# the short log summary of the changes since the last annotated tag.
|
||||
# hooks.envelopesender
|
||||
# If set then the -f option is passed to sendmail to allow the envelope
|
||||
# sender address to be set
|
||||
# hooks.emailprefix
|
||||
# All emails have their subjects prefixed with this prefix, or "[SCM]"
|
||||
# if emailprefix is unset, to aid filtering
|
||||
# hooks.showrev
|
||||
# The shell command used to format each revision in the email, with
|
||||
# "%s" replaced with the commit id. Defaults to "git rev-list -1
|
||||
# --pretty %s", displaying the commit id, author, date and log
|
||||
# message. To list full patches separated by a blank line, you
|
||||
# could set this to "git show -C %s; echo".
|
||||
# To list a gitweb/cgit URL *and* a full patch for each change set, use this:
|
||||
# "t=%s; printf 'http://.../?id=%%s' \$t; echo;echo; git show -C \$t; echo"
|
||||
# Be careful if "..." contains things that will be expanded by shell "eval"
|
||||
# or printf.
|
||||
# hooks.emailmaxlines
|
||||
# The maximum number of lines that should be included in the generated
|
||||
# email body. If not specified, there is no limit.
|
||||
# Lines beyond the limit are suppressed and counted, and a final
|
||||
# line is added indicating the number of suppressed lines.
|
||||
# hooks.diffopts
|
||||
# Alternate options for the git diff-tree invocation that shows changes.
|
||||
# Default is "--stat --summary --find-copies-harder". Add -p to those
|
||||
# options to include a unified diff of changes in addition to the usual
|
||||
# summary output.
|
||||
#
|
||||
# Notes
|
||||
# -----
|
||||
# All emails include the headers "X-Git-Refname", "X-Git-Oldrev",
|
||||
# "X-Git-Newrev", and "X-Git-Reftype" to enable fine tuned filtering and
|
||||
# give information for debugging.
|
||||
#
|
||||
|
||||
# ---------------------------- Functions
|
||||
|
||||
#
|
||||
# Function to prepare for email generation. This decides what type
|
||||
# of update this is and whether an email should even be generated.
|
||||
#
|
||||
prep_for_email()
|
||||
{
|
||||
# --- Arguments
|
||||
oldrev=$(git rev-parse $1)
|
||||
newrev=$(git rev-parse $2)
|
||||
refname="$3"
|
||||
|
||||
# --- Interpret
|
||||
# 0000->1234 (create)
|
||||
# 1234->2345 (update)
|
||||
# 2345->0000 (delete)
|
||||
if expr "$oldrev" : '0*$' >/dev/null
|
||||
then
|
||||
change_type="create"
|
||||
else
|
||||
if expr "$newrev" : '0*$' >/dev/null
|
||||
then
|
||||
change_type="delete"
|
||||
else
|
||||
change_type="update"
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Get the revision types
|
||||
newrev_type=$(git cat-file -t $newrev 2> /dev/null)
|
||||
oldrev_type=$(git cat-file -t "$oldrev" 2> /dev/null)
|
||||
case "$change_type" in
|
||||
create|update)
|
||||
rev="$newrev"
|
||||
rev_type="$newrev_type"
|
||||
;;
|
||||
delete)
|
||||
rev="$oldrev"
|
||||
rev_type="$oldrev_type"
|
||||
;;
|
||||
esac
|
||||
|
||||
# The revision type tells us what type the commit is, combined with
|
||||
# the location of the ref we can decide between
|
||||
# - working branch
|
||||
# - tracking branch
|
||||
# - unannoted tag
|
||||
# - annotated tag
|
||||
case "$refname","$rev_type" in
|
||||
refs/tags/*,commit)
|
||||
# un-annotated tag
|
||||
refname_type="tag"
|
||||
short_refname=${refname##refs/tags/}
|
||||
;;
|
||||
refs/tags/*,tag)
|
||||
# annotated tag
|
||||
refname_type="annotated tag"
|
||||
short_refname=${refname##refs/tags/}
|
||||
# change recipients
|
||||
if [ -n "$announcerecipients" ]; then
|
||||
recipients="$announcerecipients"
|
||||
fi
|
||||
;;
|
||||
refs/heads/*,commit)
|
||||
# branch
|
||||
refname_type="branch"
|
||||
short_refname=${refname##refs/heads/}
|
||||
;;
|
||||
refs/remotes/*,commit)
|
||||
# tracking branch
|
||||
refname_type="tracking branch"
|
||||
short_refname=${refname##refs/remotes/}
|
||||
echo >&2 "*** Push-update of tracking branch, $refname"
|
||||
echo >&2 "*** - no email generated."
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
# Anything else (is there anything else?)
|
||||
echo >&2 "*** Unknown type of update to $refname ($rev_type)"
|
||||
echo >&2 "*** - no email generated"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Check if we've got anyone to send to
|
||||
if [ -z "$recipients" ]; then
|
||||
case "$refname_type" in
|
||||
"annotated tag")
|
||||
config_name="hooks.announcelist"
|
||||
;;
|
||||
*)
|
||||
config_name="hooks.mailinglist"
|
||||
;;
|
||||
esac
|
||||
echo >&2 "*** $config_name is not set so no email will be sent"
|
||||
echo >&2 "*** for $refname update $oldrev->$newrev"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
#
|
||||
# Top level email generation function. This calls the appropriate
|
||||
# body-generation routine after outputting the common header.
|
||||
#
|
||||
# Note this function doesn't actually generate any email output, that is
|
||||
# taken care of by the functions it calls:
|
||||
# - generate_email_header
|
||||
# - generate_create_XXXX_email
|
||||
# - generate_update_XXXX_email
|
||||
# - generate_delete_XXXX_email
|
||||
# - generate_email_footer
|
||||
#
|
||||
# Note also that this function cannot 'exit' from the script; when this
|
||||
# function is running (in hook script mode), the send_mail() function
|
||||
# is already executing in another process, connected via a pipe, and
|
||||
# if this function exits without, whatever has been generated to that
|
||||
# point will be sent as an email... even if nothing has been generated.
|
||||
#
|
||||
generate_email()
|
||||
{
|
||||
# Email parameters
|
||||
# The email subject will contain the best description of the ref
|
||||
# that we can build from the parameters
|
||||
describe=$(git describe $rev 2>/dev/null)
|
||||
if [ -z "$describe" ]; then
|
||||
describe=$rev
|
||||
fi
|
||||
|
||||
generate_email_header
|
||||
|
||||
# Call the correct body generation function
|
||||
fn_name=general
|
||||
case "$refname_type" in
|
||||
"tracking branch"|branch)
|
||||
fn_name=branch
|
||||
;;
|
||||
"annotated tag")
|
||||
fn_name=atag
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "$maxlines" ]; then
|
||||
generate_${change_type}_${fn_name}_email
|
||||
else
|
||||
generate_${change_type}_${fn_name}_email | limit_lines $maxlines
|
||||
fi
|
||||
|
||||
generate_email_footer
|
||||
}
|
||||
|
||||
generate_email_header()
|
||||
{
|
||||
# --- Email (all stdout will be the email)
|
||||
# Generate header
|
||||
cat <<-EOF
|
||||
To: $recipients
|
||||
Subject: ${emailprefix}$projectdesc $refname_type $short_refname ${change_type}d. $describe
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
X-Git-Refname: $refname
|
||||
X-Git-Reftype: $refname_type
|
||||
X-Git-Oldrev: $oldrev
|
||||
X-Git-Newrev: $newrev
|
||||
Auto-Submitted: auto-generated
|
||||
|
||||
This is an automated email from the git hooks/post-receive script. It was
|
||||
generated because a ref change was pushed to the repository containing
|
||||
the project "$projectdesc".
|
||||
|
||||
The $refname_type, $short_refname has been ${change_type}d
|
||||
EOF
|
||||
}
|
||||
|
||||
generate_email_footer()
|
||||
{
|
||||
SPACE=" "
|
||||
cat <<-EOF
|
||||
|
||||
|
||||
hooks/post-receive
|
||||
--${SPACE}
|
||||
$projectdesc
|
||||
EOF
|
||||
}
|
||||
|
||||
# --------------- Branches
|
||||
|
||||
#
|
||||
# Called for the creation of a branch
|
||||
#
|
||||
generate_create_branch_email()
|
||||
{
|
||||
# This is a new branch and so oldrev is not valid
|
||||
echo " at $newrev ($newrev_type)"
|
||||
echo ""
|
||||
|
||||
echo $LOGBEGIN
|
||||
show_new_revisions
|
||||
echo $LOGEND
|
||||
}
|
||||
|
||||
#
|
||||
# Called for the change of a pre-existing branch
|
||||
#
|
||||
generate_update_branch_email()
|
||||
{
|
||||
# Consider this:
|
||||
# 1 --- 2 --- O --- X --- 3 --- 4 --- N
|
||||
#
|
||||
# O is $oldrev for $refname
|
||||
# N is $newrev for $refname
|
||||
# X is a revision pointed to by some other ref, for which we may
|
||||
# assume that an email has already been generated.
|
||||
# In this case we want to issue an email containing only revisions
|
||||
# 3, 4, and N. Given (almost) by
|
||||
#
|
||||
# git rev-list N ^O --not --all
|
||||
#
|
||||
# The reason for the "almost", is that the "--not --all" will take
|
||||
# precedence over the "N", and effectively will translate to
|
||||
#
|
||||
# git rev-list N ^O ^X ^N
|
||||
#
|
||||
# So, we need to build up the list more carefully. git rev-parse
|
||||
# will generate a list of revs that may be fed into git rev-list.
|
||||
# We can get it to make the "--not --all" part and then filter out
|
||||
# the "^N" with:
|
||||
#
|
||||
# git rev-parse --not --all | grep -v N
|
||||
#
|
||||
# Then, using the --stdin switch to git rev-list we have effectively
|
||||
# manufactured
|
||||
#
|
||||
# git rev-list N ^O ^X
|
||||
#
|
||||
# This leaves a problem when someone else updates the repository
|
||||
# while this script is running. Their new value of the ref we're
|
||||
# working on would be included in the "--not --all" output; and as
|
||||
# our $newrev would be an ancestor of that commit, it would exclude
|
||||
# all of our commits. What we really want is to exclude the current
|
||||
# value of $refname from the --not list, rather than N itself. So:
|
||||
#
|
||||
# git rev-parse --not --all | grep -v $(git rev-parse $refname)
|
||||
#
|
||||
# Get's us to something pretty safe (apart from the small time
|
||||
# between refname being read, and git rev-parse running - for that,
|
||||
# I give up)
|
||||
#
|
||||
#
|
||||
# Next problem, consider this:
|
||||
# * --- B --- * --- O ($oldrev)
|
||||
# \
|
||||
# * --- X --- * --- N ($newrev)
|
||||
#
|
||||
# That is to say, there is no guarantee that oldrev is a strict
|
||||
# subset of newrev (it would have required a --force, but that's
|
||||
# allowed). So, we can't simply say rev-list $oldrev..$newrev.
|
||||
# Instead we find the common base of the two revs and list from
|
||||
# there.
|
||||
#
|
||||
# As above, we need to take into account the presence of X; if
|
||||
# another branch is already in the repository and points at some of
|
||||
# the revisions that we are about to output - we don't want them.
|
||||
# The solution is as before: git rev-parse output filtered.
|
||||
#
|
||||
# Finally, tags: 1 --- 2 --- O --- T --- 3 --- 4 --- N
|
||||
#
|
||||
# Tags pushed into the repository generate nice shortlog emails that
|
||||
# summarise the commits between them and the previous tag. However,
|
||||
# those emails don't include the full commit messages that we output
|
||||
# for a branch update. Therefore we still want to output revisions
|
||||
# that have been output on a tag email.
|
||||
#
|
||||
# Luckily, git rev-parse includes just the tool. Instead of using
|
||||
# "--all" we use "--branches"; this has the added benefit that
|
||||
# "remotes/" will be ignored as well.
|
||||
|
||||
# List all of the revisions that were removed by this update, in a
|
||||
# fast-forward update, this list will be empty, because rev-list O
|
||||
# ^N is empty. For a non-fast-forward, O ^N is the list of removed
|
||||
# revisions
|
||||
fast_forward=""
|
||||
rev=""
|
||||
for rev in $(git rev-list $newrev..$oldrev)
|
||||
do
|
||||
revtype=$(git cat-file -t "$rev")
|
||||
echo " discards $rev ($revtype)"
|
||||
done
|
||||
if [ -z "$rev" ]; then
|
||||
fast_forward=1
|
||||
fi
|
||||
|
||||
# List all the revisions from baserev to newrev in a kind of
|
||||
# "table-of-contents"; note this list can include revisions that
|
||||
# have already had notification emails and is present to show the
|
||||
# full detail of the change from rolling back the old revision to
|
||||
# the base revision and then forward to the new revision
|
||||
for rev in $(git rev-list $oldrev..$newrev)
|
||||
do
|
||||
revtype=$(git cat-file -t "$rev")
|
||||
echo " via $rev ($revtype)"
|
||||
done
|
||||
|
||||
if [ "$fast_forward" ]; then
|
||||
echo " from $oldrev ($oldrev_type)"
|
||||
else
|
||||
# 1. Existing revisions were removed. In this case newrev
|
||||
# is a subset of oldrev - this is the reverse of a
|
||||
# fast-forward, a rewind
|
||||
# 2. New revisions were added on top of an old revision,
|
||||
# this is a rewind and addition.
|
||||
|
||||
# (1) certainly happened, (2) possibly. When (2) hasn't
|
||||
# happened, we set a flag to indicate that no log printout
|
||||
# is required.
|
||||
|
||||
echo ""
|
||||
|
||||
# Find the common ancestor of the old and new revisions and
|
||||
# compare it with newrev
|
||||
baserev=$(git merge-base $oldrev $newrev)
|
||||
rewind_only=""
|
||||
if [ "$baserev" = "$newrev" ]; then
|
||||
echo "This update discarded existing revisions and left the branch pointing at"
|
||||
echo "a previous point in the repository history."
|
||||
echo ""
|
||||
echo " * -- * -- N ($newrev)"
|
||||
echo " \\"
|
||||
echo " O -- O -- O ($oldrev)"
|
||||
echo ""
|
||||
echo "The removed revisions are not necessarily gone - if another reference"
|
||||
echo "still refers to them they will stay in the repository."
|
||||
rewind_only=1
|
||||
else
|
||||
echo "This update added new revisions after undoing existing revisions. That is"
|
||||
echo "to say, the old revision is not a strict subset of the new revision. This"
|
||||
echo "situation occurs when you --force push a change and generate a repository"
|
||||
echo "containing something like this:"
|
||||
echo ""
|
||||
echo " * -- * -- B -- O -- O -- O ($oldrev)"
|
||||
echo " \\"
|
||||
echo " N -- N -- N ($newrev)"
|
||||
echo ""
|
||||
echo "When this happens we assume that you've already had alert emails for all"
|
||||
echo "of the O revisions, and so we here report only the revisions in the N"
|
||||
echo "branch from the common base, B."
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [ -z "$rewind_only" ]; then
|
||||
echo "Those revisions listed above that are new to this repository have"
|
||||
echo "not appeared on any other notification email; so we list those"
|
||||
echo "revisions in full, below."
|
||||
|
||||
echo ""
|
||||
echo $LOGBEGIN
|
||||
show_new_revisions
|
||||
|
||||
# XXX: Need a way of detecting whether git rev-list actually
|
||||
# outputted anything, so that we can issue a "no new
|
||||
# revisions added by this update" message
|
||||
|
||||
echo $LOGEND
|
||||
else
|
||||
echo "No new revisions were added by this update."
|
||||
fi
|
||||
|
||||
# The diffstat is shown from the old revision to the new revision.
|
||||
# This is to show the truth of what happened in this change.
|
||||
# There's no point showing the stat from the base to the new
|
||||
# revision because the base is effectively a random revision at this
|
||||
# point - the user will be interested in what this revision changed
|
||||
# - including the undoing of previous revisions in the case of
|
||||
# non-fast-forward updates.
|
||||
echo ""
|
||||
echo "Summary of changes:"
|
||||
git diff-tree $diffopts $oldrev..$newrev
|
||||
}
|
||||
|
||||
#
|
||||
# Called for the deletion of a branch
|
||||
#
|
||||
generate_delete_branch_email()
|
||||
{
|
||||
echo " was $oldrev"
|
||||
echo ""
|
||||
echo $LOGBEGIN
|
||||
git diff-tree -s --always --encoding=UTF-8 --pretty=oneline $oldrev
|
||||
echo $LOGEND
|
||||
}
|
||||
|
||||
# --------------- Annotated tags
|
||||
|
||||
#
|
||||
# Called for the creation of an annotated tag
|
||||
#
|
||||
generate_create_atag_email()
|
||||
{
|
||||
echo " at $newrev ($newrev_type)"
|
||||
|
||||
generate_atag_email
|
||||
}
|
||||
|
||||
#
|
||||
# Called for the update of an annotated tag (this is probably a rare event
|
||||
# and may not even be allowed)
|
||||
#
|
||||
generate_update_atag_email()
|
||||
{
|
||||
echo " to $newrev ($newrev_type)"
|
||||
echo " from $oldrev (which is now obsolete)"
|
||||
|
||||
generate_atag_email
|
||||
}
|
||||
|
||||
#
|
||||
# Called when an annotated tag is created or changed
|
||||
#
|
||||
generate_atag_email()
|
||||
{
|
||||
# Use git for-each-ref to pull out the individual fields from the
|
||||
# tag
|
||||
eval $(git for-each-ref --shell --format='
|
||||
tagobject=%(*objectname)
|
||||
tagtype=%(*objecttype)
|
||||
tagger=%(taggername)
|
||||
tagged=%(taggerdate)' $refname
|
||||
)
|
||||
|
||||
echo " tagging $tagobject ($tagtype)"
|
||||
case "$tagtype" in
|
||||
commit)
|
||||
|
||||
# If the tagged object is a commit, then we assume this is a
|
||||
# release, and so we calculate which tag this tag is
|
||||
# replacing
|
||||
prevtag=$(git describe --abbrev=0 $newrev^ 2>/dev/null)
|
||||
|
||||
if [ -n "$prevtag" ]; then
|
||||
echo " replaces $prevtag"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo " length $(git cat-file -s $tagobject) bytes"
|
||||
;;
|
||||
esac
|
||||
echo " tagged by $tagger"
|
||||
echo " on $tagged"
|
||||
|
||||
echo ""
|
||||
echo $LOGBEGIN
|
||||
|
||||
# Show the content of the tag message; this might contain a change
|
||||
# log or release notes so is worth displaying.
|
||||
git cat-file tag $newrev | sed -e '1,/^$/d'
|
||||
|
||||
echo ""
|
||||
case "$tagtype" in
|
||||
commit)
|
||||
# Only commit tags make sense to have rev-list operations
|
||||
# performed on them
|
||||
if [ -n "$prevtag" ]; then
|
||||
# Show changes since the previous release
|
||||
git shortlog "$prevtag..$newrev"
|
||||
else
|
||||
# No previous tag, show all the changes since time
|
||||
# began
|
||||
git shortlog $newrev
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
# XXX: Is there anything useful we can do for non-commit
|
||||
# objects?
|
||||
;;
|
||||
esac
|
||||
|
||||
echo $LOGEND
|
||||
}
|
||||
|
||||
#
|
||||
# Called for the deletion of an annotated tag
|
||||
#
|
||||
generate_delete_atag_email()
|
||||
{
|
||||
echo " was $oldrev"
|
||||
echo ""
|
||||
echo $LOGBEGIN
|
||||
git diff-tree -s --always --encoding=UTF-8 --pretty=oneline $oldrev
|
||||
echo $LOGEND
|
||||
}
|
||||
|
||||
# --------------- General references
|
||||
|
||||
#
|
||||
# Called when any other type of reference is created (most likely a
|
||||
# non-annotated tag)
|
||||
#
|
||||
generate_create_general_email()
|
||||
{
|
||||
echo " at $newrev ($newrev_type)"
|
||||
|
||||
generate_general_email
|
||||
}
|
||||
|
||||
#
|
||||
# Called when any other type of reference is updated (most likely a
|
||||
# non-annotated tag)
|
||||
#
|
||||
generate_update_general_email()
|
||||
{
|
||||
echo " to $newrev ($newrev_type)"
|
||||
echo " from $oldrev"
|
||||
|
||||
generate_general_email
|
||||
}
|
||||
|
||||
#
|
||||
# Called for creation or update of any other type of reference
|
||||
#
|
||||
generate_general_email()
|
||||
{
|
||||
# Unannotated tags are more about marking a point than releasing a
|
||||
# version; therefore we don't do the shortlog summary that we do for
|
||||
# annotated tags above - we simply show that the point has been
|
||||
# marked, and print the log message for the marked point for
|
||||
# reference purposes
|
||||
#
|
||||
# Note this section also catches any other reference type (although
|
||||
# there aren't any) and deals with them in the same way.
|
||||
|
||||
echo ""
|
||||
if [ "$newrev_type" = "commit" ]; then
|
||||
echo $LOGBEGIN
|
||||
git diff-tree -s --always --encoding=UTF-8 --pretty=medium $newrev
|
||||
echo $LOGEND
|
||||
else
|
||||
# What can we do here? The tag marks an object that is not
|
||||
# a commit, so there is no log for us to display. It's
|
||||
# probably not wise to output git cat-file as it could be a
|
||||
# binary blob. We'll just say how big it is
|
||||
echo "$newrev is a $newrev_type, and is $(git cat-file -s $newrev) bytes long."
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# Called for the deletion of any other type of reference
|
||||
#
|
||||
generate_delete_general_email()
|
||||
{
|
||||
echo " was $oldrev"
|
||||
echo ""
|
||||
echo $LOGBEGIN
|
||||
git diff-tree -s --always --encoding=UTF-8 --pretty=oneline $oldrev
|
||||
echo $LOGEND
|
||||
}
|
||||
|
||||
|
||||
# --------------- Miscellaneous utilities
|
||||
|
||||
#
|
||||
# Show new revisions as the user would like to see them in the email.
|
||||
#
|
||||
show_new_revisions()
|
||||
{
|
||||
# This shows all log entries that are not already covered by
|
||||
# another ref - i.e. commits that are now accessible from this
|
||||
# ref that were previously not accessible
|
||||
# (see generate_update_branch_email for the explanation of this
|
||||
# command)
|
||||
|
||||
# Revision range passed to rev-list differs for new vs. updated
|
||||
# branches.
|
||||
if [ "$change_type" = create ]
|
||||
then
|
||||
# Show all revisions exclusive to this (new) branch.
|
||||
revspec=$newrev
|
||||
else
|
||||
# Branch update; show revisions not part of $oldrev.
|
||||
revspec=$oldrev..$newrev
|
||||
fi
|
||||
|
||||
other_branches=$(git for-each-ref --format='%(refname)' refs/heads/ |
|
||||
grep -F -v $refname)
|
||||
git rev-parse --not $other_branches |
|
||||
if [ -z "$custom_showrev" ]
|
||||
then
|
||||
git rev-list --pretty --stdin $revspec
|
||||
else
|
||||
git rev-list --stdin $revspec |
|
||||
while read onerev
|
||||
do
|
||||
eval $(printf "$custom_showrev" $onerev)
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
limit_lines()
|
||||
{
|
||||
lines=0
|
||||
skipped=0
|
||||
while IFS="" read -r line; do
|
||||
lines=$((lines + 1))
|
||||
if [ $lines -gt $1 ]; then
|
||||
skipped=$((skipped + 1))
|
||||
else
|
||||
printf "%s\n" "$line"
|
||||
fi
|
||||
done
|
||||
if [ $skipped -ne 0 ]; then
|
||||
echo "... $skipped lines suppressed ..."
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
send_mail()
|
||||
{
|
||||
if [ -n "$envelopesender" ]; then
|
||||
/usr/sbin/sendmail -t -f "$envelopesender"
|
||||
else
|
||||
/usr/sbin/sendmail -t
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------------------------- main()
|
||||
|
||||
# --- Constants
|
||||
LOGBEGIN="- Log -----------------------------------------------------------------"
|
||||
LOGEND="-----------------------------------------------------------------------"
|
||||
|
||||
# --- Config
|
||||
# Set GIT_DIR either from the working directory, or from the environment
|
||||
# variable.
|
||||
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
|
||||
if [ -z "$GIT_DIR" ]; then
|
||||
echo >&2 "fatal: post-receive: GIT_DIR not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
projectdesc=$(sed -ne '1p' "$GIT_DIR/description" 2>/dev/null)
|
||||
# Check if the description is unchanged from it's default, and shorten it to
|
||||
# a more manageable length if it is
|
||||
if expr "$projectdesc" : "Unnamed repository.*$" >/dev/null
|
||||
then
|
||||
projectdesc="UNNAMED PROJECT"
|
||||
fi
|
||||
|
||||
recipients=$(git config hooks.mailinglist)
|
||||
announcerecipients=$(git config hooks.announcelist)
|
||||
envelopesender=$(git config hooks.envelopesender)
|
||||
emailprefix=$(git config hooks.emailprefix || echo '[SCM] ')
|
||||
custom_showrev=$(git config hooks.showrev)
|
||||
maxlines=$(git config hooks.emailmaxlines)
|
||||
diffopts=$(git config hooks.diffopts)
|
||||
: ${diffopts:="--stat --summary --find-copies-harder"}
|
||||
|
||||
# --- Main loop
|
||||
# Allow dual mode: run from the command line just like the update hook, or
|
||||
# if no arguments are given then run as a hook script
|
||||
if [ -n "$1" -a -n "$2" -a -n "$3" ]; then
|
||||
# Output to the terminal in command line mode - if someone wanted to
|
||||
# resend an email; they could redirect the output to sendmail
|
||||
# themselves
|
||||
prep_for_email $2 $3 $1 && PAGER= generate_email
|
||||
else
|
||||
while read oldrev newrev refname
|
||||
do
|
||||
prep_for_email $oldrev $newrev $refname || continue
|
||||
generate_email $maxlines | send_mail
|
||||
done
|
||||
fi
|
||||
42
third_party/git/contrib/hooks/pre-auto-gc-battery
vendored
Executable file
42
third_party/git/contrib/hooks/pre-auto-gc-battery
vendored
Executable file
|
|
@ -0,0 +1,42 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to verify if you are on battery, in case you
|
||||
# are running Linux or OS X. Called by git-gc --auto with no arguments.
|
||||
# The hook should exit with non-zero status after issuing an appropriate
|
||||
# message if it wants to stop the auto repacking.
|
||||
#
|
||||
# This hook is stored in the contrib/hooks directory. Your distribution
|
||||
# may have put this somewhere else. If you want to use this hook, you
|
||||
# should make this script executable then link to it in the repository
|
||||
# you would like to use it in.
|
||||
#
|
||||
# For example, if the hook is stored in
|
||||
# /usr/share/git-core/contrib/hooks/pre-auto-gc-battery:
|
||||
#
|
||||
# cd /path/to/your/repository.git
|
||||
# ln -sf /usr/share/git-core/contrib/hooks/pre-auto-gc-battery \
|
||||
# hooks/pre-auto-gc
|
||||
|
||||
if test -x /sbin/on_ac_power && (/sbin/on_ac_power;test $? -ne 1)
|
||||
then
|
||||
exit 0
|
||||
elif test "$(cat /sys/class/power_supply/AC/online 2>/dev/null)" = 1
|
||||
then
|
||||
exit 0
|
||||
elif grep -q 'on-line' /proc/acpi/ac_adapter/AC/state 2>/dev/null
|
||||
then
|
||||
exit 0
|
||||
elif grep -q '0x01$' /proc/apm 2>/dev/null
|
||||
then
|
||||
exit 0
|
||||
elif grep -q "AC Power \+: 1" /proc/pmu/info 2>/dev/null
|
||||
then
|
||||
exit 0
|
||||
elif test -x /usr/bin/pmset && /usr/bin/pmset -g batt |
|
||||
grep -q "drawing from 'AC Power'"
|
||||
then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Auto packing deferred; not on AC"
|
||||
exit 1
|
||||
214
third_party/git/contrib/hooks/setgitperms.perl
vendored
Executable file
214
third_party/git/contrib/hooks/setgitperms.perl
vendored
Executable file
|
|
@ -0,0 +1,214 @@
|
|||
#!/usr/bin/perl
|
||||
#
|
||||
# Copyright (c) 2006 Josh England
|
||||
#
|
||||
# This script can be used to save/restore full permissions and ownership data
|
||||
# within a git working tree.
|
||||
#
|
||||
# To save permissions/ownership data, place this script in your .git/hooks
|
||||
# directory and enable a `pre-commit` hook with the following lines:
|
||||
# #!/bin/sh
|
||||
# SUBDIRECTORY_OK=1 . git-sh-setup
|
||||
# $GIT_DIR/hooks/setgitperms.perl -r
|
||||
#
|
||||
# To restore permissions/ownership data, place this script in your .git/hooks
|
||||
# directory and enable a `post-merge` and `post-checkout` hook with the
|
||||
# following lines:
|
||||
# #!/bin/sh
|
||||
# SUBDIRECTORY_OK=1 . git-sh-setup
|
||||
# $GIT_DIR/hooks/setgitperms.perl -w
|
||||
#
|
||||
use strict;
|
||||
use Getopt::Long;
|
||||
use File::Find;
|
||||
use File::Basename;
|
||||
|
||||
my $usage =
|
||||
"usage: setgitperms.perl [OPTION]... <--read|--write>
|
||||
This program uses a file `.gitmeta` to store/restore permissions and uid/gid
|
||||
info for all files/dirs tracked by git in the repository.
|
||||
|
||||
---------------------------------Read Mode-------------------------------------
|
||||
-r, --read Reads perms/etc from working dir into a .gitmeta file
|
||||
-s, --stdout Output to stdout instead of .gitmeta
|
||||
-d, --diff Show unified diff of perms file (XOR with --stdout)
|
||||
|
||||
---------------------------------Write Mode------------------------------------
|
||||
-w, --write Modify perms/etc in working dir to match the .gitmeta file
|
||||
-v, --verbose Be verbose
|
||||
|
||||
\n";
|
||||
|
||||
my ($stdout, $showdiff, $verbose, $read_mode, $write_mode);
|
||||
|
||||
if ((@ARGV < 0) || !GetOptions(
|
||||
"stdout", \$stdout,
|
||||
"diff", \$showdiff,
|
||||
"read", \$read_mode,
|
||||
"write", \$write_mode,
|
||||
"verbose", \$verbose,
|
||||
)) { die $usage; }
|
||||
die $usage unless ($read_mode xor $write_mode);
|
||||
|
||||
my $topdir = `git rev-parse --show-cdup` or die "\n"; chomp $topdir;
|
||||
my $gitdir = $topdir . '.git';
|
||||
my $gitmeta = $topdir . '.gitmeta';
|
||||
|
||||
if ($write_mode) {
|
||||
# Update the working dir permissions/ownership based on data from .gitmeta
|
||||
open (IN, "<$gitmeta") or die "Could not open $gitmeta for reading: $!\n";
|
||||
while (defined ($_ = <IN>)) {
|
||||
chomp;
|
||||
if (/^(.*) mode=(\S+)\s+uid=(\d+)\s+gid=(\d+)/) {
|
||||
# Compare recorded perms to actual perms in the working dir
|
||||
my ($path, $mode, $uid, $gid) = ($1, $2, $3, $4);
|
||||
my $fullpath = $topdir . $path;
|
||||
my (undef,undef,$wmode,undef,$wuid,$wgid) = lstat($fullpath);
|
||||
$wmode = sprintf "%04o", $wmode & 07777;
|
||||
if ($mode ne $wmode) {
|
||||
$verbose && print "Updating permissions on $path: old=$wmode, new=$mode\n";
|
||||
chmod oct($mode), $fullpath;
|
||||
}
|
||||
if ($uid != $wuid || $gid != $wgid) {
|
||||
if ($verbose) {
|
||||
# Print out user/group names instead of uid/gid
|
||||
my $pwname = getpwuid($uid);
|
||||
my $grpname = getgrgid($gid);
|
||||
my $wpwname = getpwuid($wuid);
|
||||
my $wgrpname = getgrgid($wgid);
|
||||
$pwname = $uid if !defined $pwname;
|
||||
$grpname = $gid if !defined $grpname;
|
||||
$wpwname = $wuid if !defined $wpwname;
|
||||
$wgrpname = $wgid if !defined $wgrpname;
|
||||
|
||||
print "Updating uid/gid on $path: old=$wpwname/$wgrpname, new=$pwname/$grpname\n";
|
||||
}
|
||||
chown $uid, $gid, $fullpath;
|
||||
}
|
||||
}
|
||||
else {
|
||||
warn "Invalid input format in $gitmeta:\n\t$_\n";
|
||||
}
|
||||
}
|
||||
close IN;
|
||||
}
|
||||
elsif ($read_mode) {
|
||||
# Handle merge conflicts in the .gitperms file
|
||||
if (-e "$gitdir/MERGE_MSG") {
|
||||
if (`grep ====== $gitmeta`) {
|
||||
# Conflict not resolved -- abort the commit
|
||||
print "PERMISSIONS/OWNERSHIP CONFLICT\n";
|
||||
print " Resolve the conflict in the $gitmeta file and then run\n";
|
||||
print " `.git/hooks/setgitperms.perl --write` to reconcile.\n";
|
||||
exit 1;
|
||||
}
|
||||
elsif (`grep $gitmeta $gitdir/MERGE_MSG`) {
|
||||
# A conflict in .gitmeta has been manually resolved. Verify that
|
||||
# the working dir perms matches the current .gitmeta perms for
|
||||
# each file/dir that conflicted.
|
||||
# This is here because a `setgitperms.perl --write` was not
|
||||
# performed due to a merge conflict, so permissions/ownership
|
||||
# may not be consistent with the manually merged .gitmeta file.
|
||||
my @conflict_diff = `git show \$(cat $gitdir/MERGE_HEAD)`;
|
||||
my @conflict_files;
|
||||
my $metadiff = 0;
|
||||
|
||||
# Build a list of files that conflicted from the .gitmeta diff
|
||||
foreach my $line (@conflict_diff) {
|
||||
if ($line =~ m|^diff --git a/$gitmeta b/$gitmeta|) {
|
||||
$metadiff = 1;
|
||||
}
|
||||
elsif ($line =~ /^diff --git/) {
|
||||
$metadiff = 0;
|
||||
}
|
||||
elsif ($metadiff && $line =~ /^\+(.*) mode=/) {
|
||||
push @conflict_files, $1;
|
||||
}
|
||||
}
|
||||
|
||||
# Verify that each conflict file now has permissions consistent
|
||||
# with the .gitmeta file
|
||||
foreach my $file (@conflict_files) {
|
||||
my $absfile = $topdir . $file;
|
||||
my $gm_entry = `grep "^$file mode=" $gitmeta`;
|
||||
if ($gm_entry =~ /mode=(\d+) uid=(\d+) gid=(\d+)/) {
|
||||
my ($gm_mode, $gm_uid, $gm_gid) = ($1, $2, $3);
|
||||
my (undef,undef,$mode,undef,$uid,$gid) = lstat("$absfile");
|
||||
$mode = sprintf("%04o", $mode & 07777);
|
||||
if (($gm_mode ne $mode) || ($gm_uid != $uid)
|
||||
|| ($gm_gid != $gid)) {
|
||||
print "PERMISSIONS/OWNERSHIP CONFLICT\n";
|
||||
print " Mismatch found for file: $file\n";
|
||||
print " Run `.git/hooks/setgitperms.perl --write` to reconcile.\n";
|
||||
exit 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
print "Warning! Permissions/ownership no longer being tracked for file: $file\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# No merge conflicts -- write out perms/ownership data to .gitmeta file
|
||||
unless ($stdout) {
|
||||
open (OUT, ">$gitmeta.tmp") or die "Could not open $gitmeta.tmp for writing: $!\n";
|
||||
}
|
||||
|
||||
my @files = `git ls-files`;
|
||||
my %dirs;
|
||||
|
||||
foreach my $path (@files) {
|
||||
chomp $path;
|
||||
# We have to manually add stats for parent directories
|
||||
my $parent = dirname($path);
|
||||
while (!exists $dirs{$parent}) {
|
||||
$dirs{$parent} = 1;
|
||||
next if $parent eq '.';
|
||||
printstats($parent);
|
||||
$parent = dirname($parent);
|
||||
}
|
||||
# Now the git-tracked file
|
||||
printstats($path);
|
||||
}
|
||||
|
||||
# diff the temporary metadata file to see if anything has changed
|
||||
# If no metadata has changed, don't overwrite the real file
|
||||
# This is just so `git commit -a` doesn't try to commit a bogus update
|
||||
unless ($stdout) {
|
||||
if (! -e $gitmeta) {
|
||||
rename "$gitmeta.tmp", $gitmeta;
|
||||
}
|
||||
else {
|
||||
my $diff = `diff -U 0 $gitmeta $gitmeta.tmp`;
|
||||
if ($diff ne '') {
|
||||
rename "$gitmeta.tmp", $gitmeta;
|
||||
}
|
||||
else {
|
||||
unlink "$gitmeta.tmp";
|
||||
}
|
||||
if ($showdiff) {
|
||||
print $diff;
|
||||
}
|
||||
}
|
||||
close OUT;
|
||||
}
|
||||
# Make sure the .gitmeta file is tracked
|
||||
system("git add $gitmeta");
|
||||
}
|
||||
|
||||
|
||||
sub printstats {
|
||||
my $path = $_[0];
|
||||
$path =~ s/@/\@/g;
|
||||
my (undef,undef,$mode,undef,$uid,$gid) = lstat($path);
|
||||
$path =~ s/%/\%/g;
|
||||
if ($stdout) {
|
||||
print $path;
|
||||
printf " mode=%04o uid=$uid gid=$gid\n", $mode & 07777;
|
||||
}
|
||||
else {
|
||||
print OUT $path;
|
||||
printf OUT " mode=%04o uid=$uid gid=$gid\n", $mode & 07777;
|
||||
}
|
||||
}
|
||||
421
third_party/git/contrib/hooks/update-paranoid
vendored
Executable file
421
third_party/git/contrib/hooks/update-paranoid
vendored
Executable file
|
|
@ -0,0 +1,421 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use File::Spec;
|
||||
|
||||
$ENV{PATH} = '/opt/git/bin';
|
||||
my $acl_git = '/vcs/acls.git';
|
||||
my $acl_branch = 'refs/heads/master';
|
||||
my $debug = 0;
|
||||
|
||||
=doc
|
||||
Invoked as: update refname old-sha1 new-sha1
|
||||
|
||||
This script is run by git-receive-pack once for each ref that the
|
||||
client is trying to modify. If we exit with a non-zero exit value
|
||||
then the update for that particular ref is denied, but updates for
|
||||
other refs in the same run of receive-pack may still be allowed.
|
||||
|
||||
We are run after the objects have been uploaded, but before the
|
||||
ref is actually modified. We take advantage of that fact when we
|
||||
look for "new" commits and tags (the new objects won't show up in
|
||||
`rev-list --all`).
|
||||
|
||||
This script loads and parses the content of the config file
|
||||
"users/$this_user.acl" from the $acl_branch commit of $acl_git ODB.
|
||||
The acl file is a git-config style file, but uses a slightly more
|
||||
restricted syntax as the Perl parser contained within this script
|
||||
is not nearly as permissive as git-config.
|
||||
|
||||
Example:
|
||||
|
||||
[user]
|
||||
committer = John Doe <john.doe@example.com>
|
||||
committer = John R. Doe <john.doe@example.com>
|
||||
|
||||
[repository "acls"]
|
||||
allow = heads/master
|
||||
allow = CDUR for heads/jd/
|
||||
allow = C for ^tags/v\\d+$
|
||||
|
||||
For all new commit or tag objects the committer (or tagger) line
|
||||
within the object must exactly match one of the user.committer
|
||||
values listed in the acl file ("HEAD:users/$this_user.acl").
|
||||
|
||||
For a branch to be modified an allow line within the matching
|
||||
repository section must be matched for both the refname and the
|
||||
opcode.
|
||||
|
||||
Repository sections are matched on the basename of the repository
|
||||
(after removing the .git suffix).
|
||||
|
||||
The opcode abbrevations are:
|
||||
|
||||
C: create new ref
|
||||
D: delete existing ref
|
||||
U: fast-forward existing ref (no commit loss)
|
||||
R: rewind/rebase existing ref (commit loss)
|
||||
|
||||
if no opcodes are listed before the "for" keyword then "U" (for
|
||||
fast-forward update only) is assumed as this is the most common
|
||||
usage.
|
||||
|
||||
Refnames are matched by always assuming a prefix of "refs/".
|
||||
This hook forbids pushing or deleting anything not under "refs/".
|
||||
|
||||
Refnames that start with ^ are Perl regular expressions, and the ^
|
||||
is kept as part of the regexp. \\ is needed to get just one \, so
|
||||
\\d expands to \d in Perl. The 3rd allow line above is an example.
|
||||
|
||||
Refnames that don't start with ^ but that end with / are prefix
|
||||
matches (2nd allow line above); all other refnames are strict
|
||||
equality matches (1st allow line).
|
||||
|
||||
Anything pushed to "heads/" (ok, really "refs/heads/") must be
|
||||
a commit. Tags are not permitted here.
|
||||
|
||||
Anything pushed to "tags/" (err, really "refs/tags/") must be an
|
||||
annotated tag. Commits, blobs, trees, etc. are not permitted here.
|
||||
Annotated tag signatures aren't checked, nor are they required.
|
||||
|
||||
The special subrepository of 'info/new-commit-check' can
|
||||
be created and used to allow users to push new commits and
|
||||
tags from another local repository to this one, even if they
|
||||
aren't the committer/tagger of those objects. In a nut shell
|
||||
the info/new-commit-check directory is a Git repository whose
|
||||
objects/info/alternates file lists this repository and all other
|
||||
possible sources, and whose refs subdirectory contains symlinks
|
||||
to this repository's refs subdirectory, and to all other possible
|
||||
sources refs subdirectories. Yes, this means that you cannot
|
||||
use packed-refs in those repositories as they won't be resolved
|
||||
correctly.
|
||||
|
||||
=cut
|
||||
|
||||
my $git_dir = $ENV{GIT_DIR};
|
||||
my $new_commit_check = "$git_dir/info/new-commit-check";
|
||||
my $ref = $ARGV[0];
|
||||
my $old = $ARGV[1];
|
||||
my $new = $ARGV[2];
|
||||
my $new_type;
|
||||
my ($this_user) = getpwuid $<; # REAL_USER_ID
|
||||
my $repository_name;
|
||||
my %user_committer;
|
||||
my @allow_rules;
|
||||
my @path_rules;
|
||||
my %diff_cache;
|
||||
|
||||
sub deny ($) {
|
||||
print STDERR "-Deny- $_[0]\n" if $debug;
|
||||
print STDERR "\ndenied: $_[0]\n\n";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
sub grant ($) {
|
||||
print STDERR "-Grant- $_[0]\n" if $debug;
|
||||
exit 0;
|
||||
}
|
||||
|
||||
sub info ($) {
|
||||
print STDERR "-Info- $_[0]\n" if $debug;
|
||||
}
|
||||
|
||||
sub git_value (@) {
|
||||
open(T,'-|','git',@_); local $_ = <T>; chop; close T; $_;
|
||||
}
|
||||
|
||||
sub match_string ($$) {
|
||||
my ($acl_n, $ref) = @_;
|
||||
($acl_n eq $ref)
|
||||
|| ($acl_n =~ m,/$, && substr($ref,0,length $acl_n) eq $acl_n)
|
||||
|| ($acl_n =~ m,^\^, && $ref =~ m:$acl_n:);
|
||||
}
|
||||
|
||||
sub parse_config ($$$$) {
|
||||
my $data = shift;
|
||||
local $ENV{GIT_DIR} = shift;
|
||||
my $br = shift;
|
||||
my $fn = shift;
|
||||
return unless git_value('rev-list','--max-count=1',$br,'--',$fn);
|
||||
info "Loading $br:$fn";
|
||||
open(I,'-|','git','cat-file','blob',"$br:$fn");
|
||||
my $section = '';
|
||||
while (<I>) {
|
||||
chomp;
|
||||
if (/^\s*$/ || /^\s*#/) {
|
||||
} elsif (/^\[([a-z]+)\]$/i) {
|
||||
$section = lc $1;
|
||||
} elsif (/^\[([a-z]+)\s+"(.*)"\]$/i) {
|
||||
$section = join('.',lc $1,$2);
|
||||
} elsif (/^\s*([a-z][a-z0-9]+)\s*=\s*(.*?)\s*$/i) {
|
||||
push @{$data->{join('.',$section,lc $1)}}, $2;
|
||||
} else {
|
||||
deny "bad config file line $. in $br:$fn";
|
||||
}
|
||||
}
|
||||
close I;
|
||||
}
|
||||
|
||||
sub all_new_committers () {
|
||||
local $ENV{GIT_DIR} = $git_dir;
|
||||
$ENV{GIT_DIR} = $new_commit_check if -d $new_commit_check;
|
||||
|
||||
info "Getting committers of new commits.";
|
||||
my %used;
|
||||
open(T,'-|','git','rev-list','--pretty=raw',$new,'--not','--all');
|
||||
while (<T>) {
|
||||
next unless s/^committer //;
|
||||
chop;
|
||||
s/>.*$/>/;
|
||||
info "Found $_." unless $used{$_}++;
|
||||
}
|
||||
close T;
|
||||
info "No new commits." unless %used;
|
||||
keys %used;
|
||||
}
|
||||
|
||||
sub all_new_taggers () {
|
||||
my %exists;
|
||||
open(T,'-|','git','for-each-ref','--format=%(objectname)','refs/tags');
|
||||
while (<T>) {
|
||||
chop;
|
||||
$exists{$_} = 1;
|
||||
}
|
||||
close T;
|
||||
|
||||
info "Getting taggers of new tags.";
|
||||
my %used;
|
||||
my $obj = $new;
|
||||
my $obj_type = $new_type;
|
||||
while ($obj_type eq 'tag') {
|
||||
last if $exists{$obj};
|
||||
$obj_type = '';
|
||||
open(T,'-|','git','cat-file','tag',$obj);
|
||||
while (<T>) {
|
||||
chop;
|
||||
if (/^object ([a-z0-9]{40})$/) {
|
||||
$obj = $1;
|
||||
} elsif (/^type (.+)$/) {
|
||||
$obj_type = $1;
|
||||
} elsif (s/^tagger //) {
|
||||
s/>.*$/>/;
|
||||
info "Found $_." unless $used{$_}++;
|
||||
last;
|
||||
}
|
||||
}
|
||||
close T;
|
||||
}
|
||||
info "No new tags." unless %used;
|
||||
keys %used;
|
||||
}
|
||||
|
||||
sub check_committers (@) {
|
||||
my @bad;
|
||||
foreach (@_) { push @bad, $_ unless $user_committer{$_}; }
|
||||
if (@bad) {
|
||||
print STDERR "\n";
|
||||
print STDERR "You are not $_.\n" foreach (sort @bad);
|
||||
deny "You cannot push changes not committed by you.";
|
||||
}
|
||||
}
|
||||
|
||||
sub load_diff ($) {
|
||||
my $base = shift;
|
||||
my $d = $diff_cache{$base};
|
||||
unless ($d) {
|
||||
local $/ = "\0";
|
||||
my %this_diff;
|
||||
if ($base =~ /^0{40}$/) {
|
||||
# Don't load the diff at all; we are making the
|
||||
# branch and have no base to compare to in this
|
||||
# case. A file level ACL makes no sense in this
|
||||
# context. Having an empty diff will allow the
|
||||
# branch creation.
|
||||
#
|
||||
} else {
|
||||
open(T,'-|','git','diff-tree',
|
||||
'-r','--name-status','-z',
|
||||
$base,$new) or return undef;
|
||||
while (<T>) {
|
||||
my $op = $_;
|
||||
chop $op;
|
||||
|
||||
my $path = <T>;
|
||||
chop $path;
|
||||
|
||||
$this_diff{$path} = $op;
|
||||
}
|
||||
close T or return undef;
|
||||
}
|
||||
$d = \%this_diff;
|
||||
$diff_cache{$base} = $d;
|
||||
}
|
||||
return $d;
|
||||
}
|
||||
|
||||
deny "No GIT_DIR inherited from caller" unless $git_dir;
|
||||
deny "Need a ref name" unless $ref;
|
||||
deny "Refusing funny ref $ref" unless $ref =~ s,^refs/,,;
|
||||
deny "Bad old value $old" unless $old =~ /^[a-z0-9]{40}$/;
|
||||
deny "Bad new value $new" unless $new =~ /^[a-z0-9]{40}$/;
|
||||
deny "Cannot determine who you are." unless $this_user;
|
||||
grant "No change requested." if $old eq $new;
|
||||
|
||||
$repository_name = File::Spec->rel2abs($git_dir);
|
||||
$repository_name =~ m,/([^/]+)(?:\.git|/\.git)$,;
|
||||
$repository_name = $1;
|
||||
info "Updating in '$repository_name'.";
|
||||
|
||||
my $op;
|
||||
if ($old =~ /^0{40}$/) { $op = 'C'; }
|
||||
elsif ($new =~ /^0{40}$/) { $op = 'D'; }
|
||||
else { $op = 'R'; }
|
||||
|
||||
# This is really an update (fast-forward) if the
|
||||
# merge base of $old and $new is $old.
|
||||
#
|
||||
$op = 'U' if ($op eq 'R'
|
||||
&& $ref =~ m,^heads/,
|
||||
&& $old eq git_value('merge-base',$old,$new));
|
||||
|
||||
# Load the user's ACL file. Expand groups (user.memberof) one level.
|
||||
{
|
||||
my %data = ('user.committer' => []);
|
||||
parse_config(\%data,$acl_git,$acl_branch,"external/$repository_name.acl");
|
||||
|
||||
%data = (
|
||||
'user.committer' => $data{'user.committer'},
|
||||
'user.memberof' => [],
|
||||
);
|
||||
parse_config(\%data,$acl_git,$acl_branch,"users/$this_user.acl");
|
||||
|
||||
%user_committer = map {$_ => $_} @{$data{'user.committer'}};
|
||||
my $rule_key = "repository.$repository_name.allow";
|
||||
my $rules = $data{$rule_key} || [];
|
||||
|
||||
foreach my $group (@{$data{'user.memberof'}}) {
|
||||
my %g;
|
||||
parse_config(\%g,$acl_git,$acl_branch,"groups/$group.acl");
|
||||
my $group_rules = $g{$rule_key};
|
||||
push @$rules, @$group_rules if $group_rules;
|
||||
}
|
||||
|
||||
RULE:
|
||||
foreach (@$rules) {
|
||||
while (/\${user\.([a-z][a-zA-Z0-9]+)}/) {
|
||||
my $k = lc $1;
|
||||
my $v = $data{"user.$k"};
|
||||
next RULE unless defined $v;
|
||||
next RULE if @$v != 1;
|
||||
next RULE unless defined $v->[0];
|
||||
s/\${user\.$k}/$v->[0]/g;
|
||||
}
|
||||
|
||||
if (/^([AMD ]+)\s+of\s+([^\s]+)\s+for\s+([^\s]+)\s+diff\s+([^\s]+)$/) {
|
||||
my ($ops, $pth, $ref, $bst) = ($1, $2, $3, $4);
|
||||
$ops =~ s/ //g;
|
||||
$pth =~ s/\\\\/\\/g;
|
||||
$ref =~ s/\\\\/\\/g;
|
||||
push @path_rules, [$ops, $pth, $ref, $bst];
|
||||
} elsif (/^([AMD ]+)\s+of\s+([^\s]+)\s+for\s+([^\s]+)$/) {
|
||||
my ($ops, $pth, $ref) = ($1, $2, $3);
|
||||
$ops =~ s/ //g;
|
||||
$pth =~ s/\\\\/\\/g;
|
||||
$ref =~ s/\\\\/\\/g;
|
||||
push @path_rules, [$ops, $pth, $ref, $old];
|
||||
} elsif (/^([CDRU ]+)\s+for\s+([^\s]+)$/) {
|
||||
my $ops = $1;
|
||||
my $ref = $2;
|
||||
$ops =~ s/ //g;
|
||||
$ref =~ s/\\\\/\\/g;
|
||||
push @allow_rules, [$ops, $ref];
|
||||
} elsif (/^for\s+([^\s]+)$/) {
|
||||
# Mentioned, but nothing granted?
|
||||
} elsif (/^[^\s]+$/) {
|
||||
s/\\\\/\\/g;
|
||||
push @allow_rules, ['U', $_];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($op ne 'D') {
|
||||
$new_type = git_value('cat-file','-t',$new);
|
||||
|
||||
if ($ref =~ m,^heads/,) {
|
||||
deny "$ref must be a commit." unless $new_type eq 'commit';
|
||||
} elsif ($ref =~ m,^tags/,) {
|
||||
deny "$ref must be an annotated tag." unless $new_type eq 'tag';
|
||||
}
|
||||
|
||||
check_committers (all_new_committers);
|
||||
check_committers (all_new_taggers) if $new_type eq 'tag';
|
||||
}
|
||||
|
||||
info "$this_user wants $op for $ref";
|
||||
foreach my $acl_entry (@allow_rules) {
|
||||
my ($acl_ops, $acl_n) = @$acl_entry;
|
||||
next unless $acl_ops =~ /^[CDRU]+$/; # Uhh.... shouldn't happen.
|
||||
next unless $acl_n;
|
||||
next unless $op =~ /^[$acl_ops]$/;
|
||||
next unless match_string $acl_n, $ref;
|
||||
|
||||
# Don't test path rules on branch deletes.
|
||||
#
|
||||
grant "Allowed by: $acl_ops for $acl_n" if $op eq 'D';
|
||||
|
||||
# Aggregate matching path rules; allow if there aren't
|
||||
# any matching this ref.
|
||||
#
|
||||
my %pr;
|
||||
foreach my $p_entry (@path_rules) {
|
||||
my ($p_ops, $p_n, $p_ref, $p_bst) = @$p_entry;
|
||||
next unless $p_ref;
|
||||
push @{$pr{$p_bst}}, $p_entry if match_string $p_ref, $ref;
|
||||
}
|
||||
grant "Allowed by: $acl_ops for $acl_n" unless %pr;
|
||||
|
||||
# Allow only if all changes against a single base are
|
||||
# allowed by file path rules.
|
||||
#
|
||||
my @bad;
|
||||
foreach my $p_bst (keys %pr) {
|
||||
my $diff_ref = load_diff $p_bst;
|
||||
deny "Cannot difference trees." unless ref $diff_ref;
|
||||
|
||||
my %fd = %$diff_ref;
|
||||
foreach my $p_entry (@{$pr{$p_bst}}) {
|
||||
my ($p_ops, $p_n, $p_ref, $p_bst) = @$p_entry;
|
||||
next unless $p_ops =~ /^[AMD]+$/;
|
||||
next unless $p_n;
|
||||
|
||||
foreach my $f_n (keys %fd) {
|
||||
my $f_op = $fd{$f_n};
|
||||
next unless $f_op;
|
||||
next unless $f_op =~ /^[$p_ops]$/;
|
||||
delete $fd{$f_n} if match_string $p_n, $f_n;
|
||||
}
|
||||
last unless %fd;
|
||||
}
|
||||
|
||||
if (%fd) {
|
||||
push @bad, [$p_bst, \%fd];
|
||||
} else {
|
||||
# All changes relative to $p_bst were allowed.
|
||||
#
|
||||
grant "Allowed by: $acl_ops for $acl_n diff $p_bst";
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $bad_ref (@bad) {
|
||||
my ($p_bst, $fd) = @$bad_ref;
|
||||
print STDERR "\n";
|
||||
print STDERR "Not allowed to make the following changes:\n";
|
||||
print STDERR "(base: $p_bst)\n";
|
||||
foreach my $f_n (sort keys %$fd) {
|
||||
print STDERR " $fd->{$f_n} $f_n\n";
|
||||
}
|
||||
}
|
||||
deny "You are not permitted to $op $ref";
|
||||
}
|
||||
close A;
|
||||
deny "You are not permitted to $op $ref";
|
||||
132
third_party/git/contrib/long-running-filter/example.pl
vendored
Executable file
132
third_party/git/contrib/long-running-filter/example.pl
vendored
Executable file
|
|
@ -0,0 +1,132 @@
|
|||
#!/usr/bin/perl
|
||||
#
|
||||
# Example implementation for the Git filter protocol version 2
|
||||
# See Documentation/gitattributes.txt, section "Filter Protocol"
|
||||
#
|
||||
# Please note, this pass-thru filter is a minimal skeleton. No proper
|
||||
# error handling was implemented.
|
||||
#
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my $MAX_PACKET_CONTENT_SIZE = 65516;
|
||||
|
||||
sub packet_bin_read {
|
||||
my $buffer;
|
||||
my $bytes_read = read STDIN, $buffer, 4;
|
||||
if ( $bytes_read == 0 ) {
|
||||
|
||||
# EOF - Git stopped talking to us!
|
||||
exit();
|
||||
}
|
||||
elsif ( $bytes_read != 4 ) {
|
||||
die "invalid packet: '$buffer'";
|
||||
}
|
||||
my $pkt_size = hex($buffer);
|
||||
if ( $pkt_size == 0 ) {
|
||||
return ( 1, "" );
|
||||
}
|
||||
elsif ( $pkt_size > 4 ) {
|
||||
my $content_size = $pkt_size - 4;
|
||||
$bytes_read = read STDIN, $buffer, $content_size;
|
||||
if ( $bytes_read != $content_size ) {
|
||||
die "invalid packet ($content_size bytes expected; $bytes_read bytes read)";
|
||||
}
|
||||
return ( 0, $buffer );
|
||||
}
|
||||
else {
|
||||
die "invalid packet size: $pkt_size";
|
||||
}
|
||||
}
|
||||
|
||||
sub packet_txt_read {
|
||||
my ( $res, $buf ) = packet_bin_read();
|
||||
unless ( $buf =~ s/\n$// ) {
|
||||
die "A non-binary line MUST be terminated by an LF.";
|
||||
}
|
||||
return ( $res, $buf );
|
||||
}
|
||||
|
||||
sub packet_bin_write {
|
||||
my $buf = shift;
|
||||
print STDOUT sprintf( "%04x", length($buf) + 4 );
|
||||
print STDOUT $buf;
|
||||
STDOUT->flush();
|
||||
}
|
||||
|
||||
sub packet_txt_write {
|
||||
packet_bin_write( $_[0] . "\n" );
|
||||
}
|
||||
|
||||
sub packet_flush {
|
||||
print STDOUT sprintf( "%04x", 0 );
|
||||
STDOUT->flush();
|
||||
}
|
||||
|
||||
( packet_txt_read() eq ( 0, "git-filter-client" ) ) || die "bad initialize";
|
||||
( packet_txt_read() eq ( 0, "version=2" ) ) || die "bad version";
|
||||
( packet_bin_read() eq ( 1, "" ) ) || die "bad version end";
|
||||
|
||||
packet_txt_write("git-filter-server");
|
||||
packet_txt_write("version=2");
|
||||
packet_flush();
|
||||
|
||||
( packet_txt_read() eq ( 0, "capability=clean" ) ) || die "bad capability";
|
||||
( packet_txt_read() eq ( 0, "capability=smudge" ) ) || die "bad capability";
|
||||
( packet_bin_read() eq ( 1, "" ) ) || die "bad capability end";
|
||||
|
||||
packet_txt_write("capability=clean");
|
||||
packet_txt_write("capability=smudge");
|
||||
packet_flush();
|
||||
|
||||
while (1) {
|
||||
my ($command) = packet_txt_read() =~ /^command=(.+)$/;
|
||||
my ($pathname) = packet_txt_read() =~ /^pathname=(.+)$/;
|
||||
|
||||
if ( $pathname eq "" ) {
|
||||
die "bad pathname '$pathname'";
|
||||
}
|
||||
|
||||
packet_bin_read();
|
||||
|
||||
my $input = "";
|
||||
{
|
||||
binmode(STDIN);
|
||||
my $buffer;
|
||||
my $done = 0;
|
||||
while ( !$done ) {
|
||||
( $done, $buffer ) = packet_bin_read();
|
||||
$input .= $buffer;
|
||||
}
|
||||
}
|
||||
|
||||
my $output;
|
||||
if ( $command eq "clean" ) {
|
||||
### Perform clean here ###
|
||||
$output = $input;
|
||||
}
|
||||
elsif ( $command eq "smudge" ) {
|
||||
### Perform smudge here ###
|
||||
$output = $input;
|
||||
}
|
||||
else {
|
||||
die "bad command '$command'";
|
||||
}
|
||||
|
||||
packet_txt_write("status=success");
|
||||
packet_flush();
|
||||
while ( length($output) > 0 ) {
|
||||
my $packet = substr( $output, 0, $MAX_PACKET_CONTENT_SIZE );
|
||||
packet_bin_write($packet);
|
||||
if ( length($output) > $MAX_PACKET_CONTENT_SIZE ) {
|
||||
$output = substr( $output, $MAX_PACKET_CONTENT_SIZE );
|
||||
}
|
||||
else {
|
||||
$output = "";
|
||||
}
|
||||
}
|
||||
packet_flush(); # flush content!
|
||||
packet_flush(); # empty list, keep "status=success" unchanged!
|
||||
|
||||
}
|
||||
2
third_party/git/contrib/mw-to-git/.gitignore
vendored
Normal file
2
third_party/git/contrib/mw-to-git/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
git-remote-mediawiki
|
||||
git-mw
|
||||
28
third_party/git/contrib/mw-to-git/.perlcriticrc
vendored
Normal file
28
third_party/git/contrib/mw-to-git/.perlcriticrc
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# These 3 rules demand to add the s, m and x flag to *every* regexp. This is
|
||||
# overkill and would be harmful for readability.
|
||||
[-RegularExpressions::RequireExtendedFormatting]
|
||||
[-RegularExpressions::RequireDotMatchAnything]
|
||||
[-RegularExpressions::RequireLineBoundaryMatching]
|
||||
|
||||
# This rule says that builtin functions should not be called with parentheses
|
||||
# e.g.: (taken from CPAN's documentation)
|
||||
# open($handle, '>', $filename); #not ok
|
||||
# open $handle, '>', $filename; #ok
|
||||
# Applying such a rule would mean modifying a huge number of lines for a
|
||||
# question of style.
|
||||
[-CodeLayout::ProhibitParensWithBuiltins]
|
||||
|
||||
# This rule states that each system call should have its return value checked
|
||||
# The problem is that it includes the print call. Checking every print call's
|
||||
# return value would be harmful to the code readabilty.
|
||||
# This configuration keeps all default function but print.
|
||||
[InputOutput::RequireCheckedSyscalls]
|
||||
functions = open say close
|
||||
|
||||
# This rule demands to add a dependency for the Readonly module. This is not
|
||||
# wished.
|
||||
[-ValuesAndExpressions::ProhibitConstantPragma]
|
||||
|
||||
# This rule is not really useful (rather a question of style) and produces many
|
||||
# warnings among the code.
|
||||
[-ValuesAndExpressions::ProhibitNoisyQuotes]
|
||||
101
third_party/git/contrib/mw-to-git/Git/Mediawiki.pm
vendored
Normal file
101
third_party/git/contrib/mw-to-git/Git/Mediawiki.pm
vendored
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
package Git::Mediawiki;
|
||||
|
||||
use 5.008;
|
||||
use strict;
|
||||
use POSIX;
|
||||
use Git;
|
||||
|
||||
BEGIN {
|
||||
|
||||
our ($VERSION, @ISA, @EXPORT, @EXPORT_OK);
|
||||
|
||||
# Totally unstable API.
|
||||
$VERSION = '0.01';
|
||||
|
||||
require Exporter;
|
||||
|
||||
@ISA = qw(Exporter);
|
||||
|
||||
@EXPORT = ();
|
||||
|
||||
# Methods which can be called as standalone functions as well:
|
||||
@EXPORT_OK = qw(clean_filename smudge_filename connect_maybe
|
||||
EMPTY HTTP_CODE_OK HTTP_CODE_PAGE_NOT_FOUND);
|
||||
}
|
||||
|
||||
# Mediawiki filenames can contain forward slashes. This variable decides by which pattern they should be replaced
|
||||
use constant SLASH_REPLACEMENT => '%2F';
|
||||
|
||||
# Used to test for empty strings
|
||||
use constant EMPTY => q{};
|
||||
|
||||
# HTTP codes
|
||||
use constant HTTP_CODE_OK => 200;
|
||||
use constant HTTP_CODE_PAGE_NOT_FOUND => 404;
|
||||
|
||||
sub clean_filename {
|
||||
my $filename = shift;
|
||||
$filename =~ s{@{[SLASH_REPLACEMENT]}}{/}g;
|
||||
# [, ], |, {, and } are forbidden by MediaWiki, even URL-encoded.
|
||||
# Do a variant of URL-encoding, i.e. looks like URL-encoding,
|
||||
# but with _ added to prevent MediaWiki from thinking this is
|
||||
# an actual special character.
|
||||
$filename =~ s/[\[\]\{\}\|]/sprintf("_%%_%x", ord($&))/ge;
|
||||
# If we use the uri escape before
|
||||
# we should unescape here, before anything
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
sub smudge_filename {
|
||||
my $filename = shift;
|
||||
$filename =~ s{/}{@{[SLASH_REPLACEMENT]}}g;
|
||||
$filename =~ s/ /_/g;
|
||||
# Decode forbidden characters encoded in clean_filename
|
||||
$filename =~ s/_%_([0-9a-fA-F][0-9a-fA-F])/sprintf('%c', hex($1))/ge;
|
||||
return substr($filename, 0, NAME_MAX-length('.mw'));
|
||||
}
|
||||
|
||||
sub connect_maybe {
|
||||
my $wiki = shift;
|
||||
if ($wiki) {
|
||||
return $wiki;
|
||||
}
|
||||
|
||||
my $remote_name = shift;
|
||||
my $remote_url = shift;
|
||||
my ($wiki_login, $wiki_password, $wiki_domain);
|
||||
|
||||
$wiki_login = Git::config("remote.${remote_name}.mwLogin");
|
||||
$wiki_password = Git::config("remote.${remote_name}.mwPassword");
|
||||
$wiki_domain = Git::config("remote.${remote_name}.mwDomain");
|
||||
|
||||
$wiki = MediaWiki::API->new;
|
||||
$wiki->{config}->{api_url} = "${remote_url}/api.php";
|
||||
if ($wiki_login) {
|
||||
my %credential = (
|
||||
'url' => $remote_url,
|
||||
'username' => $wiki_login,
|
||||
'password' => $wiki_password
|
||||
);
|
||||
Git::credential(\%credential);
|
||||
my $request = {lgname => $credential{username},
|
||||
lgpassword => $credential{password},
|
||||
lgdomain => $wiki_domain};
|
||||
if ($wiki->login($request)) {
|
||||
Git::credential(\%credential, 'approve');
|
||||
print {*STDERR} qq(Logged in mediawiki user "$credential{username}".\n);
|
||||
} else {
|
||||
print {*STDERR} qq(Failed to log in mediawiki user "$credential{username}" on ${remote_url}\n);
|
||||
print {*STDERR} ' (error ' .
|
||||
$wiki->{error}->{code} . ': ' .
|
||||
$wiki->{error}->{details} . ")\n";
|
||||
Git::credential(\%credential, 'reject');
|
||||
exit 1;
|
||||
}
|
||||
}
|
||||
|
||||
return $wiki;
|
||||
}
|
||||
|
||||
1; # Famous last words
|
||||
58
third_party/git/contrib/mw-to-git/Makefile
vendored
Normal file
58
third_party/git/contrib/mw-to-git/Makefile
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#
|
||||
# Copyright (C) 2013
|
||||
# Matthieu Moy <Matthieu.Moy@imag.fr>
|
||||
#
|
||||
# To build and test:
|
||||
#
|
||||
# make
|
||||
# bin-wrapper/git mw preview Some_page.mw
|
||||
# bin-wrapper/git clone mediawiki::http://example.com/wiki/
|
||||
#
|
||||
# To install, run Git's toplevel 'make install' then run:
|
||||
#
|
||||
# make install
|
||||
|
||||
GIT_MEDIAWIKI_PM=Git/Mediawiki.pm
|
||||
SCRIPT_PERL=git-remote-mediawiki.perl
|
||||
SCRIPT_PERL+=git-mw.perl
|
||||
GIT_ROOT_DIR=../..
|
||||
HERE=contrib/mw-to-git/
|
||||
|
||||
INSTALL = install
|
||||
|
||||
SCRIPT_PERL_FULL=$(patsubst %,$(HERE)/%,$(SCRIPT_PERL))
|
||||
INSTLIBDIR=$(shell $(MAKE) -C $(GIT_ROOT_DIR)/ \
|
||||
-s --no-print-directory prefix=$(prefix) \
|
||||
perllibdir=$(perllibdir) perllibdir)
|
||||
DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
|
||||
INSTLIBDIR_SQ = $(subst ','\'',$(INSTLIBDIR))
|
||||
|
||||
all: build
|
||||
|
||||
test: all
|
||||
$(MAKE) -C t
|
||||
|
||||
check: perlcritic test
|
||||
|
||||
install_pm:
|
||||
$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(INSTLIBDIR_SQ)/Git'
|
||||
$(INSTALL) -m 644 $(GIT_MEDIAWIKI_PM) \
|
||||
'$(DESTDIR_SQ)$(INSTLIBDIR_SQ)/$(GIT_MEDIAWIKI_PM)'
|
||||
|
||||
build:
|
||||
$(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \
|
||||
build-perl-script
|
||||
|
||||
install: install_pm
|
||||
$(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \
|
||||
install-perl-script
|
||||
|
||||
clean:
|
||||
$(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \
|
||||
clean-perl-script
|
||||
|
||||
perlcritic:
|
||||
perlcritic -5 $(SCRIPT_PERL)
|
||||
-perlcritic -2 $(SCRIPT_PERL)
|
||||
|
||||
.PHONY: all test check install_pm install clean perlcritic
|
||||
14
third_party/git/contrib/mw-to-git/bin-wrapper/git
vendored
Executable file
14
third_party/git/contrib/mw-to-git/bin-wrapper/git
vendored
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/sh
|
||||
|
||||
# git executable wrapper script for Git-Mediawiki to run tests without
|
||||
# installing all the scripts and perl packages.
|
||||
|
||||
GIT_ROOT_DIR=../../..
|
||||
GIT_EXEC_PATH=$(cd "$(dirname "$0")" && cd ${GIT_ROOT_DIR} && pwd)
|
||||
|
||||
GITPERLLIB="$GIT_EXEC_PATH"'/contrib/mw-to-git'"${GITPERLLIB:+:$GITPERLLIB}"
|
||||
PATH="$GIT_EXEC_PATH"'/contrib/mw-to-git:'"$PATH"
|
||||
|
||||
export GITPERLLIB PATH
|
||||
|
||||
exec "${GIT_EXEC_PATH}/bin-wrappers/git" "$@"
|
||||
368
third_party/git/contrib/mw-to-git/git-mw.perl
vendored
Executable file
368
third_party/git/contrib/mw-to-git/git-mw.perl
vendored
Executable file
|
|
@ -0,0 +1,368 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
# Copyright (C) 2013
|
||||
# Benoit Person <benoit.person@ensimag.imag.fr>
|
||||
# Celestin Matte <celestin.matte@ensimag.imag.fr>
|
||||
# License: GPL v2 or later
|
||||
|
||||
# Set of tools for git repo with a mediawiki remote.
|
||||
# Documentation & bugtracker: https://github.com/moy/Git-Mediawiki/
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Getopt::Long;
|
||||
use URI::URL qw(url);
|
||||
use LWP::UserAgent;
|
||||
use HTML::TreeBuilder;
|
||||
|
||||
use Git;
|
||||
use MediaWiki::API;
|
||||
use Git::Mediawiki qw(clean_filename connect_maybe
|
||||
EMPTY HTTP_CODE_PAGE_NOT_FOUND);
|
||||
|
||||
# By default, use UTF-8 to communicate with Git and the user
|
||||
binmode STDERR, ':encoding(UTF-8)';
|
||||
binmode STDOUT, ':encoding(UTF-8)';
|
||||
|
||||
# Global parameters
|
||||
my $verbose = 0;
|
||||
sub v_print {
|
||||
if ($verbose) {
|
||||
return print {*STDERR} @_;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
# Preview parameters
|
||||
my $file_name = EMPTY;
|
||||
my $remote_name = EMPTY;
|
||||
my $preview_file_name = EMPTY;
|
||||
my $autoload = 0;
|
||||
sub file {
|
||||
$file_name = shift;
|
||||
return $file_name;
|
||||
}
|
||||
|
||||
my %commands = (
|
||||
'help' =>
|
||||
[\&help, {}, \&help],
|
||||
'preview' =>
|
||||
[\&preview, {
|
||||
'<>' => \&file,
|
||||
'output|o=s' => \$preview_file_name,
|
||||
'remote|r=s' => \$remote_name,
|
||||
'autoload|a' => \$autoload
|
||||
}, \&preview_help]
|
||||
);
|
||||
|
||||
# Search for sub-command
|
||||
my $cmd = $commands{'help'};
|
||||
for (0..@ARGV-1) {
|
||||
if (defined $commands{$ARGV[$_]}) {
|
||||
$cmd = $commands{$ARGV[$_]};
|
||||
splice @ARGV, $_, 1;
|
||||
last;
|
||||
}
|
||||
};
|
||||
GetOptions( %{$cmd->[1]},
|
||||
'help|h' => \&{$cmd->[2]},
|
||||
'verbose|v' => \$verbose);
|
||||
|
||||
# Launch command
|
||||
&{$cmd->[0]};
|
||||
|
||||
############################# Preview Functions ################################
|
||||
|
||||
sub preview_help {
|
||||
print {*STDOUT} <<'END';
|
||||
USAGE: git mw preview [--remote|-r <remote name>] [--autoload|-a]
|
||||
[--output|-o <output filename>] [--verbose|-v]
|
||||
<blob> | <filename>
|
||||
|
||||
DESCRIPTION:
|
||||
Preview is an utiliy to preview local content of a mediawiki repo as if it was
|
||||
pushed on the remote.
|
||||
|
||||
For that, preview searches for the remote name of the current branch's
|
||||
upstream if --remote is not set. If that remote is not found or if it
|
||||
is not a mediawiki, it lists all mediawiki remotes configured and asks
|
||||
you to replay your command with the --remote option set properly.
|
||||
|
||||
Then, it searches for a file named 'filename'. If it's not found in
|
||||
the current dir, it will assume it's a blob.
|
||||
|
||||
The content retrieved in the file (or in the blob) will then be parsed
|
||||
by the remote mediawiki and combined with a template retrieved from
|
||||
the mediawiki.
|
||||
|
||||
Finally, preview will save the HTML result in a file. and autoload it
|
||||
in your default web browser if the option --autoload is present.
|
||||
|
||||
OPTIONS:
|
||||
-r <remote name>, --remote <remote name>
|
||||
If the remote is a mediawiki, the template and the parse engine
|
||||
used for the preview will be those of that remote.
|
||||
If not, a list of valid remotes will be shown.
|
||||
|
||||
-a, --autoload
|
||||
Try to load the HTML output in a new tab (or new window) of your
|
||||
default web browser.
|
||||
|
||||
-o <output filename>, --output <output filename>
|
||||
Change the HTML output filename. Default filename is based on the
|
||||
input filename with its extension replaced by '.html'.
|
||||
|
||||
-v, --verbose
|
||||
Show more information on what's going on under the hood.
|
||||
END
|
||||
exit;
|
||||
}
|
||||
|
||||
sub preview {
|
||||
my $wiki;
|
||||
my ($remote_url, $wiki_page_name);
|
||||
my ($new_content, $template);
|
||||
my $file_content;
|
||||
|
||||
if ($file_name eq EMPTY) {
|
||||
die "Missing file argument, see `git mw help`\n";
|
||||
}
|
||||
|
||||
v_print("### Selecting remote\n");
|
||||
if ($remote_name eq EMPTY) {
|
||||
$remote_name = find_upstream_remote_name();
|
||||
if ($remote_name) {
|
||||
$remote_url = mediawiki_remote_url_maybe($remote_name);
|
||||
}
|
||||
|
||||
if (! $remote_url) {
|
||||
my @valid_remotes = find_mediawiki_remotes();
|
||||
|
||||
if ($#valid_remotes == 0) {
|
||||
print {*STDERR} "No mediawiki remote in this repo. \n";
|
||||
exit 1;
|
||||
} else {
|
||||
my $remotes_list = join("\n\t", @valid_remotes);
|
||||
print {*STDERR} <<"MESSAGE";
|
||||
There are multiple mediawiki remotes, which of:
|
||||
${remotes_list}
|
||||
do you want ? Use the -r option to specify the remote.
|
||||
MESSAGE
|
||||
}
|
||||
|
||||
exit 1;
|
||||
}
|
||||
} else {
|
||||
if (!is_valid_remote($remote_name)) {
|
||||
die "${remote_name} is not a remote\n";
|
||||
}
|
||||
|
||||
$remote_url = mediawiki_remote_url_maybe($remote_name);
|
||||
if (! $remote_url) {
|
||||
die "${remote_name} is not a mediawiki remote\n";
|
||||
}
|
||||
}
|
||||
v_print("selected remote:\n\tname: ${remote_name}\n\turl: ${remote_url}\n");
|
||||
|
||||
$wiki = connect_maybe($wiki, $remote_name, $remote_url);
|
||||
|
||||
# Read file content
|
||||
if (! -e $file_name) {
|
||||
$file_content = git_cmd_try {
|
||||
Git::command('cat-file', 'blob', $file_name); }
|
||||
"%s failed w/ code %d";
|
||||
|
||||
if ($file_name =~ /(.+):(.+)/) {
|
||||
$file_name = $2;
|
||||
}
|
||||
} else {
|
||||
open my $read_fh, "<", $file_name
|
||||
or die "could not open ${file_name}: $!\n";
|
||||
$file_content = do { local $/ = undef; <$read_fh> };
|
||||
close $read_fh
|
||||
or die "unable to close: $!\n";
|
||||
}
|
||||
|
||||
v_print("### Retrieving template\n");
|
||||
($wiki_page_name = clean_filename($file_name)) =~ s/\.[^.]+$//;
|
||||
$template = get_template($remote_url, $wiki_page_name);
|
||||
|
||||
v_print("### Parsing local content\n");
|
||||
$new_content = $wiki->api({
|
||||
action => 'parse',
|
||||
text => $file_content,
|
||||
title => $wiki_page_name
|
||||
}, {
|
||||
skip_encoding => 1
|
||||
}) or die "No response from remote mediawiki\n";
|
||||
$new_content = $new_content->{'parse'}->{'text'}->{'*'};
|
||||
|
||||
v_print("### Merging contents\n");
|
||||
if ($preview_file_name eq EMPTY) {
|
||||
($preview_file_name = $file_name) =~ s/\.[^.]+$/.html/;
|
||||
}
|
||||
open(my $save_fh, '>:encoding(UTF-8)', $preview_file_name)
|
||||
or die "Could not open: $!\n";
|
||||
print {$save_fh} merge_contents($template, $new_content, $remote_url);
|
||||
close($save_fh)
|
||||
or die "Could not close: $!\n";
|
||||
|
||||
v_print("### Results\n");
|
||||
if ($autoload) {
|
||||
v_print("Launching browser w/ file: ${preview_file_name}");
|
||||
system('git', 'web--browse', $preview_file_name);
|
||||
} else {
|
||||
print {*STDERR} "Preview file saved as: ${preview_file_name}\n";
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
# uses global scope variable: $remote_name
|
||||
sub merge_contents {
|
||||
my $template = shift;
|
||||
my $content = shift;
|
||||
my $remote_url = shift;
|
||||
my ($content_tree, $html_tree, $mw_content_text);
|
||||
my $template_content_id = 'bodyContent';
|
||||
|
||||
$html_tree = HTML::TreeBuilder->new;
|
||||
$html_tree->parse($template);
|
||||
|
||||
$content_tree = HTML::TreeBuilder->new;
|
||||
$content_tree->parse($content);
|
||||
|
||||
$template_content_id = Git::config("remote.${remote_name}.mwIDcontent")
|
||||
|| $template_content_id;
|
||||
v_print("Using '${template_content_id}' as the content ID\n");
|
||||
|
||||
$mw_content_text = $html_tree->look_down('id', $template_content_id);
|
||||
if (!defined $mw_content_text) {
|
||||
print {*STDERR} <<"CONFIG";
|
||||
Could not combine the new content with the template. You might want to
|
||||
configure `mediawiki.IDContent` in your config:
|
||||
git config --add remote.${remote_name}.mwIDcontent <id>
|
||||
and re-run the command afterward.
|
||||
CONFIG
|
||||
exit 1;
|
||||
}
|
||||
$mw_content_text->delete_content();
|
||||
$mw_content_text->push_content($content_tree);
|
||||
|
||||
make_links_absolute($html_tree, $remote_url);
|
||||
|
||||
return $html_tree->as_HTML;
|
||||
}
|
||||
|
||||
sub make_links_absolute {
|
||||
my $html_tree = shift;
|
||||
my $remote_url = shift;
|
||||
for (@{ $html_tree->extract_links() }) {
|
||||
my ($link, $element, $attr) = @{ $_ };
|
||||
my $url = url($link)->canonical;
|
||||
if ($url !~ /#/) {
|
||||
$element->attr($attr, URI->new_abs($url, $remote_url));
|
||||
}
|
||||
}
|
||||
return $html_tree;
|
||||
}
|
||||
|
||||
sub is_valid_remote {
|
||||
my $remote = shift;
|
||||
my @remotes = git_cmd_try {
|
||||
Git::command('remote') }
|
||||
"%s failed w/ code %d";
|
||||
my $found_remote = 0;
|
||||
foreach my $remote (@remotes) {
|
||||
if ($remote eq $remote) {
|
||||
$found_remote = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
return $found_remote;
|
||||
}
|
||||
|
||||
sub find_mediawiki_remotes {
|
||||
my @remotes = git_cmd_try {
|
||||
Git::command('remote'); }
|
||||
"%s failed w/ code %d";
|
||||
my $remote_url;
|
||||
my @valid_remotes = ();
|
||||
foreach my $remote (@remotes) {
|
||||
$remote_url = mediawiki_remote_url_maybe($remote);
|
||||
if ($remote_url) {
|
||||
push(@valid_remotes, $remote);
|
||||
}
|
||||
}
|
||||
return @valid_remotes;
|
||||
}
|
||||
|
||||
sub find_upstream_remote_name {
|
||||
my $current_branch = git_cmd_try {
|
||||
Git::command_oneline('symbolic-ref', '--short', 'HEAD') }
|
||||
"%s failed w/ code %d";
|
||||
return Git::config("branch.${current_branch}.remote");
|
||||
}
|
||||
|
||||
sub mediawiki_remote_url_maybe {
|
||||
my $remote = shift;
|
||||
|
||||
# Find remote url
|
||||
my $remote_url = Git::config("remote.${remote}.url");
|
||||
if ($remote_url =~ s/mediawiki::(.*)/$1/) {
|
||||
return url($remote_url)->canonical;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub get_template {
|
||||
my $url = shift;
|
||||
my $page_name = shift;
|
||||
my ($req, $res, $code, $url_after);
|
||||
|
||||
$req = LWP::UserAgent->new;
|
||||
if ($verbose) {
|
||||
$req->show_progress(1);
|
||||
}
|
||||
|
||||
$res = $req->get("${url}/index.php?title=${page_name}");
|
||||
if (!$res->is_success) {
|
||||
$code = $res->code;
|
||||
$url_after = $res->request()->uri(); # resolve all redirections
|
||||
if ($code == HTTP_CODE_PAGE_NOT_FOUND) {
|
||||
if ($verbose) {
|
||||
print {*STDERR} <<"WARNING";
|
||||
Warning: Failed to retrieve '$page_name'. Create it on the mediawiki if you want
|
||||
all the links to work properly.
|
||||
Trying to use the mediawiki homepage as a fallback template ...
|
||||
WARNING
|
||||
}
|
||||
|
||||
# LWP automatically redirects GET request
|
||||
$res = $req->get("${url}/index.php");
|
||||
if (!$res->is_success) {
|
||||
$url_after = $res->request()->uri(); # resolve all redirections
|
||||
die "Failed to get homepage @ ${url_after} w/ code ${code}\n";
|
||||
}
|
||||
} else {
|
||||
die "Failed to get '${page_name}' @ ${url_after} w/ code ${code}\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $res->decoded_content;
|
||||
}
|
||||
|
||||
############################## Help Functions ##################################
|
||||
|
||||
sub help {
|
||||
print {*STDOUT} <<'END';
|
||||
usage: git mw <command> <args>
|
||||
|
||||
git mw commands are:
|
||||
help Display help information about git mw
|
||||
preview Parse and render local file into HTML
|
||||
END
|
||||
exit;
|
||||
}
|
||||
1374
third_party/git/contrib/mw-to-git/git-remote-mediawiki.perl
vendored
Executable file
1374
third_party/git/contrib/mw-to-git/git-remote-mediawiki.perl
vendored
Executable file
File diff suppressed because it is too large
Load diff
7
third_party/git/contrib/mw-to-git/git-remote-mediawiki.txt
vendored
Normal file
7
third_party/git/contrib/mw-to-git/git-remote-mediawiki.txt
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
Git-Mediawiki is a project which aims the creation of a gate
|
||||
between git and mediawiki, allowing git users to push and pull
|
||||
objects from mediawiki just as one would do with a classic git
|
||||
repository thanks to remote-helpers.
|
||||
|
||||
For more information, visit the wiki at
|
||||
https://github.com/moy/Git-Mediawiki/wiki
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue