summaryrefslogtreecommitdiff
path: root/contrib
diff options
context:
space:
mode:
Diffstat (limited to 'contrib')
-rw-r--r--contrib/buildsystems/Generators.pm42
-rw-r--r--contrib/buildsystems/Generators/QMake.pm189
-rw-r--r--contrib/buildsystems/Generators/Vcproj.pm626
-rw-r--r--contrib/buildsystems/engine.pl359
-rw-r--r--contrib/buildsystems/generate29
-rw-r--r--contrib/buildsystems/parse.pl228
-rw-r--r--contrib/ciabot/README12
-rwxr-xr-xcontrib/ciabot/ciabot.py222
-rwxr-xr-xcontrib/ciabot/ciabot.sh192
-rwxr-xr-xcontrib/completion/git-completion.bash537
-rw-r--r--contrib/emacs/git-blame.el156
-rw-r--r--contrib/emacs/git.el26
-rw-r--r--contrib/examples/builtin-fetch--tool.c574
-rwxr-xr-xcontrib/examples/git-fetch.sh4
-rwxr-xr-xcontrib/examples/git-merge.sh11
-rwxr-xr-xcontrib/examples/git-notes.sh121
-rwxr-xr-xcontrib/examples/git-resolve.sh2
-rwxr-xr-xcontrib/fast-import/git-p4189
-rwxr-xr-xcontrib/fast-import/import-directories.perl416
-rwxr-xr-xcontrib/fast-import/import-tars.perl53
-rwxr-xr-xcontrib/fast-import/import-zips.py2
-rwxr-xr-xcontrib/git-resurrect.sh1
-rwxr-xr-xcontrib/hg-to-git/hg-to-git.py10
-rwxr-xr-xcontrib/hooks/post-receive-email13
-rw-r--r--contrib/p4import/git-p4import.py2
25 files changed, 3689 insertions, 327 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("&quot;$rel_dir\\$_&quot;", @{$$build_structure{"LIBS_${libname}_INCLUDES"}})));
+ my $cflags = join(" ", sort(@{$$build_structure{"LIBS_${libname}_CFLAGS"}}));
+ $cflags =~ s/\"/&quot;/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/\"/\\&quot;/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("&quot;$rel_dir\\$_&quot;", @{$$build_structure{"APPS_${appname}_INCLUDES"}})));
+ my $cflags = join(" ", sort(@{$$build_structure{"APPS_${appname}_CFLAGS"}}));
+ $cflags =~ s/\"/&quot;/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/\"/\\&quot;/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..d0627e0852
--- /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/\&/&amp\;/g; s/</&lt\;/g; s/>/&gt\;/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 745b5fb78b..67569901e7 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -21,13 +21,7 @@
# 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.
-#
-# 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
@@ -48,6 +42,24 @@
# 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
@@ -84,14 +96,133 @@ __gitdir ()
fi
}
+# stores the divergence from upstream in $p
+# used by GIT_PS1_SHOWUPSTREAM
+__git_ps1_show_upstream ()
+{
+ local key value
+ local svn_remote=() svn_url_pattern count n
+ local upstream=git legacy="" verbose=""
+
+ # 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}\)" 2>/dev/null))
+ if [[ 0 -ne ${#svn_upstream[@]} ]]; then
+ svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]}
+ svn_upstream=${svn_upstream%@*}
+ for ((n=1; "$n" <= "${#svn_remote[@]}"; ++n)); do
+ svn_upstream=${svn_upstream#${svn_remote[$n]}}
+ done
+
+ if [[ -z "$svn_upstream" ]]; then
+ # default branch name for checkouts with no layout:
+ upstream=${GIT_SVN_ID:-git-svn}
+ else
+ upstream=${svn_upstream#/}
+ fi
+ elif [[ "svn+git" = "$upstream" ]]; then
+ upstream="@{upstream}"
+ fi
+ ;;
+ esac
+
+ # Find how many commits we are ahead/behind our upstream
+ if [[ -z "$legacy" ]]; then
+ count="$(git rev-list --count --left-right \
+ "$upstream"...HEAD 2>/dev/null)"
+ else
+ # produce equivalent output to --count for older versions of git
+ local commits
+ if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)"
+ then
+ local commit behind=0 ahead=0
+ for commit in $commits
+ do
+ case "$commit" in
+ "<"*) 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
+ local r=""
+ local b=""
if [ -f "$g/rebase-merge/interactive" ]; then
r="|REBASE-i"
b="$(cat "$g/rebase-merge/head-name")"
@@ -133,11 +264,12 @@ __git_ps1 ()
}
fi
- local w
- local i
- local s
- local u
- local c
+ 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
@@ -148,11 +280,9 @@ __git_ps1 ()
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 --ignore-submodules \
- --quiet --exit-code || w="*"
+ git diff --no-ext-diff --quiet --exit-code || w="*"
if git rev-parse --quiet --verify HEAD >/dev/null; then
- git diff-index --cached --quiet \
- --ignore-submodules HEAD -- || i="+"
+ git diff-index --cached --quiet HEAD -- || i="+"
else
i="#"
fi
@@ -167,13 +297,14 @@ __git_ps1 ()
u="%"
fi
fi
- fi
- if [ -n "${1-}" ]; then
- printf "$1" "$c${b##refs/heads/}$w$i$s$u$r"
- else
- printf " (%s)" "$c${b##refs/heads/}$w$i$s$u$r"
+ 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
}
@@ -261,7 +392,9 @@ __git_refs ()
refs="${cur%/*}"
;;
*)
- if [ -e "$dir/HEAD" ]; then echo HEAD; fi
+ 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"
;;
@@ -318,22 +451,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/\.$//
@@ -343,8 +468,17 @@ __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 ()
{
@@ -422,7 +556,17 @@ __git_complete_remote_or_refspec ()
while [ $c -lt $COMP_CWORD ]; do
i="${COMP_WORDS[c]}"
case "$i" in
- --all|--mirror) [ "$cmd" = "push" ] && no_complete_refspec=1 ;;
+ --mirror) [ "$cmd" = "push" ] && no_complete_refspec=1 ;;
+ --all)
+ case "$cmd" in
+ push) no_complete_refspec=1 ;;
+ fetch)
+ COMPREPLY=()
+ return
+ ;;
+ *) ;;
+ esac
+ ;;
-*) ;;
*) remote="$i"; break ;;
esac
@@ -478,29 +622,26 @@ __git_complete_remote_or_refspec ()
__git_complete_strategy ()
{
+ __git_compute_merge_strategies
case "${COMP_WORDS[COMP_CWORD-1]}" in
-s|--strategy)
- __gitcomp "$(__git_merge_strategies)"
+ __gitcomp "$__git_merge_strategies"
return 0
esac
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--strategy=*)
- __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}"
+ __gitcomp "$__git_merge_strategies" "" "${cur##--strategy=}"
return 0
;;
esac
return 1
}
-__git_all_commands ()
+__git_list_all_commands ()
{
- if [ -n "${__git_all_commandlist-}" ]; then
- echo "$__git_all_commandlist"
- return
- fi
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;;
@@ -508,17 +649,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;;
@@ -570,6 +712,7 @@ __git_porcelain_commands ()
read-tree) : plumbing;;
receive-pack) : plumbing;;
reflog) : plumbing;;
+ remote-*) : transport;;
repo-config) : deprecated;;
rerere) : plumbing;;
rev-list) : plumbing;;
@@ -599,17 +742,22 @@ __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_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
@@ -621,15 +769,24 @@ __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 requires 1 argument
-__git_find_subcommand ()
+# __git_find_on_cmdline requires 1 argument
+__git_find_on_cmdline ()
{
local word subcommand c=1
@@ -663,7 +820,7 @@ _git_am ()
{
local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)"
if [ -d "$dir"/rebase-apply ]; then
- __gitcomp "--skip --resolved --abort"
+ __gitcomp "--skip --continue --resolved --abort"
return
fi
case "$cur" in
@@ -674,8 +831,9 @@ _git_am ()
--*)
__gitcomp "
--3way --committer-date-is-author-date --ignore-date
+ --ignore-whitespace --ignore-space-change
--interactive --keep --no-utf8 --signoff --utf8
- --whitespace=
+ --whitespace= --scissors
"
return
esac
@@ -695,6 +853,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
@@ -746,7 +905,7 @@ _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"
return
@@ -780,6 +939,7 @@ _git_branch ()
__gitcomp "
--color --no-color --verbose --abbrev= --no-abbrev
--track --no-track --contains --merged --no-merged
+ --set-upstream
"
;;
*)
@@ -816,7 +976,21 @@ _git_checkout ()
{
__git_has_doubledash && return
- __gitcomp "$(__git_refs)"
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ case "$cur" in
+ --conflict=*)
+ __gitcomp "diff3 merge" "" "${cur##--conflict=}"
+ ;;
+ --*)
+ __gitcomp "
+ --quiet --ours --theirs --track --no-track --merge
+ --conflict= --orphan --patch
+ "
+ ;;
+ *)
+ __gitcomp "$(__git_refs)"
+ ;;
+ esac
}
_git_cherry ()
@@ -882,10 +1056,31 @@ _git_commit ()
local cur="${COMP_WORDS[COMP_CWORD]}"
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
@@ -918,6 +1113,8 @@ __git_diff_common_options="--stat --numstat --shortstat --summary
--inter-hunk-context=
--patience
--raw
+ --dirstat --dirstat= --dirstat-by-file
+ --dirstat-by-file= --cumulative
"
_git_diff ()
@@ -938,11 +1135,13 @@ _git_diff ()
}
__git_mergetools_common="diffuse ecmerge emerge kdiff3 meld opendiff
- tkdiff vimdiff gvimdiff xxdiff araxis
+ tkdiff vimdiff gvimdiff xxdiff araxis p4merge
"
_git_difftool ()
{
+ __git_has_doubledash && return
+
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--tool=*)
@@ -950,16 +1149,20 @@ _git_difftool ()
return
;;
--*)
- __gitcomp "--tool="
+ __gitcomp "--cached --staged --pickaxe-all --pickaxe-regex
+ --base --ours --theirs
+ --no-renames --diff-filter= --find-copies-harder
+ --relative --ignore-submodules
+ --tool="
return
;;
esac
- COMPREPLY=()
+ __git_complete_file
}
__git_fetch_options="
--quiet --verbose --append --upload-pack --force --keep --depth=
- --tags --no-tags
+ --tags --no-tags --all --prune --dry-run
"
_git_fetch ()
@@ -991,7 +1194,7 @@ _git_format_patch ()
--numbered --start-number
--numbered-files
--keep-subject
- --signoff
+ --signoff --signature --no-signature
--in-reply-to= --cc=
--full-index --binary
--not --all
@@ -1033,6 +1236,11 @@ _git_gc ()
COMPREPLY=()
}
+_git_gitk ()
+{
+ _gitk
+}
+
_git_grep ()
{
__git_has_doubledash && return
@@ -1047,13 +1255,15 @@ _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 ()
@@ -1065,7 +1275,8 @@ _git_help ()
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
@@ -1170,6 +1381,10 @@ _git_log ()
__gitcomp "$__git_log_date_formats" "" "${cur##--date=}"
return
;;
+ --decorate=*)
+ __gitcomp "long short" "" "${cur##--decorate=}"
+ return
+ ;;
--*)
__gitcomp "
$__git_log_common_options
@@ -1182,7 +1397,7 @@ _git_log ()
--pretty= --format= --oneline
--cherry-pick
--graph
- --decorate
+ --decorate --decorate=
--walk-reflogs
--parents --children
$merge
@@ -1197,7 +1412,7 @@ _git_log ()
__git_merge_options="
--no-commit --no-stat --log --no-log --squash --strategy
- --commit --stat --no-squash --ff --no-ff
+ --commit --stat --no-squash --ff --no-ff --ff-only
"
_git_merge ()
@@ -1251,6 +1466,24 @@ _git_name_rev ()
__gitcomp "--tags --all --stdin"
}
+_git_notes ()
+{
+ local subcommands="edit show"
+ if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then
+ __gitcomp "$subcommands"
+ return
+ fi
+
+ case "${COMP_WORDS[COMP_CWORD-1]}" in
+ -m|-F)
+ COMPREPLY=()
+ ;;
+ *)
+ __gitcomp "$(__git_refs)"
+ ;;
+ esac
+}
+
_git_pull ()
{
__git_complete_strategy && return
@@ -1302,8 +1535,19 @@ _git_rebase ()
fi
__git_complete_strategy && return
case "$cur" in
+ --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)"
@@ -1349,6 +1593,11 @@ _git_send_email ()
COMPREPLY=()
}
+_git_stage ()
+{
+ _git_add
+}
+
__git_config_get_set_variables ()
{
local prevword word config_file= c=$COMP_CWORD
@@ -1407,7 +1656,8 @@ _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.interactive|\
@@ -1508,7 +1758,8 @@ _git_config ()
pager.*)
local pfx="${cur%.*}."
cur="${cur#*.}"
- __gitcomp "$(__git_all_commands)" "$pfx" "$cur"
+ __git_compute_all_commands
+ __gitcomp "$__git_all_commands" "$pfx" "$cur"
return
;;
remote.*.*)
@@ -1529,13 +1780,14 @@ _git_config ()
url.*.*)
local pfx="${cur%.*}."
cur="${cur##*.}"
- __gitcomp "insteadof" "$pfx" "$cur"
+ __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur"
return
;;
esac
__gitcomp "
add.ignore-errors
alias.
+ apply.ignorewhitespace
apply.whitespace
branch.autosetupmerge
branch.autosetuprebase
@@ -1616,6 +1868,7 @@ _git_config ()
format.headers
format.numbered
format.pretty
+ format.signature
format.signoff
format.subjectprefix
format.suffix
@@ -1753,7 +2006,7 @@ _git_config ()
_git_remote ()
{
local subcommands="add rename rm show prune update set-head"
- local subcommand="$(__git_find_subcommand "$subcommands")"
+ local subcommand="$(__git_find_on_cmdline "$subcommands")"
if [ -z "$subcommand" ]; then
__gitcomp "$subcommands"
return
@@ -1765,13 +2018,9 @@ _git_remote ()
;;
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"
;;
@@ -1781,6 +2030,11 @@ _git_remote ()
esac
}
+_git_replace ()
+{
+ __gitcomp "$(__git_refs)"
+}
+
_git_reset ()
{
__git_has_doubledash && return
@@ -1788,7 +2042,7 @@ _git_reset ()
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
- __gitcomp "--merge --mixed --hard --soft"
+ __gitcomp "--merge --mixed --hard --soft --patch"
return
;;
esac
@@ -1884,18 +2138,30 @@ _git_show_branch ()
_git_stash ()
{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ 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,--*|pop,--*)
- __gitcomp "--index"
+ __gitcomp "--index --quiet"
;;
show,--*|drop,--*|branch,--*)
COMPREPLY=()
@@ -1916,7 +2182,7 @@ _git_submodule ()
__git_has_doubledash && return
local subcommands="add status init update summary foreach sync"
- if [ -z "$(__git_find_subcommand "$subcommands")" ]; then
+ if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
@@ -1936,9 +2202,9 @@ _git_svn ()
init fetch clone rebase dcommit log find-rev
set-tree commit-diff info create-ignore propget
proplist show-ignore show-externals branch tag blame
- migrate
+ migrate mkdirs reset gc
"
- local subcommand="$(__git_find_subcommand "$subcommands")"
+ local subcommand="$(__git_find_on_cmdline "$subcommands")"
if [ -z "$subcommand" ]; then
__gitcomp "$subcommands"
else
@@ -1983,7 +2249,7 @@ _git_svn ()
__gitcomp "--stdin $cmt_opts $fc_opts"
;;
create-ignore,--*|propget,--*|proplist,--*|show-ignore,--*|\
- show-externals,--*)
+ show-externals,--*|mkdirs,--*)
__gitcomp "--revision="
;;
log,--*)
@@ -2020,6 +2286,9 @@ _git_svn ()
--no-auth-cache --username=
"
;;
+ reset,--*)
+ __gitcomp "--revision= --parent"
+ ;;
*)
COMPREPLY=()
;;
@@ -2061,6 +2330,11 @@ _git_tag ()
esac
}
+_git_whatchanged ()
+{
+ _git_log
+}
+
_git ()
{
local i c=1 command __git_dir
@@ -2091,67 +2365,20 @@ _git ()
--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 ;;
- difftool) _git_difftool ;;
- fetch) _git_fetch ;;
- format-patch) _git_format_patch ;;
- fsck) _git_fsck ;;
- 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 ;;
- stage) _git_add ;;
- 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 ()
diff --git a/contrib/emacs/git-blame.el b/contrib/emacs/git-blame.el
index 4fa70c5ad4..7f4c792978 100644
--- a/contrib/emacs/git-blame.el
+++ b/contrib/emacs/git-blame.el
@@ -80,6 +80,57 @@
(eval-when-compile (require 'cl)) ; to use `push', `pop'
+(defface git-blame-prefix-face
+ '((((background dark)) (:foreground "gray"
+ :background "black"))
+ (((background light)) (:foreground "gray"
+ :background "white"))
+ (t (:weight bold)))
+ "The face used for the hash prefix."
+ :group 'git-blame)
+
+(defgroup git-blame nil
+ "A minor mode showing Git blame information."
+ :group 'git
+ :link '(function-link git-blame-mode))
+
+
+(defcustom git-blame-use-colors t
+ "Use colors to indicate commits in `git-blame-mode'."
+ :type 'boolean
+ :group 'git-blame)
+
+(defcustom git-blame-prefix-format
+ "%h %20A:"
+ "The format of the prefix added to each line in `git-blame'
+mode. The format is passed to `format-spec' with the following format keys:
+
+ %h - the abbreviated hash
+ %H - the full hash
+ %a - the author name
+ %A - the author email
+ %c - the committer name
+ %C - the committer email
+ %s - the commit summary
+"
+ :group 'git-blame)
+
+(defcustom git-blame-mouseover-format
+ "%h %a %A: %s"
+ "The format of the description shown when pointing at a line in
+`git-blame' mode. The format string is passed to `format-spec'
+with the following format keys:
+
+ %h - the abbreviated hash
+ %H - the full hash
+ %a - the author name
+ %A - the author email
+ %c - the committer name
+ %C - the committer email
+ %s - the commit summary
+"
+ :group 'git-blame)
+
(defun git-blame-color-scale (&rest elements)
"Given a list, returns a list of triples formed with each
@@ -302,72 +353,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 eace9c18eb..214930a021 100644
--- a/contrib/emacs/git.el
+++ b/contrib/emacs/git.el
@@ -429,16 +429,19 @@ Each entry is a cons of (SHORT-NAME . FULL-NAME)."
(git-get-string-sha1
(git-call-process-string-display-error "write-tree"))))
-(defun git-commit-tree (buffer tree head)
- "Call git-commit-tree with buffer as input and return the resulting commit SHA1."
+(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
@@ -474,7 +477,7 @@ Each entry is a cons of (SHORT-NAME . FULL-NAME)."
(apply #'git-run-command-region
buffer log-start log-end env
"commit-tree" tree (nreverse args))))))
- (when commit (git-update-ref "HEAD" commit head subject))
+ (when commit (git-update-ref "HEAD" commit parent subject))
commit)))
(defun git-empty-db-p ()
@@ -1043,7 +1046,7 @@ The FILES list must be sorted."
(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))
@@ -1116,15 +1119,6 @@ The FILES list must be sorted."
(when buffer (with-current-buffer buffer (revert-buffer t t t)))))
(git-success-message "Reverted" names))))))
-(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)
- (git-success-message "Resolved" files)))))
-
(defun git-remove-handled ()
"Remove handled files from the status list."
(interactive)
@@ -1553,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)
@@ -1595,7 +1588,6 @@ amended version of it."
("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]
diff --git a/contrib/examples/builtin-fetch--tool.c b/contrib/examples/builtin-fetch--tool.c
new file mode 100644
index 0000000000..cd10dbcbc9
--- /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 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, &not_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-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..8f617fcb70 100755
--- a/contrib/examples/git-merge.sh
+++ b/contrib/examples/git-merge.sh
@@ -14,7 +14,7 @@ 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)
s,strategy= merge strategy to use
m,message= message to be used for the merge commit (if any)
"
@@ -31,10 +31,11 @@ LF='
'
all_strategies='recur recursive octopus resolve stupid ours subtree'
+all_strategies="$all_strategies recursive-ours recursive-theirs"
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=
allow_fast_forward=t
@@ -353,7 +354,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,11 +366,11 @@ 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
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/fast-import/git-p4 b/contrib/fast-import/git-p4
index 342529db30..c1ea643ace 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()
@@ -728,13 +729,10 @@ class P4Submit(Command):
tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
tmpFile.close()
mtime = os.stat(fileName).st_mtime
- defaultEditor = "vi"
- if platform.system() == "Windows":
- defaultEditor = "notepad"
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)
response = "y"
@@ -804,7 +802,7 @@ class P4Submit(Command):
self.oldWorkingDirectory = os.getcwd()
chdir(self.clientPath)
- print "Syncronizing p4 checkout..."
+ print "Synchronizing p4 checkout..."
p4_system("sync ...")
self.check()
@@ -861,8 +859,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
@@ -950,10 +948,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
@@ -965,52 +1036,37 @@ class P4Sync(Command):
if includeFile:
filesForCommit.append(f)
- if f['action'] not in ('delete', 'purge'):
+ 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 += filedata[j]['data']
- del filedata[j]['data']
- j += 1
-
- 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
@@ -1023,7 +1079,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"])
@@ -1051,33 +1106,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"] in ("delete", "purge"):
- 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"])
@@ -1627,7 +1656,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))
diff --git a/contrib/fast-import/import-directories.perl b/contrib/fast-import/import-directories.perl
new file mode 100755
index 0000000000..3a5da4ab00
--- /dev/null
+++ b/contrib/fast-import/import-directories.perl
@@ -0,0 +1,416 @@
+#!/usr/bin/perl -w
+#
+# 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 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 78e40d2a13..95438e1ed4 100755
--- a/contrib/fast-import/import-tars.perl
+++ b/contrib/fast-import/import-tars.perl
@@ -8,9 +8,20 @@
## 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";
@@ -38,6 +49,9 @@ 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 {
@@ -109,12 +123,43 @@ foreach my $tar_file (@ARGV)
$have_top_dir = 0 if $top_dir ne $1;
}
+ my $commit_msg = "Imported from $tar_file.";
+ my $this_committer_name = $committer_name;
+ my $this_committer_email = $committer_email;
+ my $this_author_name = $author_name;
+ my $this_author_email = $author_email;
+ if ($metaext ne '') {
+ # Optionally read a commit message from <filename.tar>.msg
+ # Add a line on the form "Committer: name <e-mail>" to override
+ # the committer and "Author: name <e-mail>" to override the
+ # author for this tar ball.
+ if (open MSG, '<', "${tar_file}.${metaext}") {
+ my $header_done = 0;
+ $commit_msg = '';
+ while (<MSG>) {
+ if (!$header_done && /^Committer:\s+([^<>]*)\s+<(.*)>\s*$/i) {
+ $this_committer_name = $1;
+ $this_committer_email = $2;
+ } elsif (!$header_done && /^Author:\s+([^<>]*)\s+<(.*)>\s*$/i) {
+ $this_author_name = $1;
+ $this_author_email = $2;
+ } elsif (!$header_done && /^$/) { # empty line ends header.
+ $header_done = 1;
+ } else {
+ $commit_msg .= $_;
+ $header_done = 1;
+ }
+ }
+ close MSG;
+ }
+ }
+
print FI <<EOF;
commit $branch_ref
-author $author_name <$author_email> $author_time +0000
-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
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
index c364dda696..a4ed4c3c62 100755
--- a/contrib/git-resurrect.sh
+++ b/contrib/git-resurrect.sh
@@ -9,6 +9,7 @@ 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
--
diff --git a/contrib/hg-to-git/hg-to-git.py b/contrib/hg-to-git/hg-to-git.py
index 2a6839d81e..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
@@ -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 2a66063e44..30ae63d74d 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
@@ -315,8 +322,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=""
@@ -411,7 +418,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
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.