diff options
Diffstat (limited to 'contrib')
47 files changed, 6010 insertions, 1167 deletions
diff --git a/contrib/buildsystems/Generators.pm b/contrib/buildsystems/Generators.pm new file mode 100644 index 0000000000..408ef714b8 --- /dev/null +++ b/contrib/buildsystems/Generators.pm @@ -0,0 +1,42 @@ +package Generators; +require Exporter; + +use strict; +use File::Basename; +no strict 'refs'; +use vars qw($VERSION @AVAILABLE); + +our $VERSION = '1.00'; +our(@ISA, @EXPORT, @EXPORT_OK, @AVAILABLE); +@ISA = qw(Exporter); + +BEGIN { + local(*D); + my $me = $INC{"Generators.pm"}; + die "Couldn't find myself in \@INC, which is required to load the generators!" if ("$me" eq ""); + $me = dirname($me); + if (opendir(D,"$me/Generators")) { + foreach my $gen (readdir(D)) { + next if ($gen =~ /^\.\.?$/); + require "${me}/Generators/$gen"; + $gen =~ s,\.pm,,; + push(@AVAILABLE, $gen); + } + closedir(D); + my $gens = join(', ', @AVAILABLE); + } + + push @EXPORT_OK, qw(available); +} + +sub available { + return @AVAILABLE; +} + +sub generate { + my ($gen, $git_dir, $out_dir, $rel_dir, %build_structure) = @_; + return eval("Generators::${gen}::generate(\$git_dir, \$out_dir, \$rel_dir, \%build_structure)") if grep(/^$gen$/, @AVAILABLE); + die "Generator \"${gen}\" is not available!\nAvailable generators are: @AVAILABLE\n"; +} + +1; diff --git a/contrib/buildsystems/Generators/QMake.pm b/contrib/buildsystems/Generators/QMake.pm new file mode 100644 index 0000000000..ff3b657e61 --- /dev/null +++ b/contrib/buildsystems/Generators/QMake.pm @@ -0,0 +1,189 @@ +package Generators::QMake; +require Exporter; + +use strict; +use vars qw($VERSION); + +our $VERSION = '1.00'; +our(@ISA, @EXPORT, @EXPORT_OK, @AVAILABLE); +@ISA = qw(Exporter); + +BEGIN { + push @EXPORT_OK, qw(generate); +} + +sub generate { + my ($git_dir, $out_dir, $rel_dir, %build_structure) = @_; + + my @libs = @{$build_structure{"LIBS"}}; + foreach (@libs) { + createLibProject($_, $git_dir, $out_dir, $rel_dir, %build_structure); + } + + my @apps = @{$build_structure{"APPS"}}; + foreach (@apps) { + createAppProject($_, $git_dir, $out_dir, $rel_dir, %build_structure); + } + + createGlueProject($git_dir, $out_dir, $rel_dir, %build_structure); + return 0; +} + +sub createLibProject { + my ($libname, $git_dir, $out_dir, $rel_dir, %build_structure) = @_; + print "Generate $libname lib project\n"; + $rel_dir = "../$rel_dir"; + + my $sources = join(" \\\n\t", sort(map("$rel_dir/$_", @{$build_structure{"LIBS_${libname}_SOURCES"}}))); + my $defines = join(" \\\n\t", sort(@{$build_structure{"LIBS_${libname}_DEFINES"}})); + my $includes= join(" \\\n\t", sort(map("$rel_dir/$_", @{$build_structure{"LIBS_${libname}_INCLUDES"}}))); + my $cflags = join(" ", sort(@{$build_structure{"LIBS_${libname}_CFLAGS"}})); + + my $cflags_debug = $cflags; + $cflags_debug =~ s/-MT/-MTd/; + $cflags_debug =~ s/-O.//; + + my $cflags_release = $cflags; + $cflags_release =~ s/-MTd/-MT/; + + my @tmp = @{$build_structure{"LIBS_${libname}_LFLAGS"}}; + my @tmp2 = (); + foreach (@tmp) { + if (/^-LTCG/) { + } elsif (/^-L/) { + $_ =~ s/^-L/-LIBPATH:$rel_dir\//; + } + push(@tmp2, $_); + } + my $lflags = join(" ", sort(@tmp)); + + my $target = $libname; + $target =~ s/\//_/g; + $defines =~ s/-D//g; + $defines =~ s/"/\\\\"/g; + $includes =~ s/-I//g; + mkdir "$target" || die "Could not create the directory $target for lib project!\n"; + open F, ">$target/$target.pro" || die "Could not open $target/$target.pro for writing!\n"; + print F << "EOM"; +TEMPLATE = lib +TARGET = $target +DESTDIR = $rel_dir + +CONFIG -= qt +CONFIG += static + +QMAKE_CFLAGS = +QMAKE_CFLAGS_RELEASE = $cflags_release +QMAKE_CFLAGS_DEBUG = $cflags_debug +QMAKE_LIBFLAGS = $lflags + +DEFINES += \\ + $defines + +INCLUDEPATH += \\ + $includes + +SOURCES += \\ + $sources +EOM + close F; +} + +sub createAppProject { + my ($appname, $git_dir, $out_dir, $rel_dir, %build_structure) = @_; + print "Generate $appname app project\n"; + $rel_dir = "../$rel_dir"; + + my $sources = join(" \\\n\t", sort(map("$rel_dir/$_", @{$build_structure{"APPS_${appname}_SOURCES"}}))); + my $defines = join(" \\\n\t", sort(@{$build_structure{"APPS_${appname}_DEFINES"}})); + my $includes= join(" \\\n\t", sort(map("$rel_dir/$_", @{$build_structure{"APPS_${appname}_INCLUDES"}}))); + my $cflags = join(" ", sort(@{$build_structure{"APPS_${appname}_CFLAGS"}})); + + my $cflags_debug = $cflags; + $cflags_debug =~ s/-MT/-MTd/; + $cflags_debug =~ s/-O.//; + + my $cflags_release = $cflags; + $cflags_release =~ s/-MTd/-MT/; + + my $libs; + foreach (sort(@{$build_structure{"APPS_${appname}_LIBS"}})) { + $_ =~ s/\//_/g; + $libs .= " $_"; + } + my @tmp = @{$build_structure{"APPS_${appname}_LFLAGS"}}; + my @tmp2 = (); + foreach (@tmp) { + # next if ($_ eq "-NODEFAULTLIB:MSVCRT.lib"); + if (/^-LTCG/) { + } elsif (/^-L/) { + $_ =~ s/^-L/-LIBPATH:$rel_dir\//; + } + push(@tmp2, $_); + } + my $lflags = join(" ", sort(@tmp)); + + my $target = $appname; + $target =~ s/\.exe//; + $target =~ s/\//_/g; + $defines =~ s/-D//g; + $defines =~ s/"/\\\\"/g; + $includes =~ s/-I//g; + mkdir "$target" || die "Could not create the directory $target for app project!\n"; + open F, ">$target/$target.pro" || die "Could not open $target/$target.pro for writing!\n"; + print F << "EOM"; +TEMPLATE = app +TARGET = $target +DESTDIR = $rel_dir + +CONFIG -= qt embed_manifest_exe +CONFIG += console + +QMAKE_CFLAGS = +QMAKE_CFLAGS_RELEASE = $cflags_release +QMAKE_CFLAGS_DEBUG = $cflags_debug +QMAKE_LFLAGS = $lflags +LIBS = $libs + +DEFINES += \\ + $defines + +INCLUDEPATH += \\ + $includes + +win32:QMAKE_LFLAGS += -LIBPATH:$rel_dir +else: QMAKE_LFLAGS += -L$rel_dir + +SOURCES += \\ + $sources +EOM + close F; +} + +sub createGlueProject { + my ($git_dir, $out_dir, $rel_dir, %build_structure) = @_; + my $libs = join(" \\ \n", map("\t$_|$_.pro", @{$build_structure{"LIBS"}})); + my $apps = join(" \\ \n", map("\t$_|$_.pro", @{$build_structure{"APPS"}})); + $libs =~ s/\.a//g; + $libs =~ s/\//_/g; + $libs =~ s/\|/\//g; + $apps =~ s/\.exe//g; + $apps =~ s/\//_/g; + $apps =~ s/\|/\//g; + + my $filename = $out_dir; + $filename =~ s/.*\/([^\/]+)$/$1/; + $filename =~ s/\/$//; + print "Generate glue project $filename.pro\n"; + open F, ">$filename.pro" || die "Could not open $filename.pro for writing!\n"; + print F << "EOM"; +TEMPLATE = subdirs +CONFIG += ordered +SUBDIRS += \\ +$libs \\ +$apps +EOM + close F; +} + +1; diff --git a/contrib/buildsystems/Generators/Vcproj.pm b/contrib/buildsystems/Generators/Vcproj.pm new file mode 100644 index 0000000000..cfa74adcc2 --- /dev/null +++ b/contrib/buildsystems/Generators/Vcproj.pm @@ -0,0 +1,626 @@ +package Generators::Vcproj; +require Exporter; + +use strict; +use vars qw($VERSION); + +our $VERSION = '1.00'; +our(@ISA, @EXPORT, @EXPORT_OK, @AVAILABLE); +@ISA = qw(Exporter); + +BEGIN { + push @EXPORT_OK, qw(generate); +} + +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 { + my ($git_dir, $out_dir, $rel_dir, %build_structure) = @_; + my @libs = @{$build_structure{"LIBS"}}; + foreach (@libs) { + createLibProject($_, $git_dir, $out_dir, $rel_dir, \%build_structure); + } + + my @apps = @{$build_structure{"APPS"}}; + foreach (@apps) { + createAppProject($_, $git_dir, $out_dir, $rel_dir, \%build_structure); + } + + createGlueProject($git_dir, $out_dir, $rel_dir, %build_structure); + return 0; +} + +sub createLibProject { + my ($libname, $git_dir, $out_dir, $rel_dir, $build_structure) = @_; + print "Generate $libname vcproj lib project\n"; + $rel_dir = "..\\$rel_dir"; + $rel_dir =~ s/\//\\/g; + + my $target = $libname; + $target =~ s/\//_/g; + $target =~ s/\.a//; + + my $uuid = $GUIDS[$guid_index]; + $$build_structure{"LIBS_${target}_GUID"} = $uuid; + $guid_index += 1; + + my @srcs = sort(map("$rel_dir\\$_", @{$$build_structure{"LIBS_${libname}_SOURCES"}})); + my @sources; + foreach (@srcs) { + $_ =~ s/\//\\/g; + push(@sources, $_); + } + my $defines = join(",", sort(@{$$build_structure{"LIBS_${libname}_DEFINES"}})); + my $includes= join(";", sort(map(""$rel_dir\\$_"", @{$$build_structure{"LIBS_${libname}_INCLUDES"}}))); + my $cflags = join(" ", sort(@{$$build_structure{"LIBS_${libname}_CFLAGS"}})); + $cflags =~ s/\"/"/g; + + my $cflags_debug = $cflags; + $cflags_debug =~ s/-MT/-MTd/; + $cflags_debug =~ s/-O.//; + + my $cflags_release = $cflags; + $cflags_release =~ s/-MTd/-MT/; + + my @tmp = @{$$build_structure{"LIBS_${libname}_LFLAGS"}}; + my @tmp2 = (); + foreach (@tmp) { + if (/^-LTCG/) { + } elsif (/^-L/) { + $_ =~ s/^-L/-LIBPATH:$rel_dir\//; + } + push(@tmp2, $_); + } + my $lflags = join(" ", sort(@tmp)); + + $defines =~ s/-D//g; + $defines =~ s/\"/\\"/g; + $defines =~ s/\'//g; + $includes =~ s/-I//g; + mkdir "$target" || die "Could not create the directory $target for lib project!\n"; + open F, ">$target/$target.vcproj" || die "Could not open $target/$target.pro for writing!\n"; + binmode F, ":crlf"; + print F << "EOM"; +<?xml version="1.0" encoding = "Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="9,00" + Name="$target" + ProjectGUID="$uuid"> + <Platforms> + <Platform + Name="Win32"/> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="$rel_dir" + ConfigurationType="4" + CharacterSet="0" + IntermediateDirectory="\$(ProjectDir)\$(ConfigurationName)" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + AdditionalOptions="$cflags_debug" + Optimization="0" + InlineFunctionExpansion="1" + AdditionalIncludeDirectories="$includes" + PreprocessorDefinitions="WIN32,_DEBUG,$defines" + MinimalRebuild="true" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb" + WarningLevel="3" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + SuppressStartupBanner="true" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="$rel_dir" + ConfigurationType="4" + CharacterSet="0" + WholeProgramOptimization="1" + IntermediateDirectory="\$(ProjectDir)\$(ConfigurationName)" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + AdditionalOptions="$cflags_release" + Optimization="2" + InlineFunctionExpansion="1" + EnableIntrinsicFunctions="true" + AdditionalIncludeDirectories="$includes" + PreprocessorDefinitions="WIN32,NDEBUG,$defines" + RuntimeLibrary="0" + EnableFunctionLevelLinking="true" + UsePrecompiledHeader="0" + ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb" + WarningLevel="3" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + SuppressStartupBanner="true" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"> +EOM + foreach(@sources) { + print F << "EOM"; + <File + RelativePath="$_"/> +EOM + } + print F << "EOM"; + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject> +EOM + close F; +} + +sub createAppProject { + my ($appname, $git_dir, $out_dir, $rel_dir, $build_structure) = @_; + print "Generate $appname vcproj app project\n"; + $rel_dir = "..\\$rel_dir"; + $rel_dir =~ s/\//\\/g; + + my $target = $appname; + $target =~ s/\//_/g; + $target =~ s/\.exe//; + + my $uuid = $GUIDS[$guid_index]; + $$build_structure{"APPS_${target}_GUID"} = $uuid; + $guid_index += 1; + + my @srcs = sort(map("$rel_dir\\$_", @{$$build_structure{"APPS_${appname}_SOURCES"}})); + my @sources; + foreach (@srcs) { + $_ =~ s/\//\\/g; + push(@sources, $_); + } + my $defines = join(",", sort(@{$$build_structure{"APPS_${appname}_DEFINES"}})); + my $includes= join(";", sort(map(""$rel_dir\\$_"", @{$$build_structure{"APPS_${appname}_INCLUDES"}}))); + my $cflags = join(" ", sort(@{$$build_structure{"APPS_${appname}_CFLAGS"}})); + $cflags =~ s/\"/"/g; + + my $cflags_debug = $cflags; + $cflags_debug =~ s/-MT/-MTd/; + $cflags_debug =~ s/-O.//; + + my $cflags_release = $cflags; + $cflags_release =~ s/-MTd/-MT/; + + my $libs; + foreach (sort(@{$$build_structure{"APPS_${appname}_LIBS"}})) { + $_ =~ s/\//_/g; + $libs .= " $_"; + } + my @tmp = @{$$build_structure{"APPS_${appname}_LFLAGS"}}; + my @tmp2 = (); + foreach (@tmp) { + if (/^-LTCG/) { + } elsif (/^-L/) { + $_ =~ s/^-L/-LIBPATH:$rel_dir\//; + } + push(@tmp2, $_); + } + my $lflags = join(" ", sort(@tmp)) . " -LIBPATH:$rel_dir"; + + $defines =~ s/-D//g; + $defines =~ s/\"/\\"/g; + $defines =~ s/\'//g; + $defines =~ s/\\\\/\\/g; + $includes =~ s/-I//g; + mkdir "$target" || die "Could not create the directory $target for lib project!\n"; + open F, ">$target/$target.vcproj" || die "Could not open $target/$target.pro for writing!\n"; + binmode F, ":crlf"; + print F << "EOM"; +<?xml version="1.0" encoding = "Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="9,00" + Name="$target" + ProjectGUID="$uuid"> + <Platforms> + <Platform + Name="Win32"/> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="$rel_dir" + ConfigurationType="1" + CharacterSet="0" + IntermediateDirectory="\$(ProjectDir)\$(ConfigurationName)" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + AdditionalOptions="$cflags_debug" + Optimization="0" + InlineFunctionExpansion="1" + AdditionalIncludeDirectories="$includes" + PreprocessorDefinitions="WIN32,_DEBUG,$defines" + MinimalRebuild="true" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb" + WarningLevel="3" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="$libs" + AdditionalOptions="$lflags" + LinkIncremental="2" + GenerateDebugInformation="true" + SubSystem="1" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="$rel_dir" + ConfigurationType="1" + CharacterSet="0" + WholeProgramOptimization="1" + IntermediateDirectory="\$(ProjectDir)\$(ConfigurationName)" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + AdditionalOptions="$cflags_release" + Optimization="2" + InlineFunctionExpansion="1" + EnableIntrinsicFunctions="true" + AdditionalIncludeDirectories="$includes" + PreprocessorDefinitions="WIN32,NDEBUG,$defines" + RuntimeLibrary="0" + EnableFunctionLevelLinking="true" + UsePrecompiledHeader="0" + ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb" + WarningLevel="3" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="$libs" + AdditionalOptions="$lflags" + LinkIncremental="1" + GenerateDebugInformation="true" + SubSystem="1" + TargetMachine="1" + OptimizeReferences="2" + EnableCOMDATFolding="2" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"> +EOM + foreach(@sources) { + print F << "EOM"; + <File + RelativePath="$_"/> +EOM + } + print F << "EOM"; + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject> +EOM + close F; +} + +sub createGlueProject { + my ($git_dir, $out_dir, $rel_dir, %build_structure) = @_; + print "Generate solutions file\n"; + $rel_dir = "..\\$rel_dir"; + $rel_dir =~ s/\//\\/g; + my $SLN_HEAD = "Microsoft Visual Studio Solution File, Format Version 10.00\n# Visual Studio 2008\n"; + my $SLN_PRE = "Project(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = "; + my $SLN_POST = "\nEndProject\n"; + + my @libs = @{$build_structure{"LIBS"}}; + my @tmp; + foreach (@libs) { + $_ =~ s/\//_/g; + $_ =~ s/\.a//; + push(@tmp, $_); + } + @libs = @tmp; + + my @apps = @{$build_structure{"APPS"}}; + @tmp = (); + foreach (@apps) { + $_ =~ s/\//_/g; + $_ =~ s/\.exe//; + 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) { + my $appname = $_; + my $uuid = $build_structure{"APPS_${appname}_GUID"}; + print F "$SLN_PRE"; + print F "\"${appname}\", \"${appname}\\${appname}.vcproj\", \"${uuid}\"\n"; + print F " ProjectSection(ProjectDependencies) = postProject\n"; + print F " ${uuid_libgit} = ${uuid_libgit}\n"; + print F " ${uuid_xdiff_lib} = ${uuid_xdiff_lib}\n"; + print F " EndProjectSection"; + print F "$SLN_POST"; + } + + print F << "EOM"; +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection +EOM + print F << "EOM"; + GlobalSection(ProjectConfigurationPlatforms) = postSolution +EOM + foreach (@libs) { + my $libname = $_; + my $uuid = $build_structure{"LIBS_${libname}_GUID"}; + print F "\t\t${uuid}.Debug|Win32.ActiveCfg = Debug|Win32\n"; + print F "\t\t${uuid}.Debug|Win32.Build.0 = Debug|Win32\n"; + print F "\t\t${uuid}.Release|Win32.ActiveCfg = Release|Win32\n"; + print F "\t\t${uuid}.Release|Win32.Build.0 = Release|Win32\n"; + } + 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"; + } + + print F << "EOM"; + EndGlobalSection +EndGlobal +EOM + close F; +} + +1; diff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl new file mode 100644 index 0000000000..23da787dc5 --- /dev/null +++ b/contrib/buildsystems/engine.pl @@ -0,0 +1,359 @@ +#!/usr/bin/perl -w +###################################################################### +# Do not call this script directly! +# +# The generate script ensures that @INC is correct before the engine +# is executed. +# +# Copyright (C) 2009 Marius Storm-Olsen <mstormo@gmail.com> +###################################################################### +use strict; +use File::Basename; +use File::Spec; +use Cwd; +use Generators; + +my (%build_structure, %compile_options, @makedry); +my $out_dir = getcwd(); +my $git_dir = $out_dir; +$git_dir =~ s=\\=/=g; +$git_dir = dirname($git_dir) while (!-e "$git_dir/git.c" && "$git_dir" ne ""); +die "Couldn't find Git repo" if ("$git_dir" eq ""); + +my @gens = Generators::available(); +my $gen = "Vcproj"; + +sub showUsage +{ + my $genlist = join(', ', @gens); + print << "EOM"; +generate usage: + -g <GENERATOR> --gen <GENERATOR> Specify the buildsystem generator (default: $gen) + Available: $genlist + -o <PATH> --out <PATH> Specify output directory generation (default: .) + -i <FILE> --in <FILE> Specify input file, instead of running GNU Make + -h,-? --help This help +EOM + exit 0; +} + +# Parse command-line options +while (@ARGV) { + my $arg = shift @ARGV; + if ("$arg" eq "-h" || "$arg" eq "--help" || "$arg" eq "-?") { + showUsage(); + exit(0); + } elsif("$arg" eq "--out" || "$arg" eq "-o") { + $out_dir = shift @ARGV; + } elsif("$arg" eq "--gen" || "$arg" eq "-g") { + $gen = shift @ARGV; + } elsif("$arg" eq "--in" || "$arg" eq "-i") { + my $infile = shift @ARGV; + open(F, "<$infile") || die "Couldn't open file $infile"; + @makedry = <F>; + close(F); + } +} + +# NOT using File::Spec->rel2abs($path, $base) here, as +# it fails badly for me in the msysgit environment +$git_dir = File::Spec->rel2abs($git_dir); +$out_dir = File::Spec->rel2abs($out_dir); +my $rel_dir = makeOutRel2Git($git_dir, $out_dir); + +# Print some information so the user feels informed +print << "EOM"; +----- +Generator: $gen +Git dir: $git_dir +Out dir: $out_dir +----- +Running GNU Make to figure out build structure... +EOM + +# Pipe a make --dry-run into a variable, if not already loaded from file +@makedry = `cd $git_dir && make -n MSVC=1 V=1 2>/dev/null` if !@makedry; + +# Parse the make output into usable info +parseMakeOutput(); + +# Finally, ask the generator to start generating.. +Generators::generate($gen, $git_dir, $out_dir, $rel_dir, %build_structure); + +# main flow ends here +# ------------------------------------------------------------------------------------------------- + + +# 1) path: /foo/bar/baz 2) path: /foo/bar/baz 3) path: /foo/bar/baz +# base: /foo/bar/baz/temp base: /foo/bar base: /tmp +# rel: .. rel: baz rel: ../foo/bar/baz +sub makeOutRel2Git +{ + my ($path, $base) = @_; + my $rel; + if ("$path" eq "$base") { + return "."; + } elsif ($base =~ /^$path/) { + # case 1 + my $tmp = $base; + $tmp =~ s/^$path//; + foreach (split('/', $tmp)) { + $rel .= "../" if ("$_" ne ""); + } + } elsif ($path =~ /^$base/) { + # case 2 + $rel = $path; + $rel =~ s/^$base//; + $rel = "./$rel"; + } else { + my $tmp = $base; + foreach (split('/', $tmp)) { + $rel .= "../" if ("$_" ne ""); + } + $rel .= $path; + } + $rel =~ s/\/\//\//g; # simplify + $rel =~ s/\/$//; # don't end with / + return $rel; +} + +sub parseMakeOutput +{ + print "Parsing GNU Make output to figure out build structure...\n"; + my $line = 0; + while (my $text = shift @makedry) { + my $ate_next; + do { + $ate_next = 0; + $line++; + chomp $text; + chop $text if ($text =~ /\r$/); + if ($text =~ /\\$/) { + $text =~ s/\\$//; + $text .= shift @makedry; + $ate_next = 1; + } + } while($ate_next); + + if ($text =~ /^test /) { + # options to test (eg -o) may be mistaken for linker options + next; + } + + if($text =~ / -c /) { + # compilation + handleCompileLine($text, $line); + + } elsif ($text =~ / -o /) { + # linking executable + handleLinkLine($text, $line); + + } elsif ($text =~ /\.o / && $text =~ /\.a /) { + # libifying + handleLibLine($text, $line); +# +# } elsif ($text =~ /^cp /) { +# # copy file around +# +# } elsif ($text =~ /^rm -f /) { +# # shell command +# +# } elsif ($text =~ /^make[ \[]/) { +# # make output +# +# } elsif ($text =~ /^echo /) { +# # echo to file +# +# } elsif ($text =~ /^if /) { +# # shell conditional +# +# } elsif ($text =~ /^tclsh /) { +# # translation stuff +# +# } elsif ($text =~ /^umask /) { +# # handling boilerplates +# +# } elsif ($text =~ /\$\(\:\)/) { +# # ignore +# +# } elsif ($text =~ /^FLAGS=/) { +# # flags check for dependencies +# +# } elsif ($text =~ /^'\/usr\/bin\/perl' -MError -e/) { +# # perl commands for copying files +# +# } elsif ($text =~ /generate-cmdlist\.sh/) { +# # command for generating list of commands +# +# } elsif ($text =~ /new locations or Tcl/) { +# # command for detecting Tcl/Tk changes +# +# } elsif ($text =~ /mkdir -p/) { +# # command creating path +# +# } elsif ($text =~ /: no custom templates yet/) { +# # whatever +# +# } else { +# print "Unhandled (line: $line): $text\n"; + } + } + +# use Data::Dumper; +# print "Parsed build structure:\n"; +# print Dumper(%build_structure); +} + +# variables for the compilation part of each step +my (@defines, @incpaths, @cflags, @sources); + +sub clearCompileStep +{ + @defines = (); + @incpaths = (); + @cflags = (); + @sources = (); +} + +sub removeDuplicates +{ + my (%dupHash, $entry); + %dupHash = map { $_, 1 } @defines; + @defines = keys %dupHash; + + %dupHash = map { $_, 1 } @incpaths; + @incpaths = keys %dupHash; + + %dupHash = map { $_, 1 } @cflags; + @cflags = keys %dupHash; +} + +sub handleCompileLine +{ + my ($line, $lineno) = @_; + my @parts = split(' ', $line); + my $sourcefile; + shift(@parts); # ignore cmd + while (my $part = shift @parts) { + if ("$part" eq "-o") { + # ignore object file + shift @parts; + } elsif ("$part" eq "-c") { + # ignore compile flag + } elsif ("$part" eq "-c") { + } elsif ($part =~ /^.?-I/) { + push(@incpaths, $part); + } elsif ($part =~ /^.?-D/) { + push(@defines, $part); + } elsif ($part =~ /^-/) { + push(@cflags, $part); + } elsif ($part =~ /\.(c|cc|cpp)$/) { + $sourcefile = $part; + } else { + die "Unhandled compiler option @ line $lineno: $part"; + } + } + @{$compile_options{"${sourcefile}_CFLAGS"}} = @cflags; + @{$compile_options{"${sourcefile}_DEFINES"}} = @defines; + @{$compile_options{"${sourcefile}_INCPATHS"}} = @incpaths; + clearCompileStep(); +} + +sub handleLibLine +{ + my ($line, $lineno) = @_; + my (@objfiles, @lflags, $libout, $part); + # kill cmd and rm 'prefix' + $line =~ s/^rm -f .* && .* rcs //; + my @parts = split(' ', $line); + while ($part = shift @parts) { + if ($part =~ /^-/) { + push(@lflags, $part); + } elsif ($part =~ /\.(o|obj)$/) { + push(@objfiles, $part); + } elsif ($part =~ /\.(a|lib)$/) { + $libout = $part; + $libout =~ s/\.a$//; + } else { + die "Unhandled lib option @ line $lineno: $part"; + } + } +# print "LibOut: '$libout'\nLFlags: @lflags\nOfiles: @objfiles\n"; +# exit(1); + foreach (@objfiles) { + my $sourcefile = $_; + $sourcefile =~ s/\.o/.c/; + push(@sources, $sourcefile); + push(@cflags, @{$compile_options{"${sourcefile}_CFLAGS"}}); + push(@defines, @{$compile_options{"${sourcefile}_DEFINES"}}); + push(@incpaths, @{$compile_options{"${sourcefile}_INCPATHS"}}); + } + removeDuplicates(); + + push(@{$build_structure{"LIBS"}}, $libout); + @{$build_structure{"LIBS_${libout}"}} = ("_DEFINES", "_INCLUDES", "_CFLAGS", "_SOURCES", + "_OBJECTS"); + @{$build_structure{"LIBS_${libout}_DEFINES"}} = @defines; + @{$build_structure{"LIBS_${libout}_INCLUDES"}} = @incpaths; + @{$build_structure{"LIBS_${libout}_CFLAGS"}} = @cflags; + @{$build_structure{"LIBS_${libout}_LFLAGS"}} = @lflags; + @{$build_structure{"LIBS_${libout}_SOURCES"}} = @sources; + @{$build_structure{"LIBS_${libout}_OBJECTS"}} = @objfiles; + clearCompileStep(); +} + +sub handleLinkLine +{ + my ($line, $lineno) = @_; + my (@objfiles, @lflags, @libs, $appout, $part); + my @parts = split(' ', $line); + shift(@parts); # ignore cmd + while ($part = shift @parts) { + if ($part =~ /^-IGNORE/) { + push(@lflags, $part); + } elsif ($part =~ /^-[GRIMDO]/) { + # eat compiler flags + } elsif ("$part" eq "-o") { + $appout = shift @parts; + } elsif ("$part" eq "-lz") { + push(@libs, "zlib.lib"); + } elsif ("$part" eq "-lcrypto") { + push(@libs, "libeay32.lib"); + } elsif ("$part" eq "-lssl") { + push(@libs, "ssleay32.lib"); + } elsif ($part =~ /^-/) { + push(@lflags, $part); + } elsif ($part =~ /\.(a|lib)$/) { + $part =~ s/\.a$/.lib/; + push(@libs, $part); + } elsif ($part =~ /\.(o|obj)$/) { + push(@objfiles, $part); + } else { + die "Unhandled lib option @ line $lineno: $part"; + } + } +# print "AppOut: '$appout'\nLFlags: @lflags\nLibs : @libs\nOfiles: @objfiles\n"; +# exit(1); + foreach (@objfiles) { + my $sourcefile = $_; + $sourcefile =~ s/\.o/.c/; + push(@sources, $sourcefile); + push(@cflags, @{$compile_options{"${sourcefile}_CFLAGS"}}); + push(@defines, @{$compile_options{"${sourcefile}_DEFINES"}}); + push(@incpaths, @{$compile_options{"${sourcefile}_INCPATHS"}}); + } + removeDuplicates(); + + removeDuplicates(); + push(@{$build_structure{"APPS"}}, $appout); + @{$build_structure{"APPS_${appout}"}} = ("_DEFINES", "_INCLUDES", "_CFLAGS", "_LFLAGS", + "_SOURCES", "_OBJECTS", "_LIBS"); + @{$build_structure{"APPS_${appout}_DEFINES"}} = @defines; + @{$build_structure{"APPS_${appout}_INCLUDES"}} = @incpaths; + @{$build_structure{"APPS_${appout}_CFLAGS"}} = @cflags; + @{$build_structure{"APPS_${appout}_LFLAGS"}} = @lflags; + @{$build_structure{"APPS_${appout}_SOURCES"}} = @sources; + @{$build_structure{"APPS_${appout}_OBJECTS"}} = @objfiles; + @{$build_structure{"APPS_${appout}_LIBS"}} = @libs; + clearCompileStep(); +} diff --git a/contrib/buildsystems/generate b/contrib/buildsystems/generate new file mode 100644 index 0000000000..bc10f25ff2 --- /dev/null +++ b/contrib/buildsystems/generate @@ -0,0 +1,29 @@ +#!/usr/bin/perl -w +###################################################################### +# Generate buildsystem files +# +# This script generate buildsystem files based on the output of a +# GNU Make --dry-run, enabling Windows users to develop Git with their +# trusted IDE with native projects. +# +# Note: +# It is not meant as *the* way of building Git with MSVC, but merely a +# convenience. The correct way of building Git with MSVC is to use the +# GNU Make tool to build with the maintained Makefile in the root of +# the project. If you have the msysgit environment installed and +# available in your current console, together with the Visual Studio +# environment you wish to build for, all you have to do is run the +# command: +# make MSVC=1 +# +# Copyright (C) 2009 Marius Storm-Olsen <mstormo@gmail.com> +###################################################################### +use strict; +use File::Basename; +use Cwd; + +my $git_dir = getcwd(); +$git_dir =~ s=\\=/=g; +$git_dir = dirname($git_dir) while (!-e "$git_dir/git.c" && "$git_dir" ne ""); +die "Couldn't find Git repo" if ("$git_dir" eq ""); +exec join(" ", ("PERL5LIB=${git_dir}/contrib/buildsystems ${git_dir}/contrib/buildsystems/engine.pl", @ARGV)); diff --git a/contrib/buildsystems/parse.pl b/contrib/buildsystems/parse.pl new file mode 100644 index 0000000000..c9656ece99 --- /dev/null +++ b/contrib/buildsystems/parse.pl @@ -0,0 +1,228 @@ +#!/usr/bin/perl -w +###################################################################### +# Do not call this script directly! +# +# The generate script ensures that @INC is correct before the engine +# is executed. +# +# Copyright (C) 2009 Marius Storm-Olsen <mstormo@gmail.com> +###################################################################### +use strict; +use File::Basename; +use Cwd; + +my $file = $ARGV[0]; +die "No file provided!" if !defined $file; + +my ($cflags, $target, $type, $line); + +open(F, "<$file") || die "Couldn't open file $file"; +my @data = <F>; +close(F); + +while (my $text = shift @data) { + my $ate_next; + do { + $ate_next = 0; + $line++; + chomp $text; + chop $text if ($text =~ /\r$/); + if ($text =~ /\\$/) { + $text =~ s/\\$//; + $text .= shift @data; + $ate_next = 1; + } + } while($ate_next); + + if($text =~ / -c /) { + # compilation + handleCompileLine($text, $line); + + } elsif ($text =~ / -o /) { + # linking executable + handleLinkLine($text, $line); + + } elsif ($text =~ /\.o / && $text =~ /\.a /) { + # libifying + handleLibLine($text, $line); + +# } elsif ($text =~ /^cp /) { +# # copy file around +# +# } elsif ($text =~ /^rm -f /) { +# # shell command +# +# } elsif ($text =~ /^make[ \[]/) { +# # make output +# +# } elsif ($text =~ /^echo /) { +# # echo to file +# +# } elsif ($text =~ /^if /) { +# # shell conditional +# +# } elsif ($text =~ /^tclsh /) { +# # translation stuff +# +# } elsif ($text =~ /^umask /) { +# # handling boilerplates +# +# } elsif ($text =~ /\$\(\:\)/) { +# # ignore +# +# } elsif ($text =~ /^FLAGS=/) { +# # flags check for dependencies +# +# } elsif ($text =~ /^'\/usr\/bin\/perl' -MError -e/) { +# # perl commands for copying files +# +# } elsif ($text =~ /generate-cmdlist\.sh/) { +# # command for generating list of commands +# +# } elsif ($text =~ /^test / && $text =~ /|| rm -f /) { +# # commands removing executables, if they exist +# +# } elsif ($text =~ /new locations or Tcl/) { +# # command for detecting Tcl/Tk changes +# +# } elsif ($text =~ /mkdir -p/) { +# # command creating path +# +# } elsif ($text =~ /: no custom templates yet/) { +# # whatever + + } else { +# print "Unhandled (line: $line): $text\n"; + } +} +close(F); + +# use Data::Dumper; +# print "Parsed build structure:\n"; +# print Dumper(%build_structure); + +# ------------------------------------------------------------------- +# Functions under here +# ------------------------------------------------------------------- +my (%build_structure, @defines, @incpaths, @cflags, @sources); + +sub clearCompileStep +{ + @defines = (); + @incpaths = (); + @cflags = (); + @sources = (); +} + +sub removeDuplicates +{ + my (%dupHash, $entry); + %dupHash = map { $_, 1 } @defines; + @defines = keys %dupHash; + + %dupHash = map { $_, 1 } @incpaths; + @incpaths = keys %dupHash; + + %dupHash = map { $_, 1 } @cflags; + @cflags = keys %dupHash; + + %dupHash = map { $_, 1 } @sources; + @sources = keys %dupHash; +} + +sub handleCompileLine +{ + my ($line, $lineno) = @_; + my @parts = split(' ', $line); + shift(@parts); # ignore cmd + while (my $part = shift @parts) { + if ("$part" eq "-o") { + # ignore object file + shift @parts; + } elsif ("$part" eq "-c") { + # ignore compile flag + } elsif ("$part" eq "-c") { + } elsif ($part =~ /^.?-I/) { + push(@incpaths, $part); + } elsif ($part =~ /^.?-D/) { + push(@defines, $part); + } elsif ($part =~ /^-/) { + push(@cflags, $part); + } elsif ($part =~ /\.(c|cc|cpp)$/) { + push(@sources, $part); + } else { + die "Unhandled compiler option @ line $lineno: $part"; + } + } + #print "Sources: @sources\nCFlags: @cflags\nDefine: @defines\nIncpat: @incpaths\n"; + #exit(1); +} + +sub handleLibLine +{ + my ($line, $lineno) = @_; + my (@objfiles, @lflags, $libout, $part); + # kill cmd and rm 'prefix' + $line =~ s/^rm -f .* && .* rcs //; + my @parts = split(' ', $line); + while ($part = shift @parts) { + if ($part =~ /^-/) { + push(@lflags, $part); + } elsif ($part =~ /\.(o|obj)$/) { + push(@objfiles, $part); + } elsif ($part =~ /\.(a|lib)$/) { + $libout = $part; + } else { + die "Unhandled lib option @ line $lineno: $part"; + } + } + #print "LibOut: '$libout'\nLFlags: @lflags\nOfiles: @objfiles\n"; + #exit(1); + removeDuplicates(); + push(@{$build_structure{"LIBS"}}, $libout); + @{$build_structure{"LIBS_${libout}"}} = ("_DEFINES", "_INCLUDES", "_CFLAGS", "_SOURCES", + "_OBJECTS"); + @{$build_structure{"LIBS_${libout}_DEFINES"}} = @defines; + @{$build_structure{"LIBS_${libout}_INCLUDES"}} = @incpaths; + @{$build_structure{"LIBS_${libout}_CFLAGS"}} = @cflags; + @{$build_structure{"LIBS_${libout}_SOURCES"}} = @sources; + @{$build_structure{"LIBS_${libout}_OBJECTS"}} = @objfiles; + clearCompileStep(); +} + +sub handleLinkLine +{ + my ($line, $lineno) = @_; + my (@objfiles, @lflags, @libs, $appout, $part); + my @parts = split(' ', $line); + shift(@parts); # ignore cmd + while ($part = shift @parts) { + if ($part =~ /^-[GRIDO]/) { + # eat compiler flags + } elsif ("$part" eq "-o") { + $appout = shift @parts; + } elsif ($part =~ /^-/) { + push(@lflags, $part); + } elsif ($part =~ /\.(a|lib)$/) { + push(@libs, $part); + } elsif ($part =~ /\.(o|obj)$/) { + push(@objfiles, $part); + } else { + die "Unhandled lib option @ line $lineno: $part"; + } + } + #print "AppOut: '$appout'\nLFlags: @lflags\nLibs : @libs\nOfiles: @objfiles\n"; + #exit(1); + removeDuplicates(); + push(@{$build_structure{"APPS"}}, $appout); + @{$build_structure{"APPS_${appout}"}} = ("_DEFINES", "_INCLUDES", "_CFLAGS", "_LFLAGS", + "_SOURCES", "_OBJECTS", "_LIBS"); + @{$build_structure{"APPS_${appout}_DEFINES"}} = @defines; + @{$build_structure{"APPS_${appout}_INCLUDES"}} = @incpaths; + @{$build_structure{"APPS_${appout}_CFLAGS"}} = @cflags; + @{$build_structure{"APPS_${appout}_LFLAGS"}} = @lflags; + @{$build_structure{"APPS_${appout}_SOURCES"}} = @sources; + @{$build_structure{"APPS_${appout}_OBJECTS"}} = @objfiles; + @{$build_structure{"APPS_${appout}_LIBS"}} = @libs; + clearCompileStep(); +} diff --git a/contrib/ciabot/README b/contrib/ciabot/README new file mode 100644 index 0000000000..3b916acece --- /dev/null +++ b/contrib/ciabot/README @@ -0,0 +1,12 @@ +These are hook scripts for the CIA notification service at <http://cia.vc/> + +They are maintained by Eric S. Raymond <esr@thyrsus.com>. There is an +upstream resource page for them at <http://www.catb.org/esr/ciabot/>, +but they are unlikely to change rapidly. + +You probably want the Python version; it's faster, more capable, and +better documented. The shell version is maintained only as a fallback +for use on hosting sites that don't permit Python hook scripts. + +You will find installation instructions for each script in its comment +header. diff --git a/contrib/ciabot/ciabot.py b/contrib/ciabot/ciabot.py new file mode 100755 index 0000000000..9775dffb5d --- /dev/null +++ b/contrib/ciabot/ciabot.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python +# Copyright (c) 2010 Eric S. Raymond <esr@thyrsus.com> +# Distributed under BSD terms. +# +# This script contains porcelain and porcelain byproducts. +# It's Python because the Python standard libraries avoid portability/security +# issues raised by callouts in the ancestral Perl and sh scripts. It should +# be compatible back to Python 2.1.5 +# +# usage: ciabot.py [-V] [-n] [-p projectname] [refname [commits...]] +# +# This script is meant to be run either in a post-commit hook or in an +# update hook. If there's nothing unusual about your hosting setup, +# you can specify the project name with a -p option and avoid having +# to modify this script. Try it with -n to see the notification mail +# dumped to stdout and verify that it looks sane. With -V it dumps its +# version and exits. +# +# In post-commit, run it without arguments (other than possibly a -p +# option). It will query for current HEAD and the latest commit ID to +# get the information it needs. +# +# In update, call it with a refname followed by a list of commits: +# You want to reverse the order git rev-list emits becxause it lists +# from most recent to oldest. +# +# /path/to/ciabot.py ${refname} $(git rev-list ${oldhead}..${newhead} | tac) +# +# Note: this script uses mail, not XML-RPC, in order to avoid stalling +# until timeout when the CIA XML-RPC server is down. +# + +# +# The project as known to CIA. You will either want to change this +# or invoke the script with a -p option to set it. +# +project=None + +# +# You may not need to change these: +# +import os, sys, commands, socket, urllib + +# Name of the repository. +# You can hardwire this to make the script faster. +repo = os.path.basename(os.getcwd()) + +# Fully-qualified domain name of this host. +# You can hardwire this to make the script faster. +host = socket.getfqdn() + +# Changeset URL prefix for your repo: when the commit ID is appended +# to this, it should point at a CGI that will display the commit +# through gitweb or something similar. The defaults will probably +# work if you have a typical gitweb/cgit setup. +# +#urlprefix="http://%(host)s/cgi-bin/gitweb.cgi?p=%(repo)s;a=commit;h=" +urlprefix="http://%(host)s/cgi-bin/cgit.cgi/%(repo)s/commit/?id=" + +# The service used to turn your gitwebbish URL into a tinyurl so it +# will take up less space on the IRC notification line. +tinyifier = "http://tinyurl.com/api-create.php?url=" + +# The template used to generate the XML messages to CIA. You can make +# visible changes to the IRC-bot notification lines by hacking this. +# The default will produce a notfication line that looks like this: +# +# ${project}: ${author} ${repo}:${branch} * ${rev} ${files}: ${logmsg} ${url} +# +# By omitting $files you can collapse the files part to a single slash. +xml = '''\ +<message> + <generator> + <name>CIA Python client for Git</name> + <version>%(gitver)s</version> + <url>%(generator)s</url> + </generator> + <source> + <project>%(project)s</project> + <branch>%(repo)s:%(branch)s</branch> + </source> + <timestamp>%(ts)s</timestamp> + <body> + <commit> + <author>%(author)s</author> + <revision>%(rev)s</revision> + <files> + %(files)s + </files> + <log>%(logmsg)s %(url)s</log> + <url>%(url)s</url> + </commit> + </body> +</message> +''' + +# +# No user-serviceable parts below this line: +# + +# Addresses for the e-mail. The from address is a dummy, since CIA +# will never reply to this mail. +fromaddr = "CIABOT-NOREPLY@" + host +toaddr = "cia@cia.navi.cx" + +# Identify the generator script. +# Should only change when the script itself gets a new home and maintainer. +generator="http://www.catb.org/~esr/ciabot.py" + +def do(command): + return commands.getstatusoutput(command)[1] + +def report(refname, merged): + "Generate a commit notification to be reported to CIA" + + # Try to tinyfy a reference to a web view for this commit. + try: + url = open(urllib.urlretrieve(tinyifier + urlprefix + merged)[0]).read() + except: + url = urlprefix + merged + + branch = os.path.basename(refname) + + # Compute a shortnane for the revision + rev = do("git describe '"+ merged +"' 2>/dev/null") or merged[:12] + + # Extract the neta-information for the commit + rawcommit = do("git cat-file commit " + merged) + files=do("git diff-tree -r --name-only '"+ merged +"' | sed -e '1d' -e 's-.*-<file>&</file>-'") + inheader = True + headers = {} + logmsg = "" + for line in rawcommit.split("\n"): + if inheader: + if line: + fields = line.split() + headers[fields[0]] = " ".join(fields[1:]) + else: + inheader = False + else: + logmsg = line + break + (author, ts) = headers["author"].split(">") + + # This discards the part of the authors addrsss after @. + # Might be bnicece to ship the full email address, if not + # for spammers' address harvesters - getting this wrong + # would make the freenode #commits channel into harvester heaven. + author = author.replace("<", "").split("@")[0].split()[-1] + + # This ignores the timezone. Not clear what to do with it... + ts = ts.strip().split()[0] + + context = locals() + context.update(globals()) + + out = xml % context + + message = '''\ +Message-ID: <%(merged)s.%(author)s@%(project)s> +From: %(fromaddr)s +To: %(toaddr)s +Content-type: text/xml +Subject: DeliverXML + +%(out)s''' % locals() + + return message + +if __name__ == "__main__": + import getopt + + try: + (options, arguments) = getopt.getopt(sys.argv[1:], "np:V") + except getopt.GetoptError, msg: + print "ciabot.py: " + str(msg) + raise SystemExit, 1 + + mailit = True + for (switch, val) in options: + if switch == '-p': + project = val + elif switch == '-n': + mailit = False + elif switch == '-V': + print "ciabot.py: version 3.2" + sys.exit(0) + + # Cough and die if user has not specified a project + if not project: + sys.stderr.write("ciabot.py: no project specified, bailing out.\n") + sys.exit(1) + + # We'll need the git version number. + gitver = do("git --version").split()[0] + + urlprefix = urlprefix % globals() + + # The script wants a reference to head followed by the list of + # commit ID to report about. + if len(arguments) == 0: + refname = do("git symbolic-ref HEAD 2>/dev/null") + merges = [do("git rev-parse HEAD")] + else: + refname = arguments[0] + merges = arguments[1:] + + if mailit: + import smtplib + server = smtplib.SMTP('localhost') + + for merged in merges: + message = report(refname, merged) + if mailit: + server.sendmail(fromaddr, [toaddr], message) + else: + print message + + if mailit: + server.quit() + +#End diff --git a/contrib/ciabot/ciabot.sh b/contrib/ciabot/ciabot.sh new file mode 100755 index 0000000000..eb87bba38e --- /dev/null +++ b/contrib/ciabot/ciabot.sh @@ -0,0 +1,192 @@ +#!/bin/sh +# Distributed under the terms of the GNU General Public License v2 +# Copyright (c) 2006 Fernando J. Pereda <ferdy@gentoo.org> +# Copyright (c) 2008 Natanael Copa <natanael.copa@gmail.com> +# Copyright (c) 2010 Eric S. Raymond <esr@thyrsus.com> +# +# This is a version 3.x of ciabot.sh; use -V to find the exact +# version. Versions 1 and 2 were shipped in 2006 and 2008 and are not +# version-stamped. The version 2 maintainer has passed the baton. +# +# Note: This script should be considered obsolete. +# There is a faster, better-documented rewrite in Python: find it as ciabot.py +# Use this only if your hosting site forbids Python hooks. +# +# Originally based on Git ciabot.pl by Petr Baudis. +# This script contains porcelain and porcelain byproducts. +# +# usage: ciabot.sh [-V] [-n] [-p projectname] [refname commit] +# +# This script is meant to be run either in a post-commit hook or in an +# update hook. If there's nothing unusual about your hosting setup, +# you can specify the project name with a -p option and avoid having +# to modify this script. Try it with -n first to see the notification +# mail dumped to stdout and verify that it looks sane. Use -V to dump +# the version and exit. +# +# In post-commit, run it without arguments (other than possibly a -p +# option). It will query for current HEAD and the latest commit ID to +# get the information it needs. +# +# In update, you have to call it once per merged commit: +# +# refname=$1 +# oldhead=$2 +# newhead=$3 +# for merged in $(git rev-list ${oldhead}..${newhead} | tac) ; do +# /path/to/ciabot.bash ${refname} ${merged} +# done +# +# The reason for the tac call ids that git rev-list emits commits from +# most recent to least - better to ship notifactions from oldest to newest. +# +# Note: this script uses mail, not XML-RPC, in order to avoid stalling +# until timeout when the CIA XML-RPC server is down. +# + +# +# The project as known to CIA. You will either want to change this +# or set the project name with a -p option. +# +project= + +# +# You may not need to change these: +# + +# Name of the repository. +# You can hardwire this to make the script faster. +repo="`basename ${PWD}`" + +# Fully qualified domain name of the repo host. +# You can hardwire this to make the script faster. +host=`hostname --fqdn` + +# Changeset URL prefix for your repo: when the commit ID is appended +# to this, it should point at a CGI that will display the commit +# through gitweb or something similar. The defaults will probably +# work if you have a typical gitweb/cgit setup. +#urlprefix="http://${host}/cgi-bin/gitweb.cgi?p=${repo};a=commit;h=" +urlprefix="http://${host}/cgi-bin/cgit.cgi/${repo}/commit/?id=" + +# +# You probably will not need to change the following: +# + +# Identify the script. Should change only when the script itself +# gets a new home and maintainer. +generator="http://www.catb.org/~esr/ciabot/ciabot.sh" + +# Addresses for the e-mail +from="CIABOT-NOREPLY@${host}" +to="cia@cia.navi.cx" + +# SMTP client to use - may need to edit the absolute pathname for your system +sendmail="sendmail -t -f ${from}" + +# +# No user-serviceable parts below this line: +# + +# Should include all places sendmail is likely to lurk. +PATH="$PATH:/usr/sbin/" + +mode=mailit +while getopts pnV opt +do + case $opt in + p) project=$2; shift ; shift ;; + n) mode=dumpit; shift ;; + V) echo "ciabot.sh: version 3.2"; exit 0; shift ;; + esac +done + +# Cough and die if user has not specified a project +if [ -z "$project" ] +then + echo "ciabot.sh: no project specified, bailing out." >&2 + exit 1 +fi + +if [ $# -eq 0 ] ; then + refname=$(git symbolic-ref HEAD 2>/dev/null) + merged=$(git rev-parse HEAD) +else + refname=$1 + merged=$2 +fi + +# This tries to turn your gitwebbish URL into a tinyurl so it will take up +# less space on the IRC notification line. Some repo sites (I'm looking at +# you, berlios.de!) forbid wget calls for security reasons. On these, +# the code will fall back to the full un-tinyfied URL. +longurl=${urlprefix}${merged} +url=$(wget -O - -q http://tinyurl.com/api-create.php?url=${longurl} 2>/dev/null) +if [ -z "$url" ]; then + url="${longurl}" +fi + +refname=${refname##refs/heads/} + +gitver=$(git --version) +gitver=${gitver##* } + +rev=$(git describe ${merged} 2>/dev/null) +# ${merged:0:12} was the only bashism left in the 2008 version of this +# script, according to checkbashisms. Replace it with ${merged} here +# because it was just a fallback anyway, and it's worth accepting a +# longer fallback for faster execution and removing the bash +# dependency. +[ -z ${rev} ] && rev=${merged} + +# This discards the part of the author's address after @. +# Might be nice to ship the full email address, if not +# for spammers' address harvesters - getting this wrong +# would make the freenode #commits channel into harvester heaven. +rawcommit=$(git cat-file commit ${merged}) +author=$(echo "$rawcommit" | sed -n -e '/^author .*<\([^@]*\).*$/s--\1-p') +logmessage=$(echo "$rawcommit" | sed -e '1,/^$/d' | head -n 1) +logmessage=$(echo "$logmessage" | sed 's/\&/&\;/g; s/</<\;/g; s/>/>\;/g') +ts=$(echo "$rawcommit" | sed -n -e '/^author .*> \([0-9]\+\).*$/s--\1-p') +files=$(git diff-tree -r --name-only ${merged} | sed -e '1d' -e 's-.*-<file>&</file>-') + +out=" +<message> + <generator> + <name>CIA Shell client for Git</name> + <version>${gitver}</version> + <url>${generator}</url> + </generator> + <source> + <project>${project}</project> + <branch>$repo:${refname}</branch> + </source> + <timestamp>${ts}</timestamp> + <body> + <commit> + <author>${author}</author> + <revision>${rev}</revision> + <files> + ${files} + </files> + <log>${logmessage} ${url}</log> + <url>${url}</url> + </commit> + </body> +</message>" + +if [ "$mode" = "dumpit" ] +then + sendmail=cat +fi + +${sendmail} << EOM +Message-ID: <${merged}.${author}@${project}> +From: ${from} +To: ${to} +Content-type: text/xml +Subject: DeliverXML +${out} +EOM + +# vim: set tw=70 : diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 8fc01fb497..893b7716ca 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1,3 +1,4 @@ +#!bash # # bash completion support for core Git. # @@ -20,19 +21,50 @@ # 2) Added the following line to your .bashrc: # source ~/.git-completion.sh # -# 3) You may want to make sure the git executable is available -# in your PATH before this script is sourced, as some caching -# is performed while the script loads. If git isn't found -# at source time then all lookups will be done on demand, -# which may be slightly slower. +# Or, add the following lines to your .zshrc: +# autoload bashcompinit +# bashcompinit +# source ~/.git-completion.sh # -# 4) Consider changing your PS1 to also show the current branch: +# 3) Consider changing your PS1 to also show the current branch: # PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ ' # # The argument to __git_ps1 will be displayed only if you # are currently in a git repository. The %s token will be # the name of the current branch. # +# In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty +# value, unstaged (*) and staged (+) changes will be shown next +# to the branch name. You can configure this per-repository +# with the bash.showDirtyState variable, which defaults to true +# once GIT_PS1_SHOWDIRTYSTATE is enabled. +# +# You can also see if currently something is stashed, by setting +# GIT_PS1_SHOWSTASHSTATE to a nonempty value. If something is stashed, +# then a '$' will be shown next to the branch name. +# +# If you would like to see if there're untracked files, then you can +# set GIT_PS1_SHOWUNTRACKEDFILES to a nonempty value. If there're +# untracked files, then a '%' will be shown next to the branch name. +# +# If you would like to see the difference between HEAD and its +# upstream, set GIT_PS1_SHOWUPSTREAM="auto". A "<" indicates +# you are behind, ">" indicates you are ahead, and "<>" +# indicates you have diverged. You can further control +# behaviour by setting GIT_PS1_SHOWUPSTREAM to a space-separated +# list of values: +# verbose show number of commits ahead/behind (+/-) upstream +# legacy don't use the '--count' option available in recent +# versions of git-rev-list +# git always compare HEAD to @{upstream} +# svn always compare HEAD to your SVN upstream +# By default, __git_ps1 will compare HEAD to your SVN upstream +# if it can find one, or @{upstream} otherwise. Once you have +# set GIT_PS1_SHOWUPSTREAM, you can override it on a +# per-repository basis by setting the bash.showUpstream config +# variable. +# +# # To submit patches: # # *) Read Documentation/SubmittingPatches @@ -50,10 +82,12 @@ case "$COMP_WORDBREAKS" in *) COMP_WORDBREAKS="$COMP_WORDBREAKS:" esac +# __gitdir accepts 0 or 1 arguments (i.e., location) +# returns location of .git repo __gitdir () { - if [ -z "$1" ]; then - if [ -n "$__git_dir" ]; then + if [ -z "${1-}" ]; then + if [ -n "${__git_dir-}" ]; then echo "$__git_dir" elif [ -d .git ]; then echo .git @@ -67,58 +101,220 @@ __gitdir () fi } -__git_ps1 () +# stores the divergence from upstream in $p +# used by GIT_PS1_SHOWUPSTREAM +__git_ps1_show_upstream () { - local g="$(git rev-parse --git-dir 2>/dev/null)" - if [ -n "$g" ]; then - local r - local b - if [ -d "$g/rebase-apply" ] - then - if test -f "$g/rebase-apply/rebasing" - then - r="|REBASE" - elif test -f "$g/rebase-apply/applying" - then - r="|AM" + local key value + local svn_remote=() svn_url_pattern count n + local upstream=git legacy="" verbose="" + + # get some config options from git-config + while read key value; do + case "$key" in + bash.showupstream) + GIT_PS1_SHOWUPSTREAM="$value" + if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then + p="" + return + fi + ;; + svn-remote.*.url) + svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value" + svn_url_pattern+="\\|$value" + upstream=svn+git # default upstream is SVN if available, else git + ;; + esac + done < <(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ') + + # parse configuration values + for option in ${GIT_PS1_SHOWUPSTREAM}; do + case "$option" in + git|svn) upstream="$option" ;; + verbose) verbose=1 ;; + legacy) legacy=1 ;; + esac + done + + # Find our upstream + case "$upstream" in + git) upstream="@{upstream}" ;; + svn*) + # get the upstream from the "git-svn-id: ..." in a commit message + # (git-svn uses essentially the same procedure internally) + local svn_upstream=($(git log --first-parent -1 \ + --grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null)) + if [[ 0 -ne ${#svn_upstream[@]} ]]; then + svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]} + svn_upstream=${svn_upstream%@*} + local n_stop="${#svn_remote[@]}" + for ((n=1; n <= n_stop; ++n)); do + svn_upstream=${svn_upstream#${svn_remote[$n]}} + done + + if [[ -z "$svn_upstream" ]]; then + # default branch name for checkouts with no layout: + upstream=${GIT_SVN_ID:-git-svn} else - r="|AM/REBASE" + upstream=${svn_upstream#/} fi - b="$(git symbolic-ref HEAD 2>/dev/null)" - elif [ -f "$g/rebase-merge/interactive" ] + elif [[ "svn+git" = "$upstream" ]]; then + upstream="@{upstream}" + fi + ;; + esac + + # Find how many commits we are ahead/behind our upstream + if [[ -z "$legacy" ]]; then + count="$(git rev-list --count --left-right \ + "$upstream"...HEAD 2>/dev/null)" + else + # produce equivalent output to --count for older versions of git + local commits + if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)" then + local commit behind=0 ahead=0 + for commit in $commits + do + case "$commit" in + "<"*) let ++behind + ;; + *) let ++ahead + ;; + esac + done + count="$behind $ahead" + else + count="" + fi + fi + + # calculate the result + if [[ -z "$verbose" ]]; then + case "$count" in + "") # no upstream + p="" ;; + "0 0") # equal to upstream + p="=" ;; + "0 "*) # ahead of upstream + p=">" ;; + *" 0") # behind upstream + p="<" ;; + *) # diverged from upstream + p="<>" ;; + esac + else + case "$count" in + "") # no upstream + p="" ;; + "0 0") # equal to upstream + p=" u=" ;; + "0 "*) # ahead of upstream + p=" u+${count#0 }" ;; + *" 0") # behind upstream + p=" u-${count% 0}" ;; + *) # diverged from upstream + p=" u+${count#* }-${count% *}" ;; + esac + fi + +} + + +# __git_ps1 accepts 0 or 1 arguments (i.e., format string) +# returns text to add to bash PS1 prompt (includes branch name) +__git_ps1 () +{ + local g="$(__gitdir)" + if [ -n "$g" ]; then + local r="" + local b="" + if [ -f "$g/rebase-merge/interactive" ]; then r="|REBASE-i" b="$(cat "$g/rebase-merge/head-name")" - elif [ -d "$g/rebase-merge" ] - then + elif [ -d "$g/rebase-merge" ]; then r="|REBASE-m" b="$(cat "$g/rebase-merge/head-name")" - elif [ -f "$g/MERGE_HEAD" ] - then - r="|MERGING" - b="$(git symbolic-ref HEAD 2>/dev/null)" else - if [ -f "$g/BISECT_LOG" ] - then + if [ -d "$g/rebase-apply" ]; then + if [ -f "$g/rebase-apply/rebasing" ]; then + r="|REBASE" + elif [ -f "$g/rebase-apply/applying" ]; then + r="|AM" + else + r="|AM/REBASE" + fi + elif [ -f "$g/MERGE_HEAD" ]; then + r="|MERGING" + elif [ -f "$g/BISECT_LOG" ]; then r="|BISECTING" fi - if ! b="$(git symbolic-ref HEAD 2>/dev/null)" - then - if ! b="$(git describe --exact-match HEAD 2>/dev/null)" - then - b="$(cut -c1-7 "$g/HEAD")..." + + b="$(git symbolic-ref HEAD 2>/dev/null)" || { + + b="$( + case "${GIT_PS1_DESCRIBE_STYLE-}" in + (contains) + git describe --contains HEAD ;; + (branch) + git describe --contains --all HEAD ;; + (describe) + git describe HEAD ;; + (* | default) + git describe --tags --exact-match HEAD ;; + esac 2>/dev/null)" || + + b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." || + b="unknown" + b="($b)" + } + fi + + local w="" + local i="" + local s="" + local u="" + local c="" + local p="" + + if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then + if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then + c="BARE:" + else + b="GIT_DIR!" + fi + elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then + if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then + if [ "$(git config --bool bash.showDirtyState)" != "false" ]; then + git diff --no-ext-diff --quiet --exit-code || w="*" + if git rev-parse --quiet --verify HEAD >/dev/null; then + git diff-index --cached --quiet HEAD -- || i="+" + else + i="#" + fi fi fi - fi + if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then + git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$" + fi - if [ -n "$1" ]; then - printf "$1" "${b##refs/heads/}$r" - else - printf " (%s)" "${b##refs/heads/}$r" + if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ]; then + if [ -n "$(git ls-files --others --exclude-standard)" ]; then + u="%" + fi + fi + + if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then + __git_ps1_show_upstream + fi fi + + local f="$w$i$s$u" + printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p" fi } +# __gitcomp_1 requires 2 arguments __gitcomp_1 () { local c IFS=' '$'\t'$'\n' @@ -131,9 +327,168 @@ __gitcomp_1 () done } +# The following function is based on code from: +# +# bash_completion - programmable completion functions for bash 3.2+ +# +# Copyright © 2006-2008, Ian Macdonald <ian@caliban.org> +# © 2009-2010, Bash Completion Maintainers +# <bash-completion-devel@lists.alioth.debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# The latest version of this software can be obtained here: +# +# http://bash-completion.alioth.debian.org/ +# +# RELEASE: 2.x + +# This function can be used to access a tokenized list of words +# on the command line: +# +# __git_reassemble_comp_words_by_ref '=:' +# if test "${words_[cword_-1]}" = -w +# then +# ... +# fi +# +# The argument should be a collection of characters from the list of +# word completion separators (COMP_WORDBREAKS) to treat as ordinary +# characters. +# +# This is roughly equivalent to going back in time and setting +# COMP_WORDBREAKS to exclude those characters. The intent is to +# make option types like --date=<type> and <rev>:<path> easy to +# recognize by treating each shell word as a single token. +# +# It is best not to set COMP_WORDBREAKS directly because the value is +# shared with other completion scripts. By the time the completion +# function gets called, COMP_WORDS has already been populated so local +# changes to COMP_WORDBREAKS have no effect. +# +# Output: words_, cword_, cur_. + +__git_reassemble_comp_words_by_ref() +{ + local exclude i j first + # Which word separators to exclude? + exclude="${1//[^$COMP_WORDBREAKS]}" + cword_=$COMP_CWORD + if [ -z "$exclude" ]; then + words_=("${COMP_WORDS[@]}") + return + fi + # List of word completion separators has shrunk; + # re-assemble words to complete. + for ((i=0, j=0; i < ${#COMP_WORDS[@]}; i++, j++)); do + # Append each nonempty word consisting of just + # word separator characters to the current word. + first=t + while + [ $i -gt 0 ] && + [ -n "${COMP_WORDS[$i]}" ] && + # word consists of excluded word separators + [ "${COMP_WORDS[$i]//[^$exclude]}" = "${COMP_WORDS[$i]}" ] + do + # Attach to the previous token, + # unless the previous token is the command name. + if [ $j -ge 2 ] && [ -n "$first" ]; then + ((j--)) + fi + first= + words_[$j]=${words_[j]}${COMP_WORDS[i]} + if [ $i = $COMP_CWORD ]; then + cword_=$j + fi + if (($i < ${#COMP_WORDS[@]} - 1)); then + ((i++)) + else + # Done. + return + fi + done + words_[$j]=${words_[j]}${COMP_WORDS[i]} + if [ $i = $COMP_CWORD ]; then + cword_=$j + fi + done +} + +if ! type _get_comp_words_by_ref >/dev/null 2>&1; then +if [[ -z ${ZSH_VERSION:+set} ]]; then +_get_comp_words_by_ref () +{ + local exclude cur_ words_ cword_ + if [ "$1" = "-n" ]; then + exclude=$2 + shift 2 + fi + __git_reassemble_comp_words_by_ref "$exclude" + cur_=${words_[cword_]} + while [ $# -gt 0 ]; do + case "$1" in + cur) + cur=$cur_ + ;; + prev) + prev=${words_[$cword_-1]} + ;; + words) + words=("${words_[@]}") + ;; + cword) + cword=$cword_ + ;; + esac + shift + done +} +else +_get_comp_words_by_ref () +{ + while [ $# -gt 0 ]; do + case "$1" in + cur) + cur=${COMP_WORDS[COMP_CWORD]} + ;; + prev) + prev=${COMP_WORDS[COMP_CWORD-1]} + ;; + words) + words=("${COMP_WORDS[@]}") + ;; + cword) + cword=$COMP_CWORD + ;; + -n) + # assume COMP_WORDBREAKS is already set sanely + shift + ;; + esac + shift + done +} +fi +fi + +# __gitcomp accepts 1, 2, 3, or 4 arguments +# generates completion reply with compgen __gitcomp () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur if [ $# -gt 2 ]; then cur="$3" fi @@ -143,25 +498,23 @@ __gitcomp () ;; *) local IFS=$'\n' - COMPREPLY=($(compgen -P "$2" \ - -W "$(__gitcomp_1 "$1" "$4")" \ + COMPREPLY=($(compgen -P "${2-}" \ + -W "$(__gitcomp_1 "${1-}" "${4-}")" \ -- "$cur")) ;; esac } +# __git_heads accepts 0 or 1 arguments (to pass to __gitdir) __git_heads () { - local cmd i is_hash=y dir="$(__gitdir "$1")" + local cmd i is_hash=y dir="$(__gitdir "${1-}")" if [ -d "$dir" ]; then - for i in $(git --git-dir="$dir" \ - for-each-ref --format='%(refname)' \ - refs/heads ); do - echo "${i#refs/heads/}" - done + git --git-dir="$dir" for-each-ref --format='%(refname:short)' \ + refs/heads return fi - for i in $(git ls-remote "$1" 2>/dev/null); do + for i in $(git ls-remote "${1-}" 2>/dev/null); do case "$is_hash,$i" in y,*) is_hash=n ;; n,*^{}) is_hash=y ;; @@ -171,18 +524,16 @@ __git_heads () done } +# __git_tags accepts 0 or 1 arguments (to pass to __gitdir) __git_tags () { - local cmd i is_hash=y dir="$(__gitdir "$1")" + local cmd i is_hash=y dir="$(__gitdir "${1-}")" if [ -d "$dir" ]; then - for i in $(git --git-dir="$dir" \ - for-each-ref --format='%(refname)' \ - refs/tags ); do - echo "${i#refs/tags/}" - done + git --git-dir="$dir" for-each-ref --format='%(refname:short)' \ + refs/tags return fi - for i in $(git ls-remote "$1" 2>/dev/null); do + for i in $(git ls-remote "${1-}" 2>/dev/null); do case "$is_hash,$i" in y,*) is_hash=n ;; n,*^{}) is_hash=y ;; @@ -192,21 +543,46 @@ __git_tags () done } +# __git_refs accepts 0, 1 (to pass to __gitdir), or 2 arguments +# presence of 2nd argument means use the guess heuristic employed +# by checkout for tracking branches __git_refs () { - local cmd i is_hash=y dir="$(__gitdir "$1")" + local i is_hash=y dir="$(__gitdir "${1-}")" track="${2-}" + local cur format refs + _get_comp_words_by_ref -n =: cur if [ -d "$dir" ]; then - if [ -e "$dir/HEAD" ]; then echo HEAD; fi - for i in $(git --git-dir="$dir" \ - for-each-ref --format='%(refname)' \ - refs/tags refs/heads refs/remotes); do - case "$i" in - refs/tags/*) echo "${i#refs/tags/}" ;; - refs/heads/*) echo "${i#refs/heads/}" ;; - refs/remotes/*) echo "${i#refs/remotes/}" ;; - *) echo "$i" ;; - esac - done + case "$cur" in + refs|refs/*) + format="refname" + refs="${cur%/*}" + track="" + ;; + *) + for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD; do + if [ -e "$dir/$i" ]; then echo $i; fi + done + format="refname:short" + refs="refs/tags refs/heads refs/remotes" + ;; + esac + git --git-dir="$dir" for-each-ref --format="%($format)" \ + $refs + if [ -n "$track" ]; then + # employ the heuristic used by git checkout + # Try to find a remote branch that matches the completion word + # but only output if the branch name is unique + local ref entry + git --git-dir="$dir" for-each-ref --shell --format="ref=%(refname:short)" \ + "refs/remotes/" | \ + while read entry; do + eval "$entry" + ref="${ref#*/}" + if [[ "$ref" == "$cur"* ]]; then + echo "$ref" + fi + done | uniq -u + fi return fi for i in $(git ls-remote "$dir" 2>/dev/null); do @@ -221,6 +597,7 @@ __git_refs () done } +# __git_refs2 requires 1 argument (to pass to __git_refs) __git_refs2 () { local i @@ -229,6 +606,7 @@ __git_refs2 () done } +# __git_refs_remotes requires 1 argument (to pass to ls-remote) __git_refs_remotes () { local cmd i is_hash=y @@ -255,22 +633,14 @@ __git_remotes () echo ${i#$d/remotes/} done [ "$ngoff" ] && shopt -u nullglob - for i in $(git --git-dir="$d" config --list); do - case "$i" in - remote.*.url=*) - i="${i#remote.}" - echo "${i/.url=*/}" - ;; - esac + for i in $(git --git-dir="$d" config --get-regexp 'remote\..*\.url' 2>/dev/null); do + i="${i#remote.}" + echo "${i/.url*/}" done } -__git_merge_strategies () +__git_list_merge_strategies () { - if [ -n "$__git_merge_strategylist" ]; then - echo "$__git_merge_strategylist" - return - fi git merge -s help 2>&1 | sed -n -e '/[Aa]vailable strategies are: /,/^$/{ s/\.$// @@ -280,12 +650,22 @@ __git_merge_strategies () p }' } -__git_merge_strategylist= -__git_merge_strategylist=$(__git_merge_strategies 2>/dev/null) + +__git_merge_strategies= +# 'git merge -s help' (and thus detection of the merge strategy +# list) fails, unfortunately, if run outside of any git working +# tree. __git_merge_strategies is set to the empty string in +# that case, and the detection will be repeated the next time it +# is needed. +__git_compute_merge_strategies () +{ + : ${__git_merge_strategies:=$(__git_list_merge_strategies)} +} __git_complete_file () { - local pfx ls ref cur="${COMP_WORDS[COMP_CWORD]}" + local pfx ls ref cur + _get_comp_words_by_ref -n =: cur case "$cur" in ?*:*) ref="${cur%%:*}" @@ -333,7 +713,8 @@ __git_complete_file () __git_complete_revlist () { - local pfx cur="${COMP_WORDS[COMP_CWORD]}" + local pfx cur + _get_comp_words_by_ref -n =: cur case "$cur" in *...*) pfx="${cur%...*}..." @@ -351,14 +732,102 @@ __git_complete_revlist () esac } -__git_all_commands () +__git_complete_remote_or_refspec () { - if [ -n "$__git_all_commandlist" ]; then - echo "$__git_all_commandlist" + local cur words cword + _get_comp_words_by_ref -n =: cur words cword + local cmd="${words[1]}" + local i c=2 remote="" pfx="" lhs=1 no_complete_refspec=0 + while [ $c -lt $cword ]; do + i="${words[c]}" + case "$i" in + --mirror) [ "$cmd" = "push" ] && no_complete_refspec=1 ;; + --all) + case "$cmd" in + push) no_complete_refspec=1 ;; + fetch) + COMPREPLY=() + return + ;; + *) ;; + esac + ;; + -*) ;; + *) remote="$i"; break ;; + esac + c=$((++c)) + done + if [ -z "$remote" ]; then + __gitcomp "$(__git_remotes)" + return + fi + if [ $no_complete_refspec = 1 ]; then + COMPREPLY=() return fi + [ "$remote" = "." ] && remote= + case "$cur" in + *:*) + case "$COMP_WORDBREAKS" in + *:*) : great ;; + *) pfx="${cur%%:*}:" ;; + esac + cur="${cur#*:}" + lhs=0 + ;; + +*) + pfx="+" + cur="${cur#+}" + ;; + esac + case "$cmd" in + fetch) + if [ $lhs = 1 ]; then + __gitcomp "$(__git_refs2 "$remote")" "$pfx" "$cur" + else + __gitcomp "$(__git_refs)" "$pfx" "$cur" + fi + ;; + pull) + if [ $lhs = 1 ]; then + __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur" + else + __gitcomp "$(__git_refs)" "$pfx" "$cur" + fi + ;; + push) + if [ $lhs = 1 ]; then + __gitcomp "$(__git_refs)" "$pfx" "$cur" + else + __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur" + fi + ;; + esac +} + +__git_complete_strategy () +{ + local cur prev + _get_comp_words_by_ref -n =: cur prev + __git_compute_merge_strategies + case "$prev" in + -s|--strategy) + __gitcomp "$__git_merge_strategies" + return 0 + esac + case "$cur" in + --strategy=*) + __gitcomp "$__git_merge_strategies" "" "${cur##--strategy=}" + return 0 + ;; + esac + return 1 +} + +__git_list_all_commands () +{ local i IFS=" "$'\n' - for i in $(git help -a|egrep '^ ') + for i in $(git help -a|egrep '^ [a-zA-Z0-9]') do case $i in *--*) : helper pattern;; @@ -366,17 +835,18 @@ __git_all_commands () esac done } -__git_all_commandlist= -__git_all_commandlist="$(__git_all_commands 2>/dev/null)" -__git_porcelain_commands () +__git_all_commands= +__git_compute_all_commands () +{ + : ${__git_all_commands:=$(__git_list_all_commands)} +} + +__git_list_porcelain_commands () { - if [ -n "$__git_porcelain_commandlist" ]; then - echo "$__git_porcelain_commandlist" - return - fi local i IFS=" "$'\n' - for i in "help" $(__git_all_commands) + __git_compute_all_commands + for i in "help" $__git_all_commands do case $i in *--*) : helper pattern;; @@ -427,7 +897,7 @@ __git_porcelain_commands () quiltimport) : import;; read-tree) : plumbing;; receive-pack) : plumbing;; - reflog) : plumbing;; + remote-*) : transport;; repo-config) : deprecated;; rerere) : plumbing;; rev-list) : plumbing;; @@ -457,40 +927,69 @@ __git_porcelain_commands () esac done } -__git_porcelain_commandlist= -__git_porcelain_commandlist="$(__git_porcelain_commands 2>/dev/null)" + +__git_porcelain_commands= +__git_compute_porcelain_commands () +{ + __git_compute_all_commands + : ${__git_porcelain_commands:=$(__git_list_porcelain_commands)} +} + +__git_pretty_aliases () +{ + local i IFS=$'\n' + for i in $(git --git-dir="$(__gitdir)" config --get-regexp "pretty\..*" 2>/dev/null); do + case "$i" in + pretty.*) + i="${i#pretty.}" + echo "${i/ */}" + ;; + esac + done +} __git_aliases () { local i IFS=$'\n' - for i in $(git --git-dir="$(__gitdir)" config --list); do + for i in $(git --git-dir="$(__gitdir)" config --get-regexp "alias\..*" 2>/dev/null); do case "$i" in alias.*) i="${i#alias.}" - echo "${i/=*/}" + echo "${i/ */}" ;; esac done } +# __git_aliased_command requires 1 argument __git_aliased_command () { local word cmdline=$(git --git-dir="$(__gitdir)" \ config --get "alias.$1") for word in $cmdline; do - if [ "${word##-*}" ]; then - echo $word + case "$word" in + \!gitk|gitk) + echo "gitk" return - fi + ;; + \!*) : shell command alias ;; + -*) : option ;; + *=*) : setting env ;; + git) : git itself ;; + *) + echo "$word" + return + esac done } -__git_find_subcommand () +# __git_find_on_cmdline requires 1 argument +__git_find_on_cmdline () { - local word subcommand c=1 - - while [ $c -lt $COMP_CWORD ]; do - word="${COMP_WORDS[c]}" + local word subcommand c=1 words cword + _get_comp_words_by_ref -n =: words cword + while [ $c -lt $cword ]; do + word="${words[c]}" for subcommand in $1; do if [ "$subcommand" = "$word" ]; then echo "$subcommand" @@ -503,9 +1002,10 @@ __git_find_subcommand () __git_has_doubledash () { - local c=1 - while [ $c -lt $COMP_CWORD ]; do - if [ "--" = "${COMP_WORDS[c]}" ]; then + local c=1 words cword + _get_comp_words_by_ref -n =: words cword + while [ $c -lt $cword ]; do + if [ "--" = "${words[c]}" ]; then return 0 fi c=$((++c)) @@ -517,9 +1017,10 @@ __git_whitespacelist="nowarn warn error error-all fix" _git_am () { - local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)" + local cur dir="$(__gitdir)" + _get_comp_words_by_ref -n =: cur if [ -d "$dir"/rebase-apply ]; then - __gitcomp "--skip --resolved --abort" + __gitcomp "--skip --continue --resolved --abort" return fi case "$cur" in @@ -529,8 +1030,10 @@ _git_am () ;; --*) __gitcomp " - --signoff --utf8 --binary --3way --interactive - --whitespace= + --3way --committer-date-is-author-date --ignore-date + --ignore-whitespace --ignore-space-change + --interactive --keep --no-utf8 --signoff --utf8 + --whitespace= --scissors " return esac @@ -539,7 +1042,8 @@ _git_am () _git_apply () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --whitespace=*) __gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}" @@ -550,6 +1054,7 @@ _git_apply () --stat --numstat --summary --check --index --cached --index-info --reverse --reject --unidiff-zero --apply --no-add --exclude= + --ignore-whitespace --ignore-space-change --whitespace= --inaccurate-eof --verbose " return @@ -561,12 +1066,13 @@ _git_add () { __git_has_doubledash && return - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " --interactive --refresh --patch --update --dry-run - --ignore-errors + --ignore-errors --intent-to-add " return esac @@ -575,7 +1081,8 @@ _git_add () _git_archive () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --format=*) __gitcomp "$(git archive --list)" "" "${cur##--format=}" @@ -601,14 +1108,18 @@ _git_bisect () __git_has_doubledash && return local subcommands="start bad good skip reset visualize replay log run" - local subcommand="$(__git_find_subcommand "$subcommands")" + local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then - __gitcomp "$subcommands" + if [ -f "$(__gitdir)"/BISECT_START ]; then + __gitcomp "$subcommands" + else + __gitcomp "replay start" + fi return fi case "$subcommand" in - bad|good|reset|skip) + bad|good|reset|skip|start) __gitcomp "$(__git_refs)" ;; *) @@ -619,10 +1130,11 @@ _git_bisect () _git_branch () { - local i c=1 only_local_ref="n" has_r="n" + local i c=1 only_local_ref="n" has_r="n" cur words cword - while [ $c -lt $COMP_CWORD ]; do - i="${COMP_WORDS[c]}" + _get_comp_words_by_ref -n =: cur words cword + while [ $c -lt $cword ]; do + i="${words[c]}" case "$i" in -d|-m) only_local_ref="y" ;; -r) has_r="y" ;; @@ -630,12 +1142,12 @@ _git_branch () c=$((++c)) done - case "${COMP_WORDS[COMP_CWORD]}" in - --*=*) COMPREPLY=() ;; + case "$cur" in --*) __gitcomp " --color --no-color --verbose --abbrev= --no-abbrev --track --no-track --contains --merged --no-merged + --set-upstream " ;; *) @@ -650,8 +1162,10 @@ _git_branch () _git_bundle () { - local cmd="${COMP_WORDS[2]}" - case "$COMP_CWORD" in + local words cword + _get_comp_words_by_ref -n =: words cword + local cmd="${words[2]}" + case "$cword" in 2) __gitcomp "create list-heads verify unbundle" ;; @@ -672,7 +1186,28 @@ _git_checkout () { __git_has_doubledash && return - __gitcomp "$(__git_refs)" + local cur + _get_comp_words_by_ref -n =: cur + case "$cur" in + --conflict=*) + __gitcomp "diff3 merge" "" "${cur##--conflict=}" + ;; + --*) + __gitcomp " + --quiet --ours --theirs --track --no-track --merge + --conflict= --orphan --patch + " + ;; + *) + # check if --track, --no-track, or --no-guess was specified + # if so, disable DWIM mode + local flags="--track --no-track --no-guess" track=1 + if [ -n "$(__git_find_on_cmdline "$flags")" ]; then + track='' + fi + __gitcomp "$(__git_refs '' $track)" + ;; + esac } _git_cherry () @@ -682,7 +1217,8 @@ _git_cherry () _git_cherry_pick () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--edit --no-commit" @@ -697,7 +1233,8 @@ _git_clean () { __git_has_doubledash && return - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--dry-run --quiet" @@ -709,7 +1246,8 @@ _git_clean () _git_clone () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -736,12 +1274,34 @@ _git_commit () { __git_has_doubledash && return - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in + --cleanup=*) + __gitcomp "default strip verbatim whitespace + " "" "${cur##--cleanup=}" + return + ;; + --reuse-message=*) + __gitcomp "$(__git_refs)" "" "${cur##--reuse-message=}" + return + ;; + --reedit-message=*) + __gitcomp "$(__git_refs)" "" "${cur##--reedit-message=}" + return + ;; + --untracked-files=*) + __gitcomp "all no normal" "" "${cur##--untracked-files=}" + return + ;; --*) __gitcomp " --all --author= --signoff --verify --no-verify --edit --amend --include --only --interactive + --dry-run --reuse-message= --reedit-message= + --reset-author --file= --message= --template= + --cleanup= --untracked-files --untracked-files= + --verbose --quiet " return esac @@ -750,7 +1310,8 @@ _git_commit () _git_describe () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -762,23 +1323,33 @@ _git_describe () __gitcomp "$(__git_refs)" } -_git_diff () -{ - __git_has_doubledash && return - - local cur="${COMP_WORDS[COMP_CWORD]}" - case "$cur" in - --*) - __gitcomp "--cached --stat --numstat --shortstat --summary +__git_diff_common_options="--stat --numstat --shortstat --summary --patch-with-stat --name-only --name-status --color --no-color --color-words --no-renames --check --full-index --binary --abbrev --diff-filter= - --find-copies-harder --pickaxe-all --pickaxe-regex + --find-copies-harder --text --ignore-space-at-eol --ignore-space-change --ignore-all-space --exit-code --quiet --ext-diff --no-ext-diff --no-prefix --src-prefix= --dst-prefix= - --base --ours --theirs + --inter-hunk-context= + --patience + --raw + --dirstat --dirstat= --dirstat-by-file + --dirstat-by-file= --cumulative +" + +_git_diff () +{ + __git_has_doubledash && return + + local cur + _get_comp_words_by_ref -n =: cur + case "$cur" in + --*) + __gitcomp "--cached --staged --pickaxe-all --pickaxe-regex + --base --ours --theirs --no-index + $__git_diff_common_options " return ;; @@ -786,46 +1357,77 @@ _git_diff () __git_complete_file } -_git_fetch () +__git_mergetools_common="diffuse ecmerge emerge kdiff3 meld opendiff + tkdiff vimdiff gvimdiff xxdiff araxis p4merge +" + +_git_difftool () { - local cur="${COMP_WORDS[COMP_CWORD]}" + __git_has_doubledash && return - if [ "$COMP_CWORD" = 2 ]; then - __gitcomp "$(__git_remotes)" - else - case "$cur" in - *:*) - local pfx="" - case "$COMP_WORDBREAKS" in - *:*) : great ;; - *) pfx="${cur%%:*}:" ;; - esac - __gitcomp "$(__git_refs)" "$pfx" "${cur#*:}" - ;; - *) - __gitcomp "$(__git_refs2 "${COMP_WORDS[2]}")" - ;; - esac - fi + local cur + _get_comp_words_by_ref -n =: cur + case "$cur" in + --tool=*) + __gitcomp "$__git_mergetools_common kompare" "" "${cur##--tool=}" + return + ;; + --*) + __gitcomp "--cached --staged --pickaxe-all --pickaxe-regex + --base --ours --theirs + --no-renames --diff-filter= --find-copies-harder + --relative --ignore-submodules + --tool=" + return + ;; + esac + __git_complete_file +} + +__git_fetch_options=" + --quiet --verbose --append --upload-pack --force --keep --depth= + --tags --no-tags --all --prune --dry-run +" + +_git_fetch () +{ + local cur + _get_comp_words_by_ref -n =: cur + case "$cur" in + --*) + __gitcomp "$__git_fetch_options" + return + ;; + esac + __git_complete_remote_or_refspec } _git_format_patch () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in + --thread=*) + __gitcomp " + deep shallow + " "" "${cur##--thread=}" + return + ;; --*) __gitcomp " - --stdout --attach --thread + --stdout --attach --no-attach --thread --thread= --output-directory --numbered --start-number --numbered-files --keep-subject - --signoff - --in-reply-to= + --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= " return ;; @@ -833,9 +1435,26 @@ _git_format_patch () __git_complete_revlist } +_git_fsck () +{ + local cur + _get_comp_words_by_ref -n =: cur + case "$cur" in + --*) + __gitcomp " + --tags --root --unreachable --cache --no-reflogs --full + --strict --verbose --lost-found + " + return + ;; + esac + COMPREPLY=() +} + _git_gc () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--prune --aggressive" @@ -845,11 +1464,17 @@ _git_gc () COMPREPLY=() } +_git_gitk () +{ + _gitk +} + _git_grep () { __git_has_doubledash && return - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -859,34 +1484,40 @@ _git_grep () --extended-regexp --basic-regexp --fixed-strings --files-with-matches --name-only --files-without-match + --max-depth --count --and --or --not --all-match " return ;; esac - COMPREPLY=() + + __gitcomp "$(__git_refs)" } _git_help () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--all --info --man --web" return ;; esac - __gitcomp "$(__git_all_commands) + __git_compute_all_commands + __gitcomp "$__git_all_commands attributes cli core-tutorial cvs-migration diffcore gitk glossary hooks ignore modules repository-layout tutorial tutorial-2 + workflows " } _git_init () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --shared=*) __gitcomp " @@ -906,7 +1537,8 @@ _git_ls_files () { __git_has_doubledash && return - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--cached --deleted --modified --others --ignored @@ -932,48 +1564,78 @@ _git_ls_tree () __git_complete_file } +# Options that go well for log, shortlog and gitk +__git_log_common_options=" + --not --all + --branches --tags --remotes + --first-parent --merges --no-merges + --max-count= + --max-age= --since= --after= + --min-age= --until= --before= +" +# Options that go well for log and gitk (not shortlog) +__git_log_gitk_options=" + --dense --sparse --full-history + --simplify-merges --simplify-by-decoration + --left-right +" +# Options that go well for log and shortlog (not gitk) +__git_log_shortlog_options=" + --author= --committer= --grep= + --all-match +" + +__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 () { __git_has_doubledash && return - local cur="${COMP_WORDS[COMP_CWORD]}" local g="$(git rev-parse --git-dir 2>/dev/null)" local merge="" if [ -f "$g/MERGE_HEAD" ]; then merge="--merge" fi + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --pretty=*) - __gitcomp " - oneline short medium full fuller email raw + __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases) " "" "${cur##--pretty=}" return ;; + --format=*) + __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases) + " "" "${cur##--format=}" + return + ;; --date=*) - __gitcomp " - relative iso8601 rfc2822 short local default - " "" "${cur##--date=}" + __gitcomp "$__git_log_date_formats" "" "${cur##--date=}" + return + ;; + --decorate=*) + __gitcomp "long short" "" "${cur##--decorate=}" return ;; --*) __gitcomp " - --max-count= --max-age= --since= --after= - --min-age= --before= --until= + $__git_log_common_options + $__git_log_shortlog_options + $__git_log_gitk_options --root --topo-order --date-order --reverse - --no-merges --follow + --follow --full-diff --abbrev-commit --abbrev= --relative-date --date= - --author= --committer= --grep= - --all-match - --pretty= --name-status --name-only --raw - --not --all - --left-right --cherry-pick + --pretty= --format= --oneline + --cherry-pick --graph - --stat --numstat --shortstat - --decorate --diff-filter= - --color-words --walk-reflogs - --parents --children --full-history + --decorate --decorate= + --walk-reflogs + --parents --children $merge + $__git_diff_common_options + --pickaxe-all --pickaxe-regex " return ;; @@ -981,23 +1643,20 @@ _git_log () __git_complete_revlist } +__git_merge_options=" + --no-commit --no-stat --log --no-log --squash --strategy + --commit --stat --no-squash --ff --no-ff --ff-only +" + _git_merge () { - local cur="${COMP_WORDS[COMP_CWORD]}" - case "${COMP_WORDS[COMP_CWORD-1]}" in - -s|--strategy) - __gitcomp "$(__git_merge_strategies)" - return - esac + __git_complete_strategy && return + + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in - --strategy=*) - __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}" - return - ;; --*) - __gitcomp " - --no-commit --no-stat --log --no-log --squash --strategy - " + __gitcomp "$__git_merge_options" return esac __gitcomp "$(__git_refs)" @@ -1005,13 +1664,11 @@ _git_merge () _git_mergetool () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --tool=*) - __gitcomp " - kdiff3 tkdiff meld xxdiff emerge - vimdiff gvimdiff ecmerge opendiff - " "" "${cur##--tool=}" + __gitcomp "$__git_mergetools_common tortoisemerge" "" "${cur##--tool=}" return ;; --*) @@ -1029,7 +1686,8 @@ _git_merge_base () _git_mv () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--dry-run" @@ -1044,91 +1702,225 @@ _git_name_rev () __gitcomp "--tags --all --stdin" } -_git_pull () +_git_notes () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local subcommands='add append copy edit list prune remove show' + local subcommand="$(__git_find_on_cmdline "$subcommands")" + local cur words cword + _get_comp_words_by_ref -n =: cur words cword - if [ "$COMP_CWORD" = 2 ]; then - __gitcomp "$(__git_remotes)" - else - __gitcomp "$(__git_refs "${COMP_WORDS[2]}")" - fi -} - -_git_push () -{ - local cur="${COMP_WORDS[COMP_CWORD]}" - - if [ "$COMP_CWORD" = 2 ]; then - __gitcomp "$(__git_remotes)" - else - case "$cur" in - *:*) - local pfx="" - case "$COMP_WORDBREAKS" in - *:*) : great ;; - *) pfx="${cur%%:*}:" ;; - esac - - __gitcomp "$(__git_refs "${COMP_WORDS[2]}")" "$pfx" "${cur#*:}" + case "$subcommand,$cur" in + ,--*) + __gitcomp '--ref' + ;; + ,*) + case "${words[cword-1]}" in + --ref) + __gitcomp "$(__git_refs)" + ;; + *) + __gitcomp "$subcommands --ref" ;; - +*) - __gitcomp "$(__git_refs)" + "${cur#+}" + esac + ;; + add,--reuse-message=*|append,--reuse-message=*) + __gitcomp "$(__git_refs)" "" "${cur##--reuse-message=}" + ;; + add,--reedit-message=*|append,--reedit-message=*) + __gitcomp "$(__git_refs)" "" "${cur##--reedit-message=}" + ;; + add,--*|append,--*) + __gitcomp '--file= --message= --reedit-message= + --reuse-message=' + ;; + copy,--*) + __gitcomp '--stdin' + ;; + prune,--*) + __gitcomp '--dry-run --verbose' + ;; + prune,*) + ;; + *) + case "${words[cword-1]}" in + -m|-F) ;; *) __gitcomp "$(__git_refs)" ;; esac - fi + ;; + esac +} + +_git_pull () +{ + __git_complete_strategy && return + + local cur + _get_comp_words_by_ref -n =: cur + case "$cur" in + --*) + __gitcomp " + --rebase --no-rebase + $__git_merge_options + $__git_fetch_options + " + return + ;; + esac + __git_complete_remote_or_refspec +} + +_git_push () +{ + local cur prev + _get_comp_words_by_ref -n =: cur prev + case "$prev" in + --repo) + __gitcomp "$(__git_remotes)" + return + esac + case "$cur" in + --repo=*) + __gitcomp "$(__git_remotes)" "" "${cur##--repo=}" + return + ;; + --*) + __gitcomp " + --all --mirror --tags --dry-run --force --verbose + --receive-pack= --repo= + " + return + ;; + esac + __git_complete_remote_or_refspec } _git_rebase () { - local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)" + local dir="$(__gitdir)" + local cur + _get_comp_words_by_ref -n =: cur if [ -d "$dir"/rebase-apply ] || [ -d "$dir"/rebase-merge ]; then __gitcomp "--continue --skip --abort" return fi - case "${COMP_WORDS[COMP_CWORD-1]}" in - -s|--strategy) - __gitcomp "$(__git_merge_strategies)" - return - esac + __git_complete_strategy && return case "$cur" in - --strategy=*) - __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}" + --whitespace=*) + __gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}" return ;; --*) - __gitcomp "--onto --merge --strategy --interactive" + __gitcomp " + --onto --merge --strategy --interactive + --preserve-merges --stat --no-stat + --committer-date-is-author-date --ignore-date + --ignore-whitespace --whitespace= + --autosquash + " + return esac __gitcomp "$(__git_refs)" } +_git_reflog () +{ + local subcommands="show delete expire" + local subcommand="$(__git_find_on_cmdline "$subcommands")" + + if [ -z "$subcommand" ]; then + __gitcomp "$subcommands" + else + __gitcomp "$(__git_refs)" + fi +} + +__git_send_email_confirm_options="always never auto cc compose" +__git_send_email_suppresscc_options="author self cc bodycc sob cccmd body all" + _git_send_email () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in + --confirm=*) + __gitcomp " + $__git_send_email_confirm_options + " "" "${cur##--confirm=}" + return + ;; + --suppress-cc=*) + __gitcomp " + $__git_send_email_suppresscc_options + " "" "${cur##--suppress-cc=}" + + return + ;; + --smtp-encryption=*) + __gitcomp "ssl tls" "" "${cur##--smtp-encryption=}" + return + ;; --*) - __gitcomp "--bcc --cc --cc-cmd --chain-reply-to --compose - --dry-run --envelope-sender --from --identity + __gitcomp "--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 --no-suppress-from --no-thread --quiet --signed-off-by-cc --smtp-pass --smtp-server - --smtp-server-port --smtp-ssl --smtp-user --subject - --suppress-cc --suppress-from --thread --to" + --smtp-server-port --smtp-encryption= --smtp-user + --subject --suppress-cc= --suppress-from --thread --to + --validate --no-validate" return ;; esac COMPREPLY=() } +_git_stage () +{ + _git_add +} + +__git_config_get_set_variables () +{ + local words cword + _get_comp_words_by_ref -n =: words cword + local prevword word config_file= c=$cword + while [ $c -gt 1 ]; do + word="${words[c]}" + case "$word" in + --global|--system|--file=*) + config_file="$word" + break + ;; + -f|--file) + config_file="$word $prevword" + break + ;; + esac + prevword=$word + c=$((--c)) + done + + git --git-dir="$(__gitdir)" config $config_file --list 2>/dev/null | + while read line + do + case "$line" in + *.*=*) + echo "${line/=*/}" + ;; + esac + done +} + _git_config () { - local cur="${COMP_WORDS[COMP_CWORD]}" - local prv="${COMP_WORDS[COMP_CWORD-1]}" - case "$prv" in + local cur prev + _get_comp_words_by_ref -n =: cur prev + case "$prev" in branch.*.remote) __gitcomp "$(__git_remotes)" return @@ -1138,13 +1930,13 @@ _git_config () return ;; remote.*.fetch) - local remote="${prv#remote.}" + local remote="${prev#remote.}" remote="${remote%.fetch}" __gitcomp "$(__git_refs_remotes "$remote")" return ;; remote.*.push) - local remote="${prv#remote.}" + local remote="${prev#remote.}" remote="${remote%.push}" __gitcomp "$(git --git-dir="$(__gitdir)" \ for-each-ref --format='%(refname):%(refname)' \ @@ -1152,20 +1944,50 @@ _git_config () return ;; pull.twohead|pull.octopus) - __gitcomp "$(__git_merge_strategies)" + __git_compute_merge_strategies + __gitcomp "$__git_merge_strategies" return ;; - color.branch|color.diff|color.status) + color.branch|color.diff|color.interactive|\ + color.showbranch|color.status|color.ui) __gitcomp "always never auto" return ;; + color.pager) + __gitcomp "false true" + return + ;; color.*.*) __gitcomp " - black red green yellow blue magenta cyan white + normal black red green yellow blue magenta cyan white bold dim ul blink reverse " return ;; + help.format) + __gitcomp "man info web html" + return + ;; + log.date) + __gitcomp "$__git_log_date_formats" + return + ;; + sendemail.aliasesfiletype) + __gitcomp "mutt mailrc pine elm gnus" + return + ;; + sendemail.confirm) + __gitcomp "$__git_send_email_confirm_options" + return + ;; + sendemail.suppresscc) + __gitcomp "$__git_send_email_suppresscc_options" + return + ;; + --get|--get-all|--unset|--unset-all) + __gitcomp "$(__git_config_get_set_variables)" + return + ;; *.*) COMPREPLY=() return @@ -1185,7 +2007,7 @@ _git_config () branch.*.*) local pfx="${cur%.*}." cur="${cur##*.}" - __gitcomp "remote merge" "$pfx" "$cur" + __gitcomp "remote merge mergeoptions rebase" "$pfx" "$cur" return ;; branch.*) @@ -1194,12 +2016,46 @@ _git_config () __gitcomp "$(__git_heads)" "$pfx" "$cur" "." return ;; + guitool.*.*) + local pfx="${cur%.*}." + cur="${cur##*.}" + __gitcomp " + argprompt cmd confirm needsfile noconsole norescan + prompt revprompt revunmerged title + " "$pfx" "$cur" + return + ;; + difftool.*.*) + local pfx="${cur%.*}." + cur="${cur##*.}" + __gitcomp "cmd path" "$pfx" "$cur" + return + ;; + man.*.*) + local pfx="${cur%.*}." + cur="${cur##*.}" + __gitcomp "cmd path" "$pfx" "$cur" + return + ;; + mergetool.*.*) + local pfx="${cur%.*}." + cur="${cur##*.}" + __gitcomp "cmd path trustExitCode" "$pfx" "$cur" + return + ;; + pager.*) + local pfx="${cur%.*}." + cur="${cur#*.}" + __git_compute_all_commands + __gitcomp "$__git_all_commands" "$pfx" "$cur" + return + ;; remote.*.*) local pfx="${cur%.*}." cur="${cur##*.}" __gitcomp " - url fetch push skipDefaultUpdate - receivepack uploadpack tagopt + url proxy fetch push mirror skipDefaultUpdate + receivepack uploadpack tagopt pushurl " "$pfx" "$cur" return ;; @@ -1209,114 +2065,312 @@ _git_config () __gitcomp "$(__git_remotes)" "$pfx" "$cur" "." return ;; + url.*.*) + local pfx="${cur%.*}." + cur="${cur##*.}" + __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur" + return + ;; esac __gitcomp " + add.ignoreErrors + advice.commitBeforeMerge + advice.detachedHead + advice.implicitIdentity + advice.pushNonFastForward + advice.resolveConflict + advice.statusHints + alias. + am.keepcr + apply.ignorewhitespace apply.whitespace - core.fileMode - core.gitProxy - core.ignoreStat - core.preferSymlinkRefs - core.logAllRefUpdates - core.loosecompression - core.repositoryFormatVersion - core.sharedRepository - core.warnAmbiguousRefs - core.compression - core.packedGitWindowSize - core.packedGitLimit + branch.autosetupmerge + branch.autosetuprebase + browser. clean.requireForce color.branch color.branch.current color.branch.local - color.branch.remote 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.plain - color.diff.meta + color.diff.commit color.diff.frag - color.diff.old + color.diff.func + color.diff.meta color.diff.new - color.diff.commit + 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.header color.status.added color.status.changed + color.status.header + color.status.nobranch color.status.untracked + color.status.updated + color.ui + commit.status + commit.template + core.abbrevguard + core.askpass + core.attributesfile + core.autocrlf + core.bare + core.bigFileThreshold + core.compression + core.createObject + core.deltaBaseCacheLimit + core.editor + core.eol + core.excludesfile + core.fileMode + core.fsyncobjectfiles + core.gitProxy + core.ignoreCygwinFSTricks + core.ignoreStat + core.ignorecase + core.logAllRefUpdates + core.loosecompression + core.notesRef + core.packedGitLimit + core.packedGitWindowSize + core.pager + core.preferSymlinkRefs + core.preloadindex + core.quotepath + core.repositoryFormatVersion + core.safecrlf + core.sharedRepository + core.sparseCheckout + core.symlinks + core.trustctime + core.warnAmbiguousRefs + core.whitespace + core.worktree + diff.autorefreshindex + diff.external + diff.ignoreSubmodules + diff.mnemonicprefix + diff.noprefix diff.renameLimit diff.renames + diff.suppressBlankEmpty + diff.tool + diff.wordRegex + difftool. + difftool.prompt + fetch.recurseSubmodules fetch.unpackLimit + format.attach + format.cc format.headers + format.numbered + format.pretty + format.signature + format.signoff format.subjectprefix - gitcvs.enabled - gitcvs.logfile - gitcvs.allbinary - gitcvs.dbname gitcvs.dbdriver gitcvs.dbuser gitcvs.dbpass - gitcvs.dbtablenameprefix + format.suffix + format.thread + format.to + gc. + gc.aggressiveWindow + gc.auto + gc.autopacklimit gc.packrefs + gc.pruneexpire gc.reflogexpire gc.reflogexpireunreachable gc.rerereresolved gc.rerereunresolved - http.sslVerify - http.sslCert - http.sslKey - http.sslCAInfo - http.sslCAPath - http.maxRequests + 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.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.summary merge.verbosity - pack.window - pack.depth - pack.windowMemory + mergetool. + mergetool.keepBackup + mergetool.keepTemporaries + mergetool.prompt + notes.displayRef + notes.rewrite. + notes.rewrite.amend + notes.rewrite.rebase + notes.rewriteMode + notes.rewriteRef pack.compression - pack.deltaCacheSize pack.deltaCacheLimit + pack.deltaCacheSize + pack.depth + pack.indexVersion + pack.packSizeLimit + pack.threads + pack.window + pack.windowMemory + pager. + pretty. pull.octopus pull.twohead - repack.useDeltaBaseOffset + push.default + rebase.autosquash + rebase.stat + receive.autogc + receive.denyCurrentBranch + receive.denyDeleteCurrent + receive.denyDeletes + receive.denyNonFastForwards + receive.fsckObjects + receive.unpackLimit + receive.updateserverinfo + 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.validate showbranch.default + status.relativePaths + status.showUntrackedFiles + status.submodulesummary + submodule. tar.umask transfer.unpackLimit - receive.unpackLimit - receive.denyNonFastForwards - user.name + url. user.email + user.name user.signingkey + web.browser branch. remote. " } _git_remote () { - local subcommands="add rm show prune update" - local subcommand="$(__git_find_subcommand "$subcommands")" + local subcommands="add rename rm show prune update set-head" + local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then __gitcomp "$subcommands" return fi case "$subcommand" in - rm|show|prune) + rename|rm|show|prune) __gitcomp "$(__git_remotes)" ;; update) local i c='' IFS=$'\n' - for i in $(git --git-dir="$(__gitdir)" config --list); do - case "$i" in - remotes.*) - i="${i#remotes.}" - c="$c ${i/=*/}" - ;; - esac + for i in $(git --git-dir="$(__gitdir)" config --get-regexp "remotes\..*" 2>/dev/null); do + i="${i#remotes.}" + c="$c ${i/ */}" done __gitcomp "$c" ;; @@ -1326,14 +2380,20 @@ _git_remote () esac } +_git_replace () +{ + __gitcomp "$(__git_refs)" +} + _git_reset () { __git_has_doubledash && return - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) - __gitcomp "--mixed --hard --soft" + __gitcomp "--merge --mixed --hard --soft --patch" return ;; esac @@ -1342,7 +2402,8 @@ _git_reset () _git_revert () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--edit --mainline --no-edit --no-commit --signoff" @@ -1356,7 +2417,8 @@ _git_rm () { __git_has_doubledash && return - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--cached --dry-run --ignore-unmatch --quiet" @@ -1370,16 +2432,13 @@ _git_shortlog () { __git_has_doubledash && return - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " - --max-count= --max-age= --since= --after= - --min-age= --before= --until= - --no-merges - --author= --committer= --grep= - --all-match - --not --all + $__git_log_common_options + $__git_log_shortlog_options --numbered --summary " return @@ -1392,16 +2451,23 @@ _git_show () { __git_has_doubledash && return - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --pretty=*) - __gitcomp " - oneline short medium full fuller email raw + __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases) " "" "${cur##--pretty=}" return ;; + --format=*) + __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases) + " "" "${cur##--format=}" + return + ;; --*) - __gitcomp "--pretty=" + __gitcomp "--pretty= --format= --abbrev-commit --oneline + $__git_diff_common_options + " return ;; esac @@ -1410,13 +2476,15 @@ _git_show () _git_show_branch () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " --all --remotes --topo-order --current --more= --list --independent --merge-base --no-name - --sha1-name --topics --reflog + --color --no-color + --sha1-name --sparse --topics --reflog " return ;; @@ -1426,20 +2494,33 @@ _git_show_branch () _git_stash () { + local cur + _get_comp_words_by_ref -n =: cur + local save_opts='--keep-index --no-keep-index --quiet --patch' local subcommands='save list show apply clear drop pop create branch' - local subcommand="$(__git_find_subcommand "$subcommands")" + local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then - __gitcomp "$subcommands" + case "$cur" in + --*) + __gitcomp "$save_opts" + ;; + *) + if [ -z "$(__git_find_on_cmdline "$save_opts")" ]; then + __gitcomp "$subcommands" + else + COMPREPLY=() + fi + ;; + esac else - local cur="${COMP_WORDS[COMP_CWORD]}" case "$subcommand,$cur" in save,--*) - __gitcomp "--keep-index" + __gitcomp "$save_opts" ;; - apply,--*) - __gitcomp "--index" + apply,--*|pop,--*) + __gitcomp "--index --quiet" ;; - show,--*|drop,--*|pop,--*|branch,--*) + show,--*|drop,--*|branch,--*) COMPREPLY=() ;; show,*|apply,*|drop,*|pop,*|branch,*) @@ -1457,9 +2538,10 @@ _git_submodule () { __git_has_doubledash && return - local subcommands="add status init update" - if [ -z "$(__git_find_subcommand "$subcommands")" ]; then - local cur="${COMP_WORDS[COMP_CWORD]}" + local subcommands="add status init update summary foreach sync" + if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--quiet --cached" @@ -1477,9 +2559,10 @@ _git_svn () local subcommands=" init fetch clone rebase dcommit log find-rev set-tree commit-diff info create-ignore propget - proplist show-ignore show-externals + proplist show-ignore show-externals branch tag blame + migrate mkdirs reset gc " - local subcommand="$(__git_find_subcommand "$subcommands")" + local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then __gitcomp "$subcommands" else @@ -1488,19 +2571,22 @@ _git_svn () --follow-parent --authors-file= --repack= --no-metadata --use-svm-props --use-svnsync-props --log-window-size= --no-checkout --quiet - --repack-flags --user-log-author $remote_opts + --repack-flags --use-log-author --localtime + --ignore-paths= $remote_opts " local init_opts=" --template= --shared= --trunk= --tags= --branches= --stdlayout --minimize-url --no-metadata --use-svm-props --use-svnsync-props - --rewrite-root= $remote_opts + --rewrite-root= --prefix= --use-log-author + --add-author-from $remote_opts " local cmt_opts=" --edit --rmdir --find-copies-harder --copy-similarity= " - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$subcommand,$cur" in fetch,--*) __gitcomp "--revision= --fetch-all $fc_opts" @@ -1514,27 +2600,28 @@ _git_svn () dcommit,--*) __gitcomp " --merge --strategy= --verbose --dry-run - --fetch-all --no-rebase $cmt_opts $fc_opts + --fetch-all --no-rebase --commit-url + --revision $cmt_opts $fc_opts " ;; set-tree,--*) __gitcomp "--stdin $cmt_opts $fc_opts" ;; create-ignore,--*|propget,--*|proplist,--*|show-ignore,--*|\ - show-externals,--*) + show-externals,--*|mkdirs,--*) __gitcomp "--revision=" ;; log,--*) __gitcomp " --limit= --revision= --verbose --incremental --oneline --show-commit --non-recursive - --authors-file= + --authors-file= --color " ;; rebase,--*) __gitcomp " --merge --verbose --strategy= --local - --fetch-all $fc_opts + --fetch-all --dry-run $fc_opts " ;; commit-diff,--*) @@ -1543,6 +2630,24 @@ _git_svn () info,--*) __gitcomp "--url" ;; + branch,--*) + __gitcomp "--dry-run --message --tag" + ;; + tag,--*) + __gitcomp "--dry-run --message" + ;; + blame,--*) + __gitcomp "--git-format" + ;; + migrate,--*) + __gitcomp " + --config-dir= --ignore-paths= --minimize + --no-auth-cache --username= + " + ;; + reset,--*) + __gitcomp "--revision= --parent" + ;; *) COMPREPLY=() ;; @@ -1553,8 +2658,10 @@ _git_svn () _git_tag () { local i c=1 f=0 - while [ $c -lt $COMP_CWORD ]; do - i="${COMP_WORDS[c]}" + local words cword prev + _get_comp_words_by_ref -n =: words cword prev + while [ $c -lt $cword ]; do + i="${words[c]}" case "$i" in -d|-v) __gitcomp "$(__git_tags)" @@ -1567,7 +2674,7 @@ _git_tag () c=$((++c)) done - case "${COMP_WORDS[COMP_CWORD-1]}" in + case "$prev" in -m|-F) COMPREPLY=() ;; @@ -1584,12 +2691,24 @@ _git_tag () esac } +_git_whatchanged () +{ + _git_log +} + _git () { local i c=1 command __git_dir - while [ $c -lt $COMP_CWORD ]; do - i="${COMP_WORDS[c]}" + if [[ -n ${ZSH_VERSION-} ]]; then + emulate -L bash + setopt KSH_TYPESET + fi + + local cur words cword + _get_comp_words_by_ref -n =: cur words cword + while [ $c -lt $cword ]; do + i="${words[c]}" case "$i" in --git-dir=*) __git_dir="${i#--git-dir=}" ;; --bare) __git_dir="." ;; @@ -1601,8 +2720,7 @@ _git () done if [ -z "$command" ]; then - case "${COMP_WORDS[COMP_CWORD]}" in - --*=*) COMPREPLY=() ;; + case "$cur" in --*) __gitcomp " --paginate --no-pager @@ -1610,96 +2728,92 @@ _git () --bare --version --exec-path + --html-path --work-tree= --help " ;; - *) __gitcomp "$(__git_porcelain_commands) $(__git_aliases)" ;; + *) __git_compute_porcelain_commands + __gitcomp "$__git_porcelain_commands $(__git_aliases)" ;; esac return fi + local completion_func="_git_${command//-/_}" + declare -f $completion_func >/dev/null && $completion_func && return + local expansion=$(__git_aliased_command "$command") - [ "$expansion" ] && command="$expansion" - - case "$command" in - am) _git_am ;; - add) _git_add ;; - apply) _git_apply ;; - archive) _git_archive ;; - bisect) _git_bisect ;; - bundle) _git_bundle ;; - branch) _git_branch ;; - checkout) _git_checkout ;; - cherry) _git_cherry ;; - cherry-pick) _git_cherry_pick ;; - clean) _git_clean ;; - clone) _git_clone ;; - commit) _git_commit ;; - config) _git_config ;; - describe) _git_describe ;; - diff) _git_diff ;; - fetch) _git_fetch ;; - format-patch) _git_format_patch ;; - gc) _git_gc ;; - grep) _git_grep ;; - help) _git_help ;; - init) _git_init ;; - log) _git_log ;; - ls-files) _git_ls_files ;; - ls-remote) _git_ls_remote ;; - ls-tree) _git_ls_tree ;; - merge) _git_merge;; - mergetool) _git_mergetool;; - merge-base) _git_merge_base ;; - mv) _git_mv ;; - name-rev) _git_name_rev ;; - pull) _git_pull ;; - push) _git_push ;; - rebase) _git_rebase ;; - remote) _git_remote ;; - reset) _git_reset ;; - revert) _git_revert ;; - rm) _git_rm ;; - send-email) _git_send_email ;; - shortlog) _git_shortlog ;; - show) _git_show ;; - show-branch) _git_show_branch ;; - stash) _git_stash ;; - submodule) _git_submodule ;; - svn) _git_svn ;; - tag) _git_tag ;; - whatchanged) _git_log ;; - *) COMPREPLY=() ;; - esac + if [ -n "$expansion" ]; then + completion_func="_git_${expansion//-/_}" + declare -f $completion_func >/dev/null && $completion_func + fi } _gitk () { + if [[ -n ${ZSH_VERSION-} ]]; then + emulate -L bash + setopt KSH_TYPESET + fi + __git_has_doubledash && return - local cur="${COMP_WORDS[COMP_CWORD]}" - local g="$(git rev-parse --git-dir 2>/dev/null)" + local cur + local g="$(__gitdir)" local merge="" if [ -f "$g/MERGE_HEAD" ]; then merge="--merge" fi + _get_comp_words_by_ref -n =: cur case "$cur" in --*) - __gitcomp "--not --all $merge" + __gitcomp " + $__git_log_common_options + $__git_log_gitk_options + $merge + " return ;; esac __git_complete_revlist } -complete -o default -o nospace -F _git git -complete -o default -o nospace -F _gitk gitk +complete -o bashdefault -o default -o nospace -F _git git 2>/dev/null \ + || complete -o default -o nospace -F _git git +complete -o bashdefault -o default -o nospace -F _gitk gitk 2>/dev/null \ + || complete -o default -o nospace -F _gitk gitk # The following are necessary only for Cygwin, and only are needed # when the user has tab-completed the executable name and consequently # included the '.exe' suffix. # if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then -complete -o default -o nospace -F _git git.exe +complete -o bashdefault -o default -o nospace -F _git git.exe 2>/dev/null \ + || complete -o default -o nospace -F _git git.exe +fi + +if [[ -n ${ZSH_VERSION-} ]]; then + shopt () { + local option + if [ $# -ne 2 ]; then + echo "USAGE: $0 (-q|-s|-u) <option>" >&2 + return 1 + fi + case "$2" in + nullglob) + option="$2" + ;; + *) + echo "$0: invalid option: $2" >&2 + return 1 + esac + case "$1" in + -q) setopt | grep -q "$option" ;; + -u) unsetopt "$option" ;; + -s) setopt "$option" ;; + *) + echo "$0: invalid flag: $1" >&2 + return 1 + esac + } fi diff --git a/contrib/convert-objects/convert-objects.c b/contrib/convert-objects/convert-objects.c index 90e7900e6d..f3b57bf1d2 100644 --- a/contrib/convert-objects/convert-objects.c +++ b/contrib/convert-objects/convert-objects.c @@ -59,7 +59,7 @@ static void convert_ascii_sha1(void *buffer) struct entry *entry; if (get_sha1_hex(buffer, sha1)) - die("expected sha1, got '%s'", (char*) buffer); + die("expected sha1, got '%s'", (char *) buffer); entry = convert_entry(sha1); memcpy(buffer, sha1_to_hex(entry->new_sha1), 40); } @@ -100,7 +100,7 @@ static int write_subdirectory(void *buffer, unsigned long size, const char *base if (!slash) { newlen += sprintf(new + newlen, "%o %s", mode, path); new[newlen++] = '\0'; - hashcpy((unsigned char*)new + newlen, (unsigned char *) buffer + len - 20); + hashcpy((unsigned char *)new + newlen, (unsigned char *) buffer + len - 20); newlen += 20; used += len; @@ -271,7 +271,7 @@ static void convert_commit(void *buffer, unsigned long size, unsigned char *resu unsigned long orig_size = size; if (memcmp(buffer, "tree ", 5)) - die("Bad commit '%s'", (char*) buffer); + die("Bad commit '%s'", (char *) buffer); convert_ascii_sha1((char *) buffer + 5); buffer = (char *) buffer + 46; /* "tree " + "hex sha1" + "\n" */ while (!memcmp(buffer, "parent ", 7)) { diff --git a/contrib/emacs/Makefile b/contrib/emacs/Makefile index a48540a92b..24d9312941 100644 --- a/contrib/emacs/Makefile +++ b/contrib/emacs/Makefile @@ -2,7 +2,7 @@ EMACS = emacs -ELC = git.elc vc-git.elc git-blame.elc +ELC = git.elc git-blame.elc INSTALL ?= install INSTALL_ELC = $(INSTALL) -m 644 prefix ?= $(HOME) diff --git a/contrib/emacs/README b/contrib/emacs/README new file mode 100644 index 0000000000..82368bdbff --- /dev/null +++ b/contrib/emacs/README @@ -0,0 +1,39 @@ +This directory contains 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: + + (add-to-list 'load-path ".../git/contrib/emacs") + (require 'git) + (require 'git-blame) + + +The following modules are available: + +* 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'. + +* 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. + +* vc-git.el: + + This file used to contain the VC-mode backend for git, but it is no + longer distributed with git. It is now maintained as part of Emacs + and included in standard Emacs distributions starting from version + 22.2. + + If you have an earlier Emacs version, upgrading to Emacs 22 is + recommended, since the VC mode in older Emacs is not generic enough + to be able to support git in a reasonable manner, and no attempt has + been made to backport vc-git.el. diff --git a/contrib/emacs/git-blame.el b/contrib/emacs/git-blame.el index 4fa70c5ad4..d351cfb6e7 100644 --- a/contrib/emacs/git-blame.el +++ b/contrib/emacs/git-blame.el @@ -79,6 +79,58 @@ ;;; 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) @@ -302,72 +354,69 @@ See also function `git-blame-mode'." (src-line (string-to-number (match-string 2))) (res-line (string-to-number (match-string 3))) (num-lines (string-to-number (match-string 4)))) - (setq git-blame-current - (if (string= hash "0000000000000000000000000000000000000000") - nil - (git-blame-new-commit - hash src-line res-line num-lines)))) - (delete-region (point) (match-end 0)) - t) - ((looking-at "filename \\(.+\\)\n") - (let ((filename (match-string 1))) - (git-blame-add-info "filename" filename)) - (delete-region (point) (match-end 0)) + (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))) - (git-blame-add-info key value)) - (delete-region (point) (match-end 0)) - t) - ((looking-at "boundary\n") - (setq git-blame-current nil) - (delete-region (point) (match-end 0)) + (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 src-line res-line num-lines) +(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) (save-excursion (set-buffer git-blame-file) - (let ((info (gethash hash git-blame-cache)) - (inhibit-point-motion-hooks t) + (let ((inhibit-point-motion-hooks t) (inhibit-modification-hooks t)) - (when (not info) - ;; 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))) - (setq info (list hash src-line res-line num-lines - (git-describe-commit hash) - (cons 'color color)))) - (puthash hash info git-blame-cache)) - (goto-line res-line) - (while (> num-lines 0) - (if (get-text-property (point) 'git-blame) - (forward-line) - (let* ((start (point)) - (end (progn (forward-line 1) (point))) - (ovl (make-overlay start end))) - (push ovl git-blame-overlays) - (overlay-put ovl 'git-blame info) - (overlay-put ovl 'help-echo hash) + (goto-line 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 (nthcdr 5 info))))) - ;; the point-entered property doesn't seem to work in overlays - ;;(overlay-put ovl 'point-entered - ;; `(lambda (x y) (git-blame-identify ,hash))) - (let ((modified (buffer-modified-p))) - (put-text-property (if (= start 1) start (1- start)) (1- end) - 'point-entered - `(lambda (x y) (git-blame-identify ,hash))) - (set-buffer-modified-p modified)))) - (setq num-lines (1- num-lines)))))) - -(defun git-blame-add-info (key value) - (if git-blame-current - (nconc git-blame-current (list (cons (intern key) value))))) + (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))) diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index c1cf1cbcc0..214930a021 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -1,6 +1,6 @@ ;;; git.el --- A user interface for git -;; Copyright (C) 2005, 2006, 2007 Alexandre Julliard <julliard@winehq.org> +;; Copyright (C) 2005, 2006, 2007, 2008, 2009 Alexandre Julliard <julliard@winehq.org> ;; Version: 1.0 @@ -34,15 +34,21 @@ ;; To start: `M-x git-status' ;; ;; TODO -;; - portability to XEmacs ;; - diff against other branch ;; - renaming files from the status buffer ;; - creating tags ;; - fetch/pull -;; - switching branches ;; - revlist browser ;; - git-show-branch browser -;; - menus +;; + +;;; 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)) @@ -173,7 +179,7 @@ if there is already one that displays the same directory." (defconst git-log-msg-separator "--- log message follows this line ---") (defvar git-log-edit-font-lock-keywords - `(("^\\(Author:\\|Date:\\|Parent:\\|Signed-off-by:\\)\\(.*\\)$" + `(("^\\(Author:\\|Date:\\|Merge:\\|Signed-off-by:\\)\\(.*\\)$" (1 font-lock-keyword-face) (2 font-lock-function-name-face)) (,(concat "^\\(" (regexp-quote git-log-msg-separator) "\\)$") @@ -183,11 +189,9 @@ if there is already one that displays the same directory." "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-env (buffer env &rest args) +(defun git-call-process (buffer &rest args) "Wrapper for call-process that sets environment strings." - (let ((process-environment (append (git-get-env-strings env) - process-environment))) - (apply #'call-process "git" nil buffer nil args))) + (apply #'call-process "git" nil buffer nil args)) (defun git-call-process-display-error (&rest args) "Wrapper for call-process that displays error messages." @@ -197,17 +201,26 @@ if there is already one that displays the same directory." (let ((default-directory dir) (buffer-read-only nil)) (erase-buffer) - (eq 0 (apply 'call-process "git" nil (list buffer t) nil args)))))) + (eq 0 (apply #'git-call-process (list buffer t) args)))))) (unless ok (display-message-or-buffer buffer)) ok)) -(defun git-call-process-env-string (env &rest args) - "Wrapper for call-process that sets environment strings, -and returns the process output as a string, or nil if the git failed." +(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-env t env args)) + (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)) @@ -215,7 +228,7 @@ and returns the process output as a string, or nil if the git failed." (with-current-buffer buffer (cd dir) (apply #'call-process-region start end program - nil (list output-buffer nil) nil args)))) + 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." @@ -226,19 +239,21 @@ and returns the process output as a string, or nil if the git failed." (let ((default-directory dir) (buffer-read-only nil)) (erase-buffer) - (apply #'git-call-process-env buffer nil args))) + (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." - (unless (eq 0 (if env - (git-run-process-region - buffer start end "env" - (append (git-get-env-strings env) (list "git") args)) + (with-temp-buffer + (if (eq 0 (if env (git-run-process-region - buffer start end "git" args))) - (error "Failed to run \"git %s\":\n%s" (mapconcat (lambda (x) x) args " ") (buffer-string)))) + 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." @@ -327,7 +342,7 @@ and returns the process output as a string, or nil if the git failed." (let ((cdup (with-output-to-string (with-current-buffer standard-output (cd dir) - (unless (eq 0 (call-process "git" nil t nil "rev-parse" "--show-cdup")) + (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")))))) @@ -348,8 +363,8 @@ and returns the process output as a string, or nil if the git failed." (sort-lines nil (point-min) (point-max)) (save-buffer)) (when created - (git-call-process-env nil nil "update-index" "--add" "--" (file-relative-name ignore-name))) - (git-update-status-files (list (file-relative-name ignore-name)) 'unknown))) + (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 @@ -367,49 +382,66 @@ and returns the process output as a string, or nil if the git failed." (defun git-rev-parse (rev) "Parse a revision name and return its SHA1." (git-get-string-sha1 - (git-call-process-env-string nil "rev-parse" rev))) + (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-env-string nil "config" key))) + (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-env-string nil "symbolic-ref" ref))) + (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)))) - (push newval args) + (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." - (apply #'git-call-process-env nil - (if index-file `(("GIT_INDEX_FILE" . ,index-file)) nil) - "read-tree" (if tree (list tree)))) + (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." - (git-get-string-sha1 - (git-call-process-env-string (and index-file `(("GIT_INDEX_FILE" . ,index-file))) "write-tree"))) - -(defun git-commit-tree (buffer tree head) - "Call git-commit-tree with buffer as input and return the resulting commit SHA1." + (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 head + (when parent (setq subject "commit: ") (push "-p" args) - (push head args)) + (push parent args)) (with-current-buffer buffer (goto-char (point-min)) (if @@ -424,11 +456,11 @@ and returns the process output as a string, or nil if the git failed." (when (re-search-forward "^Date: +\\(.*\\)$" nil t) (setq author-date (match-string 1))) (goto-char (point-min)) - (while (re-search-forward "^Parent: +\\([0-9a-f]+\\)" nil t) - (unless (string-equal head (match-string 1)) - (setq subject "commit (merge): ") + (when (re-search-forward "^Merge: +\\(.*\\)" nil t) + (setq subject "commit (merge): ") + (dolist (parent (split-string (match-string 1) " +" t)) (push "-p" args) - (push (match-string 1) args)))) + (push parent args)))) (setq log-start (point-min))) (setq log-end (point-max)) (goto-char log-start) @@ -437,22 +469,20 @@ and returns the process output as a string, or nil if the git failed." (setq coding-system-for-write buffer-file-coding-system)) (let ((commit (git-get-string-sha1 - (with-output-to-string - (with-current-buffer standard-output - (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)))))))) - (and (git-update-ref "HEAD" commit head subject) - commit)))) + (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 (call-process "git" nil nil nil "rev-parse" "--verify" "HEAD")))) + (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." @@ -468,7 +498,7 @@ and returns the process output as a string, or nil if the git failed." (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-env-string nil "log" "--max-count=1" "--pretty=oneline" commit))) + (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)))) @@ -487,14 +517,11 @@ and returns the process output as a string, or nil if the git failed." 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-clear-status (status) - "Remove everything from the status list." - (ewoc-filter status (lambda (info) nil))) - (defun git-set-fileinfo-state (info state) "Set the state of a file info." (unless (eq (git-fileinfo->state info) state) @@ -502,24 +529,26 @@ and returns the process output as a string, or nil if the git failed." (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." + "Apply FUNC to the status files names in the FILES list. +The list must be sorted." (when files - (setq files (sort files #'string-lessp)) (let ((file (pop files)) (node (ewoc-nth status 0))) (while (and file node) - (let ((info (ewoc-data node))) - (if (string-lessp (git-fileinfo->name info) file) + (let* ((info (ewoc-data node)) + (name (git-fileinfo->name info))) + (if (string-lessp name file) (setq node (ewoc-next status node)) - (if (string-equal (git-fileinfo->name info) file) + (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." + "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 @@ -553,29 +582,29 @@ and returns the process output as a string, or nil if the git failed." (let* ((old-type (lsh (or old-perm 0) -9)) (new-type (lsh (or new-perm 0) -9)) (str (case new-type - (?\100 ;; file + (64 ;; file (case old-type - (?\100 nil) - (?\120 " (type change symlink -> file)") - (?\160 " (type change subproject -> file)"))) - (?\120 ;; symlink + (64 nil) + (80 " (type change symlink -> file)") + (112 " (type change subproject -> file)"))) + (80 ;; symlink (case old-type - (?\100 " (type change file -> symlink)") - (?\160 " (type change subproject -> symlink)") + (64 " (type change file -> symlink)") + (112 " (type change subproject -> symlink)") (t " (symlink)"))) - (?\160 ;; subproject + (112 ;; subproject (case old-type - (?\100 " (type change file -> subproject)") - (?\120 " (type change symlink -> subproject)") + (64 " (type change file -> subproject)") + (80 " (type change symlink -> subproject)") (t " (subproject)"))) - (?\110 nil) ;; directory (internal, not a real git state) - (?\000 ;; deleted or unknown + (72 nil) ;; directory (internal, not a real git state) + (0 ;; deleted or unknown (case old-type - (?\120 " (symlink)") - (?\160 " (subproject)"))) + (80 " (symlink)") + (112 " (subproject)"))) (t (format " (unknown type %o)" new-type))))) (cond (str (propertize str 'face 'git-status-face)) - ((eq new-type ?\110) "/") + ((eq new-type 72) "/") (t "")))) (defun git-rename-as-string (info) @@ -612,39 +641,52 @@ and returns the process output as a string, or nil if the git failed." (git-file-type-as-string old-perm new-perm) (git-rename-as-string info))))) -(defun git-insert-info-list (status infolist) - "Insert a list of file infos in the status buffer, replacing existing ones if any." - (setq infolist (sort infolist - (lambda (info1 info2) - (string-lessp (git-fileinfo->name info1) - (git-fileinfo->name info2))))) - (let ((info (pop infolist)) - (node (ewoc-nth status 0))) +(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 - (cond ((not node) - (setq node (ewoc-enter-last status info)) - (setq info (pop infolist))) - ((string-lessp (git-fileinfo->name (ewoc-data node)) - (git-fileinfo->name info)) - (setq node (ewoc-next status node))) - ((string-equal (git-fileinfo->name (ewoc-data node)) - (git-fileinfo->name info)) - ;; preserve the marked flag - (setf (git-fileinfo->marked info) (git-fileinfo->marked (ewoc-data node))) - (setf (git-fileinfo->needs-refresh info) t) - (setf (ewoc-data node) info) - (setq info (pop infolist))) - (t - (setq node (ewoc-enter-before status node info)) - (setq info (pop infolist))))))) + (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 ((remaining (copy-sequence files)) - infolist) + (let (infolist) (with-temp-buffer - (apply #'git-call-process-env t nil "diff-index" "-z" "-M" "HEAD" "--" files) + (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" @@ -659,11 +701,12 @@ Return the list of files that haven't been handled." (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 remaining (delete name remaining)) - (when new-name (setq remaining (delete new-name remaining)))))) - (git-insert-info-list status infolist) - remaining)) + (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." @@ -677,42 +720,40 @@ Return the list of files that haven't been handled." Return the list of files that haven't been handled." (let (infolist) (with-temp-buffer - (apply #'git-call-process-env t nil "ls-files" "-z" (append options (list "--") files)) + (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 files (delete name files))))) - (git-insert-info-list status infolist) - files)) + 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 ((remaining (copy-sequence files)) - infolist) + (let (infolist) (with-temp-buffer - (apply #'git-call-process-env t nil "ls-files" "-z" "-s" "-c" "--" files) + (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 remaining (delete name remaining))))) - (git-insert-info-list status infolist) - remaining)) + (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-env t nil "ls-files" "-z" "-u" "--" files) + (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 () @@ -732,12 +773,19 @@ Return the list of files that haven't been handled." (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 (files &optional default-state) - "Update the status of FILES from the index." +(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.")) - (when (or git-show-uptodate files) - (git-run-ls-files-cached git-status files 'uptodate)) - (let* ((remaining-files + ;; 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)))) @@ -746,13 +794,17 @@ Return the list of files that haven't been handled." (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"))) - (git-set-filenames-state git-status remaining-files default-state) + (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." - (setq files (sort files #'string-lessp)) (let ((file (and files (pop files))) (node (ewoc-nth status 0))) (while node @@ -778,13 +830,13 @@ Return the list of files that haven't been handled." (list (ewoc-data (ewoc-locate git-status))))) (defun git-marked-files-state (&rest states) - "Return marked files that are in the specified 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))) - result)) + (nreverse result))) (defun git-refresh-files () "Refresh all files that need it and clear the needs-refresh flag." @@ -824,19 +876,18 @@ Return the list of files that haven't been handled." (defun git-update-index (index-file files) "Run git-update-index on a list of files." - (let ((env (and index-file `(("GIT_INDEX_FILE" . ,index-file)))) + (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)))) - (when added - (apply #'git-call-process-env nil env "update-index" "--add" "--" (git-get-filenames added))) - (when deleted - (apply #'git-call-process-env nil env "update-index" "--remove" "--" (git-get-filenames deleted))) - (when modified - (apply #'git-call-process-env nil env "update-index" "--" (git-get-filenames 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." @@ -862,33 +913,30 @@ Return the list of files that haven't been handled." (message "You cannot commit unmerged files, resolve them first.") (unwind-protect (let ((files (git-marked-files-state 'added 'deleted 'modified)) - head head-tree) + head tree head-tree) (unless (git-empty-db-p) (setq head (git-rev-parse "HEAD") head-tree (git-rev-parse "HEAD^{tree}"))) - (if files - (progn - (message "Running git commit...") - (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 - (let ((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) 'uptodate) - (git-call-process-env nil nil "rerere") - (git-call-process-env nil nil "gc" "--auto") - (git-refresh-files) - (git-refresh-ewoc-hf git-status) - (message "Committed %s." commit) - (git-run-hook "post-commit" nil))) - (message "Commit aborted.")))) - (message "No files to commit."))) + (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)))))) @@ -990,15 +1038,20 @@ Return the list of files that haven't been handled." (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)))) + (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 'uptodate) + (git-update-status-files files) (git-success-message "Added" files)))) (defun git-ignore-file () @@ -1008,7 +1061,7 @@ Return the list of files that haven't been handled." (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 'ignored) + (git-update-status-files files) (git-success-message "Ignored" files))) (defun git-remove-file () @@ -1018,7 +1071,9 @@ Return the list of files that haven't been handled." (unless files (push (file-relative-name (read-file-name "File to remove: " nil nil t)) files)) (if (yes-or-no-p - (format "Remove %d file%s? " (length files) (if (> (length files) 1) "s" ""))) + (if (cdr files) + (format "Remove %d files? " (length files)) + (format "Remove %s? " (car files)))) (progn (dolist (name files) (ignore-errors @@ -1026,7 +1081,7 @@ Return the list of files that haven't been handled." (delete-directory name) (delete-file name)))) (when (apply 'git-call-process-display-error "update-index" "--remove" "--" files) - (git-update-status-files files nil) + (git-update-status-files files) (git-success-message "Removed" files))) (message "Aborting")))) @@ -1037,7 +1092,9 @@ Return the list of files that haven't been handled." added modified) (when (and files (yes-or-no-p - (format "Revert %d file%s? " (length files) (if (> (length files) 1) "s" "")))) + (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)) @@ -1053,22 +1110,14 @@ Return the list of files that haven't been handled." (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))))) - (git-update-status-files (append added modified) 'uptodate) + (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" (git-get-filenames files))))))) - -(defun git-resolve-file () - "Resolve conflicts in marked file(s)." - (interactive) - (let ((files (git-get-filenames (git-marked-files-state 'unmerged)))) - (when files - (when (apply 'git-call-process-display-error "update-index" "--" files) - (git-update-status-files files 'uptodate) - (git-success-message "Resolved" files))))) + (git-success-message "Reverted" names)))))) (defun git-remove-handled () "Remove handled files from the status list." @@ -1225,11 +1274,10 @@ Return the list of files that haven't been handled." (goto-char (point-max)) (insert sign-off "\n")))) -(defun git-setup-log-buffer (buffer &optional author-name author-email subject date msg) +(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 ((merge-heads (git-get-merge-heads)) - (dir default-directory) + (let ((dir default-directory) (committer-name (git-get-committer-name)) (committer-email (git-get-committer-email)) (sign-off git-append-signed-off-by)) @@ -1243,9 +1291,8 @@ Return the list of files that haven't been handled." (or author-email committer-email) (if date (format "Date: %s\n" date) "") (if merge-heads - (format "Parent: %s\n%s\n" - (git-rev-parse "HEAD") - (mapconcat (lambda (str) (concat "Parent: " str)) merge-heads "\n")) + (format "Merge: %s\n" + (mapconcat 'identity merge-heads " ")) "")) 'face 'git-header-face) (propertize git-log-msg-separator 'face 'git-separator-face) @@ -1285,22 +1332,25 @@ Return the list of files that haven't been handled." (goto-char (point-min)) (when (re-search-forward "^Date: \\(.*\\)$" nil t) (setq date (match-string 1))))) - (git-setup-log-buffer buffer author-name author-email subject date)) + (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) (log-edit 'git-do-commit nil 'git-log-edit-files buffer)) (setq font-lock-keywords (font-lock-compile-keywords git-log-edit-font-lock-keywords)) + (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 (author-name author-email subject date msg) + (let (parents author-name author-email subject date msg) (with-temp-buffer (let ((coding-system (git-get-logoutput-coding-system))) - (git-call-process-env t nil "log" "-1" "--pretty=medium" commit) + (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))) @@ -1312,18 +1362,48 @@ Return the list of files that haven't been handled." (setq subject (pop msg)) (while (and msg (zerop (length (car msg))) (pop msg))))) (git-setup-log-buffer (get-buffer-create "*git-commit*") - author-name author-email subject date + parents author-name author-email subject date (mapconcat #'identity msg "\n")))) (defun git-get-commit-files (commit) - "Retrieve the list of files modified by COMMIT." + "Retrieve a sorted list of files modified by COMMIT." (let (files) (with-temp-buffer - (git-call-process-env t nil "diff-tree" "-r" "-z" "--name-only" "--no-commit-id" commit) + (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))) - 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 @@ -1333,13 +1413,52 @@ amended version of it." (when (git-empty-db-p) (error "No commit to amend.")) (let* ((commit (git-rev-parse "HEAD")) (files (git-get-commit-files commit))) - (when (git-call-process-display-error "reset" "--soft" "HEAD^") - (git-update-status-files (copy-sequence files) 'uptodate) - (git-mark-files git-status files) - (git-refresh-files) + (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) @@ -1377,27 +1496,10 @@ amended version of it." (defun git-refresh-status () "Refresh the git status buffer." (interactive) - (let* ((status git-status) - (pos (ewoc-locate status)) - (marked-files (git-get-filenames (ewoc-collect status (lambda (info) (git-fileinfo->marked info))))) - (cur-name (and pos (git-fileinfo->name (ewoc-data pos))))) - (unless status (error "Not in git-status buffer.")) - (message "Refreshing git status...") - (git-call-process-env nil nil "update-index" "--refresh") - (git-clear-status status) - (git-update-status-files nil) - ; restore file marks - (when marked-files - (git-status-filenames-map status - (lambda (info) - (setf (git-fileinfo->marked info) t) - (setf (git-fileinfo->needs-refresh info) t)) - marked-files) - (git-refresh-files)) - ; move point to the current file name if any - (message "Refreshing git status...done") - (let ((node (and cur-name (git-find-status-file status cur-name)))) - (when node (ewoc-goto-node status node))))) + (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." @@ -1434,6 +1536,7 @@ amended version of it." (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) @@ -1444,7 +1547,6 @@ amended version of it." (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 "R" 'git-resolve-file) (define-key map "t" toggle-map) (define-key map "T" 'git-toggle-all-marks) (define-key map "u" 'git-unmark-file) @@ -1455,6 +1557,10 @@ amended version of it." (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) @@ -1475,10 +1581,13 @@ amended version of it." `("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] - ["Mark as Resolved" git-resolve-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] @@ -1490,6 +1599,7 @@ amended version of it." ["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] @@ -1576,8 +1686,8 @@ Meant to be used in `after-save-hook'." (let ((filename (file-relative-name file dir))) ; skip files located inside the .git directory (unless (string-match "^\\.git/" filename) - (git-call-process-env nil nil "add" "--refresh" "--" filename) - (git-update-status-files (list filename) 'uptodate))))))) + (git-call-process nil "add" "--refresh" "--" filename) + (git-update-status-files (list filename)))))))) (defun git-help () "Display help for Git mode." diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el deleted file mode 100644 index b8f6be5c0a..0000000000 --- a/contrib/emacs/vc-git.el +++ /dev/null @@ -1,216 +0,0 @@ -;;; vc-git.el --- VC backend for the git version control system - -;; Copyright (C) 2006 Alexandre Julliard - -;; 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, write to the Free -;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, -;; MA 02111-1307 USA - -;;; Commentary: - -;; This file contains a VC backend for the git version control -;; system. -;; -;; To install: put this file on the load-path and add GIT to the list -;; of supported backends in `vc-handled-backends'; the following line, -;; placed in your ~/.emacs, will accomplish this: -;; -;; (add-to-list 'vc-handled-backends 'GIT) -;; -;; TODO -;; - changelog generation -;; - working with revisions other than HEAD -;; - -(eval-when-compile (require 'cl)) - -(defvar git-commits-coding-system 'utf-8 - "Default coding system for git commits.") - -(defun vc-git--run-command-string (file &rest args) - "Run a git command on FILE and return its output as string." - (let* ((ok t) - (str (with-output-to-string - (with-current-buffer standard-output - (unless (eq 0 (apply #'call-process "git" nil '(t nil) nil - (append args (list (file-relative-name file))))) - (setq ok nil)))))) - (and ok str))) - -(defun vc-git--run-command (file &rest args) - "Run a git command on FILE, discarding any output." - (let ((name (file-relative-name file))) - (eq 0 (apply #'call-process "git" nil (get-buffer "*Messages") nil (append args (list name)))))) - -(defun vc-git-registered (file) - "Check whether FILE is registered with git." - (with-temp-buffer - (let* ((dir (file-name-directory file)) - (name (file-relative-name file dir))) - (and (ignore-errors - (when dir (cd dir)) - (eq 0 (call-process "git" nil '(t nil) nil "ls-files" "-c" "-z" "--" name))) - (let ((str (buffer-string))) - (and (> (length str) (length name)) - (string= (substring str 0 (1+ (length name))) (concat name "\0")))))))) - -(defun vc-git-state (file) - "git-specific version of `vc-state'." - (let ((diff (vc-git--run-command-string file "diff-index" "-z" "HEAD" "--"))) - (if (and diff (string-match ":[0-7]\\{6\\} [0-7]\\{6\\} [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} [ADMU]\0[^\0]+\0" diff)) - 'edited - 'up-to-date))) - -(defun vc-git-workfile-version (file) - "git-specific version of `vc-workfile-version'." - (let ((str (with-output-to-string - (with-current-buffer standard-output - (call-process "git" nil '(t nil) nil "symbolic-ref" "HEAD"))))) - (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str) - (match-string 2 str) - str))) - -(defun vc-git-symbolic-commit (commit) - "Translate COMMIT string into symbolic form. -Returns nil if not possible." - (and commit - (with-temp-buffer - (and - (zerop - (call-process "git" nil '(t nil) nil "name-rev" - "--name-only" "--tags" - commit)) - (goto-char (point-min)) - (= (forward-line 2) 1) - (bolp) - (buffer-substring-no-properties (point-min) (1- (point-max))))))) - -(defun vc-git-previous-version (file rev) - "git-specific version of `vc-previous-version'." - (let ((default-directory (file-name-directory (expand-file-name file))) - (file (file-name-nondirectory file))) - (vc-git-symbolic-commit - (with-temp-buffer - (and - (zerop - (call-process "git" nil '(t nil) nil "rev-list" - "-2" rev "--" file)) - (goto-char (point-max)) - (bolp) - (zerop (forward-line -1)) - (not (bobp)) - (buffer-substring-no-properties - (point) - (1- (point-max)))))))) - -(defun vc-git-next-version (file rev) - "git-specific version of `vc-next-version'." - (let* ((default-directory (file-name-directory - (expand-file-name file))) - (file (file-name-nondirectory file)) - (current-rev - (with-temp-buffer - (and - (zerop - (call-process "git" nil '(t nil) nil "rev-list" - "-1" rev "--" file)) - (goto-char (point-max)) - (bolp) - (zerop (forward-line -1)) - (bobp) - (buffer-substring-no-properties - (point) - (1- (point-max))))))) - (and current-rev - (vc-git-symbolic-commit - (with-temp-buffer - (and - (zerop - (call-process "git" nil '(t nil) nil "rev-list" - "HEAD" "--" file)) - (goto-char (point-min)) - (search-forward current-rev nil t) - (zerop (forward-line -1)) - (buffer-substring-no-properties - (point) - (progn (forward-line 1) (1- (point)))))))))) - -(defun vc-git-revert (file &optional contents-done) - "Revert FILE to the version stored in the git repository." - (if contents-done - (vc-git--run-command file "update-index" "--") - (vc-git--run-command file "checkout" "HEAD"))) - -(defun vc-git-checkout-model (file) - 'implicit) - -(defun vc-git-workfile-unchanged-p (file) - (let ((sha1 (vc-git--run-command-string file "hash-object" "--")) - (head (vc-git--run-command-string file "ls-tree" "-z" "HEAD" "--"))) - (and head - (string-match "[0-7]\\{6\\} blob \\([0-9a-f]\\{40\\}\\)\t[^\0]+\0" head) - (string= (car (split-string sha1 "\n")) (match-string 1 head))))) - -(defun vc-git-register (file &optional rev comment) - "Register FILE into the git version-control system." - (vc-git--run-command file "update-index" "--add" "--")) - -(defun vc-git-print-log (file &optional buffer) - (let ((name (file-relative-name file)) - (coding-system-for-read git-commits-coding-system)) - (vc-do-command buffer 'async "git" name "rev-list" "--pretty" "HEAD" "--"))) - -(defun vc-git-diff (file &optional rev1 rev2 buffer) - (let ((name (file-relative-name file)) - (buf (or buffer "*vc-diff*"))) - (if (and rev1 rev2) - (vc-do-command buf 0 "git" name "diff-tree" "-p" rev1 rev2 "--") - (vc-do-command buf 0 "git" name "diff-index" "-p" (or rev1 "HEAD") "--")) - ; git-diff-index doesn't set exit status like diff does - (if (vc-git-workfile-unchanged-p file) 0 1))) - -(defun vc-git-checkin (file rev comment) - (let ((coding-system-for-write git-commits-coding-system)) - (vc-git--run-command file "commit" "-m" comment "--only" "--"))) - -(defun vc-git-checkout (file &optional editable rev destfile) - (if destfile - (let ((fullname (substring - (vc-git--run-command-string file "ls-files" "-z" "--full-name" "--") - 0 -1)) - (coding-system-for-read 'no-conversion) - (coding-system-for-write 'no-conversion)) - (with-temp-file destfile - (eq 0 (call-process "git" nil t nil "cat-file" "blob" - (concat (or rev "HEAD") ":" fullname))))) - (vc-git--run-command file "checkout" (or rev "HEAD")))) - -(defun vc-git-annotate-command (file buf &optional rev) - ; FIXME: rev is ignored - (let ((name (file-relative-name file))) - (call-process "git" nil buf nil "blame" name))) - -(defun vc-git-annotate-time () - (and (re-search-forward "[0-9a-f]+ (.* \\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\) \\([-+0-9]+\\) +[0-9]+)" nil t) - (vc-annotate-convert-time - (apply #'encode-time (mapcar (lambda (match) (string-to-number (match-string match))) '(6 5 4 3 2 1 7)))))) - -;; Not really useful since we can't do anything with the revision yet -;;(defun vc-annotate-extract-revision-at-line () -;; (save-excursion -;; (move-beginning-of-line 1) -;; (and (looking-at "[0-9a-f]+") -;; (buffer-substring (match-beginning 0) (match-end 0))))) - -(provide 'vc-git) diff --git a/contrib/examples/builtin-fetch--tool.c b/contrib/examples/builtin-fetch--tool.c new file mode 100644 index 0000000000..3140e405fa --- /dev/null +++ b/contrib/examples/builtin-fetch--tool.c @@ -0,0 +1,574 @@ +#include "builtin.h" +#include "cache.h" +#include "refs.h" +#include "commit.h" +#include "sigchain.h" + +static char *get_stdin(void) +{ + struct strbuf buf = STRBUF_INIT; + if (strbuf_read(&buf, 0, 1024) < 0) { + die_errno("error reading standard input"); + } + return strbuf_detach(&buf, NULL); +} + +static void show_new(enum object_type type, unsigned char *sha1_new) +{ + fprintf(stderr, " %s: %s\n", typename(type), + find_unique_abbrev(sha1_new, DEFAULT_ABBREV)); +} + +static int update_ref_env(const char *action, + const char *refname, + unsigned char *sha1, + unsigned char *oldval) +{ + char msg[1024]; + const char *rla = getenv("GIT_REFLOG_ACTION"); + + if (!rla) + rla = "(reflog update)"; + if (snprintf(msg, sizeof(msg), "%s: %s", rla, action) >= sizeof(msg)) + warning("reflog message too long: %.*s...", 50, msg); + return update_ref(msg, refname, sha1, oldval, 0, QUIET_ON_ERR); +} + +static int update_local_ref(const char *name, + const char *new_head, + const char *note, + int verbose, int force) +{ + unsigned char sha1_old[20], sha1_new[20]; + char oldh[41], newh[41]; + struct commit *current, *updated; + enum object_type type; + + if (get_sha1_hex(new_head, sha1_new)) + die("malformed object name %s", new_head); + + type = sha1_object_info(sha1_new, NULL); + if (type < 0) + die("object %s not found", new_head); + + if (!*name) { + /* Not storing */ + if (verbose) { + fprintf(stderr, "* fetched %s\n", note); + show_new(type, sha1_new); + } + return 0; + } + + if (get_sha1(name, sha1_old)) { + const char *msg; + just_store: + /* new ref */ + if (!strncmp(name, "refs/tags/", 10)) + msg = "storing tag"; + else + msg = "storing head"; + fprintf(stderr, "* %s: storing %s\n", + name, note); + show_new(type, sha1_new); + return update_ref_env(msg, name, sha1_new, NULL); + } + + if (!hashcmp(sha1_old, sha1_new)) { + if (verbose) { + fprintf(stderr, "* %s: same as %s\n", name, note); + show_new(type, sha1_new); + } + return 0; + } + + if (!strncmp(name, "refs/tags/", 10)) { + fprintf(stderr, "* %s: updating with %s\n", name, note); + show_new(type, sha1_new); + return update_ref_env("updating tag", name, sha1_new, NULL); + } + + current = lookup_commit_reference(sha1_old); + updated = lookup_commit_reference(sha1_new); + if (!current || !updated) + goto just_store; + + strcpy(oldh, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); + strcpy(newh, find_unique_abbrev(sha1_new, DEFAULT_ABBREV)); + + if (in_merge_bases(current, &updated, 1)) { + fprintf(stderr, "* %s: fast-forward to %s\n", + name, note); + fprintf(stderr, " old..new: %s..%s\n", oldh, newh); + return update_ref_env("fast-forward", name, sha1_new, sha1_old); + } + if (!force) { + fprintf(stderr, + "* %s: not updating to non-fast-forward %s\n", + name, note); + fprintf(stderr, + " old...new: %s...%s\n", oldh, newh); + return 1; + } + fprintf(stderr, + "* %s: forcing update to non-fast-forward %s\n", + name, note); + fprintf(stderr, " old...new: %s...%s\n", oldh, newh); + return update_ref_env("forced-update", name, sha1_new, sha1_old); +} + +static int append_fetch_head(FILE *fp, + const char *head, const char *remote, + const char *remote_name, const char *remote_nick, + const char *local_name, int not_for_merge, + int verbose, int force) +{ + struct commit *commit; + int remote_len, i, note_len; + unsigned char sha1[20]; + char note[1024]; + const char *what, *kind; + + if (get_sha1(head, sha1)) + return error("Not a valid object name: %s", head); + commit = lookup_commit_reference_gently(sha1, 1); + if (!commit) + not_for_merge = 1; + + if (!strcmp(remote_name, "HEAD")) { + kind = ""; + what = ""; + } + else if (!strncmp(remote_name, "refs/heads/", 11)) { + kind = "branch"; + what = remote_name + 11; + } + else if (!strncmp(remote_name, "refs/tags/", 10)) { + kind = "tag"; + what = remote_name + 10; + } + else if (!strncmp(remote_name, "refs/remotes/", 13)) { + kind = "remote-tracking branch"; + what = remote_name + 13; + } + else { + kind = ""; + what = remote_name; + } + + remote_len = strlen(remote); + for (i = remote_len - 1; remote[i] == '/' && 0 <= i; i--) + ; + remote_len = i + 1; + if (4 < i && !strncmp(".git", remote + i - 3, 4)) + remote_len = i - 3; + + note_len = 0; + if (*what) { + if (*kind) + note_len += sprintf(note + note_len, "%s ", kind); + note_len += sprintf(note + note_len, "'%s' of ", what); + } + note_len += sprintf(note + note_len, "%.*s", remote_len, remote); + fprintf(fp, "%s\t%s\t%s\n", + sha1_to_hex(commit ? commit->object.sha1 : sha1), + not_for_merge ? "not-for-merge" : "", + note); + return update_local_ref(local_name, head, note, verbose, force); +} + +static char *keep; +static void remove_keep(void) +{ + if (keep && *keep) + unlink(keep); +} + +static void remove_keep_on_signal(int signo) +{ + remove_keep(); + sigchain_pop(signo); + raise(signo); +} + +static char *find_local_name(const char *remote_name, const char *refs, + int *force_p, int *not_for_merge_p) +{ + const char *ref = refs; + int len = strlen(remote_name); + + while (ref) { + const char *next; + int single_force, not_for_merge; + + while (*ref == '\n') + ref++; + if (!*ref) + break; + next = strchr(ref, '\n'); + + single_force = not_for_merge = 0; + if (*ref == '+') { + single_force = 1; + ref++; + } + if (*ref == '.') { + not_for_merge = 1; + ref++; + if (*ref == '+') { + single_force = 1; + ref++; + } + } + if (!strncmp(remote_name, ref, len) && ref[len] == ':') { + const char *local_part = ref + len + 1; + int retlen; + + if (!next) + retlen = strlen(local_part); + else + retlen = next - local_part; + *force_p = single_force; + *not_for_merge_p = not_for_merge; + return xmemdupz(local_part, retlen); + } + ref = next; + } + return NULL; +} + +static int fetch_native_store(FILE *fp, + const char *remote, + const char *remote_nick, + const char *refs, + int verbose, int force) +{ + char buffer[1024]; + int err = 0; + + sigchain_push_common(remove_keep_on_signal); + atexit(remove_keep); + + while (fgets(buffer, sizeof(buffer), stdin)) { + int len; + char *cp; + char *local_name; + int single_force, not_for_merge; + + for (cp = buffer; *cp && !isspace(*cp); cp++) + ; + if (*cp) + *cp++ = 0; + len = strlen(cp); + if (len && cp[len-1] == '\n') + cp[--len] = 0; + if (!strcmp(buffer, "failed")) + die("Fetch failure: %s", remote); + if (!strcmp(buffer, "pack")) + continue; + if (!strcmp(buffer, "keep")) { + char *od = get_object_directory(); + int len = strlen(od) + strlen(cp) + 50; + keep = xmalloc(len); + sprintf(keep, "%s/pack/pack-%s.keep", od, cp); + continue; + } + + local_name = find_local_name(cp, refs, + &single_force, ¬_for_merge); + if (!local_name) + continue; + err |= append_fetch_head(fp, + buffer, remote, cp, remote_nick, + local_name, not_for_merge, + verbose, force || single_force); + } + return err; +} + +static int parse_reflist(const char *reflist) +{ + const char *ref; + + printf("refs='"); + for (ref = reflist; ref; ) { + const char *next; + while (*ref && isspace(*ref)) + ref++; + if (!*ref) + break; + for (next = ref; *next && !isspace(*next); next++) + ; + printf("\n%.*s", (int)(next - ref), ref); + ref = next; + } + printf("'\n"); + + printf("rref='"); + for (ref = reflist; ref; ) { + const char *next, *colon; + while (*ref && isspace(*ref)) + ref++; + if (!*ref) + break; + for (next = ref; *next && !isspace(*next); next++) + ; + if (*ref == '.') + ref++; + if (*ref == '+') + ref++; + colon = strchr(ref, ':'); + putchar('\n'); + printf("%.*s", (int)((colon ? colon : next) - ref), ref); + ref = next; + } + printf("'\n"); + return 0; +} + +static int expand_refs_wildcard(const char *ls_remote_result, int numrefs, + const char **refs) +{ + int i, matchlen, replacelen; + int found_one = 0; + const char *remote = *refs++; + numrefs--; + + if (numrefs == 0) { + fprintf(stderr, "Nothing specified for fetching with remote.%s.fetch\n", + remote); + printf("empty\n"); + } + + for (i = 0; i < numrefs; i++) { + const char *ref = refs[i]; + const char *lref = ref; + const char *colon; + const char *tail; + const char *ls; + const char *next; + + if (*lref == '+') + lref++; + colon = strchr(lref, ':'); + tail = lref + strlen(lref); + if (!(colon && + 2 < colon - lref && + colon[-1] == '*' && + colon[-2] == '/' && + 2 < tail - (colon + 1) && + tail[-1] == '*' && + tail[-2] == '/')) { + /* not a glob */ + if (!found_one++) + printf("explicit\n"); + printf("%s\n", ref); + continue; + } + + /* glob */ + if (!found_one++) + printf("glob\n"); + + /* lref to colon-2 is remote hierarchy name; + * colon+1 to tail-2 is local. + */ + matchlen = (colon-1) - lref; + replacelen = (tail-1) - (colon+1); + for (ls = ls_remote_result; ls; ls = next) { + const char *eol; + unsigned char sha1[20]; + int namelen; + + while (*ls && isspace(*ls)) + ls++; + next = strchr(ls, '\n'); + eol = !next ? (ls + strlen(ls)) : next; + if (!memcmp("^{}", eol-3, 3)) + continue; + if (eol - ls < 40) + continue; + if (get_sha1_hex(ls, sha1)) + continue; + ls += 40; + while (ls < eol && isspace(*ls)) + ls++; + /* ls to next (or eol) is the name. + * is it identical to lref to colon-2? + */ + if ((eol - ls) <= matchlen || + strncmp(ls, lref, matchlen)) + continue; + + /* Yes, it is a match */ + namelen = eol - ls; + if (lref != ref) + putchar('+'); + printf("%.*s:%.*s%.*s\n", + namelen, ls, + replacelen, colon + 1, + namelen - matchlen, ls + matchlen); + } + } + return 0; +} + +static int pick_rref(int sha1_only, const char *rref, const char *ls_remote_result) +{ + int err = 0; + int lrr_count = lrr_count, i, pass; + const char *cp; + struct lrr { + const char *line; + const char *name; + int namelen; + int shown; + } *lrr_list = lrr_list; + + for (pass = 0; pass < 2; pass++) { + /* pass 0 counts and allocates, pass 1 fills... */ + cp = ls_remote_result; + i = 0; + while (1) { + const char *np; + while (*cp && isspace(*cp)) + cp++; + if (!*cp) + break; + np = strchrnul(cp, '\n'); + if (pass) { + lrr_list[i].line = cp; + lrr_list[i].name = cp + 41; + lrr_list[i].namelen = np - (cp + 41); + } + i++; + cp = np; + } + if (!pass) { + lrr_count = i; + lrr_list = xcalloc(lrr_count, sizeof(*lrr_list)); + } + } + + while (1) { + const char *next; + int rreflen; + int i; + + while (*rref && isspace(*rref)) + rref++; + if (!*rref) + break; + next = strchrnul(rref, '\n'); + rreflen = next - rref; + + for (i = 0; i < lrr_count; i++) { + struct lrr *lrr = &(lrr_list[i]); + + if (rreflen == lrr->namelen && + !memcmp(lrr->name, rref, rreflen)) { + if (!lrr->shown) + printf("%.*s\n", + sha1_only ? 40 : lrr->namelen + 41, + lrr->line); + lrr->shown = 1; + break; + } + } + if (lrr_count <= i) { + error("pick-rref: %.*s not found", rreflen, rref); + err = 1; + } + rref = next; + } + free(lrr_list); + return err; +} + +int cmd_fetch__tool(int argc, const char **argv, const char *prefix) +{ + int verbose = 0; + int force = 0; + int sopt = 0; + + while (1 < argc) { + const char *arg = argv[1]; + if (!strcmp("-v", arg)) + verbose = 1; + else if (!strcmp("-f", arg)) + force = 1; + else if (!strcmp("-s", arg)) + sopt = 1; + else + break; + argc--; + argv++; + } + + if (argc <= 1) + return error("Missing subcommand"); + + if (!strcmp("append-fetch-head", argv[1])) { + int result; + FILE *fp; + char *filename; + + if (argc != 8) + return error("append-fetch-head takes 6 args"); + filename = git_path("FETCH_HEAD"); + fp = fopen(filename, "a"); + if (!fp) + return error("cannot open %s: %s\n", filename, strerror(errno)); + result = append_fetch_head(fp, argv[2], argv[3], + argv[4], argv[5], + argv[6], !!argv[7][0], + verbose, force); + fclose(fp); + return result; + } + if (!strcmp("native-store", argv[1])) { + int result; + FILE *fp; + char *filename; + + if (argc != 5) + return error("fetch-native-store takes 3 args"); + filename = git_path("FETCH_HEAD"); + fp = fopen(filename, "a"); + if (!fp) + return error("cannot open %s: %s\n", filename, strerror(errno)); + result = fetch_native_store(fp, argv[2], argv[3], argv[4], + verbose, force); + fclose(fp); + return result; + } + if (!strcmp("parse-reflist", argv[1])) { + const char *reflist; + if (argc != 3) + return error("parse-reflist takes 1 arg"); + reflist = argv[2]; + if (!strcmp(reflist, "-")) + reflist = get_stdin(); + return parse_reflist(reflist); + } + if (!strcmp("pick-rref", argv[1])) { + const char *ls_remote_result; + if (argc != 4) + return error("pick-rref takes 2 args"); + ls_remote_result = argv[3]; + if (!strcmp(ls_remote_result, "-")) + ls_remote_result = get_stdin(); + return pick_rref(sopt, argv[2], ls_remote_result); + } + if (!strcmp("expand-refs-wildcard", argv[1])) { + const char *reflist; + if (argc < 4) + return error("expand-refs-wildcard takes at least 2 args"); + reflist = argv[2]; + if (!strcmp(reflist, "-")) + reflist = get_stdin(); + return expand_refs_wildcard(reflist, argc - 3, argv + 3); + } + + return error("Unknown subcommand: %s", argv[1]); +} diff --git a/contrib/examples/git-commit.sh b/contrib/examples/git-commit.sh index 5c72f655c7..23ffb028d1 100755 --- a/contrib/examples/git-commit.sh +++ b/contrib/examples/git-commit.sh @@ -631,7 +631,7 @@ then if test -z "$quiet" then commit=`git diff-tree --always --shortstat --pretty="format:%h: %s"\ - --summary --root HEAD --` + --abbrev --summary --root HEAD --` echo "Created${initial_commit:+ initial} commit $commit" fi fi diff --git a/contrib/examples/git-fetch.sh b/contrib/examples/git-fetch.sh index e44af2c86d..a314273bd5 100755 --- a/contrib/examples/git-fetch.sh +++ b/contrib/examples/git-fetch.sh @@ -127,10 +127,12 @@ then orig_head=$(git rev-parse --verify HEAD 2>/dev/null) fi -# Allow --notags from remote.$1.tagopt +# Allow --tags/--notags from remote.$1.tagopt case "$tags$no_tags" in '') case "$(git config --get "remote.$1.tagopt")" in + --tags) + tags=t ;; --no-tags) no_tags=t ;; esac diff --git a/contrib/examples/git-merge.sh b/contrib/examples/git-merge.sh index e9588eec33..7b922c3948 100755 --- a/contrib/examples/git-merge.sh +++ b/contrib/examples/git-merge.sh @@ -14,8 +14,11 @@ summary (synonym to --stat) log add list of one-line log to merge commit message squash create a single commit instead of doing a merge commit perform a commit if the merge succeeds (default) -ff allow fast forward (default) +ff allow fast-forward (default) +ff-only abort if fast-forward is not possible +rerere-autoupdate update index with any reused conflict resolution s,strategy= merge strategy to use +X= option for selected merge strategy m,message= message to be used for the merge commit (if any) " @@ -25,25 +28,32 @@ require_work_tree cd_to_toplevel test -z "$(git ls-files -u)" || - die "You are in the middle of a conflicted merge." + die "Merge is not possible because you have unmerged files." + +! test -e "$GIT_DIR/MERGE_HEAD" || + die 'You have not concluded your merge (MERGE_HEAD exists).' LF=' ' all_strategies='recur recursive octopus resolve stupid ours subtree' +all_strategies="$all_strategies recursive-ours recursive-theirs" +not_strategies='base file index tree' default_twohead_strategies='recursive' default_octopus_strategies='octopus' no_fast_forward_strategies='subtree ours' -no_trivial_strategies='recursive recur subtree ours' +no_trivial_strategies='recursive recur subtree ours recursive-ours recursive-theirs' use_strategies= +xopt= allow_fast_forward=t +fast_forward_only= allow_trivial_merge=t -squash= no_commit= log_arg= +squash= no_commit= log_arg= rr_arg= dropsave() { rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \ - "$GIT_DIR/MERGE_STASH" || exit 1 + "$GIT_DIR/MERGE_STASH" "$GIT_DIR/MERGE_MODE" || exit 1 } savestate() { @@ -130,21 +140,34 @@ finish () { merge_name () { remote="$1" rh=$(git rev-parse --verify "$remote^0" 2>/dev/null) || return - bh=$(git show-ref -s --verify "refs/heads/$remote" 2>/dev/null) - if test "$rh" = "$bh" - then - echo "$rh branch '$remote' of ." - elif truname=$(expr "$remote" : '\(.*\)~[1-9][0-9]*$') && + if truname=$(expr "$remote" : '\(.*\)~[0-9]*$') && git show-ref -q --verify "refs/heads/$truname" 2>/dev/null then echo "$rh branch '$truname' (early part) of ." - elif test "$remote" = "FETCH_HEAD" -a -r "$GIT_DIR/FETCH_HEAD" + return + fi + if found_ref=$(git rev-parse --symbolic-full-name --verify \ + "$remote" 2>/dev/null) + then + expanded=$(git check-ref-format --branch "$remote") || + exit + if test "${found_ref#refs/heads/}" != "$found_ref" + then + echo "$rh branch '$expanded' of ." + return + elif test "${found_ref#refs/remotes/}" != "$found_ref" + then + echo "$rh remote branch '$expanded' of ." + return + fi + fi + if test "$remote" = "FETCH_HEAD" -a -r "$GIT_DIR/FETCH_HEAD" then sed -e 's/ not-for-merge / /' -e 1q \ "$GIT_DIR/FETCH_HEAD" - else - echo "$rh commit '$remote'" + return fi + echo "$rh commit '$remote'" } parse_config () { @@ -171,16 +194,36 @@ parse_config () { --no-ff) test "$squash" != t || die "You cannot combine --squash with --no-ff." + test "$fast_forward_only" != t || + die "You cannot combine --ff-only with --no-ff." allow_fast_forward=f ;; + --ff-only) + test "$allow_fast_forward" != f || + die "You cannot combine --ff-only with --no-ff." + fast_forward_only=t ;; + --rerere-autoupdate|--no-rerere-autoupdate) + rr_arg=$1 ;; -s|--strategy) shift case " $all_strategies " in *" $1 "*) - use_strategies="$use_strategies$1 " ;; + use_strategies="$use_strategies$1 " + ;; *) - die "available strategies are: $all_strategies" ;; + case " $not_strategies " in + *" $1 "*) + false + esac && + type "git-merge-$1" >/dev/null 2>&1 || + die "available strategies are: $all_strategies" + use_strategies="$use_strategies$1 " + ;; esac ;; + -X) + shift + xopt="${xopt:+$xopt }$(git rev-parse --sq-quote "--$1")" + ;; -m|--message) shift merge_msg="$1" @@ -244,6 +287,10 @@ then exit 1 fi + test "$squash" != t || + die "Squash commit into empty head not supported yet" + test "$allow_fast_forward" = t || + die "Non-fast-forward into an empty head does not make sense" rh=$(git rev-parse --verify "$1^0") || die "$1 - not something we can merge" @@ -260,12 +307,18 @@ else # the given message. If remote is invalid we will die # later in the common codepath so we discard the error # in this loop. - merge_name=$(for remote + merge_msg="$( + for remote do merge_name "$remote" - done | git fmt-merge-msg $log_arg - ) - merge_msg="${merge_msg:+$merge_msg$LF$LF}$merge_name" + done | + if test "$have_message" = t + then + git fmt-merge-msg -m "$merge_msg" $log_arg + else + git fmt-merge-msg $log_arg + fi + )" fi head=$(git rev-parse --verify "$head_arg"^0) || usage @@ -334,7 +387,7 @@ case "$#" in common=$(git merge-base --all $head "$@") ;; *) - common=$(git show-branch --merge-base $head "$@") + common=$(git merge-base --all --octopus $head "$@") ;; esac echo "$head" >"$GIT_DIR/ORIG_HEAD" @@ -353,7 +406,7 @@ t,1,"$head",*) # Again the most common case of merging one remote. echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $1)" git update-index --refresh 2>/dev/null - msg="Fast forward" + msg="Fast-forward" if test -n "$have_message" then msg="$msg (no commit created; -m option ignored)" @@ -365,15 +418,15 @@ t,1,"$head",*) exit 0 ;; ?,1,?*"$LF"?*,*) - # We are not doing octopus and not fast forward. Need a + # We are not doing octopus and not fast-forward. Need a # real merge. ;; ?,1,*,) - # We are not doing octopus, not fast forward, and have only + # We are not doing octopus, not fast-forward, and have only # one common. git update-index --refresh 2>/dev/null - case "$allow_trivial_merge" in - t) + case "$allow_trivial_merge,$fast_forward_only" in + t,) # See if it is really trivial. git var GIT_COMMITTER_IDENT >/dev/null || exit echo "Trying really trivial in-index merge..." @@ -412,6 +465,11 @@ t,1,"$head",*) ;; esac +if test "$fast_forward_only" = t +then + die "Not possible to fast-forward, aborting." +fi + # We are going to make a new commit. git var GIT_COMMITTER_IDENT >/dev/null || exit @@ -450,7 +508,7 @@ do # Remember which strategy left the state in the working tree wt_strategy=$strategy - git-merge-$strategy $common -- "$head_arg" "$@" + eval 'git-merge-$strategy '"$xopt"' $common -- "$head_arg" "$@"' exit=$? if test "$no_commit" = t && test "$exit" = 0 then @@ -488,9 +546,9 @@ if test '' != "$result_tree" then if test "$allow_fast_forward" = "t" then - parents=$(git show-branch --independent "$head" "$@") + parents=$(git merge-base --independent "$head" "$@") else - parents=$(git rev-parse "$head" "$@") + parents=$(git rev-parse "$head" "$@") fi parents=$(echo "$parents" | sed -e 's/^/-p /') result_commit=$(printf '%s\n' "$merge_msg" | git commit-tree $result_tree $parents) || exit @@ -532,7 +590,15 @@ else do echo $remote done >"$GIT_DIR/MERGE_HEAD" - printf '%s\n' "$merge_msg" >"$GIT_DIR/MERGE_MSG" + printf '%s\n' "$merge_msg" >"$GIT_DIR/MERGE_MSG" || + die "Could not write to $GIT_DIR/MERGE_MSG" + if test "$allow_fast_forward" != t + then + printf "%s" no-ff + else + : + fi >"$GIT_DIR/MERGE_MODE" || + die "Could not write to $GIT_DIR/MERGE_MODE" fi if test "$merge_was_ok" = t @@ -549,6 +615,6 @@ Conflicts: sed -e 's/^[^ ]* / /' | uniq } >>"$GIT_DIR/MERGE_MSG" - git rerere + git rerere $rr_arg die "Automatic merge failed; fix conflicts and then commit the result." fi diff --git a/contrib/examples/git-notes.sh b/contrib/examples/git-notes.sh new file mode 100755 index 0000000000..e642e47d9f --- /dev/null +++ b/contrib/examples/git-notes.sh @@ -0,0 +1,121 @@ +#!/bin/sh + +USAGE="(edit [-F <file> | -m <msg>] | show) [commit]" +. git-sh-setup + +test -z "$1" && usage +ACTION="$1"; shift + +test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="$(git config core.notesref)" +test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="refs/notes/commits" + +MESSAGE= +while test $# != 0 +do + case "$1" in + -m) + test "$ACTION" = "edit" || usage + shift + if test "$#" = "0"; then + die "error: option -m needs an argument" + else + if [ -z "$MESSAGE" ]; then + MESSAGE="$1" + else + MESSAGE="$MESSAGE + +$1" + fi + shift + fi + ;; + -F) + test "$ACTION" = "edit" || usage + shift + if test "$#" = "0"; then + die "error: option -F needs an argument" + else + if [ -z "$MESSAGE" ]; then + MESSAGE="$(cat "$1")" + else + MESSAGE="$MESSAGE + +$(cat "$1")" + fi + shift + fi + ;; + -*) + usage + ;; + *) + break + ;; + esac +done + +COMMIT=$(git rev-parse --verify --default HEAD "$@") || +die "Invalid commit: $@" + +case "$ACTION" in +edit) + if [ "${GIT_NOTES_REF#refs/notes/}" = "$GIT_NOTES_REF" ]; then + die "Refusing to edit notes in $GIT_NOTES_REF (outside of refs/notes/)" + fi + + MSG_FILE="$GIT_DIR/new-notes-$COMMIT" + GIT_INDEX_FILE="$MSG_FILE.idx" + export GIT_INDEX_FILE + + trap ' + test -f "$MSG_FILE" && rm "$MSG_FILE" + test -f "$GIT_INDEX_FILE" && rm "$GIT_INDEX_FILE" + ' 0 + + CURRENT_HEAD=$(git show-ref "$GIT_NOTES_REF" | cut -f 1 -d ' ') + if [ -z "$CURRENT_HEAD" ]; then + PARENT= + else + PARENT="-p $CURRENT_HEAD" + git read-tree "$GIT_NOTES_REF" || die "Could not read index" + fi + + if [ -z "$MESSAGE" ]; then + GIT_NOTES_REF= git log -1 $COMMIT | sed "s/^/#/" > "$MSG_FILE" + if [ ! -z "$CURRENT_HEAD" ]; then + git cat-file blob :$COMMIT >> "$MSG_FILE" 2> /dev/null + fi + core_editor="$(git config core.editor)" + ${GIT_EDITOR:-${core_editor:-${VISUAL:-${EDITOR:-vi}}}} "$MSG_FILE" + else + echo "$MESSAGE" > "$MSG_FILE" + fi + + grep -v ^# < "$MSG_FILE" | git stripspace > "$MSG_FILE".processed + mv "$MSG_FILE".processed "$MSG_FILE" + if [ -s "$MSG_FILE" ]; then + BLOB=$(git hash-object -w "$MSG_FILE") || + die "Could not write into object database" + git update-index --add --cacheinfo 0644 $BLOB $COMMIT || + die "Could not write index" + else + test -z "$CURRENT_HEAD" && + die "Will not initialise with empty tree" + git update-index --force-remove $COMMIT || + die "Could not update index" + fi + + TREE=$(git write-tree) || die "Could not write tree" + NEW_HEAD=$(echo Annotate $COMMIT | git commit-tree $TREE $PARENT) || + die "Could not annotate" + git update-ref -m "Annotate $COMMIT" \ + "$GIT_NOTES_REF" $NEW_HEAD $CURRENT_HEAD +;; +show) + git rev-parse -q --verify "$GIT_NOTES_REF":$COMMIT > /dev/null || + die "No note for commit $COMMIT." + git show "$GIT_NOTES_REF":$COMMIT +;; +*) + usage +esac diff --git a/contrib/examples/git-resolve.sh b/contrib/examples/git-resolve.sh index 0ee1bd898e..8f98142f77 100755 --- a/contrib/examples/git-resolve.sh +++ b/contrib/examples/git-resolve.sh @@ -48,7 +48,7 @@ case "$common" in "$head") echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $merge)" git read-tree -u -m $head $merge || exit 1 - git update-ref -m "resolve $merge_name: Fast forward" \ + git update-ref -m "resolve $merge_name: Fast-forward" \ HEAD "$merge" "$head" git diff-tree -p $head $merge | git apply --stat dropheads diff --git a/contrib/examples/git-revert.sh b/contrib/examples/git-revert.sh index 49f00321b2..60a05a8b97 100755 --- a/contrib/examples/git-revert.sh +++ b/contrib/examples/git-revert.sh @@ -181,7 +181,6 @@ Conflicts: esac exit 1 } -echo >&2 "Finished one $me." # If we are cherry-pick, and if the merge did not result in # hand-editing, we will hit this commit and inherit the original diff --git a/contrib/examples/git-svnimport.perl b/contrib/examples/git-svnimport.perl index a13bb6afec..b09ff8f12f 100755 --- a/contrib/examples/git-svnimport.perl +++ b/contrib/examples/git-svnimport.perl @@ -1,4 +1,4 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl # This tool is copyright (c) 2005, Matthias Urlichs. # It is released under the Gnu Public License, version 2. @@ -287,9 +287,9 @@ my $last_rev = ""; my $last_branch; my $current_rev = $opt_s || 1; unless(-d $git_dir) { - system("git-init"); + system("git init"); die "Cannot init the GIT db at $git_tree: $?\n" if $?; - system("git-read-tree"); + system("git read-tree --empty"); die "Cannot init an empty tree: $?\n" if $?; $last_branch = $opt_o; @@ -303,7 +303,7 @@ unless(-d $git_dir) { -f "$git_dir/svn2git" or die "'$git_dir/svn2git' does not exist.\n". "You need that file for incremental imports.\n"; - open(F, "git-symbolic-ref HEAD |") or + open(F, "git symbolic-ref HEAD |") or die "Cannot run git-symbolic-ref: $!\n"; chomp ($last_branch = <F>); $last_branch = basename($last_branch); @@ -331,7 +331,7 @@ EOM "$git_dir/refs/heads/$opt_o") == 0; # populate index - system('git-read-tree', $last_rev); + system('git', 'read-tree', $last_rev); die "read-tree failed: $?\n" if $?; # Get the last import timestamps @@ -399,7 +399,7 @@ sub get_file($$$) { my $pid = open(my $F, '-|'); die $! unless defined $pid; if (!$pid) { - exec("git-hash-object", "-w", $name) + exec("git", "hash-object", "-w", $name) or die "Cannot create object: $!\n"; } my $sha = <$F>; @@ -423,7 +423,7 @@ sub get_ignore($$$$$) { my $pid = open(my $F, '-|'); die $! unless defined $pid; if (!$pid) { - exec("git-hash-object", "-w", $name) + exec("git", "hash-object", "-w", $name) or die "Cannot create object: $!\n"; } my $sha = <$F>; @@ -547,7 +547,7 @@ sub copy_path($$$$$$$$) { my $pid = open my $f,'-|'; die $! unless defined $pid; if (!$pid) { - exec("git-ls-tree","-r","-z",$gitrev,$srcpath) + exec("git","ls-tree","-r","-z",$gitrev,$srcpath) or die $!; } local $/ = "\0"; @@ -634,7 +634,7 @@ sub commit { my $rev; if($revision > $opt_s and defined $parent) { - open(H,'-|',"git-rev-parse","--verify",$parent); + open(H,'-|',"git","rev-parse","--verify",$parent); $rev = <H>; close(H) or do { print STDERR "$revision: cannot find commit '$parent'!\n"; @@ -671,7 +671,7 @@ sub commit { unlink($git_index); } elsif ($rev ne $last_rev) { print "Switching from $last_rev to $rev ($branch)\n" if $opt_v; - system("git-read-tree", $rev); + system("git", "read-tree", $rev); die "read-tree failed for $rev: $?\n" if $?; $last_rev = $rev; } @@ -740,7 +740,7 @@ sub commit { my $pid = open my $F, "-|"; die "$!" unless defined $pid; if (!$pid) { - exec("git-ls-files", "-z", @o1) or die $!; + exec("git", "ls-files", "-z", @o1) or die $!; } @o1 = (); local $/ = "\0"; @@ -758,7 +758,7 @@ sub commit { @o2 = @o1; @o1 = (); } - system("git-update-index","--force-remove","--",@o2); + system("git","update-index","--force-remove","--",@o2); die "Cannot remove files: $?\n" if $?; } } @@ -770,7 +770,7 @@ sub commit { @n2 = @new; @new = (); } - system("git-update-index","--add", + system("git","update-index","--add", (map { ('--cacheinfo', @$_) } @n2)); die "Cannot add files: $?\n" if $?; } @@ -778,7 +778,7 @@ sub commit { my $pid = open(C,"-|"); die "Cannot fork: $!" unless defined $pid; unless($pid) { - exec("git-write-tree"); + exec("git","write-tree"); die "Cannot exec git-write-tree: $!\n"; } chomp(my $tree = <C>); @@ -830,7 +830,7 @@ sub commit { "GIT_COMMITTER_NAME=$committer_name", "GIT_COMMITTER_EMAIL=$committer_email", "GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)), - "git-commit-tree", $tree,@par); + "git", "commit-tree", $tree,@par); die "Cannot exec git-commit-tree: $!\n"; } $pw->writer(); @@ -874,7 +874,7 @@ sub commit { $dest =~ tr/_/\./ if $opt_u; - system('git-tag', '-f', $dest, $cid) == 0 + system('git', 'tag', '-f', $dest, $cid) == 0 or die "Cannot create tag $dest: $!\n"; print "Created tag '$dest' on '$branch'\n" if $opt_v; @@ -937,7 +937,7 @@ while ($to_rev < $opt_l) { my $pid = fork(); die "Fork: $!\n" unless defined $pid; unless($pid) { - exec("git-repack", "-d") + exec("git", "repack", "-d") or die "Cannot repack: $!\n"; } waitpid($pid, 0); @@ -958,7 +958,7 @@ if($orig_branch) { system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master") if $forward_master; unless ($opt_i) { - system('git-read-tree', '-m', '-u', 'SVN2GIT_HEAD', 'HEAD'); + system('git', 'read-tree', '-m', '-u', 'SVN2GIT_HEAD', 'HEAD'); die "read-tree failed: $?\n" if $?; } } else { @@ -966,7 +966,7 @@ if($orig_branch) { print "DONE; creating $orig_branch branch\n" if $opt_v and (not defined $opt_l or $opt_l > 0); system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master") unless -f "$git_dir/refs/heads/master"; - system('git-update-ref', 'HEAD', "$orig_branch"); + system('git', 'update-ref', 'HEAD', "$orig_branch"); unless ($opt_i) { system('git checkout'); die "checkout failed: $?\n" if $?; diff --git a/contrib/examples/git-svnimport.txt b/contrib/examples/git-svnimport.txt index 71aad8b45b..3bb871e42f 100644 --- a/contrib/examples/git-svnimport.txt +++ b/contrib/examples/git-svnimport.txt @@ -114,9 +114,9 @@ due to SVN memory leaks. (These have been worked around.) -R <repack_each_revs>:: Specify how often git repository should be repacked. + -The default value is 1000. git-svnimport will do import in chunks of 1000 -revisions, after each chunk git repository will be repacked. To disable -this behavior specify some big value here which is mote than number of +The default value is 1000. git-svnimport will do imports in chunks of 1000 +revisions, after each chunk the git repository will be repacked. To disable +this behavior specify some large value here which is greater than the number of revisions to import. -P <path_from_trunk>:: diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index 2b122d3f51..04ce7e3b02 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -8,12 +8,10 @@ # License: MIT <http://www.opensource.org/licenses/mit-license.php> # -import optparse, sys, os, marshal, popen2, subprocess, shelve -import tempfile, getopt, sha, os.path, time, platform +import optparse, sys, os, marshal, subprocess, shelve +import tempfile, getopt, os.path, time, platform import re -from sets import Set; - verbose = False @@ -201,7 +199,7 @@ def isModeExec(mode): def isModeExecChanged(src_mode, dst_mode): return isModeExec(src_mode) != isModeExec(dst_mode) -def p4CmdList(cmd, stdin=None, stdin_mode='w+b'): +def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None): cmd = p4_build_cmd("-G %s" % (cmd)) if verbose: sys.stderr.write("Opening pipe: %s\n" % cmd) @@ -224,7 +222,10 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b'): try: while True: entry = marshal.load(p4.stdout) - result.append(entry) + if cb is not None: + cb(entry) + else: + result.append(entry) except EOFError: pass exitCode = p4.wait() @@ -245,7 +246,22 @@ def p4Cmd(cmd): def p4Where(depotPath): if not depotPath.endswith("/"): depotPath += "/" - output = p4Cmd("where %s..." % depotPath) + depotPath = depotPath + "..." + outputList = p4CmdList("where %s" % depotPath) + output = None + for entry in outputList: + if "depotFile" in entry: + if entry["depotFile"] == depotPath: + output = entry + break + elif "data" in entry: + data = entry.get("data") + space = data.find(" ") + if data[:space] == depotPath: + output = entry + break + if output == None: + return "" if output["code"] == "error": return "" clientPath = "" @@ -316,8 +332,11 @@ def gitBranchExists(branch): stderr=subprocess.PIPE, stdout=subprocess.PIPE); return proc.wait() == 0; +_gitConfig = {} def gitConfig(key): - return read_pipe("git config %s" % key, ignore_error=True).strip() + if not _gitConfig.has_key(key): + _gitConfig[key] = read_pipe("git config %s" % key, ignore_error=True).strip() + return _gitConfig[key] def p4BranchesInGit(branchesAreInRemotes = True): branches = {} @@ -424,13 +443,14 @@ def p4ChangesForPaths(depotPaths, changeRange): output = p4_read_pipe_lines("changes " + ' '.join (["%s...%s" % (p, changeRange) for p in depotPaths])) - changes = [] + changes = {} for line in output: - changeNum = line.split(" ")[1] - changes.append(int(changeNum)) + changeNum = int(line.split(" ")[1]) + changes[changeNum] = True - changes.sort() - return changes + changelist = changes.keys() + changelist.sort() + return changelist class Command: def __init__(self): @@ -686,7 +706,9 @@ class P4Submit(Command): submitTemplate = self.prepareLogMessage(template, logMessage) if os.environ.has_key("P4DIFF"): del(os.environ["P4DIFF"]) - diff = p4_read_pipe("diff -du ...") + diff = "" + for editedFile in editedFiles: + diff += p4_read_pipe("diff -du %r" % editedFile) newdiff = "" for newFile in filesToAdd: @@ -708,23 +730,35 @@ class P4Submit(Command): newdiff = newdiff.replace("\n", "\r\n") tmpFile.write(submitTemplate + separatorLine + diff + newdiff) tmpFile.close() - defaultEditor = "vi" - if platform.system() == "Windows": - defaultEditor = "notepad" + mtime = os.stat(fileName).st_mtime if os.environ.has_key("P4EDITOR"): editor = os.environ.get("P4EDITOR") else: - editor = os.environ.get("EDITOR", defaultEditor); + editor = read_pipe("git var GIT_EDITOR").strip() system(editor + " " + fileName) - tmpFile = open(fileName, "rb") - message = tmpFile.read() - tmpFile.close() - os.remove(fileName) - submitTemplate = message[:message.index(separatorLine)] - if self.isWindows: - submitTemplate = submitTemplate.replace("\r\n", "\n") - p4_write_pipe("submit -i", submitTemplate) + response = "y" + if os.stat(fileName).st_mtime <= mtime: + response = "x" + while response != "y" and response != "n": + response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ") + + if response == "y": + tmpFile = open(fileName, "rb") + message = tmpFile.read() + tmpFile.close() + submitTemplate = message[:message.index(separatorLine)] + if self.isWindows: + submitTemplate = submitTemplate.replace("\r\n", "\n") + p4_write_pipe("submit -i", submitTemplate) + else: + for f in editedFiles: + p4_system("revert \"%s\"" % f); + for f in filesToAdd: + p4_system("revert \"%s\"" % f); + system("rm %s" %f) + + os.remove(fileName) else: fileName = "submit.txt" file = open(fileName, "w+") @@ -770,7 +804,7 @@ class P4Submit(Command): self.oldWorkingDirectory = os.getcwd() chdir(self.clientPath) - print "Syncronizing p4 checkout..." + print "Synchronizing p4 checkout..." p4_system("sync ...") self.check() @@ -827,8 +861,8 @@ class P4Sync(Command): self.usage += " //depot/path[@revRange]" self.silent = False - self.createdBranches = Set() - self.committedChanges = Set() + self.createdBranches = set() + self.committedChanges = set() self.branch = "" self.detectBranches = False self.detectLabels = False @@ -916,10 +950,83 @@ class P4Sync(Command): return branches - ## Should move this out, doesn't use SELF. - def readP4Files(self, files): + # output one file from the P4 stream + # - helper for streamP4Files + + def streamOneP4File(self, file, contents): + if file["type"] == "apple": + print "\nfile %s is a strange apple file that forks. Ignoring" % \ + file['depotFile'] + return + + relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes) + if verbose: + sys.stderr.write("%s\n" % relPath) + + mode = "644" + if isP4Exec(file["type"]): + mode = "755" + elif file["type"] == "symlink": + mode = "120000" + # p4 print on a symlink contains "target\n", so strip it off + data = ''.join(contents) + contents = [data[:-1]] + + if self.isWindows and file["type"].endswith("text"): + mangled = [] + for data in contents: + data = data.replace("\r\n", "\n") + mangled.append(data) + contents = mangled + + if file['type'] in ('text+ko', 'unicode+ko', 'binary+ko'): + contents = map(lambda text: re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', text), contents) + elif file['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'): + contents = map(lambda text: re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$\n]*\$',r'$\1$', text), contents) + + self.gitStream.write("M %s inline %s\n" % (mode, relPath)) + + # total length... + length = 0 + for d in contents: + length = length + len(d) + + self.gitStream.write("data %d\n" % length) + for d in contents: + self.gitStream.write(d) + self.gitStream.write("\n") + + def streamOneP4Deletion(self, file): + relPath = self.stripRepoPath(file['path'], self.branchPrefixes) + if verbose: + sys.stderr.write("delete %s\n" % relPath) + self.gitStream.write("D %s\n" % relPath) + + # handle another chunk of streaming data + def streamP4FilesCb(self, marshalled): + + if marshalled.has_key('depotFile') and self.stream_have_file_info: + # start of a new file - output the old one first + self.streamOneP4File(self.stream_file, self.stream_contents) + self.stream_file = {} + self.stream_contents = [] + self.stream_have_file_info = False + + # pick up the new file information... for the + # 'data' field we need to append to our array + for k in marshalled.keys(): + if k == 'data': + self.stream_contents.append(marshalled['data']) + else: + self.stream_file[k] = marshalled[k] + + self.stream_have_file_info = True + + # Stream directly from "p4 files" into "git fast-import" + def streamP4Files(self, files): filesForCommit = [] filesToRead = [] + filesToDelete = [] for f in files: includeFile = True @@ -931,52 +1038,37 @@ class P4Sync(Command): if includeFile: filesForCommit.append(f) - if f['action'] != 'delete': + if f['action'] not in ('delete', 'move/delete', 'purge'): filesToRead.append(f) + else: + filesToDelete.append(f) - filedata = [] - if len(filesToRead) > 0: - filedata = p4CmdList('-x - print', - stdin='\n'.join(['%s#%s' % (f['path'], f['rev']) - for f in filesToRead]), - stdin_mode='w+') - - if "p4ExitCode" in filedata[0]: - die("Problems executing p4. Error: [%d]." - % (filedata[0]['p4ExitCode'])); - - j = 0; - contents = {} - while j < len(filedata): - stat = filedata[j] - j += 1 - text = []; - while j < len(filedata) and filedata[j]['code'] in ('text', 'unicode', 'binary'): - text.append(filedata[j]['data']) - j += 1 - text = ''.join(text) - - if not stat.has_key('depotFile'): - sys.stderr.write("p4 print fails with: %s\n" % repr(stat)) - continue + # deleted files... + for f in filesToDelete: + self.streamOneP4Deletion(f) - if stat['type'] in ('text+ko', 'unicode+ko', 'binary+ko'): - text = re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', text) - elif stat['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'): - text = re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$\n]*\$',r'$\1$', text) + if len(filesToRead) > 0: + self.stream_file = {} + self.stream_contents = [] + self.stream_have_file_info = False - contents[stat['depotFile']] = text + # curry self argument + def streamP4FilesCbSelf(entry): + self.streamP4FilesCb(entry) - for f in filesForCommit: - path = f['path'] - if contents.has_key(path): - f['data'] = contents[path] + p4CmdList("-x - print", + '\n'.join(['%s#%s' % (f['path'], f['rev']) + for f in filesToRead]), + cb=streamP4FilesCbSelf) - return filesForCommit + # do the last chunk + if self.stream_file.has_key('depotFile'): + self.streamOneP4File(self.stream_file, self.stream_contents) def commit(self, details, files, branch, branchPrefixes, parent = ""): epoch = details["time"] author = details["user"] + self.branchPrefixes = branchPrefixes if self.verbose: print "commit into %s" % branch @@ -989,7 +1081,6 @@ class P4Sync(Command): new_files.append (f) else: sys.stderr.write("Ignoring file outside of prefix: %s\n" % path) - files = self.readP4Files(new_files) self.gitStream.write("commit %s\n" % branch) # gitStream.write("mark :%s\n" % details["change"]) @@ -1017,33 +1108,7 @@ class P4Sync(Command): print "parent %s" % parent self.gitStream.write("from %s\n" % parent) - for file in files: - if file["type"] == "apple": - print "\nfile %s is a strange apple file that forks. Ignoring!" % file['path'] - continue - - relPath = self.stripRepoPath(file['path'], branchPrefixes) - if file["action"] == "delete": - self.gitStream.write("D %s\n" % relPath) - else: - data = file['data'] - - mode = "644" - if isP4Exec(file["type"]): - mode = "755" - elif file["type"] == "symlink": - mode = "120000" - # p4 print on a symlink contains "target\n", so strip it off - data = data[:-1] - - if self.isWindows and file["type"].endswith("text"): - data = data.replace("\r\n", "\n") - - self.gitStream.write("M %s inline %s\n" % (mode, relPath)) - self.gitStream.write("data %s\n" % len(data)) - self.gitStream.write(data) - self.gitStream.write("\n") - + self.streamP4Files(new_files) self.gitStream.write("\n") change = int(details["change"]) @@ -1062,7 +1127,7 @@ class P4Sync(Command): cleanedFiles = {} for info in files: - if info["action"] == "delete": + if info["action"] in ("delete", "purge"): continue cleanedFiles[info["depotFile"]] = info["rev"] @@ -1108,7 +1173,7 @@ class P4Sync(Command): s = '' for (key, val) in self.users.items(): - s += "%s\t%s\n" % (key, val) + s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1)) open(self.getUserCacheFilename(), "wb").write(s) self.userMapFromPerforceServer = True @@ -1385,7 +1450,7 @@ class P4Sync(Command): if change > newestRevision: newestRevision = change - if info["action"] == "delete": + if info["action"] in ("delete", "purge"): # don't increase the file cnt, otherwise details["depotFile123"] will have gaps! #fileCnt = fileCnt + 1 continue @@ -1593,7 +1658,7 @@ class P4Sync(Command): if len(self.changesFile) > 0: output = open(self.changesFile).readlines() - changeSet = Set() + changeSet = set() for line in output: changeSet.add(int(line)) @@ -1733,8 +1798,12 @@ class P4Clone(P4Sync): if not P4Sync.run(self, depotPaths): return False if self.branch != "master": - if gitBranchExists("refs/remotes/p4/master"): - system("git branch master refs/remotes/p4/master") + if self.importIntoRemotes: + masterbranch = "refs/remotes/p4/master" + else: + masterbranch = "refs/heads/p4/master" + if gitBranchExists(masterbranch): + system("git branch master %s" % masterbranch) system("git checkout -f") else: print "Could not detect main branch. No checkout/master branch created." diff --git a/contrib/fast-import/import-directories.perl b/contrib/fast-import/import-directories.perl new file mode 100755 index 0000000000..7f3afa5ac4 --- /dev/null +++ b/contrib/fast-import/import-directories.perl @@ -0,0 +1,417 @@ +#!/usr/bin/perl +# +# Copyright 2008-2009 Peter Krefting <peter@softwolves.pp.se> +# +# ------------------------------------------------------------------------ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# ------------------------------------------------------------------------ + +=pod + +=head1 NAME + +import-directories - Import bits and pieces to Git. + +=head1 SYNOPSIS + +B<import-directories.perl> F<configfile> F<outputfile> + +=head1 DESCRIPTION + +Script to import arbitrary projects version controlled by the "copy the +source directory to a new location and edit it there"-version controlled +projects into version control. Handles projects with arbitrary branching +and version trees, taking a file describing the inputs and generating a +file compatible with the L<git-fast-import(1)> format. + +=head1 CONFIGURATION FILE + +=head2 Format + +The configuration file is based on the standard I<.ini> format. + + ; Comments start with semi-colons + [section] + key=value + +Please see below for information on how to escape special characters. + +=head2 Global configuration + +Global configuration is done in the B<[config]> section, which should be +the first section in the file. Configuration can be changed by +repeating configuration sections later on. + + [config] + ; configure conversion of CRLFs. "convert" means that all CRLFs + ; should be converted into LFs (suitable for the core.autocrlf + ; setting set to true in Git). "none" means that all data is + ; treated as binary. + crlf=convert + +=head2 Revision configuration + +Each revision that is to be imported is described in three +sections. Revisions should be defined in topological order, so +that a revision's parent has always been defined when a new revision +is introduced. All the sections for one revision must be defined +before defining the next revision. + +Each revision is assigned a unique numerical identifier. The +numbers do not need to be consecutive, nor monotonically +increasing. + +For instance, if your configuration file contains only the two +revisions 4711 and 42, where 4711 is the initial commit, the +only requirement is that 4711 is completely defined before 42. + +=pod + +=head3 Revision description section + +A section whose section name is just an integer gives meta-data +about the revision. + + [3] + ; author sets the author of the revisions + author=Peter Krefting <peter@softwolves.pp.se> + ; branch sets the branch that the revision should be committed to + branch=master + ; parent describes the revision that is the parent of this commit + ; (optional) + parent=1 + ; merges describes a revision that is merged into this commit + ; (optional; can be repeated) + merges=2 + ; selects one file to take the timestamp from + ; (optional; if unspecified, the most recent file from the .files + ; section is used) + timestamp=3/source.c + +=head3 Revision contents section + +A section whose section name is an integer followed by B<.files> +describe all the files included in this revision. If a file that +was available previously is not included in this revision, it will +be removed. + +If an on-disk revision is incomplete, you can point to files from +a previous revision. There are no restriction as to where the source +files are located, nor to the names of them. + + [3.files] + ; the key is the path inside the repository, the value is the path + ; as seen from the importer script. + source.c=ver-3.00/source.c + source.h=ver-2.99/source.h + readme.txt=ver-3.00/introduction to the project.txt + +File names are treated as byte strings (but please see below on +quoting rules), and should be stored in the configuration file in +the encoding that should be used in the generated repository. + +=head3 Revision commit message section + +A section whose section name is an integer followed by B<.message> +gives the commit message. This section is read verbatim, up until +the beginning of the next section. As such, a commit message may not +contain a line that begins with an opening square bracket ("[") and +ends with a closing square bracket ("]"), unless they are surrounded +by whitespace or other characters. + + [3.message] + Implement foobar. + ; trailing blank lines are ignored. + +=cut + +# Globals +use strict; +use warnings; +use integer; +my $crlfmode = 0; +my @revs; +my (%revmap, %message, %files, %author, %branch, %parent, %merges, %time, %timesource); +my $sectiontype = 0; +my $rev = 0; +my $mark = 1; + +# Check command line +if ($#ARGV < 1 || $ARGV[0] =~ /^--?h/) +{ + exec('perldoc', $0); + exit 1; +} + +# Open configuration +my $config = $ARGV[0]; +open CFG, '<', $config or die "Cannot open configuration file \"$config\": "; + +# Open output +my $output = $ARGV[1]; +open OUT, '>', $output or die "Cannot create output file \"$output\": "; +binmode OUT; + +LINE: while (my $line = <CFG>) +{ + $line =~ s/\r?\n$//; + next LINE if $sectiontype != 4 && $line eq ''; + next LINE if $line =~ /^;/; + my $oldsectiontype = $sectiontype; + my $oldrev = $rev; + + # Sections + if ($line =~ m"^\[(config|(\d+)(|\.files|\.message))\]$") + { + if ($1 eq 'config') + { + $sectiontype = 1; + } + elsif ($3 eq '') + { + $sectiontype = 2; + $rev = $2; + # Create a new revision + die "Duplicate rev: $line\n " if defined $revmap{$rev}; + print "Reading revision $rev\n"; + push @revs, $rev; + $revmap{$rev} = $mark ++; + $time{$revmap{$rev}} = 0; + } + elsif ($3 eq '.files') + { + $sectiontype = 3; + $rev = $2; + die "Revision mismatch: $line\n " unless $rev == $oldrev; + } + elsif ($3 eq '.message') + { + $sectiontype = 4; + $rev = $2; + die "Revision mismatch: $line\n " unless $rev == $oldrev; + } + else + { + die "Internal parse error: $line\n "; + } + next LINE; + } + + # Parse data + if ($sectiontype != 4) + { + # Key and value + if ($line =~ m"^\s*([^\s].*=.*[^\s])\s*$") + { + my ($key, $value) = &parsekeyvaluepair($1); + # Global configuration + if (1 == $sectiontype) + { + if ($key eq 'crlf') + { + $crlfmode = 1, next LINE if $value eq 'convert'; + $crlfmode = 0, next LINE if $value eq 'none'; + } + die "Unknown configuration option: $line\n "; + } + # Revision specification + if (2 == $sectiontype) + { + my $current = $revmap{$rev}; + $author{$current} = $value, next LINE if $key eq 'author'; + $branch{$current} = $value, next LINE if $key eq 'branch'; + $parent{$current} = $value, next LINE if $key eq 'parent'; + $timesource{$current} = $value, next LINE if $key eq 'timestamp'; + push(@{$merges{$current}}, $value), next LINE if $key eq 'merges'; + die "Unknown revision option: $line\n "; + } + # Filespecs + if (3 == $sectiontype) + { + # Add the file and create a marker + die "File not found: $line\n " unless -f $value; + my $current = $revmap{$rev}; + ${$files{$current}}{$key} = $mark; + my $time = &fileblob($value, $crlfmode, $mark ++); + + # Update revision timestamp if more recent than other + # files seen, or if this is the file we have selected + # to take the time stamp from using the "timestamp" + # directive. + if ((defined $timesource{$current} && $timesource{$current} eq $value) + || $time > $time{$current}) + { + $time{$current} = $time; + } + } + } + else + { + die "Parse error: $line\n "; + } + } + else + { + # Commit message + my $current = $revmap{$rev}; + if (defined $message{$current}) + { + $message{$current} .= "\n"; + } + $message{$current} .= $line; + } +} +close CFG; + +# Start spewing out data for git-fast-import +foreach my $commit (@revs) +{ + # Progress + print OUT "progress Creating revision $commit\n"; + + # Create commit header + my $mark = $revmap{$commit}; + + # Branch and commit id + print OUT "commit refs/heads/", $branch{$mark}, "\nmark :", $mark, "\n"; + + # Author and timestamp + die "No timestamp defined for $commit (no files?)\n" unless defined $time{$mark}; + print OUT "committer ", $author{$mark}, " ", $time{$mark}, " +0100\n"; + + # Commit message + die "No message defined for $commit\n" unless defined $message{$mark}; + my $message = $message{$mark}; + $message =~ s/\n$//; # Kill trailing empty line + print OUT "data ", length($message), "\n", $message, "\n"; + + # Parent and any merges + print OUT "from :", $revmap{$parent{$mark}}, "\n" if defined $parent{$mark}; + if (defined $merges{$mark}) + { + foreach my $merge (@{$merges{$mark}}) + { + print OUT "merge :", $revmap{$merge}, "\n"; + } + } + + # Output file marks + print OUT "deleteall\n"; # start from scratch + foreach my $file (sort keys %{$files{$mark}}) + { + print OUT "M 644 :", ${$files{$mark}}{$file}, " $file\n"; + } + print OUT "\n"; +} + +# Create one file blob +sub fileblob +{ + my ($filename, $crlfmode, $mark) = @_; + + # Import the file + print OUT "progress Importing $filename\nblob\nmark :$mark\n"; + open FILE, '<', $filename or die "Cannot read $filename\n "; + binmode FILE; + my ($size, $mtime) = (stat(FILE))[7,9]; + my $file; + read FILE, $file, $size; + close FILE; + $file =~ s/\r\n/\n/g if $crlfmode; + print OUT "data ", length($file), "\n", $file, "\n"; + + return $mtime; +} + +# Parse a key=value pair +sub parsekeyvaluepair +{ +=pod + +=head2 Escaping special characters + +Key and value strings may be enclosed in quotes, in which case +whitespace inside the quotes is preserved. Additionally, an equal +sign may be included in the key by preceding it with a backslash. +For example: + + "key1 "=value1 + key2=" value2" + key\=3=value3 + key4=value=4 + "key5""=value5 + +Here the first key is "key1 " (note the trailing white-space) and the +second value is " value2" (note the leading white-space). The third +key contains an equal sign "key=3" and so does the fourth value, which +does not need to be escaped. The fifth key contains a trailing quote, +which does not need to be escaped since it is inside a surrounding +quote. + +=cut + my $pair = shift; + + # Separate key and value by the first non-quoted equal sign + my ($key, $value); + if ($pair =~ /^(.*[^\\])=(.*)$/) + { + ($key, $value) = ($1, $2) + } + else + { + die "Parse error: $pair\n "; + } + + # Unquote and unescape the key and value separately + return (&unescape($key), &unescape($value)); +} + +# Unquote and unescape +sub unescape +{ + my $string = shift; + + # First remove enclosing quotes. Backslash before the trailing + # quote leaves both. + if ($string =~ /^"(.*[^\\])"$/) + { + $string = $1; + } + + # Second remove any backslashes inside the unquoted string. + # For later: Handle special sequences like \t ? + $string =~ s/\\(.)/$1/g; + + return $string; +} + +__END__ + +=pod + +=head1 EXAMPLES + +B<import-directories.perl> F<project.import> + +=head1 AUTHOR + +Copyright 2008-2009 Peter Krefting E<lt>peter@softwolves.pp.se> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation. + +=cut diff --git a/contrib/fast-import/import-tars.perl b/contrib/fast-import/import-tars.perl index 23aeb257b9..95438e1ed4 100755 --- a/contrib/fast-import/import-tars.perl +++ b/contrib/fast-import/import-tars.perl @@ -8,19 +8,35 @@ ## perl import-tars.perl *.tar.bz2 ## git whatchanged import-tars ## +## Use --metainfo to specify the extension for a meta data file, where +## import-tars can read the commit message and optionally author and +## committer information. +## +## echo 'This is the commit message' > myfile.tar.bz2.msg +## perl import-tars.perl --metainfo=msg myfile.tar.bz2 use strict; -die "usage: import-tars *.tar.{gz,bz2,Z}\n" unless @ARGV; +use Getopt::Long; + +my $metaext = ''; + +die "usage: import-tars [--metainfo=extension] *.tar.{gz,bz2,lzma,xz,Z}\n" + unless GetOptions('metainfo=s' => \$metaext) && @ARGV; my $branch_name = 'import-tars'; my $branch_ref = "refs/heads/$branch_name"; -my $committer_name = 'T Ar Creator'; -my $committer_email = 'tar@example.com'; +my $author_name = $ENV{'GIT_AUTHOR_NAME'} || 'T Ar Creator'; +my $author_email = $ENV{'GIT_AUTHOR_EMAIL'} || 'tar@example.com'; +my $committer_name = $ENV{'GIT_COMMITTER_NAME'} || `git config --get user.name`; +my $committer_email = $ENV{'GIT_COMMITTER_EMAIL'} || `git config --get user.email`; + +chomp($committer_name, $committer_email); open(FI, '|-', 'git', 'fast-import', '--quiet') or die "Unable to start git fast-import: $!\n"; foreach my $tar_file (@ARGV) { + my $commit_time = time; $tar_file =~ m,([^/]+)$,; my $tar_name = $1; @@ -33,13 +49,16 @@ foreach my $tar_file (@ARGV) } elsif ($tar_name =~ s/\.tar\.Z$//) { open(I, '-|', 'uncompress', '-c', $tar_file) or die "Unable to uncompress -c $tar_file: $!\n"; + } elsif ($tar_name =~ s/\.(tar\.(lzma|xz)|(tlz|txz))$//) { + open(I, '-|', 'xz', '-dc', $tar_file) + or die "Unable to xz -dc $tar_file: $!\n"; } elsif ($tar_name =~ s/\.tar$//) { open(I, $tar_file) or die "Unable to open $tar_file: $!\n"; } else { die "Unrecognized compression format: $tar_file\n"; } - my $commit_time = 0; + my $author_time = 0; my $next_mark = 1; my $have_top_dir = 1; my ($top_dir, %files); @@ -77,10 +96,16 @@ foreach my $tar_file (@ARGV) $mtime = oct $mtime; next if $typeflag == 5; # directory - print FI "blob\n", "mark :$next_mark\n", "data $size\n"; - while ($size > 0 && read(I, $_, 512) == 512) { - print FI substr($_, 0, $size); - $size -= 512; + print FI "blob\n", "mark :$next_mark\n"; + if ($typeflag == 2) { # symbolic link + print FI "data ", length($linkname), "\n", $linkname; + $mode = 0120000; + } else { + print FI "data $size\n"; + while ($size > 0 && read(I, $_, 512) == 512) { + print FI substr($_, 0, $size); + $size -= 512; + } } print FI "\n"; @@ -92,17 +117,49 @@ foreach my $tar_file (@ARGV) } $files{$path} = [$next_mark++, $mode]; - $commit_time = $mtime if $mtime > $commit_time; + $author_time = $mtime if $mtime > $author_time; $path =~ m,^([^/]+)/,; $top_dir = $1 unless $top_dir; $have_top_dir = 0 if $top_dir ne $1; } + my $commit_msg = "Imported from $tar_file."; + my $this_committer_name = $committer_name; + my $this_committer_email = $committer_email; + my $this_author_name = $author_name; + my $this_author_email = $author_email; + if ($metaext ne '') { + # Optionally read a commit message from <filename.tar>.msg + # Add a line on the form "Committer: name <e-mail>" to override + # the committer and "Author: name <e-mail>" to override the + # author for this tar ball. + if (open MSG, '<', "${tar_file}.${metaext}") { + my $header_done = 0; + $commit_msg = ''; + while (<MSG>) { + if (!$header_done && /^Committer:\s+([^<>]*)\s+<(.*)>\s*$/i) { + $this_committer_name = $1; + $this_committer_email = $2; + } elsif (!$header_done && /^Author:\s+([^<>]*)\s+<(.*)>\s*$/i) { + $this_author_name = $1; + $this_author_email = $2; + } elsif (!$header_done && /^$/) { # empty line ends header. + $header_done = 1; + } else { + $commit_msg .= $_; + $header_done = 1; + } + } + close MSG; + } + } + print FI <<EOF; commit $branch_ref -committer $committer_name <$committer_email> $commit_time +0000 +author $this_author_name <$this_author_email> $author_time +0000 +committer $this_committer_name <$this_committer_email> $commit_time +0000 data <<END_OF_COMMIT_MESSAGE -Imported from $tar_file. +$commit_msg END_OF_COMMIT_MESSAGE deleteall @@ -112,14 +169,15 @@ EOF { my ($mark, $mode) = @{$files{$path}}; $path =~ s,^([^/]+)/,, if $have_top_dir; - printf FI "M %o :%i %s\n", $mode & 0111 ? 0755 : 0644, $mark, $path; + $mode = $mode & 0111 ? 0755 : 0644 unless $mode == 0120000; + printf FI "M %o :%i %s\n", $mode, $mark, $path; } print FI "\n"; print FI <<EOF; tag $tar_name from $branch_ref -tagger $committer_name <$committer_email> $commit_time +0000 +tagger $author_name <$author_email> $author_time +0000 data <<END_OF_TAG_MESSAGE Package $tar_name END_OF_TAG_MESSAGE diff --git a/contrib/fast-import/import-zips.py b/contrib/fast-import/import-zips.py index 7051a83a59..82f5ed3ddc 100755 --- a/contrib/fast-import/import-zips.py +++ b/contrib/fast-import/import-zips.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python ## zip archive frontend for git-fast-import ## diff --git a/contrib/git-resurrect.sh b/contrib/git-resurrect.sh new file mode 100755 index 0000000000..a4ed4c3c62 --- /dev/null +++ b/contrib/git-resurrect.sh @@ -0,0 +1,181 @@ +#!/bin/sh + +USAGE="[-a] [-r] [-m] [-t] [-n] [-b <newname>] <name>" +LONG_USAGE="git-resurrect attempts to find traces of a branch tip +called <name>, and tries to resurrect it. Currently, the reflog is +searched for checkout messages, and with -r also merge messages. With +-m and -t, the history of all refs is scanned for Merge <name> into +other/Merge <other> into <name> (respectively) commit subjects, which +is rather slow but allows you to resurrect other people's topic +branches." + +OPTIONS_KEEPDASHDASH= +OPTIONS_SPEC="\ +git resurrect $USAGE +-- +b,branch= save branch as <newname> instead of <name> +a,all same as -l -r -m -t +k,keep-going full rev-list scan (instead of first match) +l,reflog scan reflog for checkouts (enabled by default) +r,reflog-merges scan for merges recorded in reflog +m,merges scan for merges into other branches (slow) +t,merge-targets scan for merges of other branches into <name> +n,dry-run don't recreate the branch" + +. git-sh-setup + +search_reflog () { + sed -ne 's~^\([^ ]*\) .*\tcheckout: moving from '"$1"' .*~\1~p' \ + < "$GIT_DIR"/logs/HEAD +} + +search_reflog_merges () { + git rev-parse $( + sed -ne 's~^[^ ]* \([^ ]*\) .*\tmerge '"$1"':.*~\1^2~p' \ + < "$GIT_DIR"/logs/HEAD + ) +} + +_x40="[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]" +_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40" + +search_merges () { + git rev-list --all --grep="Merge branch '$1'" \ + --pretty=tformat:"%P %s" | + sed -ne "/^$_x40 \($_x40\) Merge .*/ {s//\1/p;$early_exit}" +} + +search_merge_targets () { + git rev-list --all --grep="Merge branch '[^']*' into $branch\$" \ + --pretty=tformat:"%H %s" --all | + sed -ne "/^\($_x40\) Merge .*/ {s//\1/p;$early_exit} " +} + +dry_run= +early_exit=q +scan_reflog=t +scan_reflog_merges= +scan_merges= +scan_merge_targets= +new_name= + +while test "$#" != 0; do + case "$1" in + -b|--branch) + shift + new_name="$1" + ;; + -n|--dry-run) + dry_run=t + ;; + --no-dry-run) + dry_run= + ;; + -k|--keep-going) + early_exit= + ;; + --no-keep-going) + early_exit=q + ;; + -m|--merges) + scan_merges=t + ;; + --no-merges) + scan_merges= + ;; + -l|--reflog) + scan_reflog=t + ;; + --no-reflog) + scan_reflog= + ;; + -r|--reflog_merges) + scan_reflog_merges=t + ;; + --no-reflog_merges) + scan_reflog_merges= + ;; + -t|--merge-targets) + scan_merge_targets=t + ;; + --no-merge-targets) + scan_merge_targets= + ;; + -a|--all) + scan_reflog=t + scan_reflog_merges=t + scan_merges=t + scan_merge_targets=t + ;; + --) + shift + break + ;; + *) + usage + ;; + esac + shift +done + +test "$#" = 1 || usage + +all_strategies="$scan_reflog$scan_reflog_merges$scan_merges$scan_merge_targets" +if test -z "$all_strategies"; then + die "must enable at least one of -lrmt" +fi + +branch="$1" +test -z "$new_name" && new_name="$branch" + +if test ! -z "$scan_reflog"; then + if test -r "$GIT_DIR"/logs/HEAD; then + candidates="$(search_reflog $branch)" + else + die 'reflog scanning requested, but' \ + '$GIT_DIR/logs/HEAD not readable' + fi +fi +if test ! -z "$scan_reflog_merges"; then + if test -r "$GIT_DIR"/logs/HEAD; then + candidates="$candidates $(search_reflog_merges $branch)" + else + die 'reflog scanning requested, but' \ + '$GIT_DIR/logs/HEAD not readable' + fi +fi +if test ! -z "$scan_merges"; then + candidates="$candidates $(search_merges $branch)" +fi +if test ! -z "$scan_merge_targets"; then + candidates="$candidates $(search_merge_targets $branch)" +fi + +candidates="$(git rev-parse $candidates | sort -u)" + +if test -z "$candidates"; then + hint= + test "z$all_strategies" != "ztttt" \ + && hint=" (maybe try again with -a)" + die "no candidates for $branch found$hint" +fi + +echo "** Candidates for $branch **" +for cmt in $candidates; do + git --no-pager log --pretty=tformat:"%ct:%h [%cr] %s" --abbrev-commit -1 $cmt +done \ +| sort -n | cut -d: -f2- + +newest="$(git rev-list -1 $candidates)" +if test ! -z "$dry_run"; then + printf "** Most recent: " + git --no-pager log -1 --pretty=tformat:"%h %s" $newest +elif ! git rev-parse --verify --quiet $new_name >/dev/null; then + printf "** Restoring $new_name to " + git --no-pager log -1 --pretty=tformat:"%h %s" $newest + git branch $new_name $newest +else + printf "Most recent: " + git --no-pager log -1 --pretty=tformat:"%h %s" $newest + echo "** $new_name already exists, doing nothing" +fi diff --git a/contrib/git-shell-commands/README b/contrib/git-shell-commands/README new file mode 100644 index 0000000000..438463b160 --- /dev/null +++ b/contrib/git-shell-commands/README @@ -0,0 +1,18 @@ +Sample programs callable through git-shell. Place a directory named +'git-shell-commands' in the home directory of a user whose shell is +git-shell. Then anyone logging in as that user will be able to run +executables in the 'git-shell-commands' directory. + +Provided commands: + +help: Prints out the names of available commands. When run +interactively, git-shell will automatically run 'help' on startup, +provided it exists. + +list: Displays any bare repository whose name ends with ".git" under +user's home directory. No other git repositories are visible, +although they might be clonable through git-shell. 'list' is designed +to minimize the number of calls to git that must be made in finding +available repositories; if your setup has additional repositories that +should be user-discoverable, you may wish to modify 'list' +accordingly. diff --git a/contrib/git-shell-commands/help b/contrib/git-shell-commands/help new file mode 100755 index 0000000000..535770c6ec --- /dev/null +++ b/contrib/git-shell-commands/help @@ -0,0 +1,18 @@ +#!/bin/sh + +if tty -s +then + echo "Run 'help' for help, or 'exit' to leave. Available commands:" +else + echo "Run 'help' for help. Available commands:" +fi + +cd "$(dirname "$0")" + +for cmd in * +do + case "$cmd" in + help) ;; + *) [ -f "$cmd" ] && [ -x "$cmd" ] && echo "$cmd" ;; + esac +done diff --git a/contrib/git-shell-commands/list b/contrib/git-shell-commands/list new file mode 100755 index 0000000000..6f89938821 --- /dev/null +++ b/contrib/git-shell-commands/list @@ -0,0 +1,10 @@ +#!/bin/sh + +print_if_bare_repo=' + if "$(git --git-dir="$1" rev-parse --is-bare-repository)" = true + then + printf "%s\n" "${1#./}" + fi +' + +find -type d -name "*.git" -exec sh -c "$print_if_bare_repo" -- \{} \; -prune 2>/dev/null diff --git a/contrib/hg-to-git/hg-to-git.py b/contrib/hg-to-git/hg-to-git.py index 7b03204ed1..046cb2b268 100755 --- a/contrib/hg-to-git/hg-to-git.py +++ b/contrib/hg-to-git/hg-to-git.py @@ -1,4 +1,4 @@ -#! /usr/bin/python +#!/usr/bin/env python """ hg-to-git.py - A Mercurial to GIT converter @@ -20,7 +20,7 @@ """ import os, os.path, sys -import tempfile, popen2, pickle, getopt +import tempfile, pickle, getopt import re # Maps hg version -> git version @@ -59,14 +59,14 @@ def getgitenv(user, date): elems = re.compile('(.*?)\s+<(.*)>').match(user) if elems: env += 'export GIT_AUTHOR_NAME="%s" ;' % elems.group(1) - env += 'export GIT_COMMITER_NAME="%s" ;' % elems.group(1) + env += 'export GIT_COMMITTER_NAME="%s" ;' % elems.group(1) env += 'export GIT_AUTHOR_EMAIL="%s" ;' % elems.group(2) - env += 'export GIT_COMMITER_EMAIL="%s" ;' % elems.group(2) + env += 'export GIT_COMMITTER_EMAIL="%s" ;' % elems.group(2) else: env += 'export GIT_AUTHOR_NAME="%s" ;' % user - env += 'export GIT_COMMITER_NAME="%s" ;' % user + env += 'export GIT_COMMITTER_NAME="%s" ;' % user env += 'export GIT_AUTHOR_EMAIL= ;' - env += 'export GIT_COMMITER_EMAIL= ;' + env += 'export GIT_COMMITTER_EMAIL= ;' env += 'export GIT_AUTHOR_DATE="%s" ;' % date env += 'export GIT_COMMITTER_DATE="%s" ;' % date diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email index 41368950d6..21989fc6ab 100644..100755 --- a/contrib/hooks/post-receive-email +++ b/contrib/hooks/post-receive-email @@ -23,6 +23,13 @@ # possible for the email to be from someone other than the person doing the # push. # +# To help with debugging and use on pre-v1.5.1 git servers, this script will +# also obey the interface of hooks/update, taking its arguments on the +# command line. Unfortunately, hooks/update is called once for each ref. +# To avoid firing one email per ref, this script just prints its output to +# the screen when used in this mode. The output can then be redirected if +# wanted. +# # Config # ------ # hooks.mailinglist @@ -38,6 +45,21 @@ # hooks.emailprefix # All emails have their subjects prefixed with this prefix, or "[SCM]" # if emailprefix is unset, to aid filtering +# hooks.showrev +# The shell command used to format each revision in the email, with +# "%s" replaced with the commit id. Defaults to "git rev-list -1 +# --pretty %s", displaying the commit id, author, date and log +# message. To list full patches separated by a blank line, you +# could set this to "git show -C %s; echo". +# To list a gitweb/cgit URL *and* a full patch for each change set, use this: +# "t=%s; printf 'http://.../?id=%%s' \$t; echo;echo; git show -C \$t; echo" +# Be careful if "..." contains things that will be expanded by shell "eval" +# or printf. +# hooks.emailmaxlines +# The maximum number of lines that should be included in the generated +# email body. If not specified, there is no limit. +# Lines beyond the limit are suppressed and counted, and a final +# line is added indicating the number of suppressed lines. # # Notes # ----- @@ -49,24 +71,16 @@ # ---------------------------- Functions # -# Top level email generation function. This decides what type of update -# this is and calls the appropriate body-generation routine after outputting -# the common header -# -# Note this function doesn't actually generate any email output, that is -# taken care of by the functions it calls: -# - generate_email_header -# - generate_create_XXXX_email -# - generate_update_XXXX_email -# - generate_delete_XXXX_email -# - generate_email_footer +# Function to prepare for email generation. This decides what type +# of update this is and whether an email should even be generated. # -generate_email() +prep_for_email() { # --- Arguments oldrev=$(git rev-parse $1) newrev=$(git rev-parse $2) refname="$3" + maxlines=$4 # --- Interpret # 0000->1234 (create) @@ -130,13 +144,13 @@ generate_email() short_refname=${refname##refs/remotes/} echo >&2 "*** Push-update of tracking branch, $refname" echo >&2 "*** - no email generated." - exit 0 + return 1 ;; *) # Anything else (is there anything else?) echo >&2 "*** Unknown type of update to $refname ($rev_type)" echo >&2 "*** - no email generated" - exit 1 + return 1 ;; esac @@ -152,9 +166,32 @@ generate_email() esac echo >&2 "*** $config_name is not set so no email will be sent" echo >&2 "*** for $refname update $oldrev->$newrev" - exit 0 + return 1 fi + return 0 +} + +# +# Top level email generation function. This calls the appropriate +# body-generation routine after outputting the common header. +# +# Note this function doesn't actually generate any email output, that is +# taken care of by the functions it calls: +# - generate_email_header +# - generate_create_XXXX_email +# - generate_update_XXXX_email +# - generate_delete_XXXX_email +# - generate_email_footer +# +# Note also that this function cannot 'exit' from the script; when this +# function is running (in hook script mode), the send_mail() function +# is already executing in another process, connected via a pipe, and +# if this function exits without, whatever has been generated to that +# point will be sent as an email... even if nothing has been generated. +# +generate_email() +{ # Email parameters # The email subject will contain the best description of the ref # that we can build from the parameters @@ -175,7 +212,12 @@ generate_email() fn_name=atag ;; esac - generate_${change_type}_${fn_name}_email + + if [ -z "$maxlines" ]; then + generate_${change_type}_${fn_name}_email + else + generate_${change_type}_${fn_name}_email | limit_lines $maxlines + fi generate_email_footer } @@ -186,7 +228,7 @@ generate_email_header() # Generate header cat <<-EOF To: $recipients - Subject: ${emailprefix}$projectdesc $refname_type, $short_refname, ${change_type}d. $describe + Subject: ${emailprefix}$projectdesc $refname_type $short_refname ${change_type}d. $describe X-Git-Refname: $refname X-Git-Reftype: $refname_type X-Git-Oldrev: $oldrev @@ -224,13 +266,7 @@ generate_create_branch_email() echo "" echo $LOGBEGIN - # This shows all log entries that are not already covered by - # another ref - i.e. commits that are now accessible from this - # ref that were previously not accessible - # (see generate_update_branch_email for the explanation of this - # command) - git rev-parse --not --branches | grep -v $(git rev-parse $refname) | - git rev-list --pretty --stdin $newrev + show_new_revisions echo $LOGEND } @@ -311,8 +347,8 @@ generate_update_branch_email() # "remotes/" will be ignored as well. # List all of the revisions that were removed by this update, in a - # fast forward update, this list will be empty, because rev-list O - # ^N is empty. For a non fast forward, O ^N is the list of removed + # fast-forward update, this list will be empty, because rev-list O + # ^N is empty. For a non-fast-forward, O ^N is the list of removed # revisions fast_forward="" rev="" @@ -390,8 +426,7 @@ generate_update_branch_email() echo "" echo $LOGBEGIN - git rev-parse --not --branches | grep -v $(git rev-parse $refname) | - git rev-list --pretty --stdin $oldrev..$newrev + show_new_revisions # XXX: Need a way of detecting whether git rev-list actually # outputted anything, so that we can issue a "no new @@ -408,7 +443,7 @@ generate_update_branch_email() # revision because the base is effectively a random revision at this # point - the user will be interested in what this revision changed # - including the undoing of previous revisions in the case of - # non-fast forward updates. + # non-fast-forward updates. echo "" echo "Summary of changes:" git diff-tree --stat --summary --find-copies-harder $oldrev..$newrev @@ -591,6 +626,65 @@ generate_delete_general_email() echo $LOGEND } + +# --------------- Miscellaneous utilities + +# +# Show new revisions as the user would like to see them in the email. +# +show_new_revisions() +{ + # This shows all log entries that are not already covered by + # another ref - i.e. commits that are now accessible from this + # ref that were previously not accessible + # (see generate_update_branch_email for the explanation of this + # command) + + # Revision range passed to rev-list differs for new vs. updated + # branches. + if [ "$change_type" = create ] + then + # Show all revisions exclusive to this (new) branch. + revspec=$newrev + else + # Branch update; show revisions not part of $oldrev. + revspec=$oldrev..$newrev + fi + + other_branches=$(git for-each-ref --format='%(refname)' refs/heads/ | + grep -F -v $refname) + git rev-parse --not $other_branches | + if [ -z "$custom_showrev" ] + then + git rev-list --pretty --stdin $revspec + else + git rev-list --stdin $revspec | + while read onerev + do + eval $(printf "$custom_showrev" $onerev) + done + fi +} + + +limit_lines() +{ + lines=0 + skipped=0 + while IFS="" read -r line; do + lines=$((lines + 1)) + if [ $lines -gt $1 ]; then + skipped=$((skipped + 1)) + else + printf "%s\n" "$line" + fi + done + if [ $skipped -ne 0 ]; then + echo "... $skipped lines suppressed ..." + fi +} + + send_mail() { if [ -n "$envelopesender" ]; then @@ -615,7 +709,7 @@ if [ -z "$GIT_DIR" ]; then exit 1 fi -projectdesc=$(sed -ne '1p' "$GIT_DIR/description") +projectdesc=$(sed -ne '1p' "$GIT_DIR/description" 2>/dev/null) # Check if the description is unchanged from it's default, and shorten it to # a more manageable length if it is if expr "$projectdesc" : "Unnamed repository.*$" >/dev/null @@ -627,6 +721,8 @@ recipients=$(git config hooks.mailinglist) announcerecipients=$(git config hooks.announcelist) envelopesender=$(git config hooks.envelopesender) emailprefix=$(git config hooks.emailprefix || echo '[SCM] ') +custom_showrev=$(git config hooks.showrev) +maxlines=$(git config hooks.emailmaxlines) # --- Main loop # Allow dual mode: run from the command line just like the update hook, or @@ -635,10 +731,11 @@ if [ -n "$1" -a -n "$2" -a -n "$3" ]; then # Output to the terminal in command line mode - if someone wanted to # resend an email; they could redirect the output to sendmail # themselves - PAGER= generate_email $2 $3 $1 + prep_for_email $2 $3 $1 && PAGER= generate_email else while read oldrev newrev refname do - generate_email $oldrev $newrev $refname | send_mail + prep_for_email $oldrev $newrev $refname || continue + generate_email $maxlines | send_mail done fi diff --git a/contrib/hooks/pre-auto-gc-battery b/contrib/hooks/pre-auto-gc-battery index 0096f57b7e..1f914c94aa 100644 --- a/contrib/hooks/pre-auto-gc-battery +++ b/contrib/hooks/pre-auto-gc-battery @@ -1,9 +1,9 @@ #!/bin/sh # # An example hook script to verify if you are on battery, in case you -# are running Linux. Called by git-gc --auto with no arguments. The hook -# should exit with non-zero status after issuing an appropriate message -# if it wants to stop the auto repacking. +# are running Linux or OS X. Called by git-gc --auto with no arguments. +# The hook should exit with non-zero status after issuing an appropriate +# message if it wants to stop the auto repacking. # # This hook is stored in the contrib/hooks directory. Your distribution # may have put this somewhere else. If you want to use this hook, you @@ -30,6 +30,13 @@ then elif grep -q '0x01$' /proc/apm 2>/dev/null then exit 0 +elif grep -q "AC Power \+: 1" /proc/pmu/info 2>/dev/null +then + exit 0 +elif test -x /usr/bin/pmset && /usr/bin/pmset -g batt | + grep -q "Currently drawing from 'AC Power'" +then + exit 0 fi echo "Auto packing deferred; not on AC" diff --git a/contrib/p4import/git-p4import.py b/contrib/p4import/git-p4import.py index 0f3d97b67e..b6e534b65b 100644 --- a/contrib/p4import/git-p4import.py +++ b/contrib/p4import/git-p4import.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # # This tool is copyright (c) 2006, Sean Estabrooks. # It is released under the Gnu Public License, version 2. diff --git a/contrib/rerere-train.sh b/contrib/rerere-train.sh new file mode 100755 index 0000000000..2cfe1b936b --- /dev/null +++ b/contrib/rerere-train.sh @@ -0,0 +1,52 @@ +#!/bin/sh +# Copyright (c) 2008, Nanako Shiraishi +# Prime rerere database from existing merge commits + +me=rerere-train +USAGE="$me rev-list-args" + +SUBDIRECTORY_OK=Yes +OPTIONS_SPEC= +. git-sh-setup +require_work_tree +cd_to_toplevel + +# Remember original branch +branch=$(git symbolic-ref -q HEAD) || +original_HEAD=$(git rev-parse --verify HEAD) || { + echo >&2 "Not on any branch and no commit yet?" + exit 1 +} + +mkdir -p "$GIT_DIR/rr-cache" || exit + +git rev-list --parents "$@" | +while read commit parent1 other_parents +do + if test -z "$other_parents" + then + # Skip non-merges + continue + fi + git checkout -q "$parent1^0" + if git merge $other_parents >/dev/null 2>&1 + then + # Cleanly merges + continue + fi + if test -s "$GIT_DIR/MERGE_RR" + then + git show -s --pretty=format:"Learning from %h %s" "$commit" + git rerere + git checkout -q $commit -- . + git rerere + fi + git reset -q --hard +done + +if test -z "$branch" +then + git checkout "$original_HEAD" +else + git checkout "${branch#refs/heads/}" +fi diff --git a/contrib/svn-fe/.gitignore b/contrib/svn-fe/.gitignore new file mode 100644 index 0000000000..02a7791585 --- /dev/null +++ b/contrib/svn-fe/.gitignore @@ -0,0 +1,4 @@ +/*.xml +/*.1 +/*.html +/svn-fe diff --git a/contrib/svn-fe/Makefile b/contrib/svn-fe/Makefile new file mode 100644 index 0000000000..360d8da417 --- /dev/null +++ b/contrib/svn-fe/Makefile @@ -0,0 +1,63 @@ +all:: svn-fe$X + +CC = gcc +RM = rm -f +MV = mv + +CFLAGS = -g -O2 -Wall +LDFLAGS = +ALL_CFLAGS = $(CFLAGS) +ALL_LDFLAGS = $(LDFLAGS) +EXTLIBS = + +GIT_LIB = ../../libgit.a +VCSSVN_LIB = ../../vcs-svn/lib.a +LIBS = $(VCSSVN_LIB) $(GIT_LIB) $(EXTLIBS) + +QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir +QUIET_SUBDIR1 = + +ifneq ($(findstring $(MAKEFLAGS),w),w) +PRINT_DIR = --no-print-directory +else # "make -w" +NO_SUBDIR = : +endif + +ifneq ($(findstring $(MAKEFLAGS),s),s) +ifndef V + QUIET_CC = @echo ' ' CC $@; + QUIET_LINK = @echo ' ' LINK $@; + QUIET_SUBDIR0 = +@subdir= + QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ + $(MAKE) $(PRINT_DIR) -C $$subdir +endif +endif + +svn-fe$X: svn-fe.o $(VCSSVN_LIB) $(GIT_LIB) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ svn-fe.o \ + $(ALL_LDFLAGS) $(LIBS) + +svn-fe.o: svn-fe.c ../../vcs-svn/svndump.h + $(QUIET_CC)$(CC) -I../../vcs-svn -o $*.o -c $(ALL_CFLAGS) $< + +svn-fe.html: svn-fe.txt + $(QUIET_SUBDIR0)../../Documentation $(QUIET_SUBDIR1) \ + MAN_TXT=../contrib/svn-fe/svn-fe.txt \ + ../contrib/svn-fe/$@ + +svn-fe.1: svn-fe.txt + $(QUIET_SUBDIR0)../../Documentation $(QUIET_SUBDIR1) \ + MAN_TXT=../contrib/svn-fe/svn-fe.txt \ + ../contrib/svn-fe/$@ + $(MV) ../../Documentation/svn-fe.1 . + +../../vcs-svn/lib.a: FORCE + $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) vcs-svn/lib.a + +../../libgit.a: FORCE + $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) libgit.a + +clean: + $(RM) svn-fe$X svn-fe.o svn-fe.html svn-fe.xml svn-fe.1 + +.PHONY: all clean FORCE diff --git a/contrib/svn-fe/svn-fe.c b/contrib/svn-fe/svn-fe.c new file mode 100644 index 0000000000..a2677b03e0 --- /dev/null +++ b/contrib/svn-fe/svn-fe.c @@ -0,0 +1,16 @@ +/* + * This file is in the public domain. + * You may freely use, modify, distribute, and relicense it. + */ + +#include <stdlib.h> +#include "svndump.h" + +int main(int argc, char **argv) +{ + svndump_init(NULL); + svndump_read((argc > 1) ? argv[1] : NULL); + svndump_deinit(); + svndump_reset(); + return 0; +} diff --git a/contrib/svn-fe/svn-fe.txt b/contrib/svn-fe/svn-fe.txt new file mode 100644 index 0000000000..cd075b96c5 --- /dev/null +++ b/contrib/svn-fe/svn-fe.txt @@ -0,0 +1,70 @@ +svn-fe(1) +========= + +NAME +---- +svn-fe - convert an SVN "dumpfile" to a fast-import stream + +SYNOPSIS +-------- +svnadmin dump --incremental REPO | svn-fe [url] | git fast-import + +DESCRIPTION +----------- + +Converts a Subversion dumpfile into input suitable for +git-fast-import(1) and similar importers. REPO is a path to a +Subversion repository mirrored on the local disk. Remote Subversion +repositories can be mirrored on local disk using the `svnsync` +command. + +Note: this tool is very young. The details of its commandline +interface may change in backward incompatible ways. + +INPUT FORMAT +------------ +Subversion's repository dump format is documented in full in +`notes/dump-load-format.txt` from the Subversion source tree. +Files in this format can be generated using the 'svnadmin dump' or +'svk admin dump' command. + +Dumps produced with 'svnadmin dump --deltas' (dumpfile format v3) +are not supported. + +OUTPUT FORMAT +------------- +The fast-import format is documented by the git-fast-import(1) +manual page. + +NOTES +----- +Subversion dumps do not record a separate author and committer for +each revision, nor a separate display name and email address for +each author. Like git-svn(1), 'svn-fe' will use the name + +--------- +user <user@UUID> +--------- + +as committer, where 'user' is the value of the `svn:author` property +and 'UUID' the repository's identifier. + +To support incremental imports, 'svn-fe' puts a `git-svn-id` line at +the end of each commit log message if passed an url on the command +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 +may be useful for this purpose. + +BUGS +---- +Empty directories and unknown properties are silently discarded. + +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), +https://svn.apache.org/repos/asf/subversion/trunk/notes/dump-load-format.txt diff --git a/contrib/thunderbird-patch-inline/README b/contrib/thunderbird-patch-inline/README index 39f96aa115..000147bbe4 100644 --- a/contrib/thunderbird-patch-inline/README +++ b/contrib/thunderbird-patch-inline/README @@ -1,12 +1,12 @@ appp.sh is a script that is supposed to be used together with ExternalEditor -for Mozilla Thundebird. It will let you include patches inline in e-mails +for Mozilla Thunderbird. It will let you include patches inline in e-mails in an easy way. Usage: - Generate the patch with git format-patch. - Start writing a new e-mail in Thunderbird. - Press the external editor button (or Ctrl-E) to run appp.sh -- Select the previosly generated patch file. +- Select the previously generated patch file. - Finish editing the e-mail. Any text that is entered into the message editor before appp.sh is called diff --git a/contrib/thunderbird-patch-inline/appp.sh b/contrib/thunderbird-patch-inline/appp.sh index cc518f3c89..5eb4a51643 100755 --- a/contrib/thunderbird-patch-inline/appp.sh +++ b/contrib/thunderbird-patch-inline/appp.sh @@ -1,8 +1,8 @@ -#!/bin/bash +#!/bin/sh # Copyright 2008 Lukas Sandström <luksan@gmail.com> # # AppendPatch - A script to be used together with ExternalEditor -# for Mozilla Thunderbird to properly include pathes inline i e-mails. +# for Mozilla Thunderbird to properly include patches inline in e-mails. # ExternalEditor can be downloaded at http://globs.org/articles.php?lng=en&pg=2 diff --git a/contrib/vim/README b/contrib/vim/README index 9e7881fea9..fca1e17251 100644 --- a/contrib/vim/README +++ b/contrib/vim/README @@ -1,8 +1,32 @@ -To syntax highlight git's commit messages, you need to: - 1. Copy syntax/gitcommit.vim to vim's syntax directory: - $ mkdir -p $HOME/.vim/syntax - $ cp syntax/gitcommit.vim $HOME/.vim/syntax - 2. Auto-detect the editing of git commit files: - $ cat >>$HOME/.vimrc <<'EOF' - autocmd BufNewFile,BufRead COMMIT_EDITMSG set filetype=gitcommit - EOF +Syntax highlighting for git commit messages, config files, etc. is +included with the vim distribution as of vim 7.2, and should work +automatically. + +If you have an older version of vim, you can get the latest syntax +files from the vim project: + + http://ftp.vim.org/pub/vim/runtime/syntax/git.vim + http://ftp.vim.org/pub/vim/runtime/syntax/gitcommit.vim + http://ftp.vim.org/pub/vim/runtime/syntax/gitconfig.vim + http://ftp.vim.org/pub/vim/runtime/syntax/gitrebase.vim + http://ftp.vim.org/pub/vim/runtime/syntax/gitsendemail.vim + +These files are also available via FTP at the same location. + +To install: + + 1. Copy these files to vim's syntax directory $HOME/.vim/syntax + 2. To auto-detect the editing of various git-related filetypes: + $ cat >>$HOME/.vim/filetype.vim <<'EOF' + autocmd BufNewFile,BufRead *.git/COMMIT_EDITMSG setf gitcommit + autocmd BufNewFile,BufRead *.git/config,.gitconfig setf gitconfig + autocmd BufNewFile,BufRead git-rebase-todo setf gitrebase + autocmd BufNewFile,BufRead .msg.[0-9]* + \ if getline(1) =~ '^From.*# This line is ignored.$' | + \ setf gitsendemail | + \ endif + autocmd BufNewFile,BufRead *.git/** + \ if getline(1) =~ '^\x\{40\}\>\|^ref: ' | + \ setf git | + \ endif + EOF diff --git a/contrib/vim/syntax/gitcommit.vim b/contrib/vim/syntax/gitcommit.vim deleted file mode 100644 index 332121b40e..0000000000 --- a/contrib/vim/syntax/gitcommit.vim +++ /dev/null @@ -1,18 +0,0 @@ -syn region gitLine start=/^#/ end=/$/ -syn region gitCommit start=/^# Changes to be committed:$/ end=/^#$/ contains=gitHead,gitCommitFile -syn region gitHead contained start=/^# (.*)/ end=/^#$/ -syn region gitChanged start=/^# Changed but not updated:/ end=/^#$/ contains=gitHead,gitChangedFile -syn region gitUntracked start=/^# Untracked files:/ end=/^#$/ contains=gitHead,gitUntrackedFile - -syn match gitCommitFile contained /^#\t.*/hs=s+2 -syn match gitChangedFile contained /^#\t.*/hs=s+2 -syn match gitUntrackedFile contained /^#\t.*/hs=s+2 - -hi def link gitLine Comment -hi def link gitCommit Comment -hi def link gitChanged Comment -hi def link gitHead Comment -hi def link gitUntracked Comment -hi def link gitCommitFile Type -hi def link gitChangedFile Constant -hi def link gitUntrackedFile Constant diff --git a/contrib/workdir/git-new-workdir b/contrib/workdir/git-new-workdir index 7959eab902..75e8b25817 100755 --- a/contrib/workdir/git-new-workdir +++ b/contrib/workdir/git-new-workdir @@ -22,7 +22,7 @@ branch=$3 # want to make sure that what is pointed to has a .git directory ... git_dir=$(cd "$orig_git" 2>/dev/null && git rev-parse --git-dir 2>/dev/null) || - die "\"$orig_git\" is not a git repository!" + die "Not a git repository: \"$orig_git\"" case "$git_dir" in .git) @@ -42,7 +42,7 @@ then fi # don't link to a workdir -if test -L "$git_dir/config" +if test -h "$git_dir/config" then die "\"$orig_git\" is a working directory only, please specify" \ "a complete repository." @@ -54,13 +54,13 @@ then die "destination directory '$new_workdir' already exists." fi -# make sure the the links use full paths +# make sure the links use full paths git_dir=$(cd "$git_dir"; pwd) # create the workdir mkdir -p "$new_workdir/.git" || die "unable to create \"$new_workdir\"!" -# create the links to the original repo. explictly exclude index, HEAD and +# create the links to the original repo. explicitly exclude index, HEAD and # logs/HEAD from the list since they are purely related to the current working # directory, and should not be shared. for x in config refs logs/refs objects info hooks packed-refs remotes rr-cache svn |