summaryrefslogtreecommitdiff
path: root/contrib
diff options
context:
space:
mode:
Diffstat (limited to 'contrib')
-rw-r--r--contrib/buildsystems/Generators.pm2
-rw-r--r--contrib/buildsystems/Generators/Vcproj.pm119
-rw-r--r--contrib/buildsystems/Generators/Vcxproj.pm392
-rwxr-xr-xcontrib/buildsystems/engine.pl59
-rw-r--r--contrib/coccinelle/README41
-rw-r--r--contrib/coccinelle/array.cocci61
-rw-r--r--contrib/coccinelle/commit.cocci34
-rw-r--r--contrib/coccinelle/flex_alloc.cocci13
-rw-r--r--contrib/coccinelle/hashmap.cocci16
-rw-r--r--contrib/coccinelle/object_id.cocci119
-rw-r--r--contrib/coccinelle/preincr.cocci5
-rw-r--r--contrib/coccinelle/strbuf.cocci30
-rw-r--r--contrib/coccinelle/the_repository.pending.cocci144
-rw-r--r--contrib/completion/git-completion.bash1446
-rw-r--r--contrib/completion/git-completion.zsh20
-rw-r--r--contrib/completion/git-prompt.sh43
-rwxr-xr-xcontrib/convert-grafts-to-replace-refs.sh28
-rwxr-xr-xcontrib/coverage-diff.sh108
-rw-r--r--contrib/credential/netrc/.gitignore1
-rw-r--r--contrib/credential/netrc/Makefile33
-rwxr-xr-xcontrib/credential/netrc/git-credential-netrc.perl (renamed from contrib/credential/netrc/git-credential-netrc)57
-rwxr-xr-xcontrib/credential/netrc/t-git-credential-netrc.sh32
-rwxr-xr-xcontrib/credential/netrc/test.command-option-gpg2
-rwxr-xr-xcontrib/credential/netrc/test.git-config-gpg2
-rw-r--r--contrib/credential/netrc/test.netrc.gpg0
-rwxr-xr-xcontrib/credential/netrc/test.pl89
-rw-r--r--contrib/credential/wincred/git-credential-wincred.c3
-rw-r--r--contrib/diff-highlight/DiffHighlight.pm9
-rw-r--r--contrib/emacs/.gitignore1
-rw-r--r--contrib/emacs/Makefile21
-rw-r--r--contrib/emacs/README32
-rw-r--r--contrib/emacs/git-blame.el489
-rw-r--r--contrib/emacs/git.el1710
-rwxr-xr-xcontrib/fast-import/import-tars.perl33
-rw-r--r--contrib/git-jump/README12
-rwxr-xr-xcontrib/git-jump/git-jump2
-rwxr-xr-xcontrib/hg-to-git/hg-to-git.py50
-rw-r--r--contrib/hooks/multimail/CHANGES56
-rw-r--r--contrib/hooks/multimail/CONTRIBUTING.rst28
-rw-r--r--contrib/hooks/multimail/README.Git4
-rw-r--r--contrib/hooks/multimail/README.rst (renamed from contrib/hooks/multimail/README)38
-rw-r--r--contrib/hooks/multimail/doc/gitolite.rst9
-rwxr-xr-xcontrib/hooks/multimail/git_multimail.py196
-rwxr-xr-xcontrib/hooks/multimail/migrate-mailhook-config13
-rwxr-xr-xcontrib/hooks/multimail/post-receive.example4
-rwxr-xr-xcontrib/hooks/post-receive-email2
-rwxr-xr-xcontrib/hooks/update-paranoid2
-rw-r--r--contrib/mw-to-git/.perlcriticrc2
-rw-r--r--contrib/mw-to-git/Makefile5
-rwxr-xr-xcontrib/mw-to-git/git-remote-mediawiki.perl2
-rw-r--r--contrib/mw-to-git/t/install-wiki/db_install.php2
-rwxr-xr-xcontrib/mw-to-git/t/t9360-mw-to-git-clone.sh4
-rwxr-xr-xcontrib/mw-to-git/t/test-gitmw-lib.sh2
-rw-r--r--contrib/subtree/Makefile10
-rwxr-xr-xcontrib/subtree/git-subtree.sh153
-rwxr-xr-xcontrib/subtree/t/t7900-subtree.sh121
-rw-r--r--contrib/svn-fe/svn-fe.txt4
-rwxr-xr-xcontrib/svn-fe/svnrdump_sim.py4
-rw-r--r--contrib/update-unicode/README6
-rwxr-xr-xcontrib/update-unicode/update_unicode.sh2
-rw-r--r--contrib/vscode/.gitattributes1
-rw-r--r--contrib/vscode/README.md14
-rwxr-xr-xcontrib/vscode/init.sh375
63 files changed, 2874 insertions, 3443 deletions
diff --git a/contrib/buildsystems/Generators.pm b/contrib/buildsystems/Generators.pm
index 408ef71..aa4cbaa 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 cfa74ad..737647e 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/</&lt;/g;
+ $cflags =~ s/>/&gt;/g;
my $cflags_debug = $cflags;
$cflags_debug =~ s/-MT/-MTd/;
@@ -127,6 +82,8 @@ sub createLibProject {
$defines =~ s/-D//g;
$defines =~ s/\"/\\&quot;/g;
+ $defines =~ s/</&lt;/g;
+ $defines =~ s/>/&gt;/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("&quot;$rel_dir\\$_&quot;", @{$$build_structure{"APPS_${appname}_INCLUDES"}})));
my $cflags = join(" ", sort(@{$$build_structure{"APPS_${appname}_CFLAGS"}}));
$cflags =~ s/\"/&quot;/g;
+ $cflags =~ s/</&lt;/g;
+ $cflags =~ s/>/&gt;/g;
my $cflags_debug = $cflags;
$cflags_debug =~ s/-MT/-MTd/;
@@ -351,6 +303,8 @@ sub createAppProject {
$defines =~ s/-D//g;
$defines =~ s/\"/\\&quot;/g;
+ $defines =~ s/</&lt;/g;
+ $defines =~ s/>/&gt;/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 0000000..5c666f9
--- /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/</&lt;/g;
+ $cflags =~ s/>/&gt;/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/</&lt;/g;
+ $defines =~ s/>/&gt;/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 23da787..0709785 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/README b/contrib/coccinelle/README
index 9c2f887..f0e80bd 100644
--- a/contrib/coccinelle/README
+++ b/contrib/coccinelle/README
@@ -1,2 +1,43 @@
This directory provides examples of Coccinelle (http://coccinelle.lip6.fr/)
semantic patches that might be useful to developers.
+
+There are two types of semantic patches:
+
+ * Using the semantic transformation to check for bad patterns in the code;
+ The target 'make coccicheck' is designed to check for these patterns and
+ it is expected that any resulting patch indicates a regression.
+ The patches resulting from 'make coccicheck' are small and infrequent,
+ so once they are found, they can be sent to the mailing list as per usual.
+
+ Example for introducing new patterns:
+ 67947c34ae (convert "hashcmp() != 0" to "!hasheq()", 2018-08-28)
+ b84c783882 (fsck: s/++i > 1/i++/, 2018-10-24)
+
+ Example of fixes using this approach:
+ 248f66ed8e (run-command: use strbuf_addstr() for adding a string to
+ a strbuf, 2018-03-25)
+ f919ffebed (Use MOVE_ARRAY, 2018-01-22)
+
+ These types of semantic patches are usually part of testing, c.f.
+ 0860a7641b (travis-ci: fail if Coccinelle static analysis found something
+ to transform, 2018-07-23)
+
+ * Using semantic transformations in large scale refactorings throughout
+ the code base.
+
+ When applying the semantic patch into a real patch, sending it to the
+ mailing list in the usual way, such a patch would be expected to have a
+ lot of textual and semantic conflicts as such large scale refactorings
+ change function signatures that are used widely in the code base.
+ A textual conflict would arise if surrounding code near any call of such
+ function changes. A semantic conflict arises when other patch series in
+ flight introduce calls to such functions.
+
+ So to aid these large scale refactorings, semantic patches can be used.
+ However we do not want to store them in the same place as the checks for
+ bad patterns, as then automated builds would fail.
+ That is why semantic patches 'contrib/coccinelle/*.pending.cocci'
+ are ignored for checks, and can be applied using 'make coccicheck-pending'.
+
+ This allows to expose plans of pending large scale refactorings without
+ impacting the bad pattern checks.
diff --git a/contrib/coccinelle/array.cocci b/contrib/coccinelle/array.cocci
index 0158682..46b8d2e 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
new file mode 100644
index 0000000..778e470
--- /dev/null
+++ b/contrib/coccinelle/commit.cocci
@@ -0,0 +1,34 @@
+@@
+expression c;
+@@
+- &c->maybe_tree->object.oid
++ get_commit_tree_oid(c)
+
+@@
+expression c;
+@@
+- c->maybe_tree->object.oid.hash
++ get_commit_tree_oid(c)->hash
+
+@@
+identifier f !~ "^set_commit_tree$";
+expression c;
+expression s;
+@@
+ f(...) {<...
+- c->maybe_tree = s
++ set_commit_tree(c, s)
+ ...>}
+
+// These excluded functions must access c->maybe_tree 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;
+@@
+ 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 0000000..e9f7f6d
--- /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 0000000..d69e120
--- /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 09afdbf..ddf4f22 100644
--- a/contrib/coccinelle/object_id.cocci
+++ b/contrib/coccinelle/object_id.cocci
@@ -1,110 +1,87 @@
@@
-expression E1;
+struct object_id OID;
@@
-- is_null_sha1(E1.hash)
-+ is_null_oid(&E1)
+- is_null_sha1(OID.hash)
++ is_null_oid(&OID)
@@
-expression E1;
+struct object_id *OIDPTR;
@@
-- is_null_sha1(E1->hash)
-+ is_null_oid(E1)
+- is_null_sha1(OIDPTR->hash)
++ is_null_oid(OIDPTR)
@@
-expression E1;
+struct object_id OID;
@@
-- sha1_to_hex(E1.hash)
-+ oid_to_hex(&E1)
-
-@@
-identifier f != oid_to_hex;
-expression E1;
-@@
- f(...) {...
-- sha1_to_hex(E1->hash)
-+ oid_to_hex(E1)
- ...}
-
-@@
-expression E1, E2;
-@@
-- sha1_to_hex_r(E1, E2.hash)
-+ oid_to_hex_r(E1, &E2)
-
-@@
-identifier f != oid_to_hex_r;
-expression E1, E2;
-@@
- f(...) {...
-- sha1_to_hex_r(E1, E2->hash)
-+ oid_to_hex_r(E1, E2)
- ...}
-
-@@
-expression E1;
-@@
-- hashclr(E1.hash)
-+ oidclr(&E1)
+- hashclr(OID.hash)
++ oidclr(&OID)
@@
identifier f != oidclr;
-expression E1;
+struct object_id *OIDPTR;
@@
- f(...) {...
-- hashclr(E1->hash)
-+ oidclr(E1)
- ...}
+ f(...) {<...
+- hashclr(OIDPTR->hash)
++ oidclr(OIDPTR)
+ ...>}
@@
-expression E1, E2;
+struct object_id OID1, OID2;
@@
-- hashcmp(E1.hash, E2.hash)
-+ oidcmp(&E1, &E2)
+- hashcmp(OID1.hash, OID2.hash)
++ oidcmp(&OID1, &OID2)
@@
identifier f != oidcmp;
-expression E1, E2;
+struct object_id *OIDPTR1, OIDPTR2;
@@
- f(...) {...
-- hashcmp(E1->hash, E2->hash)
-+ oidcmp(E1, E2)
- ...}
+ f(...) {<...
+- hashcmp(OIDPTR1->hash, OIDPTR2->hash)
++ oidcmp(OIDPTR1, OIDPTR2)
+ ...>}
@@
-expression E1, E2;
+struct object_id *OIDPTR;
+struct object_id OID;
@@
-- hashcmp(E1->hash, E2.hash)
-+ oidcmp(E1, &E2)
+- hashcmp(OIDPTR->hash, OID.hash)
++ oidcmp(OIDPTR, &OID)
@@
-expression E1, E2;
+struct object_id *OIDPTR;
+struct object_id OID;
@@
-- hashcmp(E1.hash, E2->hash)
-+ oidcmp(&E1, E2)
+- hashcmp(OID.hash, OIDPTR->hash)
++ oidcmp(&OID, OIDPTR)
@@
-expression E1, E2;
+struct object_id *OIDPTR1;
+struct object_id *OIDPTR2;
@@
-- hashcpy(E1.hash, E2.hash)
-+ oidcpy(&E1, &E2)
+- oidcmp(OIDPTR1, OIDPTR2) == 0
++ oideq(OIDPTR1, OIDPTR2)
@@
-identifier f != oidcpy;
+identifier f != hasheq;
expression E1, E2;
@@
- f(...) {...
-- hashcpy(E1->hash, E2->hash)
-+ oidcpy(E1, E2)
- ...}
+ f(...) {<...
+- hashcmp(E1, E2) == 0
++ hasheq(E1, E2)
+ ...>}
@@
-expression E1, E2;
+struct object_id *OIDPTR1;
+struct object_id *OIDPTR2;
@@
-- hashcpy(E1->hash, E2.hash)
-+ oidcpy(E1, &E2)
+- oidcmp(OIDPTR1, OIDPTR2) != 0
++ !oideq(OIDPTR1, OIDPTR2)
@@
+identifier f != hasheq;
expression E1, E2;
@@
-- hashcpy(E1.hash, E2->hash)
-+ oidcpy(&E1, E2)
+ f(...) {<...
+- hashcmp(E1, E2) != 0
++ !hasheq(E1, E2)
+ ...>}
diff --git a/contrib/coccinelle/preincr.cocci b/contrib/coccinelle/preincr.cocci
new file mode 100644
index 0000000..7fe1e8d
--- /dev/null
+++ b/contrib/coccinelle/preincr.cocci
@@ -0,0 +1,5 @@
+@ preincrement @
+identifier i;
+@@
+- ++i > 1
++ i++
diff --git a/contrib/coccinelle/strbuf.cocci b/contrib/coccinelle/strbuf.cocci
index e34eada..d9ada69 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 0000000..2ee702e
--- /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 75724ca..4b59004 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 ;;
@@ -94,6 +95,70 @@ __git ()
${__git_dir:+--git-dir="$__git_dir"} "$@" 2>/dev/null
}
+# Removes backslash escaping, single quotes and double quotes from a word,
+# stores the result in the variable $dequoted_word.
+# 1: The word to dequote.
+__git_dequote ()
+{
+ local rest="$1" len ch
+
+ dequoted_word=""
+
+ while test -n "$rest"; do
+ len=${#dequoted_word}
+ dequoted_word="$dequoted_word${rest%%[\\\'\"]*}"
+ rest="${rest:$((${#dequoted_word}-$len))}"
+
+ case "${rest:0:1}" in
+ \\)
+ ch="${rest:1:1}"
+ case "$ch" in
+ $'\n')
+ ;;
+ *)
+ dequoted_word="$dequoted_word$ch"
+ ;;
+ esac
+ rest="${rest:2}"
+ ;;
+ \')
+ rest="${rest:1}"
+ len=${#dequoted_word}
+ dequoted_word="$dequoted_word${rest%%\'*}"
+ rest="${rest:$((${#dequoted_word}-$len+1))}"
+ ;;
+ \")
+ rest="${rest:1}"
+ while test -n "$rest" ; do
+ len=${#dequoted_word}
+ dequoted_word="$dequoted_word${rest%%[\\\"]*}"
+ rest="${rest:$((${#dequoted_word}-$len))}"
+ case "${rest:0:1}" in
+ \\)
+ ch="${rest:1:1}"
+ case "$ch" in
+ \"|\\|\$|\`)
+ dequoted_word="$dequoted_word$ch"
+ ;;
+ $'\n')
+ ;;
+ *)
+ dequoted_word="$dequoted_word\\$ch"
+ ;;
+ esac
+ rest="${rest:2}"
+ ;;
+ \")
+ rest="${rest:1}"
+ break
+ ;;
+ esac
+ done
+ ;;
+ esac
+ done
+}
+
# The following function is based on code from:
#
# bash_completion - programmable completion functions for bash 3.2+
@@ -266,13 +331,36 @@ __gitcomp ()
case "$cur_" in
--*=)
;;
+ --no-*)
+ local c i=0 IFS=$' \t\n'
+ for c in $1; do
+ if [[ $c == "--" ]]; then
+ continue
+ fi
+ c="$c${4-}"
+ if [[ $c == "$cur_"* ]]; then
+ case $c in
+ --*=|*.) ;;
+ *) c="$c " ;;
+ esac
+ COMPREPLY[i++]="${2-}$c"
+ fi
+ done
+ ;;
*)
local c i=0 IFS=$' \t\n'
for c in $1; do
+ if [[ $c == "--" ]]; then
+ c="--no-...${4-}"
+ if [[ $c == "$cur_"* ]]; then
+ COMPREPLY[i++]="${2-}$c "
+ fi
+ break
+ fi
c="$c${4-}"
if [[ $c == "$cur_"* ]]; then
case $c in
- --*=*|*.) ;;
+ *=|*.) ;;
*) c="$c " ;;
esac
COMPREPLY[i++]="${2-}$c"
@@ -284,7 +372,11 @@ __gitcomp ()
# Clear the variables caching builtins' options when (re-)sourcing
# the completion script.
-unset $(set |sed -ne 's/^\(__gitcomp_builtin_[a-zA-Z0-9_][a-zA-Z0-9_]*\)=.*/\1/p') 2>/dev/null
+if [[ -n ${ZSH_VERSION-} ]]; then
+ unset ${(M)${(k)parameters[@]}:#__gitcomp_builtin_*} 2>/dev/null
+else
+ unset $(compgen -v __gitcomp_builtin_)
+fi
# This function is equivalent to
#
@@ -309,7 +401,8 @@ __gitcomp_builtin ()
if [ -z "$options" ]; then
# leading and trailing spaces are significant to make
# option removal work correctly.
- options=" $(__git ${cmd/_/ } --git-completion-helper) $incl "
+ options=" $incl $(__git ${cmd/_/ } --git-completion-helper) " || return
+
for i in $excl; do
options="${options/ $i / }"
done
@@ -342,6 +435,24 @@ __gitcomp_nl ()
__gitcomp_nl_append "$@"
}
+# Fills the COMPREPLY array with prefiltered paths without any additional
+# processing.
+# 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 components.
+__gitcomp_file_direct ()
+{
+ local IFS=$'\n'
+
+ COMPREPLY=($1)
+
+ # use a hack to enable file mode in bash < 4
+ compopt -o filenames +o nospace 2>/dev/null ||
+ compgen -f /non-existing-dir/ >/dev/null ||
+ true
+}
+
# Generates completion reply with compgen from newline-separated possible
# completion filenames.
# It accepts 1 to 3 arguments:
@@ -361,7 +472,8 @@ __gitcomp_file ()
# use a hack to enable file mode in bash < 4
compopt -o filenames +o nospace 2>/dev/null ||
- compgen -f /non-existing-dir/ > /dev/null
+ compgen -f /non-existing-dir/ >/dev/null ||
+ true
}
# Execute 'git ls-files', unless the --committable option is specified, in
@@ -371,10 +483,12 @@ __gitcomp_file ()
__git_ls_files_helper ()
{
if [ "$2" == "--committable" ]; then
- __git -C "$1" diff-index --name-only --relative HEAD
+ __git -C "$1" -c core.quotePath=false diff-index \
+ --name-only --relative HEAD -- "${3//\\/\\\\}*"
else
# NOTE: $2 is not quoted in order to support multiple options
- __git -C "$1" ls-files --exclude-standard $2
+ __git -C "$1" -c core.quotePath=false ls-files \
+ --exclude-standard $2 -- "${3//\\/\\\\}*"
fi
}
@@ -385,17 +499,103 @@ __git_ls_files_helper ()
# If provided, only files within the specified directory are listed.
# Sub directories are never recursed. Path must have a trailing
# slash.
+# 3: List only paths matching this path component (optional).
__git_index_files ()
{
- local root="${2-.}" file
+ local root="$2" match="$3"
- __git_ls_files_helper "$root" "$1" |
- while read -r file; do
- case "$file" in
- ?*/*) echo "${file%%/*}" ;;
- *) echo "$file" ;;
- esac
- done | sort | uniq
+ __git_ls_files_helper "$root" "$1" "${match:-?}" |
+ awk -F / -v pfx="${2//\\/\\\\}" '{
+ paths[$1] = 1
+ }
+ END {
+ for (p in paths) {
+ if (substr(p, 1, 1) != "\"") {
+ # No special characters, easy!
+ print pfx p
+ continue
+ }
+
+ # The path is quoted.
+ p = dequote(p)
+ if (p == "")
+ continue
+
+ # 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 directory
+ # both quoted and unquoted.
+ if (p in paths)
+ # We have seen the same directory unquoted,
+ # skip it.
+ continue
+ else
+ print pfx p
+ }
+ }
+ function dequote(p, bs_idx, out, esc, esc_idx, dec) {
+ # Skip opening double quote.
+ p = substr(p, 2)
+
+ # Interpret backslash escape sequences.
+ while ((bs_idx = index(p, "\\")) != 0) {
+ out = out substr(p, 1, bs_idx - 1)
+ esc = substr(p, bs_idx + 1, 1)
+ p = substr(p, bs_idx + 2)
+
+ if ((esc_idx = index("abtvfr\"\\", esc)) != 0) {
+ # C-style one-character escape sequence.
+ out = out substr("\a\b\t\v\f\r\"\\",
+ esc_idx, 1)
+ } else if (esc == "n") {
+ # Uh-oh, a newline character.
+ # We cannot reliably put a pathname
+ # containing a newline into COMPREPLY,
+ # and the newline would create a mess.
+ # Skip this path.
+ return ""
+ } else {
+ # Must be a \nnn octal value, then.
+ dec = esc * 64 + \
+ substr(p, 1, 1) * 8 + \
+ substr(p, 2, 1)
+ out = out sprintf("%c", dec)
+ p = substr(p, 3)
+ }
+ }
+ # Drop closing double quote, if there is one.
+ # (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)
+ else
+ out = out p
+
+ return out
+ }'
+}
+
+# __git_complete_index_file requires 1 argument:
+# 1: the options to pass to ls-file
+#
+# The exception is --committable, which finds the files appropriate commit.
+__git_complete_index_file ()
+{
+ local dequoted_word pfx="" cur_
+
+ __git_dequote "$cur"
+
+ case "$dequoted_word" in
+ ?*/*)
+ pfx="${dequoted_word%/*}/"
+ cur_="${dequoted_word##*/}"
+ ;;
+ *)
+ cur_="$dequoted_word"
+ esac
+
+ __gitcomp_file_direct "$(__git_index_files "$1" "$pfx" "$cur_")"
}
# Lists branches from the local repository.
@@ -655,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
@@ -665,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
@@ -682,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_%...*}..."
@@ -714,26 +912,6 @@ __git_complete_revlist_file ()
esac
}
-
-# __git_complete_index_file requires 1 argument:
-# 1: the options to pass to ls-file
-#
-# The exception is --committable, which finds the files appropriate commit.
-__git_complete_index_file ()
-{
- local pfx="" cur_="$cur"
-
- case "$cur_" in
- ?*/*)
- pfx="${cur_%/*}"
- cur_="${cur_##*/}"
- pfx="${pfx}/"
- ;;
- esac
-
- __gitcomp_file "$(__git_index_files "$1" ${pfx:+"$pfx"})" "$pfx" "$cur_"
-}
-
__git_complete_file ()
{
__git_complete_revlist_file
@@ -765,6 +943,7 @@ __git_complete_remote_or_refspec ()
*) ;;
esac
;;
+ --multiple) no_complete_refspec=1; break ;;
-*) ;;
*) remote="$i"; break ;;
esac
@@ -824,136 +1003,30 @@ __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
}
-__git_commands () {
- if test -n "${GIT_TESTING_COMMAND_COMPLETION:-}"
- then
- printf "%s" "${GIT_TESTING_COMMAND_COMPLETION}"
- else
- git help -a|egrep '^ [a-zA-Z0-9]'
- fi
-}
-
-__git_list_all_commands ()
-{
- local i IFS=" "$'\n'
- for i in $(__git_commands)
- do
- case $i in
- *--*) : helper pattern;;
- *) echo $i;;
- esac
- done
-}
-
__git_all_commands=
__git_compute_all_commands ()
{
test -n "$__git_all_commands" ||
- __git_all_commands=$(__git_list_all_commands)
-}
-
-__git_list_porcelain_commands ()
-{
- local i IFS=" "$'\n'
- __git_compute_all_commands
- for i in $__git_all_commands
- do
- case $i in
- *--*) : helper pattern;;
- applymbox) : ask gittus;;
- applypatch) : ask gittus;;
- archimport) : import;;
- cat-file) : plumbing;;
- check-attr) : plumbing;;
- check-ignore) : plumbing;;
- check-mailmap) : plumbing;;
- check-ref-format) : plumbing;;
- checkout-index) : plumbing;;
- column) : internal helper;;
- commit-tree) : plumbing;;
- count-objects) : infrequent;;
- credential) : credentials;;
- credential-*) : credentials helper;;
- cvsexportcommit) : export;;
- cvsimport) : import;;
- cvsserver) : daemon;;
- daemon) : daemon;;
- diff-files) : plumbing;;
- diff-index) : plumbing;;
- diff-tree) : plumbing;;
- fast-import) : import;;
- fast-export) : export;;
- fsck-objects) : plumbing;;
- fetch-pack) : plumbing;;
- fmt-merge-msg) : plumbing;;
- for-each-ref) : plumbing;;
- hash-object) : plumbing;;
- http-*) : transport;;
- index-pack) : plumbing;;
- init-db) : deprecated;;
- local-fetch) : plumbing;;
- ls-files) : plumbing;;
- ls-remote) : plumbing;;
- ls-tree) : plumbing;;
- mailinfo) : plumbing;;
- mailsplit) : plumbing;;
- merge-*) : plumbing;;
- mktree) : plumbing;;
- mktag) : plumbing;;
- pack-objects) : plumbing;;
- pack-redundant) : plumbing;;
- pack-refs) : plumbing;;
- parse-remote) : plumbing;;
- patch-id) : plumbing;;
- prune) : plumbing;;
- prune-packed) : plumbing;;
- quiltimport) : import;;
- read-tree) : plumbing;;
- receive-pack) : plumbing;;
- remote-*) : transport;;
- rerere) : plumbing;;
- rev-list) : plumbing;;
- rev-parse) : plumbing;;
- runstatus) : plumbing;;
- sh-setup) : internal;;
- shell) : daemon;;
- show-ref) : plumbing;;
- send-pack) : plumbing;;
- show-index) : plumbing;;
- ssh-*) : transport;;
- stripspace) : plumbing;;
- symbolic-ref) : plumbing;;
- unpack-file) : plumbing;;
- unpack-objects) : plumbing;;
- update-index) : plumbing;;
- update-ref) : plumbing;;
- update-server-info) : daemon;;
- upload-archive) : plumbing;;
- upload-pack) : plumbing;;
- write-tree) : plumbing;;
- var) : infrequent;;
- verify-pack) : infrequent;;
- verify-tag) : plumbing;;
- *) echo $i;;
- esac
- done
-}
-
-__git_porcelain_commands=
-__git_compute_porcelain_commands ()
-{
- test -n "$__git_porcelain_commands" ||
- __git_porcelain_commands=$(__git_list_porcelain_commands)
+ __git_all_commands=$(__git --list-cmds=main,others,alias,nohelpers)
}
# Lists all set config variables starting with the given section prefix,
@@ -971,11 +1044,6 @@ __git_pretty_aliases ()
__git_get_config_variables "pretty"
}
-__git_aliases ()
-{
- __git_get_config_variables "alias"
-}
-
# __git_aliased_command requires 1 argument
__git_aliased_command ()
{
@@ -1001,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
@@ -1111,6 +1196,8 @@ __git_count_arguments ()
}
__git_whitespacelist="nowarn warn error error-all fix"
+__git_patchformat="mbox stgit stgit-series hg mboxrd"
+__git_showcurrentpatch="diff raw"
__git_am_inprogress_options="--skip --continue --resolved --abort --quit --show-current-patch"
_git_am ()
@@ -1125,8 +1212,16 @@ _git_am ()
__gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}"
return
;;
+ --patch-format=*)
+ __gitcomp "$__git_patchformat" "" "${cur##--patch-format=}"
+ return
+ ;;
+ --show-current-patch=*)
+ __gitcomp "$__git_showcurrentpatch" "" "${cur##--show-current-patch=}"
+ return
+ ;;
--*)
- __gitcomp_builtin am "--no-utf8" \
+ __gitcomp_builtin am "" \
"$__git_am_inprogress_options"
return
esac
@@ -1148,6 +1243,10 @@ _git_apply ()
_git_add ()
{
case "$cur" in
+ --chmod=*)
+ __gitcomp "+x -x" "" "${cur##--chmod=}"
+ return
+ ;;
--*)
__gitcomp_builtin add
return
@@ -1173,10 +1272,7 @@ _git_archive ()
return
;;
--*)
- __gitcomp "
- --format= --list --verbose
- --prefix= --remote= --exec= --output
- "
+ __gitcomp_builtin archive "--format= --list --verbose --prefix= --worktree-attributes"
return
;;
esac
@@ -1208,6 +1304,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"
@@ -1226,9 +1324,7 @@ _git_branch ()
__git_complete_refs --cur="${cur##--set-upstream-to=}"
;;
--*)
- __gitcomp_builtin branch "--no-color --no-abbrev
- --no-track --no-column
- "
+ __gitcomp_builtin branch
;;
*)
if [ $only_local_ref = "y" -a $has_r = "n" ]; then
@@ -1269,7 +1365,7 @@ _git_checkout ()
__gitcomp "diff3 merge" "" "${cur##--conflict=}"
;;
--*)
- __gitcomp_builtin checkout "--no-track --no-recurse-submodules"
+ __gitcomp_builtin checkout
;;
*)
# check if --track, --no-track, or --no-guess was specified
@@ -1284,18 +1380,9 @@ _git_checkout ()
esac
}
-_git_cherry ()
-{
- case "$cur" in
- --*)
- __gitcomp_builtin cherry
- return
- esac
-
- __git_complete_refs
-}
+__git_sequencer_inprogress_options="--continue --quit --abort --skip"
-__git_cherry_pick_inprogress_options="--continue --quit --abort"
+__git_cherry_pick_inprogress_options=$__git_sequencer_inprogress_options
_git_cherry_pick ()
{
@@ -1304,6 +1391,9 @@ _git_cherry_pick ()
__gitcomp "$__git_cherry_pick_inprogress_options"
return
fi
+
+ __git_complete_strategy && return
+
case "$cur" in
--*)
__gitcomp_builtin cherry-pick "" \
@@ -1330,9 +1420,20 @@ _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 "--no-single-branch"
+ __gitcomp_builtin clone
return
;;
esac
@@ -1365,7 +1466,7 @@ _git_commit ()
return
;;
--*)
- __gitcomp_builtin commit "--no-edit --verify"
+ __gitcomp_builtin commit
return
esac
@@ -1391,9 +1492,16 @@ __git_diff_algorithms="myers minimal patience histogram"
__git_diff_submodule_formats="diff log short"
+__git_color_moved_opts="no default plain blocks zebra dimmed-zebra"
+
+__git_color_moved_ws_opts="no ignore-space-at-eol ignore-space-change
+ ignore-all-space allow-indentation-change"
+
__git_diff_common_options="--stat --numstat --shortstat --summary
--patch-with-stat --name-only --name-status --color
--no-color --color-words --no-renames --check
+ --color-moved --color-moved= --no-color-moved
+ --color-moved-ws= --no-color-moved-ws
--full-index --binary --abbrev --diff-filter=
--find-copies-harder --ignore-cr-at-eol
--text --ignore-space-at-eol --ignore-space-change
@@ -1407,6 +1515,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 ()
@@ -1422,6 +1532,14 @@ _git_diff ()
__gitcomp "$__git_diff_submodule_formats" "" "${cur##--submodule=}"
return
;;
+ --color-moved=*)
+ __gitcomp "$__git_color_moved_opts" "" "${cur##--color-moved=}"
+ return
+ ;;
+ --color-moved-ws=*)
+ __gitcomp "$__git_color_moved_ws_opts" "" "${cur##--color-moved-ws=}"
+ return
+ ;;
--*)
__gitcomp "--cached --staged --pickaxe-all --pickaxe-regex
--base --ours --theirs --no-index
@@ -1434,7 +1552,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 ()
@@ -1467,21 +1586,21 @@ _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 "--no-tags"
+ __gitcomp_builtin fetch
return
;;
esac
__git_complete_remote_or_refspec
}
-__git_format_patch_options="
- --stdout --attach --no-attach --thread --thread= --no-thread
- --numbered --start-number --numbered-files --keep-subject --signoff
- --signature --no-signature --in-reply-to= --cc= --full-index --binary
- --not --all --cover-letter --no-prefix --src-prefix= --dst-prefix=
- --inline --suffix= --ignore-if-in-upstream --subject-prefix=
- --output-directory --reroll-count --to= --quiet --notes
+__git_format_patch_extra_options="
+ --full-index --not --all --no-prefix --src-prefix=
+ --dst-prefix= --notes
"
_git_format_patch ()
@@ -1494,7 +1613,7 @@ _git_format_patch ()
return
;;
--*)
- __gitcomp "$__git_format_patch_options"
+ __gitcomp_builtin format-patch "$__git_format_patch_extra_options"
return
;;
esac
@@ -1505,7 +1624,7 @@ _git_fsck ()
{
case "$cur" in
--*)
- __gitcomp_builtin fsck "--no-reflogs"
+ __gitcomp_builtin fsck
return
;;
esac
@@ -1583,13 +1702,12 @@ _git_help ()
return
;;
esac
- __git_compute_all_commands
- __gitcomp "$__git_all_commands $(__git_aliases)
- attributes cli core-tutorial cvs-migration
- diffcore everyday gitk glossary hooks ignore modules
- namespaces repository-layout revisions tutorial tutorial-2
- workflows
- "
+ if test -n "$GIT_TESTING_ALL_COMMAND_LIST"
+ then
+ __gitcomp "$GIT_TESTING_ALL_COMMAND_LIST $(__git --list-cmds=alias,list-guide) gitk"
+ else
+ __gitcomp "$(__git --list-cmds=main,nohelpers,alias,list-guide) gitk"
+ fi
}
_git_init ()
@@ -1612,7 +1730,7 @@ _git_ls_files ()
{
case "$cur" in
--*)
- __gitcomp_builtin ls-files "--no-empty-directory"
+ __gitcomp_builtin ls-files
return
;;
esac
@@ -1668,8 +1786,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 ()
{
@@ -1715,6 +1833,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
@@ -1722,19 +1844,23 @@ _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
+ --patch --no-patch
"
return
;;
@@ -1763,12 +1889,7 @@ _git_merge ()
case "$cur" in
--*)
- __gitcomp_builtin merge "--no-rerere-autoupdate
- --no-commit --no-edit --no-ff
- --no-log --no-progress
- --no-squash --no-stat
- --no-verify-signatures
- "
+ __gitcomp_builtin merge
return
esac
__git_complete_refs
@@ -1782,7 +1903,7 @@ _git_mergetool ()
return
;;
--*)
- __gitcomp "--tool= --prompt --no-prompt"
+ __gitcomp "--tool= --prompt --no-prompt --gui --no-gui"
return
;;
esac
@@ -1867,10 +1988,7 @@ _git_pull ()
return
;;
--*)
- __gitcomp_builtin pull "--no-autostash --no-commit --no-edit
- --no-ff --no-log --no-progress --no-rebase
- --no-squash --no-stat --no-tags
- --no-verify-signatures"
+ __gitcomp_builtin pull
return
;;
@@ -1929,15 +2047,32 @@ _git_push ()
__git_complete_remote_or_refspec
}
+_git_range_diff ()
+{
+ case "$cur" in
+ --*)
+ __gitcomp "
+ --creation-factor= --no-dual-color
+ $__git_diff_common_options
+ "
+ return
+ ;;
+ esac
+ __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
@@ -1946,20 +2081,13 @@ _git_rebase ()
__gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}"
return
;;
+ --onto=*)
+ __git_complete_refs --cur="${cur##--onto=}"
+ return
+ ;;
--*)
- __gitcomp "
- --onto --merge --strategy --interactive
- --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
@@ -2019,7 +2147,7 @@ _git_send_email ()
return
;;
--*)
- __gitcomp "--annotate --bcc --cc --cc-cmd --chain-reply-to
+ __gitcomp_builtin send-email "--annotate --bcc --cc --cc-cmd --chain-reply-to
--compose --confirm= --dry-run --envelope-sender
--from --identity
--in-reply-to --no-chain-reply-to --no-signed-off-by-cc
@@ -2028,7 +2156,7 @@ _git_send_email ()
--smtp-server-port --smtp-encryption= --smtp-user
--subject --suppress-cc= --suppress-from --thread --to
--validate --no-validate
- $__git_format_patch_options"
+ $__git_format_patch_extra_options"
return
;;
esac
@@ -2061,7 +2189,7 @@ _git_status ()
return
;;
--*)
- __gitcomp_builtin status "--no-column"
+ __gitcomp_builtin status
return
;;
esac
@@ -2086,6 +2214,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
@@ -2108,489 +2274,287 @@ __git_config_get_set_variables ()
__git config $config_file --name-only --list
}
-_git_config ()
+__git_config_vars=
+__git_compute_config_vars ()
{
- case "$prev" in
+ test -n "$__git_config_vars" ||
+ __git_config_vars="$(git help --config-for-completion | sort -u)"
+}
+
+# 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="$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="${varname,,}"
+ else
+ 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 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.aliasesfiletype)
- __gitcomp "mutt mailrc pine elm gnus"
+ sendemail.aliasfiletype)
+ __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_"
+ argPrompt cmd confirm needsFile noConsole noRescan
+ prompt revPrompt revUnmerged title
+ " "$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_"
+ receivepack uploadpack tagOpt pushurl
+ " "$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_"
- return
- ;;
- esac
- __gitcomp "
- add.ignoreErrors
- advice.amWorkDir
- advice.commitBeforeMerge
- advice.detachedHead
- advice.implicitIdentity
- advice.pushAlreadyExists
- advice.pushFetchFirst
- advice.pushNeedsForce
- advice.pushNonFFCurrent
- advice.pushNonFFMatching
- advice.pushUpdateRejected
- advice.resolveConflict
- advice.rmHints
- advice.statusHints
- advice.statusUoption
- advice.ignoredHook
- alias.
- am.keepcr
- am.threeWay
- apply.ignorewhitespace
- apply.whitespace
- branch.autosetupmerge
- branch.autosetuprebase
- browser.
- clean.requireForce
- color.branch
- color.branch.current
- color.branch.local
- color.branch.plain
- color.branch.remote
- color.decorate.HEAD
- color.decorate.branch
- color.decorate.remoteBranch
- color.decorate.stash
- color.decorate.tag
- color.diff
- color.diff.commit
- color.diff.frag
- color.diff.func
- color.diff.meta
- color.diff.new
- color.diff.old
- color.diff.plain
- color.diff.whitespace
- color.grep
- color.grep.context
- color.grep.filename
- color.grep.function
- color.grep.linenumber
- color.grep.match
- color.grep.selected
- color.grep.separator
- color.interactive
- color.interactive.error
- color.interactive.header
- color.interactive.help
- color.interactive.prompt
- color.pager
- color.showbranch
- color.status
- color.status.added
- color.status.changed
- color.status.header
- color.status.localBranch
- color.status.nobranch
- color.status.remoteBranch
- color.status.unmerged
- color.status.untracked
- color.status.updated
- color.ui
- commit.cleanup
- commit.gpgSign
- commit.status
- commit.template
- commit.verbose
- core.abbrev
- core.askpass
- core.attributesfile
- core.autocrlf
- core.bare
- core.bigFileThreshold
- core.checkStat
- core.commentChar
- core.compression
- core.createObject
- core.deltaBaseCacheLimit
- core.editor
- core.eol
- core.excludesfile
- core.fileMode
- core.fsyncobjectfiles
- core.gitProxy
- core.hideDotFiles
- core.hooksPath
- core.ignoreStat
- core.ignorecase
- core.logAllRefUpdates
- core.loosecompression
- core.notesRef
- core.packedGitLimit
- core.packedGitWindowSize
- core.packedRefsTimeout
- core.pager
- core.precomposeUnicode
- core.preferSymlinkRefs
- core.preloadindex
- core.protectHFS
- core.protectNTFS
- core.quotepath
- core.repositoryFormatVersion
- core.safecrlf
- core.sharedRepository
- core.sparseCheckout
- core.splitIndex
- core.sshCommand
- core.symlinks
- core.trustctime
- core.untrackedCache
- core.warnAmbiguousRefs
- core.whitespace
- core.worktree
- credential.helper
- credential.useHttpPath
- credential.username
- credentialCache.ignoreSIGHUP
- diff.autorefreshindex
- diff.external
- diff.ignoreSubmodules
- diff.mnemonicprefix
- diff.noprefix
- diff.renameLimit
- diff.renames
- diff.statGraphWidth
- diff.submodule
- diff.suppressBlankEmpty
- diff.tool
- diff.wordRegex
- diff.algorithm
- difftool.
- difftool.prompt
- fetch.recurseSubmodules
- fetch.unpackLimit
- format.attach
- format.cc
- format.coverLetter
- format.from
- format.headers
- format.numbered
- format.pretty
- format.signature
- format.signoff
- format.subjectprefix
- format.suffix
- format.thread
- format.to
- gc.
- gc.aggressiveDepth
- gc.aggressiveWindow
- gc.auto
- gc.autoDetach
- gc.autopacklimit
- gc.logExpiry
- gc.packrefs
- gc.pruneexpire
- gc.reflogexpire
- gc.reflogexpireunreachable
- gc.rerereresolved
- gc.rerereunresolved
- gc.worktreePruneExpire
- gitcvs.allbinary
- gitcvs.commitmsgannotation
- gitcvs.dbTableNamePrefix
- gitcvs.dbdriver
- gitcvs.dbname
- gitcvs.dbpass
- gitcvs.dbuser
- gitcvs.enabled
- gitcvs.logfile
- gitcvs.usecrlfattr
- guitool.
- gui.blamehistoryctx
- gui.commitmsgwidth
- gui.copyblamethreshold
- gui.diffcontext
- gui.encoding
- gui.fastcopyblame
- gui.matchtrackingbranch
- gui.newbranchtemplate
- gui.pruneduringfetch
- gui.spellingdictionary
- gui.trustmtime
- help.autocorrect
- help.browser
- help.format
- http.lowSpeedLimit
- http.lowSpeedTime
- http.maxRequests
- http.minSessions
- http.noEPSV
- http.postBuffer
- http.proxy
- http.sslCipherList
- http.sslVersion
- http.sslCAInfo
- http.sslCAPath
- http.sslCert
- http.sslCertPasswordProtected
- http.sslKey
- http.sslVerify
- http.useragent
- i18n.commitEncoding
- i18n.logOutputEncoding
- imap.authMethod
- imap.folder
- imap.host
- imap.pass
- imap.port
- imap.preformattedHTML
- imap.sslverify
- imap.tunnel
- imap.user
- init.templatedir
- instaweb.browser
- instaweb.httpd
- instaweb.local
- instaweb.modulepath
- instaweb.port
- interactive.singlekey
- log.date
- log.decorate
- log.showroot
- mailmap.file
- man.
- man.viewer
- merge.
- merge.conflictstyle
- merge.log
- merge.renameLimit
- merge.renormalize
- merge.stat
- merge.tool
- merge.verbosity
- mergetool.
- mergetool.keepBackup
- mergetool.keepTemporaries
- mergetool.prompt
- notes.displayRef
- notes.rewrite.
- notes.rewrite.amend
- notes.rewrite.rebase
- notes.rewriteMode
- notes.rewriteRef
- pack.compression
- pack.deltaCacheLimit
- pack.deltaCacheSize
- pack.depth
- pack.indexVersion
- pack.packSizeLimit
- pack.threads
- pack.window
- pack.windowMemory
- pager.
- pretty.
- pull.octopus
- pull.twohead
- push.default
- push.followTags
- rebase.autosquash
- rebase.stat
- receive.autogc
- receive.denyCurrentBranch
- receive.denyDeleteCurrent
- receive.denyDeletes
- receive.denyNonFastForwards
- receive.fsckObjects
- receive.unpackLimit
- receive.updateserverinfo
- remote.pushdefault
- remotes.
- repack.usedeltabaseoffset
- rerere.autoupdate
- rerere.enabled
- sendemail.
- sendemail.aliasesfile
- sendemail.aliasfiletype
- sendemail.bcc
- sendemail.cc
- sendemail.cccmd
- sendemail.chainreplyto
- sendemail.confirm
- sendemail.envelopesender
- sendemail.from
- sendemail.identity
- sendemail.multiedit
- sendemail.signedoffbycc
- sendemail.smtpdomain
- sendemail.smtpencryption
- sendemail.smtppass
- sendemail.smtpserver
- sendemail.smtpserveroption
- sendemail.smtpserverport
- sendemail.smtpuser
- sendemail.suppresscc
- sendemail.suppressfrom
- sendemail.thread
- sendemail.to
- sendemail.tocmd
- sendemail.validate
- sendemail.smtpbatchsize
- sendemail.smtprelogindelay
- showbranch.default
- status.relativePaths
- status.showUntrackedFiles
- status.submodulesummary
- submodule.
- tar.umask
- transfer.unpackLimit
- url.
- user.email
- user.name
- user.signingkey
- web.browser
- branch. remote.
- "
+ local pfx="${cur_%.*}."
+ cur_="${cur_##*.}"
+ __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur_" "$sfx"
+ return
+ ;;
+ *.*)
+ __git_compute_config_vars
+ __gitcomp "$__git_config_vars" "" "$cur_" "$sfx"
+ ;;
+ *)
+ __git_compute_config_vars
+ __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
}
_git_remote ()
@@ -2614,7 +2578,7 @@ _git_remote ()
case "$subcommand,$cur" in
add,--*)
- __gitcomp_builtin remote_add "--no-tags"
+ __gitcomp_builtin remote_add
;;
add,*)
;;
@@ -2631,7 +2595,7 @@ _git_remote ()
__gitcomp_builtin remote_update
;;
update,*)
- __gitcomp "$(__git_get_config_variables "remotes")"
+ __gitcomp "$(__git_remotes) $(__git_get_config_variables "remotes")"
;;
set-url,--*)
__gitcomp_builtin remote_set-url
@@ -2651,6 +2615,10 @@ _git_remote ()
_git_replace ()
{
case "$cur" in
+ --format=*)
+ __gitcomp "short medium long" "" "${cur##--format=}"
+ return
+ ;;
--*)
__gitcomp_builtin replace
return
@@ -2683,7 +2651,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 ()
{
@@ -2692,9 +2675,10 @@ _git_revert ()
__gitcomp "$__git_revert_inprogress_options"
return
fi
+ __git_complete_strategy && return
case "$cur" in
--*)
- __gitcomp_builtin revert "--no-edit" \
+ __gitcomp_builtin revert "" \
"$__git_revert_inprogress_options"
return
;;
@@ -2750,8 +2734,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
@@ -2764,13 +2749,34 @@ _git_show_branch ()
{
case "$cur" in
--*)
- __gitcomp_builtin show-branch "--no-color"
+ __gitcomp_builtin show-branch
return
;;
esac
__git_complete_revlist
}
+_git_sparse_checkout ()
+{
+ local subcommands="list init set disable"
+ local subcommand="$(__git_find_on_cmdline "$subcommands")"
+ if [ -z "$subcommand" ]; then
+ __gitcomp "$subcommands"
+ return
+ fi
+
+ case "$subcommand,$cur" in
+ init,--*)
+ __gitcomp "--cone"
+ ;;
+ set,--*)
+ __gitcomp "--stdin"
+ ;;
+ *)
+ ;;
+ esac
+}
+
_git_stash ()
{
local save_opts='--all --keep-index --no-keep-index --quiet --patch --include-untracked'
@@ -2809,6 +2815,9 @@ _git_stash ()
drop,--*)
__gitcomp "--quiet"
;;
+ list,--*)
+ __gitcomp "--name-status --oneline --patch-with-stat"
+ ;;
show,--*|branch,--*)
;;
branch,*)
@@ -2833,7 +2842,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
@@ -2864,6 +2873,9 @@ _git_submodule ()
--force --rebase --merge --reference --depth --recursive --jobs
"
;;
+ set-branch,--*)
+ __gitcomp "--default --branch"
+ ;;
summary,--*)
__gitcomp "--cached --files --summary-limit"
;;
@@ -2894,6 +2906,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="
@@ -3015,33 +3028,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
+ ;;
+ *,--*)
+ __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
;;
- remove,--*)
- __gitcomp "--force"
+ -*) # 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 () {
@@ -3057,7 +3120,7 @@ __git_complete_common () {
__git_cmds_with_parseopt_helper=
__git_support_parseopt_helper () {
test -n "$__git_cmds_with_parseopt_helper" ||
- __git_cmds_with_parseopt_helper="$(__git --list-parseopt-builtins)"
+ __git_cmds_with_parseopt_helper="$(__git --list-cmds=parseopt)"
case " $__git_cmds_with_parseopt_helper " in
*" $1 "*)
@@ -3072,10 +3135,17 @@ __git_support_parseopt_helper () {
__git_complete_command () {
local command="$1"
local completion_func="_git_${command//-/_}"
- if declare -f $completion_func >/dev/null 2>/dev/null; then
+ if ! declare -f $completion_func >/dev/null 2>/dev/null &&
+ declare -f _completion_loader >/dev/null 2>/dev/null
+ then
+ _completion_loader "git-$command"
+ fi
+ if declare -f $completion_func >/dev/null 2>/dev/null
+ then
$completion_func
return 0
- elif __git_support_parseopt_helper "$command"; then
+ elif __git_support_parseopt_helper "$command"
+ then
__git_complete_common "$command"
return 0
else
@@ -3113,7 +3183,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
;;
@@ -3136,8 +3210,14 @@ __git_main ()
--help
"
;;
- *) __git_compute_porcelain_commands
- __gitcomp "$__git_porcelain_commands $(__git_aliases)" ;;
+ *)
+ if test -n "$GIT_TESTING_PORCELAIN_COMMAND_LIST"
+ then
+ __gitcomp "$GIT_TESTING_PORCELAIN_COMMAND_LIST"
+ else
+ __gitcomp "$(__git --list-cmds=list-mainporcelain,others,nohelpers,alias,list-complete,config)"
+ fi
+ ;;
esac
return
fi
@@ -3175,7 +3255,10 @@ __gitk_main ()
__git_complete_revlist
}
-if [[ -n ${ZSH_VERSION-} ]]; then
+if [[ -n ${ZSH_VERSION-} ]] &&
+ # Don't define these functions when sourced from 'git-completion.zsh',
+ # it has its own implementations.
+ [[ -z ${GIT_SOURCING_ZSH_COMPLETION-} ]]; then
echo "WARNING: this script is deprecated, please see git-completion.zsh" 1>&2
autoload -U +X compinit && compinit
@@ -3224,13 +3307,22 @@ if [[ -n ${ZSH_VERSION-} ]]; then
compadd -Q -S "${4- }" -p "${2-}" -- ${=1} && _ret=0
}
+ __gitcomp_file_direct ()
+ {
+ emulate -L zsh
+
+ local IFS=$'\n'
+ compset -P '*[=:]'
+ compadd -f -- ${=1} && _ret=0
+ }
+
__gitcomp_file ()
{
emulate -L zsh
local IFS=$'\n'
compset -P '*[=:]'
- compadd -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 c3521fb..ce47e86 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)
@@ -39,7 +40,7 @@ if [ -z "$script" ]; then
test -f $e && script="$e" && break
done
fi
-ZSH_VERSION='' . "$script"
+GIT_SOURCING_ZSH_COMPLETION=y . "$script"
__gitcomp ()
{
@@ -93,13 +94,22 @@ __gitcomp_nl_append ()
compadd -Q -S "${4- }" -p "${2-}" -- ${=1} && _ret=0
}
+__gitcomp_file_direct ()
+{
+ emulate -L zsh
+
+ local IFS=$'\n'
+ compset -P '*[=:]'
+ compadd -f -- ${=1} && _ret=0
+}
+
__gitcomp_file ()
{
emulate -L zsh
local IFS=$'\n'
compset -P '*[=:]'
- compadd -Q -p "${2-}" -f -- ${=1} && _ret=0
+ compadd -p "${2-}" -f -- ${=1} && _ret=0
}
__git_zsh_bash_func ()
@@ -140,9 +150,11 @@ __git_zsh_cmd_common ()
push:'update remote refs along with associated objects'
rebase:'forward-port local commits to the updated upstream head'
reset:'reset current HEAD to the specified state'
+ restore:'restore working tree files'
rm:'remove files from the working tree and from the index'
show:'show various types of objects'
status:'show the working tree status'
+ switch:'switch branches'
tag:'create, list, delete or verify a tag object signed with GPG')
_describe -t common-commands 'common commands' list && _ret=0
}
diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh
index 983e419..014cd7c 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)
@@ -398,11 +429,7 @@ __git_ps1 ()
__git_eread "$g/rebase-merge/head-name" b
__git_eread "$g/rebase-merge/msgnum" step
__git_eread "$g/rebase-merge/end" total
- if [ -f "$g/rebase-merge/interactive" ]; then
- r="|REBASE-i"
- else
- r="|REBASE-m"
- fi
+ r="|REBASE"
else
if [ -d "$g/rebase-apply" ]; then
__git_eread "$g/rebase-apply/next" step
@@ -417,10 +444,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/convert-grafts-to-replace-refs.sh b/contrib/convert-grafts-to-replace-refs.sh
deleted file mode 100755
index 0cbc917..0000000
--- a/contrib/convert-grafts-to-replace-refs.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/sh
-
-# You should execute this script in the repository where you
-# want to convert grafts to replace refs.
-
-GRAFTS_FILE="${GIT_DIR:-.git}/info/grafts"
-
-. $(git --exec-path)/git-sh-setup
-
-test -f "$GRAFTS_FILE" || die "Could not find graft file: '$GRAFTS_FILE'"
-
-grep '^[^# ]' "$GRAFTS_FILE" |
-while read definition
-do
- if test -n "$definition"
- then
- echo "Converting: $definition"
- git replace --graft $definition ||
- die "Conversion failed for: $definition"
- fi
-done
-
-mv "$GRAFTS_FILE" "$GRAFTS_FILE.bak" ||
- die "Could not rename '$GRAFTS_FILE' to '$GRAFTS_FILE.bak'"
-
-echo "Success!"
-echo "All the grafts in '$GRAFTS_FILE' have been converted to replace refs!"
-echo "The grafts file '$GRAFTS_FILE' has been renamed: '$GRAFTS_FILE.bak'"
diff --git a/contrib/coverage-diff.sh b/contrib/coverage-diff.sh
new file mode 100755
index 0000000..4ec419f
--- /dev/null
+++ b/contrib/coverage-diff.sh
@@ -0,0 +1,108 @@
+#!/bin/sh
+
+# Usage: Run 'contrib/coverage-diff.sh <version1> <version2>' from source-root
+# after running
+#
+# make coverage-test
+# make coverage-report
+#
+# while checked out at <version2>. This script combines the *.gcov files
+# generated by the 'make' commands above with 'git diff <version1> <version2>'
+# to report new lines that are not covered by the test suite.
+
+V1=$1
+V2=$2
+
+diff_lines () {
+ perl -e '
+ my $line_num;
+ while (<>) {
+ # Hunk header? Grab the beginning in postimage.
+ if (/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/) {
+ $line_num = $1;
+ next;
+ }
+
+ # Have we seen a hunk? Ignore "diff --git" etc.
+ next unless defined $line_num;
+
+ # Deleted line? Ignore.
+ if (/^-/) {
+ next;
+ }
+
+ # Show only the line number of added lines.
+ if (/^\+/) {
+ print "$line_num\n";
+ }
+ # Either common context or added line appear in
+ # the postimage. Count it.
+ $line_num++;
+ }
+ '
+}
+
+files=$(git diff --name-only "$V1" "$V2" -- \*.c)
+
+# create empty file
+>coverage-data.txt
+
+for file in $files
+do
+ git diff "$V1" "$V2" -- "$file" |
+ diff_lines |
+ sort >new_lines.txt
+
+ if ! test -s new_lines.txt
+ then
+ continue
+ fi
+
+ hash_file=$(echo $file | sed "s/\//\#/")
+
+ if ! test -s "$hash_file.gcov"
+ then
+ continue
+ fi
+
+ sed -ne '/#####:/{
+ s/ #####://
+ s/:.*//
+ s/ //g
+ p
+ }' "$hash_file.gcov" |
+ sort >uncovered_lines.txt
+
+ comm -12 uncovered_lines.txt new_lines.txt |
+ sed -e 's/$/\)/' |
+ sed -e 's/^/ /' >uncovered_new_lines.txt
+
+ grep -q '[^[:space:]]' <uncovered_new_lines.txt &&
+ echo $file >>coverage-data.txt &&
+ git blame -s "$V2" -- "$file" |
+ sed 's/\t//g' |
+ grep -f uncovered_new_lines.txt >>coverage-data.txt &&
+ echo >>coverage-data.txt
+
+ rm -f new_lines.txt uncovered_lines.txt uncovered_new_lines.txt
+done
+
+cat coverage-data.txt
+
+echo "Commits introducing uncovered code:"
+
+commit_list=$(cat coverage-data.txt |
+ grep -E '^[0-9a-f]{7,} ' |
+ awk '{print $1;}' |
+ sort |
+ uniq)
+
+(
+ for commit in $commit_list
+ do
+ git log --no-decorate --pretty=format:'%an %h: %s' -1 $commit
+ echo
+ done
+) | sort
+
+rm coverage-data.txt
diff --git a/contrib/credential/netrc/.gitignore b/contrib/credential/netrc/.gitignore
new file mode 100644
index 0000000..d41cdde
--- /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 51b7613..c284fb8 100644
--- a/contrib/credential/netrc/Makefile
+++ b/contrib/credential/netrc/Makefile
@@ -1,5 +1,30 @@
-test:
- ./test.pl
+# The default target of this Makefile is...
+all::
-testverbose:
- ./test.pl -d -v
+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: 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 1571a7b..bc57cc6 100755
--- a/contrib/credential/netrc/git-credential-netrc
+++ b/contrib/credential/netrc/git-credential-netrc.perl
@@ -5,8 +5,9 @@ use warnings;
use Getopt::Long;
use File::Basename;
+use Git;
-my $VERSION = "0.1";
+my $VERSION = "0.2";
my %options = (
help => 0,
@@ -54,6 +55,7 @@ GetOptions(\%options,
"insecure|k",
"verbose|v",
"file|f=s@",
+ 'gpg|g:s',
);
if ($options{help}) {
@@ -62,27 +64,31 @@ if ($options{help}) {
print <<EOHIPPUS;
-$0 [-f AUTHFILE1] [-f AUTHFILEN] [-d] [-v] [-k] get
+$0 [(-f <authfile>)...] [-g <program>] [-d] [-v] [-k] get
Version $VERSION by tzz\@lifelogs.com. License: BSD.
Options:
- -f|--file AUTHFILE : specify netrc-style files. Files with the .gpg extension
- will be decrypted by GPG before parsing. Multiple -f
- arguments are OK. They are processed in order, and the
- first matching entry found is returned via the credential
- helper protocol (see below).
+ -f|--file <authfile>: specify netrc-style files. Files with the .gpg
+ extension will be decrypted by GPG before parsing.
+ Multiple -f arguments are OK. They are processed in
+ order, and the first matching entry found is returned
+ via the credential helper protocol (see below).
- When no -f option is given, .authinfo.gpg, .netrc.gpg,
- .authinfo, and .netrc files in your home directory are used
- in this order.
+ When no -f option is given, .authinfo.gpg, .netrc.gpg,
+ .authinfo, and .netrc files in your home directory are
+ used in this order.
- -k|--insecure : ignore bad file ownership or permissions
+ -g|--gpg <program> : specify the program for GPG. By default, this is the
+ value of gpg.program in the git repository or global
+ option or gpg.
- -d|--debug : turn on debugging (developer info)
+ -k|--insecure : ignore bad file ownership or permissions
- -v|--verbose : be more verbose (show files and information found)
+ -d|--debug : turn on debugging (developer info)
+
+ -v|--verbose : be more verbose (show files and information found)
To enable this credential helper:
@@ -99,8 +105,9 @@ in the path.)
git config credential.helper '$shortname -f AUTHFILE -v'
-Only "get" mode is supported by this credential helper. It opens every AUTHFILE
-and looks for the first entry that matches the requested search criteria:
+Only "get" mode is supported by this credential helper. It opens every
+<authfile> and looks for the first entry that matches the requested search
+criteria:
'port|protocol':
The protocol that will be used (e.g., https). (protocol=X)
@@ -120,7 +127,7 @@ host=github.com
protocol=https
username=tzz
-this credential helper will look for the first entry in every AUTHFILE that
+this credential helper will look for the first entry in every <authfile> that
matches
machine github.com port https login tzz
@@ -137,8 +144,8 @@ Then, the helper will print out whatever tokens it got from the entry, including
back to "protocol". Any redundant entry tokens (part of the original query) are
skipped.
-Again, note that only the first matching entry from all the AUTHFILEs, processed
-in the sequence given on the command line, is used.
+Again, note that only the first matching entry from all the <authfile>s,
+processed in the sequence given on the command line, is used.
Netrc/authinfo tokens can be quoted as 'STRING' or "STRING".
@@ -152,7 +159,7 @@ EOHIPPUS
my $mode = shift @ARGV;
# Credentials must get a parameter, so die if it's missing.
-die "Syntax: $0 [-f AUTHFILE1] [-f AUTHFILEN] [-d] get" unless defined $mode;
+die "Syntax: $0 [(-f <authfile>)...] [-d] get" unless defined $mode;
# Only support 'get' mode; with any other unsupported ones we just exit.
exit 0 unless $mode eq 'get';
@@ -172,6 +179,8 @@ unless (scalar @$files) {
$files = $options{file} = [ map { glob $_ } @candidates ];
}
+load_config(\%options);
+
my $query = read_credential_data_from_stdin();
FILE:
@@ -233,7 +242,7 @@ sub load_netrc {
my $io;
if ($gpgmode) {
- my @cmd = (qw(gpg --decrypt), $file);
+ my @cmd = ($options{'gpg'}, qw(--decrypt), $file);
log_verbose("Using GPG to open $file: [@cmd]");
open $io, "-|", @cmd;
} else {
@@ -410,6 +419,14 @@ sub print_credential_data {
printf "%s=%s\n", $git_token, $entry->{$git_token};
}
}
+sub load_config {
+ # load settings from git config
+ my $options = shift;
+ # set from command argument, gpg.program option, or default to gpg
+ $options->{'gpg'} //= Git::config('gpg.program')
+ // 'gpg';
+ log_verbose("using $options{'gpg'} for GPG operations");
+}
sub log_verbose {
return unless $options{verbose};
printf STDERR @_;
diff --git a/contrib/credential/netrc/t-git-credential-netrc.sh b/contrib/credential/netrc/t-git-credential-netrc.sh
new file mode 100755
index 0000000..07227d0
--- /dev/null
+++ b/contrib/credential/netrc/t-git-credential-netrc.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+(
+ cd ../../../t
+ test_description='git-credential-netrc'
+ . ./test-lib.sh
+
+ if ! test_have_prereq PERL; then
+ skip_all='skipping perl interface tests, perl not available'
+ test_done
+ fi
+
+ perl -MTest::More -e 0 2>/dev/null || {
+ skip_all="Perl Test::More unavailable, skipping test"
+ test_done
+ }
+
+ # set up test repository
+
+ test_expect_success \
+ 'set up test repository' \
+ 'git config --add gpg.program test.git-config-gpg'
+
+ # The external test will outputs its own plan
+ test_external_has_tap=1
+
+ export PERL5LIB="$GITPERLLIB"
+ test_external \
+ 'git-credential-netrc' \
+ perl "$GIT_BUILD_DIR"/contrib/credential/netrc/test.pl
+
+ test_done
+)
diff --git a/contrib/credential/netrc/test.command-option-gpg b/contrib/credential/netrc/test.command-option-gpg
new file mode 100755
index 0000000..d8f1285
--- /dev/null
+++ b/contrib/credential/netrc/test.command-option-gpg
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo machine command-option-gpg login username password password
diff --git a/contrib/credential/netrc/test.git-config-gpg b/contrib/credential/netrc/test.git-config-gpg
new file mode 100755
index 0000000..65cf594
--- /dev/null
+++ b/contrib/credential/netrc/test.git-config-gpg
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo machine git-config-gpg login username password password
diff --git a/contrib/credential/netrc/test.netrc.gpg b/contrib/credential/netrc/test.netrc.gpg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/credential/netrc/test.netrc.gpg
diff --git a/contrib/credential/netrc/test.pl b/contrib/credential/netrc/test.pl
index 169b646..c0fb371 100755
--- a/contrib/credential/netrc/test.pl
+++ b/contrib/credential/netrc/test.pl
@@ -2,82 +2,115 @@
use warnings;
use strict;
-use Test;
+use Test::More qw(no_plan);
+use File::Basename;
+use File::Spec::Functions qw(:DEFAULT rel2abs);
use IPC::Open2;
-BEGIN { plan tests => 15 }
+BEGIN {
+ # t-git-credential-netrc.sh kicks off our testing, so we have to go
+ # from there.
+ Test::More->builder->current_test(1);
+}
my @global_credential_args = @ARGV;
-my $netrc = './test.netrc';
-print "# Testing insecure file, nothing should be found\n";
+my $scriptDir = dirname rel2abs $0;
+my ($netrc, $netrcGpg, $gcNetrc) = map { catfile $scriptDir, $_; }
+ qw(test.netrc
+ test.netrc.gpg
+ git-credential-netrc);
+local $ENV{PATH} = join ':'
+ , $scriptDir
+ , $ENV{PATH}
+ ? $ENV{PATH}
+ : ();
+
+diag "Testing insecure file, nothing should be found\n";
chmod 0644, $netrc;
my $cred = run_credential(['-f', $netrc, 'get'],
{ host => 'github.com' });
-ok(scalar keys %$cred, 0, "Got 0 keys from insecure file");
+ok(scalar keys %$cred == 0, "Got 0 keys from insecure file");
-print "# Testing missing file, nothing should be found\n";
+diag "Testing missing file, nothing should be found\n";
chmod 0644, $netrc;
$cred = run_credential(['-f', '///nosuchfile///', 'get'],
{ host => 'github.com' });
-ok(scalar keys %$cred, 0, "Got 0 keys from missing file");
+ok(scalar keys %$cred == 0, "Got 0 keys from missing file");
chmod 0600, $netrc;
-print "# Testing with invalid data\n";
+diag "Testing with invalid data\n";
$cred = run_credential(['-f', $netrc, 'get'],
"bad data");
-ok(scalar keys %$cred, 4, "Got first found keys with bad data");
+ok(scalar keys %$cred == 4, "Got first found keys with bad data");
-print "# Testing netrc file for a missing corovamilkbar entry\n";
+diag "Testing netrc file for a missing corovamilkbar entry\n";
$cred = run_credential(['-f', $netrc, 'get'],
{ host => 'corovamilkbar' });
-ok(scalar keys %$cred, 0, "Got no corovamilkbar keys");
+ok(scalar keys %$cred == 0, "Got no corovamilkbar keys");
-print "# Testing netrc file for a github.com entry\n";
+diag "Testing netrc file for a github.com entry\n";
$cred = run_credential(['-f', $netrc, 'get'],
{ host => 'github.com' });
-ok(scalar keys %$cred, 2, "Got 2 Github keys");
+ok(scalar keys %$cred == 2, "Got 2 Github keys");
-ok($cred->{password}, 'carolknows', "Got correct Github password");
-ok($cred->{username}, 'carol', "Got correct Github username");
+is($cred->{password}, 'carolknows', "Got correct Github password");
+is($cred->{username}, 'carol', "Got correct Github username");
-print "# Testing netrc file for a username-specific entry\n";
+diag "Testing netrc file for a username-specific entry\n";
$cred = run_credential(['-f', $netrc, 'get'],
{ host => 'imap', username => 'bob' });
-ok(scalar keys %$cred, 2, "Got 2 username-specific keys");
+ok(scalar keys %$cred == 2, "Got 2 username-specific keys");
-ok($cred->{password}, 'bobwillknow', "Got correct user-specific password");
-ok($cred->{protocol}, 'imaps', "Got correct user-specific protocol");
+is($cred->{password}, 'bobwillknow', "Got correct user-specific password");
+is($cred->{protocol}, 'imaps', "Got correct user-specific protocol");
-print "# Testing netrc file for a host:port-specific entry\n";
+diag "Testing netrc file for a host:port-specific entry\n";
$cred = run_credential(['-f', $netrc, 'get'],
{ host => 'imap2:1099' });
-ok(scalar keys %$cred, 2, "Got 2 host:port-specific keys");
+ok(scalar keys %$cred == 2, "Got 2 host:port-specific keys");
-ok($cred->{password}, 'tzzknow', "Got correct host:port-specific password");
-ok($cred->{username}, 'tzz', "Got correct host:port-specific username");
+is($cred->{password}, 'tzzknow', "Got correct host:port-specific password");
+is($cred->{username}, 'tzz', "Got correct host:port-specific username");
-print "# Testing netrc file that 'host:port kills host' entry\n";
+diag "Testing netrc file that 'host:port kills host' entry\n";
$cred = run_credential(['-f', $netrc, 'get'],
{ host => 'imap2' });
-ok(scalar keys %$cred, 2, "Got 2 'host:port kills host' keys");
+ok(scalar keys %$cred == 2, "Got 2 'host:port kills host' keys");
+
+is($cred->{password}, 'bobwillknow', "Got correct 'host:port kills host' password");
+is($cred->{username}, 'bob', "Got correct 'host:port kills host' username");
+
+diag 'Testing netrc file decryption by git config gpg.program setting\n';
+$cred = run_credential( ['-f', $netrcGpg, 'get']
+ , { host => 'git-config-gpg' }
+ );
+
+ok(scalar keys %$cred == 2, 'Got keys decrypted by git config option');
+
+diag 'Testing netrc file decryption by gpg option\n';
+$cred = run_credential( ['-f', $netrcGpg, '-g', 'test.command-option-gpg', 'get']
+ , { host => 'command-option-gpg' }
+ );
+
+ok(scalar keys %$cred == 2, 'Got keys decrypted by command option');
-ok($cred->{password}, 'bobwillknow', "Got correct 'host:port kills host' password");
-ok($cred->{username}, 'bob', "Got correct 'host:port kills host' username");
+my $is_passing = eval { Test::More->is_passing };
+exit($is_passing ? 0 : 1) unless $@ =~ /Can't locate object method/;
sub run_credential
{
my $args = shift @_;
my $data = shift @_;
my $pid = open2(my $chld_out, my $chld_in,
- './git-credential-netrc', @global_credential_args,
+ $gcNetrc, @global_credential_args,
@$args);
die "Couldn't open pipe to netrc credential helper: $!" unless $pid;
diff --git a/contrib/credential/wincred/git-credential-wincred.c b/contrib/credential/wincred/git-credential-wincred.c
index 86518cd..5bdad41 100644
--- a/contrib/credential/wincred/git-credential-wincred.c
+++ b/contrib/credential/wincred/git-credential-wincred.c
@@ -75,7 +75,8 @@ static CredDeleteWT CredDeleteW;
static void load_cred_funcs(void)
{
/* load DLLs */
- advapi = LoadLibrary("advapi32.dll");
+ advapi = LoadLibraryExA("advapi32.dll", NULL,
+ LOAD_LIBRARY_SEARCH_SYSTEM32);
if (!advapi)
die("failed to load advapi32.dll");
diff --git a/contrib/diff-highlight/DiffHighlight.pm b/contrib/diff-highlight/DiffHighlight.pm
index 5367545..e258992 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/emacs/.gitignore b/contrib/emacs/.gitignore
deleted file mode 100644
index c531d98..0000000
--- a/contrib/emacs/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*.elc
diff --git a/contrib/emacs/Makefile b/contrib/emacs/Makefile
deleted file mode 100644
index 24d9312..0000000
--- a/contrib/emacs/Makefile
+++ /dev/null
@@ -1,21 +0,0 @@
-## Build and install stuff
-
-EMACS = emacs
-
-ELC = git.elc git-blame.elc
-INSTALL ?= install
-INSTALL_ELC = $(INSTALL) -m 644
-prefix ?= $(HOME)
-emacsdir = $(prefix)/share/emacs/site-lisp
-RM ?= rm -f
-
-all: $(ELC)
-
-install: all
- $(INSTALL) -d $(DESTDIR)$(emacsdir)
- $(INSTALL_ELC) $(ELC:.elc=.el) $(ELC) $(DESTDIR)$(emacsdir)
-
-%.elc: %.el
- $(EMACS) -batch -f batch-byte-compile $<
-
-clean:; $(RM) $(ELC)
diff --git a/contrib/emacs/README b/contrib/emacs/README
index 82368bd..977a16f 100644
--- a/contrib/emacs/README
+++ b/contrib/emacs/README
@@ -1,30 +1,24 @@
-This directory contains various modules for Emacs support.
+This directory used to contain various modules for Emacs support.
-To make the modules available to Emacs, you should add this directory
-to your load-path, and then require the modules you want. This can be
-done by adding to your .emacs something like this:
+These were added shortly after Git was first released. Since then
+Emacs's own support for Git got better than what was offered by these
+modes. There are also popular 3rd-party Git modes such as Magit which
+offer replacements for these.
- (add-to-list 'load-path ".../git/contrib/emacs")
- (require 'git)
- (require 'git-blame)
-
-
-The following modules are available:
+The following modules were available, and can be dug up from the Git
+history:
* git.el:
- Status manager that displays the state of all the files of the
- project, and provides easy access to the most frequently used git
- commands. The user interface is as far as possible compatible with
- the pcl-cvs mode. It can be started with `M-x git-status'.
+ Wrapper for "git status" that provided access to other git commands.
+
+ Modern alternatives to this include Magit, and VC mode that ships
+ with Emacs.
* git-blame.el:
- Emacs implementation of incremental git-blame. When you turn it on
- while viewing a file, the editor buffer will be updated by setting
- the background of individual lines to a color that reflects which
- commit it comes from. And when you move around the buffer, a
- one-line summary will be shown in the echo area.
+ A wrapper for "git blame" written before Emacs's own vc-annotate
+ mode learned to invoke git-blame, which can be done via C-x v g.
* vc-git.el:
diff --git a/contrib/emacs/git-blame.el b/contrib/emacs/git-blame.el
index 510e0f7..6a8a2b8 100644
--- a/contrib/emacs/git-blame.el
+++ b/contrib/emacs/git-blame.el
@@ -1,483 +1,6 @@
-;;; git-blame.el --- Minor mode for incremental blame for Git -*- coding: utf-8 -*-
-;;
-;; Copyright (C) 2007 David KÃ¥gedal
-;;
-;; Authors: David KÃ¥gedal <davidk@lysator.liu.se>
-;; Created: 31 Jan 2007
-;; Message-ID: <87iren2vqx.fsf@morpheus.local>
-;; License: GPL
-;; Keywords: git, version control, release management
-;;
-;; Compatibility: Emacs21, Emacs22 and EmacsCVS
-;; Git 1.5 and up
-
-;; This file is *NOT* part of GNU Emacs.
-;; This file is distributed under the same terms as GNU Emacs.
-
-;; This program is free software; you can redistribute it and/or
-;; modify it under the terms of the GNU General Public License as
-;; published by the Free Software Foundation; either version 2 of
-;; the License, or (at your option) any later version.
-
-;; This program is distributed in the hope that it will be
-;; useful, but WITHOUT ANY WARRANTY; without even the implied
-;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
-;; PURPOSE. See the GNU General Public License for more details.
-
-;; You should have received a copy of the GNU General Public
-;; License along with this program; if not, see
-;; <http://www.gnu.org/licenses/>.
-
-;; http://www.fsf.org/copyleft/gpl.html
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;;
-;;; Commentary:
-;;
-;; Here is an Emacs implementation of incremental git-blame. When you
-;; turn it on while viewing a file, the editor buffer will be updated by
-;; setting the background of individual lines to a color that reflects
-;; which commit it comes from. And when you move around the buffer, a
-;; one-line summary will be shown in the echo area.
-
-;;; Installation:
-;;
-;; To use this package, put it somewhere in `load-path' (or add
-;; directory with git-blame.el to `load-path'), and add the following
-;; line to your .emacs:
-;;
-;; (require 'git-blame)
-;;
-;; If you do not want to load this package before it is necessary, you
-;; can make use of the `autoload' feature, e.g. by adding to your .emacs
-;; the following lines
-;;
-;; (autoload 'git-blame-mode "git-blame"
-;; "Minor mode for incremental blame for Git." t)
-;;
-;; Then first use of `M-x git-blame-mode' would load the package.
-
-;;; Compatibility:
-;;
-;; It requires GNU Emacs 21 or later and Git 1.5.0 and up
-;;
-;; If you'are using Emacs 20, try changing this:
-;;
-;; (overlay-put ovl 'face (list :background
-;; (cdr (assq 'color (cddddr info)))))
-;;
-;; to
-;;
-;; (overlay-put ovl 'face (cons 'background-color
-;; (cdr (assq 'color (cddddr info)))))
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;;
-;;; Code:
-
-(eval-when-compile (require 'cl)) ; to use `push', `pop'
-(require 'format-spec)
-
-(defface git-blame-prefix-face
- '((((background dark)) (:foreground "gray"
- :background "black"))
- (((background light)) (:foreground "gray"
- :background "white"))
- (t (:weight bold)))
- "The face used for the hash prefix."
- :group 'git-blame)
-
-(defgroup git-blame nil
- "A minor mode showing Git blame information."
- :group 'git
- :link '(function-link git-blame-mode))
-
-
-(defcustom git-blame-use-colors t
- "Use colors to indicate commits in `git-blame-mode'."
- :type 'boolean
- :group 'git-blame)
-
-(defcustom git-blame-prefix-format
- "%h %20A:"
- "The format of the prefix added to each line in `git-blame'
-mode. The format is passed to `format-spec' with the following format keys:
-
- %h - the abbreviated hash
- %H - the full hash
- %a - the author name
- %A - the author email
- %c - the committer name
- %C - the committer email
- %s - the commit summary
-"
- :group 'git-blame)
-
-(defcustom git-blame-mouseover-format
- "%h %a %A: %s"
- "The format of the description shown when pointing at a line in
-`git-blame' mode. The format string is passed to `format-spec'
-with the following format keys:
-
- %h - the abbreviated hash
- %H - the full hash
- %a - the author name
- %A - the author email
- %c - the committer name
- %C - the committer email
- %s - the commit summary
-"
- :group 'git-blame)
-
-
-(defun git-blame-color-scale (&rest elements)
- "Given a list, returns a list of triples formed with each
-elements of the list.
-
-a b => bbb bba bab baa abb aba aaa aab"
- (let (result)
- (dolist (a elements)
- (dolist (b elements)
- (dolist (c elements)
- (setq result (cons (format "#%s%s%s" a b c) result)))))
- result))
-
-;; (git-blame-color-scale "0c" "04" "24" "1c" "2c" "34" "14" "3c") =>
-;; ("#3c3c3c" "#3c3c14" "#3c3c34" "#3c3c2c" "#3c3c1c" "#3c3c24"
-;; "#3c3c04" "#3c3c0c" "#3c143c" "#3c1414" "#3c1434" "#3c142c" ...)
-
-(defmacro git-blame-random-pop (l)
- "Select a random element from L and returns it. Also remove
-selected element from l."
- ;; only works on lists with unique elements
- `(let ((e (elt ,l (random (length ,l)))))
- (setq ,l (remove e ,l))
- e))
-
-(defvar git-blame-log-oneline-format
- "format:[%cr] %cn: %s"
- "*Formatting option used for describing current line in the minibuffer.
-
-This option is used to pass to git log --pretty= command-line option,
-and describe which commit the current line was made.")
-
-(defvar git-blame-dark-colors
- (git-blame-color-scale "0c" "04" "24" "1c" "2c" "34" "14" "3c")
- "*List of colors (format #RGB) to use in a dark environment.
-
-To check out the list, evaluate (list-colors-display git-blame-dark-colors).")
-
-(defvar git-blame-light-colors
- (git-blame-color-scale "c4" "d4" "cc" "dc" "f4" "e4" "fc" "ec")
- "*List of colors (format #RGB) to use in a light environment.
-
-To check out the list, evaluate (list-colors-display git-blame-light-colors).")
-
-(defvar git-blame-colors '()
- "Colors used by git-blame. The list is built once when activating git-blame
-minor mode.")
-
-(defvar git-blame-ancient-color "dark green"
- "*Color to be used for ancient commit.")
-
-(defvar git-blame-autoupdate t
- "*Automatically update the blame display while editing")
-
-(defvar git-blame-proc nil
- "The running git-blame process")
-(make-variable-buffer-local 'git-blame-proc)
-
-(defvar git-blame-overlays nil
- "The git-blame overlays used in the current buffer.")
-(make-variable-buffer-local 'git-blame-overlays)
-
-(defvar git-blame-cache nil
- "A cache of git-blame information for the current buffer")
-(make-variable-buffer-local 'git-blame-cache)
-
-(defvar git-blame-idle-timer nil
- "An idle timer that updates the blame")
-(make-variable-buffer-local 'git-blame-cache)
-
-(defvar git-blame-update-queue nil
- "A queue of update requests")
-(make-variable-buffer-local 'git-blame-update-queue)
-
-;; FIXME: docstrings
-(defvar git-blame-file nil)
-(defvar git-blame-current nil)
-
-(defvar git-blame-mode nil)
-(make-variable-buffer-local 'git-blame-mode)
-
-(defvar git-blame-mode-line-string " blame"
- "String to display on the mode line when git-blame is active.")
-
-(or (assq 'git-blame-mode minor-mode-alist)
- (setq minor-mode-alist
- (cons '(git-blame-mode git-blame-mode-line-string) minor-mode-alist)))
-
-;;;###autoload
-(defun git-blame-mode (&optional arg)
- "Toggle minor mode for displaying Git blame
-
-With prefix ARG, turn the mode on if ARG is positive."
- (interactive "P")
- (cond
- ((null arg)
- (if git-blame-mode (git-blame-mode-off) (git-blame-mode-on)))
- ((> (prefix-numeric-value arg) 0) (git-blame-mode-on))
- (t (git-blame-mode-off))))
-
-(defun git-blame-mode-on ()
- "Turn on git-blame mode.
-
-See also function `git-blame-mode'."
- (make-local-variable 'git-blame-colors)
- (if git-blame-autoupdate
- (add-hook 'after-change-functions 'git-blame-after-change nil t)
- (remove-hook 'after-change-functions 'git-blame-after-change t))
- (git-blame-cleanup)
- (let ((bgmode (cdr (assoc 'background-mode (frame-parameters)))))
- (if (eq bgmode 'dark)
- (setq git-blame-colors git-blame-dark-colors)
- (setq git-blame-colors git-blame-light-colors)))
- (setq git-blame-cache (make-hash-table :test 'equal))
- (setq git-blame-mode t)
- (git-blame-run))
-
-(defun git-blame-mode-off ()
- "Turn off git-blame mode.
-
-See also function `git-blame-mode'."
- (git-blame-cleanup)
- (if git-blame-idle-timer (cancel-timer git-blame-idle-timer))
- (setq git-blame-mode nil))
-
-;;;###autoload
-(defun git-reblame ()
- "Recalculate all blame information in the current buffer"
- (interactive)
- (unless git-blame-mode
- (error "Git-blame is not active"))
-
- (git-blame-cleanup)
- (git-blame-run))
-
-(defun git-blame-run (&optional startline endline)
- (if git-blame-proc
- ;; Should maybe queue up a new run here
- (message "Already running git blame")
- (let ((display-buf (current-buffer))
- (blame-buf (get-buffer-create
- (concat " git blame for " (buffer-name))))
- (args '("--incremental" "--contents" "-")))
- (if startline
- (setq args (append args
- (list "-L" (format "%d,%d" startline endline)))))
- (setq args (append args
- (list (file-name-nondirectory buffer-file-name))))
- (setq git-blame-proc
- (apply 'start-process
- "git-blame" blame-buf
- "git" "blame"
- args))
- (with-current-buffer blame-buf
- (erase-buffer)
- (make-local-variable 'git-blame-file)
- (make-local-variable 'git-blame-current)
- (setq git-blame-file display-buf)
- (setq git-blame-current nil))
- (set-process-filter git-blame-proc 'git-blame-filter)
- (set-process-sentinel git-blame-proc 'git-blame-sentinel)
- (process-send-region git-blame-proc (point-min) (point-max))
- (process-send-eof git-blame-proc))))
-
-(defun remove-git-blame-text-properties (start end)
- (let ((modified (buffer-modified-p))
- (inhibit-read-only t))
- (remove-text-properties start end '(point-entered nil))
- (set-buffer-modified-p modified)))
-
-(defun git-blame-cleanup ()
- "Remove all blame properties"
- (mapc 'delete-overlay git-blame-overlays)
- (setq git-blame-overlays nil)
- (remove-git-blame-text-properties (point-min) (point-max)))
-
-(defun git-blame-update-region (start end)
- "Rerun blame to get updates between START and END"
- (let ((overlays (overlays-in start end)))
- (while overlays
- (let ((overlay (pop overlays)))
- (if (< (overlay-start overlay) start)
- (setq start (overlay-start overlay)))
- (if (> (overlay-end overlay) end)
- (setq end (overlay-end overlay)))
- (setq git-blame-overlays (delete overlay git-blame-overlays))
- (delete-overlay overlay))))
- (remove-git-blame-text-properties start end)
- ;; We can be sure that start and end are at line breaks
- (git-blame-run (1+ (count-lines (point-min) start))
- (count-lines (point-min) end)))
-
-(defun git-blame-sentinel (proc status)
- (with-current-buffer (process-buffer proc)
- (with-current-buffer git-blame-file
- (setq git-blame-proc nil)
- (if git-blame-update-queue
- (git-blame-delayed-update))))
- ;;(kill-buffer (process-buffer proc))
- ;;(message "git blame finished")
- )
-
-(defvar in-blame-filter nil)
-
-(defun git-blame-filter (proc str)
- (with-current-buffer (process-buffer proc)
- (save-excursion
- (goto-char (process-mark proc))
- (insert-before-markers str)
- (goto-char (point-min))
- (unless in-blame-filter
- (let ((more t)
- (in-blame-filter t))
- (while more
- (setq more (git-blame-parse))))))))
-
-(defun git-blame-parse ()
- (cond ((looking-at "\\([0-9a-f]\\{40\\}\\) \\([0-9]+\\) \\([0-9]+\\) \\([0-9]+\\)\n")
- (let ((hash (match-string 1))
- (src-line (string-to-number (match-string 2)))
- (res-line (string-to-number (match-string 3)))
- (num-lines (string-to-number (match-string 4))))
- (delete-region (point) (match-end 0))
- (setq git-blame-current (list (git-blame-new-commit hash)
- src-line res-line num-lines)))
- t)
- ((looking-at "\\([a-z-]+\\) \\(.+\\)\n")
- (let ((key (match-string 1))
- (value (match-string 2)))
- (delete-region (point) (match-end 0))
- (git-blame-add-info (car git-blame-current) key value)
- (when (string= key "filename")
- (git-blame-create-overlay (car git-blame-current)
- (caddr git-blame-current)
- (cadddr git-blame-current))
- (setq git-blame-current nil)))
- t)
- (t
- nil)))
-
-(defun git-blame-new-commit (hash)
- (with-current-buffer git-blame-file
- (or (gethash hash git-blame-cache)
- ;; Assign a random color to each new commit info
- ;; Take care not to select the same color multiple times
- (let* ((color (if git-blame-colors
- (git-blame-random-pop git-blame-colors)
- git-blame-ancient-color))
- (info `(,hash (color . ,color))))
- (puthash hash info git-blame-cache)
- info))))
-
-(defun git-blame-create-overlay (info start-line num-lines)
- (with-current-buffer git-blame-file
- (save-excursion
- (let ((inhibit-point-motion-hooks t)
- (inhibit-modification-hooks t))
- (goto-char (point-min))
- (forward-line (1- start-line))
- (let* ((start (point))
- (end (progn (forward-line num-lines) (point)))
- (ovl (make-overlay start end))
- (hash (car info))
- (spec `((?h . ,(substring hash 0 6))
- (?H . ,hash)
- (?a . ,(git-blame-get-info info 'author))
- (?A . ,(git-blame-get-info info 'author-mail))
- (?c . ,(git-blame-get-info info 'committer))
- (?C . ,(git-blame-get-info info 'committer-mail))
- (?s . ,(git-blame-get-info info 'summary)))))
- (push ovl git-blame-overlays)
- (overlay-put ovl 'git-blame info)
- (overlay-put ovl 'help-echo
- (format-spec git-blame-mouseover-format spec))
- (if git-blame-use-colors
- (overlay-put ovl 'face (list :background
- (cdr (assq 'color (cdr info))))))
- (overlay-put ovl 'line-prefix
- (propertize (format-spec git-blame-prefix-format spec)
- 'face 'git-blame-prefix-face)))))))
-
-(defun git-blame-add-info (info key value)
- (nconc info (list (cons (intern key) value))))
-
-(defun git-blame-get-info (info key)
- (cdr (assq key (cdr info))))
-
-(defun git-blame-current-commit ()
- (let ((info (get-char-property (point) 'git-blame)))
- (if info
- (car info)
- (error "No commit info"))))
-
-(defun git-describe-commit (hash)
- (with-temp-buffer
- (call-process "git" nil t nil
- "log" "-1"
- (concat "--pretty=" git-blame-log-oneline-format)
- hash)
- (buffer-substring (point-min) (point-max))))
-
-(defvar git-blame-last-identification nil)
-(make-variable-buffer-local 'git-blame-last-identification)
-(defun git-blame-identify (&optional hash)
- (interactive)
- (let ((info (gethash (or hash (git-blame-current-commit)) git-blame-cache)))
- (when (and info (not (eq info git-blame-last-identification)))
- (message "%s" (nth 4 info))
- (setq git-blame-last-identification info))))
-
-;; (defun git-blame-after-save ()
-;; (when git-blame-mode
-;; (git-blame-cleanup)
-;; (git-blame-run)))
-;; (add-hook 'after-save-hook 'git-blame-after-save)
-
-(defun git-blame-after-change (start end length)
- (when git-blame-mode
- (git-blame-enq-update start end)))
-
-(defvar git-blame-last-update nil)
-(make-variable-buffer-local 'git-blame-last-update)
-(defun git-blame-enq-update (start end)
- "Mark the region between START and END as needing blame update"
- ;; Try to be smart and avoid multiple callouts for sequential
- ;; editing
- (cond ((and git-blame-last-update
- (= start (cdr git-blame-last-update)))
- (setcdr git-blame-last-update end))
- ((and git-blame-last-update
- (= end (car git-blame-last-update)))
- (setcar git-blame-last-update start))
- (t
- (setq git-blame-last-update (cons start end))
- (setq git-blame-update-queue (nconc git-blame-update-queue
- (list git-blame-last-update)))))
- (unless (or git-blame-proc git-blame-idle-timer)
- (setq git-blame-idle-timer
- (run-with-idle-timer 0.5 nil 'git-blame-delayed-update))))
-
-(defun git-blame-delayed-update ()
- (setq git-blame-idle-timer nil)
- (if git-blame-update-queue
- (let ((first (pop git-blame-update-queue))
- (inhibit-point-motion-hooks t))
- (git-blame-update-region (car first) (cdr first)))))
-
-(provide 'git-blame)
-
-;;; git-blame.el ends here
+(error "git-blame.el no longer ships with git. It's recommended
+to replace its use with Emacs's own vc-annotate. See
+contrib/emacs/README in git's
+sources (https://github.com/git/git/blob/master/contrib/emacs/README)
+for more info on suggested alternatives and for why this
+happened.")
diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el
index 97919f2..03f9262 100644
--- a/contrib/emacs/git.el
+++ b/contrib/emacs/git.el
@@ -1,1704 +1,6 @@
-;;; git.el --- A user interface for git
-
-;; Copyright (C) 2005, 2006, 2007, 2008, 2009 Alexandre Julliard <julliard@winehq.org>
-
-;; Version: 1.0
-
-;; This program is free software; you can redistribute it and/or
-;; modify it under the terms of the GNU General Public License as
-;; published by the Free Software Foundation; either version 2 of
-;; the License, or (at your option) any later version.
-;;
-;; This program is distributed in the hope that it will be
-;; useful, but WITHOUT ANY WARRANTY; without even the implied
-;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
-;; PURPOSE. See the GNU General Public License for more details.
-;;
-;; You should have received a copy of the GNU General Public
-;; License along with this program; if not, see
-;; <http://www.gnu.org/licenses/>.
-
-;;; Commentary:
-
-;; This file contains an interface for the git version control
-;; system. It provides easy access to the most frequently used git
-;; commands. The user interface is as far as possible identical to
-;; that of the PCL-CVS mode.
-;;
-;; To install: put this file on the load-path and place the following
-;; in your .emacs file:
-;;
-;; (require 'git)
-;;
-;; To start: `M-x git-status'
-;;
-;; TODO
-;; - diff against other branch
-;; - renaming files from the status buffer
-;; - creating tags
-;; - fetch/pull
-;; - revlist browser
-;; - git-show-branch browser
-;;
-
-;;; Compatibility:
-;;
-;; This file works on GNU Emacs 21 or later. It may work on older
-;; versions but this is not guaranteed.
-;;
-;; It may work on XEmacs 21, provided that you first install the ewoc
-;; and log-edit packages.
-;;
-
-(eval-when-compile (require 'cl))
-(require 'ewoc)
-(require 'log-edit)
-(require 'easymenu)
-
-
-;;;; Customizations
-;;;; ------------------------------------------------------------
-
-(defgroup git nil
- "A user interface for the git versioning system."
- :group 'tools)
-
-(defcustom git-committer-name nil
- "User name to use for commits.
-The default is to fall back to the repository config,
-then to `add-log-full-name' and then to `user-full-name'."
- :group 'git
- :type '(choice (const :tag "Default" nil)
- (string :tag "Name")))
-
-(defcustom git-committer-email nil
- "Email address to use for commits.
-The default is to fall back to the git repository config,
-then to `add-log-mailing-address' and then to `user-mail-address'."
- :group 'git
- :type '(choice (const :tag "Default" nil)
- (string :tag "Email")))
-
-(defcustom git-commits-coding-system nil
- "Default coding system for the log message of git commits."
- :group 'git
- :type '(choice (const :tag "From repository config" nil)
- (coding-system)))
-
-(defcustom git-append-signed-off-by nil
- "Whether to append a Signed-off-by line to the commit message before editing."
- :group 'git
- :type 'boolean)
-
-(defcustom git-reuse-status-buffer t
- "Whether `git-status' should try to reuse an existing buffer
-if there is already one that displays the same directory."
- :group 'git
- :type 'boolean)
-
-(defcustom git-per-dir-ignore-file ".gitignore"
- "Name of the per-directory ignore file."
- :group 'git
- :type 'string)
-
-(defcustom git-show-uptodate nil
- "Whether to display up-to-date files."
- :group 'git
- :type 'boolean)
-
-(defcustom git-show-ignored nil
- "Whether to display ignored files."
- :group 'git
- :type 'boolean)
-
-(defcustom git-show-unknown t
- "Whether to display unknown files."
- :group 'git
- :type 'boolean)
-
-
-(defface git-status-face
- '((((class color) (background light)) (:foreground "purple"))
- (((class color) (background dark)) (:foreground "salmon")))
- "Git mode face used to highlight added and modified files."
- :group 'git)
-
-(defface git-unmerged-face
- '((((class color) (background light)) (:foreground "red" :bold t))
- (((class color) (background dark)) (:foreground "red" :bold t)))
- "Git mode face used to highlight unmerged files."
- :group 'git)
-
-(defface git-unknown-face
- '((((class color) (background light)) (:foreground "goldenrod" :bold t))
- (((class color) (background dark)) (:foreground "goldenrod" :bold t)))
- "Git mode face used to highlight unknown files."
- :group 'git)
-
-(defface git-uptodate-face
- '((((class color) (background light)) (:foreground "grey60"))
- (((class color) (background dark)) (:foreground "grey40")))
- "Git mode face used to highlight up-to-date files."
- :group 'git)
-
-(defface git-ignored-face
- '((((class color) (background light)) (:foreground "grey60"))
- (((class color) (background dark)) (:foreground "grey40")))
- "Git mode face used to highlight ignored files."
- :group 'git)
-
-(defface git-mark-face
- '((((class color) (background light)) (:foreground "red" :bold t))
- (((class color) (background dark)) (:foreground "tomato" :bold t)))
- "Git mode face used for the file marks."
- :group 'git)
-
-(defface git-header-face
- '((((class color) (background light)) (:foreground "blue"))
- (((class color) (background dark)) (:foreground "blue")))
- "Git mode face used for commit headers."
- :group 'git)
-
-(defface git-separator-face
- '((((class color) (background light)) (:foreground "brown"))
- (((class color) (background dark)) (:foreground "brown")))
- "Git mode face used for commit separator."
- :group 'git)
-
-(defface git-permission-face
- '((((class color) (background light)) (:foreground "green" :bold t))
- (((class color) (background dark)) (:foreground "green" :bold t)))
- "Git mode face used for permission changes."
- :group 'git)
-
-
-;;;; Utilities
-;;;; ------------------------------------------------------------
-
-(defconst git-log-msg-separator "--- log message follows this line ---")
-
-(defvar git-log-edit-font-lock-keywords
- `(("^\\(Author:\\|Date:\\|Merge:\\|Signed-off-by:\\)\\(.*\\)$"
- (1 font-lock-keyword-face)
- (2 font-lock-function-name-face))
- (,(concat "^\\(" (regexp-quote git-log-msg-separator) "\\)$")
- (1 font-lock-comment-face))))
-
-(defun git-get-env-strings (env)
- "Build a list of NAME=VALUE strings from a list of environment strings."
- (mapcar (lambda (entry) (concat (car entry) "=" (cdr entry))) env))
-
-(defun git-call-process (buffer &rest args)
- "Wrapper for call-process that sets environment strings."
- (apply #'call-process "git" nil buffer nil args))
-
-(defun git-call-process-display-error (&rest args)
- "Wrapper for call-process that displays error messages."
- (let* ((dir default-directory)
- (buffer (get-buffer-create "*Git Command Output*"))
- (ok (with-current-buffer buffer
- (let ((default-directory dir)
- (buffer-read-only nil))
- (erase-buffer)
- (eq 0 (apply #'git-call-process (list buffer t) args))))))
- (unless ok (display-message-or-buffer buffer))
- ok))
-
-(defun git-call-process-string (&rest args)
- "Wrapper for call-process that returns the process output as a string,
-or nil if the git command failed."
- (with-temp-buffer
- (and (eq 0 (apply #'git-call-process t args))
- (buffer-string))))
-
-(defun git-call-process-string-display-error (&rest args)
- "Wrapper for call-process that displays error message and returns
-the process output as a string, or nil if the git command failed."
- (with-temp-buffer
- (if (eq 0 (apply #'git-call-process (list t t) args))
- (buffer-string)
- (display-message-or-buffer (current-buffer))
- nil)))
-
-(defun git-run-process-region (buffer start end program args)
- "Run a git process with a buffer region as input."
- (let ((output-buffer (current-buffer))
- (dir default-directory))
- (with-current-buffer buffer
- (cd dir)
- (apply #'call-process-region start end program
- nil (list output-buffer t) nil args))))
-
-(defun git-run-command-buffer (buffer-name &rest args)
- "Run a git command, sending the output to a buffer named BUFFER-NAME."
- (let ((dir default-directory)
- (buffer (get-buffer-create buffer-name)))
- (message "Running git %s..." (car args))
- (with-current-buffer buffer
- (let ((default-directory dir)
- (buffer-read-only nil))
- (erase-buffer)
- (apply #'git-call-process buffer args)))
- (message "Running git %s...done" (car args))
- buffer))
-
-(defun git-run-command-region (buffer start end env &rest args)
- "Run a git command with specified buffer region as input."
- (with-temp-buffer
- (if (eq 0 (if env
- (git-run-process-region
- buffer start end "env"
- (append (git-get-env-strings env) (list "git") args))
- (git-run-process-region buffer start end "git" args)))
- (buffer-string)
- (display-message-or-buffer (current-buffer))
- nil)))
-
-(defun git-run-hook (hook env &rest args)
- "Run a git hook and display its output if any."
- (let ((dir default-directory)
- (hook-name (expand-file-name (concat ".git/hooks/" hook))))
- (or (not (file-executable-p hook-name))
- (let (status (buffer (get-buffer-create "*Git Hook Output*")))
- (with-current-buffer buffer
- (erase-buffer)
- (cd dir)
- (setq status
- (if env
- (apply #'call-process "env" nil (list buffer t) nil
- (append (git-get-env-strings env) (list hook-name) args))
- (apply #'call-process hook-name nil (list buffer t) nil args))))
- (display-message-or-buffer buffer)
- (eq 0 status)))))
-
-(defun git-get-string-sha1 (string)
- "Read a SHA1 from the specified string."
- (and string
- (string-match "[0-9a-f]\\{40\\}" string)
- (match-string 0 string)))
-
-(defun git-get-committer-name ()
- "Return the name to use as GIT_COMMITTER_NAME."
- ; copied from log-edit
- (or git-committer-name
- (git-config "user.name")
- (and (boundp 'add-log-full-name) add-log-full-name)
- (and (fboundp 'user-full-name) (user-full-name))
- (and (boundp 'user-full-name) user-full-name)))
-
-(defun git-get-committer-email ()
- "Return the email address to use as GIT_COMMITTER_EMAIL."
- ; copied from log-edit
- (or git-committer-email
- (git-config "user.email")
- (and (boundp 'add-log-mailing-address) add-log-mailing-address)
- (and (fboundp 'user-mail-address) (user-mail-address))
- (and (boundp 'user-mail-address) user-mail-address)))
-
-(defun git-get-commits-coding-system ()
- "Return the coding system to use for commits."
- (let ((repo-config (git-config "i18n.commitencoding")))
- (or git-commits-coding-system
- (and repo-config
- (fboundp 'locale-charset-to-coding-system)
- (locale-charset-to-coding-system repo-config))
- 'utf-8)))
-
-(defun git-get-logoutput-coding-system ()
- "Return the coding system used for git-log output."
- (let ((repo-config (or (git-config "i18n.logoutputencoding")
- (git-config "i18n.commitencoding"))))
- (or git-commits-coding-system
- (and repo-config
- (fboundp 'locale-charset-to-coding-system)
- (locale-charset-to-coding-system repo-config))
- 'utf-8)))
-
-(defun git-escape-file-name (name)
- "Escape a file name if necessary."
- (if (string-match "[\n\t\"\\]" name)
- (concat "\""
- (mapconcat (lambda (c)
- (case c
- (?\n "\\n")
- (?\t "\\t")
- (?\\ "\\\\")
- (?\" "\\\"")
- (t (char-to-string c))))
- name "")
- "\"")
- name))
-
-(defun git-success-message (text files)
- "Print a success message after having handled FILES."
- (let ((n (length files)))
- (if (equal n 1)
- (message "%s %s" text (car files))
- (message "%s %d files" text n))))
-
-(defun git-get-top-dir (dir)
- "Retrieve the top-level directory of a git tree."
- (let ((cdup (with-output-to-string
- (with-current-buffer standard-output
- (cd dir)
- (unless (eq 0 (git-call-process t "rev-parse" "--show-cdup"))
- (error "cannot find top-level git tree for %s." dir))))))
- (expand-file-name (concat (file-name-as-directory dir)
- (car (split-string cdup "\n"))))))
-
-;stolen from pcl-cvs
-(defun git-append-to-ignore (file)
- "Add a file name to the ignore file in its directory."
- (let* ((fullname (expand-file-name file))
- (dir (file-name-directory fullname))
- (name (file-name-nondirectory fullname))
- (ignore-name (expand-file-name git-per-dir-ignore-file dir))
- (created (not (file-exists-p ignore-name))))
- (save-window-excursion
- (set-buffer (find-file-noselect ignore-name))
- (goto-char (point-max))
- (unless (zerop (current-column)) (insert "\n"))
- (insert "/" name "\n")
- (sort-lines nil (point-min) (point-max))
- (save-buffer))
- (when created
- (git-call-process nil "update-index" "--add" "--" (file-relative-name ignore-name)))
- (git-update-status-files (list (file-relative-name ignore-name)))))
-
-; propertize definition for XEmacs, stolen from erc-compat
-(eval-when-compile
- (unless (fboundp 'propertize)
- (defun propertize (string &rest props)
- (let ((string (copy-sequence string)))
- (while props
- (put-text-property 0 (length string) (nth 0 props) (nth 1 props) string)
- (setq props (cddr props)))
- string))))
-
-;;;; Wrappers for basic git commands
-;;;; ------------------------------------------------------------
-
-(defun git-rev-parse (rev)
- "Parse a revision name and return its SHA1."
- (git-get-string-sha1
- (git-call-process-string "rev-parse" rev)))
-
-(defun git-config (key)
- "Retrieve the value associated to KEY in the git repository config file."
- (let ((str (git-call-process-string "config" key)))
- (and str (car (split-string str "\n")))))
-
-(defun git-symbolic-ref (ref)
- "Wrapper for the git-symbolic-ref command."
- (let ((str (git-call-process-string "symbolic-ref" ref)))
- (and str (car (split-string str "\n")))))
-
-(defun git-update-ref (ref newval &optional oldval reason)
- "Update a reference by calling git-update-ref."
- (let ((args (and oldval (list oldval))))
- (when newval (push newval args))
- (push ref args)
- (when reason
- (push reason args)
- (push "-m" args))
- (unless newval (push "-d" args))
- (apply 'git-call-process-display-error "update-ref" args)))
-
-(defun git-for-each-ref (&rest specs)
- "Return a list of refs using git-for-each-ref.
-Each entry is a cons of (SHORT-NAME . FULL-NAME)."
- (let (refs)
- (with-temp-buffer
- (apply #'git-call-process t "for-each-ref" "--format=%(refname)" specs)
- (goto-char (point-min))
- (while (re-search-forward "^[^/\n]+/[^/\n]+/\\(.+\\)$" nil t)
- (push (cons (match-string 1) (match-string 0)) refs)))
- (nreverse refs)))
-
-(defun git-read-tree (tree &optional index-file)
- "Read a tree into the index file."
- (let ((process-environment
- (append (and index-file (list (concat "GIT_INDEX_FILE=" index-file))) process-environment)))
- (apply 'git-call-process-display-error "read-tree" (if tree (list tree)))))
-
-(defun git-write-tree (&optional index-file)
- "Call git-write-tree and return the resulting tree SHA1 as a string."
- (let ((process-environment
- (append (and index-file (list (concat "GIT_INDEX_FILE=" index-file))) process-environment)))
- (git-get-string-sha1
- (git-call-process-string-display-error "write-tree"))))
-
-(defun git-commit-tree (buffer tree parent)
- "Create a commit and possibly update HEAD.
-Create a commit with the message in BUFFER using the tree with hash TREE.
-Use PARENT as the parent of the new commit. If PARENT is the current \"HEAD\",
-update the \"HEAD\" reference to the new commit."
- (let ((author-name (git-get-committer-name))
- (author-email (git-get-committer-email))
- (subject "commit (initial): ")
- author-date log-start log-end args coding-system-for-write)
- (when parent
- (setq subject "commit: ")
- (push "-p" args)
- (push parent args))
- (with-current-buffer buffer
- (goto-char (point-min))
- (if
- (setq log-start (re-search-forward (concat "^" (regexp-quote git-log-msg-separator) "\n") nil t))
- (save-restriction
- (narrow-to-region (point-min) log-start)
- (goto-char (point-min))
- (when (re-search-forward "^Author: +\\(.*?\\) *<\\(.*\\)> *$" nil t)
- (setq author-name (match-string 1)
- author-email (match-string 2)))
- (goto-char (point-min))
- (when (re-search-forward "^Date: +\\(.*\\)$" nil t)
- (setq author-date (match-string 1)))
- (goto-char (point-min))
- (when (re-search-forward "^Merge: +\\(.*\\)" nil t)
- (setq subject "commit (merge): ")
- (dolist (parent (split-string (match-string 1) " +" t))
- (push "-p" args)
- (push parent args))))
- (setq log-start (point-min)))
- (setq log-end (point-max))
- (goto-char log-start)
- (when (re-search-forward ".*$" nil t)
- (setq subject (concat subject (match-string 0))))
- (setq coding-system-for-write buffer-file-coding-system))
- (let ((commit
- (git-get-string-sha1
- (let ((env `(("GIT_AUTHOR_NAME" . ,author-name)
- ("GIT_AUTHOR_EMAIL" . ,author-email)
- ("GIT_COMMITTER_NAME" . ,(git-get-committer-name))
- ("GIT_COMMITTER_EMAIL" . ,(git-get-committer-email)))))
- (when author-date (push `("GIT_AUTHOR_DATE" . ,author-date) env))
- (apply #'git-run-command-region
- buffer log-start log-end env
- "commit-tree" tree (nreverse args))))))
- (when commit (git-update-ref "HEAD" commit parent subject))
- commit)))
-
-(defun git-empty-db-p ()
- "Check if the git db is empty (no commit done yet)."
- (not (eq 0 (git-call-process nil "rev-parse" "--verify" "HEAD"))))
-
-(defun git-get-merge-heads ()
- "Retrieve the merge heads from the MERGE_HEAD file if present."
- (let (heads)
- (when (file-readable-p ".git/MERGE_HEAD")
- (with-temp-buffer
- (insert-file-contents ".git/MERGE_HEAD" nil nil nil t)
- (goto-char (point-min))
- (while (re-search-forward "[0-9a-f]\\{40\\}" nil t)
- (push (match-string 0) heads))))
- (nreverse heads)))
-
-(defun git-get-commit-description (commit)
- "Get a one-line description of COMMIT."
- (let ((coding-system-for-read (git-get-logoutput-coding-system)))
- (let ((descr (git-call-process-string "log" "--max-count=1" "--pretty=oneline" commit)))
- (if (and descr (string-match "\\`\\([0-9a-f]\\{40\\}\\) *\\(.*\\)$" descr))
- (concat (substring (match-string 1 descr) 0 10) " - " (match-string 2 descr))
- descr))))
-
-;;;; File info structure
-;;;; ------------------------------------------------------------
-
-; fileinfo structure stolen from pcl-cvs
-(defstruct (git-fileinfo
- (:copier nil)
- (:constructor git-create-fileinfo (state name &optional old-perm new-perm rename-state orig-name marked))
- (:conc-name git-fileinfo->))
- marked ;; t/nil
- state ;; current state
- name ;; file name
- old-perm new-perm ;; permission flags
- rename-state ;; rename or copy state
- orig-name ;; original name for renames or copies
- needs-update ;; whether file needs to be updated
- needs-refresh) ;; whether file needs to be refreshed
-
-(defvar git-status nil)
-
-(defun git-set-fileinfo-state (info state)
- "Set the state of a file info."
- (unless (eq (git-fileinfo->state info) state)
- (setf (git-fileinfo->state info) state
- (git-fileinfo->new-perm info) (git-fileinfo->old-perm info)
- (git-fileinfo->rename-state info) nil
- (git-fileinfo->orig-name info) nil
- (git-fileinfo->needs-update info) nil
- (git-fileinfo->needs-refresh info) t)))
-
-(defun git-status-filenames-map (status func files &rest args)
- "Apply FUNC to the status files names in the FILES list.
-The list must be sorted."
- (when files
- (let ((file (pop files))
- (node (ewoc-nth status 0)))
- (while (and file node)
- (let* ((info (ewoc-data node))
- (name (git-fileinfo->name info)))
- (if (string-lessp name file)
- (setq node (ewoc-next status node))
- (if (string-equal name file)
- (apply func info args))
- (setq file (pop files))))))))
-
-(defun git-set-filenames-state (status files state)
- "Set the state of a list of named files. The list must be sorted"
- (when files
- (git-status-filenames-map status #'git-set-fileinfo-state files state)
- (unless state ;; delete files whose state has been set to nil
- (ewoc-filter status (lambda (info) (git-fileinfo->state info))))))
-
-(defun git-state-code (code)
- "Convert from a string to a added/deleted/modified state."
- (case (string-to-char code)
- (?M 'modified)
- (?? 'unknown)
- (?A 'added)
- (?D 'deleted)
- (?U 'unmerged)
- (?T 'modified)
- (t nil)))
-
-(defun git-status-code-as-string (code)
- "Format a git status code as string."
- (case code
- ('modified (propertize "Modified" 'face 'git-status-face))
- ('unknown (propertize "Unknown " 'face 'git-unknown-face))
- ('added (propertize "Added " 'face 'git-status-face))
- ('deleted (propertize "Deleted " 'face 'git-status-face))
- ('unmerged (propertize "Unmerged" 'face 'git-unmerged-face))
- ('uptodate (propertize "Uptodate" 'face 'git-uptodate-face))
- ('ignored (propertize "Ignored " 'face 'git-ignored-face))
- (t "? ")))
-
-(defun git-file-type-as-string (old-perm new-perm)
- "Return a string describing the file type based on its permissions."
- (let* ((old-type (lsh (or old-perm 0) -9))
- (new-type (lsh (or new-perm 0) -9))
- (str (case new-type
- (64 ;; file
- (case old-type
- (64 nil)
- (80 " (type change symlink -> file)")
- (112 " (type change subproject -> file)")))
- (80 ;; symlink
- (case old-type
- (64 " (type change file -> symlink)")
- (112 " (type change subproject -> symlink)")
- (t " (symlink)")))
- (112 ;; subproject
- (case old-type
- (64 " (type change file -> subproject)")
- (80 " (type change symlink -> subproject)")
- (t " (subproject)")))
- (72 nil) ;; directory (internal, not a real git state)
- (0 ;; deleted or unknown
- (case old-type
- (80 " (symlink)")
- (112 " (subproject)")))
- (t (format " (unknown type %o)" new-type)))))
- (cond (str (propertize str 'face 'git-status-face))
- ((eq new-type 72) "/")
- (t ""))))
-
-(defun git-rename-as-string (info)
- "Return a string describing the copy or rename associated with INFO, or an empty string if none."
- (let ((state (git-fileinfo->rename-state info)))
- (if state
- (propertize
- (concat " ("
- (if (eq state 'copy) "copied from "
- (if (eq (git-fileinfo->state info) 'added) "renamed from "
- "renamed to "))
- (git-escape-file-name (git-fileinfo->orig-name info))
- ")") 'face 'git-status-face)
- "")))
-
-(defun git-permissions-as-string (old-perm new-perm)
- "Format a permission change as string."
- (propertize
- (if (or (not old-perm)
- (not new-perm)
- (eq 0 (logand ?\111 (logxor old-perm new-perm))))
- " "
- (if (eq 0 (logand ?\111 old-perm)) "+x" "-x"))
- 'face 'git-permission-face))
-
-(defun git-fileinfo-prettyprint (info)
- "Pretty-printer for the git-fileinfo structure."
- (let ((old-perm (git-fileinfo->old-perm info))
- (new-perm (git-fileinfo->new-perm info)))
- (insert (concat " " (if (git-fileinfo->marked info) (propertize "*" 'face 'git-mark-face) " ")
- " " (git-status-code-as-string (git-fileinfo->state info))
- " " (git-permissions-as-string old-perm new-perm)
- " " (git-escape-file-name (git-fileinfo->name info))
- (git-file-type-as-string old-perm new-perm)
- (git-rename-as-string info)))))
-
-(defun git-update-node-fileinfo (node info)
- "Update the fileinfo of the specified node. The names are assumed to match already."
- (let ((data (ewoc-data node)))
- (setf
- ;; preserve the marked flag
- (git-fileinfo->marked info) (git-fileinfo->marked data)
- (git-fileinfo->needs-update data) nil)
- (when (not (equal info data))
- (setf (git-fileinfo->needs-refresh info) t
- (ewoc-data node) info))))
-
-(defun git-insert-info-list (status infolist files)
- "Insert a sorted list of file infos in the status buffer, replacing existing ones if any."
- (let* ((info (pop infolist))
- (node (ewoc-nth status 0))
- (name (and info (git-fileinfo->name info)))
- remaining)
- (while info
- (let ((nodename (and node (git-fileinfo->name (ewoc-data node)))))
- (while (and files (string-lessp (car files) name))
- (push (pop files) remaining))
- (when (and files (string-equal (car files) name))
- (setq files (cdr files)))
- (cond ((not nodename)
- (setq node (ewoc-enter-last status info))
- (setq info (pop infolist))
- (setq name (and info (git-fileinfo->name info))))
- ((string-lessp nodename name)
- (setq node (ewoc-next status node)))
- ((string-equal nodename name)
- ;; preserve the marked flag
- (git-update-node-fileinfo node info)
- (setq info (pop infolist))
- (setq name (and info (git-fileinfo->name info))))
- (t
- (setq node (ewoc-enter-before status node info))
- (setq info (pop infolist))
- (setq name (and info (git-fileinfo->name info)))))))
- (nconc (nreverse remaining) files)))
-
-(defun git-run-diff-index (status files)
- "Run git-diff-index on FILES and parse the results into STATUS.
-Return the list of files that haven't been handled."
- (let (infolist)
- (with-temp-buffer
- (apply #'git-call-process t "diff-index" "-z" "-M" "HEAD" "--" files)
- (goto-char (point-min))
- (while (re-search-forward
- ":\\([0-7]\\{6\\}\\) \\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} \\(\\([ADMUT]\\)\0\\([^\0]+\\)\\|\\([CR]\\)[0-9]*\0\\([^\0]+\\)\0\\([^\0]+\\)\\)\0"
- nil t 1)
- (let ((old-perm (string-to-number (match-string 1) 8))
- (new-perm (string-to-number (match-string 2) 8))
- (state (or (match-string 4) (match-string 6)))
- (name (or (match-string 5) (match-string 7)))
- (new-name (match-string 8)))
- (if new-name ; copy or rename
- (if (eq ?C (string-to-char state))
- (push (git-create-fileinfo 'added new-name old-perm new-perm 'copy name) infolist)
- (push (git-create-fileinfo 'deleted name 0 0 'rename new-name) infolist)
- (push (git-create-fileinfo 'added new-name old-perm new-perm 'rename name) infolist))
- (push (git-create-fileinfo (git-state-code state) name old-perm new-perm) infolist)))))
- (setq infolist (sort (nreverse infolist)
- (lambda (info1 info2)
- (string-lessp (git-fileinfo->name info1)
- (git-fileinfo->name info2)))))
- (git-insert-info-list status infolist files)))
-
-(defun git-find-status-file (status file)
- "Find a given file in the status ewoc and return its node."
- (let ((node (ewoc-nth status 0)))
- (while (and node (not (string= file (git-fileinfo->name (ewoc-data node)))))
- (setq node (ewoc-next status node)))
- node))
-
-(defun git-run-ls-files (status files default-state &rest options)
- "Run git-ls-files on FILES and parse the results into STATUS.
-Return the list of files that haven't been handled."
- (let (infolist)
- (with-temp-buffer
- (apply #'git-call-process t "ls-files" "-z" (append options (list "--") files))
- (goto-char (point-min))
- (while (re-search-forward "\\([^\0]*?\\)\\(/?\\)\0" nil t 1)
- (let ((name (match-string 1)))
- (push (git-create-fileinfo default-state name 0
- (if (string-equal "/" (match-string 2)) (lsh ?\110 9) 0))
- infolist))))
- (setq infolist (nreverse infolist)) ;; assume it is sorted already
- (git-insert-info-list status infolist files)))
-
-(defun git-run-ls-files-cached (status files default-state)
- "Run git-ls-files -c on FILES and parse the results into STATUS.
-Return the list of files that haven't been handled."
- (let (infolist)
- (with-temp-buffer
- (apply #'git-call-process t "ls-files" "-z" "-s" "-c" "--" files)
- (goto-char (point-min))
- (while (re-search-forward "\\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} 0\t\\([^\0]+\\)\0" nil t)
- (let* ((new-perm (string-to-number (match-string 1) 8))
- (old-perm (if (eq default-state 'added) 0 new-perm))
- (name (match-string 2)))
- (push (git-create-fileinfo default-state name old-perm new-perm) infolist))))
- (setq infolist (nreverse infolist)) ;; assume it is sorted already
- (git-insert-info-list status infolist files)))
-
-(defun git-run-ls-unmerged (status files)
- "Run git-ls-files -u on FILES and parse the results into STATUS."
- (with-temp-buffer
- (apply #'git-call-process t "ls-files" "-z" "-u" "--" files)
- (goto-char (point-min))
- (let (unmerged-files)
- (while (re-search-forward "[0-7]\\{6\\} [0-9a-f]\\{40\\} [123]\t\\([^\0]+\\)\0" nil t)
- (push (match-string 1) unmerged-files))
- (setq unmerged-files (nreverse unmerged-files)) ;; assume it is sorted already
- (git-set-filenames-state status unmerged-files 'unmerged))))
-
-(defun git-get-exclude-files ()
- "Get the list of exclude files to pass to git-ls-files."
- (let (files
- (config (git-config "core.excludesfile")))
- (when (file-readable-p ".git/info/exclude")
- (push ".git/info/exclude" files))
- (when (and config (file-readable-p config))
- (push config files))
- files))
-
-(defun git-run-ls-files-with-excludes (status files default-state &rest options)
- "Run git-ls-files on FILES with appropriate --exclude-from options."
- (let ((exclude-files (git-get-exclude-files)))
- (apply #'git-run-ls-files status files default-state "--directory" "--no-empty-directory"
- (concat "--exclude-per-directory=" git-per-dir-ignore-file)
- (append options (mapcar (lambda (f) (concat "--exclude-from=" f)) exclude-files)))))
-
-(defun git-update-status-files (&optional files mark-files)
- "Update the status of FILES from the index.
-The FILES list must be sorted."
- (unless git-status (error "Not in git-status buffer."))
- ;; set the needs-update flag on existing files
- (if files
- (git-status-filenames-map
- git-status (lambda (info) (setf (git-fileinfo->needs-update info) t)) files)
- (ewoc-map (lambda (info) (setf (git-fileinfo->needs-update info) t) nil) git-status)
- (git-call-process nil "update-index" "--refresh")
- (when git-show-uptodate
- (git-run-ls-files-cached git-status nil 'uptodate)))
- (let ((remaining-files
- (if (git-empty-db-p) ; we need some special handling for an empty db
- (git-run-ls-files-cached git-status files 'added)
- (git-run-diff-index git-status files))))
- (git-run-ls-unmerged git-status files)
- (when (or remaining-files (and git-show-unknown (not files)))
- (setq remaining-files (git-run-ls-files-with-excludes git-status remaining-files 'unknown "-o")))
- (when (or remaining-files (and git-show-ignored (not files)))
- (setq remaining-files (git-run-ls-files-with-excludes git-status remaining-files 'ignored "-o" "-i")))
- (unless files
- (setq remaining-files (git-get-filenames (ewoc-collect git-status #'git-fileinfo->needs-update))))
- (when remaining-files
- (setq remaining-files (git-run-ls-files-cached git-status remaining-files 'uptodate)))
- (git-set-filenames-state git-status remaining-files nil)
- (when mark-files (git-mark-files git-status files))
- (git-refresh-files)
- (git-refresh-ewoc-hf git-status)))
-
-(defun git-mark-files (status files)
- "Mark all the specified FILES, and unmark the others."
- (let ((file (and files (pop files)))
- (node (ewoc-nth status 0)))
- (while node
- (let ((info (ewoc-data node)))
- (if (and file (string-equal (git-fileinfo->name info) file))
- (progn
- (unless (git-fileinfo->marked info)
- (setf (git-fileinfo->marked info) t)
- (setf (git-fileinfo->needs-refresh info) t))
- (setq file (pop files))
- (setq node (ewoc-next status node)))
- (when (git-fileinfo->marked info)
- (setf (git-fileinfo->marked info) nil)
- (setf (git-fileinfo->needs-refresh info) t))
- (if (and file (string-lessp file (git-fileinfo->name info)))
- (setq file (pop files))
- (setq node (ewoc-next status node))))))))
-
-(defun git-marked-files ()
- "Return a list of all marked files, or if none a list containing just the file at cursor position."
- (unless git-status (error "Not in git-status buffer."))
- (or (ewoc-collect git-status (lambda (info) (git-fileinfo->marked info)))
- (list (ewoc-data (ewoc-locate git-status)))))
-
-(defun git-marked-files-state (&rest states)
- "Return a sorted list of marked files that are in the specified states."
- (let ((files (git-marked-files))
- result)
- (dolist (info files)
- (when (memq (git-fileinfo->state info) states)
- (push info result)))
- (nreverse result)))
-
-(defun git-refresh-files ()
- "Refresh all files that need it and clear the needs-refresh flag."
- (unless git-status (error "Not in git-status buffer."))
- (ewoc-map
- (lambda (info)
- (let ((refresh (git-fileinfo->needs-refresh info)))
- (setf (git-fileinfo->needs-refresh info) nil)
- refresh))
- git-status)
- ; move back to goal column
- (when goal-column (move-to-column goal-column)))
-
-(defun git-refresh-ewoc-hf (status)
- "Refresh the ewoc header and footer."
- (let ((branch (git-symbolic-ref "HEAD"))
- (head (if (git-empty-db-p) "Nothing committed yet"
- (git-get-commit-description "HEAD")))
- (merge-heads (git-get-merge-heads)))
- (ewoc-set-hf status
- (format "Directory: %s\nBranch: %s\nHead: %s%s\n"
- default-directory
- (if branch
- (if (string-match "^refs/heads/" branch)
- (substring branch (match-end 0))
- branch)
- "none (detached HEAD)")
- head
- (if merge-heads
- (concat "\nMerging: "
- (mapconcat (lambda (str) (git-get-commit-description str)) merge-heads "\n "))
- ""))
- (if (ewoc-nth status 0) "" " No changes."))))
-
-(defun git-get-filenames (files)
- (mapcar (lambda (info) (git-fileinfo->name info)) files))
-
-(defun git-update-index (index-file files)
- "Run git-update-index on a list of files."
- (let ((process-environment (append (and index-file (list (concat "GIT_INDEX_FILE=" index-file)))
- process-environment))
- added deleted modified)
- (dolist (info files)
- (case (git-fileinfo->state info)
- ('added (push info added))
- ('deleted (push info deleted))
- ('modified (push info modified))))
- (and
- (or (not added) (apply #'git-call-process-display-error "update-index" "--add" "--" (git-get-filenames added)))
- (or (not deleted) (apply #'git-call-process-display-error "update-index" "--remove" "--" (git-get-filenames deleted)))
- (or (not modified) (apply #'git-call-process-display-error "update-index" "--" (git-get-filenames modified))))))
-
-(defun git-run-pre-commit-hook ()
- "Run the pre-commit hook if any."
- (unless git-status (error "Not in git-status buffer."))
- (let ((files (git-marked-files-state 'added 'deleted 'modified)))
- (or (not files)
- (not (file-executable-p ".git/hooks/pre-commit"))
- (let ((index-file (make-temp-file "gitidx")))
- (unwind-protect
- (let ((head-tree (unless (git-empty-db-p) (git-rev-parse "HEAD^{tree}"))))
- (git-read-tree head-tree index-file)
- (git-update-index index-file files)
- (git-run-hook "pre-commit" `(("GIT_INDEX_FILE" . ,index-file))))
- (delete-file index-file))))))
-
-(defun git-do-commit ()
- "Perform the actual commit using the current buffer as log message."
- (interactive)
- (let ((buffer (current-buffer))
- (index-file (make-temp-file "gitidx")))
- (with-current-buffer log-edit-parent-buffer
- (if (git-marked-files-state 'unmerged)
- (message "You cannot commit unmerged files, resolve them first.")
- (unwind-protect
- (let ((files (git-marked-files-state 'added 'deleted 'modified))
- head tree head-tree)
- (unless (git-empty-db-p)
- (setq head (git-rev-parse "HEAD")
- head-tree (git-rev-parse "HEAD^{tree}")))
- (message "Running git commit...")
- (when
- (and
- (git-read-tree head-tree index-file)
- (git-update-index nil files) ;update both the default index
- (git-update-index index-file files) ;and the temporary one
- (setq tree (git-write-tree index-file)))
- (if (or (not (string-equal tree head-tree))
- (yes-or-no-p "The tree was not modified, do you really want to perform an empty commit? "))
- (let ((commit (git-commit-tree buffer tree head)))
- (when commit
- (condition-case nil (delete-file ".git/MERGE_HEAD") (error nil))
- (condition-case nil (delete-file ".git/MERGE_MSG") (error nil))
- (with-current-buffer buffer (erase-buffer))
- (git-update-status-files (git-get-filenames files))
- (git-call-process nil "rerere")
- (git-call-process nil "gc" "--auto")
- (message "Committed %s." commit)
- (git-run-hook "post-commit" nil)))
- (message "Commit aborted."))))
- (delete-file index-file))))))
-
-
-;;;; Interactive functions
-;;;; ------------------------------------------------------------
-
-(defun git-mark-file ()
- "Mark the file that the cursor is on and move to the next one."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (let* ((pos (ewoc-locate git-status))
- (info (ewoc-data pos)))
- (setf (git-fileinfo->marked info) t)
- (ewoc-invalidate git-status pos)
- (ewoc-goto-next git-status 1)))
-
-(defun git-unmark-file ()
- "Unmark the file that the cursor is on and move to the next one."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (let* ((pos (ewoc-locate git-status))
- (info (ewoc-data pos)))
- (setf (git-fileinfo->marked info) nil)
- (ewoc-invalidate git-status pos)
- (ewoc-goto-next git-status 1)))
-
-(defun git-unmark-file-up ()
- "Unmark the file that the cursor is on and move to the previous one."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (let* ((pos (ewoc-locate git-status))
- (info (ewoc-data pos)))
- (setf (git-fileinfo->marked info) nil)
- (ewoc-invalidate git-status pos)
- (ewoc-goto-prev git-status 1)))
-
-(defun git-mark-all ()
- "Mark all files."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (ewoc-map (lambda (info) (unless (git-fileinfo->marked info)
- (setf (git-fileinfo->marked info) t))) git-status)
- ; move back to goal column after invalidate
- (when goal-column (move-to-column goal-column)))
-
-(defun git-unmark-all ()
- "Unmark all files."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (ewoc-map (lambda (info) (when (git-fileinfo->marked info)
- (setf (git-fileinfo->marked info) nil)
- t)) git-status)
- ; move back to goal column after invalidate
- (when goal-column (move-to-column goal-column)))
-
-(defun git-toggle-all-marks ()
- "Toggle all file marks."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (ewoc-map (lambda (info) (setf (git-fileinfo->marked info) (not (git-fileinfo->marked info))) t) git-status)
- ; move back to goal column after invalidate
- (when goal-column (move-to-column goal-column)))
-
-(defun git-next-file (&optional n)
- "Move the selection down N files."
- (interactive "p")
- (unless git-status (error "Not in git-status buffer."))
- (ewoc-goto-next git-status n))
-
-(defun git-prev-file (&optional n)
- "Move the selection up N files."
- (interactive "p")
- (unless git-status (error "Not in git-status buffer."))
- (ewoc-goto-prev git-status n))
-
-(defun git-next-unmerged-file (&optional n)
- "Move the selection down N unmerged files."
- (interactive "p")
- (unless git-status (error "Not in git-status buffer."))
- (let* ((last (ewoc-locate git-status))
- (node (ewoc-next git-status last)))
- (while (and node (> n 0))
- (when (eq 'unmerged (git-fileinfo->state (ewoc-data node)))
- (setq n (1- n))
- (setq last node))
- (setq node (ewoc-next git-status node)))
- (ewoc-goto-node git-status last)))
-
-(defun git-prev-unmerged-file (&optional n)
- "Move the selection up N unmerged files."
- (interactive "p")
- (unless git-status (error "Not in git-status buffer."))
- (let* ((last (ewoc-locate git-status))
- (node (ewoc-prev git-status last)))
- (while (and node (> n 0))
- (when (eq 'unmerged (git-fileinfo->state (ewoc-data node)))
- (setq n (1- n))
- (setq last node))
- (setq node (ewoc-prev git-status node)))
- (ewoc-goto-node git-status last)))
-
-(defun git-insert-file (file)
- "Insert file(s) into the git-status buffer."
- (interactive "fInsert file: ")
- (git-update-status-files (list (file-relative-name file))))
-
-(defun git-add-file ()
- "Add marked file(s) to the index cache."
- (interactive)
- (let ((files (git-get-filenames (git-marked-files-state 'unknown 'ignored 'unmerged))))
- ;; FIXME: add support for directories
- (unless files
- (push (file-relative-name (read-file-name "File to add: " nil nil t)) files))
- (when (apply 'git-call-process-display-error "update-index" "--add" "--" files)
- (git-update-status-files files)
- (git-success-message "Added" files))))
-
-(defun git-ignore-file ()
- "Add marked file(s) to the ignore list."
- (interactive)
- (let ((files (git-get-filenames (git-marked-files-state 'unknown))))
- (unless files
- (push (file-relative-name (read-file-name "File to ignore: " nil nil t)) files))
- (dolist (f files) (git-append-to-ignore f))
- (git-update-status-files files)
- (git-success-message "Ignored" files)))
-
-(defun git-remove-file ()
- "Remove the marked file(s)."
- (interactive)
- (let ((files (git-get-filenames (git-marked-files-state 'added 'modified 'unknown 'uptodate 'ignored))))
- (unless files
- (push (file-relative-name (read-file-name "File to remove: " nil nil t)) files))
- (if (yes-or-no-p
- (if (cdr files)
- (format "Remove %d files? " (length files))
- (format "Remove %s? " (car files))))
- (progn
- (dolist (name files)
- (ignore-errors
- (if (file-directory-p name)
- (delete-directory name)
- (delete-file name))))
- (when (apply 'git-call-process-display-error "update-index" "--remove" "--" files)
- (git-update-status-files files)
- (git-success-message "Removed" files)))
- (message "Aborting"))))
-
-(defun git-revert-file ()
- "Revert changes to the marked file(s)."
- (interactive)
- (let ((files (git-marked-files-state 'added 'deleted 'modified 'unmerged))
- added modified)
- (when (and files
- (yes-or-no-p
- (if (cdr files)
- (format "Revert %d files? " (length files))
- (format "Revert %s? " (git-fileinfo->name (car files))))))
- (dolist (info files)
- (case (git-fileinfo->state info)
- ('added (push (git-fileinfo->name info) added))
- ('deleted (push (git-fileinfo->name info) modified))
- ('unmerged (push (git-fileinfo->name info) modified))
- ('modified (push (git-fileinfo->name info) modified))))
- ;; check if a buffer contains one of the files and isn't saved
- (dolist (file modified)
- (let ((buffer (get-file-buffer file)))
- (when (and buffer (buffer-modified-p buffer))
- (error "Buffer %s is modified. Please kill or save modified buffers before reverting." (buffer-name buffer)))))
- (let ((ok (and
- (or (not added)
- (apply 'git-call-process-display-error "update-index" "--force-remove" "--" added))
- (or (not modified)
- (apply 'git-call-process-display-error "checkout" "HEAD" modified))))
- (names (git-get-filenames files)))
- (git-update-status-files names)
- (when ok
- (dolist (file modified)
- (let ((buffer (get-file-buffer file)))
- (when buffer (with-current-buffer buffer (revert-buffer t t t)))))
- (git-success-message "Reverted" names))))))
-
-(defun git-remove-handled ()
- "Remove handled files from the status list."
- (interactive)
- (ewoc-filter git-status
- (lambda (info)
- (case (git-fileinfo->state info)
- ('ignored git-show-ignored)
- ('uptodate git-show-uptodate)
- ('unknown git-show-unknown)
- (t t))))
- (unless (ewoc-nth git-status 0) ; refresh header if list is empty
- (git-refresh-ewoc-hf git-status)))
-
-(defun git-toggle-show-uptodate ()
- "Toogle the option for showing up-to-date files."
- (interactive)
- (if (setq git-show-uptodate (not git-show-uptodate))
- (git-refresh-status)
- (git-remove-handled)))
-
-(defun git-toggle-show-ignored ()
- "Toogle the option for showing ignored files."
- (interactive)
- (if (setq git-show-ignored (not git-show-ignored))
- (progn
- (message "Inserting ignored files...")
- (git-run-ls-files-with-excludes git-status nil 'ignored "-o" "-i")
- (git-refresh-files)
- (git-refresh-ewoc-hf git-status)
- (message "Inserting ignored files...done"))
- (git-remove-handled)))
-
-(defun git-toggle-show-unknown ()
- "Toogle the option for showing unknown files."
- (interactive)
- (if (setq git-show-unknown (not git-show-unknown))
- (progn
- (message "Inserting unknown files...")
- (git-run-ls-files-with-excludes git-status nil 'unknown "-o")
- (git-refresh-files)
- (git-refresh-ewoc-hf git-status)
- (message "Inserting unknown files...done"))
- (git-remove-handled)))
-
-(defun git-expand-directory (info)
- "Expand the directory represented by INFO to list its files."
- (when (eq (lsh (git-fileinfo->new-perm info) -9) ?\110)
- (let ((dir (git-fileinfo->name info)))
- (git-set-filenames-state git-status (list dir) nil)
- (git-run-ls-files-with-excludes git-status (list (concat dir "/")) 'unknown "-o")
- (git-refresh-files)
- (git-refresh-ewoc-hf git-status)
- t)))
-
-(defun git-setup-diff-buffer (buffer)
- "Setup a buffer for displaying a diff."
- (let ((dir default-directory))
- (with-current-buffer buffer
- (diff-mode)
- (goto-char (point-min))
- (setq default-directory dir)
- (setq buffer-read-only t)))
- (display-buffer buffer)
- ; shrink window only if it displays the status buffer
- (when (eq (window-buffer) (current-buffer))
- (shrink-window-if-larger-than-buffer)))
-
-(defun git-diff-file ()
- "Diff the marked file(s) against HEAD."
- (interactive)
- (let ((files (git-marked-files)))
- (git-setup-diff-buffer
- (apply #'git-run-command-buffer "*git-diff*" "diff-index" "-p" "-M" "HEAD" "--" (git-get-filenames files)))))
-
-(defun git-diff-file-merge-head (arg)
- "Diff the marked file(s) against the first merge head (or the nth one with a numeric prefix)."
- (interactive "p")
- (let ((files (git-marked-files))
- (merge-heads (git-get-merge-heads)))
- (unless merge-heads (error "No merge in progress"))
- (git-setup-diff-buffer
- (apply #'git-run-command-buffer "*git-diff*" "diff-index" "-p" "-M"
- (or (nth (1- arg) merge-heads) "HEAD") "--" (git-get-filenames files)))))
-
-(defun git-diff-unmerged-file (stage)
- "Diff the marked unmerged file(s) against the specified stage."
- (let ((files (git-marked-files)))
- (git-setup-diff-buffer
- (apply #'git-run-command-buffer "*git-diff*" "diff-files" "-p" stage "--" (git-get-filenames files)))))
-
-(defun git-diff-file-base ()
- "Diff the marked unmerged file(s) against the common base file."
- (interactive)
- (git-diff-unmerged-file "-1"))
-
-(defun git-diff-file-mine ()
- "Diff the marked unmerged file(s) against my pre-merge version."
- (interactive)
- (git-diff-unmerged-file "-2"))
-
-(defun git-diff-file-other ()
- "Diff the marked unmerged file(s) against the other's pre-merge version."
- (interactive)
- (git-diff-unmerged-file "-3"))
-
-(defun git-diff-file-combined ()
- "Do a combined diff of the marked unmerged file(s)."
- (interactive)
- (git-diff-unmerged-file "-c"))
-
-(defun git-diff-file-idiff ()
- "Perform an interactive diff on the current file."
- (interactive)
- (let ((files (git-marked-files-state 'added 'deleted 'modified)))
- (unless (eq 1 (length files))
- (error "Cannot perform an interactive diff on multiple files."))
- (let* ((filename (car (git-get-filenames files)))
- (buff1 (find-file-noselect filename))
- (buff2 (git-run-command-buffer (concat filename ".~HEAD~") "cat-file" "blob" (concat "HEAD:" filename))))
- (ediff-buffers buff1 buff2))))
-
-(defun git-log-file ()
- "Display a log of changes to the marked file(s)."
- (interactive)
- (let* ((files (git-marked-files))
- (coding-system-for-read git-commits-coding-system)
- (buffer (apply #'git-run-command-buffer "*git-log*" "rev-list" "--pretty" "HEAD" "--" (git-get-filenames files))))
- (with-current-buffer buffer
- ; (git-log-mode) FIXME: implement log mode
- (goto-char (point-min))
- (setq buffer-read-only t))
- (display-buffer buffer)))
-
-(defun git-log-edit-files ()
- "Return a list of marked files for use in the log-edit buffer."
- (with-current-buffer log-edit-parent-buffer
- (git-get-filenames (git-marked-files-state 'added 'deleted 'modified))))
-
-(defun git-log-edit-diff ()
- "Run a diff of the current files being committed from a log-edit buffer."
- (with-current-buffer log-edit-parent-buffer
- (git-diff-file)))
-
-(defun git-append-sign-off (name email)
- "Append a Signed-off-by entry to the current buffer, avoiding duplicates."
- (let ((sign-off (format "Signed-off-by: %s <%s>" name email))
- (case-fold-search t))
- (goto-char (point-min))
- (unless (re-search-forward (concat "^" (regexp-quote sign-off)) nil t)
- (goto-char (point-min))
- (unless (re-search-forward "^Signed-off-by: " nil t)
- (setq sign-off (concat "\n" sign-off)))
- (goto-char (point-max))
- (insert sign-off "\n"))))
-
-(defun git-setup-log-buffer (buffer &optional merge-heads author-name author-email subject date msg)
- "Setup the log buffer for a commit."
- (unless git-status (error "Not in git-status buffer."))
- (let ((dir default-directory)
- (committer-name (git-get-committer-name))
- (committer-email (git-get-committer-email))
- (sign-off git-append-signed-off-by))
- (with-current-buffer buffer
- (cd dir)
- (erase-buffer)
- (insert
- (propertize
- (format "Author: %s <%s>\n%s%s"
- (or author-name committer-name)
- (or author-email committer-email)
- (if date (format "Date: %s\n" date) "")
- (if merge-heads
- (format "Merge: %s\n"
- (mapconcat 'identity merge-heads " "))
- ""))
- 'face 'git-header-face)
- (propertize git-log-msg-separator 'face 'git-separator-face)
- "\n")
- (when subject (insert subject "\n\n"))
- (cond (msg (insert msg "\n"))
- ((file-readable-p ".git/rebase-apply/msg")
- (insert-file-contents ".git/rebase-apply/msg"))
- ((file-readable-p ".git/MERGE_MSG")
- (insert-file-contents ".git/MERGE_MSG")))
- ; delete empty lines at end
- (goto-char (point-min))
- (when (re-search-forward "\n+\\'" nil t)
- (replace-match "\n" t t))
- (when sign-off (git-append-sign-off committer-name committer-email)))
- buffer))
-
-(define-derived-mode git-log-edit-mode log-edit-mode "Git-Log-Edit"
- "Major mode for editing git log messages.
-
-Set up git-specific `font-lock-keywords' for `log-edit-mode'."
- (set (make-local-variable 'font-lock-defaults)
- '(git-log-edit-font-lock-keywords t t)))
-
-(defun git-commit-file ()
- "Commit the marked file(s), asking for a commit message."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (when (git-run-pre-commit-hook)
- (let ((buffer (get-buffer-create "*git-commit*"))
- (coding-system (git-get-commits-coding-system))
- author-name author-email subject date)
- (when (eq 0 (buffer-size buffer))
- (when (file-readable-p ".git/rebase-apply/info")
- (with-temp-buffer
- (insert-file-contents ".git/rebase-apply/info")
- (goto-char (point-min))
- (when (re-search-forward "^Author: \\(.*\\)\nEmail: \\(.*\\)$" nil t)
- (setq author-name (match-string 1))
- (setq author-email (match-string 2)))
- (goto-char (point-min))
- (when (re-search-forward "^Subject: \\(.*\\)$" nil t)
- (setq subject (match-string 1)))
- (goto-char (point-min))
- (when (re-search-forward "^Date: \\(.*\\)$" nil t)
- (setq date (match-string 1)))))
- (git-setup-log-buffer buffer (git-get-merge-heads) author-name author-email subject date))
- (if (boundp 'log-edit-diff-function)
- (log-edit 'git-do-commit nil '((log-edit-listfun . git-log-edit-files)
- (log-edit-diff-function . git-log-edit-diff)) buffer 'git-log-edit-mode)
- (log-edit 'git-do-commit nil 'git-log-edit-files buffer
- 'git-log-edit-mode))
- (setq paragraph-separate (concat (regexp-quote git-log-msg-separator) "$\\|Author: \\|Date: \\|Merge: \\|Signed-off-by: \\|\f\\|[ ]*$"))
- (setq buffer-file-coding-system coding-system)
- (re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t))))
-
-(defun git-setup-commit-buffer (commit)
- "Setup the commit buffer with the contents of COMMIT."
- (let (parents author-name author-email subject date msg)
- (with-temp-buffer
- (let ((coding-system (git-get-logoutput-coding-system)))
- (git-call-process t "log" "-1" "--pretty=medium" "--abbrev=40" commit)
- (goto-char (point-min))
- (when (re-search-forward "^Merge: *\\(.*\\)$" nil t)
- (setq parents (cdr (split-string (match-string 1) " +"))))
- (when (re-search-forward "^Author: *\\(.*\\) <\\(.*\\)>$" nil t)
- (setq author-name (match-string 1))
- (setq author-email (match-string 2)))
- (when (re-search-forward "^Date: *\\(.*\\)$" nil t)
- (setq date (match-string 1)))
- (while (re-search-forward "^ \\(.*\\)$" nil t)
- (push (match-string 1) msg))
- (setq msg (nreverse msg))
- (setq subject (pop msg))
- (while (and msg (zerop (length (car msg))) (pop msg)))))
- (git-setup-log-buffer (get-buffer-create "*git-commit*")
- parents author-name author-email subject date
- (mapconcat #'identity msg "\n"))))
-
-(defun git-get-commit-files (commit)
- "Retrieve a sorted list of files modified by COMMIT."
- (let (files)
- (with-temp-buffer
- (git-call-process t "diff-tree" "-m" "-r" "-z" "--name-only" "--no-commit-id" "--root" commit)
- (goto-char (point-min))
- (while (re-search-forward "\\([^\0]*\\)\0" nil t 1)
- (push (match-string 1) files)))
- (sort files #'string-lessp)))
-
-(defun git-read-commit-name (prompt &optional default)
- "Ask for a commit name, with completion for local branch, remote branch and tag."
- (completing-read prompt
- (list* "HEAD" "ORIG_HEAD" "FETCH_HEAD" (mapcar #'car (git-for-each-ref)))
- nil nil nil nil default))
-
-(defun git-checkout (branch &optional merge)
- "Checkout a branch, tag, or any commit.
-Use a prefix arg if git should merge while checking out."
- (interactive
- (list (git-read-commit-name "Checkout: ")
- current-prefix-arg))
- (unless git-status (error "Not in git-status buffer."))
- (let ((args (list branch "--")))
- (when merge (push "-m" args))
- (when (apply #'git-call-process-display-error "checkout" args)
- (git-update-status-files))))
-
-(defun git-branch (branch)
- "Create a branch from the current HEAD and switch to it."
- (interactive (list (git-read-commit-name "Branch: ")))
- (unless git-status (error "Not in git-status buffer."))
- (if (git-rev-parse (concat "refs/heads/" branch))
- (if (yes-or-no-p (format "Branch %s already exists, replace it? " branch))
- (and (git-call-process-display-error "branch" "-f" branch)
- (git-call-process-display-error "checkout" branch))
- (message "Canceled."))
- (git-call-process-display-error "checkout" "-b" branch))
- (git-refresh-ewoc-hf git-status))
-
-(defun git-amend-commit ()
- "Undo the last commit on HEAD, and set things up to commit an
-amended version of it."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (when (git-empty-db-p) (error "No commit to amend."))
- (let* ((commit (git-rev-parse "HEAD"))
- (files (git-get-commit-files commit)))
- (when (if (git-rev-parse "HEAD^")
- (git-call-process-display-error "reset" "--soft" "HEAD^")
- (and (git-update-ref "ORIG_HEAD" commit)
- (git-update-ref "HEAD" nil commit)))
- (git-update-status-files files t)
- (git-setup-commit-buffer commit)
- (git-commit-file))))
-
-(defun git-cherry-pick-commit (arg)
- "Cherry-pick a commit."
- (interactive (list (git-read-commit-name "Cherry-pick commit: ")))
- (unless git-status (error "Not in git-status buffer."))
- (let ((commit (git-rev-parse (concat arg "^0"))))
- (unless commit (error "Not a valid commit '%s'." arg))
- (when (git-rev-parse (concat commit "^2"))
- (error "Cannot cherry-pick a merge commit."))
- (let ((files (git-get-commit-files commit))
- (ok (git-call-process-display-error "cherry-pick" "-n" commit)))
- (git-update-status-files files ok)
- (with-current-buffer (git-setup-commit-buffer commit)
- (goto-char (point-min))
- (if (re-search-forward "^\n*Signed-off-by:" nil t 1)
- (goto-char (match-beginning 0))
- (goto-char (point-max)))
- (insert "(cherry picked from commit " commit ")\n"))
- (when ok (git-commit-file)))))
-
-(defun git-revert-commit (arg)
- "Revert a commit."
- (interactive (list (git-read-commit-name "Revert commit: ")))
- (unless git-status (error "Not in git-status buffer."))
- (let ((commit (git-rev-parse (concat arg "^0"))))
- (unless commit (error "Not a valid commit '%s'." arg))
- (when (git-rev-parse (concat commit "^2"))
- (error "Cannot revert a merge commit."))
- (let ((files (git-get-commit-files commit))
- (subject (git-get-commit-description commit))
- (ok (git-call-process-display-error "revert" "-n" commit)))
- (git-update-status-files files ok)
- (when (string-match "^[0-9a-f]+ - \\(.*\\)$" subject)
- (setq subject (match-string 1 subject)))
- (git-setup-log-buffer (get-buffer-create "*git-commit*")
- (git-get-merge-heads) nil nil (format "Revert \"%s\"" subject) nil
- (format "This reverts commit %s.\n" commit))
- (when ok (git-commit-file)))))
-
-(defun git-find-file ()
- "Visit the current file in its own buffer."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (let ((info (ewoc-data (ewoc-locate git-status))))
- (unless (git-expand-directory info)
- (find-file (git-fileinfo->name info))
- (when (eq 'unmerged (git-fileinfo->state info))
- (smerge-mode 1)))))
-
-(defun git-find-file-other-window ()
- "Visit the current file in its own buffer in another window."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (let ((info (ewoc-data (ewoc-locate git-status))))
- (find-file-other-window (git-fileinfo->name info))
- (when (eq 'unmerged (git-fileinfo->state info))
- (smerge-mode))))
-
-(defun git-find-file-imerge ()
- "Visit the current file in interactive merge mode."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (let ((info (ewoc-data (ewoc-locate git-status))))
- (find-file (git-fileinfo->name info))
- (smerge-ediff)))
-
-(defun git-view-file ()
- "View the current file in its own buffer."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (let ((info (ewoc-data (ewoc-locate git-status))))
- (view-file (git-fileinfo->name info))))
-
-(defun git-refresh-status ()
- "Refresh the git status buffer."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (message "Refreshing git status...")
- (git-update-status-files)
- (message "Refreshing git status...done"))
-
-(defun git-status-quit ()
- "Quit git-status mode."
- (interactive)
- (bury-buffer))
-
-;;;; Major Mode
-;;;; ------------------------------------------------------------
-
-(defvar git-status-mode-hook nil
- "Run after `git-status-mode' is setup.")
-
-(defvar git-status-mode-map nil
- "Keymap for git major mode.")
-
-(defvar git-status nil
- "List of all files managed by the git-status mode.")
-
-(unless git-status-mode-map
- (let ((map (make-keymap))
- (commit-map (make-sparse-keymap))
- (diff-map (make-sparse-keymap))
- (toggle-map (make-sparse-keymap)))
- (suppress-keymap map)
- (define-key map "?" 'git-help)
- (define-key map "h" 'git-help)
- (define-key map " " 'git-next-file)
- (define-key map "a" 'git-add-file)
- (define-key map "c" 'git-commit-file)
- (define-key map "\C-c" commit-map)
- (define-key map "d" diff-map)
- (define-key map "=" 'git-diff-file)
- (define-key map "f" 'git-find-file)
- (define-key map "\r" 'git-find-file)
- (define-key map "g" 'git-refresh-status)
- (define-key map "i" 'git-ignore-file)
- (define-key map "I" 'git-insert-file)
- (define-key map "l" 'git-log-file)
- (define-key map "m" 'git-mark-file)
- (define-key map "M" 'git-mark-all)
- (define-key map "n" 'git-next-file)
- (define-key map "N" 'git-next-unmerged-file)
- (define-key map "o" 'git-find-file-other-window)
- (define-key map "p" 'git-prev-file)
- (define-key map "P" 'git-prev-unmerged-file)
- (define-key map "q" 'git-status-quit)
- (define-key map "r" 'git-remove-file)
- (define-key map "t" toggle-map)
- (define-key map "T" 'git-toggle-all-marks)
- (define-key map "u" 'git-unmark-file)
- (define-key map "U" 'git-revert-file)
- (define-key map "v" 'git-view-file)
- (define-key map "x" 'git-remove-handled)
- (define-key map "\C-?" 'git-unmark-file-up)
- (define-key map "\M-\C-?" 'git-unmark-all)
- ; the commit submap
- (define-key commit-map "\C-a" 'git-amend-commit)
- (define-key commit-map "\C-b" 'git-branch)
- (define-key commit-map "\C-o" 'git-checkout)
- (define-key commit-map "\C-p" 'git-cherry-pick-commit)
- (define-key commit-map "\C-v" 'git-revert-commit)
- ; the diff submap
- (define-key diff-map "b" 'git-diff-file-base)
- (define-key diff-map "c" 'git-diff-file-combined)
- (define-key diff-map "=" 'git-diff-file)
- (define-key diff-map "e" 'git-diff-file-idiff)
- (define-key diff-map "E" 'git-find-file-imerge)
- (define-key diff-map "h" 'git-diff-file-merge-head)
- (define-key diff-map "m" 'git-diff-file-mine)
- (define-key diff-map "o" 'git-diff-file-other)
- ; the toggle submap
- (define-key toggle-map "u" 'git-toggle-show-uptodate)
- (define-key toggle-map "i" 'git-toggle-show-ignored)
- (define-key toggle-map "k" 'git-toggle-show-unknown)
- (define-key toggle-map "m" 'git-toggle-all-marks)
- (setq git-status-mode-map map))
- (easy-menu-define git-menu git-status-mode-map
- "Git Menu"
- `("Git"
- ["Refresh" git-refresh-status t]
- ["Commit" git-commit-file t]
- ["Checkout..." git-checkout t]
- ["New Branch..." git-branch t]
- ["Cherry-pick Commit..." git-cherry-pick-commit t]
- ["Revert Commit..." git-revert-commit t]
- ("Merge"
- ["Next Unmerged File" git-next-unmerged-file t]
- ["Prev Unmerged File" git-prev-unmerged-file t]
- ["Interactive Merge File" git-find-file-imerge t]
- ["Diff Against Common Base File" git-diff-file-base t]
- ["Diff Combined" git-diff-file-combined t]
- ["Diff Against Merge Head" git-diff-file-merge-head t]
- ["Diff Against Mine" git-diff-file-mine t]
- ["Diff Against Other" git-diff-file-other t])
- "--------"
- ["Add File" git-add-file t]
- ["Revert File" git-revert-file t]
- ["Ignore File" git-ignore-file t]
- ["Remove File" git-remove-file t]
- ["Insert File" git-insert-file t]
- "--------"
- ["Find File" git-find-file t]
- ["View File" git-view-file t]
- ["Diff File" git-diff-file t]
- ["Interactive Diff File" git-diff-file-idiff t]
- ["Log" git-log-file t]
- "--------"
- ["Mark" git-mark-file t]
- ["Mark All" git-mark-all t]
- ["Unmark" git-unmark-file t]
- ["Unmark All" git-unmark-all t]
- ["Toggle All Marks" git-toggle-all-marks t]
- ["Hide Handled Files" git-remove-handled t]
- "--------"
- ["Show Uptodate Files" git-toggle-show-uptodate :style toggle :selected git-show-uptodate]
- ["Show Ignored Files" git-toggle-show-ignored :style toggle :selected git-show-ignored]
- ["Show Unknown Files" git-toggle-show-unknown :style toggle :selected git-show-unknown]
- "--------"
- ["Quit" git-status-quit t])))
-
-
-;; git mode should only run in the *git status* buffer
-(put 'git-status-mode 'mode-class 'special)
-
-(defun git-status-mode ()
- "Major mode for interacting with Git.
-Commands:
-\\{git-status-mode-map}"
- (kill-all-local-variables)
- (buffer-disable-undo)
- (setq mode-name "git status"
- major-mode 'git-status-mode
- goal-column 17
- buffer-read-only t)
- (use-local-map git-status-mode-map)
- (let ((buffer-read-only nil))
- (erase-buffer)
- (let ((status (ewoc-create 'git-fileinfo-prettyprint "" "")))
- (set (make-local-variable 'git-status) status))
- (set (make-local-variable 'list-buffers-directory) default-directory)
- (make-local-variable 'git-show-uptodate)
- (make-local-variable 'git-show-ignored)
- (make-local-variable 'git-show-unknown)
- (run-hooks 'git-status-mode-hook)))
-
-(defun git-find-status-buffer (dir)
- "Find the git status buffer handling a specified directory."
- (let ((list (buffer-list))
- (fulldir (expand-file-name dir))
- found)
- (while (and list (not found))
- (let ((buffer (car list)))
- (with-current-buffer buffer
- (when (and list-buffers-directory
- (string-equal fulldir (expand-file-name list-buffers-directory))
- (eq major-mode 'git-status-mode))
- (setq found buffer))))
- (setq list (cdr list)))
- found))
-
-(defun git-status (dir)
- "Entry point into git-status mode."
- (interactive "DSelect directory: ")
- (setq dir (git-get-top-dir dir))
- (if (file-exists-p (concat (file-name-as-directory dir) ".git"))
- (let ((buffer (or (and git-reuse-status-buffer (git-find-status-buffer dir))
- (create-file-buffer (expand-file-name "*git-status*" dir)))))
- (switch-to-buffer buffer)
- (cd dir)
- (git-status-mode)
- (git-refresh-status)
- (goto-char (point-min))
- (add-hook 'after-save-hook 'git-update-saved-file))
- (message "%s is not a git working tree." dir)))
-
-(defun git-update-saved-file ()
- "Update the corresponding git-status buffer when a file is saved.
-Meant to be used in `after-save-hook'."
- (let* ((file (expand-file-name buffer-file-name))
- (dir (condition-case nil (git-get-top-dir (file-name-directory file)) (error nil)))
- (buffer (and dir (git-find-status-buffer dir))))
- (when buffer
- (with-current-buffer buffer
- (let ((filename (file-relative-name file dir)))
- ; skip files located inside the .git directory
- (unless (string-match "^\\.git/" filename)
- (git-call-process nil "add" "--refresh" "--" filename)
- (git-update-status-files (list filename))))))))
-
-(defun git-help ()
- "Display help for Git mode."
- (interactive)
- (describe-function 'git-status-mode))
-
-(provide 'git)
-;;; git.el ends here
+(error "git.el no longer ships with git. It's recommended to
+replace its use with Magit, or simply delete references to git.el
+in your initialization file(s). See contrib/emacs/README in git's
+sources (https://github.com/git/git/blob/master/contrib/emacs/README)
+for suggested alternatives and for why this happened. Emacs's own
+VC mode and Magit are viable alternatives.")
diff --git a/contrib/fast-import/import-tars.perl b/contrib/fast-import/import-tars.perl
index d60b431..d50ce26 100755
--- a/contrib/fast-import/import-tars.perl
+++ b/contrib/fast-import/import-tars.perl
@@ -63,6 +63,8 @@ foreach my $tar_file (@ARGV)
my $have_top_dir = 1;
my ($top_dir, %files);
+ my $next_path = '';
+
while (read(I, $_, 512) == 512) {
my ($name, $mode, $uid, $gid, $size, $mtime,
$chksum, $typeflag, $linkname, $magic,
@@ -70,6 +72,13 @@ foreach my $tar_file (@ARGV)
$prefix) = unpack 'Z100 Z8 Z8 Z8 Z12 Z12
Z8 Z1 Z100 Z6
Z2 Z32 Z32 Z8 Z8 Z*', $_;
+
+ unless ($next_path eq '') {
+ # Recover name from previous extended header
+ $name = $next_path;
+ $next_path = '';
+ }
+
last unless length($name);
if ($name eq '././@LongLink') {
# GNU tar extension
@@ -90,13 +99,31 @@ foreach my $tar_file (@ARGV)
Z8 Z1 Z100 Z6
Z2 Z32 Z32 Z8 Z8 Z*', $_;
}
- next if $name =~ m{/\z};
$mode = oct $mode;
$size = oct $size;
$mtime = oct $mtime;
next if $typeflag == 5; # directory
- if ($typeflag != 1) { # handle hard links later
+ if ($typeflag eq 'x') { # extended header
+ # If extended header, check for path
+ my $pax_header = '';
+ while ($size > 0 && read(I, $_, 512) == 512) {
+ $pax_header = $pax_header . substr($_, 0, $size);
+ $size -= 512;
+ }
+
+ my @lines = split /\n/, $pax_header;
+ foreach my $line (@lines) {
+ my ($len, $entry) = split / /, $line;
+ my ($key, $value) = split /=/, $entry;
+ if ($key eq 'path') {
+ $next_path = $value;
+ }
+ }
+ next;
+ } elsif ($name =~ m{/\z}) { # directory
+ next;
+ } elsif ($typeflag != 1) { # handle hard links later
print FI "blob\n", "mark :$next_mark\n";
if ($typeflag == 2) { # symbolic link
print FI "data ", length($linkname), "\n",
@@ -112,6 +139,8 @@ foreach my $tar_file (@ARGV)
print FI "\n";
}
+ next if ($typeflag eq 'g'); # ignore global header
+
my $path;
if ($prefix) {
$path = "$prefix/$name";
diff --git a/contrib/git-jump/README b/contrib/git-jump/README
index 4484bda..2f618a7 100644
--- a/contrib/git-jump/README
+++ b/contrib/git-jump/README
@@ -25,6 +25,13 @@ git-jump will feed this to the editor:
foo.c:2: printf("hello word!\n");
-----------------------------------
+Or, when running 'git jump grep', column numbers will also be emitted,
+e.g. `git jump grep "hello"` would return:
+
+-----------------------------------
+foo.c:2:9: printf("hello word!\n");
+-----------------------------------
+
Obviously this trivial case isn't that interesting; you could just open
`foo.c` yourself. But when you have many changes scattered across a
project, you can use the editor's support to "jump" from point to point.
@@ -35,7 +42,8 @@ Git-jump can generate four types of interesting lists:
2. The beginning of any merge conflict markers.
- 3. Any grep matches.
+ 3. Any grep matches, including the column of the first match on a
+ line.
4. Any whitespace errors detected by `git diff --check`.
@@ -82,7 +90,7 @@ which does something similar to `git jump grep`. However, it is limited
to positioning the cursor to the correct line in only the first file,
leaving you to locate subsequent hits in that file or other files using
the editor or pager. By contrast, git-jump provides the editor with a
-complete list of files and line numbers for each match.
+complete list of files, lines, and a column number for each match.
Limitations
diff --git a/contrib/git-jump/git-jump b/contrib/git-jump/git-jump
index 80ab059..931b0fe 100755
--- a/contrib/git-jump/git-jump
+++ b/contrib/git-jump/git-jump
@@ -52,7 +52,7 @@ mode_merge() {
# editor shows them to us in the status bar.
mode_grep() {
cmd=$(git config jump.grepCmd)
- test -n "$cmd" || cmd="git grep -n"
+ test -n "$cmd" || cmd="git grep -n --column"
$cmd "$@" |
perl -pe '
s/[ \t]+/ /g;
diff --git a/contrib/hg-to-git/hg-to-git.py b/contrib/hg-to-git/hg-to-git.py
index de3f816..7eb1b24 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 2076cf9..35791fd 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 da65570..de20a54 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 161b023..0444442 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 5105373..7c0fc4a 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 00aedd9..5054833 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 73fdda6..f563be8 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 992657b..241ba22 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 1ea113d..0f98c5a 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 8747b84..ff565eb 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 d18b317..0092d67 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 158958d..b733326 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/Makefile b/contrib/mw-to-git/Makefile
index a4b6f7a..4e60351 100644
--- a/contrib/mw-to-git/Makefile
+++ b/contrib/mw-to-git/Makefile
@@ -21,8 +21,9 @@ HERE=contrib/mw-to-git/
INSTALL = install
SCRIPT_PERL_FULL=$(patsubst %,$(HERE)/%,$(SCRIPT_PERL))
-INSTLIBDIR=$(shell $(MAKE) -C $(GIT_ROOT_DIR)/perl \
- -s --no-print-directory instlibdir)
+INSTLIBDIR=$(shell $(MAKE) -C $(GIT_ROOT_DIR)/ \
+ -s --no-print-directory prefix=$(prefix) \
+ perllibdir=$(perllibdir) perllibdir)
DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
INSTLIBDIR_SQ = $(subst ','\'',$(INSTLIBDIR))
diff --git a/contrib/mw-to-git/git-remote-mediawiki.perl b/contrib/mw-to-git/git-remote-mediawiki.perl
index af9cbc9..d8ff2e6 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 0f3f4e0..b033849 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 22f069d..9106833 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 &&
@@ -247,7 +247,7 @@ test_expect_success 'Test of resistance to modification of category on wiki for
wiki_editpage Notconsidered "this page will not appear on local" false &&
wiki_editpage Othercategory "this page will not appear on local" false -c=Cattwo &&
wiki_editpage Tobeedited "this page have been modified" true -c=Catone &&
- wiki_delete_page Tobedeleted
+ wiki_delete_page Tobedeleted &&
git clone -c remote.origin.categories="Catone" \
mediawiki::'"$WIKI_URL"' mw_dir_14 &&
wiki_getallpage ref_page_14 Catone &&
diff --git a/contrib/mw-to-git/t/test-gitmw-lib.sh b/contrib/mw-to-git/t/test-gitmw-lib.sh
index 6546294..3948a00 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/Makefile b/contrib/subtree/Makefile
index 5c6cc4a..6fa7496 100644
--- a/contrib/subtree/Makefile
+++ b/contrib/subtree/Makefile
@@ -25,14 +25,16 @@ ASCIIDOC_HTML = xhtml11
ASCIIDOC_DOCBOOK = docbook
ASCIIDOC_EXTRA =
XMLTO = xmlto
+XMLTO_EXTRA =
ifdef USE_ASCIIDOCTOR
ASCIIDOC = asciidoctor
ASCIIDOC_CONF =
ASCIIDOC_HTML = xhtml5
-ASCIIDOC_DOCBOOK = docbook45
+ASCIIDOC_DOCBOOK = docbook
ASCIIDOC_EXTRA += -I../../Documentation -rasciidoctor-extensions
ASCIIDOC_EXTRA += -alitdd='&\#x2d;&\#x2d;'
+XMLTO_EXTRA += --skip-validation
endif
ifndef SHELL_PATH
@@ -59,6 +61,10 @@ $(GIT_SUBTREE): $(GIT_SUBTREE_SH)
doc: $(GIT_SUBTREE_DOC) $(GIT_SUBTREE_HTML)
+man: $(GIT_SUBTREE_DOC)
+
+html: $(GIT_SUBTREE_HTML)
+
install: $(GIT_SUBTREE)
$(INSTALL) -d -m 755 $(DESTDIR)$(gitexecdir)
$(INSTALL) -m 755 $(GIT_SUBTREE) $(DESTDIR)$(gitexecdir)
@@ -74,7 +80,7 @@ install-html: $(GIT_SUBTREE_HTML)
$(INSTALL) -m 644 $^ $(DESTDIR)$(htmldir)
$(GIT_SUBTREE_DOC): $(GIT_SUBTREE_XML)
- $(XMLTO) -m $(MANPAGE_XSL) man $^
+ $(XMLTO) -m $(MANPAGE_XSL) $(XMLTO_EXTRA) man $^
$(GIT_SUBTREE_XML): $(GIT_SUBTREE_TXT)
$(ASCIIDOC) -b $(ASCIIDOC_DOCBOOK) -d manpage $(ASCIIDOC_CONF) \
diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh
index d3f39a8..868e18b 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."
@@ -231,12 +238,14 @@ cache_miss () {
}
check_parents () {
- missed=$(cache_miss "$@")
+ missed=$(cache_miss "$1")
+ local indent=$(($2 + 1))
for miss in $missed
do
if ! test -r "$cachedir/notree/$miss"
then
debug " incorrect order: $miss"
+ process_split_commit "$miss" "" "$indent"
fi
done
}
@@ -340,7 +349,12 @@ find_existing_splits () {
revs="$2"
main=
sub=
- git log --grep="^git-subtree-dir: $dir/*\$" \
+ local grep_format="^git-subtree-dir: $dir/*\$"
+ if test -n "$ignore_joins"
+ then
+ grep_format="^Add '$dir/' from commit '"
+ fi
+ git log --grep="$grep_format" \
--no-show-signature --pretty=format:'START %H%n%s%n%n%b%nEND%n' $revs |
while read a b junk
do
@@ -534,6 +548,7 @@ copy_or_skip () {
nonidentical=
p=
gotparents=
+ copycommit=
for parent in $newparents
do
ptree=$(toptree_for_commit $parent) || exit $?
@@ -541,7 +556,24 @@ copy_or_skip () {
if test "$ptree" = "$tree"
then
# an identical parent could be used in place of this rev.
- identical="$parent"
+ if test -n "$identical"
+ then
+ # if a previous identical parent was found, check whether
+ # one is already an ancestor of the other
+ mergebase=$(git merge-base $identical $parent)
+ if test "$identical" = "$mergebase"
+ then
+ # current identical commit is an ancestor of parent
+ identical="$parent"
+ elif test "$parent" != "$mergebase"
+ then
+ # no common history; commit must be copied
+ copycommit=1
+ fi
+ else
+ # first identical parent detected
+ identical="$parent"
+ fi
else
nonidentical="$parent"
fi
@@ -564,7 +596,6 @@ copy_or_skip () {
fi
done
- copycommit=
if test -n "$identical" && test -n "$nonidentical"
then
extras=$(git rev-list --count $identical..$nonidentical)
@@ -598,6 +629,58 @@ ensure_valid_ref_format () {
die "'$1' does not look like a ref"
}
+process_split_commit () {
+ local rev="$1"
+ local parents="$2"
+ local indent=$3
+
+ if test $indent -eq 0
+ then
+ revcount=$(($revcount + 1))
+ else
+ # processing commit without normal parent information;
+ # fetch from repo
+ parents=$(git rev-parse "$rev^@")
+ extracount=$(($extracount + 1))
+ fi
+
+ progress "$revcount/$revmax ($createcount) [$extracount]"
+
+ debug "Processing commit: $rev"
+ exists=$(cache_get "$rev")
+ if test -n "$exists"
+ then
+ debug " prior: $exists"
+ return
+ fi
+ createcount=$(($createcount + 1))
+ debug " parents: $parents"
+ check_parents "$parents" "$indent"
+ newparents=$(cache_get $parents)
+ debug " newparents: $newparents"
+
+ tree=$(subtree_for_commit "$rev" "$dir")
+ debug " tree is: $tree"
+
+ # ugly. is there no better way to tell if this is a subtree
+ # vs. a mainline commit? Does it matter?
+ if test -z "$tree"
+ then
+ set_notree "$rev"
+ if test -n "$newparents"
+ then
+ cache_set "$rev" "$rev"
+ fi
+ return
+ fi
+
+ newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $?
+ debug " newrev is: $newrev"
+ cache_set "$rev" "$newrev"
+ cache_set latest_new "$newrev"
+ cache_set latest_old "$rev"
+}
+
cmd_add () {
if test -e "$dir"
then
@@ -640,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 $?
@@ -689,12 +771,7 @@ cmd_split () {
done
fi
- if test -n "$ignore_joins"
- then
- unrevs=
- else
- unrevs="$(find_existing_splits "$dir" "$revs")"
- fi
+ unrevs="$(find_existing_splits "$dir" "$revs")"
# We can't restrict rev-list to only $dir here, because some of our
# parents have the $dir contents the root, and those won't match.
@@ -703,45 +780,11 @@ cmd_split () {
revmax=$(eval "$grl" | wc -l)
revcount=0
createcount=0
+ extracount=0
eval "$grl" |
while read rev parents
do
- revcount=$(($revcount + 1))
- progress "$revcount/$revmax ($createcount)"
- debug "Processing commit: $rev"
- exists=$(cache_get "$rev")
- if test -n "$exists"
- then
- debug " prior: $exists"
- continue
- fi
- createcount=$(($createcount + 1))
- debug " parents: $parents"
- newparents=$(cache_get $parents)
- debug " newparents: $newparents"
-
- tree=$(subtree_for_commit "$rev" "$dir")
- debug " tree is: $tree"
-
- check_parents $parents
-
- # ugly. is there no better way to tell if this is a subtree
- # vs. a mainline commit? Does it matter?
- if test -z "$tree"
- then
- set_notree "$rev"
- if test -n "$newparents"
- then
- cache_set "$rev" "$rev"
- fi
- continue
- fi
-
- newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $?
- debug " newrev is: $newrev"
- cache_set "$rev" "$newrev"
- cache_set latest_new "$newrev"
- cache_set latest_old "$rev"
+ process_split_commit "$rev" "$parents" 0
done || exit $?
latest_new=$(cache_get latest_new)
@@ -780,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/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh
index d05c613..57ff4b2 100755
--- a/contrib/subtree/t/t7900-subtree.sh
+++ b/contrib/subtree/t/t7900-subtree.sh
@@ -540,26 +540,10 @@ test_expect_success 'make sure exactly the right set of files ends up in the sub
git fetch .. subproj-br &&
git merge FETCH_HEAD &&
- chks="sub1
-sub2
-sub3
-sub4" &&
- chks_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\''
-$chks
-TXT
-) &&
- chkms="main-sub1
-main-sub2
-main-sub3
-main-sub4" &&
- chkms_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\''
-$chkms
-TXT
-) &&
-
- subfiles=$(git ls-files) &&
- check_equal "$subfiles" "$chkms
-$chks"
+ test_write_lines main-sub1 main-sub2 main-sub3 main-sub4 \
+ sub1 sub2 sub3 sub4 >expect &&
+ git ls-files >actual &&
+ test_cmp expect actual
)
'
@@ -606,25 +590,11 @@ test_expect_success 'make sure the subproj *only* contains commits that affect t
git fetch .. subproj-br &&
git merge FETCH_HEAD &&
- chks="sub1
-sub2
-sub3
-sub4" &&
- chks_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\''
-$chks
-TXT
-) &&
- chkms="main-sub1
-main-sub2
-main-sub3
-main-sub4" &&
- chkms_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\''
-$chkms
-TXT
-) &&
- allchanges=$(git log --name-only --pretty=format:"" | sort | sed "/^$/d") &&
- check_equal "$allchanges" "$chkms
-$chks"
+ test_write_lines main-sub1 main-sub2 main-sub3 main-sub4 \
+ sub1 sub2 sub3 sub4 >expect &&
+ git log --name-only --pretty=format:"" >log &&
+ sort <log | sed "/^\$/ d" >actual &&
+ test_cmp expect actual
)
'
@@ -675,29 +645,16 @@ test_expect_success 'make sure exactly the right set of files ends up in the mai
cd "$subtree_test_count" &&
git subtree pull --prefix="sub dir" ./"sub proj" master &&
- chkm="main1
-main2" &&
- chks="sub1
-sub2
-sub3
-sub4" &&
- chks_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\''
-$chks
-TXT
-) &&
- chkms="main-sub1
-main-sub2
-main-sub3
-main-sub4" &&
- chkms_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\''
-$chkms
-TXT
-) &&
- mainfiles=$(git ls-files) &&
- check_equal "$mainfiles" "$chkm
-$chkms_sub
-$chks_sub"
-)
+ test_write_lines main1 main2 >chkm &&
+ test_write_lines main-sub1 main-sub2 main-sub3 main-sub4 >chkms &&
+ sed "s,^,sub dir/," chkms >chkms_sub &&
+ test_write_lines sub1 sub2 sub3 sub4 >chks &&
+ sed "s,^,sub dir/," chks >chks_sub &&
+
+ cat chkm chkms_sub chks_sub >expect &&
+ git ls-files >actual &&
+ test_cmp expect actual
+ )
'
next_test
@@ -708,7 +665,7 @@ test_expect_success 'make sure each filename changed exactly once in the entire
test_create_commit "$subtree_test_count/sub proj" sub1 &&
(
cd "$subtree_test_count" &&
- git config log.date relative
+ git config log.date relative &&
git fetch ./"sub proj" master &&
git subtree add --prefix="sub dir" FETCH_HEAD
) &&
@@ -748,37 +705,21 @@ test_expect_success 'make sure each filename changed exactly once in the entire
cd "$subtree_test_count" &&
git subtree pull --prefix="sub dir" ./"sub proj" master &&
- chkm="main1
-main2" &&
- chks="sub1
-sub2
-sub3
-sub4" &&
- chks_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\''
-$chks
-TXT
-) &&
- chkms="main-sub1
-main-sub2
-main-sub3
-main-sub4" &&
- chkms_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\''
-$chkms
-TXT
-) &&
+ test_write_lines main1 main2 >chkm &&
+ test_write_lines sub1 sub2 sub3 sub4 >chks &&
+ test_write_lines main-sub1 main-sub2 main-sub3 main-sub4 >chkms &&
+ sed "s,^,sub dir/," chkms >chkms_sub &&
# main-sub?? and /"sub dir"/main-sub?? both change, because those are the
# changes that were split into their own history. And "sub dir"/sub?? never
# change, since they were *only* changed in the subtree branch.
- allchanges=$(git log --name-only --pretty=format:"" | sort | sed "/^$/d") &&
- expected=''"$(cat <<TXT | sort
-$chkms
-$chkm
-$chks
-$chkms_sub
-TXT
-)"'' &&
- check_equal "$allchanges" "$expected"
+ git log --name-only --pretty=format:"" >log &&
+ sort <log >sorted-log &&
+ sed "/^$/ d" sorted-log >actual &&
+
+ cat chkms chkm chks chkms_sub >expect-unsorted &&
+ sort expect-unsorted >expect &&
+ test_cmp expect actual
)
'
diff --git a/contrib/svn-fe/svn-fe.txt b/contrib/svn-fe/svn-fe.txt
index a3425f4..19333fc 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 11ac6f6..8a3cee6 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':
diff --git a/contrib/update-unicode/README b/contrib/update-unicode/README
index b9e2fc8..151a197 100644
--- a/contrib/update-unicode/README
+++ b/contrib/update-unicode/README
@@ -1,10 +1,10 @@
TL;DR: Run update_unicode.sh after the publication of a new Unicode
-standard and commit the resulting unicode_widths.h file.
+standard and commit the resulting unicode-widths.h file.
The long version
================
-The Git source code ships the file unicode_widths.h which contains
+The Git source code ships the file unicode-widths.h which contains
tables of zero and double width Unicode code points, respectively.
These tables are generated using update_unicode.sh in this directory.
update_unicode.sh itself uses a third-party tool, uniset, to query two
@@ -16,5 +16,5 @@ This requires a current-ish version of autoconf (2.69 works per December
On each run, update_unicode.sh checks whether more recent Unicode data
files are available from the Unicode consortium, and rebuilds the header
-unicode_widths.h with the new data. The new header can then be
+unicode-widths.h with the new data. The new header can then be
committed.
diff --git a/contrib/update-unicode/update_unicode.sh b/contrib/update-unicode/update_unicode.sh
index e05db92..aa90865 100755
--- a/contrib/update-unicode/update_unicode.sh
+++ b/contrib/update-unicode/update_unicode.sh
@@ -6,7 +6,7 @@
#Cf Format a format control character
#
cd "$(dirname "$0")"
-UNICODEWIDTH_H=$(git rev-parse --show-toplevel)/unicode_width.h
+UNICODEWIDTH_H=$(git rev-parse --show-toplevel)/unicode-width.h
wget -N http://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt \
http://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt &&
diff --git a/contrib/vscode/.gitattributes b/contrib/vscode/.gitattributes
new file mode 100644
index 0000000..e89f223
--- /dev/null
+++ b/contrib/vscode/.gitattributes
@@ -0,0 +1 @@
+init.sh whitespace=-indent-with-non-tab
diff --git a/contrib/vscode/README.md b/contrib/vscode/README.md
new file mode 100644
index 0000000..8202d62
--- /dev/null
+++ b/contrib/vscode/README.md
@@ -0,0 +1,14 @@
+Configuration for VS Code
+=========================
+
+[VS Code](https://code.visualstudio.com/) is a lightweight but powerful source
+code editor which runs on your desktop and is available for
+[Windows](https://code.visualstudio.com/docs/setup/windows),
+[macOS](https://code.visualstudio.com/docs/setup/mac) and
+[Linux](https://code.visualstudio.com/docs/setup/linux). Among other languages,
+it has [support for C/C++ via an extension](https://github.com/Microsoft/vscode-cpptools).
+
+To start developing Git with VS Code, simply run the Unix shell script called
+`init.sh` in this directory, which creates the configuration files in
+`.vscode/` that VS Code consumes. `init.sh` needs access to `make` and `gcc`,
+so run the script in a Git SDK shell if you are using Windows.
diff --git a/contrib/vscode/init.sh b/contrib/vscode/init.sh
new file mode 100755
index 0000000..27de949
--- /dev/null
+++ b/contrib/vscode/init.sh
@@ -0,0 +1,375 @@
+#!/bin/sh
+
+die () {
+ echo "$*" >&2
+ exit 1
+}
+
+cd "$(dirname "$0")"/../.. ||
+die "Could not cd to top-level directory"
+
+mkdir -p .vscode ||
+die "Could not create .vscode/"
+
+# General settings
+
+cat >.vscode/settings.json.new <<\EOF ||
+{
+ "C_Cpp.intelliSenseEngine": "Default",
+ "C_Cpp.intelliSenseEngineFallback": "Disabled",
+ "[git-commit]": {
+ "editor.wordWrap": "wordWrapColumn",
+ "editor.wordWrapColumn": 72
+ },
+ "[c]": {
+ "editor.detectIndentation": false,
+ "editor.insertSpaces": false,
+ "editor.tabSize": 8,
+ "editor.wordWrap": "wordWrapColumn",
+ "editor.wordWrapColumn": 80,
+ "files.trimTrailingWhitespace": true
+ },
+ "files.associations": {
+ "*.h": "c",
+ "*.c": "c"
+ },
+ "cSpell.ignorePaths": [
+ ],
+ "cSpell.words": [
+ "DATAW",
+ "DBCACHED",
+ "DFCHECK",
+ "DTYPE",
+ "Hamano",
+ "HCAST",
+ "HEXSZ",
+ "HKEY",
+ "HKLM",
+ "IFGITLINK",
+ "IFINVALID",
+ "ISBROKEN",
+ "ISGITLINK",
+ "ISSYMREF",
+ "Junio",
+ "LPDWORD",
+ "LPPROC",
+ "LPWSTR",
+ "MSVCRT",
+ "NOARG",
+ "NOCOMPLETE",
+ "NOINHERIT",
+ "RENORMALIZE",
+ "STARTF",
+ "STARTUPINFOEXW",
+ "Schindelin",
+ "UCRT",
+ "YESNO",
+ "argcp",
+ "beginthreadex",
+ "committish",
+ "contentp",
+ "cpath",
+ "cpidx",
+ "ctim",
+ "dequote",
+ "envw",
+ "ewah",
+ "fdata",
+ "fherr",
+ "fhin",
+ "fhout",
+ "fragp",
+ "fsmonitor",
+ "hnsec",
+ "idents",
+ "includeif",
+ "interpr",
+ "iprog",
+ "isexe",
+ "iskeychar",
+ "kompare",
+ "mksnpath",
+ "mktag",
+ "mktree",
+ "mmblob",
+ "mmbuffer",
+ "mmfile",
+ "noenv",
+ "nparents",
+ "ntpath",
+ "ondisk",
+ "ooid",
+ "oplen",
+ "osdl",
+ "pnew",
+ "pold",
+ "ppinfo",
+ "pushf",
+ "pushv",
+ "rawsz",
+ "rebasing",
+ "reencode",
+ "repo",
+ "rerere",
+ "scld",
+ "sharedrepo",
+ "spawnv",
+ "spawnve",
+ "spawnvpe",
+ "strdup'ing",
+ "submodule",
+ "submodules",
+ "topath",
+ "topo",
+ "tpatch",
+ "unexecutable",
+ "unhide",
+ "unkc",
+ "unkv",
+ "unmark",
+ "unmatch",
+ "unsets",
+ "unshown",
+ "untracked",
+ "untrackedcache",
+ "unuse",
+ "upos",
+ "uval",
+ "vreportf",
+ "wargs",
+ "wargv",
+ "wbuffer",
+ "wcmd",
+ "wcsnicmp",
+ "wcstoutfdup",
+ "wdeltaenv",
+ "wdir",
+ "wenv",
+ "wenvblk",
+ "wenvcmp",
+ "wenviron",
+ "wenvpos",
+ "wenvsz",
+ "wfile",
+ "wfilename",
+ "wfopen",
+ "wfreopen",
+ "wfullpath",
+ "which'll",
+ "wlink",
+ "wmain",
+ "wmkdir",
+ "wmktemp",
+ "wnewpath",
+ "wotype",
+ "wpath",
+ "wpathname",
+ "wpgmptr",
+ "wpnew",
+ "wpointer",
+ "wpold",
+ "wpos",
+ "wputenv",
+ "wrmdir",
+ "wship",
+ "wtarget",
+ "wtemplate",
+ "wunlink",
+ "xcalloc",
+ "xgetcwd",
+ "xmallocz",
+ "xmemdupz",
+ "xmmap",
+ "xopts",
+ "xrealloc",
+ "xsnprintf",
+ "xutftowcs",
+ "xutftowcsn",
+ "xwcstoutf"
+ ],
+ "cSpell.ignoreRegExpList": [
+ "\\\"(DIRC|FSMN|REUC|UNTR)\\\"",
+ "\\\\u[0-9a-fA-Fx]{4}\\b",
+ "\\b(filfre|frotz|xyzzy)\\b",
+ "\\bCMIT_FMT_DEFAULT\\b",
+ "\\bde-munge\\b",
+ "\\bGET_OID_DISAMBIGUATORS\\b",
+ "\\bHASH_RENORMALIZE\\b",
+ "\\bTREESAMEness\\b",
+ "\\bUSE_STDEV\\b",
+ "\\Wchar *\\*\\W*utfs\\W",
+ "cURL's",
+ "nedmalloc'ed",
+ "ntifs\\.h",
+ ],
+}
+EOF
+die "Could not write settings.json"
+
+# Infer some setup-specific locations/names
+
+GCCPATH="$(which gcc)"
+GDBPATH="$(which gdb)"
+MAKECOMMAND="make -j5 DEVELOPER=1"
+OSNAME=
+X=
+case "$(uname -s)" in
+MINGW*)
+ GCCPATH="$(cygpath -am "$GCCPATH")"
+ GDBPATH="$(cygpath -am "$GDBPATH")"
+ MAKE_BASH="$(cygpath -am /git-cmd.exe) --command=usr\\\\bin\\\\bash.exe"
+ MAKECOMMAND="$MAKE_BASH -lc \\\"$MAKECOMMAND\\\""
+ OSNAME=Win32
+ X=.exe
+ ;;
+Linux)
+ OSNAME=Linux
+ ;;
+Darwin)
+ OSNAME=macOS
+ ;;
+esac
+
+# Default build task
+
+cat >.vscode/tasks.json.new <<EOF ||
+{
+ // See https://go.microsoft.com/fwlink/?LinkId=733558
+ // for the documentation about the tasks.json format
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "make",
+ "type": "shell",
+ "command": "$MAKECOMMAND",
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ }
+ }
+ ]
+}
+EOF
+die "Could not install default build task"
+
+# Debugger settings
+
+cat >.vscode/launch.json.new <<EOF ||
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit:
+ // https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "(gdb) Launch",
+ "type": "cppdbg",
+ "request": "launch",
+ "program": "\${workspaceFolder}/git$X",
+ "args": [],
+ "stopAtEntry": false,
+ "cwd": "\${workspaceFolder}",
+ "environment": [],
+ "externalConsole": true,
+ "MIMode": "gdb",
+ "miDebuggerPath": "$GDBPATH",
+ "setupCommands": [
+ {
+ "description": "Enable pretty-printing for gdb",
+ "text": "-enable-pretty-printing",
+ "ignoreFailures": true
+ }
+ ]
+ }
+ ]
+}
+EOF
+die "Could not write launch configuration"
+
+# C/C++ extension settings
+
+make -f - OSNAME=$OSNAME GCCPATH="$GCCPATH" vscode-init \
+ >.vscode/c_cpp_properties.json <<\EOF ||
+include Makefile
+
+vscode-init:
+ @mkdir -p .vscode && \
+ incs= && defs= && \
+ for e in $(ALL_CFLAGS) \
+ '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \
+ '-DGIT_LOCALE_PATH="$(localedir_relative_SQ)"' \
+ '-DBINDIR="$(bindir_relative_SQ)"' \
+ '-DFALLBACK_RUNTIME_PREFIX="$(prefix_SQ)"' \
+ '-DDEFAULT_GIT_TEMPLATE_DIR="$(template_dir_SQ)"' \
+ '-DETC_GITCONFIG="$(ETC_GITCONFIG_SQ)"' \
+ '-DETC_GITATTRIBUTES="$(ETC_GITATTRIBUTES_SQ)"' \
+ '-DGIT_LOCALE_PATH="$(localedir_relative_SQ)"' \
+ '-DCURL_DISABLE_TYPECHECK', \
+ '-DGIT_HTML_PATH="$(htmldir_relative_SQ)"' \
+ '-DGIT_MAN_PATH="$(mandir_relative_SQ)"' \
+ '-DGIT_INFO_PATH="$(infodir_relative_SQ)"'; do \
+ case "$$e" in \
+ -I.) \
+ incs="$$(printf '% 16s"$${workspaceRoot}",\n%s' \
+ "" "$$incs")" \
+ ;; \
+ -I/*) \
+ incs="$$(printf '% 16s"%s",\n%s' \
+ "" "$${e#-I}" "$$incs")" \
+ ;; \
+ -I*) \
+ incs="$$(printf '% 16s"$${workspaceRoot}/%s",\n%s' \
+ "" "$${e#-I}" "$$incs")" \
+ ;; \
+ -D*) \
+ defs="$$(printf '% 16s"%s",\n%s' \
+ "" "$$(echo "$${e#-D}" | sed 's/"/\\&/g')" \
+ "$$defs")" \
+ ;; \
+ esac; \
+ done && \
+ echo '{' && \
+ echo ' "configurations": [' && \
+ echo ' {' && \
+ echo ' "name": "$(OSNAME)",' && \
+ echo ' "intelliSenseMode": "clang-x64",' && \
+ echo ' "includePath": [' && \
+ echo "$$incs" | sort | sed '$$s/,$$//' && \
+ echo ' ],' && \
+ echo ' "defines": [' && \
+ echo "$$defs" | sort | sed '$$s/,$$//' && \
+ echo ' ],' && \
+ echo ' "browse": {' && \
+ echo ' "limitSymbolsToIncludedHeaders": true,' && \
+ echo ' "databaseFilename": "",' && \
+ echo ' "path": [' && \
+ echo ' "$${workspaceRoot}"' && \
+ echo ' ]' && \
+ echo ' },' && \
+ echo ' "cStandard": "c11",' && \
+ echo ' "cppStandard": "c++17",' && \
+ echo ' "compilerPath": "$(GCCPATH)"' && \
+ echo ' }' && \
+ echo ' ],' && \
+ echo ' "version": 4' && \
+ echo '}'
+EOF
+die "Could not write settings for the C/C++ extension"
+
+for file in .vscode/settings.json .vscode/tasks.json .vscode/launch.json
+do
+ if test -f $file
+ then
+ if git diff --no-index --quiet --exit-code $file $file.new
+ then
+ rm $file.new
+ else
+ printf "The file $file.new has these changes:\n\n"
+ git --no-pager diff --no-index $file $file.new
+ printf "\n\nMaybe \`mv $file.new $file\`?\n\n"
+ fi
+ else
+ mv $file.new $file
+ fi
+done