diff options
Diffstat (limited to 'contrib')
37 files changed, 1570 insertions, 452 deletions
diff --git a/contrib/buildsystems/Generators.pm b/contrib/buildsystems/Generators.pm index 408ef714b8..aa4cbaa2ad 100644 --- a/contrib/buildsystems/Generators.pm +++ b/contrib/buildsystems/Generators.pm @@ -17,7 +17,7 @@ BEGIN { $me = dirname($me); if (opendir(D,"$me/Generators")) { foreach my $gen (readdir(D)) { - next if ($gen =~ /^\.\.?$/); + next unless ($gen =~ /\.pm$/); require "${me}/Generators/$gen"; $gen =~ s,\.pm,,; push(@AVAILABLE, $gen); diff --git a/contrib/buildsystems/Generators/Vcproj.pm b/contrib/buildsystems/Generators/Vcproj.pm index cfa74adcc2..737647e76a 100644 --- a/contrib/buildsystems/Generators/Vcproj.pm +++ b/contrib/buildsystems/Generators/Vcproj.pm @@ -3,6 +3,7 @@ require Exporter; use strict; use vars qw($VERSION); +use Digest::SHA qw(sha256_hex); our $VERSION = '1.00'; our(@ISA, @EXPORT, @EXPORT_OK, @AVAILABLE); @@ -12,59 +13,12 @@ BEGIN { push @EXPORT_OK, qw(generate); } -my $guid_index = 0; -my @GUIDS = ( - "{E07B9989-2BF7-4F21-8918-BE22BA467AC3}", - "{278FFB51-0296-4A44-A81A-22B87B7C3592}", - "{7346A2C4-F0FD-444F-9EBE-1AF23B2B5650}", - "{67F421AC-EB34-4D49-820B-3196807B423F}", - "{385DCFE1-CC8C-4211-A451-80FCFC31CA51}", - "{97CC46C5-D2CC-4D26-B634-E75792B79916}", - "{C7CE21FE-6EF8-4012-A5C7-A22BCEDFBA11}", - "{51575134-3FDF-42D1-BABD-3FB12669C6C9}", - "{0AE195E4-9823-4B87-8E6F-20C5614AF2FF}", - "{4B918255-67CA-43BB-A46C-26704B666E6B}", - "{18CCFEEF-C8EE-4CC1-A265-26F95C9F4649}", - "{5D5D90FA-01B7-4973-AFE5-CA88C53AC197}", - "{1F054320-036D-49E1-B384-FB5DF0BC8AC0}", - "{7CED65EE-F2D9-4171-825B-C7D561FE5786}", - "{8D341679-0F07-4664-9A56-3BA0DE88B9BC}", - "{C189FEDC-2957-4BD7-9FA4-7622241EA145}", - "{66844203-1B9F-4C53-9274-164FFF95B847}", - "{E4FEA145-DECC-440D-AEEA-598CF381FD43}", - "{73300A8E-C8AC-41B0-B555-4F596B681BA7}", - "{873FDEB1-D01D-40BF-A1BF-8BBC58EC0F51}", - "{7922C8BE-76C5-4AC6-8BF7-885C0F93B782}", - "{E245D370-308B-4A49-BFC1-1E527827975F}", - "{F6FA957B-66FC-4ED7-B260-E59BBE4FE813}", - "{E6055070-0198-431A-BC49-8DB6CEE770AE}", - "{54159234-C3EB-43DA-906B-CE5DA5C74654}", - "{594CFC35-0B60-46F6-B8EF-9983ACC1187D}", - "{D93FCAB7-1F01-48D2-B832-F761B83231A5}", - "{DBA5E6AC-E7BE-42D3-8703-4E787141526E}", - "{6171953F-DD26-44C7-A3BE-CC45F86FC11F}", - "{9E19DDBE-F5E4-4A26-A2FE-0616E04879B8}", - "{AE81A615-99E3-4885-9CE0-D9CAA193E867}", - "{FBF4067E-1855-4F6C-8BCD-4D62E801A04D}", - "{17007948-6593-4AEB-8106-F7884B4F2C19}", - "{199D4C8D-8639-4DA6-82EF-08668C35DEE0}", - "{E085E50E-C140-4CF3-BE4B-094B14F0DDD6}", - "{00785268-A9CC-4E40-AC29-BAC0019159CE}", - "{4C06F56A-DCDB-46A6-B67C-02339935CF12}", - "{3A62D3FD-519E-4EC9-8171-D2C1BFEA022F}", - "{3A62D3FD-519E-4EC9-8171-D2C1BFEA022F}", - "{9392EB58-D7BA-410B-B1F0-B2FAA6BC89A7}", - "{2ACAB2D5-E0CE-4027-BCA0-D78B2D7A6C66}", - "{86E216C3-43CE-481A-BCB2-BE5E62850635}", - "{FB631291-7923-4B91-9A57-7B18FDBB7A42}", - "{0A176EC9-E934-45B8-B87F-16C7F4C80039}", - "{DF55CA80-46E8-4C53-B65B-4990A23DD444}", - "{3A0F9895-55D2-4710-BE5E-AD7498B5BF44}", - "{294BDC5A-F448-48B6-8110-DD0A81820F8C}", - "{4B9F66E9-FAC9-47AB-B1EF-C16756FBFD06}", - "{72EA49C6-2806-48BD-B81B-D4905102E19C}", - "{5728EB7E-8929-486C-8CD5-3238D060E768}" -); +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) = @_; @@ -92,9 +46,8 @@ sub createLibProject { $target =~ s/\//_/g; $target =~ s/\.a//; - my $uuid = $GUIDS[$guid_index]; + my $uuid = generate_guid($libname); $$build_structure{"LIBS_${target}_GUID"} = $uuid; - $guid_index += 1; my @srcs = sort(map("$rel_dir\\$_", @{$$build_structure{"LIBS_${libname}_SOURCES"}})); my @sources; @@ -106,6 +59,8 @@ sub createLibProject { 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/; @@ -127,6 +82,8 @@ sub createLibProject { $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"; @@ -163,9 +120,6 @@ sub createLibProject { Name="VCXMLDataGeneratorTool" /> <Tool - Name="VCWebServiceProxyGeneratorTool" - /> - <Tool Name="VCMIDLTool" /> <Tool @@ -229,9 +183,6 @@ sub createLibProject { Name="VCXMLDataGeneratorTool" /> <Tool - Name="VCWebServiceProxyGeneratorTool" - /> - <Tool Name="VCMIDLTool" /> <Tool @@ -311,9 +262,8 @@ sub createAppProject { $target =~ s/\//_/g; $target =~ s/\.exe//; - my $uuid = $GUIDS[$guid_index]; + my $uuid = generate_guid($appname); $$build_structure{"APPS_${target}_GUID"} = $uuid; - $guid_index += 1; my @srcs = sort(map("$rel_dir\\$_", @{$$build_structure{"APPS_${appname}_SOURCES"}})); my @sources; @@ -325,6 +275,8 @@ sub createAppProject { 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/; @@ -351,6 +303,8 @@ sub createAppProject { $defines =~ s/-D//g; $defines =~ s/\"/\\"/g; + $defines =~ s/</</g; + $defines =~ s/>/>/g; $defines =~ s/\'//g; $defines =~ s/\\\\/\\/g; $includes =~ s/-I//g; @@ -388,9 +342,6 @@ sub createAppProject { Name="VCXMLDataGeneratorTool" /> <Tool - Name="VCWebServiceProxyGeneratorTool" - /> - <Tool Name="VCMIDLTool" /> <Tool @@ -459,9 +410,6 @@ sub createAppProject { Name="VCXMLDataGeneratorTool" /> <Tool - Name="VCWebServiceProxyGeneratorTool" - /> - <Tool Name="VCMIDLTool" /> <Tool @@ -561,20 +509,18 @@ sub createGlueProject { foreach (@apps) { $_ =~ s/\//_/g; $_ =~ s/\.exe//; - push(@tmp, $_); + 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"; - 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"; - } + my $uuid_libgit = $build_structure{"LIBS_libgit_GUID"}; my $uuid_xdiff_lib = $build_structure{"LIBS_xdiff_lib_GUID"}; foreach (@apps) { @@ -588,6 +534,13 @@ sub createGlueProject { 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 @@ -599,17 +552,17 @@ EOM print F << "EOM"; GlobalSection(ProjectConfigurationPlatforms) = postSolution EOM - foreach (@libs) { - my $libname = $_; - my $uuid = $build_structure{"LIBS_${libname}_GUID"}; + 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 (@apps) { - my $appname = $_; - my $uuid = $build_structure{"APPS_${appname}_GUID"}; + 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"; diff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm new file mode 100644 index 0000000000..5c666f9ac0 --- /dev/null +++ b/contrib/buildsystems/Generators/Vcxproj.pm @@ -0,0 +1,392 @@ +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/g; + $libs_debug =~ s/libcurl\.lib/libcurl-d\.lib/g; + } + + $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"; + } + if ($target eq 'git-remote-http') { + print F " <Import Project=\"LinkOrCopyRemoteHttp.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; diff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl index 23da787dc5..070978506a 100755 --- a/contrib/buildsystems/engine.pl +++ b/contrib/buildsystems/engine.pl @@ -12,6 +12,7 @@ use File::Basename; use File::Spec; use Cwd; use Generators; +use Text::ParseWords; my (%build_structure, %compile_options, @makedry); my $out_dir = getcwd(); @@ -31,6 +32,7 @@ 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 @@ -38,6 +40,7 @@ EOM } # Parse command-line options +my $make_out; while (@ARGV) { my $arg = shift @ARGV; if ("$arg" eq "-h" || "$arg" eq "--help" || "$arg" eq "-?") { @@ -45,6 +48,8 @@ while (@ARGV) { 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") { @@ -52,6 +57,8 @@ while (@ARGV) { open(F, "<$infile") || die "Couldn't open file $infile"; @makedry = <F>; close(F); + } else { + die "Unknown option: " . $arg; } } @@ -72,7 +79,19 @@ Running GNU Make to figure out build structure... EOM # Pipe a make --dry-run into a variable, if not already loaded from file -@makedry = `cd $git_dir && make -n MSVC=1 V=1 2>/dev/null` if !@makedry; +# 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(); @@ -140,6 +159,12 @@ sub parseMakeOutput 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); @@ -231,7 +256,7 @@ sub removeDuplicates sub handleCompileLine { my ($line, $lineno) = @_; - my @parts = split(' ', $line); + my @parts = shellwords($line); my $sourcefile; shift(@parts); # ignore cmd while (my $part = shift @parts) { @@ -265,7 +290,7 @@ sub handleLibLine my (@objfiles, @lflags, $libout, $part); # kill cmd and rm 'prefix' $line =~ s/^rm -f .* && .* rcs //; - my @parts = split(' ', $line); + my @parts = shellwords($line); while ($part = shift @parts) { if ($part =~ /^-/) { push(@lflags, $part); @@ -282,7 +307,7 @@ sub handleLibLine # exit(1); foreach (@objfiles) { my $sourcefile = $_; - $sourcefile =~ s/\.o/.c/; + $sourcefile =~ s/\.o$/.c/; push(@sources, $sourcefile); push(@cflags, @{$compile_options{"${sourcefile}_CFLAGS"}}); push(@defines, @{$compile_options{"${sourcefile}_DEFINES"}}); @@ -306,7 +331,7 @@ sub handleLinkLine { my ($line, $lineno) = @_; my (@objfiles, @lflags, @libs, $appout, $part); - my @parts = split(' ', $line); + my @parts = shellwords($line); shift(@parts); # ignore cmd while ($part = shift @parts) { if ($part =~ /^-IGNORE/) { @@ -317,26 +342,36 @@ sub handleLinkLine $appout = shift @parts; } elsif ("$part" eq "-lz") { push(@libs, "zlib.lib"); - } elsif ("$part" eq "-lcrypto") { - push(@libs, "libeay32.lib"); + } elsif ("$part" eq "-lcrypto") { + push(@libs, "libcrypto.lib"); } elsif ("$part" eq "-lssl") { - push(@libs, "ssleay32.lib"); - } elsif ($part =~ /^-/) { + push(@libs, "libssl.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 =~ /\.(o|obj)$/) { + } 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 lib option @ line $lineno: $part"; + 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/; + $sourcefile =~ s/\.o$/.c/; push(@sources, $sourcefile); push(@cflags, @{$compile_options{"${sourcefile}_CFLAGS"}}); push(@defines, @{$compile_options{"${sourcefile}_DEFINES"}}); diff --git a/contrib/coccinelle/array.cocci b/contrib/coccinelle/array.cocci index 01586821dc..46b8d2ee11 100644 --- a/contrib/coccinelle/array.cocci +++ b/contrib/coccinelle/array.cocci @@ -1,29 +1,60 @@ @@ -type T; -T *dst; -T *src; -expression n; +expression dst, src, n, E; @@ -- memcpy(dst, src, (n) * sizeof(*dst)); -+ COPY_ARRAY(dst, src, n); + memcpy(dst, src, n * sizeof( +- E[...] ++ *(E) + )) @@ type T; -T *dst; -T *src; -expression n; +T *ptr; +T[] arr; +expression E, n; @@ -- memcpy(dst, src, (n) * sizeof(*src)); -+ COPY_ARRAY(dst, src, 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; -T *src; +T *dst_ptr; +T *src_ptr; +T[] dst_arr; +T[] src_arr; expression n; @@ -- memcpy(dst, src, (n) * sizeof(T)); -+ COPY_ARRAY(dst, src, 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; diff --git a/contrib/coccinelle/commit.cocci b/contrib/coccinelle/commit.cocci index c49aa558f0..778e4704f6 100644 --- a/contrib/coccinelle/commit.cocci +++ b/contrib/coccinelle/commit.cocci @@ -10,19 +10,25 @@ expression c; - c->maybe_tree->object.oid.hash + get_commit_tree_oid(c)->hash -// These excluded functions must access c->maybe_tree direcly. @@ -identifier f !~ "^(get_commit_tree|get_commit_tree_in_graph_one|load_tree_for_commit)$"; +identifier f !~ "^set_commit_tree$"; expression c; +expression s; @@ f(...) {<... -- c->maybe_tree -+ get_commit_tree(c) +- c->maybe_tree = s ++ set_commit_tree(c, s) ...>} +// These excluded functions must access c->maybe_tree directly. +// 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; -expression s; @@ -- get_commit_tree(c) = s -+ c->maybe_tree = s + f(...) {<... +- c->maybe_tree ++ repo_get_commit_tree(specify_the_right_repo_here, c) + ...>} diff --git a/contrib/coccinelle/flex_alloc.cocci b/contrib/coccinelle/flex_alloc.cocci new file mode 100644 index 0000000000..e9f7f6d861 --- /dev/null +++ b/contrib/coccinelle/flex_alloc.cocci @@ -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); diff --git a/contrib/coccinelle/hashmap.cocci b/contrib/coccinelle/hashmap.cocci new file mode 100644 index 0000000000..d69e120ccf --- /dev/null +++ b/contrib/coccinelle/hashmap.cocci @@ -0,0 +1,16 @@ +@ hashmap_entry_init_usage @ +expression E; +struct hashmap_entry HME; +@@ +- HME.hash = E; ++ hashmap_entry_init(&HME, E); + +@@ +identifier f !~ "^hashmap_entry_init$"; +expression E; +struct hashmap_entry *HMEP; +@@ + f(...) {<... +- HMEP->hash = E; ++ hashmap_entry_init(HMEP, E); + ...>} diff --git a/contrib/coccinelle/object_id.cocci b/contrib/coccinelle/object_id.cocci index 6a7cf3e02d..ddf4f22bd7 100644 --- a/contrib/coccinelle/object_id.cocci +++ b/contrib/coccinelle/object_id.cocci @@ -13,38 +13,6 @@ struct object_id *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) @@ -87,36 +55,6 @@ struct object_id OID; + oidcmp(&OID, OIDPTR) @@ -struct object_id OID1, OID2; -@@ -- hashcpy(OID1.hash, OID2.hash) -+ oidcpy(&OID1, &OID2) - -@@ -identifier f != oidcpy; -struct object_id *OIDPTR1; -struct object_id *OIDPTR2; -@@ - f(...) {<... -- hashcpy(OIDPTR1->hash, OIDPTR2->hash) -+ oidcpy(OIDPTR1, OIDPTR2) - ...>} - -@@ -struct object_id *OIDPTR; -struct object_id OID; -@@ -- hashcpy(OIDPTR->hash, OID.hash) -+ oidcpy(OIDPTR, &OID) - -@@ -struct object_id *OIDPTR; -struct object_id OID; -@@ -- hashcpy(OID.hash, OIDPTR->hash) -+ oidcpy(&OID, OIDPTR) - -@@ struct object_id *OIDPTR1; struct object_id *OIDPTR2; @@ diff --git a/contrib/coccinelle/strbuf.cocci b/contrib/coccinelle/strbuf.cocci index e34eada1ad..d9ada69b43 100644 --- a/contrib/coccinelle/strbuf.cocci +++ b/contrib/coccinelle/strbuf.cocci @@ -13,6 +13,36 @@ constant 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"; @@ diff --git a/contrib/coccinelle/the_repository.pending.cocci b/contrib/coccinelle/the_repository.pending.cocci new file mode 100644 index 0000000000..2ee702ecf7 --- /dev/null +++ b/contrib/coccinelle/the_repository.pending.cocci @@ -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); diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 9e8ec95c3c..6f6430e4e6 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -37,7 +37,8 @@ # GIT_COMPLETION_CHECKOUT_NO_GUESS # # When set to "1", do not include "DWIM" suggestions in git-checkout -# completion (e.g., completing "foo" when "origin/foo" exists). +# and git-switch completion (e.g., completing "foo" when "origin/foo" +# exists). case "$COMP_WORDBREAKS" in *:*) : great ;; @@ -339,7 +340,7 @@ __gitcomp () c="$c${4-}" if [[ $c == "$cur_"* ]]; then case $c in - --*=*|*.) ;; + --*=|*.) ;; *) c="$c " ;; esac COMPREPLY[i++]="${2-}$c" @@ -359,7 +360,7 @@ __gitcomp () c="$c${4-}" if [[ $c == "$cur_"* ]]; then case $c in - --*=*|*.) ;; + *=|*.) ;; *) c="$c " ;; esac COMPREPLY[i++]="${2-}$c" @@ -400,7 +401,8 @@ __gitcomp_builtin () if [ -z "$options" ]; then # leading and trailing spaces are significant to make # option removal work correctly. - options=" $incl $(__git ${cmd/_/ } --git-completion-helper) " + options=" $incl $(__git ${cmd/_/ } --git-completion-helper) " || return + for i in $excl; do options="${options/ $i / }" done @@ -438,7 +440,7 @@ __gitcomp_nl () # Callers must take care of providing only paths that match the current path # to be completed and adding any prefix path components, if necessary. # 1: List of newline-separated matching paths, complete with all prefix -# path componens. +# path components. __gitcomp_file_direct () { local IFS=$'\n' @@ -522,7 +524,7 @@ __git_index_files () # Even when a directory name itself does not contain # any special characters, it will still be quoted if # any of its (stripped) trailing path components do. - # Because of this we may have seen the same direcory + # Because of this we may have seen the same directory # both quoted and unquoted. if (p in paths) # We have seen the same directory unquoted, @@ -548,7 +550,7 @@ __git_index_files () esc_idx, 1) } else if (esc == "n") { # Uh-oh, a newline character. - # We cant reliably put a pathname + # We cannot reliably put a pathname # containing a newline into COMPREPLY, # and the newline would create a mess. # Skip this path. @@ -563,7 +565,7 @@ __git_index_files () } } # Drop closing double quote, if there is one. - # (There isnt any if this is a directory, as it was + # (There is not any if this is a directory, as it was # already stripped with the trailing path components.) if (substr(p, length(p), 1) == "\"") out = out substr(p, 1, length(p) - 1) @@ -853,9 +855,14 @@ __git_compute_merge_strategies () __git_merge_strategies=$(__git_list_merge_strategies) } +__git_merge_strategy_options="ours theirs subtree subtree= patience + histogram diff-algorithm= ignore-space-change ignore-all-space + ignore-space-at-eol renormalize no-renormalize no-renames + find-renames find-renames= rename-threshold=" + __git_complete_revlist_file () { - local pfx ls ref cur_="$cur" + local dequoted_word pfx ls ref cur_="$cur" case "$cur_" in *..?*:*) return @@ -863,14 +870,18 @@ __git_complete_revlist_file () ?*:*) ref="${cur_%%:*}" cur_="${cur_#*:}" - case "$cur_" in + + __git_dequote "$cur_" + + case "$dequoted_word" in ?*/*) - pfx="${cur_%/*}" - cur_="${cur_##*/}" + pfx="${dequoted_word%/*}" + cur_="${dequoted_word##*/}" ls="$ref:$pfx" pfx="$pfx/" ;; *) + cur_="$dequoted_word" ls="$ref" ;; esac @@ -880,21 +891,10 @@ __git_complete_revlist_file () *) pfx="$ref:$pfx" ;; esac - __gitcomp_nl "$(__git ls-tree "$ls" \ - | sed '/^100... blob /{ - s,^.* ,, - s,$, , - } - /^120000 blob /{ - s,^.* ,, - s,$, , - } - /^040000 tree /{ - s,^.* ,, - s,$,/, - } - s/^.* //')" \ - "$pfx" "$cur_" "" + __gitcomp_file "$(__git ls-tree "$ls" \ + | sed 's/^.* // + s/$//')" \ + "$pfx" "$cur_" ;; *...*) pfx="${cur_%...*}..." @@ -1003,12 +1003,21 @@ __git_complete_strategy () -s|--strategy) __gitcomp "$__git_merge_strategies" return 0 + ;; + -X) + __gitcomp "$__git_merge_strategy_options" + return 0 + ;; esac case "$cur" in --strategy=*) __gitcomp "$__git_merge_strategies" "" "${cur##--strategy=}" return 0 ;; + --strategy-option=*) + __gitcomp "$__git_merge_strategy_options" "" "${cur##--strategy-option=}" + return 0 + ;; esac return 1 } @@ -1017,7 +1026,7 @@ __git_all_commands= __git_compute_all_commands () { test -n "$__git_all_commands" || - __git_all_commands=$(git --list-cmds=main,others,alias,nohelpers) + __git_all_commands=$(__git --list-cmds=main,others,alias,nohelpers) } # Lists all set config variables starting with the given section prefix, @@ -1060,15 +1069,32 @@ __git_aliased_command () done } -# __git_find_on_cmdline requires 1 argument +# Check whether one of the given words is present on the command line, +# and print the first word found. +# +# Usage: __git_find_on_cmdline [<option>]... "<wordlist>" +# --show-idx: Optionally show the index of the found word in the $words array. __git_find_on_cmdline () { - local word subcommand c=1 + local word c=1 show_idx + + while test $# -gt 1; do + case "$1" in + --show-idx) show_idx=y ;; + *) return 1 ;; + esac + shift + done + local wordlist="$1" + while [ $c -lt $cword ]; do - word="${words[c]}" - for subcommand in $1; do - if [ "$subcommand" = "$word" ]; then - echo "$subcommand" + for word in $wordlist; do + if [ "$word" = "${words[c]}" ]; then + if [ -n "$show_idx" ]; then + echo "$c $word" + else + echo "$word" + fi return fi done @@ -1170,6 +1196,7 @@ __git_count_arguments () } __git_whitespacelist="nowarn warn error error-all fix" +__git_patchformat="mbox stgit stgit-series hg mboxrd" __git_am_inprogress_options="--skip --continue --resolved --abort --quit --show-current-patch" _git_am () @@ -1184,6 +1211,10 @@ _git_am () __gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}" return ;; + --patch-format=*) + __gitcomp "$__git_patchformat" "" "${cur##--patch-format=}" + return + ;; --*) __gitcomp_builtin am "" \ "$__git_am_inprogress_options" @@ -1207,6 +1238,10 @@ _git_apply () _git_add () { case "$cur" in + --chmod=*) + __gitcomp "+x -x" "" "${cur##--chmod=}" + return + ;; --*) __gitcomp_builtin add return @@ -1232,10 +1267,7 @@ _git_archive () return ;; --*) - __gitcomp " - --format= --list --verbose - --prefix= --remote= --exec= --output - " + __gitcomp_builtin archive "--format= --list --verbose --prefix= --worktree-attributes" return ;; esac @@ -1267,6 +1299,8 @@ _git_bisect () esac } +__git_ref_fieldlist="refname objecttype objectsize objectname upstream push HEAD symref" + _git_branch () { local i c=1 only_local_ref="n" has_r="n" @@ -1341,7 +1375,9 @@ _git_checkout () esac } -__git_cherry_pick_inprogress_options="--continue --quit --abort" +__git_sequencer_inprogress_options="--continue --quit --abort --skip" + +__git_cherry_pick_inprogress_options=$__git_sequencer_inprogress_options _git_cherry_pick () { @@ -1350,6 +1386,9 @@ _git_cherry_pick () __gitcomp "$__git_cherry_pick_inprogress_options" return fi + + __git_complete_strategy && return + case "$cur" in --*) __gitcomp_builtin cherry-pick "" \ @@ -1376,7 +1415,18 @@ _git_clean () _git_clone () { + case "$prev" in + -c|--config) + __git_complete_config_variable_name_and_value + return + ;; + esac case "$cur" in + --config=*) + __git_complete_config_variable_name_and_value \ + --cur="${cur##--config=}" + return + ;; --*) __gitcomp_builtin clone return @@ -1453,6 +1503,8 @@ __git_diff_common_options="--stat --numstat --shortstat --summary --dirstat-by-file= --cumulative --diff-algorithm= --submodule --submodule= --ignore-submodules + --indent-heuristic --no-indent-heuristic + --textconv --no-textconv " _git_diff () @@ -1480,7 +1532,8 @@ _git_diff () } __git_mergetools_common="diffuse diffmerge ecmerge emerge kdiff3 meld opendiff - tkdiff vimdiff gvimdiff xxdiff araxis p4merge bc codecompare + tkdiff vimdiff gvimdiff xxdiff araxis p4merge bc + codecompare smerge " _git_difftool () @@ -1513,6 +1566,10 @@ _git_fetch () __gitcomp "$__git_fetch_recurse_submodules" "" "${cur##--recurse-submodules=}" return ;; + --filter=*) + __gitcomp "blob:none blob:limit= sparse:oid=" "" "${cur##--filter=}" + return + ;; --*) __gitcomp_builtin fetch return @@ -1627,9 +1684,9 @@ _git_help () esac if test -n "$GIT_TESTING_ALL_COMMAND_LIST" then - __gitcomp "$GIT_TESTING_ALL_COMMAND_LIST $(git --list-cmds=alias,list-guide) gitk" + __gitcomp "$GIT_TESTING_ALL_COMMAND_LIST $(__git --list-cmds=alias,list-guide) gitk" else - __gitcomp "$(git --list-cmds=main,nohelpers,alias,list-guide) gitk" + __gitcomp "$(__git --list-cmds=main,nohelpers,alias,list-guide) gitk" fi } @@ -1709,8 +1766,8 @@ __git_log_shortlog_options=" --all-match --invert-grep " -__git_log_pretty_formats="oneline short medium full fuller email raw format:" -__git_log_date_formats="relative iso8601 rfc2822 short local default raw" +__git_log_pretty_formats="oneline short medium full fuller reference email raw format: tformat: mboxrd" +__git_log_date_formats="relative iso8601 iso8601-strict rfc2822 short local default raw unix format:" _git_log () { @@ -1756,6 +1813,10 @@ _git_log () __gitcomp "$__git_diff_submodule_formats" "" "${cur##--submodule=}" return ;; + --no-walk=*) + __gitcomp "sorted unsorted" "" "${cur##--no-walk=}" + return + ;; --*) __gitcomp " $__git_log_common_options @@ -1763,16 +1824,19 @@ _git_log () $__git_log_gitk_options --root --topo-order --date-order --reverse --follow --full-diff - --abbrev-commit --abbrev= + --abbrev-commit --no-abbrev-commit --abbrev= --relative-date --date= --pretty= --format= --oneline --show-signature --cherry-mark --cherry-pick --graph - --decorate --decorate= + --decorate --decorate= --no-decorate --walk-reflogs + --no-walk --no-walk= --do-walk --parents --children + --expand-tabs --expand-tabs= --no-expand-tabs + --patch $merge $__git_diff_common_options --pickaxe-all --pickaxe-regex @@ -1976,15 +2040,18 @@ _git_range_diff () __git_complete_revlist } +__git_rebase_inprogress_options="--continue --skip --abort --quit --show-current-patch" +__git_rebase_interactive_inprogress_options="$__git_rebase_inprogress_options --edit-todo" + _git_rebase () { __git_find_repo_path if [ -f "$__git_repo_path"/rebase-merge/interactive ]; then - __gitcomp "--continue --skip --abort --quit --edit-todo --show-current-patch" + __gitcomp "$__git_rebase_interactive_inprogress_options" return elif [ -d "$__git_repo_path"/rebase-apply ] || \ [ -d "$__git_repo_path"/rebase-merge ]; then - __gitcomp "--continue --skip --abort --quit --show-current-patch" + __gitcomp "$__git_rebase_inprogress_options" return fi __git_complete_strategy && return @@ -1993,20 +2060,13 @@ _git_rebase () __gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}" return ;; + --onto=*) + __git_complete_refs --cur="${cur##--onto=}" + return + ;; --*) - __gitcomp " - --onto --merge --strategy --interactive - --rebase-merges --preserve-merges --stat --no-stat - --committer-date-is-author-date --ignore-date - --ignore-whitespace --whitespace= - --autosquash --no-autosquash - --fork-point --no-fork-point - --autostash --no-autostash - --verify --no-verify - --keep-empty --root --force-rebase --no-ff - --rerere-autoupdate - --exec - " + __gitcomp_builtin rebase "" \ + "$__git_rebase_interactive_inprogress_options" return esac @@ -2133,6 +2193,44 @@ _git_status () __git_complete_index_file "$complete_opt" } +_git_switch () +{ + case "$cur" in + --conflict=*) + __gitcomp "diff3 merge" "" "${cur##--conflict=}" + ;; + --*) + __gitcomp_builtin switch + ;; + *) + # check if --track, --no-track, or --no-guess was specified + # if so, disable DWIM mode + local track_opt="--track" only_local_ref=n + if [ "$GIT_COMPLETION_CHECKOUT_NO_GUESS" = "1" ] || + [ -n "$(__git_find_on_cmdline "--track --no-track --no-guess")" ]; then + track_opt='' + fi + # explicit --guess enables DWIM mode regardless of + # $GIT_COMPLETION_CHECKOUT_NO_GUESS + if [ -n "$(__git_find_on_cmdline "--guess")" ]; then + track_opt='--track' + fi + if [ -z "$(__git_find_on_cmdline "-d --detach")" ]; then + only_local_ref=y + else + # --guess --detach is invalid combination, no + # dwim will be done when --detach is specified + track_opt= + fi + if [ $only_local_ref = y -a -z "$track_opt" ]; then + __gitcomp_direct "$(__git_heads "" "$cur" " ")" + else + __git_complete_refs $track_opt + fi + ;; + esac +} + __git_config_get_set_variables () { local prevword word config_file= c=$cword @@ -2159,181 +2257,282 @@ __git_config_vars= __git_compute_config_vars () { test -n "$__git_config_vars" || - __git_config_vars="$(git help --config-for-completion | sort | uniq)" + __git_config_vars="$(git help --config-for-completion | sort -u)" } -_git_config () +# Completes possible values of various configuration variables. +# +# Usage: __git_complete_config_variable_value [<option>]... +# --varname=<word>: The name of the configuration variable whose value is +# to be completed. Defaults to the previous word on the +# command line. +# --cur=<word>: The current value to be completed. Defaults to the current +# word to be completed. +__git_complete_config_variable_value () { - local varname + local varname="$prev" cur_="$cur" + + while test $# != 0; do + case "$1" in + --varname=*) varname="${1##--varname=}" ;; + --cur=*) cur_="${1##--cur=}" ;; + *) return 1 ;; + esac + shift + done if [ "${BASH_VERSINFO[0]:-0}" -ge 4 ]; then - varname="${prev,,}" + varname="${varname,,}" else - varname="$(echo "$prev" |tr A-Z a-z)" + varname="$(echo "$varname" |tr A-Z a-z)" fi case "$varname" in branch.*.remote|branch.*.pushremote) - __gitcomp_nl "$(__git_remotes)" + __gitcomp_nl "$(__git_remotes)" "" "$cur_" return ;; branch.*.merge) - __git_complete_refs + __git_complete_refs --cur="$cur_" return ;; branch.*.rebase) - __gitcomp "false true merges preserve interactive" + __gitcomp "false true merges preserve interactive" "" "$cur_" return ;; remote.pushdefault) - __gitcomp_nl "$(__git_remotes)" + __gitcomp_nl "$(__git_remotes)" "" "$cur_" return ;; remote.*.fetch) - local remote="${prev#remote.}" + local remote="${varname#remote.}" remote="${remote%.fetch}" - if [ -z "$cur" ]; then + if [ -z "$cur_" ]; then __gitcomp_nl "refs/heads/" "" "" "" return fi - __gitcomp_nl "$(__git_refs_remotes "$remote")" + __gitcomp_nl "$(__git_refs_remotes "$remote")" "" "$cur_" return ;; remote.*.push) - local remote="${prev#remote.}" + local remote="${varname#remote.}" remote="${remote%.push}" __gitcomp_nl "$(__git for-each-ref \ - --format='%(refname):%(refname)' refs/heads)" + --format='%(refname):%(refname)' refs/heads)" "" "$cur_" return ;; pull.twohead|pull.octopus) __git_compute_merge_strategies - __gitcomp "$__git_merge_strategies" - return - ;; - color.branch|color.diff|color.interactive|\ - color.showbranch|color.status|color.ui) - __gitcomp "always never auto" + __gitcomp "$__git_merge_strategies" "" "$cur_" return ;; color.pager) - __gitcomp "false true" + __gitcomp "false true" "" "$cur_" return ;; color.*.*) __gitcomp " normal black red green yellow blue magenta cyan white bold dim ul blink reverse - " + " "" "$cur_" + return + ;; + color.*) + __gitcomp "false true always never auto" "" "$cur_" return ;; diff.submodule) - __gitcomp "log short" + __gitcomp "$__git_diff_submodule_formats" "" "$cur_" return ;; help.format) - __gitcomp "man info web html" + __gitcomp "man info web html" "" "$cur_" return ;; log.date) - __gitcomp "$__git_log_date_formats" + __gitcomp "$__git_log_date_formats" "" "$cur_" return ;; sendemail.aliasfiletype) - __gitcomp "mutt mailrc pine elm gnus" + __gitcomp "mutt mailrc pine elm gnus" "" "$cur_" return ;; sendemail.confirm) - __gitcomp "$__git_send_email_confirm_options" + __gitcomp "$__git_send_email_confirm_options" "" "$cur_" return ;; sendemail.suppresscc) - __gitcomp "$__git_send_email_suppresscc_options" + __gitcomp "$__git_send_email_suppresscc_options" "" "$cur_" return ;; sendemail.transferencoding) - __gitcomp "7bit 8bit quoted-printable base64" - return - ;; - --get|--get-all|--unset|--unset-all) - __gitcomp_nl "$(__git_config_get_set_variables)" + __gitcomp "7bit 8bit quoted-printable base64" "" "$cur_" return ;; *.*) return ;; esac - case "$cur" in - --*) - __gitcomp_builtin config - return - ;; +} + +# Completes configuration sections, subsections, variable names. +# +# Usage: __git_complete_config_variable_name [<option>]... +# --cur=<word>: The current configuration section/variable name to be +# completed. Defaults to the current word to be completed. +# --sfx=<suffix>: A suffix to be appended to each fully completed +# configuration variable name (but not to sections or +# subsections) instead of the default space. +__git_complete_config_variable_name () +{ + local cur_="$cur" sfx + + while test $# != 0; do + case "$1" in + --cur=*) cur_="${1##--cur=}" ;; + --sfx=*) sfx="${1##--sfx=}" ;; + *) return 1 ;; + esac + shift + done + + case "$cur_" in branch.*.*) - local pfx="${cur%.*}." cur_="${cur##*.}" - __gitcomp "remote pushRemote merge mergeOptions rebase" "$pfx" "$cur_" + local pfx="${cur_%.*}." + cur_="${cur_##*.}" + __gitcomp "remote pushRemote merge mergeOptions rebase" "$pfx" "$cur_" "$sfx" return ;; branch.*) - local pfx="${cur%.*}." cur_="${cur#*.}" + local pfx="${cur%.*}." + cur_="${cur#*.}" __gitcomp_direct "$(__git_heads "$pfx" "$cur_" ".")" - __gitcomp_nl_append $'autoSetupMerge\nautoSetupRebase\n' "$pfx" "$cur_" + __gitcomp_nl_append $'autoSetupMerge\nautoSetupRebase\n' "$pfx" "$cur_" "$sfx" return ;; guitool.*.*) - local pfx="${cur%.*}." cur_="${cur##*.}" + local pfx="${cur_%.*}." + cur_="${cur_##*.}" __gitcomp " argPrompt cmd confirm needsFile noConsole noRescan prompt revPrompt revUnmerged title - " "$pfx" "$cur_" + " "$pfx" "$cur_" "$sfx" return ;; difftool.*.*) - local pfx="${cur%.*}." cur_="${cur##*.}" - __gitcomp "cmd path" "$pfx" "$cur_" + local pfx="${cur_%.*}." + cur_="${cur_##*.}" + __gitcomp "cmd path" "$pfx" "$cur_" "$sfx" return ;; man.*.*) - local pfx="${cur%.*}." cur_="${cur##*.}" - __gitcomp "cmd path" "$pfx" "$cur_" + local pfx="${cur_%.*}." + cur_="${cur_##*.}" + __gitcomp "cmd path" "$pfx" "$cur_" "$sfx" return ;; mergetool.*.*) - local pfx="${cur%.*}." cur_="${cur##*.}" - __gitcomp "cmd path trustExitCode" "$pfx" "$cur_" + local pfx="${cur_%.*}." + cur_="${cur_##*.}" + __gitcomp "cmd path trustExitCode" "$pfx" "$cur_" "$sfx" return ;; pager.*) - local pfx="${cur%.*}." cur_="${cur#*.}" + local pfx="${cur_%.*}." + cur_="${cur_#*.}" __git_compute_all_commands - __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_" + __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_" "$sfx" return ;; remote.*.*) - local pfx="${cur%.*}." cur_="${cur##*.}" + local pfx="${cur_%.*}." + cur_="${cur_##*.}" __gitcomp " url proxy fetch push mirror skipDefaultUpdate receivepack uploadpack tagOpt pushurl - " "$pfx" "$cur_" + " "$pfx" "$cur_" "$sfx" return ;; remote.*) - local pfx="${cur%.*}." cur_="${cur#*.}" + local pfx="${cur_%.*}." + cur_="${cur_#*.}" __gitcomp_nl "$(__git_remotes)" "$pfx" "$cur_" "." - __gitcomp_nl_append "pushDefault" "$pfx" "$cur_" + __gitcomp_nl_append "pushDefault" "$pfx" "$cur_" "$sfx" return ;; url.*.*) - local pfx="${cur%.*}." cur_="${cur##*.}" - __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur_" + local pfx="${cur_%.*}." + cur_="${cur_##*.}" + __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur_" "$sfx" return ;; *.*) __git_compute_config_vars - __gitcomp "$__git_config_vars" + __gitcomp "$__git_config_vars" "" "$cur_" "$sfx" ;; *) __git_compute_config_vars - __gitcomp "$(echo "$__git_config_vars" | sed 's/\.[^ ]*/./g')" + __gitcomp "$(echo "$__git_config_vars" | + awk -F . '{ + sections[$1] = 1 + } + END { + for (s in sections) + print s "." + } + ')" "" "$cur_" + ;; + esac +} + +# Completes '='-separated configuration sections/variable names and values +# for 'git -c section.name=value'. +# +# Usage: __git_complete_config_variable_name_and_value [<option>]... +# --cur=<word>: The current configuration section/variable name/value to be +# completed. Defaults to the current word to be completed. +__git_complete_config_variable_name_and_value () +{ + local cur_="$cur" + + while test $# != 0; do + case "$1" in + --cur=*) cur_="${1##--cur=}" ;; + *) return 1 ;; + esac + shift + done + + case "$cur_" in + *=*) + __git_complete_config_variable_value \ + --varname="${cur_%%=*}" --cur="${cur_#*=}" + ;; + *) + __git_complete_config_variable_name --cur="$cur_" --sfx='=' + ;; + esac +} + +_git_config () +{ + case "$prev" in + --get|--get-all|--unset|--unset-all) + __gitcomp_nl "$(__git_config_get_set_variables)" + return + ;; + *.*) + __git_complete_config_variable_value + return + ;; + esac + case "$cur" in + --*) + __gitcomp_builtin config + ;; + *) + __git_complete_config_variable_name + ;; esac } @@ -2395,6 +2594,10 @@ _git_remote () _git_replace () { case "$cur" in + --format=*) + __gitcomp "short medium long" "" "${cur##--format=}" + return + ;; --*) __gitcomp_builtin replace return @@ -2427,7 +2630,22 @@ _git_reset () __git_complete_refs } -__git_revert_inprogress_options="--continue --quit --abort" +_git_restore () +{ + case "$cur" in + --conflict=*) + __gitcomp "diff3 merge" "" "${cur##--conflict=}" + ;; + --source=*) + __git_complete_refs --cur="${cur##--source=}" + ;; + --*) + __gitcomp_builtin restore + ;; + esac +} + +__git_revert_inprogress_options=$__git_sequencer_inprogress_options _git_revert () { @@ -2436,6 +2654,7 @@ _git_revert () __gitcomp "$__git_revert_inprogress_options" return fi + __git_complete_strategy && return case "$cur" in --*) __gitcomp_builtin revert "" \ @@ -2494,8 +2713,9 @@ _git_show () return ;; --*) - __gitcomp "--pretty= --format= --abbrev-commit --oneline - --show-signature + __gitcomp "--pretty= --format= --abbrev-commit --no-abbrev-commit + --oneline --show-signature --patch + --expand-tabs --expand-tabs= --no-expand-tabs $__git_diff_common_options " return @@ -2580,7 +2800,7 @@ _git_submodule () { __git_has_doubledash && return - local subcommands="add status init deinit update summary foreach sync" + local subcommands="add status init deinit update set-branch set-url summary foreach sync absorbgitdirs" local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then case "$cur" in @@ -2611,6 +2831,9 @@ _git_submodule () --force --rebase --merge --reference --depth --recursive --jobs " ;; + set-branch,--*) + __gitcomp "--default --branch" + ;; summary,--*) __gitcomp "--cached --files --summary-limit" ;; @@ -2641,6 +2864,7 @@ _git_svn () --log-window-size= --no-checkout --quiet --repack-flags --use-log-author --localtime --add-author-from + --recursive --ignore-paths= --include-paths= $remote_opts " local init_opts=" @@ -2762,33 +2986,83 @@ _git_whatchanged () _git_log } +__git_complete_worktree_paths () +{ + local IFS=$'\n' + __gitcomp_nl "$(git worktree list --porcelain | + # Skip the first entry: it's the path of the main worktree, + # which can't be moved, removed, locked, etc. + sed -n -e '2,$ s/^worktree //p')" +} + _git_worktree () { local subcommands="add list lock move prune remove unlock" - local subcommand="$(__git_find_on_cmdline "$subcommands")" - if [ -z "$subcommand" ]; then + local subcommand subcommand_idx + + subcommand="$(__git_find_on_cmdline --show-idx "$subcommands")" + subcommand_idx="${subcommand% *}" + subcommand="${subcommand#* }" + + case "$subcommand,$cur" in + ,*) __gitcomp "$subcommands" - else - case "$subcommand,$cur" in - add,--*) - __gitcomp_builtin worktree_add - ;; - list,--*) - __gitcomp_builtin worktree_list - ;; - lock,--*) - __gitcomp_builtin worktree_lock - ;; - prune,--*) - __gitcomp_builtin worktree_prune - ;; - remove,--*) - __gitcomp "--force" + ;; + *,--*) + __gitcomp_builtin worktree_$subcommand + ;; + add,*) # usage: git worktree add [<options>] <path> [<commit-ish>] + # Here we are not completing an --option, it's either the + # path or a ref. + case "$prev" in + -b|-B) # Complete refs for branch to be created/reseted. + __git_complete_refs ;; - *) + -*) # The previous word is an -o|--option without an + # unstuck argument: have to complete the path for + # the new worktree, so don't list anything, but let + # Bash fall back to filename completion. + ;; + *) # The previous word is not an --option, so it must + # be either the 'add' subcommand, the unstuck + # argument of an option (e.g. branch for -b|-B), or + # the path for the new worktree. + if [ $cword -eq $((subcommand_idx+1)) ]; then + # Right after the 'add' subcommand: have to + # complete the path, so fall back to Bash + # filename completion. + : + else + case "${words[cword-2]}" in + -b|-B) # After '-b <branch>': have to + # complete the path, so fall back + # to Bash filename completion. + ;; + *) # After the path: have to complete + # the ref to be checked out. + __git_complete_refs + ;; + esac + fi ;; esac - fi + ;; + lock,*|remove,*|unlock,*) + __git_complete_worktree_paths + ;; + move,*) + if [ $cword -eq $((subcommand_idx+1)) ]; then + # The first parameter must be an existing working + # tree to be moved. + __git_complete_worktree_paths + else + # The second parameter is the destination: it could + # be any path, so don't list anything, but let Bash + # fall back to filename completion. + : + fi + ;; + esac } __git_complete_common () { @@ -2867,7 +3141,11 @@ __git_main () # Bash filename completion return ;; - -c|--namespace) + -c) + __git_complete_config_variable_name_and_value + return + ;; + --namespace) # we don't support completing these options' arguments return ;; @@ -2895,7 +3173,7 @@ __git_main () then __gitcomp "$GIT_TESTING_PORCELAIN_COMMAND_LIST" else - __gitcomp "$(git --list-cmds=list-mainporcelain,others,nohelpers,alias,list-complete,config)" + __gitcomp "$(__git --list-cmds=list-mainporcelain,others,nohelpers,alias,list-complete,config)" fi ;; esac @@ -2993,7 +3271,7 @@ if [[ -n ${ZSH_VERSION-} ]] && local IFS=$'\n' compset -P '*[=:]' - compadd -Q -f -- ${=1} && _ret=0 + compadd -f -- ${=1} && _ret=0 } __gitcomp_file () @@ -3002,7 +3280,7 @@ if [[ -n ${ZSH_VERSION-} ]] && local IFS=$'\n' compset -P '*[=:]' - compadd -Q -p "${2-}" -f -- ${=1} && _ret=0 + compadd -p "${2-}" -f -- ${=1} && _ret=0 } _git () diff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh index 049d6b80f6..eef4eff53d 100644 --- a/contrib/completion/git-completion.zsh +++ b/contrib/completion/git-completion.zsh @@ -11,8 +11,9 @@ # # 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: +# The recommended way to install this script is to make a copy of it in +# ~/.zsh/ directory as ~/.zsh/git-completion.zsh and then add the following +# to your ~/.zshrc file: # # fpath=(~/.zsh $fpath) @@ -99,7 +100,7 @@ __gitcomp_file_direct () local IFS=$'\n' compset -P '*[=:]' - compadd -Q -f -- ${=1} && _ret=0 + compadd -f -- ${=1} && _ret=0 } __gitcomp_file () @@ -108,7 +109,7 @@ __gitcomp_file () local IFS=$'\n' compset -P '*[=:]' - compadd -Q -p "${2-}" -f -- ${=1} && _ret=0 + compadd -p "${2-}" -f -- ${=1} && _ret=0 } __git_zsh_bash_func () diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh index 983e419d2b..1d510cd47b 100644 --- a/contrib/completion/git-prompt.sh +++ b/contrib/completion/git-prompt.sh @@ -286,6 +286,37 @@ __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) @@ -417,10 +448,8 @@ __git_ps1 () fi elif [ -f "$g/MERGE_HEAD" ]; then r="|MERGING" - elif [ -f "$g/CHERRY_PICK_HEAD" ]; then - r="|CHERRY-PICKING" - elif [ -f "$g/REVERT_HEAD" ]; then - r="|REVERTING" + elif __git_sequencer_status; then + : elif [ -f "$g/BISECT_LOG" ]; then r="|BISECTING" fi diff --git a/contrib/credential/netrc/.gitignore b/contrib/credential/netrc/.gitignore new file mode 100644 index 0000000000..d41cdde84b --- /dev/null +++ b/contrib/credential/netrc/.gitignore @@ -0,0 +1 @@ +git-credential-netrc diff --git a/contrib/credential/netrc/Makefile b/contrib/credential/netrc/Makefile index 6174e3bb83..c284fb8ac4 100644 --- a/contrib/credential/netrc/Makefile +++ b/contrib/credential/netrc/Makefile @@ -1,8 +1,30 @@ # The default target of this Makefile is... all:: -test: +SCRIPT_PERL = git-credential-netrc.perl +GIT_ROOT_DIR = ../../.. +HERE = contrib/credential/netrc + +SCRIPT_PERL_FULL = $(patsubst %,$(HERE)/%,$(SCRIPT_PERL)) + +all:: build + +build: + $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \ + build-perl-script + +install: build + $(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 + +test: build ./t-git-credential-netrc.sh -testverbose: +testverbose: build ./t-git-credential-netrc.sh -d -v + +.PHONY: all build install clean test testverbose diff --git a/contrib/credential/netrc/git-credential-netrc b/contrib/credential/netrc/git-credential-netrc.perl index ebfc123ec6..bc57cc6588 100755 --- a/contrib/credential/netrc/git-credential-netrc +++ b/contrib/credential/netrc/git-credential-netrc.perl @@ -423,7 +423,7 @@ 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') + $options->{'gpg'} //= Git::config('gpg.program') // 'gpg'; log_verbose("using $options{'gpg'} for GPG operations"); } diff --git a/contrib/diff-highlight/DiffHighlight.pm b/contrib/diff-highlight/DiffHighlight.pm index 536754583b..e2589922a6 100644 --- a/contrib/diff-highlight/DiffHighlight.pm +++ b/contrib/diff-highlight/DiffHighlight.pm @@ -4,6 +4,11 @@ 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 = ( @@ -67,7 +72,7 @@ sub handle_line { (?:$COLOR?\|$COLOR?[ ])* # zero or more trailing "|" [ ]* # trailing whitespace for merges /x) { - my $graph_prefix = $&; + my $graph_prefix = $&; # We must flush before setting graph indent, since the # new commit may be indented differently from what we @@ -134,7 +139,7 @@ sub highlight_stdin { # 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>/dev/null`; + my $s = `git config --get-color $key 2>$NULL`; return length($s) ? $s : $default; } diff --git a/contrib/hg-to-git/hg-to-git.py b/contrib/hg-to-git/hg-to-git.py index de3f81667e..7eb1b24cc7 100755 --- a/contrib/hg-to-git/hg-to-git.py +++ b/contrib/hg-to-git/hg-to-git.py @@ -42,7 +42,7 @@ hgnewcsets = 0 def usage(): - print """\ + print("""\ %s: [OPTIONS] <hgprj> options: @@ -54,7 +54,7 @@ options: required: hgprj: name of the HG project to import (directory) -""" % sys.argv[0] +""" % sys.argv[0]) #------------------------------------------------------------------------------ @@ -104,22 +104,22 @@ os.chdir(hgprj) if state: if os.path.exists(state): if verbose: - print 'State does exist, reading' + print('State does exist, reading') f = open(state, 'r') hgvers = pickle.load(f) else: - print 'State does not exist, first run' + 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 + print('tip is', tip) # Calculate the branches if verbose: - print 'analysing the branches...' + print('analysing the branches...') hgchildren["0"] = () hgparents["0"] = (None, None) hgbranch["0"] = "master" @@ -154,15 +154,15 @@ for cset in range(1, int(tip) + 1): else: hgbranch[str(cset)] = "branch-" + str(cset) -if not hgvers.has_key("0"): - print 'creating repository' +if "0" not in hgvers: + 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)): + if str(cset) in hgvers: continue hgnewcsets += 1 @@ -180,27 +180,27 @@ for cset in range(int(tip) + 1): 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 + print('-----------------------------------------') + print('cset:', cset) + print('branch:', hgbranch[str(cset)]) + print('user:', user) + print('date:', date) + print('comment:', csetcomment) if parent: - print 'parent:', parent + print('parent:', parent) if mparent: - print 'mparent:', mparent + print('mparent:', mparent) if tag: - print 'tag:', tag - print '-----------------------------------------' + 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)] + 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)] + print('checking out branch', hgbranch[str(cset)]) os.system('git checkout %s' % hgbranch[str(cset)]) # merge @@ -209,7 +209,7 @@ for cset in range(int(tip) + 1): otherbranch = hgbranch[mparent] else: otherbranch = hgbranch[parent] - print 'merging', otherbranch, 'into', hgbranch[str(cset)] + 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 @@ -233,12 +233,12 @@ for cset in range(int(tip) + 1): # delete branch if not used anymore... if mparent and len(hgchildren[str(cset)]): - print "Deleting unused branch:", otherbranch + 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 + print('record', cset, '->', vvv) hgvers[str(cset)] = vvv if hgnewcsets >= opt_nrepack and opt_nrepack != -1: @@ -247,7 +247,7 @@ if hgnewcsets >= opt_nrepack and opt_nrepack != -1: # write the state for incrementals if state: if verbose: - print 'Writing state' + print('Writing state') f = open(state, 'w') pickle.dump(hgvers, f) diff --git a/contrib/hooks/multimail/CHANGES b/contrib/hooks/multimail/CHANGES index 2076cf972b..35791fd02c 100644 --- a/contrib/hooks/multimail/CHANGES +++ b/contrib/hooks/multimail/CHANGES @@ -1,3 +1,59 @@ +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 ============= diff --git a/contrib/hooks/multimail/CONTRIBUTING.rst b/contrib/hooks/multimail/CONTRIBUTING.rst index da65570e9b..de20a54287 100644 --- a/contrib/hooks/multimail/CONTRIBUTING.rst +++ b/contrib/hooks/multimail/CONTRIBUTING.rst @@ -4,9 +4,8 @@ Contributing git-multimail is an open-source project, built by volunteers. We would welcome your help! -The current maintainers are Matthieu Moy -<matthieu.moy@grenoble-inp.fr> and Michael Haggerty -<mhagger@alum.mit.edu>. +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 @@ -33,6 +32,29 @@ 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 diff --git a/contrib/hooks/multimail/README.Git b/contrib/hooks/multimail/README.Git index 161b0230a0..044444245d 100644 --- a/contrib/hooks/multimail/README.Git +++ b/contrib/hooks/multimail/README.Git @@ -6,10 +6,10 @@ website: https://github.com/git-multimail/git-multimail The version in this directory was obtained from the upstream project -on August 17 2016 and consists of the "git-multimail" subdirectory from +on January 07 2019 and consists of the "git-multimail" subdirectory from revision - 07b1cb6bfd7be156c62e1afa17cae13b850a869f refs/tags/1.4.0 + 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. diff --git a/contrib/hooks/multimail/README b/contrib/hooks/multimail/README.rst index 5105373aea..7c0fc4a6ef 100644 --- a/contrib/hooks/multimail/README +++ b/contrib/hooks/multimail/README.rst @@ -1,4 +1,4 @@ -git-multimail version 1.4.0 +git-multimail version 1.5.0 =========================== .. image:: https://travis-ci.org/git-multimail/git-multimail.svg?branch=master @@ -20,8 +20,8 @@ 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. See section -"Getting involved" below for more information. +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: @@ -89,6 +89,10 @@ Requirements 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 ---------- @@ -369,7 +373,7 @@ multimailhook.mailer unset, then the value of multimailhook.from is used. multimailhook.smtpServerTimeout - Timeout in seconds. + Timeout in seconds. Default is 10. multimailhook.smtpEncryption Set the security type. Allowed values: ``none``, ``ssl``, ``tls`` (starttls). @@ -419,8 +423,20 @@ multimailhook.from, multimailhook.fromCommit, multimailhook.fromRefchange If config values are unset, the value of the From: header is determined as follows: - 1. (gitolite environment only) Parse gitolite.conf, looking for a - block of comments that looks like this:: + 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> @@ -436,6 +452,11 @@ multimailhook.from, multimailhook.fromCommit, multimailhook.fromRefchange 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 @@ -484,6 +505,11 @@ multimailhook.maxCommitEmails 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 diff --git a/contrib/hooks/multimail/doc/gitolite.rst b/contrib/hooks/multimail/doc/gitolite.rst index 00aedd9c57..5054833105 100644 --- a/contrib/hooks/multimail/doc/gitolite.rst +++ b/contrib/hooks/multimail/doc/gitolite.rst @@ -46,6 +46,15 @@ and add:: 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. diff --git a/contrib/hooks/multimail/git_multimail.py b/contrib/hooks/multimail/git_multimail.py index 73fdda6b14..f563be82fc 100755 --- a/contrib/hooks/multimail/git_multimail.py +++ b/contrib/hooks/multimail/git_multimail.py @@ -1,6 +1,6 @@ #! /usr/bin/env python -__version__ = '1.4.0' +__version__ = '1.5.0' # Copyright (c) 2015-2016 Matthieu Moy and others # Copyright (c) 2012-2014 Michael Haggerty and others @@ -64,7 +64,9 @@ except ImportError: # Python < 2.6 do not have ssl, but that's OK if we don't use it. pass import time -import cgi + +import uuid +import base64 PYTHON3 = sys.version_info >= (3, 0) @@ -73,7 +75,7 @@ if sys.version_info <= (2, 5): for element in iterable: if not element: return False - return True + return True def is_ascii(s): @@ -93,7 +95,7 @@ if PYTHON3: unicode = str def write_str(f, msg): - # Try outputing with the default encoding. If it fails, + # Try outputting with the default encoding. If it fails, # try UTF-8. try: f.buffer.write(msg.encode(sys.getdefaultencoding())) @@ -108,6 +110,12 @@ if PYTHON3: return out.decode(sys.getdefaultencoding()) except UnicodeEncodeError: return out.decode(ENCODING) + + import html + + def html_escape(s): + return html.escape(s) + else: def is_string(s): try: @@ -130,6 +138,10 @@ else: def next(it): return it.next() + import cgi + + def html_escape(s): + return cgi.escape(s, True) try: from email.charset import Charset @@ -190,6 +202,7 @@ Content-Transfer-Encoding: 8bit Message-ID: %(msgid)s From: %(fromaddr)s Reply-To: %(reply_to)s +Thread-Index: %(thread_index)s X-Git-Host: %(fqdn)s X-Git-Repo: %(repo_shortname)s X-Git-Refname: %(refname)s @@ -322,6 +335,7 @@ From: %(fromaddr)s Reply-To: %(reply_to)s In-Reply-To: %(reply_to_msgid)s References: %(reply_to_msgid)s +Thread-Index: %(thread_index)s X-Git-Host: %(fqdn)s X-Git-Repo: %(repo_shortname)s X-Git-Refname: %(refname)s @@ -763,6 +777,9 @@ class GitObject(object): def __eq__(self, other): return isinstance(other, GitObject) and self.sha1 == other.sha1 + def __ne__(self, other): + return not self == other + def __hash__(self): return hash(self.sha1) @@ -852,7 +869,7 @@ class Change(object): if html_escape_val: for k in values: if is_string(values[k]): - values[k] = cgi.escape(values[k], True) + values[k] = html_escape(values[k]) for line in template.splitlines(True): yield line % values @@ -909,7 +926,7 @@ class Change(object): raise NotImplementedError() - def generate_email_body(self): + def generate_email_body(self, push): """Generate the main part of the email body, a line at a time. The text in the body might be truncated after a specified @@ -936,7 +953,7 @@ class Change(object): yield "<pre style='margin:0'>\n" for line in lines: - yield cgi.escape(line) + yield html_escape(line) yield '</pre>\n' else: @@ -1011,7 +1028,7 @@ class Change(object): fgcolor = '404040' # Chop the trailing LF, we don't want it inside <pre>. - line = cgi.escape(line[:-1]) + line = html_escape(line[:-1]) if bgcolor or fgcolor: style = 'display:block; white-space:pre;' @@ -1060,6 +1077,10 @@ class Revision(Change): self.author = read_git_output(['log', '--no-walk', '--format=%aN <%aE>', self.rev.sha1]) self.recipients = self.environment.get_revision_recipients(self) + # -s is short for --no-patch, but -s works on older git's (e.g. 1.7) + self.parents = read_git_lines(['show', '-s', '--format=%P', + self.rev.sha1])[0].split() + self.cc_recipients = '' if self.environment.get_scancommitforcc(): self.cc_recipients = ', '.join(to.strip() for to in self._cc_recipients()) @@ -1090,6 +1111,7 @@ class Revision(Change): oneline = oneline[:max_subject_length - 6] + ' [...]' values['rev'] = self.rev.sha1 + values['parents'] = ' '.join(self.parents) values['rev_short'] = self.rev.short values['change_type'] = self.change_type values['refname'] = self.refname @@ -1097,6 +1119,7 @@ class Revision(Change): values['short_refname'] = self.reference_change.short_refname values['refname_type'] = self.reference_change.refname_type values['reply_to_msgid'] = self.reference_change.msgid + values['thread_index'] = self.reference_change.thread_index values['num'] = self.num values['tot'] = self.tot values['recipients'] = self.recipients @@ -1244,6 +1267,23 @@ class ReferenceChange(Change): old=old, new=new, rev=rev, ) + @staticmethod + def make_thread_index(): + """Return a string appropriate for the Thread-Index header, + needed by MS Outlook to get threading right. + + The format is (base64-encoded): + - 1 byte must be 1 + - 5 bytes encode a date (hardcoded here) + - 16 bytes for a globally unique identifier + + FIXME: Unfortunately, even with the Thread-Index field, MS + Outlook doesn't seem to do the threading reliably (see + https://github.com/git-multimail/git-multimail/pull/194). + """ + thread_index = b'\x01\x00\x00\x12\x34\x56' + uuid.uuid4().bytes + return base64.standard_b64encode(thread_index).decode('ascii') + def __init__(self, environment, refname, short_refname, old, new, rev): Change.__init__(self, environment) self.change_type = { @@ -1257,6 +1297,7 @@ class ReferenceChange(Change): self.new = new self.rev = rev self.msgid = make_msgid() + self.thread_index = self.make_thread_index() self.diffopts = environment.diffopts self.graphopts = environment.graphopts self.logopts = environment.logopts @@ -1276,6 +1317,7 @@ class ReferenceChange(Change): values['refname'] = self.refname values['short_refname'] = self.short_refname values['msgid'] = self.msgid + values['thread_index'] = self.thread_index values['recipients'] = self.recipients values['oldrev'] = str(self.old) values['oldrev_short'] = self.old.short @@ -1941,6 +1983,9 @@ class Mailer(object): def __init__(self, environment): self.environment = environment + def close(self): + pass + def send(self, lines, to_addrs): """Send an email consisting of lines. @@ -2054,6 +2099,7 @@ class SMTPMailer(Mailer): self.username = smtpuser self.password = smtppass self.smtpcacerts = smtpcacerts + self.loggedin = False try: def call(klass, server, timeout): try: @@ -2083,7 +2129,7 @@ class SMTPMailer(Mailer): # equivalent to # self.smtp.ehlo() # self.smtp.starttls() - # with acces to the ssl layer + # with access to the ssl layer self.smtp.ehlo() if not self.smtp.has_extn("starttls"): raise smtplib.SMTPException("STARTTLS extension not supported by server") @@ -2102,7 +2148,7 @@ class SMTPMailer(Mailer): cert_reqs=ssl.CERT_NONE ) self.environment.get_logger().error( - '*** Warning, the server certificat is not verified (smtp) ***\n' + '*** Warning, the server certificate is not verified (smtp) ***\n' '*** set the option smtpCACerts ***\n' ) if not hasattr(self.smtp.sock, "read"): @@ -2130,20 +2176,30 @@ class SMTPMailer(Mailer): % (self.smtpserver, sys.exc_info()[1])) sys.exit(1) - def __del__(self): + def close(self): if hasattr(self, 'smtp'): self.smtp.quit() del self.smtp + def __del__(self): + self.close() + def send(self, lines, to_addrs): try: if self.username or self.password: - self.smtp.login(self.username, self.password) + if not self.loggedin: + self.smtp.login(self.username, self.password) + self.loggedin = True msg = ''.join(lines) # turn comma-separated list into Python list if needed. if is_string(to_addrs): to_addrs = [email for (name, email) in getaddresses([to_addrs])] self.smtp.sendmail(self.envelopesender, to_addrs, msg) + except socket.timeout: + self.environment.get_logger().error( + '*** Error sending email ***\n' + '*** SMTP server timed out (timeout is %s)\n' + % self.smtpservertimeout) except smtplib.SMTPResponseException: err = sys.exc_info()[1] self.environment.get_logger().error( @@ -2171,7 +2227,8 @@ class OutputMailer(Mailer): SEPARATOR = '=' * 75 + '\n' - def __init__(self, f): + def __init__(self, f, environment=None): + super(OutputMailer, self).__init__(environment=environment) self.f = f def send(self, lines, to_addrs): @@ -2382,6 +2439,7 @@ class Environment(object): self.html_in_footer = False self.commitBrowseURL = None self.maxcommitemails = 500 + self.excludemergerevisions = False self.diffopts = ['--stat', '--summary', '--find-copies-harder'] self.graphopts = ['--oneline', '--decorate'] self.logopts = [] @@ -2621,6 +2679,8 @@ class ConfigOptionsEnvironmentMixin(ConfigEnvironmentMixin): self.commitBrowseURL = config.get('commitBrowseURL') + self.excludemergerevisions = config.get('excludeMergeRevisions') + maxcommitemails = config.get('maxcommitemails') if maxcommitemails is not None: try: @@ -3129,7 +3189,7 @@ class ProjectdescEnvironmentMixin(Environment): self.COMPUTED_KEYS += ['projectdesc'] def get_projectdesc(self): - """Return a one-line descripition of the project.""" + """Return a one-line description of the project.""" git_dir = get_git_dir() try: @@ -3152,7 +3212,10 @@ class GitoliteEnvironmentHighPrecMixin(Environment): return self.osenv.get('GL_USER', 'unknown user') -class GitoliteEnvironmentLowPrecMixin(Environment): +class GitoliteEnvironmentLowPrecMixin( + ConfigEnvironmentMixin, + Environment): + def get_repo_shortname(self): # The gitolite environment variable $GL_REPO is a pretty good # repo_shortname (though it's probably not as good as a value @@ -3162,6 +3225,16 @@ class GitoliteEnvironmentLowPrecMixin(Environment): super(GitoliteEnvironmentLowPrecMixin, self).get_repo_shortname() ) + @staticmethod + def _compile_regex(re_template): + return ( + re.compile(re_template % x) + for x in ( + r'BEGIN\s+USER\s+EMAILS', + r'([^\s]+)\s+(.*)', + r'END\s+USER\s+EMAILS', + )) + def get_fromaddr(self, change=None): GL_USER = self.osenv.get('GL_USER') if GL_USER is not None: @@ -3174,18 +3247,42 @@ class GitoliteEnvironmentLowPrecMixin(Environment): GL_CONF = self.osenv.get( 'GL_CONF', os.path.join(GL_ADMINDIR, 'conf', 'gitolite.conf')) + + mailaddress_map = self.config.get('MailaddressMap') + # If relative, consider relative to GL_CONF: + if mailaddress_map: + mailaddress_map = os.path.join(os.path.dirname(GL_CONF), + mailaddress_map) + if os.path.isfile(mailaddress_map): + f = open(mailaddress_map, 'rU') + try: + # Leading '#' is optional + re_begin, re_user, re_end = self._compile_regex( + r'^(?:\s*#)?\s*%s\s*$') + for l in f: + l = l.rstrip('\n') + if re_begin.match(l) or re_end.match(l): + continue # Ignore these lines + m = re_user.match(l) + if m: + if m.group(1) == GL_USER: + return m.group(2) + else: + continue # Not this user, but not an error + raise ConfigurationException( + "Syntax error in mail address map.\n" + "Check file {}.\n" + "Line: {}".format(mailaddress_map, l)) + + finally: + f.close() + if os.path.isfile(GL_CONF): f = open(GL_CONF, 'rU') try: in_user_emails_section = False - re_template = r'^\s*#\s*%s\s*$' - re_begin, re_user, re_end = ( - re.compile(re_template % x) - for x in ( - r'BEGIN\s+USER\s+EMAILS', - re.escape(GL_USER) + r'\s+(.*)', - r'END\s+USER\s+EMAILS', - )) + re_begin, re_user, re_end = self._compile_regex( + r'^\s*#\s*%s\s*$') for l in f: l = l.rstrip('\n') if not in_user_emails_section: @@ -3195,8 +3292,8 @@ class GitoliteEnvironmentLowPrecMixin(Environment): if re_end.match(l): break m = re_user.match(l) - if m: - return m.group(1) + if m and m.group(1) == GL_USER: + return m.group(2) finally: f.close() return super(GitoliteEnvironmentLowPrecMixin, self).get_fromaddr(change) @@ -3228,7 +3325,7 @@ class StashEnvironmentHighPrecMixin(Environment): self.__repo = repo def get_pusher(self): - return re.match('(.*?)\s*<', self.__user).group(1) + return re.match(r'(.*?)\s*<', self.__user).group(1) def get_pusher_email(self): return self.__user @@ -3262,7 +3359,7 @@ class GerritEnvironmentHighPrecMixin(Environment): if self.__submitter.find('<') != -1: # Submitter has a configured email, we transformed # __submitter into an RFC 2822 string already. - return re.match('(.*?)\s*<', self.__submitter).group(1) + return re.match(r'(.*?)\s*<', self.__submitter).group(1) else: # Submitter has no configured email, it's just his name. return self.__submitter @@ -3615,6 +3712,9 @@ class Push(object): for (num, sha1) in enumerate(sha1s): rev = Revision(change, GitObject(sha1), num=num + 1, tot=len(sha1s)) + if len(rev.parents) > 1 and change.environment.excludemergerevisions: + # skipping a merge commit + continue if not rev.recipients and rev.cc_recipients: change.environment.log_msg('*** Replacing Cc: with To:') rev.recipients = rev.cc_recipients @@ -3664,11 +3764,14 @@ def run_as_post_receive_hook(environment, mailer): changes.append( ReferenceChange.create(environment, oldrev, newrev, refname) ) - if changes: - push = Push(environment, changes) + if not changes: + mailer.close() + return + push = Push(environment, changes) + try: push.send_emails(mailer, body_filter=environment.filter_body) - if hasattr(mailer, '__del__'): - mailer.__del__() + finally: + mailer.close() def run_as_update_hook(environment, mailer, refname, oldrev, newrev, force_send=False): @@ -3687,10 +3790,14 @@ def run_as_update_hook(environment, mailer, refname, oldrev, newrev, force_send= refname, ), ] + if not changes: + mailer.close() + return push = Push(environment, changes, force_send) - push.send_emails(mailer, body_filter=environment.filter_body) - if hasattr(mailer, '__del__'): - mailer.__del__() + try: + push.send_emails(mailer, body_filter=environment.filter_body) + finally: + mailer.close() def check_ref_filter(environment): @@ -3860,7 +3967,7 @@ def build_environment_klass(env_name): low_prec_mixin = known_env['lowprec'] environment_mixins.append(low_prec_mixin) environment_mixins.append(Environment) - klass_name = env_name.capitalize() + 'Environement' + klass_name = env_name.capitalize() + 'Environment' environment_klass = type( klass_name, tuple(environment_mixins), @@ -4057,21 +4164,21 @@ class Logger(object): environment, 'git_multimail.error', environment.error_log_file, logging.ERROR) self.loggers.append(error_log_file) - def info(self, msg): + def info(self, msg, *args, **kwargs): for l in self.loggers: - l.info(msg) + l.info(msg, *args, **kwargs) - def debug(self, msg): + def debug(self, msg, *args, **kwargs): for l in self.loggers: - l.debug(msg) + l.debug(msg, *args, **kwargs) - def warning(self, msg): + def warning(self, msg, *args, **kwargs): for l in self.loggers: - l.warning(msg) + l.warning(msg, *args, **kwargs) - def error(self, msg): + def error(self, msg, *args, **kwargs): for l in self.loggers: - l.error(msg) + l.error(msg, *args, **kwargs) def main(args): @@ -4189,7 +4296,7 @@ def main(args): show_env(environment, sys.stderr) if options.stdout or environment.stdout: - mailer = OutputMailer(sys.stdout) + mailer = OutputMailer(sys.stdout, environment) else: mailer = choose_mailer(config, environment) @@ -4234,5 +4341,6 @@ def main(args): sys.stderr.write(msg) sys.exit(1) + if __name__ == '__main__': main(sys.argv[1:]) diff --git a/contrib/hooks/multimail/migrate-mailhook-config b/contrib/hooks/multimail/migrate-mailhook-config index 992657bbdc..241ba22fa3 100755 --- a/contrib/hooks/multimail/migrate-mailhook-config +++ b/contrib/hooks/multimail/migrate-mailhook-config @@ -110,11 +110,12 @@ def is_section_empty(section, local): try: read_output( - ['git', 'config'] - + local_option - + ['--get-regexp', '^%s\.' % (section,)] + ['git', 'config'] + + local_option + + ['--get-regexp', '^%s\.' % (section,)] ) - except CommandError, e: + except CommandError: + t, e, traceback = sys.exc_info() if e.retcode == 1: # This means that no settings were found. return True @@ -188,7 +189,9 @@ def migrate_config(strict=False, retain=False, overwrite=False): sys.stderr.write( '...copying "%s.%s" to "%s.%s"\n' % (old.section, name, new.section, name) ) - new.set_recipients(name, old.get_recipients(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( diff --git a/contrib/hooks/multimail/post-receive.example b/contrib/hooks/multimail/post-receive.example index 1ea113d274..0f98c5a23d 100755 --- a/contrib/hooks/multimail/post-receive.example +++ b/contrib/hooks/multimail/post-receive.example @@ -30,7 +30,6 @@ script's behavior could be changed or customized. """ import sys -import os # If necessary, add the path to the directory containing # git_multimail.py to the Python path as follows. (This is not @@ -57,7 +56,7 @@ 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 +# configuration in .git/config (must come before instantiating 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')) @@ -86,6 +85,7 @@ mailer = git_multimail.choose_mailer(config, environment) # 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' diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email index 8747b84334..ff565eb3d8 100755 --- a/contrib/hooks/post-receive-email +++ b/contrib/hooks/post-receive-email @@ -329,7 +329,7 @@ generate_update_branch_email() # # git rev-parse --not --all | grep -v $(git rev-parse $refname) # - # Get's us to something pretty safe (apart from the small time + # Gets us to something pretty safe (apart from the small time # between refname being read, and git rev-parse running - for that, # I give up) # diff --git a/contrib/hooks/update-paranoid b/contrib/hooks/update-paranoid index d18b317b2f..0092d67b8a 100755 --- a/contrib/hooks/update-paranoid +++ b/contrib/hooks/update-paranoid @@ -49,7 +49,7 @@ opcode. Repository sections are matched on the basename of the repository (after removing the .git suffix). -The opcode abbrevations are: +The opcode abbreviations are: C: create new ref D: delete existing ref diff --git a/contrib/mw-to-git/.perlcriticrc b/contrib/mw-to-git/.perlcriticrc index 158958d363..b7333267ad 100644 --- a/contrib/mw-to-git/.perlcriticrc +++ b/contrib/mw-to-git/.perlcriticrc @@ -14,7 +14,7 @@ # 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. +# return value would be harmful to the code readability. # This configuration keeps all default function but print. [InputOutput::RequireCheckedSyscalls] functions = open say close diff --git a/contrib/mw-to-git/git-remote-mediawiki.perl b/contrib/mw-to-git/git-remote-mediawiki.perl index af9cbc9d0f..d8ff2e69c4 100755 --- a/contrib/mw-to-git/git-remote-mediawiki.perl +++ b/contrib/mw-to-git/git-remote-mediawiki.perl @@ -79,7 +79,7 @@ chomp($export_media); $export_media = !($export_media eq 'false'); my $wiki_login = run_git("config --get remote.${remotename}.mwLogin"); -# Note: mwPassword is discourraged. Use the credential system instead. +# Note: mwPassword is discouraged. Use the credential system instead. my $wiki_passwd = run_git("config --get remote.${remotename}.mwPassword"); my $wiki_domain = run_git("config --get remote.${remotename}.mwDomain"); chomp($wiki_login); diff --git a/contrib/mw-to-git/t/install-wiki/db_install.php b/contrib/mw-to-git/t/install-wiki/db_install.php index 0f3f4e018a..b033849800 100644 --- a/contrib/mw-to-git/t/install-wiki/db_install.php +++ b/contrib/mw-to-git/t/install-wiki/db_install.php @@ -24,7 +24,7 @@ $url = 'http://localhost:'.$port.'/wiki/mw-config/index.php'; $db_dir = urlencode($tmp); $tmp_cookie = tempnam($tmp, "COOKIE_"); /* - * Fetchs a page with cURL. + * Fetches a page with cURL. */ function get($page_name = "") { $curl = curl_init(); diff --git a/contrib/mw-to-git/t/t9360-mw-to-git-clone.sh b/contrib/mw-to-git/t/t9360-mw-to-git-clone.sh index cfbfe7ddf6..9106833578 100755 --- a/contrib/mw-to-git/t/t9360-mw-to-git-clone.sh +++ b/contrib/mw-to-git/t/t9360-mw-to-git-clone.sh @@ -143,7 +143,7 @@ test_expect_success 'Git clone works with one specific page cloned ' ' test_expect_success 'Git clone works with multiple specific page cloned ' ' wiki_reset && wiki_editpage foo "I will be there" false && - wiki_editpage bar "I will not disapear" false && + wiki_editpage bar "I will not disappear" false && wiki_editpage namnam "I be erased" false && wiki_editpage nyancat "nyan nyan nyan you will not erase me" false && wiki_delete_page namnam && diff --git a/contrib/mw-to-git/t/test-gitmw-lib.sh b/contrib/mw-to-git/t/test-gitmw-lib.sh index 6546294f15..3948a00282 100755 --- a/contrib/mw-to-git/t/test-gitmw-lib.sh +++ b/contrib/mw-to-git/t/test-gitmw-lib.sh @@ -279,7 +279,7 @@ start_lighttpd () { "$LIGHTTPD_DIR"/lighttpd -f "$WEB"/lighttpd.conf if test $? -ne 0 ; then - echo "Could not execute http deamon lighttpd" + echo "Could not execute http daemon lighttpd" exit 1 fi } diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index 147201dc6c..868e18b9a1 100755 --- a/contrib/subtree/git-subtree.sh +++ b/contrib/subtree/git-subtree.sh @@ -14,7 +14,7 @@ git subtree add --prefix=<prefix> <repository> <ref> git subtree merge --prefix=<prefix> <commit> git subtree pull --prefix=<prefix> <repository> <ref> git subtree push --prefix=<prefix> <repository> <ref> -git subtree split --prefix=<prefix> <commit...> +git subtree split --prefix=<prefix> <commit> -- h,help show the help q quiet @@ -77,6 +77,12 @@ assert () { fi } +ensure_single_rev () { + if test $# -ne 1 + then + die "You must provide exactly one revision. Got: '$@'" + fi +} while test $# -gt 0 do @@ -185,6 +191,7 @@ if test "$command" != "pull" && then revs=$(git rev-parse $default --revs-only "$@") || exit $? dirs=$(git rev-parse --no-revs --no-flags "$@") || exit $? + ensure_single_rev $revs if test -n "$dirs" then die "Error: Use --prefix instead of bare filenames." @@ -716,9 +723,8 @@ cmd_add_repository () { } cmd_add_commit () { - revs=$(git rev-parse $default --revs-only "$@") || exit $? - set -- $revs - rev="$1" + rev=$(git rev-parse $default --revs-only "$@") || exit $? + ensure_single_rev $rev debug "Adding $dir as '$rev'..." git read-tree --prefix="$dir" $rev || exit $? @@ -817,16 +823,10 @@ cmd_split () { } cmd_merge () { - revs=$(git rev-parse $default --revs-only "$@") || exit $? + rev=$(git rev-parse $default --revs-only "$@") || exit $? + ensure_single_rev $rev ensure_clean - set -- $revs - if test $# -ne 1 - then - die "You must provide exactly one revision. Got: '$revs'" - fi - rev="$1" - if test -n "$squash" then first_split="$(find_latest_squash "$dir")" diff --git a/contrib/svn-fe/svn-fe.txt b/contrib/svn-fe/svn-fe.txt index a3425f4770..19333fc8df 100644 --- a/contrib/svn-fe/svn-fe.txt +++ b/contrib/svn-fe/svn-fe.txt @@ -56,7 +56,7 @@ line. This line has the form `git-svn-id: URL@REVNO UUID`. The resulting repository will generally require further processing to put each project in its own repository and to separate the history -of each branch. The 'git filter-branch --subdirectory-filter' command +of each branch. The 'git filter-repo --subdirectory-filter' command may be useful for this purpose. BUGS @@ -67,5 +67,5 @@ The exit status does not reflect whether an error was detected. SEE ALSO -------- -git-svn(1), svn2git(1), svk(1), git-filter-branch(1), git-fast-import(1), +git-svn(1), svn2git(1), svk(1), git-filter-repo(1), git-fast-import(1), https://svn.apache.org/repos/asf/subversion/trunk/notes/dump-load-format.txt diff --git a/contrib/svn-fe/svnrdump_sim.py b/contrib/svn-fe/svnrdump_sim.py index 11ac6f6927..8a3cee6175 100755 --- a/contrib/svn-fe/svnrdump_sim.py +++ b/contrib/svn-fe/svnrdump_sim.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python """ Simulates svnrdump by replaying an existing dump from a file, taking care of the specified revision range. @@ -54,7 +54,7 @@ if __name__ == "__main__": print("usage: %s dump URL -rLOWER:UPPER") sys.exit(1) if not sys.argv[1] == 'dump': - raise NotImplementedError('only "dump" is suppported.') + raise NotImplementedError('only "dump" is supported.') url = sys.argv[2] r = ('0', 'HEAD') if len(sys.argv) == 4 and sys.argv[3][0:2] == '-r': |